@tinkerlandai/longplay-react-native 0.10.15-rc.2 → 0.10.16-rc.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 +1 -1
- package/README.md +6 -6
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +4513 -14
- package/dist/index.d.ts +4513 -14
- package/dist/index.js +2 -2
- package/native-sdk/android/assets/indiana/index.html +48 -217
- package/native-sdk/android/java/ai/tinkerland/indiana/CustomerResponseSemantics.kt +1 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/SDKVersion.kt +1 -1
- package/native-sdk/android/java/ai/tinkerland/indiana/ScreenUiSemantics.kt +108 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/WireSchema.kt +2 -1
- package/native-sdk/android/resources/funnel-wire-schemas.json +1 -1
- package/native-sdk/android/resources/renderer-data-url.txt +1 -1
- package/native-sdk/ios/CustomerResponseSemantics.swift +1 -0
- package/native-sdk/ios/SDKVersion.swift +1 -1
- package/native-sdk/ios/ScreenUiSemantics.swift +133 -0
- package/native-sdk/ios/WebView/index.html +48 -217
- package/native-sdk/ios/WebView/renderer-data-url.txt +1 -1
- package/native-sdk/ios/WireSchema.swift +2 -2
- package/native-sdk/ios/funnel-wire-schemas.json +1 -1
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ internal object IndianaCustomerResponseSemantics {
|
|
|
18
18
|
private fun strings(value: Any?) = (value as? JSONArray)?.let { array -> (0 until array.length()).map { array.getString(it) } } ?: emptyList()
|
|
19
19
|
fun rules(original: Any?, schema: JSONObject): Any? {
|
|
20
20
|
val rules = schema.optJSONArray("x-indiana-rules")?.let { strings(it).toSet() } ?: emptySet()
|
|
21
|
+
for (rule in rules) if (rule.startsWith("screenUi") || rule == "screenDesktopWidth") IndianaScreenUiSemantics.validate(rule, original)
|
|
21
22
|
val value = if ("trim" in rules && original is String) original.trim { it in "\u0009\u000a\u000b\u000c\u000d\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff" } else original
|
|
22
23
|
if ("positive" in rules) require(n(value) > BigDecimal.ZERO); if ("atLeastOne" in rules) require(n(value) >= BigDecimal.ONE)
|
|
23
24
|
if ("lowercase" in rules) require(s(value) == s(value).lowercase())
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
package ai.tinkerland.indiana
|
|
2
|
+
|
|
3
|
+
import org.json.JSONArray
|
|
4
|
+
import org.json.JSONObject
|
|
5
|
+
import kotlin.math.abs
|
|
6
|
+
import kotlin.math.max
|
|
7
|
+
import kotlin.math.min
|
|
8
|
+
import kotlin.math.round
|
|
9
|
+
|
|
10
|
+
/** Bounded display data only: no expression evaluation, scripts or commercial authority. */
|
|
11
|
+
internal object IndianaScreenUiSemantics {
|
|
12
|
+
private val reserved = setOf("constructor", "prototype", "__proto__", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toString", "toLocaleString", "valueOf")
|
|
13
|
+
private fun require(valid: Boolean) { if (!valid) throw IndianaClientException("invalid_response") }
|
|
14
|
+
private fun keys(value: JSONObject) = value.keys().asSequence().toSet()
|
|
15
|
+
private fun obj(value: Any?, allowed: Set<String>? = null): JSONObject {
|
|
16
|
+
require(value is JSONObject); val result = value as JSONObject; val fields = keys(result)
|
|
17
|
+
require(fields.intersect(reserved).isEmpty() && (allowed == null || allowed.containsAll(fields)))
|
|
18
|
+
return result
|
|
19
|
+
}
|
|
20
|
+
private fun identifier(value: Any?) = value is String && Regex("^[A-Za-z][A-Za-z0-9_-]{0,63}$").matches(value) && value !in reserved
|
|
21
|
+
private fun number(value: Any?): Double? = (value as? Number)?.toDouble()?.takeIf { it.isFinite() }
|
|
22
|
+
private fun scalar(value: Any?) = value === JSONObject.NULL || value is Boolean || number(value) != null || value is String && value.codePointCount(0,value.length) <= 2000
|
|
23
|
+
private fun same(a: Any?, b: Any?): Boolean { val x = number(a); val y = number(b); return if (x != null && y != null) x == y else if (a === JSONObject.NULL) b === JSONObject.NULL else a is Boolean && b is Boolean && a == b || a is String && b is String && a == b }
|
|
24
|
+
private fun rows(value: Any?): List<Any?> { require(value is JSONArray); val list=value as JSONArray; return (0 until list.length()).map { list.opt(it) } }
|
|
25
|
+
private fun stateValue(d: JSONObject, value: Any?): Boolean = when(d.optString("type")) {
|
|
26
|
+
"enum" -> rows(d.opt("values")).any { same(it,value) }
|
|
27
|
+
"boolean" -> value is Boolean
|
|
28
|
+
"text" -> value === JSONObject.NULL || value is String && value.length <= (number(d.opt("maxLength")) ?: -1.0)
|
|
29
|
+
"number" -> { val n=number(value); val lo=number(d.opt("min")); val hi=number(d.opt("max")); val step=number(d.opt("step")); if(n==null || lo==null || hi==null || step==null || step<=0 || n<lo || n>hi) false else { val steps=(n-lo)/step; steps.isFinite() && abs(steps-round(steps))<=min(1e-7,max(1.0,abs(steps))*2.220446049250313e-16*8) } }
|
|
30
|
+
else -> false
|
|
31
|
+
}
|
|
32
|
+
private fun state(value: Any?) {
|
|
33
|
+
val d=obj(value); val kind=d.optString("type"); val fields=mapOf("enum" to setOf("type","values","default"),"number" to setOf("type","min","max","step","default"),"text" to setOf("type","maxLength","default"),"boolean" to setOf("type","default"))
|
|
34
|
+
require(fields[kind] == keys(d) && scalar(d.opt("default")))
|
|
35
|
+
if(kind=="enum") { val values=rows(d.opt("values")); require(values.size in 1..64 && values.all(::scalar)); for(i in values.indices) require(values.take(i).none { same(it,values[i]) }) }
|
|
36
|
+
if(kind=="number") { val lo=number(d.opt("min")); val hi=number(d.opt("max")); val step=number(d.opt("step")); require(lo!=null && hi!=null && step!=null && lo<=hi && step>0) }
|
|
37
|
+
if(kind=="text") { val length=number(d.opt("maxLength")); require(length!=null && length in 0.0..2000.0 && round(length)==length) }
|
|
38
|
+
require(stateValue(d,d.opt("default")))
|
|
39
|
+
}
|
|
40
|
+
private fun arity(value: Any?) {
|
|
41
|
+
val d=obj(value,setOf("op","args")); require(keys(d)==setOf("op","args")); val args=rows(d.opt("args")); val count=args.size
|
|
42
|
+
val valid=when(d.optString("op")) { "now"->count==0; "concat"->true; "lookup"->count>=4 && count%2==0; "if"->count==3; "sub","div","eq","ne","gt","gte","lt","lte"->count==2; "round","floor","not","number","parse_number","money","duration"->count==1; "add","mul","min","max","and","or"->count>=1; else->false }; require(valid)
|
|
43
|
+
}
|
|
44
|
+
private fun expression(value: Any?, budget: IntArray): Set<String> {
|
|
45
|
+
val pending=java.util.ArrayDeque<Pair<Any?,Int>>(); pending.add(value to 1); val refs=mutableSetOf<String>()
|
|
46
|
+
while(pending.isNotEmpty()) { val (item,depth)=pending.removeLast(); require(++budget[0]<=2000 && depth<=12); if(scalar(item)) continue; val d=obj(item)
|
|
47
|
+
if(d.has("ref")) { require(keys(d)==setOf("ref") && identifier(d.opt("ref"))); refs.add(d.getString("ref")) }
|
|
48
|
+
else { arity(d); val args=rows(d.opt("args")); require(args.size<=2000); for(child in args) pending.add(child to depth+1) }
|
|
49
|
+
}; return refs
|
|
50
|
+
}
|
|
51
|
+
private fun binding(value: Any?) {
|
|
52
|
+
val d=obj(value,setOf("target","property","expression","className")); val property=d.optString("property")
|
|
53
|
+
require(identifier(d.opt("target")) && d.has("expression") && property in setOf("text","visible","disabled","checked","value","class","aria-label","aria-selected","aria-checked","aria-valuetext","tabindex"))
|
|
54
|
+
require((property=="class")==d.has("className") && (!d.has("className") || identifier(d.opt("className"))))
|
|
55
|
+
}
|
|
56
|
+
private fun ui(value: Any?) {
|
|
57
|
+
val ui=obj(value,setOf("state","derived","bindings","controls","timers","effects","clock")); val state=obj(if(ui.has("state")) ui.opt("state") else JSONObject()); val derived=obj(if(ui.has("derived")) ui.opt("derived") else JSONObject()); val stateKeys=keys(state); val derivedKeys=keys(derived)
|
|
58
|
+
require(state.length()<=32 && derived.length()<=96 && stateKeys.intersect(derivedKeys).isEmpty() && (stateKeys+derivedKeys).all(::identifier))
|
|
59
|
+
for(key in stateKeys) this.state(state.opt(key))
|
|
60
|
+
val available=stateKeys+derivedKeys; val budget=intArrayOf(0); val dependencies=derivedKeys.associateWith { expression(derived.opt(it),budget) }; require(dependencies.values.all { available.containsAll(it) })
|
|
61
|
+
val active=mutableSetOf<String>(); val visited=mutableSetOf<String>()
|
|
62
|
+
fun visit(key: String) { require(key !in active); if(key in visited) return; active.add(key); for(ref in dependencies.getValue(key).intersect(derivedKeys)) visit(ref); active.remove(key); visited.add(key) }
|
|
63
|
+
for(key in derivedKeys) visit(key)
|
|
64
|
+
fun read(value: Any?) { require(available.containsAll(expression(value,budget))) }
|
|
65
|
+
fun updates(value: Any?) { val set=obj(value); require(set.length() in 1..32 && stateKeys.containsAll(keys(set))); for(key in keys(set)) read(set.opt(key)) }
|
|
66
|
+
fun list(key: String, limit: Int): List<Any?> { val values=if(ui.has(key)) rows(ui.opt(key)) else emptyList(); require(values.size<=limit); return values }
|
|
67
|
+
require(!ui.has("clock") || ui.opt("clock") is Boolean)
|
|
68
|
+
for(raw in list("bindings",256)) { binding(raw); read(obj(raw).opt("expression")) }
|
|
69
|
+
val effectTargets=mutableSetOf<String>()
|
|
70
|
+
val effectBounds=mapOf("speed" to (0.0 to 4.0),"pitch" to (0.05 to 4.0),"fill" to (0.05 to 1.0),"feather" to (0.0 to 1.0),"warp" to (0.0 to 4.0),"warpScale" to (0.0 to 32.0),"detailScale" to (0.0 to 32.0),"waveScale" to (0.0 to 32.0),"falloff" to (0.1 to 32.0),"brightness" to (0.0 to 32.0),"ambient" to (0.0 to 1.0),"gamma" to (0.05 to 8.0),"vignette" to (0.0 to 1.0),"opacity" to (0.0 to 1.0))
|
|
71
|
+
for(raw in list("effects",8)) {
|
|
72
|
+
val e=obj(raw)
|
|
73
|
+
require(identifier(e.opt("target")) && effectTargets.add(e.getString("target")))
|
|
74
|
+
if(e.optString("type")=="number_counter") {
|
|
75
|
+
obj(e,setOf("type","target","value","prefix","format")); require(e.has("value")); read(e.opt("value"))
|
|
76
|
+
val n=number(e.opt("value")); require(n==null || abs(n)<=1e12)
|
|
77
|
+
if(e.has("prefix")) { val prefix=e.opt("prefix"); require(prefix is String && prefix.codePointCount(0,prefix.length)<=20) }
|
|
78
|
+
if(e.has("format")) require(e.opt("format") is String && e.getString("format") in setOf("number","short"))
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
obj(e,effectBounds.keys+setOf("type","target","color","hotColor","enabled")); require(e.optString("type")=="mosaic_waves")
|
|
82
|
+
for(key in listOf("color","hotColor")) require(e.opt(key) is String && Regex("^#[0-9a-fA-F]{6}$").matches(e.getString(key)))
|
|
83
|
+
for((key,bounds) in effectBounds) if(e.has(key)) { val n=number(e.opt(key)); require(n!=null && n>=bounds.first && n<=bounds.second) }
|
|
84
|
+
if(e.has("enabled")) read(e.opt("enabled"))
|
|
85
|
+
}
|
|
86
|
+
val controls=list("controls",64); val controlTargets=controls.map { obj(it).optString("target") }.toSet(); val navigationTargets=mutableSetOf<String>(); val inactiveTargets=mutableSetOf<String>()
|
|
87
|
+
for(raw in controls) {
|
|
88
|
+
val c=obj(raw); val kind=c.optString("type"); val fields=mapOf("input" to setOf("type","target","state","set"),"action" to setOf("type","target","set"),"carousel" to setOf("type","target","state","initialIndex","maxWidth","previousTarget","nextTarget","inactiveTargets","inactiveMaxWidth"),"scroll_latch" to setOf("type","target","state"))
|
|
89
|
+
require(fields[kind]?.containsAll(keys(c))==true && identifier(c.opt("target")))
|
|
90
|
+
if(kind!="action") { require(identifier(c.opt("state")) && c.optString("state") in stateKeys); val d=obj(state.opt(c.getString("state")))
|
|
91
|
+
if(kind=="scroll_latch") require(d.optString("type")=="boolean")
|
|
92
|
+
if(kind=="input" && d.optString("type")=="enum") require(rows(d.opt("values")).all { it is String })
|
|
93
|
+
if(kind=="carousel" && c.has("inactiveMaxWidth")) { val width=number(c.opt("inactiveMaxWidth")); require(c.has("inactiveTargets") && width!=null && width in 1.0..10000.0 && round(width)==width) }
|
|
94
|
+
if(kind=="carousel" && c.has("inactiveTargets")) { val ids=rows(c.opt("inactiveTargets")); require(ids.size<=20); for(id in ids) { require(identifier(id)); val article=id as String; require(article!=c.optString("target") && inactiveTargets.add(article)) } }
|
|
95
|
+
if(kind=="carousel") for(field in listOf("previousTarget","nextTarget")) if(c.has(field)) { require(identifier(c.opt(field))); val nav=c.getString(field); require(nav !in controlTargets && navigationTargets.add(nav)) }
|
|
96
|
+
if(kind=="carousel") { val lo=number(d.opt("min")); val hi=number(d.opt("max")); require(d.optString("type")=="number" && lo!=null && hi!=null && lo>=0 && round(lo)==lo && round(hi)==hi && number(d.opt("step"))==1.0 && (!c.has("initialIndex") || stateValue(d,c.opt("initialIndex")))); if(c.has("maxWidth")) { val width=number(c.opt("maxWidth")); require(width!=null && width in 1.0..10000.0 && round(width)==width) } }
|
|
97
|
+
}
|
|
98
|
+
if(kind=="action" || c.has("set")) updates(c.opt("set"))
|
|
99
|
+
}
|
|
100
|
+
for(raw in list("timers",4)) { val timer=obj(raw,setOf("afterMs","set")); val delay=number(timer.opt("afterMs")); require(delay!=null && delay in 0.0..60000.0 && round(delay)==delay); updates(timer.opt("set")) }
|
|
101
|
+
}
|
|
102
|
+
fun validate(rule: String, value: Any?) {
|
|
103
|
+
when(rule) { "screenUi"->ui(value); "screenUiState"->state(value); "screenUiIdentifier"->require(identifier(value)); "screenUiArity"->arity(value); "screenUiBinding"->binding(value)
|
|
104
|
+
"screenDesktopWidth"->{ val d=obj(value,setOf("min","max","viewport")); val lo=number(d.opt("min")); val hi=number(d.opt("max")); val width=number(d.opt("viewport")); require(d.length()==3 && lo!=null && hi!=null && width!=null && lo>=1 && lo<=hi && hi<=1800 && width in 1.0..100.0) }
|
|
105
|
+
else->throw IndianaClientException("invalid_response")
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -48,7 +48,8 @@ internal object IndianaSchema {
|
|
|
48
48
|
"array" -> if (value !is JSONArray) return false
|
|
49
49
|
}
|
|
50
50
|
if (value is String) {
|
|
51
|
-
|
|
51
|
+
val length = value.codePointCount(0, value.length)
|
|
52
|
+
if (schema.has("minLength") && length < schema.getInt("minLength") || schema.has("maxLength") && length > schema.getInt("maxLength")) return false
|
|
52
53
|
if (schema.has("pattern") && !patterns.getOrPut(schema.getString("pattern")) { Regex(schema.getString("pattern")) }.containsMatchIn(value)) return false
|
|
53
54
|
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 }
|
|
54
55
|
}
|