@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.
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/UpdatekitUpdater.podspec +18 -0
- package/android/build.gradle +49 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/updatekit/updater/BundleInfo.java +100 -0
- package/android/src/main/java/com/updatekit/updater/BundleStatus.java +28 -0
- package/android/src/main/java/com/updatekit/updater/BundleStore.java +264 -0
- package/android/src/main/java/com/updatekit/updater/DateUtils.java +17 -0
- package/android/src/main/java/com/updatekit/updater/HashUtils.java +32 -0
- package/android/src/main/java/com/updatekit/updater/HostedManifestKeys.java +37 -0
- package/android/src/main/java/com/updatekit/updater/ManifestClient.java +209 -0
- package/android/src/main/java/com/updatekit/updater/ManifestVerifier.java +146 -0
- package/android/src/main/java/com/updatekit/updater/StatsClient.java +80 -0
- package/android/src/main/java/com/updatekit/updater/UpdaterPlugin.java +963 -0
- package/android/src/main/java/com/updatekit/updater/ZipUtils.java +72 -0
- package/dist/esm/definitions.d.ts +229 -0
- package/dist/esm/definitions.d.ts.map +1 -0
- package/dist/esm/definitions.js +17 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +85 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +25 -0
- package/dist/esm/web.d.ts.map +1 -0
- package/dist/esm/web.js +56 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +165 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +168 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/UpdaterPlugin/BundleInfo.swift +39 -0
- package/ios/Sources/UpdaterPlugin/BundleStatus.swift +9 -0
- package/ios/Sources/UpdaterPlugin/BundleStore.swift +233 -0
- package/ios/Sources/UpdaterPlugin/Downloader.swift +120 -0
- package/ios/Sources/UpdaterPlugin/HashUtils.swift +33 -0
- package/ios/Sources/UpdaterPlugin/HostedManifestKeys.swift +23 -0
- package/ios/Sources/UpdaterPlugin/ManifestClient.swift +161 -0
- package/ios/Sources/UpdaterPlugin/ManifestVerifier.swift +115 -0
- package/ios/Sources/UpdaterPlugin/StatsClient.swift +66 -0
- package/ios/Sources/UpdaterPlugin/UpdaterPlugin.m +15 -0
- package/ios/Sources/UpdaterPlugin/UpdaterPlugin.swift +913 -0
- package/ios/Sources/UpdaterPlugin/ZipUtils.swift +90 -0
- 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":"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"}
|
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,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
|
+
}
|