@capawesome/capacitor-live-update 7.2.1 → 7.3.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 +78 -26
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +165 -35
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateConfig.java +23 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +52 -13
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +17 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/events/NextBundleSetEvent.java +26 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SyncOptions.java +4 -0
- package/dist/docs.json +109 -1
- package/dist/esm/definitions.d.ts +65 -2
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/Classes/Events/NextBundleSetEvent.swift +16 -0
- package/ios/Plugin/Classes/Options/SyncOptions.swift +4 -0
- package/ios/Plugin/LiveUpdate.swift +101 -0
- package/ios/Plugin/LiveUpdateConfig.swift +2 -0
- package/ios/Plugin/LiveUpdateHttpClient.swift +5 -3
- package/ios/Plugin/LiveUpdatePlugin.swift +29 -11
- package/ios/Plugin/LiveUpdatePreferences.swift +14 -0
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ import CommonCrypto
|
|
|
11
11
|
|
|
12
12
|
// swiftlint:disable type_body_length
|
|
13
13
|
@objc public class LiveUpdate: NSObject {
|
|
14
|
+
private let autoUpdateIntervalMs: Int64 = 15 * 60 * 1000 // 15 minutes
|
|
14
15
|
private let bundlesDirectory = "NoCloud/ionic_built_snapshots" // DO NOT CHANGE! (See https://dub.sh/BLluidt)
|
|
15
16
|
private let cachesDirectoryUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
|
16
17
|
private let config: LiveUpdateConfig
|
|
@@ -24,6 +25,8 @@ import CommonCrypto
|
|
|
24
25
|
|
|
25
26
|
private var rollbackDispatchWorkItem: DispatchWorkItem?
|
|
26
27
|
private var rollbackPerformed = false
|
|
28
|
+
private var lastAutoUpdateCheckTimestamp: Int64 = 0
|
|
29
|
+
private var syncInProgress = false
|
|
27
30
|
|
|
28
31
|
init(config: LiveUpdateConfig, plugin: LiveUpdatePlugin) {
|
|
29
32
|
self.config = config
|
|
@@ -128,6 +131,18 @@ import CommonCrypto
|
|
|
128
131
|
completion(result, nil)
|
|
129
132
|
}
|
|
130
133
|
|
|
134
|
+
@objc public func handleLoad() {
|
|
135
|
+
if config.autoUpdateStrategy == "background" {
|
|
136
|
+
performAutoUpdate()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@objc public func handleAppWillEnterForeground() {
|
|
141
|
+
if config.autoUpdateStrategy == "background" {
|
|
142
|
+
performAutoUpdate()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
131
146
|
@objc public func ready(completion: @escaping (Result?, Error?) -> Void) {
|
|
132
147
|
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is ready.")
|
|
133
148
|
if config.readyTimeout <= 0 {
|
|
@@ -142,6 +157,10 @@ import CommonCrypto
|
|
|
142
157
|
// Get the current and previous bundle IDs
|
|
143
158
|
let currentBundleId = getCurrentBundleId()
|
|
144
159
|
let previousBundleId = getPreviousBundleId()
|
|
160
|
+
// Block the rolled back bundle if enabled
|
|
161
|
+
if config.autoBlockRolledBackBundles && rollbackPerformed, let previousBundleId = previousBundleId {
|
|
162
|
+
addBlockedBundleId(previousBundleId)
|
|
163
|
+
}
|
|
145
164
|
// Return the result
|
|
146
165
|
let result = ReadyResult(currentBundleId: currentBundleId, previousBundleId: previousBundleId, rollback: rollbackPerformed)
|
|
147
166
|
completion(result, nil)
|
|
@@ -194,6 +213,14 @@ import CommonCrypto
|
|
|
194
213
|
}
|
|
195
214
|
|
|
196
215
|
@objc public func sync(_ options: SyncOptions) async throws -> SyncResult {
|
|
216
|
+
if syncInProgress {
|
|
217
|
+
throw CustomError.syncInProgress
|
|
218
|
+
}
|
|
219
|
+
syncInProgress = true
|
|
220
|
+
defer {
|
|
221
|
+
syncInProgress = false
|
|
222
|
+
}
|
|
223
|
+
|
|
197
224
|
let channel = options.getChannel()
|
|
198
225
|
// Fetch the latest bundle
|
|
199
226
|
let fetchLatestBundleOptions = FetchLatestBundleOptions(channel: channel)
|
|
@@ -206,6 +233,11 @@ import CommonCrypto
|
|
|
206
233
|
let checksum = response.checksum
|
|
207
234
|
let signature = response.signature
|
|
208
235
|
let downloadUrl = response.url
|
|
236
|
+
// Check if the bundle is blocked
|
|
237
|
+
if isBlockedBundleId(latestBundleId) {
|
|
238
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Bundle is blocked and will not be downloaded.")
|
|
239
|
+
return SyncResult(nextBundleId: nil)
|
|
240
|
+
}
|
|
209
241
|
// Check if bundle already exists
|
|
210
242
|
if hasBundleById(latestBundleId) {
|
|
211
243
|
var nextBundleId: String?
|
|
@@ -645,6 +677,30 @@ import CommonCrypto
|
|
|
645
677
|
plugin.notifyDownloadBundleProgressListeners(event)
|
|
646
678
|
}
|
|
647
679
|
|
|
680
|
+
private func performAutoUpdate() {
|
|
681
|
+
// Check if enough time has passed since the last check
|
|
682
|
+
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
683
|
+
if lastAutoUpdateCheckTimestamp > 0 && (now - lastAutoUpdateCheckTimestamp) < autoUpdateIntervalMs {
|
|
684
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update skipped. Last check was less than 15 minutes ago.")
|
|
685
|
+
return
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Update the timestamp
|
|
689
|
+
lastAutoUpdateCheckTimestamp = now
|
|
690
|
+
|
|
691
|
+
// Run sync in background task
|
|
692
|
+
Task {
|
|
693
|
+
do {
|
|
694
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update started.")
|
|
695
|
+
let options = SyncOptions(channel: nil)
|
|
696
|
+
_ = try await sync(options)
|
|
697
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update completed successfully.")
|
|
698
|
+
} catch {
|
|
699
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update failed: ", error.localizedDescription)
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
648
704
|
private func rollback() {
|
|
649
705
|
// Set the rollback flag
|
|
650
706
|
rollbackPerformed = true
|
|
@@ -705,6 +761,51 @@ import CommonCrypto
|
|
|
705
761
|
private func setNextBundleById(_ bundleId: String?) {
|
|
706
762
|
let path = buildCapacitorServerPathFor(bundleId: bundleId)
|
|
707
763
|
setNextCapacitorServerPath(path: path)
|
|
764
|
+
|
|
765
|
+
// Notify listeners
|
|
766
|
+
notifyNextBundleSetListeners(bundleId)
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
private func notifyNextBundleSetListeners(_ bundleId: String?) {
|
|
770
|
+
let event = NextBundleSetEvent(bundleId: bundleId)
|
|
771
|
+
plugin.notifyNextBundleSetListeners(event)
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
private func addBlockedBundleId(_ bundleId: String) {
|
|
775
|
+
var blockedList: [String] = []
|
|
776
|
+
|
|
777
|
+
// Parse existing blocked IDs
|
|
778
|
+
if let blockedIds = preferences.getBlockedBundleIds(), !blockedIds.isEmpty {
|
|
779
|
+
blockedList = blockedIds.split(separator: ",").map(String.init)
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Skip if already blocked
|
|
783
|
+
if blockedList.contains(bundleId) {
|
|
784
|
+
return
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Remove oldest if limit reached
|
|
788
|
+
if blockedList.count >= 100 {
|
|
789
|
+
blockedList.removeFirst()
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Add new bundle
|
|
793
|
+
blockedList.append(bundleId)
|
|
794
|
+
|
|
795
|
+
// Save back to preferences
|
|
796
|
+
let newBlockedIds = blockedList.joined(separator: ",")
|
|
797
|
+
preferences.setBlockedBundleIds(newBlockedIds)
|
|
798
|
+
|
|
799
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Bundle blocked: ", bundleId)
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
private func isBlockedBundleId(_ bundleId: String) -> Bool {
|
|
803
|
+
guard let blockedIds = preferences.getBlockedBundleIds(), !blockedIds.isEmpty else {
|
|
804
|
+
return false
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
let blockedList = blockedIds.split(separator: ",").map(String.init)
|
|
808
|
+
return blockedList.contains(bundleId)
|
|
708
809
|
}
|
|
709
810
|
|
|
710
811
|
private func setNextCapacitorServerPath(path: String) {
|
|
@@ -7,11 +7,13 @@ public class LiveUpdateHttpClient: NSObject {
|
|
|
7
7
|
private let config: LiveUpdateConfig
|
|
8
8
|
|
|
9
9
|
public static func getChecksumFromResponse(response: HTTPURLResponse) -> String? {
|
|
10
|
-
|
|
10
|
+
guard let headers = response.allHeaderFields as? [String: String] else { return nil }
|
|
11
|
+
return headers.first(where: { $0.key.lowercased() == "x-checksum" })?.value
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
public static func getSignatureFromResponse(response: HTTPURLResponse) -> String? {
|
|
14
|
-
|
|
15
|
+
guard let headers = response.allHeaderFields as? [String: String] else { return nil }
|
|
16
|
+
return headers.first(where: { $0.key.lowercased() == "x-signature" })?.value
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
init(config: LiveUpdateConfig) {
|
|
@@ -27,7 +29,7 @@ public class LiveUpdateHttpClient: NSObject {
|
|
|
27
29
|
if let callback = callback {
|
|
28
30
|
callback(progress)
|
|
29
31
|
}
|
|
30
|
-
}.responseData { response in
|
|
32
|
+
}.responseData(emptyResponseCodes: [200, 204, 205]) { response in
|
|
31
33
|
continuation.resume(returning: response)
|
|
32
34
|
}
|
|
33
35
|
}
|
|
@@ -8,7 +8,7 @@ import Capacitor
|
|
|
8
8
|
@objc(LiveUpdatePlugin)
|
|
9
9
|
public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
10
10
|
public static let tag = "LiveUpdate"
|
|
11
|
-
public static let version = "7.
|
|
11
|
+
public static let version = "7.3.0"
|
|
12
12
|
public static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
|
|
13
13
|
|
|
14
14
|
public let identifier = "LiveUpdatePlugin"
|
|
@@ -35,14 +35,25 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
35
35
|
]
|
|
36
36
|
|
|
37
37
|
private let eventDownloadBundleProgess = "downloadBundleProgress"
|
|
38
|
+
private let eventNextBundleSet = "nextBundleSet"
|
|
38
39
|
|
|
39
40
|
private var config: LiveUpdateConfig?
|
|
40
41
|
private var implementation: LiveUpdate?
|
|
41
|
-
private var syncInProgress = false
|
|
42
42
|
|
|
43
43
|
override public func load() {
|
|
44
44
|
self.config = liveUpdateConfig()
|
|
45
45
|
self.implementation = LiveUpdate(config: config!, plugin: self)
|
|
46
|
+
|
|
47
|
+
// Notify implementation about load
|
|
48
|
+
implementation?.handleLoad()
|
|
49
|
+
|
|
50
|
+
// Register for app resume notifications
|
|
51
|
+
NotificationCenter.default.addObserver(
|
|
52
|
+
self,
|
|
53
|
+
selector: #selector(handleAppWillEnterForeground),
|
|
54
|
+
name: UIApplication.willEnterForegroundNotification,
|
|
55
|
+
object: nil
|
|
56
|
+
)
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
@objc func deleteBundle(_ call: CAPPluginCall) {
|
|
@@ -90,6 +101,11 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
90
101
|
@objc func fetchLatestBundle(_ call: CAPPluginCall) {
|
|
91
102
|
Task {
|
|
92
103
|
do {
|
|
104
|
+
guard let appId = config?.appId, !appId.isEmpty else {
|
|
105
|
+
call.reject(CustomError.appIdMissing.localizedDescription)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
93
109
|
let options = FetchLatestBundleOptions(call)
|
|
94
110
|
let result = try await implementation?.fetchLatestBundle(options)
|
|
95
111
|
if let result = result?.toJSObject() as? JSObject {
|
|
@@ -265,25 +281,17 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
265
281
|
@objc func sync(_ call: CAPPluginCall) {
|
|
266
282
|
Task {
|
|
267
283
|
do {
|
|
268
|
-
guard let
|
|
284
|
+
guard let appId = config?.appId, !appId.isEmpty else {
|
|
269
285
|
call.reject(CustomError.appIdMissing.localizedDescription)
|
|
270
286
|
return
|
|
271
287
|
}
|
|
272
288
|
|
|
273
|
-
guard !syncInProgress else {
|
|
274
|
-
call.reject(CustomError.syncInProgress.localizedDescription)
|
|
275
|
-
return
|
|
276
|
-
}
|
|
277
|
-
syncInProgress = true
|
|
278
|
-
|
|
279
289
|
let options = SyncOptions(call)
|
|
280
290
|
let result = try await implementation?.sync(options)
|
|
281
|
-
self.syncInProgress = false
|
|
282
291
|
if let result = result?.toJSObject() as? JSObject {
|
|
283
292
|
resolveCall(call, result)
|
|
284
293
|
}
|
|
285
294
|
} catch {
|
|
286
|
-
self.syncInProgress = false
|
|
287
295
|
rejectCall(call, error)
|
|
288
296
|
}
|
|
289
297
|
}
|
|
@@ -295,11 +303,21 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
295
303
|
}
|
|
296
304
|
}
|
|
297
305
|
|
|
306
|
+
func notifyNextBundleSetListeners(_ event: NextBundleSetEvent) {
|
|
307
|
+
notifyListeners(eventNextBundleSet, data: event.toJSObject(), retainUntilConsumed: false)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@objc private func handleAppWillEnterForeground() {
|
|
311
|
+
implementation?.handleAppWillEnterForeground()
|
|
312
|
+
}
|
|
313
|
+
|
|
298
314
|
private func liveUpdateConfig() -> LiveUpdateConfig {
|
|
299
315
|
var config = LiveUpdateConfig()
|
|
300
316
|
|
|
301
317
|
config.appId = getConfig().getString("appId", config.appId)
|
|
318
|
+
config.autoBlockRolledBackBundles = getConfig().getBoolean("autoBlockRolledBackBundles", config.autoBlockRolledBackBundles)
|
|
302
319
|
config.autoDeleteBundles = getConfig().getBoolean("autoDeleteBundles", config.autoDeleteBundles)
|
|
320
|
+
config.autoUpdateStrategy = getConfig().getString("autoUpdateStrategy", config.autoUpdateStrategy) ?? config.autoUpdateStrategy
|
|
303
321
|
config.defaultChannel = getConfig().getString("defaultChannel", config.defaultChannel)
|
|
304
322
|
config.httpTimeout = getConfig().getInt("httpTimeout", config.httpTimeout)
|
|
305
323
|
config.publicKey = getConfig().getString("publicKey", config.publicKey)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
public class LiveUpdatePreferences: NSObject {
|
|
4
|
+
private let blockedBundleIdsKey = "blockedBundleIds" // DO NOT CHANGE
|
|
4
5
|
private let channelKey = "channel" // DO NOT CHANGE
|
|
5
6
|
private let customIdKey = "customId" // DO NOT CHANGE
|
|
6
7
|
private let previousBundleIdKey = "previousBundleIdKey" // DO NOT CHANGE
|
|
@@ -17,6 +18,19 @@ public class LiveUpdatePreferences: NSObject {
|
|
|
17
18
|
return UserDefaults.standard.string(forKey: applyPrefix(to: previousBundleIdKey))
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
public func getBlockedBundleIds() -> String? {
|
|
22
|
+
return UserDefaults.standard.string(forKey: applyPrefix(to: blockedBundleIdsKey))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public func setBlockedBundleIds(_ value: String?) {
|
|
26
|
+
if let value = value {
|
|
27
|
+
UserDefaults.standard.set(value, forKey: applyPrefix(to: blockedBundleIdsKey))
|
|
28
|
+
} else {
|
|
29
|
+
UserDefaults.standard.removeObject(forKey: applyPrefix(to: blockedBundleIdsKey))
|
|
30
|
+
}
|
|
31
|
+
UserDefaults.standard.synchronize()
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
public func setChannel(_ value: String?) {
|
|
21
35
|
if let value = value {
|
|
22
36
|
UserDefaults.standard.set(value, forKey: applyPrefix(to: channelKey))
|