commit d7d3834f215bc28dd82bb58f2b8a19e40cef8de8
parent 8f06688f55ef2aef6178d08c7a21722fbfef7db8
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Thu, 28 Dec 2023 23:11:17 +0100

fea(scripting): networking

Diffstat:
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/scripting/JSModule.kt | 2++
Acommon/src/main/kotlin/me/rhunk/snapenhance/common/scripting/impl/Networking.kt | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 165 insertions(+), 0 deletions(-)

diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/JSModule.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/JSModule.kt @@ -4,6 +4,7 @@ import android.os.Handler import android.widget.Toast import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding import me.rhunk.snapenhance.common.scripting.bindings.BindingsContext +import me.rhunk.snapenhance.common.scripting.impl.Networking import me.rhunk.snapenhance.common.scripting.impl.JavaInterfaces import me.rhunk.snapenhance.common.scripting.ktx.contextScope import me.rhunk.snapenhance.common.scripting.ktx.putFunction @@ -55,6 +56,7 @@ class JSModule( registerBindings( JavaInterfaces(), InterfaceManager(), + Networking(), ) moduleObject.putFunction("setField") { args -> diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/impl/Networking.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/impl/Networking.kt @@ -0,0 +1,162 @@ +package me.rhunk.snapenhance.common.scripting.impl + +import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding +import me.rhunk.snapenhance.common.scripting.bindings.BindingSide +import me.rhunk.snapenhance.common.scripting.ktx.contextScope +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.WebSocket +import okhttp3.WebSocketListener +import okio.ByteString +import okio.ByteString.Companion.toByteString +import org.mozilla.javascript.Function +import org.mozilla.javascript.Scriptable +import org.mozilla.javascript.annotations.JSFunction +import org.mozilla.javascript.annotations.JSGetter + + +class Networking : AbstractBinding("networking", BindingSide.COMMON) { + private val defaultHttpClient = OkHttpClient() + + inner class RequestBuilderWrapper( + val requestBuilder: Request.Builder + ) { + @JSFunction + fun url(url: String) = requestBuilder.url(url).let { this } + + @JSFunction + fun addHeader(name: String, value: String) = requestBuilder.addHeader(name, value).let { this } + + @JSFunction + fun removeHeader(name: String) = requestBuilder.removeHeader(name).let { this } + + @JSFunction + fun method(method: String, body: String) = requestBuilder.method(method, okhttp3.RequestBody.create(null, body)).let { this } + + @JSFunction + fun method(method: String, body: java.io.InputStream) = requestBuilder.method(method, okhttp3.RequestBody.create(null, body.readBytes())).let { this } + + @JSFunction + fun method(method: String, body: ByteArray) = requestBuilder.method(method, okhttp3.RequestBody.create(null, body)).let { this } + } + + inner class ResponseWrapper( + private val response: Response + ) { + @get:JSGetter + val statusCode get() = response.code + @get:JSGetter + val statusMessage get() = response.message + @get:JSGetter + val headers get() = response.headers.toMultimap().mapValues { it.value.joinToString(", ") } + @get:JSGetter + val bodyAsString get() = response.body.string() + @get:JSGetter + val bodyAsStream get() = response.body.byteStream() + @get:JSGetter + val bodyAsByteArray get() = response.body.bytes() + @get:JSGetter + val contentLength get() = response.body.contentLength() + @JSFunction fun getHeader(name: String) = response.header(name) + @JSFunction fun close() = response.close() + } + + inner class WebsocketWrapper( + private val websocket: WebSocket + ) { + @JSFunction fun cancel() = websocket.cancel() + @JSFunction fun close(code: Int, reason: String) = websocket.close(code, reason) + @JSFunction fun queueSize() = websocket.queueSize() + @JSFunction fun send(bytes: ByteArray) = websocket.send(bytes.toByteString()) + @JSFunction fun send(text: String) = websocket.send(text) + } + + @JSFunction + fun getUrl(url: String, callback: (error: String?, response: String) -> Unit) { + defaultHttpClient.newCall(Request.Builder().url(url).build()).enqueue(object : okhttp3.Callback { + override fun onFailure(call: okhttp3.Call, e: java.io.IOException) { + callback(e.message, "") + } + + override fun onResponse(call: okhttp3.Call, response: Response) { + response.use { + callback(null, it.body.string()) + } + } + }) + } + + @JSFunction + fun getUrlAsStream(url: String, callback: (error: String?, response: java.io.InputStream) -> Unit) { + defaultHttpClient.newCall(Request.Builder().url(url).build()).enqueue(object : okhttp3.Callback { + override fun onFailure(call: okhttp3.Call, e: java.io.IOException) { + callback(e.message, java.io.ByteArrayInputStream(byteArrayOf())) + } + + override fun onResponse(call: okhttp3.Call, response: Response) { + response.use { + callback(null, it.body.byteStream()) + } + } + }) + } + + @JSFunction + fun newRequest() = RequestBuilderWrapper(Request.Builder()) + + @JSFunction + fun newWebSocket(requestBuilder: RequestBuilderWrapper, listener: Scriptable): WebsocketWrapper { + return defaultHttpClient.newWebSocket(requestBuilder.requestBuilder.build(), object: WebSocketListener() { + private fun callListener(name: String, websocket: WebSocket, vararg args: Any?) { + contextScope { + (listener.get(name, listener) as? Function)?.call(this, listener, listener, arrayOf(WebsocketWrapper(websocket), *args)) + } + } + + override fun onOpen(webSocket: WebSocket, response: Response) { + callListener("onOpen", webSocket, ResponseWrapper(response)) + } + + override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { + callListener("onClosed", webSocket, code, reason) + } + + override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { + callListener("onClosing", webSocket, code, reason) + } + + override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { + callListener("onFailure", webSocket, t.message, response?.let { ResponseWrapper(it) }) + } + + override fun onMessage(webSocket: WebSocket, bytes: ByteString) { + callListener("onMessageBytes", webSocket, bytes.toByteArray()) + } + + override fun onMessage(webSocket: WebSocket, text: String) { + callListener("onMessageText", webSocket, text) + } + }).let { WebsocketWrapper(it) } + } + + @JSFunction + fun enqueue(requestBuilder: RequestBuilderWrapper, callback: (error: String?, response: ResponseWrapper?) -> Unit) { + defaultHttpClient.newCall(requestBuilder.requestBuilder.build()).enqueue(object : okhttp3.Callback { + override fun onFailure(call: okhttp3.Call, e: java.io.IOException) { + callback(e.message, null) + } + + override fun onResponse(call: okhttp3.Call, response: Response) { + response.use { + callback(null, ResponseWrapper(it)) + } + } + }) + } + + @JSFunction + fun execute(requestBuilder: RequestBuilderWrapper) = ResponseWrapper(defaultHttpClient.newCall(requestBuilder.requestBuilder.build()).execute()) + + override fun getObject() = this +}+ \ No newline at end of file