Skip to content

Commit

Permalink
Improve how suspending API calls are exported to javascript as Promise
Browse files Browse the repository at this point in the history
  • Loading branch information
floscher committed Aug 26, 2024
1 parent b63b22d commit 5ba0cc7
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -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<SipgateApiV2.Contacts, PagedList<Contact>, Array<Contact>>(SipgateApiV2.Contacts()) { it.items.toTypedArray() }
val history = basicGet<SipgateApiV2.History, PagedList<HistoryEntry>, Array<HistoryEntry>>(SipgateApiV2.History()) { it.items.toTypedArray() }
fun userDevices(userId: String) = basicGet<SipgateApiV2.UserId.Devices, BasicList<Device>, Array<Device>>(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 <reified R: Any, reified T: Any> basicGet(resource: R): suspend ((T) -> Unit, (Throwable) -> Unit) -> Unit = basicGet<R, T, T>(resource) { it }

private inline fun <reified R: Any, reified S: Any, T: Any> 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<S>()))
} 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}!"))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
@@ -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<UserInfo> = normalGetPromise(
credentials,
SipgateApiV2.Authorization.UserInfo()
)
fun contacts(): Promise<Array<Contact>> = asPromise(api.contacts)
fun history(): Promise<Array<HistoryEntry>> = asPromise(api.history)
fun userDevices(userId: String): Promise<Array<Device>> = asPromise(api.userDevices(userId))
fun userInfo(): Promise<UserInfo> = asPromise(api.userInfo)

fun contacts(
credentials: SipgateApiCredentials
): Promise<Array<Contact>> = normalGetPromise<SipgateApiV2.Contacts, PagedList<Contact>, Array<Contact>>(
credentials,
SipgateApiV2.Contacts()
) { it.items.toTypedArray() }

fun history(
credentials: SipgateApiCredentials
): Promise<Array<HistoryEntry>> = normalGetPromise<SipgateApiV2.History, PagedList<HistoryEntry>, Array<HistoryEntry>>(
credentials,
SipgateApiV2.History(limit = 50)
) { it.items.toTypedArray() }

fun userDevices(
credentials: SipgateApiCredentials,
userId: String
): Promise<Array<Device>> = normalGetPromise<SipgateApiV2.UserId.Devices, BasicList<Device>, Array<Device>>(
credentials,
SipgateApiV2.UserId.Devices(SipgateApiV2.UserId(userId = userId))
) { it.items.toTypedArray() }

private inline fun <reified R: Any, reified T: Any> normalGetPromise(credential: SipgateApiCredentials, resource: R): Promise<T> = normalGetPromise<R, T, T>(credential, resource) { it }

private inline fun <reified R: Any, reified S: Any, reified T: Any> normalGetPromise(credential: SipgateApiCredentials, resource: R, crossinline f: (S) -> T): Promise<T> = Promise { resolve, reject ->
private fun <T: Any> asPromise(
f: suspend ((T) -> Unit, (Throwable) -> Unit) -> Unit
): Promise<T> = 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<S>()))
} 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)
}
}
}
4 changes: 2 additions & 2 deletions web/src/sipgator-login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -64,7 +64,7 @@ export class SipgatorLogin extends LitElement {
<input type="password" placeholder="Token" name="password" .value="${ this.newUser.token }" @input="${ this.setNewPassword }">
</div>
<button @click="${ () => {
JsSipgateApiV2.getInstance().userInfo(this.newUser)
new JsSipgateApiV2(this.newUser).userInfo()
.then(ui => {
StorageUtil.getInstance().currentCredential = this.newUser
this.reloadCredentials()
Expand Down
2 changes: 1 addition & 1 deletion web/src/sipgator-page-contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class SipgatorPageContacts extends SipgatorPage {
}

async contactComponent(credential: SipgateApiCredentials) {
return JsSipgateApiV2.getInstance().contacts(credential)
return new JsSipgateApiV2(credential).contacts()
.then(it =>
html`${it.map(c => html`<a class="row padding surface-container">
<div class="max">
Expand Down
2 changes: 1 addition & 1 deletion web/src/sipgator-page-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class SipgatorPageHistory extends SipgatorPage {
}

private async historyComponent(credential: SipgateApiCredentials) {
return JsSipgateApiV2.getInstance().history(credential).then(it => it.map(h => html`
return new JsSipgateApiV2(credential).history().then(it => it.map(h => html`
<div class="row padding surface-container wave">
<i>${h.incoming ? (h.status === "NOPICKUP" ? "call_missed" : "call_received") : "call_made"}</i>
<span class="badge none padding">${ h.type }</span>
Expand Down
6 changes: 4 additions & 2 deletions web/src/sipgator-page-numbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ export class SipgatorPageNumbers extends SipgatorPage {
}

private async numberComponent(credentials: SipgateApiCredentials) {
return JsSipgateApiV2.getInstance().userInfo(credentials).then(it =>
JsSipgateApiV2.getInstance().userDevices(credentials, it.sub)
const apiClient = new JsSipgateApiV2(credentials)
return apiClient.userInfo().then(it =>
apiClient.userDevices(it.sub)
.then(s => s.map(s2 => html`
<h2>${s2.alias}</h2>
<p>Auf diesem Gerät aktive eingehende Telefonleitungen:</p>
<p>${ s2.activePhonelines.concat(s2.activeGroups).map(l => html`<span class="chip">${l.alias}</span>`) }</p>
`))
.catch(e => html`Error getting user devices: ${e}`)
).catch(() => html`Error getting user info`)
}
}

0 comments on commit 5ba0cc7

Please sign in to comment.