@otakit/capacitor-updater 1.2.0 → 2.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/README.md +21 -14
- package/android/src/main/java/com/updatekit/updater/HostedManifestKeys.java +5 -6
- package/android/src/main/java/com/updatekit/updater/ManifestClient.java +26 -46
- package/android/src/main/java/com/updatekit/updater/ManifestVerifier.java +1 -75
- package/android/src/main/java/com/updatekit/updater/UpdaterPlugin.java +104 -70
- package/dist/esm/definitions.d.ts +5 -23
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +0 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +0 -4
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +0 -8
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +0 -10
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +0 -10
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/UpdaterPlugin/HostedManifestKeys.swift +5 -5
- package/ios/Sources/UpdaterPlugin/ManifestClient.swift +26 -50
- package/ios/Sources/UpdaterPlugin/ManifestVerifier.swift +2 -56
- package/ios/Sources/UpdaterPlugin/UpdaterPlugin.m +0 -2
- package/ios/Sources/UpdaterPlugin/UpdaterPlugin.swift +90 -67
- package/package.json +1 -1
|
@@ -23,8 +23,6 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
23
23
|
CAPPluginMethod(name: "download", returnType: CAPPluginReturnPromise),
|
|
24
24
|
CAPPluginMethod(name: "apply", returnType: CAPPluginReturnPromise),
|
|
25
25
|
CAPPluginMethod(name: "debugGetState", returnType: CAPPluginReturnPromise),
|
|
26
|
-
CAPPluginMethod(name: "debugCheck", returnType: CAPPluginReturnPromise),
|
|
27
|
-
CAPPluginMethod(name: "debugDownload", returnType: CAPPluginReturnPromise),
|
|
28
26
|
CAPPluginMethod(name: "notifyAppReady", returnType: CAPPluginReturnPromise),
|
|
29
27
|
CAPPluginMethod(name: "debugReset", returnType: CAPPluginReturnPromise),
|
|
30
28
|
CAPPluginMethod(name: "debugListBundles", returnType: CAPPluginReturnPromise),
|
|
@@ -41,6 +39,7 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
41
39
|
private var allowInsecureUrls = false
|
|
42
40
|
private var updateMode: UpdateMode = .nextLaunch
|
|
43
41
|
private var updateUrl = UpdaterPlugin.defaultUpdateURL
|
|
42
|
+
private var cdnUrl = UpdaterPlugin.defaultCdnURL
|
|
44
43
|
private var appId: String?
|
|
45
44
|
private var channel: String?
|
|
46
45
|
private var runtimeVersion: String?
|
|
@@ -51,15 +50,22 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
51
50
|
private var checkInProgress = false
|
|
52
51
|
private var foregroundObserver: NSObjectProtocol?
|
|
53
52
|
private static let defaultUpdateURL = "https://www.otakit.app/api/v1"
|
|
53
|
+
private static let defaultCdnURL = "https://cdn.otakit.app"
|
|
54
54
|
private static let apiPathSuffix = "/api/v1"
|
|
55
55
|
private static let lastCheckTimestampKey = "otakit_last_check_timestamp"
|
|
56
56
|
|
|
57
57
|
public override func load() {
|
|
58
58
|
let envUpdateUrl = ProcessInfo.processInfo.environment["OTAKIT_SERVER_URL"]
|
|
59
|
+
let envCdnUrl = ProcessInfo.processInfo.environment["OTAKIT_CDN_URL"]
|
|
59
60
|
updateUrl = resolveUpdateUrl(
|
|
60
61
|
configured: getConfig().getString("serverUrl"),
|
|
61
62
|
env: envUpdateUrl
|
|
62
63
|
)
|
|
64
|
+
cdnUrl = resolveCdnUrl(
|
|
65
|
+
configured: getConfig().getString("cdnUrl"),
|
|
66
|
+
env: envCdnUrl,
|
|
67
|
+
resolvedUpdateUrl: updateUrl
|
|
68
|
+
)
|
|
63
69
|
appId = getConfig().getString("appId")
|
|
64
70
|
channel = trimToNil(getConfig().getString("channel"))
|
|
65
71
|
runtimeVersion = trimToNil(getConfig().getString("runtimeVersion"))
|
|
@@ -88,7 +94,7 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
88
94
|
manifestKeys = [(kid: "_invalid_", key: Data())]
|
|
89
95
|
}
|
|
90
96
|
|
|
91
|
-
if manifestKeys.isEmpty && HostedManifestKeys.
|
|
97
|
+
if manifestKeys.isEmpty && HostedManifestKeys.matchesManagedManifestURL(cdnUrl) {
|
|
92
98
|
manifestKeys = HostedManifestKeys.defaults
|
|
93
99
|
}
|
|
94
100
|
|
|
@@ -154,7 +160,11 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
154
160
|
return
|
|
155
161
|
}
|
|
156
162
|
|
|
157
|
-
if shouldThrottleCheck() { return }
|
|
163
|
+
if updateMode != .immediate && shouldThrottleCheck() { return }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if trigger == .launch && updateMode != .immediate && shouldThrottleCheck() {
|
|
167
|
+
return
|
|
158
168
|
}
|
|
159
169
|
|
|
160
170
|
// Acquire in-flight guard (submit-time)
|
|
@@ -166,7 +176,6 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
166
176
|
defer { releaseCheckInProgress() }
|
|
167
177
|
do {
|
|
168
178
|
let result = try await performCheckAndDownload(channel: nil, emitEvents: true)
|
|
169
|
-
recordCheckTimestamp()
|
|
170
179
|
if result != nil {
|
|
171
180
|
activateStagedBundleForReload()
|
|
172
181
|
reloadWebView()
|
|
@@ -184,7 +193,6 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
184
193
|
defer { releaseCheckInProgress() }
|
|
185
194
|
do {
|
|
186
195
|
let result = try await performCheckAndDownload(channel: nil, emitEvents: true)
|
|
187
|
-
recordCheckTimestamp()
|
|
188
196
|
if result != nil {
|
|
189
197
|
activateStagedBundleForReload()
|
|
190
198
|
reloadWebView()
|
|
@@ -282,27 +290,8 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
282
290
|
call.resolve(payload)
|
|
283
291
|
}
|
|
284
292
|
|
|
285
|
-
@objc func debugCheck(_ call: CAPPluginCall) {
|
|
286
|
-
let requestedChannel = call.getString("channel")
|
|
287
|
-
let targetChannel = resolveTargetChannel(requestedChannel)
|
|
288
|
-
Task {
|
|
289
|
-
do {
|
|
290
|
-
let latest = try await fetchLatest(channel: targetChannel)
|
|
291
|
-
if let latest {
|
|
292
|
-
let staged = findMatchingStagedBundle(latest: latest, targetChannel: targetChannel)
|
|
293
|
-
call.resolve(manifestToDictionary(latest, downloaded: staged != nil))
|
|
294
|
-
} else {
|
|
295
|
-
call.resolve()
|
|
296
|
-
}
|
|
297
|
-
} catch {
|
|
298
|
-
call.reject("debugCheck failed: \(error.localizedDescription)")
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
293
|
@objc func check(_ call: CAPPluginCall) {
|
|
304
|
-
|
|
305
|
-
if shouldThrottleCheck() || !claimCheckInProgress() {
|
|
294
|
+
if !claimCheckInProgress() {
|
|
306
295
|
if let stagedId = store.getStagedBundleId(),
|
|
307
296
|
let staged = store.getBundle(id: stagedId) {
|
|
308
297
|
var payload: [String: Any] = [
|
|
@@ -329,9 +318,11 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
329
318
|
defer { releaseCheckInProgress() }
|
|
330
319
|
do {
|
|
331
320
|
let latest = try await fetchLatest(channel: targetChannel)
|
|
332
|
-
// check() does NOT record timestamp — only download() and automatic paths do.
|
|
333
|
-
// This keeps check() -> download() working without the throttle blocking download().
|
|
334
321
|
if let latest {
|
|
322
|
+
if isCurrentBundleLatest(latest: latest, targetChannel: targetChannel) {
|
|
323
|
+
call.resolve()
|
|
324
|
+
return
|
|
325
|
+
}
|
|
335
326
|
let staged = findMatchingStagedBundle(latest: latest, targetChannel: targetChannel)
|
|
336
327
|
call.resolve(manifestToDictionary(latest, downloaded: staged != nil))
|
|
337
328
|
} else {
|
|
@@ -343,28 +334,8 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
343
334
|
}
|
|
344
335
|
}
|
|
345
336
|
|
|
346
|
-
@objc func debugDownload(_ call: CAPPluginCall) {
|
|
347
|
-
let requestedChannel = call.getString("channel")
|
|
348
|
-
Task {
|
|
349
|
-
do {
|
|
350
|
-
let bundle = try await performCheckAndDownload(
|
|
351
|
-
channel: requestedChannel,
|
|
352
|
-
emitEvents: true
|
|
353
|
-
)
|
|
354
|
-
if let bundle {
|
|
355
|
-
call.resolve(bundle.toDictionary())
|
|
356
|
-
} else {
|
|
357
|
-
call.resolve()
|
|
358
|
-
}
|
|
359
|
-
} catch {
|
|
360
|
-
call.reject("debugDownload failed: \(error.localizedDescription)")
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
337
|
@objc func download(_ call: CAPPluginCall) {
|
|
366
|
-
|
|
367
|
-
if shouldThrottleCheck() || !claimCheckInProgress() {
|
|
338
|
+
if !claimCheckInProgress() {
|
|
368
339
|
if let stagedId = store.getStagedBundleId(),
|
|
369
340
|
let staged = store.getBundle(id: stagedId) {
|
|
370
341
|
call.resolve(staged.toDictionary())
|
|
@@ -377,7 +348,6 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
377
348
|
defer { releaseCheckInProgress() }
|
|
378
349
|
do {
|
|
379
350
|
let bundle = try await performCheckAndDownload(channel: nil, emitEvents: true)
|
|
380
|
-
recordCheckTimestamp()
|
|
381
351
|
if let bundle {
|
|
382
352
|
call.resolve(bundle.toDictionary())
|
|
383
353
|
} else {
|
|
@@ -504,15 +474,11 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
504
474
|
throw NSError(domain: "OtaKit", code: 1, userInfo: [NSLocalizedDescriptionKey: "Missing appId in plugin config"])
|
|
505
475
|
}
|
|
506
476
|
|
|
507
|
-
let current = store.getCurrentBundle()
|
|
508
477
|
return try await ManifestClient.fetchLatest(
|
|
509
|
-
|
|
478
|
+
cdnUrl: cdnUrl,
|
|
510
479
|
appId: appId,
|
|
511
480
|
channel: channel,
|
|
512
|
-
currentVersion: current.version,
|
|
513
|
-
currentReleaseId: current.releaseId,
|
|
514
481
|
runtimeVersion: runtimeVersion,
|
|
515
|
-
platform: "ios",
|
|
516
482
|
allowInsecureUrls: allowInsecureUrls,
|
|
517
483
|
manifestKeys: manifestKeys.map {
|
|
518
484
|
ManifestKey(kid: $0.kid, derData: $0.key)
|
|
@@ -542,6 +508,13 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
542
508
|
)
|
|
543
509
|
}
|
|
544
510
|
|
|
511
|
+
if isCurrentBundleLatest(latest: manifest, targetChannel: targetChannel) {
|
|
512
|
+
if emitEvents {
|
|
513
|
+
notifyListeners("noUpdateAvailable", data: [:])
|
|
514
|
+
}
|
|
515
|
+
return nil
|
|
516
|
+
}
|
|
517
|
+
|
|
545
518
|
let staged = findMatchingStagedBundle(latest: manifest, targetChannel: targetChannel)
|
|
546
519
|
if emitEvents {
|
|
547
520
|
notifyListeners("updateAvailable", data: manifestToDictionary(manifest, downloaded: staged != nil))
|
|
@@ -939,6 +912,42 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
939
912
|
return payload
|
|
940
913
|
}
|
|
941
914
|
|
|
915
|
+
private func isCurrentBundleLatest(
|
|
916
|
+
latest: LatestManifest,
|
|
917
|
+
targetChannel: String?
|
|
918
|
+
) -> Bool {
|
|
919
|
+
doesBundleMatchLatest(store.getCurrentBundle(), latest: latest, targetChannel: targetChannel)
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
private func doesBundleMatchLatest(
|
|
923
|
+
_ bundle: BundleInfo,
|
|
924
|
+
latest: LatestManifest,
|
|
925
|
+
targetChannel: String?
|
|
926
|
+
) -> Bool {
|
|
927
|
+
if trimToNil(bundle.channel) != targetChannel {
|
|
928
|
+
return false
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if trimToNil(bundle.runtimeVersion) != trimToNil(latest.runtimeVersion) {
|
|
932
|
+
return false
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if let latestReleaseId = latest.releaseId,
|
|
936
|
+
let bundleReleaseId = bundle.releaseId,
|
|
937
|
+
latestReleaseId == bundleReleaseId {
|
|
938
|
+
return true
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if !latest.sha256.isEmpty,
|
|
942
|
+
let bundleSha = bundle.sha256,
|
|
943
|
+
!bundleSha.isEmpty,
|
|
944
|
+
latest.sha256 == bundleSha {
|
|
945
|
+
return true
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
return latest.version == bundle.version
|
|
949
|
+
}
|
|
950
|
+
|
|
942
951
|
private func findMatchingStagedBundle(
|
|
943
952
|
latest: LatestManifest,
|
|
944
953
|
targetChannel: String?
|
|
@@ -959,18 +968,7 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
959
968
|
return nil
|
|
960
969
|
}
|
|
961
970
|
|
|
962
|
-
|
|
963
|
-
let stagedReleaseId = staged.releaseId,
|
|
964
|
-
latestReleaseId == stagedReleaseId {
|
|
965
|
-
return staged
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
if latest.version == staged.version,
|
|
969
|
-
latest.sha256 == staged.sha256 {
|
|
970
|
-
return staged
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
return nil
|
|
971
|
+
return doesBundleMatchLatest(staged, latest: latest, targetChannel: targetChannel) ? staged : nil
|
|
974
972
|
}
|
|
975
973
|
|
|
976
974
|
private func pruneIncompatibleBundles() {
|
|
@@ -1077,6 +1075,25 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1077
1075
|
return UpdaterPlugin.defaultUpdateURL
|
|
1078
1076
|
}
|
|
1079
1077
|
|
|
1078
|
+
private func resolveCdnUrl(configured: String?, env: String?, resolvedUpdateUrl: String) -> String {
|
|
1079
|
+
let configuredValue = configured?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1080
|
+
if let configuredValue, !configuredValue.isEmpty {
|
|
1081
|
+
return normalizeCdnUrl(configuredValue)
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
let envValue = env?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1085
|
+
if let envValue, !envValue.isEmpty {
|
|
1086
|
+
return normalizeCdnUrl(envValue)
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
if resolvedUpdateUrl.lowercased() != UpdaterPlugin.defaultUpdateURL.lowercased(),
|
|
1090
|
+
resolvedUpdateUrl.lowercased().hasSuffix(UpdaterPlugin.apiPathSuffix) {
|
|
1091
|
+
return String(resolvedUpdateUrl.dropLast(UpdaterPlugin.apiPathSuffix.count))
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
return UpdaterPlugin.defaultCdnURL
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1080
1097
|
private func normalizeUpdateUrl(_ raw: String) -> String {
|
|
1081
1098
|
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1082
1099
|
let withoutTrailingSlash = trimmed.replacingOccurrences(
|
|
@@ -1092,6 +1109,12 @@ public class UpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1092
1109
|
return withoutTrailingSlash + UpdaterPlugin.apiPathSuffix
|
|
1093
1110
|
}
|
|
1094
1111
|
|
|
1112
|
+
private func normalizeCdnUrl(_ raw: String) -> String {
|
|
1113
|
+
raw
|
|
1114
|
+
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1115
|
+
.replacingOccurrences(of: "/+$", with: "", options: .regularExpression)
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1095
1118
|
private func getFreeDiskSpace() -> Int64 {
|
|
1096
1119
|
do {
|
|
1097
1120
|
let attributes = try fileManager.attributesOfFileSystem(
|