@onramper/onramper-react-native 1.0.1
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/CHANGELOG.md +21 -0
- package/LICENSE +202 -0
- package/NOTICE +5 -0
- package/OnramperReactNative.podspec +36 -0
- package/README.md +114 -0
- package/app.plugin.js +51 -0
- package/ios/Frameworks/LICENSE +202 -0
- package/ios/Frameworks/NOTICE +5 -0
- package/ios/Frameworks/OnramperSDK.xcframework/Info.plist +48 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/OnramperSDK.framework/Info.plist +0 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/arm64-apple-ios.swiftinterface +548 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/OnramperSDK.framework/OnramperSDK +0 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/OnramperSDK.framework/OnramperSDK_OnramperSDK.bundle/Info.plist +0 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/OnramperSDK.framework/OnramperSDK_OnramperSDK.bundle/PrivacyInfo.xcprivacy +27 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/OnramperSDK.framework/PrivacyInfo.xcprivacy +27 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/dSYMs/OnramperSDK.framework.dSYM/Contents/Info.plist +20 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/dSYMs/OnramperSDK.framework.dSYM/Contents/Resources/DWARF/OnramperSDK +0 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64/dSYMs/OnramperSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/OnramperSDK.yml +2518 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/Info.plist +0 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +548 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/Modules/OnramperSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +548 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/OnramperSDK +0 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/OnramperSDK_OnramperSDK.bundle/Info.plist +0 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/OnramperSDK_OnramperSDK.bundle/PrivacyInfo.xcprivacy +27 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/OnramperSDK.framework/PrivacyInfo.xcprivacy +27 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/OnramperSDK.framework.dSYM/Contents/Info.plist +20 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/OnramperSDK.framework.dSYM/Contents/Resources/DWARF/OnramperSDK +0 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/OnramperSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/OnramperSDK.yml +2518 -0
- package/ios/Frameworks/OnramperSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/OnramperSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/OnramperSDK.yml +2487 -0
- package/ios/HybridOnramperCheckoutButton.swift +112 -0
- package/ios/HybridOnramperNitro.swift +194 -0
- package/ios/OnramperCheckoutBridge.swift +88 -0
- package/ios/OnramperSDKBridge.swift +185 -0
- package/ios/PreparedIntentRegistry.swift +78 -0
- package/lib/commonjs/OnramperCheckoutButtonView.js +16 -0
- package/lib/commonjs/OnramperCheckoutButtonView.js.map +1 -0
- package/lib/commonjs/OnramperClient.js +161 -0
- package/lib/commonjs/OnramperClient.js.map +1 -0
- package/lib/commonjs/OnramperNative.js +22 -0
- package/lib/commonjs/OnramperNative.js.map +1 -0
- package/lib/commonjs/errors.js +35 -0
- package/lib/commonjs/errors.js.map +1 -0
- package/lib/commonjs/events.js +6 -0
- package/lib/commonjs/events.js.map +1 -0
- package/lib/commonjs/generated/OnramperCheckoutButtonConfig.json +10 -0
- package/lib/commonjs/index.js +27 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/OnramperCheckoutButton.nitro.js +6 -0
- package/lib/commonjs/specs/OnramperCheckoutButton.nitro.js.map +1 -0
- package/lib/commonjs/specs/OnramperNitro.nitro.js +6 -0
- package/lib/commonjs/specs/OnramperNitro.nitro.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/OnramperCheckoutButtonView.js +11 -0
- package/lib/module/OnramperCheckoutButtonView.js.map +1 -0
- package/lib/module/OnramperClient.js +156 -0
- package/lib/module/OnramperClient.js.map +1 -0
- package/lib/module/OnramperNative.js +18 -0
- package/lib/module/OnramperNative.js.map +1 -0
- package/lib/module/errors.js +30 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/events.js +4 -0
- package/lib/module/events.js.map +1 -0
- package/lib/module/generated/OnramperCheckoutButtonConfig.json +10 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/specs/OnramperCheckoutButton.nitro.js +4 -0
- package/lib/module/specs/OnramperCheckoutButton.nitro.js.map +1 -0
- package/lib/module/specs/OnramperNitro.nitro.js +4 -0
- package/lib/module/specs/OnramperNitro.nitro.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/src/OnramperCheckoutButtonView.d.ts +9 -0
- package/lib/typescript/commonjs/src/OnramperCheckoutButtonView.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/OnramperClient.d.ts +49 -0
- package/lib/typescript/commonjs/src/OnramperClient.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/OnramperNative.d.ts +9 -0
- package/lib/typescript/commonjs/src/OnramperNative.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/errors.d.ts +13 -0
- package/lib/typescript/commonjs/src/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/events.d.ts +48 -0
- package/lib/typescript/commonjs/src/events.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +9 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/specs/OnramperCheckoutButton.nitro.d.ts +10 -0
- package/lib/typescript/commonjs/src/specs/OnramperCheckoutButton.nitro.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/specs/OnramperNitro.nitro.d.ts +39 -0
- package/lib/typescript/commonjs/src/specs/OnramperNitro.nitro.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/types.d.ts +127 -0
- package/lib/typescript/commonjs/src/types.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/src/OnramperCheckoutButtonView.d.ts +9 -0
- package/lib/typescript/module/src/OnramperCheckoutButtonView.d.ts.map +1 -0
- package/lib/typescript/module/src/OnramperClient.d.ts +49 -0
- package/lib/typescript/module/src/OnramperClient.d.ts.map +1 -0
- package/lib/typescript/module/src/OnramperNative.d.ts +9 -0
- package/lib/typescript/module/src/OnramperNative.d.ts.map +1 -0
- package/lib/typescript/module/src/errors.d.ts +13 -0
- package/lib/typescript/module/src/errors.d.ts.map +1 -0
- package/lib/typescript/module/src/events.d.ts +48 -0
- package/lib/typescript/module/src/events.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +9 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -0
- package/lib/typescript/module/src/specs/OnramperCheckoutButton.nitro.d.ts +10 -0
- package/lib/typescript/module/src/specs/OnramperCheckoutButton.nitro.d.ts.map +1 -0
- package/lib/typescript/module/src/specs/OnramperNitro.nitro.d.ts +39 -0
- package/lib/typescript/module/src/specs/OnramperNitro.nitro.d.ts.map +1 -0
- package/lib/typescript/module/src/types.d.ts +127 -0
- package/lib/typescript/module/src/types.d.ts.map +1 -0
- package/nitro.json +19 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/ios/OnramperReactNative+autolinking.rb +62 -0
- package/nitrogen/generated/ios/OnramperReactNative-Swift-Cxx-Bridge.cpp +107 -0
- package/nitrogen/generated/ios/OnramperReactNative-Swift-Cxx-Bridge.hpp +299 -0
- package/nitrogen/generated/ios/OnramperReactNative-Swift-Cxx-Umbrella.hpp +60 -0
- package/nitrogen/generated/ios/OnramperReactNativeAutolinking.mm +41 -0
- package/nitrogen/generated/ios/OnramperReactNativeAutolinking.swift +38 -0
- package/nitrogen/generated/ios/c++/HybridOnramperCheckoutButtonSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridOnramperCheckoutButtonSpecSwift.hpp +81 -0
- package/nitrogen/generated/ios/c++/HybridOnramperNitroSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridOnramperNitroSpecSwift.hpp +150 -0
- package/nitrogen/generated/ios/c++/views/HybridOnramperCheckoutButtonComponent.mm +122 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_NitroSessionCredentials____.swift +61 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_NitroSessionCredentials.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_PreparedIntentResult.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_NitroSessionCredentials__.swift +66 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridOnramperCheckoutButtonSpec.swift +55 -0
- package/nitrogen/generated/ios/swift/HybridOnramperCheckoutButtonSpec_cxx.swift +156 -0
- package/nitrogen/generated/ios/swift/HybridOnramperNitroSpec.swift +63 -0
- package/nitrogen/generated/ios/swift/HybridOnramperNitroSpec_cxx.swift +309 -0
- package/nitrogen/generated/ios/swift/NitroSessionCredentials.swift +34 -0
- package/nitrogen/generated/ios/swift/OnramperNitroConfig.swift +49 -0
- package/nitrogen/generated/ios/swift/PreparedIntentResult.swift +34 -0
- package/nitrogen/generated/shared/c++/HybridOnramperCheckoutButtonSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridOnramperCheckoutButtonSpec.hpp +63 -0
- package/nitrogen/generated/shared/c++/HybridOnramperNitroSpec.cpp +29 -0
- package/nitrogen/generated/shared/c++/HybridOnramperNitroSpec.hpp +80 -0
- package/nitrogen/generated/shared/c++/NitroSessionCredentials.hpp +87 -0
- package/nitrogen/generated/shared/c++/OnramperNitroConfig.hpp +99 -0
- package/nitrogen/generated/shared/c++/PreparedIntentResult.hpp +87 -0
- package/nitrogen/generated/shared/c++/views/HybridOnramperCheckoutButtonComponent.cpp +83 -0
- package/nitrogen/generated/shared/c++/views/HybridOnramperCheckoutButtonComponent.hpp +111 -0
- package/nitrogen/generated/shared/json/OnramperCheckoutButtonConfig.json +10 -0
- package/package.json +107 -0
- package/src/OnramperCheckoutButtonView.tsx +15 -0
- package/src/OnramperClient.ts +170 -0
- package/src/OnramperNative.ts +19 -0
- package/src/errors.ts +69 -0
- package/src/events.ts +23 -0
- package/src/generated/OnramperCheckoutButtonConfig.json +10 -0
- package/src/index.ts +30 -0
- package/src/specs/OnramperCheckoutButton.nitro.ts +16 -0
- package/src/specs/OnramperNitro.nitro.ts +60 -0
- package/src/types.ts +133 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import NitroModules
|
|
3
|
+
import SwiftUI
|
|
4
|
+
import UIKit
|
|
5
|
+
|
|
6
|
+
/// Nitro view that renders the SwiftUI `OnramperCheckoutButton` produced by
|
|
7
|
+
/// `getCheckoutRequirements` and stashed in `PreparedIntentRegistry.shared`
|
|
8
|
+
/// under `intentHandle`. The button is hosted in a `UIHostingController` whose
|
|
9
|
+
/// view is added to our container; the controller is attached as a child of the
|
|
10
|
+
/// nearest React view controller so the SDK button can present its login /
|
|
11
|
+
/// webview sheets.
|
|
12
|
+
final class HybridOnramperCheckoutButton: HybridOnramperCheckoutButtonSpec {
|
|
13
|
+
private let container = CheckoutContainerView()
|
|
14
|
+
private var mountedHandle: String?
|
|
15
|
+
|
|
16
|
+
var intentHandle: String = "" {
|
|
17
|
+
didSet {
|
|
18
|
+
guard !intentHandle.isEmpty, intentHandle != mountedHandle else { return }
|
|
19
|
+
mount(handle: intentHandle)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var view: UIView { container }
|
|
24
|
+
|
|
25
|
+
private func mount(handle: String) {
|
|
26
|
+
mountedHandle = handle
|
|
27
|
+
Task { @MainActor [weak self] in
|
|
28
|
+
guard let self else { return }
|
|
29
|
+
guard let entry = await PreparedIntentRegistry.shared.consume(handle) else {
|
|
30
|
+
// Handle was invalidated or already consumed — leave the view empty.
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
// Pin the SwiftUI content to the container's width so long ToS text wraps
|
|
34
|
+
// instead of laying out at its intrinsic (single-line) width and spilling
|
|
35
|
+
// past the right edge. `.fixedSize(vertical:)` lets it grow downward.
|
|
36
|
+
self.container.host(AnyView(
|
|
37
|
+
entry.button
|
|
38
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
39
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
40
|
+
))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Container that hosts the SwiftUI button and (re)attaches the hosting
|
|
46
|
+
/// controller to the React view-controller hierarchy when it enters a window.
|
|
47
|
+
final class CheckoutContainerView: UIView {
|
|
48
|
+
private var hostingController: UIHostingController<AnyView>?
|
|
49
|
+
|
|
50
|
+
@MainActor
|
|
51
|
+
func host(_ rootView: AnyView) {
|
|
52
|
+
// Re-hosting (e.g. when `intentHandle` changes) must tear the previous
|
|
53
|
+
// controller down first, otherwise its view/constraints accumulate and the
|
|
54
|
+
// old controller stays childed to the parent VC.
|
|
55
|
+
detachHosted()
|
|
56
|
+
let controller = UIHostingController(rootView: rootView)
|
|
57
|
+
controller.view.backgroundColor = .clear
|
|
58
|
+
controller.view.translatesAutoresizingMaskIntoConstraints = false
|
|
59
|
+
hostingController = controller
|
|
60
|
+
addSubview(controller.view)
|
|
61
|
+
NSLayoutConstraint.activate([
|
|
62
|
+
controller.view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
63
|
+
controller.view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
64
|
+
controller.view.topAnchor.constraint(equalTo: topAnchor),
|
|
65
|
+
controller.view.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
66
|
+
])
|
|
67
|
+
attachChildIfPossible()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override func didMoveToWindow() {
|
|
71
|
+
super.didMoveToWindow()
|
|
72
|
+
attachChildIfPossible()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private func attachChildIfPossible() {
|
|
76
|
+
guard let controller = hostingController, controller.parent == nil,
|
|
77
|
+
let parent = nearestViewController() else { return }
|
|
78
|
+
parent.addChild(controller)
|
|
79
|
+
controller.didMove(toParent: parent)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private func nearestViewController() -> UIViewController? {
|
|
83
|
+
var responder: UIResponder? = self
|
|
84
|
+
while let current = responder {
|
|
85
|
+
if let vc = current as? UIViewController { return vc }
|
|
86
|
+
responder = current.next
|
|
87
|
+
}
|
|
88
|
+
return nil
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Detaches the currently hosted controller from the VC hierarchy and removes
|
|
92
|
+
/// its view/constraints. Safe to call when nothing is hosted.
|
|
93
|
+
@MainActor
|
|
94
|
+
private func detachHosted() {
|
|
95
|
+
guard let controller = hostingController else { return }
|
|
96
|
+
controller.willMove(toParent: nil)
|
|
97
|
+
controller.view.removeFromSuperview()
|
|
98
|
+
controller.removeFromParent()
|
|
99
|
+
hostingController = nil
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
deinit {
|
|
103
|
+
// deinit can't hop to @MainActor; the container is torn down on the main
|
|
104
|
+
// thread by UIKit, so touching the controller here is safe.
|
|
105
|
+
if let controller = hostingController {
|
|
106
|
+
controller.willMove(toParent: nil)
|
|
107
|
+
controller.view.removeFromSuperview()
|
|
108
|
+
controller.removeFromParent()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
#endif
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import Combine
|
|
2
|
+
import Foundation
|
|
3
|
+
import NitroModules
|
|
4
|
+
import OnramperSDK
|
|
5
|
+
import SwiftUI
|
|
6
|
+
|
|
7
|
+
/// Nitro bridge to OnramperSDK's `OnramperClient`. Wraps the @MainActor SDK
|
|
8
|
+
/// client, mirrors its `@Published state` and `events` stream to stored JS
|
|
9
|
+
/// callbacks, and feeds the SDK's `sessionExpirationHandler` from a stored
|
|
10
|
+
/// async JS callback. The public typed JS API lives in `OnramperClient.ts`.
|
|
11
|
+
final class HybridOnramperNitro: HybridOnramperNitroSpec {
|
|
12
|
+
private var client: OnramperClient?
|
|
13
|
+
private var stateObservation: AnyCancellable?
|
|
14
|
+
private var eventTask: Task<Void, Never>?
|
|
15
|
+
|
|
16
|
+
private var onState: ((String) -> Void)?
|
|
17
|
+
private var onEvent: ((String) -> Void)?
|
|
18
|
+
// Nitro wraps a callback's return value in a Promise; our callback already
|
|
19
|
+
// returns Promise<NitroSessionCredentials>, hence the doubly-nested Promise.
|
|
20
|
+
private var sessionHandler: (() -> Promise<Promise<NitroSessionCredentials>>)?
|
|
21
|
+
|
|
22
|
+
// Handle of the most recent prepared intent owned by *this* client, used to
|
|
23
|
+
// enforce single-flight per client without touching other clients' intents.
|
|
24
|
+
private var lastPreparedHandle: String?
|
|
25
|
+
|
|
26
|
+
// MARK: - Listener registration (synchronous)
|
|
27
|
+
|
|
28
|
+
func setStateListener(onState: @escaping (String) -> Void) {
|
|
29
|
+
self.onState = onState
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func setEventListener(onEvent: @escaping (String) -> Void) {
|
|
33
|
+
self.onEvent = onEvent
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func setSessionExpirationHandler(handler: @escaping () -> Promise<Promise<NitroSessionCredentials>>) {
|
|
37
|
+
self.sessionHandler = handler
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// MARK: - Lifecycle
|
|
41
|
+
|
|
42
|
+
func configure(config: OnramperNitroConfig) throws -> Promise<Void> {
|
|
43
|
+
return Promise.async { [weak self] in
|
|
44
|
+
guard let self else { return }
|
|
45
|
+
try await self.performConfigure(config)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@MainActor
|
|
50
|
+
private func performConfigure(_ config: OnramperNitroConfig) async throws {
|
|
51
|
+
// Idempotent: a second configure() with the SDK already built is a no-op.
|
|
52
|
+
if client != nil { return }
|
|
53
|
+
|
|
54
|
+
let environment: OnramperConfiguration.Environment =
|
|
55
|
+
config.environment == "development" ? .development : .production
|
|
56
|
+
let theme: OnramperConfiguration.Theme = {
|
|
57
|
+
switch config.theme {
|
|
58
|
+
case "light": return .light
|
|
59
|
+
case "dark": return .dark
|
|
60
|
+
default: return .system
|
|
61
|
+
}
|
|
62
|
+
}()
|
|
63
|
+
let logLevel: LogLevel = {
|
|
64
|
+
switch config.logLevel {
|
|
65
|
+
case "error": return .error
|
|
66
|
+
case "info": return .info
|
|
67
|
+
case "debug": return .debug
|
|
68
|
+
default: return .off
|
|
69
|
+
}
|
|
70
|
+
}()
|
|
71
|
+
|
|
72
|
+
let swiftConfig = OnramperConfiguration(
|
|
73
|
+
apiKey: config.apiKey,
|
|
74
|
+
clientId: config.clientId,
|
|
75
|
+
environment: environment,
|
|
76
|
+
theme: theme,
|
|
77
|
+
logLevel: logLevel,
|
|
78
|
+
sessionExpirationHandler: { [weak self] in
|
|
79
|
+
guard let self, let handler = self.sessionHandler else {
|
|
80
|
+
throw OnramperError.userTokenRefreshFailed("no session expiration handler registered")
|
|
81
|
+
}
|
|
82
|
+
// Await twice: outer Promise = the callback invocation, inner Promise =
|
|
83
|
+
// the JS async function's own returned Promise. Then map to the SDK type.
|
|
84
|
+
let creds = try await handler().await().await()
|
|
85
|
+
return SessionCredentials(sessionId: creds.sessionId, sessionToken: creds.sessionToken)
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
let newClient = OnramperClient(configuration: swiftConfig)
|
|
90
|
+
client = newClient
|
|
91
|
+
attachObservers(to: newClient)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@MainActor
|
|
95
|
+
private func attachObservers(to client: OnramperClient) {
|
|
96
|
+
stateObservation = client.$state.sink { [weak self] state in
|
|
97
|
+
self?.onState?(jsonString(state.toJSDict()))
|
|
98
|
+
}
|
|
99
|
+
// `events` is a computed AsyncStream that captures a single continuation, so
|
|
100
|
+
// access it exactly once here. CheckoutEvent is Sendable → safe to iterate
|
|
101
|
+
// off the main actor.
|
|
102
|
+
let stream = client.events
|
|
103
|
+
eventTask = Task { [weak self] in
|
|
104
|
+
for await event in stream {
|
|
105
|
+
self?.onEvent?(jsonString(event.toJSDict()))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@MainActor
|
|
111
|
+
private func requireClient() throws -> OnramperClient {
|
|
112
|
+
guard let client else { throw OnramperError.notInitialized }
|
|
113
|
+
return client
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func initialize(sessionId: String, sessionToken: String) throws -> Promise<Void> {
|
|
117
|
+
return Promise.async { [weak self] in
|
|
118
|
+
guard let self else { return }
|
|
119
|
+
let client = try await self.requireClient()
|
|
120
|
+
try await client.initialize(sessionId: sessionId, sessionToken: sessionToken)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func reset() throws -> Promise<Void> {
|
|
125
|
+
return Promise.async { [weak self] in
|
|
126
|
+
guard let self else { return }
|
|
127
|
+
let client = try await self.requireClient()
|
|
128
|
+
await client.reset()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
func signOut() throws -> Promise<Void> {
|
|
133
|
+
return Promise.async { [weak self] in
|
|
134
|
+
guard let self else { return }
|
|
135
|
+
let client = try await self.requireClient()
|
|
136
|
+
await client.signOut()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// MARK: - Checkout
|
|
141
|
+
|
|
142
|
+
func getCheckoutRequirements(requestJson: String, styleJson: String) throws -> Promise<PreparedIntentResult> {
|
|
143
|
+
return Promise.async { [weak self] in
|
|
144
|
+
guard let self else { throw OnramperError.notInitialized }
|
|
145
|
+
return try await self.performGetCheckoutRequirements(requestJson: requestJson, styleJson: styleJson)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@MainActor
|
|
150
|
+
private func performGetCheckoutRequirements(requestJson: String, styleJson: String) async throws -> PreparedIntentResult {
|
|
151
|
+
let client = try requireClient()
|
|
152
|
+
let request = try decodeCheckoutRequest(requestJson)
|
|
153
|
+
let style = decodeCheckoutButtonStyle(styleJson)
|
|
154
|
+
|
|
155
|
+
// Single-flight, scoped to this client: a new prepared intent supersedes
|
|
156
|
+
// this instance's prior unconsumed one, but leaves other clients' intents
|
|
157
|
+
// in the shared registry untouched.
|
|
158
|
+
if let prev = lastPreparedHandle {
|
|
159
|
+
await PreparedIntentRegistry.shared.drop(prev)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let result = try await client.getCheckoutRequirements(request, buttonStyle: style)
|
|
163
|
+
let entry = PreparedIntentRegistry.PreparedIntent(button: AnyView(result.button), createdAt: Date())
|
|
164
|
+
let handle = await PreparedIntentRegistry.shared.store(entry)
|
|
165
|
+
lastPreparedHandle = handle
|
|
166
|
+
let quoteDict = (codableToJSValue(result.quote) as? [String: Any]) ?? [:]
|
|
167
|
+
return PreparedIntentResult(intentHandle: handle, quoteJson: jsonString(quoteDict))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
func cancelPreparedIntent(intentHandle: String) throws -> Promise<Void> {
|
|
171
|
+
if intentHandle == lastPreparedHandle {
|
|
172
|
+
lastPreparedHandle = nil
|
|
173
|
+
}
|
|
174
|
+
return Promise.async {
|
|
175
|
+
await PreparedIntentRegistry.shared.drop(intentHandle)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// MARK: - Teardown
|
|
180
|
+
|
|
181
|
+
/// Releases the stored JS callbacks (Nitro holds strong refs to them) and the
|
|
182
|
+
/// SDK client. Called from `OnramperClient.destroy()` in JS.
|
|
183
|
+
func dispose() {
|
|
184
|
+
stateObservation?.cancel()
|
|
185
|
+
stateObservation = nil
|
|
186
|
+
eventTask?.cancel()
|
|
187
|
+
eventTask = nil
|
|
188
|
+
onState = nil
|
|
189
|
+
onEvent = nil
|
|
190
|
+
sessionHandler = nil
|
|
191
|
+
lastPreparedHandle = nil
|
|
192
|
+
client = nil
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import Foundation
|
|
3
|
+
import OnramperSDK
|
|
4
|
+
import SwiftUI
|
|
5
|
+
|
|
6
|
+
// Decodes the flat JS CheckoutRequest / CheckoutButtonStyle JSON into the SDK's
|
|
7
|
+
// nested request + styled button config. (The SDK's OnramperTransactionData.init
|
|
8
|
+
// lowercases ISO codes, so we construct via inits rather than Codable-decoding
|
|
9
|
+
// straight into CheckoutIntentRequest.)
|
|
10
|
+
|
|
11
|
+
private struct CheckoutRequestJSON: Decodable {
|
|
12
|
+
let source: String
|
|
13
|
+
let destination: String?
|
|
14
|
+
let amount: Double
|
|
15
|
+
let type: String
|
|
16
|
+
let country: String?
|
|
17
|
+
let subdivision: String?
|
|
18
|
+
let paymentMethod: String
|
|
19
|
+
let wallet: WalletJSON
|
|
20
|
+
let onlyOnramps: [String]?
|
|
21
|
+
|
|
22
|
+
struct WalletJSON: Decodable {
|
|
23
|
+
let network: String
|
|
24
|
+
let address: String
|
|
25
|
+
let memo: String?
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func decodeCheckoutRequest(_ json: String) throws -> CheckoutIntentRequest {
|
|
30
|
+
guard let data = json.data(using: .utf8) else {
|
|
31
|
+
throw OnramperError.invalidRequest(field: nil, debugInfo: "request JSON not UTF-8")
|
|
32
|
+
}
|
|
33
|
+
do {
|
|
34
|
+
let r = try JSONDecoder().decode(CheckoutRequestJSON.self, from: data)
|
|
35
|
+
let txData = OnramperTransactionData(
|
|
36
|
+
source: r.source,
|
|
37
|
+
destination: r.destination,
|
|
38
|
+
amount: r.amount,
|
|
39
|
+
type: r.type == "sell" ? .sell : .buy,
|
|
40
|
+
country: r.country,
|
|
41
|
+
subdivision: r.subdivision,
|
|
42
|
+
paymentMethod: r.paymentMethod,
|
|
43
|
+
wallet: WalletInfo(network: r.wallet.network, address: r.wallet.address, memo: r.wallet.memo)
|
|
44
|
+
)
|
|
45
|
+
return CheckoutIntentRequest(onramperTransactionData: txData, onlyOnramps: r.onlyOnramps)
|
|
46
|
+
} catch {
|
|
47
|
+
throw OnramperError.invalidRequest(field: nil, debugInfo: "request decode failed: \(error)")
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private struct CheckoutButtonStyleJSON: Decodable {
|
|
52
|
+
let backgroundColor: String?
|
|
53
|
+
let foregroundColor: String?
|
|
54
|
+
let borderRadius: Double?
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func decodeCheckoutButtonStyle(_ json: String) -> CheckoutButtonStyle {
|
|
58
|
+
var style = CheckoutButtonStyle()
|
|
59
|
+
guard let data = json.data(using: .utf8),
|
|
60
|
+
let s = try? JSONDecoder().decode(CheckoutButtonStyleJSON.self, from: data) else {
|
|
61
|
+
return style
|
|
62
|
+
}
|
|
63
|
+
if let hex = s.backgroundColor, let color = Color(hex: hex) { style.backgroundColor = color }
|
|
64
|
+
if let hex = s.foregroundColor, let color = Color(hex: hex) { style.foregroundColor = color }
|
|
65
|
+
if let radius = s.borderRadius { style.borderRadius = CGFloat(radius) }
|
|
66
|
+
return style
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
extension Color {
|
|
70
|
+
init?(hex: String) {
|
|
71
|
+
let s = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#"))
|
|
72
|
+
guard s.count == 6 || s.count == 8, let value = UInt64(s, radix: 16) else { return nil }
|
|
73
|
+
let r, g, b, a: Double
|
|
74
|
+
if s.count == 6 {
|
|
75
|
+
r = Double((value >> 16) & 0xFF) / 255
|
|
76
|
+
g = Double((value >> 8) & 0xFF) / 255
|
|
77
|
+
b = Double(value & 0xFF) / 255
|
|
78
|
+
a = 1
|
|
79
|
+
} else {
|
|
80
|
+
r = Double((value >> 24) & 0xFF) / 255
|
|
81
|
+
g = Double((value >> 16) & 0xFF) / 255
|
|
82
|
+
b = Double((value >> 8) & 0xFF) / 255
|
|
83
|
+
a = Double(value & 0xFF) / 255
|
|
84
|
+
}
|
|
85
|
+
self = Color(.sRGB, red: r, green: g, blue: b, opacity: a)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
#endif
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import OnramperSDK
|
|
3
|
+
|
|
4
|
+
// Maps the SDK's non-Codable Swift enums (OnramperState, CheckoutEvent) and
|
|
5
|
+
// OnramperError into JS-friendly dictionaries, then to JSON strings for the
|
|
6
|
+
// Nitro bridge. This is SDK-shape mapping, not Expo-specific — it has no
|
|
7
|
+
// dependency on any bridging framework.
|
|
8
|
+
|
|
9
|
+
// MARK: - Error → code / structured info
|
|
10
|
+
|
|
11
|
+
extension OnramperError {
|
|
12
|
+
var jsCode: String {
|
|
13
|
+
switch self {
|
|
14
|
+
case .notInitialized: return "notInitialized"
|
|
15
|
+
case .initializationFailed: return "initializationFailed"
|
|
16
|
+
case .attestationFailed: return "attestationFailed"
|
|
17
|
+
case .invalidStateTransition: return "invalidStateTransition"
|
|
18
|
+
case .invalidState: return "invalidState"
|
|
19
|
+
case .networkError: return "networkError"
|
|
20
|
+
case .decodingError: return "decodingError"
|
|
21
|
+
case .timeout: return "timeout"
|
|
22
|
+
case .requirementNotSatisfied: return "requirementNotSatisfied"
|
|
23
|
+
case .amountOutOfRange: return "amountOutOfRange"
|
|
24
|
+
case .oidcFlowCancelled: return "oidcFlowCancelled"
|
|
25
|
+
case .oidcTokenExchangeFailed: return "oidcTokenExchangeFailed"
|
|
26
|
+
case .oidcFlowFailed: return "oidcFlowFailed"
|
|
27
|
+
case .userTokenInvalid: return "userTokenInvalid"
|
|
28
|
+
case .userTokenRefreshFailed: return "userTokenRefreshFailed"
|
|
29
|
+
case .webviewLoadFailed: return "webviewLoadFailed"
|
|
30
|
+
case .deepLinkFailed: return "deepLinkFailed"
|
|
31
|
+
case .invalidRequest: return "invalidRequest"
|
|
32
|
+
case .quoteUnavailable: return "quoteUnavailable"
|
|
33
|
+
case .checkoutForbidden: return "checkoutForbidden"
|
|
34
|
+
case .temporaryFailure: return "temporaryFailure"
|
|
35
|
+
case .unrecoverable: return "unrecoverable"
|
|
36
|
+
case .configurationError: return "configurationError"
|
|
37
|
+
case .deviceBlocked: return "deviceBlocked"
|
|
38
|
+
case .securityStorageFailed: return "securityStorageFailed"
|
|
39
|
+
case .securityTrustFailed: return "securityTrustFailed"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
var jsInfo: [String: Any]? {
|
|
44
|
+
switch self {
|
|
45
|
+
case .amountOutOfRange(let min, let max):
|
|
46
|
+
return ["min": min, "max": max]
|
|
47
|
+
case .networkError(let code, _):
|
|
48
|
+
return ["status": code]
|
|
49
|
+
case .securityStorageFailed(let status):
|
|
50
|
+
return ["status": Int(status)]
|
|
51
|
+
case .securityTrustFailed(let host):
|
|
52
|
+
return ["host": host]
|
|
53
|
+
case .invalidStateTransition(let from, let to):
|
|
54
|
+
return ["from": from, "to": to]
|
|
55
|
+
case .invalidState(let expected, let actual):
|
|
56
|
+
return ["expected": expected, "actual": actual]
|
|
57
|
+
case .invalidRequest(let field, let debugInfo):
|
|
58
|
+
var info: [String: Any] = [:]
|
|
59
|
+
if let field { info["field"] = field }
|
|
60
|
+
if let debugInfo { info["debugInfo"] = debugInfo }
|
|
61
|
+
return info.isEmpty ? nil : info
|
|
62
|
+
case .temporaryFailure(let retryAfter, let debugInfo):
|
|
63
|
+
var info: [String: Any] = [:]
|
|
64
|
+
if let retryAfter { info["retryAfter"] = retryAfter }
|
|
65
|
+
if let debugInfo { info["debugInfo"] = debugInfo }
|
|
66
|
+
return info.isEmpty ? nil : info
|
|
67
|
+
case .quoteUnavailable(let debugInfo),
|
|
68
|
+
.checkoutForbidden(let debugInfo),
|
|
69
|
+
.unrecoverable(let debugInfo):
|
|
70
|
+
return debugInfo.map { ["debugInfo": $0] }
|
|
71
|
+
default:
|
|
72
|
+
return nil
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// A JS-friendly error dict: `{ code, message, info? }`.
|
|
77
|
+
func toJSDict() -> [String: Any] {
|
|
78
|
+
var dict: [String: Any] = ["code": jsCode, "message": errorDescription ?? "Unknown error"]
|
|
79
|
+
if let info = jsInfo { dict["info"] = info }
|
|
80
|
+
return dict
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// MARK: - Enum raw values
|
|
85
|
+
|
|
86
|
+
extension RenderType {
|
|
87
|
+
var jsValue: String { rawValue } // "webview" | "deeplink"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
extension CheckoutPaymentType {
|
|
91
|
+
var jsValue: String { rawValue } // "applepay" | "revolutpay"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// MARK: - State → dict
|
|
95
|
+
|
|
96
|
+
extension OnramperState {
|
|
97
|
+
func toJSDict() -> [String: Any] {
|
|
98
|
+
switch self {
|
|
99
|
+
case .idle: return ["kind": "idle"]
|
|
100
|
+
case .initializing: return ["kind": "initializing"]
|
|
101
|
+
case .ready: return ["kind": "ready"]
|
|
102
|
+
case .checkoutPreparing: return ["kind": "checkoutPreparing"]
|
|
103
|
+
case .requireLogin(let requirements):
|
|
104
|
+
return ["kind": "requireLogin", "requirements": codableToJSArray(requirements)]
|
|
105
|
+
case .authenticating: return ["kind": "authenticating"]
|
|
106
|
+
case .readyToCheckout: return ["kind": "readyToCheckout"]
|
|
107
|
+
case .finalizing: return ["kind": "finalizing"]
|
|
108
|
+
case .rendering(let ctx):
|
|
109
|
+
return [
|
|
110
|
+
"kind": "rendering",
|
|
111
|
+
"url": ctx.url.absoluteString,
|
|
112
|
+
"renderType": ctx.renderType.jsValue,
|
|
113
|
+
"paymentType": ctx.paymentType.jsValue,
|
|
114
|
+
]
|
|
115
|
+
case .completed: return ["kind": "completed"]
|
|
116
|
+
case .failed(let error):
|
|
117
|
+
return ["kind": "failed", "error": error.toJSDict()]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// MARK: - Event → dict
|
|
123
|
+
|
|
124
|
+
extension CheckoutEvent {
|
|
125
|
+
func toJSDict() -> [String: Any] {
|
|
126
|
+
switch self {
|
|
127
|
+
case .stateChanged(let state):
|
|
128
|
+
return ["type": "stateChanged", "state": state.toJSDict()]
|
|
129
|
+
case .checkoutStarted(let intentId):
|
|
130
|
+
return ["type": "checkoutStarted", "intentId": intentId]
|
|
131
|
+
case .loginRequired(let requirements):
|
|
132
|
+
return ["type": "loginRequired", "requirements": codableToJSArray(requirements)]
|
|
133
|
+
case .readyToCheckout:
|
|
134
|
+
return ["type": "readyToCheckout"]
|
|
135
|
+
case .requirementSatisfied(let kind):
|
|
136
|
+
return ["type": "requirementSatisfied", "requirementType": kind.rawValue]
|
|
137
|
+
case .checkoutFinalized(let response):
|
|
138
|
+
return ["type": "checkoutFinalized", "response": codableToJSValue(response)]
|
|
139
|
+
case .renderingStarted(let url, let renderType):
|
|
140
|
+
return ["type": "renderingStarted", "url": url.absoluteString, "renderType": renderType.jsValue]
|
|
141
|
+
case .completed(let checkoutId):
|
|
142
|
+
return ["type": "completed", "checkoutId": checkoutId]
|
|
143
|
+
case .failed(let err):
|
|
144
|
+
return ["type": "failed", "error": err.toJSDict()]
|
|
145
|
+
case .checkoutCancelled:
|
|
146
|
+
return ["type": "cancelled"]
|
|
147
|
+
// Provider-lifecycle events (third-party checkout webviews).
|
|
148
|
+
case .providerReady:
|
|
149
|
+
return ["type": "providerReady"]
|
|
150
|
+
case .paymentAuthorized:
|
|
151
|
+
return ["type": "paymentAuthorized"]
|
|
152
|
+
case .paymentProcessing:
|
|
153
|
+
return ["type": "paymentProcessing"]
|
|
154
|
+
case .paymentCancelled:
|
|
155
|
+
return ["type": "paymentCancelled"]
|
|
156
|
+
case .providerError(let reason):
|
|
157
|
+
return ["type": "providerError", "reason": reason]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// MARK: - Codable → JS value / JSON string
|
|
163
|
+
|
|
164
|
+
func codableToJSValue<T: Codable>(_ value: T) -> Any {
|
|
165
|
+
guard let data = try? JSONEncoder().encode(value),
|
|
166
|
+
let obj = try? JSONSerialization.jsonObject(with: data) else {
|
|
167
|
+
return [:]
|
|
168
|
+
}
|
|
169
|
+
return obj
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
func codableToJSArray<T: Codable>(_ values: [T]) -> [Any] {
|
|
173
|
+
(codableToJSValue(values) as? [Any]) ?? []
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// Serializes a JS-friendly dict to a JSON string for transport across the
|
|
177
|
+
/// Nitro callback boundary. Returns "{}" on failure (should not happen for the
|
|
178
|
+
/// fixed-shape dicts produced above).
|
|
179
|
+
func jsonString(_ dict: [String: Any]) -> String {
|
|
180
|
+
guard let data = try? JSONSerialization.data(withJSONObject: dict),
|
|
181
|
+
let str = String(data: data, encoding: .utf8) else {
|
|
182
|
+
return "{}"
|
|
183
|
+
}
|
|
184
|
+
return str
|
|
185
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
import SwiftUI
|
|
4
|
+
|
|
5
|
+
actor PreparedIntentRegistry {
|
|
6
|
+
/// Process-wide registry. `getCheckoutRequirements` (on HybridOnramperNitro)
|
|
7
|
+
/// stores the SwiftUI button under a ULID handle; the separate
|
|
8
|
+
/// HybridOnramperCheckoutButton view consumes it by handle. Handles are
|
|
9
|
+
/// unique ULIDs, so a single shared registry is safe across clients.
|
|
10
|
+
static let shared = PreparedIntentRegistry()
|
|
11
|
+
|
|
12
|
+
struct PreparedIntent {
|
|
13
|
+
let button: AnyView
|
|
14
|
+
let createdAt: Date
|
|
15
|
+
var consumed: Bool = false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private var entries: [String: PreparedIntent] = [:]
|
|
19
|
+
private let ttl: TimeInterval
|
|
20
|
+
|
|
21
|
+
init(ttl: TimeInterval = 300) {
|
|
22
|
+
self.ttl = ttl
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func store(_ entry: PreparedIntent) -> String {
|
|
26
|
+
sweepExpired()
|
|
27
|
+
let handle = Self.makeULID()
|
|
28
|
+
entries[handle] = entry
|
|
29
|
+
return handle
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func consume(_ handle: String) -> PreparedIntent? {
|
|
33
|
+
guard var entry = entries[handle], !entry.consumed else { return nil }
|
|
34
|
+
entry.consumed = true
|
|
35
|
+
entries[handle] = entry
|
|
36
|
+
return entry
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func drop(_ handle: String) {
|
|
40
|
+
entries.removeValue(forKey: handle)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func invalidateAll() {
|
|
44
|
+
entries.removeAll()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private func sweepExpired() {
|
|
48
|
+
let now = Date()
|
|
49
|
+
entries = entries.filter { now.timeIntervalSince($0.value.createdAt) < ttl }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private static func makeULID() -> String {
|
|
53
|
+
var bytes = [UInt8](repeating: 0, count: 16)
|
|
54
|
+
_ = SecRandomCopyBytes(kSecRandomDefault, 10, &bytes[6])
|
|
55
|
+
let ts = UInt64(Date().timeIntervalSince1970 * 1000)
|
|
56
|
+
for i in 0..<6 {
|
|
57
|
+
bytes[i] = UInt8((ts >> ((5 - i) * 8)) & 0xFF)
|
|
58
|
+
}
|
|
59
|
+
let alphabet = Array("0123456789ABCDEFGHJKMNPQRSTVWXYZ")
|
|
60
|
+
var out = ""
|
|
61
|
+
var bits: UInt64 = 0
|
|
62
|
+
var bitCount = 0
|
|
63
|
+
for byte in bytes {
|
|
64
|
+
bits = (bits << 8) | UInt64(byte)
|
|
65
|
+
bitCount += 8
|
|
66
|
+
while bitCount >= 5 {
|
|
67
|
+
bitCount -= 5
|
|
68
|
+
let idx = Int((bits >> bitCount) & 0x1F)
|
|
69
|
+
out.append(alphabet[idx])
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if bitCount > 0 {
|
|
73
|
+
let idx = Int((bits << (5 - bitCount)) & 0x1F)
|
|
74
|
+
out.append(alphabet[idx])
|
|
75
|
+
}
|
|
76
|
+
return String(out.prefix(26))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.OnramperCheckoutButtonView = void 0;
|
|
7
|
+
var _reactNativeNitroModules = require("react-native-nitro-modules");
|
|
8
|
+
var _OnramperCheckoutButtonConfig = _interopRequireDefault(require("./generated/OnramperCheckoutButtonConfig.json"));
|
|
9
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
/**
|
|
11
|
+
* Native checkout button. Renders the SwiftUI `OnramperCheckoutButton` that
|
|
12
|
+
* `OnramperClient.getCheckoutRequirements()` prepared, addressed by `intentHandle`.
|
|
13
|
+
* Standard view props (e.g. `style`) are supported via `HybridViewProps`.
|
|
14
|
+
*/
|
|
15
|
+
const OnramperCheckoutButtonView = exports.OnramperCheckoutButtonView = (0, _reactNativeNitroModules.getHostComponent)('OnramperCheckoutButton', () => _OnramperCheckoutButtonConfig.default);
|
|
16
|
+
//# sourceMappingURL=OnramperCheckoutButtonView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_reactNativeNitroModules","require","_OnramperCheckoutButtonConfig","_interopRequireDefault","e","__esModule","default","OnramperCheckoutButtonView","exports","getHostComponent","OnramperCheckoutButtonConfig"],"sourceRoot":"../../src","sources":["OnramperCheckoutButtonView.tsx"],"mappings":";;;;;;AAAA,IAAAA,wBAAA,GAAAC,OAAA;AACA,IAAAC,6BAAA,GAAAC,sBAAA,CAAAF,OAAA;AAAyF,SAAAE,uBAAAC,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAKzF;AACA;AACA;AACA;AACA;AACO,MAAMG,0BAA0B,GAAAC,OAAA,CAAAD,0BAAA,GAAG,IAAAE,yCAAgB,EACxD,wBAAwB,EACxB,MAAMC,qCACR,CAAC","ignoreList":[]}
|