@sigx/lynx-updates 0.6.1
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/LICENSE +21 -0
- package/README.md +162 -0
- package/android/com/sigx/updates/UpdateDownloader.kt +154 -0
- package/android/com/sigx/updates/UpdateStore.kt +367 -0
- package/android/com/sigx/updates/UpdatesActivityHook.kt +25 -0
- package/android/com/sigx/updates/UpdatesBundleResolver.kt +18 -0
- package/android/com/sigx/updates/UpdatesEventBus.kt +54 -0
- package/android/com/sigx/updates/UpdatesLifecyclePublisher.kt +42 -0
- package/android/com/sigx/updates/UpdatesModule.kt +235 -0
- package/dist/controller.d.ts +31 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +344 -0
- package/dist/controller.js.map +1 -0
- package/dist/events.d.ts +18 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +61 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/native.d.ts +41 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +161 -0
- package/dist/native.js.map +1 -0
- package/dist/provider/static-manifest.d.ts +66 -0
- package/dist/provider/static-manifest.d.ts.map +1 -0
- package/dist/provider/static-manifest.js +173 -0
- package/dist/provider/static-manifest.js.map +1 -0
- package/dist/state.d.ts +23 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +73 -0
- package/dist/state.js.map +1 -0
- package/dist/types.d.ts +203 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/updates.d.ts +45 -0
- package/dist/updates.d.ts.map +1 -0
- package/dist/updates.js +67 -0
- package/dist/updates.js.map +1 -0
- package/dist/use-updates.d.ts +16 -0
- package/dist/use-updates.d.ts.map +1 -0
- package/dist/use-updates.js +29 -0
- package/dist/use-updates.js.map +1 -0
- package/ios/UpdateDownloader.swift +152 -0
- package/ios/UpdateStore.swift +286 -0
- package/ios/UpdatesBundleResolver.swift +15 -0
- package/ios/UpdatesEventBus.swift +59 -0
- package/ios/UpdatesLifecyclePublisher.swift +48 -0
- package/ios/UpdatesModule.swift +178 -0
- package/package.json +59 -0
- package/signalx-module.json +35 -0
package/dist/events.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native → JS event channel for the Updates module (same GlobalEventEmitter
|
|
3
|
+
* pattern as `@sigx/lynx-background`'s `__sigxBackgroundFire`).
|
|
4
|
+
*
|
|
5
|
+
* Wire shape:
|
|
6
|
+
* { kind: 'progress', receivedBytes: number, totalBytes: number | null }
|
|
7
|
+
* { kind: 'foreground' }
|
|
8
|
+
*/
|
|
9
|
+
const EVENT_CHANNEL = '__sigxUpdatesEvent';
|
|
10
|
+
function emitter() {
|
|
11
|
+
if (typeof lynx === 'undefined')
|
|
12
|
+
return undefined;
|
|
13
|
+
return lynx.getJSModule?.('GlobalEventEmitter');
|
|
14
|
+
}
|
|
15
|
+
function safeParse(s) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(s);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function normalize(raw) {
|
|
24
|
+
const event = (typeof raw === 'string' ? safeParse(raw) : raw);
|
|
25
|
+
if (!event || typeof event !== 'object')
|
|
26
|
+
return undefined;
|
|
27
|
+
if (event.kind === 'foreground')
|
|
28
|
+
return { kind: 'foreground' };
|
|
29
|
+
if (event.kind === 'progress' && typeof event.receivedBytes === 'number') {
|
|
30
|
+
return {
|
|
31
|
+
kind: 'progress',
|
|
32
|
+
receivedBytes: event.receivedBytes,
|
|
33
|
+
totalBytes: typeof event.totalBytes === 'number' && event.totalBytes >= 0
|
|
34
|
+
? event.totalBytes
|
|
35
|
+
: null,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
export function addNativeUpdatesListener(cb) {
|
|
41
|
+
const e = emitter();
|
|
42
|
+
if (!e) {
|
|
43
|
+
// Web / test fallback — no native bridge; real delivery is on-device.
|
|
44
|
+
return () => { };
|
|
45
|
+
}
|
|
46
|
+
const wrapped = (raw) => {
|
|
47
|
+
const event = normalize(raw);
|
|
48
|
+
if (!event)
|
|
49
|
+
return;
|
|
50
|
+
try {
|
|
51
|
+
cb(event);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.warn(`[updates] listener for ${EVENT_CHANNEL} threw:`, err);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
e.addListener(EVENT_CHANNEL, wrapped);
|
|
58
|
+
return () => e.removeListener(EVENT_CHANNEL, wrapped);
|
|
59
|
+
}
|
|
60
|
+
export const __EVENT_CHANNEL_FOR_TESTS = EVENT_CHANNEL;
|
|
61
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAiB3C,SAAS,OAAO;IACZ,IAAI,OAAO,IAAI,KAAK,WAAW;QAAE,OAAO,SAAS,CAAC;IAClD,OAAQ,IAAiB,CAAC,WAAW,EAAE,CAAC,oBAAoB,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,SAAS,CAAC,CAAS;IACxB,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,SAAS,CAAC;IAAC,CAAC;AAC7D,CAAC;AAED,SAAS,SAAS,CAAC,GAAY;IAC3B,MAAM,KAAK,GAAG,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAE9C,CAAC;IAChB,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC1D,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAC/D,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC,aAAa,KAAK,QAAQ,EAAE,CAAC;QACvE,OAAO;YACH,IAAI,EAAE,UAAU;YAChB,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,UAAU,EAAE,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,IAAI,CAAC;gBACrE,CAAC,CAAC,KAAK,CAAC,UAAU;gBAClB,CAAC,CAAC,IAAI;SACb,CAAC;IACN,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,wBAAwB,CACpC,EAAuC;IAEvC,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IACpB,IAAI,CAAC,CAAC,EAAE,CAAC;QACL,sEAAsE;QACtE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IACpB,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,GAAY,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,CAAC;YACD,EAAE,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,0BAA0B,aAAa,SAAS,EAAE,GAAG,CAAC,CAAC;QACxE,CAAC;IACL,CAAC,CAAC;IACF,CAAC,CAAC,WAAW,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACtC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,CAAC,MAAM,yBAAyB,GAAG,aAAa,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Updates } from './updates.js';
|
|
2
|
+
export { useUpdates } from './use-updates.js';
|
|
3
|
+
export { StaticManifestProvider, validateUpdatesManifest, type StaticManifestDocument, type StaticManifestEntry, type StaticManifestProviderOptions, } from './provider/static-manifest.js';
|
|
4
|
+
export { UpdatesError, type UpdatesErrorCode, type CurrentUpdateInfo, type DownloadProgress, type DownloadSpec, type UpdateCheckContext, type UpdateCheckResult, type UpdateManifest, type UpdateMode, type UpdatePlatform, type UpdateProvider, type UpdatesConfig, type UpdatesEvent, type UpdatesState, type UpdateStatus, } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACH,sBAAsB,EACtB,uBAAuB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,6BAA6B,GACrC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACH,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,YAAY,GACpB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACH,sBAAsB,EACtB,uBAAuB,GAI1B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACH,YAAY,GAef,MAAM,YAAY,CAAC"}
|
package/dist/native.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS side of the `Updates` native module — thin promise wrappers over the
|
|
3
|
+
* callback-based bridge, plus the build-time defines baked by
|
|
4
|
+
* `@sigx/lynx-plugin`.
|
|
5
|
+
*
|
|
6
|
+
* Every wrapper degrades gracefully when the module is absent (web preview,
|
|
7
|
+
* tests, app built without the package): reads return inert defaults,
|
|
8
|
+
* mutations reject with `native-unavailable`.
|
|
9
|
+
*/
|
|
10
|
+
import { type CurrentUpdateInfo, type DownloadSpec } from './types.js';
|
|
11
|
+
export declare function nativeAvailable(): boolean;
|
|
12
|
+
/** Default channel baked at build time ('production' when unset). */
|
|
13
|
+
export declare function bakedChannel(): string;
|
|
14
|
+
/**
|
|
15
|
+
* The installed binary's runtime fingerprint. Native (BuildConfig /
|
|
16
|
+
* Info.plist) is authoritative; the build define is an informational
|
|
17
|
+
* fallback for environments without the module (web preview).
|
|
18
|
+
*/
|
|
19
|
+
export declare function getInstalledRuntimeVersion(): string;
|
|
20
|
+
export declare function getPlatform(): 'android' | 'ios';
|
|
21
|
+
export declare function getCurrentUpdate(): Promise<CurrentUpdateInfo>;
|
|
22
|
+
/**
|
|
23
|
+
* Stream the bundle to the native staging slot and verify its SHA-256.
|
|
24
|
+
* Resolves when the update is fully staged for apply; progress arrives on
|
|
25
|
+
* the `__sigxUpdatesEvent` channel (see `events.ts`).
|
|
26
|
+
*/
|
|
27
|
+
export declare function nativeDownload(spec: DownloadSpec, updateId: string, runtimeVersion: string, manifestJson: string): Promise<void>;
|
|
28
|
+
/** Stage the downloaded update to load on the next cold launch. */
|
|
29
|
+
export declare function nativeApplyOnNextLaunch(updateId: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Stage + reload the LynxView in place. On success the JS context is torn
|
|
32
|
+
* down, so the returned promise only ever settles on FAILURE.
|
|
33
|
+
*/
|
|
34
|
+
export declare function nativeApplyNow(updateId: string): Promise<void>;
|
|
35
|
+
/** Commit the pending update as healthy (idempotent). */
|
|
36
|
+
export declare function nativeMarkReady(): Promise<void>;
|
|
37
|
+
/** Configure rollback tuning for subsequent launches. */
|
|
38
|
+
export declare function nativeSetRollbackOptions(maxFailedLaunches: number): Promise<void>;
|
|
39
|
+
/** Drop every downloaded update; the baked bundle loads on next launch. */
|
|
40
|
+
export declare function nativeClearUpdates(): Promise<void>;
|
|
41
|
+
//# sourceMappingURL=native.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAgB,KAAK,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAWrF,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED,qEAAqE;AACrE,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CASnD;AAED,wBAAgB,WAAW,IAAI,SAAS,GAAG,KAAK,CAM/C;AAyCD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAyBnE;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAChC,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,mEAAmE;AACnE,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM7E;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMpE;AAED,yDAAyD;AACzD,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAIrD;AAED,yDAAyD;AACzD,wBAAsB,wBAAwB,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvF;AAED,2EAA2E;AAC3E,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAIxD"}
|
package/dist/native.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS side of the `Updates` native module — thin promise wrappers over the
|
|
3
|
+
* callback-based bridge, plus the build-time defines baked by
|
|
4
|
+
* `@sigx/lynx-plugin`.
|
|
5
|
+
*
|
|
6
|
+
* Every wrapper degrades gracefully when the module is absent (web preview,
|
|
7
|
+
* tests, app built without the package): reads return inert defaults,
|
|
8
|
+
* mutations reject with `native-unavailable`.
|
|
9
|
+
*/
|
|
10
|
+
import { callAsync, callSync, isModuleAvailable } from '@sigx/lynx-core';
|
|
11
|
+
import { UpdatesError } from './types.js';
|
|
12
|
+
const MODULE = 'Updates';
|
|
13
|
+
export function nativeAvailable() {
|
|
14
|
+
return isModuleAvailable(MODULE);
|
|
15
|
+
}
|
|
16
|
+
/** Default channel baked at build time ('production' when unset). */
|
|
17
|
+
export function bakedChannel() {
|
|
18
|
+
return typeof __SIGX_UPDATES_CHANNEL__ === 'string' ? __SIGX_UPDATES_CHANNEL__ : 'production';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The installed binary's runtime fingerprint. Native (BuildConfig /
|
|
22
|
+
* Info.plist) is authoritative; the build define is an informational
|
|
23
|
+
* fallback for environments without the module (web preview).
|
|
24
|
+
*/
|
|
25
|
+
export function getInstalledRuntimeVersion() {
|
|
26
|
+
if (nativeAvailable()) {
|
|
27
|
+
const v = callSync(MODULE, 'getInstalledRuntimeVersion');
|
|
28
|
+
if (typeof v === 'string' && v.length > 0)
|
|
29
|
+
return v;
|
|
30
|
+
}
|
|
31
|
+
if (typeof __SIGX_RUNTIME_VERSIONS__ === 'object' && __SIGX_RUNTIME_VERSIONS__) {
|
|
32
|
+
return __SIGX_RUNTIME_VERSIONS__.android ?? __SIGX_RUNTIME_VERSIONS__.ios ?? 'unknown';
|
|
33
|
+
}
|
|
34
|
+
return 'unknown';
|
|
35
|
+
}
|
|
36
|
+
export function getPlatform() {
|
|
37
|
+
if (nativeAvailable()) {
|
|
38
|
+
const p = callSync(MODULE, 'getPlatform');
|
|
39
|
+
if (p === 'android' || p === 'ios')
|
|
40
|
+
return p;
|
|
41
|
+
}
|
|
42
|
+
return 'android';
|
|
43
|
+
}
|
|
44
|
+
const EMBEDDED_INFO = {
|
|
45
|
+
updateId: null,
|
|
46
|
+
version: '',
|
|
47
|
+
embeddedVersion: '',
|
|
48
|
+
runtimeVersion: 'unknown',
|
|
49
|
+
isEmbedded: true,
|
|
50
|
+
isFirstLaunchAfterUpdate: false,
|
|
51
|
+
didRollBack: false,
|
|
52
|
+
rolledBackUpdateId: null,
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Normalize native-side codes (E_* prefixed) to the public
|
|
56
|
+
* {@link UpdatesErrorCode} union so consumers never see an out-of-contract
|
|
57
|
+
* code. Unknown codes collapse to the call site's fallback.
|
|
58
|
+
*/
|
|
59
|
+
function normalizeCode(raw, fallback) {
|
|
60
|
+
switch (raw) {
|
|
61
|
+
case 'E_DOWNLOAD_IN_PROGRESS': return 'download-in-progress';
|
|
62
|
+
case 'E_RUNTIME_MISMATCH': return 'runtime-mismatch';
|
|
63
|
+
case 'E_NO_VIEW': return 'no-view';
|
|
64
|
+
case 'hash-mismatch': return 'hash-mismatch';
|
|
65
|
+
case 'apply-failed': return 'apply-failed';
|
|
66
|
+
default: return fallback;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function throwIfNativeError(result, code) {
|
|
70
|
+
const err = result;
|
|
71
|
+
if (err && typeof err === 'object' && typeof err.error === 'string') {
|
|
72
|
+
throw new UpdatesError(normalizeCode(err.code, code), err.error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export async function getCurrentUpdate() {
|
|
76
|
+
if (!nativeAvailable())
|
|
77
|
+
return EMBEDDED_INFO;
|
|
78
|
+
const raw = await callAsync(MODULE, 'getCurrentUpdate');
|
|
79
|
+
// Reads surface native failures too — callAsync resolves error maps.
|
|
80
|
+
throwIfNativeError(raw, 'native-error');
|
|
81
|
+
if (!raw || typeof raw !== 'object')
|
|
82
|
+
return EMBEDDED_INFO;
|
|
83
|
+
const embeddedVersion = typeof raw.embeddedVersion === 'string' ? raw.embeddedVersion : '';
|
|
84
|
+
const isEmbedded = raw.isEmbedded !== false && typeof raw.updateId !== 'string';
|
|
85
|
+
return {
|
|
86
|
+
updateId: typeof raw.updateId === 'string' ? raw.updateId : null,
|
|
87
|
+
// The embedded bundle carries no update.json — its version IS the
|
|
88
|
+
// store-shipped app version.
|
|
89
|
+
version: typeof raw.version === 'string' && raw.version.length > 0
|
|
90
|
+
? raw.version
|
|
91
|
+
: (isEmbedded ? embeddedVersion : ''),
|
|
92
|
+
embeddedVersion,
|
|
93
|
+
runtimeVersion: typeof raw.runtimeVersion === 'string' ? raw.runtimeVersion : getInstalledRuntimeVersion(),
|
|
94
|
+
isEmbedded,
|
|
95
|
+
isFirstLaunchAfterUpdate: raw.isFirstLaunchAfterUpdate === true,
|
|
96
|
+
didRollBack: raw.didRollBack === true,
|
|
97
|
+
rolledBackUpdateId: typeof raw.rolledBackUpdateId === 'string' && raw.rolledBackUpdateId.length > 0
|
|
98
|
+
? raw.rolledBackUpdateId
|
|
99
|
+
: null,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Stream the bundle to the native staging slot and verify its SHA-256.
|
|
104
|
+
* Resolves when the update is fully staged for apply; progress arrives on
|
|
105
|
+
* the `__sigxUpdatesEvent` channel (see `events.ts`).
|
|
106
|
+
*/
|
|
107
|
+
export async function nativeDownload(spec, updateId, runtimeVersion, manifestJson) {
|
|
108
|
+
if (!nativeAvailable()) {
|
|
109
|
+
throw new UpdatesError('native-unavailable', 'Updates native module is not available');
|
|
110
|
+
}
|
|
111
|
+
const result = await callAsync(MODULE, 'downloadUpdate', {
|
|
112
|
+
url: spec.url,
|
|
113
|
+
sha256: spec.sha256,
|
|
114
|
+
headers: spec.headers ?? {},
|
|
115
|
+
updateId,
|
|
116
|
+
runtimeVersion,
|
|
117
|
+
manifestJson,
|
|
118
|
+
});
|
|
119
|
+
throwIfNativeError(result, 'download-failed');
|
|
120
|
+
}
|
|
121
|
+
/** Stage the downloaded update to load on the next cold launch. */
|
|
122
|
+
export async function nativeApplyOnNextLaunch(updateId) {
|
|
123
|
+
if (!nativeAvailable()) {
|
|
124
|
+
throw new UpdatesError('native-unavailable', 'Updates native module is not available');
|
|
125
|
+
}
|
|
126
|
+
const result = await callAsync(MODULE, 'applyOnNextLaunch', updateId);
|
|
127
|
+
throwIfNativeError(result, 'apply-failed');
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Stage + reload the LynxView in place. On success the JS context is torn
|
|
131
|
+
* down, so the returned promise only ever settles on FAILURE.
|
|
132
|
+
*/
|
|
133
|
+
export async function nativeApplyNow(updateId) {
|
|
134
|
+
if (!nativeAvailable()) {
|
|
135
|
+
throw new UpdatesError('native-unavailable', 'Updates native module is not available');
|
|
136
|
+
}
|
|
137
|
+
const result = await callAsync(MODULE, 'applyNow', updateId);
|
|
138
|
+
throwIfNativeError(result, 'apply-failed');
|
|
139
|
+
}
|
|
140
|
+
/** Commit the pending update as healthy (idempotent). */
|
|
141
|
+
export async function nativeMarkReady() {
|
|
142
|
+
if (!nativeAvailable())
|
|
143
|
+
return;
|
|
144
|
+
const result = await callAsync(MODULE, 'markReady');
|
|
145
|
+
throwIfNativeError(result, 'native-error');
|
|
146
|
+
}
|
|
147
|
+
/** Configure rollback tuning for subsequent launches. */
|
|
148
|
+
export async function nativeSetRollbackOptions(maxFailedLaunches) {
|
|
149
|
+
if (!nativeAvailable())
|
|
150
|
+
return;
|
|
151
|
+
const result = await callAsync(MODULE, 'setRollbackOptions', { maxFailedLaunches });
|
|
152
|
+
throwIfNativeError(result, 'native-error');
|
|
153
|
+
}
|
|
154
|
+
/** Drop every downloaded update; the baked bundle loads on next launch. */
|
|
155
|
+
export async function nativeClearUpdates() {
|
|
156
|
+
if (!nativeAvailable())
|
|
157
|
+
return;
|
|
158
|
+
const result = await callAsync(MODULE, 'clearUpdates');
|
|
159
|
+
throwIfNativeError(result, 'native-error');
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in static-manifest backend: a JSON document on any static host/CDN.
|
|
3
|
+
*
|
|
4
|
+
* Document shape (one URL can serve every channel/runtime-version — old
|
|
5
|
+
* binaries keep matching their entry while new binaries pick up new ones):
|
|
6
|
+
*
|
|
7
|
+
* ```json
|
|
8
|
+
* {
|
|
9
|
+
* "schemaVersion": 1,
|
|
10
|
+
* "updates": [{
|
|
11
|
+
* "id": "a1b2c3d4e5f60718",
|
|
12
|
+
* "version": "1.4.2",
|
|
13
|
+
* "channel": "production",
|
|
14
|
+
* "platforms": ["android"],
|
|
15
|
+
* "runtimeVersion": "fp1-3aa01b2c44de9921",
|
|
16
|
+
* "bundleUrl": "updates/a1b2c3d4e5f60718/main.lynx.bundle",
|
|
17
|
+
* "sha256": "<64-hex>",
|
|
18
|
+
* "mandatory": false,
|
|
19
|
+
* "createdAt": "2026-06-12T10:00:00Z",
|
|
20
|
+
* "metadata": { "releaseNotes": "Bug fixes." }
|
|
21
|
+
* }]
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* `sigx updates:publish` emits this document; relative `bundleUrl`s resolve
|
|
26
|
+
* against the manifest URL so the output directory can be dropped onto any
|
|
27
|
+
* static host unchanged.
|
|
28
|
+
*/
|
|
29
|
+
import { type UpdateCheckContext, type UpdateCheckResult, type UpdateManifest, type UpdateProvider } from '../types.js';
|
|
30
|
+
export interface StaticManifestEntry extends Omit<UpdateManifest, 'id' | 'mandatory'> {
|
|
31
|
+
id?: string;
|
|
32
|
+
mandatory?: boolean;
|
|
33
|
+
/** Platforms this entry serves. Default: both. */
|
|
34
|
+
platforms?: string[];
|
|
35
|
+
/** Release channel. Default 'production'. */
|
|
36
|
+
channel?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface StaticManifestDocument {
|
|
39
|
+
schemaVersion: number;
|
|
40
|
+
updates: StaticManifestEntry[];
|
|
41
|
+
}
|
|
42
|
+
export interface StaticManifestProviderOptions {
|
|
43
|
+
/** Absolute URL of the manifest JSON. */
|
|
44
|
+
url: string;
|
|
45
|
+
/** Extra headers sent with the manifest request AND bundle download. */
|
|
46
|
+
headers?: Record<string, string>;
|
|
47
|
+
/** Injectable fetch for tests. Defaults to `globalThis.fetch`. */
|
|
48
|
+
fetchImpl?: typeof globalThis.fetch;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validate a parsed manifest document. Returns human-readable errors
|
|
52
|
+
* (empty array = valid). Exported for `sigx updates:publish` and tests.
|
|
53
|
+
*/
|
|
54
|
+
export declare function validateUpdatesManifest(doc: unknown): string[];
|
|
55
|
+
export declare class StaticManifestProvider implements UpdateProvider {
|
|
56
|
+
private readonly options;
|
|
57
|
+
readonly name = "static-manifest";
|
|
58
|
+
constructor(options: StaticManifestProviderOptions);
|
|
59
|
+
checkForUpdate(ctx: UpdateCheckContext): Promise<UpdateCheckResult>;
|
|
60
|
+
resolveDownload(manifest: UpdateManifest): Promise<{
|
|
61
|
+
url: string;
|
|
62
|
+
sha256: string;
|
|
63
|
+
headers: Record<string, string> | undefined;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=static-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-manifest.d.ts","sourceRoot":"","sources":["../../src/provider/static-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAEH,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,cAAc,EACtB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,WAAW,CAAC;IACjF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,mBAAmB,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,6BAA6B;IAC1C,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kEAAkE;IAClE,SAAS,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACvC;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAE,CAwC9D;AA0CD,qBAAa,sBAAuB,YAAW,cAAc;IAG7C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,IAAI,qBAAqB;IAElC,YAA6B,OAAO,EAAE,6BAA6B,EAAI;IAEjE,cAAc,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA8CxE;IAEK,eAAe,CAAC,QAAQ,EAAE,cAAc;;;;OAM7C;CACJ"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in static-manifest backend: a JSON document on any static host/CDN.
|
|
3
|
+
*
|
|
4
|
+
* Document shape (one URL can serve every channel/runtime-version — old
|
|
5
|
+
* binaries keep matching their entry while new binaries pick up new ones):
|
|
6
|
+
*
|
|
7
|
+
* ```json
|
|
8
|
+
* {
|
|
9
|
+
* "schemaVersion": 1,
|
|
10
|
+
* "updates": [{
|
|
11
|
+
* "id": "a1b2c3d4e5f60718",
|
|
12
|
+
* "version": "1.4.2",
|
|
13
|
+
* "channel": "production",
|
|
14
|
+
* "platforms": ["android"],
|
|
15
|
+
* "runtimeVersion": "fp1-3aa01b2c44de9921",
|
|
16
|
+
* "bundleUrl": "updates/a1b2c3d4e5f60718/main.lynx.bundle",
|
|
17
|
+
* "sha256": "<64-hex>",
|
|
18
|
+
* "mandatory": false,
|
|
19
|
+
* "createdAt": "2026-06-12T10:00:00Z",
|
|
20
|
+
* "metadata": { "releaseNotes": "Bug fixes." }
|
|
21
|
+
* }]
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* `sigx updates:publish` emits this document; relative `bundleUrl`s resolve
|
|
26
|
+
* against the manifest URL so the output directory can be dropped onto any
|
|
27
|
+
* static host unchanged.
|
|
28
|
+
*/
|
|
29
|
+
import { UpdatesError, } from '../types.js';
|
|
30
|
+
/**
|
|
31
|
+
* Validate a parsed manifest document. Returns human-readable errors
|
|
32
|
+
* (empty array = valid). Exported for `sigx updates:publish` and tests.
|
|
33
|
+
*/
|
|
34
|
+
export function validateUpdatesManifest(doc) {
|
|
35
|
+
const errors = [];
|
|
36
|
+
if (!doc || typeof doc !== 'object')
|
|
37
|
+
return ['Manifest must be a JSON object'];
|
|
38
|
+
const d = doc;
|
|
39
|
+
if (d.schemaVersion !== 1)
|
|
40
|
+
errors.push('Unsupported or missing "schemaVersion" (expected 1)');
|
|
41
|
+
if (!Array.isArray(d.updates)) {
|
|
42
|
+
errors.push('Missing "updates" array');
|
|
43
|
+
return errors;
|
|
44
|
+
}
|
|
45
|
+
d.updates.forEach((entry, i) => {
|
|
46
|
+
if (!entry || typeof entry !== 'object') {
|
|
47
|
+
errors.push(`updates[${i}] must be an object`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const e = entry;
|
|
51
|
+
for (const field of ['version', 'runtimeVersion', 'bundleUrl', 'sha256']) {
|
|
52
|
+
if (typeof e[field] !== 'string' || e[field].length === 0) {
|
|
53
|
+
errors.push(`updates[${i}].${field} must be a non-empty string`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (typeof e.sha256 === 'string' && !/^[0-9a-f]{64}$/i.test(e.sha256)) {
|
|
57
|
+
errors.push(`updates[${i}].sha256 must be 64 hex characters`);
|
|
58
|
+
}
|
|
59
|
+
// Optional fields must still be well-typed — selection logic calls
|
|
60
|
+
// .includes()/string compares on them, and a malformed value must
|
|
61
|
+
// surface as check-failed, not a TypeError.
|
|
62
|
+
if (e.platforms !== undefined &&
|
|
63
|
+
(!Array.isArray(e.platforms) || !e.platforms.every((p) => p === 'android' || p === 'ios'))) {
|
|
64
|
+
errors.push(`updates[${i}].platforms must be an array of 'android' | 'ios'`);
|
|
65
|
+
}
|
|
66
|
+
for (const field of ['channel', 'id', 'createdAt']) {
|
|
67
|
+
if (e[field] !== undefined && typeof e[field] !== 'string') {
|
|
68
|
+
errors.push(`updates[${i}].${field} must be a string when present`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (e.mandatory !== undefined && typeof e.mandatory !== 'boolean') {
|
|
72
|
+
errors.push(`updates[${i}].mandatory must be a boolean when present`);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return errors;
|
|
76
|
+
}
|
|
77
|
+
/** Default id derivation: content-addressed prefix of the bundle hash. */
|
|
78
|
+
function entryId(entry) {
|
|
79
|
+
return entry.id ?? entry.sha256.slice(0, 16);
|
|
80
|
+
}
|
|
81
|
+
/** Resolve a possibly-relative bundle URL against the manifest URL. */
|
|
82
|
+
function resolveBundleUrl(bundleUrl, manifestUrl) {
|
|
83
|
+
if (/^https?:\/\//i.test(bundleUrl))
|
|
84
|
+
return bundleUrl;
|
|
85
|
+
// Manual resolution — the Lynx BG runtime has no global URL constructor.
|
|
86
|
+
const base = manifestUrl.replace(/[^/]*$/, '');
|
|
87
|
+
return base + bundleUrl.replace(/^\.\//, '');
|
|
88
|
+
}
|
|
89
|
+
function toManifest(entry, manifestUrl) {
|
|
90
|
+
return {
|
|
91
|
+
id: entryId(entry),
|
|
92
|
+
version: entry.version,
|
|
93
|
+
runtimeVersion: entry.runtimeVersion,
|
|
94
|
+
bundleUrl: resolveBundleUrl(entry.bundleUrl, manifestUrl),
|
|
95
|
+
sha256: entry.sha256.toLowerCase(),
|
|
96
|
+
mandatory: entry.mandatory === true,
|
|
97
|
+
createdAt: entry.createdAt,
|
|
98
|
+
metadata: entry.metadata,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Newest entry wins: by createdAt when present, else last in array order. */
|
|
102
|
+
function newest(entries) {
|
|
103
|
+
if (entries.length === 0)
|
|
104
|
+
return undefined;
|
|
105
|
+
const sorted = [...entries].sort((a, b) => {
|
|
106
|
+
const ta = a.createdAt ? Date.parse(a.createdAt) : Number.NaN;
|
|
107
|
+
const tb = b.createdAt ? Date.parse(b.createdAt) : Number.NaN;
|
|
108
|
+
if (Number.isNaN(ta) && Number.isNaN(tb))
|
|
109
|
+
return 0; // stable → array order
|
|
110
|
+
if (Number.isNaN(ta))
|
|
111
|
+
return -1;
|
|
112
|
+
if (Number.isNaN(tb))
|
|
113
|
+
return 1;
|
|
114
|
+
return ta - tb;
|
|
115
|
+
});
|
|
116
|
+
return sorted[sorted.length - 1];
|
|
117
|
+
}
|
|
118
|
+
export class StaticManifestProvider {
|
|
119
|
+
constructor(options) {
|
|
120
|
+
this.options = options;
|
|
121
|
+
this.name = 'static-manifest';
|
|
122
|
+
}
|
|
123
|
+
async checkForUpdate(ctx) {
|
|
124
|
+
const fetchImpl = this.options.fetchImpl ?? globalThis.fetch;
|
|
125
|
+
if (typeof fetchImpl !== 'function') {
|
|
126
|
+
throw new UpdatesError('check-failed', 'No fetch implementation available — import @sigx/lynx (or @sigx/lynx-http) before checking for updates');
|
|
127
|
+
}
|
|
128
|
+
let doc;
|
|
129
|
+
try {
|
|
130
|
+
const res = await fetchImpl(this.options.url, { headers: this.options.headers });
|
|
131
|
+
if (!res.ok) {
|
|
132
|
+
throw new UpdatesError('check-failed', `Manifest request failed: HTTP ${res.status}`);
|
|
133
|
+
}
|
|
134
|
+
doc = await res.json();
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if (err instanceof UpdatesError)
|
|
138
|
+
throw err;
|
|
139
|
+
throw new UpdatesError('check-failed', `Manifest request failed: ${err?.message ?? err}`);
|
|
140
|
+
}
|
|
141
|
+
const errors = validateUpdatesManifest(doc);
|
|
142
|
+
if (errors.length > 0) {
|
|
143
|
+
throw new UpdatesError('check-failed', `Invalid updates manifest: ${errors.join('; ')}`);
|
|
144
|
+
}
|
|
145
|
+
const channel = ctx.channel ?? 'production';
|
|
146
|
+
const candidates = doc.updates.filter((e) => (e.platforms ?? ['android', 'ios']).includes(ctx.platform) &&
|
|
147
|
+
(e.channel ?? 'production') === channel);
|
|
148
|
+
const compatible = candidates.filter((e) => e.runtimeVersion === ctx.runtimeVersion);
|
|
149
|
+
const best = newest(compatible);
|
|
150
|
+
if (best) {
|
|
151
|
+
const manifest = toManifest(best, this.options.url);
|
|
152
|
+
return manifest.id === ctx.currentUpdateId
|
|
153
|
+
? { type: 'up-to-date' }
|
|
154
|
+
: { type: 'update-available', manifest };
|
|
155
|
+
}
|
|
156
|
+
// No compatible entry — surface the newest INCOMPATIBLE one (if any)
|
|
157
|
+
// so the app can tell "you're current" apart from "a newer release
|
|
158
|
+
// exists but needs a store update".
|
|
159
|
+
const incompatible = newest(candidates);
|
|
160
|
+
if (incompatible) {
|
|
161
|
+
return { type: 'incompatible', manifest: toManifest(incompatible, this.options.url) };
|
|
162
|
+
}
|
|
163
|
+
return { type: 'up-to-date' };
|
|
164
|
+
}
|
|
165
|
+
async resolveDownload(manifest) {
|
|
166
|
+
return {
|
|
167
|
+
url: manifest.bundleUrl,
|
|
168
|
+
sha256: manifest.sha256,
|
|
169
|
+
headers: this.options.headers,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=static-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-manifest.js","sourceRoot":"","sources":["../../src/provider/static-manifest.ts"],"names":[],"mappings":"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"}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global reactive store + event fan-out for the updates state machine.
|
|
3
|
+
* The controller is the only writer; UI reads via `useUpdates()` or
|
|
4
|
+
* `Updates.getState()`, imperative code subscribes via `Updates.addListener`.
|
|
5
|
+
*/
|
|
6
|
+
import type { UpdatesEvent, UpdatesState } from './types.js';
|
|
7
|
+
/** The store — a deeply reactive proxy (object signal). @internal */
|
|
8
|
+
export declare const store: import("@sigx/reactivity").Signal<UpdatesState>;
|
|
9
|
+
type Listener = (event: UpdatesEvent) => void;
|
|
10
|
+
export declare function addListener(fn: Listener): () => void;
|
|
11
|
+
/** Emit an event to subscribers (never throws). @internal */
|
|
12
|
+
export declare function emit(event: UpdatesEvent): void;
|
|
13
|
+
/**
|
|
14
|
+
* Fully detached manifest copy — `metadata` is the only nested field, so
|
|
15
|
+
* cloning it makes the snapshot deep. @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare function cloneManifest(manifest: UpdatesState['manifest']): UpdatesState['manifest'];
|
|
18
|
+
/** Snapshot of the current state (plain object, detached from the proxy). */
|
|
19
|
+
export declare function getStateSnapshot(): UpdatesState;
|
|
20
|
+
/** Test-only: reset the store and drop all listeners. @internal */
|
|
21
|
+
export declare function __resetForTests(): void;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAqB,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAwBhF,qEAAqE;AACrE,eAAO,MAAM,KAAK,iDAAuC,CAAC;AAE1D,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AAG9C,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,IAAI,CAGpD;AAED,6DAA6D;AAC7D,wBAAgB,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAQ9C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,CAM1F;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,IAAI,YAAY,CAS/C;AAED,mEAAmE;AACnE,wBAAgB,eAAe,IAAI,IAAI,CAGtC"}
|