@tinkerlandai/indiana-react-native 0.9.1 → 0.9.5
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/IndianaReactNative.podspec +19 -0
- package/README.md +61 -4
- package/THIRD_PARTY_NOTICES.md +74 -0
- package/android/build.gradle +24 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/ai/tinkerland/indiana/reactnative/IndianaBridgeModule.kt +237 -0
- package/android/src/main/java/ai/tinkerland/indiana/reactnative/IndianaBridgePackage.kt +11 -0
- package/dist/index.cjs +17 -1
- package/dist/index.d.cts +57408 -604
- package/dist/index.d.ts +57408 -604
- package/dist/index.js +17 -1
- package/ios/IndianaBridge.m +11 -0
- package/ios/IndianaBridge.swift +275 -0
- package/native-sdk/android/assets/indiana/index.html +268 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/BillingAuthorizationTerms.kt +55 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/BundledWebView.kt +90 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/ContentSession.kt +45 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/ContentView.kt +36 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/CustomerOperations.generated.kt +84 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/CustomerResponseSemantics.kt +193 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/CustomerSemantics.kt +120 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/CustomerSession.kt +106 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/FeatureClient.kt +65 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/FunnelCommandStorage.kt +163 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/FunnelConfigurationRelay.kt +50 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/FunnelSession.kt +208 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/FunnelView.kt +71 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/GeneratedProtocol.kt +16 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/GoogleStorefront.kt +165 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/HTTPTransport.kt +43 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/IndianaClient.kt +526 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/NativePendingKeystore.kt +81 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/NativeReceiptRelay.kt +28 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/NativeStoreOffer.kt +24 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/PaywallContent.kt +75 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/PaywallSession.kt +144 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/PaywallView.kt +74 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/Protocol.kt +28 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/PushInstallation.kt +170 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/RendererNavigation.kt +13 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/SDKVersion.kt +6 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/StoreSubscriptionManagement.kt +19 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/WireSchema.kt +76 -0
- package/native-sdk/android/resources/funnel-wire-schemas.json +1 -0
- package/native-sdk/android/resources/indiana-third-party-notices.txt +101 -0
- package/native-sdk/android/resources/renderer-data-url.txt +1 -0
- package/native-sdk/ios/AnalyticsReceipt.swift +10 -0
- package/native-sdk/ios/AppleStorefront.swift +218 -0
- package/native-sdk/ios/BillingAuthorizationTerms.swift +57 -0
- package/native-sdk/ios/BundledWebView.swift +87 -0
- package/native-sdk/ios/ContentSession.swift +48 -0
- package/native-sdk/ios/ContentView.swift +51 -0
- package/native-sdk/ios/CustomerOperations.generated.swift +166 -0
- package/native-sdk/ios/CustomerResponseSemantics.swift +232 -0
- package/native-sdk/ios/CustomerSemantics.swift +145 -0
- package/native-sdk/ios/CustomerSession.swift +129 -0
- package/native-sdk/ios/FeatureClient.swift +86 -0
- package/native-sdk/ios/FunnelCommandStorage.swift +163 -0
- package/native-sdk/ios/FunnelConfigurationRelay.swift +48 -0
- package/native-sdk/ios/FunnelSession.swift +232 -0
- package/native-sdk/ios/FunnelView.swift +125 -0
- package/native-sdk/ios/GeneratedProtocol.swift +16 -0
- package/native-sdk/ios/HTTPTransport.swift +74 -0
- package/native-sdk/ios/IndianaClient.swift +601 -0
- package/native-sdk/ios/NativePendingKeychain.swift +52 -0
- package/native-sdk/ios/NativeReceiptRelay.swift +28 -0
- package/native-sdk/ios/NativeStoreOffer.swift +34 -0
- package/native-sdk/ios/PaywallContent.swift +75 -0
- package/native-sdk/ios/PaywallSession.swift +124 -0
- package/native-sdk/ios/PaywallView.swift +87 -0
- package/native-sdk/ios/Protocol.swift +49 -0
- package/native-sdk/ios/PushInstallation.swift +154 -0
- package/native-sdk/ios/RendererNavigation.swift +8 -0
- package/native-sdk/ios/Resources.swift +15 -0
- package/native-sdk/ios/SDKVersion.swift +4 -0
- package/native-sdk/ios/StoreSubscriptionManagement.swift +11 -0
- package/native-sdk/ios/WebView/THIRD_PARTY_NOTICES.txt +101 -0
- package/native-sdk/ios/WebView/index.html +268 -0
- package/native-sdk/ios/WebView/renderer-data-url.txt +1 -0
- package/native-sdk/ios/WireSchema.swift +107 -0
- package/native-sdk/ios/funnel-wire-schemas.json +1 -0
- package/package.json +8 -3
- package/react-native.config.cjs +12 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <React/RCTEventEmitter.h>
|
|
3
|
+
@interface RCT_EXTERN_MODULE(IndianaBridge, RCTEventEmitter)
|
|
4
|
+
RCT_EXTERN_METHOD(create:(NSString *)configuration resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
5
|
+
RCT_EXTERN_METHOD(identify:(NSString *)instanceId session:(NSString *)session resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
6
|
+
RCT_EXTERN_METHOD(invoke:(NSString *)instanceId method:(NSString *)method input:(NSString *)input resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
7
|
+
RCT_EXTERN_METHOD(reset:(NSString *)instanceId revision:(nonnull NSNumber *)revision resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
8
|
+
RCT_EXTERN_METHOD(destroy:(NSString *)instanceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
9
|
+
RCT_EXTERN_METHOD(acknowledge:(NSString *)instanceId requestId:(NSString *)requestId accepted:(BOOL)accepted resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
10
|
+
RCT_EXTERN_METHOD(provideCredential:(NSString *)instanceId requestId:(NSString *)requestId token:(NSString * _Nullable)token resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
11
|
+
@end
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
@preconcurrency import React
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
/// One scoped Customer client per JS SDK instance. No Server Key crosses this bridge.
|
|
6
|
+
@MainActor @objc(IndianaBridge)
|
|
7
|
+
final class IndianaBridge: RCTEventEmitter {
|
|
8
|
+
private struct Configuration: Decodable {
|
|
9
|
+
let baseUrl: String
|
|
10
|
+
let scope: IndianaScope
|
|
11
|
+
let timeout: Double
|
|
12
|
+
let credentialEvents: Bool?
|
|
13
|
+
}
|
|
14
|
+
@MainActor private final class Instance {
|
|
15
|
+
let client: IndianaClient
|
|
16
|
+
var revision = UUID()
|
|
17
|
+
var identitySequence = -1
|
|
18
|
+
var actions: [String: CheckedContinuation<Void, Error>] = [:]
|
|
19
|
+
var credentials: [String: CheckedContinuation<String, Error>] = [:]
|
|
20
|
+
var close: (() -> Void)?
|
|
21
|
+
var presentation: UUID?
|
|
22
|
+
init(_ client: IndianaClient) { self.client = client }
|
|
23
|
+
}
|
|
24
|
+
private var instances: [String: Instance] = [:]
|
|
25
|
+
@objc nonisolated override static func requiresMainQueueSetup() -> Bool { true }
|
|
26
|
+
@objc nonisolated override var methodQueue: DispatchQueue { .main }
|
|
27
|
+
nonisolated override func supportedEvents() -> [String]! { ["IndianaAction"] }
|
|
28
|
+
nonisolated override func invalidate() {
|
|
29
|
+
Task { @MainActor in
|
|
30
|
+
let owned = instances.values; instances.removeAll()
|
|
31
|
+
for value in owned {
|
|
32
|
+
value.revision = UUID(); value.close?(); cancelActions(value)
|
|
33
|
+
await value.client.reset()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
super.invalidate()
|
|
37
|
+
}
|
|
38
|
+
private func cancelActions(_ owner: Instance) {
|
|
39
|
+
let pending = owner.actions.values; owner.actions.removeAll()
|
|
40
|
+
pending.forEach { $0.resume(throwing: IndianaClientError.identityChanged) }
|
|
41
|
+
let credentials = owner.credentials.values; owner.credentials.removeAll()
|
|
42
|
+
credentials.forEach { $0.resume(throwing: IndianaClientError.identityChanged) }
|
|
43
|
+
}
|
|
44
|
+
private func credential(_ id: String) async throws -> String {
|
|
45
|
+
let owner = try instance(id), request = UUID().uuidString
|
|
46
|
+
return try await withCheckedThrowingContinuation { continuation in
|
|
47
|
+
owner.credentials[request] = continuation
|
|
48
|
+
sendEvent(withName: "IndianaAction", body: ["instanceId": id, "requestId": request, "revision": owner.identitySequence, "kind": "credential"])
|
|
49
|
+
Task { @MainActor in
|
|
50
|
+
try? await Task.sleep(for: .seconds(45))
|
|
51
|
+
owner.credentials.removeValue(forKey: request)?.resume(throwing: IndianaClientError.temporaryUnavailable)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
@objc(provideCredential:requestId:token:resolver:rejecter:)
|
|
56
|
+
func provideCredential(_ id: String, requestId: String, token: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
57
|
+
do {
|
|
58
|
+
let owner = try instance(id)
|
|
59
|
+
if let continuation = owner.credentials.removeValue(forKey: requestId) {
|
|
60
|
+
if let token { continuation.resume(returning: token) }
|
|
61
|
+
else { continuation.resume(throwing: IndianaClientError.unauthenticated) }
|
|
62
|
+
}
|
|
63
|
+
resolve(nil)
|
|
64
|
+
} catch { fail(reject, error) }
|
|
65
|
+
}
|
|
66
|
+
private func action(_ owner: Instance, revision: UUID, _ payload: IndianaJSON) async throws {
|
|
67
|
+
guard owner.revision == revision else { throw IndianaClientError.identityChanged }
|
|
68
|
+
guard let id = instances.first(where: { $0.value === owner })?.key else { throw IndianaClientError.identityChanged }
|
|
69
|
+
let request = UUID().uuidString
|
|
70
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
71
|
+
owner.actions[request] = continuation
|
|
72
|
+
do {
|
|
73
|
+
let object = try JSONSerialization.jsonObject(with: JSONEncoder().encode(payload))
|
|
74
|
+
sendEvent(withName: "IndianaAction", body: ["instanceId": id, "requestId": request, "revision": owner.identitySequence, "action": object])
|
|
75
|
+
} catch { owner.actions.removeValue(forKey: request)?.resume(throwing: error) }
|
|
76
|
+
Task { @MainActor in
|
|
77
|
+
try? await Task.sleep(for: .seconds(45))
|
|
78
|
+
owner.actions.removeValue(forKey: request)?.resume(throwing: IndianaClientError.temporaryUnavailable)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
@objc(acknowledge:requestId:accepted:resolver:rejecter:)
|
|
83
|
+
func acknowledge(_ id: String, requestId: String, accepted: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
84
|
+
do {
|
|
85
|
+
let owner = try instance(id)
|
|
86
|
+
if let continuation = owner.actions.removeValue(forKey: requestId) {
|
|
87
|
+
if accepted { continuation.resume() } else { continuation.resume(throwing: IndianaClientError.unsupportedCapability) }
|
|
88
|
+
}
|
|
89
|
+
resolve(nil)
|
|
90
|
+
} catch { fail(reject, error) }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private func json(_ text: String) throws -> Data {
|
|
94
|
+
guard let data = text.data(using: .utf8), data.count <= 2_097_152 else { throw IndianaClientError.invalidResponse }
|
|
95
|
+
return data
|
|
96
|
+
}
|
|
97
|
+
private func instance(_ id: String) throws -> Instance {
|
|
98
|
+
guard let value = instances[id] else { throw IndianaClientError.invalidConfiguration }
|
|
99
|
+
return value
|
|
100
|
+
}
|
|
101
|
+
private func encode<T: Encodable>(_ value: T) throws -> String {
|
|
102
|
+
guard let text = String(data: try JSONEncoder().encode(value), encoding: .utf8) else { throw IndianaClientError.invalidResponse }
|
|
103
|
+
return text
|
|
104
|
+
}
|
|
105
|
+
private func fail(_ reject: RCTPromiseRejectBlock, _ error: Error) {
|
|
106
|
+
// Credentials and provider payloads never enter bridge error messages.
|
|
107
|
+
reject("INDIANA_NATIVE_ERROR", String(describing: type(of: error)), nil)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@objc(create:resolver:rejecter:)
|
|
111
|
+
func create(_ configuration: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
112
|
+
Task {
|
|
113
|
+
do {
|
|
114
|
+
let input = try JSONDecoder().decode(Configuration.self, from: json(configuration))
|
|
115
|
+
guard let url = URL(string: input.baseUrl) else { throw IndianaClientError.invalidConfiguration }
|
|
116
|
+
let id = UUID().uuidString
|
|
117
|
+
instances[id] = Instance(IndianaClient(configuration: try IndianaConfiguration(baseURL: url, scope: input.scope, timeout: input.timeout)))
|
|
118
|
+
if input.credentialEvents == true { await instances[id]!.client.setAccessTokenProvider { @MainActor [weak self] in
|
|
119
|
+
guard let self else { throw IndianaClientError.identityChanged }
|
|
120
|
+
return try await self.credential(id)
|
|
121
|
+
} }
|
|
122
|
+
resolve(id)
|
|
123
|
+
} catch { fail(reject, error) }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@objc(identify:session:resolver:rejecter:)
|
|
128
|
+
func identify(_ id: String, session: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
129
|
+
Task {
|
|
130
|
+
do {
|
|
131
|
+
let value = try instance(id)
|
|
132
|
+
let frame = try JSONDecoder().decode([String: IndianaJSON].self, from: json(session))
|
|
133
|
+
guard case .number(let number) = frame["revision"], number >= 0, number <= 9_007_199_254_740_991, number.rounded() == number, let credentials = frame["credentials"] else { throw IndianaClientError.invalidResponse }
|
|
134
|
+
let sequence = Int(number)
|
|
135
|
+
guard sequence >= value.identitySequence else { throw IndianaClientError.identityChanged }
|
|
136
|
+
value.identitySequence = sequence
|
|
137
|
+
let data = try JSONEncoder().encode(credentials)
|
|
138
|
+
value.revision = UUID(); value.close?(); value.close = nil; value.presentation = nil; cancelActions(value)
|
|
139
|
+
let token = try JSONDecoder().decode(String.self, from: data)
|
|
140
|
+
try await value.client.identify(customerToken: token)
|
|
141
|
+
resolve(nil)
|
|
142
|
+
} catch { fail(reject, error) }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@objc(reset:revision:resolver:rejecter:)
|
|
147
|
+
func reset(_ id: String, revision: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
148
|
+
Task {
|
|
149
|
+
do {
|
|
150
|
+
let value = try instance(id)
|
|
151
|
+
let number = revision.doubleValue
|
|
152
|
+
guard number.isFinite, number >= 0, number <= 9_007_199_254_740_991, number.rounded() == number else { throw IndianaClientError.invalidResponse }
|
|
153
|
+
guard revision.intValue >= value.identitySequence else { resolve(nil); return }
|
|
154
|
+
value.identitySequence = revision.intValue
|
|
155
|
+
value.revision = UUID(); value.close?(); value.close = nil; value.presentation = nil; cancelActions(value)
|
|
156
|
+
await value.client.reset()
|
|
157
|
+
resolve(nil)
|
|
158
|
+
} catch { fail(reject, error) }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@objc(destroy:resolver:rejecter:)
|
|
163
|
+
func destroy(_ id: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
164
|
+
Task {
|
|
165
|
+
guard let value = instances.removeValue(forKey: id) else { resolve(nil); return }
|
|
166
|
+
value.revision = UUID(); value.close?(); value.close = nil; cancelActions(value)
|
|
167
|
+
await value.client.reset()
|
|
168
|
+
resolve(nil)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@objc(invoke:method:input:resolver:rejecter:)
|
|
173
|
+
func invoke(_ id: String, method: String, input: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
174
|
+
Task {
|
|
175
|
+
do {
|
|
176
|
+
let owner = try instance(id), revision = owner.revision
|
|
177
|
+
let fields = try JSONDecoder().decode([String: IndianaJSON].self, from: json(input))
|
|
178
|
+
switch method {
|
|
179
|
+
case "restorePurchases":
|
|
180
|
+
let results = try await owner.client.restorePurchases()
|
|
181
|
+
guard revision == owner.revision else { throw IndianaClientError.identityChanged }
|
|
182
|
+
resolve(try encode(results.map { try JSONDecoder().decode(IndianaJSON.self, from: $0) }))
|
|
183
|
+
case "purchaseSubscription", "switchSubscription":
|
|
184
|
+
guard fields["accepted"] == .bool(true), let raw = fields["operationId"]?.string, let operationId = UUID(uuidString: raw) else { throw IndianaClientError.invalidResponse }
|
|
185
|
+
let result = method == "purchaseSubscription"
|
|
186
|
+
? try await owner.client.purchaseSubscription(operationId: operationId, confirm: { _ in true })
|
|
187
|
+
: try await owner.client.switchSubscription(operationId: operationId, confirm: { _ in true })
|
|
188
|
+
guard revision == owner.revision else { throw IndianaClientError.identityChanged }
|
|
189
|
+
resolve(String(data: result, encoding: .utf8))
|
|
190
|
+
case "manageSubscriptions":
|
|
191
|
+
guard let scene = UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first(where: { $0.activationState == .foregroundActive }) else { throw IndianaClientError.unsupportedCapability }
|
|
192
|
+
try await IndianaStoreSubscriptions.manage(in: scene)
|
|
193
|
+
resolve("null")
|
|
194
|
+
case "showFunnel", "showPaywall", "showContent":
|
|
195
|
+
try await present(owner, revision: revision, method: method, fields: fields, resolve: resolve, reject: reject)
|
|
196
|
+
default: throw IndianaClientError.unsupportedCapability
|
|
197
|
+
}
|
|
198
|
+
} catch { fail(reject, error) }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private func presenter() throws -> UIViewController {
|
|
203
|
+
guard let scene = UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first(where: { $0.activationState == .foregroundActive }),
|
|
204
|
+
var controller = scene.windows.first(where: { $0.isKeyWindow })?.rootViewController else { throw IndianaClientError.unsupportedCapability }
|
|
205
|
+
while let next = controller.presentedViewController { controller = next }
|
|
206
|
+
return controller
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private func present(_ owner: Instance, revision: UUID, method: String, fields: [String: IndianaJSON], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) async throws {
|
|
210
|
+
guard owner.presentation == nil else { throw IndianaClientError.unsupportedCapability }
|
|
211
|
+
let presenter = try presenter()
|
|
212
|
+
let reservation = UUID()
|
|
213
|
+
owner.presentation = reservation
|
|
214
|
+
var shown = false
|
|
215
|
+
defer { if !shown, owner.presentation == reservation { owner.presentation = nil } }
|
|
216
|
+
var settled = false
|
|
217
|
+
var controller: UIViewController!
|
|
218
|
+
var release: (() -> Void)?
|
|
219
|
+
let finish: @MainActor @Sendable (IndianaJSON) -> Void = { value in
|
|
220
|
+
guard !settled else { return }; settled = true
|
|
221
|
+
if owner.presentation == reservation { owner.close = nil; owner.presentation = nil }
|
|
222
|
+
release?()
|
|
223
|
+
controller?.dismiss(animated: true)
|
|
224
|
+
let result = owner.revision == revision ? value : .object(["status": .string("dismissed")])
|
|
225
|
+
do { resolve(try self.encode(result)) } catch { self.fail(reject, error) }
|
|
226
|
+
}
|
|
227
|
+
let dismissed: @MainActor @Sendable () -> Void = { finish(.object(["status": .string("dismissed")])) }
|
|
228
|
+
let failed: @MainActor @Sendable (Error) -> Void = { error in
|
|
229
|
+
guard !settled else { return }; settled = true
|
|
230
|
+
if owner.presentation == reservation { owner.close = nil; owner.presentation = nil }
|
|
231
|
+
release?()
|
|
232
|
+
controller?.dismiss(animated: true); self.fail(reject, error)
|
|
233
|
+
}
|
|
234
|
+
let purchase: @MainActor @Sendable (IndianaJSON) async throws -> Void = { operation in
|
|
235
|
+
guard owner.revision == revision else { throw IndianaClientError.identityChanged }
|
|
236
|
+
guard let raw = operation.object?["operationId"]?.string, let id = UUID(uuidString: raw) else { throw IndianaClientError.invalidResponse }
|
|
237
|
+
_ = try await owner.client.purchaseSubscription(operationId: id, confirm: { _ in true })
|
|
238
|
+
}
|
|
239
|
+
if method == "showFunnel" {
|
|
240
|
+
guard let token = fields["runToken"]?.string else { throw IndianaClientError.invalidResponse }
|
|
241
|
+
let session = try await owner.client.funnel(runToken: token)
|
|
242
|
+
let view = IndianaFunnelViewController(session: session)
|
|
243
|
+
view.onPurchase = purchase; view.onDismiss = dismissed; view.onError = failed
|
|
244
|
+
view.onEvent = { name in try await self.action(owner, revision: revision, .object(["type": .string("event"), "name": .string(name)])) }
|
|
245
|
+
view.onComplete = { run in
|
|
246
|
+
do { finish(.object(["status": .string("complete"), "result": try JSONDecoder().decode(IndianaJSON.self, from: JSONEncoder().encode(run))])) }
|
|
247
|
+
catch { failed(error) }
|
|
248
|
+
}
|
|
249
|
+
controller = view
|
|
250
|
+
release = { Task { await view.close() } }
|
|
251
|
+
} else if method == "showPaywall" {
|
|
252
|
+
guard let raw = fields["provider"]?.string, let provider = IndianaSubscriptionProvider(rawValue: raw), let currency = fields["currency"]?.string else { throw IndianaClientError.invalidResponse }
|
|
253
|
+
let session = try await owner.client.paywall(paywallId: fields["paywallId"]?.string.flatMap(UUID.init(uuidString:)), paywallVersionId: fields["paywallVersionId"]?.string.flatMap(UUID.init(uuidString:)), provider: provider, currency: currency, locale: fields["locale"]?.string ?? "en")
|
|
254
|
+
let view = IndianaPaywallViewController(session: session)
|
|
255
|
+
view.onPurchase = purchase; view.onDismiss = dismissed; view.onError = failed
|
|
256
|
+
view.onEvent = { name in try await self.action(owner, revision: revision, .object(["type": .string("event"), "name": .string(name)])) }
|
|
257
|
+
view.onComplete = { result in finish(.object(["status": .string("complete"), "result": result])) }
|
|
258
|
+
controller = view
|
|
259
|
+
release = { Task { await view.close() } }
|
|
260
|
+
} else {
|
|
261
|
+
guard let selector = fields["selector"], let kind = fields["kind"] else { throw IndianaClientError.invalidResponse }
|
|
262
|
+
let session = try await owner.client.content(["kind": kind, "selector": selector], locale: fields["locale"]?.string ?? "en", inputs: fields["inputs"]?.object ?? [:])
|
|
263
|
+
let view = IndianaContentViewController(session: session)
|
|
264
|
+
view.onDismiss = dismissed; view.onError = failed
|
|
265
|
+
view.onAction = { payload in try await self.action(owner, revision: revision, payload) }
|
|
266
|
+
controller = view
|
|
267
|
+
release = { Task { await session.close() } }
|
|
268
|
+
}
|
|
269
|
+
guard revision == owner.revision, owner.presentation == reservation else { release?(); throw IndianaClientError.identityChanged }
|
|
270
|
+
owner.close = dismissed
|
|
271
|
+
controller.isModalInPresentation = true
|
|
272
|
+
presenter.present(controller, animated: true)
|
|
273
|
+
shown = true
|
|
274
|
+
}
|
|
275
|
+
}
|