@tinkerlandai/longplay-react-native 0.10.7 → 0.10.11
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/README.md +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +26549 -12669
- package/dist/index.d.ts +26549 -12669
- package/dist/index.js +2 -2
- package/native-sdk/android/assets/indiana/index.html +40 -40
- package/native-sdk/android/java/ai/tinkerland/indiana/BundledWebView.kt +6 -2
- package/native-sdk/android/java/ai/tinkerland/indiana/CustomerSession.kt +9 -3
- package/native-sdk/android/java/ai/tinkerland/indiana/FunnelConfigurationRelay.kt +47 -21
- package/native-sdk/android/java/ai/tinkerland/indiana/FunnelProcess.kt +120 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/FunnelSession.kt +57 -16
- package/native-sdk/android/java/ai/tinkerland/indiana/HTTPTransport.kt +48 -20
- package/native-sdk/android/java/ai/tinkerland/indiana/IndianaClient.kt +8 -2
- package/native-sdk/android/java/ai/tinkerland/indiana/PaywallSession.kt +82 -59
- package/native-sdk/android/java/ai/tinkerland/indiana/SDKVersion.kt +1 -1
- package/native-sdk/android/java/ai/tinkerland/indiana/WireSchema.kt +3 -1
- package/native-sdk/android/java/ai/tinkerland/longplay/Longplay.kt +3 -0
- package/native-sdk/android/resources/funnel-wire-schemas.json +1 -1
- package/native-sdk/ios/BundledWebView.swift +5 -1
- package/native-sdk/ios/CustomerSession.swift +7 -3
- package/native-sdk/ios/FunnelConfigurationRelay.swift +1 -0
- package/native-sdk/ios/FunnelProcess.swift +148 -0
- package/native-sdk/ios/FunnelSession.swift +37 -9
- package/native-sdk/ios/HTTPTransport.swift +2 -0
- package/native-sdk/ios/IndianaClient.swift +7 -1
- package/native-sdk/ios/NativeReceiptRelay.swift +3 -1
- package/native-sdk/ios/PaywallSession.swift +5 -4
- package/native-sdk/ios/SDKVersion.swift +1 -1
- package/native-sdk/ios/WebView/index.html +40 -40
- package/native-sdk/ios/funnel-wire-schemas.json +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package ai.tinkerland.indiana
|
|
2
2
|
|
|
3
3
|
import kotlinx.coroutines.sync.Mutex
|
|
4
|
-
import kotlinx.coroutines.sync.withLock
|
|
5
4
|
import org.json.JSONObject
|
|
6
5
|
import org.json.JSONArray
|
|
7
6
|
import java.net.URI
|
|
@@ -20,6 +19,7 @@ class IndianaPaywallSession internal constructor(
|
|
|
20
19
|
private val quoted = AtomicReference<JSONObject?>(null)
|
|
21
20
|
private val purchased = AtomicReference<JSONObject?>(null)
|
|
22
21
|
private val mutex = Mutex()
|
|
22
|
+
private val stateLock = Any()
|
|
23
23
|
private var pendingPreview: String? = null
|
|
24
24
|
@Volatile private var invalidation: (() -> Unit)? = null
|
|
25
25
|
private val selector: JSONObject
|
|
@@ -29,8 +29,17 @@ class IndianaPaywallSession internal constructor(
|
|
|
29
29
|
else JSONObject().put("type", "version").put("versionId", versionId.toString())
|
|
30
30
|
}
|
|
31
31
|
val current: JSONObject? get() = view.get()?.let { JSONObject(it.toString()) }
|
|
32
|
-
fun close() {
|
|
33
|
-
|
|
32
|
+
fun close() {
|
|
33
|
+
val callback = synchronized(stateLock) {
|
|
34
|
+
token.set(null); view.set(null); quoted.set(null); purchased.set(null); pendingPreview = null
|
|
35
|
+
val current = invalidation; invalidation = null; current
|
|
36
|
+
}
|
|
37
|
+
callback?.invoke()
|
|
38
|
+
}
|
|
39
|
+
internal fun onInvalidation(callback: () -> Unit) {
|
|
40
|
+
val closed = synchronized(stateLock) { if (token.get() == null) true else { invalidation = callback; false } }
|
|
41
|
+
if (closed) callback()
|
|
42
|
+
}
|
|
34
43
|
internal fun hostConfiguration() = configuration
|
|
35
44
|
internal fun hostInit() = JSONObject().put("event", "init").put("surface", "paywall")
|
|
36
45
|
.put("scope", JSONObject().put("projectId", configuration.scope.projectId.toString()).put("environment", configuration.scope.environment.value))
|
|
@@ -51,64 +60,78 @@ class IndianaPaywallSession internal constructor(
|
|
|
51
60
|
return result
|
|
52
61
|
}
|
|
53
62
|
suspend fun refresh(): JSONObject = hostCommand(JSONObject().put("operation", "resolve").put("input", JSONObject()))
|
|
54
|
-
internal suspend fun hostCommand(payload: JSONObject): JSONObject
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
result.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (policy.optString("type") != "checkout") throw IndianaClientException("invalid_command")
|
|
75
|
-
val selection = policy.getJSONObject("product")
|
|
76
|
-
val allowed = if (selection.getString("type") == "fixed") selection.getString("productVersionId") == product
|
|
77
|
-
else selection.getJSONArray("allowedProductVersionIds").let { ids -> (0 until ids.length()).any { ids.getString(it) == product } }
|
|
78
|
-
if (!allowed) throw IndianaClientException("invalid_command")
|
|
79
|
-
val fingerprint = canonical(payload)
|
|
80
|
-
if (pendingPreview != null && pendingPreview != fingerprint) {
|
|
81
|
-
val status = purchased.get()?.optString("status")
|
|
82
|
-
if (status == null || status == "pending") throw IndianaClientException("command_pending")
|
|
83
|
-
quoted.set(null); purchased.set(null)
|
|
63
|
+
internal suspend fun hostCommand(payload: JSONObject): JSONObject {
|
|
64
|
+
if (!mutex.tryLock()) throw IndianaClientException("command_pending")
|
|
65
|
+
try {
|
|
66
|
+
if (token.get() == null || payload.keys().asSequence().toSet() != setOf("operation", "input")) throw IndianaClientException("invalid_command")
|
|
67
|
+
val input = payload.getJSONObject("input")
|
|
68
|
+
return when (val action = payload.getString("operation")) {
|
|
69
|
+
"resolve" -> {
|
|
70
|
+
if (input.length() != 0) throw IndianaClientException("invalid_command")
|
|
71
|
+
val prior = view.get()
|
|
72
|
+
val selected = prior?.let { JSONObject().put("type", "version").put("versionId", it.getString("id")) } ?: selector
|
|
73
|
+
val result = send("resolvePaywall", "paywalls/resolve", JSONObject().put("selector", selected)
|
|
74
|
+
.put("commerce", JSONObject().put("provider", provider.value).put("currency", currency)).put("locale", locale)).getJSONObject("paywall")
|
|
75
|
+
val key = if (selector.getString("type") == "version") "id" else "paywallId"
|
|
76
|
+
if (result.getString(key) != selector.getString(if (key == "id") "versionId" else key) ||
|
|
77
|
+
prior != null && (result.getString("id") != prior.getString("id") || result.getJSONObject("offering").getString("id") != prior.getJSONObject("offering").getString("id") || result.getJSONObject("content").getJSONObject("artifact").getString("sha256") != prior.getJSONObject("content").getJSONObject("artifact").getString("sha256")) ||
|
|
78
|
+
result.getJSONObject("document").getString("offeringVersionId") != result.getJSONObject("offering").getString("id")) throw IndianaClientException("invalid_response")
|
|
79
|
+
synchronized(stateLock) {
|
|
80
|
+
if (token.get() == null) throw IndianaClientException("identity_changed")
|
|
81
|
+
view.set(result); JSONObject(result.toString())
|
|
82
|
+
}
|
|
84
83
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
84
|
+
"preview" -> {
|
|
85
|
+
if (!setOf("actionId", "productVersionId", "requestId", "offerId", "nativeOffer", "featureQuantities").containsAll(input.keys().asSequence().toSet())) throw IndianaClientException("invalid_command")
|
|
86
|
+
val product = input.getString("productVersionId")
|
|
87
|
+
val policy = view.get()?.getJSONObject("document")?.getJSONObject("buttons")?.optJSONObject(input.getString("actionId")) ?: throw IndianaClientException("invalid_command")
|
|
88
|
+
if (policy.optString("type") != "checkout") throw IndianaClientException("invalid_command")
|
|
89
|
+
val selection = policy.getJSONObject("product")
|
|
90
|
+
val allowed = if (selection.getString("type") == "fixed") selection.getString("productVersionId") == product
|
|
91
|
+
else selection.getJSONArray("allowedProductVersionIds").let { ids -> (0 until ids.length()).any { ids.getString(it) == product } }
|
|
92
|
+
if (!allowed) throw IndianaClientException("invalid_command")
|
|
93
|
+
val fingerprint = canonical(payload)
|
|
94
|
+
if (pendingPreview != null && pendingPreview != fingerprint) {
|
|
95
|
+
val status = purchased.get()?.optString("status")
|
|
96
|
+
if (status == null || status == "pending") throw IndianaClientException("command_pending")
|
|
97
|
+
}
|
|
98
|
+
if (pendingPreview == fingerprint) quoted.get()?.let { return JSONObject(it.toString()) }
|
|
99
|
+
val selectedPlan = JSONObject().put("productVersionId", product)
|
|
100
|
+
if (input.has("featureQuantities")) selectedPlan.put("featureQuantities", input.getJSONObject("featureQuantities"))
|
|
101
|
+
val body = JSONObject().put("requestId", input.getString("requestId"))
|
|
102
|
+
.put("plans", JSONArray().put(selectedPlan)).put("provider", provider.value).put("currency", currency)
|
|
103
|
+
for (key in listOf("offerId", "nativeOffer")) if (input.has(key)) body.put(key, input.get(key))
|
|
104
|
+
IndianaSchema.validate("previewAttachRequest", body)
|
|
105
|
+
synchronized(stateLock) {
|
|
106
|
+
if (token.get() == null) throw IndianaClientException("identity_changed")
|
|
107
|
+
if (pendingPreview != fingerprint) { quoted.set(null); purchased.set(null) }
|
|
108
|
+
pendingPreview = fingerprint
|
|
109
|
+
}
|
|
110
|
+
val result = send("previewAttach", "subscriptions/preview-attach", body).getJSONObject("quote")
|
|
111
|
+
if (result.getString("provider") != provider.value || result.getString("currency") != currency || result.getJSONArray("lines").length() != 1 || result.getJSONArray("lines").getJSONObject(0).getString("productVersionId") != product) throw IndianaClientException("invalid_response")
|
|
112
|
+
synchronized(stateLock) {
|
|
113
|
+
if (token.get() == null) throw IndianaClientException("identity_changed")
|
|
114
|
+
quoted.set(result); purchased.set(null); JSONObject(result.toString())
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
"confirm", "cancel", "read" -> {
|
|
118
|
+
if (input.length() != 0) throw IndianaClientException("invalid_command")
|
|
119
|
+
val id = quoted.get()?.getString("operationId") ?: throw IndianaClientException("invalid_command")
|
|
120
|
+
val body = JSONObject().put("operationId", id)
|
|
121
|
+
if (action != "read") body.put("intent", action)
|
|
122
|
+
val result = send(if (action == "read") "readSubscriptionOperation" else "attach",
|
|
123
|
+
if (action == "read") "subscriptions/operations/read" else "subscriptions/attach", body).getJSONObject("operation")
|
|
124
|
+
if (result.getString("operationId") != id || result.getString("currency") != currency) throw IndianaClientException("invalid_response")
|
|
125
|
+
synchronized(stateLock) {
|
|
126
|
+
if (token.get() == null) throw IndianaClientException("identity_changed")
|
|
127
|
+
purchased.get()?.takeIf { it.getString("status") == "complete" || it.getString("status") != "pending" && result.getString("status") == "pending" }?.let { return JSONObject(it.toString()) }
|
|
128
|
+
purchased.set(result)
|
|
129
|
+
JSONObject(result.toString())
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else -> throw IndianaClientException("invalid_command")
|
|
109
133
|
}
|
|
110
|
-
|
|
111
|
-
}
|
|
134
|
+
} finally { mutex.unlock() }
|
|
112
135
|
}
|
|
113
136
|
internal fun trustedPurchase(value: JSONObject): JSONObject {
|
|
114
137
|
val saved = purchased.get() ?: throw IndianaClientException("invalid_command")
|
|
@@ -3,8 +3,10 @@ import org.json.JSONObject
|
|
|
3
3
|
import org.json.JSONArray
|
|
4
4
|
import java.net.URI
|
|
5
5
|
import java.util.UUID
|
|
6
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
6
7
|
/** Generated contract interpreter for the native wire boundary; no business policy is evaluated here. */
|
|
7
8
|
internal object IndianaSchema {
|
|
9
|
+
private val patterns = ConcurrentHashMap<String, Regex>()
|
|
8
10
|
private val schemas: JSONObject by lazy { IndianaSchema::class.java.getResourceAsStream("/funnel-wire-schemas.json")?.bufferedReader(Charsets.UTF_8)?.use { JSONObject(it.readText()) } ?: throw IndianaClientException("invalid_response") }
|
|
9
11
|
fun validate(name: String, value: JSONObject) { val root = schemas.getJSONObject(name);if (!valid(value, root, root, 0)) throw IndianaClientException("invalid_response") }
|
|
10
12
|
fun customerResponse(name: String, value: JSONObject): JSONObject { val root = schemas.getJSONObject(name); return adapt(value, root, root, listOf(name), 0) as JSONObject }
|
|
@@ -47,7 +49,7 @@ internal object IndianaSchema {
|
|
|
47
49
|
}
|
|
48
50
|
if (value is String) {
|
|
49
51
|
if (schema.has("minLength") && value.length < schema.getInt("minLength") || schema.has("maxLength") && value.length > schema.getInt("maxLength")) return false
|
|
50
|
-
if (schema.has("pattern") && !Regex(schema.getString("pattern")).containsMatchIn(value)) return false
|
|
52
|
+
if (schema.has("pattern") && !patterns.getOrPut(schema.getString("pattern")) { Regex(schema.getString("pattern")) }.containsMatchIn(value)) return false
|
|
51
53
|
try { when (schema.optString("format")) { "uuid" -> UUID.fromString(value);"date-time" -> utcMillis(value);"uri" -> if (URI(value).scheme == null) return false } } catch (_: Exception) { return false }
|
|
52
54
|
}
|
|
53
55
|
if (value is Number) {
|
|
@@ -30,3 +30,6 @@ typealias LongplayStoreException = ai.tinkerland.indiana.IndianaStoreException
|
|
|
30
30
|
typealias LongplayStoreSubscriptions = ai.tinkerland.indiana.IndianaStoreSubscriptions
|
|
31
31
|
typealias LongplaySubscriptionPlan = ai.tinkerland.indiana.IndianaSubscriptionPlan
|
|
32
32
|
typealias LongplaySubscriptionProvider = ai.tinkerland.indiana.IndianaSubscriptionProvider
|
|
33
|
+
|
|
34
|
+
typealias LongplayFunnelProcess = ai.tinkerland.indiana.IndianaFunnelProcess
|
|
35
|
+
typealias LongplayFunnelProcessBridge = ai.tinkerland.indiana.IndianaFunnelProcessBridge
|