package dev.solsynth.snConnect.services import dev.solsynth.snConnect.models.WebSocketPacket import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.launch import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.WebSocket import okhttp3.WebSocketListener import okhttp3.Response import okio.ByteString import java.util.logging.Logger class SnService(private val baseUrl: String, val clientId: String, val clientSecret: String, val botApiKey: String?) { val client = OkHttpClient.Builder().build(); private val logger = Logger.getLogger(SnService::class.java.name) private var websocket: WebSocket? = null fun getUrl(service: String, segment: String): String { return "$baseUrl/$service$segment" } fun getWsBaseUrl(): String { return baseUrl.replaceFirst("http", "ws") } fun connectWebSocket(listener: WebSocketListener): WebSocket { val request = Request.Builder() .url("${getWsBaseUrl()}/ws") .apply { botApiKey?.let { header("Authorization", "Bearer $it") } } .build() return client.newWebSocket(request, listener) } fun connectWebSocketAsFlow(): Flow = channelFlow { val url = "${getWsBaseUrl()}/ws" val request = Request.Builder() .url(url) .apply { botApiKey?.let { header("Authorization", "Bearer $it") } } .build() fun connect() { websocket = client.newWebSocket(request, object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { logger.info("WebSocket connection opened to $url") } override fun onMessage(webSocket: WebSocket, text: String) { WebSocketPacket.fromJson(text)?.let { trySend(it) } } override fun onMessage(webSocket: WebSocket, bytes: ByteString) { val text = bytes.string(Charsets.UTF_8) WebSocketPacket.fromJson(text)?.let { trySend(it) } } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { logger.severe("WebSocket connection failed: ${t.message}, response: ${response?.code}") websocket = null GlobalScope.launch { delay(1000) connect() } } override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { logger.info("WebSocket connection closed: code=$code, reason=$reason") websocket = null GlobalScope.launch { delay(1000) connect() } } }) } connect() awaitClose { websocket?.close(1000, "Shutting down") websocket = null } } fun disconnect() { websocket?.close(1000, "Disconnecting") websocket = null } }