mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 10:19:43 +00:00
fix(mobile): prepare v1.0.7 OTA release
This commit is contained in:
1 parent
2664791233
commit
db5980c9bd
16 files changed
+205
-45
No files matched your search
@@ -10,6 +10,8 @@ const files = {
|
||||
app: path.join(repoRoot, "apps/mobile/app.json"),
|
||||
android: path.join(repoRoot, "apps/mobile/android/app/build.gradle"),
|
||||
ios: path.join(repoRoot, "apps/mobile/ios/Sauci.xcodeproj/project.pbxproj"),
|
||||
iosInfo: path.join(repoRoot, "apps/mobile/ios/Sauci/Info.plist"),
|
||||
widgetInfo: path.join(repoRoot, "apps/mobile/targets/widget/Info.plist"),
|
||||
};
|
||||
|
||||
const semverPattern = /^([0-9]+)\.([0-9]+)\.([0-9]+)$/;
|
||||
@@ -29,6 +31,8 @@ function readState() {
|
||||
const appRaw = fs.readFileSync(files.app, "utf8");
|
||||
const androidRaw = fs.readFileSync(files.android, "utf8");
|
||||
const iosRaw = fs.readFileSync(files.ios, "utf8");
|
||||
const iosInfoRaw = fs.readFileSync(files.iosInfo, "utf8");
|
||||
const widgetInfoRaw = fs.readFileSync(files.widgetInfo, "utf8");
|
||||
const appVersion = JSON.parse(appRaw).expo?.version;
|
||||
const androidVersions = [...androidRaw.matchAll(/\bversionName\s+["']([^"']+)["']/g)].map(
|
||||
(match) => match[1],
|
||||
@@ -36,18 +40,35 @@ function readState() {
|
||||
const iosVersions = [...iosRaw.matchAll(/\bMARKETING_VERSION\s*=\s*([^;]+);/g)].map(
|
||||
(match) => match[1].trim(),
|
||||
);
|
||||
const plistVersion = (raw, label) => {
|
||||
const versions = [...raw.matchAll(/<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/g)].map(
|
||||
(match) => match[1],
|
||||
);
|
||||
if (versions.length !== 1) fail(`expected exactly one ${label} CFBundleShortVersionString`);
|
||||
return versions[0];
|
||||
};
|
||||
const iosInfoVersion = plistVersion(iosInfoRaw, "iOS app Info.plist");
|
||||
const widgetInfoVersion = plistVersion(widgetInfoRaw, "iOS widget Info.plist");
|
||||
|
||||
if (typeof appVersion !== "string") fail("apps/mobile/app.json has no expo.version");
|
||||
if (androidVersions.length !== 1) fail("expected exactly one Android versionName");
|
||||
if (iosVersions.length === 0) fail("found no iOS MARKETING_VERSION values");
|
||||
|
||||
const versions = [appVersion, ...androidVersions, ...iosVersions];
|
||||
const versions = [appVersion, ...androidVersions, ...iosVersions, iosInfoVersion, widgetInfoVersion];
|
||||
versions.forEach((version) => parseVersion(version, `configured version ${version}`));
|
||||
if (new Set(versions).size !== 1) {
|
||||
fail(`public versions disagree: ${versions.join(", ")}`);
|
||||
}
|
||||
|
||||
return { appRaw, androidRaw, iosRaw, current: appVersion, iosCount: iosVersions.length };
|
||||
return {
|
||||
appRaw,
|
||||
androidRaw,
|
||||
iosRaw,
|
||||
iosInfoRaw,
|
||||
widgetInfoRaw,
|
||||
current: appVersion,
|
||||
iosCount: iosVersions.length,
|
||||
};
|
||||
}
|
||||
|
||||
function nextVersion(current, bump) {
|
||||
@@ -98,8 +119,18 @@ const iosRaw = state.iosRaw.replace(
|
||||
/(\bMARKETING_VERSION\s*=\s*)([^;]+)(;)/g,
|
||||
`$1${desired}$3`,
|
||||
);
|
||||
const iosInfoRaw = state.iosInfoRaw.replace(
|
||||
/(<key>CFBundleShortVersionString<\/key>\s*<string>)([^<]+)(<\/string>)/,
|
||||
`$1${desired}$3`,
|
||||
);
|
||||
const widgetInfoRaw = state.widgetInfoRaw.replace(
|
||||
/(<key>CFBundleShortVersionString<\/key>\s*<string>)([^<]+)(<\/string>)/,
|
||||
`$1${desired}$3`,
|
||||
);
|
||||
|
||||
fs.writeFileSync(files.app, appRaw);
|
||||
fs.writeFileSync(files.android, androidRaw);
|
||||
fs.writeFileSync(files.ios, iosRaw);
|
||||
fs.writeFileSync(files.iosInfo, iosInfoRaw);
|
||||
fs.writeFileSync(files.widgetInfo, widgetInfoRaw);
|
||||
console.log(`updated public version ${state.current} -> ${desired}`);
|
||||
@@ -93,7 +93,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 48
|
||||
versionName "1.0.6"
|
||||
versionName "1.0.7"
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
}
|
||||
signingConfigs {
|
||||
|
||||
@@ -27,9 +27,14 @@
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/notification_icon"/>
|
||||
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
|
||||
<meta-data android:name="expo.modules.notifications.default_notification_icon" android:resource="@drawable/notification_icon"/>
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
|
||||
<meta-data android:name="expo.modules.updates.CODE_SIGNING_CERTIFICATE" android:value="-----BEGIN CERTIFICATE-----
MIICzTCCAbWgAwIBAgIJAPrPjxnUOGisMA0GCSqGSIb3DQEBCwUAMBAxDjAMBgNV
BAMTBVNhdWNpMB4XDTI2MDgzMDE4MTAzMFoXDTM2MDgzMDE4MTAzMFowEDEOMAwG
A1UEAxMFU2F1Y2kwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWiUac
8i9bIbNZjv8iunVr6kqlgb9vea98JHz/A83biyszg0+GpmLYsr4zV2F7A6RitiIx
SOML+wVedyD0F2quWccBGXinvMAds0TxgIOjQfP+8+VJ/duDjveHvn+OFW4s7XxW
SY7TK1ufDcPWo/f5XzfqtBveug49zv3/F3MwfmMUbEY/zvH7nayPGJNuSyf3wISn
JIt0BsHNhWykRXCzjaF2fy3nAebOyezbtbapCjUlxPENVtBCzBQ7C9kaQXCBw5mV
hxhJlOxHWXpHJgDLHpev8A/CWQRh67tsevjKi6R6SDlf0R0xSbDJTElx8goWOOQW
VO9aDqI+oH0KpWfnAgMBAAGjKjAoMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8E
DDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAyUc20bIRCxoBTJg84mPl
2nFDWSaAfzgo0K0b6x2T1suuq4LAOUgzBqJISrQAALd0besK1qUZSAMhjQu1wPZT
MHuWRMxQHfOkyHMYUfOoPMS4bE+4K0WjzfoVt3y1A+oxhcIAQUUGyNgNmebfEN07
JlX0B8UI8aIMjyfpsIDgZx1piHKwittGy/V4MPOLbmT2BBwS9BELcmWE4CwWbfeo
JM4lXY0hk3M/GTstRYaUKiwBWEU+z1PdAmWAgrtxpVnXN+oaToWh0WbVkbF1K6r5
3oBGFTL7kT+Wogg9GK04XoAREw0jZJ/k77oJx68lGVBNiB+Ue2KqX0H/0cf2CslY
Ug==
-----END CERTIFICATE-----
"/>
|
||||
<meta-data android:name="expo.modules.updates.CODE_SIGNING_METADATA" android:value="{"keyid":"main","alg":"rsa-v1_5-sha256"}"/>
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_RUNTIME_VERSION" android:value="@string/expo_runtime_version"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="5000"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://ota.apps.jbrunton.co.uk/manifest"/>
|
||||
<meta-data android:name="expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY" android:value="{"expo-channel-name":"production","expo-app-id":"9a66f3c2-fda7-416f-87e2-6db833b32e9d","xprem-branch":""}"/>
|
||||
<service android:name="com.reactnativeandroidwidget.RNWidgetCollectionService" android:permission="android.permission.BIND_REMOTEVIEWS"/>
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
@@ -44,6 +49,12 @@
|
||||
<data android:scheme="com.sauci.app"/>
|
||||
<data android:scheme="exp+sauci"/>
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true" data-generated="true">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="https" android:host="sauci.app" android:pathPrefix="/join"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<receiver android:name=".widget.LiveDraw" android:exported="true" android:label="Live Draw">
|
||||
<intent-filter>
|
||||
|
||||
@@ -3,4 +3,5 @@
|
||||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
||||
<string name="widget_livedraw_description" translatable="false">See your shared drawing with your partner.</string>
|
||||
<string name="expo_runtime_version">1.0.7</string>
|
||||
</resources>
|
||||
@@ -48,6 +48,13 @@ const releaseChannel = process.env.RELEASE_CHANNEL || 'development';
|
||||
*/
|
||||
const codeSigningDisabled = Boolean(process.env.DISABLE_CODE_SIGNING);
|
||||
|
||||
if (releaseChannel === 'production' && codeSigningDisabled) {
|
||||
throw new Error(
|
||||
'RELEASE_CHANNEL=production does not permit DISABLE_CODE_SIGNING. ' +
|
||||
'Production builds must embed the XPREM code-signing certificate and metadata.',
|
||||
);
|
||||
}
|
||||
|
||||
function assertUpdateConfigIsReal() {
|
||||
const certificatePath = path.join(__dirname, CODE_SIGNING_CERTIFICATE_PATH);
|
||||
const certificate = fs.existsSync(certificatePath)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"expo": {
|
||||
"name": "Sauci",
|
||||
"slug": "sauci",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"scheme": "app.sauci",
|
||||
|
||||
@@ -117,14 +117,12 @@ export default function RootLayout() {
|
||||
|
||||
// Handle initial URL if app was opened via deep link
|
||||
const handleDeepLink = async (url: string | null) => {
|
||||
console.log('[DeepLink] handleDeepLink called with URL:', url);
|
||||
if (!url) return;
|
||||
|
||||
// Invite links (https://sauci.app/join/{code} or app.sauci://join?code={code})
|
||||
// are handled separately from the Supabase auth deep link flow below.
|
||||
const inviteLink = parseInviteLinkCode(url);
|
||||
if (inviteLink) {
|
||||
console.log('[DeepLink] Invite link detected:', inviteLink);
|
||||
Events.inviteLinkOpened(inviteLink.source);
|
||||
await stashPendingInviteCode(inviteLink.code);
|
||||
|
||||
@@ -204,13 +202,11 @@ export default function RootLayout() {
|
||||
|
||||
// Get initial URL
|
||||
Linking.getInitialURL().then((url) => {
|
||||
console.log('[DeepLink] getInitialURL returned:', url);
|
||||
handleDeepLink(url);
|
||||
});
|
||||
|
||||
// Listen for new deep links while app is open
|
||||
const linkingSubscription = Linking.addEventListener("url", (event) => {
|
||||
console.log('[DeepLink] URL event listener triggered with:', event.url);
|
||||
handleDeepLink(event.url);
|
||||
});
|
||||
|
||||
|
||||
@@ -570,7 +570,7 @@
|
||||
INFOPLIST_FILE = Sauci/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0.6;
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -599,7 +599,7 @@
|
||||
INFOPLIST_FILE = Sauci/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0.6;
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -782,7 +782,7 @@
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.0.6;
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.sauci.app.LiveDrawWidget;
|
||||
@@ -818,7 +818,7 @@
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.0.6;
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.6</string>
|
||||
<string>1.0.7</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:sauci.app</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.sauci.app</string>
|
||||
|
||||
@@ -4,9 +4,49 @@
|
||||
<dict>
|
||||
<key>EXUpdatesCheckOnLaunch</key>
|
||||
<string>ALWAYS</string>
|
||||
<key>EXUpdatesCodeSigningCertificate</key>
|
||||
<string>-----BEGIN CERTIFICATE-----
|
||||
MIICzTCCAbWgAwIBAgIJAPrPjxnUOGisMA0GCSqGSIb3DQEBCwUAMBAxDjAMBgNV
|
||||
BAMTBVNhdWNpMB4XDTI2MDgzMDE4MTAzMFoXDTM2MDgzMDE4MTAzMFowEDEOMAwG
|
||||
A1UEAxMFU2F1Y2kwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWiUac
|
||||
8i9bIbNZjv8iunVr6kqlgb9vea98JHz/A83biyszg0+GpmLYsr4zV2F7A6RitiIx
|
||||
SOML+wVedyD0F2quWccBGXinvMAds0TxgIOjQfP+8+VJ/duDjveHvn+OFW4s7XxW
|
||||
SY7TK1ufDcPWo/f5XzfqtBveug49zv3/F3MwfmMUbEY/zvH7nayPGJNuSyf3wISn
|
||||
JIt0BsHNhWykRXCzjaF2fy3nAebOyezbtbapCjUlxPENVtBCzBQ7C9kaQXCBw5mV
|
||||
hxhJlOxHWXpHJgDLHpev8A/CWQRh67tsevjKi6R6SDlf0R0xSbDJTElx8goWOOQW
|
||||
VO9aDqI+oH0KpWfnAgMBAAGjKjAoMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8E
|
||||
DDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAyUc20bIRCxoBTJg84mPl
|
||||
2nFDWSaAfzgo0K0b6x2T1suuq4LAOUgzBqJISrQAALd0besK1qUZSAMhjQu1wPZT
|
||||
MHuWRMxQHfOkyHMYUfOoPMS4bE+4K0WjzfoVt3y1A+oxhcIAQUUGyNgNmebfEN07
|
||||
JlX0B8UI8aIMjyfpsIDgZx1piHKwittGy/V4MPOLbmT2BBwS9BELcmWE4CwWbfeo
|
||||
JM4lXY0hk3M/GTstRYaUKiwBWEU+z1PdAmWAgrtxpVnXN+oaToWh0WbVkbF1K6r5
|
||||
3oBGFTL7kT+Wogg9GK04XoAREw0jZJ/k77oJx68lGVBNiB+Ue2KqX0H/0cf2CslY
|
||||
Ug==
|
||||
-----END CERTIFICATE-----
|
||||
</string>
|
||||
<key>EXUpdatesCodeSigningMetadata</key>
|
||||
<dict>
|
||||
<key>keyid</key>
|
||||
<string>main</string>
|
||||
<key>alg</key>
|
||||
<string>rsa-v1_5-sha256</string>
|
||||
</dict>
|
||||
<key>EXUpdatesEnabled</key>
|
||||
<false/>
|
||||
<true/>
|
||||
<key>EXUpdatesLaunchWaitMs</key>
|
||||
<integer>0</integer>
|
||||
<integer>5000</integer>
|
||||
<key>EXUpdatesRequestHeaders</key>
|
||||
<dict>
|
||||
<key>expo-channel-name</key>
|
||||
<string>production</string>
|
||||
<key>expo-app-id</key>
|
||||
<string>9a66f3c2-fda7-416f-87e2-6db833b32e9d</string>
|
||||
<key>xprem-branch</key>
|
||||
<string/>
|
||||
</dict>
|
||||
<key>EXUpdatesRuntimeVersion</key>
|
||||
<string>1.0.7</string>
|
||||
<key>EXUpdatesURL</key>
|
||||
<string>https://ota.apps.jbrunton.co.uk/manifest</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,46 @@
|
||||
const configPath = require.resolve("../../app.config");
|
||||
|
||||
type UpdateConfig = {
|
||||
updates: {
|
||||
codeSigningCertificate?: string;
|
||||
codeSigningMetadata?: Record<string, string>;
|
||||
};
|
||||
};
|
||||
|
||||
function resolveConfig(environment: Record<string, string>): UpdateConfig {
|
||||
const previous = {
|
||||
DISABLE_CODE_SIGNING: process.env.DISABLE_CODE_SIGNING,
|
||||
RELEASE_CHANNEL: process.env.RELEASE_CHANNEL,
|
||||
};
|
||||
|
||||
Object.assign(process.env, environment);
|
||||
jest.resetModules();
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
return (require(configPath) as () => UpdateConfig)();
|
||||
} finally {
|
||||
for (const [key, value] of Object.entries(previous)) {
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("Expo OTA configuration", () => {
|
||||
it("rejects disabling code signing for a production build", () => {
|
||||
expect(() =>
|
||||
resolveConfig({ RELEASE_CHANNEL: "production", DISABLE_CODE_SIGNING: "1" }),
|
||||
).toThrow("RELEASE_CHANNEL=production does not permit DISABLE_CODE_SIGNING");
|
||||
});
|
||||
|
||||
it("keeps local development code-signing opt-out available", () => {
|
||||
const config = resolveConfig({ RELEASE_CHANNEL: "development", DISABLE_CODE_SIGNING: "1" });
|
||||
|
||||
expect(config.updates.codeSigningCertificate).toBeUndefined();
|
||||
expect(config.updates.codeSigningMetadata).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -112,6 +112,7 @@ describe("authStore", () => {
|
||||
});
|
||||
|
||||
it("clears user-scoped stores and hosted auth when the API rejects the session", async () => {
|
||||
useAuthStore.setState({ sealedCount: 3 } as any);
|
||||
(authClient.auth.getSession as jest.Mock).mockResolvedValueOnce({
|
||||
data: { session: { user: { id: "me" } } },
|
||||
});
|
||||
@@ -122,6 +123,7 @@ describe("authStore", () => {
|
||||
|
||||
expect(useAuthStore.getState()).toMatchObject({
|
||||
user: null,
|
||||
sealedCount: 0,
|
||||
isAuthenticated: false,
|
||||
isAnonymous: false,
|
||||
isLoading: false,
|
||||
@@ -132,4 +134,28 @@ describe("authStore", () => {
|
||||
expect(useSubscriptionStore.getState().subscription).toMatchObject({ isProUser: false });
|
||||
expect(authClient.auth.signOut).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clears sealed answers during an explicit sign-out", async () => {
|
||||
useAuthStore.setState({ user: profile, sealedCount: 3, isAuthenticated: true } as any);
|
||||
|
||||
await useAuthStore.getState().signOut();
|
||||
|
||||
expect(useAuthStore.getState()).toMatchObject({
|
||||
user: null,
|
||||
sealedCount: 0,
|
||||
isAuthenticated: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("clears sealed answers when the auth listener clears the user", () => {
|
||||
useAuthStore.setState({ user: profile, sealedCount: 3, isAuthenticated: true } as any);
|
||||
|
||||
useAuthStore.getState().setUser(null);
|
||||
|
||||
expect(useAuthStore.getState()).toMatchObject({
|
||||
user: null,
|
||||
sealedCount: 0,
|
||||
isAuthenticated: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -88,7 +88,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError && error.status === 401) {
|
||||
console.log("[Auth] Session rejected by API, signing out");
|
||||
set({ user: null, couple: null, partner: null, isAuthenticated: false, isAnonymous: false, isLoading: false });
|
||||
set({ user: null, couple: null, partner: null, sealedCount: 0, isAuthenticated: false, isAnonymous: false, isLoading: false });
|
||||
const { useMatchStore, usePacksStore, useMessageStore, useSubscriptionStore, useNotificationPreferencesStore, useStreakStore, useResponsesStore, useQuizStore } = getOtherStores();
|
||||
useMatchStore.getState().clearMatches();
|
||||
usePacksStore.getState().clearPacks();
|
||||
@@ -137,6 +137,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
user: null,
|
||||
couple: null,
|
||||
partner: null,
|
||||
sealedCount: 0,
|
||||
isAuthenticated: false,
|
||||
isAnonymous: false,
|
||||
isLoading: false,
|
||||
@@ -171,7 +172,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
...(user === null && { isAnonymous: false }),
|
||||
isLoading: false,
|
||||
// Clear couple/partner when user is null (signed out)
|
||||
...(user === null && { couple: null, partner: null })
|
||||
...(user === null && { couple: null, partner: null, sealedCount: 0 })
|
||||
});
|
||||
// Clear other stores when user signs out
|
||||
if (user === null) {
|
||||
@@ -211,4 +212,3 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
void syncTimezone(userId);
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</dict>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>45</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.6</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.7</string>
|
||||
</dict>
|
||||
</plist>
|
||||
+21
-23
@@ -12,10 +12,10 @@ already installed build.
|
||||
|
||||
## Status
|
||||
|
||||
The client is wired with the real server values (app id
|
||||
`9a66f3c2-fda7-416f-87e2-6db833b32e9d` and the Sauci code-signing certificate,
|
||||
SHA-256 fingerprint beginning `DF:C9:17:A9`). What remains is the native
|
||||
prebuild and a store build; see [Server registration](#server-registration).
|
||||
The client and checked-in native projects are wired with the real server values
|
||||
(app id `9a66f3c2-fda7-416f-87e2-6db833b32e9d` and the Sauci code-signing
|
||||
certificate, SHA-256 fingerprint beginning `DF:C9:17:A9`). A new store build is
|
||||
required before updates can flow; see [Server registration](#server-registration).
|
||||
|
||||
## How it works
|
||||
|
||||
@@ -54,7 +54,7 @@ runtimeVersion: { policy: 'appVersion' }
|
||||
```
|
||||
|
||||
The runtime version is therefore the `expo.version` field in `app.json`, for
|
||||
example `1.0.6`. An update is only offered to builds whose runtime version
|
||||
example `1.0.7`. An update is only offered to builds whose runtime version
|
||||
matches exactly.
|
||||
|
||||
The practical rule: **any change that touches native code, native dependencies,
|
||||
@@ -136,28 +136,27 @@ the Sauci app.
|
||||
|
||||
`apps/mobile/ios` and `apps/mobile/android` are committed but generated by Expo
|
||||
CNG, and `docs/releasing.md` treats `expo prebuild` as a source change rather
|
||||
than a release step. The native projects currently still carry the
|
||||
updates-disabled defaults:
|
||||
than a release step. The checked-in native projects embed the production updates
|
||||
configuration:
|
||||
|
||||
- `ios/Sauci/Supporting/Expo.plist`: `EXUpdatesEnabled` is `false`.
|
||||
- `android/app/src/main/AndroidManifest.xml`: `expo.modules.updates.ENABLED` is
|
||||
`false`.
|
||||
- `ios/Sauci/Supporting/Expo.plist` enables updates and contains the manifest
|
||||
URL, runtime version, request headers, code-signing metadata, and public
|
||||
certificate.
|
||||
- `android/app/src/main/AndroidManifest.xml` enables updates and contains the
|
||||
same production configuration; `android/app/src/main/res/values/strings.xml`
|
||||
supplies its resolved runtime version.
|
||||
|
||||
The `expo-updates` config plugin writes the URL, the resolved runtime version,
|
||||
the request headers, the code-signing metadata, and the certificate contents into
|
||||
these files. Those values embed the real app id and certificate, so the native
|
||||
projects are deliberately left untouched until both land.
|
||||
|
||||
With the real app id and certificate now committed, regenerate and commit the
|
||||
native projects as a reviewed source change:
|
||||
The `expo-updates` config plugin wrote those values from the dynamic config as a
|
||||
reviewed source change. When future changes intentionally affect native config,
|
||||
regenerate and review the native diff separately:
|
||||
|
||||
```bash
|
||||
cd apps/mobile
|
||||
RELEASE_CHANNEL=production npx expo prebuild
|
||||
git diff # expect only expo-updates related native changes
|
||||
git diff # retain only reviewed expo-updates native changes
|
||||
```
|
||||
|
||||
A build produced before that prebuild will not check for updates.
|
||||
A build produced before this native source change will not check for updates.
|
||||
|
||||
## Publishing an update
|
||||
|
||||
@@ -199,7 +198,7 @@ production deploy and none of the review latency.
|
||||
curl -sD - "https://ota.apps.jbrunton.co.uk/manifest" \
|
||||
-H "expo-app-id: <xprem-app-id>" \
|
||||
-H "expo-channel-name: production" \
|
||||
-H "expo-runtime-version: 1.0.6" \
|
||||
-H "expo-runtime-version: 1.0.7" \
|
||||
-H "expo-platform: ios"
|
||||
```
|
||||
|
||||
@@ -238,6 +237,5 @@ documented in the homelab repo (`unraid/docs/xprem-ota.md`).
|
||||
| Publish token | `XPREM_SAUCI_API_KEY` in the homelab repo `.env`; export as `EOO_TOKEN` when publishing |
|
||||
| Channels | `staging` and `production` created server-side, mapped 1:1 to branches of the same names. `development` intentionally has no server channel — dev clients run with `DISABLE_CODE_SIGNING=1` against the packager |
|
||||
|
||||
Remaining before updates flow: run the prebuild described in
|
||||
[Native projects](#native-projects), commit the native diff, and cut a new store
|
||||
build. Only builds produced after that point can receive updates.
|
||||
Remaining before updates flow: cut a new store build from this checked-in native
|
||||
source. Only builds produced after that point can receive updates.
|
||||
Reference in new issue
Block a user