@capgo/capacitor-updater 8.0.0 → 8.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.
- package/CapgoCapacitorUpdater.podspec +7 -5
- package/Package.swift +37 -0
- package/README.md +1461 -231
- package/android/build.gradle +29 -12
- package/android/proguard-rules.pro +45 -0
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +223 -195
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +23 -23
- package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +13 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +2159 -1234
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +1507 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +330 -121
- package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +43 -49
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +4 -4
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +260 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +221 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +808 -117
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +156 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +32 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +338 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +72 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +169 -0
- package/dist/docs.json +2187 -625
- package/dist/esm/definitions.d.ts +1286 -249
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/history.d.ts +1 -0
- package/dist/esm/history.js +283 -0
- package/dist/esm/history.js.map +1 -0
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +5 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +36 -41
- package/dist/esm/web.js +94 -35
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +376 -35
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +376 -35
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +69 -0
- package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +55 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleInfo.swift +37 -10
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleStatus.swift +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +1605 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +1526 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +267 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +220 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +311 -0
- package/ios/Sources/CapacitorUpdaterPlugin/Logger.swift +310 -0
- package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +274 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +112 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/UserDefaultsExtension.swift +0 -2
- package/package.json +41 -35
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +0 -1130
- package/ios/Plugin/CapacitorUpdater.swift +0 -858
- package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
- package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -675
- package/ios/Plugin/CryptoCipher.swift +0 -240
- /package/{LICENCE → LICENSE} +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayCondition.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayUntilNext.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/Info.plist +0 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Foundation
|
|
8
|
+
|
|
9
|
+
extension Collection {
|
|
10
|
+
subscript(safe index: Index) -> Element? {
|
|
11
|
+
return indices.contains(index) ? self[index] : nil
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
extension URL {
|
|
15
|
+
var isDirectory: Bool {
|
|
16
|
+
(try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
|
|
17
|
+
}
|
|
18
|
+
var exist: Bool {
|
|
19
|
+
return FileManager().fileExists(atPath: self.path)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
struct SetChannelDec: Decodable {
|
|
23
|
+
let status: String?
|
|
24
|
+
let error: String?
|
|
25
|
+
let message: String?
|
|
26
|
+
}
|
|
27
|
+
public class SetChannel: NSObject {
|
|
28
|
+
var status: String = ""
|
|
29
|
+
var error: String = ""
|
|
30
|
+
var message: String = ""
|
|
31
|
+
}
|
|
32
|
+
extension SetChannel {
|
|
33
|
+
func toDict() -> [String: Any] {
|
|
34
|
+
var dict: [String: Any] = [String: Any]()
|
|
35
|
+
let otherSelf: Mirror = Mirror(reflecting: self)
|
|
36
|
+
for child: Mirror.Child in otherSelf.children {
|
|
37
|
+
if let key: String = child.label {
|
|
38
|
+
dict[key] = child.value
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return dict
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
struct GetChannelDec: Decodable {
|
|
45
|
+
let channel: String?
|
|
46
|
+
let status: String?
|
|
47
|
+
let error: String?
|
|
48
|
+
let message: String?
|
|
49
|
+
let allowSet: Bool?
|
|
50
|
+
}
|
|
51
|
+
public class GetChannel: NSObject {
|
|
52
|
+
var channel: String = ""
|
|
53
|
+
var status: String = ""
|
|
54
|
+
var error: String = ""
|
|
55
|
+
var message: String = ""
|
|
56
|
+
var allowSet: Bool = true
|
|
57
|
+
}
|
|
58
|
+
extension GetChannel {
|
|
59
|
+
func toDict() -> [String: Any] {
|
|
60
|
+
var dict: [String: Any] = [String: Any]()
|
|
61
|
+
let otherSelf: Mirror = Mirror(reflecting: self)
|
|
62
|
+
for child: Mirror.Child in otherSelf.children {
|
|
63
|
+
if let key: String = child.label {
|
|
64
|
+
dict[key] = child.value
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return dict
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
struct ChannelInfo: Codable {
|
|
71
|
+
let id: String?
|
|
72
|
+
let name: String?
|
|
73
|
+
let `public`: Bool?
|
|
74
|
+
let allow_self_set: Bool?
|
|
75
|
+
}
|
|
76
|
+
struct ListChannelsDec: Decodable {
|
|
77
|
+
let channels: [ChannelInfo]?
|
|
78
|
+
let error: String?
|
|
79
|
+
|
|
80
|
+
init(from decoder: Decoder) throws {
|
|
81
|
+
let container = try decoder.singleValueContainer()
|
|
82
|
+
|
|
83
|
+
if let channelsArray = try? container.decode([ChannelInfo].self) {
|
|
84
|
+
// Backend returns direct array
|
|
85
|
+
self.channels = channelsArray
|
|
86
|
+
self.error = nil
|
|
87
|
+
} else {
|
|
88
|
+
// Handle error response
|
|
89
|
+
let errorContainer = try decoder.container(keyedBy: CodingKeys.self)
|
|
90
|
+
self.channels = nil
|
|
91
|
+
self.error = try? errorContainer.decode(String.self, forKey: .error)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private enum CodingKeys: String, CodingKey {
|
|
96
|
+
case error
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
public class ListChannels: NSObject {
|
|
100
|
+
var channels: [[String: Any]] = []
|
|
101
|
+
var error: String = ""
|
|
102
|
+
}
|
|
103
|
+
extension ListChannels {
|
|
104
|
+
func toDict() -> [String: Any] {
|
|
105
|
+
var dict: [String: Any] = [String: Any]()
|
|
106
|
+
let otherSelf: Mirror = Mirror(reflecting: self)
|
|
107
|
+
for child: Mirror.Child in otherSelf.children {
|
|
108
|
+
if let key: String = child.label {
|
|
109
|
+
dict[key] = child.value
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return dict
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
struct InfoObject: Codable {
|
|
116
|
+
let platform: String?
|
|
117
|
+
let device_id: String?
|
|
118
|
+
let app_id: String?
|
|
119
|
+
let custom_id: String?
|
|
120
|
+
let version_build: String?
|
|
121
|
+
let version_code: String?
|
|
122
|
+
let version_os: String?
|
|
123
|
+
var version_name: String?
|
|
124
|
+
var old_version_name: String?
|
|
125
|
+
let plugin_version: String?
|
|
126
|
+
let is_emulator: Bool?
|
|
127
|
+
let is_prod: Bool?
|
|
128
|
+
var action: String?
|
|
129
|
+
var channel: String?
|
|
130
|
+
var defaultChannel: String?
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public struct ManifestEntry: Codable {
|
|
134
|
+
let file_name: String?
|
|
135
|
+
let file_hash: String?
|
|
136
|
+
let download_url: String?
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
extension ManifestEntry {
|
|
140
|
+
func toDict() -> [String: Any] {
|
|
141
|
+
var dict: [String: Any] = [String: Any]()
|
|
142
|
+
let mirror = Mirror(reflecting: self)
|
|
143
|
+
for child in mirror.children {
|
|
144
|
+
if let key = child.label {
|
|
145
|
+
dict[key] = child.value
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return dict
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
struct AppVersionDec: Decodable {
|
|
153
|
+
let version: String?
|
|
154
|
+
let checksum: String?
|
|
155
|
+
let url: String?
|
|
156
|
+
let message: String?
|
|
157
|
+
let error: String?
|
|
158
|
+
let session_key: String?
|
|
159
|
+
let major: Bool?
|
|
160
|
+
let breaking: Bool?
|
|
161
|
+
let data: [String: String]?
|
|
162
|
+
let manifest: [ManifestEntry]?
|
|
163
|
+
let link: String?
|
|
164
|
+
let comment: String?
|
|
165
|
+
// The HTTP status code is captured separately in CapgoUpdater; this struct only mirrors JSON.
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public class AppVersion: NSObject {
|
|
169
|
+
var version: String = ""
|
|
170
|
+
var checksum: String = ""
|
|
171
|
+
var url: String = ""
|
|
172
|
+
var message: String?
|
|
173
|
+
var error: String?
|
|
174
|
+
var sessionKey: String?
|
|
175
|
+
var major: Bool?
|
|
176
|
+
var breaking: Bool?
|
|
177
|
+
var data: [String: String]?
|
|
178
|
+
var manifest: [ManifestEntry]?
|
|
179
|
+
var link: String?
|
|
180
|
+
var comment: String?
|
|
181
|
+
var statusCode: Int = 0
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
extension AppVersion {
|
|
185
|
+
func toDict() -> [String: Any] {
|
|
186
|
+
var dict: [String: Any] = [String: Any]()
|
|
187
|
+
let otherSelf: Mirror = Mirror(reflecting: self)
|
|
188
|
+
for child: Mirror.Child in otherSelf.children {
|
|
189
|
+
if let key: String = child.label {
|
|
190
|
+
if key == "manifest", let manifestEntries = child.value as? [ManifestEntry] {
|
|
191
|
+
dict[key] = manifestEntries.map { $0.toDict() }
|
|
192
|
+
} else {
|
|
193
|
+
dict[key] = child.value
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return dict
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
extension OperatingSystemVersion {
|
|
202
|
+
func getFullVersion(separator: String = ".") -> String {
|
|
203
|
+
return "\(majorVersion)\(separator)\(minorVersion)\(separator)\(patchVersion)"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
extension Bundle {
|
|
207
|
+
var versionName: String? {
|
|
208
|
+
return infoDictionary?["CFBundleShortVersionString"] as? String
|
|
209
|
+
}
|
|
210
|
+
var versionCode: String? {
|
|
211
|
+
return infoDictionary?["CFBundleVersion"] as? String
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
extension ISO8601DateFormatter {
|
|
216
|
+
convenience init(_ formatOptions: Options) {
|
|
217
|
+
self.init()
|
|
218
|
+
self.formatOptions = formatOptions
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
extension Formatter {
|
|
222
|
+
static let iso8601withFractionalSeconds: ISO8601DateFormatter = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
|
|
223
|
+
}
|
|
224
|
+
extension Date {
|
|
225
|
+
var iso8601withFractionalSeconds: String { return Formatter.iso8601withFractionalSeconds.string(from: self) }
|
|
226
|
+
}
|
|
227
|
+
extension String {
|
|
228
|
+
|
|
229
|
+
var fileURL: URL {
|
|
230
|
+
return URL(fileURLWithPath: self)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
var lastPathComponent: String {
|
|
234
|
+
get {
|
|
235
|
+
return fileURL.lastPathComponent
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
var iso8601withFractionalSeconds: Date? {
|
|
239
|
+
return Formatter.iso8601withFractionalSeconds.date(from: self)
|
|
240
|
+
}
|
|
241
|
+
func trim(using characterSet: CharacterSet = .whitespacesAndNewlines) -> String {
|
|
242
|
+
return trimmingCharacters(in: characterSet)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
enum CustomError: Error {
|
|
247
|
+
// Throw when an unzip fail
|
|
248
|
+
case cannotUnzip
|
|
249
|
+
case cannotWrite
|
|
250
|
+
case cannotDecode
|
|
251
|
+
case cannotUnflat
|
|
252
|
+
case cannotCreateDirectory
|
|
253
|
+
case cannotDeleteDirectory
|
|
254
|
+
case cannotDecryptSessionKey
|
|
255
|
+
case invalidBase64
|
|
256
|
+
|
|
257
|
+
// Throw in all other cases
|
|
258
|
+
case unexpected(code: Int)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
extension CustomError: LocalizedError {
|
|
262
|
+
public var errorDescription: String? {
|
|
263
|
+
switch self {
|
|
264
|
+
case .cannotUnzip:
|
|
265
|
+
return NSLocalizedString(
|
|
266
|
+
"The file cannot be unzip",
|
|
267
|
+
comment: "Invalid zip"
|
|
268
|
+
)
|
|
269
|
+
case .cannotCreateDirectory:
|
|
270
|
+
return NSLocalizedString(
|
|
271
|
+
"The folder cannot be created",
|
|
272
|
+
comment: "Invalid folder"
|
|
273
|
+
)
|
|
274
|
+
case .cannotDeleteDirectory:
|
|
275
|
+
return NSLocalizedString(
|
|
276
|
+
"The folder cannot be deleted",
|
|
277
|
+
comment: "Invalid folder"
|
|
278
|
+
)
|
|
279
|
+
case .cannotUnflat:
|
|
280
|
+
return NSLocalizedString(
|
|
281
|
+
"The file cannot be unflat",
|
|
282
|
+
comment: "Invalid folder"
|
|
283
|
+
)
|
|
284
|
+
case .unexpected:
|
|
285
|
+
return NSLocalizedString(
|
|
286
|
+
"An unexpected error occurred.",
|
|
287
|
+
comment: "Unexpected Error"
|
|
288
|
+
)
|
|
289
|
+
case .cannotDecode:
|
|
290
|
+
return NSLocalizedString(
|
|
291
|
+
"Decoding the zip failed with this key",
|
|
292
|
+
comment: "Invalid public key"
|
|
293
|
+
)
|
|
294
|
+
case .cannotWrite:
|
|
295
|
+
return NSLocalizedString(
|
|
296
|
+
"Cannot write to the destination",
|
|
297
|
+
comment: "Invalid destination"
|
|
298
|
+
)
|
|
299
|
+
case .cannotDecryptSessionKey:
|
|
300
|
+
return NSLocalizedString(
|
|
301
|
+
"Decrypting the session key failed",
|
|
302
|
+
comment: "Invalid session key"
|
|
303
|
+
)
|
|
304
|
+
case .invalidBase64:
|
|
305
|
+
return NSLocalizedString(
|
|
306
|
+
"Decrypting the base64 failed",
|
|
307
|
+
comment: "Invalid checksum key"
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import Capacitor
|
|
2
|
+
import Foundation
|
|
3
|
+
import os.log
|
|
4
|
+
import WebKit
|
|
5
|
+
|
|
6
|
+
public class Logger {
|
|
7
|
+
public enum LogLevel: Int {
|
|
8
|
+
case silent = 0
|
|
9
|
+
case error
|
|
10
|
+
case warn
|
|
11
|
+
case info
|
|
12
|
+
case debug
|
|
13
|
+
|
|
14
|
+
public static subscript(_ str: String) -> LogLevel? {
|
|
15
|
+
var index = 0
|
|
16
|
+
|
|
17
|
+
while let item = LogLevel(rawValue: index) {
|
|
18
|
+
if str == "\(item)" {
|
|
19
|
+
return item
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
index += 1
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return nil
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public func asOSLogType() -> OSLogType {
|
|
29
|
+
switch self {
|
|
30
|
+
case .debug:
|
|
31
|
+
return OSLogType.debug
|
|
32
|
+
case .info:
|
|
33
|
+
return OSLogType.info
|
|
34
|
+
case .warn:
|
|
35
|
+
return OSLogType.default
|
|
36
|
+
case .error:
|
|
37
|
+
return OSLogType.error
|
|
38
|
+
case .silent:
|
|
39
|
+
return OSLogType.info // Compiler wants this, it will never be used
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
public func asString() -> String {
|
|
43
|
+
switch self {
|
|
44
|
+
case .debug:
|
|
45
|
+
return "debug"
|
|
46
|
+
case .info:
|
|
47
|
+
return "info"
|
|
48
|
+
case .warn:
|
|
49
|
+
return "warn"
|
|
50
|
+
case .error:
|
|
51
|
+
return "error"
|
|
52
|
+
case .silent:
|
|
53
|
+
return "info"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public struct Options {
|
|
59
|
+
public var level: LogLevel
|
|
60
|
+
public var labels: [String: String]
|
|
61
|
+
public var useSyslog: Bool
|
|
62
|
+
|
|
63
|
+
public init(level: LogLevel = LogLevel.info, labels: [String: String] = [:], useSyslog: Bool = false) {
|
|
64
|
+
self.level = level
|
|
65
|
+
self.labels = labels
|
|
66
|
+
self.useSyslog = useSyslog
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private var _labels: [LogLevel: String] = [
|
|
71
|
+
LogLevel.silent: "",
|
|
72
|
+
LogLevel.error: "🔴",
|
|
73
|
+
LogLevel.warn: "🟠",
|
|
74
|
+
LogLevel.info: "🟢",
|
|
75
|
+
LogLevel.debug: "🔎"
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
public var labels: [String: String] {
|
|
79
|
+
get {
|
|
80
|
+
var result: [String: String] = [:]
|
|
81
|
+
|
|
82
|
+
for (level, label) in _labels {
|
|
83
|
+
result[String(describing: level)] = label
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
set {
|
|
90
|
+
for (level, label) in newValue {
|
|
91
|
+
if let logLevel = LogLevel[level] {
|
|
92
|
+
_labels[logLevel] = label
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public var level = LogLevel.info
|
|
99
|
+
|
|
100
|
+
public var levelName: String {
|
|
101
|
+
get {
|
|
102
|
+
String(describing: level)
|
|
103
|
+
}
|
|
104
|
+
set {
|
|
105
|
+
if let newLevel = LogLevel[newValue] {
|
|
106
|
+
level = newLevel
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private var _tag = ""
|
|
112
|
+
|
|
113
|
+
public var tag: String {
|
|
114
|
+
get {
|
|
115
|
+
_tag
|
|
116
|
+
}
|
|
117
|
+
set {
|
|
118
|
+
if !newValue.isEmpty {
|
|
119
|
+
_tag = newValue
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private let kDefaultTimerLabel = "default"
|
|
125
|
+
private var timers: [String: Date] = [:]
|
|
126
|
+
public var useSyslog = false
|
|
127
|
+
public var webView: WKWebView?
|
|
128
|
+
|
|
129
|
+
public func setWebView(webView: WKWebView) {
|
|
130
|
+
self.webView = webView
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public init(
|
|
134
|
+
withTag tag: String,
|
|
135
|
+
config: InstanceConfiguration? = nil,
|
|
136
|
+
options: Options? = nil
|
|
137
|
+
) {
|
|
138
|
+
self.tag = tag
|
|
139
|
+
if let config = config {
|
|
140
|
+
// The logger plugin's name is LoggerBridge, we want to look at the config
|
|
141
|
+
// named "Logger", so we can't use plugin.getConfigValue().
|
|
142
|
+
if let configLevel = getConfigValue("level", from: config) as? String,
|
|
143
|
+
let logLevel = LogLevel[configLevel] {
|
|
144
|
+
level = logLevel
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if let configLabels = getConfigValue("labels", from: config) as? [String: String] {
|
|
148
|
+
labels = configLabels
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if let configSyslog = getConfigValue("useSyslog", from: config) as? Bool {
|
|
152
|
+
useSyslog = configSyslog
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if let options = options {
|
|
157
|
+
self.level = options.level
|
|
158
|
+
self.labels = options.labels
|
|
159
|
+
self.useSyslog = options.useSyslog
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private func getConfigValue(_ configKey: String, from config: InstanceConfiguration) -> Any? {
|
|
164
|
+
if let config = config.pluginConfigurations as? JSObject {
|
|
165
|
+
return config[keyPath: KeyPath(stringLiteral: "Logger.\(configKey)")]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return nil
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public func error(_ message: String) {
|
|
172
|
+
log(atLevel: LogLevel.error, message: message)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public func warn(_ message: String) {
|
|
176
|
+
log(atLevel: LogLevel.warn, message: message)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public func info(_ message: String) {
|
|
180
|
+
log(atLevel: LogLevel.info, message: message)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public func log(_ message: String) {
|
|
184
|
+
log(atLevel: LogLevel.info, message: message)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public func debug(_ message: String) {
|
|
188
|
+
log(atLevel: LogLevel.debug, message: message)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public func dir(_ value: Any?) {
|
|
192
|
+
// Check the log level here to avoid conversion to string
|
|
193
|
+
if canLog(atLevel: LogLevel.info) {
|
|
194
|
+
log(atLevel: LogLevel.info, message: String(describing: value))
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public func log(atLevel level: LogLevel, message: String) {
|
|
199
|
+
// This will never fail, but we have to keep swift happy
|
|
200
|
+
if let label = _labels[level] {
|
|
201
|
+
log(atLevel: level, label: label, tag: tag, message: message)
|
|
202
|
+
if let webView = self.webView {
|
|
203
|
+
DispatchQueue.main.async {
|
|
204
|
+
let combined = "\(label) \(self.tag) : \(message)"
|
|
205
|
+
let jsArg = self.toJSStringLiteral(combined)
|
|
206
|
+
webView.evaluateJavaScript("console.\(level.asString())(\(jsArg))", completionHandler: nil)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private func toJSStringLiteral(_ value: String) -> String {
|
|
213
|
+
// Prefer JSON encoding to produce a valid JS string literal
|
|
214
|
+
if let data = try? JSONEncoder().encode(value),
|
|
215
|
+
let encoded = String(data: data, encoding: .utf8) {
|
|
216
|
+
return encoded
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Fallback manual escaping (unlikely to be used)
|
|
220
|
+
var s = value
|
|
221
|
+
s = s.replacingOccurrences(of: "\\", with: "\\\\")
|
|
222
|
+
s = s.replacingOccurrences(of: "\"", with: "\\\"")
|
|
223
|
+
s = s.replacingOccurrences(of: "'", with: "\\'")
|
|
224
|
+
s = s.replacingOccurrences(of: "\n", with: "\\n")
|
|
225
|
+
s = s.replacingOccurrences(of: "\r", with: "\\r")
|
|
226
|
+
s = s.replacingOccurrences(of: "\u{2028}", with: "\\u2028")
|
|
227
|
+
s = s.replacingOccurrences(of: "\u{2029}", with: "\\u2029")
|
|
228
|
+
return "\"\(s)\""
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
public func log(atLevel level: LogLevel, label: String?, tag: String, message: String) {
|
|
232
|
+
// This will never fail, but we have to keep swift happy
|
|
233
|
+
if let label = label ?? _labels[level] {
|
|
234
|
+
print(atLevel: level, label: label, tag: tag, message: message)
|
|
235
|
+
// eval
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private func canLog(atLevel level: LogLevel) -> Bool {
|
|
240
|
+
self.level.rawValue >= level.rawValue
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private func print(atLevel level: LogLevel, label: String, tag: String, message: String) {
|
|
244
|
+
guard canLog(atLevel: level) else {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
var msg = message
|
|
249
|
+
|
|
250
|
+
if !label.isEmpty {
|
|
251
|
+
// If the label is ASCII, put it after the tag, otherwise before.
|
|
252
|
+
if label[label.startIndex].isASCII {
|
|
253
|
+
msg = "[\(tag)] \(label): \(message)"
|
|
254
|
+
} else {
|
|
255
|
+
msg = "\(label) [\(tag)]: \(message)"
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
msg = "[\(tag)]: \(message)"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if useSyslog {
|
|
262
|
+
os_log("%{public}@", type: level.asOSLogType(), msg)
|
|
263
|
+
} else {
|
|
264
|
+
Swift.print(msg)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public func time(_ label: String?) {
|
|
269
|
+
timers[label ?? ""] = Date()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public func timeLog(_ label: String?) {
|
|
273
|
+
if let timer = timers[label ?? ""] {
|
|
274
|
+
info(formatTimeInterval(timer.timeIntervalSinceNow))
|
|
275
|
+
} else {
|
|
276
|
+
warn("timer \(label ?? kDefaultTimerLabel) does not exist")
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
public func timeEnd(_ label: String?) {
|
|
281
|
+
timeLog(label)
|
|
282
|
+
timers.removeValue(forKey: label ?? kDefaultTimerLabel)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private func formatTimeInterval(_ interval: TimeInterval) -> String {
|
|
286
|
+
let int = Int(interval)
|
|
287
|
+
let millis = Int(((1 + interval.remainder(dividingBy: 1)) * 1000).rounded())
|
|
288
|
+
let seconds = int % 60
|
|
289
|
+
let minutes = (int / 60) % 60
|
|
290
|
+
let hours = (int / 3600)
|
|
291
|
+
|
|
292
|
+
if seconds < 1 {
|
|
293
|
+
return "\(millis)ms"
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if minutes < 1 {
|
|
297
|
+
return "\(seconds).\(String(format: "%0.3d", millis))s"
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if hours < 1 {
|
|
301
|
+
return "\(minutes):\(String(format: "%0.2d", seconds)).\(String(format: "%0.3d", millis)) (min:sec.ms)"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return "\(hours):\(String(format: "%0.2d", minutes)):\(String(format: "%0.2d", seconds)) (hr:min:sec)"
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
public func trace() {
|
|
308
|
+
info(String(format: "%@", Thread.callStackSymbols))
|
|
309
|
+
}
|
|
310
|
+
}
|