@otakit/capacitor-updater 2.1.2 → 2.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.
- package/README.md +142 -4
- package/android/src/main/java/com/otakit/updater/BundleCrypto.java +81 -0
- package/android/src/main/java/com/otakit/updater/BundleStore.java +15 -0
- package/android/src/main/java/com/otakit/updater/ManifestClient.java +69 -2
- package/android/src/main/java/com/otakit/updater/ManifestVerifier.java +43 -0
- package/android/src/main/java/com/otakit/updater/UpdaterPlugin.java +277 -19
- package/dist/esm/definitions.d.ts +98 -0
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +7 -1
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +25 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +31 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +31 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/UpdaterPlugin/BundleCrypto.swift +91 -0
- package/ios/Sources/UpdaterPlugin/BundleStore.swift +13 -0
- package/ios/Sources/UpdaterPlugin/ManifestClient.swift +45 -1
- package/ios/Sources/UpdaterPlugin/ManifestVerifier.swift +29 -0
- package/ios/Sources/UpdaterPlugin/UpdaterPlugin.swift +256 -19
- package/package.json +1 -1
package/dist/esm/web.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WebPlugin } from '@capacitor/core';
|
|
2
|
-
import type { OtaKitBridgePlugin, BundleInfo, CheckResult, DownloadResult, OtaKitState } from './definitions';
|
|
2
|
+
import type { OtaKitBridgePlugin, BundleInfo, ChannelInfo, CheckResult, DownloadResult, OtaKitState } from './definitions';
|
|
3
3
|
/**
|
|
4
4
|
* Web implementation of the native bridge.
|
|
5
5
|
* Most methods are no-ops since OTA updates don't apply to web.
|
|
@@ -13,5 +13,11 @@ export declare class OtaKitWeb extends WebPlugin implements OtaKitBridgePlugin {
|
|
|
13
13
|
update(): Promise<void>;
|
|
14
14
|
notifyAppReady(): Promise<void>;
|
|
15
15
|
getLastFailure(): Promise<BundleInfo | null>;
|
|
16
|
+
private static readonly OVERRIDE_CHANNEL_KEY;
|
|
17
|
+
private static isValidChannelName;
|
|
18
|
+
setChannel(options: {
|
|
19
|
+
channel: string | null;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
getChannel(): Promise<ChannelInfo>;
|
|
16
22
|
}
|
|
17
23
|
//# sourceMappingURL=web.d.ts.map
|
package/dist/esm/web.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,KAAK,EACV,kBAAkB,EAClB,UAAU,EAEV,WAAW,EACX,cAAc,EACd,WAAW,EACZ,MAAM,eAAe,CAAC;AAEvB;;;GAGG;AACH,qBAAa,SAAU,SAAQ,SAAU,YAAW,kBAAkB;IACpE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAI7B;IAEI,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC;IAShC,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC;IAK7B,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAKnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B,cAAc,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,KAAK,EACV,kBAAkB,EAClB,UAAU,EAEV,WAAW,EACX,WAAW,EACX,cAAc,EACd,WAAW,EACZ,MAAM,eAAe,CAAC;AAEvB;;;GAGG;AACH,qBAAa,SAAU,SAAQ,SAAU,YAAW,kBAAkB;IACpE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAI7B;IAEI,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC;IAShC,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC;IAK7B,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAKnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B,cAAc,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAIlD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAA6B;IAEzE,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAQ3B,UAAU,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAa9D,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC;CAOzC"}
|
package/dist/esm/web.js
CHANGED
|
@@ -38,5 +38,30 @@ export class OtaKitWeb extends WebPlugin {
|
|
|
38
38
|
async getLastFailure() {
|
|
39
39
|
return null;
|
|
40
40
|
}
|
|
41
|
+
static OVERRIDE_CHANNEL_KEY = 'otakit_override_channel';
|
|
42
|
+
static isValidChannelName(name) {
|
|
43
|
+
if (!/^[A-Za-z0-9._-]{1,64}$/.test(name) || name.includes('..') || name === '.') {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const lower = name.toLowerCase();
|
|
47
|
+
return lower !== 'base' && lower !== 'default';
|
|
48
|
+
}
|
|
49
|
+
async setChannel(options) {
|
|
50
|
+
if (options.channel === null) {
|
|
51
|
+
window.localStorage.removeItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!OtaKitWeb.isValidChannelName(options.channel)) {
|
|
55
|
+
throw new Error(`Invalid channel name '${options.channel}': use 1-64 letters, numbers, '.', '_' or '-' (reserved names: base, default)`);
|
|
56
|
+
}
|
|
57
|
+
window.localStorage.setItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY, options.channel);
|
|
58
|
+
}
|
|
59
|
+
async getChannel() {
|
|
60
|
+
const override = window.localStorage.getItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY);
|
|
61
|
+
if (override !== null) {
|
|
62
|
+
return { channel: override, source: 'override' };
|
|
63
|
+
}
|
|
64
|
+
return { channel: null, source: 'config' };
|
|
65
|
+
}
|
|
41
66
|
}
|
|
42
67
|
//# sourceMappingURL=web.js.map
|
package/dist/esm/web.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAY5C;;;GAGG;AACH,MAAM,OAAO,SAAU,SAAQ,SAAS;IACrB,cAAc,GAAe;QAC5C,EAAE,EAAE,SAAS;QACb,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,SAAyB;KAClC,CAAC;IAEF,KAAK,CAAC,QAAQ;QACZ,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,cAAc;YAC5B,QAAQ,EAAE,IAAI,CAAC,cAAc;YAC7B,MAAM,EAAE,IAAI;YACZ,cAAc,EAAE,IAAI,CAAC,cAAc,CAAC,OAAO;SAC5C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK;QACT,OAAO,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;QACvD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;QAC1D,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,KAAK;QACT,OAAO,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,uEAAuE;IACzE,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,MAAM,CAAU,oBAAoB,GAAG,yBAAyB,CAAC;IAEjE,MAAM,CAAC,kBAAkB,CAAC,IAAY;QAC5C,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YAChF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACjC,OAAO,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,SAAS,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,OAAmC;QAClD,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC7B,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;YAC/D,OAAO;QACT,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CACb,yBAAyB,OAAO,CAAC,OAAO,+EAA+E,CACxH,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,CAAC,oBAAoB,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/E,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QAC7E,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;QACnD,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC7C,CAAC"}
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -43,6 +43,12 @@ const OtaKit = {
|
|
|
43
43
|
update: () => NativeOtaKit.update(),
|
|
44
44
|
notifyAppReady: () => NativeOtaKit.notifyAppReady(),
|
|
45
45
|
getLastFailure: async () => normalizeNullable(await NativeOtaKit.getLastFailure()),
|
|
46
|
+
setChannel: (options) => NativeOtaKit.setChannel(options),
|
|
47
|
+
getChannel: () => NativeOtaKit.getChannel(),
|
|
48
|
+
// NativeOtaKit is the registerPlugin proxy, which implements Capacitor's
|
|
49
|
+
// listener API; this plain-object wrapper must forward it explicitly.
|
|
50
|
+
addListener: ((eventName, listenerFunc) => NativeOtaKit.addListener(eventName, listenerFunc)),
|
|
51
|
+
removeAllListeners: () => NativeOtaKit.removeAllListeners(),
|
|
46
52
|
};
|
|
47
53
|
|
|
48
54
|
/**
|
|
@@ -84,6 +90,31 @@ class OtaKitWeb extends core.WebPlugin {
|
|
|
84
90
|
async getLastFailure() {
|
|
85
91
|
return null;
|
|
86
92
|
}
|
|
93
|
+
static OVERRIDE_CHANNEL_KEY = 'otakit_override_channel';
|
|
94
|
+
static isValidChannelName(name) {
|
|
95
|
+
if (!/^[A-Za-z0-9._-]{1,64}$/.test(name) || name.includes('..') || name === '.') {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const lower = name.toLowerCase();
|
|
99
|
+
return lower !== 'base' && lower !== 'default';
|
|
100
|
+
}
|
|
101
|
+
async setChannel(options) {
|
|
102
|
+
if (options.channel === null) {
|
|
103
|
+
window.localStorage.removeItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!OtaKitWeb.isValidChannelName(options.channel)) {
|
|
107
|
+
throw new Error(`Invalid channel name '${options.channel}': use 1-64 letters, numbers, '.', '_' or '-' (reserved names: base, default)`);
|
|
108
|
+
}
|
|
109
|
+
window.localStorage.setItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY, options.channel);
|
|
110
|
+
}
|
|
111
|
+
async getChannel() {
|
|
112
|
+
const override = window.localStorage.getItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY);
|
|
113
|
+
if (override !== null) {
|
|
114
|
+
return { channel: override, source: 'override' };
|
|
115
|
+
}
|
|
116
|
+
return { channel: null, source: 'config' };
|
|
117
|
+
}
|
|
87
118
|
}
|
|
88
119
|
|
|
89
120
|
var web = /*#__PURE__*/Object.freeze({
|
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["/**\n * Bundle status enum.\n */\nexport var BundleStatus;\n(function (BundleStatus) {\n /** Factory-installed bundle */\n BundleStatus[\"BUILTIN\"] = \"builtin\";\n /** Downloaded and staged, awaiting activation */\n BundleStatus[\"PENDING\"] = \"pending\";\n /** Active but not yet confirmed */\n BundleStatus[\"TRIAL\"] = \"trial\";\n /** Confirmed working */\n BundleStatus[\"SUCCESS\"] = \"success\";\n /** Failed (hash mismatch, extraction error, rollback) */\n BundleStatus[\"ERROR\"] = \"error\";\n})(BundleStatus || (BundleStatus = {}));\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nconst NativeOtaKit = registerPlugin('OtaKit', {\n web: () => import('./web').then((m) => new m.OtaKitWeb()),\n});\n/**\n * Check if result is an empty object (iOS returns {} instead of null)\n */\nfunction isEmptyObject(obj) {\n return obj !== null && typeof obj === 'object' && Object.keys(obj).length === 0;\n}\n/**\n * Wrapped plugin that normalizes null returns from native code.\n * iOS call.resolve() without arguments returns {} instead of null.\n */\nfunction normalizeNullable(value) {\n return isEmptyObject(value) ? null : value;\n}\nconst OtaKit = {\n getState: () => NativeOtaKit.getState(),\n check: () => NativeOtaKit.check(),\n download: () => NativeOtaKit.download(),\n apply: () => NativeOtaKit.apply(),\n update: () => NativeOtaKit.update(),\n notifyAppReady: () => NativeOtaKit.notifyAppReady(),\n getLastFailure: async () => normalizeNullable(await NativeOtaKit.getLastFailure()),\n};\nexport * from './definitions';\nexport { OtaKit };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Web implementation of the native bridge.\n * Most methods are no-ops since OTA updates don't apply to web.\n */\nexport class OtaKitWeb extends WebPlugin {\n BUILTIN_BUNDLE = {\n id: 'builtin',\n version: '0.0.0',\n status: 'builtin',\n };\n async getState() {\n return {\n current: this.BUILTIN_BUNDLE,\n fallback: this.BUILTIN_BUNDLE,\n staged: null,\n builtinVersion: this.BUILTIN_BUNDLE.version,\n };\n }\n async check() {\n console.warn('OtaKit.check() is not supported on web');\n return { kind: 'no_update' };\n }\n async download() {\n console.warn('OtaKit.download() is not supported on web');\n return { kind: 'no_update' };\n }\n async apply() {\n console.warn('OtaKit.apply() is not supported on web');\n throw new Error('OtaKit.apply() is not supported on web');\n }\n async update() {\n await this.download();\n }\n async notifyAppReady() {\n // No-op on web, but don't warn - apps should call this unconditionally\n }\n async getLastFailure() {\n return null;\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["BundleStatus","registerPlugin","WebPlugin"],"mappings":";;;;AAAA;AACA;AACA;AACWA;AACX,CAAC,UAAU,YAAY,EAAE;AACzB;AACA,IAAI,YAAY,CAAC,SAAS,CAAC,GAAG,SAAS;AACvC;AACA,IAAI,YAAY,CAAC,SAAS,CAAC,GAAG,SAAS;AACvC;AACA,IAAI,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO;AACnC;AACA,IAAI,YAAY,CAAC,SAAS,CAAC,GAAG,SAAS;AACvC;AACA,IAAI,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO;AACnC,CAAC,EAAEA,oBAAY,KAAKA,oBAAY,GAAG,EAAE,CAAC,CAAC;;ACdvC,MAAM,YAAY,GAAGC,mBAAc,CAAC,QAAQ,EAAE;AAC9C,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;AAC7D,CAAC,CAAC;AACF;AACA;AACA;AACA,SAAS,aAAa,CAAC,GAAG,EAAE;AAC5B,IAAI,OAAO,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC;AACnF;AACA;AACA;AACA;AACA;AACA,SAAS,iBAAiB,CAAC,KAAK,EAAE;AAClC,IAAI,OAAO,aAAa,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,KAAK;AAC9C;AACK,MAAC,MAAM,GAAG;AACf,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC,QAAQ,EAAE;AAC3C,IAAI,KAAK,EAAE,MAAM,YAAY,CAAC,KAAK,EAAE;AACrC,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC,QAAQ,EAAE;AAC3C,IAAI,KAAK,EAAE,MAAM,YAAY,CAAC,KAAK,EAAE;AACrC,IAAI,MAAM,EAAE,MAAM,YAAY,CAAC,MAAM,EAAE;AACvC,IAAI,cAAc,EAAE,MAAM,YAAY,CAAC,cAAc,EAAE;AACvD,IAAI,cAAc,EAAE,YAAY,iBAAiB,CAAC,MAAM,YAAY,CAAC,cAAc,EAAE,CAAC;AACtF;;
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["/**\n * Bundle status enum.\n */\nexport var BundleStatus;\n(function (BundleStatus) {\n /** Factory-installed bundle */\n BundleStatus[\"BUILTIN\"] = \"builtin\";\n /** Downloaded and staged, awaiting activation */\n BundleStatus[\"PENDING\"] = \"pending\";\n /** Active but not yet confirmed */\n BundleStatus[\"TRIAL\"] = \"trial\";\n /** Confirmed working */\n BundleStatus[\"SUCCESS\"] = \"success\";\n /** Failed (hash mismatch, extraction error, rollback) */\n BundleStatus[\"ERROR\"] = \"error\";\n})(BundleStatus || (BundleStatus = {}));\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nconst NativeOtaKit = registerPlugin('OtaKit', {\n web: () => import('./web').then((m) => new m.OtaKitWeb()),\n});\n/**\n * Check if result is an empty object (iOS returns {} instead of null)\n */\nfunction isEmptyObject(obj) {\n return obj !== null && typeof obj === 'object' && Object.keys(obj).length === 0;\n}\n/**\n * Wrapped plugin that normalizes null returns from native code.\n * iOS call.resolve() without arguments returns {} instead of null.\n */\nfunction normalizeNullable(value) {\n return isEmptyObject(value) ? null : value;\n}\nconst OtaKit = {\n getState: () => NativeOtaKit.getState(),\n check: () => NativeOtaKit.check(),\n download: () => NativeOtaKit.download(),\n apply: () => NativeOtaKit.apply(),\n update: () => NativeOtaKit.update(),\n notifyAppReady: () => NativeOtaKit.notifyAppReady(),\n getLastFailure: async () => normalizeNullable(await NativeOtaKit.getLastFailure()),\n setChannel: (options) => NativeOtaKit.setChannel(options),\n getChannel: () => NativeOtaKit.getChannel(),\n // NativeOtaKit is the registerPlugin proxy, which implements Capacitor's\n // listener API; this plain-object wrapper must forward it explicitly.\n addListener: ((eventName, listenerFunc) => NativeOtaKit.addListener(eventName, listenerFunc)),\n removeAllListeners: () => NativeOtaKit.removeAllListeners(),\n};\nexport * from './definitions';\nexport { OtaKit };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Web implementation of the native bridge.\n * Most methods are no-ops since OTA updates don't apply to web.\n */\nexport class OtaKitWeb extends WebPlugin {\n BUILTIN_BUNDLE = {\n id: 'builtin',\n version: '0.0.0',\n status: 'builtin',\n };\n async getState() {\n return {\n current: this.BUILTIN_BUNDLE,\n fallback: this.BUILTIN_BUNDLE,\n staged: null,\n builtinVersion: this.BUILTIN_BUNDLE.version,\n };\n }\n async check() {\n console.warn('OtaKit.check() is not supported on web');\n return { kind: 'no_update' };\n }\n async download() {\n console.warn('OtaKit.download() is not supported on web');\n return { kind: 'no_update' };\n }\n async apply() {\n console.warn('OtaKit.apply() is not supported on web');\n throw new Error('OtaKit.apply() is not supported on web');\n }\n async update() {\n await this.download();\n }\n async notifyAppReady() {\n // No-op on web, but don't warn - apps should call this unconditionally\n }\n async getLastFailure() {\n return null;\n }\n static OVERRIDE_CHANNEL_KEY = 'otakit_override_channel';\n static isValidChannelName(name) {\n if (!/^[A-Za-z0-9._-]{1,64}$/.test(name) || name.includes('..') || name === '.') {\n return false;\n }\n const lower = name.toLowerCase();\n return lower !== 'base' && lower !== 'default';\n }\n async setChannel(options) {\n if (options.channel === null) {\n window.localStorage.removeItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY);\n return;\n }\n if (!OtaKitWeb.isValidChannelName(options.channel)) {\n throw new Error(`Invalid channel name '${options.channel}': use 1-64 letters, numbers, '.', '_' or '-' (reserved names: base, default)`);\n }\n window.localStorage.setItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY, options.channel);\n }\n async getChannel() {\n const override = window.localStorage.getItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY);\n if (override !== null) {\n return { channel: override, source: 'override' };\n }\n return { channel: null, source: 'config' };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["BundleStatus","registerPlugin","WebPlugin"],"mappings":"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"}
|
package/dist/plugin.js
CHANGED
|
@@ -42,6 +42,12 @@ var capacitorUpdater = (function (exports, core) {
|
|
|
42
42
|
update: () => NativeOtaKit.update(),
|
|
43
43
|
notifyAppReady: () => NativeOtaKit.notifyAppReady(),
|
|
44
44
|
getLastFailure: async () => normalizeNullable(await NativeOtaKit.getLastFailure()),
|
|
45
|
+
setChannel: (options) => NativeOtaKit.setChannel(options),
|
|
46
|
+
getChannel: () => NativeOtaKit.getChannel(),
|
|
47
|
+
// NativeOtaKit is the registerPlugin proxy, which implements Capacitor's
|
|
48
|
+
// listener API; this plain-object wrapper must forward it explicitly.
|
|
49
|
+
addListener: ((eventName, listenerFunc) => NativeOtaKit.addListener(eventName, listenerFunc)),
|
|
50
|
+
removeAllListeners: () => NativeOtaKit.removeAllListeners(),
|
|
45
51
|
};
|
|
46
52
|
|
|
47
53
|
/**
|
|
@@ -83,6 +89,31 @@ var capacitorUpdater = (function (exports, core) {
|
|
|
83
89
|
async getLastFailure() {
|
|
84
90
|
return null;
|
|
85
91
|
}
|
|
92
|
+
static OVERRIDE_CHANNEL_KEY = 'otakit_override_channel';
|
|
93
|
+
static isValidChannelName(name) {
|
|
94
|
+
if (!/^[A-Za-z0-9._-]{1,64}$/.test(name) || name.includes('..') || name === '.') {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const lower = name.toLowerCase();
|
|
98
|
+
return lower !== 'base' && lower !== 'default';
|
|
99
|
+
}
|
|
100
|
+
async setChannel(options) {
|
|
101
|
+
if (options.channel === null) {
|
|
102
|
+
window.localStorage.removeItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!OtaKitWeb.isValidChannelName(options.channel)) {
|
|
106
|
+
throw new Error(`Invalid channel name '${options.channel}': use 1-64 letters, numbers, '.', '_' or '-' (reserved names: base, default)`);
|
|
107
|
+
}
|
|
108
|
+
window.localStorage.setItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY, options.channel);
|
|
109
|
+
}
|
|
110
|
+
async getChannel() {
|
|
111
|
+
const override = window.localStorage.getItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY);
|
|
112
|
+
if (override !== null) {
|
|
113
|
+
return { channel: override, source: 'override' };
|
|
114
|
+
}
|
|
115
|
+
return { channel: null, source: 'config' };
|
|
116
|
+
}
|
|
86
117
|
}
|
|
87
118
|
|
|
88
119
|
var web = /*#__PURE__*/Object.freeze({
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["/**\n * Bundle status enum.\n */\nexport var BundleStatus;\n(function (BundleStatus) {\n /** Factory-installed bundle */\n BundleStatus[\"BUILTIN\"] = \"builtin\";\n /** Downloaded and staged, awaiting activation */\n BundleStatus[\"PENDING\"] = \"pending\";\n /** Active but not yet confirmed */\n BundleStatus[\"TRIAL\"] = \"trial\";\n /** Confirmed working */\n BundleStatus[\"SUCCESS\"] = \"success\";\n /** Failed (hash mismatch, extraction error, rollback) */\n BundleStatus[\"ERROR\"] = \"error\";\n})(BundleStatus || (BundleStatus = {}));\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nconst NativeOtaKit = registerPlugin('OtaKit', {\n web: () => import('./web').then((m) => new m.OtaKitWeb()),\n});\n/**\n * Check if result is an empty object (iOS returns {} instead of null)\n */\nfunction isEmptyObject(obj) {\n return obj !== null && typeof obj === 'object' && Object.keys(obj).length === 0;\n}\n/**\n * Wrapped plugin that normalizes null returns from native code.\n * iOS call.resolve() without arguments returns {} instead of null.\n */\nfunction normalizeNullable(value) {\n return isEmptyObject(value) ? null : value;\n}\nconst OtaKit = {\n getState: () => NativeOtaKit.getState(),\n check: () => NativeOtaKit.check(),\n download: () => NativeOtaKit.download(),\n apply: () => NativeOtaKit.apply(),\n update: () => NativeOtaKit.update(),\n notifyAppReady: () => NativeOtaKit.notifyAppReady(),\n getLastFailure: async () => normalizeNullable(await NativeOtaKit.getLastFailure()),\n};\nexport * from './definitions';\nexport { OtaKit };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Web implementation of the native bridge.\n * Most methods are no-ops since OTA updates don't apply to web.\n */\nexport class OtaKitWeb extends WebPlugin {\n BUILTIN_BUNDLE = {\n id: 'builtin',\n version: '0.0.0',\n status: 'builtin',\n };\n async getState() {\n return {\n current: this.BUILTIN_BUNDLE,\n fallback: this.BUILTIN_BUNDLE,\n staged: null,\n builtinVersion: this.BUILTIN_BUNDLE.version,\n };\n }\n async check() {\n console.warn('OtaKit.check() is not supported on web');\n return { kind: 'no_update' };\n }\n async download() {\n console.warn('OtaKit.download() is not supported on web');\n return { kind: 'no_update' };\n }\n async apply() {\n console.warn('OtaKit.apply() is not supported on web');\n throw new Error('OtaKit.apply() is not supported on web');\n }\n async update() {\n await this.download();\n }\n async notifyAppReady() {\n // No-op on web, but don't warn - apps should call this unconditionally\n }\n async getLastFailure() {\n return null;\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["BundleStatus","registerPlugin","WebPlugin"],"mappings":";;;IAAA;IACA;IACA;AACWA;IACX,CAAC,UAAU,YAAY,EAAE;IACzB;IACA,IAAI,YAAY,CAAC,SAAS,CAAC,GAAG,SAAS;IACvC;IACA,IAAI,YAAY,CAAC,SAAS,CAAC,GAAG,SAAS;IACvC;IACA,IAAI,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO;IACnC;IACA,IAAI,YAAY,CAAC,SAAS,CAAC,GAAG,SAAS;IACvC;IACA,IAAI,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO;IACnC,CAAC,EAAEA,oBAAY,KAAKA,oBAAY,GAAG,EAAE,CAAC,CAAC;;ICdvC,MAAM,YAAY,GAAGC,mBAAc,CAAC,QAAQ,EAAE;IAC9C,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;IAC7D,CAAC,CAAC;IACF;IACA;IACA;IACA,SAAS,aAAa,CAAC,GAAG,EAAE;IAC5B,IAAI,OAAO,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC;IACnF;IACA;IACA;IACA;IACA;IACA,SAAS,iBAAiB,CAAC,KAAK,EAAE;IAClC,IAAI,OAAO,aAAa,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,KAAK;IAC9C;AACK,UAAC,MAAM,GAAG;IACf,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC,QAAQ,EAAE;IAC3C,IAAI,KAAK,EAAE,MAAM,YAAY,CAAC,KAAK,EAAE;IACrC,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC,QAAQ,EAAE;IAC3C,IAAI,KAAK,EAAE,MAAM,YAAY,CAAC,KAAK,EAAE;IACrC,IAAI,MAAM,EAAE,MAAM,YAAY,CAAC,MAAM,EAAE;IACvC,IAAI,cAAc,EAAE,MAAM,YAAY,CAAC,cAAc,EAAE;IACvD,IAAI,cAAc,EAAE,YAAY,iBAAiB,CAAC,MAAM,YAAY,CAAC,cAAc,EAAE,CAAC;IACtF;;
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["/**\n * Bundle status enum.\n */\nexport var BundleStatus;\n(function (BundleStatus) {\n /** Factory-installed bundle */\n BundleStatus[\"BUILTIN\"] = \"builtin\";\n /** Downloaded and staged, awaiting activation */\n BundleStatus[\"PENDING\"] = \"pending\";\n /** Active but not yet confirmed */\n BundleStatus[\"TRIAL\"] = \"trial\";\n /** Confirmed working */\n BundleStatus[\"SUCCESS\"] = \"success\";\n /** Failed (hash mismatch, extraction error, rollback) */\n BundleStatus[\"ERROR\"] = \"error\";\n})(BundleStatus || (BundleStatus = {}));\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nconst NativeOtaKit = registerPlugin('OtaKit', {\n web: () => import('./web').then((m) => new m.OtaKitWeb()),\n});\n/**\n * Check if result is an empty object (iOS returns {} instead of null)\n */\nfunction isEmptyObject(obj) {\n return obj !== null && typeof obj === 'object' && Object.keys(obj).length === 0;\n}\n/**\n * Wrapped plugin that normalizes null returns from native code.\n * iOS call.resolve() without arguments returns {} instead of null.\n */\nfunction normalizeNullable(value) {\n return isEmptyObject(value) ? null : value;\n}\nconst OtaKit = {\n getState: () => NativeOtaKit.getState(),\n check: () => NativeOtaKit.check(),\n download: () => NativeOtaKit.download(),\n apply: () => NativeOtaKit.apply(),\n update: () => NativeOtaKit.update(),\n notifyAppReady: () => NativeOtaKit.notifyAppReady(),\n getLastFailure: async () => normalizeNullable(await NativeOtaKit.getLastFailure()),\n setChannel: (options) => NativeOtaKit.setChannel(options),\n getChannel: () => NativeOtaKit.getChannel(),\n // NativeOtaKit is the registerPlugin proxy, which implements Capacitor's\n // listener API; this plain-object wrapper must forward it explicitly.\n addListener: ((eventName, listenerFunc) => NativeOtaKit.addListener(eventName, listenerFunc)),\n removeAllListeners: () => NativeOtaKit.removeAllListeners(),\n};\nexport * from './definitions';\nexport { OtaKit };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Web implementation of the native bridge.\n * Most methods are no-ops since OTA updates don't apply to web.\n */\nexport class OtaKitWeb extends WebPlugin {\n BUILTIN_BUNDLE = {\n id: 'builtin',\n version: '0.0.0',\n status: 'builtin',\n };\n async getState() {\n return {\n current: this.BUILTIN_BUNDLE,\n fallback: this.BUILTIN_BUNDLE,\n staged: null,\n builtinVersion: this.BUILTIN_BUNDLE.version,\n };\n }\n async check() {\n console.warn('OtaKit.check() is not supported on web');\n return { kind: 'no_update' };\n }\n async download() {\n console.warn('OtaKit.download() is not supported on web');\n return { kind: 'no_update' };\n }\n async apply() {\n console.warn('OtaKit.apply() is not supported on web');\n throw new Error('OtaKit.apply() is not supported on web');\n }\n async update() {\n await this.download();\n }\n async notifyAppReady() {\n // No-op on web, but don't warn - apps should call this unconditionally\n }\n async getLastFailure() {\n return null;\n }\n static OVERRIDE_CHANNEL_KEY = 'otakit_override_channel';\n static isValidChannelName(name) {\n if (!/^[A-Za-z0-9._-]{1,64}$/.test(name) || name.includes('..') || name === '.') {\n return false;\n }\n const lower = name.toLowerCase();\n return lower !== 'base' && lower !== 'default';\n }\n async setChannel(options) {\n if (options.channel === null) {\n window.localStorage.removeItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY);\n return;\n }\n if (!OtaKitWeb.isValidChannelName(options.channel)) {\n throw new Error(`Invalid channel name '${options.channel}': use 1-64 letters, numbers, '.', '_' or '-' (reserved names: base, default)`);\n }\n window.localStorage.setItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY, options.channel);\n }\n async getChannel() {\n const override = window.localStorage.getItem(OtaKitWeb.OVERRIDE_CHANNEL_KEY);\n if (override !== null) {\n return { channel: override, source: 'override' };\n }\n return { channel: null, source: 'config' };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["BundleStatus","registerPlugin","WebPlugin"],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import CryptoKit
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
enum BundleCryptoError: Error, LocalizedError {
|
|
5
|
+
case noMatchingKey(String)
|
|
6
|
+
case invalidParameter(String)
|
|
7
|
+
case decryptionFailed(String)
|
|
8
|
+
|
|
9
|
+
var errorDescription: String? {
|
|
10
|
+
switch self {
|
|
11
|
+
case let .noMatchingKey(kid):
|
|
12
|
+
return "no matching bundle key (kid \(kid))"
|
|
13
|
+
case let .invalidParameter(name):
|
|
14
|
+
return "invalid bundle encryption parameter: \(name)"
|
|
15
|
+
case let .decryptionFailed(detail):
|
|
16
|
+
return "bundle decryption failed: \(detail)"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// AES-256-GCM bundle decryption.
|
|
22
|
+
///
|
|
23
|
+
/// The CLI encrypts the zip with a random per-bundle DEK and wraps the DEK
|
|
24
|
+
/// under the app KEK; both ciphertexts carry the 16-byte GCM tag appended.
|
|
25
|
+
enum BundleCrypto {
|
|
26
|
+
private static let tagLength = 16
|
|
27
|
+
private static let keyLength = 32
|
|
28
|
+
|
|
29
|
+
static func unwrapDek(
|
|
30
|
+
kek: Data,
|
|
31
|
+
wrapNonceB64: String,
|
|
32
|
+
wrappedDekB64: String
|
|
33
|
+
) throws -> Data {
|
|
34
|
+
guard kek.count == keyLength else {
|
|
35
|
+
throw BundleCryptoError.invalidParameter("bundle key length")
|
|
36
|
+
}
|
|
37
|
+
guard let wrapNonce = Data(base64Encoded: wrapNonceB64) else {
|
|
38
|
+
throw BundleCryptoError.invalidParameter("wrapNonce")
|
|
39
|
+
}
|
|
40
|
+
guard let wrappedDek = Data(base64Encoded: wrappedDekB64),
|
|
41
|
+
wrappedDek.count == keyLength + tagLength else {
|
|
42
|
+
throw BundleCryptoError.invalidParameter("wrappedDek")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let dek: Data
|
|
46
|
+
do {
|
|
47
|
+
let sealedBox = try AES.GCM.SealedBox(
|
|
48
|
+
nonce: AES.GCM.Nonce(data: wrapNonce),
|
|
49
|
+
ciphertext: wrappedDek.prefix(keyLength),
|
|
50
|
+
tag: wrappedDek.suffix(tagLength)
|
|
51
|
+
)
|
|
52
|
+
dek = try AES.GCM.open(sealedBox, using: SymmetricKey(data: kek))
|
|
53
|
+
} catch {
|
|
54
|
+
throw BundleCryptoError.decryptionFailed("DEK unwrap: \(error.localizedDescription)")
|
|
55
|
+
}
|
|
56
|
+
guard dek.count == keyLength else {
|
|
57
|
+
throw BundleCryptoError.decryptionFailed("unwrapped DEK has unexpected length")
|
|
58
|
+
}
|
|
59
|
+
return dek
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static func decryptFile(
|
|
63
|
+
dek: Data,
|
|
64
|
+
nonceB64: String,
|
|
65
|
+
input: URL,
|
|
66
|
+
output: URL
|
|
67
|
+
) throws {
|
|
68
|
+
guard let nonce = Data(base64Encoded: nonceB64) else {
|
|
69
|
+
throw BundleCryptoError.invalidParameter("nonce")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let ciphertextWithTag = try Data(contentsOf: input)
|
|
73
|
+
guard ciphertextWithTag.count > tagLength else {
|
|
74
|
+
throw BundleCryptoError.invalidParameter("ciphertext too short")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
do {
|
|
78
|
+
let sealedBox = try AES.GCM.SealedBox(
|
|
79
|
+
nonce: AES.GCM.Nonce(data: nonce),
|
|
80
|
+
ciphertext: ciphertextWithTag.prefix(ciphertextWithTag.count - tagLength),
|
|
81
|
+
tag: ciphertextWithTag.suffix(tagLength)
|
|
82
|
+
)
|
|
83
|
+
let plaintext = try AES.GCM.open(sealedBox, using: SymmetricKey(data: dek))
|
|
84
|
+
try plaintext.write(to: output, options: .atomic)
|
|
85
|
+
} catch let error as BundleCryptoError {
|
|
86
|
+
throw error
|
|
87
|
+
} catch {
|
|
88
|
+
throw BundleCryptoError.decryptionFailed(error.localizedDescription)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -7,6 +7,7 @@ final class BundleStore {
|
|
|
7
7
|
static let stagedBundleId = "otakit_staged_bundle_id"
|
|
8
8
|
static let lastFailedBundleInfo = "otakit_last_failed_bundle_info"
|
|
9
9
|
static let lastResolvedRuntimeKey = "otakit_last_resolved_runtime_key"
|
|
10
|
+
static let overrideChannel = "otakit_override_channel"
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
private let defaults = UserDefaults.standard
|
|
@@ -206,6 +207,18 @@ final class BundleStore {
|
|
|
206
207
|
return try? decoder.decode(BundleInfo.self, from: data)
|
|
207
208
|
}
|
|
208
209
|
|
|
210
|
+
func getOverrideChannel() -> String? {
|
|
211
|
+
defaults.string(forKey: Keys.overrideChannel)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
func setOverrideChannel(_ channel: String?) {
|
|
215
|
+
if let channel {
|
|
216
|
+
defaults.set(channel, forKey: Keys.overrideChannel)
|
|
217
|
+
} else {
|
|
218
|
+
defaults.removeObject(forKey: Keys.overrideChannel)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
209
222
|
func getLastResolvedRuntimeKey() -> String? {
|
|
210
223
|
defaults.string(forKey: Keys.lastResolvedRuntimeKey)
|
|
211
224
|
}
|
|
@@ -3,6 +3,14 @@ import Foundation
|
|
|
3
3
|
private let baseChannelKey = "__base__"
|
|
4
4
|
private let defaultRuntimeKey = "__default__"
|
|
5
5
|
|
|
6
|
+
struct ManifestEncryption {
|
|
7
|
+
let alg: String
|
|
8
|
+
let kid: String
|
|
9
|
+
let wrapNonce: String
|
|
10
|
+
let wrappedDek: String
|
|
11
|
+
let nonce: String
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
struct LatestManifest {
|
|
7
15
|
let version: String
|
|
8
16
|
let url: String
|
|
@@ -10,6 +18,9 @@ struct LatestManifest {
|
|
|
10
18
|
let size: Int
|
|
11
19
|
let runtimeVersion: String?
|
|
12
20
|
let releaseId: String
|
|
21
|
+
let strategy: String
|
|
22
|
+
let forceImmediate: Bool
|
|
23
|
+
let encryption: ManifestEncryption?
|
|
13
24
|
}
|
|
14
25
|
|
|
15
26
|
struct ManifestSignature {
|
|
@@ -112,6 +123,12 @@ enum ManifestClient {
|
|
|
112
123
|
throw ManifestClientError.invalidResponse
|
|
113
124
|
}
|
|
114
125
|
|
|
126
|
+
let strategy = (object["strategy"] as? String)?
|
|
127
|
+
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
128
|
+
.nilIfEmpty ?? "zip"
|
|
129
|
+
let forceImmediate = object["forceImmediate"] as? Bool ?? false
|
|
130
|
+
let encryption = try parseEncryption(object["encryption"])
|
|
131
|
+
|
|
115
132
|
guard let dlURL = URL(string: downloadUrl) else {
|
|
116
133
|
throw ManifestClientError.invalidURL
|
|
117
134
|
}
|
|
@@ -133,6 +150,9 @@ enum ManifestClient {
|
|
|
133
150
|
sha256: sha256,
|
|
134
151
|
size: size,
|
|
135
152
|
runtimeVersion: runtimeVersion,
|
|
153
|
+
strategy: strategy,
|
|
154
|
+
forceImmediate: forceImmediate,
|
|
155
|
+
encryption: encryption,
|
|
136
156
|
signature: signature,
|
|
137
157
|
trustedKeys: manifestKeys
|
|
138
158
|
)
|
|
@@ -144,7 +164,31 @@ enum ManifestClient {
|
|
|
144
164
|
sha256: sha256,
|
|
145
165
|
size: size,
|
|
146
166
|
runtimeVersion: runtimeVersion,
|
|
147
|
-
releaseId: releaseId
|
|
167
|
+
releaseId: releaseId,
|
|
168
|
+
strategy: strategy,
|
|
169
|
+
forceImmediate: forceImmediate,
|
|
170
|
+
encryption: encryption
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private static func parseEncryption(_ rawValue: Any?) throws -> ManifestEncryption? {
|
|
175
|
+
guard let rawValue, !(rawValue is NSNull) else {
|
|
176
|
+
return nil
|
|
177
|
+
}
|
|
178
|
+
guard let object = rawValue as? [String: Any],
|
|
179
|
+
let alg = object["alg"] as? String,
|
|
180
|
+
let kid = object["kid"] as? String,
|
|
181
|
+
let wrapNonce = object["wrapNonce"] as? String,
|
|
182
|
+
let wrappedDek = object["wrappedDek"] as? String,
|
|
183
|
+
let nonce = object["nonce"] as? String else {
|
|
184
|
+
throw ManifestClientError.invalidResponse
|
|
185
|
+
}
|
|
186
|
+
return ManifestEncryption(
|
|
187
|
+
alg: alg,
|
|
188
|
+
kid: kid,
|
|
189
|
+
wrapNonce: wrapNonce,
|
|
190
|
+
wrappedDek: wrappedDek,
|
|
191
|
+
nonce: nonce
|
|
148
192
|
)
|
|
149
193
|
}
|
|
150
194
|
|
|
@@ -31,6 +31,9 @@ enum ManifestVerifier {
|
|
|
31
31
|
sha256: String,
|
|
32
32
|
size: Int,
|
|
33
33
|
runtimeVersion: String?,
|
|
34
|
+
strategy: String,
|
|
35
|
+
forceImmediate: Bool,
|
|
36
|
+
encryption: ManifestEncryption?,
|
|
34
37
|
signature: ManifestSignature,
|
|
35
38
|
trustedKeys: [ManifestKey]
|
|
36
39
|
) throws {
|
|
@@ -41,6 +44,9 @@ enum ManifestVerifier {
|
|
|
41
44
|
sha256: sha256,
|
|
42
45
|
size: size,
|
|
43
46
|
runtimeVersion: runtimeVersion,
|
|
47
|
+
strategy: strategy,
|
|
48
|
+
forceImmediate: forceImmediate,
|
|
49
|
+
encryption: encryption,
|
|
44
50
|
kid: signature.kid,
|
|
45
51
|
iat: signature.iat,
|
|
46
52
|
exp: signature.exp
|
|
@@ -78,6 +84,23 @@ enum ManifestVerifier {
|
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
/// Encode the encryption block for the canonical payload.
|
|
88
|
+
/// Must match the server's `encodeEncryptionForPayload` exactly.
|
|
89
|
+
private static func encodeEncryptionForPayload(_ encryption: ManifestEncryption?) -> String {
|
|
90
|
+
guard let encryption else {
|
|
91
|
+
return "null"
|
|
92
|
+
}
|
|
93
|
+
return [
|
|
94
|
+
encryption.alg,
|
|
95
|
+
encryption.kid,
|
|
96
|
+
encryption.wrapNonce,
|
|
97
|
+
encryption.wrappedDek,
|
|
98
|
+
encryption.nonce,
|
|
99
|
+
].joined(separator: "|")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Canonical payload v2 — must match the server's `buildCanonicalPayload`
|
|
103
|
+
/// (console/lib/manifest-signing.ts) and the Android mirror byte-for-byte.
|
|
81
104
|
private static func buildCanonicalPayload(
|
|
82
105
|
appId: String,
|
|
83
106
|
channel: String?,
|
|
@@ -85,6 +108,9 @@ enum ManifestVerifier {
|
|
|
85
108
|
sha256: String,
|
|
86
109
|
size: Int,
|
|
87
110
|
runtimeVersion: String?,
|
|
111
|
+
strategy: String,
|
|
112
|
+
forceImmediate: Bool,
|
|
113
|
+
encryption: ManifestEncryption?,
|
|
88
114
|
kid: String,
|
|
89
115
|
iat: Int,
|
|
90
116
|
exp: Int
|
|
@@ -97,6 +123,9 @@ enum ManifestVerifier {
|
|
|
97
123
|
"sha256:\(sha256)",
|
|
98
124
|
"size:\(size)",
|
|
99
125
|
"runtimeVersion:\(runtimeVersion ?? "null")",
|
|
126
|
+
"strategy:\(strategy)",
|
|
127
|
+
"forceImmediate:\(forceImmediate ? "true" : "false")",
|
|
128
|
+
"encryption:\(encodeEncryptionForPayload(encryption))",
|
|
100
129
|
"kid:\(kid)",
|
|
101
130
|
"iat:\(iat)",
|
|
102
131
|
"exp:\(exp)",
|