@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.
- package/{UpdatekitUpdater.podspec → OtaKitUpdater.podspec} +1 -1
- package/README.md +165 -130
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/BundleInfo.java +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/BundleStatus.java +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/BundleStore.java +38 -9
- package/android/src/main/java/com/{updatekit → otakit}/updater/DateUtils.java +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/DeviceEventClient.java +3 -4
- package/android/src/main/java/com/{updatekit → otakit}/updater/HashUtils.java +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/HostedManifestKeys.java +1 -1
- package/android/src/main/java/com/{updatekit → otakit}/updater/ManifestClient.java +32 -15
- package/android/src/main/java/com/{updatekit → otakit}/updater/ManifestVerifier.java +1 -1
- package/android/src/main/java/com/otakit/updater/UpdaterCoordinator.java +726 -0
- package/android/src/main/java/com/otakit/updater/UpdaterPlugin.java +1191 -0
- package/android/src/main/java/com/{updatekit → otakit}/updater/ZipUtils.java +1 -1
- package/dist/esm/definitions.d.ts +48 -105
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +6 -60
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +7 -11
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +8 -14
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +14 -74
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +14 -74
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/UpdaterPlugin/BundleStore.swift +28 -6
- package/ios/Sources/UpdaterPlugin/Downloader.swift +1 -1
- package/ios/Sources/UpdaterPlugin/ManifestClient.swift +8 -4
- package/ios/Sources/UpdaterPlugin/UpdaterCoordinator.swift +635 -0
- package/ios/Sources/UpdaterPlugin/UpdaterPlugin.m +3 -5
- package/ios/Sources/UpdaterPlugin/UpdaterPlugin.swift +506 -551
- package/package.json +2 -2
- package/android/src/main/java/com/updatekit/updater/UpdaterPlugin.java +0 -1191
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
final class UpdaterCoordinator {
|
|
4
|
+
struct StateSnapshot {
|
|
5
|
+
let current: BundleInfo
|
|
6
|
+
let fallback: BundleInfo
|
|
7
|
+
let staged: BundleInfo?
|
|
8
|
+
let builtinVersion: String
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
struct DeviceEventPayload {
|
|
12
|
+
let action: DeviceEventAction
|
|
13
|
+
let bundleVersion: String?
|
|
14
|
+
let runtimeVersion: String?
|
|
15
|
+
let channel: String?
|
|
16
|
+
let releaseId: String?
|
|
17
|
+
let detail: String?
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
enum LatestManifestClassification {
|
|
21
|
+
case noUpdate
|
|
22
|
+
case alreadyStaged(BundleInfo)
|
|
23
|
+
case updateAvailable
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
struct StartupPreparation {
|
|
27
|
+
let activationPath: String?
|
|
28
|
+
let trialBundleId: String?
|
|
29
|
+
let cleanupBundleIds: [String]
|
|
30
|
+
let eventPayload: DeviceEventPayload?
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
struct ApplyPreparation {
|
|
34
|
+
let activationPath: String?
|
|
35
|
+
let trialBundleId: String?
|
|
36
|
+
let cleanupBundleIds: [String]
|
|
37
|
+
|
|
38
|
+
var didApply: Bool {
|
|
39
|
+
activationPath != nil
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
struct NotifyReadyPreparation {
|
|
44
|
+
let eventPayload: DeviceEventPayload?
|
|
45
|
+
let cleanupBundleIds: [String]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
struct RollbackPreparation {
|
|
49
|
+
let didRollback: Bool
|
|
50
|
+
let activationPath: String?
|
|
51
|
+
let eventPayload: DeviceEventPayload?
|
|
52
|
+
let cleanupBundleIds: [String]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private let store: BundleStore
|
|
56
|
+
private let operationLock = NSLock()
|
|
57
|
+
private let stateLock = NSLock()
|
|
58
|
+
private var operationInProgress = false
|
|
59
|
+
|
|
60
|
+
init(store: BundleStore) {
|
|
61
|
+
self.store = store
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var nativeBuild: String {
|
|
65
|
+
store.nativeBuild
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func bundleDirectory(for id: String) -> URL {
|
|
69
|
+
store.bundleDirectory(for: id)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func tryBeginOperation() -> Bool {
|
|
73
|
+
operationLock.lock()
|
|
74
|
+
defer { operationLock.unlock() }
|
|
75
|
+
if operationInProgress {
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
operationInProgress = true
|
|
79
|
+
return true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func endOperation() {
|
|
83
|
+
operationLock.lock()
|
|
84
|
+
defer { operationLock.unlock() }
|
|
85
|
+
operationInProgress = false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func snapshotState(
|
|
89
|
+
isStagedBundleUsable: @escaping (BundleInfo) -> Bool
|
|
90
|
+
) -> StateSnapshot {
|
|
91
|
+
withStateLock {
|
|
92
|
+
let staged = readStagedBundleLocked(
|
|
93
|
+
isCompatibleRuntime: nil,
|
|
94
|
+
isUsable: isStagedBundleUsable
|
|
95
|
+
)
|
|
96
|
+
return StateSnapshot(
|
|
97
|
+
current: store.getCurrentBundle(),
|
|
98
|
+
fallback: store.getFallbackBundle(),
|
|
99
|
+
staged: staged,
|
|
100
|
+
builtinVersion: store.builtinVersion
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
func lastFailure() -> BundleInfo? {
|
|
106
|
+
withStateLock {
|
|
107
|
+
store.getLastFailedBundle()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
func pruneIncompatibleBundles(
|
|
112
|
+
isCompatibleRuntime: @escaping (BundleInfo) -> Bool
|
|
113
|
+
) -> [String] {
|
|
114
|
+
withStateLock {
|
|
115
|
+
var cleanupBundleIds = Set<String>()
|
|
116
|
+
|
|
117
|
+
for bundle in store.listDownloadedBundles() where !isCompatibleRuntime(bundle) {
|
|
118
|
+
detachBundleReferencesLocked(bundleId: bundle.id)
|
|
119
|
+
cleanupBundleIds.insert(bundle.id)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if let failed = store.getLastFailedBundle(),
|
|
123
|
+
!isCompatibleRuntime(failed) {
|
|
124
|
+
store.setLastFailedBundle(nil)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return Array(cleanupBundleIds)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func normalizeStartupState(
|
|
132
|
+
isBundleUsable: @escaping (BundleInfo) -> Bool
|
|
133
|
+
) -> StartupPreparation {
|
|
134
|
+
withStateLock {
|
|
135
|
+
var cleanupBundleIds = Set<String>()
|
|
136
|
+
clearStaleBundlePointersLocked()
|
|
137
|
+
normalizeStagedPointerLocked(
|
|
138
|
+
isBundleUsable: isBundleUsable,
|
|
139
|
+
cleanupBundleIds: &cleanupBundleIds
|
|
140
|
+
)
|
|
141
|
+
normalizeFallbackPointerLocked(
|
|
142
|
+
isBundleUsable: isBundleUsable,
|
|
143
|
+
cleanupBundleIds: &cleanupBundleIds
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
var eventPayload: DeviceEventPayload?
|
|
147
|
+
let current = store.getCurrentBundle()
|
|
148
|
+
if !current.isBuiltin,
|
|
149
|
+
current.status == .trial {
|
|
150
|
+
let rollback = rollbackLocked(
|
|
151
|
+
reason: "app_restarted_before_notify",
|
|
152
|
+
isBundleUsable: isBundleUsable,
|
|
153
|
+
cleanupBundleIds: &cleanupBundleIds
|
|
154
|
+
)
|
|
155
|
+
eventPayload = rollback.eventPayload
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
var normalizedCurrent = store.getCurrentBundle()
|
|
159
|
+
if !normalizedCurrent.isBuiltin,
|
|
160
|
+
!isBundleUsable(normalizedCurrent) {
|
|
161
|
+
cleanupBundleIds.insert(normalizedCurrent.id)
|
|
162
|
+
normalizedCurrent = restoreFallbackOrBuiltinLocked(
|
|
163
|
+
isBundleUsable: isBundleUsable,
|
|
164
|
+
cleanupBundleIds: &cleanupBundleIds
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
var trialBundleId: String?
|
|
169
|
+
if !normalizedCurrent.isBuiltin,
|
|
170
|
+
normalizedCurrent.status == .pending {
|
|
171
|
+
normalizedCurrent = updateStatusLocked(normalizedCurrent, status: .trial)
|
|
172
|
+
trialBundleId = normalizedCurrent.id
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return StartupPreparation(
|
|
176
|
+
activationPath: normalizedCurrent.isBuiltin ? nil : normalizedCurrent.path,
|
|
177
|
+
trialBundleId: trialBundleId,
|
|
178
|
+
cleanupBundleIds: Array(cleanupBundleIds),
|
|
179
|
+
eventPayload: eventPayload
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
func isRuntimeUnresolved(currentRuntimeKey: String) -> Bool {
|
|
185
|
+
withStateLock {
|
|
186
|
+
store.getLastResolvedRuntimeKey() != currentRuntimeKey
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
func resolveRuntimeKey(_ currentRuntimeKey: String) {
|
|
191
|
+
withStateLock {
|
|
192
|
+
store.setLastResolvedRuntimeKey(currentRuntimeKey)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func classifyLatestManifest(
|
|
197
|
+
_ manifest: LatestManifest,
|
|
198
|
+
targetChannel: String?,
|
|
199
|
+
isStagedBundleUsable: @escaping (BundleInfo) -> Bool
|
|
200
|
+
) -> LatestManifestClassification {
|
|
201
|
+
withStateLock {
|
|
202
|
+
let current = store.getCurrentBundle()
|
|
203
|
+
if doesBundleMatchLatest(current, latest: manifest, targetChannel: targetChannel) {
|
|
204
|
+
return .noUpdate
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if let failed = store.getLastFailedBundle(),
|
|
208
|
+
doesFailedBundleMatchLatest(failed, latest: manifest, targetChannel: targetChannel) {
|
|
209
|
+
return .noUpdate
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if let staged = readStagedBundleLocked(
|
|
213
|
+
isCompatibleRuntime: { [self] bundle in
|
|
214
|
+
self.trimToNil(bundle.runtimeVersion) == self.trimToNil(manifest.runtimeVersion)
|
|
215
|
+
},
|
|
216
|
+
isUsable: isStagedBundleUsable
|
|
217
|
+
),
|
|
218
|
+
doesBundleMatchLatest(staged, latest: manifest, targetChannel: targetChannel) {
|
|
219
|
+
return .alreadyStaged(staged)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return .updateAvailable
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
func stageDownloadedBundle(_ bundle: BundleInfo) throws -> [String] {
|
|
227
|
+
try withStateLock {
|
|
228
|
+
var cleanupBundleIds = Set<String>()
|
|
229
|
+
let previousStagedId = store.getStagedBundleId()
|
|
230
|
+
try store.saveBundle(bundle)
|
|
231
|
+
store.setStagedBundleId(bundle.id)
|
|
232
|
+
|
|
233
|
+
if let previousStagedId,
|
|
234
|
+
previousStagedId != bundle.id,
|
|
235
|
+
previousStagedId != "builtin",
|
|
236
|
+
previousStagedId != store.getCurrentBundleId(),
|
|
237
|
+
previousStagedId != store.getFallbackBundleId() {
|
|
238
|
+
cleanupBundleIds.insert(previousStagedId)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return Array(cleanupBundleIds)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
func prepareApplyStaged(
|
|
246
|
+
isCompatibleRuntime: @escaping (BundleInfo) -> Bool,
|
|
247
|
+
isBundleUsable: @escaping (BundleInfo) -> Bool
|
|
248
|
+
) -> ApplyPreparation {
|
|
249
|
+
withStateLock {
|
|
250
|
+
var cleanupBundleIds = Set<String>()
|
|
251
|
+
guard var staged = stagedBundleLocked(
|
|
252
|
+
cleanInvalid: true,
|
|
253
|
+
isCompatibleRuntime: isCompatibleRuntime,
|
|
254
|
+
isUsable: isBundleUsable,
|
|
255
|
+
cleanupBundleIds: &cleanupBundleIds
|
|
256
|
+
) else {
|
|
257
|
+
return ApplyPreparation(
|
|
258
|
+
activationPath: nil,
|
|
259
|
+
trialBundleId: nil,
|
|
260
|
+
cleanupBundleIds: Array(cleanupBundleIds)
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let previousCurrent = store.getCurrentBundle()
|
|
265
|
+
if previousCurrent.isBuiltin {
|
|
266
|
+
store.setFallbackBundleId(nil)
|
|
267
|
+
} else {
|
|
268
|
+
store.setFallbackBundleId(previousCurrent.id)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
store.setCurrentBundleId(staged.id)
|
|
272
|
+
store.setStagedBundleId(nil)
|
|
273
|
+
|
|
274
|
+
var trialBundleId: String?
|
|
275
|
+
if staged.status == .pending {
|
|
276
|
+
staged = updateStatusLocked(staged, status: .trial)
|
|
277
|
+
trialBundleId = staged.id
|
|
278
|
+
} else if staged.status == .trial {
|
|
279
|
+
trialBundleId = staged.id
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return ApplyPreparation(
|
|
283
|
+
activationPath: staged.path,
|
|
284
|
+
trialBundleId: trialBundleId,
|
|
285
|
+
cleanupBundleIds: Array(cleanupBundleIds)
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
func prepareNotifyAppReady() -> NotifyReadyPreparation {
|
|
291
|
+
withStateLock {
|
|
292
|
+
let current = store.getCurrentBundle()
|
|
293
|
+
guard !current.isBuiltin,
|
|
294
|
+
current.status == .trial else {
|
|
295
|
+
return NotifyReadyPreparation(eventPayload: nil, cleanupBundleIds: [])
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let oldFallbackId = store.getFallbackBundleId()
|
|
299
|
+
_ = updateStatusLocked(current, status: .success)
|
|
300
|
+
store.setFallbackBundleId(current.id)
|
|
301
|
+
|
|
302
|
+
var cleanupBundleIds = Set<String>()
|
|
303
|
+
if let oldFallbackId,
|
|
304
|
+
oldFallbackId != current.id {
|
|
305
|
+
cleanupBundleIds.insert(oldFallbackId)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return NotifyReadyPreparation(
|
|
309
|
+
eventPayload: DeviceEventPayload(
|
|
310
|
+
action: .applied,
|
|
311
|
+
bundleVersion: current.version,
|
|
312
|
+
runtimeVersion: current.runtimeVersion,
|
|
313
|
+
channel: current.channel,
|
|
314
|
+
releaseId: current.releaseId,
|
|
315
|
+
detail: nil
|
|
316
|
+
),
|
|
317
|
+
cleanupBundleIds: Array(cleanupBundleIds)
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
func prepareRollback(
|
|
323
|
+
reason: String,
|
|
324
|
+
isBundleUsable: @escaping (BundleInfo) -> Bool
|
|
325
|
+
) -> RollbackPreparation {
|
|
326
|
+
withStateLock {
|
|
327
|
+
var cleanupBundleIds = Set<String>()
|
|
328
|
+
let rollback = rollbackLocked(
|
|
329
|
+
reason: reason,
|
|
330
|
+
isBundleUsable: isBundleUsable,
|
|
331
|
+
cleanupBundleIds: &cleanupBundleIds
|
|
332
|
+
)
|
|
333
|
+
return RollbackPreparation(
|
|
334
|
+
didRollback: rollback.didRollback,
|
|
335
|
+
activationPath: rollback.activationPath,
|
|
336
|
+
eventPayload: rollback.eventPayload,
|
|
337
|
+
cleanupBundleIds: Array(cleanupBundleIds)
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
func isCurrentTrialBundle(_ bundleId: String) -> Bool {
|
|
343
|
+
withStateLock {
|
|
344
|
+
let current = store.getCurrentBundle()
|
|
345
|
+
return current.id == bundleId && current.status == .trial
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
func cleanupBundles(_ bundleIds: [String]) {
|
|
350
|
+
let uniqueIds = Set(bundleIds).filter { !$0.isEmpty && $0 != "builtin" }
|
|
351
|
+
for bundleId in uniqueIds {
|
|
352
|
+
try? FileManager.default.removeItem(at: store.bundleDirectory(for: bundleId))
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private struct LockedRollbackResult {
|
|
357
|
+
let didRollback: Bool
|
|
358
|
+
let activationPath: String?
|
|
359
|
+
let eventPayload: DeviceEventPayload?
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private func withStateLock<T>(_ work: () throws -> T) rethrows -> T {
|
|
363
|
+
stateLock.lock()
|
|
364
|
+
defer { stateLock.unlock() }
|
|
365
|
+
return try work()
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private func clearStaleBundlePointersLocked() {
|
|
369
|
+
if let currentId = store.getCurrentBundleId(),
|
|
370
|
+
store.getBundle(id: currentId) == nil {
|
|
371
|
+
store.setCurrentBundleId(nil)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if let fallbackId = store.getFallbackBundleId(),
|
|
375
|
+
store.getBundle(id: fallbackId) == nil {
|
|
376
|
+
store.setFallbackBundleId(nil)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if let stagedId = store.getStagedBundleId(),
|
|
380
|
+
store.getBundle(id: stagedId) == nil {
|
|
381
|
+
store.setStagedBundleId(nil)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private func normalizeFallbackPointerLocked(
|
|
386
|
+
isBundleUsable: (BundleInfo) -> Bool,
|
|
387
|
+
cleanupBundleIds: inout Set<String>
|
|
388
|
+
) {
|
|
389
|
+
guard let fallbackId = store.getFallbackBundleId(),
|
|
390
|
+
let fallback = store.getBundle(id: fallbackId) else {
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
guard !fallback.isBuiltin else {
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if !isBundleUsable(fallback) {
|
|
399
|
+
store.setFallbackBundleId(nil)
|
|
400
|
+
cleanupBundleIds.insert(fallback.id)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private func normalizeStagedPointerLocked(
|
|
405
|
+
isBundleUsable: (BundleInfo) -> Bool,
|
|
406
|
+
cleanupBundleIds: inout Set<String>
|
|
407
|
+
) {
|
|
408
|
+
guard let stagedId = store.getStagedBundleId() else {
|
|
409
|
+
return
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
guard let staged = store.getBundle(id: stagedId) else {
|
|
413
|
+
store.setStagedBundleId(nil)
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if !isBundleUsable(staged) {
|
|
418
|
+
store.setStagedBundleId(nil)
|
|
419
|
+
cleanupBundleIds.insert(staged.id)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private func readStagedBundleLocked(
|
|
424
|
+
isCompatibleRuntime: ((BundleInfo) -> Bool)?,
|
|
425
|
+
isUsable: (BundleInfo) -> Bool
|
|
426
|
+
) -> BundleInfo? {
|
|
427
|
+
var ignored = Set<String>()
|
|
428
|
+
return stagedBundleLocked(
|
|
429
|
+
cleanInvalid: false,
|
|
430
|
+
isCompatibleRuntime: isCompatibleRuntime,
|
|
431
|
+
isUsable: isUsable,
|
|
432
|
+
cleanupBundleIds: &ignored
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private func stagedBundleLocked(
|
|
437
|
+
cleanInvalid: Bool,
|
|
438
|
+
isCompatibleRuntime: ((BundleInfo) -> Bool)?,
|
|
439
|
+
isUsable: (BundleInfo) -> Bool,
|
|
440
|
+
cleanupBundleIds: inout Set<String>
|
|
441
|
+
) -> BundleInfo? {
|
|
442
|
+
guard let stagedId = store.getStagedBundleId() else {
|
|
443
|
+
return nil
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
guard let staged = store.getBundle(id: stagedId) else {
|
|
447
|
+
if cleanInvalid {
|
|
448
|
+
store.setStagedBundleId(nil)
|
|
449
|
+
}
|
|
450
|
+
return nil
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if let isCompatibleRuntime,
|
|
454
|
+
!isCompatibleRuntime(staged) {
|
|
455
|
+
if cleanInvalid {
|
|
456
|
+
store.setStagedBundleId(nil)
|
|
457
|
+
cleanupBundleIds.insert(staged.id)
|
|
458
|
+
}
|
|
459
|
+
return nil
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if !isUsable(staged) {
|
|
463
|
+
if cleanInvalid {
|
|
464
|
+
store.setStagedBundleId(nil)
|
|
465
|
+
cleanupBundleIds.insert(staged.id)
|
|
466
|
+
}
|
|
467
|
+
return nil
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return staged
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
private func detachBundleReferencesLocked(bundleId: String) {
|
|
474
|
+
if store.getCurrentBundleId() == bundleId {
|
|
475
|
+
store.setCurrentBundleId(nil)
|
|
476
|
+
}
|
|
477
|
+
if store.getFallbackBundleId() == bundleId {
|
|
478
|
+
store.setFallbackBundleId(nil)
|
|
479
|
+
}
|
|
480
|
+
if store.getStagedBundleId() == bundleId {
|
|
481
|
+
store.setStagedBundleId(nil)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private func rollbackLocked(
|
|
486
|
+
reason: String,
|
|
487
|
+
isBundleUsable: (BundleInfo) -> Bool,
|
|
488
|
+
cleanupBundleIds: inout Set<String>
|
|
489
|
+
) -> LockedRollbackResult {
|
|
490
|
+
let current = store.getCurrentBundle()
|
|
491
|
+
guard !current.isBuiltin else {
|
|
492
|
+
return LockedRollbackResult(
|
|
493
|
+
didRollback: false,
|
|
494
|
+
activationPath: nil,
|
|
495
|
+
eventPayload: nil
|
|
496
|
+
)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
let failed = updateStatusLocked(current, status: .error)
|
|
500
|
+
store.setLastFailedBundle(failed)
|
|
501
|
+
store.setStagedBundleId(nil)
|
|
502
|
+
|
|
503
|
+
let fallback = restoreFallbackOrBuiltinLocked(
|
|
504
|
+
isBundleUsable: isBundleUsable,
|
|
505
|
+
cleanupBundleIds: &cleanupBundleIds
|
|
506
|
+
)
|
|
507
|
+
cleanupBundleIds.insert(current.id)
|
|
508
|
+
|
|
509
|
+
return LockedRollbackResult(
|
|
510
|
+
didRollback: true,
|
|
511
|
+
activationPath: fallback.isBuiltin ? nil : fallback.path,
|
|
512
|
+
eventPayload: DeviceEventPayload(
|
|
513
|
+
action: .rollback,
|
|
514
|
+
bundleVersion: current.version,
|
|
515
|
+
runtimeVersion: current.runtimeVersion,
|
|
516
|
+
channel: current.channel,
|
|
517
|
+
releaseId: current.releaseId,
|
|
518
|
+
detail: reason
|
|
519
|
+
)
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private func restoreFallbackOrBuiltinLocked(
|
|
524
|
+
isBundleUsable: (BundleInfo) -> Bool,
|
|
525
|
+
cleanupBundleIds: inout Set<String>
|
|
526
|
+
) -> BundleInfo {
|
|
527
|
+
if let fallbackId = store.getFallbackBundleId(),
|
|
528
|
+
let fallback = store.getBundle(id: fallbackId),
|
|
529
|
+
!fallback.isBuiltin,
|
|
530
|
+
isBundleUsable(fallback) {
|
|
531
|
+
store.setCurrentBundleId(fallback.id)
|
|
532
|
+
return fallback
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if let fallbackId = store.getFallbackBundleId() {
|
|
536
|
+
store.setFallbackBundleId(nil)
|
|
537
|
+
if fallbackId != "builtin" {
|
|
538
|
+
cleanupBundleIds.insert(fallbackId)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
store.setCurrentBundleId(nil)
|
|
543
|
+
return store.builtinBundle()
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private func updateStatusLocked(
|
|
547
|
+
_ bundle: BundleInfo,
|
|
548
|
+
status: BundleStatus
|
|
549
|
+
) -> BundleInfo {
|
|
550
|
+
let updated = BundleInfo(
|
|
551
|
+
id: bundle.id,
|
|
552
|
+
version: bundle.version,
|
|
553
|
+
runtimeVersion: bundle.runtimeVersion,
|
|
554
|
+
status: status,
|
|
555
|
+
downloadedAt: bundle.downloadedAt,
|
|
556
|
+
sha256: bundle.sha256,
|
|
557
|
+
path: bundle.path,
|
|
558
|
+
channel: bundle.channel,
|
|
559
|
+
releaseId: bundle.releaseId
|
|
560
|
+
)
|
|
561
|
+
try? store.saveBundle(updated)
|
|
562
|
+
return updated
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private func doesFailedBundleMatchLatest(
|
|
566
|
+
_ failed: BundleInfo,
|
|
567
|
+
latest: LatestManifest,
|
|
568
|
+
targetChannel: String?
|
|
569
|
+
) -> Bool {
|
|
570
|
+
if trimToNil(failed.channel) != targetChannel {
|
|
571
|
+
return false
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if trimToNil(failed.runtimeVersion) != trimToNil(latest.runtimeVersion) {
|
|
575
|
+
return false
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if let failedReleaseId = trimToNil(failed.releaseId),
|
|
579
|
+
let latestReleaseId = trimToNil(latest.releaseId),
|
|
580
|
+
latestReleaseId == failedReleaseId {
|
|
581
|
+
return true
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if let failedSha = trimToNil(failed.sha256),
|
|
585
|
+
let latestSha = trimToNil(latest.sha256),
|
|
586
|
+
latestSha == failedSha {
|
|
587
|
+
return true
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return false
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
private func doesBundleMatchLatest(
|
|
594
|
+
_ bundle: BundleInfo,
|
|
595
|
+
latest: LatestManifest,
|
|
596
|
+
targetChannel: String?
|
|
597
|
+
) -> Bool {
|
|
598
|
+
if trimToNil(bundle.channel) != targetChannel {
|
|
599
|
+
return false
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if trimToNil(bundle.runtimeVersion) != trimToNil(latest.runtimeVersion) {
|
|
603
|
+
return false
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if let bundleReleaseId = trimToNil(bundle.releaseId),
|
|
607
|
+
let latestReleaseId = trimToNil(latest.releaseId),
|
|
608
|
+
latestReleaseId == bundleReleaseId {
|
|
609
|
+
return true
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if let bundleSha = trimToNil(bundle.sha256),
|
|
613
|
+
let latestSha = trimToNil(latest.sha256),
|
|
614
|
+
latestSha == bundleSha {
|
|
615
|
+
return true
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if trimToNil(bundle.releaseId) != nil ||
|
|
619
|
+
trimToNil(latest.releaseId) != nil ||
|
|
620
|
+
trimToNil(bundle.sha256) != nil ||
|
|
621
|
+
trimToNil(latest.sha256) != nil {
|
|
622
|
+
return false
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return latest.version == bundle.version
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
private func trimToNil(_ value: String?) -> String? {
|
|
629
|
+
guard let value else {
|
|
630
|
+
return nil
|
|
631
|
+
}
|
|
632
|
+
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
633
|
+
return trimmed.isEmpty ? nil : trimmed
|
|
634
|
+
}
|
|
635
|
+
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
#import <Capacitor/Capacitor.h>
|
|
2
2
|
|
|
3
3
|
CAP_PLUGIN(UpdaterPlugin, "OtaKit",
|
|
4
|
+
CAP_PLUGIN_METHOD(getState, CAPPluginReturnPromise);
|
|
4
5
|
CAP_PLUGIN_METHOD(check, CAPPluginReturnPromise);
|
|
5
6
|
CAP_PLUGIN_METHOD(download, CAPPluginReturnPromise);
|
|
6
7
|
CAP_PLUGIN_METHOD(apply, CAPPluginReturnPromise);
|
|
7
|
-
CAP_PLUGIN_METHOD(
|
|
8
|
+
CAP_PLUGIN_METHOD(update, CAPPluginReturnPromise);
|
|
8
9
|
CAP_PLUGIN_METHOD(notifyAppReady, CAPPluginReturnPromise);
|
|
9
|
-
CAP_PLUGIN_METHOD(
|
|
10
|
-
CAP_PLUGIN_METHOD(debugListBundles, CAPPluginReturnPromise);
|
|
11
|
-
CAP_PLUGIN_METHOD(debugDeleteBundle, CAPPluginReturnPromise);
|
|
12
|
-
CAP_PLUGIN_METHOD(debugGetLastFailure, CAPPluginReturnPromise);
|
|
10
|
+
CAP_PLUGIN_METHOD(getLastFailure, CAPPluginReturnPromise);
|
|
13
11
|
)
|