@otakit/capacitor-updater 2.1.2 → 2.3.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.
@@ -1,3 +1,4 @@
1
+ import type { PluginListenerHandle } from '@capacitor/core';
1
2
  /**
2
3
  * Bundle status enum.
3
4
  */
@@ -36,14 +37,22 @@ export interface LatestVersion {
36
37
  version: string;
37
38
  /** Native compatibility lane for this update. */
38
39
  runtimeVersion?: string;
39
- /** Download URL */
40
- url: string;
41
- /** SHA-256 checksum */
40
+ /** Bundle download URL. Present for the 'zip' strategy; absent for 'deltas'. */
41
+ url?: string;
42
+ /** SHA-256 checksum: the zip hash for 'zip', the canonical filesHash for 'deltas'. */
42
43
  sha256: string;
43
- /** Bundle size in bytes */
44
+ /** Bundle size in bytes (total decompressed size for 'deltas') */
44
45
  size: number;
45
46
  /** Release history ID associated with this manifest */
46
47
  releaseId: string;
48
+ /** Update strategy this manifest was published with. Defaults to 'zip'. */
49
+ strategy?: 'zip' | 'deltas';
50
+ /**
51
+ * True when the release is marked force-immediate: automatic flows apply
52
+ * and reload it on the next lifecycle event regardless of shadow or
53
+ * apply-staged policies. Manual API behavior is unchanged.
54
+ */
55
+ forceImmediate?: boolean;
47
56
  }
48
57
  export interface OtaKitState {
49
58
  current: BundleInfo;
@@ -56,6 +65,12 @@ export interface OtaKitManifestKey {
56
65
  kid: string;
57
66
  key: string;
58
67
  }
68
+ export interface OtaKitBundleKey {
69
+ /** Key ID: first 16 hex chars of sha256(key). Printed by `otakit generate-encryption-key`. */
70
+ kid: string;
71
+ /** 256-bit AES key, base64. Inject from an env var at build time; do not commit. */
72
+ key: string;
73
+ }
59
74
  export interface CheckNoUpdateResult {
60
75
  kind: 'no_update';
61
76
  }
@@ -76,6 +91,46 @@ export interface DownloadStagedResult {
76
91
  bundle: BundleInfo;
77
92
  }
78
93
  export type DownloadResult = DownloadNoUpdateResult | DownloadStagedResult;
94
+ export interface ChannelInfo {
95
+ /** The effective release channel, or null for the base channel. */
96
+ channel: string | null;
97
+ /** Where the effective channel comes from: a runtime override or static config. */
98
+ source: 'override' | 'config';
99
+ }
100
+ /**
101
+ * Payload for the `updateStaged` event: a bundle was downloaded, verified,
102
+ * and staged, ready to apply.
103
+ */
104
+ export interface UpdateStagedEvent {
105
+ bundle: BundleInfo;
106
+ }
107
+ /**
108
+ * Payload for the `updateApplied` event: a newly activated bundle was
109
+ * confirmed healthy via notifyAppReady().
110
+ */
111
+ export interface UpdateAppliedEvent {
112
+ bundle: BundleInfo;
113
+ }
114
+ /**
115
+ * Payload for the `downloadFailed` and `rollback` events.
116
+ */
117
+ export interface UpdateFailedEvent {
118
+ version: string;
119
+ runtimeVersion?: string;
120
+ releaseId?: string;
121
+ channel?: string;
122
+ /** Stable failure reason, e.g. "hash_mismatch", "insufficient_disk_space", "notify_timeout". */
123
+ reason: string;
124
+ }
125
+ /**
126
+ * Update lifecycle events emitted by the plugin.
127
+ *
128
+ * Events fire only while the app process is alive. Reconcile with
129
+ * getState() and getLastFailure() on startup for anything that happened
130
+ * while no listener was attached (e.g. a bundle staged in a previous
131
+ * session, or a startup rollback).
132
+ */
133
+ export type OtaKitEventName = 'updateAvailable' | 'updateStaged' | 'updateApplied' | 'downloadFailed' | 'rollback';
79
134
  /**
80
135
  * Plugin configuration for capacitor.config.ts.
81
136
  */
@@ -109,6 +164,11 @@ export interface OtaKitConfig {
109
164
  cdnUrl?: string;
110
165
  /** Custom manifest verification keys for self-hosted or custom trust. */
111
166
  manifestKeys?: OtaKitManifestKey[];
167
+ /**
168
+ * Bundle decryption keys for end-to-end encrypted bundles.
169
+ * Array to allow rotation: ship old + new keys together during a transition.
170
+ */
171
+ bundleKeys?: OtaKitBundleKey[];
112
172
  /** Allow HTTP only for localhost development. Defaults to false. */
113
173
  allowInsecureUrls?: boolean;
114
174
  }
@@ -153,6 +213,40 @@ export interface OtaKitPlugin {
153
213
  * Returns null if no failure has occurred.
154
214
  */
155
215
  getLastFailure(): Promise<BundleInfo | null>;
216
+ /**
217
+ * Override the release channel at runtime (e.g. a "Join beta" toggle).
218
+ * Pass null to clear the override and return to the configured channel.
219
+ *
220
+ * The override is persisted across launches and takes effect on the next
221
+ * check/download/automatic cycle — it does not trigger anything by itself.
222
+ * Rejects invalid channel names without persisting.
223
+ */
224
+ setChannel(options: {
225
+ channel: string | null;
226
+ }): Promise<void>;
227
+ /**
228
+ * Get the effective release channel and where it comes from
229
+ * (a runtime override or the static plugin config).
230
+ */
231
+ getChannel(): Promise<ChannelInfo>;
232
+ /**
233
+ * Subscribe to update lifecycle events.
234
+ *
235
+ * Events fire only while the app is running. On startup, reconcile with
236
+ * getState() (anything staged while not listening) and getLastFailure()
237
+ * (startup rollbacks happen before JS boots and cannot reach a listener).
238
+ * apply() reloads the WebView and destroys the JS context, so attach
239
+ * `updateApplied` / `rollback` listeners early in app startup.
240
+ */
241
+ addListener(eventName: 'updateAvailable', listenerFunc: (latest: LatestVersion) => void): Promise<PluginListenerHandle>;
242
+ addListener(eventName: 'updateStaged', listenerFunc: (event: UpdateStagedEvent) => void): Promise<PluginListenerHandle>;
243
+ addListener(eventName: 'updateApplied', listenerFunc: (event: UpdateAppliedEvent) => void): Promise<PluginListenerHandle>;
244
+ addListener(eventName: 'downloadFailed', listenerFunc: (event: UpdateFailedEvent) => void): Promise<PluginListenerHandle>;
245
+ addListener(eventName: 'rollback', listenerFunc: (event: UpdateFailedEvent) => void): Promise<PluginListenerHandle>;
246
+ /**
247
+ * Remove all registered event listeners.
248
+ */
249
+ removeAllListeners(): Promise<void>;
156
250
  }
157
251
  export interface OtaKitBridgePlugin {
158
252
  getState(): Promise<OtaKitState>;
@@ -162,5 +256,11 @@ export interface OtaKitBridgePlugin {
162
256
  update(): Promise<void>;
163
257
  notifyAppReady(): Promise<void>;
164
258
  getLastFailure(): Promise<BundleInfo | null>;
259
+ setChannel(options: {
260
+ channel: string | null;
261
+ }): Promise<void>;
262
+ getChannel(): Promise<ChannelInfo>;
263
+ addListener(eventName: OtaKitEventName, listenerFunc: (event: unknown) => void): Promise<PluginListenerHandle>;
264
+ removeAllListeners(): Promise<void>;
165
265
  }
166
266
  //# sourceMappingURL=definitions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAN,IAAY,YAWX;AAXD,WAAY,YAAY;IACtB,+BAA+B;IAC/B,mCAAmB,CAAA;IACnB,iDAAiD;IACjD,mCAAmB,CAAA;IACnB,mCAAmC;IACnC,+BAAe,CAAA;IACf,wBAAwB;IACxB,mCAAmB,CAAA;IACnB,yDAAyD;IACzD,+BAAe,CAAA;AACjB,CAAC,EAXW,YAAY,KAAZ,YAAY,QAWvB"}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAN,IAAY,YAWX;AAXD,WAAY,YAAY;IACtB,+BAA+B;IAC/B,mCAAmB,CAAA;IACnB,iDAAiD;IACjD,mCAAmB,CAAA;IACnB,mCAAmC;IACnC,+BAAe,CAAA;IACf,wBAAwB;IACxB,mCAAmB,CAAA;IACnB,yDAAyD;IACzD,+BAAe,CAAA;AACjB,CAAC,EAXW,YAAY,KAAZ,YAAY,QAWvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsB,YAAY,EAAc,MAAM,eAAe,CAAC;AAqBlF,QAAA,MAAM,MAAM,EAAE,YASb,CAAC;AAEF,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsB,YAAY,EAA+B,MAAM,eAAe,CAAC;AAqBnG,QAAA,MAAM,MAAM,EAAE,YAgBb,CAAC;AAEF,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,CAAC"}
package/dist/esm/index.js CHANGED
@@ -23,6 +23,12 @@ const OtaKit = {
23
23
  update: () => NativeOtaKit.update(),
24
24
  notifyAppReady: () => NativeOtaKit.notifyAppReady(),
25
25
  getLastFailure: async () => normalizeNullable(await NativeOtaKit.getLastFailure()),
26
+ setChannel: (options) => NativeOtaKit.setChannel(options),
27
+ getChannel: () => NativeOtaKit.getChannel(),
28
+ // NativeOtaKit is the registerPlugin proxy, which implements Capacitor's
29
+ // listener API; this plain-object wrapper must forward it explicitly.
30
+ addListener: ((eventName, listenerFunc) => NativeOtaKit.addListener(eventName, listenerFunc)),
31
+ removeAllListeners: () => NativeOtaKit.removeAllListeners(),
26
32
  };
27
33
  export * from './definitions';
28
34
  export { OtaKit };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,YAAY,GAAG,cAAc,CAAqB,QAAQ,EAAE;IAChE,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;CAC1D,CAAC,CAAC;AAEH;;GAEG;AACH,SAAS,aAAa,CAAC,GAAY;IACjC,OAAO,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAClF,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAI,KAAQ;IACpC,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AAC7C,CAAC;AAED,MAAM,MAAM,GAAiB;IAC3B,QAAQ,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE;IACvC,KAAK,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE;IACjC,QAAQ,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE;IACvC,KAAK,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE;IACjC,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE;IACnC,cAAc,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE;IACnD,cAAc,EAAE,KAAK,IAAgC,EAAE,CACrD,iBAAiB,CAAC,MAAM,YAAY,CAAC,cAAc,EAAE,CAAC;CACzD,CAAC;AAEF,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,YAAY,GAAG,cAAc,CAAqB,QAAQ,EAAE;IAChE,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;CAC1D,CAAC,CAAC;AAEH;;GAEG;AACH,SAAS,aAAa,CAAC,GAAY;IACjC,OAAO,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAClF,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAI,KAAQ;IACpC,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AAC7C,CAAC;AAED,MAAM,MAAM,GAAiB;IAC3B,QAAQ,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE;IACvC,KAAK,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE;IACjC,QAAQ,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE;IACvC,KAAK,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE;IACjC,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE;IACnC,cAAc,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE;IACnD,cAAc,EAAE,KAAK,IAAgC,EAAE,CACrD,iBAAiB,CAAC,MAAM,YAAY,CAAC,cAAc,EAAE,CAAC;IACxD,UAAU,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC;IACzD,UAAU,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE;IAC3C,yEAAyE;IACzE,sEAAsE;IACtE,WAAW,EAAE,CAAC,CAAC,SAA0B,EAAE,YAAsC,EAAE,EAAE,CACnF,YAAY,CAAC,WAAW,CAAC,SAAS,EAAE,YAAY,CAAC,CAAgC;IACnF,kBAAkB,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,kBAAkB,EAAE;CAC5D,CAAC;AAEF,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,CAAC"}
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
@@ -41,6 +42,23 @@ final class BundleStore {
41
42
  return directory
42
43
  }()
43
44
 
45
+ /// Content-addressed file cache for the deltas strategy (`otakit_files/<sha256>`).
46
+ private(set) lazy var filesCacheDirectory: URL = {
47
+ let appSupport = fileManager.urls(
48
+ for: .applicationSupportDirectory,
49
+ in: .userDomainMask
50
+ ).first!
51
+ let directory = appSupport.appendingPathComponent(
52
+ "otakit_files",
53
+ isDirectory: true
54
+ )
55
+ try? fileManager.createDirectory(
56
+ at: directory,
57
+ withIntermediateDirectories: true
58
+ )
59
+ return directory
60
+ }()
61
+
44
62
  var builtinVersion: String {
45
63
  Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
46
64
  }
@@ -206,6 +224,18 @@ final class BundleStore {
206
224
  return try? decoder.decode(BundleInfo.self, from: data)
207
225
  }
208
226
 
227
+ func getOverrideChannel() -> String? {
228
+ defaults.string(forKey: Keys.overrideChannel)
229
+ }
230
+
231
+ func setOverrideChannel(_ channel: String?) {
232
+ if let channel {
233
+ defaults.set(channel, forKey: Keys.overrideChannel)
234
+ } else {
235
+ defaults.removeObject(forKey: Keys.overrideChannel)
236
+ }
237
+ }
238
+
209
239
  func getLastResolvedRuntimeKey() -> String? {
210
240
  defaults.string(forKey: Keys.lastResolvedRuntimeKey)
211
241
  }