@otakit/capacitor-updater 2.0.1 → 2.1.1

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 (37) hide show
  1. package/{UpdatekitUpdater.podspec → OtaKitUpdater.podspec} +1 -1
  2. package/README.md +165 -130
  3. package/android/build.gradle +1 -1
  4. package/android/src/main/java/com/{updatekit → otakit}/updater/BundleInfo.java +1 -1
  5. package/android/src/main/java/com/{updatekit → otakit}/updater/BundleStatus.java +1 -1
  6. package/android/src/main/java/com/{updatekit → otakit}/updater/BundleStore.java +38 -9
  7. package/android/src/main/java/com/{updatekit → otakit}/updater/DateUtils.java +1 -1
  8. package/android/src/main/java/com/{updatekit → otakit}/updater/DeviceEventClient.java +3 -4
  9. package/android/src/main/java/com/{updatekit → otakit}/updater/HashUtils.java +1 -1
  10. package/android/src/main/java/com/{updatekit → otakit}/updater/HostedManifestKeys.java +1 -1
  11. package/android/src/main/java/com/{updatekit → otakit}/updater/ManifestClient.java +32 -15
  12. package/android/src/main/java/com/{updatekit → otakit}/updater/ManifestVerifier.java +1 -1
  13. package/android/src/main/java/com/otakit/updater/UpdaterCoordinator.java +726 -0
  14. package/android/src/main/java/com/otakit/updater/UpdaterPlugin.java +1191 -0
  15. package/android/src/main/java/com/{updatekit → otakit}/updater/ZipUtils.java +1 -1
  16. package/dist/esm/definitions.d.ts +48 -105
  17. package/dist/esm/definitions.d.ts.map +1 -1
  18. package/dist/esm/definitions.js.map +1 -1
  19. package/dist/esm/index.d.ts.map +1 -1
  20. package/dist/esm/index.js +6 -60
  21. package/dist/esm/index.js.map +1 -1
  22. package/dist/esm/web.d.ts +7 -11
  23. package/dist/esm/web.d.ts.map +1 -1
  24. package/dist/esm/web.js +8 -14
  25. package/dist/esm/web.js.map +1 -1
  26. package/dist/plugin.cjs.js +14 -74
  27. package/dist/plugin.cjs.js.map +1 -1
  28. package/dist/plugin.js +14 -74
  29. package/dist/plugin.js.map +1 -1
  30. package/ios/Sources/UpdaterPlugin/BundleStore.swift +28 -6
  31. package/ios/Sources/UpdaterPlugin/Downloader.swift +1 -1
  32. package/ios/Sources/UpdaterPlugin/ManifestClient.swift +8 -4
  33. package/ios/Sources/UpdaterPlugin/UpdaterCoordinator.swift +635 -0
  34. package/ios/Sources/UpdaterPlugin/UpdaterPlugin.m +3 -5
  35. package/ios/Sources/UpdaterPlugin/UpdaterPlugin.swift +506 -551
  36. package/package.json +2 -2
  37. package/android/src/main/java/com/updatekit/updater/UpdaterPlugin.java +0 -1191
package/dist/plugin.js CHANGED
@@ -34,73 +34,19 @@ var capacitorUpdater = (function (exports, core) {
34
34
  function normalizeNullable(value) {
35
35
  return isEmptyObject(value) ? null : value;
36
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
37
  const OtaKit = {
85
- getState,
86
- check,
87
- download,
88
- apply,
89
- update,
38
+ getState: () => NativeOtaKit.getState(),
39
+ check: () => NativeOtaKit.check(),
40
+ download: () => NativeOtaKit.download(),
41
+ apply: () => NativeOtaKit.apply(),
42
+ update: () => NativeOtaKit.update(),
90
43
  notifyAppReady: () => NativeOtaKit.notifyAppReady(),
91
- debug: {
92
- reset: () => NativeOtaKit.debugReset(),
93
- listBundles: () => NativeOtaKit.debugListBundles(),
94
- deleteBundle: (options) => NativeOtaKit.debugDeleteBundle(options),
95
- getLastFailure: async () => normalizeNullable(await NativeOtaKit.debugGetLastFailure()),
96
- },
97
- addListener: NativeOtaKit.addListener.bind(NativeOtaKit),
98
- removeAllListeners: () => NativeOtaKit.removeAllListeners(),
44
+ getLastFailure: async () => normalizeNullable(await NativeOtaKit.getLastFailure()),
99
45
  };
100
46
 
101
47
  /**
102
48
  * Web implementation of the native bridge.
103
- * Most methods are no-ops since OTA updates don't apply to web
49
+ * Most methods are no-ops since OTA updates don't apply to web.
104
50
  */
105
51
  class OtaKitWeb extends core.WebPlugin {
106
52
  BUILTIN_BUNDLE = {
@@ -108,7 +54,7 @@ var capacitorUpdater = (function (exports, core) {
108
54
  version: '0.0.0',
109
55
  status: 'builtin',
110
56
  };
111
- async debugGetState() {
57
+ async getState() {
112
58
  return {
113
59
  current: this.BUILTIN_BUNDLE,
114
60
  fallback: this.BUILTIN_BUNDLE,
@@ -118,29 +64,23 @@ var capacitorUpdater = (function (exports, core) {
118
64
  }
119
65
  async check() {
120
66
  console.warn('OtaKit.check() is not supported on web');
121
- return null;
67
+ return { kind: 'no_update' };
122
68
  }
123
69
  async download() {
124
70
  console.warn('OtaKit.download() is not supported on web');
125
- return null;
71
+ return { kind: 'no_update' };
126
72
  }
127
73
  async apply() {
128
74
  console.warn('OtaKit.apply() is not supported on web');
129
75
  throw new Error('OtaKit.apply() is not supported on web');
130
76
  }
77
+ async update() {
78
+ await this.download();
79
+ }
131
80
  async notifyAppReady() {
132
81
  // No-op on web, but don't warn - apps should call this unconditionally
133
82
  }
134
- async debugReset() {
135
- console.warn('OtaKit.debug.reset() is not supported on web');
136
- }
137
- async debugListBundles() {
138
- return { bundles: [] };
139
- }
140
- async debugDeleteBundle(_options) {
141
- console.warn('OtaKit.debug.deleteBundle() is not supported on web');
142
- }
143
- async debugGetLastFailure() {
83
+ async getLastFailure() {
144
84
  return null;
145
85
  }
146
86
  }
@@ -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}\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 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 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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"}
1
+ {"version":3,"file":"plugin.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["/**\n * Bundle status enum.\n */\nexport var BundleStatus;\n(function (BundleStatus) {\n /** Factory-installed bundle */\n BundleStatus[\"BUILTIN\"] = \"builtin\";\n /** Downloaded and staged, awaiting activation */\n BundleStatus[\"PENDING\"] = \"pending\";\n /** Active but not yet confirmed */\n BundleStatus[\"TRIAL\"] = \"trial\";\n /** Confirmed working */\n BundleStatus[\"SUCCESS\"] = \"success\";\n /** Failed (hash mismatch, extraction error, rollback) */\n BundleStatus[\"ERROR\"] = \"error\";\n})(BundleStatus || (BundleStatus = {}));\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nconst NativeOtaKit = registerPlugin('OtaKit', {\n web: () => import('./web').then((m) => new m.OtaKitWeb()),\n});\n/**\n * Check if result is an empty object (iOS returns {} instead of null)\n */\nfunction isEmptyObject(obj) {\n return obj !== null && typeof obj === 'object' && Object.keys(obj).length === 0;\n}\n/**\n * Wrapped plugin that normalizes null returns from native code.\n * iOS call.resolve() without arguments returns {} instead of null.\n */\nfunction normalizeNullable(value) {\n return isEmptyObject(value) ? null : value;\n}\nconst OtaKit = {\n getState: () => NativeOtaKit.getState(),\n check: () => NativeOtaKit.check(),\n download: () => NativeOtaKit.download(),\n apply: () => NativeOtaKit.apply(),\n update: () => NativeOtaKit.update(),\n notifyAppReady: () => NativeOtaKit.notifyAppReady(),\n getLastFailure: async () => normalizeNullable(await NativeOtaKit.getLastFailure()),\n};\nexport * from './definitions';\nexport { OtaKit };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/**\n * Web implementation of the native bridge.\n * Most methods are no-ops since OTA updates don't apply to web.\n */\nexport class OtaKitWeb extends WebPlugin {\n BUILTIN_BUNDLE = {\n id: 'builtin',\n version: '0.0.0',\n status: 'builtin',\n };\n async getState() {\n return {\n current: this.BUILTIN_BUNDLE,\n fallback: this.BUILTIN_BUNDLE,\n staged: null,\n builtinVersion: this.BUILTIN_BUNDLE.version,\n };\n }\n async check() {\n console.warn('OtaKit.check() is not supported on web');\n return { kind: 'no_update' };\n }\n async download() {\n console.warn('OtaKit.download() is not supported on web');\n return { kind: 'no_update' };\n }\n async apply() {\n console.warn('OtaKit.apply() is not supported on web');\n throw new Error('OtaKit.apply() is not supported on web');\n }\n async update() {\n await this.download();\n }\n async notifyAppReady() {\n // No-op on web, but don't warn - apps should call this unconditionally\n }\n async getLastFailure() {\n return null;\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["BundleStatus","registerPlugin","WebPlugin"],"mappings":";;;IAAA;IACA;IACA;AACWA;IACX,CAAC,UAAU,YAAY,EAAE;IACzB;IACA,IAAI,YAAY,CAAC,SAAS,CAAC,GAAG,SAAS;IACvC;IACA,IAAI,YAAY,CAAC,SAAS,CAAC,GAAG,SAAS;IACvC;IACA,IAAI,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO;IACnC;IACA,IAAI,YAAY,CAAC,SAAS,CAAC,GAAG,SAAS;IACvC;IACA,IAAI,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO;IACnC,CAAC,EAAEA,oBAAY,KAAKA,oBAAY,GAAG,EAAE,CAAC,CAAC;;ICdvC,MAAM,YAAY,GAAGC,mBAAc,CAAC,QAAQ,EAAE;IAC9C,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;IAC7D,CAAC,CAAC;IACF;IACA;IACA;IACA,SAAS,aAAa,CAAC,GAAG,EAAE;IAC5B,IAAI,OAAO,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC;IACnF;IACA;IACA;IACA;IACA;IACA,SAAS,iBAAiB,CAAC,KAAK,EAAE;IAClC,IAAI,OAAO,aAAa,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,KAAK;IAC9C;AACK,UAAC,MAAM,GAAG;IACf,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC,QAAQ,EAAE;IAC3C,IAAI,KAAK,EAAE,MAAM,YAAY,CAAC,KAAK,EAAE;IACrC,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC,QAAQ,EAAE;IAC3C,IAAI,KAAK,EAAE,MAAM,YAAY,CAAC,KAAK,EAAE;IACrC,IAAI,MAAM,EAAE,MAAM,YAAY,CAAC,MAAM,EAAE;IACvC,IAAI,cAAc,EAAE,MAAM,YAAY,CAAC,cAAc,EAAE;IACvD,IAAI,cAAc,EAAE,YAAY,iBAAiB,CAAC,MAAM,YAAY,CAAC,cAAc,EAAE,CAAC;IACtF;;ICxBA;IACA;IACA;IACA;IACO,MAAM,SAAS,SAASC,cAAS,CAAC;IACzC,IAAI,cAAc,GAAG;IACrB,QAAQ,EAAE,EAAE,SAAS;IACrB,QAAQ,OAAO,EAAE,OAAO;IACxB,QAAQ,MAAM,EAAE,SAAS;IACzB,KAAK;IACL,IAAI,MAAM,QAAQ,GAAG;IACrB,QAAQ,OAAO;IACf,YAAY,OAAO,EAAE,IAAI,CAAC,cAAc;IACxC,YAAY,QAAQ,EAAE,IAAI,CAAC,cAAc;IACzC,YAAY,MAAM,EAAE,IAAI;IACxB,YAAY,cAAc,EAAE,IAAI,CAAC,cAAc,CAAC,OAAO;IACvD,SAAS;IACT,IAAI;IACJ,IAAI,MAAM,KAAK,GAAG;IAClB,QAAQ,OAAO,CAAC,IAAI,CAAC,wCAAwC,CAAC;IAC9D,QAAQ,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE;IACpC,IAAI;IACJ,IAAI,MAAM,QAAQ,GAAG;IACrB,QAAQ,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC;IACjE,QAAQ,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE;IACpC,IAAI;IACJ,IAAI,MAAM,KAAK,GAAG;IAClB,QAAQ,OAAO,CAAC,IAAI,CAAC,wCAAwC,CAAC;IAC9D,QAAQ,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC;IACjE,IAAI;IACJ,IAAI,MAAM,MAAM,GAAG;IACnB,QAAQ,MAAM,IAAI,CAAC,QAAQ,EAAE;IAC7B,IAAI;IACJ,IAAI,MAAM,cAAc,GAAG;IAC3B;IACA,IAAI;IACJ,IAAI,MAAM,cAAc,GAAG;IAC3B,QAAQ,OAAO,IAAI;IACnB,IAAI;IACJ;;;;;;;;;;;;;;;"}
@@ -5,7 +5,8 @@ final class BundleStore {
5
5
  static let currentBundleId = "otakit_current_bundle_id"
6
6
  static let fallbackBundleId = "otakit_fallback_bundle_id"
7
7
  static let stagedBundleId = "otakit_staged_bundle_id"
8
- static let failedBundleInfo = "otakit_failed_bundle_info"
8
+ static let lastFailedBundleInfo = "otakit_last_failed_bundle_info"
9
+ static let lastResolvedRuntimeKey = "otakit_last_resolved_runtime_key"
9
10
  }
10
11
 
11
12
  private let defaults = UserDefaults.standard
@@ -142,6 +143,10 @@ final class BundleStore {
142
143
  return bundle
143
144
  }
144
145
 
146
+ func getCurrentBundleId() -> String? {
147
+ defaults.string(forKey: Keys.currentBundleId)
148
+ }
149
+
145
150
  func setCurrentBundleId(_ id: String?) {
146
151
  if let id {
147
152
  defaults.set(id, forKey: Keys.currentBundleId)
@@ -160,6 +165,10 @@ final class BundleStore {
160
165
  return bundle
161
166
  }
162
167
 
168
+ func getFallbackBundleId() -> String? {
169
+ defaults.string(forKey: Keys.fallbackBundleId)
170
+ }
171
+
163
172
  func setFallbackBundleId(_ id: String?) {
164
173
  if let id {
165
174
  defaults.set(id, forKey: Keys.fallbackBundleId)
@@ -180,23 +189,36 @@ final class BundleStore {
180
189
  }
181
190
  }
182
191
 
183
- func setFailedBundle(_ bundle: BundleInfo?) {
192
+ func setLastFailedBundle(_ bundle: BundleInfo?) {
184
193
  if let bundle {
185
194
  if let data = try? encoder.encode(bundle) {
186
- defaults.set(data, forKey: Keys.failedBundleInfo)
195
+ defaults.set(data, forKey: Keys.lastFailedBundleInfo)
187
196
  }
188
197
  } else {
189
- defaults.removeObject(forKey: Keys.failedBundleInfo)
198
+ defaults.removeObject(forKey: Keys.lastFailedBundleInfo)
190
199
  }
191
200
  }
192
201
 
193
- func getFailedBundle() -> BundleInfo? {
194
- guard let data = defaults.data(forKey: Keys.failedBundleInfo) else {
202
+ func getLastFailedBundle() -> BundleInfo? {
203
+ guard let data = defaults.data(forKey: Keys.lastFailedBundleInfo) else {
195
204
  return nil
196
205
  }
197
206
  return try? decoder.decode(BundleInfo.self, from: data)
198
207
  }
199
208
 
209
+ func getLastResolvedRuntimeKey() -> String? {
210
+ defaults.string(forKey: Keys.lastResolvedRuntimeKey)
211
+ }
212
+
213
+ func setLastResolvedRuntimeKey(_ runtimeKey: String?) {
214
+ if let runtimeKey {
215
+ defaults.set(runtimeKey, forKey: Keys.lastResolvedRuntimeKey)
216
+ } else {
217
+ // nil clears the key so the next cold start is treated as unresolved again
218
+ defaults.removeObject(forKey: Keys.lastResolvedRuntimeKey)
219
+ }
220
+ }
221
+
200
222
  func markStatus(bundleId: String, status: BundleStatus) {
201
223
  guard var bundle = getBundle(id: bundleId) else {
202
224
  return
@@ -78,7 +78,7 @@ private final class DownloadDelegate: NSObject, URLSessionDownloadDelegate {
78
78
  }
79
79
 
80
80
  let temporaryZip = FileManager.default.temporaryDirectory
81
- .appendingPathComponent("updatekit-\(UUID().uuidString).zip")
81
+ .appendingPathComponent("otakit-\(UUID().uuidString).zip")
82
82
 
83
83
  do {
84
84
  try FileManager.default.moveItem(at: location, to: temporaryZip)
@@ -59,11 +59,15 @@ enum ManifestClient {
59
59
  let runtimeKey = runtimeVersion?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty
60
60
  ?? defaultRuntimeKey
61
61
 
62
- guard let url = URL(
63
- string: "\(sanitizedBase)/manifests/\(appId)/\(channelKey)/\(runtimeKey)/manifest.json"
64
- ) else {
62
+ guard let baseURL = URL(string: sanitizedBase) else {
65
63
  throw ManifestClientError.invalidURL
66
64
  }
65
+ let url = baseURL
66
+ .appendingPathComponent("manifests", isDirectory: true)
67
+ .appendingPathComponent(appId, isDirectory: true)
68
+ .appendingPathComponent(channelKey, isDirectory: true)
69
+ .appendingPathComponent(runtimeKey, isDirectory: true)
70
+ .appendingPathComponent("manifest.json", isDirectory: false)
67
71
 
68
72
  try requireHTTPS(url: url, allowInsecure: allowInsecureUrls)
69
73
 
@@ -114,7 +118,7 @@ enum ManifestClient {
114
118
  try requireHTTPS(url: dlURL, allowInsecure: allowInsecureUrls)
115
119
 
116
120
  if manifestKeys.isEmpty {
117
- print("[UpdateKit] WARNING: No manifest signing keys configured — signature verification is disabled for this request.")
121
+ print("[OtaKit] WARNING: No manifest signing keys configured — signature verification is disabled for this request.")
118
122
  }
119
123
 
120
124
  if !manifestKeys.isEmpty {