diff --git a/README.md b/README.md index 01e44c0cf..6ae81f7af 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,9 @@ peripheral.launch { > ```kotlin > peripheral.cancel() > ``` +> +> Once a [`Peripheral`] is cancelled (via `cancel`) it can no longer be used (e.g. calling `connect` will throw +> `IllegalStateException`). > [!TIP] > `launch`ed coroutines from a `Peripheral` object are permitted to run until `Peripheral.cancel()` is called diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2eea5640d..bbf4c437d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] android-compile = "34" android-min = "21" -atomicfu = "0.26.0" +atomicfu = "0.26.1" coroutines = "1.9.0" jvm-toolchain = "11" kotlin = "2.0.21" diff --git a/kable-core/api/android/kable-core.api b/kable-core/api/android/kable-core.api index 9fc2ecb46..30fd8754d 100644 --- a/kable-core/api/android/kable-core.api +++ b/kable-core/api/android/kable-core.api @@ -246,7 +246,7 @@ public final class com/juul/kable/IdentifierKt { public static final fun toIdentifier (Ljava/lang/String;)Ljava/lang/String; } -public final class com/juul/kable/InternalException : java/lang/IllegalStateException { +public final class com/juul/kable/InternalError : java/lang/Error { } public final class com/juul/kable/Kable { @@ -319,6 +319,7 @@ public abstract interface class com/juul/kable/Peripheral : kotlinx/coroutines/C public abstract fun getName ()Ljava/lang/String; public abstract fun getServices ()Lkotlinx/coroutines/flow/StateFlow; public abstract fun getState ()Lkotlinx/coroutines/flow/StateFlow; + public abstract fun maximumWriteValueLengthForType (Lcom/juul/kable/WriteType;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun observe (Lcom/juul/kable/Characteristic;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; public abstract fun read (Lcom/juul/kable/Characteristic;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun read (Lcom/juul/kable/Descriptor;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -350,8 +351,10 @@ public final class com/juul/kable/PeripheralBuilder { public final class com/juul/kable/PeripheralKt { public static final fun Peripheral (Landroid/bluetooth/BluetoothDevice;Lkotlin/jvm/functions/Function1;)Lcom/juul/kable/Peripheral; public static final fun Peripheral (Lcom/juul/kable/Advertisement;Lkotlin/jvm/functions/Function1;)Lcom/juul/kable/Peripheral; + public static final fun Peripheral (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/juul/kable/Peripheral; public static synthetic fun Peripheral$default (Landroid/bluetooth/BluetoothDevice;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/juul/kable/Peripheral; public static synthetic fun Peripheral$default (Lcom/juul/kable/Advertisement;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/juul/kable/Peripheral; + public static synthetic fun Peripheral$default (Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/juul/kable/Peripheral; } public final class com/juul/kable/Peripheral_deprecatedKt { diff --git a/kable-core/api/jvm/kable-core.api b/kable-core/api/jvm/kable-core.api index b0cd45197..85f52dd9d 100644 --- a/kable-core/api/jvm/kable-core.api +++ b/kable-core/api/jvm/kable-core.api @@ -179,7 +179,7 @@ public final class com/juul/kable/IdentifierKt { public static final fun toIdentifier (Ljava/lang/String;)Ljava/lang/String; } -public final class com/juul/kable/InternalException : java/lang/IllegalStateException { +public final class com/juul/kable/InternalError : java/lang/Error { } public final class com/juul/kable/LazyCharacteristic : com/juul/kable/Characteristic { @@ -235,6 +235,7 @@ public abstract interface class com/juul/kable/Peripheral : kotlinx/coroutines/C public abstract fun getName ()Ljava/lang/String; public abstract fun getServices ()Lkotlinx/coroutines/flow/StateFlow; public abstract fun getState ()Lkotlinx/coroutines/flow/StateFlow; + public abstract fun maximumWriteValueLengthForType (Lcom/juul/kable/WriteType;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun observe (Lcom/juul/kable/Characteristic;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; public abstract fun read (Lcom/juul/kable/Characteristic;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun read (Lcom/juul/kable/Descriptor;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/kable-core/src/androidMain/kotlin/BluetoothDeviceAndroidPeripheral.kt b/kable-core/src/androidMain/kotlin/BluetoothDeviceAndroidPeripheral.kt index a6309587f..6f4255721 100644 --- a/kable-core/src/androidMain/kotlin/BluetoothDeviceAndroidPeripheral.kt +++ b/kable-core/src/androidMain/kotlin/BluetoothDeviceAndroidPeripheral.kt @@ -48,6 +48,9 @@ import kotlin.uuid.toKotlinUuid // https://github.com/JuulLabs/kable/issues/295 private const val DISCOVER_SERVICES_RETRIES = 5 +private const val DEFAULT_ATT_MTU = 23 +private const val ATT_MTU_HEADER_SIZE = 3 + internal class BluetoothDeviceAndroidPeripheral( private val bluetoothDevice: BluetoothDevice, private val autoConnectPredicate: () -> Boolean, @@ -146,7 +149,7 @@ internal class BluetoothDeviceAndroidPeripheral( } override suspend fun connect(): CoroutineScope = - connectAction.await() + connectAction.awaitConnect() override suspend fun disconnect() { connectAction.cancelAndJoin( @@ -164,6 +167,9 @@ internal class BluetoothDeviceAndroidPeripheral( .requestConnectionPriority(priority.intValue) } + override suspend fun maximumWriteValueLengthForType(writeType: WriteType): Int = + (mtu.value ?: DEFAULT_ATT_MTU) - ATT_MTU_HEADER_SIZE + @ExperimentalApi // Experimental until Web Bluetooth advertisements APIs are stable. override suspend fun rssi(): Int = connectionOrThrow().execute { diff --git a/kable-core/src/androidMain/kotlin/Connection.kt b/kable-core/src/androidMain/kotlin/Connection.kt index 1d069455e..9c937552a 100644 --- a/kable-core/src/androidMain/kotlin/Connection.kt +++ b/kable-core/src/androidMain/kotlin/Connection.kt @@ -183,7 +183,7 @@ internal class Connection( // `guard` should always enforce a 1:1 matching of request-to-response, but if an Android // `BluetoothGattCallback` method is called out-of-order then we'll cast to the wrong type. return response as? T - ?: throw InternalException( + ?: throw InternalError( "Expected response type ${type.simpleName} but received ${response::class.simpleName}", ) } diff --git a/kable-core/src/androidMain/kotlin/Peripheral.kt b/kable-core/src/androidMain/kotlin/Peripheral.kt index 5b48c3419..176d770a0 100644 --- a/kable-core/src/androidMain/kotlin/Peripheral.kt +++ b/kable-core/src/androidMain/kotlin/Peripheral.kt @@ -10,6 +10,12 @@ public actual fun Peripheral( return Peripheral(advertisement.bluetoothDevice, builderAction) } +/** @throws IllegalStateException If bluetooth is not supported. */ +public fun Peripheral( + identifier: Identifier, + builderAction: PeripheralBuilderAction = {}, +): Peripheral = Peripheral(getBluetoothAdapter().getRemoteDevice(identifier), builderAction) + public fun Peripheral( bluetoothDevice: BluetoothDevice, builderAction: PeripheralBuilderAction = {}, diff --git a/kable-core/src/androidMain/kotlin/bluetooth/CheckBluetoothIsOn.kt b/kable-core/src/androidMain/kotlin/bluetooth/CheckBluetoothIsOn.kt index 4db4b1cf1..a05ee88dc 100644 --- a/kable-core/src/androidMain/kotlin/bluetooth/CheckBluetoothIsOn.kt +++ b/kable-core/src/androidMain/kotlin/bluetooth/CheckBluetoothIsOn.kt @@ -4,7 +4,7 @@ import android.bluetooth.BluetoothAdapter.STATE_OFF import android.bluetooth.BluetoothAdapter.STATE_ON import android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF import android.bluetooth.BluetoothAdapter.STATE_TURNING_ON -import com.juul.kable.InternalException +import com.juul.kable.InternalError import com.juul.kable.UnmetRequirementException import com.juul.kable.UnmetRequirementReason.BluetoothDisabled import com.juul.kable.getBluetoothAdapter @@ -29,5 +29,5 @@ private fun nameFor(state: Int) = when (state) { STATE_ON -> "STATE_ON" STATE_TURNING_OFF -> "STATE_TURNING_OFF" STATE_TURNING_ON -> "STATE_TURNING_ON" - else -> throw InternalException("Unsupported bluetooth state: $state") + else -> throw InternalError("Unsupported bluetooth state: $state") } diff --git a/kable-core/src/androidMain/kotlin/scan/ScanError.kt b/kable-core/src/androidMain/kotlin/scan/ScanError.kt index 6301814f8..1a98c772b 100644 --- a/kable-core/src/androidMain/kotlin/scan/ScanError.kt +++ b/kable-core/src/androidMain/kotlin/scan/ScanError.kt @@ -6,7 +6,7 @@ import android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED import android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR import android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES import android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY -import com.juul.kable.InternalException +import com.juul.kable.InternalError @JvmInline internal value class ScanError(internal val errorCode: Int) { @@ -18,7 +18,7 @@ internal value class ScanError(internal val errorCode: Int) { SCAN_FAILED_FEATURE_UNSUPPORTED -> "SCAN_FAILED_FEATURE_UNSUPPORTED" SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES -> "SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES" SCAN_FAILED_SCANNING_TOO_FREQUENTLY -> "SCAN_FAILED_SCANNING_TOO_FREQUENTLY" - else -> throw InternalException("Unsupported error code $errorCode") + else -> throw InternalError("Unsupported error code $errorCode") }.let { name -> "$name($errorCode)" } } @@ -43,5 +43,5 @@ internal val ScanError.message: String SCAN_FAILED_SCANNING_TOO_FREQUENTLY -> "Failed to start scan as application tries to scan too frequently" - else -> throw InternalException("Unsupported error code $errorCode") + else -> throw InternalError("Unsupported error code $errorCode") } diff --git a/kable-core/src/appleMain/kotlin/CBPeripheralCoreBluetoothPeripheral.kt b/kable-core/src/appleMain/kotlin/CBPeripheralCoreBluetoothPeripheral.kt index aa1dfb59a..0c12abab3 100644 --- a/kable-core/src/appleMain/kotlin/CBPeripheralCoreBluetoothPeripheral.kt +++ b/kable-core/src/appleMain/kotlin/CBPeripheralCoreBluetoothPeripheral.kt @@ -27,6 +27,8 @@ import kotlinx.coroutines.flow.onSubscription import kotlinx.coroutines.flow.updateAndGet import kotlinx.coroutines.sync.withLock import kotlinx.io.IOException +import platform.CoreBluetooth.CBCharacteristicWriteWithResponse +import platform.CoreBluetooth.CBCharacteristicWriteWithoutResponse import platform.CoreBluetooth.CBDescriptor import platform.CoreBluetooth.CBManagerState import platform.CoreBluetooth.CBManagerStatePoweredOn @@ -136,7 +138,7 @@ internal class CBPeripheralCoreBluetoothPeripheral( } override suspend fun connect(): CoroutineScope = - connectAction.await() + connectAction.awaitConnect() override suspend fun disconnect() { connectAction.cancelAndJoin( @@ -144,6 +146,14 @@ internal class CBPeripheralCoreBluetoothPeripheral( ) } + override suspend fun maximumWriteValueLengthForType(writeType: WriteType): Int { + val type = when (writeType) { + WithResponse -> CBCharacteristicWriteWithResponse + WithoutResponse -> CBCharacteristicWriteWithoutResponse + } + return cbPeripheral.maximumWriteValueLengthForType(type).toInt() + } + @ExperimentalApi // Experimental until Web Bluetooth advertisements APIs are stable. @Throws(CancellationException::class, IOException::class) override suspend fun rssi(): Int = connectionOrThrow().execute { diff --git a/kable-core/src/appleMain/kotlin/Connection.kt b/kable-core/src/appleMain/kotlin/Connection.kt index b737f4a17..0d16621c2 100644 --- a/kable-core/src/appleMain/kotlin/Connection.kt +++ b/kable-core/src/appleMain/kotlin/Connection.kt @@ -164,7 +164,7 @@ internal class Connection( // `guard` should always enforce a 1:1 matching of request-to-response, but if an Android // `BluetoothGattCallback` method is called out-of-order then we'll cast to the wrong type. return response as? T - ?: throw InternalException( + ?: throw InternalError( "Expected response type ${type.simpleName} but received ${response::class.simpleName}", ) } diff --git a/kable-core/src/appleMain/kotlin/bluetooth/CheckBluetoothIsOn.kt b/kable-core/src/appleMain/kotlin/bluetooth/CheckBluetoothIsOn.kt index 5354fc4f1..be848e45d 100644 --- a/kable-core/src/appleMain/kotlin/bluetooth/CheckBluetoothIsOn.kt +++ b/kable-core/src/appleMain/kotlin/bluetooth/CheckBluetoothIsOn.kt @@ -1,7 +1,7 @@ package com.juul.kable.bluetooth import com.juul.kable.CentralManager -import com.juul.kable.InternalException +import com.juul.kable.InternalError import com.juul.kable.UnmetRequirementException import com.juul.kable.UnmetRequirementReason.BluetoothDisabled import platform.CoreBluetooth.CBManagerState @@ -33,5 +33,5 @@ private fun nameFor(state: CBManagerState) = when (state) { CBManagerStateUnauthorized -> "Unauthorized" CBManagerStateUnknown -> "Unknown" CBManagerStateUnsupported -> "Unsupported" - else -> throw InternalException("Unsupported bluetooth state: $state") + else -> throw InternalError("Unsupported bluetooth state: $state") } diff --git a/kable-core/src/commonMain/kotlin/InternalError.kt b/kable-core/src/commonMain/kotlin/InternalError.kt new file mode 100644 index 000000000..b84e57c61 --- /dev/null +++ b/kable-core/src/commonMain/kotlin/InternalError.kt @@ -0,0 +1,25 @@ +package com.juul.kable + +/** + * An [Error] that signifies that an unexpected condition or state was encountered in the Kable + * internals. + * + * May be thrown under the following (non-exhaustive) list of conditions: + * - A new system level feature was added but Kable does not yet properly support it + * - A programming error in Kable was encountered (e.g. a state when outside the designed bounds) + * + * Kable will likely be in an inconsistent state and will unlikely continue to function properly. It + * is recommended that the application be restarted (e.g. by not catching this exception and + * allowing the application to crash). + * + * If encountered, please report this exception (and provide logs) to: + * https://github.com/JuulLabs/kable/issues + */ +@Suppress("ktlint:standard:indent") +public class InternalError internal constructor( + message: String, + cause: Throwable? = null, +) : Error( + "$message, please report issue to https://github.com/JuulLabs/kable/issues and provide logs", + cause, +) diff --git a/kable-core/src/commonMain/kotlin/InternalException.kt b/kable-core/src/commonMain/kotlin/InternalException.kt deleted file mode 100644 index 028537c58..000000000 --- a/kable-core/src/commonMain/kotlin/InternalException.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.juul.kable - -public class InternalException internal constructor( - message: String, - cause: Throwable? = null, -) : IllegalStateException( - "$message, please report issue to https://github.com/JuulLabs/kable/issues and provide logs", - cause, - ) diff --git a/kable-core/src/commonMain/kotlin/Peripheral.kt b/kable-core/src/commonMain/kotlin/Peripheral.kt index 26c961d79..1ce86048c 100644 --- a/kable-core/src/commonMain/kotlin/Peripheral.kt +++ b/kable-core/src/commonMain/kotlin/Peripheral.kt @@ -152,6 +152,15 @@ public interface Peripheral : CoroutineScope { */ public val services: StateFlow?> + /** + * Return the current ATT MTU size, minus the size of the ATT headers (3 bytes). + * + * On Android, this will be the default (23 - 3) unless you called [requestMtu] when connecting. + * For iOS, this is automatically negotiated, and can also vary depending on the writeType. + * On JavaScript, this will return the default (23 - 3) every time as there is no ATT MTU property available. + */ + public suspend fun maximumWriteValueLengthForType(writeType: WriteType): Int + /** * On JavaScript, requires Chrome 79+ with the * `chrome://flags/#enable-experimental-web-platform-features` flag enabled. diff --git a/kable-core/src/commonMain/kotlin/SharedRepeatableAction.awaitConnect.kt b/kable-core/src/commonMain/kotlin/SharedRepeatableAction.awaitConnect.kt new file mode 100644 index 000000000..2f128f555 --- /dev/null +++ b/kable-core/src/commonMain/kotlin/SharedRepeatableAction.awaitConnect.kt @@ -0,0 +1,10 @@ +package com.juul.kable + +import kotlinx.coroutines.CoroutineScope + +internal suspend fun SharedRepeatableAction.awaitConnect(): CoroutineScope = + try { + await() + } catch (e: IllegalStateException) { + throw IllegalStateException("Cannot connect peripheral that has been cancelled", e) + } diff --git a/kable-core/src/commonMain/kotlin/SharedRepeatableAction.kt b/kable-core/src/commonMain/kotlin/SharedRepeatableAction.kt index 1b4d8e194..b219c4eef 100644 --- a/kable-core/src/commonMain/kotlin/SharedRepeatableAction.kt +++ b/kable-core/src/commonMain/kotlin/SharedRepeatableAction.kt @@ -86,6 +86,7 @@ internal class SharedRepeatableAction( private suspend fun stateOrNull(): State? = guard.withLock { state } + /** @throws IllegalStateException if parent [scope] is not active. */ suspend fun await() = getOrCreate().action.await() /** diff --git a/kable-core/src/jsMain/kotlin/BluetoothDeviceWebBluetoothPeripheral.kt b/kable-core/src/jsMain/kotlin/BluetoothDeviceWebBluetoothPeripheral.kt index 9d0adc837..f56064cdf 100644 --- a/kable-core/src/jsMain/kotlin/BluetoothDeviceWebBluetoothPeripheral.kt +++ b/kable-core/src/jsMain/kotlin/BluetoothDeviceWebBluetoothPeripheral.kt @@ -32,6 +32,8 @@ import kotlin.coroutines.resumeWithException import kotlin.time.Duration private const val ADVERTISEMENT_RECEIVED = "advertisementreceived" +private const val DEFAULT_ATT_MTU = 23 +private const val ATT_MTU_HEADER_SIZE = 3 internal class BluetoothDeviceWebBluetoothPeripheral( private val bluetoothDevice: BluetoothDevice, @@ -106,7 +108,7 @@ internal class BluetoothDeviceWebBluetoothPeripheral( } override suspend fun connect(): CoroutineScope = - connectAction.await() + connectAction.awaitConnect() override suspend fun disconnect() { connectAction.cancelAndJoin( @@ -114,6 +116,9 @@ internal class BluetoothDeviceWebBluetoothPeripheral( ) } + override suspend fun maximumWriteValueLengthForType(writeType: WriteType): Int = + DEFAULT_ATT_MTU - ATT_MTU_HEADER_SIZE + /** * Per [Web Bluetooth / Scanning Sample][https://googlechrome.github.io/samples/web-bluetooth/scan.html]: * @@ -160,7 +165,7 @@ internal class BluetoothDeviceWebBluetoothPeripheral( continuation.resume(rssi) } else { continuation.resumeWithException( - InternalException("BluetoothAdvertisingEvent.rssi was null"), + InternalError("BluetoothAdvertisingEvent.rssi was null"), ) } } diff --git a/kable-core/src/jsMain/kotlin/BluetoothWebBluetoothScanner.kt b/kable-core/src/jsMain/kotlin/BluetoothWebBluetoothScanner.kt index e76eaa09f..ff53fbdfe 100644 --- a/kable-core/src/jsMain/kotlin/BluetoothWebBluetoothScanner.kt +++ b/kable-core/src/jsMain/kotlin/BluetoothWebBluetoothScanner.kt @@ -57,7 +57,7 @@ internal class BluetoothWebBluetoothScanner( throw IllegalStateException("Scanning not supported", e) } catch (e: JsError) { ensureActive() - throw InternalException("Failed to request scan", e) + throw InternalError("Failed to request scan", e) } logger.verbose { message = "Adding scan listener" } @@ -95,7 +95,7 @@ internal class BluetoothWebBluetoothScanner( detail("options", JSON.stringify(options)) message = e.toString() } - throw InternalException("Failed to start scan", e) + throw InternalError("Failed to start scan", e) } awaitClose { diff --git a/kable-core/src/jsMain/kotlin/Connection.kt b/kable-core/src/jsMain/kotlin/Connection.kt index c3b108ffc..b2dfb0001 100644 --- a/kable-core/src/jsMain/kotlin/Connection.kt +++ b/kable-core/src/jsMain/kotlin/Connection.kt @@ -91,7 +91,7 @@ internal class Connection( // we throw `InternalException`, as the Web Bluetooth Permission API spec is not stable, nor // is it utilized by Kable. // https://webbluetoothcg.github.io/web-bluetooth/#permission-api-integration - ?: throw InternalException("GATT server unavailable") + ?: throw InternalError("GATT server unavailable") private fun servicesOrThrow(): List = discoveredServices.value ?: error("Services have not been discovered") @@ -134,7 +134,7 @@ internal class Connection( coroutineContext.ensureActive() throw when (e) { is DOMException -> IOException("Failed to start notification", e) - else -> InternalException("Unexpected start notification failure", e) + else -> InternalError("Unexpected start notification failure", e) } } } @@ -163,7 +163,7 @@ internal class Connection( // No-op: System implicitly clears notifications on disconnect. } - else -> throw InternalException("Unexpected stop notification failure", e) + else -> throw InternalError("Unexpected stop notification failure", e) } } finally { val listener = observationListeners.remove(platformCharacteristic) ?: return diff --git a/kable-core/src/jsMain/kotlin/RequestPeripheral.kt b/kable-core/src/jsMain/kotlin/RequestPeripheral.kt index 3312ec560..ce6c21f85 100644 --- a/kable-core/src/jsMain/kotlin/RequestPeripheral.kt +++ b/kable-core/src/jsMain/kotlin/RequestPeripheral.kt @@ -36,7 +36,7 @@ public suspend fun requestPeripheral( coroutineContext.ensureActive() throw when (e) { is TypeError -> IllegalStateException("Requesting a device is not supported", e) - else -> InternalException("Failed to invoke device request", e) + else -> InternalError("Failed to invoke device request", e) } } @@ -70,10 +70,10 @@ public suspend fun requestPeripheral( detail("processed", JSON.stringify(requestDeviceOptions)) message = e.toString() } - throw InternalException("Type error when requesting device", e) + throw InternalError("Type error when requesting device", e) } - else -> throw InternalException("Failed to request device", e) + else -> throw InternalError("Failed to request device", e) } }?.let(builder::build) } diff --git a/kable-core/src/jsMain/kotlin/bluetooth/IsSupported.kt b/kable-core/src/jsMain/kotlin/bluetooth/IsSupported.kt index 96dd92ea7..ee6985f28 100644 --- a/kable-core/src/jsMain/kotlin/bluetooth/IsSupported.kt +++ b/kable-core/src/jsMain/kotlin/bluetooth/IsSupported.kt @@ -1,6 +1,6 @@ package com.juul.kable.bluetooth -import com.juul.kable.InternalException +import com.juul.kable.InternalError import com.juul.kable.bluetoothOrNull import js.errors.JsError import js.errors.TypeError @@ -17,6 +17,6 @@ internal actual suspend fun isSupported(): Boolean { return try { promise.await() } catch (e: JsError) { - throw InternalException("Failed to get bluetooth availability", e) + throw InternalError("Failed to get bluetooth availability", e) } }