diff --git a/modules/events-library/build.gradle.kts b/modules/events-library/build.gradle.kts index e7ac840..8a716fa 100644 --- a/modules/events-library/build.gradle.kts +++ b/modules/events-library/build.gradle.kts @@ -35,11 +35,12 @@ kotlin { all { languageSettings.apply { - optIn("kotlin.RequiresOptIn") optIn("kotlin.ExperimentalStdlibApi") - optIn("kotlin.time.ExperimentalTime") - optIn("kotlinx.serialization.ExperimentalSerializationApi") + optIn("kotlin.RequiresOptIn") optIn("kotlin.js.ExperimentalJsExport") + optIn("kotlin.time.ExperimentalTime") + optIn("kotlinx.coroutines.FlowPreview") + optIn("kotlinx.serialization.ExperimentalSerializationApi") } } @@ -76,7 +77,6 @@ kotlin { val jvmMain by getting { dependencies { - implementation("com.github.aSemy:ts-generator:v1.2.1") implementation(kotlin("reflect")) } } @@ -95,6 +95,7 @@ kotlin { } } + val jvmJar: TaskProvider = tasks.named(kotlin.jvm().artifactsTaskName) val mainCompilation: Provider = kotlin.jvm().compilations.named("main").map { it.runtimeDependencyFiles } @@ -111,13 +112,16 @@ val generateTypeScript by tasks.registering(GenerateTypeScriptTask::class) { args(temporaryDir.canonicalPath) } + val schemaTsDistributionName: Provider = providers.provider { "${rootProject.name}-${project.name}" } + val generateTypeScriptOutputFiles: Provider = generateTypeScript.map { it.outputs.files.asFileTree } + val schemaTs by distributions.registering { distributionBaseName.set(schemaTsDistributionName) contents { @@ -125,9 +129,11 @@ val schemaTs by distributions.registering { } } + val schemaTsZipTask: TaskProvider = tasks.named("${schemaTs.name}DistZip") val schemaTsZipTaskArchiveFile: Provider = schemaTsZipTask.flatMap { it.archiveFile } + val typeScriptModelGenerated: Configuration by configurations.creating { asProvider() typescriptAttributes(objects) diff --git a/modules/events-library/src/jvmMain/kotlin/dev/adamko/kafkatorio/events/schema/ValueClassTransformer.kt b/modules/events-library/src/jvmMain/kotlin/dev/adamko/kafkatorio/events/schema/ValueClassTransformer.kt deleted file mode 100644 index daedc28..0000000 --- a/modules/events-library/src/jvmMain/kotlin/dev/adamko/kafkatorio/events/schema/ValueClassTransformer.kt +++ /dev/null @@ -1,94 +0,0 @@ -package dev.adamko.kafkatorio.events.schema - -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.contract -import kotlin.reflect.KClass -import kotlin.reflect.KClassifier -import kotlin.reflect.KProperty -import kotlin.reflect.KType -import kotlin.reflect.KTypeProjection -import kotlin.reflect.full.createType -import kotlin.reflect.full.declaredMemberProperties -import kotlin.reflect.full.starProjectedType -import kotlin.reflect.full.withNullability -import me.ntrrgc.tsGenerator.ClassTransformer - -/** - * 'Unwrap' value classes - */ -object ValueClassTransformer : ClassTransformer { - - private val mappedValueClasses: MutableMap = mutableMapOf( - // don't unwrap unsigned numbers - they're mapped to Factorio types - UInt::class.starProjectedType to UInt::class.starProjectedType, - UByte::class.starProjectedType to UByte::class.starProjectedType, - UShort::class.starProjectedType to UShort::class.starProjectedType, - ULong::class.starProjectedType to ULong::class.starProjectedType, - ) - - override fun transformPropertyType(type: KType, property: KProperty<*>, klass: KClass<*>): KType { - val unwrappedType = - unwrapValueClassType(type, property, klass) - ?: unwrapArgs(type, property, klass) - return when (unwrappedType) { - null -> super.transformPropertyType(type, property, klass) - else -> super.transformPropertyType(unwrappedType, property, klass) - } - } - - private fun unwrapArgs(type: KType, property: KProperty<*>, klass: KClass<*>): KType? { - - val classifier: KClassifier = type.classifier ?: return type - - return if (type.arguments.isNotEmpty()) { - println("unwrapping args for $type - ${type.arguments}") - - val args = type.arguments.map { arg: KTypeProjection -> - // recursively map each argument - val argType = arg.type!! - val converted = transformPropertyType(argType, property, klass) - KTypeProjection(arg.variance, converted.withNullability(argType.isMarkedNullable)) - } - - classifier.createType( - arguments = args, - nullable = type.isMarkedNullable, - annotations = type.annotations, - ) - } else { - null - } - } - - private fun unwrapValueClassType(type: KType, property: KProperty<*>, klass: KClass<*>): KType? { - - val valueKlass: KClass<*> = type.classifier as? KClass<*> ?: return null - - return if (isValueClass(type)) { - val mappedType = mappedValueClasses.getOrPut(type.withNullability(false)) { - - val result = valueKlass - .declaredMemberProperties - .first() - .returnType - .withNullability(type.isMarkedNullable) - - println("unwrapping value class $type -> $result") - - // recursively unwrap... - val transformed = transformPropertyType(result, property, klass) - transformed.withNullability(false) - } - mappedType.withNullability(type.isMarkedNullable) - } else { - null - } - } - - @OptIn(ExperimentalContracts::class) - private fun isValueClass(type: KType?): Boolean { - contract { returnsNotNull() implies (type != null) } - return (type?.classifier as? KClass<*>)?.isValue == true - } - -} diff --git a/modules/events-library/src/jvmMain/kotlin/dev/adamko/kafkatorio/events/schema/kt2ts.kt b/modules/events-library/src/jvmMain/kotlin/dev/adamko/kafkatorio/events/schema/kt2ts.kt deleted file mode 100644 index eaf55a8..0000000 --- a/modules/events-library/src/jvmMain/kotlin/dev/adamko/kafkatorio/events/schema/kt2ts.kt +++ /dev/null @@ -1,147 +0,0 @@ -package dev.adamko.kafkatorio.events.schema - -//import dev.adamko.kafkatorio.schema.events.KafkatorioInstantPacketData -//import dev.adamko.kafkatorio.schema.events.KafkatorioKeyedPacketData -//import dev.adamko.kafkatorio.schema.events.KafkatorioKeyedPacketKey -////import dev.adamko.kafkatorio.schema.events.KafkatorioPacket -//import dev.adamko.kafkatorio.schema.prototypes.FactorioPrototype -//import kotlin.io.path.Path -//import kotlin.io.path.createDirectories -//import me.ntrrgc.tsGenerator.TypeScriptGenerator -//import org.intellij.lang.annotations.Language -// -///** -// * Convert Kotlin classes to Typescript classes, using [TypeScriptGenerator] -// */ -//fun main(args: Array) { -// -// val gen = TypeScriptGenerator( -// rootClasses = buildSet { -//// add(KafkatorioPacket::class) -//// addAll(KafkatorioPacket::class.sealedSubclasses) -// -// add(KafkatorioInstantPacketData::class) -// addAll(KafkatorioInstantPacketData::class.sealedSubclasses) -// -// add(KafkatorioKeyedPacketData::class) -// addAll(KafkatorioKeyedPacketData::class.sealedSubclasses) -// addAll(KafkatorioKeyedPacketKey::class.sealedSubclasses) -// -// add(FactorioPrototype::class) -// addAll(FactorioPrototype::class.sealedSubclasses) -// }, -// mappings = mapOf( -// // builtin Factorio numeric types > `typed-factorio/generated/builtin-types.d.ts` -// Float::class to "float", -// Double::class to "double", -// Int::class to "int", -// Byte::class to "int8", -// UInt::class to "uint", -// UByte::class to "uint8", -// UShort::class to "uint16", -// ULong::class to "uint64", -// ), -// classTransformers = listOf( -// ValueClassTransformer, -//// PropertyNameTransformer, -// ), -//// voidType = VoidType.UNDEFINED, -// ) -// -// val definitions = gen.definitionsText -// -// when (val filename = args.firstOrNull()) { -// null -> println(definitions) -// else -> { -// val outPath = Path(filename) -// outPath.createDirectories() -// -// splitDefinitions(definitions) -// .forEach { (defFile, def) -> -// val outFile = outPath.resolve(defFile).toFile() -// outFile.printWriter().use { -// it.println(kt2tsHeader) -// it.print(def) -// } -// println("Generated file: $outFile") -// } -// -// } -// } -//} -// -// -///** Split the TypeScript interfaces into different files */ -//private fun splitDefinitions(definitions: String): Map { -// return definitions -// .split("\n\n") -// .groupBy { def -> -// -// when { -// def isInterface "KafkatorioPacket" -// || def extendsAny listOf("KafkatorioPacket") -// || def isType "KafkatorioPacketDataType" -// -> "kafkatorio-packet.d.ts" -// -// def isInterface "KafkatorioInstantPacketData" -// || def extends "KafkatorioInstantPacketData" -// || def isType "KafkatorioInstantPacketDataType" -// -// || def isInterface "ConfigurationUpdateGameData" -// || def isInterface "ConfigurationUpdateModData" -// -> "kafkatorio-instant-packet.d.ts" -// -// def isInterface "KafkatorioKeyedPacket" -// || def isInterface "KafkatorioKeyedPacketData" -// || def extends "KafkatorioKeyedPacket" -// || def isType "KafkatorioKeyedPacketDataType" -// -// || def isInterface "KafkatorioKeyedPacketKey" -// -> "kafkatorio-keyed-packet.d.ts" -// -// def isInterface "FactorioPrototypes" -// || def isInterface "FactorioPrototype" -// || def extends "FactorioPrototype" -// || def isType "PrototypeObjectName" -// -> "prototype.d.ts" -// -// else -> "schema.d.ts" -// } -// } -// .mapValues { (_, def) -> -// def -// .sorted() -// .joinToString("\n\n", postfix = "\n") -// .replace(" ", " ") -// } -//} -// -//@Language("TypeScript") -//val kt2tsHeader = """ -// |// Generated by TypeScriptGenerator - do not edit this file manually -// | -//""".trimMargin() -// -//private infix fun String.extends(types: String): Boolean = -// contains(Regex(".+extends.*(${types})")) -// -//private infix fun String.extendsAny(types: List): Boolean = -// extends(types.joinToString("|") { "$it,".replaceAfterLast(',', "") }) -// -//private infix fun String.isInterface(type: String): Boolean = -// contains(Regex("interface $type.*")) -// -// -//private infix fun String.isType(type: String): Boolean = -// contains(Regex("type $type.*")) -// -// -////object PropertyNameTransformer : ClassTransformer { -//// override fun transformPropertyName( -//// propertyName: String, -//// property: KProperty<*>, -//// klass: KClass<*> -//// ): String { -//// return camelCaseToSnakeCase(propertyName) -//// } -////} diff --git a/modules/events-library/src/jvmMain/kotlin/dev/adamko/kafkatorio/library/FileEventsFlow.kt b/modules/events-library/src/jvmMain/kotlin/dev/adamko/kafkatorio/library/FileEventsFlow.kt index 05b79c1..2502da5 100644 --- a/modules/events-library/src/jvmMain/kotlin/dev/adamko/kafkatorio/library/FileEventsFlow.kt +++ b/modules/events-library/src/jvmMain/kotlin/dev/adamko/kafkatorio/library/FileEventsFlow.kt @@ -1,4 +1,4 @@ -package dev.adamko.kafkatorio.processor.misc +package dev.adamko.kafkatorio.library import java.io.File import java.nio.file.FileSystems diff --git a/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/SyslogSocketServer.kt b/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/SyslogSocketServer.kt deleted file mode 100644 index 3c4a87a..0000000 --- a/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/SyslogSocketServer.kt +++ /dev/null @@ -1,168 +0,0 @@ -package dev.adamko.kafkatorio.processor.tileserver - -//import com.github.palindromicity.syslog.AllowableDeviations -//import com.github.palindromicity.syslog.SyslogParser -//import com.github.palindromicity.syslog.SyslogParserBuilder -//import com.github.palindromicity.syslog.SyslogSpecification -//import com.github.palindromicity.syslog.dsl.SyslogFieldKeys -//import dev.adamko.kafkatorio.server.config.ApplicationProperties -//import dev.adamko.kafkatorio.processor.tileserver.SyslogMsg.Companion.toSyslogMsg -//import io.ktor.network.selector.SelectorManager -//import io.ktor.network.sockets.aSocket -//import io.ktor.network.sockets.awaitClosed -//import io.ktor.network.sockets.openReadChannel -//import io.ktor.utils.io.readUTF8Line -//import java.util.EnumSet -//import kotlinx.coroutines.CoroutineDispatcher -//import kotlinx.coroutines.Dispatchers -//import kotlinx.coroutines.coroutineScope -//import kotlinx.coroutines.flow.MutableSharedFlow -//import kotlinx.coroutines.flow.SharedFlow -//import kotlinx.coroutines.flow.asSharedFlow -//import kotlinx.coroutines.isActive -//import kotlinx.coroutines.launch -//import kotlinx.coroutines.yield -// -// -//class SyslogSocketServer( -// appProps: ApplicationProperties, -// dispatcher: CoroutineDispatcher = Dispatchers.IO, -//) { -// -// private val selectorManager: SelectorManager = SelectorManager(dispatcher) -// -// -// private val serverSocketTcp = aSocket(selectorManager) -// .tcp() -// .bind( -// appProps.socketServerHost, -// appProps.socketServerPort, -// ) -// -// -//// private val serverSocketUdp: BoundDatagramSocket = aSocket(selectorManager) -//// .udp() -//// .bind( -//// localAddress = InetSocketAddress( -//// appProps.socketServerHost, -//// appProps.socketServerPortUdp, -//// ), -//// ) -// -// -// private val syslogParser: SyslogParser = SyslogParserBuilder() -// .withDeviations(EnumSet.of(AllowableDeviations.PRIORITY, AllowableDeviations.VERSION)) -// .forSpecification(SyslogSpecification.RFC_5424) -// .build() -// -// -// private val _messages: MutableSharedFlow = MutableSharedFlow() -// val messages: SharedFlow -// get() = _messages.asSharedFlow() -// -// -// suspend fun start() { -//// udp() -// tcp() -// } -// -//// private suspend fun udp() { -//// println("[SyslogSocketServer] started listening: ${serverSocketUdp.localAddress}") -//// -//// serverSocketUdp.incoming -//// .receiveAsFlow() -//// .map {udpPacket: Datagram -> -//// val msg = udpPacket.packet.readText() -//// println("[SyslogSocketServer] received msg: $msg") -//// syslogParser.parseLine(msg).toSyslogMsg() -//// }.collect(_messages) -//// } -// -// private suspend fun tcp() = coroutineScope { -// println("[SyslogSocketServer] started listening: ${serverSocketTcp.localAddress}") -// -// while (isActive) { -// val socket = serverSocketTcp.accept() -// -// launch { -// val channel = socket.openReadChannel() -// try { -// while (socket.socketContext.isActive) { -// val line = channel.readUTF8Line() -// println("[SyslogSocketServer] received: $line") -// -// val syslog = syslogParser.parseLine(line).toSyslogMsg() -// -// _messages.emit(syslog) -// -// yield() -// } -// } catch (e: Exception) { -// socket.awaitClosed() -// } -// } -// } -// -// yield() -// } -//} -// -// -//data class SyslogMsg( -// val src: Map -//) { -// -// val message: String? -// get() = src[SyslogFieldKeys.MESSAGE.field] -// -// val headerAppName: String? -// get() = src[SyslogFieldKeys.HEADER_APPNAME.field] -// -// val headerHostName: String? -// get() = src[SyslogFieldKeys.HEADER_HOSTNAME.field] -// -// val headerPri: String? -// get() = src[SyslogFieldKeys.HEADER_PRI.field] -// -// val headerPriSeverity: String? -// get() = src[SyslogFieldKeys.HEADER_PRI_SEVERITY.field] -// -// val headerPriFacility: String? -// get() = src[SyslogFieldKeys.HEADER_PRI_FACILITY.field] -// -// val headerProcId: String? -// get() = src[SyslogFieldKeys.HEADER_PROCID.field] -// -// val headerTimestamp: String? -// get() = src[SyslogFieldKeys.HEADER_TIMESTAMP.field] -// -// val headerMsgId: String? -// get() = src[SyslogFieldKeys.HEADER_MSGID.field] -// -// val headerVersion: String? -// get() = src[SyslogFieldKeys.HEADER_VERSION.field] -// -// val structuredBase: String? -// get() = src[SyslogFieldKeys.STRUCTURED_BASE.field] -// -// val structuredElementIdFmt: String? -// get() = src[SyslogFieldKeys.STRUCTURED_ELEMENT_ID_FMT.field] -// -// val structuredElementIdPnameFmt: String? -// get() = src[SyslogFieldKeys.STRUCTURED_ELEMENT_ID_PNAME_FMT.field] -// -// val structuredElementIdPnamePattern: String? -// get() = src[SyslogFieldKeys.STRUCTURED_ELEMENT_ID_PNAME_PATTERN.field] -// -// companion object { -// fun Map.toSyslogMsg(): SyslogMsg { -// val map = entries.mapNotNull { (k, v) -> -// when (v) { -// is String -> k to v -// else -> null -// } -// }.toMap() -// return SyslogMsg(map) -// } -// } -//} diff --git a/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/WebMapTileServer.kt b/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/WebMapTileServer.kt deleted file mode 100644 index 3f488e5..0000000 --- a/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/WebMapTileServer.kt +++ /dev/null @@ -1,86 +0,0 @@ -package dev.adamko.kafkatorio.processor.tileserver - -//import dev.adamko.kafkatorio.processor.config.ApplicationProperties -//import java.security.MessageDigest -//import org.http4k.core.Filter -//import org.http4k.core.HttpHandler -//import org.http4k.core.MemoryBody -//import org.http4k.core.Method.GET -//import org.http4k.core.Request -//import org.http4k.core.Response -//import org.http4k.core.Status -//import org.http4k.core.then -//import org.http4k.routing.ResourceLoader -//import org.http4k.routing.RoutingHttpHandler -//import org.http4k.routing.bind -//import org.http4k.routing.routes -//import org.http4k.routing.static -// -//internal class WebMapTileServer( -// appProps: ApplicationProperties -//) { -// -// private val tilesLoader = ResourceLoader.Directory(appProps.serverDataDir.toFile().canonicalPath) -// -// -// private val filters = listOf( -// ETagChecker(), -// ).reduce(Filter::then) -// -// -// private val routes: RoutingHttpHandler = routes( -// "/tiles" bind GET to static(tilesLoader).withFilter(filters) -// ) -// -// -// fun build(): HttpHandler { -// return routes -// } -// -// -// private class ETagChecker : Filter { -// -// override fun invoke(next: HttpHandler): HttpHandler = { request: Request -> -// val requestedETag = request.header("If-None-Match") -// -// var response = next(request) -// val responseBytes = response.body.stream.use { it.readAllBytes() } -// val currentETag = responseBytes.md5() -// -// response = response.body(MemoryBody(responseBytes)) -// -// when { -// currentETag == null -> { -//// println("[${request.etagKey}] currentETag is null (requested: $requestedETag), executing request") -// response -// .header("Cache-Control", "no-cache, max-age=0, must-revalidate") -// } -// requestedETag == currentETag -> { -//// println("[${request.etagKey}] currentETag == requested, returning 304 $requestedETag") -// Response(Status.NOT_MODIFIED) -// .header("Cache-Control", "no-cache, max-age=0, must-revalidate") -// } -// else -> { -//// println("[${request.etagKey}] currentETag != requested, executing request and adding currentETag:$currentETag (requested:$requestedETag)") -// response -// .header("ETag", currentETag) -//// .header("Cache-Control", "no-store") -// .header("Cache-Control", "no-cache, max-age=0, must-revalidate") -// } -// } -// } -// } -// -// -// companion object { -// private fun ByteArray.md5(): String? { -// return if (isEmpty()) { -// null -// } else { -// MessageDigest.getInstance("MD5") -// .digest(this) -// .joinToString("") { "%02x".format(it) } -// } -// } -// } -//} diff --git a/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/WebServer.kt b/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/WebServer.kt deleted file mode 100644 index d72dc27..0000000 --- a/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/WebServer.kt +++ /dev/null @@ -1,16 +0,0 @@ -package dev.adamko.kafkatorio.processor.tileserver -// -//import dev.adamko.kafkatorio.processor.config.ApplicationProperties -//import org.http4k.server.Http4kServer -//import org.http4k.server.PolyHandler -//import org.http4k.server.Undertow -//import org.http4k.server.asServer -// -//internal fun webServer( -// tileServer: WebMapTileServer, -// wsServer: WebsocketServer, -// appProps: ApplicationProperties, -//): Http4kServer = PolyHandler( -// tileServer.build(), -// wsServer.build(), -//).asServer(Undertow(appProps.webPort())) diff --git a/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/WebsocketServer.kt b/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/WebsocketServer.kt deleted file mode 100644 index dcf7bd3..0000000 --- a/modules/events-server/src/main/kotlin/dev/adamko/kafkatorio/processor/tileserver/WebsocketServer.kt +++ /dev/null @@ -1,141 +0,0 @@ -package dev.adamko.kafkatorio.processor.tileserver -// -//import kotlin.coroutines.resume -//import kotlin.coroutines.resumeWithException -//import kotlin.time.Duration.Companion.seconds -//import kotlin.time.TimeSource -//import kotlinx.coroutines.CoroutineExceptionHandler -//import kotlinx.coroutines.CoroutineName -//import kotlinx.coroutines.CoroutineScope -//import kotlinx.coroutines.Dispatchers -//import kotlinx.coroutines.SupervisorJob -//import kotlinx.coroutines.channels.BufferOverflow -//import kotlinx.coroutines.delay -//import kotlinx.coroutines.flow.MutableSharedFlow -//import kotlinx.coroutines.flow.launchIn -//import kotlinx.coroutines.flow.onEach -//import kotlinx.coroutines.flow.withIndex -//import kotlinx.coroutines.isActive -//import kotlinx.coroutines.job -//import kotlinx.coroutines.launch -//import kotlinx.coroutines.supervisorScope -//import kotlinx.coroutines.suspendCancellableCoroutine -//import org.http4k.routing.bind -//import org.http4k.routing.websockets -//import org.http4k.websocket.Websocket -//import org.http4k.websocket.WsHandler -//import org.http4k.websocket.WsMessage -//import org.http4k.websocket.WsStatus -// -// -//class WebsocketServer { -// -// private val serverScope = CoroutineScope( -// CoroutineName("WsServer") + -// Dispatchers.IO + -// SupervisorJob() + -// CoroutineExceptionHandler { _, e -> -// println("[ws.WsServer] handling exception ${e.message}") -// } -// ) -// -// -// private val clients: MutableSharedFlow = MutableSharedFlow( -// replay = 100, -// extraBufferCapacity = 100, -// BufferOverflow.DROP_OLDEST, -// ) -// -// -// private val messages: MutableSharedFlow = MutableSharedFlow( -// replay = 100, -// extraBufferCapacity = 100, -// BufferOverflow.DROP_OLDEST, -// ) -// -// -// private fun bindClient(ws: Websocket) = serverScope.launch { -// println("[ws.bindClient] ${ws.upgradeRequest.toMessage()}") -// clients.emit(ws) -// } -// -// -// fun sendMessage(msg: String) = serverScope.launch { -// println("[ws.sendMessage] $msg") -// messages.emit(msg) -// } -// -// -// fun build(): WsHandler { -// val startTime = TimeSource.Monotonic.markNow() -// -// serverScope.launch { -// supervisorScope { -// clients -// .withIndex() -// .onEach { (id, ws) -> -// createClient(id, ws) -// }.launchIn(serverScope) -// } -// } -// -// serverScope.launch { -// while (isActive) { -// delay(10.seconds) -// sendMessage("elapsed ${startTime.elapsedNow()}") -// } -// } -// -// return websockets( -// "/ws/foo" bind { ws: Websocket -> -// serverScope.launch { bindClient(ws) } -// } -// ) -// } -// -// -// private fun createClient(id: Int, ws: Websocket) { -// val clientJob = SupervisorJob(serverScope.coroutineContext.job) -// -// serverScope.launch(CoroutineName("ws-client-$id") + clientJob) { -// launch { -// suspendCancellableCoroutine { cont -> -// println("[ws-client] entering suspendCancellableCoroutine") -// -// cont.invokeOnCancellation { e: Throwable? -> -// launch { sendMessage("[ws-client.invokeOnCompletion] closing client ws ${e?.message}") } -// when (e) { -// null -> ws.close(WsStatus.NORMAL) -// else -> ws.close(WsStatus.ABNORMAL_CLOSE) -// } -// } -// -// ws.onError { e: Throwable -> -// launch { sendMessage("[ws-client.onError] client $id error [${e.message}]") } -// if (!cont.isCompleted) { -// cont.resumeWithException(e) -// } -// } -// -// ws.onClose { status: WsStatus -> -// launch { sendMessage("[ws-client.onClose] client $id disconnected [${status}]") } -// if (!cont.isCompleted) { -// cont.resume(status) -// } -// } -// -// ws.onMessage { wsMsg -> -// launch { sendMessage("$id says: ${wsMsg.bodyString()}") } -// } -// } -// } -// -// println("[ws-client] $id listening to messages") -// messages -// .onEach { msg -> -// println("[ws-client] sending message to $id") -// ws.send(WsMessage(msg)) -// }.launchIn(this) -// } -// } -//}