Add websocket connection support, add v3 protocol support, fix syncMessage console error

This commit is contained in:
andrewheadricke 2025-10-28 11:21:49 +11:00
parent f6fea65063
commit a4ccd3692a
5 changed files with 136 additions and 12 deletions

View file

@ -39,7 +39,7 @@ class Connection extends EventEmitter {
data.writeByte(Constants.CommandCodes.AppStart);
data.writeByte(1); // appVer
data.writeBytes(new Uint8Array(6)); // reserved
data.writeString("test"); // appName
data.writeString("test client"); // appName
await this.sendToRadioFrame(data.toBytes());
}
@ -334,6 +334,10 @@ class Connection extends EventEmitter {
this.onContactMsgRecvResponse(bufferReader);
} else if(responseCode === Constants.ResponseCodes.ChannelMsgRecv){
this.onChannelMsgRecvResponse(bufferReader);
} else if(responseCode === Constants.ResponseCodes.ContactMsgRecv3){
this.onContactMsgRecvResponse(bufferReader);
} else if(responseCode === Constants.ResponseCodes.ChannelMsgRecv3){
this.onChannelMsgRecvResponse(bufferReader);
} else if(responseCode === Constants.ResponseCodes.ContactsStart){
this.onContactsStartResponse(bufferReader);
} else if(responseCode === Constants.ResponseCodes.Contact){
@ -635,23 +639,50 @@ class Connection extends EventEmitter {
}
onContactMsgRecvResponse(bufferReader) {
this.emit(Constants.ResponseCodes.ContactMsgRecv, {
if (Constants.SupportedCompanionProtocolVersion >= 3) {
this.emit(Constants.ResponseCodes.ContactMsgRecv3, {
snr: bufferReader.readByte() / 4.0,
reserved1: bufferReader.readByte(),
reserved2: bufferReader.readByte(),
pubKeyPrefix: bufferReader.readBytes(6),
pathLen: bufferReader.readByte(),
txtType: bufferReader.readByte(),
senderTimestamp: bufferReader.readUInt32LE(),
text: bufferReader.readString(),
});
}
onChannelMsgRecvResponse(bufferReader) {
this.emit(Constants.ResponseCodes.ChannelMsgRecv, {
channelIdx: bufferReader.readInt8(), // reserved (0 for now, ie. 'public')
pathLen: bufferReader.readByte(), // 0xFF if was sent direct, otherwise hop count for flood-mode
});
} else {
this.emit(Constants.ResponseCodes.ContactMsgRecv, {
pubKeyPrefix: bufferReader.readBytes(6),
pathLen: bufferReader.readByte(),
txtType: bufferReader.readByte(),
senderTimestamp: bufferReader.readUInt32LE(),
text: bufferReader.readString(),
});
});
}
}
onChannelMsgRecvResponse(bufferReader) {
if (Constants.SupportedCompanionProtocolVersion >= 3) {
this.emit(Constants.ResponseCodes.ChannelMsgRecv3, {
snr: bufferReader.readByte() / 4.0,
reserved1: bufferReader.readByte(),
reserved2: bufferReader.readByte(),
channelIdx: bufferReader.readInt8(), // reserved (0 for now, ie. 'public')
pathLen: bufferReader.readByte(), // 0xFF if was sent direct, otherwise hop count for flood-mode
txtType: bufferReader.readByte(),
senderTimestamp: bufferReader.readUInt32LE(),
text: bufferReader.readString(),
});
} else {
this.emit(Constants.ResponseCodes.ChannelMsgRecv, {
channelIdx: bufferReader.readInt8(), // reserved (0 for now, ie. 'public')
pathLen: bufferReader.readByte(), // 0xFF if was sent direct, otherwise hop count for flood-mode
txtType: bufferReader.readByte(),
senderTimestamp: bufferReader.readUInt32LE(),
text: bufferReader.readString(),
});
}
}
getSelfInfo(timeoutMillis = null) {
@ -963,6 +994,8 @@ class Connection extends EventEmitter {
const onContactMessageReceived = (message) => {
this.off(Constants.ResponseCodes.ContactMsgRecv, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv, onChannelMessageReceived);
this.off(Constants.ResponseCodes.ContactMsgRecv3, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv3, onChannelMessageReceived);
this.off(Constants.ResponseCodes.NoMoreMessages, onNoMoreMessagesReceived);
resolve({
contactMessage: message,
@ -973,6 +1006,8 @@ class Connection extends EventEmitter {
const onChannelMessageReceived = (message) => {
this.off(Constants.ResponseCodes.ContactMsgRecv, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv, onChannelMessageReceived);
this.off(Constants.ResponseCodes.ContactMsgRecv3, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv3, onChannelMessageReceived);
this.off(Constants.ResponseCodes.NoMoreMessages, onNoMoreMessagesReceived);
resolve({
channelMessage: message,
@ -983,6 +1018,8 @@ class Connection extends EventEmitter {
const onNoMoreMessagesReceived = () => {
this.off(Constants.ResponseCodes.ContactMsgRecv, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv, onChannelMessageReceived);
this.off(Constants.ResponseCodes.ContactMsgRecv3, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv3, onChannelMessageReceived);
this.off(Constants.ResponseCodes.NoMoreMessages, onNoMoreMessagesReceived);
resolve(null);
}
@ -990,6 +1027,8 @@ class Connection extends EventEmitter {
// listen for events
this.once(Constants.ResponseCodes.ContactMsgRecv, onContactMessageReceived);
this.once(Constants.ResponseCodes.ChannelMsgRecv, onChannelMessageReceived);
this.once(Constants.ResponseCodes.ContactMsgRecv3, onContactMessageReceived);
this.once(Constants.ResponseCodes.ChannelMsgRecv3, onChannelMessageReceived);
this.once(Constants.ResponseCodes.NoMoreMessages, onNoMoreMessagesReceived);
// sync next message from device

View file

@ -0,0 +1,73 @@
import Connection from "./connection.js";
// Easy way to test this is to run `websocat -s 5000`
class WebSocketConnection extends Connection {
constructor(url) {
super();
let self = this
this.isClosing = false
let socket = new WebSocket(url)
socket.onopen = (event) => {
//console.log("connected")
this.onConnected();
}
socket.onerror = function(error) {
//console.log(error);
self.isClosing = true
self.onDisconnected();
}
socket.onmessage = async function(event) {
//console.log('got message', event.data)
let buf = await event.data.arrayBuffer();
self.onFrameReceived(buf);
}
socket.onclose = function() {
if (!self.isClosing) {
self.onDisconnected();
}
}
this.socket = socket
}
static async open() {
// ensure browser supports web bluetooth
let url = prompt("Enter WebSocket URL", "ws://127.0.0.1:5000")
if (url.startsWith("ws://") || url.startsWith("wss://")) {
} else {
url = "ws://" + url
}
return new WebSocketConnection(url);
}
async close() {
try {
this.isClosing = true
this.socket.close()
} catch(e) {
//console.log("close error", e)
// ignore error when disconnecting
}
}
async write(bytes) {
try {
this.socket.send(bytes)
} catch(e) {
console.log("failed to write to ble device", e);
}
}
async sendToRadioFrame(frame) {
this.emit("tx", frame);
await this.write(frame);
}
}
export default WebSocketConnection;

View file

@ -1,6 +1,6 @@
class Constants {
static SupportedCompanionProtocolVersion = 1;
static SupportedCompanionProtocolVersion = 3;
static SerialFrameTypes = {
Incoming: 0x3e, // ">"
@ -71,6 +71,8 @@ class Constants {
DeviceInfo: 13,
PrivateKey: 14,
Disabled: 15,
ContactMsgRecv3: 16,
ChannelMsgRecv3: 17,
ChannelInfo: 18,
SignStart: 19,
Signature: 20,