@tinkerlandai/longplay-react-native 0.10.15 → 0.10.16

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.
@@ -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())
@@ -2,5 +2,5 @@
2
2
  package ai.tinkerland.indiana
3
3
 
4
4
  object IndianaSDK {
5
- const val version = "0.10.15"
5
+ const val version = "0.10.16"
6
6
  }
@@ -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
- if (schema.has("minLength") && value.length < schema.getInt("minLength") || schema.has("maxLength") && value.length > schema.getInt("maxLength")) return false
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
  }