Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose JavaScript peripheral advertisments #95

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions core/src/jsMain/kotlin/Peripheral.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import kotlinx.coroutines.Deferred
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.await
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.job
import kotlinx.coroutines.suspendCancellableCoroutine
import org.khronos.webgl.DataView
import kotlin.coroutines.CoroutineContext
import org.w3c.dom.events.Event as JsEvent
Expand Down Expand Up @@ -59,23 +61,14 @@ public class JsPeripheral internal constructor(

private val supportsAdvertisements = js("BluetoothDevice.prototype.watchAdvertisements") != null

public override suspend fun rssi(): Int = suspendCancellableCoroutine { continuation ->
public val advertisements: Flow<Advertisement> = callbackFlow {
check(supportsAdvertisements) { "watchAdvertisements unavailable" }

lateinit var listener: (JsEvent) -> Unit
val cleanup = {
bluetoothDevice.removeEventListener(ADVERTISEMENT_RECEIVED, listener)
// At the time of writing `unwatchAdvertisements()` remains unimplemented
if (bluetoothDevice.watchingAdvertisements && js("BluetoothDevice.prototype.unwatchAdvertisements") != null) {
bluetoothDevice.unwatchAdvertisements()
}
}

listener = {
val event = it as BluetoothAdvertisingEvent
cleanup()
if (continuation.isActive && event.rssi != null) {
continuation.resume(event.rssi, onCancellation = null)
val listener: (JsEvent) -> Unit = {
runCatching {
offer(Advertisement(it as BluetoothAdvertisingEvent))
}.onFailure {
Comment on lines +68 to +70
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than use runCatching, we should bump the Coroutines version and use trySend.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick scan shows five other instances of runCatching in this repo - perhaps the use of this pattern should be reviewed more broadly as a separate issue?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, I am going to update the other usages soon. 👍

console.warn("Unable to deliver advertisement event due to failure in flow or premature closing.")
}
}

Expand All @@ -84,11 +77,18 @@ public class JsPeripheral internal constructor(
}
bluetoothDevice.addEventListener(ADVERTISEMENT_RECEIVED, listener)

continuation.invokeOnCancellation {
cleanup()
awaitClose {
bluetoothDevice.removeEventListener(ADVERTISEMENT_RECEIVED, listener)
// At the time of writing `unwatchAdvertisements()` remains unimplemented
if (bluetoothDevice.watchingAdvertisements && js("BluetoothDevice.prototype.unwatchAdvertisements") != null) {
bluetoothDevice.unwatchAdvertisements()
}
}
}

public override suspend fun rssi(): Int =
advertisements.first().rssi

private val gatt: BluetoothRemoteGATTServer
get() = bluetoothDevice.gatt!! // fixme: !!

Expand Down