From 5ba0cc7433d250c24e9e481706c97cdb9ae98ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Sch=C3=A4fer?= Date: Mon, 26 Aug 2024 10:29:39 +0200 Subject: [PATCH] Improve how suspending API calls are exported to javascript as Promise --- .../sipgator/common/api/CommonSipgateApiV2.kt | 57 ++++++++++++++ .../fumix/sipgator/common/api/SipgateApiV2.kt | 2 - .../de/fumix/sipgator/common/api/ColorUtil.kt | 2 +- .../sipgator/common/api/JsSipgateApiV2.kt | 75 +++---------------- web/src/sipgator-login.ts | 4 +- web/src/sipgator-page-contacts.ts | 2 +- web/src/sipgator-page-history.ts | 2 +- web/src/sipgator-page-numbers.ts | 6 +- 8 files changed, 76 insertions(+), 74 deletions(-) create mode 100644 common/src/commonMain/kotlin/de/fumix/sipgator/common/api/CommonSipgateApiV2.kt diff --git a/common/src/commonMain/kotlin/de/fumix/sipgator/common/api/CommonSipgateApiV2.kt b/common/src/commonMain/kotlin/de/fumix/sipgator/common/api/CommonSipgateApiV2.kt new file mode 100644 index 0000000..bbeaf9e --- /dev/null +++ b/common/src/commonMain/kotlin/de/fumix/sipgator/common/api/CommonSipgateApiV2.kt @@ -0,0 +1,57 @@ +package de.fumix.sipgator.common.api + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.plugins.resources.get +import io.ktor.client.request.accept +import io.ktor.client.request.basicAuth +import io.ktor.client.request.headers +import io.ktor.client.request.host +import io.ktor.http.ContentType +import io.ktor.http.URLProtocol +import io.ktor.http.isSuccess +import de.fumix.sipgator.common.api.dto.BasicList +import de.fumix.sipgator.common.api.dto.Contact +import de.fumix.sipgator.common.api.dto.Device +import de.fumix.sipgator.common.api.dto.HistoryEntry +import de.fumix.sipgator.common.api.dto.PagedList +import de.fumix.sipgator.common.api.dto.UserInfo +import de.fumix.sipgator.common.model.SipgateApiCredentials + +data class CommonSipgateApiV2(private val credential: SipgateApiCredentials) { + private val client: HttpClient = httpClient() + + val contacts = basicGet, Array>(SipgateApiV2.Contacts()) { it.items.toTypedArray() } + val history = basicGet, Array>(SipgateApiV2.History()) { it.items.toTypedArray() } + fun userDevices(userId: String) = basicGet, Array>(SipgateApiV2.UserId.Devices(SipgateApiV2.UserId(userId = userId))) { it.items.toTypedArray() } + val userInfo: suspend ((UserInfo) -> Unit, (Throwable) -> Unit) -> Unit = basicGet(SipgateApiV2.Authorization.UserInfo()) + + private inline fun basicGet(resource: R): suspend ((T) -> Unit, (Throwable) -> Unit) -> Unit = basicGet(resource) { it } + + private inline fun basicGet(resource: R, crossinline f: (S) -> T): suspend ((T) -> Unit, (Throwable) -> Unit) -> Unit { + return { resolve, reject -> + val response = client.get(resource) { + host = "api.sipgate.com" + url { + protocol = URLProtocol.HTTPS + } + headers { + accept(ContentType.Application.Json) + basicAuth(credential.tokenName, credential.token) + } + } + + if (response.status.isSuccess()) { + try { + resolve(f(response.body())) + } catch (e: Exception) { + println("A") + reject(SipgateApiException.Decode(response.status, "Failed to decode API response! ${e}")) + } + } else { + println("B") + reject(SipgateApiException.Http(response.status, "Request failed for resource ${resource::class.simpleName}!")) + } + } + } +} diff --git a/common/src/commonMain/kotlin/de/fumix/sipgator/common/api/SipgateApiV2.kt b/common/src/commonMain/kotlin/de/fumix/sipgator/common/api/SipgateApiV2.kt index 30e1995..20a9481 100644 --- a/common/src/commonMain/kotlin/de/fumix/sipgator/common/api/SipgateApiV2.kt +++ b/common/src/commonMain/kotlin/de/fumix/sipgator/common/api/SipgateApiV2.kt @@ -1,8 +1,6 @@ package de.fumix.sipgator.common.api -import io.ktor.client.plugins.resources.get import io.ktor.resources.Resource -import kotlin.js.JsExport @Resource("/v2") class SipgateApiV2 { diff --git a/common/src/jsMain/kotlin/de/fumix/sipgator/common/api/ColorUtil.kt b/common/src/jsMain/kotlin/de/fumix/sipgator/common/api/ColorUtil.kt index 2e713cc..3a60b9d 100644 --- a/common/src/jsMain/kotlin/de/fumix/sipgator/common/api/ColorUtil.kt +++ b/common/src/jsMain/kotlin/de/fumix/sipgator/common/api/ColorUtil.kt @@ -13,4 +13,4 @@ fun generateCssGradient(bytes: ByteArray): String = ( .take(3) .joinToString(", ", prefix = "linear-gradient(to bottom right, ", postfix = ")") -fun Byte.toRange(range: IntRange) = ((toUByte().toDouble() / UByte.MAX_VALUE.toDouble()) * (range.last - range.first) + range.first).roundToInt().coerceIn(range) +fun Byte.toRange(range: IntRange): Int = ((toUByte().toDouble() / UByte.MAX_VALUE.toDouble()) * (range.last - range.first) + range.first).roundToInt().coerceIn(range) diff --git a/common/src/jsMain/kotlin/de/fumix/sipgator/common/api/JsSipgateApiV2.kt b/common/src/jsMain/kotlin/de/fumix/sipgator/common/api/JsSipgateApiV2.kt index 5672bdf..98eadae 100644 --- a/common/src/jsMain/kotlin/de/fumix/sipgator/common/api/JsSipgateApiV2.kt +++ b/common/src/jsMain/kotlin/de/fumix/sipgator/common/api/JsSipgateApiV2.kt @@ -1,84 +1,29 @@ package de.fumix.sipgator.common.api -import io.ktor.client.call.body -import io.ktor.client.plugins.resources.get -import io.ktor.client.request.accept -import io.ktor.client.request.basicAuth -import io.ktor.client.request.host -import io.ktor.client.statement.bodyAsText -import io.ktor.http.ContentType -import io.ktor.http.URLProtocol -import io.ktor.http.headers -import io.ktor.http.isSuccess -import kotlin.js.ExperimentalJsExport -import kotlin.js.JsExport import kotlin.js.Promise import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -import de.fumix.sipgator.common.api.dto.BasicList import de.fumix.sipgator.common.api.dto.Contact import de.fumix.sipgator.common.api.dto.Device import de.fumix.sipgator.common.api.dto.HistoryEntry -import de.fumix.sipgator.common.api.dto.PagedList import de.fumix.sipgator.common.api.dto.UserInfo import de.fumix.sipgator.common.model.SipgateApiCredentials -@OptIn(ExperimentalJsExport::class) @JsExport -object JsSipgateApiV2 { - private val client = httpClient() +class JsSipgateApiV2(credentials: SipgateApiCredentials) { + private val api = CommonSipgateApiV2(credentials) - fun userInfo( - credentials: SipgateApiCredentials - ): Promise = normalGetPromise( - credentials, - SipgateApiV2.Authorization.UserInfo() - ) + fun contacts(): Promise> = asPromise(api.contacts) + fun history(): Promise> = asPromise(api.history) + fun userDevices(userId: String): Promise> = asPromise(api.userDevices(userId)) + fun userInfo(): Promise = asPromise(api.userInfo) - fun contacts( - credentials: SipgateApiCredentials - ): Promise> = normalGetPromise, Array>( - credentials, - SipgateApiV2.Contacts() - ) { it.items.toTypedArray() } - - fun history( - credentials: SipgateApiCredentials - ): Promise> = normalGetPromise, Array>( - credentials, - SipgateApiV2.History(limit = 50) - ) { it.items.toTypedArray() } - - fun userDevices( - credentials: SipgateApiCredentials, - userId: String - ): Promise> = normalGetPromise, Array>( - credentials, - SipgateApiV2.UserId.Devices(SipgateApiV2.UserId(userId = userId)) - ) { it.items.toTypedArray() } - - private inline fun normalGetPromise(credential: SipgateApiCredentials, resource: R): Promise = normalGetPromise(credential, resource) { it } - - private inline fun normalGetPromise(credential: SipgateApiCredentials, resource: R, crossinline f: (S) -> T): Promise = Promise { resolve, reject -> + private fun asPromise( + f: suspend ((T) -> Unit, (Throwable) -> Unit) -> Unit + ): Promise = Promise { resolve, reject -> GlobalScope.launch(Dispatchers.Default) { - val response = client.get(resource) { - host = "api.sipgate.com" - url { protocol = URLProtocol.HTTPS } - headers { - accept(ContentType.Application.Json) - basicAuth(credential.tokenName, credential.token) - } - } - if (response.status.isSuccess()) { - try { - resolve(f(response.body())) - } catch (e: Exception) { - reject(SipgateApiException.Decode(response.status, "Failed to decode API response! " + e.message)) - } - } else { - reject(SipgateApiException.Http(response.status, "Request failed for resource ${resource::class.simpleName}!")) - } + f(resolve, reject) } } } diff --git a/web/src/sipgator-login.ts b/web/src/sipgator-login.ts index 64bc01f..ccc6af4 100644 --- a/web/src/sipgator-login.ts +++ b/web/src/sipgator-login.ts @@ -2,7 +2,7 @@ import { css, html, LitElement } from "lit" import { customElement, property, state } from "lit/decorators.js" import { classMap } from "lit/directives/class-map.js" import { until } from "lit/directives/until.js" -import { JsSipgateApiV2, SipgateApiCredentials, StorageUtil, Nullable, generateCssGradient } from "SipGator-common" +import { generateCssGradient, JsSipgateApiV2, Nullable, SipgateApiCredentials, StorageUtil } from "SipGator-common" import { CredentialUpdateEvent } from "./credential-update-event.ts" import { showSnackbar } from "./util-snackbar.ts" @@ -64,7 +64,7 @@ export class SipgatorLogin extends LitElement {