meshcore.js/src/connection/web_ble_connection.js
Manuel Bahamóndez-Honores 2d4cb35e51 Replace hand-written .d.ts with JSDoc + tsc auto-generation
- Delete hand-written index.d.ts that drifted from source
- Add JSDoc type annotations to all source files
- Create src/types.js with shared typedefs (SelfInfo, Contact,
  ContactMessage, ChannelInfo, RepeaterStats, etc.)
- Add tsconfig.json for declaration generation
- Add build:types script and prepublishOnly hook
- Add GitHub Actions CI workflow for type checking
- Use Uint8Array everywhere (no Buffer references)
- Add semantic type aliases (EpochSeconds, Milliseconds, MilliVolts)
- Add typed event overloads on Connection (on/once/off)
- All 11 PR #15 review comments addressed
2026-02-18 05:49:46 -03:00

122 lines
3.7 KiB
JavaScript

import Constants from "../constants.js";
import Connection from "./connection.js";
class WebBleConnection extends Connection {
/** @param {any} bleDevice */
constructor(bleDevice) {
super();
this.bleDevice = bleDevice;
/** @type {any} */
this.gattServer = null;
/** @type {any} */
this.rxCharacteristic = null;
/** @type {any} */
this.txCharacteristic = null;
this.init();
}
/** @returns {Promise<WebBleConnection | null | undefined>} */
static async open() {
// ensure browser supports web bluetooth
// @ts-ignore - Web Bluetooth API
if(!navigator.bluetooth){
alert("Web Bluetooth is not supported in this browser");
return;
}
// ask user to select device
// @ts-ignore - Web Bluetooth API
const device = await navigator.bluetooth.requestDevice({
filters: [
{
services: [
Constants.Ble.ServiceUuid.toLowerCase(),
],
},
],
});
// make sure user selected a device
if(!device){
return null;
}
return new WebBleConnection(device);
}
async init() {
// listen for ble disconnect
this.bleDevice.addEventListener("gattserverdisconnected", () => {
this.onDisconnected();
});
// connect to gatt server
this.gattServer = await this.bleDevice.gatt.connect();
// find service
const service = await this.gattServer.getPrimaryService(Constants.Ble.ServiceUuid.toLowerCase());
const characteristics = await service.getCharacteristics();
// find rx characteristic (we write to this one, it's where the radio reads from)
this.rxCharacteristic = characteristics.find((characteristic) => {
return characteristic.uuid.toLowerCase() === Constants.Ble.CharacteristicUuidRx.toLowerCase();
});
// find tx characteristic (we read this one, it's where the radio writes to)
this.txCharacteristic = characteristics.find((characteristic) => {
return characteristic.uuid.toLowerCase() === Constants.Ble.CharacteristicUuidTx.toLowerCase();
});
// listen for frames from transmitted to us from the ble device
await this.txCharacteristic.startNotifications();
this.txCharacteristic.addEventListener('characteristicvaluechanged', (event) => {
const frame = new Uint8Array(event.target.value.buffer);
this.onFrameReceived(frame);
});
// fire connected event
await this.onConnected();
}
/** @returns {Promise<void>} */
async close() {
try {
this.gattServer?.disconnect();
this.gattServer = null;
} catch(e) {
// ignore error when disconnecting
}
}
/**
* @param {Uint8Array} bytes
* @returns {Promise<void>}
*/
async write(bytes) {
try {
// fixme: NetworkError: GATT operation already in progress.
// todo: implement mutex to prevent multiple writes when another write is in progress
// we write to the rx characteristic, as that's where the radio reads from
await this.rxCharacteristic.writeValue(bytes);
} catch(e) {
console.log("failed to write to ble device", e);
}
}
/**
* @param {Uint8Array} frame
* @returns {Promise<void>}
*/
async sendToRadioFrame(frame) {
this.emit("tx", frame);
await this.write(frame);
}
}
export default WebBleConnection;