Skip to content
Merged
Show file tree
Hide file tree
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
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ atomicfu = "0.29.0"
coroutines = "1.10.2"
jna = "5.18.1"
jvm-toolchain = "17"
kotlin = "2.2.20"
kotlin = "2.2.21"
Copy link
Member

Choose a reason for hiding this comment

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

This is fine, just curious if this bump was needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

2.2.21 contained some fixes around exception handling for WASM in Safari based browsers

tuulbox = "8.1.0"

[libraries]
Expand All @@ -16,6 +16,7 @@ equalsverifier = { module = "nl.jqno.equalsverifier:equalsverifier", version = "
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
khronicle = { module = "com.juul.khronicle:khronicle-core", version = "0.6.0" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser", version = "0.5.0" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
Expand All @@ -26,6 +27,7 @@ tomlkt = { module = "net.peanuuutz.tomlkt:tomlkt", version = "0.5.0" }
tuulbox-collections = { module = "com.juul.tuulbox:collections", version.ref = "tuulbox" }
tuulbox-coroutines = { module = "com.juul.tuulbox:coroutines", version.ref = "tuulbox" }
wrappers-bom = { module = "org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom", version = "2025.12.6" }
wrappers-browser = { module = "org.jetbrains.kotlin-wrappers:kotlin-browser" }
wrappers-web = { module = "org.jetbrains.kotlin-wrappers:kotlin-web" }

[plugins]
Expand Down
7 changes: 5 additions & 2 deletions kable-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,19 @@ kotlin {
macosArm64()
macosX64()
jvm()
wasmJs().browser()

sourceSets {
all {
languageSettings {
optIn("kotlin.js.ExperimentalWasmJsInterop")
optIn("kotlin.uuid.ExperimentalUuidApi")
}
}

commonMain.dependencies {
api(libs.kotlinx.coroutines.core)
api(libs.kotlinx.io)
implementation(libs.tuulbox.collections)
}

commonTest.dependencies {
Expand Down Expand Up @@ -59,7 +60,9 @@ kotlin {
implementation(libs.robolectric)
}

jsMain.dependencies {
webMain.dependencies {
api(libs.kotlinx.browser)
api(libs.wrappers.browser)
api(libs.wrappers.web)
api(project.dependencies.platform(libs.wrappers.bom))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.juul.kable

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
import kotlinx.coroutines.Job
Expand All @@ -22,6 +21,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import kotlin.coroutines.cancellation.CancellationException
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
Expand Down
36 changes: 0 additions & 36 deletions kable-core/src/jsMain/kotlin/Bluetooth.kt

This file was deleted.

This file was deleted.

7 changes: 0 additions & 7 deletions kable-core/src/jsMain/kotlin/external/Navigator.kt

This file was deleted.

6 changes: 6 additions & 0 deletions kable-core/src/jsMain/kotlin/interop/Await.js.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.juul.kable.interop

import kotlin.js.Promise
import kotlinx.coroutines.await as kotlinxAwait

internal actual suspend fun <T : JsAny?> Promise<T>.await(): T = kotlinxAwait()
48 changes: 0 additions & 48 deletions kable-core/src/jsMain/kotlin/logs/SystemLogEngine.kt

This file was deleted.

9 changes: 0 additions & 9 deletions kable-core/src/jsTest/kotlin/Environment.kt

This file was deleted.

6 changes: 6 additions & 0 deletions kable-core/src/wasmJsMain/kotlin/interop/Await.wasmJs.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.juul.kable.interop

import kotlin.js.Promise
import kotlinx.coroutines.await as kotlinxAwait

internal actual suspend fun <T : JsAny?> Promise<T>.await(): T = kotlinxAwait()
27 changes: 27 additions & 0 deletions kable-core/src/webMain/kotlin/Bluetooth.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.juul.kable

import com.juul.kable.external.Bluetooth
import com.juul.kable.external.getBluetooth
import web.navigator.Navigator
import kotlin.js.js
import kotlin.js.undefined

private val navigator: Navigator =
js("window.navigator")

/**
* @return [Bluetooth] object or `null` if bluetooth is [unavailable](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth#browser_compatibility).
*/
internal fun bluetoothOrNull(): Bluetooth? =
getBluetooth(navigator).takeIf { it !== undefined }

/**
* @throws IllegalStateException If bluetooth is [unavailable](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth#browser_compatibility).
*/
internal fun bluetoothOrThrow(): Bluetooth {
val bluetooth = getBluetooth(navigator)
if (bluetooth === undefined) {
error("Bluetooth unavailable")
}
return bluetooth
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ package com.juul.kable

import com.juul.kable.external.BluetoothAdvertisingEvent
import com.juul.kable.external.BluetoothDevice
import com.juul.kable.external.iterable
import js.array.component1
import js.array.component2
import js.iterable.iterator
import org.khronos.webgl.DataView
import kotlin.js.toInt
import kotlin.js.toJsNumber
import kotlin.js.toJsString
import kotlin.js.toList
import kotlin.uuid.Uuid

internal class BluetoothAdvertisingEventWebBluetoothAdvertisement(
Expand Down Expand Up @@ -32,7 +38,7 @@ internal class BluetoothAdvertisingEventWebBluetoothAdvertisement(
get() = advertisement.txPower

override val uuids: List<Uuid>
get() = advertisement.uuids.map { it.toUuid() }
get() = advertisement.uuids.toList().map { it.toString().toUuid() }

override fun serviceData(uuid: Uuid): ByteArray? =
serviceDataAsDataView(uuid)?.buffer?.toByteArray()
Expand All @@ -41,22 +47,19 @@ internal class BluetoothAdvertisingEventWebBluetoothAdvertisement(
manufacturerDataAsDataView(companyIdentifierCode)?.buffer?.toByteArray()

override fun serviceDataAsDataView(uuid: Uuid): DataView? =
advertisement.serviceData.asDynamic().get(uuid.toString()) as? DataView
advertisement.serviceData.get(uuid.toString().toJsString())

override fun manufacturerDataAsDataView(companyIdentifierCode: Int): DataView? =
advertisement.manufacturerData.asDynamic().get(companyIdentifierCode.toString()) as? DataView
advertisement.manufacturerData.get(companyIdentifierCode.toJsNumber())

override val manufacturerData: ManufacturerData?
get() = advertisement.manufacturerData.entries().iterable().firstOrNull()?.let { entry ->
ManufacturerData(
entry[0] as Int,
(entry[1] as DataView).buffer.toByteArray(),
)
get() = Iterable { advertisement.manufacturerData.entries().iterator() }.firstOrNull()?.let { (key, value) ->
ManufacturerData(key.toInt(), value.buffer.toByteArray())
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class.js != other::class.js) return false
if (other == null || this::class != other::class) return false
other as BluetoothAdvertisingEventWebBluetoothAdvertisement
return advertisement == other.advertisement
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import com.juul.kable.Bluetooth.Availability.Available
import com.juul.kable.Bluetooth.Availability.Unavailable
import com.juul.kable.Reason.BluetoothUndefined
import com.juul.kable.external.BluetoothAvailabilityChanged
import kotlinx.coroutines.await
import com.juul.kable.interop.await
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onStart
import org.w3c.dom.events.Event
import web.events.EventType
import web.events.addEventListener
import web.events.removeEventListener

@Deprecated(
message = "`Bluetooth.availability` has inconsistent behavior across platforms. " +
Expand All @@ -27,14 +29,14 @@ public actual enum class Reason {
BluetoothUndefined,
}

private const val AVAILABILITY_CHANGED = "availabilitychanged"
private val AVAILABILITY_CHANGED = EventType<BluetoothAvailabilityChanged>("availabilitychanged")

internal actual val bluetoothAvailability: Flow<Bluetooth.Availability> =
bluetoothOrNull()?.let { bluetooth ->
callbackFlow {
// https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/onavailabilitychanged
val listener: (Event) -> Unit = { event ->
val isAvailable = event.unsafeCast<BluetoothAvailabilityChanged>().value
val listener: (BluetoothAvailabilityChanged) -> Unit = { event ->
val isAvailable = event.value
trySend(if (isAvailable) Available else Unavailable(reason = null))
}

Expand Down
Loading
Loading