diff options
author | Robert Jaros <rjaros@finn.pl> | 2020-03-11 16:18:17 +0100 |
---|---|---|
committer | Robert Jaros <rjaros@finn.pl> | 2020-03-11 16:18:17 +0100 |
commit | 9e243a469ae6544e8cf523ad09b959f541c3f565 (patch) | |
tree | be9c7447e221af49180c9e98434df7f988b940b8 /kvision-modules/kvision-remote/src | |
parent | ec6084c42c13a621e17b17bd40d90b5c7879f0ec (diff) | |
download | kvision-9e243a469ae6544e8cf523ad09b959f541c3f565.tar.gz kvision-9e243a469ae6544e8cf523ad09b959f541c3f565.tar.bz2 kvision-9e243a469ae6544e8cf523ad09b959f541c3f565.zip |
Upgrade to Kotlin 1.3.70 + other dependencies (Coroutinse, Serialization, Spring Boot)
Major refactoring of build architecture.
Diffstat (limited to 'kvision-modules/kvision-remote/src')
9 files changed, 0 insertions, 1584 deletions
diff --git a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/CallAgent.kt b/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/CallAgent.kt deleted file mode 100644 index 9110d4df..00000000 --- a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/CallAgent.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.remote - -import kotlinx.serialization.ImplicitReflectionSerializer -import kotlinx.serialization.stringify -import org.w3c.dom.get -import pl.treksoft.jquery.JQueryAjaxSettings -import pl.treksoft.jquery.JQueryXHR -import pl.treksoft.jquery.jQuery -import kotlin.browser.window -import kotlin.js.Promise -import kotlin.js.JSON as NativeJSON - -/** - * HTTP status unauthorized (401). - */ -const val HTTP_UNAUTHORIZED = 401 - -/** - * An agent responsible for remote calls. - */ -open class CallAgent { - - private val kvUrlPrefix = window["kv_remote_url_prefix"] - private val urlPrefix: String = if (kvUrlPrefix != undefined) kvUrlPrefix else "" - private var counter = 1 - - /** - * Makes an JSON-RPC call to the remote server. - * @param url an URL address - * @param data data to be sent - * @param method a HTTP method - * @return a promise of the result - */ - @UseExperimental(ImplicitReflectionSerializer::class) - @Suppress("UnsafeCastFromDynamic", "ComplexMethod") - fun jsonRpcCall( - url: String, - data: List<String?> = listOf(), - method: HttpMethod = HttpMethod.POST - ): Promise<String> { - val jsonRpcRequest = JsonRpcRequest(counter++, url, data) - val jsonData = if (method == HttpMethod.GET) { - obj { id = jsonRpcRequest.id } - } else { - JSON.plain.stringify(jsonRpcRequest) - } - return Promise { resolve, reject -> - jQuery.ajax(urlPrefix + url, obj { - this.contentType = "application/json" - this.data = jsonData - this.method = method.name - this.success = - { data: dynamic, _: Any, _: Any -> - when { - data.id != jsonRpcRequest.id -> reject(Exception("Invalid response ID")) - data.error != null -> { - if (data.exceptionType == "pl.treksoft.kvision.remote.ServiceException") { - reject(ServiceException(data.error.toString())) - } else { - reject(Exception(data.error.toString())) - } - } - data.result != null -> resolve(data.result) - else -> reject(Exception("Invalid response")) - } - } - this.error = - { xhr: JQueryXHR, _: String, errorText: String -> - val message = if (xhr.responseJSON != null && xhr.responseJSON != undefined) { - NativeJSON.stringify(xhr.responseJSON) - } else if (xhr.responseText != undefined) { - xhr.responseText - } else { - errorText - } - if (xhr.status.toInt() == HTTP_UNAUTHORIZED) { - reject(SecurityException(message)) - } else { - reject(Exception(message)) - } - } - this.xhrFields = obj { - this.withCredentials = true - } - }) - } - } - - /** - * Makes a remote call to the remote server. - * @param url an URL address - * @param data data to be sent - * @param method a HTTP method - * @param contentType a content type of the request - * @param beforeSend a function to set request parameters - * @return a promise of the result - */ - @Suppress("UnsafeCastFromDynamic", "ComplexMethod") - fun remoteCall( - url: String, - data: dynamic = null, - method: HttpMethod = HttpMethod.GET, - contentType: String = "application/json", - beforeSend: ((JQueryXHR, JQueryAjaxSettings) -> Boolean)? = null - ): Promise<dynamic> { - return Promise { resolve, reject -> - jQuery.ajax(urlPrefix + url, obj { - this.contentType = if (contentType != "multipart/form-data") contentType else false - this.data = data - this.method = method.name - this.processData = if (contentType != "multipart/form-data") undefined else false - this.success = - { data: dynamic, _: Any, _: Any -> - resolve(data) - } - this.error = - { xhr: JQueryXHR, _: String, errorText: String -> - val message = if (xhr.responseJSON != null && xhr.responseJSON != undefined) { - NativeJSON.stringify(xhr.responseJSON) - } else if (xhr.responseText != undefined) { - xhr.responseText - } else { - errorText - } - if (xhr.status.toInt() == HTTP_UNAUTHORIZED) { - reject(SecurityException(message)) - } else { - reject(Exception(message)) - } - } - this.beforeSend = beforeSend - this.xhrFields = obj { - this.withCredentials = true - } - }) - } - } -} diff --git a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/KVRemoteAgent.kt b/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/KVRemoteAgent.kt deleted file mode 100644 index 86d90e4c..00000000 --- a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/KVRemoteAgent.kt +++ /dev/null @@ -1,562 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.remote - -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.asDeferred -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlinx.serialization.ImplicitReflectionSerializer -import kotlinx.serialization.list -import kotlinx.serialization.serializer -import kotlinx.serialization.stringify -import kotlin.reflect.KClass - -/** - * Client side agent for JSON-RPC remote calls. - */ -@Suppress("LargeClass", "TooManyFunctions") -@UseExperimental(ImplicitReflectionSerializer::class, ExperimentalCoroutinesApi::class) -open class KVRemoteAgent<T : Any>(val serviceManager: KVServiceManager<T>) : RemoteAgent { - - val callAgent = CallAgent() - - /** - * Executes defined call to a remote web service. - */ - suspend inline fun <reified RET : Any, T> call(noinline function: suspend T.() -> RET): RET { - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, method = method).then { - try { - @Suppress("UNCHECKED_CAST") - deserialize<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnum(RET::class as KClass<Any>, it) as RET - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer(), it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined call to a remote web service. - */ - suspend inline fun <reified RET : Any, T> call( - noinline function: suspend T.() -> List<RET> - ): List<RET> { - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, method = method).then { - try { - deserializeList<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnumList(RET::class as KClass<Any>, it) as List<RET> - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer().list, it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined call to a remote web service. - */ - suspend inline fun <reified PAR, reified RET : Any, T> call( - noinline function: suspend T.(PAR) -> RET, p: PAR - ): RET { - val data = serialize(p) - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, listOf(data), method).then { - try { - @Suppress("UNCHECKED_CAST") - deserialize<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnum(RET::class as KClass<Any>, it) as RET - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer(), it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined call to a remote web service. - */ - suspend inline fun <reified PAR, reified RET : Any, T> call( - noinline function: suspend T.(PAR) -> List<RET>, p: PAR - ): List<RET> { - val data = serialize(p) - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, listOf(data), method).then { - try { - deserializeList<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnumList(RET::class as KClass<Any>, it) as List<RET> - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer().list, it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined call to a remote web service. - */ - suspend inline fun <reified PAR1, reified PAR2, reified RET : Any, T> call( - noinline function: suspend T.(PAR1, PAR2) -> RET, p1: PAR1, p2: PAR2 - ): RET { - val data1 = serialize(p1) - val data2 = serialize(p2) - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, listOf(data1, data2), method).then { - try { - @Suppress("UNCHECKED_CAST") - deserialize<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnum(RET::class as KClass<Any>, it) as RET - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer(), it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined call to a remote web service. - */ - suspend inline fun <reified PAR1, reified PAR2, reified RET : Any, T> call( - noinline function: suspend T.(PAR1, PAR2) -> List<RET>, p1: PAR1, p2: PAR2 - ): List<RET> { - val data1 = serialize(p1) - val data2 = serialize(p2) - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, listOf(data1, data2), method).then { - try { - deserializeList<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnumList(RET::class as KClass<Any>, it) as List<RET> - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer().list, it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined call to a remote web service. - */ - suspend inline fun <reified PAR1, reified PAR2, reified PAR3, reified RET : Any, T> call( - noinline function: suspend T.(PAR1, PAR2, PAR3) -> RET, p1: PAR1, p2: PAR2, p3: PAR3 - ): RET { - val data1 = serialize(p1) - val data2 = serialize(p2) - val data3 = serialize(p3) - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, listOf(data1, data2, data3), method).then { - try { - @Suppress("UNCHECKED_CAST") - deserialize<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnum(RET::class as KClass<Any>, it) as RET - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer(), it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined call to a remote web service. - */ - suspend inline fun <reified PAR1, reified PAR2, reified PAR3, reified RET : Any, T> call( - noinline function: suspend T.(PAR1, PAR2, PAR3) -> List<RET>, p1: PAR1, p2: PAR2, p3: PAR3 - ): List<RET> { - val data1 = serialize(p1) - val data2 = serialize(p2) - val data3 = serialize(p3) - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, listOf(data1, data2, data3), method).then { - try { - deserializeList<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnumList(RET::class as KClass<Any>, it) as List<RET> - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer().list, it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined call to a remote web service. - */ - suspend inline fun <reified PAR1, reified PAR2, reified PAR3, reified PAR4, reified RET : Any, T> call( - noinline function: suspend T.(PAR1, PAR2, PAR3, PAR4) -> RET, p1: PAR1, p2: PAR2, p3: PAR3, p4: PAR4 - ): RET { - val data1 = serialize(p1) - val data2 = serialize(p2) - val data3 = serialize(p3) - val data4 = serialize(p4) - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, listOf(data1, data2, data3, data4), method).then { - try { - @Suppress("UNCHECKED_CAST") - deserialize<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnum(RET::class as KClass<Any>, it) as RET - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer(), it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined call to a remote web service. - */ - suspend inline fun <reified PAR1, reified PAR2, reified PAR3, reified PAR4, reified RET : Any, T> call( - noinline function: suspend T.(PAR1, PAR2, PAR3, PAR4) -> List<RET>, - p1: PAR1, - p2: PAR2, - p3: PAR3, - p4: PAR4 - ): List<RET> { - val data1 = serialize(p1) - val data2 = serialize(p2) - val data3 = serialize(p3) - val data4 = serialize(p4) - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, listOf(data1, data2, data3, data4), method).then { - try { - deserializeList<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnumList(RET::class as KClass<Any>, it) as List<RET> - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer().list, it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined call to a remote web service. - */ - @Suppress("LongParameterList") - suspend inline fun <reified PAR1, reified PAR2, reified PAR3, reified PAR4, reified PAR5, - reified RET : Any, T> call( - noinline function: suspend T.(PAR1, PAR2, PAR3, PAR4, PAR5) -> RET, - p1: PAR1, - p2: PAR2, - p3: PAR3, - p4: PAR4, - p5: PAR5 - ): RET { - val data1 = serialize(p1) - val data2 = serialize(p2) - val data3 = serialize(p3) - val data4 = serialize(p4) - val data5 = serialize(p5) - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, listOf(data1, data2, data3, data4, data5), method).then { - try { - @Suppress("UNCHECKED_CAST") - deserialize<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnum(RET::class as KClass<Any>, it) as RET - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer(), it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined call to a remote web service. - */ - @Suppress("LongParameterList") - suspend inline fun <reified PAR1, reified PAR2, reified PAR3, reified PAR4, reified PAR5, - reified RET : Any, T> call( - noinline function: suspend T.(PAR1, PAR2, PAR3, PAR4, PAR5) -> List<RET>, - p1: PAR1, - p2: PAR2, - p3: PAR3, - p4: PAR4, - p5: PAR5 - ): List<RET> { - val data1 = serialize(p1) - val data2 = serialize(p2) - val data3 = serialize(p3) - val data4 = serialize(p4) - val data5 = serialize(p5) - val (url, method) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - return callAgent.jsonRpcCall(url, listOf(data1, data2, data3, data4, data5), method).then { - try { - deserializeList<RET>(it, RET::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnumList(RET::class as KClass<Any>, it) as List<RET> - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(RET::class.serializer().list, it) - } - } - }.asDeferred().await() - } - - /** - * Executes defined web socket connection - */ - @Suppress("ComplexMethod", "TooGenericExceptionCaught") - suspend inline fun <reified PAR1 : Any, reified PAR2 : Any> webSocket( - noinline function: suspend T.(ReceiveChannel<PAR1>, SendChannel<PAR2>) -> Unit, - noinline handler: suspend (SendChannel<PAR1>, ReceiveChannel<PAR2>) -> Unit - ) { - val (url, _) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - val socket = Socket() - val requestChannel = Channel<PAR1>() - val responseChannel = Channel<PAR2>() - try { - coroutineScope { - socket.connect(getWebSocketUrl(url)) - lateinit var responseJob: Job - lateinit var handlerJob: Job - val requestJob = launch { - for (par1 in requestChannel) { - val param = serialize(par1) - val str = JSON.plain.stringify(JsonRpcRequest(0, url, listOf(param))) - if (!socket.sendOrFalse(str)) break - } - responseJob.cancel() - handlerJob.cancel() - if (!requestChannel.isClosedForReceive) requestChannel.close() - if (!responseChannel.isClosedForSend) responseChannel.close() - } - responseJob = launch { - while (true) { - val str = socket.receiveOrNull() ?: break - val data = kotlin.js.JSON.parse<JsonRpcResponse>(str).result ?: "" - val par2 = try { - @Suppress("UNCHECKED_CAST") - deserialize<PAR2>(data, PAR2::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnum(PAR2::class as KClass<Any>, data) as PAR2 - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(PAR2::class.serializer(), data) - } - } - responseChannel.send(par2) - } - requestJob.cancel() - handlerJob.cancel() - if (!requestChannel.isClosedForReceive) requestChannel.close() - if (!responseChannel.isClosedForSend) responseChannel.close() - } - handlerJob = launch { - exceptionHelper { - handler(requestChannel, responseChannel) - } - requestJob.cancel() - responseJob.cancel() - if (!requestChannel.isClosedForReceive) requestChannel.close() - if (!responseChannel.isClosedForSend) responseChannel.close() - } - } - } catch (e: Exception) { - console.log(e) - } - if (!requestChannel.isClosedForReceive) requestChannel.close() - if (!responseChannel.isClosedForSend) responseChannel.close() - socket.close() - } - - /** - * Executes defined web socket connection returning list objects - */ - @Suppress("ComplexMethod", "TooGenericExceptionCaught") - suspend inline fun <reified PAR1 : Any, reified PAR2 : Any> webSocket( - noinline function: suspend T.(ReceiveChannel<PAR1>, SendChannel<List<PAR2>>) -> Unit, - noinline handler: suspend (SendChannel<PAR1>, ReceiveChannel<List<PAR2>>) -> Unit - ) { - val (url, _) = - serviceManager.getCalls()[function.toString().replace("\\s".toRegex(), "")] - ?: throw IllegalStateException("Function not specified!") - val socket = Socket() - val requestChannel = Channel<PAR1>() - val responseChannel = Channel<List<PAR2>>() - try { - coroutineScope { - socket.connect(getWebSocketUrl(url)) - lateinit var responseJob: Job - lateinit var handlerJob: Job - val requestJob = launch { - for (par1 in requestChannel) { - val param = serialize(par1) - val str = JSON.plain.stringify(JsonRpcRequest(0, url, listOf(param))) - if (!socket.sendOrFalse(str)) break - } - responseJob.cancel() - handlerJob.cancel() - if (!requestChannel.isClosedForReceive) requestChannel.close() - if (!responseChannel.isClosedForSend) responseChannel.close() - } - responseJob = launch { - while (true) { - val str = socket.receiveOrNull() ?: break - val data = kotlin.js.JSON.parse<JsonRpcResponse>(str).result ?: "" - val par2 = try { - deserializeList<PAR2>(data, PAR2::class.js.name) - } catch (t: NotStandardTypeException) { - try { - @Suppress("UNCHECKED_CAST") - tryDeserializeEnumList(PAR2::class as KClass<Any>, data) as List<PAR2> - } catch (t: NotEnumTypeException) { - JSON.nonstrict.parse(PAR2::class.serializer().list, data) - } - } - responseChannel.send(par2) - } - requestJob.cancel() - handlerJob.cancel() - if (!requestChannel.isClosedForReceive) requestChannel.close() - if (!responseChannel.isClosedForSend) responseChannel.close() - } - handlerJob = launch { - exceptionHelper { - handler(requestChannel, responseChannel) - } - requestJob.cancel() - responseJob.cancel() - if (!requestChannel.isClosedForReceive) requestChannel.close() - if (!responseChannel.isClosedForSend) responseChannel.close() - } - } - } catch (e: Exception) { - console.log(e) - } - if (!requestChannel.isClosedForReceive) requestChannel.close() - if (!responseChannel.isClosedForSend) responseChannel.close() - socket.close() - } - - /** - * @suppress internal function - */ - suspend fun Socket.receiveOrNull(): String? { - return try { - this.receive() - } catch (e: SocketClosedException) { - console.log("Socket was closed: ${e.reason}") - null - } - } - - /** - * @suppress internal function - */ - fun Socket.sendOrFalse(str: String): Boolean { - return try { - this.send(str) - true - } catch (e: SocketClosedException) { - console.log("Socket was closed: ${e.reason}") - false - } - } - - /** - * @suppress internal function - */ - @Suppress("TooGenericExceptionCaught") - suspend fun exceptionHelper(block: suspend () -> Unit) { - try { - block() - } catch (e: Exception) { - console.log(e) - } - } - - /** - * @suppress - * Internal function - */ - inline fun <reified PAR> serialize(value: PAR): String? { - return value?.let { - @Suppress("UNCHECKED_CAST") - trySerialize((PAR::class as KClass<Any>), it as Any) - } - } - -} diff --git a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt b/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt deleted file mode 100644 index aaf63153..00000000 --- a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.remote - -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -import kotlin.reflect.KClass - -/** - * Multiplatform service manager. - */ -actual open class KVServiceManager<T : Any> actual constructor(serviceClass: KClass<T>) { - - protected val calls: MutableMap<String, Pair<String, HttpMethod>> = mutableMapOf() - var counter: Int = 0 - - /** - * Binds a given route with a function of the receiver. - * @param function a function of the receiver - * @param method a HTTP method - * @param route a route - */ - protected actual inline fun <reified RET> bind( - noinline function: suspend T.() -> RET, - method: HttpMethod, route: String? - ) { - val routeDef = route ?: "route${this::class.simpleName}${counter++}" - calls[function.toString().replace("\\s".toRegex(), "")] = Pair("/kv/$routeDef", method) - } - - /** - * Binds a given route with a function of the receiver. - * @param function a function of the receiver - * @param method a HTTP method - * @param route a route - */ - protected actual inline fun <reified PAR, reified RET> bind( - noinline function: suspend T.(PAR) -> RET, - method: HttpMethod, route: String? - ) { - if (method == HttpMethod.GET) - throw UnsupportedOperationException("GET method is only supported for methods without parameters") - val routeDef = route ?: "route${this::class.simpleName}${counter++}" - calls[function.toString().replace("\\s".toRegex(), "")] = Pair("/kv/$routeDef", method) - } - - /** - * Binds a given route with a function of the receiver. - * @param function a function of the receiver - * @param method a HTTP method - * @param route a route - */ - protected actual inline fun <reified PAR1, reified PAR2, reified RET> bind( - noinline function: suspend T.(PAR1, PAR2) -> RET, - method: HttpMethod, route: String? - ) { - if (method == HttpMethod.GET) - throw UnsupportedOperationException("GET method is only supported for methods without parameters") - val routeDef = route ?: "route${this::class.simpleName}${counter++}" - calls[function.toString().replace("\\s".toRegex(), "")] = Pair("/kv/$routeDef", method) - } - - /** - * Binds a given route with a function of the receiver. - * @param function a function of the receiver - * @param method a HTTP method - * @param route a route - */ - protected actual inline fun <reified PAR1, reified PAR2, reified PAR3, reified RET> bind( - noinline function: suspend T.(PAR1, PAR2, PAR3) -> RET, - method: HttpMethod, route: String? - ) { - if (method == HttpMethod.GET) - throw UnsupportedOperationException("GET method is only supported for methods without parameters") - val routeDef = route ?: "route${this::class.simpleName}${counter++}" - calls[function.toString().replace("\\s".toRegex(), "")] = Pair("/kv/$routeDef", method) - } - - /** - * Binds a given route with a function of the receiver. - * @param function a function of the receiver - * @param method a HTTP method - * @param route a route - */ - protected actual inline fun <reified PAR1, reified PAR2, reified PAR3, reified PAR4, reified RET> bind( - noinline function: suspend T.(PAR1, PAR2, PAR3, PAR4) -> RET, - method: HttpMethod, route: String? - ) { - if (method == HttpMethod.GET) - throw UnsupportedOperationException("GET method is only supported for methods without parameters") - val routeDef = route ?: "route${this::class.simpleName}${counter++}" - calls[function.toString().replace("\\s".toRegex(), "")] = Pair("/kv/$routeDef", method) - } - - /** - * Binds a given route with a function of the receiver. - * @param function a function of the receiver - * @param method a HTTP method - * @param route a route - */ - protected actual inline fun <reified PAR1, reified PAR2, reified PAR3, - reified PAR4, reified PAR5, reified RET> bind( - noinline function: suspend T.(PAR1, PAR2, PAR3, PAR4, PAR5) -> RET, - method: HttpMethod, route: String? - ) { - if (method == HttpMethod.GET) - throw UnsupportedOperationException("GET method is only supported for methods without parameters") - val routeDef = route ?: "route${this::class.simpleName}${counter++}" - calls[function.toString().replace("\\s".toRegex(), "")] = Pair("/kv/$routeDef", method) - } - - /** - * Binds a given function of the receiver as a tabulator component source - * @param function a function of the receiver - */ - protected actual inline fun <reified RET> bindTabulatorRemote( - noinline function: suspend T.(Int?, Int?, List<RemoteFilter>?, List<RemoteSorter>?, String?) -> RemoteData<RET> - ) { - val routeDef = "route${this::class.simpleName}${counter++}" - calls[function.toString().replace("\\s".toRegex(), "")] = Pair("/kv/$routeDef", HttpMethod.POST) - } - - /** - * Binds a given web socket connetion with a function of the receiver. - * @param function a function of the receiver - * @param route a web socket route - */ - protected actual inline fun <reified PAR1 : Any, reified PAR2 : Any> bind( - noinline function: suspend T.(ReceiveChannel<PAR1>, SendChannel<PAR2>) -> Unit, - route: String? - ) { - val routeDef = "route${this::class.simpleName}${counter++}" - calls[function.toString().replace("\\s".toRegex(), "")] = Pair("/kvws/$routeDef", HttpMethod.POST) - } - - /** - * Returns the map of defined paths. - */ - fun getCalls(): Map<String, Pair<String, HttpMethod>> = calls - -} diff --git a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt b/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt deleted file mode 100644 index 1fb90507..00000000 --- a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.remote - -import kotlinx.serialization.ImplicitReflectionSerializer -import kotlinx.serialization.KSerializer -import kotlinx.serialization.internal.BooleanSerializer -import kotlinx.serialization.internal.ByteSerializer -import kotlinx.serialization.internal.CharSerializer -import kotlinx.serialization.internal.DoubleSerializer -import kotlinx.serialization.internal.FloatSerializer -import kotlinx.serialization.internal.IntSerializer -import kotlinx.serialization.internal.LongSerializer -import kotlinx.serialization.internal.ShortSerializer -import kotlinx.serialization.internal.StringSerializer -import kotlinx.serialization.list -import kotlinx.serialization.serializer -import pl.treksoft.kvision.types.JsonDateSerializer -import pl.treksoft.kvision.types.toStringInternal -import kotlin.js.Date -import kotlin.reflect.KClass - -internal class NotStandardTypeException(type: String) : Exception("Not a standard type: $type!") - -internal class NotEnumTypeException : Exception("Not the Enum type!") - -/** - * Interface for client side agent for JSON-RPC remote calls. - */ -interface RemoteAgent { - - /** - * @suppress - * Internal function - */ - @UseExperimental(ImplicitReflectionSerializer::class) - @Suppress("ComplexMethod", "TooGenericExceptionCaught", "NestedBlockDepth") - fun trySerialize(kClass: KClass<Any>, value: Any): String { - return if (value is List<*>) { - if (value.size > 0) { - when { - value[0] is String -> - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(StringSerializer.list as KSerializer<Any>, value) - value[0] is Date -> - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(JsonDateSerializer.list as KSerializer<Any>, value) - value[0] is Int -> - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(IntSerializer.list as KSerializer<Any>, value) - value[0] is Long -> - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(LongSerializer.list as KSerializer<Any>, value) - value[0] is Boolean -> - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(BooleanSerializer.list as KSerializer<Any>, value) - value[0] is Float -> - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(FloatSerializer.list as KSerializer<Any>, value) - value[0] is Double -> - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(DoubleSerializer.list as KSerializer<Any>, value) - value[0] is Char -> - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(CharSerializer.list as KSerializer<Any>, value) - value[0] is Short -> - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(ShortSerializer.list as KSerializer<Any>, value) - value[0] is Byte -> - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(ByteSerializer.list as KSerializer<Any>, value) - value[0] is Enum<*> -> "[" + value.joinToString(",") { "\"$it\"" } + "]" - else -> try { - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(kClass.serializer().list as KSerializer<Any>, value) - } catch (e: Throwable) { - try { - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(value[0]!!::class.serializer().list as KSerializer<Any>, value) - } catch (e: Throwable) { - try { - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(StringSerializer.list as KSerializer<Any>, value) - } catch (e: Throwable) { - value.toString() - } - } - } - } - } else { - "[]" - } - } else { - when (value) { - is Enum<*> -> "\"$value\"" - is String -> value - is Char -> "\"$value\"" - is Date -> "\"${value.toStringInternal()}\"" - else -> try { - @Suppress("UNCHECKED_CAST") - JSON.plain.stringify(kClass.serializer(), value) - } catch (e: Throwable) { - value.toString() - } - } - } - } - - /** - * @suppress - * Internal function - */ - @Suppress("UNCHECKED_CAST", "ComplexMethod") - fun <RET> deserialize(value: String, jsType: String): RET { - return when (jsType) { - "String" -> JSON.plain.parse(StringSerializer, value) as RET - "Number" -> JSON.plain.parse(DoubleSerializer, value) as RET - "Long" -> JSON.plain.parse(LongSerializer, value) as RET - "Boolean" -> JSON.plain.parse(BooleanSerializer, value) as RET - "BoxedChar" -> JSON.plain.parse(CharSerializer, value) as RET - "Short" -> JSON.plain.parse(ShortSerializer, value) as RET - "Date" -> JSON.plain.parse(JsonDateSerializer, value) as RET - "Byte" -> JSON.plain.parse(ByteSerializer, value) as RET - else -> throw NotStandardTypeException(jsType) - } - } - - /** - * @suppress - * Internal function - */ - @Suppress("UNCHECKED_CAST", "ComplexMethod") - fun <RET> deserializeList(value: String, jsType: String): List<RET> { - return when (jsType) { - "String" -> JSON.plain.parse(StringSerializer.list, value) as List<RET> - "Number" -> JSON.plain.parse(DoubleSerializer.list, value) as List<RET> - "Long" -> JSON.plain.parse(LongSerializer.list, value) as List<RET> - "Boolean" -> JSON.plain.parse(BooleanSerializer.list, value) as List<RET> - "BoxedChar" -> JSON.plain.parse(CharSerializer.list, value) as List<RET> - "Short" -> JSON.plain.parse(ShortSerializer.list, value) as List<RET> - "Date" -> JSON.plain.parse(JsonDateSerializer.list, value) as List<RET> - "Byte" -> JSON.plain.parse(ByteSerializer.list, value) as List<RET> - else -> throw NotStandardTypeException(jsType) - } - } - - /** - * @suppress - * Internal function - */ - @Suppress("TooGenericExceptionCaught", "ThrowsCount") - fun tryDeserializeEnum(kClass: KClass<Any>, value: String): Any { - return try { - if (kClass.asDynamic().jClass.`$metadata$`.interfaces[0].name == "Enum") { - findEnumValue(kClass, JSON.plain.parse(StringSerializer, value)) ?: throw NotEnumTypeException() - } else { - throw NotEnumTypeException() - } - } catch (e: Throwable) { - throw NotEnumTypeException() - } - } - - /** - * @suppress - * Internal function - */ - @Suppress("TooGenericExceptionCaught", "ThrowsCount") - fun tryDeserializeEnumList(kClass: KClass<Any>, value: String): List<Any> { - return try { - if (kClass.asDynamic().jClass.`$metadata$`.interfaces[0].name == "Enum") { - JSON.plain.parse(StringSerializer.list, value).map { - findEnumValue(kClass, JSON.plain.parse(StringSerializer, it)) ?: throw NotEnumTypeException() - } - } else { - throw NotEnumTypeException() - } - } catch (e: Throwable) { - throw NotEnumTypeException() - } - } - - fun findEnumValue(kClass: KClass<Any>, value: String): Any? { - return (kClass.asDynamic().jClass.values() as Array<Any>).find { - it.asDynamic().name == value - } - } -} diff --git a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/Security.kt b/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/Security.kt deleted file mode 100644 index fd2b5cb0..00000000 --- a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/Security.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.remote - -import kotlinx.coroutines.asDeferred -import kotlinx.serialization.Serializable - -/** - * A security exception. - */ -class SecurityException(message: String) : Exception(message) - -/** - * Username and password credentials. - */ -@Serializable -data class Credentials(val username: String? = null, val password: String? = null) - -/** - * Form login dispatcher. - */ -class LoginService(val loginEndpoint: String) { - val loginAgent = CallAgent() - - /** - * Login with a form. - * @param credentials username and password credentials - */ - suspend fun login(credentials: Credentials?): Boolean = - if (credentials?.username != null) { - loginAgent.remoteCall(loginEndpoint, obj { - this.username = credentials.username - this.password = credentials.password - }, HttpMethod.POST, "application/x-www-form-urlencoded").then { _: dynamic -> true }.asDeferred().await() - } else { - throw SecurityException("Credentials cannot be empty") - } -} - -/** - * Form login dispatcher. - */ -abstract class SecurityMgr { - - private var isLoggedIn = false - - /** - * Executes given block of code after successful authentication. - * @param block a block of code - */ - @Suppress("NestedBlockDepth", "TooGenericExceptionCaught") - suspend fun <T> withAuth(block: suspend () -> T): T { - return try { - block().also { - if (!isLoggedIn) { - isLoggedIn = true - afterLogin() - } - } - } catch (e: Exception) { - if (e is SecurityException || !isLoggedIn) { - afterError() - isLoggedIn = false - while (!isLoggedIn) { - try { - login() - isLoggedIn = true - afterLogin() - } catch (e: Throwable) { - console.log(e) - } - } - block() - } else { - throw e - } - } - } - - /** - * Login user. - * @return true if login is successful - * @throws SecurityException if login is not successful - */ - abstract suspend fun login(): Boolean - - /** - * Method called after successful login. - */ - open suspend fun afterLogin() {} - - /** - * Method called after error. - */ - open suspend fun afterError() {} -} diff --git a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/Socket.kt b/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/Socket.kt deleted file mode 100644 index f668d57a..00000000 --- a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/Socket.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.remote - -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import org.w3c.dom.CloseEvent -import org.w3c.dom.ErrorEvent -import org.w3c.dom.MessageEvent -import org.w3c.dom.WebSocket -import org.w3c.dom.WebSocket.Companion.CLOSED -import org.w3c.dom.WebSocket.Companion.CLOSING -import org.w3c.dom.WebSocket.Companion.OPEN -import org.w3c.dom.events.Event -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -// -// Code taken from: https://discuss.kotlinlang.org/t/js-coroutines-experiements/8245/2 -// - -/** - * Websocket closed exception class. - */ -class SocketClosedException(val reason: String) : Throwable(reason) - -/** - * A websocket client implementation. - */ -class Socket { - private var eventQueue: Channel<Event> = Channel(Channel.UNLIMITED) - private lateinit var ws: WebSocket - val state: Short - get() = ws.readyState - - private fun onWsEvent(event: Event) { - GlobalScope.launch { eventQueue.send(event) } - } - - /** - * Connect to a websocket. - */ - suspend fun connect(url: String, retryDelay: Long = 1000) { - while (true) { - val connected = suspendCoroutine<Boolean> { cont -> - while (eventQueue.poll() != null) {/*drain*/ - } - ws = WebSocket(url) - ws.onopen = { - ws.onclose = ::onWsEvent - ws.onerror = ::onWsEvent - cont.resume(true) - } - ws.onmessage = ::onWsEvent - ws.onerror = { - logError(it) - } - ws.onclose = { - cont.resume(false) - } - } - if (connected) - break - else - delay(retryDelay) - } - } - - /** - * Receive a string from a websocket. - */ - @Suppress("ThrowsCount", "MagicNumber") - suspend fun receive(): String { - return when (val event = eventQueue.receive()) { - is MessageEvent -> { - event.data as String - } - is CloseEvent -> { - val reason = getReason(event.code) - throw SocketClosedException(reason) - } - is ErrorEvent -> { - logError(event) - close() - throw SocketClosedException(event.message) - } - else -> { - val reason = getReason(4001) - console.error(reason) - close() - throw SocketClosedException(reason) - } - } - } - - /** - * Send string to a websocket. - */ - @Suppress("MagicNumber") - fun send(obj: String) { - when { - isClosed() -> { - console.error(getReason(4002)) - throw SocketClosedException(getReason(4002)) - } - else -> ws.send(obj) - } - } - - /** - * Close a websocket. - */ - @Suppress("MagicNumber") - fun close(code: Short = 1000) { - when (state) { - OPEN -> ws.close(code, getReason(1000)) - } - } - - /** - * Returns if a websocket is closed. - */ - fun isClosed(): Boolean { - return when (state) { - CLOSED, CLOSING -> true - else -> false - } - } - - private fun logError(event: Event) = console.error("An error %o occurred when connecting to ${ws.url}", event) - - @Suppress("ComplexMethod", "MagicNumber") - private fun getReason(code: Short): String { - return when (code.toInt()) { // See http://tools.ietf.org/html/rfc6455#section-7.4.1 - 1000 -> "Normal closure" - 1001 -> "An endpoint is \"going away\", such as a server going down or " + - "a browser having navigated away from a page." - 1002 -> "An endpoint is terminating the connection due to a protocol error" - 1003 -> "An endpoint is terminating the connection because it has received a type of data it cannot " + - "accept (e.g., an endpoint that understands only text data MAY send this if it receives a " + - "binary message)." - 1004 -> "Reserved. The specific meaning might be defined in the future." - 1005 -> "No status code was actually present." - 1006 -> "The connection was closed abnormally, e.g., without sending or receiving a Close control frame" - 1007 -> "An endpoint is terminating the connection because it has received data within a message that " + - "was not consistent with the type of the message (e.g., non-UTF-8 " + - "[http://tools.ietf.org/html/rfc3629] data within a text message)." - 1008 -> "An endpoint is terminating the connection because it has received a message that " + - "\"violates its policy\". This reason is given either if there is no other sutible reason, or " + - "if there is a need to hide specific details about the policy." - 1009 -> "An endpoint is terminating the connection because it has received a message that is too big " + - "for it to process." - 1010 -> "An endpoint (client ) is terminating the connection because it has expected the server to " + - "negotiate one or more extension, but the server didn't return them in the response message of " + - "the WebSocket handshake. <br /> Specifically, the extensions that are needed are: " - 1011 -> "A server is terminating the connection because it encountered an unexpected condition that " + - "prevented it from fulfilling the request." - 1015 -> "The connection was closed due to a failure to perform a TLS handshake (e.g., the server " + - "certificate can't be verified)." - 4001 -> "Unexpected event" - 4002 -> "You are trying to use closed socket" - else -> "Unknown reason" - } - } -} diff --git a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/Utils.kt b/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/Utils.kt deleted file mode 100644 index 465fbf59..00000000 --- a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/Utils.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.remote - -import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonConfiguration -import kotlinx.serialization.modules.serializersModuleOf -import pl.treksoft.kvision.types.JsonDateSerializer -import kotlin.browser.window -import kotlin.js.Date - -/** - * JavaScript Object type - */ -external class Object - -/** - * Helper function for creating JavaScript objects. - */ -fun obj(init: dynamic.() -> Unit): dynamic { - return (Object()).apply(init) -} - -/** - * JSON utility functions - */ -object JSON { - - val plain = Json(context = serializersModuleOf(Date::class, JsonDateSerializer)) - - val nonstrict = Json( - configuration = JsonConfiguration.Stable.copy(strictMode = false), - context = serializersModuleOf(Date::class, JsonDateSerializer) - ) - - /** - * An extension function to convert Serializable object to JS dynamic object - * @param serializer a serializer for T - */ - fun <T> T.toObj(serializer: SerializationStrategy<T>): dynamic { - return kotlin.js.JSON.parse(plain.stringify(serializer, this)) - } -} - -/** - * Creates a websocket URL from current window.location and given path. - */ -fun getWebSocketUrl(url: String): String { - val location = window.location - val scheme = if (location.protocol == "https:") "wss" else "ws" - val port = if (location.port == "8088") ":8080" - else if (location.port != "0" && location.port != "") ":${location.port}" else "" - return "$scheme://${location.hostname}$port$url" -} diff --git a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/types/Date.kt b/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/types/Date.kt deleted file mode 100644 index 78370a70..00000000 --- a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/types/Date.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.types - -import kotlinx.serialization.Decoder -import kotlinx.serialization.Encoder -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialDescriptor -import kotlinx.serialization.internal.SerialClassDescImpl -import kotlin.js.Date -import kotlin.math.absoluteValue - -actual typealias LocalDateTime = Date - -actual typealias LocalDate = Date - -actual typealias LocalTime = Date - -actual typealias OffsetDateTime = Date - -actual typealias OffsetTime = Date - -/** - * JSON date serializer. - */ -internal object JsonDateSerializer : KSerializer<Date> { - override val descriptor: SerialDescriptor = SerialClassDescImpl("kotlin.js.Date") - - override fun deserialize(decoder: Decoder): Date { - return decoder.decodeString().toDateInternal() - } - - override fun serialize(encoder: Encoder, obj: Date) { - encoder.encodeString(obj.toStringInternal()) - } -} - -@Suppress("ComplexMethod", "MagicNumber") -internal fun String.toDateInternal(): Date { - val dt = this.split(':', 'T', '-', '+') - val utcCheck = this[length - 1] == 'Z' - val ds = if (utcCheck) dt[5].dropLast(1).split(".") else dt[5].split(".") - val tzCheck = this[length - 6] - return if (!utcCheck && tzCheck != '-' && tzCheck != '+') { - Date( - dt[0].toInt(), - dt[1].toInt() - 1, - dt[2].toInt(), - dt[3].toInt(), - dt[4].toInt(), - ds[0].toInt(), - if (ds.size == 2) ds[1].toInt() else 0 - ) - } else { - val sign = if (utcCheck || tzCheck == '+') 1 else -1 - Date( - Date.UTC( - dt[0].toInt(), - dt[1].toInt() - 1, - dt[2].toInt(), - if (utcCheck) { dt[3].toInt() } else { dt[3].toInt() - sign * dt[6].toInt() }, - dt[4].toInt(), - ds[0].toInt(), - if (ds.size == 2) ds[1].toInt() else 0 - ) - ) - } -} - -internal fun Date.toStringInternal(): String { - @Suppress("MagicNumber") - val tz = this.getTimezoneOffset() / 60 - val sign = if (tz > 0) "-" else "+" - return "" + this.getFullYear() + "-" + ("0" + (this.getMonth() + 1)).takeLast(2) + "-" + - ("0" + this.getDate()).takeLast(2) + "T" + ("0" + this.getHours()).takeLast(2) + ":" + - ("0" + this.getMinutes()).takeLast(2) + ":" + ("0" + this.getSeconds()).takeLast(2) + "." + - this.getMilliseconds() + sign + ("0${tz.absoluteValue}").takeLast(2) + ":00" -} diff --git a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/types/Decimal.kt b/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/types/Decimal.kt deleted file mode 100644 index d1c0366e..00000000 --- a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/types/Decimal.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.types - -actual typealias Decimal = Double |