@capacitor/ios 6.0.0-beta.2 → 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.
@@ -41,13 +41,11 @@ public class AppUUID {
41
41
  }
42
42
 
43
43
  private static func readUUID() -> String {
44
- let defaults = UserDefaults.standard
45
- return defaults.string(forKey: key) ?? ""
44
+ KeyValueStore.standard[key] ?? ""
46
45
  }
47
46
 
48
47
  private static func writeUUID(_ uuid: String) {
49
- let defaults = UserDefaults.standard
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 = UserDefaults.standard.string(forKey: "lastBinaryVersionCode"),
22
- let lastVersionName = UserDefaults.standard.string(forKey: "lastBinaryVersionName") {
23
- return (curVersionCode.isEqual(lastVersionCode) == false || curVersionName.isEqual(lastVersionName) == false)
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 = UserDefaults.standard.string(forKey: "serverBasePath"), !persistedPath.isEmpty {
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 prefs = UserDefaults.standard
323
- prefs.set(versionCode, forKey: "lastBinaryVersionCode")
324
- prefs.set(versionName, forKey: "lastBinaryVersionName")
325
- prefs.set("", forKey: "serverBasePath")
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
- /// - data: The value to encode.
86
- /// - encoder: The encoder to use. Defaults to `JSValueEncoder()`.
87
- /// - Throws: If the value cannot be encoded.
88
- ///
89
- /// The data paramater _must_ be encodable as a
90
- /// ``JSObject``, otherwise this method will throw.
91
- func resolve<T: Encodable>(with data: T, encoder: JSValueEncoder = JSValueEncoder()) throws {
92
- let encoded = try encoder.encodeJSObject(data)
93
- resolve(encoded)
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
- if autoRegisterPlugins {
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
- let releasingClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(classes)
285
- let numClasses: Int32 = objc_getClassList(releasingClasses, classCount)
286
-
287
- for classIndex in 0..<Int(numClasses) {
288
- if let aClass: AnyClass = classes[classIndex] {
289
- if class_getSuperclass(aClass) == CDVPlugin.self {
290
- injectCordovaFiles = true
291
- }
292
- if class_conformsToProtocol(aClass, CAPBridgedPlugin.self),
293
- let pluginType = aClass as? CapacitorPlugin.Type {
294
- if aClass is CAPInstancePlugin.Type { continue }
295
- registerPlugin(pluginType)
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
 
@@ -31,8 +31,8 @@ public final class JSValueDecoder: TopLevelDecoder {
31
31
  typealias CodingUserInfo = [CodingUserInfoKey: Any]
32
32
 
33
33
  private final class _JSValueDecoder {
34
- internal var codingPath: [CodingKey] = []
35
- internal var userInfo: CodingUserInfo = [:]
34
+ var codingPath: [CodingKey] = []
35
+ var userInfo: CodingUserInfo = [:]
36
36
  fileprivate var data: JSValue
37
37
 
38
38
  init(data: JSValue) {
@@ -123,7 +123,7 @@ extension Array: JSValueEncodingContainer where Element == EncodingContainer {
123
123
  var data: JSValue? {
124
124
  guard count != 0 else { return nil }
125
125
  guard count != 1 else { return self[0].data }
126
- var data: (any JSValue)? = nil
126
+ var data: (any JSValue)?
127
127
 
128
128
  for container in self {
129
129
  if data == nil {
@@ -189,7 +189,7 @@ extension _JSValueEncoder: Encoder {
189
189
  }
190
190
  }
191
191
 
192
- fileprivate final class KeyedContainer<Key> where Key: CodingKey {
192
+ private final class KeyedContainer<Key> where Key: CodingKey {
193
193
  var object: JSObject? {
194
194
  encodedKeyedValue?.reduce(into: [:]) { obj, next in
195
195
  let (key, value) = next
@@ -239,7 +239,7 @@ extension KeyedContainer: KeyedEncodingContainerProtocol {
239
239
 
240
240
  // This is a perectly valid name for this method. The underscore is to avoid a conflict with the
241
241
  // protocol requirement.
242
- //swiftlint:disable:next identifier_name
242
+ // swiftlint:disable:next identifier_name
243
243
  func _encodeIfPresent<T>(_ value: T?, forKey key: Key) throws where T: Encodable {
244
244
  switch optionalEncodingStrategy {
245
245
  case .explicitNulls:
@@ -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
- let boundary = UUID().uuidString
66
- let contentType = "multipart/form-data; boundary=\(boundary)"
67
- request.setValue(contentType, forHTTPHeaderField: "Content-Type")
68
- headers["Content-Type"] = contentType
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
- // Update the contentType with the new boundary
105
- let boundary = UUID().uuidString
106
- let contentType = "multipart/form-data; boundary=\(boundary)"
107
- request.setValue(contentType, forHTTPHeaderField: "Content-Type")
108
- headers["Content-Type"] = contentType
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("\r\n--\(boundary)\r\n".data(using: .utf8)!)
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("\r\n--\(boundary)\r\n".data(using: .utf8)!)
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
  }
@@ -24,6 +24,7 @@ CAP_PLUGIN(CAPConsolePlugin, "Console",
24
24
  )
25
25
 
26
26
  CAP_PLUGIN(CAPWebViewPlugin, "WebView",
27
+ CAP_PLUGIN_METHOD(setServerAssetPath, CAPPluginReturnPromise);
27
28
  CAP_PLUGIN_METHOD(setServerBasePath, CAPPluginReturnPromise);
28
29
  CAP_PLUGIN_METHOD(getServerBasePath, CAPPluginReturnPromise);
29
30
  CAP_PLUGIN_METHOD(persistServerBasePath, CAPPluginReturnPromise);
@@ -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
- output["headers"] = response.allHeaderFields
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 {
@@ -3,6 +3,13 @@ import Foundation
3
3
  @objc(CAPWebViewPlugin)
4
4
  public class CAPWebViewPlugin: CAPPlugin {
5
5
 
6
+ @objc func setServerAssetPath(_ call: CAPPluginCall) {
7
+ if let path = call.getString("path"), let viewController = bridge?.viewController as? CAPBridgeViewController {
8
+ viewController.setServerBasePath(path: Bundle.main.url(forResource: path, withExtension: nil)?.path ?? path)
9
+ call.resolve()
10
+ }
11
+ }
12
+
6
13
  @objc func setServerBasePath(_ call: CAPPluginCall) {
7
14
  if let path = call.getString("path"), let viewController = bridge?.viewController as? CAPBridgeViewController {
8
15
  viewController.setServerBasePath(path: path)
@@ -22,8 +29,7 @@ public class CAPWebViewPlugin: CAPPlugin {
22
29
  @objc func persistServerBasePath(_ call: CAPPluginCall) {
23
30
  if let viewController = bridge?.viewController as? CAPBridgeViewController {
24
31
  let path = viewController.getServerBasePath()
25
- let defaults = UserDefaults.standard
26
- defaults.set(path, forKey: "serverBasePath")
32
+ KeyValueStore.standard["serverBasePath"] = path
27
33
  call.resolve()
28
34
  }
29
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 self.serverUrl != nil && self.serverUrl?.scheme != localUrl.scheme {
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;
@@ -279,6 +295,10 @@ var nativeBridge = (function (exports) {
279
295
  callback(result.path);
280
296
  });
281
297
  };
298
+ IonicWebView.setServerAssetPath = (path) => {
299
+ var _a;
300
+ (_a = Plugins === null || Plugins === void 0 ? void 0 : Plugins.WebView) === null || _a === void 0 ? void 0 : _a.setServerAssetPath({ path });
301
+ };
282
302
  IonicWebView.setServerBasePath = (path) => {
283
303
  var _a;
284
304
  (_a = Plugins === null || Plugins === void 0 ? void 0 : Plugins.WebView) === null || _a === void 0 ? void 0 : _a.setServerBasePath({ path });
@@ -355,15 +375,15 @@ var nativeBridge = (function (exports) {
355
375
  typeof c.dir === 'function');
356
376
  };
357
377
  const serializeConsoleMessage = (msg) => {
358
- if (typeof msg === 'object') {
359
- try {
378
+ try {
379
+ if (typeof msg === 'object') {
360
380
  msg = JSON.stringify(msg);
361
381
  }
362
- catch (e) {
363
- // ignore
364
- }
382
+ return String(msg);
383
+ }
384
+ catch (e) {
385
+ return '';
365
386
  }
366
- return String(msg);
367
387
  };
368
388
  const platform = getPlatformId(win);
369
389
  if (platform == 'android' || platform == 'ios') {
@@ -472,6 +492,19 @@ var nativeBridge = (function (exports) {
472
492
  if (request.url.startsWith(`${cap.getServerUrl()}/`)) {
473
493
  return win.CapacitorWebFetch(resource, options);
474
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
+ }
475
508
  const tag = `CapacitorHttp fetch ${Date.now()} ${resource}`;
476
509
  console.time(tag);
477
510
  try {
@@ -541,12 +574,11 @@ var nativeBridge = (function (exports) {
541
574
  });
542
575
  xhr.readyState = 0;
543
576
  const prototype = win.CapacitorWebXMLHttpRequest.prototype;
544
- const isRelativeURL = (url) => !url || !(url.startsWith('http:') || url.startsWith('https:'));
545
577
  const isProgressEventAvailable = () => typeof ProgressEvent !== 'undefined' &&
546
578
  ProgressEvent.prototype instanceof Event;
547
579
  // XHR patch abort
548
580
  prototype.abort = function () {
549
- if (isRelativeURL(this._url)) {
581
+ if (isRelativeOrProxyUrl(this._url)) {
550
582
  return win.CapacitorWebXMLHttpRequest.abort.call(this);
551
583
  }
552
584
  this.readyState = 0;
@@ -557,10 +589,18 @@ var nativeBridge = (function (exports) {
557
589
  };
558
590
  // XHR patch open
559
591
  prototype.open = function (method, url) {
592
+ this._method = method.toLocaleUpperCase();
560
593
  this._url = url;
561
- this._method = method;
562
- if (isRelativeURL(url)) {
563
- return win.CapacitorWebXMLHttpRequest.open.call(this, method, url);
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);
564
604
  }
565
605
  setTimeout(() => {
566
606
  this.dispatchEvent(new Event('loadstart'));
@@ -569,14 +609,14 @@ var nativeBridge = (function (exports) {
569
609
  };
570
610
  // XHR patch set request header
571
611
  prototype.setRequestHeader = function (header, value) {
572
- if (isRelativeURL(this._url)) {
612
+ if (isRelativeOrProxyUrl(this._url)) {
573
613
  return win.CapacitorWebXMLHttpRequest.setRequestHeader.call(this, header, value);
574
614
  }
575
615
  this._headers[header] = value;
576
616
  };
577
617
  // XHR patch send
578
618
  prototype.send = function (body) {
579
- if (isRelativeURL(this._url)) {
619
+ if (isRelativeOrProxyUrl(this._url)) {
580
620
  return win.CapacitorWebXMLHttpRequest.send.call(this, body);
581
621
  }
582
622
  const tag = `CapacitorHttp XMLHttpRequest ${Date.now()} ${this._url}`;
@@ -638,7 +678,8 @@ var nativeBridge = (function (exports) {
638
678
  else {
639
679
  this.response = nativeResponse.data;
640
680
  }
641
- this.responseText = ((_a = nativeResponse.headers['Content-Type']) === null || _a === void 0 ? void 0 : _a.startsWith('application/json'))
681
+ this.responseText = ((_a = (nativeResponse.headers['Content-Type'] ||
682
+ nativeResponse.headers['content-type'])) === null || _a === void 0 ? void 0 : _a.startsWith('application/json'))
642
683
  ? JSON.stringify(nativeResponse.data)
643
684
  : nativeResponse.data;
644
685
  this.responseURL = nativeResponse.url;
@@ -695,7 +736,7 @@ var nativeBridge = (function (exports) {
695
736
  };
696
737
  // XHR patch getAllResponseHeaders
697
738
  prototype.getAllResponseHeaders = function () {
698
- if (isRelativeURL(this._url)) {
739
+ if (isRelativeOrProxyUrl(this._url)) {
699
740
  return win.CapacitorWebXMLHttpRequest.getAllResponseHeaders.call(this);
700
741
  }
701
742
  let returnString = '';
@@ -708,7 +749,7 @@ var nativeBridge = (function (exports) {
708
749
  };
709
750
  // XHR patch getResponseHeader
710
751
  prototype.getResponseHeader = function (name) {
711
- if (isRelativeURL(this._url)) {
752
+ if (isRelativeOrProxyUrl(this._url)) {
712
753
  return win.CapacitorWebXMLHttpRequest.getResponseHeader.call(this, name);
713
754
  }
714
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/*.{swift,h,m}", "#{prefix}Capacitor/Capacitor/Codable/*.{swift,h,m}",
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>
@@ -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-beta.2",
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)",
@@ -26,9 +26,10 @@
26
26
  "xc:build:xcframework": "scripts/build.sh xcframework"
27
27
  },
28
28
  "peerDependencies": {
29
- "@capacitor/core": "^6.0.0-beta.2"
29
+ "@capacitor/core": "^6.0.0-rc.2"
30
30
  },
31
31
  "publishConfig": {
32
32
  "access": "public"
33
- }
33
+ },
34
+ "gitHead": "8046d907d2d2c0779316adbcabda426acee2dfc3"
34
35
  }