@tinkerlandai/indiana-react-native 0.9.2 → 0.10.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/IndianaReactNative.podspec +19 -0
- package/README.md +62 -5
- 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 +96889 -6007
- package/dist/index.d.ts +96889 -6007
- 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 +197 -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/MerchantCommerceSemantics.kt +38 -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 +236 -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/MerchantCommerceSemantics.swift +51 -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,57 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Current immutable Stripe authorization terms. They contain no payment credential.
|
|
4
|
+
internal enum IndianaBillingAuthorizationTerms {
|
|
5
|
+
static func validate(_ terms: [String: IndianaJSON]) throws {
|
|
6
|
+
func text(_ object: [String: IndianaJSON], _ key: String) throws -> String {
|
|
7
|
+
guard let value = object[key]?.string else { throw IndianaFunnelError.invalidResponse }; return value
|
|
8
|
+
}
|
|
9
|
+
func quantity(_ value: IndianaJSON?, positive: Bool = false) throws -> Decimal {
|
|
10
|
+
guard let raw = value?.string, raw.range(of: "^(0|[1-9][0-9]{0,11})(\\.[0-9]{1,6})?$", options: .regularExpression) != nil,
|
|
11
|
+
let amount = Decimal(string: raw, locale: Locale(identifier: "en_US_POSIX")), !positive || amount > 0 else { throw IndianaFunnelError.invalidResponse }; return amount
|
|
12
|
+
}
|
|
13
|
+
func amount(_ key: String) throws {
|
|
14
|
+
let raw = try text(terms, key)
|
|
15
|
+
guard raw.range(of: "^[1-9][0-9]{0,14}$", options: .regularExpression) != nil else { throw IndianaFunnelError.invalidResponse }
|
|
16
|
+
}
|
|
17
|
+
let kind = try text(terms, "kind"), currency = try text(terms, "currency")
|
|
18
|
+
guard ["auto_topup", "postpaid"].contains(kind), currency.range(of: "^[A-Z]{3}$", options: .regularExpression) != nil else { throw IndianaFunnelError.invalidResponse }
|
|
19
|
+
for key in ["productVersionId", "internal_feature_id"] { guard UUID(uuidString: try text(terms, key)) != nil else { throw IndianaFunnelError.invalidResponse } }
|
|
20
|
+
let base: Set<String> = ["kind", "productVersionId", "internal_feature_id", "currency"]
|
|
21
|
+
if kind == "auto_topup" {
|
|
22
|
+
guard Set(terms.keys) == base.union(["threshold", "quantity", "purchaseAmountMinor", "purchaseUnits"]).union(terms["purchaseLimit"] == nil ? [] : ["purchaseLimit"]),
|
|
23
|
+
let threshold=terms["threshold"]?.string,threshold.range(of:"^-?(0|[1-9][0-9]{0,11})(\\.[0-9]{1,6})?$",options:.regularExpression) != nil else {throw IndianaFunnelError.invalidResponse}
|
|
24
|
+
if let value=terms["purchaseLimit"] {
|
|
25
|
+
guard let limit=value.object,Set(limit.keys)==Set(["interval","intervalCount","limit"]),["hour","day","week","month"].contains(try text(limit,"interval")),
|
|
26
|
+
case .number(let count)?=limit["intervalCount"],count>=1,count<=12,count.rounded()==count,
|
|
27
|
+
case .number(let cap)?=limit["limit"],cap>=1,cap<=1000000,cap.rounded()==cap else {throw IndianaFunnelError.invalidResponse}
|
|
28
|
+
}
|
|
29
|
+
guard try quantity(terms["quantity"],positive:true)>=1 else {throw IndianaFunnelError.invalidResponse}
|
|
30
|
+
_=try quantity(terms["purchaseUnits"],positive:true);try amount("purchaseAmountMinor");return
|
|
31
|
+
}
|
|
32
|
+
try amount("maxAmountMinorPerPeriod")
|
|
33
|
+
guard Set(terms.keys) == base.union(["maxAmountMinorPerPeriod","maxOutstandingMinor", "cashPrice"]),
|
|
34
|
+
let price = terms["cashPrice"]?.object, Set(price.keys) == Set(["currency", "pricing"]).union(price["billingCycle"] == nil ? [] : ["billingCycle"]), try text(price, "currency") == currency,
|
|
35
|
+
let pricing = price["pricing"]?.object else { throw IndianaFunnelError.invalidResponse }
|
|
36
|
+
if let value = price["billingCycle"] {
|
|
37
|
+
guard let cycle=value.object, Set(cycle.keys) == Set(["interval","count","anchor"]),
|
|
38
|
+
["month","year"].contains(try text(cycle,"interval")), ["membership","calendar"].contains(try text(cycle,"anchor")),
|
|
39
|
+
case .number(let count)?=cycle["count"], count >= 1, count <= 12, count.rounded() == count else { throw IndianaFunnelError.invalidResponse }
|
|
40
|
+
}
|
|
41
|
+
try amount("maxOutstandingMinor"); _ = try quantity(pricing["units"], positive: true)
|
|
42
|
+
let pricingKind = try text(pricing, "kind")
|
|
43
|
+
if pricingKind == "flat" {
|
|
44
|
+
guard Set(pricing.keys) == Set(["kind", "units", "unitAmountMinor"]) else { throw IndianaFunnelError.invalidResponse }
|
|
45
|
+
_ = try quantity(pricing["unitAmountMinor"]); return
|
|
46
|
+
}
|
|
47
|
+
guard ["graduated", "volume"].contains(pricingKind), Set(pricing.keys) == Set(["kind", "units", "tiers"]),
|
|
48
|
+
case .array(let tiers)? = pricing["tiers"], (1...12).contains(tiers.count) else { throw IndianaFunnelError.invalidResponse }
|
|
49
|
+
var previous: Decimal = 0
|
|
50
|
+
for (index, row) in tiers.enumerated() {
|
|
51
|
+
guard let tier = row.object, Set(tier.keys) == Set(["upTo", "unitAmountMinor"]) else { throw IndianaFunnelError.invalidResponse }
|
|
52
|
+
_ = try quantity(tier["unitAmountMinor"])
|
|
53
|
+
if index == tiers.count - 1 { guard tier["upTo"] == .null else { throw IndianaFunnelError.invalidResponse } }
|
|
54
|
+
else { let ceiling = try quantity(tier["upTo"], positive: true); guard ceiling > previous else { throw IndianaFunnelError.invalidResponse }; previous = ceiling }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
enum IndianaBundledWebViewResources {
|
|
4
|
+
static let origin = URL(string: "https://indiana-sdk.invalid/")!
|
|
5
|
+
static func rendererURL() throws -> String {
|
|
6
|
+
guard let file = IndianaResources.bundle.url(forResource: "renderer-data-url", withExtension: "txt", subdirectory: "WebView") else { throw IndianaClientError.invalidResponse }
|
|
7
|
+
return try String(contentsOf: file, encoding: .utf8)
|
|
8
|
+
}
|
|
9
|
+
static func html() throws -> String {
|
|
10
|
+
guard let file = IndianaResources.bundle.url(forResource: "index", withExtension: "html", subdirectory: "WebView") else { throw IndianaClientError.invalidResponse }
|
|
11
|
+
return try String(contentsOf: file, encoding: .utf8)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#if canImport(UIKit)
|
|
16
|
+
import UIKit
|
|
17
|
+
@preconcurrency import WebKit
|
|
18
|
+
|
|
19
|
+
/// Shared local renderer transport for Funnel and independent Paywall. No network code loading.
|
|
20
|
+
@MainActor final class IndianaBundledWebView: NSObject, @preconcurrency WKNavigationDelegate, @preconcurrency WKScriptMessageHandler {
|
|
21
|
+
let webView: WKWebView
|
|
22
|
+
private let dispatch: @MainActor (String, IndianaJSON) async throws -> IndianaJSON
|
|
23
|
+
private let onError: @MainActor (Error) -> Void
|
|
24
|
+
private var requests = Set<UUID>()
|
|
25
|
+
private var stopped = false
|
|
26
|
+
init(label: String, dispatch: @escaping @MainActor (String, IndianaJSON) async throws -> IndianaJSON, onError: @escaping @MainActor (Error) -> Void) {
|
|
27
|
+
self.dispatch = dispatch; self.onError = onError
|
|
28
|
+
let config = WKWebViewConfiguration()
|
|
29
|
+
config.websiteDataStore = .nonPersistent()
|
|
30
|
+
config.defaultWebpagePreferences.allowsContentJavaScript = true
|
|
31
|
+
webView = WKWebView(frame: .zero, configuration: config)
|
|
32
|
+
super.init()
|
|
33
|
+
config.userContentController.add(WeakBundledHandler(self), name: "indianaFunnel")
|
|
34
|
+
webView.navigationDelegate = self; webView.isOpaque = false; webView.backgroundColor = .clear
|
|
35
|
+
webView.scrollView.backgroundColor = .clear; webView.accessibilityLabel = label
|
|
36
|
+
}
|
|
37
|
+
func start() {
|
|
38
|
+
guard !stopped else { return }
|
|
39
|
+
do { webView.loadHTMLString(try IndianaBundledWebViewResources.html(), baseURL: IndianaBundledWebViewResources.origin) }
|
|
40
|
+
catch { onError(error) }
|
|
41
|
+
}
|
|
42
|
+
func send(_ value: IndianaJSON) {
|
|
43
|
+
guard !stopped, let data=try? JSONEncoder().encode(value), let text=String(data:data,encoding:.utf8) else { return }
|
|
44
|
+
webView.evaluateJavaScript("window.__indianaFunnelReceive(\(text))", completionHandler: nil)
|
|
45
|
+
}
|
|
46
|
+
func event(_ name: String) { send(.object(["event":.string(name)])) }
|
|
47
|
+
func destroy() {
|
|
48
|
+
guard !stopped else { return }; event("invalidated"); stopped = true; requests.removeAll()
|
|
49
|
+
webView.stopLoading(); webView.configuration.userContentController.removeScriptMessageHandler(forName:"indianaFunnel")
|
|
50
|
+
webView.isHidden = true; webView.loadHTMLString("", baseURL:nil)
|
|
51
|
+
}
|
|
52
|
+
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
|
53
|
+
guard !stopped, message.webView === webView, message.frameInfo.isMainFrame,
|
|
54
|
+
message.frameInfo.securityOrigin.protocol == "https", message.frameInfo.securityOrigin.host == "indiana-sdk.invalid",
|
|
55
|
+
[0,443].contains(message.frameInfo.securityOrigin.port),
|
|
56
|
+
let data=try? JSONSerialization.data(withJSONObject:message.body), data.count <= 262144,
|
|
57
|
+
let value=try? JSONDecoder().decode(IndianaJSON.self,from:data).object,
|
|
58
|
+
Set(value.keys) == Set(["id","method","payload"]), let id=value["id"]?.string.flatMap(UUID.init(uuidString:)),
|
|
59
|
+
let method=value["method"]?.string, requests.count < 8, requests.insert(id).inserted else { return }
|
|
60
|
+
Task {
|
|
61
|
+
defer { requests.remove(id) }
|
|
62
|
+
do {
|
|
63
|
+
let result=try await dispatch(method,value["payload"] ?? .object([:]))
|
|
64
|
+
guard !stopped else { return }; send(.object(["id":.string(id.uuidString.lowercased()),"result":result]))
|
|
65
|
+
} catch {
|
|
66
|
+
guard !stopped else { return }
|
|
67
|
+
var detail:[String:IndianaJSON] = ["code":.string("NATIVE_OPERATION_FAILED")]
|
|
68
|
+
if let e=error as? IndianaClientError, case .requestFailed(let status)=e { detail["status"] = .number(Double(status)) }
|
|
69
|
+
if let e=error as? IndianaFunnelError, case .commandPending=e { detail["code"] = .string("COMMAND_PENDING") }
|
|
70
|
+
send(.object(["id":.string(id.uuidString.lowercased()),"error":.object(detail)])); onError(error)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
|
75
|
+
guard !stopped, let url=action.request.url else { decisionHandler(.cancel); return }
|
|
76
|
+
if action.targetFrame?.isMainFrame == true {
|
|
77
|
+
decisionHandler(url == IndianaBundledWebViewResources.origin || url.absoluteString == "about:blank" ? .allow : .cancel)
|
|
78
|
+
} else { decisionHandler(indianaAllowsFunnelRendererNavigation(url,baseURL:IndianaBundledWebViewResources.origin) ? .allow : .cancel) }
|
|
79
|
+
}
|
|
80
|
+
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { if !stopped { onError(error) } }
|
|
81
|
+
}
|
|
82
|
+
@MainActor private final class WeakBundledHandler: NSObject, @preconcurrency WKScriptMessageHandler {
|
|
83
|
+
weak var owner: IndianaBundledWebView?
|
|
84
|
+
init(_ owner:IndianaBundledWebView) { self.owner=owner }
|
|
85
|
+
func userContentController(_ controller:WKUserContentController,didReceive message:WKScriptMessage) { owner?.userContentController(controller,didReceive:message) }
|
|
86
|
+
}
|
|
87
|
+
#endif
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// An independent, version-pinned Page, Popup or Banner; it creates no Funnel Run.
|
|
4
|
+
public actor IndianaContentSession {
|
|
5
|
+
private let configuration: IndianaConfiguration
|
|
6
|
+
private let send: @Sendable ([String: IndianaJSON]) async throws -> IndianaJSON
|
|
7
|
+
private var input: [String: IndianaJSON]
|
|
8
|
+
private let locale: String
|
|
9
|
+
private let inputs: [String: IndianaJSON]
|
|
10
|
+
public private(set) var current: IndianaJSON?
|
|
11
|
+
private var stopped = false
|
|
12
|
+
private var invalidation: (@Sendable () -> Void)?
|
|
13
|
+
init(configuration: IndianaConfiguration, input: [String: IndianaJSON], locale: String, inputs: [String: IndianaJSON], send: @escaping @Sendable ([String: IndianaJSON]) async throws -> IndianaJSON) throws {
|
|
14
|
+
guard !locale.isEmpty, locale.count <= 35, inputs.count <= 40 else { throw IndianaClientError.invalidConfiguration }
|
|
15
|
+
self.configuration = configuration; self.input = input; self.locale = locale; self.inputs = inputs; self.send = send
|
|
16
|
+
}
|
|
17
|
+
public func close() { guard !stopped else { return }; stopped = true; current = nil; invalidation?(); invalidation = nil }
|
|
18
|
+
public func refresh() async throws -> IndianaJSON {
|
|
19
|
+
guard !stopped else { throw IndianaClientError.identityChanged }
|
|
20
|
+
let result = try await send(input)
|
|
21
|
+
guard !stopped, let content = result.object?["content"]?.object, let id = content["id"]?.string,
|
|
22
|
+
content["kind"] == input["kind"], content["projectId"]?.string == configuration.scope.projectId.uuidString.lowercased() else { throw IndianaClientError.invalidResponse }
|
|
23
|
+
if let current, current.object?["id"]?.string != id { throw IndianaClientError.invalidResponse }
|
|
24
|
+
input["selector"] = .object(["type": .string("version"), "id": .string(id)])
|
|
25
|
+
current = .object(content); return .object(content)
|
|
26
|
+
}
|
|
27
|
+
func onInvalidation(_ callback: @escaping @Sendable () -> Void) { invalidation = callback; if stopped { callback() } }
|
|
28
|
+
func hostInit() -> IndianaJSON { .object(["event": .string("init"), "surface": .string("content"), "scope": .object(["projectId": .string(configuration.scope.projectId.uuidString.lowercased()), "environment": .string(configuration.scope.environment.rawValue)]), "apiOrigin": .string(configuration.apiOrigin)]) }
|
|
29
|
+
func hostContent() async throws -> IndianaJSON {
|
|
30
|
+
guard !stopped else { throw IndianaClientError.identityChanged }
|
|
31
|
+
let content: IndianaJSON
|
|
32
|
+
if let current { content = current } else { content = try await refresh() }
|
|
33
|
+
return .object(["content": content, "locale": .string(locale), "inputs": .object(inputs)])
|
|
34
|
+
}
|
|
35
|
+
func trustedAction(_ intent: IndianaJSON) throws -> IndianaJSON {
|
|
36
|
+
guard !stopped, let value = intent.object, Set(value.keys) == Set(["requestId", "contentVersionId", "actionId", "event", "value", "answers", "selection", "requestedClose"]),
|
|
37
|
+
let request = value["requestId"]?.string, UUID(uuidString: request) != nil,
|
|
38
|
+
let current = current?.object, value["contentVersionId"] == current["id"],
|
|
39
|
+
let actionID = value["actionId"]?.string, let document = current["document"]?.object,
|
|
40
|
+
document["contract"]?.object?["outputs"]?.object?[actionID] != nil,
|
|
41
|
+
case .array(let actions)? = document["screen"]?.object?["actions"],
|
|
42
|
+
let action = actions.compactMap(\.object).first(where: { $0["id"]?.string == actionID }),
|
|
43
|
+
action["type"]?.string == "event", action["event"] == value["event"], action["close"] == value["requestedClose"],
|
|
44
|
+
let properties = action["properties"]?.object, value["value"] == (properties["value"] ?? .object(properties)),
|
|
45
|
+
value["answers"]?.object != nil, value["selection"]?.object != nil else { throw IndianaFunnelError.invalidCommand }
|
|
46
|
+
return intent
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import SwiftUI
|
|
4
|
+
|
|
5
|
+
/// Independent content presented through the same isolated packaged renderer as Funnel.
|
|
6
|
+
@MainActor public final class IndianaContentViewController: UIViewController {
|
|
7
|
+
public let session: IndianaContentSession
|
|
8
|
+
public var onAction: (@MainActor (IndianaJSON) async throws -> Void)?
|
|
9
|
+
public var onDismiss: (@MainActor () -> Void)?
|
|
10
|
+
public var onError: (@MainActor (Error) -> Void)?
|
|
11
|
+
private var host: IndianaBundledWebView!
|
|
12
|
+
private var stopped = false
|
|
13
|
+
public init(session: IndianaContentSession) { self.session = session; super.init(nibName: nil, bundle: nil) }
|
|
14
|
+
required init?(coder: NSCoder) { fatalError("Use init(session:)") }
|
|
15
|
+
public override func viewDidLoad() {
|
|
16
|
+
super.viewDidLoad()
|
|
17
|
+
host = IndianaBundledWebView(label: "Indiana content", dispatch: { [weak self] method, payload in
|
|
18
|
+
guard let self else { throw IndianaClientError.identityChanged }; return try await self.dispatch(method, payload)
|
|
19
|
+
}, onError: { [weak self] error in self?.onError?(error) })
|
|
20
|
+
host.webView.frame = view.bounds; host.webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]; view.addSubview(host.webView)
|
|
21
|
+
Task { [weak self, session] in await session.onInvalidation { [weak self] in Task { @MainActor in self?.invalidate() } } }
|
|
22
|
+
host.start()
|
|
23
|
+
}
|
|
24
|
+
public func close() async { await session.close(); invalidate() }
|
|
25
|
+
private func invalidate() { guard !stopped else { return }; stopped = true; host?.destroy(); onDismiss?() }
|
|
26
|
+
private func dispatch(_ method: String, _ payload: IndianaJSON) async throws -> IndianaJSON {
|
|
27
|
+
switch method {
|
|
28
|
+
case "ready": host.send(await session.hostInit()); return .null
|
|
29
|
+
case "content": return try await session.hostContent()
|
|
30
|
+
case "contentAction":
|
|
31
|
+
let trusted = try await session.trustedAction(payload)
|
|
32
|
+
guard let onAction else { throw IndianaClientError.unsupportedCapability }
|
|
33
|
+
try await onAction(trusted); return .null
|
|
34
|
+
case "dismiss": await close(); return .null
|
|
35
|
+
case "error": onError?(IndianaClientError.invalidResponse); return .null
|
|
36
|
+
default: throw IndianaFunnelError.invalidCommand
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@MainActor public struct IndianaContentView: UIViewControllerRepresentable {
|
|
42
|
+
public let session: IndianaContentSession
|
|
43
|
+
public var onAction: (@MainActor (IndianaJSON) async throws -> Void)?
|
|
44
|
+
public var onDismiss: (@MainActor () -> Void)?
|
|
45
|
+
public var onError: (@MainActor (Error) -> Void)?
|
|
46
|
+
public init(session: IndianaContentSession, onAction: (@MainActor (IndianaJSON) async throws -> Void)? = nil, onDismiss: (@MainActor () -> Void)? = nil, onError: (@MainActor (Error) -> Void)? = nil) { self.session = session; self.onAction = onAction; self.onDismiss = onDismiss; self.onError = onError }
|
|
47
|
+
public func makeUIViewController(context: Context) -> IndianaContentViewController { let view = IndianaContentViewController(session: session); updateUIViewController(view, context: context); return view }
|
|
48
|
+
public func updateUIViewController(_ view: IndianaContentViewController, context: Context) { view.onAction = onAction; view.onDismiss = onDismiss; view.onError = onError }
|
|
49
|
+
public static func dismantleUIViewController(_ view: IndianaContentViewController, coordinator: ()) { Task { await view.close() } }
|
|
50
|
+
}
|
|
51
|
+
#endif
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Generated by scripts/sdk/generate-native-protocol.mjs. Do not edit.
|
|
2
|
+
import Foundation
|
|
3
|
+
public enum IndianaCustomerOperation: String, CaseIterable, Sendable {
|
|
4
|
+
case bindPushInstallation
|
|
5
|
+
case readPushInstallation
|
|
6
|
+
case updatePushAddress
|
|
7
|
+
case readMessagingPreference
|
|
8
|
+
case setMessagingPreference
|
|
9
|
+
case getFeatures
|
|
10
|
+
case evaluateFeatures
|
|
11
|
+
case trackEvents
|
|
12
|
+
case restoreNativePurchase
|
|
13
|
+
case setupPayment
|
|
14
|
+
case readPaymentSetup
|
|
15
|
+
case previewAttach
|
|
16
|
+
case attach
|
|
17
|
+
case readSubscriptionOperation
|
|
18
|
+
case previewUpdate
|
|
19
|
+
case update
|
|
20
|
+
case readSubscriptionUpdate
|
|
21
|
+
case readBillingControls
|
|
22
|
+
case prepareBillingAuthorization
|
|
23
|
+
case confirmBillingAuthorization
|
|
24
|
+
case setBillingCoupons
|
|
25
|
+
case revokeBillingAuthorization
|
|
26
|
+
case listBillingInvoices
|
|
27
|
+
case confirmBillingAdjustment
|
|
28
|
+
case checkEntitlement
|
|
29
|
+
case resolveContent
|
|
30
|
+
case resolvePaywall
|
|
31
|
+
case resolveOffering
|
|
32
|
+
case readHoldings
|
|
33
|
+
case readLedger
|
|
34
|
+
case readBillingInvoice
|
|
35
|
+
case readCreditStatement
|
|
36
|
+
case quoteBalance
|
|
37
|
+
case redeemReward
|
|
38
|
+
case createReferralCode
|
|
39
|
+
case claimRewardProduct
|
|
40
|
+
case redeemCoupon
|
|
41
|
+
case listOffers
|
|
42
|
+
case grantOffer
|
|
43
|
+
var path: String { switch self {
|
|
44
|
+
case .bindPushInstallation: return "push/installations/bind"
|
|
45
|
+
case .readPushInstallation: return "push/installations/read"
|
|
46
|
+
case .updatePushAddress: return "push/installations/address"
|
|
47
|
+
case .readMessagingPreference: return "messaging/preferences/read"
|
|
48
|
+
case .setMessagingPreference: return "messaging/preferences/set"
|
|
49
|
+
case .getFeatures: return "features"
|
|
50
|
+
case .evaluateFeatures: return "features/evaluate"
|
|
51
|
+
case .trackEvents: return "events"
|
|
52
|
+
case .restoreNativePurchase: return "subscriptions/native/restore"
|
|
53
|
+
case .setupPayment: return "billing/setup-payment"
|
|
54
|
+
case .readPaymentSetup: return "billing/payment-setup"
|
|
55
|
+
case .previewAttach: return "subscriptions/preview-attach"
|
|
56
|
+
case .attach: return "subscriptions/attach"
|
|
57
|
+
case .readSubscriptionOperation: return "subscriptions/operations/read"
|
|
58
|
+
case .previewUpdate: return "subscriptions/preview-update"
|
|
59
|
+
case .update: return "subscriptions/update"
|
|
60
|
+
case .readSubscriptionUpdate: return "subscriptions/updates/read"
|
|
61
|
+
case .readBillingControls: return "billing/controls/read"
|
|
62
|
+
case .prepareBillingAuthorization: return "billing/authorizations/prepare"
|
|
63
|
+
case .confirmBillingAuthorization: return "billing/authorizations/confirm"
|
|
64
|
+
case .setBillingCoupons: return "billing/coupons"
|
|
65
|
+
case .revokeBillingAuthorization: return "billing/authorizations/revoke"
|
|
66
|
+
case .listBillingInvoices: return "billing/invoices/read"
|
|
67
|
+
case .confirmBillingAdjustment: return "billing/invoices/confirm-adjustment"
|
|
68
|
+
case .checkEntitlement: return "entitlements/check"
|
|
69
|
+
case .resolveContent: return "content/resolve"
|
|
70
|
+
case .resolvePaywall: return "paywalls/resolve"
|
|
71
|
+
case .resolveOffering: return "offerings/resolve"
|
|
72
|
+
case .readHoldings: return "customer-holdings"
|
|
73
|
+
case .readLedger: return "customer-ledger"
|
|
74
|
+
case .readBillingInvoice: return "billing/invoices/detail"
|
|
75
|
+
case .readCreditStatement: return "credit-statements/read"
|
|
76
|
+
case .quoteBalance: return "balances/quote"
|
|
77
|
+
case .redeemReward: return "rewards/redeem"
|
|
78
|
+
case .createReferralCode: return "rewards/referral-code"
|
|
79
|
+
case .claimRewardProduct: return "rewards/product/claim"
|
|
80
|
+
case .redeemCoupon: return "offers/redeem"
|
|
81
|
+
case .listOffers: return "offers"
|
|
82
|
+
case .grantOffer: return "offers/grant"
|
|
83
|
+
} }
|
|
84
|
+
var method: String { switch self {
|
|
85
|
+
case .bindPushInstallation: return "POST"
|
|
86
|
+
case .readPushInstallation: return "POST"
|
|
87
|
+
case .updatePushAddress: return "POST"
|
|
88
|
+
case .readMessagingPreference: return "POST"
|
|
89
|
+
case .setMessagingPreference: return "POST"
|
|
90
|
+
case .getFeatures: return "GET"
|
|
91
|
+
case .evaluateFeatures: return "POST"
|
|
92
|
+
case .trackEvents: return "POST"
|
|
93
|
+
case .restoreNativePurchase: return "POST"
|
|
94
|
+
case .setupPayment: return "POST"
|
|
95
|
+
case .readPaymentSetup: return "POST"
|
|
96
|
+
case .previewAttach: return "POST"
|
|
97
|
+
case .attach: return "POST"
|
|
98
|
+
case .readSubscriptionOperation: return "POST"
|
|
99
|
+
case .previewUpdate: return "POST"
|
|
100
|
+
case .update: return "POST"
|
|
101
|
+
case .readSubscriptionUpdate: return "POST"
|
|
102
|
+
case .readBillingControls: return "POST"
|
|
103
|
+
case .prepareBillingAuthorization: return "POST"
|
|
104
|
+
case .confirmBillingAuthorization: return "POST"
|
|
105
|
+
case .setBillingCoupons: return "POST"
|
|
106
|
+
case .revokeBillingAuthorization: return "POST"
|
|
107
|
+
case .listBillingInvoices: return "POST"
|
|
108
|
+
case .confirmBillingAdjustment: return "POST"
|
|
109
|
+
case .checkEntitlement: return "POST"
|
|
110
|
+
case .resolveContent: return "POST"
|
|
111
|
+
case .resolvePaywall: return "POST"
|
|
112
|
+
case .resolveOffering: return "POST"
|
|
113
|
+
case .readHoldings: return "POST"
|
|
114
|
+
case .readLedger: return "POST"
|
|
115
|
+
case .readBillingInvoice: return "POST"
|
|
116
|
+
case .readCreditStatement: return "POST"
|
|
117
|
+
case .quoteBalance: return "POST"
|
|
118
|
+
case .redeemReward: return "POST"
|
|
119
|
+
case .createReferralCode: return "POST"
|
|
120
|
+
case .claimRewardProduct: return "POST"
|
|
121
|
+
case .redeemCoupon: return "POST"
|
|
122
|
+
case .listOffers: return "GET"
|
|
123
|
+
case .grantOffer: return "POST"
|
|
124
|
+
} }
|
|
125
|
+
}
|
|
126
|
+
extension IndianaClient {
|
|
127
|
+
public func bindPushInstallation(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.bindPushInstallation, input: input) }
|
|
128
|
+
public func readPushInstallation(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.readPushInstallation, input: input) }
|
|
129
|
+
public func updatePushAddress(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.updatePushAddress, input: input) }
|
|
130
|
+
public func readMessagingPreference(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.readMessagingPreference, input: input) }
|
|
131
|
+
public func setMessagingPreference(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.setMessagingPreference, input: input) }
|
|
132
|
+
public func getFeatures(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.getFeatures, input: input) }
|
|
133
|
+
public func evaluateFeatures(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.evaluateFeatures, input: input) }
|
|
134
|
+
public func trackEvents(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.trackEvents, input: input) }
|
|
135
|
+
public func restoreNativePurchase(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.restoreNativePurchase, input: input) }
|
|
136
|
+
public func setupPayment(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.setupPayment, input: input) }
|
|
137
|
+
public func readPaymentSetup(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.readPaymentSetup, input: input) }
|
|
138
|
+
public func previewAttach(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.previewAttach, input: input) }
|
|
139
|
+
public func attach(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.attach, input: input) }
|
|
140
|
+
public func readSubscriptionOperation(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.readSubscriptionOperation, input: input) }
|
|
141
|
+
public func previewUpdate(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.previewUpdate, input: input) }
|
|
142
|
+
public func update(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.update, input: input) }
|
|
143
|
+
public func readSubscriptionUpdate(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.readSubscriptionUpdate, input: input) }
|
|
144
|
+
public func readBillingControls(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.readBillingControls, input: input) }
|
|
145
|
+
public func prepareBillingAuthorization(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.prepareBillingAuthorization, input: input) }
|
|
146
|
+
public func confirmBillingAuthorization(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.confirmBillingAuthorization, input: input) }
|
|
147
|
+
public func setBillingCoupons(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.setBillingCoupons, input: input) }
|
|
148
|
+
public func revokeBillingAuthorization(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.revokeBillingAuthorization, input: input) }
|
|
149
|
+
public func listBillingInvoices(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.listBillingInvoices, input: input) }
|
|
150
|
+
public func confirmBillingAdjustment(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.confirmBillingAdjustment, input: input) }
|
|
151
|
+
public func checkEntitlement(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.checkEntitlement, input: input) }
|
|
152
|
+
public func resolveContent(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.resolveContent, input: input) }
|
|
153
|
+
public func resolvePaywall(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.resolvePaywall, input: input) }
|
|
154
|
+
public func resolveOffering(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.resolveOffering, input: input) }
|
|
155
|
+
public func readHoldings(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.readHoldings, input: input) }
|
|
156
|
+
public func readLedger(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.readLedger, input: input) }
|
|
157
|
+
public func readBillingInvoice(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.readBillingInvoice, input: input) }
|
|
158
|
+
public func readCreditStatement(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.readCreditStatement, input: input) }
|
|
159
|
+
public func quoteBalance(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.quoteBalance, input: input) }
|
|
160
|
+
public func redeemReward(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.redeemReward, input: input) }
|
|
161
|
+
public func createReferralCode(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.createReferralCode, input: input) }
|
|
162
|
+
public func claimRewardProduct(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.claimRewardProduct, input: input) }
|
|
163
|
+
public func redeemCoupon(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.redeemCoupon, input: input) }
|
|
164
|
+
public func listOffers(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.listOffers, input: input) }
|
|
165
|
+
public func grantOffer(_ input: [String: IndianaJSON]) async throws -> IndianaJSON { try await customerOperation(.grantOffer, input: input) }
|
|
166
|
+
}
|