diff --git a/apps/mobile/app.config.ts b/apps/mobile/app.config.ts index 363639020..81b4fb101 100644 --- a/apps/mobile/app.config.ts +++ b/apps/mobile/app.config.ts @@ -41,7 +41,7 @@ export default ({ config }: ConfigContext): ExpoConfig => { }, owner: "follow", updates: { - url: "https://folo-custom-expo-updates.vercel.app/api/manifest", + url: "https://ota.folo.is/manifest", requestHeaders: { "expo-channel-name": channelName, }, diff --git a/apps/mobile/src/modules/ota/__tests__/store.test.ts b/apps/mobile/src/modules/ota/__tests__/store.test.ts index f5d39daac..8b5cc4777 100644 --- a/apps/mobile/src/modules/ota/__tests__/store.test.ts +++ b/apps/mobile/src/modules/ota/__tests__/store.test.ts @@ -18,5 +18,41 @@ describe("reduceOtaState", () => { expect(state.status).toBe("ready") expect(state.pendingVersion).toBe("0.4.2") + expect(state.errorMessage).toBeNull() + }) + + it("stores the latest error without clearing the pending version", () => { + const state = reduceOtaState( + { + status: "ready", + pendingVersion: "0.4.2", + errorMessage: null, + }, + { + type: "failed", + message: "Network unavailable", + }, + ) + + expect(state.status).toBe("error") + expect(state.pendingVersion).toBe("0.4.2") + expect(state.errorMessage).toBe("Network unavailable") + }) + + it("resets the OTA state back to idle", () => { + const state = reduceOtaState( + { + status: "error", + pendingVersion: "0.4.2", + errorMessage: "Network unavailable", + }, + { + type: "reset", + }, + ) + + expect(state.status).toBe("idle") + expect(state.pendingVersion).toBeNull() + expect(state.errorMessage).toBeNull() }) }) diff --git a/apps/mobile/src/modules/ota/provider.tsx b/apps/mobile/src/modules/ota/provider.tsx index 7cc8c1349..92ae2d7d2 100644 --- a/apps/mobile/src/modules/ota/provider.tsx +++ b/apps/mobile/src/modules/ota/provider.tsx @@ -14,24 +14,13 @@ const resolvePendingVersion = (manifest: Manifest | undefined): string => { return Updates.runtimeVersion ?? "unknown" } - const metadataVersion = + const metadataReleaseVersion = "metadata" in manifest && manifest.metadata && typeof manifest.metadata === "object" - ? Reflect.get(manifest.metadata, "version") + ? Reflect.get(manifest.metadata, "releaseVersion") : undefined - if (typeof metadataVersion === "string" && metadataVersion.length > 0) { - return metadataVersion - } - - const expoClientVersion = - "extra" in manifest && - manifest.extra?.expoClient && - typeof manifest.extra.expoClient.version === "string" - ? manifest.extra.expoClient.version - : undefined - - if (expoClientVersion) { - return expoClientVersion + if (typeof metadataReleaseVersion === "string" && metadataReleaseVersion.length > 0) { + return metadataReleaseVersion } if ("runtimeVersion" in manifest && typeof manifest.runtimeVersion === "string") { @@ -42,10 +31,42 @@ const resolvePendingVersion = (manifest: Manifest | undefined): string => { } export const OtaProvider = ({ children }: PropsWithChildren) => { + const { checkError, downloadError, downloadedUpdate, isUpdatePending } = Updates.useUpdates() const [state, dispatch] = useReducer(reduceOtaState, initialOtaState) + const nativePendingVersion = useMemo(() => { + if (!isUpdatePending || downloadedUpdate?.type !== Updates.UpdateInfoType.NEW) { + return null + } + + return resolvePendingVersion(downloadedUpdate.manifest) + }, [downloadedUpdate, isUpdatePending]) + useEffect(() => { - if (__DEV__ || !Updates.isEnabled) { + if (!nativePendingVersion) { + return + } + + dispatch({ + type: "downloaded", + version: nativePendingVersion, + }) + }, [nativePendingVersion]) + + useEffect(() => { + const error = downloadError || checkError + if (!error) { + return + } + + dispatch({ + type: "failed", + message: error.message, + }) + }, [checkError, downloadError]) + + useEffect(() => { + if (__DEV__ || !Updates.isEnabled || nativePendingVersion) { return } @@ -61,7 +82,9 @@ export const OtaProvider = ({ children }: PropsWithChildren) => { } if (!checkResult.isAvailable) { - dispatch({ type: "reset" }) + if (!isUpdatePending) { + dispatch({ type: "reset" }) + } return } @@ -79,7 +102,9 @@ export const OtaProvider = ({ children }: PropsWithChildren) => { return } - dispatch({ type: "reset" }) + if (!isUpdatePending) { + dispatch({ type: "reset" }) + } } catch (error) { if (isCancelled) { return @@ -100,9 +125,19 @@ export const OtaProvider = ({ children }: PropsWithChildren) => { isCancelled = true task.cancel() } - }, []) + }, [isUpdatePending, nativePendingVersion]) - const value = useMemo(() => state, [state]) + const value = useMemo(() => { + if (!nativePendingVersion) { + return state + } + + return { + status: "ready" as const, + pendingVersion: nativePendingVersion, + errorMessage: null, + } + }, [nativePendingVersion, state]) return {children} }