@capacitor-plus/ios 8.0.0-beta.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/Capacitor/Capacitor/AppUUID.swift +51 -0
- package/Capacitor/Capacitor/Array+Capacitor.swift +31 -0
- package/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift +32 -0
- package/Capacitor/Capacitor/CAPBridge.swift +25 -0
- package/Capacitor/Capacitor/CAPBridgeDelegate.swift +6 -0
- package/Capacitor/Capacitor/CAPBridgeProtocol.swift +148 -0
- package/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.h +6 -0
- package/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.m +5 -0
- package/Capacitor/Capacitor/CAPBridgeViewController.swift +384 -0
- package/Capacitor/Capacitor/CAPBridgedJSTypes.h +18 -0
- package/Capacitor/Capacitor/CAPBridgedJSTypes.m +43 -0
- package/Capacitor/Capacitor/CAPBridgedPlugin+getMethod.swift +13 -0
- package/Capacitor/Capacitor/CAPBridgedPlugin.h +41 -0
- package/Capacitor/Capacitor/CAPFile.swift +14 -0
- package/Capacitor/Capacitor/CAPInstanceConfiguration.h +38 -0
- package/Capacitor/Capacitor/CAPInstanceConfiguration.m +93 -0
- package/Capacitor/Capacitor/CAPInstanceConfiguration.swift +78 -0
- package/Capacitor/Capacitor/CAPInstanceDescriptor.h +167 -0
- package/Capacitor/Capacitor/CAPInstanceDescriptor.m +56 -0
- package/Capacitor/Capacitor/CAPInstanceDescriptor.swift +198 -0
- package/Capacitor/Capacitor/CAPInstancePlugin.swift +10 -0
- package/Capacitor/Capacitor/CAPLog.swift +11 -0
- package/Capacitor/Capacitor/CAPNotifications.swift +69 -0
- package/Capacitor/Capacitor/CAPPlugin+LoadInstance.swift +20 -0
- package/Capacitor/Capacitor/CAPPlugin.h +60 -0
- package/Capacitor/Capacitor/CAPPlugin.m +180 -0
- package/Capacitor/Capacitor/CAPPluginCall.h +25 -0
- package/Capacitor/Capacitor/CAPPluginCall.m +36 -0
- package/Capacitor/Capacitor/CAPPluginCall.swift +97 -0
- package/Capacitor/Capacitor/CAPPluginMethod.h +36 -0
- package/Capacitor/Capacitor/CAPPluginMethod.m +44 -0
- package/Capacitor/Capacitor/CAPPluginMethod.swift +17 -0
- package/Capacitor/Capacitor/Capacitor.h +15 -0
- package/Capacitor/Capacitor/Capacitor.modulemap +8 -0
- package/Capacitor/Capacitor/CapacitorBridge.swift +768 -0
- package/Capacitor/Capacitor/CapacitorExtension.swift +24 -0
- package/Capacitor/Capacitor/Codable/JSValueDecoder.swift +479 -0
- package/Capacitor/Capacitor/Codable/JSValueEncoder.swift +671 -0
- package/Capacitor/Capacitor/Data+Capacitor.swift +22 -0
- package/Capacitor/Capacitor/DocLinks.swift +8 -0
- package/Capacitor/Capacitor/Info.plist +24 -0
- package/Capacitor/Capacitor/JS.swift +122 -0
- package/Capacitor/Capacitor/JSExport.swift +218 -0
- package/Capacitor/Capacitor/JSTypes.swift +254 -0
- package/Capacitor/Capacitor/KeyPath.swift +60 -0
- package/Capacitor/Capacitor/KeyValueStore.swift +303 -0
- package/Capacitor/Capacitor/NotificationHandlerProtocol.swift +6 -0
- package/Capacitor/Capacitor/NotificationRouter.swift +60 -0
- package/Capacitor/Capacitor/PluginCallResult.swift +106 -0
- package/Capacitor/Capacitor/PluginConfig.swift +56 -0
- package/Capacitor/Capacitor/Plugins/CapacitorCookieManager.swift +141 -0
- package/Capacitor/Capacitor/Plugins/CapacitorCookies.swift +57 -0
- package/Capacitor/Capacitor/Plugins/CapacitorHttp.swift +57 -0
- package/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift +225 -0
- package/Capacitor/Capacitor/Plugins/Console.swift +16 -0
- package/Capacitor/Capacitor/Plugins/HttpRequestHandler.swift +236 -0
- package/Capacitor/Capacitor/Plugins/SystemBars.swift +133 -0
- package/Capacitor/Capacitor/Plugins/WebView.swift +44 -0
- package/Capacitor/Capacitor/PrivacyInfo.xcprivacy +14 -0
- package/Capacitor/Capacitor/Router.swift +29 -0
- package/Capacitor/Capacitor/TmpViewController.swift +8 -0
- package/Capacitor/Capacitor/UIColor.swift +55 -0
- package/Capacitor/Capacitor/UIStatusBarManager+CAPHandleTapAction.m +37 -0
- package/Capacitor/Capacitor/WKWebView+Capacitor.m +15 -0
- package/Capacitor/Capacitor/WKWebView+Capacitor.swift +80 -0
- package/Capacitor/Capacitor/WebViewAssetHandler.swift +550 -0
- package/Capacitor/Capacitor/WebViewDelegationHandler.swift +361 -0
- package/Capacitor/Capacitor/assets/native-bridge.js +1029 -0
- package/Capacitor.podspec +23 -0
- package/CapacitorCordova/CapacitorCordova/CapacitorCordova.h +25 -0
- package/CapacitorCordova/CapacitorCordova/CapacitorCordova.modulemap +6 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/AppDelegate.h +8 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/AppDelegate.m +5 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDV.h +28 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVAvailability.h +109 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVAvailabilityDeprecated.h +21 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVCommandDelegate.h +49 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVCommandDelegateImpl.h +39 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVCommandDelegateImpl.m +149 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVConfigParser.h +31 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVConfigParser.m +81 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVInvokedUrlCommand.h +52 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVInvokedUrlCommand.m +116 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVPlugin+Resources.h +39 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVPlugin+Resources.m +38 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVPlugin.h +76 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVPlugin.m +136 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVPluginManager.h +25 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVPluginManager.m +77 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVPluginResult.h +82 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVPluginResult.m +216 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVScreenOrientationDelegate.h +33 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVURLProtocol.h +27 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVURLProtocol.m +74 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVViewController.h +30 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVViewController.m +37 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVWebViewProcessPoolFactory.h +34 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/CDVWebViewProcessPoolFactory.m +55 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/NSDictionary+CordovaPreferences.h +35 -0
- package/CapacitorCordova/CapacitorCordova/Classes/Public/NSDictionary+CordovaPreferences.m +63 -0
- package/CapacitorCordova/CapacitorCordova/Info.plist +26 -0
- package/CapacitorCordova/CapacitorCordova/PrivacyInfo.xcprivacy +14 -0
- package/CapacitorCordova.podspec +26 -0
- package/LICENSE +21 -0
- package/package.json +33 -0
- package/scripts/pods_helpers.rb +12 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
//
|
|
2
|
+
// KeyValueStore.swift
|
|
3
|
+
// Capacitor
|
|
4
|
+
//
|
|
5
|
+
// Created by Steven Sherry on 1/5/24.
|
|
6
|
+
// Copyright © 2024 Drifty Co. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
|
|
11
|
+
/// A generic KeyValueStore that allows storing and retrieving values associated with string keys.
|
|
12
|
+
/// The store supports both ephemeral (in-memory) storage and persistent (file-based) storage backends
|
|
13
|
+
/// by default, however it can also take anything that conforms to ``KeyValueStoreBackend`` as
|
|
14
|
+
/// a backend.
|
|
15
|
+
///
|
|
16
|
+
/// This class provides methods to get, set and delete key-value pairs for any type of value, provided the
|
|
17
|
+
/// types conform to `Codable`. The default ``Backend/ephemeral`` and ``Backend/persistent(suiteName:)``
|
|
18
|
+
/// backends are thread-safe.
|
|
19
|
+
///
|
|
20
|
+
/// ## Usage Examples
|
|
21
|
+
///
|
|
22
|
+
/// ### Non-throwing API
|
|
23
|
+
/// ```swift
|
|
24
|
+
/// let store = KeyValueStore.standard
|
|
25
|
+
/// // Set
|
|
26
|
+
/// store["key"] = "value"
|
|
27
|
+
///
|
|
28
|
+
/// // Get
|
|
29
|
+
/// if let value = store["key", as: String.self] {
|
|
30
|
+
/// // Do something with value
|
|
31
|
+
/// }
|
|
32
|
+
///
|
|
33
|
+
/// // Delete
|
|
34
|
+
/// // The type here is a required argument because
|
|
35
|
+
/// // it is unable to be inferred
|
|
36
|
+
/// store["key", as: String.self] = nil
|
|
37
|
+
/// // or
|
|
38
|
+
/// store["key"] = nil as String?
|
|
39
|
+
/// ```
|
|
40
|
+
///
|
|
41
|
+
///
|
|
42
|
+
/// ### Throwing API
|
|
43
|
+
///
|
|
44
|
+
/// ```swift
|
|
45
|
+
/// let store = KeyValueStore.standard
|
|
46
|
+
/// do {
|
|
47
|
+
/// // Set
|
|
48
|
+
/// try store.set("key", value: "value")
|
|
49
|
+
///
|
|
50
|
+
/// // Get
|
|
51
|
+
/// if let value = try store.get("key", as: String.self) {
|
|
52
|
+
/// // Do something with value
|
|
53
|
+
/// }
|
|
54
|
+
///
|
|
55
|
+
/// // Delete
|
|
56
|
+
/// try store.delete("key")
|
|
57
|
+
/// } catch {
|
|
58
|
+
/// print(error.localizedDescription)
|
|
59
|
+
/// }
|
|
60
|
+
/// ```
|
|
61
|
+
///
|
|
62
|
+
/// ### Throwing vs Non-throwing
|
|
63
|
+
///
|
|
64
|
+
/// Of the built-in backends, both ``Backend/ephemeral`` and ``Backend/persistent(suiteName:)`` will throw in the following cases:
|
|
65
|
+
/// * The data read from the file retrieved during ``get(_:as:)`` is unable to be decoded as the type provided.
|
|
66
|
+
/// * The value provided to ``set(_:value:)`` encounters an error during encoding.
|
|
67
|
+
/// * This is more likely to happen with types that have custom `Encodable` implementations
|
|
68
|
+
///
|
|
69
|
+
/// ``Backend/persistent(suiteName:)`` will throw for the following additional cases:
|
|
70
|
+
/// * A file is unable to be read from disk during ``get(_:as:)``
|
|
71
|
+
/// * The existence of the file on disk is checked before attempting to read the file, so out of the
|
|
72
|
+
/// [possible file reading errors](https://developer.apple.com/documentation/foundation/1448136-nserror_codes#file-reading-errors),
|
|
73
|
+
/// the only likely candidate would be
|
|
74
|
+
/// [NSFileReadCorruptFileError](https://developer.apple.com/documentation/foundation/1448136-nserror_codes/nsfilereadcorruptfileerror).
|
|
75
|
+
/// In practice, this should never happen since writes happen atomically.
|
|
76
|
+
/// * The data from the value encoded in ``set(_:value:)`` is unable to be written to disk
|
|
77
|
+
/// * Of the [possible file writing errors](https://developer.apple.com/documentation/foundation/1448136-nserror_codes#file-writing-errors),
|
|
78
|
+
/// the only likely candidates are
|
|
79
|
+
/// [NSFileWriteInvalidFileNameError](https://developer.apple.com/documentation/foundation/1448136-nserror_codes/nsfilewriteinvalidfilenameerror)
|
|
80
|
+
/// if the key provided makes for an invalid file name and
|
|
81
|
+
/// [NSFileWriteOutOfSpaceError](https://developer.apple.com/documentation/foundation/1448136-nserror_codes/nsfilewriteoutofspaceerror)
|
|
82
|
+
/// if the user has no space left on disk
|
|
83
|
+
///
|
|
84
|
+
/// The throwing API should be used in cases where detailed error information is needed for logging or diagnostics. The non-throwing API should be used
|
|
85
|
+
/// in cases where silent failure is preferred.
|
|
86
|
+
public class KeyValueStore {
|
|
87
|
+
|
|
88
|
+
/// The built-in storage backends
|
|
89
|
+
public enum Backend {
|
|
90
|
+
/// An in-memory backing store
|
|
91
|
+
case ephemeral
|
|
92
|
+
/// A persistent file-based backing store using the
|
|
93
|
+
/// `suiteName` as an identifier for the collection of files
|
|
94
|
+
case persistent(suiteName: String)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private let backend: any KeyValueStoreBackend
|
|
98
|
+
|
|
99
|
+
/// Creates an instance of ``KeyValueStore`` with a custom backend
|
|
100
|
+
/// - Parameter backend: The custom backend implementation
|
|
101
|
+
public init(backend: any KeyValueStoreBackend) {
|
|
102
|
+
self.backend = backend
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Creates an instance of ``KeyValueStore`` with the provided built-in ``Backend``
|
|
106
|
+
/// - Parameter type: The type of ``Backend`` to use
|
|
107
|
+
public init(type: Backend) {
|
|
108
|
+
switch type {
|
|
109
|
+
case .ephemeral:
|
|
110
|
+
backend = InMemoryStore()
|
|
111
|
+
case .persistent(suiteName: let name):
|
|
112
|
+
backend = FileStore.with(name: name)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Creates an instance of ``KeyValueStore`` with ``Backend/persistent(suiteName:)``
|
|
117
|
+
/// - Parameter suiteName: The suite name to provide ``Backend/persistent(suiteName:)``
|
|
118
|
+
public convenience init(suiteName: String) {
|
|
119
|
+
self.init(type: .persistent(suiteName: suiteName))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Retrieves a value of the specified type and key
|
|
123
|
+
/// - Parameters:
|
|
124
|
+
/// - key: The unique identifier for the value
|
|
125
|
+
/// - type: The expected type of the value being retried
|
|
126
|
+
/// - Returns: A decoded value of the given type or `nil` if there is no such value
|
|
127
|
+
public func `get`<T>(_ key: String, as type: T.Type = T.self) throws -> T? where T: Decodable {
|
|
128
|
+
try backend.get(key, as: type)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Stores the value under the specified key
|
|
132
|
+
/// - Parameters:
|
|
133
|
+
/// - key: The unique identifier
|
|
134
|
+
/// - value: The value to be stored
|
|
135
|
+
public func `set`<T>(_ key: String, value: T) throws where T: Encodable {
|
|
136
|
+
try backend.set(key, value: value)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// Deletes the value for the specified key
|
|
140
|
+
public func `delete`(_ key: String) throws {
|
|
141
|
+
try backend.delete(key)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// Convenience for accessing and modifying values in the store without calling ``get(_:as:)``, ``set(_:value:)``, or ``delete(_:)``
|
|
145
|
+
/// - Parameters:
|
|
146
|
+
/// - key: The unique identifier for the value to access or modify
|
|
147
|
+
/// - type: The type the value is stored as
|
|
148
|
+
///
|
|
149
|
+
/// This method is only really necessary when accessing a key and the type cannot be inferred from it's context.
|
|
150
|
+
/// ```swift
|
|
151
|
+
/// let store = KeyValueStore.standard
|
|
152
|
+
///
|
|
153
|
+
/// // Get
|
|
154
|
+
/// let value = store["key", as: String.self]
|
|
155
|
+
///
|
|
156
|
+
/// // If the type can be inferred then it may be omitted
|
|
157
|
+
/// let value: String? = store["key"]
|
|
158
|
+
/// let value = store["key"] as String?
|
|
159
|
+
/// let value = store["key"] ?? "default"
|
|
160
|
+
///
|
|
161
|
+
/// // Delete
|
|
162
|
+
/// store["key", as: String.self] = nil
|
|
163
|
+
/// store["key"] = nil as String?
|
|
164
|
+
/// ```
|
|
165
|
+
public subscript<T> (_ key: String, as type: T.Type = T.self) -> T? where T: Codable {
|
|
166
|
+
get { try? self.get(key) }
|
|
167
|
+
set {
|
|
168
|
+
if let newValue {
|
|
169
|
+
try? self.set(key, value: newValue)
|
|
170
|
+
} else {
|
|
171
|
+
try? self.delete(key)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// A shared persistent instance of ``KeyValueStore``
|
|
177
|
+
public static let standard = KeyValueStore(type: .persistent(suiteName: "standard"))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public protocol KeyValueStoreBackend {
|
|
181
|
+
func `get`<T>(_ key: String, as type: T.Type) throws -> T? where T: Decodable
|
|
182
|
+
func `set`<T>(_ key: String, value: T) throws where T: Encodable
|
|
183
|
+
func `delete`(_ key: String) throws
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private class FileStore: KeyValueStoreBackend {
|
|
187
|
+
private let cache = ConcurrentDictionary<Data>()
|
|
188
|
+
private let decoder = JSONDecoder()
|
|
189
|
+
private let encoder = JSONEncoder()
|
|
190
|
+
private let baseUrl: URL
|
|
191
|
+
|
|
192
|
+
private init(baseUrl: URL) {
|
|
193
|
+
self.baseUrl = baseUrl
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func get<T>(_ key: String, as type: T.Type) throws -> T? where T: Decodable {
|
|
197
|
+
if let cached = cache[key],
|
|
198
|
+
let value = try? decoder.decode(type, from: cached) {
|
|
199
|
+
return value
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let fileCacheLocation = baseUrl.appendingPathComponent(key)
|
|
203
|
+
guard FileManager.default.fileExists(atPath: fileCacheLocation.path) else { return nil }
|
|
204
|
+
|
|
205
|
+
let data = try Data(contentsOf: fileCacheLocation)
|
|
206
|
+
let decoded = try decoder.decode(type, from: data)
|
|
207
|
+
|
|
208
|
+
cache[key] = data
|
|
209
|
+
return decoded
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
func set<T>(_ key: String, value: T) throws where T: Encodable {
|
|
213
|
+
let encoded = try encoder.encode(value)
|
|
214
|
+
try encoded.write(to: url(for: key), options: .atomic)
|
|
215
|
+
cache[key] = encoded
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
func delete(_ key: String) throws {
|
|
219
|
+
cache[key] = nil
|
|
220
|
+
try FileManager.default.removeItem(at: url(for: key))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private func url(for key: String) -> URL {
|
|
224
|
+
baseUrl.appendingPathComponent(key)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private static let instances = ConcurrentDictionary<FileStore>()
|
|
228
|
+
|
|
229
|
+
// This ensures we essentially have singletons for accessing file based resources
|
|
230
|
+
// so we don't have a scenario where two separate instances may be writing to
|
|
231
|
+
// the same files.
|
|
232
|
+
static func with(name: String) -> FileStore {
|
|
233
|
+
if let existing = instances[name] { return existing }
|
|
234
|
+
guard let library = try? FileManager
|
|
235
|
+
.default
|
|
236
|
+
.url(
|
|
237
|
+
for: .libraryDirectory,
|
|
238
|
+
in: .userDomainMask,
|
|
239
|
+
appropriateFor: nil,
|
|
240
|
+
create: true
|
|
241
|
+
)
|
|
242
|
+
else { fatalError("⚡️ ❌ Library URL unable to be accessed or created by the current application. This is an impossible state.") }
|
|
243
|
+
|
|
244
|
+
let url = library.appendingPathComponent("kvstore").appendingPathComponent(name)
|
|
245
|
+
|
|
246
|
+
// Create the folder if it doesn't exist. This should never throw for the current base directory, so we ignore the exception.
|
|
247
|
+
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
|
|
248
|
+
|
|
249
|
+
let new = FileStore(baseUrl: url)
|
|
250
|
+
instances[name] = new
|
|
251
|
+
return new
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private class InMemoryStore: KeyValueStoreBackend {
|
|
256
|
+
private let storage = ConcurrentDictionary<Data>()
|
|
257
|
+
private let decoder = JSONDecoder()
|
|
258
|
+
private let encoder = JSONEncoder()
|
|
259
|
+
|
|
260
|
+
func get<T>(_ key: String, as type: T.Type) throws -> T? where T: Decodable {
|
|
261
|
+
guard let data = storage[key] else { return nil }
|
|
262
|
+
return try decoder.decode(type, from: data)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
func set<T>(_ key: String, value: T) throws where T: Encodable {
|
|
266
|
+
let data = try encoder.encode(value)
|
|
267
|
+
storage[key] = data
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
func delete(_ key: String) {
|
|
271
|
+
storage[key] = nil
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
class ConcurrentDictionary<Value> {
|
|
276
|
+
typealias StorageType = [String: Value]
|
|
277
|
+
private var storage: StorageType
|
|
278
|
+
private let lock = NSLock()
|
|
279
|
+
|
|
280
|
+
init(_ initial: StorageType = [:]) {
|
|
281
|
+
storage = initial
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
subscript (_ key: String) -> Value? {
|
|
285
|
+
get {
|
|
286
|
+
lock.withLock {
|
|
287
|
+
storage[key]
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
set {
|
|
292
|
+
lock.withLock {
|
|
293
|
+
storage[key] = newValue
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
func withLock<T>(_ body: (_ storage: inout StorageType) -> T) -> T {
|
|
299
|
+
lock.withLock {
|
|
300
|
+
body(&storage)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
@objc(CAPNotificationRouter) public class NotificationRouter: NSObject, UNUserNotificationCenterDelegate {
|
|
4
|
+
var handleApplicationNotifications: Bool {
|
|
5
|
+
get {
|
|
6
|
+
return UNUserNotificationCenter.current().delegate === self
|
|
7
|
+
}
|
|
8
|
+
set {
|
|
9
|
+
let center = UNUserNotificationCenter.current()
|
|
10
|
+
|
|
11
|
+
if newValue {
|
|
12
|
+
center.delegate = self
|
|
13
|
+
} else if center.delegate === self {
|
|
14
|
+
center.delegate = nil
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public weak var pushNotificationHandler: NotificationHandlerProtocol? {
|
|
20
|
+
didSet {
|
|
21
|
+
if pushNotificationHandler != nil, oldValue != nil {
|
|
22
|
+
CAPLog.print("Push notification handler overriding previous instance: \(String(describing: type(of: oldValue)))")
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public weak var localNotificationHandler: NotificationHandlerProtocol? {
|
|
28
|
+
didSet {
|
|
29
|
+
if localNotificationHandler != nil, oldValue != nil {
|
|
30
|
+
CAPLog.print("Local notification handler overriding previous instance: \(String(describing: type(of: oldValue)))")
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public func userNotificationCenter(_ center: UNUserNotificationCenter,
|
|
36
|
+
willPresent notification: UNNotification,
|
|
37
|
+
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
|
38
|
+
let presentationOptions: UNNotificationPresentationOptions?
|
|
39
|
+
|
|
40
|
+
if notification.request.trigger?.isKind(of: UNPushNotificationTrigger.self) == true {
|
|
41
|
+
presentationOptions = pushNotificationHandler?.willPresent(notification: notification)
|
|
42
|
+
} else {
|
|
43
|
+
presentationOptions = localNotificationHandler?.willPresent(notification: notification)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
completionHandler(presentationOptions ?? [])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public func userNotificationCenter(_ center: UNUserNotificationCenter,
|
|
50
|
+
didReceive response: UNNotificationResponse,
|
|
51
|
+
withCompletionHandler completionHandler: @escaping () -> Void) {
|
|
52
|
+
if response.notification.request.trigger?.isKind(of: UNPushNotificationTrigger.self) == true {
|
|
53
|
+
pushNotificationHandler?.didReceive(response: response)
|
|
54
|
+
} else {
|
|
55
|
+
localNotificationHandler?.didReceive(response: response)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
completionHandler()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public typealias PluginCallResultData = [String: Any]
|
|
4
|
+
|
|
5
|
+
public enum PluginCallResult {
|
|
6
|
+
case dictionary(PluginCallResultData)
|
|
7
|
+
|
|
8
|
+
enum SerializationError: Error {
|
|
9
|
+
case invalidObject
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
func jsonRepresentation(includingFields: PluginCallResultData? = nil) throws -> String? {
|
|
13
|
+
switch self {
|
|
14
|
+
case .dictionary(var dictionary):
|
|
15
|
+
if let fields = includingFields {
|
|
16
|
+
dictionary.merge(fields) { (current, _) in current }
|
|
17
|
+
}
|
|
18
|
+
dictionary = prepare(dictionary: dictionary)
|
|
19
|
+
guard JSONSerialization.isValidJSONObject(dictionary) else {
|
|
20
|
+
throw SerializationError.invalidObject
|
|
21
|
+
}
|
|
22
|
+
let data = try JSONSerialization.data(withJSONObject: dictionary, options: [])
|
|
23
|
+
return String(data: data, encoding: .utf8)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private static let formatter = ISO8601DateFormatter()
|
|
28
|
+
|
|
29
|
+
private func prepare(dictionary: PluginCallResultData) -> PluginCallResultData {
|
|
30
|
+
return dictionary.mapValues { (value) -> Any in
|
|
31
|
+
if let date = value as? Date {
|
|
32
|
+
return PluginCallResult.formatter.string(from: date)
|
|
33
|
+
} else if let aDictionary = value as? PluginCallResultData {
|
|
34
|
+
return prepare(dictionary: aDictionary)
|
|
35
|
+
} else if let anArray = value as? [Any] {
|
|
36
|
+
return prepare(array: anArray)
|
|
37
|
+
}
|
|
38
|
+
return value
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private func prepare(array: [Any]) -> [Any] {
|
|
43
|
+
return array.map { (value) -> Any in
|
|
44
|
+
if let date = value as? Date {
|
|
45
|
+
return PluginCallResult.formatter.string(from: date)
|
|
46
|
+
} else if let aDictionary = value as? PluginCallResultData {
|
|
47
|
+
return prepare(dictionary: aDictionary)
|
|
48
|
+
} else if let anArray = value as? [Any] {
|
|
49
|
+
return prepare(array: anArray)
|
|
50
|
+
}
|
|
51
|
+
return value
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@objc public class CAPPluginCallResult: NSObject {
|
|
57
|
+
public let resultData: PluginCallResult?
|
|
58
|
+
|
|
59
|
+
@objc public var data: PluginCallResultData? {
|
|
60
|
+
guard let result = resultData else {
|
|
61
|
+
return nil
|
|
62
|
+
}
|
|
63
|
+
switch result {
|
|
64
|
+
case .dictionary(let data):
|
|
65
|
+
return data
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@objc(init:)
|
|
70
|
+
public init(_ data: PluginCallResultData?) {
|
|
71
|
+
if let data = data {
|
|
72
|
+
resultData = .dictionary(data)
|
|
73
|
+
} else {
|
|
74
|
+
resultData = nil
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@objc public class CAPPluginCallError: NSObject {
|
|
80
|
+
@objc public let message: String
|
|
81
|
+
@objc public let code: String?
|
|
82
|
+
@objc public let error: Error?
|
|
83
|
+
public let resultData: PluginCallResult?
|
|
84
|
+
|
|
85
|
+
@objc public var data: PluginCallResultData? {
|
|
86
|
+
guard let result = resultData else {
|
|
87
|
+
return nil
|
|
88
|
+
}
|
|
89
|
+
switch result {
|
|
90
|
+
case .dictionary(let data):
|
|
91
|
+
return data
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@objc(init:code:error:data:)
|
|
96
|
+
public init(message: String, code: String?, error: Error?, data: PluginCallResultData?) {
|
|
97
|
+
self.message = message
|
|
98
|
+
self.code = code
|
|
99
|
+
self.error = error
|
|
100
|
+
if let data = data {
|
|
101
|
+
resultData = .dictionary(["data": data])
|
|
102
|
+
} else {
|
|
103
|
+
resultData = nil
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
@objc public class PluginConfig: NSObject {
|
|
4
|
+
|
|
5
|
+
// The object containing the plugin config values
|
|
6
|
+
private var config: JSObject
|
|
7
|
+
|
|
8
|
+
init(config: JSObject) {
|
|
9
|
+
self.config = config
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@objc public func getString(_ configKey: String, _ defaultValue: String? = nil) -> String? {
|
|
13
|
+
if let val = (self.config)[keyPath: KeyPath(configKey)] as? String {
|
|
14
|
+
return val
|
|
15
|
+
}
|
|
16
|
+
return defaultValue
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@objc public func getBoolean(_ configKey: String, _ defaultValue: Bool) -> Bool {
|
|
20
|
+
if let val = (self.config)[keyPath: KeyPath(configKey)] as? Bool {
|
|
21
|
+
return val
|
|
22
|
+
}
|
|
23
|
+
return defaultValue
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@objc public func getInt(_ configKey: String, _ defaultValue: Int) -> Int {
|
|
27
|
+
if let val = (self.config)[keyPath: KeyPath(configKey)] as? Int {
|
|
28
|
+
return val
|
|
29
|
+
}
|
|
30
|
+
return defaultValue
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public func getArray(_ configKey: String, _ defaultValue: JSArray? = nil) -> JSArray? {
|
|
34
|
+
if let val = (self.config)[keyPath: KeyPath(configKey)] as? JSArray {
|
|
35
|
+
return val
|
|
36
|
+
}
|
|
37
|
+
return defaultValue
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public func getObject(_ configKey: String) -> JSObject? {
|
|
41
|
+
return (self.config)[keyPath: KeyPath(configKey)] as? JSObject
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@objc public func isEmpty() -> Bool {
|
|
45
|
+
return self.config.isEmpty
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Gets the JSObject containing the config of the the provided plugin ID.
|
|
50
|
+
*
|
|
51
|
+
* @return The config for that plugin
|
|
52
|
+
*/
|
|
53
|
+
public func getConfigJSON() -> JSObject {
|
|
54
|
+
return self.config
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public class CapacitorWKCookieObserver: NSObject, WKHTTPCookieStoreObserver {
|
|
4
|
+
// Sync WKWebView Cookies to HTTPCookieStorage
|
|
5
|
+
public func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
|
|
6
|
+
DispatchQueue.main.async {
|
|
7
|
+
cookieStore.getAllCookies { cookies in
|
|
8
|
+
cookies.forEach { cookie in
|
|
9
|
+
HTTPCookieStorage.shared.setCookie(cookie)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public class CapacitorCookieManager {
|
|
17
|
+
var config: InstanceConfiguration?
|
|
18
|
+
|
|
19
|
+
init(_ capConfig: InstanceConfiguration?) {
|
|
20
|
+
self.config = capConfig
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public func getServerUrl() -> URL? {
|
|
24
|
+
return self.config?.serverURL ?? self.config?.localURL
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private func isUrlSanitized(_ urlString: String) -> Bool {
|
|
28
|
+
return urlString.isEmpty || urlString == getServerUrl()?.absoluteString || urlString.hasPrefix("http://") || urlString.hasPrefix("https://")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public func getServerUrl(_ urlString: String?) -> URL? {
|
|
32
|
+
guard let urlString = urlString else {
|
|
33
|
+
return getServerUrl()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if urlString.isEmpty {
|
|
37
|
+
return getServerUrl()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let validUrlString = (isUrlSanitized(urlString)) ? urlString : "http://\(urlString)"
|
|
41
|
+
|
|
42
|
+
guard let url = URL(string: validUrlString) else {
|
|
43
|
+
return getServerUrl()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return url
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public func encode(_ value: String) -> String {
|
|
50
|
+
return value.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)!
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public func decode(_ value: String) -> String {
|
|
54
|
+
return value.removingPercentEncoding!
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public func setCookie(_ domain: String, _ action: String) {
|
|
58
|
+
let url = getServerUrl(domain)!
|
|
59
|
+
let jar = HTTPCookieStorage.shared
|
|
60
|
+
let field = ["Set-Cookie": action]
|
|
61
|
+
let cookies = HTTPCookie.cookies(withResponseHeaderFields: field, for: url)
|
|
62
|
+
jar.setCookies(cookies, for: url, mainDocumentURL: nil)
|
|
63
|
+
syncCookiesToWebView()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public func setCookie(_ url: URL, _ key: String, _ value: String, _ expires: String?, _ path: String?) {
|
|
67
|
+
let jar = HTTPCookieStorage.shared
|
|
68
|
+
let field = ["Set-Cookie": "\(key)=\(value); expires=\(expires ?? ""); path=\(path ?? "/")"]
|
|
69
|
+
let cookies = HTTPCookie.cookies(withResponseHeaderFields: field, for: url)
|
|
70
|
+
jar.setCookies(cookies, for: url, mainDocumentURL: nil)
|
|
71
|
+
syncCookiesToWebView()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public func getCookiesAsMap(_ url: URL) -> [String: String] {
|
|
75
|
+
syncCookiesToWebView()
|
|
76
|
+
var cookiesMap: [String: String] = [:]
|
|
77
|
+
let jar = HTTPCookieStorage.shared
|
|
78
|
+
if let cookies = jar.cookies(for: url) {
|
|
79
|
+
for cookie in cookies {
|
|
80
|
+
if !cookie.isHTTPOnly {
|
|
81
|
+
cookiesMap[cookie.name] = cookie.value
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return cookiesMap
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public func getCookies() -> String {
|
|
89
|
+
syncCookiesToWebView()
|
|
90
|
+
let jar = HTTPCookieStorage.shared
|
|
91
|
+
guard let url = self.getServerUrl() else { return "" }
|
|
92
|
+
guard let cookies = jar.cookies(for: url) else { return "" }
|
|
93
|
+
let filteredCookies = cookies.filter { !$0.isHTTPOnly }
|
|
94
|
+
return filteredCookies.map({"\($0.name)=\($0.value)"}).joined(separator: "; ")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public func deleteCookie(_ url: URL, _ key: String) {
|
|
98
|
+
let jar = HTTPCookieStorage.shared
|
|
99
|
+
if let cookie = jar.cookies(for: url)?.first(where: { (cookie) -> Bool in
|
|
100
|
+
return cookie.name == key
|
|
101
|
+
}) {
|
|
102
|
+
jar.deleteCookie(cookie)
|
|
103
|
+
DispatchQueue.main.async {
|
|
104
|
+
WKWebsiteDataStore.default().httpCookieStore.delete(cookie)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public func clearCookies(_ url: URL) {
|
|
110
|
+
let jar = HTTPCookieStorage.shared
|
|
111
|
+
jar.cookies(for: url)?.forEach({ (cookie) in
|
|
112
|
+
jar.deleteCookie(cookie)
|
|
113
|
+
DispatchQueue.main.async {
|
|
114
|
+
WKWebsiteDataStore.default().httpCookieStore.delete(cookie)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public func clearAllCookies() {
|
|
120
|
+
let jar = HTTPCookieStorage.shared
|
|
121
|
+
jar.cookies?.forEach({ (cookie) in
|
|
122
|
+
jar.deleteCookie(cookie)
|
|
123
|
+
DispatchQueue.main.async {
|
|
124
|
+
WKWebsiteDataStore.default().httpCookieStore.delete(cookie)
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public func syncCookiesToWebView() {
|
|
130
|
+
if let cookies = HTTPCookieStorage.shared.cookies {
|
|
131
|
+
for cookie in cookies {
|
|
132
|
+
DispatchQueue.main.async {
|
|
133
|
+
WKWebsiteDataStore.default()
|
|
134
|
+
.httpCookieStore
|
|
135
|
+
.setCookie(cookie, completionHandler: nil)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|