@rovq/react-native 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ import { RovqError } from './errors';
2
+ import { checkForUpdate, downloadUpdate, installUpdate, restartApp } from './api';
3
+ import type { UpdateInfo } from './api';
4
+
5
+ /**
6
+ * - `background`: downloads without showing anything. The update loads on the next start.
7
+ * - `foreground`: shows progress, then restarts the app as soon as the update is ready.
8
+ * - `auto`: foreground for a release published with `--mandatory`, background otherwise.
9
+ */
10
+ export type UpdateMode = 'background' | 'foreground' | 'auto';
11
+
12
+ export type UpdatePhase =
13
+ | 'idle'
14
+ | 'checking'
15
+ | 'upToDate'
16
+ | 'downloading'
17
+ | 'installing'
18
+ /** Installed in the background. It loads on the next start. */
19
+ | 'ready'
20
+ /** About to restart, in the foreground. */
21
+ | 'restarting'
22
+ | 'error';
23
+
24
+ export interface UpdateState {
25
+ phase: UpdatePhase;
26
+ /** From 0 to 1 while downloading, otherwise null. */
27
+ progress: number | null;
28
+ update: UpdateInfo | null;
29
+ /** True once an update was found that this mode handles in the foreground. */
30
+ foreground: boolean;
31
+ error: RovqError | null;
32
+ }
33
+
34
+ export const INITIAL_STATE: UpdateState = {
35
+ phase: 'idle',
36
+ progress: null,
37
+ update: null,
38
+ foreground: false,
39
+ error: null,
40
+ };
41
+
42
+ /**
43
+ * Checks, downloads, and installs, reporting each step. It never throws: a failure ends in the
44
+ * `error` phase, and the app keeps running what it has.
45
+ */
46
+ export async function runUpdateFlow(
47
+ mode: UpdateMode,
48
+ report: (state: UpdateState) => void,
49
+ ): Promise<UpdateState> {
50
+ let state: UpdateState = { ...INITIAL_STATE, phase: 'checking' };
51
+ const set = (change: Partial<UpdateState>) => {
52
+ state = { ...state, ...change };
53
+ report(state);
54
+ return state;
55
+ };
56
+ report(state);
57
+
58
+ try {
59
+ const check = await checkForUpdate();
60
+ if (check.status === 'ROLLED_BACK') {
61
+ // The server stopped this release. In the foreground, go back right away.
62
+ if (mode === 'foreground') {
63
+ set({ phase: 'restarting', foreground: true });
64
+ restartApp();
65
+ return state;
66
+ }
67
+ return set({ phase: 'ready' });
68
+ }
69
+ if (check.status === 'UP_TO_DATE') return set({ phase: 'upToDate' });
70
+
71
+ const foreground = mode === 'foreground' || (mode === 'auto' && check.update.mandatory);
72
+ set({ phase: 'downloading', update: check.update, foreground, progress: 0 });
73
+ await downloadUpdate({
74
+ onProgress: ({ receivedBytes, totalBytes }) =>
75
+ set({ progress: totalBytes > 0 ? receivedBytes / totalBytes : 0 }),
76
+ });
77
+ set({ phase: 'installing', progress: 1 });
78
+ await installUpdate();
79
+
80
+ if (!foreground) return set({ phase: 'ready', progress: null });
81
+ set({ phase: 'restarting' });
82
+ restartApp();
83
+ return state;
84
+ } catch (error) {
85
+ const rovqError =
86
+ error instanceof RovqError
87
+ ? error
88
+ : new RovqError('UNKNOWN', error instanceof Error ? error.message : String(error));
89
+ return set({ phase: 'error', error: rovqError, progress: null });
90
+ }
91
+ }