diff --git a/src/actions/ble.ts b/src/actions/ble.ts index 9e791308..40151489 100644 --- a/src/actions/ble.ts +++ b/src/actions/ble.ts @@ -1,5 +1,9 @@ import { Action } from 'redux'; import { ThunkAction } from 'redux-thunk'; +import { + PolyfillBluetoothRemoteGATTCharacteristic, + polyfillBluetoothRemoteGATTCharacteristic, +} from '../utils/web-bluetooth'; import * as notification from './notification'; const pybricksServiceUUID = 'c5f50001-8280-46da-89f4-6d8051e4aeef'; @@ -11,7 +15,7 @@ const bleNusCharTXUUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'; const bleNusMaxSize = 20; let device: BluetoothDevice | undefined; -let rxChar: BluetoothRemoteGATTCharacteristic | undefined; +let rxChar: PolyfillBluetoothRemoteGATTCharacteristic | undefined; export enum BLEConnectActionType { /** @@ -120,7 +124,9 @@ export function connect(): BLEThunkAction { const server = await device.gatt.connect(); try { const service = await server.getPrimaryService(bleNusServiceUUID); - rxChar = await service.getCharacteristic(bleNusCharRXUUID); + rxChar = polyfillBluetoothRemoteGATTCharacteristic( + await service.getCharacteristic(bleNusCharRXUUID), + ); const txChar = await service.getCharacteristic(bleNusCharTXUUID); txChar.addEventListener('characteristicvaluechanged', () => { if (!txChar.value) { @@ -150,7 +156,7 @@ export function write(value: ArrayBuffer): BLEThunkAction { return async function (): Promise { // TODO: do we need to dispatch any Action<>s here? for (let i = 0; i < value.byteLength; i += bleNusMaxSize) { - await rxChar?.writeValue(value.slice(i, i + bleNusMaxSize)); + await rxChar?.writeValueWithoutResponse(value.slice(i, i + bleNusMaxSize)); } }; } diff --git a/src/services/bootloader.ts b/src/services/bootloader.ts index 3cf994d5..4d6c760b 100644 --- a/src/services/bootloader.ts +++ b/src/services/bootloader.ts @@ -10,10 +10,14 @@ import { didSend, } from '../actions/bootloader'; import { CharacteristicUUID, ServiceUUID } from '../protocols/bootloader'; +import { + PolyfillBluetoothRemoteGATTCharacteristic, + polyfillBluetoothRemoteGATTCharacteristic, +} from '../utils/web-bluetooth'; import { combineServices } from '.'; let device: BluetoothDevice | undefined; -let char: BluetoothRemoteGATTCharacteristic | undefined; +let char: PolyfillBluetoothRemoteGATTCharacteristic | undefined; async function connect(action: Action, dispatch: Dispatch): Promise { if (action.type !== BootloaderConnectionActionType.Connect) { @@ -55,7 +59,9 @@ async function connect(action: Action, dispatch: Dispatch): Promise { const server = await device.gatt.connect(); try { const service = await server.getPrimaryService(ServiceUUID); - char = await service.getCharacteristic(CharacteristicUUID); + char = polyfillBluetoothRemoteGATTCharacteristic( + await service.getCharacteristic(CharacteristicUUID), + ); char.addEventListener('characteristicvaluechanged', () => { if (!char || !char.value) { return; @@ -82,16 +88,11 @@ async function send(action: Action, dispatch: Dispatch): Promise { throw Error('Not connected'); } const sendAction = action as BootloaderConnectionSendAction; - // Fall back to legacy WebBluetooth writeValue if new methods are not - // available. - const writeValue = sendAction.withResponse - ? // eslint-disable-next-line @typescript-eslint/ban-ts-ignore - // @ts-ignore - char.writeValueWithResponse || char.writeValue - : // eslint-disable-next-line @typescript-eslint/ban-ts-ignore - // @ts-ignore - char.writeValueWithoutResponse || char.writeValue; - await writeValue(sendAction.data); + if (sendAction.withResponse) { + await char.writeValueWithResponse(sendAction.data); + } else { + await char.writeValueWithoutResponse(sendAction.data); + } dispatch(didSend()); } catch (err) { dispatch(didSend(err)); diff --git a/src/utils/web-bluetooth.ts b/src/utils/web-bluetooth.ts new file mode 100644 index 00000000..66f5191c --- /dev/null +++ b/src/utils/web-bluetooth.ts @@ -0,0 +1,27 @@ +/** + * Current definition of BluetoothRemoteGATTCharacteristic doesn't include + * new Web Bluetooth APIs. + */ +export interface PolyfillBluetoothRemoteGATTCharacteristic + extends BluetoothRemoteGATTCharacteristic { + writeValueWithResponse(value: BufferSource): Promise; + writeValueWithoutResponse(value: BufferSource): Promise; +} + +/** + * Fills in writeValueWithResponse and writeValueWithoutResponse for backward + * compatibility. + * @param char a remote GATT characteristic object + */ +export function polyfillBluetoothRemoteGATTCharacteristic( + char: BluetoothRemoteGATTCharacteristic, +): PolyfillBluetoothRemoteGATTCharacteristic { + const polyfill = (char as unknown) as PolyfillBluetoothRemoteGATTCharacteristic; + if (!polyfill.writeValueWithResponse) { + polyfill.writeValueWithResponse = char.writeValue; + } + if (!polyfill.writeValueWithoutResponse) { + polyfill.writeValueWithoutResponse = char.writeValue; + } + return polyfill; +}