js: support spawn jsapi

This commit is contained in:
weishu
2024-02-23 00:16:04 +08:00
parent d02855a40a
commit 77d16ac896
3 changed files with 234 additions and 25 deletions

View File

@@ -25,10 +25,13 @@ import com.google.accompanist.web.WebView
import com.google.accompanist.web.rememberWebViewState
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.ShellUtils
import me.weishu.kernelsu.ui.util.createRootShell
import me.weishu.kernelsu.ui.util.serveModule
import org.json.JSONArray
import org.json.JSONObject
import java.util.concurrent.CompletableFuture
@SuppressLint("SetJavaScriptEnabled")
@Destination
@@ -83,6 +86,23 @@ class WebViewInterface(val context: Context, val webView: WebView) {
exec(cmd, null, callbackFunc)
}
private fun processOptions(sb: StringBuilder, options: String?) {
val opts = if (options == null) JSONObject() else {
JSONObject(options)
}
val cwd = opts.optString("cwd")
if (!TextUtils.isEmpty(cwd)) {
sb.append("cd ${cwd};")
}
opts.optJSONObject("env")?.let { env ->
env.keys().forEach { key ->
sb.append("export ${key}=${env.getString(key)};")
}
}
}
@JavascriptInterface
fun exec(
cmd: String,
@@ -94,18 +114,7 @@ class WebViewInterface(val context: Context, val webView: WebView) {
}
val finalCommand = StringBuilder()
val cwd = opts.optString("cwd")
if (!TextUtils.isEmpty(cwd)) {
finalCommand.append("cd ${cwd};")
}
opts.optJSONObject("env")?.let { env ->
env.keys().forEach { key ->
finalCommand.append("export ${key}=${env.getString(key)};")
}
}
processOptions(finalCommand, options)
finalCommand.append(cmd)
val shell = createRootShell()
@@ -124,6 +133,78 @@ class WebViewInterface(val context: Context, val webView: WebView) {
}
}
@JavascriptInterface
fun spawn(command: String, args: String, options: String?, callbackFunc: String) {
val finalCommand = StringBuilder()
processOptions(finalCommand, options)
if (!TextUtils.isEmpty(args)) {
finalCommand.append(command).append(" ")
JSONArray(args).let { argsArray ->
for (i in 0 until argsArray.length()) {
finalCommand.append(argsArray.getString(i))
finalCommand.append(" ")
}
}
} else {
finalCommand.append(command)
}
val shell = createRootShell()
val emitData = fun(name: String, data: String) {
val jsCode =
"javascript: (function() { try { ${callbackFunc}.${name}.emit('data', ${
JSONObject.quote(
data
)
}); } catch(e) { console.error('emitData', e); } })();"
webView.post {
webView.loadUrl(jsCode)
}
}
val stdout = object : CallbackList<String>() {
override fun onAddElement(s: String) {
emitData("stdout", s)
}
}
val stderr = object : CallbackList<String>() {
override fun onAddElement(s: String) {
emitData("stderr", s)
}
}
val future = shell.newJob().add(finalCommand.toString()).to(stdout, stderr).enqueue()
val completableFuture = CompletableFuture.supplyAsync {
future.get()
}
completableFuture.thenAccept { result ->
val emitExitCode =
"javascript: (function() { try { ${callbackFunc}.emit('exit', ${result.code}); } catch(e) { console.error(`emitExit error: \${e}`); } })();"
webView.post {
webView.loadUrl(emitExitCode)
}
if (result.code != 0) {
val emitErrCode =
"javascript: (function() { try { var err = new Error(); err.exitCode = ${result.code}; err.message = ${
JSONObject.quote(
result.err.joinToString(
"\n"
)
)
};${callbackFunc}.emit('error', err); } catch(e) { console.error('emitErr', e); } })();"
webView.post {
webView.loadUrl(emitErrCode)
}
}
}
}
@JavascriptInterface
fun toast(msg: String) {
webView.post {