From 78a1f94ef56c37e1d7329e784b6ddf392aa5246a Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sat, 26 Oct 2024 19:29:28 +0100 Subject: [PATCH] wip: poc --- satellite/package.json | 4 +- satellite/src/client.ts | 125 ++++++++++++++----------- satellite/src/clientImplementations.ts | 72 ++++++++++++++ yarn.lock | 26 +++++ 4 files changed, 171 insertions(+), 56 deletions(-) create mode 100644 satellite/src/clientImplementations.ts diff --git a/satellite/package.json b/satellite/package.json index 64ecd21..57e30e9 100644 --- a/satellite/package.json +++ b/satellite/package.json @@ -31,6 +31,7 @@ "@types/koa-static": "^4.0.4", "@types/node": "^20.17.1", "@types/semver": "^7.5.8", + "@types/ws": "^8.5.12", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "cross-env": "^7.0.3", @@ -70,7 +71,8 @@ "node-hid": "^3.1.2", "semver": "^7.6.3", "tslib": "^2.8.0", - "usb": "^2.14.0" + "usb": "^2.14.0", + "ws": "^8.18.0" }, "lint-staged": { "*.{css,json,md,scss}": [ diff --git a/satellite/src/client.ts b/satellite/src/client.ts index d178e5a..9b956cf 100644 --- a/satellite/src/client.ts +++ b/satellite/src/client.ts @@ -1,8 +1,13 @@ import { EventEmitter } from 'eventemitter3' -import { Socket } from 'net' import { ClientCapabilities, CompanionClient, DeviceDrawProps, DeviceRegisterProps } from './device-types/api.js' import { DEFAULT_PORT } from './lib.js' import * as semver from 'semver' +import { + CompanionSatelliteTcpClient, + CompanionSatelliteWsClient, + ICompanionSatelliteClient, + ICompanionSatelliteClientOptions, +} from './clientImplementations.js' const PING_UNACKED_LIMIT = 15 // Arbitrary number const PING_IDLE_TIMEOUT = 1000 // Pings are allowed to be late if another packet has been received recently @@ -89,7 +94,7 @@ export type CompanionSatelliteClientEvents = { export class CompanionSatelliteClient extends EventEmitter implements CompanionClient { private readonly debug: boolean - private socket: Socket | undefined + private socket: ICompanionSatelliteClient | undefined private receiveBuffer = '' @@ -144,69 +149,79 @@ export class CompanionSatelliteClient extends EventEmitter { - this.emit('error', e) - }) - this.socket.on('close', () => { - if (this.debug) { - this.emit('log', 'Connection closed') - } - - this._registeredDevices.clear() - this._pendingDevices.clear() + if (this.socket) { + this.socket.destroy() + this.socket = undefined + } - if (this._connected) { - this.emit('disconnected') - } - this._connected = false - this.receiveBuffer = '' + if (!this._host) { + this.emit('log', `Missing host for connection`) + return + } - if (this._pingInterval) { - clearInterval(this._pingInterval) - this._pingInterval = undefined - } + const socketOptions: ICompanionSatelliteClientOptions = { + onError: (e) => { + this.emit('error', e) + }, + onClose: () => { + if (this.debug) { + this.emit('log', 'Connection closed') + } - if (!this._retryConnectTimeout && this.socket === socket) { - this._retryConnectTimeout = setTimeout(() => { - this._retryConnectTimeout = undefined - this.emit('log', 'Trying reconnect') - this.initSocket() - }, RECONNECT_DELAY) - } - }) + this._registeredDevices.clear() + this._pendingDevices.clear() - this.socket.on('data', (d) => this._handleReceivedData(d)) + if (this._connected) { + this.emit('disconnected') + } + this._connected = false + this.receiveBuffer = '' - this.socket.on('connect', () => { - if (this.debug) { - this.emit('log', 'Connected') - } + if (this._pingInterval) { + clearInterval(this._pingInterval) + this._pingInterval = undefined + } - this._registeredDevices.clear() - this._pendingDevices.clear() + if (!this._retryConnectTimeout && this.socket === socket) { + this._retryConnectTimeout = setTimeout(() => { + this._retryConnectTimeout = undefined + this.emit('log', 'Trying reconnect') + this.initSocket() + }, RECONNECT_DELAY) + } + }, + onData: (d) => this._handleReceivedData(d), + onConnect: () => { + if (this.debug) { + this.emit('log', 'Connected') + } - this._connected = true - this._pingUnackedCount = 0 - this.receiveBuffer = '' + this._registeredDevices.clear() + this._pendingDevices.clear() - if (!this._pingInterval) { - this._pingInterval = setInterval(() => this.sendPing(), PING_INTERVAL) - } + this._connected = true + this._pingUnackedCount = 0 + this.receiveBuffer = '' - if (!this.socket) { - // should never hit, but just in case - this.disconnect() - return - } + if (!this._pingInterval) { + this._pingInterval = setInterval(() => this.sendPing(), PING_INTERVAL) + } - // 'connected' gets emitted once we receive 'Begin' - }) + if (!this.socket) { + // should never hit, but just in case + this.disconnect() + return + } - if (this._host) { - this.emit('log', `Connecting to ${this._host}:${this._port}`) - this.socket.connect(this._port, this._host) + // 'connected' gets emitted once we receive 'Begin' + }, } + + // const socket = new CompanionSatelliteTcpClient(socketOptions, this._host, this._port) + const socket = new CompanionSatelliteWsClient(socketOptions, `ws://${this._host}:${this._port}`) + this.socket = socket + + this.emit('log', `Connecting to ${this._host}:${this._port}`) } private sendPing(): void { @@ -268,9 +283,9 @@ export class CompanionSatelliteClient extends EventEmitter void + onClose: () => void + onData: (data: string) => void + onConnect: () => void +} + +export interface ICompanionSatelliteClient { + write(data: string): void + end(): void + destroy(): void +} + +export class CompanionSatelliteTcpClient implements ICompanionSatelliteClient { + #socket: Socket + + constructor(options: ICompanionSatelliteClientOptions, host: string, port: number) { + this.#socket = new Socket() + + this.#socket.on('error', (err) => options.onError(err)) + this.#socket.on('close', () => options.onClose()) + this.#socket.on('data', (data) => options.onData(data.toString())) + this.#socket.on('connect', () => options.onConnect()) + + this.#socket.connect(port, host) + } + + write(data: string): void { + this.#socket.write(data) + } + end(): void { + this.#socket.end() + } + destroy(): void { + this.#socket.destroy() + } +} + +export class CompanionSatelliteWsClient implements ICompanionSatelliteClient { + #socket: WebSocket + + constructor(options: ICompanionSatelliteClientOptions, address: string) { + this.#socket = new WebSocket(address, { + timeout: 5000, + }) + + this.#socket.on('error', (err) => options.onError(err)) + this.#socket.on('close', () => options.onClose()) + this.#socket.on('message', (data) => options.onData(data.toString())) + this.#socket.on('open', () => options.onConnect()) + } + + write(data: string): void { + this.#socket.send(data) + } + end(): void { + this.#socket.terminate() + } + destroy(): void { + this.#socket.close() + } +} diff --git a/yarn.lock b/yarn.lock index f79f243..be7c5a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2001,6 +2001,15 @@ __metadata: languageName: node linkType: hard +"@types/ws@npm:^8.5.12": + version: 8.5.12 + resolution: "@types/ws@npm:8.5.12" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/3fd77c9e4e05c24ce42bfc7647f7506b08c40a40fe2aea236ef6d4e96fc7cb4006a81ed1b28ec9c457e177a74a72924f4768b7b4652680b42dfd52bc380e15f9 + languageName: node + linkType: hard + "@types/yauzl@npm:^2.9.1": version: 2.10.0 resolution: "@types/yauzl@npm:2.10.0" @@ -6776,6 +6785,7 @@ __metadata: "@types/koa-static": "npm:^4.0.4" "@types/node": "npm:^20.17.1" "@types/semver": "npm:^7.5.8" + "@types/ws": "npm:^8.5.12" "@typescript-eslint/eslint-plugin": "npm:^7.18.0" "@typescript-eslint/parser": "npm:^7.18.0" "@xencelabs-quick-keys/node": "npm:^1.0.0" @@ -6805,6 +6815,7 @@ __metadata: tslib: "npm:^2.8.0" tsx: "npm:^4.19.1" usb: "npm:^2.14.0" + ws: "npm:^8.18.0" languageName: unknown linkType: soft @@ -7789,6 +7800,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06 + languageName: node + linkType: hard + "xmlbuilder@npm:>=11.0.1, xmlbuilder@npm:^15.1.1": version: 15.1.1 resolution: "xmlbuilder@npm:15.1.1"