@otakit/capacitor-updater 2.1.1 → 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/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
@@ -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;CAGnD"}
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
@@ -1 +1 @@
1
- {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAW5C;;;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;CACF"}
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"}
@@ -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({
@@ -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":"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"}
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({
@@ -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":"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"}
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)",