@otakit/capacitor-updater 1.0.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -0
  3. package/UpdatekitUpdater.podspec +18 -0
  4. package/android/build.gradle +49 -0
  5. package/android/src/main/AndroidManifest.xml +3 -0
  6. package/android/src/main/java/com/updatekit/updater/BundleInfo.java +100 -0
  7. package/android/src/main/java/com/updatekit/updater/BundleStatus.java +28 -0
  8. package/android/src/main/java/com/updatekit/updater/BundleStore.java +264 -0
  9. package/android/src/main/java/com/updatekit/updater/DateUtils.java +17 -0
  10. package/android/src/main/java/com/updatekit/updater/HashUtils.java +32 -0
  11. package/android/src/main/java/com/updatekit/updater/HostedManifestKeys.java +37 -0
  12. package/android/src/main/java/com/updatekit/updater/ManifestClient.java +209 -0
  13. package/android/src/main/java/com/updatekit/updater/ManifestVerifier.java +146 -0
  14. package/android/src/main/java/com/updatekit/updater/StatsClient.java +80 -0
  15. package/android/src/main/java/com/updatekit/updater/UpdaterPlugin.java +963 -0
  16. package/android/src/main/java/com/updatekit/updater/ZipUtils.java +72 -0
  17. package/dist/esm/definitions.d.ts +229 -0
  18. package/dist/esm/definitions.d.ts.map +1 -0
  19. package/dist/esm/definitions.js +17 -0
  20. package/dist/esm/definitions.js.map +1 -0
  21. package/dist/esm/index.d.ts +5 -0
  22. package/dist/esm/index.d.ts.map +1 -0
  23. package/dist/esm/index.js +85 -0
  24. package/dist/esm/index.js.map +1 -0
  25. package/dist/esm/web.d.ts +25 -0
  26. package/dist/esm/web.d.ts.map +1 -0
  27. package/dist/esm/web.js +56 -0
  28. package/dist/esm/web.js.map +1 -0
  29. package/dist/plugin.cjs.js +165 -0
  30. package/dist/plugin.cjs.js.map +1 -0
  31. package/dist/plugin.js +168 -0
  32. package/dist/plugin.js.map +1 -0
  33. package/ios/Sources/UpdaterPlugin/BundleInfo.swift +39 -0
  34. package/ios/Sources/UpdaterPlugin/BundleStatus.swift +9 -0
  35. package/ios/Sources/UpdaterPlugin/BundleStore.swift +233 -0
  36. package/ios/Sources/UpdaterPlugin/Downloader.swift +120 -0
  37. package/ios/Sources/UpdaterPlugin/HashUtils.swift +33 -0
  38. package/ios/Sources/UpdaterPlugin/HostedManifestKeys.swift +23 -0
  39. package/ios/Sources/UpdaterPlugin/ManifestClient.swift +161 -0
  40. package/ios/Sources/UpdaterPlugin/ManifestVerifier.swift +115 -0
  41. package/ios/Sources/UpdaterPlugin/StatsClient.swift +66 -0
  42. package/ios/Sources/UpdaterPlugin/UpdaterPlugin.m +15 -0
  43. package/ios/Sources/UpdaterPlugin/UpdaterPlugin.swift +913 -0
  44. package/ios/Sources/UpdaterPlugin/ZipUtils.swift +90 -0
  45. package/package.json +85 -0
@@ -0,0 +1,165 @@
1
+ 'use strict';
2
+
3
+ var core = require('@capacitor/core');
4
+
5
+ /**
6
+ * Bundle status enum.
7
+ */
8
+ exports.BundleStatus = void 0;
9
+ (function (BundleStatus) {
10
+ /** Factory-installed bundle */
11
+ BundleStatus["BUILTIN"] = "builtin";
12
+ /** Downloaded and staged, awaiting activation */
13
+ BundleStatus["PENDING"] = "pending";
14
+ /** Active but not yet confirmed */
15
+ BundleStatus["TRIAL"] = "trial";
16
+ /** Confirmed working */
17
+ BundleStatus["SUCCESS"] = "success";
18
+ /** Failed (hash mismatch, extraction error, rollback) */
19
+ BundleStatus["ERROR"] = "error";
20
+ })(exports.BundleStatus || (exports.BundleStatus = {}));
21
+
22
+ const NativeOtaKit = core.registerPlugin('OtaKit', {
23
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.OtaKitWeb()),
24
+ });
25
+ /**
26
+ * Check if result is an empty object (iOS returns {} instead of null)
27
+ */
28
+ function isEmptyObject(obj) {
29
+ return obj !== null && typeof obj === 'object' && Object.keys(obj).length === 0;
30
+ }
31
+ /**
32
+ * Wrapped plugin that normalizes null returns from native code.
33
+ * iOS call.resolve() without arguments returns {} instead of null.
34
+ */
35
+ function normalizeNullable(value) {
36
+ return isEmptyObject(value) ? null : value;
37
+ }
38
+ function toPublicState(value) {
39
+ return {
40
+ current: value.current,
41
+ staged: value.staged,
42
+ builtinVersion: value.builtinVersion,
43
+ };
44
+ }
45
+ async function getState() {
46
+ return toPublicState(await NativeOtaKit.debugGetState());
47
+ }
48
+ async function check() {
49
+ return normalizeNullable(await NativeOtaKit.check());
50
+ }
51
+ async function download() {
52
+ return normalizeNullable(await NativeOtaKit.download());
53
+ }
54
+ function apply() {
55
+ return NativeOtaKit.apply();
56
+ }
57
+ async function update() {
58
+ const state = await getState();
59
+ let latest;
60
+ try {
61
+ latest = await check();
62
+ }
63
+ catch (error) {
64
+ if (state.staged) {
65
+ await apply();
66
+ return;
67
+ }
68
+ throw error;
69
+ }
70
+ if (latest) {
71
+ if (latest.downloaded) {
72
+ await apply();
73
+ return;
74
+ }
75
+ const bundle = await download();
76
+ if (bundle) {
77
+ await apply();
78
+ }
79
+ return;
80
+ }
81
+ if (state.staged) {
82
+ await apply();
83
+ }
84
+ }
85
+ const OtaKit = {
86
+ getState,
87
+ check,
88
+ download,
89
+ apply,
90
+ update,
91
+ notifyAppReady: () => NativeOtaKit.notifyAppReady(),
92
+ debug: {
93
+ check: async (options) => normalizeNullable(await NativeOtaKit.debugCheck(options)),
94
+ download: async (options) => normalizeNullable(await NativeOtaKit.debugDownload(options)),
95
+ reset: () => NativeOtaKit.debugReset(),
96
+ listBundles: () => NativeOtaKit.debugListBundles(),
97
+ deleteBundle: (options) => NativeOtaKit.debugDeleteBundle(options),
98
+ getLastFailure: async () => normalizeNullable(await NativeOtaKit.debugGetLastFailure()),
99
+ },
100
+ addListener: NativeOtaKit.addListener.bind(NativeOtaKit),
101
+ removeAllListeners: () => NativeOtaKit.removeAllListeners(),
102
+ };
103
+
104
+ /**
105
+ * Web implementation of the native bridge.
106
+ * Most methods are no-ops since OTA updates don't apply to web
107
+ */
108
+ class OtaKitWeb extends core.WebPlugin {
109
+ BUILTIN_BUNDLE = {
110
+ id: 'builtin',
111
+ version: '0.0.0',
112
+ status: 'builtin',
113
+ };
114
+ async debugGetState() {
115
+ return {
116
+ current: this.BUILTIN_BUNDLE,
117
+ fallback: this.BUILTIN_BUNDLE,
118
+ staged: null,
119
+ builtinVersion: this.BUILTIN_BUNDLE.version,
120
+ };
121
+ }
122
+ async check() {
123
+ console.warn('OtaKit.check() is not supported on web');
124
+ return null;
125
+ }
126
+ async download() {
127
+ console.warn('OtaKit.download() is not supported on web');
128
+ return null;
129
+ }
130
+ async apply() {
131
+ console.warn('OtaKit.apply() is not supported on web');
132
+ throw new Error('OtaKit.apply() is not supported on web');
133
+ }
134
+ async debugCheck() {
135
+ console.warn('OtaKit.debug.check() is not supported on web');
136
+ return null;
137
+ }
138
+ async debugDownload(_options) {
139
+ console.warn('OtaKit.debug.download() is not supported on web');
140
+ return null;
141
+ }
142
+ async notifyAppReady() {
143
+ // No-op on web, but don't warn - apps should call this unconditionally
144
+ }
145
+ async debugReset() {
146
+ console.warn('OtaKit.debug.reset() is not supported on web');
147
+ }
148
+ async debugListBundles() {
149
+ return { bundles: [] };
150
+ }
151
+ async debugDeleteBundle(_options) {
152
+ console.warn('OtaKit.debug.deleteBundle() is not supported on web');
153
+ }
154
+ async debugGetLastFailure() {
155
+ return null;
156
+ }
157
+ }
158
+
159
+ var web = /*#__PURE__*/Object.freeze({
160
+ __proto__: null,
161
+ OtaKitWeb: OtaKitWeb
162
+ });
163
+
164
+ exports.OtaKit = OtaKit;
165
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +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}\nfunction toPublicState(value) {\n return {\n current: value.current,\n staged: value.staged,\n builtinVersion: value.builtinVersion,\n };\n}\nasync function getState() {\n return toPublicState(await NativeOtaKit.debugGetState());\n}\nasync function check() {\n return normalizeNullable(await NativeOtaKit.check());\n}\nasync function download() {\n return normalizeNullable(await NativeOtaKit.download());\n}\nfunction apply() {\n return NativeOtaKit.apply();\n}\nasync function update() {\n const state = await getState();\n let latest;\n try {\n latest = await check();\n }\n catch (error) {\n if (state.staged) {\n await apply();\n return;\n }\n throw error;\n }\n if (latest) {\n if (latest.downloaded) {\n await apply();\n return;\n }\n const bundle = await download();\n if (bundle) {\n await apply();\n }\n return;\n }\n if (state.staged) {\n await apply();\n }\n}\nconst OtaKit = {\n getState,\n check,\n download,\n apply,\n update,\n notifyAppReady: () => NativeOtaKit.notifyAppReady(),\n debug: {\n check: async (options) => normalizeNullable(await NativeOtaKit.debugCheck(options)),\n download: async (options) => normalizeNullable(await NativeOtaKit.debugDownload(options)),\n reset: () => NativeOtaKit.debugReset(),\n listBundles: () => NativeOtaKit.debugListBundles(),\n deleteBundle: (options) => NativeOtaKit.debugDeleteBundle(options),\n getLastFailure: async () => normalizeNullable(await NativeOtaKit.debugGetLastFailure()),\n },\n addListener: NativeOtaKit.addListener.bind(NativeOtaKit),\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 debugGetState() {\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 null;\n }\n async download() {\n console.warn('OtaKit.download() is not supported on web');\n return null;\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 debugCheck() {\n console.warn('OtaKit.debug.check() is not supported on web');\n return null;\n }\n async debugDownload(_options) {\n console.warn('OtaKit.debug.download() is not supported on web');\n return null;\n }\n async notifyAppReady() {\n // No-op on web, but don't warn - apps should call this unconditionally\n }\n async debugReset() {\n console.warn('OtaKit.debug.reset() is not supported on web');\n }\n async debugListBundles() {\n return { bundles: [] };\n }\n async debugDeleteBundle(_options) {\n console.warn('OtaKit.debug.deleteBundle() is not supported on web');\n }\n async debugGetLastFailure() {\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;AACA,SAAS,aAAa,CAAC,KAAK,EAAE;AAC9B,IAAI,OAAO;AACX,QAAQ,OAAO,EAAE,KAAK,CAAC,OAAO;AAC9B,QAAQ,MAAM,EAAE,KAAK,CAAC,MAAM;AAC5B,QAAQ,cAAc,EAAE,KAAK,CAAC,cAAc;AAC5C,KAAK;AACL;AACA,eAAe,QAAQ,GAAG;AAC1B,IAAI,OAAO,aAAa,CAAC,MAAM,YAAY,CAAC,aAAa,EAAE,CAAC;AAC5D;AACA,eAAe,KAAK,GAAG;AACvB,IAAI,OAAO,iBAAiB,CAAC,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC;AACxD;AACA,eAAe,QAAQ,GAAG;AAC1B,IAAI,OAAO,iBAAiB,CAAC,MAAM,YAAY,CAAC,QAAQ,EAAE,CAAC;AAC3D;AACA,SAAS,KAAK,GAAG;AACjB,IAAI,OAAO,YAAY,CAAC,KAAK,EAAE;AAC/B;AACA,eAAe,MAAM,GAAG;AACxB,IAAI,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE;AAClC,IAAI,IAAI,MAAM;AACd,IAAI,IAAI;AACR,QAAQ,MAAM,GAAG,MAAM,KAAK,EAAE;AAC9B,IAAI;AACJ,IAAI,OAAO,KAAK,EAAE;AAClB,QAAQ,IAAI,KAAK,CAAC,MAAM,EAAE;AAC1B,YAAY,MAAM,KAAK,EAAE;AACzB,YAAY;AACZ,QAAQ;AACR,QAAQ,MAAM,KAAK;AACnB,IAAI;AACJ,IAAI,IAAI,MAAM,EAAE;AAChB,QAAQ,IAAI,MAAM,CAAC,UAAU,EAAE;AAC/B,YAAY,MAAM,KAAK,EAAE;AACzB,YAAY;AACZ,QAAQ;AACR,QAAQ,MAAM,MAAM,GAAG,MAAM,QAAQ,EAAE;AACvC,QAAQ,IAAI,MAAM,EAAE;AACpB,YAAY,MAAM,KAAK,EAAE;AACzB,QAAQ;AACR,QAAQ;AACR,IAAI;AACJ,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE;AACtB,QAAQ,MAAM,KAAK,EAAE;AACrB,IAAI;AACJ;AACK,MAAC,MAAM,GAAG;AACf,IAAI,QAAQ;AACZ,IAAI,KAAK;AACT,IAAI,QAAQ;AACZ,IAAI,KAAK;AACT,IAAI,MAAM;AACV,IAAI,cAAc,EAAE,MAAM,YAAY,CAAC,cAAc,EAAE;AACvD,IAAI,KAAK,EAAE;AACX,QAAQ,KAAK,EAAE,OAAO,OAAO,KAAK,iBAAiB,CAAC,MAAM,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AAC3F,QAAQ,QAAQ,EAAE,OAAO,OAAO,KAAK,iBAAiB,CAAC,MAAM,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;AACjG,QAAQ,KAAK,EAAE,MAAM,YAAY,CAAC,UAAU,EAAE;AAC9C,QAAQ,WAAW,EAAE,MAAM,YAAY,CAAC,gBAAgB,EAAE;AAC1D,QAAQ,YAAY,EAAE,CAAC,OAAO,KAAK,YAAY,CAAC,iBAAiB,CAAC,OAAO,CAAC;AAC1E,QAAQ,cAAc,EAAE,YAAY,iBAAiB,CAAC,MAAM,YAAY,CAAC,mBAAmB,EAAE,CAAC;AAC/F,KAAK;AACL,IAAI,WAAW,EAAE,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC;AAC5D,IAAI,kBAAkB,EAAE,MAAM,YAAY,CAAC,kBAAkB,EAAE;AAC/D;;AChFA;AACA;AACA;AACA;AACO,MAAM,SAAS,SAASC,cAAS,CAAC;AACzC,IAAI,cAAc,GAAG;AACrB,QAAQ,EAAE,EAAE,SAAS;AACrB,QAAQ,OAAO,EAAE,OAAO;AACxB,QAAQ,MAAM,EAAE,SAAS;AACzB,KAAK;AACL,IAAI,MAAM,aAAa,GAAG;AAC1B,QAAQ,OAAO;AACf,YAAY,OAAO,EAAE,IAAI,CAAC,cAAc;AACxC,YAAY,QAAQ,EAAE,IAAI,CAAC,cAAc;AACzC,YAAY,MAAM,EAAE,IAAI;AACxB,YAAY,cAAc,EAAE,IAAI,CAAC,cAAc,CAAC,OAAO;AACvD,SAAS;AACT,IAAI;AACJ,IAAI,MAAM,KAAK,GAAG;AAClB,QAAQ,OAAO,CAAC,IAAI,CAAC,wCAAwC,CAAC;AAC9D,QAAQ,OAAO,IAAI;AACnB,IAAI;AACJ,IAAI,MAAM,QAAQ,GAAG;AACrB,QAAQ,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC;AACjE,QAAQ,OAAO,IAAI;AACnB,IAAI;AACJ,IAAI,MAAM,KAAK,GAAG;AAClB,QAAQ,OAAO,CAAC,IAAI,CAAC,wCAAwC,CAAC;AAC9D,QAAQ,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC;AACjE,IAAI;AACJ,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,OAAO,CAAC,IAAI,CAAC,8CAA8C,CAAC;AACpE,QAAQ,OAAO,IAAI;AACnB,IAAI;AACJ,IAAI,MAAM,aAAa,CAAC,QAAQ,EAAE;AAClC,QAAQ,OAAO,CAAC,IAAI,CAAC,iDAAiD,CAAC;AACvE,QAAQ,OAAO,IAAI;AACnB,IAAI;AACJ,IAAI,MAAM,cAAc,GAAG;AAC3B;AACA,IAAI;AACJ,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,OAAO,CAAC,IAAI,CAAC,8CAA8C,CAAC;AACpE,IAAI;AACJ,IAAI,MAAM,gBAAgB,GAAG;AAC7B,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE;AAC9B,IAAI;AACJ,IAAI,MAAM,iBAAiB,CAAC,QAAQ,EAAE;AACtC,QAAQ,OAAO,CAAC,IAAI,CAAC,qDAAqD,CAAC;AAC3E,IAAI;AACJ,IAAI,MAAM,mBAAmB,GAAG;AAChC,QAAQ,OAAO,IAAI;AACnB,IAAI;AACJ;;;;;;;;;"}
package/dist/plugin.js ADDED
@@ -0,0 +1,168 @@
1
+ var capacitorUpdater = (function (exports, core) {
2
+ 'use strict';
3
+
4
+ /**
5
+ * Bundle status enum.
6
+ */
7
+ exports.BundleStatus = void 0;
8
+ (function (BundleStatus) {
9
+ /** Factory-installed bundle */
10
+ BundleStatus["BUILTIN"] = "builtin";
11
+ /** Downloaded and staged, awaiting activation */
12
+ BundleStatus["PENDING"] = "pending";
13
+ /** Active but not yet confirmed */
14
+ BundleStatus["TRIAL"] = "trial";
15
+ /** Confirmed working */
16
+ BundleStatus["SUCCESS"] = "success";
17
+ /** Failed (hash mismatch, extraction error, rollback) */
18
+ BundleStatus["ERROR"] = "error";
19
+ })(exports.BundleStatus || (exports.BundleStatus = {}));
20
+
21
+ const NativeOtaKit = core.registerPlugin('OtaKit', {
22
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.OtaKitWeb()),
23
+ });
24
+ /**
25
+ * Check if result is an empty object (iOS returns {} instead of null)
26
+ */
27
+ function isEmptyObject(obj) {
28
+ return obj !== null && typeof obj === 'object' && Object.keys(obj).length === 0;
29
+ }
30
+ /**
31
+ * Wrapped plugin that normalizes null returns from native code.
32
+ * iOS call.resolve() without arguments returns {} instead of null.
33
+ */
34
+ function normalizeNullable(value) {
35
+ return isEmptyObject(value) ? null : value;
36
+ }
37
+ function toPublicState(value) {
38
+ return {
39
+ current: value.current,
40
+ staged: value.staged,
41
+ builtinVersion: value.builtinVersion,
42
+ };
43
+ }
44
+ async function getState() {
45
+ return toPublicState(await NativeOtaKit.debugGetState());
46
+ }
47
+ async function check() {
48
+ return normalizeNullable(await NativeOtaKit.check());
49
+ }
50
+ async function download() {
51
+ return normalizeNullable(await NativeOtaKit.download());
52
+ }
53
+ function apply() {
54
+ return NativeOtaKit.apply();
55
+ }
56
+ async function update() {
57
+ const state = await getState();
58
+ let latest;
59
+ try {
60
+ latest = await check();
61
+ }
62
+ catch (error) {
63
+ if (state.staged) {
64
+ await apply();
65
+ return;
66
+ }
67
+ throw error;
68
+ }
69
+ if (latest) {
70
+ if (latest.downloaded) {
71
+ await apply();
72
+ return;
73
+ }
74
+ const bundle = await download();
75
+ if (bundle) {
76
+ await apply();
77
+ }
78
+ return;
79
+ }
80
+ if (state.staged) {
81
+ await apply();
82
+ }
83
+ }
84
+ const OtaKit = {
85
+ getState,
86
+ check,
87
+ download,
88
+ apply,
89
+ update,
90
+ notifyAppReady: () => NativeOtaKit.notifyAppReady(),
91
+ debug: {
92
+ check: async (options) => normalizeNullable(await NativeOtaKit.debugCheck(options)),
93
+ download: async (options) => normalizeNullable(await NativeOtaKit.debugDownload(options)),
94
+ reset: () => NativeOtaKit.debugReset(),
95
+ listBundles: () => NativeOtaKit.debugListBundles(),
96
+ deleteBundle: (options) => NativeOtaKit.debugDeleteBundle(options),
97
+ getLastFailure: async () => normalizeNullable(await NativeOtaKit.debugGetLastFailure()),
98
+ },
99
+ addListener: NativeOtaKit.addListener.bind(NativeOtaKit),
100
+ removeAllListeners: () => NativeOtaKit.removeAllListeners(),
101
+ };
102
+
103
+ /**
104
+ * Web implementation of the native bridge.
105
+ * Most methods are no-ops since OTA updates don't apply to web
106
+ */
107
+ class OtaKitWeb extends core.WebPlugin {
108
+ BUILTIN_BUNDLE = {
109
+ id: 'builtin',
110
+ version: '0.0.0',
111
+ status: 'builtin',
112
+ };
113
+ async debugGetState() {
114
+ return {
115
+ current: this.BUILTIN_BUNDLE,
116
+ fallback: this.BUILTIN_BUNDLE,
117
+ staged: null,
118
+ builtinVersion: this.BUILTIN_BUNDLE.version,
119
+ };
120
+ }
121
+ async check() {
122
+ console.warn('OtaKit.check() is not supported on web');
123
+ return null;
124
+ }
125
+ async download() {
126
+ console.warn('OtaKit.download() is not supported on web');
127
+ return null;
128
+ }
129
+ async apply() {
130
+ console.warn('OtaKit.apply() is not supported on web');
131
+ throw new Error('OtaKit.apply() is not supported on web');
132
+ }
133
+ async debugCheck() {
134
+ console.warn('OtaKit.debug.check() is not supported on web');
135
+ return null;
136
+ }
137
+ async debugDownload(_options) {
138
+ console.warn('OtaKit.debug.download() is not supported on web');
139
+ return null;
140
+ }
141
+ async notifyAppReady() {
142
+ // No-op on web, but don't warn - apps should call this unconditionally
143
+ }
144
+ async debugReset() {
145
+ console.warn('OtaKit.debug.reset() is not supported on web');
146
+ }
147
+ async debugListBundles() {
148
+ return { bundles: [] };
149
+ }
150
+ async debugDeleteBundle(_options) {
151
+ console.warn('OtaKit.debug.deleteBundle() is not supported on web');
152
+ }
153
+ async debugGetLastFailure() {
154
+ return null;
155
+ }
156
+ }
157
+
158
+ var web = /*#__PURE__*/Object.freeze({
159
+ __proto__: null,
160
+ OtaKitWeb: OtaKitWeb
161
+ });
162
+
163
+ exports.OtaKit = OtaKit;
164
+
165
+ return exports;
166
+
167
+ })({}, capacitorExports);
168
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +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}\nfunction toPublicState(value) {\n return {\n current: value.current,\n staged: value.staged,\n builtinVersion: value.builtinVersion,\n };\n}\nasync function getState() {\n return toPublicState(await NativeOtaKit.debugGetState());\n}\nasync function check() {\n return normalizeNullable(await NativeOtaKit.check());\n}\nasync function download() {\n return normalizeNullable(await NativeOtaKit.download());\n}\nfunction apply() {\n return NativeOtaKit.apply();\n}\nasync function update() {\n const state = await getState();\n let latest;\n try {\n latest = await check();\n }\n catch (error) {\n if (state.staged) {\n await apply();\n return;\n }\n throw error;\n }\n if (latest) {\n if (latest.downloaded) {\n await apply();\n return;\n }\n const bundle = await download();\n if (bundle) {\n await apply();\n }\n return;\n }\n if (state.staged) {\n await apply();\n }\n}\nconst OtaKit = {\n getState,\n check,\n download,\n apply,\n update,\n notifyAppReady: () => NativeOtaKit.notifyAppReady(),\n debug: {\n check: async (options) => normalizeNullable(await NativeOtaKit.debugCheck(options)),\n download: async (options) => normalizeNullable(await NativeOtaKit.debugDownload(options)),\n reset: () => NativeOtaKit.debugReset(),\n listBundles: () => NativeOtaKit.debugListBundles(),\n deleteBundle: (options) => NativeOtaKit.debugDeleteBundle(options),\n getLastFailure: async () => normalizeNullable(await NativeOtaKit.debugGetLastFailure()),\n },\n addListener: NativeOtaKit.addListener.bind(NativeOtaKit),\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 debugGetState() {\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 null;\n }\n async download() {\n console.warn('OtaKit.download() is not supported on web');\n return null;\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 debugCheck() {\n console.warn('OtaKit.debug.check() is not supported on web');\n return null;\n }\n async debugDownload(_options) {\n console.warn('OtaKit.debug.download() is not supported on web');\n return null;\n }\n async notifyAppReady() {\n // No-op on web, but don't warn - apps should call this unconditionally\n }\n async debugReset() {\n console.warn('OtaKit.debug.reset() is not supported on web');\n }\n async debugListBundles() {\n return { bundles: [] };\n }\n async debugDeleteBundle(_options) {\n console.warn('OtaKit.debug.deleteBundle() is not supported on web');\n }\n async debugGetLastFailure() {\n return null;\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["BundleStatus","registerPlugin","WebPlugin"],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import Foundation
2
+
3
+ struct BundleInfo: Codable {
4
+ let id: String
5
+ let version: String
6
+ let status: BundleStatus
7
+ let downloadedAt: Date?
8
+ let sha256: String?
9
+ let path: String?
10
+ let channel: String?
11
+ let releaseId: String?
12
+
13
+ var isBuiltin: Bool {
14
+ id == "builtin"
15
+ }
16
+
17
+ func toDictionary() -> [String: Any] {
18
+ var result: [String: Any] = [
19
+ "id": id,
20
+ "version": version,
21
+ "status": status.rawValue,
22
+ ]
23
+
24
+ if let downloadedAt {
25
+ result["downloadedAt"] = ISO8601DateFormatter().string(from: downloadedAt)
26
+ }
27
+ if let sha256 {
28
+ result["sha256"] = sha256
29
+ }
30
+ if let channel {
31
+ result["channel"] = channel
32
+ }
33
+ if let releaseId {
34
+ result["releaseId"] = releaseId
35
+ }
36
+
37
+ return result
38
+ }
39
+ }
@@ -0,0 +1,9 @@
1
+ import Foundation
2
+
3
+ enum BundleStatus: String, Codable {
4
+ case builtin
5
+ case pending
6
+ case trial
7
+ case success
8
+ case error
9
+ }
@@ -0,0 +1,233 @@
1
+ import Foundation
2
+
3
+ final class BundleStore {
4
+ private enum Keys {
5
+ static let currentBundleId = "otakit_current_bundle_id"
6
+ static let fallbackBundleId = "otakit_fallback_bundle_id"
7
+ static let stagedBundleId = "otakit_staged_bundle_id"
8
+ static let failedBundleInfo = "otakit_failed_bundle_info"
9
+ }
10
+
11
+ private let defaults = UserDefaults.standard
12
+ private let fileManager = FileManager.default
13
+
14
+ private lazy var decoder: JSONDecoder = {
15
+ let decoder = JSONDecoder()
16
+ decoder.dateDecodingStrategy = .iso8601
17
+ return decoder
18
+ }()
19
+
20
+ private lazy var encoder: JSONEncoder = {
21
+ let encoder = JSONEncoder()
22
+ encoder.dateEncodingStrategy = .iso8601
23
+ return encoder
24
+ }()
25
+
26
+ private(set) lazy var bundlesDirectory: URL = {
27
+ let appSupport = fileManager.urls(
28
+ for: .applicationSupportDirectory,
29
+ in: .userDomainMask
30
+ ).first!
31
+ let directory = appSupport.appendingPathComponent(
32
+ "otakit_bundles",
33
+ isDirectory: true
34
+ )
35
+ try? fileManager.createDirectory(
36
+ at: directory,
37
+ withIntermediateDirectories: true
38
+ )
39
+ return directory
40
+ }()
41
+
42
+ var builtinVersion: String {
43
+ Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
44
+ }
45
+
46
+ var nativeBuild: String {
47
+ Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1"
48
+ }
49
+
50
+ func builtinBundle() -> BundleInfo {
51
+ BundleInfo(
52
+ id: "builtin",
53
+ version: builtinVersion,
54
+ status: .builtin,
55
+ downloadedAt: nil,
56
+ sha256: nil,
57
+ path: nil,
58
+ channel: nil,
59
+ releaseId: nil
60
+ )
61
+ }
62
+
63
+ func bundleDirectory(for id: String) -> URL {
64
+ bundlesDirectory.appendingPathComponent(id, isDirectory: true)
65
+ }
66
+
67
+ private func metadataURL(for id: String) -> URL {
68
+ bundleDirectory(for: id).appendingPathComponent("bundle.json")
69
+ }
70
+
71
+ func bundlePath(id: String) -> String? {
72
+ guard let info = getBundle(id: id) else {
73
+ return nil
74
+ }
75
+ return info.path
76
+ }
77
+
78
+ func bundleExists(id: String) -> Bool {
79
+ if id == "builtin" {
80
+ return true
81
+ }
82
+ return getBundle(id: id) != nil
83
+ }
84
+
85
+ func saveBundle(_ bundle: BundleInfo) throws {
86
+ guard !bundle.isBuiltin else {
87
+ return
88
+ }
89
+
90
+ let directory = bundleDirectory(for: bundle.id)
91
+ try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
92
+ let data = try encoder.encode(bundle)
93
+ try data.write(to: metadataURL(for: bundle.id), options: .atomic)
94
+ }
95
+
96
+ func getBundle(id: String) -> BundleInfo? {
97
+ if id == "builtin" {
98
+ return builtinBundle()
99
+ }
100
+
101
+ let metadata = metadataURL(for: id)
102
+ guard let data = try? Data(contentsOf: metadata) else {
103
+ return nil
104
+ }
105
+ return try? decoder.decode(BundleInfo.self, from: data)
106
+ }
107
+
108
+ func listDownloadedBundles() -> [BundleInfo] {
109
+ guard
110
+ let ids = try? fileManager.contentsOfDirectory(
111
+ atPath: bundlesDirectory.path
112
+ )
113
+ else {
114
+ return []
115
+ }
116
+
117
+ var bundles = ids.compactMap { getBundle(id: $0) }
118
+ bundles.sort { lhs, rhs in
119
+ switch (lhs.downloadedAt, rhs.downloadedAt) {
120
+ case let (left?, right?):
121
+ return left > right
122
+ case (.some, .none):
123
+ return true
124
+ case (.none, .some):
125
+ return false
126
+ case (.none, .none):
127
+ return lhs.id < rhs.id
128
+ }
129
+ }
130
+ return bundles
131
+ }
132
+
133
+ func getCurrentBundle() -> BundleInfo {
134
+ guard
135
+ let bundleId = defaults.string(forKey: Keys.currentBundleId),
136
+ let bundle = getBundle(id: bundleId)
137
+ else {
138
+ return builtinBundle()
139
+ }
140
+ return bundle
141
+ }
142
+
143
+ func setCurrentBundleId(_ id: String?) {
144
+ if let id {
145
+ defaults.set(id, forKey: Keys.currentBundleId)
146
+ } else {
147
+ defaults.removeObject(forKey: Keys.currentBundleId)
148
+ }
149
+ }
150
+
151
+ func getFallbackBundle() -> BundleInfo {
152
+ guard
153
+ let bundleId = defaults.string(forKey: Keys.fallbackBundleId),
154
+ let bundle = getBundle(id: bundleId)
155
+ else {
156
+ return builtinBundle()
157
+ }
158
+ return bundle
159
+ }
160
+
161
+ func setFallbackBundleId(_ id: String?) {
162
+ if let id {
163
+ defaults.set(id, forKey: Keys.fallbackBundleId)
164
+ } else {
165
+ defaults.removeObject(forKey: Keys.fallbackBundleId)
166
+ }
167
+ }
168
+
169
+ func getStagedBundleId() -> String? {
170
+ defaults.string(forKey: Keys.stagedBundleId)
171
+ }
172
+
173
+ func setStagedBundleId(_ id: String?) {
174
+ if let id {
175
+ defaults.set(id, forKey: Keys.stagedBundleId)
176
+ } else {
177
+ defaults.removeObject(forKey: Keys.stagedBundleId)
178
+ }
179
+ }
180
+
181
+ func setFailedBundle(_ bundle: BundleInfo?) {
182
+ if let bundle {
183
+ if let data = try? encoder.encode(bundle) {
184
+ defaults.set(data, forKey: Keys.failedBundleInfo)
185
+ }
186
+ } else {
187
+ defaults.removeObject(forKey: Keys.failedBundleInfo)
188
+ }
189
+ }
190
+
191
+ func getFailedBundle() -> BundleInfo? {
192
+ guard let data = defaults.data(forKey: Keys.failedBundleInfo) else {
193
+ return nil
194
+ }
195
+ return try? decoder.decode(BundleInfo.self, from: data)
196
+ }
197
+
198
+ func markStatus(bundleId: String, status: BundleStatus) {
199
+ guard var bundle = getBundle(id: bundleId) else {
200
+ return
201
+ }
202
+ bundle = BundleInfo(
203
+ id: bundle.id,
204
+ version: bundle.version,
205
+ status: status,
206
+ downloadedAt: bundle.downloadedAt,
207
+ sha256: bundle.sha256,
208
+ path: bundle.path,
209
+ channel: bundle.channel,
210
+ releaseId: bundle.releaseId
211
+ )
212
+ try? saveBundle(bundle)
213
+ }
214
+
215
+ func deleteBundle(id: String) throws {
216
+ guard id != "builtin" else {
217
+ return
218
+ }
219
+
220
+ try fileManager.removeItem(at: bundleDirectory(for: id))
221
+
222
+ if getStagedBundleId() == id {
223
+ setStagedBundleId(nil)
224
+ }
225
+ if defaults.string(forKey: Keys.currentBundleId) == id {
226
+ setCurrentBundleId(nil)
227
+ }
228
+ if defaults.string(forKey: Keys.fallbackBundleId) == id {
229
+ setFallbackBundleId(nil)
230
+ }
231
+ }
232
+
233
+ }