From dfca8944ad5ff4ce443c455874441be16b57728b Mon Sep 17 00:00:00 2001 From: Piasy Xu Date: Tue, 14 Apr 2026 07:44:55 +0800 Subject: [PATCH 1/2] feat: expose socket connected state --- buildSrc/src/main/kotlin/Constants.kt | 2 +- .../com/piasy/kmp/socketio/socketio/Socket.kt | 6 ++- .../kmp/socketio/socketio/ConnectionTest.kt | 25 ++++++++++ .../socketio/socketio/ConnectionTestJvm.kt | 49 +++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTestJvm.kt diff --git a/buildSrc/src/main/kotlin/Constants.kt b/buildSrc/src/main/kotlin/Constants.kt index a3b0fe6..e19bc1c 100644 --- a/buildSrc/src/main/kotlin/Constants.kt +++ b/buildSrc/src/main/kotlin/Constants.kt @@ -1,7 +1,7 @@ object Consts { const val releaseGroup = "com.piasy" const val releaseName = "kmp-socketio" - const val releaseVersion = "1.4.3" + const val releaseVersion = "1.4.4" val androidNS = "$releaseGroup.${releaseName.replace('-', '.')}" } diff --git a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/socketio/Socket.kt b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/socketio/Socket.kt index b8ccb3e..f5e3c18 100644 --- a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/socketio/Socket.kt +++ b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/socketio/Socket.kt @@ -17,7 +17,11 @@ class Socket( private val auth: Map, private val scope: CoroutineScope, ) : Emitter() { - private var connected = false + /** + * Whether this socket namespace is currently connected. + */ + var connected = false + private set private val subs = ArrayList() private val ack = HashMap() private var ackId = 0 diff --git a/kmp-socketio/src/commonTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTest.kt b/kmp-socketio/src/commonTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTest.kt index 83774ba..d5f699d 100644 --- a/kmp-socketio/src/commonTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTest.kt +++ b/kmp-socketio/src/commonTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTest.kt @@ -76,6 +76,31 @@ abstract class ConnectionTest { assertEquals(now.toString(), args[3]) } + @Test + fun shouldExposeConnectionState() = doTest { + val isConnectedBeforeOpen = CompletableDeferred() + val isConnectedWhenConnectedEvent = CompletableDeferred() + val isConnectedWhenDisconnectedEvent = CompletableDeferred() + + val opt = IO.Options() + opt.transports = listOf(WebSocket.NAME) + client(opt = opt) { socket -> + isConnectedBeforeOpen.complete(socket.connected) + socket.on(Socket.EVENT_CONNECT) { + isConnectedWhenConnectedEvent.complete(socket.connected) + socket.close() + }.on(Socket.EVENT_DISCONNECT) { + isConnectedWhenDisconnectedEvent.complete(socket.connected) + } + + socket.open() + } + + assertFalse(isConnectedBeforeOpen.await()) + assertTrue(isConnectedWhenConnectedEvent.await()) + assertFalse(isConnectedWhenDisconnectedEvent.await()) + } + @Test open fun shouldConnectUntrusted() = doTest { val trustAllCertsHttpClientFactory = DefaultHttpClientFactory( diff --git a/kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTestJvm.kt b/kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTestJvm.kt new file mode 100644 index 0000000..a0dd47d --- /dev/null +++ b/kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTestJvm.kt @@ -0,0 +1,49 @@ +package com.piasy.kmp.socketio.socketio + +import com.piasy.kmp.xlog.Logging +import java.io.BufferedReader +import java.io.InputStreamReader +import java.util.concurrent.TimeUnit +import kotlin.concurrent.thread +import kotlin.test.AfterTest +import kotlin.test.BeforeTest + +class ConnectionTestJvm : ConnectionTest() { + private var server: Process? = null + private var serverOutputThread: Thread? = null + + @BeforeTest + override fun startServer() { + Logging.info(TAG, "startServer") + val process = ProcessBuilder("node", "src/jvmTest/resources/socket-server.js", "/") + .redirectErrorStream(true) + .start() + server = process + serverOutputThread = thread(start = true, isDaemon = true, name = "socket-server-jvm-test-stdout") { + BufferedReader(InputStreamReader(process.inputStream)).use { reader -> + while (true) { + val line = reader.readLine() ?: break + Logging.info(TAG, "SERVER OUT: $line") + } + } + } + Logging.info(TAG, "startServer finish") + } + + @AfterTest + override fun stopServer() { + Logging.info(TAG, "stopServer") + server?.let { process -> + process.destroy() + if (!process.waitFor(3, TimeUnit.SECONDS)) { + process.destroyForcibly() + process.waitFor(3, TimeUnit.SECONDS) + } + } + server = null + + serverOutputThread?.join(1000) + serverOutputThread = null + Logging.info(TAG, "stopServer finish") + } +} From 87e9c7250441e7e1c323690bb90520976880dc9e Mon Sep 17 00:00:00 2001 From: Piasy Xu Date: Tue, 14 Apr 2026 07:59:43 +0800 Subject: [PATCH 2/2] test: stabilize jvm connection state coverage --- .../socketio/socketio/ConnectionTestJvm.kt | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTestJvm.kt b/kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTestJvm.kt index a0dd47d..1c3b31b 100644 --- a/kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTestJvm.kt +++ b/kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/socketio/ConnectionTestJvm.kt @@ -1,19 +1,31 @@ package com.piasy.kmp.socketio.socketio +import com.piasy.kmp.socketio.engineio.transports.WebSocket import com.piasy.kmp.xlog.Logging import java.io.BufferedReader import java.io.InputStreamReader +import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import kotlin.test.assertFalse +import kotlin.test.assertTrue import kotlin.concurrent.thread +import kotlin.test.Test import kotlin.test.AfterTest import kotlin.test.BeforeTest +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext +import kotlin.time.Duration.Companion.seconds -class ConnectionTestJvm : ConnectionTest() { +class ConnectionTestJvm { private var server: Process? = null private var serverOutputThread: Thread? = null + private val serverReady = CountDownLatch(1) @BeforeTest - override fun startServer() { + fun startServer() { Logging.info(TAG, "startServer") val process = ProcessBuilder("node", "src/jvmTest/resources/socket-server.js", "/") .redirectErrorStream(true) @@ -24,15 +36,20 @@ class ConnectionTestJvm : ConnectionTest() { while (true) { val line = reader.readLine() ?: break Logging.info(TAG, "SERVER OUT: $line") + if (line.contains("Socket.IO server listening on port 3000")) { + serverReady.countDown() + } } } } + check(serverReady.await(3, TimeUnit.SECONDS)) { "Socket.IO test server did not start in 3s" } Logging.info(TAG, "startServer finish") } @AfterTest - override fun stopServer() { + fun stopServer() { Logging.info(TAG, "stopServer") + serverReady.countDown() server?.let { process -> process.destroy() if (!process.waitFor(3, TimeUnit.SECONDS)) { @@ -46,4 +63,37 @@ class ConnectionTestJvm : ConnectionTest() { serverOutputThread = null Logging.info(TAG, "stopServer finish") } + + @Test + fun shouldExposeConnectionState() = runTest(timeout = 10.seconds) { + withContext(Dispatchers.Default) { + delay(1000) + } + + val isConnectedBeforeOpen = CompletableDeferred() + val isConnectedWhenConnectedEvent = CompletableDeferred() + val isConnectedWhenDisconnectedEvent = CompletableDeferred() + + val opt = IO.Options() + opt.transports = listOf(WebSocket.NAME) + IO.socket("http://localhost:3000/", opt) { socket -> + isConnectedBeforeOpen.complete(socket.connected) + socket.on(Socket.EVENT_CONNECT) { + isConnectedWhenConnectedEvent.complete(socket.connected) + socket.close() + }.on(Socket.EVENT_DISCONNECT) { + isConnectedWhenDisconnectedEvent.complete(socket.connected) + } + + socket.open() + } + + assertFalse(isConnectedBeforeOpen.await()) + assertTrue(isConnectedWhenConnectedEvent.await()) + assertFalse(isConnectedWhenDisconnectedEvent.await()) + } + + companion object { + private const val TAG = "ConnectionTestJvm" + } }