@capacitor/ios 6.0.0-dev-20231222T034502.0 → 6.0.0-dev-8046d907.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 +2 -4
- package/Capacitor/Capacitor/CAPBridgeViewController.swift +12 -13
- package/Capacitor/Capacitor/CAPPluginCall.swift +17 -10
- package/Capacitor/Capacitor/CapacitorBridge.swift +30 -20
- package/Capacitor/Capacitor/KeyValueStore.swift +296 -0
- package/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift +25 -22
- package/Capacitor/Capacitor/Plugins/HttpRequestHandler.swift +20 -1
- package/Capacitor/Capacitor/Plugins/WebView.swift +1 -2
- package/Capacitor/Capacitor/PrivacyInfo.xcprivacy +14 -0
- package/Capacitor/Capacitor/WebViewAssetHandler.swift +79 -3
- package/Capacitor/Capacitor/assets/native-bridge.js +57 -20
- package/Capacitor.podspec +2 -3
- package/CapacitorCordova/CapacitorCordova/PrivacyInfo.xcprivacy +14 -0
- package/CapacitorCordova.podspec +1 -0
- package/package.json +5 -4
|
@@ -41,13 +41,11 @@ public class AppUUID {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
private static func readUUID() -> String {
|
|
44
|
-
|
|
45
|
-
return defaults.string(forKey: key) ?? ""
|
|
44
|
+
KeyValueStore.standard[key] ?? ""
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
private static func writeUUID(_ uuid: String) {
|
|
49
|
-
|
|
50
|
-
defaults.set(uuid, forKey: key)
|
|
48
|
+
KeyValueStore.standard[key] = uuid
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
}
|
|
@@ -18,9 +18,9 @@ import Cordova
|
|
|
18
18
|
public lazy final var isNewBinary: Bool = {
|
|
19
19
|
if let curVersionCode = Bundle.main.infoDictionary?["CFBundleVersion"] as? String,
|
|
20
20
|
let curVersionName = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
|
|
21
|
-
if let lastVersionCode =
|
|
22
|
-
let lastVersionName =
|
|
23
|
-
return
|
|
21
|
+
if let lastVersionCode = KeyValueStore.standard["lastBinaryVersionCode", as: String.self],
|
|
22
|
+
let lastVersionName = KeyValueStore.standard["lastBinaryVersionName", as: String.self] {
|
|
23
|
+
return curVersionCode != lastVersionCode || curVersionName != lastVersionName
|
|
24
24
|
}
|
|
25
25
|
return true
|
|
26
26
|
}
|
|
@@ -34,10 +34,6 @@ import Cordova
|
|
|
34
34
|
CAPLog.enableLogging = configuration.loggingEnabled
|
|
35
35
|
logWarnings(for: configDescriptor)
|
|
36
36
|
|
|
37
|
-
if configDescriptor.instanceType == .fixed {
|
|
38
|
-
updateBinaryVersion()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
37
|
setStatusBarDefaults()
|
|
42
38
|
setScreenOrientationDefaults()
|
|
43
39
|
|
|
@@ -55,6 +51,10 @@ import Cordova
|
|
|
55
51
|
assetHandler: assetHandler,
|
|
56
52
|
delegationHandler: delegationHandler)
|
|
57
53
|
capacitorDidLoad()
|
|
54
|
+
|
|
55
|
+
if configDescriptor.instanceType == .fixed {
|
|
56
|
+
updateBinaryVersion()
|
|
57
|
+
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
override open func viewDidLoad() {
|
|
@@ -79,7 +79,7 @@ import Cordova
|
|
|
79
79
|
open func instanceDescriptor() -> InstanceDescriptor {
|
|
80
80
|
let descriptor = InstanceDescriptor.init()
|
|
81
81
|
if !isNewBinary && !descriptor.cordovaDeployDisabled {
|
|
82
|
-
if let persistedPath =
|
|
82
|
+
if let persistedPath = KeyValueStore.standard["serverBasePath", as: String.self], !persistedPath.isEmpty {
|
|
83
83
|
if let libPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first {
|
|
84
84
|
descriptor.appLocation = URL(fileURLWithPath: libPath, isDirectory: true)
|
|
85
85
|
.appendingPathComponent("NoCloud")
|
|
@@ -319,11 +319,10 @@ extension CAPBridgeViewController {
|
|
|
319
319
|
let versionName = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
|
|
320
320
|
return
|
|
321
321
|
}
|
|
322
|
-
let
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
prefs.synchronize()
|
|
322
|
+
let store = KeyValueStore.standard
|
|
323
|
+
store["lastBinaryVersionCode"] = versionCode
|
|
324
|
+
store["lastBinaryVersionName"] = versionName
|
|
325
|
+
store["serverBasePath"] = nil as String?
|
|
327
326
|
}
|
|
328
327
|
|
|
329
328
|
private func logWarnings(for descriptor: InstanceDescriptor) {
|
|
@@ -80,17 +80,24 @@ extension CAPPluginCall: JSValueContainer {
|
|
|
80
80
|
|
|
81
81
|
// MARK: Codable Support
|
|
82
82
|
public extension CAPPluginCall {
|
|
83
|
-
/// Encodes the given value to a ``JSObject`` and resolves the call.
|
|
83
|
+
/// Encodes the given value to a ``JSObject`` and resolves the call. If an error is thrown during encoding, ``reject(_:_:_:_:)`` is called.
|
|
84
84
|
/// - Parameters:
|
|
85
|
-
///
|
|
86
|
-
///
|
|
87
|
-
///
|
|
88
|
-
///
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
/// - data: The value to encode
|
|
86
|
+
/// - encoder: The encoder to use. Defaults to `JSValueEncoder()`
|
|
87
|
+
/// - messageForRejectionFromError: A closure that takes the error thrown from ``JSValueEncoder/encodeJSObject(_:)``
|
|
88
|
+
/// and returns a string to be provided to ``reject(_:_:_:_:)``. Defaults to a function that returns "Failed encoding response".
|
|
89
|
+
func resolve<T: Encodable>(
|
|
90
|
+
with data: T,
|
|
91
|
+
encoder: JSValueEncoder = JSValueEncoder(),
|
|
92
|
+
messageForRejectionFromError: (Error) -> String = { _ in "Failed encoding response" }
|
|
93
|
+
) {
|
|
94
|
+
do {
|
|
95
|
+
let encoded = try encoder.encodeJSObject(data)
|
|
96
|
+
resolve(encoded)
|
|
97
|
+
} catch {
|
|
98
|
+
let message = messageForRejectionFromError(error)
|
|
99
|
+
reject(message, nil, error)
|
|
100
|
+
}
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
/// Decodes the options to the given type.
|
|
@@ -5,6 +5,10 @@ import Cordova
|
|
|
5
5
|
|
|
6
6
|
internal typealias CapacitorPlugin = CAPPlugin & CAPBridgedPlugin
|
|
7
7
|
|
|
8
|
+
struct RegistrationList: Codable {
|
|
9
|
+
let packageClassList: Set<String>
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
/**
|
|
9
13
|
An internal class adopting a public protocol means that we have a lot of `public` methods
|
|
10
14
|
but that is by design not a mistake. And since the bridge is the center of the whole project
|
|
@@ -90,6 +94,8 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
|
|
|
90
94
|
static let tmpVCAppeared = Notification(name: Notification.Name(rawValue: "tmpViewControllerAppeared"))
|
|
91
95
|
public static let capacitorSite = "https://capacitorjs.com/"
|
|
92
96
|
public static let fileStartIdentifier = "/_capacitor_file_"
|
|
97
|
+
public static let httpInterceptorStartIdentifier = "/_capacitor_http_interceptor_"
|
|
98
|
+
public static let httpsInterceptorStartIdentifier = "/_capacitor_https_interceptor_"
|
|
93
99
|
public static let defaultScheme = "capacitor"
|
|
94
100
|
|
|
95
101
|
public private(set) var webViewAssetHandler: WebViewAssetHandler
|
|
@@ -277,30 +283,34 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
|
|
|
277
283
|
Register all plugins that have been declared
|
|
278
284
|
*/
|
|
279
285
|
func registerPlugins() {
|
|
280
|
-
|
|
281
|
-
let classCount = objc_getClassList(nil, 0)
|
|
282
|
-
let classes = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(classCount))
|
|
286
|
+
var pluginList: [AnyClass] = [CAPHttpPlugin.self, CAPConsolePlugin.self, CAPWebViewPlugin.self, CAPCookiesPlugin.self]
|
|
283
287
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
288
|
+
if autoRegisterPlugins {
|
|
289
|
+
do {
|
|
290
|
+
if let pluginJSON = Bundle.main.url(forResource: "capacitor.config", withExtension: "json") {
|
|
291
|
+
let pluginData = try Data(contentsOf: pluginJSON)
|
|
292
|
+
let registrationList = try JSONDecoder().decode(RegistrationList.self, from: pluginData)
|
|
293
|
+
|
|
294
|
+
for plugin in registrationList.packageClassList {
|
|
295
|
+
if let pluginClass = NSClassFromString(plugin) {
|
|
296
|
+
if pluginClass == CDVPlugin.self {
|
|
297
|
+
injectCordovaFiles = true
|
|
298
|
+
} else {
|
|
299
|
+
pluginList.append(pluginClass)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
296
302
|
}
|
|
297
303
|
}
|
|
304
|
+
} catch {
|
|
305
|
+
CAPLog.print("Error registering plugins: \(error)")
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
for plugin in pluginList {
|
|
310
|
+
if plugin is CAPInstancePlugin.Type { continue }
|
|
311
|
+
if let capPlugin = plugin as? CapacitorPlugin.Type {
|
|
312
|
+
registerPlugin(capPlugin)
|
|
298
313
|
}
|
|
299
|
-
classes.deallocate()
|
|
300
|
-
} else {
|
|
301
|
-
// register core plugins only
|
|
302
|
-
[CAPHttpPlugin.self, CAPConsolePlugin.self, CAPWebViewPlugin.self, CAPCookiesPlugin.self]
|
|
303
|
-
.forEach { registerPluginType($0) }
|
|
304
314
|
}
|
|
305
315
|
}
|
|
306
316
|
|
|
@@ -0,0 +1,296 @@
|
|
|
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
|
+
private class ConcurrentDictionary<Value> {
|
|
276
|
+
private var storage: [String: Value]
|
|
277
|
+
private let lock = NSLock()
|
|
278
|
+
|
|
279
|
+
init(_ initial: [String: Value] = [:]) {
|
|
280
|
+
storage = initial
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
subscript (_ key: String) -> Value? {
|
|
284
|
+
get {
|
|
285
|
+
lock.withLock {
|
|
286
|
+
storage[key]
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
set {
|
|
291
|
+
lock.withLock {
|
|
292
|
+
storage[key] = newValue
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -51,7 +51,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
|
|
|
51
51
|
return nil
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
public func getRequestDataAsMultipartFormData(_ data: JSValue) throws -> Data {
|
|
54
|
+
public func getRequestDataAsMultipartFormData(_ data: JSValue, _ contentType: String) throws -> Data {
|
|
55
55
|
guard let obj = data as? JSObject else {
|
|
56
56
|
// Throw, other data types explicitly not supported.
|
|
57
57
|
throw CapacitorUrlRequestError.serializationError("[ data ] argument for request with content-type [ application/x-www-form-urlencoded ] may only be a plain javascript object")
|
|
@@ -62,11 +62,12 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
var data = Data()
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
var boundary = UUID().uuidString
|
|
66
|
+
if contentType.contains("="), let contentBoundary = contentType.components(separatedBy: "=").last {
|
|
67
|
+
boundary = contentBoundary
|
|
68
|
+
} else {
|
|
69
|
+
overrideContentType(boundary)
|
|
70
|
+
}
|
|
70
71
|
strings.forEach { key, value in
|
|
71
72
|
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
|
|
72
73
|
data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
|
|
@@ -77,6 +78,12 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
|
|
|
77
78
|
return data
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
private func overrideContentType(_ boundary: String) {
|
|
82
|
+
let contentType = "multipart/form-data; boundary=\(boundary)"
|
|
83
|
+
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
|
84
|
+
headers["Content-Type"] = contentType
|
|
85
|
+
}
|
|
86
|
+
|
|
80
87
|
public func getRequestDataAsString(_ data: JSValue) throws -> Data {
|
|
81
88
|
guard let stringData = data as? String else {
|
|
82
89
|
throw CapacitorUrlRequestError.serializationError("[ data ] argument could not be parsed as string")
|
|
@@ -93,20 +100,18 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
|
|
|
93
100
|
return normalized[index.lowercased()]
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
func getRequestDataFromFormData(_ data: JSValue) throws -> Data? {
|
|
103
|
+
public func getRequestDataFromFormData(_ data: JSValue, _ contentType: String) throws -> Data? {
|
|
97
104
|
guard let list = data as? JSArray else {
|
|
98
105
|
// Throw, other data types explicitly not supported.
|
|
99
106
|
throw CapacitorUrlRequestError.serializationError("Data must be an array for FormData")
|
|
100
107
|
}
|
|
101
|
-
|
|
102
108
|
var data = Data()
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
var boundary = UUID().uuidString
|
|
110
|
+
if contentType.contains("="), let contentBoundary = contentType.components(separatedBy: "=").last {
|
|
111
|
+
boundary = contentBoundary
|
|
112
|
+
} else {
|
|
113
|
+
overrideContentType(boundary)
|
|
114
|
+
}
|
|
110
115
|
for entry in list {
|
|
111
116
|
guard let item = entry as? [String: String] else {
|
|
112
117
|
throw CapacitorUrlRequestError.serializationError("Data must be an array for FormData")
|
|
@@ -120,7 +125,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
|
|
|
120
125
|
let fileName = item["fileName"]
|
|
121
126
|
let fileContentType = item["contentType"]
|
|
122
127
|
|
|
123
|
-
data.append("
|
|
128
|
+
data.append("--\(boundary)\r\n".data(using: .utf8)!)
|
|
124
129
|
data.append("Content-Disposition: form-data; name=\"\(key!)\"; filename=\"\(fileName!)\"\r\n".data(using: .utf8)!)
|
|
125
130
|
data.append("Content-Type: \(fileContentType!)\r\n".data(using: .utf8)!)
|
|
126
131
|
data.append("Content-Transfer-Encoding: binary\r\n".data(using: .utf8)!)
|
|
@@ -130,16 +135,14 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
|
|
|
130
135
|
|
|
131
136
|
data.append("\r\n".data(using: .utf8)!)
|
|
132
137
|
} else if type == "string" {
|
|
133
|
-
data.append("
|
|
138
|
+
data.append("--\(boundary)\r\n".data(using: .utf8)!)
|
|
134
139
|
data.append("Content-Disposition: form-data; name=\"\(key!)\"\r\n".data(using: .utf8)!)
|
|
135
140
|
data.append("\r\n".data(using: .utf8)!)
|
|
136
141
|
data.append(value.data(using: .utf8)!)
|
|
137
142
|
data.append("\r\n".data(using: .utf8)!)
|
|
138
143
|
}
|
|
139
|
-
|
|
140
144
|
}
|
|
141
|
-
|
|
142
|
-
data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
|
|
145
|
+
data.append("--\(boundary)--\r\n".data(using: .utf8)!)
|
|
143
146
|
|
|
144
147
|
return data
|
|
145
148
|
}
|
|
@@ -151,7 +154,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
|
|
|
151
154
|
}
|
|
152
155
|
return Data(base64Encoded: stringData)
|
|
153
156
|
} else if dataType == "formData" {
|
|
154
|
-
return try getRequestDataFromFormData(body)
|
|
157
|
+
return try getRequestDataFromFormData(body, contentType)
|
|
155
158
|
}
|
|
156
159
|
|
|
157
160
|
// If data can be parsed directly as a string, return that without processing.
|
|
@@ -162,7 +165,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate {
|
|
|
162
165
|
} else if contentType.contains("application/x-www-form-urlencoded") {
|
|
163
166
|
return try getRequestDataAsFormUrlEncoded(body)
|
|
164
167
|
} else if contentType.contains("multipart/form-data") {
|
|
165
|
-
return try getRequestDataAsMultipartFormData(body)
|
|
168
|
+
return try getRequestDataAsMultipartFormData(body, contentType)
|
|
166
169
|
} else {
|
|
167
170
|
throw CapacitorUrlRequestError.serializationError("[ data ] argument could not be parsed for content type [ \(contentType) ]")
|
|
168
171
|
}
|
|
@@ -37,6 +37,18 @@ func tryParseJson(_ data: Data) -> Any {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/// Helper to convert the headers dictionary to lower case keys. This allows case-insensitive querying in the bridge javascript.
|
|
41
|
+
/// - Parameters:
|
|
42
|
+
/// - headers: The headers as dictionary. The type is unspecific because the incoming headers are coming from the
|
|
43
|
+
/// allHeaderFields property of the HttpResponse.
|
|
44
|
+
/// - Returns: The modified headers dictionary with lowercase keys
|
|
45
|
+
private func lowerCaseHeaderDictionary(_ headers: [AnyHashable: Any]) -> [String: Any] {
|
|
46
|
+
// Lowercases the key of the headers dictionary.
|
|
47
|
+
return Dictionary(uniqueKeysWithValues: headers.map({ (key: AnyHashable, value: Any) in
|
|
48
|
+
return (String(describing: key).lowercased(), value)
|
|
49
|
+
}))
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
open class HttpRequestHandler {
|
|
41
53
|
open class CapacitorHttpRequestBuilder {
|
|
42
54
|
public var url: URL?
|
|
@@ -121,7 +133,14 @@ open class HttpRequestHandler {
|
|
|
121
133
|
var output = [:] as [String: Any]
|
|
122
134
|
|
|
123
135
|
output["status"] = response.statusCode
|
|
124
|
-
|
|
136
|
+
|
|
137
|
+
// HTTP Headers are case insensitive. The allHeaderFields dictionary returned by Apple Foundation Code has its keys capitalized.
|
|
138
|
+
// According to the documentation at https://developer.apple.com/documentation/foundation/httpurlresponse/1417930-allheaderfields
|
|
139
|
+
// "HTTP headers are case insensitive. To simplify your code, URL Loading System canonicalizes certain header field names into
|
|
140
|
+
// their standard form. For example, if the server sends a content-length header, it’s automatically adjusted to be Content-Length."
|
|
141
|
+
// To handle the case insevitivy, we are converting the header keys to lower case here. When querying for headers in the native bridge,
|
|
142
|
+
// we are lowercasing the key as well.
|
|
143
|
+
output["headers"] = lowerCaseHeaderDictionary(response.allHeaderFields)
|
|
125
144
|
output["url"] = response.url?.absoluteString
|
|
126
145
|
|
|
127
146
|
guard let data = data else {
|
|
@@ -29,8 +29,7 @@ public class CAPWebViewPlugin: CAPPlugin {
|
|
|
29
29
|
@objc func persistServerBasePath(_ call: CAPPluginCall) {
|
|
30
30
|
if let viewController = bridge?.viewController as? CAPBridgeViewController {
|
|
31
31
|
let path = viewController.getServerBasePath()
|
|
32
|
-
|
|
33
|
-
defaults.set(path, forKey: "serverBasePath")
|
|
32
|
+
KeyValueStore.standard["serverBasePath"] = path
|
|
34
33
|
call.resolve()
|
|
35
34
|
}
|
|
36
35
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
6
|
+
<array/>
|
|
7
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
8
|
+
<array/>
|
|
9
|
+
<key>NSPrivacyTrackingDomains</key>
|
|
10
|
+
<array/>
|
|
11
|
+
<key>NSPrivacyTracking</key>
|
|
12
|
+
<false/>
|
|
13
|
+
</dict>
|
|
14
|
+
</plist>
|
|
@@ -20,10 +20,25 @@ open class WebViewAssetHandler: NSObject, WKURLSchemeHandler {
|
|
|
20
20
|
self.serverUrl = serverUrl
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
private func isUsingLiveReload(_ localUrl: URL) -> Bool {
|
|
24
|
+
return self.serverUrl != nil && self.serverUrl?.scheme != localUrl.scheme
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
open func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
|
|
24
28
|
let startPath: String
|
|
25
29
|
let url = urlSchemeTask.request.url!
|
|
26
30
|
let stringToLoad = url.path
|
|
31
|
+
let localUrl = URL.init(string: url.absoluteString)!
|
|
32
|
+
|
|
33
|
+
if url.path.starts(with: CapacitorBridge.httpInterceptorStartIdentifier) {
|
|
34
|
+
handleCapacitorHttpRequest(urlSchemeTask, localUrl, false)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if url.path.starts(with: CapacitorBridge.httpsInterceptorStartIdentifier) {
|
|
39
|
+
handleCapacitorHttpRequest(urlSchemeTask, localUrl, true)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
27
42
|
|
|
28
43
|
if stringToLoad.starts(with: CapacitorBridge.fileStartIdentifier) {
|
|
29
44
|
startPath = stringToLoad.replacingOccurrences(of: CapacitorBridge.fileStartIdentifier, with: "")
|
|
@@ -31,7 +46,6 @@ open class WebViewAssetHandler: NSObject, WKURLSchemeHandler {
|
|
|
31
46
|
startPath = router.route(for: stringToLoad)
|
|
32
47
|
}
|
|
33
48
|
|
|
34
|
-
let localUrl = URL.init(string: url.absoluteString)!
|
|
35
49
|
let fileUrl = URL.init(fileURLWithPath: startPath)
|
|
36
50
|
|
|
37
51
|
do {
|
|
@@ -43,9 +57,9 @@ open class WebViewAssetHandler: NSObject, WKURLSchemeHandler {
|
|
|
43
57
|
]
|
|
44
58
|
|
|
45
59
|
// if using live reload, then set CORS headers
|
|
46
|
-
if
|
|
60
|
+
if isUsingLiveReload(localUrl) {
|
|
47
61
|
headers["Access-Control-Allow-Origin"] = self.serverUrl?.absoluteString
|
|
48
|
-
headers["Access-Control-Allow-Methods"] = "GET, OPTIONS"
|
|
62
|
+
headers["Access-Control-Allow-Methods"] = "GET, HEAD, OPTIONS, TRACE"
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
if let rangeString = urlSchemeTask.request.value(forHTTPHeaderField: "Range"),
|
|
@@ -121,6 +135,68 @@ open class WebViewAssetHandler: NSObject, WKURLSchemeHandler {
|
|
|
121
135
|
return false
|
|
122
136
|
}
|
|
123
137
|
|
|
138
|
+
func handleCapacitorHttpRequest(_ urlSchemeTask: WKURLSchemeTask, _ localUrl: URL, _ isHttpsRequest: Bool) {
|
|
139
|
+
var urlRequest = urlSchemeTask.request
|
|
140
|
+
guard let url = urlRequest.url else { return }
|
|
141
|
+
var targetUrl = url.absoluteString
|
|
142
|
+
.replacingOccurrences(of: CapacitorBridge.httpInterceptorStartIdentifier, with: "")
|
|
143
|
+
.replacingOccurrences(of: CapacitorBridge.httpsInterceptorStartIdentifier, with: "")
|
|
144
|
+
// Only replace first occurrence of the scheme
|
|
145
|
+
if let range = targetUrl.range(of: localUrl.scheme ?? InstanceDescriptorDefaults.scheme) {
|
|
146
|
+
targetUrl = targetUrl.replacingCharacters(in: range, with: isHttpsRequest ? "https" : "http")
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Only replace first occurrence of the hostname
|
|
150
|
+
if let range = targetUrl.range(of: (localUrl.host ?? InstanceDescriptorDefaults.hostname) + "/") {
|
|
151
|
+
targetUrl = targetUrl.replacingCharacters(in: range, with: "")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
urlRequest.url = URL(string: targetUrl.removingPercentEncoding ?? targetUrl)
|
|
155
|
+
|
|
156
|
+
let urlSession = URLSession.shared
|
|
157
|
+
let task = urlSession.dataTask(with: urlRequest) { (data, response, error) in
|
|
158
|
+
if let error = error {
|
|
159
|
+
urlSchemeTask.didFailWithError(error)
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if let response = response as? HTTPURLResponse {
|
|
164
|
+
let existingHeaders = response.allHeaderFields
|
|
165
|
+
var newHeaders: [AnyHashable: Any] = [:]
|
|
166
|
+
|
|
167
|
+
// if using live reload, then set CORS headers
|
|
168
|
+
if self.isUsingLiveReload(url) {
|
|
169
|
+
newHeaders = [
|
|
170
|
+
"Access-Control-Allow-Origin": self.serverUrl?.absoluteString ?? "",
|
|
171
|
+
"Access-Control-Allow-Methods": "GET, HEAD, OPTIONS, TRACE"
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if let mergedHeaders = existingHeaders.merging(newHeaders, uniquingKeysWith: { (_, newHeaders) in newHeaders }) as? [String: String] {
|
|
176
|
+
|
|
177
|
+
if let responseUrl = response.url {
|
|
178
|
+
if let modifiedResponse = HTTPURLResponse(
|
|
179
|
+
url: responseUrl,
|
|
180
|
+
statusCode: response.statusCode,
|
|
181
|
+
httpVersion: nil,
|
|
182
|
+
headerFields: mergedHeaders
|
|
183
|
+
) {
|
|
184
|
+
urlSchemeTask.didReceive(modifiedResponse)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if let data = data {
|
|
189
|
+
urlSchemeTask.didReceive(data)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
urlSchemeTask.didFinish()
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
task.resume()
|
|
198
|
+
}
|
|
199
|
+
|
|
124
200
|
public let mimeTypes = [
|
|
125
201
|
"aaf": "application/octet-stream",
|
|
126
202
|
"aca": "application/octet-stream",
|
|
@@ -111,13 +111,9 @@ var nativeBridge = (function (exports) {
|
|
|
111
111
|
}
|
|
112
112
|
else if (body instanceof FormData) {
|
|
113
113
|
const formData = await convertFormData(body);
|
|
114
|
-
const boundary = `${Date.now()}`;
|
|
115
114
|
return {
|
|
116
115
|
data: formData,
|
|
117
116
|
type: 'formData',
|
|
118
|
-
headers: {
|
|
119
|
-
'Content-Type': `multipart/form-data; boundary=--${boundary}`,
|
|
120
|
-
},
|
|
121
117
|
};
|
|
122
118
|
}
|
|
123
119
|
else if (body instanceof File) {
|
|
@@ -130,6 +126,26 @@ var nativeBridge = (function (exports) {
|
|
|
130
126
|
}
|
|
131
127
|
return { data: body, type: 'json' };
|
|
132
128
|
};
|
|
129
|
+
const CAPACITOR_HTTP_INTERCEPTOR = '/_capacitor_http_interceptor_';
|
|
130
|
+
const CAPACITOR_HTTPS_INTERCEPTOR = '/_capacitor_https_interceptor_';
|
|
131
|
+
// TODO: export as Cap function
|
|
132
|
+
const isRelativeOrProxyUrl = (url) => !url ||
|
|
133
|
+
!(url.startsWith('http:') || url.startsWith('https:')) ||
|
|
134
|
+
url.indexOf(CAPACITOR_HTTP_INTERCEPTOR) > -1 ||
|
|
135
|
+
url.indexOf(CAPACITOR_HTTPS_INTERCEPTOR) > -1;
|
|
136
|
+
// TODO: export as Cap function
|
|
137
|
+
const createProxyUrl = (url, win) => {
|
|
138
|
+
var _a, _b;
|
|
139
|
+
if (isRelativeOrProxyUrl(url))
|
|
140
|
+
return url;
|
|
141
|
+
const proxyUrl = new URL(url);
|
|
142
|
+
const bridgeUrl = new URL((_b = (_a = win.Capacitor) === null || _a === void 0 ? void 0 : _a.getServerUrl()) !== null && _b !== void 0 ? _b : '');
|
|
143
|
+
const isHttps = proxyUrl.protocol === 'https:';
|
|
144
|
+
bridgeUrl.search = proxyUrl.search;
|
|
145
|
+
bridgeUrl.hash = proxyUrl.hash;
|
|
146
|
+
bridgeUrl.pathname = `${isHttps ? CAPACITOR_HTTPS_INTERCEPTOR : CAPACITOR_HTTP_INTERCEPTOR}/${encodeURIComponent(proxyUrl.host)}${proxyUrl.pathname}`;
|
|
147
|
+
return bridgeUrl.toString();
|
|
148
|
+
};
|
|
133
149
|
const initBridge = (w) => {
|
|
134
150
|
const getPlatformId = (win) => {
|
|
135
151
|
var _a, _b;
|
|
@@ -359,15 +375,15 @@ var nativeBridge = (function (exports) {
|
|
|
359
375
|
typeof c.dir === 'function');
|
|
360
376
|
};
|
|
361
377
|
const serializeConsoleMessage = (msg) => {
|
|
362
|
-
|
|
363
|
-
|
|
378
|
+
try {
|
|
379
|
+
if (typeof msg === 'object') {
|
|
364
380
|
msg = JSON.stringify(msg);
|
|
365
381
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
382
|
+
return String(msg);
|
|
383
|
+
}
|
|
384
|
+
catch (e) {
|
|
385
|
+
return '';
|
|
369
386
|
}
|
|
370
|
-
return String(msg);
|
|
371
387
|
};
|
|
372
388
|
const platform = getPlatformId(win);
|
|
373
389
|
if (platform == 'android' || platform == 'ios') {
|
|
@@ -476,6 +492,19 @@ var nativeBridge = (function (exports) {
|
|
|
476
492
|
if (request.url.startsWith(`${cap.getServerUrl()}/`)) {
|
|
477
493
|
return win.CapacitorWebFetch(resource, options);
|
|
478
494
|
}
|
|
495
|
+
if (!(options === null || options === void 0 ? void 0 : options.method) ||
|
|
496
|
+
options.method.toLocaleUpperCase() === 'GET' ||
|
|
497
|
+
options.method.toLocaleUpperCase() === 'HEAD' ||
|
|
498
|
+
options.method.toLocaleUpperCase() === 'OPTIONS' ||
|
|
499
|
+
options.method.toLocaleUpperCase() === 'TRACE') {
|
|
500
|
+
if (typeof resource === 'string') {
|
|
501
|
+
return await win.CapacitorWebFetch(createProxyUrl(resource, win), options);
|
|
502
|
+
}
|
|
503
|
+
else if (resource instanceof Request) {
|
|
504
|
+
const modifiedRequest = new Request(createProxyUrl(resource.url, win), resource);
|
|
505
|
+
return await win.CapacitorWebFetch(modifiedRequest, options);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
479
508
|
const tag = `CapacitorHttp fetch ${Date.now()} ${resource}`;
|
|
480
509
|
console.time(tag);
|
|
481
510
|
try {
|
|
@@ -545,12 +574,11 @@ var nativeBridge = (function (exports) {
|
|
|
545
574
|
});
|
|
546
575
|
xhr.readyState = 0;
|
|
547
576
|
const prototype = win.CapacitorWebXMLHttpRequest.prototype;
|
|
548
|
-
const isRelativeURL = (url) => !url || !(url.startsWith('http:') || url.startsWith('https:'));
|
|
549
577
|
const isProgressEventAvailable = () => typeof ProgressEvent !== 'undefined' &&
|
|
550
578
|
ProgressEvent.prototype instanceof Event;
|
|
551
579
|
// XHR patch abort
|
|
552
580
|
prototype.abort = function () {
|
|
553
|
-
if (
|
|
581
|
+
if (isRelativeOrProxyUrl(this._url)) {
|
|
554
582
|
return win.CapacitorWebXMLHttpRequest.abort.call(this);
|
|
555
583
|
}
|
|
556
584
|
this.readyState = 0;
|
|
@@ -561,10 +589,18 @@ var nativeBridge = (function (exports) {
|
|
|
561
589
|
};
|
|
562
590
|
// XHR patch open
|
|
563
591
|
prototype.open = function (method, url) {
|
|
592
|
+
this._method = method.toLocaleUpperCase();
|
|
564
593
|
this._url = url;
|
|
565
|
-
this._method
|
|
566
|
-
|
|
567
|
-
|
|
594
|
+
if (!this._method ||
|
|
595
|
+
this._method === 'GET' ||
|
|
596
|
+
this._method === 'HEAD' ||
|
|
597
|
+
this._method === 'OPTIONS' ||
|
|
598
|
+
this._method === 'TRACE') {
|
|
599
|
+
if (isRelativeOrProxyUrl(url)) {
|
|
600
|
+
return win.CapacitorWebXMLHttpRequest.open.call(this, method, url);
|
|
601
|
+
}
|
|
602
|
+
this._url = createProxyUrl(this._url, win);
|
|
603
|
+
return win.CapacitorWebXMLHttpRequest.open.call(this, method, this._url);
|
|
568
604
|
}
|
|
569
605
|
setTimeout(() => {
|
|
570
606
|
this.dispatchEvent(new Event('loadstart'));
|
|
@@ -573,14 +609,14 @@ var nativeBridge = (function (exports) {
|
|
|
573
609
|
};
|
|
574
610
|
// XHR patch set request header
|
|
575
611
|
prototype.setRequestHeader = function (header, value) {
|
|
576
|
-
if (
|
|
612
|
+
if (isRelativeOrProxyUrl(this._url)) {
|
|
577
613
|
return win.CapacitorWebXMLHttpRequest.setRequestHeader.call(this, header, value);
|
|
578
614
|
}
|
|
579
615
|
this._headers[header] = value;
|
|
580
616
|
};
|
|
581
617
|
// XHR patch send
|
|
582
618
|
prototype.send = function (body) {
|
|
583
|
-
if (
|
|
619
|
+
if (isRelativeOrProxyUrl(this._url)) {
|
|
584
620
|
return win.CapacitorWebXMLHttpRequest.send.call(this, body);
|
|
585
621
|
}
|
|
586
622
|
const tag = `CapacitorHttp XMLHttpRequest ${Date.now()} ${this._url}`;
|
|
@@ -642,7 +678,8 @@ var nativeBridge = (function (exports) {
|
|
|
642
678
|
else {
|
|
643
679
|
this.response = nativeResponse.data;
|
|
644
680
|
}
|
|
645
|
-
this.responseText = ((_a = nativeResponse.headers['Content-Type']
|
|
681
|
+
this.responseText = ((_a = (nativeResponse.headers['Content-Type'] ||
|
|
682
|
+
nativeResponse.headers['content-type'])) === null || _a === void 0 ? void 0 : _a.startsWith('application/json'))
|
|
646
683
|
? JSON.stringify(nativeResponse.data)
|
|
647
684
|
: nativeResponse.data;
|
|
648
685
|
this.responseURL = nativeResponse.url;
|
|
@@ -699,7 +736,7 @@ var nativeBridge = (function (exports) {
|
|
|
699
736
|
};
|
|
700
737
|
// XHR patch getAllResponseHeaders
|
|
701
738
|
prototype.getAllResponseHeaders = function () {
|
|
702
|
-
if (
|
|
739
|
+
if (isRelativeOrProxyUrl(this._url)) {
|
|
703
740
|
return win.CapacitorWebXMLHttpRequest.getAllResponseHeaders.call(this);
|
|
704
741
|
}
|
|
705
742
|
let returnString = '';
|
|
@@ -712,7 +749,7 @@ var nativeBridge = (function (exports) {
|
|
|
712
749
|
};
|
|
713
750
|
// XHR patch getResponseHeader
|
|
714
751
|
prototype.getResponseHeader = function (name) {
|
|
715
|
-
if (
|
|
752
|
+
if (isRelativeOrProxyUrl(this._url)) {
|
|
716
753
|
return win.CapacitorWebXMLHttpRequest.getResponseHeader.call(this, name);
|
|
717
754
|
}
|
|
718
755
|
return this._headers[name];
|
package/Capacitor.podspec
CHANGED
|
@@ -16,10 +16,9 @@ Pod::Spec.new do |s|
|
|
|
16
16
|
s.ios.deployment_target = '13.0'
|
|
17
17
|
s.authors = { 'Ionic Team' => 'hi@ionicframework.com' }
|
|
18
18
|
s.source = { git: 'https://github.com/ionic-team/capacitor.git', tag: package['version'] }
|
|
19
|
-
s.source_files = "#{prefix}Capacitor/Capacitor
|
|
20
|
-
"#{prefix}Capacitor/Capacitor/Plugins/*.{swift,h,m}", "#{prefix}Capacitor/Capacitor/Plugins/**/*.{swift,h,m}"
|
|
19
|
+
s.source_files = "#{prefix}Capacitor/Capacitor/**/*.{swift,h,m}"
|
|
21
20
|
s.module_map = "#{prefix}Capacitor/Capacitor/Capacitor.modulemap"
|
|
22
|
-
s.resources = ["#{prefix}Capacitor/Capacitor/assets/native-bridge.js"]
|
|
21
|
+
s.resources = ["#{prefix}Capacitor/Capacitor/assets/native-bridge.js", "#{prefix}Capacitor/Capacitor/PrivacyInfo.xcprivacy"]
|
|
23
22
|
s.dependency 'CapacitorCordova'
|
|
24
23
|
s.swift_version = '5.1'
|
|
25
24
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
6
|
+
<array/>
|
|
7
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
8
|
+
<array/>
|
|
9
|
+
<key>NSPrivacyTrackingDomains</key>
|
|
10
|
+
<array/>
|
|
11
|
+
<key>NSPrivacyTracking</key>
|
|
12
|
+
<false/>
|
|
13
|
+
</dict>
|
|
14
|
+
</plist>
|
package/CapacitorCordova.podspec
CHANGED
|
@@ -20,6 +20,7 @@ Pod::Spec.new do |s|
|
|
|
20
20
|
s.public_header_files = "#{prefix}CapacitorCordova/CapacitorCordova/Classes/Public/*.h",
|
|
21
21
|
"#{prefix}CapacitorCordova/CapacitorCordova/CapacitorCordova.h"
|
|
22
22
|
s.module_map = "#{prefix}CapacitorCordova/CapacitorCordova/CapacitorCordova.modulemap"
|
|
23
|
+
s.resources = ["#{prefix}CapacitorCordova/CapacitorCordova/PrivacyInfo.xcprivacy"]
|
|
23
24
|
s.requires_arc = true
|
|
24
25
|
s.framework = 'WebKit'
|
|
25
26
|
end
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capacitor/ios",
|
|
3
|
-
"version": "6.0.0-dev-
|
|
3
|
+
"version": "6.0.0-dev-8046d907.0",
|
|
4
4
|
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
|
|
5
5
|
"homepage": "https://capacitorjs.com",
|
|
6
6
|
"author": "Ionic Team <hi@ionic.io> (https://ionic.io)",
|
|
@@ -20,15 +20,16 @@
|
|
|
20
20
|
"scripts/pods_helpers.rb"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"verify": "npm run xc:build:Capacitor && npm run xc:build:CapacitorCordova
|
|
23
|
+
"verify": "npm run xc:build:Capacitor && npm run xc:build:CapacitorCordova",
|
|
24
24
|
"xc:build:Capacitor": "cd Capacitor && xcodebuild -workspace Capacitor.xcworkspace -scheme Capacitor && cd ..",
|
|
25
25
|
"xc:build:CapacitorCordova": "cd CapacitorCordova && xcodebuild && cd ..",
|
|
26
26
|
"xc:build:xcframework": "scripts/build.sh xcframework"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@capacitor/core": "^6.0.0-
|
|
29
|
+
"@capacitor/core": "^6.0.0-rc.2"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public"
|
|
33
|
-
}
|
|
33
|
+
},
|
|
34
|
+
"gitHead": "8046d907d2d2c0779316adbcabda426acee2dfc3"
|
|
34
35
|
}
|