package suwayomi.tachidesk.global.impl import io.github.oshai.kotlinlogging.KotlinLogging import io.javalin.websocket.WsContext import io.javalin.websocket.WsMessageContext import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import org.eclipse.jetty.websocket.core.CloseStatus import suwayomi.tachidesk.manga.impl.update.Websocket object WebView : Websocket() { private val logger = KotlinLogging.logger {} private var driver: KcefWebView? = null override fun addClient(ctx: WsContext) { if (clients.isNotEmpty()) { // TODO: allow multiple concurrent accesses? clients.forEach { it.value.closeSession(CloseStatus(1001, "Other client connected")) } clients.clear() } if (driver == null) { driver = KcefWebView() } super.addClient(ctx) ctx.enableAutomaticPings() } override fun removeClient(ctx: WsContext) { super.removeClient(ctx) if (clients.isEmpty()) { driver?.destroy() driver = null } } override fun notifyClient( ctx: WsContext, value: String?, ) { if (value != null) { ctx.send(value) } } @Serializable sealed class TypeObject @Serializable @SerialName("loadUrl") private data class LoadUrlMessage( val url: String, val width: Int, val height: Int, ) : TypeObject() @Serializable @SerialName("resize") private data class ResizeMessage( val width: Int, val height: Int, ) : TypeObject() @Serializable @SerialName("event") data class JsEventMessage( val eventType: String, val clickX: Float, val clickY: Float, val button: Int? = null, val ctrlKey: Boolean? = null, val shiftKey: Boolean? = null, val altKey: Boolean? = null, val metaKey: Boolean? = null, val key: String? = null, val code: String? = null, val clientX: Float? = null, val clientY: Float? = null, val deltaY: Float? = null, ) : TypeObject() @Serializable @SerialName("paste") data class JsPasteMessage( val data: String, ) : TypeObject() @Serializable @SerialName("copy") class JsCopyMessage : TypeObject() @Serializable @SerialName("ping") class JsPingMessage : TypeObject() override fun handleRequest(ctx: WsMessageContext) { val dr = driver ?: return try { val event = Json.decodeFromString(ctx.message()) when (event) { is LoadUrlMessage -> { val url = event.url dr.loadUrl(url) dr.resize(event.width, event.height) logger.debug { "Loading URL $url" } } is ResizeMessage -> { dr.resize(event.width, event.height) } is JsEventMessage -> { dr.event(event) } is JsPasteMessage -> { dr.paste(event.data) } is JsCopyMessage -> { dr.copy() } is JsPingMessage -> { notifyAllClients("{\"type\":\"pong\"}") } } } catch (e: Exception) { logger.warn(e) { "Failed to deserialize client request: ${ctx.message()}" } } } }