@tinkerlandai/longplay-react-native 0.0.0-stage → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/IndianaReactNative.podspec +19 -0
  2. package/README.md +119 -2
  3. package/THIRD_PARTY_NOTICES.md +101 -0
  4. package/android/build.gradle +24 -0
  5. package/android/src/main/AndroidManifest.xml +3 -0
  6. package/android/src/main/java/ai/tinkerland/indiana/reactnative/IndianaBridgeModule.kt +237 -0
  7. package/android/src/main/java/ai/tinkerland/indiana/reactnative/IndianaBridgePackage.kt +11 -0
  8. package/dist/index.cjs +17 -0
  9. package/dist/index.d.cts +105286 -0
  10. package/dist/index.d.ts +105286 -0
  11. package/dist/index.js +17 -0
  12. package/ios/IndianaBridge.m +11 -0
  13. package/ios/IndianaBridge.swift +275 -0
  14. package/native-sdk/android/assets/indiana/index.html +268 -0
  15. package/native-sdk/android/java/ai/tinkerland/indiana/BillingAuthorizationTerms.kt +55 -0
  16. package/native-sdk/android/java/ai/tinkerland/indiana/BundledWebView.kt +90 -0
  17. package/native-sdk/android/java/ai/tinkerland/indiana/ContentSession.kt +45 -0
  18. package/native-sdk/android/java/ai/tinkerland/indiana/ContentView.kt +36 -0
  19. package/native-sdk/android/java/ai/tinkerland/indiana/CustomerOperations.generated.kt +84 -0
  20. package/native-sdk/android/java/ai/tinkerland/indiana/CustomerResponseSemantics.kt +197 -0
  21. package/native-sdk/android/java/ai/tinkerland/indiana/CustomerSemantics.kt +120 -0
  22. package/native-sdk/android/java/ai/tinkerland/indiana/CustomerSession.kt +106 -0
  23. package/native-sdk/android/java/ai/tinkerland/indiana/FeatureClient.kt +65 -0
  24. package/native-sdk/android/java/ai/tinkerland/indiana/FunnelCommandStorage.kt +163 -0
  25. package/native-sdk/android/java/ai/tinkerland/indiana/FunnelConfigurationRelay.kt +50 -0
  26. package/native-sdk/android/java/ai/tinkerland/indiana/FunnelSession.kt +208 -0
  27. package/native-sdk/android/java/ai/tinkerland/indiana/FunnelView.kt +71 -0
  28. package/native-sdk/android/java/ai/tinkerland/indiana/GeneratedProtocol.kt +16 -0
  29. package/native-sdk/android/java/ai/tinkerland/indiana/GoogleStorefront.kt +165 -0
  30. package/native-sdk/android/java/ai/tinkerland/indiana/HTTPTransport.kt +43 -0
  31. package/native-sdk/android/java/ai/tinkerland/indiana/IndianaClient.kt +526 -0
  32. package/native-sdk/android/java/ai/tinkerland/indiana/MerchantCommerceSemantics.kt +38 -0
  33. package/native-sdk/android/java/ai/tinkerland/indiana/NativePendingKeystore.kt +81 -0
  34. package/native-sdk/android/java/ai/tinkerland/indiana/NativeReceiptRelay.kt +28 -0
  35. package/native-sdk/android/java/ai/tinkerland/indiana/NativeStoreOffer.kt +24 -0
  36. package/native-sdk/android/java/ai/tinkerland/indiana/PaywallContent.kt +75 -0
  37. package/native-sdk/android/java/ai/tinkerland/indiana/PaywallSession.kt +144 -0
  38. package/native-sdk/android/java/ai/tinkerland/indiana/PaywallView.kt +74 -0
  39. package/native-sdk/android/java/ai/tinkerland/indiana/Protocol.kt +28 -0
  40. package/native-sdk/android/java/ai/tinkerland/indiana/PushInstallation.kt +170 -0
  41. package/native-sdk/android/java/ai/tinkerland/indiana/RendererNavigation.kt +13 -0
  42. package/native-sdk/android/java/ai/tinkerland/indiana/SDKVersion.kt +6 -0
  43. package/native-sdk/android/java/ai/tinkerland/indiana/StoreSubscriptionManagement.kt +19 -0
  44. package/native-sdk/android/java/ai/tinkerland/indiana/WireSchema.kt +76 -0
  45. package/native-sdk/android/java/ai/tinkerland/longplay/CustomerOperations.generated.kt +82 -0
  46. package/native-sdk/android/java/ai/tinkerland/longplay/Longplay.kt +32 -0
  47. package/native-sdk/android/resources/funnel-wire-schemas.json +1 -0
  48. package/native-sdk/android/resources/indiana-third-party-notices.txt +101 -0
  49. package/native-sdk/android/resources/renderer-data-url.txt +1 -0
  50. package/native-sdk/ios/AnalyticsReceipt.swift +10 -0
  51. package/native-sdk/ios/AppleStorefront.swift +218 -0
  52. package/native-sdk/ios/BillingAuthorizationTerms.swift +57 -0
  53. package/native-sdk/ios/BundledWebView.swift +87 -0
  54. package/native-sdk/ios/ContentSession.swift +48 -0
  55. package/native-sdk/ios/ContentView.swift +51 -0
  56. package/native-sdk/ios/CustomerOperations.generated.swift +166 -0
  57. package/native-sdk/ios/CustomerResponseSemantics.swift +236 -0
  58. package/native-sdk/ios/CustomerSemantics.swift +145 -0
  59. package/native-sdk/ios/CustomerSession.swift +129 -0
  60. package/native-sdk/ios/FeatureClient.swift +86 -0
  61. package/native-sdk/ios/FunnelCommandStorage.swift +163 -0
  62. package/native-sdk/ios/FunnelConfigurationRelay.swift +48 -0
  63. package/native-sdk/ios/FunnelSession.swift +232 -0
  64. package/native-sdk/ios/FunnelView.swift +125 -0
  65. package/native-sdk/ios/GeneratedProtocol.swift +16 -0
  66. package/native-sdk/ios/HTTPTransport.swift +74 -0
  67. package/native-sdk/ios/IndianaClient.swift +601 -0
  68. package/native-sdk/ios/MerchantCommerceSemantics.swift +51 -0
  69. package/native-sdk/ios/NativePendingKeychain.swift +52 -0
  70. package/native-sdk/ios/NativeReceiptRelay.swift +28 -0
  71. package/native-sdk/ios/NativeStoreOffer.swift +34 -0
  72. package/native-sdk/ios/PaywallContent.swift +75 -0
  73. package/native-sdk/ios/PaywallSession.swift +124 -0
  74. package/native-sdk/ios/PaywallView.swift +87 -0
  75. package/native-sdk/ios/Protocol.swift +49 -0
  76. package/native-sdk/ios/PushInstallation.swift +154 -0
  77. package/native-sdk/ios/RendererNavigation.swift +8 -0
  78. package/native-sdk/ios/Resources.swift +15 -0
  79. package/native-sdk/ios/SDKVersion.swift +4 -0
  80. package/native-sdk/ios/StoreSubscriptionManagement.swift +11 -0
  81. package/native-sdk/ios/WebView/THIRD_PARTY_NOTICES.txt +101 -0
  82. package/native-sdk/ios/WebView/index.html +268 -0
  83. package/native-sdk/ios/WebView/renderer-data-url.txt +1 -0
  84. package/native-sdk/ios/WireSchema.swift +107 -0
  85. package/native-sdk/ios/funnel-wire-schemas.json +1 -0
  86. package/package.json +62 -4
  87. package/react-native.config.cjs +12 -0
@@ -0,0 +1,170 @@
1
+ package ai.tinkerland.indiana
2
+
3
+ import android.content.Context
4
+ import android.security.keystore.KeyGenParameterSpec
5
+ import android.security.keystore.KeyProperties
6
+ import android.util.AtomicFile
7
+ import android.util.Base64
8
+ import org.json.JSONArray
9
+ import org.json.JSONObject
10
+ import java.io.File
11
+ import java.security.KeyPairGenerator
12
+ import java.security.KeyStore
13
+ import java.security.MessageDigest
14
+ import java.security.Signature
15
+ import java.security.spec.ECGenParameterSpec
16
+ import java.text.SimpleDateFormat
17
+ import java.util.Date
18
+ import java.util.Locale
19
+ import java.util.TimeZone
20
+ import java.util.UUID
21
+
22
+ enum class IndianaPushPermission(val value: String) { UNKNOWN("unknown"), DENIED("denied"), PROVISIONAL("provisional"), GRANTED("granted") }
23
+ enum class IndianaPushAddressKind(val value: String) { APNS_TOKEN("apns_token"), FCM_TOKEN("fcm_token") }
24
+ data class IndianaPushObservation(val connectionId: UUID?, val kind: IndianaPushAddressKind?, val token: String?, val permission: IndianaPushPermission) {
25
+ init {
26
+ require((connectionId == null && kind == null && token == null) || (connectionId != null && kind != null && token != null && token.length in 16..4096 && token.all { it.code in 33..126 }))
27
+ }
28
+ }
29
+ class IndianaPushObservationTicket internal constructor(internal val client: UUID, internal val identity: UUID, internal val observation: UUID)
30
+ internal const val PUSH_MAX_SEQUENCE = 9_007_199_254_740_991L
31
+ internal fun pushSigningBytes(fields: JSONArray): ByteArray {
32
+ fun quoted(value: String) = buildString {
33
+ append('"')
34
+ for (character in value) when (character) {
35
+ '"' -> append("\\\"")
36
+ '\\' -> append("\\\\")
37
+ else -> { require(character.code in 32..126); append(character) }
38
+ }
39
+ append('"')
40
+ }
41
+ return (0 until fields.length()).joinToString(",", "[", "]") { index ->
42
+ when (val value = fields.get(index)) {
43
+ JSONObject.NULL -> "null"
44
+ is String -> quoted(value)
45
+ is Long -> value.toString()
46
+ is Int -> value.toString()
47
+ else -> throw IndianaClientException("invalid_configuration")
48
+ }
49
+ }.toByteArray(Charsets.UTF_8)
50
+ }
51
+ internal data class IndianaPushJournal(val installationId: UUID, val publicKey: String, val identitySeq: Long = 0, val addressSeq: Long = 0, val detachSeq: Long? = null) {
52
+ fun validate() {
53
+ if (identitySeq !in 0..PUSH_MAX_SEQUENCE || addressSeq !in 0..PUSH_MAX_SEQUENCE || (detachSeq != null && detachSeq !in 1..identitySeq) || !publicKey.matches(Regex("[A-Za-z0-9_-]{80,200}"))) throw IndianaClientException("protected_storage_unavailable")
54
+ }
55
+ }
56
+ internal interface IndianaPushStorage {
57
+ /** Commits before returning; no Customer credential or provider token is stored. */
58
+ fun transaction(update: (IndianaPushJournal) -> IndianaPushJournal): IndianaPushJournal
59
+ fun sign(payload: ByteArray): String
60
+ }
61
+ internal fun pushP1363(der: ByteArray): ByteArray {
62
+ if (der.size !in 8..72 || der[0].toInt() != 0x30 || (der[1].toInt() and 255) != der.size - 2) throw IndianaClientException("protected_storage_unavailable")
63
+ var offset = 2
64
+ val result = ByteArray(64)
65
+ for (part in 0..1) {
66
+ if (offset + 2 > der.size || der[offset++].toInt() != 2) throw IndianaClientException("protected_storage_unavailable")
67
+ val size = der[offset++].toInt() and 255
68
+ if (size !in 1..33 || offset + size > der.size || der[offset].toInt() < 0) throw IndianaClientException("protected_storage_unavailable")
69
+ val leading = if (size > 1 && der[offset].toInt() == 0) 1 else 0
70
+ if (size - leading > 32) throw IndianaClientException("protected_storage_unavailable")
71
+ der.copyInto(result, part * 32 + 32 - size + leading, offset + leading, offset + size); offset += size
72
+ }
73
+ if (offset != der.size) throw IndianaClientException("protected_storage_unavailable")
74
+ return result
75
+ }
76
+ /** Key stays in Android Keystore; the atomic journal stays in App-private no-backup storage. */
77
+ internal class IndianaPushKeystore(context: Context, scope: IndianaScope): IndianaPushStorage {
78
+ private val namespace = "${context.packageName}:${scope.projectId}:${scope.environment.value}"
79
+ private val hash = MessageDigest.getInstance("SHA-256").digest(namespace.toByteArray()).joinToString("") { "%02x".format(it) }
80
+ private val alias = "indiana.push.v1.$hash"
81
+ private val file = AtomicFile(File(context.noBackupFilesDir, "indiana-push-$hash.json"))
82
+ companion object { private val lock = Any() }
83
+ private fun store(): KeyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
84
+ private fun base64(bytes: ByteArray) = Base64.encodeToString(bytes, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)
85
+ override fun transaction(update: (IndianaPushJournal) -> IndianaPushJournal): IndianaPushJournal = synchronized(lock) {
86
+ try {
87
+ val keystore = store()
88
+ val current = if (!file.baseFile.exists()) {
89
+ if (keystore.containsAlias(alias)) throw IndianaClientException("protected_storage_unavailable")
90
+ val spec = KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY).setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")).setDigests(KeyProperties.DIGEST_SHA256).build()
91
+ val key = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore").apply { initialize(spec) }.generateKeyPair()
92
+ IndianaPushJournal(UUID.randomUUID(), base64(key.public.encoded))
93
+ } else {
94
+ val bytes = file.openRead().use { input ->
95
+ val output = java.io.ByteArrayOutputStream(); val buffer = ByteArray(1024)
96
+ while (true) { val count = input.read(buffer); if (count < 0) break; if (output.size() + count > 4096) throw IndianaClientException("protected_storage_unavailable"); output.write(buffer, 0, count) }
97
+ output.toByteArray()
98
+ }
99
+ if (bytes.size > 4096) throw IndianaClientException("protected_storage_unavailable")
100
+ val json = JSONObject(bytes.toString(Charsets.UTF_8))
101
+ if (json.keys().asSequence().toSet() != setOf("installationId", "publicKey", "identitySeq", "addressSeq", "detachSeq")) throw IndianaClientException("protected_storage_unavailable")
102
+ IndianaPushJournal(UUID.fromString(json.getString("installationId")), json.getString("publicKey"), json.getLong("identitySeq"), json.getLong("addressSeq"), if (json.isNull("detachSeq")) null else json.getLong("detachSeq"))
103
+ }
104
+ current.validate()
105
+ if (base64(keystore.getCertificate(alias)?.publicKey?.encoded ?: throw IndianaClientException("protected_storage_unavailable")) != current.publicKey) throw IndianaClientException("protected_storage_unavailable")
106
+ val next = update(current); next.validate()
107
+ if (next.installationId != current.installationId || next.publicKey != current.publicKey || next.identitySeq < current.identitySeq || next.addressSeq < current.addressSeq) throw IndianaClientException("protected_storage_unavailable")
108
+ if (!file.baseFile.exists() || next != current) {
109
+ val data = JSONObject().put("installationId", next.installationId.toString()).put("publicKey", next.publicKey).put("identitySeq", next.identitySeq).put("addressSeq", next.addressSeq).put("detachSeq", next.detachSeq ?: JSONObject.NULL).toString().toByteArray()
110
+ val stream = file.startWrite()
111
+ try { stream.write(data); file.finishWrite(stream) } catch (error: Exception) { file.failWrite(stream); throw error }
112
+ }
113
+ next
114
+ } catch (_: Exception) { throw IndianaClientException("protected_storage_unavailable") }
115
+ }
116
+ override fun sign(payload: ByteArray): String = synchronized(lock) {
117
+ try {
118
+ val key = store().getKey(alias, null) as? java.security.PrivateKey ?: throw IndianaClientException("protected_storage_unavailable")
119
+ val signature = Signature.getInstance("SHA256withECDSA").apply { initSign(key); update(payload) }.sign()
120
+ base64(pushP1363(signature))
121
+ } catch (_: Exception) { throw IndianaClientException("protected_storage_unavailable") }
122
+ }
123
+ }
124
+ internal class IndianaPushIdentity(val storage: IndianaPushStorage) {
125
+ init { state() }
126
+ fun state() = storage.transaction { it }
127
+ fun reserve(detach: Boolean = false): Long = storage.transaction { current ->
128
+ if (current.identitySeq >= PUSH_MAX_SEQUENCE) throw IndianaClientException("protected_storage_unavailable")
129
+ val next = current.identitySeq + 1
130
+ current.copy(identitySeq = next, detachSeq = if (detach) next else current.detachSeq)
131
+ }.identitySeq
132
+ fun reserveAddress(): Long = storage.transaction { current ->
133
+ if (current.addressSeq >= PUSH_MAX_SEQUENCE) throw IndianaClientException("protected_storage_unavailable")
134
+ current.copy(addressSeq = current.addressSeq + 1)
135
+ }.addressSeq
136
+ fun clearDetach(sequence: Long) { storage.transaction { if (it.detachSeq == sequence) it.copy(detachSeq = null) else it } }
137
+ fun recover(remote: JSONObject, expected: Long): Long = storage.transaction { current ->
138
+ if (current.identitySeq != expected) throw IndianaClientException("identity_changed")
139
+ val floor = maxOf(current.identitySeq, remote.getLong("identitySeq"))
140
+ if (floor >= PUSH_MAX_SEQUENCE) throw IndianaClientException("protected_storage_unavailable")
141
+ current.copy(identitySeq = floor + 1, addressSeq = maxOf(current.addressSeq, remote.getLong("addressSeq")))
142
+ }.identitySeq
143
+ fun command(operation: String, scope: IndianaScope, sequence: Long? = null, credential: String? = null, observation: IndianaPushObservation? = null, challenge: String? = null): JSONObject {
144
+ val state = state(); val issued = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply { timeZone = TimeZone.getTimeZone("UTC") }.format(Date())
145
+ val fields = JSONArray().put("indiana_push_v1").put(operation).put(scope.projectId.toString()).put(scope.environment.value).put(state.installationId.toString()).put(state.publicKey).put(issued)
146
+ val input = JSONObject().put("installationId", state.installationId.toString()).put("publicKey", state.publicKey).put("issuedAt", issued)
147
+ fun digest(): String = MessageDigest.getInstance("SHA-256").digest((credential ?: throw IndianaClientException("unauthenticated")).toByteArray()).joinToString("") { "%02x".format(it) }
148
+ when (operation) {
149
+ "bind", "detach" -> {
150
+ require(sequence != null); fields.put(sequence).put("android"); input.put("sequence", sequence).put("platform", "android")
151
+ if (operation == "bind") { val digest = digest(); fields.put(digest); input.put("credentialDigest", digest) }
152
+ }
153
+ "read" -> { val digest = digest(); fields.put(digest); input.put("credentialDigest", digest) }
154
+ "address", "recovery_request", "recovery_confirm" -> {
155
+ require(sequence != null && observation != null); val digest = digest()
156
+ val connection = observation.connectionId?.toString() ?: JSONObject.NULL; val kind = observation.kind?.value ?: JSONObject.NULL; val token = observation.token ?: JSONObject.NULL
157
+ fields.put(sequence).put(digest).put(connection).put(kind).put(token).put(observation.permission.value)
158
+ input.put("sequence", sequence).put("credentialDigest", digest).put("connectionId", connection).put("kind", kind).put("token", token).put("permission", observation.permission.value)
159
+ if (operation == "recovery_confirm") {
160
+ if (challenge == null || !challenge.matches(Regex("[A-Za-z0-9_-]{100,3500}"))) throw IndianaClientException("invalid_configuration")
161
+ fields.put(challenge); input.put("challenge", challenge)
162
+ }
163
+ }
164
+ else -> throw IndianaClientException("invalid_configuration")
165
+ }
166
+ input.put("signature", storage.sign(pushSigningBytes(fields)))
167
+ if (operation == "detach") input.put("projectId", scope.projectId.toString()).put("environment", scope.environment.value)
168
+ return input
169
+ }
170
+ }
@@ -0,0 +1,13 @@
1
+ package ai.tinkerland.indiana
2
+
3
+ import java.net.URI
4
+
5
+ private object BundledRenderer {
6
+ val url: String by lazy {
7
+ checkNotNull(BundledRenderer::class.java.getResourceAsStream("/renderer-data-url.txt"))
8
+ .bufferedReader(Charsets.UTF_8).use { it.readText() }
9
+ }
10
+ }
11
+ /** Subframes can execute only the renderer bytes shipped with this SDK. */
12
+ internal fun allowsFunnelRendererNavigation(offered: URI, base: URI): Boolean =
13
+ base.toString() == "https://indiana-sdk.invalid/" && offered.toString().substringBefore('#') == BundledRenderer.url
@@ -0,0 +1,6 @@
1
+ // Generated by scripts/sdk/generate-native-protocol.mjs. Do not edit.
2
+ package ai.tinkerland.indiana
3
+
4
+ object IndianaSDK {
5
+ const val version = "0.10.4"
6
+ }
@@ -0,0 +1,19 @@
1
+ package ai.tinkerland.indiana
2
+
3
+ import android.app.Activity
4
+ import android.content.Intent
5
+ import android.net.Uri
6
+
7
+ /** Opens Play's renewal management. Returning to the App never proves an entitlement change. */
8
+ object IndianaStoreSubscriptions {
9
+ fun manage(activity: Activity, productId: String) {
10
+ val url = managementURL(activity.packageName, productId)
11
+ activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
12
+ }
13
+
14
+ internal fun managementURL(packageName: String, productId: String): String {
15
+ require(packageName.matches(Regex("[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)+"))) { "invalid_app_package" }
16
+ require(productId.length in 1..255 && productId.matches(Regex("[a-z0-9][a-z0-9._]*"))) { "invalid_store_product" }
17
+ return "https://play.google.com/store/account/subscriptions?sku=$productId&package=$packageName"
18
+ }
19
+ }
@@ -0,0 +1,76 @@
1
+ package ai.tinkerland.indiana
2
+ import org.json.JSONObject
3
+ import org.json.JSONArray
4
+ import java.net.URI
5
+ import java.util.UUID
6
+ /** Generated contract interpreter for the native wire boundary; no business policy is evaluated here. */
7
+ internal object IndianaSchema {
8
+ 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
+ fun validate(name: String, value: JSONObject) { val root = schemas.getJSONObject(name);if (!valid(value, root, root, 0)) throw IndianaClientException("invalid_response") }
10
+ fun customerResponse(name: String, value: JSONObject): JSONObject { val root = schemas.getJSONObject(name); return adapt(value, root, root, listOf(name), 0) as JSONObject }
11
+ private fun adapt(original: Any?, schema: JSONObject, root: JSONObject, path: List<String>, depth: Int): Any? {
12
+ if (depth > 100) throw IndianaClientException("invalid_response")
13
+ if (schema.has("\$ref")) { var target: Any = root; for (part in schema.getString("\$ref").substring(2).split('/')) target = (target as JSONObject).get(part.replace("~1", "/").replace("~0", "~")); return adapt(original, target as JSONObject, root, path, depth + 1) }
14
+ (schema.optJSONArray("anyOf") ?: schema.optJSONArray("oneOf"))?.let { branches -> val branch = (0 until branches.length()).map { branches.getJSONObject(it) }.firstOrNull { valid(original, it, root, 0) } ?: throw IndianaClientException("invalid_response"); return adapt(original, branch, root, path, depth + 1) }
15
+ var value = IndianaCustomerResponseSemantics.rules(original, schema)
16
+ schema.optJSONArray("allOf")?.let { branches -> for (n in 0 until branches.length()) value = adapt(value, branches.getJSONObject(n), root, path, depth + 1) }
17
+ if (value is JSONObject) {
18
+ val objectValue = value as JSONObject; val properties = schema.optJSONObject("properties") ?: JSONObject()
19
+ for (key in properties.keys()) if (objectValue.has(key)) objectValue.put(key, adapt(objectValue.get(key), properties.getJSONObject(key), root, path + key, depth + 1))
20
+ schema.optJSONObject("additionalProperties")?.let { additional -> for (key in objectValue.keys()) if (!properties.has(key)) objectValue.put(key, adapt(objectValue.get(key), additional, root, path + "{}", depth + 1)) }
21
+ IndianaCustomerResponseSemantics.objectValue(objectValue, properties.keys().asSequence().toSet(), path); return objectValue
22
+ }
23
+ if (value is JSONArray) { val array = value as JSONArray; schema.optJSONObject("items")?.let { child -> for (n in 0 until array.length()) array.put(n, adapt(array.opt(n), child, root, path + "[]", depth + 1)) }; return array }
24
+ if (value is String && schema.optString("pattern") in listOf("^(0|[1-9]\\d{0,11})(\\.\\d{1,6})?$", "^-?(0|[1-9]\\d{0,11})(\\.\\d{1,6})?$")) return (value as String).toBigDecimal().stripTrailingZeros().toPlainString()
25
+ return value
26
+ }
27
+ internal fun equal(a: Any?, b: Any?): Boolean = when {
28
+ a is JSONObject && b is JSONObject -> a.keys().asSequence().toSet() == b.keys().asSequence().toSet() && a.keys().asSequence().all { equal(a.opt(it), b.opt(it)) }
29
+ a is JSONArray && b is JSONArray -> a.length() == b.length() && (0 until a.length()).all { equal(a.opt(it), b.opt(it)) }
30
+ a is Number && b is Number -> a.toDouble() == b.toDouble()
31
+ else -> a == b
32
+ }
33
+ private fun valid(value: Any?, schema: JSONObject, root: JSONObject, depth: Int): Boolean {
34
+ if (depth > 100) return false
35
+ val value = try { IndianaCustomerResponseSemantics.rules(value, schema) } catch (_: Exception) { return false }
36
+ if (schema.has("\$ref")) { val ref = schema.getString("\$ref");if (!ref.startsWith("#/")) return false;var target: Any = root;for (part in ref.substring(2).split('/')) target = (target as? JSONObject)?.opt(part.replace("~1", "/").replace("~0", "~")) ?: return false;return target is JSONObject && valid(value, target, root, depth + 1) }
37
+ for (name in listOf("allOf", "anyOf", "oneOf")) schema.optJSONArray(name)?.let { choices -> val matches = (0 until choices.length()).count { valid(value, choices.getJSONObject(it), root, depth + 1) };if (name == "allOf" && matches != choices.length() || name == "anyOf" && matches == 0 || name == "oneOf" && matches != 1) return false }
38
+ if (schema.has("const") && !equal(value, schema.opt("const"))) return false
39
+ schema.optJSONArray("enum")?.let { choices -> if ((0 until choices.length()).none { equal(value, choices.opt(it)) }) return false }
40
+ when (schema.optString("type")) {
41
+ "null" -> if (value != null && value != JSONObject.NULL) return false
42
+ "boolean" -> if (value !is Boolean) return false
43
+ "string" -> if (value !is String) return false
44
+ "number", "integer" -> if (value !is Number || !value.toDouble().isFinite() || schema.getString("type") == "integer" && value.toDouble() != kotlin.math.floor(value.toDouble())) return false
45
+ "object" -> if (value !is JSONObject) return false
46
+ "array" -> if (value !is JSONArray) return false
47
+ }
48
+ if (value is String) {
49
+ 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
51
+ 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
+ }
53
+ if (value is Number) {
54
+ val n = value.toDouble()
55
+ if (schema.has("minimum") && n < schema.getDouble("minimum") || schema.has("maximum") && n > schema.getDouble("maximum") || schema.has("exclusiveMinimum") && n <= schema.getDouble("exclusiveMinimum") || schema.has("exclusiveMaximum") && n >= schema.getDouble("exclusiveMaximum")) return false
56
+ }
57
+ if (value is JSONArray) {
58
+ if (schema.has("minItems") && value.length() < schema.getInt("minItems") || schema.has("maxItems") && value.length() > schema.getInt("maxItems")) return false
59
+ if (schema.optBoolean("uniqueItems")) for (index in 0 until value.length()) if ((0 until index).any { equal(value.opt(it), value.opt(index)) }) return false
60
+ schema.optJSONObject("items")?.let { items -> if ((0 until value.length()).any { !valid(value.opt(it), items, root, depth + 1) }) return false }
61
+ schema.optJSONArray("items")?.let { tuple -> for (index in 0 until value.length()) if (index < tuple.length() && !valid(value.opt(index), tuple.getJSONObject(index), root, depth + 1)) return false;if (schema.opt("additionalItems") == false && value.length() > tuple.length()) return false }
62
+ }
63
+ if (value is JSONObject) {
64
+ if (schema.has("maxProperties") && value.length() > schema.getInt("maxProperties") || schema.has("minProperties") && value.length() < schema.getInt("minProperties")) return false
65
+ val properties = schema.optJSONObject("properties") ?: JSONObject()
66
+ schema.optJSONArray("required")?.let { required -> if ((0 until required.length()).any { !value.has(required.getString(it)) }) return false }
67
+ for (key in value.keys()) {
68
+ if (properties.has(key)) { if (!valid(value.opt(key), properties.getJSONObject(key), root, depth + 1)) return false }
69
+ else if (schema.opt("additionalProperties") == false) return false
70
+ else schema.optJSONObject("additionalProperties")?.let { if (!valid(value.opt(key), it, root, depth + 1)) return false }
71
+ schema.optJSONObject("propertyNames")?.let { if (!valid(key, it, root, depth + 1)) return false }
72
+ }
73
+ }
74
+ return true
75
+ }
76
+ }
@@ -0,0 +1,82 @@
1
+ // Generated by scripts/sdk/generate-native-protocol.mjs. Do not edit.
2
+ package ai.tinkerland.longplay
3
+ import org.json.JSONObject
4
+ import ai.tinkerland.indiana.bindPushInstallation as legacyBindPushInstallation
5
+ import ai.tinkerland.indiana.readPushInstallation as legacyReadPushInstallation
6
+ import ai.tinkerland.indiana.updatePushAddress as legacyUpdatePushAddress
7
+ import ai.tinkerland.indiana.readMessagingPreference as legacyReadMessagingPreference
8
+ import ai.tinkerland.indiana.setMessagingPreference as legacySetMessagingPreference
9
+ import ai.tinkerland.indiana.getFeatures as legacyGetFeatures
10
+ import ai.tinkerland.indiana.evaluateFeatures as legacyEvaluateFeatures
11
+ import ai.tinkerland.indiana.trackEvents as legacyTrackEvents
12
+ import ai.tinkerland.indiana.restoreNativePurchase as legacyRestoreNativePurchase
13
+ import ai.tinkerland.indiana.setupPayment as legacySetupPayment
14
+ import ai.tinkerland.indiana.readPaymentSetup as legacyReadPaymentSetup
15
+ import ai.tinkerland.indiana.previewAttach as legacyPreviewAttach
16
+ import ai.tinkerland.indiana.attach as legacyAttach
17
+ import ai.tinkerland.indiana.readSubscriptionOperation as legacyReadSubscriptionOperation
18
+ import ai.tinkerland.indiana.previewUpdate as legacyPreviewUpdate
19
+ import ai.tinkerland.indiana.update as legacyUpdate
20
+ import ai.tinkerland.indiana.readSubscriptionUpdate as legacyReadSubscriptionUpdate
21
+ import ai.tinkerland.indiana.readBillingControls as legacyReadBillingControls
22
+ import ai.tinkerland.indiana.prepareBillingAuthorization as legacyPrepareBillingAuthorization
23
+ import ai.tinkerland.indiana.confirmBillingAuthorization as legacyConfirmBillingAuthorization
24
+ import ai.tinkerland.indiana.setBillingCoupons as legacySetBillingCoupons
25
+ import ai.tinkerland.indiana.revokeBillingAuthorization as legacyRevokeBillingAuthorization
26
+ import ai.tinkerland.indiana.listBillingInvoices as legacyListBillingInvoices
27
+ import ai.tinkerland.indiana.confirmBillingAdjustment as legacyConfirmBillingAdjustment
28
+ import ai.tinkerland.indiana.checkEntitlement as legacyCheckEntitlement
29
+ import ai.tinkerland.indiana.resolveContent as legacyResolveContent
30
+ import ai.tinkerland.indiana.resolvePaywall as legacyResolvePaywall
31
+ import ai.tinkerland.indiana.resolveOffering as legacyResolveOffering
32
+ import ai.tinkerland.indiana.readHoldings as legacyReadHoldings
33
+ import ai.tinkerland.indiana.readLedgerRequest as legacyReadLedgerRequest
34
+ import ai.tinkerland.indiana.readBillingInvoice as legacyReadBillingInvoice
35
+ import ai.tinkerland.indiana.readCreditStatement as legacyReadCreditStatement
36
+ import ai.tinkerland.indiana.quoteBalance as legacyQuoteBalance
37
+ import ai.tinkerland.indiana.redeemReward as legacyRedeemReward
38
+ import ai.tinkerland.indiana.createReferralCode as legacyCreateReferralCode
39
+ import ai.tinkerland.indiana.claimRewardProduct as legacyClaimRewardProduct
40
+ import ai.tinkerland.indiana.redeemCoupon as legacyRedeemCoupon
41
+ import ai.tinkerland.indiana.listOffers as legacyListOffers
42
+ import ai.tinkerland.indiana.grantOffer as legacyGrantOffer
43
+
44
+ suspend fun LongplayClient.bindPushInstallation(input: JSONObject): JSONObject = this.legacyBindPushInstallation(input)
45
+ suspend fun LongplayClient.readPushInstallation(input: JSONObject): JSONObject = this.legacyReadPushInstallation(input)
46
+ suspend fun LongplayClient.updatePushAddress(input: JSONObject): JSONObject = this.legacyUpdatePushAddress(input)
47
+ suspend fun LongplayClient.readMessagingPreference(input: JSONObject): JSONObject = this.legacyReadMessagingPreference(input)
48
+ suspend fun LongplayClient.setMessagingPreference(input: JSONObject): JSONObject = this.legacySetMessagingPreference(input)
49
+ suspend fun LongplayClient.getFeatures(input: JSONObject): JSONObject = this.legacyGetFeatures(input)
50
+ suspend fun LongplayClient.evaluateFeatures(input: JSONObject): JSONObject = this.legacyEvaluateFeatures(input)
51
+ suspend fun LongplayClient.trackEvents(input: JSONObject): JSONObject = this.legacyTrackEvents(input)
52
+ suspend fun LongplayClient.restoreNativePurchase(input: JSONObject): JSONObject = this.legacyRestoreNativePurchase(input)
53
+ suspend fun LongplayClient.setupPayment(input: JSONObject): JSONObject = this.legacySetupPayment(input)
54
+ suspend fun LongplayClient.readPaymentSetup(input: JSONObject): JSONObject = this.legacyReadPaymentSetup(input)
55
+ suspend fun LongplayClient.previewAttach(input: JSONObject): JSONObject = this.legacyPreviewAttach(input)
56
+ suspend fun LongplayClient.attach(input: JSONObject): JSONObject = this.legacyAttach(input)
57
+ suspend fun LongplayClient.readSubscriptionOperation(input: JSONObject): JSONObject = this.legacyReadSubscriptionOperation(input)
58
+ suspend fun LongplayClient.previewUpdate(input: JSONObject): JSONObject = this.legacyPreviewUpdate(input)
59
+ suspend fun LongplayClient.update(input: JSONObject): JSONObject = this.legacyUpdate(input)
60
+ suspend fun LongplayClient.readSubscriptionUpdate(input: JSONObject): JSONObject = this.legacyReadSubscriptionUpdate(input)
61
+ suspend fun LongplayClient.readBillingControls(input: JSONObject): JSONObject = this.legacyReadBillingControls(input)
62
+ suspend fun LongplayClient.prepareBillingAuthorization(input: JSONObject): JSONObject = this.legacyPrepareBillingAuthorization(input)
63
+ suspend fun LongplayClient.confirmBillingAuthorization(input: JSONObject): JSONObject = this.legacyConfirmBillingAuthorization(input)
64
+ suspend fun LongplayClient.setBillingCoupons(input: JSONObject): JSONObject = this.legacySetBillingCoupons(input)
65
+ suspend fun LongplayClient.revokeBillingAuthorization(input: JSONObject): JSONObject = this.legacyRevokeBillingAuthorization(input)
66
+ suspend fun LongplayClient.listBillingInvoices(input: JSONObject): JSONObject = this.legacyListBillingInvoices(input)
67
+ suspend fun LongplayClient.confirmBillingAdjustment(input: JSONObject): JSONObject = this.legacyConfirmBillingAdjustment(input)
68
+ suspend fun LongplayClient.checkEntitlement(input: JSONObject): JSONObject = this.legacyCheckEntitlement(input)
69
+ suspend fun LongplayClient.resolveContent(input: JSONObject): JSONObject = this.legacyResolveContent(input)
70
+ suspend fun LongplayClient.resolvePaywall(input: JSONObject): JSONObject = this.legacyResolvePaywall(input)
71
+ suspend fun LongplayClient.resolveOffering(input: JSONObject): JSONObject = this.legacyResolveOffering(input)
72
+ suspend fun LongplayClient.readHoldings(input: JSONObject): JSONObject = this.legacyReadHoldings(input)
73
+ suspend fun LongplayClient.readLedgerRequest(input: JSONObject): JSONObject = this.legacyReadLedgerRequest(input)
74
+ suspend fun LongplayClient.readBillingInvoice(input: JSONObject): JSONObject = this.legacyReadBillingInvoice(input)
75
+ suspend fun LongplayClient.readCreditStatement(input: JSONObject): JSONObject = this.legacyReadCreditStatement(input)
76
+ suspend fun LongplayClient.quoteBalance(input: JSONObject): JSONObject = this.legacyQuoteBalance(input)
77
+ suspend fun LongplayClient.redeemReward(input: JSONObject): JSONObject = this.legacyRedeemReward(input)
78
+ suspend fun LongplayClient.createReferralCode(input: JSONObject): JSONObject = this.legacyCreateReferralCode(input)
79
+ suspend fun LongplayClient.claimRewardProduct(input: JSONObject): JSONObject = this.legacyClaimRewardProduct(input)
80
+ suspend fun LongplayClient.redeemCoupon(input: JSONObject): JSONObject = this.legacyRedeemCoupon(input)
81
+ suspend fun LongplayClient.listOffers(input: JSONObject): JSONObject = this.legacyListOffers(input)
82
+ suspend fun LongplayClient.grantOffer(input: JSONObject): JSONObject = this.legacyGrantOffer(input)
@@ -0,0 +1,32 @@
1
+ package ai.tinkerland.longplay
2
+
3
+ // Keep persistence, JNI/WebView bindings and protocol identities in the legacy implementation.
4
+ typealias LongplayClient = ai.tinkerland.indiana.IndianaClient
5
+ typealias LongplayClientException = ai.tinkerland.indiana.IndianaClientException
6
+ typealias LongplayConfiguration = ai.tinkerland.indiana.IndianaConfiguration
7
+ typealias LongplayContentSession = ai.tinkerland.indiana.IndianaContentSession
8
+ typealias LongplayContentView = ai.tinkerland.indiana.IndianaContentView
9
+ typealias LongplayCustomerOperation = ai.tinkerland.indiana.IndianaCustomerOperation
10
+ typealias LongplayCustomerSession = ai.tinkerland.indiana.IndianaCustomerSession
11
+ typealias LongplayEnvironment = ai.tinkerland.indiana.IndianaEnvironment
12
+ typealias LongplayFeatureClient = ai.tinkerland.indiana.IndianaFeatureClient
13
+ typealias LongplayFunnelCommandStorage = ai.tinkerland.indiana.IndianaFunnelCommandStorage
14
+ typealias LongplayFunnelSession = ai.tinkerland.indiana.IndianaFunnelSession
15
+ typealias LongplayFunnelStorageSnapshot = ai.tinkerland.indiana.IndianaFunnelStorageSnapshot
16
+ typealias LongplayFunnelView = ai.tinkerland.indiana.IndianaFunnelView
17
+ typealias LongplayGoogleStore = ai.tinkerland.indiana.IndianaGoogleStore
18
+ typealias LongplayNativeOfferKind = ai.tinkerland.indiana.IndianaNativeOfferKind
19
+ typealias LongplayNativeReceiptRelay = ai.tinkerland.indiana.IndianaNativeReceiptRelay
20
+ typealias LongplayNativeStoreOffer = ai.tinkerland.indiana.IndianaNativeStoreOffer
21
+ typealias LongplayPaywallSession = ai.tinkerland.indiana.IndianaPaywallSession
22
+ typealias LongplayPaywallView = ai.tinkerland.indiana.IndianaPaywallView
23
+ typealias LongplayPushAddressKind = ai.tinkerland.indiana.IndianaPushAddressKind
24
+ typealias LongplayPushObservation = ai.tinkerland.indiana.IndianaPushObservation
25
+ typealias LongplayPushObservationTicket = ai.tinkerland.indiana.IndianaPushObservationTicket
26
+ typealias LongplayPushPermission = ai.tinkerland.indiana.IndianaPushPermission
27
+ typealias LongplaySDK = ai.tinkerland.indiana.IndianaSDK
28
+ typealias LongplayScope = ai.tinkerland.indiana.IndianaScope
29
+ typealias LongplayStoreException = ai.tinkerland.indiana.IndianaStoreException
30
+ typealias LongplayStoreSubscriptions = ai.tinkerland.indiana.IndianaStoreSubscriptions
31
+ typealias LongplaySubscriptionPlan = ai.tinkerland.indiana.IndianaSubscriptionPlan
32
+ typealias LongplaySubscriptionProvider = ai.tinkerland.indiana.IndianaSubscriptionProvider