@flighthq/updater 0.1.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.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/updater.d.ts +25 -0
- package/dist/updater.d.ts.map +1 -0
- package/dist/updater.js +254 -0
- package/dist/updater.js.map +1 -0
- package/package.json +38 -0
- package/src/updater.test.ts +652 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AppUpdater, UpdateInfo, UpdaterBackend, UpdaterConfig, UpdaterSignatureConfig, UpdaterState } from '@flighthq/types';
|
|
2
|
+
export declare function attachAppUpdater(updater: AppUpdater): void;
|
|
3
|
+
export declare function cancelAppUpdateDownload(): void;
|
|
4
|
+
export declare function checkAndDownloadAppUpdate(): void;
|
|
5
|
+
export declare function checkForAppUpdate(): void;
|
|
6
|
+
export declare function createAppUpdater(): AppUpdater;
|
|
7
|
+
export declare function createUpdaterConfig(): UpdaterConfig;
|
|
8
|
+
export declare function createUpdaterState(): UpdaterState;
|
|
9
|
+
export declare function createWebUpdaterBackend(): UpdaterBackend;
|
|
10
|
+
export declare function detachAppUpdater(updater: AppUpdater): void;
|
|
11
|
+
export declare function disposeAppUpdater(updater: AppUpdater): void;
|
|
12
|
+
export declare function downloadAppUpdate(): void;
|
|
13
|
+
export declare function getAppUpdaterState(updater: AppUpdater): Readonly<UpdaterState>;
|
|
14
|
+
export declare function getUpdaterBackend(): UpdaterBackend;
|
|
15
|
+
export declare function getUpdaterChannel(): string;
|
|
16
|
+
export declare function getUpdaterConfig(): Readonly<UpdaterConfig>;
|
|
17
|
+
export declare function isAppUpdateEligible(info: Readonly<UpdateInfo>, rolloutSeed: number): boolean;
|
|
18
|
+
export declare function quitAndInstallUpdate(): void;
|
|
19
|
+
export declare function rollbackAppUpdate(): void;
|
|
20
|
+
export declare function setUpdaterBackend(backend: UpdaterBackend | null): void;
|
|
21
|
+
export declare function setUpdaterChannel(channel: string): void;
|
|
22
|
+
export declare function setUpdaterConfig(config: Readonly<UpdaterConfig>): void;
|
|
23
|
+
export declare function setUpdaterFeedUrl(url: string): void;
|
|
24
|
+
export declare function setUpdaterSignatureConfig(config: Readonly<UpdaterSignatureConfig> | null): void;
|
|
25
|
+
//# sourceMappingURL=updater.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updater.d.ts","sourceRoot":"","sources":["../src/updater.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,aAAa,EACb,sBAAsB,EACtB,YAAY,EACb,MAAM,iBAAiB,CAAC;AAMzB,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CA+C1D;AAID,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAID,wBAAgB,yBAAyB,IAAI,IAAI,CAMhD;AAID,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAID,wBAAgB,gBAAgB,IAAI,UAAU,CAe7C;AAID,wBAAgB,mBAAmB,IAAI,aAAa,CAMnD;AAGD,wBAAgB,kBAAkB,IAAI,YAAY,CAOjD;AAID,wBAAgB,uBAAuB,IAAI,cAAc,CAsDxD;AAGD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAM1D;AAID,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAE3D;AAID,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAKD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,UAAU,GAAG,QAAQ,CAAC,YAAY,CAAC,CAE9E;AAGD,wBAAgB,iBAAiB,IAAI,cAAc,CAGlD;AAGD,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAGD,wBAAgB,gBAAgB,IAAI,QAAQ,CAAC,aAAa,CAAC,CAE1D;AAMD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAE5F;AAGD,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAID,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAGD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,GAAG,IAAI,CAEtE;AAID,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEvD;AAID,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,IAAI,CAEtE;AAGD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEnD;AAKD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,QAAQ,CAAC,sBAAsB,CAAC,GAAG,IAAI,GAAG,IAAI,CAE/F"}
|
package/dist/updater.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { createSignal, emitSignal } from '@flighthq/signals';
|
|
2
|
+
// Begins delivering update lifecycle events to `updater`'s signals by subscribing to the active
|
|
3
|
+
// backend. Each subscribe* is wired to its matching signal, and the phase state is updated on every
|
|
4
|
+
// event so getAppUpdaterState reflects the latest lifecycle. Idempotent: a prior subscription is
|
|
5
|
+
// torn down first. Pair with detachAppUpdater/disposeAppUpdater.
|
|
6
|
+
export function attachAppUpdater(updater) {
|
|
7
|
+
detachAppUpdater(updater);
|
|
8
|
+
const backend = getUpdaterBackend();
|
|
9
|
+
const unsubscribes = [
|
|
10
|
+
backend.subscribeChecking(() => {
|
|
11
|
+
_setState(updater, { phase: 'Checking', info: null, progress: null, error: null });
|
|
12
|
+
emitSignal(updater.onChecking);
|
|
13
|
+
}),
|
|
14
|
+
backend.subscribeUpdateAvailable((info) => {
|
|
15
|
+
_setState(updater, { phase: 'UpdateAvailable', info, progress: null, error: null });
|
|
16
|
+
emitSignal(updater.onUpdateAvailable, info);
|
|
17
|
+
}),
|
|
18
|
+
backend.subscribeUpdateNotAvailable(() => {
|
|
19
|
+
_setState(updater, (prev) => ({ ...prev, phase: 'Idle' }));
|
|
20
|
+
emitSignal(updater.onUpdateNotAvailable);
|
|
21
|
+
}),
|
|
22
|
+
backend.subscribeDownloadProgress((progress) => {
|
|
23
|
+
_setState(updater, (prev) => ({ ...prev, phase: 'Downloading', progress }));
|
|
24
|
+
emitSignal(updater.onDownloadProgress, progress);
|
|
25
|
+
}),
|
|
26
|
+
backend.subscribeUpdateDownloaded((info) => {
|
|
27
|
+
_setState(updater, (prev) => ({ ...prev, phase: 'Downloaded', info, progress: null }));
|
|
28
|
+
emitSignal(updater.onUpdateDownloaded, info);
|
|
29
|
+
}),
|
|
30
|
+
backend.subscribeError((error) => {
|
|
31
|
+
_setState(updater, (prev) => ({ ...prev, phase: 'Error', error }));
|
|
32
|
+
emitSignal(updater.onError, error);
|
|
33
|
+
}),
|
|
34
|
+
backend.subscribeUpdateCancelled(() => {
|
|
35
|
+
_setState(updater, (prev) => ({ ...prev, phase: 'Idle' }));
|
|
36
|
+
emitSignal(updater.onUpdateCancelled);
|
|
37
|
+
}),
|
|
38
|
+
backend.subscribeUpdateStaging(() => {
|
|
39
|
+
_setState(updater, (prev) => ({ ...prev, phase: 'Staging' }));
|
|
40
|
+
emitSignal(updater.onUpdateStaging);
|
|
41
|
+
}),
|
|
42
|
+
backend.subscribeUpdateVerified(() => {
|
|
43
|
+
emitSignal(updater.onUpdateVerified);
|
|
44
|
+
}),
|
|
45
|
+
backend.subscribeUpdateRolledBack(() => {
|
|
46
|
+
_setState(updater, (prev) => ({ ...prev, phase: 'Idle', info: null, progress: null, error: null }));
|
|
47
|
+
emitSignal(updater.onUpdateRolledBack);
|
|
48
|
+
}),
|
|
49
|
+
];
|
|
50
|
+
_subscriptions.set(updater, () => {
|
|
51
|
+
for (const unsubscribe of unsubscribes)
|
|
52
|
+
unsubscribe();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Asks the active backend to cancel a download in progress. Result arrives via onUpdateCancelled or
|
|
56
|
+
// onError(kind: 'Cancelled') depending on the backend.
|
|
57
|
+
export function cancelAppUpdateDownload() {
|
|
58
|
+
getUpdaterBackend().cancelDownload();
|
|
59
|
+
}
|
|
60
|
+
// Triggers a check; if an update is found and autoDownload is true, also starts the download. This
|
|
61
|
+
// is a single-call convenience; results still arrive through signals.
|
|
62
|
+
export function checkAndDownloadAppUpdate() {
|
|
63
|
+
const config = getUpdaterConfig();
|
|
64
|
+
getUpdaterBackend().checkForUpdates();
|
|
65
|
+
if (config.autoDownload) {
|
|
66
|
+
getUpdaterBackend().downloadUpdate();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Asks the active backend to check for an available update. Lifecycle results arrive through the
|
|
70
|
+
// attached AppUpdater's signals.
|
|
71
|
+
export function checkForAppUpdate() {
|
|
72
|
+
getUpdaterBackend().checkForUpdates();
|
|
73
|
+
}
|
|
74
|
+
// Allocates an AppUpdater event entity with inert signals and an Idle state; call attachAppUpdater
|
|
75
|
+
// to start delivery.
|
|
76
|
+
export function createAppUpdater() {
|
|
77
|
+
const updater = {
|
|
78
|
+
onChecking: createSignal(),
|
|
79
|
+
onDownloadProgress: createSignal(),
|
|
80
|
+
onError: createSignal(),
|
|
81
|
+
onUpdateAvailable: createSignal(),
|
|
82
|
+
onUpdateCancelled: createSignal(),
|
|
83
|
+
onUpdateDownloaded: createSignal(),
|
|
84
|
+
onUpdateNotAvailable: createSignal(),
|
|
85
|
+
onUpdateRolledBack: createSignal(),
|
|
86
|
+
onUpdateStaging: createSignal(),
|
|
87
|
+
onUpdateVerified: createSignal(),
|
|
88
|
+
};
|
|
89
|
+
_states.set(updater, createUpdaterState());
|
|
90
|
+
return updater;
|
|
91
|
+
}
|
|
92
|
+
// Allocates an UpdaterConfig with the recommended defaults: manual download, no auto-install, no
|
|
93
|
+
// prerelease.
|
|
94
|
+
export function createUpdaterConfig() {
|
|
95
|
+
return {
|
|
96
|
+
allowPrerelease: false,
|
|
97
|
+
autoDownload: false,
|
|
98
|
+
autoInstallOnAppQuit: false,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Allocates a zeroed UpdaterState at the Idle phase with all payloads null.
|
|
102
|
+
export function createUpdaterState() {
|
|
103
|
+
return {
|
|
104
|
+
error: null,
|
|
105
|
+
info: null,
|
|
106
|
+
phase: 'Idle',
|
|
107
|
+
progress: null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// Builds the default web backend. Auto-update needs a native host, so every command no-ops and every
|
|
111
|
+
// subscribe* returns an inert unsubscribe — the browser has no application updater to drive.
|
|
112
|
+
export function createWebUpdaterBackend() {
|
|
113
|
+
let _config = createUpdaterConfig();
|
|
114
|
+
let _channel = 'stable';
|
|
115
|
+
return {
|
|
116
|
+
cancelDownload() { },
|
|
117
|
+
checkForUpdates() { },
|
|
118
|
+
downloadUpdate() { },
|
|
119
|
+
getChannel() {
|
|
120
|
+
return _channel;
|
|
121
|
+
},
|
|
122
|
+
getConfig() {
|
|
123
|
+
return _config;
|
|
124
|
+
},
|
|
125
|
+
quitAndInstall() { },
|
|
126
|
+
rollback() { },
|
|
127
|
+
setChannel(channel) {
|
|
128
|
+
_channel = channel;
|
|
129
|
+
},
|
|
130
|
+
setConfig(config) {
|
|
131
|
+
_config = { ...config };
|
|
132
|
+
},
|
|
133
|
+
setFeedUrl() { },
|
|
134
|
+
setSignatureConfig() { },
|
|
135
|
+
subscribeChecking() {
|
|
136
|
+
return () => { };
|
|
137
|
+
},
|
|
138
|
+
subscribeDownloadProgress() {
|
|
139
|
+
return () => { };
|
|
140
|
+
},
|
|
141
|
+
subscribeError() {
|
|
142
|
+
return () => { };
|
|
143
|
+
},
|
|
144
|
+
subscribeUpdateAvailable() {
|
|
145
|
+
return () => { };
|
|
146
|
+
},
|
|
147
|
+
subscribeUpdateCancelled() {
|
|
148
|
+
return () => { };
|
|
149
|
+
},
|
|
150
|
+
subscribeUpdateDownloaded() {
|
|
151
|
+
return () => { };
|
|
152
|
+
},
|
|
153
|
+
subscribeUpdateNotAvailable() {
|
|
154
|
+
return () => { };
|
|
155
|
+
},
|
|
156
|
+
subscribeUpdateRolledBack() {
|
|
157
|
+
return () => { };
|
|
158
|
+
},
|
|
159
|
+
subscribeUpdateStaging() {
|
|
160
|
+
return () => { };
|
|
161
|
+
},
|
|
162
|
+
subscribeUpdateVerified() {
|
|
163
|
+
return () => { };
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// Stops delivery to `updater` and forgets its subscription. Safe to call when not attached.
|
|
168
|
+
export function detachAppUpdater(updater) {
|
|
169
|
+
const unsubscribe = _subscriptions.get(updater);
|
|
170
|
+
if (unsubscribe !== undefined) {
|
|
171
|
+
unsubscribe();
|
|
172
|
+
_subscriptions.delete(updater);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Releases `updater` for garbage collection by detaching its backend subscription. The signals remain
|
|
176
|
+
// plain GC-managed memory afterward.
|
|
177
|
+
export function disposeAppUpdater(updater) {
|
|
178
|
+
detachAppUpdater(updater);
|
|
179
|
+
}
|
|
180
|
+
// Asks the active backend to download the available update. Progress and completion arrive through
|
|
181
|
+
// the attached AppUpdater's signals.
|
|
182
|
+
export function downloadAppUpdate() {
|
|
183
|
+
getUpdaterBackend().downloadUpdate();
|
|
184
|
+
}
|
|
185
|
+
// Returns the current queryable lifecycle state for `updater`. This lets a late subscriber or UI
|
|
186
|
+
// read whether a check is pending, what update is available, or the last error, without having
|
|
187
|
+
// listened since the beginning.
|
|
188
|
+
export function getAppUpdaterState(updater) {
|
|
189
|
+
return _states.get(updater) ?? createUpdaterState();
|
|
190
|
+
}
|
|
191
|
+
// The active updater backend, or a lazily-created web default. There is always a backend.
|
|
192
|
+
export function getUpdaterBackend() {
|
|
193
|
+
if (_backend === null)
|
|
194
|
+
_backend = createWebUpdaterBackend();
|
|
195
|
+
return _backend;
|
|
196
|
+
}
|
|
197
|
+
// Returns the active update channel string. Conventional values are 'stable', 'beta', 'alpha'.
|
|
198
|
+
export function getUpdaterChannel() {
|
|
199
|
+
return getUpdaterBackend().getChannel();
|
|
200
|
+
}
|
|
201
|
+
// Returns a copy of the active updater configuration.
|
|
202
|
+
export function getUpdaterConfig() {
|
|
203
|
+
return getUpdaterBackend().getConfig();
|
|
204
|
+
}
|
|
205
|
+
// Deterministic check for staged-rollout eligibility. Returns true when the given `rolloutSeed`
|
|
206
|
+
// (a number in [0, 1)) falls within the update's staged rollout percentage. A seed derived from a
|
|
207
|
+
// stable device/user identifier ensures consistent results across sessions for the same device.
|
|
208
|
+
// NOTE: `stagedRolloutPercent` is 0–100; 100 means full rollout.
|
|
209
|
+
export function isAppUpdateEligible(info, rolloutSeed) {
|
|
210
|
+
return rolloutSeed * 100 < info.stagedRolloutPercent;
|
|
211
|
+
}
|
|
212
|
+
// Quits the application and installs a downloaded update via the active backend.
|
|
213
|
+
export function quitAndInstallUpdate() {
|
|
214
|
+
getUpdaterBackend().quitAndInstall();
|
|
215
|
+
}
|
|
216
|
+
// Requests the active backend to roll back the last installed update. The backend must support
|
|
217
|
+
// rollback (Squirrel/MSIX); the web default no-ops. Result arrives via onUpdateRolledBack.
|
|
218
|
+
export function rollbackAppUpdate() {
|
|
219
|
+
getUpdaterBackend().rollback();
|
|
220
|
+
}
|
|
221
|
+
// Installs a native host updater backend; pass null to fall back to the web default.
|
|
222
|
+
export function setUpdaterBackend(backend) {
|
|
223
|
+
_backend = backend;
|
|
224
|
+
}
|
|
225
|
+
// Sets the active update channel. Conventional values: 'stable', 'beta', 'alpha'. Free string so
|
|
226
|
+
// hosts and apps can define their own channels. Layered over the feed URL, not replacing it.
|
|
227
|
+
export function setUpdaterChannel(channel) {
|
|
228
|
+
getUpdaterBackend().setChannel(channel);
|
|
229
|
+
}
|
|
230
|
+
// Applies an updater configuration (auto-download, auto-install-on-quit, allow-prerelease) to the
|
|
231
|
+
// active backend.
|
|
232
|
+
export function setUpdaterConfig(config) {
|
|
233
|
+
getUpdaterBackend().setConfig(config);
|
|
234
|
+
}
|
|
235
|
+
// Points the active backend at an update feed URL.
|
|
236
|
+
export function setUpdaterFeedUrl(url) {
|
|
237
|
+
getUpdaterBackend().setFeedUrl(url);
|
|
238
|
+
}
|
|
239
|
+
// Configures signature/integrity verification for the active backend. Verification executes in the
|
|
240
|
+
// host backend; this package owns the configuration contract and the result events. Pass null to
|
|
241
|
+
// clear any previously set signature config.
|
|
242
|
+
export function setUpdaterSignatureConfig(config) {
|
|
243
|
+
getUpdaterBackend().setSignatureConfig(config);
|
|
244
|
+
}
|
|
245
|
+
let _backend = null;
|
|
246
|
+
const _states = new WeakMap();
|
|
247
|
+
const _subscriptions = new WeakMap();
|
|
248
|
+
// Updates the per-entity state. Accepts either a full replacement or an updater function.
|
|
249
|
+
function _setState(updater, update) {
|
|
250
|
+
const prev = _states.get(updater) ?? createUpdaterState();
|
|
251
|
+
const next = typeof update === 'function' ? update(prev) : update;
|
|
252
|
+
_states.set(updater, next);
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=updater.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updater.js","sourceRoot":"","sources":["../src/updater.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flighthq/updater",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src/**/*.test.ts",
|
|
16
|
+
"!dist/**/*.test.js",
|
|
17
|
+
"!dist/**/*.test.d.ts",
|
|
18
|
+
"!dist/**/*.test.js.map",
|
|
19
|
+
"!dist/**/*.test.d.ts.map"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -b",
|
|
23
|
+
"clean": "tsc -b --clean",
|
|
24
|
+
"test": "vitest run --config vitest.config.ts",
|
|
25
|
+
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
26
|
+
"prepack": "npm run clean && npm run clean:dist && npm run build",
|
|
27
|
+
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@flighthq/signals": "0.1.0",
|
|
31
|
+
"@flighthq/types": "0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "^5.3.0"
|
|
35
|
+
},
|
|
36
|
+
"description": "Application auto-update lifecycle signals (checking, available, progress, downloaded, error) over a swappable web/native backend",
|
|
37
|
+
"sideEffects": false
|
|
38
|
+
}
|
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
import { connectSignal } from '@flighthq/signals';
|
|
2
|
+
import type { UpdateInfo, UpdateProgress, UpdaterBackend, UpdaterError } from '@flighthq/types';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
attachAppUpdater,
|
|
6
|
+
cancelAppUpdateDownload,
|
|
7
|
+
checkAndDownloadAppUpdate,
|
|
8
|
+
checkForAppUpdate,
|
|
9
|
+
createAppUpdater,
|
|
10
|
+
createUpdaterConfig,
|
|
11
|
+
createUpdaterState,
|
|
12
|
+
createWebUpdaterBackend,
|
|
13
|
+
detachAppUpdater,
|
|
14
|
+
disposeAppUpdater,
|
|
15
|
+
downloadAppUpdate,
|
|
16
|
+
getAppUpdaterState,
|
|
17
|
+
getUpdaterBackend,
|
|
18
|
+
getUpdaterChannel,
|
|
19
|
+
getUpdaterConfig,
|
|
20
|
+
isAppUpdateEligible,
|
|
21
|
+
quitAndInstallUpdate,
|
|
22
|
+
rollbackAppUpdate,
|
|
23
|
+
setUpdaterBackend,
|
|
24
|
+
setUpdaterChannel,
|
|
25
|
+
setUpdaterConfig,
|
|
26
|
+
setUpdaterFeedUrl,
|
|
27
|
+
setUpdaterSignatureConfig,
|
|
28
|
+
} from './updater';
|
|
29
|
+
|
|
30
|
+
const FULL_UPDATE_INFO: UpdateInfo = {
|
|
31
|
+
deltaFromVersion: null,
|
|
32
|
+
downloadSizeBytes: 10_000_000,
|
|
33
|
+
isMandatory: false,
|
|
34
|
+
minimumOsVersion: null,
|
|
35
|
+
notes: 'fixes',
|
|
36
|
+
releaseDate: '2026-06-19',
|
|
37
|
+
sha512: 'abc123',
|
|
38
|
+
stagedRolloutPercent: 100,
|
|
39
|
+
version: '1.2.3',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
interface FakeUpdaterBackend extends UpdaterBackend {
|
|
43
|
+
feedUrl: string;
|
|
44
|
+
checked: number;
|
|
45
|
+
downloaded: number;
|
|
46
|
+
cancelled: number;
|
|
47
|
+
quit: number;
|
|
48
|
+
rolledBack: number;
|
|
49
|
+
fireChecking: () => void;
|
|
50
|
+
fireUpdateAvailable: (info: Readonly<UpdateInfo>) => void;
|
|
51
|
+
fireUpdateNotAvailable: () => void;
|
|
52
|
+
fireDownloadProgress: (progress: Readonly<UpdateProgress>) => void;
|
|
53
|
+
fireUpdateDownloaded: (info: Readonly<UpdateInfo>) => void;
|
|
54
|
+
fireError: (error: Readonly<UpdaterError>) => void;
|
|
55
|
+
fireUpdateCancelled: () => void;
|
|
56
|
+
fireUpdateStaging: () => void;
|
|
57
|
+
fireUpdateVerified: () => void;
|
|
58
|
+
fireUpdateRolledBack: () => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function fakeBackend(): FakeUpdaterBackend {
|
|
62
|
+
let checking: (() => void) | null = null;
|
|
63
|
+
let updateAvailable: ((info: Readonly<UpdateInfo>) => void) | null = null;
|
|
64
|
+
let updateNotAvailable: (() => void) | null = null;
|
|
65
|
+
let downloadProgress: ((progress: Readonly<UpdateProgress>) => void) | null = null;
|
|
66
|
+
let updateDownloaded: ((info: Readonly<UpdateInfo>) => void) | null = null;
|
|
67
|
+
let error: ((error: Readonly<UpdaterError>) => void) | null = null;
|
|
68
|
+
let updateCancelled: (() => void) | null = null;
|
|
69
|
+
let updateStaging: (() => void) | null = null;
|
|
70
|
+
let updateVerified: (() => void) | null = null;
|
|
71
|
+
let updateRolledBack: (() => void) | null = null;
|
|
72
|
+
let channel = 'stable';
|
|
73
|
+
let config = createUpdaterConfig();
|
|
74
|
+
return {
|
|
75
|
+
feedUrl: '',
|
|
76
|
+
checked: 0,
|
|
77
|
+
downloaded: 0,
|
|
78
|
+
cancelled: 0,
|
|
79
|
+
quit: 0,
|
|
80
|
+
rolledBack: 0,
|
|
81
|
+
cancelDownload() {
|
|
82
|
+
this.cancelled++;
|
|
83
|
+
},
|
|
84
|
+
checkForUpdates() {
|
|
85
|
+
this.checked++;
|
|
86
|
+
},
|
|
87
|
+
downloadUpdate() {
|
|
88
|
+
this.downloaded++;
|
|
89
|
+
},
|
|
90
|
+
getChannel() {
|
|
91
|
+
return channel;
|
|
92
|
+
},
|
|
93
|
+
getConfig() {
|
|
94
|
+
return config;
|
|
95
|
+
},
|
|
96
|
+
quitAndInstall() {
|
|
97
|
+
this.quit++;
|
|
98
|
+
},
|
|
99
|
+
rollback() {
|
|
100
|
+
this.rolledBack++;
|
|
101
|
+
},
|
|
102
|
+
setChannel(c) {
|
|
103
|
+
channel = c;
|
|
104
|
+
},
|
|
105
|
+
setConfig(c) {
|
|
106
|
+
config = { ...c };
|
|
107
|
+
},
|
|
108
|
+
setFeedUrl(url) {
|
|
109
|
+
this.feedUrl = url;
|
|
110
|
+
},
|
|
111
|
+
setSignatureConfig() {},
|
|
112
|
+
subscribeChecking(listener) {
|
|
113
|
+
checking = listener;
|
|
114
|
+
return () => {
|
|
115
|
+
checking = null;
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
subscribeDownloadProgress(listener) {
|
|
119
|
+
downloadProgress = listener;
|
|
120
|
+
return () => {
|
|
121
|
+
downloadProgress = null;
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
subscribeError(listener) {
|
|
125
|
+
error = listener;
|
|
126
|
+
return () => {
|
|
127
|
+
error = null;
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
subscribeUpdateAvailable(listener) {
|
|
131
|
+
updateAvailable = listener;
|
|
132
|
+
return () => {
|
|
133
|
+
updateAvailable = null;
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
subscribeUpdateCancelled(listener) {
|
|
137
|
+
updateCancelled = listener;
|
|
138
|
+
return () => {
|
|
139
|
+
updateCancelled = null;
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
subscribeUpdateDownloaded(listener) {
|
|
143
|
+
updateDownloaded = listener;
|
|
144
|
+
return () => {
|
|
145
|
+
updateDownloaded = null;
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
subscribeUpdateNotAvailable(listener) {
|
|
149
|
+
updateNotAvailable = listener;
|
|
150
|
+
return () => {
|
|
151
|
+
updateNotAvailable = null;
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
subscribeUpdateRolledBack(listener) {
|
|
155
|
+
updateRolledBack = listener;
|
|
156
|
+
return () => {
|
|
157
|
+
updateRolledBack = null;
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
subscribeUpdateStaging(listener) {
|
|
161
|
+
updateStaging = listener;
|
|
162
|
+
return () => {
|
|
163
|
+
updateStaging = null;
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
subscribeUpdateVerified(listener) {
|
|
167
|
+
updateVerified = listener;
|
|
168
|
+
return () => {
|
|
169
|
+
updateVerified = null;
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
fireChecking() {
|
|
173
|
+
checking?.();
|
|
174
|
+
},
|
|
175
|
+
fireUpdateAvailable(info) {
|
|
176
|
+
updateAvailable?.(info);
|
|
177
|
+
},
|
|
178
|
+
fireUpdateNotAvailable() {
|
|
179
|
+
updateNotAvailable?.();
|
|
180
|
+
},
|
|
181
|
+
fireDownloadProgress(progress) {
|
|
182
|
+
downloadProgress?.(progress);
|
|
183
|
+
},
|
|
184
|
+
fireUpdateDownloaded(info) {
|
|
185
|
+
updateDownloaded?.(info);
|
|
186
|
+
},
|
|
187
|
+
fireError(e) {
|
|
188
|
+
error?.(e);
|
|
189
|
+
},
|
|
190
|
+
fireUpdateCancelled() {
|
|
191
|
+
updateCancelled?.();
|
|
192
|
+
},
|
|
193
|
+
fireUpdateStaging() {
|
|
194
|
+
updateStaging?.();
|
|
195
|
+
},
|
|
196
|
+
fireUpdateVerified() {
|
|
197
|
+
updateVerified?.();
|
|
198
|
+
},
|
|
199
|
+
fireUpdateRolledBack() {
|
|
200
|
+
updateRolledBack?.();
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
afterEach(() => setUpdaterBackend(null));
|
|
206
|
+
|
|
207
|
+
describe('attachAppUpdater', () => {
|
|
208
|
+
it('is idempotent — re-attach tears down the prior subscription first', () => {
|
|
209
|
+
const backend = fakeBackend();
|
|
210
|
+
setUpdaterBackend(backend);
|
|
211
|
+
const updater = createAppUpdater();
|
|
212
|
+
let checking = 0;
|
|
213
|
+
connectSignal(updater.onChecking, () => checking++);
|
|
214
|
+
attachAppUpdater(updater);
|
|
215
|
+
attachAppUpdater(updater); // second attach
|
|
216
|
+
backend.fireChecking();
|
|
217
|
+
// Only one subscription should be active, so checking fires once
|
|
218
|
+
expect(checking).toBe(1);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('is safe when not previously attached', () => {
|
|
222
|
+
const backend = fakeBackend();
|
|
223
|
+
setUpdaterBackend(backend);
|
|
224
|
+
const updater = createAppUpdater();
|
|
225
|
+
expect(() => attachAppUpdater(updater)).not.toThrow();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('wires all ten signals and updates state for each', () => {
|
|
229
|
+
const backend = fakeBackend();
|
|
230
|
+
setUpdaterBackend(backend);
|
|
231
|
+
const updater = createAppUpdater();
|
|
232
|
+
let checking = 0;
|
|
233
|
+
let notAvailable = 0;
|
|
234
|
+
let availableInfo: Readonly<UpdateInfo> | null = null;
|
|
235
|
+
let downloadedInfo: Readonly<UpdateInfo> | null = null;
|
|
236
|
+
let progress: Readonly<UpdateProgress> | null = null;
|
|
237
|
+
let receivedError: Readonly<UpdaterError> | null = null;
|
|
238
|
+
let cancelled = 0;
|
|
239
|
+
let staging = 0;
|
|
240
|
+
let verified = 0;
|
|
241
|
+
let rolledBack = 0;
|
|
242
|
+
|
|
243
|
+
connectSignal(updater.onChecking, () => checking++);
|
|
244
|
+
connectSignal(updater.onUpdateNotAvailable, () => notAvailable++);
|
|
245
|
+
connectSignal(updater.onUpdateAvailable, (info) => (availableInfo = info));
|
|
246
|
+
connectSignal(updater.onDownloadProgress, (p) => (progress = p));
|
|
247
|
+
connectSignal(updater.onUpdateDownloaded, (info) => (downloadedInfo = info));
|
|
248
|
+
connectSignal(updater.onError, (e) => (receivedError = e));
|
|
249
|
+
connectSignal(updater.onUpdateCancelled, () => cancelled++);
|
|
250
|
+
connectSignal(updater.onUpdateStaging, () => staging++);
|
|
251
|
+
connectSignal(updater.onUpdateVerified, () => verified++);
|
|
252
|
+
connectSignal(updater.onUpdateRolledBack, () => rolledBack++);
|
|
253
|
+
attachAppUpdater(updater);
|
|
254
|
+
|
|
255
|
+
const prog: UpdateProgress = {
|
|
256
|
+
bytesPerSecond: 1000,
|
|
257
|
+
isDelta: false,
|
|
258
|
+
percent: 42,
|
|
259
|
+
totalBytes: 100,
|
|
260
|
+
transferredBytes: 42,
|
|
261
|
+
};
|
|
262
|
+
const err: UpdaterError = { kind: 'Network', message: 'timeout' };
|
|
263
|
+
backend.fireChecking();
|
|
264
|
+
backend.fireUpdateAvailable(FULL_UPDATE_INFO);
|
|
265
|
+
backend.fireUpdateNotAvailable();
|
|
266
|
+
backend.fireDownloadProgress(prog);
|
|
267
|
+
backend.fireUpdateDownloaded(FULL_UPDATE_INFO);
|
|
268
|
+
backend.fireError(err);
|
|
269
|
+
backend.fireUpdateCancelled();
|
|
270
|
+
backend.fireUpdateStaging();
|
|
271
|
+
backend.fireUpdateVerified();
|
|
272
|
+
backend.fireUpdateRolledBack();
|
|
273
|
+
|
|
274
|
+
expect(checking).toBe(1);
|
|
275
|
+
expect(notAvailable).toBe(1);
|
|
276
|
+
expect(availableInfo).toEqual(FULL_UPDATE_INFO);
|
|
277
|
+
expect(downloadedInfo).toEqual(FULL_UPDATE_INFO);
|
|
278
|
+
expect(progress).toEqual(prog);
|
|
279
|
+
expect(receivedError).toEqual(err);
|
|
280
|
+
expect(cancelled).toBe(1);
|
|
281
|
+
expect(staging).toBe(1);
|
|
282
|
+
expect(verified).toBe(1);
|
|
283
|
+
expect(rolledBack).toBe(1);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('updates state machine phases in order', () => {
|
|
287
|
+
const backend = fakeBackend();
|
|
288
|
+
setUpdaterBackend(backend);
|
|
289
|
+
const updater = createAppUpdater();
|
|
290
|
+
attachAppUpdater(updater);
|
|
291
|
+
|
|
292
|
+
expect(getAppUpdaterState(updater).phase).toBe('Idle');
|
|
293
|
+
|
|
294
|
+
backend.fireChecking();
|
|
295
|
+
expect(getAppUpdaterState(updater).phase).toBe('Checking');
|
|
296
|
+
|
|
297
|
+
backend.fireUpdateAvailable(FULL_UPDATE_INFO);
|
|
298
|
+
expect(getAppUpdaterState(updater).phase).toBe('UpdateAvailable');
|
|
299
|
+
expect(getAppUpdaterState(updater).info).toEqual(FULL_UPDATE_INFO);
|
|
300
|
+
|
|
301
|
+
const prog: UpdateProgress = {
|
|
302
|
+
bytesPerSecond: 500,
|
|
303
|
+
isDelta: false,
|
|
304
|
+
percent: 50,
|
|
305
|
+
totalBytes: 200,
|
|
306
|
+
transferredBytes: 100,
|
|
307
|
+
};
|
|
308
|
+
backend.fireDownloadProgress(prog);
|
|
309
|
+
expect(getAppUpdaterState(updater).phase).toBe('Downloading');
|
|
310
|
+
expect(getAppUpdaterState(updater).progress).toEqual(prog);
|
|
311
|
+
|
|
312
|
+
backend.fireUpdateDownloaded(FULL_UPDATE_INFO);
|
|
313
|
+
expect(getAppUpdaterState(updater).phase).toBe('Downloaded');
|
|
314
|
+
|
|
315
|
+
backend.fireUpdateStaging();
|
|
316
|
+
expect(getAppUpdaterState(updater).phase).toBe('Staging');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('transitions to Error phase when backend fires error', () => {
|
|
320
|
+
const backend = fakeBackend();
|
|
321
|
+
setUpdaterBackend(backend);
|
|
322
|
+
const updater = createAppUpdater();
|
|
323
|
+
attachAppUpdater(updater);
|
|
324
|
+
|
|
325
|
+
const err: UpdaterError = { kind: 'Signature', message: 'bad sig' };
|
|
326
|
+
backend.fireError(err);
|
|
327
|
+
expect(getAppUpdaterState(updater).phase).toBe('Error');
|
|
328
|
+
expect(getAppUpdaterState(updater).error).toEqual(err);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('resets to Idle on cancel and rollback', () => {
|
|
332
|
+
const backend = fakeBackend();
|
|
333
|
+
setUpdaterBackend(backend);
|
|
334
|
+
const updater = createAppUpdater();
|
|
335
|
+
attachAppUpdater(updater);
|
|
336
|
+
|
|
337
|
+
backend.fireChecking();
|
|
338
|
+
backend.fireUpdateCancelled();
|
|
339
|
+
expect(getAppUpdaterState(updater).phase).toBe('Idle');
|
|
340
|
+
|
|
341
|
+
backend.fireChecking();
|
|
342
|
+
backend.fireUpdateRolledBack();
|
|
343
|
+
expect(getAppUpdaterState(updater).phase).toBe('Idle');
|
|
344
|
+
expect(getAppUpdaterState(updater).info).toBeNull();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe('cancelAppUpdateDownload', () => {
|
|
349
|
+
it('delegates to the active backend', () => {
|
|
350
|
+
const backend = fakeBackend();
|
|
351
|
+
setUpdaterBackend(backend);
|
|
352
|
+
cancelAppUpdateDownload();
|
|
353
|
+
expect(backend.cancelled).toBe(1);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe('checkAndDownloadAppUpdate', () => {
|
|
358
|
+
it('calls checkForUpdates; also calls downloadUpdate when autoDownload is true', () => {
|
|
359
|
+
const backend = fakeBackend();
|
|
360
|
+
setUpdaterBackend(backend);
|
|
361
|
+
backend.setConfig({ allowPrerelease: false, autoDownload: true, autoInstallOnAppQuit: false });
|
|
362
|
+
checkAndDownloadAppUpdate();
|
|
363
|
+
expect(backend.checked).toBe(1);
|
|
364
|
+
expect(backend.downloaded).toBe(1);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('only checks when autoDownload is false', () => {
|
|
368
|
+
const backend = fakeBackend();
|
|
369
|
+
setUpdaterBackend(backend);
|
|
370
|
+
backend.setConfig({ allowPrerelease: false, autoDownload: false, autoInstallOnAppQuit: false });
|
|
371
|
+
checkAndDownloadAppUpdate();
|
|
372
|
+
expect(backend.checked).toBe(1);
|
|
373
|
+
expect(backend.downloaded).toBe(0);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('downloads immediately under autoDownload without gating on update-available', () => {
|
|
377
|
+
const backend = fakeBackend();
|
|
378
|
+
setUpdaterBackend(backend);
|
|
379
|
+
backend.setConfig({ allowPrerelease: false, autoDownload: true, autoInstallOnAppQuit: false });
|
|
380
|
+
checkAndDownloadAppUpdate();
|
|
381
|
+
// The download fires in the same call as the check; it does not wait for fireUpdateAvailable.
|
|
382
|
+
// This pins the documented fire-both convenience for instant-trigger backends.
|
|
383
|
+
expect(backend.downloaded).toBe(1);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
describe('checkForAppUpdate', () => {
|
|
388
|
+
it('delegates to the active backend', () => {
|
|
389
|
+
const backend = fakeBackend();
|
|
390
|
+
setUpdaterBackend(backend);
|
|
391
|
+
checkForAppUpdate();
|
|
392
|
+
expect(backend.checked).toBe(1);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe('createAppUpdater', () => {
|
|
397
|
+
it('creates an entity with all ten signals', () => {
|
|
398
|
+
const updater = createAppUpdater();
|
|
399
|
+
expect(updater.onChecking).toBeDefined();
|
|
400
|
+
expect(updater.onUpdateAvailable).toBeDefined();
|
|
401
|
+
expect(updater.onUpdateNotAvailable).toBeDefined();
|
|
402
|
+
expect(updater.onDownloadProgress).toBeDefined();
|
|
403
|
+
expect(updater.onUpdateDownloaded).toBeDefined();
|
|
404
|
+
expect(updater.onError).toBeDefined();
|
|
405
|
+
expect(updater.onUpdateCancelled).toBeDefined();
|
|
406
|
+
expect(updater.onUpdateStaging).toBeDefined();
|
|
407
|
+
expect(updater.onUpdateVerified).toBeDefined();
|
|
408
|
+
expect(updater.onUpdateRolledBack).toBeDefined();
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('starts in Idle phase with all payloads null', () => {
|
|
412
|
+
const updater = createAppUpdater();
|
|
413
|
+
const state = getAppUpdaterState(updater);
|
|
414
|
+
expect(state.phase).toBe('Idle');
|
|
415
|
+
expect(state.info).toBeNull();
|
|
416
|
+
expect(state.progress).toBeNull();
|
|
417
|
+
expect(state.error).toBeNull();
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe('createUpdaterConfig', () => {
|
|
422
|
+
it('returns safe defaults', () => {
|
|
423
|
+
const config = createUpdaterConfig();
|
|
424
|
+
expect(config.autoDownload).toBe(false);
|
|
425
|
+
expect(config.autoInstallOnAppQuit).toBe(false);
|
|
426
|
+
expect(config.allowPrerelease).toBe(false);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
describe('createUpdaterState', () => {
|
|
431
|
+
it('returns Idle phase with null payloads', () => {
|
|
432
|
+
const state = createUpdaterState();
|
|
433
|
+
expect(state.phase).toBe('Idle');
|
|
434
|
+
expect(state.info).toBeNull();
|
|
435
|
+
expect(state.progress).toBeNull();
|
|
436
|
+
expect(state.error).toBeNull();
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
describe('createWebUpdaterBackend', () => {
|
|
441
|
+
it('no-ops commands and returns inert unsubscribes without throwing', () => {
|
|
442
|
+
const backend = createWebUpdaterBackend();
|
|
443
|
+
expect(() => {
|
|
444
|
+
backend.setFeedUrl('https://example.com/feed');
|
|
445
|
+
backend.checkForUpdates();
|
|
446
|
+
backend.downloadUpdate();
|
|
447
|
+
backend.cancelDownload();
|
|
448
|
+
backend.quitAndInstall();
|
|
449
|
+
backend.rollback();
|
|
450
|
+
backend.setSignatureConfig(null);
|
|
451
|
+
backend.setSignatureConfig({ algorithm: 'ed25519', publicKey: 'abc' });
|
|
452
|
+
}).not.toThrow();
|
|
453
|
+
expect(typeof backend.subscribeChecking(() => {})).toBe('function');
|
|
454
|
+
expect(typeof backend.subscribeError(() => {})).toBe('function');
|
|
455
|
+
expect(typeof backend.subscribeUpdateCancelled(() => {})).toBe('function');
|
|
456
|
+
expect(typeof backend.subscribeUpdateStaging(() => {})).toBe('function');
|
|
457
|
+
expect(typeof backend.subscribeUpdateVerified(() => {})).toBe('function');
|
|
458
|
+
expect(typeof backend.subscribeUpdateRolledBack(() => {})).toBe('function');
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('stores and returns channel', () => {
|
|
462
|
+
const backend = createWebUpdaterBackend();
|
|
463
|
+
expect(backend.getChannel()).toBe('stable');
|
|
464
|
+
backend.setChannel('beta');
|
|
465
|
+
expect(backend.getChannel()).toBe('beta');
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('stores and returns config', () => {
|
|
469
|
+
const backend = createWebUpdaterBackend();
|
|
470
|
+
const config = createUpdaterConfig();
|
|
471
|
+
expect(backend.getConfig()).toEqual(config);
|
|
472
|
+
backend.setConfig({ ...config, autoDownload: true });
|
|
473
|
+
expect(backend.getConfig().autoDownload).toBe(true);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('returns idle state for every query', () => {
|
|
477
|
+
const backend = createWebUpdaterBackend();
|
|
478
|
+
expect(backend.getChannel()).toBe('stable');
|
|
479
|
+
const config = backend.getConfig();
|
|
480
|
+
expect(config.autoDownload).toBe(false);
|
|
481
|
+
expect(config.autoInstallOnAppQuit).toBe(false);
|
|
482
|
+
expect(config.allowPrerelease).toBe(false);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
describe('detachAppUpdater', () => {
|
|
487
|
+
it('stops further delivery', () => {
|
|
488
|
+
const backend = fakeBackend();
|
|
489
|
+
setUpdaterBackend(backend);
|
|
490
|
+
const updater = createAppUpdater();
|
|
491
|
+
let checking = 0;
|
|
492
|
+
connectSignal(updater.onChecking, () => checking++);
|
|
493
|
+
attachAppUpdater(updater);
|
|
494
|
+
detachAppUpdater(updater);
|
|
495
|
+
backend.fireChecking();
|
|
496
|
+
expect(checking).toBe(0);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('is safe when not attached', () => {
|
|
500
|
+
const updater = createAppUpdater();
|
|
501
|
+
expect(() => detachAppUpdater(updater)).not.toThrow();
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
describe('disposeAppUpdater', () => {
|
|
506
|
+
it('detaches the subscription', () => {
|
|
507
|
+
const backend = fakeBackend();
|
|
508
|
+
setUpdaterBackend(backend);
|
|
509
|
+
const updater = createAppUpdater();
|
|
510
|
+
attachAppUpdater(updater);
|
|
511
|
+
expect(() => disposeAppUpdater(updater)).not.toThrow();
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
describe('downloadAppUpdate', () => {
|
|
516
|
+
it('delegates to the active backend', () => {
|
|
517
|
+
const backend = fakeBackend();
|
|
518
|
+
setUpdaterBackend(backend);
|
|
519
|
+
downloadAppUpdate();
|
|
520
|
+
expect(backend.downloaded).toBe(1);
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
describe('getAppUpdaterState', () => {
|
|
525
|
+
it('returns Idle state for an updater that has never been attached', () => {
|
|
526
|
+
const updater = createAppUpdater();
|
|
527
|
+
const state = getAppUpdaterState(updater);
|
|
528
|
+
expect(state.phase).toBe('Idle');
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('is per-entity — two updaters have independent states', () => {
|
|
532
|
+
const backend = fakeBackend();
|
|
533
|
+
setUpdaterBackend(backend);
|
|
534
|
+
const a = createAppUpdater();
|
|
535
|
+
const b = createAppUpdater();
|
|
536
|
+
attachAppUpdater(a);
|
|
537
|
+
backend.fireChecking();
|
|
538
|
+
expect(getAppUpdaterState(a).phase).toBe('Checking');
|
|
539
|
+
expect(getAppUpdaterState(b).phase).toBe('Idle');
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
describe('getUpdaterBackend', () => {
|
|
544
|
+
it('falls back to a web backend', () => {
|
|
545
|
+
expect(getUpdaterBackend()).not.toBeNull();
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
describe('getUpdaterChannel', () => {
|
|
550
|
+
it('returns the channel from the active backend', () => {
|
|
551
|
+
const backend = fakeBackend();
|
|
552
|
+
setUpdaterBackend(backend);
|
|
553
|
+
backend.setChannel('beta');
|
|
554
|
+
expect(getUpdaterChannel()).toBe('beta');
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
describe('getUpdaterConfig', () => {
|
|
559
|
+
it('returns the config from the active backend', () => {
|
|
560
|
+
const backend = fakeBackend();
|
|
561
|
+
setUpdaterBackend(backend);
|
|
562
|
+
expect(getUpdaterConfig().autoDownload).toBe(false);
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
describe('isAppUpdateEligible', () => {
|
|
567
|
+
it('returns true when seed falls within stagedRolloutPercent', () => {
|
|
568
|
+
const info: Readonly<UpdateInfo> = { ...FULL_UPDATE_INFO, stagedRolloutPercent: 50 };
|
|
569
|
+
expect(isAppUpdateEligible(info, 0.4)).toBe(true); // 0.4 * 100 = 40 < 50
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it('returns false when seed is at or above stagedRolloutPercent', () => {
|
|
573
|
+
const info: Readonly<UpdateInfo> = { ...FULL_UPDATE_INFO, stagedRolloutPercent: 50 };
|
|
574
|
+
expect(isAppUpdateEligible(info, 0.5)).toBe(false); // 0.5 * 100 = 50, not < 50
|
|
575
|
+
expect(isAppUpdateEligible(info, 0.9)).toBe(false);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('always eligible when stagedRolloutPercent is 100', () => {
|
|
579
|
+
const info: Readonly<UpdateInfo> = { ...FULL_UPDATE_INFO, stagedRolloutPercent: 100 };
|
|
580
|
+
expect(isAppUpdateEligible(info, 0.99)).toBe(true);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('never eligible when stagedRolloutPercent is 0', () => {
|
|
584
|
+
const info: Readonly<UpdateInfo> = { ...FULL_UPDATE_INFO, stagedRolloutPercent: 0 };
|
|
585
|
+
expect(isAppUpdateEligible(info, 0)).toBe(false);
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
describe('quitAndInstallUpdate', () => {
|
|
590
|
+
it('delegates to the active backend', () => {
|
|
591
|
+
const backend = fakeBackend();
|
|
592
|
+
setUpdaterBackend(backend);
|
|
593
|
+
quitAndInstallUpdate();
|
|
594
|
+
expect(backend.quit).toBe(1);
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
describe('rollbackAppUpdate', () => {
|
|
599
|
+
it('delegates to the active backend', () => {
|
|
600
|
+
const backend = fakeBackend();
|
|
601
|
+
setUpdaterBackend(backend);
|
|
602
|
+
rollbackAppUpdate();
|
|
603
|
+
expect(backend.rolledBack).toBe(1);
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
describe('setUpdaterBackend', () => {
|
|
608
|
+
it('clears back to the web fallback when passed null', () => {
|
|
609
|
+
setUpdaterBackend(fakeBackend());
|
|
610
|
+
setUpdaterBackend(null);
|
|
611
|
+
expect(getUpdaterBackend()).not.toBeNull();
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
describe('setUpdaterChannel', () => {
|
|
616
|
+
it('sets the channel on the active backend', () => {
|
|
617
|
+
const backend = fakeBackend();
|
|
618
|
+
setUpdaterBackend(backend);
|
|
619
|
+
setUpdaterChannel('alpha');
|
|
620
|
+
expect(backend.getChannel()).toBe('alpha');
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
describe('setUpdaterConfig', () => {
|
|
625
|
+
it('forwards the config to the active backend', () => {
|
|
626
|
+
const backend = fakeBackend();
|
|
627
|
+
setUpdaterBackend(backend);
|
|
628
|
+
setUpdaterConfig({ allowPrerelease: true, autoDownload: true, autoInstallOnAppQuit: true });
|
|
629
|
+
const config = backend.getConfig();
|
|
630
|
+
expect(config.allowPrerelease).toBe(true);
|
|
631
|
+
expect(config.autoDownload).toBe(true);
|
|
632
|
+
expect(config.autoInstallOnAppQuit).toBe(true);
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
describe('setUpdaterFeedUrl', () => {
|
|
637
|
+
it('forwards the URL to the active backend', () => {
|
|
638
|
+
const backend = fakeBackend();
|
|
639
|
+
setUpdaterBackend(backend);
|
|
640
|
+
setUpdaterFeedUrl('https://example.com/feed');
|
|
641
|
+
expect(backend.feedUrl).toBe('https://example.com/feed');
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
describe('setUpdaterSignatureConfig', () => {
|
|
646
|
+
it('forwards the config to the active backend without throwing', () => {
|
|
647
|
+
const backend = fakeBackend();
|
|
648
|
+
setUpdaterBackend(backend);
|
|
649
|
+
expect(() => setUpdaterSignatureConfig({ algorithm: 'ed25519', publicKey: 'pubkey' })).not.toThrow();
|
|
650
|
+
expect(() => setUpdaterSignatureConfig(null)).not.toThrow();
|
|
651
|
+
});
|
|
652
|
+
});
|