@capgo/capacitor-updater 7.2.21 → 7.4.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 +22 -16
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +48 -26
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +165 -135
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +85 -51
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipherV2.java +13 -8
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +43 -40
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +24 -20
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +8 -4
- package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +338 -0
- package/dist/docs.json +64 -30
- package/dist/esm/definitions.d.ts +13 -0
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/AES.swift +5 -3
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +125 -93
- package/ios/Plugin/CapgoUpdater.swift +70 -68
- package/ios/Plugin/CryptoCipherV2.swift +31 -26
- package/ios/Plugin/DelayUpdateUtils.swift +26 -24
- package/ios/Plugin/Logger.swift +289 -0
- package/ios/Plugin/UserDefaultsExtension.swift +0 -2
- package/package.json +1 -1
|
@@ -9,36 +9,41 @@ import CryptoKit
|
|
|
9
9
|
import BigInt
|
|
10
10
|
|
|
11
11
|
public struct CryptoCipherV2 {
|
|
12
|
+
private static var logger: Logger!
|
|
13
|
+
|
|
14
|
+
public static func setLogger(_ logger: Logger) {
|
|
15
|
+
self.logger = logger
|
|
16
|
+
}
|
|
12
17
|
|
|
13
18
|
public static func decryptChecksum(checksum: String, publicKey: String) throws -> String {
|
|
14
19
|
if publicKey.isEmpty {
|
|
15
|
-
|
|
20
|
+
logger.info("No encryption set (public key) ignored")
|
|
16
21
|
return checksum
|
|
17
22
|
}
|
|
18
23
|
do {
|
|
19
24
|
guard let checksumBytes = Data(base64Encoded: checksum) else {
|
|
20
|
-
|
|
25
|
+
logger.error("Cannot decode checksum as base64: \(checksum)")
|
|
21
26
|
throw CustomError.cannotDecode
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
if checksumBytes.isEmpty {
|
|
25
|
-
|
|
30
|
+
logger.error("Decoded checksum is empty")
|
|
26
31
|
throw CustomError.cannotDecode
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
guard let rsaPublicKey = RSAPublicKey.load(rsaPublicKey: publicKey) else {
|
|
30
|
-
|
|
35
|
+
logger.error("The public key is not a valid RSA Public key")
|
|
31
36
|
throw CustomError.cannotDecode
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
guard let decryptedChecksum = rsaPublicKey.decrypt(data: checksumBytes) else {
|
|
35
|
-
|
|
40
|
+
logger.error("decryptChecksum fail")
|
|
36
41
|
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
return decryptedChecksum.base64EncodedString()
|
|
40
45
|
} catch {
|
|
41
|
-
|
|
46
|
+
logger.error("decryptChecksum fail: \(error.localizedDescription)")
|
|
42
47
|
throw CustomError.cannotDecode
|
|
43
48
|
}
|
|
44
49
|
}
|
|
@@ -51,7 +56,7 @@ public struct CryptoCipherV2 {
|
|
|
51
56
|
do {
|
|
52
57
|
fileHandle = try FileHandle(forReadingFrom: filePath)
|
|
53
58
|
} catch {
|
|
54
|
-
|
|
59
|
+
logger.error("Cannot open file for checksum: \(filePath.path) \(error)")
|
|
55
60
|
return ""
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -59,7 +64,7 @@ public struct CryptoCipherV2 {
|
|
|
59
64
|
do {
|
|
60
65
|
try fileHandle.close()
|
|
61
66
|
} catch {
|
|
62
|
-
|
|
67
|
+
logger.error("Error closing file: \(error)")
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
70
|
|
|
@@ -72,7 +77,7 @@ public struct CryptoCipherV2 {
|
|
|
72
77
|
fileData = fileHandle.readData(ofLength: bufferSize)
|
|
73
78
|
}
|
|
74
79
|
} catch {
|
|
75
|
-
|
|
80
|
+
logger.error("Error reading file: \(error)")
|
|
76
81
|
return false
|
|
77
82
|
}
|
|
78
83
|
|
|
@@ -87,25 +92,25 @@ public struct CryptoCipherV2 {
|
|
|
87
92
|
let digest = sha256.finalize()
|
|
88
93
|
return digest.compactMap { String(format: "%02x", $0) }.joined()
|
|
89
94
|
} catch {
|
|
90
|
-
|
|
95
|
+
logger.error("Cannot get checksum: \(filePath.path) \(error)")
|
|
91
96
|
return ""
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
public static func decryptFile(filePath: URL, publicKey: String, sessionKey: String, version: String) throws {
|
|
96
101
|
if publicKey.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
97
|
-
|
|
102
|
+
logger.info("Encryption not set, no public key or seesion, ignored")
|
|
98
103
|
return
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
if !publicKey.hasPrefix("-----BEGIN RSA PUBLIC KEY-----") {
|
|
102
|
-
|
|
107
|
+
logger.error("The public key is not a valid RSA Public key")
|
|
103
108
|
return
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
do {
|
|
107
112
|
guard let rsaPublicKey = RSAPublicKey.load(rsaPublicKey: publicKey) else {
|
|
108
|
-
|
|
113
|
+
logger.error("The public key is not a valid RSA Public key")
|
|
109
114
|
throw CustomError.cannotDecode
|
|
110
115
|
}
|
|
111
116
|
|
|
@@ -114,68 +119,68 @@ public struct CryptoCipherV2 {
|
|
|
114
119
|
let encryptedKeyBase64 = sessionKeyComponents[1]
|
|
115
120
|
|
|
116
121
|
guard let ivData = Data(base64Encoded: ivBase64) else {
|
|
117
|
-
|
|
122
|
+
logger.error("Cannot decode sessionKey IV \(ivBase64)")
|
|
118
123
|
throw CustomError.cannotDecode
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
if ivData.count != 16 {
|
|
122
|
-
|
|
127
|
+
logger.error("IV data has invalid length: \(ivData.count), expected 16")
|
|
123
128
|
throw CustomError.cannotDecode
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
guard let sessionKeyDataEncrypted = Data(base64Encoded: encryptedKeyBase64) else {
|
|
127
|
-
|
|
132
|
+
logger.error("Cannot decode sessionKey data \(encryptedKeyBase64)")
|
|
128
133
|
throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
guard let sessionKeyDataDecrypted = rsaPublicKey.decrypt(data: sessionKeyDataEncrypted) else {
|
|
132
|
-
|
|
137
|
+
logger.error("Failed to decrypt session key data")
|
|
133
138
|
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
if sessionKeyDataDecrypted.count != 16 {
|
|
137
|
-
|
|
142
|
+
logger.error("Decrypted session key has invalid length: \(sessionKeyDataDecrypted.count), expected 16")
|
|
138
143
|
throw NSError(domain: "Invalid decrypted session key", code: 5, userInfo: nil)
|
|
139
144
|
}
|
|
140
145
|
|
|
141
|
-
let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
|
|
146
|
+
let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted, logger: logger)
|
|
142
147
|
|
|
143
148
|
let encryptedData: Data
|
|
144
149
|
do {
|
|
145
150
|
encryptedData = try Data(contentsOf: filePath)
|
|
146
151
|
} catch {
|
|
147
|
-
|
|
152
|
+
logger.error("Failed to read encrypted data: \(error)")
|
|
148
153
|
throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
if encryptedData.isEmpty {
|
|
152
|
-
|
|
157
|
+
logger.error("Encrypted file data is empty")
|
|
153
158
|
throw NSError(domain: "Empty encrypted data", code: 6, userInfo: nil)
|
|
154
159
|
}
|
|
155
160
|
|
|
156
161
|
guard let decryptedData = aesPrivateKey.decrypt(data: encryptedData) else {
|
|
157
|
-
|
|
162
|
+
logger.error("Failed to decrypt data")
|
|
158
163
|
throw NSError(domain: "Failed to decrypt data", code: 4, userInfo: nil)
|
|
159
164
|
}
|
|
160
165
|
|
|
161
166
|
if decryptedData.isEmpty {
|
|
162
|
-
|
|
167
|
+
logger.error("Decrypted data is empty")
|
|
163
168
|
throw NSError(domain: "Empty decrypted data", code: 7, userInfo: nil)
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
do {
|
|
167
172
|
try decryptedData.write(to: filePath, options: .atomic)
|
|
168
173
|
if !FileManager.default.fileExists(atPath: filePath.path) {
|
|
169
|
-
|
|
174
|
+
logger.error("File was not created after write")
|
|
170
175
|
throw NSError(domain: "File write failed", code: 8, userInfo: nil)
|
|
171
176
|
}
|
|
172
177
|
} catch {
|
|
173
|
-
|
|
178
|
+
logger.error("Error writing decrypted file: \(error)")
|
|
174
179
|
throw error
|
|
175
180
|
}
|
|
176
181
|
|
|
177
182
|
} catch {
|
|
178
|
-
|
|
183
|
+
logger.error("decryptFile fail")
|
|
179
184
|
throw CustomError.cannotDecode
|
|
180
185
|
}
|
|
181
186
|
}
|
|
@@ -19,6 +19,7 @@ public class DelayUpdateUtils {
|
|
|
19
19
|
|
|
20
20
|
static let DELAY_CONDITION_PREFERENCES = "DELAY_CONDITION_PREFERENCES_CAPGO"
|
|
21
21
|
static let BACKGROUND_TIMESTAMP_KEY = "BACKGROUND_TIMESTAMP_KEY_CAPGO"
|
|
22
|
+
private let logger: Logger
|
|
22
23
|
|
|
23
24
|
private let currentVersionNative: Version
|
|
24
25
|
private let installNext: () -> Void
|
|
@@ -37,9 +38,10 @@ public class DelayUpdateUtils {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
public init(currentVersionNative: Version, installNext: @escaping () -> Void) {
|
|
41
|
+
public init(currentVersionNative: Version, installNext: @escaping () -> Void, logger: Logger) {
|
|
41
42
|
self.currentVersionNative = currentVersionNative
|
|
42
43
|
self.installNext = installNext
|
|
44
|
+
self.logger = logger
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
public func checkCancelDelay(source: CancelDelaySource) {
|
|
@@ -70,14 +72,14 @@ public class DelayUpdateUtils {
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
if delta > longValue {
|
|
73
|
-
|
|
75
|
+
logger.info("Background condition (value: \(value ?? "")) deleted at index \(index). Delta: \(delta), longValue: \(longValue)")
|
|
74
76
|
} else {
|
|
75
77
|
delayConditionListToKeep.append(condition)
|
|
76
|
-
|
|
78
|
+
logger.info("Background delay (value: \(value ?? "")) condition kept at index \(index) (source: \(source.description))")
|
|
77
79
|
}
|
|
78
80
|
} else {
|
|
79
81
|
delayConditionListToKeep.append(condition)
|
|
80
|
-
|
|
82
|
+
logger.info("Background delay (value: \(value ?? "")) condition kept at index \(index) (source: \(source.description))")
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
case "kill":
|
|
@@ -85,7 +87,7 @@ public class DelayUpdateUtils {
|
|
|
85
87
|
self.installNext()
|
|
86
88
|
} else {
|
|
87
89
|
delayConditionListToKeep.append(condition)
|
|
88
|
-
|
|
90
|
+
logger.info("Kill delay (value: \(value ?? "")) condition kept at index \(index) (source: \(source.description))")
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
case "date":
|
|
@@ -96,19 +98,19 @@ public class DelayUpdateUtils {
|
|
|
96
98
|
|
|
97
99
|
if let date = dateFormatter.date(from: value) {
|
|
98
100
|
if Date() > date {
|
|
99
|
-
|
|
101
|
+
logger.info("Date delay (value: \(value)) condition removed due to expired date at index \(index)")
|
|
100
102
|
} else {
|
|
101
103
|
delayConditionListToKeep.append(condition)
|
|
102
|
-
|
|
104
|
+
logger.info("Date delay (value: \(value)) condition kept at index \(index)")
|
|
103
105
|
}
|
|
104
106
|
} else {
|
|
105
|
-
|
|
107
|
+
logger.error("Date delay (value: \(value)) condition removed due to parsing issue at index \(index)")
|
|
106
108
|
}
|
|
107
109
|
} catch {
|
|
108
|
-
|
|
110
|
+
logger.error("Date delay (value: \(value)) condition removed due to parsing issue at index \(index): \(error)")
|
|
109
111
|
}
|
|
110
112
|
} else {
|
|
111
|
-
|
|
113
|
+
logger.error("Date delay (value: \(value ?? "")) condition removed due to empty value at index \(index)")
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
case "nativeVersion":
|
|
@@ -116,20 +118,20 @@ public class DelayUpdateUtils {
|
|
|
116
118
|
do {
|
|
117
119
|
let versionLimit = try Version(value)
|
|
118
120
|
if currentVersionNative >= versionLimit {
|
|
119
|
-
|
|
121
|
+
logger.info("Native version delay (value: \(value)) condition removed due to above limit at index \(index)")
|
|
120
122
|
} else {
|
|
121
123
|
delayConditionListToKeep.append(condition)
|
|
122
|
-
|
|
124
|
+
logger.info("Native version delay (value: \(value)) condition kept at index \(index)")
|
|
123
125
|
}
|
|
124
126
|
} catch {
|
|
125
|
-
|
|
127
|
+
logger.error("Native version delay (value: \(value)) condition removed due to parsing issue at index \(index): \(error)")
|
|
126
128
|
}
|
|
127
129
|
} else {
|
|
128
|
-
|
|
130
|
+
logger.error("Native version delay (value: \(value ?? "")) condition removed due to empty value at index \(index)")
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
default:
|
|
132
|
-
|
|
134
|
+
logger.error("Unknown delay condition kind: \(kind) at index \(index)")
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
index += 1
|
|
@@ -148,10 +150,10 @@ public class DelayUpdateUtils {
|
|
|
148
150
|
do {
|
|
149
151
|
UserDefaults.standard.set(delayConditions, forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES)
|
|
150
152
|
UserDefaults.standard.synchronize()
|
|
151
|
-
|
|
153
|
+
logger.info("Delay update saved")
|
|
152
154
|
return true
|
|
153
155
|
} catch {
|
|
154
|
-
|
|
156
|
+
logger.error("Failed to delay update, [Error calling 'setMultiDelay()']: \(error)")
|
|
155
157
|
return false
|
|
156
158
|
}
|
|
157
159
|
}
|
|
@@ -160,9 +162,9 @@ public class DelayUpdateUtils {
|
|
|
160
162
|
do {
|
|
161
163
|
UserDefaults.standard.set(backgroundTimestamp, forKey: DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY)
|
|
162
164
|
UserDefaults.standard.synchronize()
|
|
163
|
-
|
|
165
|
+
logger.info("Background timestamp saved")
|
|
164
166
|
} catch {
|
|
165
|
-
|
|
167
|
+
logger.error("Failed to save background timestamp, [Error calling 'setBackgroundTimestamp()']: \(error)")
|
|
166
168
|
}
|
|
167
169
|
}
|
|
168
170
|
|
|
@@ -170,9 +172,9 @@ public class DelayUpdateUtils {
|
|
|
170
172
|
do {
|
|
171
173
|
UserDefaults.standard.removeObject(forKey: DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY)
|
|
172
174
|
UserDefaults.standard.synchronize()
|
|
173
|
-
|
|
175
|
+
logger.info("Background timestamp removed")
|
|
174
176
|
} catch {
|
|
175
|
-
|
|
177
|
+
logger.error("Failed to remove background timestamp, [Error calling 'unsetBackgroundTimestamp()']: \(error)")
|
|
176
178
|
}
|
|
177
179
|
}
|
|
178
180
|
|
|
@@ -181,7 +183,7 @@ public class DelayUpdateUtils {
|
|
|
181
183
|
let timestamp = UserDefaults.standard.object(forKey: DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY) as? Int64 ?? 0
|
|
182
184
|
return timestamp
|
|
183
185
|
} catch {
|
|
184
|
-
|
|
186
|
+
logger.error("Failed to get background timestamp, [Error calling 'getBackgroundTimestamp()']: \(error)")
|
|
185
187
|
return 0
|
|
186
188
|
}
|
|
187
189
|
}
|
|
@@ -190,10 +192,10 @@ public class DelayUpdateUtils {
|
|
|
190
192
|
do {
|
|
191
193
|
UserDefaults.standard.removeObject(forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES)
|
|
192
194
|
UserDefaults.standard.synchronize()
|
|
193
|
-
|
|
195
|
+
logger.info("All delays canceled from \(source)")
|
|
194
196
|
return true
|
|
195
197
|
} catch {
|
|
196
|
-
|
|
198
|
+
logger.error("Failed to cancel update delay: \(error)")
|
|
197
199
|
return false
|
|
198
200
|
}
|
|
199
201
|
}
|
|
@@ -0,0 +1,289 @@
|
|
|
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
|
+
webView.evaluateJavaScript("console.\(level.asString())('\(label) \(self.tag) : \(message)')", completionHandler: nil)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public func log(atLevel level: LogLevel, label: String?, tag: String, message: String) {
|
|
211
|
+
// This will never fail, but we have to keep swift happy
|
|
212
|
+
if let label = label ?? _labels[level] {
|
|
213
|
+
print(atLevel: level, label: label, tag: tag, message: message)
|
|
214
|
+
// eval
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private func canLog(atLevel level: LogLevel) -> Bool {
|
|
219
|
+
self.level.rawValue >= level.rawValue
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private func print(atLevel level: LogLevel, label: String, tag: String, message: String) {
|
|
223
|
+
guard canLog(atLevel: level) else {
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
var msg = message
|
|
228
|
+
|
|
229
|
+
if !label.isEmpty {
|
|
230
|
+
// If the label is ASCII, put it after the tag, otherwise before.
|
|
231
|
+
if label[label.startIndex].isASCII {
|
|
232
|
+
msg = "[\(tag)] \(label): \(message)"
|
|
233
|
+
} else {
|
|
234
|
+
msg = "\(label) [\(tag)]: \(message)"
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
msg = "[\(tag)]: \(message)"
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if useSyslog {
|
|
241
|
+
os_log("%{public}@", type: level.asOSLogType(), msg)
|
|
242
|
+
} else {
|
|
243
|
+
Swift.print(msg)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
public func time(_ label: String?) {
|
|
248
|
+
timers[label ?? ""] = Date()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public func timeLog(_ label: String?) {
|
|
252
|
+
if let timer = timers[label ?? ""] {
|
|
253
|
+
info(formatTimeInterval(timer.timeIntervalSinceNow))
|
|
254
|
+
} else {
|
|
255
|
+
warn("timer \(label ?? kDefaultTimerLabel) does not exist")
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
public func timeEnd(_ label: String?) {
|
|
260
|
+
timeLog(label)
|
|
261
|
+
timers.removeValue(forKey: label ?? kDefaultTimerLabel)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private func formatTimeInterval(_ interval: TimeInterval) -> String {
|
|
265
|
+
let int = Int(interval)
|
|
266
|
+
let millis = Int(((1 + interval.remainder(dividingBy: 1)) * 1000).rounded())
|
|
267
|
+
let seconds = int % 60
|
|
268
|
+
let minutes = (int / 60) % 60
|
|
269
|
+
let hours = (int / 3600)
|
|
270
|
+
|
|
271
|
+
if seconds < 1 {
|
|
272
|
+
return "\(millis)ms"
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if minutes < 1 {
|
|
276
|
+
return "\(seconds).\(String(format: "%0.3d", millis))s"
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if hours < 1 {
|
|
280
|
+
return "\(minutes):\(String(format: "%0.2d", seconds)).\(String(format: "%0.3d", millis)) (min:sec.ms)"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return "\(hours):\(String(format: "%0.2d", minutes)):\(String(format: "%0.2d", seconds)) (hr:min:sec)"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public func trace() {
|
|
287
|
+
info(String(format: "%@", Thread.callStackSymbols))
|
|
288
|
+
}
|
|
289
|
+
}
|
|
@@ -34,9 +34,7 @@ extension UserDefaults: ObjectSavable {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
func getObj<Object>(forKey: String, castTo type: Object.Type) throws -> Object where Object: Decodable {
|
|
37
|
-
// print("forKey", forKey)
|
|
38
37
|
guard let data: Data = data(forKey: forKey) else { throw ObjectSavableError.noValue }
|
|
39
|
-
// print("data", data)
|
|
40
38
|
let decoder: JSONDecoder = JSONDecoder()
|
|
41
39
|
do {
|
|
42
40
|
let object: Object = try decoder.decode(type, from: data)
|