@umutcansu/react-native-pinvault 0.0.0-stage → 2.3.2

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/LICENSE +21 -0
  3. package/README.md +386 -2
  4. package/RNPinVault.podspec +43 -0
  5. package/android/build.gradle +68 -0
  6. package/android/consumer-rules.pro +25 -0
  7. package/android/src/main/AndroidManifest.xml +16 -0
  8. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/ConfigParser.kt +300 -0
  9. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/JsEnvironmentGuard.kt +53 -0
  10. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/NativeSecurity.kt +239 -0
  11. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/PinVaultModule.kt +410 -0
  12. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/PinVaultNetworking.kt +296 -0
  13. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/PinVaultPackage.kt +25 -0
  14. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/PinnedFetch.kt +131 -0
  15. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/ResultMapper.kt +188 -0
  16. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/StrictJson.kt +225 -0
  17. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/VaultTokenStore.kt +63 -0
  18. package/ios/Core/ConfigParser.swift +276 -0
  19. package/ios/Core/JSEnvironmentGuard.swift +129 -0
  20. package/ios/Core/NativeSecurity.swift +197 -0
  21. package/ios/Core/PinnedFetch.swift +119 -0
  22. package/ios/Core/ReactNetworking.swift +172 -0
  23. package/ios/Core/ResultMapper.swift +163 -0
  24. package/ios/Core/StrictJSON.swift +214 -0
  25. package/ios/Core/VaultTokenStore.swift +66 -0
  26. package/ios/PinVaultBridge.swift +412 -0
  27. package/ios/PinVaultReactNetworking.swift +47 -0
  28. package/ios/RNPinVault.h +7 -0
  29. package/ios/RNPinVault.mm +293 -0
  30. package/ios/RNPinVaultURLRequestHandler.mm +92 -0
  31. package/lib/module/NativePinVault.js +13 -0
  32. package/lib/module/NativePinVault.js.map +1 -0
  33. package/lib/module/index.js +312 -0
  34. package/lib/module/index.js.map +1 -0
  35. package/lib/module/package.json +1 -0
  36. package/lib/module/types.js +2 -0
  37. package/lib/module/types.js.map +1 -0
  38. package/lib/typescript/package.json +1 -0
  39. package/lib/typescript/src/NativePinVault.d.ts +50 -0
  40. package/lib/typescript/src/NativePinVault.d.ts.map +1 -0
  41. package/lib/typescript/src/index.d.ts +150 -0
  42. package/lib/typescript/src/index.d.ts.map +1 -0
  43. package/lib/typescript/src/types.d.ts +346 -0
  44. package/lib/typescript/src/types.d.ts.map +1 -0
  45. package/package.json +128 -4
  46. package/src/NativePinVault.ts +72 -0
  47. package/src/index.ts +385 -0
  48. package/src/types.ts +351 -0
@@ -0,0 +1,412 @@
1
+ // The Swift side of the TurboModule (RNPinVault.mm forwards every method here).
2
+ // It calls PinVault.shared and nothing else: no pinning, key handling or vault
3
+ // decryption happens in this file. Inputs are parsed by Core/ConfigParser and
4
+ // Core/PinnedFetch, results mapped by Core/ResultMapper.
5
+ import Foundation
6
+ import PinVault
7
+ #if canImport(UIKit)
8
+ import UIKit
9
+ #endif
10
+
11
+ @objc(RNPinVaultBridge)
12
+ public final class PinVaultBridge: NSObject {
13
+ public typealias Resolve = (Any?) -> Void
14
+ public typealias Reject = (String, String, [String: Any]?) -> Void
15
+
16
+ // Process-wide, like the library: a JS reload makes a new module, not a new PinVault.
17
+ private static let tokens = VaultTokenStore()
18
+ private static let lock = NSLock()
19
+ private static var startedInProcess = false
20
+ private static var jsGuard: JSEnvironmentGuard?
21
+ private static weak var current: PinVaultBridge?
22
+ private static let startQueue = StartQueue()
23
+ private static var rnMaxResponseBytes = ReactNetworking.defaultMaxResponseBytes
24
+
25
+ private let mapper = ResultMapper(tokens: PinVaultBridge.tokens)
26
+
27
+ @objc public var emitConnectionEvent: (([String: Any]) -> Void)?
28
+ @objc public var emitGuardRequest: (([String: Any]) -> Void)?
29
+ /// RNPinVault.mm: whether RCTNetworking hands an https request to
30
+ /// RNPinVaultURLRequestHandler (true), to another handler (false), or nil = unknown.
31
+ @objc public var reactNetworkingIsPinned: (() -> NSNumber?)?
32
+
33
+ /// `PinVault.shared.registerBackgroundTask()` for the app's AppDelegate (before launch ends):
34
+ /// the app target does not link the PinVault package itself.
35
+ @objc public static func registerBackgroundTask() -> Bool {
36
+ PinVault.shared.registerBackgroundTask()
37
+ }
38
+
39
+ @objc public override init() {
40
+ super.init()
41
+ Self.lock.lock()
42
+ Self.current = self
43
+ Self.lock.unlock()
44
+ }
45
+
46
+ private static func locked<T>(_ body: () -> T) -> T {
47
+ lock.lock(); defer { lock.unlock() }
48
+ return body()
49
+ }
50
+
51
+ private static var started: Bool {
52
+ lock.lock(); defer { lock.unlock() }
53
+ return startedInProcess
54
+ }
55
+
56
+ /// For the React Native request handler: `start` has run (a failed start
57
+ /// leaves the library's sessions refusing every request).
58
+ static var isStarted: Bool { started }
59
+
60
+ /// `ios.reactNativeMaxResponseBytes` of the last start.
61
+ static var reactNativeMaxResponseBytes: Int64 { locked { rnMaxResponseBytes } }
62
+
63
+ /// An error → (exception name, message) for JS, tokens redacted.
64
+ static let describe: @Sendable (any Error) -> (String, String) = { error in
65
+ let mapped = ResultMapper(tokens: tokens).exception(error) as? [String: Any] ?? [:]
66
+ return ((mapped["name"] as? String) ?? "Error", (mapped["message"] as? String) ?? "")
67
+ }
68
+
69
+ /// Runs a library call with the JS guard's verdicts on `operations` asked
70
+ /// beforehand, so the library's synchronous guard check holds no thread.
71
+ private static func guarded<T>(_ operations: [GuardedOperation], _ body: () async throws -> T) async rethrows -> T {
72
+ guard let g = locked({ jsGuard }) else { return try await body() }
73
+ return try await g.withVerdicts(operations, body)
74
+ }
75
+
76
+ // MARK: helpers
77
+
78
+ private func run(_ resolve: @escaping Resolve, _ reject: @escaping Reject, _ body: @escaping () async throws -> Any?) {
79
+ Task.detached {
80
+ do {
81
+ resolve(try await body() ?? NSNull())
82
+ } catch let e as BridgeInputError {
83
+ reject("E_INVALID_ARGUMENT", Self.tokens.redact(e.message) ?? "", nil)
84
+ } catch {
85
+ reject("E_NATIVE", Self.tokens.redact("\(error)") ?? "", self.exceptionInfo(error))
86
+ }
87
+ }
88
+ }
89
+
90
+ private func exceptionInfo(_ error: any Error) -> [String: Any] {
91
+ let mapped = mapper.exception(error) as? [String: Any] ?? [:]
92
+ return ["exceptionName": mapped["name"] ?? "Error", "exceptionMessage": mapped["message"] ?? NSNull()]
93
+ }
94
+
95
+ private static func checkKey(_ key: String) throws {
96
+ if key.isEmpty || key.count > 128 { throw BridgeInputError("key: must be 1 to 128 characters") }
97
+ }
98
+
99
+ private static func checkToken(_ token: String?) throws {
100
+ if let token, token.count > 4096 { throw BridgeInputError("token: longer than 4096 characters") }
101
+ }
102
+
103
+ // MARK: start / config
104
+
105
+ @objc public func start(_ json: String, resolve: @escaping Resolve, reject: @escaping Reject) {
106
+ Task.detached {
107
+ await Self.startQueue.run {
108
+ let parsed: ParsedConfig
109
+ var newGuard: JSEnvironmentGuard?
110
+ do {
111
+ #if DEBUG
112
+ let release = false
113
+ #else
114
+ let release = true
115
+ #endif
116
+ parsed = try ConfigParser.parse(
117
+ json, tokens: Self.tokens,
118
+ guardFactory: { timeout in
119
+ let g = JSEnvironmentGuard(timeoutMs: timeout) { requestId, operation in
120
+ let bridge = Self.locked { Self.current }
121
+ bridge?.emitGuardRequest?(["requestId": requestId, "operation": operation])
122
+ }
123
+ newGuard = g
124
+ return g
125
+ },
126
+ listener: { event in
127
+ let bridge = Self.locked { Self.current }
128
+ guard let bridge else { return }
129
+ bridge.emitConnectionEvent?(bridge.mapper.event(event))
130
+ },
131
+ native: try NativeSecurity.load(),
132
+ release: release
133
+ )
134
+ } catch let e as BridgeInputError {
135
+ // The guard of the running config stays: a refused config changes nothing.
136
+ reject("E_INVALID_CONFIG", Self.tokens.redact(e.message) ?? "", nil)
137
+ return
138
+ } catch {
139
+ let message = (error as? PinVaultError)?.message ?? "\(error)"
140
+ reject("E_INVALID_CONFIG", Self.tokens.redact(message) ?? "", nil)
141
+ return
142
+ }
143
+ let pinned = self.reactNetworkingIsPinned?()?.boolValue
144
+ if pinned != true {
145
+ let why = !PinVaultReactNetworking.enabled
146
+ ? "Info.plist PinVaultPinReactNativeNetworking is NO"
147
+ : pinned == nil ? "RCTNetworking's handler could not be checked"
148
+ : "RCTNetworking hands https to another RCTURLRequestHandler"
149
+ NSLog("PinVault: React Native's own fetch / XMLHttpRequest / images may NOT be pinned (\(why)); README, \"Networking\"")
150
+ if parsed.requirePinnedReactNativeNetworking {
151
+ reject("E_NETWORKING_NOT_PINNED", "requirePinnedReactNativeNetworking: \(why)", nil)
152
+ return
153
+ }
154
+ }
155
+ Self.locked {
156
+ Self.jsGuard = newGuard
157
+ Self.rnMaxResponseBytes = parsed.reactNativeMaxResponseBytes
158
+ }
159
+ // A second start applies the new config: the library keeps the
160
+ // first one otherwise (the samples restart the same way).
161
+ if Self.started { PinVault.shared.reset() }
162
+ // INIT, and ENROLL for a pending enrollment the start picks up.
163
+ let result = await Self.guarded([.start, .enroll]) { await PinVault.shared.start(config: parsed.config) }
164
+ Self.locked { Self.startedInProcess = true }
165
+ resolve(self.mapper.initResult(result))
166
+ }
167
+ }
168
+ }
169
+
170
+ @objc public func updateNow(resolve: @escaping Resolve, reject: @escaping Reject) {
171
+ run(resolve, reject) { self.mapper.update(await PinVault.shared.updateNow()) }
172
+ }
173
+
174
+ @objc public func currentVersion(resolve: @escaping Resolve, reject: @escaping Reject) {
175
+ run(resolve, reject) { PinVault.shared.currentVersion() }
176
+ }
177
+
178
+ @objc public func hostPinVersions(resolve: @escaping Resolve, reject: @escaping Reject) {
179
+ run(resolve, reject) { PinVault.shared.hostPinVersions() }
180
+ }
181
+
182
+ @objc public func pinsForHost(_ hostname: String, resolve: @escaping Resolve, reject: @escaping Reject) {
183
+ run(resolve, reject) { ["pins": PinVault.shared.pinsForHost(hostname) ?? NSNull()] as [String: Any] }
184
+ }
185
+
186
+ @objc public func signingStatus(_ configApiId: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
187
+ run(resolve, reject) { PinVault.shared.signingStatus(configApiId: configApiId).map(self.mapper.signing) }
188
+ }
189
+
190
+ @objc public func isForceUpdate(resolve: @escaping Resolve, reject: @escaping Reject) {
191
+ run(resolve, reject) { PinVault.shared.isForceUpdate() }
192
+ }
193
+
194
+ @objc public func reset(resolve: @escaping Resolve, reject: @escaping Reject) {
195
+ run(resolve, reject) { PinVault.shared.reset(); return nil }
196
+ }
197
+
198
+ @objc public func schedulePeriodicUpdates(_ intervalHours: NSNumber?, resolve: @escaping Resolve, reject: @escaping Reject) {
199
+ run(resolve, reject) {
200
+ guard Self.started else { return false }
201
+ if let h = intervalHours?.int64Value, h < 1 || h > 24 * 30 {
202
+ throw BridgeInputError("intervalHours: must be between 1 and 720")
203
+ }
204
+ return PinVault.shared.schedulePeriodicUpdates(intervalHours: intervalHours?.int64Value)
205
+ }
206
+ }
207
+
208
+ @objc public func cancelPeriodicUpdates(resolve: @escaping Resolve, reject: @escaping Reject) {
209
+ run(resolve, reject) { PinVault.shared.cancelPeriodicUpdates(); return nil }
210
+ }
211
+
212
+ @objc public func enableDebugLogging(resolve: @escaping Resolve, reject: @escaping Reject) {
213
+ run(resolve, reject) {
214
+ #if DEBUG
215
+ PinVault.enableDebugLogging()
216
+ return true
217
+ #else
218
+ return false
219
+ #endif
220
+ }
221
+ }
222
+
223
+ // MARK: pinned HTTP
224
+
225
+ @objc public func fetch(_ json: String, resolve: @escaping Resolve, reject: @escaping Reject) {
226
+ Task.detached {
227
+ let request: FetchRequest
228
+ do {
229
+ request = try PinnedFetch.parse(json)
230
+ } catch let e as BridgeInputError {
231
+ reject("E_INVALID_ARGUMENT", Self.tokens.redact(e.message) ?? "", nil)
232
+ return
233
+ } catch {
234
+ reject("E_INVALID_ARGUMENT", Self.tokens.redact("\(error)") ?? "", nil)
235
+ return
236
+ }
237
+ guard Self.started else {
238
+ reject("E_NOT_STARTED", "PinVault has not started: call start() first", nil)
239
+ return
240
+ }
241
+ let session = request.settings.map { PinVault.shared.session(settings: $0) } ?? PinVault.shared.session()
242
+ do {
243
+ resolve(try await PinnedFetch.execute(session, request))
244
+ } catch {
245
+ let info = self.exceptionInfo(error)
246
+ let name = info["exceptionName"] as? String ?? "Error"
247
+ let message = info["exceptionMessage"] as? String ?? ""
248
+ reject("E_FETCH", "\(name): \(message)", info)
249
+ }
250
+ }
251
+ }
252
+
253
+ // MARK: enrollment
254
+
255
+ @objc public func deviceId(resolve: @escaping Resolve, reject: @escaping Reject) {
256
+ run(resolve, reject) {
257
+ #if canImport(UIKit)
258
+ return await MainActor.run { UIDevice.current.identifierForVendor?.uuidString.lowercased() }
259
+ #else
260
+ return nil
261
+ #endif
262
+ }
263
+ }
264
+
265
+ @objc public func enrollForResult(_ token: String, label: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
266
+ run(resolve, reject) {
267
+ if token.isEmpty || token.count > 4096 { throw BridgeInputError("token: must be 1 to 4096 characters") }
268
+ return await Self.tokens.withTransientSecret(token) {
269
+ self.mapper.enrollment(await Self.guarded([.enroll]) { await PinVault.shared.enrollForResult(token: token, label: label) })
270
+ }
271
+ }
272
+ }
273
+
274
+ @objc public func autoEnrollForResult(resolve: @escaping Resolve, reject: @escaping Reject) {
275
+ run(resolve, reject) { self.mapper.enrollment(await Self.guarded([.enroll]) { await PinVault.shared.autoEnrollForResult() }) }
276
+ }
277
+
278
+ @objc public func checkPendingEnrollment(resolve: @escaping Resolve, reject: @escaping Reject) {
279
+ run(resolve, reject) { self.mapper.enrollment(await Self.guarded([.enroll]) { await PinVault.shared.checkPendingEnrollment() }) }
280
+ }
281
+
282
+ @objc public func isEnrolled(_ label: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
283
+ run(resolve, reject) { PinVault.shared.isEnrolled(label: label) }
284
+ }
285
+
286
+ @objc public func isEnrollmentPending(_ label: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
287
+ run(resolve, reject) { PinVault.shared.isEnrollmentPending(label: label) }
288
+ }
289
+
290
+ @objc public func enrollmentVerificationCode(_ label: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
291
+ run(resolve, reject) { PinVault.shared.enrollmentVerificationCode(label: label) }
292
+ }
293
+
294
+ @objc public func enrolledClientCN(_ label: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
295
+ run(resolve, reject) { PinVault.shared.enrolledClientCN(label: label) }
296
+ }
297
+
298
+ @objc public func enrolledClientNotAfter(_ label: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
299
+ run(resolve, reject) { PinVault.shared.enrolledClientNotAfter(label: label) }
300
+ }
301
+
302
+ @objc public func unenroll(_ label: String?, wipeVaultFiles: Bool, resolve: @escaping Resolve, reject: @escaping Reject) {
303
+ run(resolve, reject) { PinVault.shared.unenroll(label: label, wipeVaultFiles: wipeVaultFiles); return nil }
304
+ }
305
+
306
+ @objc public func identityKeySecurityLevel(_ label: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
307
+ run(resolve, reject) { PinVault.shared.identityKeySecurityLevel(label: label)?.rawValue }
308
+ }
309
+
310
+ // MARK: vault files
311
+
312
+ @objc public func setVaultToken(_ key: String, token: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
313
+ run(resolve, reject) {
314
+ try Self.checkKey(key)
315
+ try Self.checkToken(token)
316
+ Self.tokens.put(key, token)
317
+ return nil
318
+ }
319
+ }
320
+
321
+ @objc public func clearVaultTokens(resolve: @escaping Resolve, reject: @escaping Reject) {
322
+ run(resolve, reject) { Self.tokens.clear() }
323
+ }
324
+
325
+ @objc public func fetchFile(_ key: String, token: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
326
+ run(resolve, reject) {
327
+ try Self.checkKey(key)
328
+ try Self.checkToken(token)
329
+ if let token { Self.tokens.put(key, token) }
330
+ return self.mapper.vaultFile(await Self.guarded([.fetchFile]) { await PinVault.shared.fetchFile(key) })
331
+ }
332
+ }
333
+
334
+ @objc public func loadFile(_ key: String, encoding: String, resolve: @escaping Resolve, reject: @escaping Reject) {
335
+ run(resolve, reject) {
336
+ try Self.checkKey(key)
337
+ let enc = try ResultMapper.checkEncoding(encoding)
338
+ return PinVault.shared.loadFile(key).map { ResultMapper.encode($0, enc) }
339
+ }
340
+ }
341
+
342
+ @objc public func fileStatus(_ key: String, resolve: @escaping Resolve, reject: @escaping Reject) {
343
+ run(resolve, reject) { try Self.checkKey(key); return PinVault.shared.fileStatus(key).rawValue }
344
+ }
345
+
346
+ @objc public func unlockFile(_ key: String, prompt json: String, resolve: @escaping Resolve, reject: @escaping Reject) {
347
+ run(resolve, reject) {
348
+ try Self.checkKey(key)
349
+ let (prompt, encoding) = try ConfigParser.unlockPrompt(json)
350
+ // LAContext's Face ID / passcode prompt is the library's.
351
+ return self.mapper.unlock(
352
+ await Self.guarded([.unlockFile]) { await PinVault.shared.unlockFile(key: key, prompt: prompt) }, encoding: encoding
353
+ )
354
+ }
355
+ }
356
+
357
+ @objc public func isFileLocked(_ key: String, resolve: @escaping Resolve, reject: @escaping Reject) {
358
+ run(resolve, reject) { try Self.checkKey(key); return PinVault.shared.isFileLocked(key) }
359
+ }
360
+
361
+ @objc public func hasFile(_ key: String, resolve: @escaping Resolve, reject: @escaping Reject) {
362
+ run(resolve, reject) { try Self.checkKey(key); return PinVault.shared.hasFile(key) }
363
+ }
364
+
365
+ @objc public func fileVersion(_ key: String, resolve: @escaping Resolve, reject: @escaping Reject) {
366
+ run(resolve, reject) { try Self.checkKey(key); return PinVault.shared.fileVersion(key) }
367
+ }
368
+
369
+ @objc public func clearFile(_ key: String, resolve: @escaping Resolve, reject: @escaping Reject) {
370
+ run(resolve, reject) { try Self.checkKey(key); PinVault.shared.clearFile(key); return nil }
371
+ }
372
+
373
+ @objc public func syncAllFiles(resolve: @escaping Resolve, reject: @escaping Reject) {
374
+ run(resolve, reject) { (await Self.guarded([.fetchFile]) { await PinVault.shared.syncAllFiles() }).mapValues(self.mapper.vaultFile) }
375
+ }
376
+
377
+ // MARK: attestation
378
+
379
+ @objc public func attestNow(_ configApiId: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
380
+ run(resolve, reject) { self.mapper.attestation(await PinVault.shared.attestNow(configApiId: configApiId)) }
381
+ }
382
+
383
+ @objc public func fetchAttestationToken(_ host: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
384
+ run(resolve, reject) { self.mapper.attestationToken(await PinVault.shared.fetchAttestationToken(host: host)) }
385
+ }
386
+
387
+ @objc public func attestationStatus(_ configApiId: String?, resolve: @escaping Resolve, reject: @escaping Reject) {
388
+ run(resolve, reject) { self.mapper.attestation(PinVault.shared.attestationStatus(configApiId: configApiId)) }
389
+ }
390
+
391
+ // MARK: environmentGuard
392
+
393
+ @objc public func answerGuard(_ requestId: String, allowed: Bool) {
394
+ let g = Self.locked { Self.jsGuard }
395
+ g?.answer(requestId, allowed: allowed)
396
+ }
397
+ }
398
+
399
+ /// Serialises `start` calls (a second start resets and starts again).
400
+ private actor StartQueue {
401
+ private var tail: Task<Void, Never>?
402
+
403
+ func run(_ body: @escaping @Sendable () async -> Void) async {
404
+ let previous = tail
405
+ let task = Task {
406
+ await previous?.value
407
+ await body()
408
+ }
409
+ tail = task
410
+ await task.value
411
+ }
412
+ }
@@ -0,0 +1,47 @@
1
+ // The Swift side of RNPinVaultURLRequestHandler.mm: React Native's https
2
+ // requests (fetch, XMLHttpRequest, <Image>) through PinVault's pinned session.
3
+ // The rules live in Core/ReactNetworking.swift.
4
+ import Foundation
5
+ import PinVault
6
+
7
+ @objc(RNPinVaultReactNetworking)
8
+ public final class PinVaultReactNetworking: NSObject {
9
+
10
+ /// Info.plist `PinVaultPinReactNativeNetworking` = NO hands https back to
11
+ /// React Native's own (unpinned) handler — a native opt-out, which a JS
12
+ /// bundle cannot flip. Default: on.
13
+ @objc public static let enabled: Bool =
14
+ (Bundle.main.object(forInfoDictionaryKey: "PinVaultPinReactNativeNetworking") as? Bool) ?? true
15
+
16
+ @objc public static func canHandle(_ request: URLRequest) -> Bool {
17
+ enabled && ReactNetworking.handles(request)
18
+ }
19
+
20
+ /// The request token for RN; nothing reaches the blocks before `resume()`.
21
+ @objc public static func prepare(
22
+ _ request: URLRequest,
23
+ didSendData: @escaping (Int64) -> Void,
24
+ didReceiveResponse: @escaping (URLResponse) -> Void,
25
+ didReceiveData: @escaping (Data) -> Void,
26
+ didComplete: @escaping (NSError?) -> Void
27
+ ) -> ReactNetworkingTask {
28
+ var send: ReactNetworking.Send?
29
+ if PinVaultBridge.isStarted {
30
+ send = { (request: URLRequest, limit: Int64) async throws -> (Data, URLResponse) in
31
+ try await PinVault.shared.session().data(for: request, maxResponseBytes: limit)
32
+ }
33
+ }
34
+ return ReactNetworking.prepare(
35
+ request,
36
+ send: send,
37
+ maxResponseBytes: PinVaultBridge.reactNativeMaxResponseBytes,
38
+ describe: PinVaultBridge.describe,
39
+ callbacks: ReactNetworking.Callbacks(
40
+ didSendData: didSendData,
41
+ didReceiveResponse: didReceiveResponse,
42
+ didReceiveData: didReceiveData,
43
+ didComplete: { didComplete($0.map { $0 as NSError }) }
44
+ )
45
+ )
46
+ }
47
+ }
@@ -0,0 +1,7 @@
1
+ // RNPinVault — the ObjC++ shell of the TurboModule. It conforms to the
2
+ // codegen protocol and forwards every call to the Swift bridge
3
+ // (PinVaultBridge.swift), which calls PinVault.shared.
4
+ #import <RNPinVaultSpec/RNPinVaultSpec.h>
5
+
6
+ @interface RNPinVault : NativePinVaultSpecBase <NativePinVaultSpec>
7
+ @end