jsMain.com.ditchoom.socket.NodeSocketClient.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of socket-js Show documentation
Show all versions of socket-js Show documentation
Simple multiplatform kotlin coroutines based socket.
@file:Suppress("EXPERIMENTAL_OVERRIDE", "EXPERIMENTAL_API_USAGE")
package com.ditchoom.socket
import com.ditchoom.buffer.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.first
import org.khronos.webgl.Uint8Array
import kotlin.coroutines.resume
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
@ExperimentalTime
open class NodeSocket : ClientSocket {
internal lateinit var netSocket: Socket
internal val incomingMessageChannel = Channel>(Channel.UNLIMITED)
private var currentBuffer :ReadBuffer? = null
internal val disconnectedFlow = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override fun isOpen() = netSocket.remoteAddress != null
override suspend fun localPort() = netSocket.localPort.toUShort()
override suspend fun remotePort() = netSocket.remotePort.toUShort()
private suspend fun readBuffer(size:UInt) :SocketDataRead {
var currentBuffer = currentBuffer ?: incomingMessageChannel.receive().result
var bytesLeft = currentBuffer.remaining().toInt()
while (bytesLeft > 0) {
val socketDataResult = incomingMessageChannel.receive()
bytesLeft -= socketDataResult.bytesRead
currentBuffer = FragmentedReadBuffer(currentBuffer, socketDataResult.result).slice()
}
this.currentBuffer = currentBuffer
return SocketDataRead(currentBuffer, size.toInt())
}
override suspend fun readBuffer(timeout: Duration): SocketDataRead {
val msg = incomingMessageChannel.receive()
netSocket.resume()
return msg.copy(result = msg.result.slice())
}
override suspend fun read(buffer: PlatformBuffer, timeout: Duration): Int {
val receivedData = readBuffer(buffer.remaining())
netSocket.resume()
val resultBuffer = receivedData.result
resultBuffer.position(0)
return receivedData.bytesRead
}
override suspend fun read(timeout: Duration, bufferSize: UInt, bufferRead: (PlatformBuffer, Int) -> T): SocketDataRead {
val receivedData = incomingMessageChannel.receive()
netSocket.resume()
val buffer = receivedData.result.slice() as PlatformBuffer
return SocketDataRead(bufferRead(buffer, receivedData.bytesRead), receivedData.bytesRead)
}
override suspend fun write(buffer: PlatformBuffer, timeout: Duration): Int {
val array = (buffer as JsBuffer).buffer
netSocket.write(array)
return array.byteLength
}
override suspend fun awaitClose() {
disconnectedFlow.asSharedFlow().first()
}
override suspend fun close() {
try {
incomingMessageChannel.close()
} catch (t: Throwable) {}
try {
netSocket.close()
} catch (t: Throwable) {}
disconnectedFlow.emit(Unit)
}
}
@ExperimentalTime
class NodeClientSocket : NodeSocket(), ClientToServerSocket {
override suspend fun open(
port: UShort,
timeout: Duration,
hostname: String?,
socketOptions: SocketOptions?
): SocketOptions {
val arrayPlatformBufferMap = HashMap()
val onRead = OnRead({
val buffer = allocateNewBuffer(4u*1024u) as JsBuffer
arrayPlatformBufferMap[buffer.buffer] = buffer
buffer.buffer
}, { bytesRead, buffer ->
val platformBuffer = arrayPlatformBufferMap.remove(buffer)!!
platformBuffer.setLimit(bytesRead)
val socketDataRead = SocketDataRead(platformBuffer.slice(), bytesRead)
incomingMessageChannel.trySend(socketDataRead)
false
})
val options = tcpOptions(port.toInt(), hostname, onRead)
val netSocket = connect(options)
this.netSocket = netSocket
netSocket.on("error") { err ->
error(err.toString())
}
netSocket.on("close") { _ ->
incomingMessageChannel.close()
netSocket.end {}
disconnectedFlow.tryEmit(Unit)
}
return SocketOptions()
}
}