@capawesome/cordova-live-update 0.1.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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1113 -0
  3. package/dist/docs.json +1654 -0
  4. package/dist/esm/definitions.d.ts +788 -0
  5. package/dist/esm/definitions.js +7 -0
  6. package/dist/esm/definitions.js.map +1 -0
  7. package/dist/esm/exec.d.ts +1 -0
  8. package/dist/esm/exec.js +8 -0
  9. package/dist/esm/exec.js.map +1 -0
  10. package/dist/esm/index.d.ts +4 -0
  11. package/dist/esm/index.js +46 -0
  12. package/dist/esm/index.js.map +1 -0
  13. package/dist/plugin.js +56 -0
  14. package/dist/plugin.js.map +1 -0
  15. package/package.json +93 -0
  16. package/plugin.xml +268 -0
  17. package/src/android/capawesome-cordova-live-update.gradle +12 -0
  18. package/src/android/capawesome-live-update.xml +5 -0
  19. package/src/android/io/capawesome/cordova/plugins/liveupdate/LiveUpdate.java +1480 -0
  20. package/src/android/io/capawesome/cordova/plugins/liveupdate/LiveUpdateConfig.java +105 -0
  21. package/src/android/io/capawesome/cordova/plugins/liveupdate/LiveUpdateHttpClient.java +114 -0
  22. package/src/android/io/capawesome/cordova/plugins/liveupdate/LiveUpdatePathHandler.java +96 -0
  23. package/src/android/io/capawesome/cordova/plugins/liveupdate/LiveUpdatePlugin.java +550 -0
  24. package/src/android/io/capawesome/cordova/plugins/liveupdate/LiveUpdatePreferences.java +151 -0
  25. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/Manifest.java +58 -0
  26. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/ManifestItem.java +37 -0
  27. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/api/GetChannelsResponseItem.java +28 -0
  28. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/api/GetLatestBundleResponse.java +74 -0
  29. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/events/DownloadBundleProgressEvent.java +33 -0
  30. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/events/NextBundleSetEvent.java +26 -0
  31. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/options/DeleteBundleOptions.java +18 -0
  32. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/options/DownloadBundleOptions.java +66 -0
  33. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/options/FetchChannelsOptions.java +39 -0
  34. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/options/FetchLatestBundleOptions.java +25 -0
  35. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/options/SetChannelOptions.java +18 -0
  36. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/options/SetConfigOptions.java +20 -0
  37. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/options/SetCustomIdOptions.java +18 -0
  38. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/options/SetNextBundleOptions.java +21 -0
  39. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/options/SyncOptions.java +25 -0
  40. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/ChannelResult.java +29 -0
  41. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/FetchChannelsResult.java +29 -0
  42. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/FetchLatestBundleResult.java +69 -0
  43. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/GetBlockedBundlesResult.java +28 -0
  44. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/GetBundlesResult.java +28 -0
  45. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/GetChannelResult.java +22 -0
  46. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/GetConfigResult.java +40 -0
  47. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/GetCurrentBundleResult.java +22 -0
  48. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/GetCustomIdResult.java +22 -0
  49. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/GetDeviceIdResult.java +22 -0
  50. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/GetDownloadedBundlesResult.java +28 -0
  51. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/GetNextBundleResult.java +22 -0
  52. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/GetVersionCodeResult.java +21 -0
  53. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/GetVersionNameResult.java +22 -0
  54. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/IsSyncingResult.java +27 -0
  55. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/ReadyResult.java +32 -0
  56. package/src/android/io/capawesome/cordova/plugins/liveupdate/classes/results/SyncResult.java +22 -0
  57. package/src/android/io/capawesome/cordova/plugins/liveupdate/enums/ArtifactType.java +6 -0
  58. package/src/android/io/capawesome/cordova/plugins/liveupdate/interfaces/Callback.java +5 -0
  59. package/src/android/io/capawesome/cordova/plugins/liveupdate/interfaces/DownloadProgressCallback.java +5 -0
  60. package/src/android/io/capawesome/cordova/plugins/liveupdate/interfaces/EmptyCallback.java +5 -0
  61. package/src/android/io/capawesome/cordova/plugins/liveupdate/interfaces/NonEmptyCallback.java +7 -0
  62. package/src/android/io/capawesome/cordova/plugins/liveupdate/interfaces/Result.java +8 -0
  63. package/src/ios/LiveUpdate.swift +895 -0
  64. package/src/ios/LiveUpdateArtifactType.swift +4 -0
  65. package/src/ios/LiveUpdateChannelResult.swift +18 -0
  66. package/src/ios/LiveUpdateConfig.swift +11 -0
  67. package/src/ios/LiveUpdateDeleteBundleOptions.swift +13 -0
  68. package/src/ios/LiveUpdateDownloadBundleOptions.swift +41 -0
  69. package/src/ios/LiveUpdateDownloadBundleProgressEvent.swift +24 -0
  70. package/src/ios/LiveUpdateError.swift +62 -0
  71. package/src/ios/LiveUpdateFetchChannelsOptions.swift +25 -0
  72. package/src/ios/LiveUpdateFetchChannelsResult.swift +15 -0
  73. package/src/ios/LiveUpdateFetchLatestBundleOptions.swift +17 -0
  74. package/src/ios/LiveUpdateFetchLatestBundleResult.swift +42 -0
  75. package/src/ios/LiveUpdateGetBlockedBundlesResult.swift +15 -0
  76. package/src/ios/LiveUpdateGetBundlesResult.swift +15 -0
  77. package/src/ios/LiveUpdateGetChannelResult.swift +15 -0
  78. package/src/ios/LiveUpdateGetChannelsResponseItem.swift +4 -0
  79. package/src/ios/LiveUpdateGetConfigResult.swift +18 -0
  80. package/src/ios/LiveUpdateGetCurrentBundleResult.swift +15 -0
  81. package/src/ios/LiveUpdateGetCustomIdResult.swift +15 -0
  82. package/src/ios/LiveUpdateGetDeviceIdResult.swift +15 -0
  83. package/src/ios/LiveUpdateGetDownloadedBundlesResult.swift +15 -0
  84. package/src/ios/LiveUpdateGetLatestBundleResponse.swift +8 -0
  85. package/src/ios/LiveUpdateGetNextBundleResult.swift +15 -0
  86. package/src/ios/LiveUpdateGetVersionCodeResult.swift +15 -0
  87. package/src/ios/LiveUpdateGetVersionNameResult.swift +15 -0
  88. package/src/ios/LiveUpdateHttpClient.swift +58 -0
  89. package/src/ios/LiveUpdateIsSyncingResult.swift +15 -0
  90. package/src/ios/LiveUpdateManifest.swift +19 -0
  91. package/src/ios/LiveUpdateManifestItem.swift +5 -0
  92. package/src/ios/LiveUpdateNextBundleSetEvent.swift +15 -0
  93. package/src/ios/LiveUpdatePlugin.swift +521 -0
  94. package/src/ios/LiveUpdatePreferences.swift +116 -0
  95. package/src/ios/LiveUpdateReadyResult.swift +21 -0
  96. package/src/ios/LiveUpdateResult.swift +5 -0
  97. package/src/ios/LiveUpdateSchemeHandler.swift +286 -0
  98. package/src/ios/LiveUpdateSetChannelOptions.swift +13 -0
  99. package/src/ios/LiveUpdateSetConfigOptions.swift +13 -0
  100. package/src/ios/LiveUpdateSetCustomIdOptions.swift +13 -0
  101. package/src/ios/LiveUpdateSetNextBundleOptions.swift +13 -0
  102. package/src/ios/LiveUpdateSyncOptions.swift +17 -0
  103. package/src/ios/LiveUpdateSyncResult.swift +15 -0
@@ -0,0 +1,286 @@
1
+ import Foundation
2
+ import WebKit
3
+
4
+ /// Serves files from the active live-update bundle directory in response to
5
+ /// WebKit URL scheme tasks. When no bundle is active (or the requested file is
6
+ /// not in the active bundle) `handle(task:)` returns `false`, allowing Cordova's
7
+ /// default scheme handler to fall back to the app's bundled `www/` folder.
8
+ public class LiveUpdateSchemeHandler: NSObject {
9
+ private let activeBundleDirLock = NSLock()
10
+ private var _activeBundleDir: URL?
11
+
12
+ private let activeTasksQueue = DispatchQueue(label: "io.capawesome.cordova.liveupdate.schemeHandler")
13
+ private var activeTasks = Set<URLSchemeTaskWrapper>()
14
+
15
+ public var activeBundleDir: URL? {
16
+ get {
17
+ activeBundleDirLock.lock()
18
+ defer { activeBundleDirLock.unlock() }
19
+ return _activeBundleDir
20
+ }
21
+ set {
22
+ activeBundleDirLock.lock()
23
+ _activeBundleDir = newValue
24
+ activeBundleDirLock.unlock()
25
+ }
26
+ }
27
+
28
+ /// Called from `CDVPlugin.overrideSchemeTask`. Returns `true` if we serve
29
+ /// the resource ourselves, `false` to fall through to the default handler.
30
+ public func handle(task: WKURLSchemeTask) -> Bool {
31
+ guard let baseDir = activeBundleDir else {
32
+ return false
33
+ }
34
+ guard let requestURL = task.request.url else {
35
+ return false
36
+ }
37
+
38
+ // Map the request path to a file inside the active bundle directory.
39
+ var relativePath = requestURL.path
40
+ if relativePath.hasPrefix("/") {
41
+ relativePath = String(relativePath.dropFirst())
42
+ }
43
+ if relativePath.isEmpty || relativePath.hasSuffix("/") {
44
+ relativePath += "index.html"
45
+ }
46
+
47
+ let fileURL = baseDir.appendingPathComponent(relativePath).standardizedFileURL
48
+
49
+ // Defense-in-depth against path traversal.
50
+ let canonicalBase = baseDir.standardizedFileURL.path
51
+ guard fileURL.path == canonicalBase || fileURL.path.hasPrefix(canonicalBase + "/") else {
52
+ return false
53
+ }
54
+
55
+ var isDirectory: ObjCBool = false
56
+ guard FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDirectory), !isDirectory.boolValue else {
57
+ // File not in the active bundle — let the default handler try.
58
+ return false
59
+ }
60
+
61
+ let wrapper = URLSchemeTaskWrapper(task: task)
62
+ activeTasksQueue.sync { _ = activeTasks.insert(wrapper) }
63
+
64
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
65
+ self?.serve(task: task, wrapper: wrapper, fileURL: fileURL, requestURL: requestURL, request: task.request)
66
+ }
67
+ return true
68
+ }
69
+
70
+ public func stop(task: WKURLSchemeTask) {
71
+ activeTasksQueue.sync {
72
+ activeTasks = activeTasks.filter { $0.task !== task }
73
+ }
74
+ }
75
+
76
+ private func isActive(_ wrapper: URLSchemeTaskWrapper) -> Bool {
77
+ return activeTasksQueue.sync { activeTasks.contains(wrapper) }
78
+ }
79
+
80
+ private func removeActive(_ wrapper: URLSchemeTaskWrapper) {
81
+ activeTasksQueue.sync { _ = activeTasks.remove(wrapper) }
82
+ }
83
+
84
+ private func serve(task: WKURLSchemeTask, wrapper: URLSchemeTaskWrapper, fileURL: URL, requestURL: URL, request: URLRequest) {
85
+ let fileHandle: FileHandle
86
+ do {
87
+ fileHandle = try FileHandle(forReadingFrom: fileURL)
88
+ } catch {
89
+ if isActive(wrapper) {
90
+ task.didFailWithError(error)
91
+ }
92
+ removeActive(wrapper)
93
+ return
94
+ }
95
+ defer { try? fileHandle.close() }
96
+
97
+ let fileSize: UInt64
98
+ do {
99
+ let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path)
100
+ fileSize = (attributes[.size] as? NSNumber)?.uint64Value ?? 0
101
+ } catch {
102
+ if isActive(wrapper) {
103
+ task.didFailWithError(error)
104
+ }
105
+ removeActive(wrapper)
106
+ return
107
+ }
108
+
109
+ var statusCode = 200
110
+ let mimeType = mimeType(for: fileURL)
111
+ var headers: [String: String] = [
112
+ "Content-Type": mimeType,
113
+ "Cache-Control": "no-cache"
114
+ ]
115
+ var responseSize = fileSize
116
+ var responseSent: UInt64 = 0
117
+
118
+ // Range request handling
119
+ if let rangeHeader = request.value(forHTTPHeaderField: "Range"), rangeHeader.hasPrefix("bytes=") {
120
+ let byteRange = rangeHeader.dropFirst("bytes=".count)
121
+ let parts = byteRange.split(separator: "-", maxSplits: 1, omittingEmptySubsequences: false)
122
+ let start: UInt64 = UInt64(parts.first.map(String.init) ?? "") ?? 0
123
+ var end: UInt64
124
+ if parts.count > 1, !parts[1].isEmpty, let parsed = UInt64(parts[1]) {
125
+ end = parsed
126
+ } else {
127
+ end = fileSize > 0 ? fileSize - 1 : 0
128
+ }
129
+
130
+ // Clamp the end to the last valid byte so an over-large range does
131
+ // not produce an invalid Content-Length or read past EOF.
132
+ if fileSize > 0 {
133
+ end = min(end, fileSize - 1)
134
+ }
135
+
136
+ // An unsatisfiable range (start past EOF, or end before start)
137
+ // must be answered with 416 rather than underflowing `end - start`.
138
+ if start >= fileSize || end < start {
139
+ headers["Content-Range"] = "bytes */\(fileSize)"
140
+ let response = HTTPURLResponse(
141
+ url: requestURL,
142
+ statusCode: 416,
143
+ httpVersion: "HTTP/1.1",
144
+ headerFields: headers
145
+ )
146
+ if isActive(wrapper), let response = response {
147
+ task.didReceive(response)
148
+ task.didFinish()
149
+ }
150
+ removeActive(wrapper)
151
+ return
152
+ }
153
+
154
+ do {
155
+ try fileHandle.seek(toOffset: start)
156
+ } catch {
157
+ if isActive(wrapper) {
158
+ task.didFailWithError(error)
159
+ }
160
+ removeActive(wrapper)
161
+ return
162
+ }
163
+ let length = end - start + 1
164
+ responseSize = length
165
+ statusCode = 206
166
+ headers["Content-Range"] = "bytes \(start)-\(end)/\(fileSize)"
167
+ headers["Content-Length"] = String(length)
168
+ } else {
169
+ headers["Content-Length"] = String(fileSize)
170
+ }
171
+
172
+ guard let response = HTTPURLResponse(
173
+ url: requestURL,
174
+ statusCode: statusCode,
175
+ httpVersion: "HTTP/1.1",
176
+ headerFields: headers
177
+ ) else {
178
+ removeActive(wrapper)
179
+ return
180
+ }
181
+
182
+ if isActive(wrapper) {
183
+ task.didReceive(response)
184
+ }
185
+
186
+ let bufferSize = 64 * 1024
187
+ while isActive(wrapper) && responseSent < responseSize {
188
+ var reachedEOF = false
189
+ autoreleasepool {
190
+ let chunkSize = min(UInt64(bufferSize), responseSize - responseSent)
191
+ let data: Data
192
+ if #available(iOS 13.4, *) {
193
+ data = (try? fileHandle.read(upToCount: Int(chunkSize))) ?? Data()
194
+ } else {
195
+ data = fileHandle.readData(ofLength: Int(chunkSize))
196
+ }
197
+ if data.isEmpty {
198
+ // `return` only exits the autoreleasepool closure; flag EOF
199
+ // so the enclosing loop terminates instead of spinning.
200
+ reachedEOF = true
201
+ return
202
+ }
203
+ if isActive(wrapper) {
204
+ task.didReceive(data)
205
+ }
206
+ responseSent += UInt64(data.count)
207
+ }
208
+ if reachedEOF {
209
+ break
210
+ }
211
+ }
212
+
213
+ if isActive(wrapper) {
214
+ task.didFinish()
215
+ }
216
+ removeActive(wrapper)
217
+ }
218
+
219
+ private func mimeType(for url: URL) -> String {
220
+ let ext = url.pathExtension.lowercased()
221
+ switch ext {
222
+ case "js", "mjs":
223
+ return "application/javascript"
224
+ case "html", "htm":
225
+ return "text/html"
226
+ case "css":
227
+ return "text/css"
228
+ case "json":
229
+ return "application/json"
230
+ case "wasm":
231
+ return "application/wasm"
232
+ case "svg":
233
+ return "image/svg+xml"
234
+ case "png":
235
+ return "image/png"
236
+ case "jpg", "jpeg":
237
+ return "image/jpeg"
238
+ case "gif":
239
+ return "image/gif"
240
+ case "webp":
241
+ return "image/webp"
242
+ case "ico":
243
+ return "image/x-icon"
244
+ case "woff":
245
+ return "font/woff"
246
+ case "woff2":
247
+ return "font/woff2"
248
+ case "ttf":
249
+ return "font/ttf"
250
+ case "otf":
251
+ return "font/otf"
252
+ case "mp4":
253
+ return "video/mp4"
254
+ case "mp3":
255
+ return "audio/mpeg"
256
+ case "wav":
257
+ return "audio/wav"
258
+ case "txt":
259
+ return "text/plain"
260
+ case "xml":
261
+ return "application/xml"
262
+ case "pdf":
263
+ return "application/pdf"
264
+ default:
265
+ return "application/octet-stream"
266
+ }
267
+ }
268
+ }
269
+
270
+ /// Identity wrapper around `WKURLSchemeTask` so we can store tasks in a `Set`
271
+ /// using reference identity (the protocol itself isn't `Hashable`).
272
+ private final class URLSchemeTaskWrapper: Hashable {
273
+ let task: WKURLSchemeTask
274
+
275
+ init(task: WKURLSchemeTask) {
276
+ self.task = task
277
+ }
278
+
279
+ static func == (lhs: URLSchemeTaskWrapper, rhs: URLSchemeTaskWrapper) -> Bool {
280
+ return lhs.task === rhs.task
281
+ }
282
+
283
+ func hash(into hasher: inout Hasher) {
284
+ hasher.combine(ObjectIdentifier(task as AnyObject))
285
+ }
286
+ }
@@ -0,0 +1,13 @@
1
+ import Foundation
2
+
3
+ @objc public class LiveUpdateSetChannelOptions: NSObject {
4
+ private var channel: String?
5
+
6
+ init(channel: String?) {
7
+ self.channel = channel
8
+ }
9
+
10
+ func getChannel() -> String? {
11
+ return channel
12
+ }
13
+ }
@@ -0,0 +1,13 @@
1
+ import Foundation
2
+
3
+ @objc public class LiveUpdateSetConfigOptions: NSObject {
4
+ let appId: String?
5
+
6
+ init(_ options: [String: Any]) {
7
+ self.appId = options["appId"] as? String
8
+ }
9
+
10
+ public func getAppId() -> String? {
11
+ return appId
12
+ }
13
+ }
@@ -0,0 +1,13 @@
1
+ import Foundation
2
+
3
+ @objc public class LiveUpdateSetCustomIdOptions: NSObject {
4
+ private var customId: String
5
+
6
+ init(customId: String) {
7
+ self.customId = customId
8
+ }
9
+
10
+ func getCustomId() -> String {
11
+ return customId
12
+ }
13
+ }
@@ -0,0 +1,13 @@
1
+ import Foundation
2
+
3
+ @objc public class LiveUpdateSetNextBundleOptions: NSObject {
4
+ private var bundleId: String?
5
+
6
+ init(_ options: [String: Any]) {
7
+ self.bundleId = options["bundleId"] as? String
8
+ }
9
+
10
+ func getBundleId() -> String? {
11
+ return bundleId
12
+ }
13
+ }
@@ -0,0 +1,17 @@
1
+ import Foundation
2
+
3
+ @objc public class LiveUpdateSyncOptions: NSObject {
4
+ private var channel: String?
5
+
6
+ init(_ options: [String: Any]) {
7
+ self.channel = options["channel"] as? String
8
+ }
9
+
10
+ init(channel: String?) {
11
+ self.channel = channel
12
+ }
13
+
14
+ func getChannel() -> String? {
15
+ return channel
16
+ }
17
+ }
@@ -0,0 +1,15 @@
1
+ import Foundation
2
+
3
+ @objc public class LiveUpdateSyncResult: NSObject, LiveUpdateResult {
4
+ let nextBundleId: String?
5
+
6
+ init(nextBundleId: String?) {
7
+ self.nextBundleId = nextBundleId
8
+ }
9
+
10
+ public func toJSObject() -> [String: Any] {
11
+ var result: [String: Any] = [:]
12
+ result["nextBundleId"] = nextBundleId == nil ? NSNull() : nextBundleId
13
+ return result
14
+ }
15
+ }