Skip to content

Commit f9698c9

Browse files
authored
Merge pull request #13 from belousovgm/fix-some-problems-with-receiving-values
Fix js object serialization
2 parents eea8f0a + 60f9448 commit f9698c9

File tree

14 files changed

+347
-75
lines changed

14 files changed

+347
-75
lines changed

.idea/copyright/IceRock.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/copyright/profiles_settings.xml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ allprojects {
3535
project build.gradle
3636
```groovy
3737
dependencies {
38-
commonMainApi("dev.icerock.moko:javascript:0.2.0")
38+
commonMainApi("dev.icerock.moko:javascript:0.3.0")
3939
}
4040
```
4141

gradle/libs.versions.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
[versions]
2-
kotlinTestVersion = "1.5.20"
2+
kotlinTestVersion = "1.6.21"
33
androidAppCompatVersion = "1.2.0"
44
materialDesignVersion = "1.0.0"
55
androidLifecycleVersion = "2.1.0"
66
androidCoreTestingVersion = "1.3.0"
77
testJUnitExtVersion = "1.1.2"
8-
quickjsVersion = "0.9.0"
9-
coroutinesVersion = "1.5.0-native-mt"
10-
kotlinxSerializationVersion = "1.1.0"
11-
mokoTestVersion = "0.4.0"
12-
mokoJavascriptVersion = "0.2.0"
8+
quickjsVersion = "0.9.2"
9+
coroutinesVersion = "1.6.0-native-mt"
10+
kotlinxSerializationVersion = "1.3.3"
11+
mokoTestVersion = "0.6.1"
12+
mokoJavascriptVersion = "0.3.0"
1313

1414

1515
[libraries]

javascript-build-logic/build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ repositories {
1010
}
1111

1212
dependencies {
13-
api("dev.icerock:mobile-multiplatform:0.12.0")
14-
api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20")
15-
api("com.android.tools.build:gradle:4.2.1")
13+
api("dev.icerock:mobile-multiplatform:0.14.1")
14+
api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21")
15+
api("com.android.tools.build:gradle:7.0.1")
1616
api("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.15.0")
1717
}

javascript-build-logic/src/main/kotlin/multiplatform-library-convention.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,23 @@ plugins {
1111

1212
kotlin {
1313
ios()
14+
iosSimulatorArm64()
1415
android {
1516
publishLibraryVariants("release", "debug")
1617
}
1718
sourceSets {
19+
val iosSimulatorArm64Main by getting
20+
val iosSimulatorArm64Test by getting
21+
1822
val mobileDeviceTest by creating
1923

2024
val commonTest by getting
25+
val iosMain by getting
2126
val iosTest by getting
2227
val androidAndroidTest by getting
2328

29+
iosSimulatorArm64Main.dependsOn(iosMain)
30+
iosSimulatorArm64Test.dependsOn(iosTest)
2431

2532
mobileDeviceTest.dependsOn(commonTest)
2633
iosTest.dependsOn(mobileDeviceTest)

javascript/build.gradle.kts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,29 @@
22
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5+
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
6+
import org.gradle.api.tasks.testing.logging.TestLogEvent
7+
58
plugins {
69
id("multiplatform-library-convention")
710
id("dev.icerock.mobile.multiplatform.android-manifest")
811
id("publication-convention")
912
}
1013

14+
android {
15+
testOptions.unitTests.isIncludeAndroidResources = true
16+
defaultConfig {
17+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18+
}
19+
20+
sourceSets {
21+
getByName("androidTest").java.srcDirs(
22+
file("src/androidAndroidTest/kotlin"),
23+
file("src/mobileDeviceTest/kotlin")
24+
)
25+
}
26+
}
27+
1128
dependencies {
1229
androidMainImplementation(libs.quickjs)
1330
commonMainImplementation(libs.kotlinSerialization)
@@ -20,3 +37,16 @@ dependencies {
2037
androidTestImplementation(libs.testJUnitExt)
2138
androidTestImplementation(libs.testJUnitExtKtx)
2239
}
40+
41+
tasks.withType<AbstractTestTask> {
42+
testLogging {
43+
exceptionFormat = TestExceptionFormat.FULL
44+
events = setOf(
45+
TestLogEvent.SKIPPED,
46+
TestLogEvent.PASSED,
47+
TestLogEvent.FAILED
48+
)
49+
showStandardStreams = true
50+
}
51+
outputs.upToDateWhen { false }
52+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.icerock.moko.javascript
6+
7+
internal interface ContextProvider {
8+
fun getBool(name: String): Boolean
9+
fun getDouble(name: String): Double
10+
11+
fun getString(name: String): String
12+
13+
fun getScript(): String
14+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.icerock.moko.javascript
6+
7+
internal class ContextProviderDynamic : ContextProvider {
8+
var context: Map<String, JsType> = emptyMap()
9+
var activeScript: String = ""
10+
11+
override fun getBool(name: String): Boolean {
12+
return context[name]!!.boolValue()
13+
}
14+
15+
override fun getDouble(name: String): Double {
16+
return context[name]!!.doubleValue()
17+
}
18+
19+
override fun getString(name: String): String {
20+
val jsType: JsType = context[name]!!
21+
return when (jsType) {
22+
is JsType.Bool, is JsType.DoubleNum, JsType.Null -> throw IllegalArgumentException()
23+
is JsType.Json -> jsType.value.toString()
24+
is JsType.Str -> jsType.value
25+
}
26+
}
27+
28+
override fun getScript(): String = activeScript
29+
}

javascript/src/androidMain/kotlin/dev/icerock/moko/javascript/JavaScriptEngine.kt

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,44 @@ package dev.icerock.moko.javascript
77
import app.cash.quickjs.QuickJs
88
import app.cash.quickjs.QuickJsException
99
import kotlinx.serialization.SerializationException
10-
import kotlinx.serialization.builtins.serializer
1110
import kotlinx.serialization.json.Json
11+
import kotlinx.serialization.json.JsonArray
1212
import kotlinx.serialization.json.JsonElement
1313
import kotlinx.serialization.json.JsonObject
1414

1515
actual class JavaScriptEngine actual constructor() {
1616
private val quickJs: QuickJs = QuickJs.create()
1717
private val json: Json = Json.Default
18+
private val jsContext: ContextProviderDynamic = ContextProviderDynamic()
1819

1920
@Volatile
2021
var isClosed = false
2122
private set
2223

23-
actual fun evaluate(
24-
context: Map<String, JsType>,
25-
script: String
26-
): JsType {
24+
init {
25+
quickJs.set("mokoJsContext", ContextProvider::class.java, jsContext)
26+
quickJs.evaluate(
27+
"""
28+
function mokoJavaScriptProcessResult(result) {
29+
if (typeof result === 'object') return JSON.stringify(result);
30+
else if (typeof result === 'array') return JSON.stringify(result);
31+
else return result;
32+
}
33+
""".trimIndent()
34+
)
35+
}
36+
37+
actual fun setContextObjects(vararg context: Pair<String, JsType>) {
38+
val scriptContext: Map<String, JsType> = context.toMap()
39+
jsContext.context = scriptContext
40+
41+
val scriptWithContext: String = buildString {
42+
fillContext(scriptContext)
43+
}
44+
quickJs.evaluate(scriptWithContext)
45+
}
46+
47+
actual fun evaluate(context: Map<String, JsType>, script: String): JsType {
2748
if (isClosed) throw JavaScriptEvaluationException(message = "Engine already closed")
2849

2950
return try {
@@ -43,37 +64,32 @@ actual class JavaScriptEngine actual constructor() {
4364
context: Map<String, JsType>,
4465
script: String
4566
): JsType {
46-
val scriptWithContext = convertContextMapToJsScript(context) + script + "\n"
47-
val result = quickJs.evaluate(scriptWithContext)
48-
return handleQuickJsResult(result)
49-
}
50-
51-
// TODO fix pass of arguments - now wrapping of string and json invalid and will be broken on multilined strings
52-
private fun convertContextMapToJsScript(context: Map<String, JsType>): String {
53-
if (context.isEmpty()) return ""
67+
jsContext.activeScript = script
68+
jsContext.context = context
5469

55-
return context.mapNotNull { pair ->
56-
prepareValueForJs(pair.value)?.let { "var ${pair.key} = $it;" }
57-
}.joinToString(separator = "")
70+
val scriptWithContext: String = buildString {
71+
fillContext(context)
72+
append("mokoJavaScriptProcessResult(eval(mokoJsContext.getScript()));")
73+
}
74+
val result: Any? = quickJs.evaluate(scriptWithContext)
75+
return handleQuickJsResult(result)
5876
}
5977

60-
private fun prepareValueForJs(valueWrapper: JsType): String? {
61-
return when (valueWrapper) {
62-
is JsType.Bool -> valueWrapper.value.toString()
63-
is JsType.DoubleNum -> valueWrapper.value.toString()
64-
is JsType.Json -> valueWrapper.value.let {
65-
Json.encodeToString(JsonElement.serializer(), it)
66-
}.let {
67-
it.replace("\"", "\\\"")
68-
}.let {
69-
"JSON.parse(\"$it\")"
70-
}
71-
is JsType.Str -> valueWrapper.value.let {
72-
it.replace("\"", "\\\"")
73-
}.let {
74-
"\"$it\""
75-
}
76-
JsType.Null -> null
78+
private fun StringBuilder.fillContext(context: Map<String, JsType>) {
79+
context.forEach { (name, jsType) ->
80+
append("const ")
81+
append(name)
82+
append(" = ")
83+
append(
84+
when (jsType) {
85+
is JsType.Bool -> "mokoJsContext.getBool('$name')"
86+
is JsType.DoubleNum -> "mokoJsContext.getDouble('$name')"
87+
is JsType.Json -> "JSON.parse(mokoJsContext.getString('$name'))"
88+
JsType.Null -> "null"
89+
is JsType.Str -> "mokoJsContext.getString('$name')"
90+
}
91+
)
92+
append(";\n")
7793
}
7894
}
7995

@@ -85,12 +101,9 @@ actual class JavaScriptEngine actual constructor() {
85101
is Double -> JsType.DoubleNum(result)
86102
is Float -> JsType.DoubleNum(result.toDouble())
87103
is String -> try {
88-
val serializeResult = json.parseToJsonElement(result)
89-
if (serializeResult is JsonObject) {
90-
JsType.Json(serializeResult)
91-
} else {
92-
JsType.Str(result)
93-
}
104+
val jsonElement: JsonElement = json.parseToJsonElement(result)
105+
if (jsonElement is JsonObject || jsonElement is JsonArray) JsType.Json(jsonElement)
106+
else JsType.Str(result)
94107
} catch (ex: SerializationException) {
95108
JsType.Str(result)
96109
} catch (ex: IllegalStateException) {

0 commit comments

Comments
 (0)