feat: upgrade buttplug package to protocol v4 and WASM v10
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 7m30s
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 7m30s
Upgrade the buttplug TypeScript client from class-based v3 protocol to interface-based v4 protocol, and the Rust/WASM server from the monolithic buttplug 9.0.9 crate to the split buttplug_core/buttplug_server/ buttplug_server_device_config 10.0.0 crates. TypeScript changes: - Messages are now plain interfaces with msgId()/setMsgId() helpers - ActuatorType → OutputType, SensorType → InputType - ScalarCmd/RotateCmd/LinearCmd → OutputCmd, SensorReadCmd → InputCmd - Client.ts → ButtplugClient.ts, new DeviceCommand/DeviceFeature files - Devices getter returns Map instead of array - Removed class-transformer/reflect-metadata dependencies Rust/WASM changes: - Split imports across buttplug_core, buttplug_server, buttplug_server_device_config - Removed ButtplugServerDowngradeWrapper (use ButtplugServer directly) - Replaced ButtplugFuture/ButtplugFutureStateShared with tokio::sync::oneshot - Updated Hardware::new for new 6-arg signature - Uses git fork (valknarthing/buttplug) to fix missing wasm deps in buttplug_core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,86 +6,83 @@
|
||||
* @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
import { EventEmitter } from "eventemitter3";
|
||||
import { ButtplugMessage } from "../core/Messages";
|
||||
import { fromJSON } from "../core/MessageUtils";
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { ButtplugMessage } from '../core/Messages';
|
||||
|
||||
export class ButtplugBrowserWebsocketConnector extends EventEmitter {
|
||||
protected _ws: WebSocket | undefined;
|
||||
protected _websocketConstructor: typeof WebSocket | null = null;
|
||||
protected _ws: WebSocket | undefined;
|
||||
protected _websocketConstructor: typeof WebSocket | null = null;
|
||||
|
||||
public constructor(private _url: string) {
|
||||
super();
|
||||
}
|
||||
public constructor(private _url: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public get Connected(): boolean {
|
||||
return this._ws !== undefined;
|
||||
}
|
||||
public get Connected(): boolean {
|
||||
return this._ws !== undefined;
|
||||
}
|
||||
|
||||
public connect = async (): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const ws = new (this._websocketConstructor ?? WebSocket)(this._url);
|
||||
const onErrorCallback = (event: Event) => {
|
||||
reject(event);
|
||||
};
|
||||
const onCloseCallback = (event: CloseEvent) => reject(event.reason);
|
||||
ws.addEventListener("open", async () => {
|
||||
this._ws = ws;
|
||||
try {
|
||||
await this.initialize();
|
||||
this._ws.addEventListener("message", (msg) => {
|
||||
this.parseIncomingMessage(msg);
|
||||
});
|
||||
this._ws.removeEventListener("close", onCloseCallback);
|
||||
this._ws.removeEventListener("error", onErrorCallback);
|
||||
this._ws.addEventListener("close", this.disconnect);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
// In websockets, our error rarely tells us much, as for security reasons
|
||||
// browsers usually only throw Error Code 1006. It's up to those using this
|
||||
// library to state what the problem might be.
|
||||
public connect = async (): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const ws = new (this._websocketConstructor ?? WebSocket)(this._url);
|
||||
const onErrorCallback = (event: Event) => {reject(event)}
|
||||
const onCloseCallback = (event: CloseEvent) => reject(event.reason)
|
||||
ws.addEventListener('open', async () => {
|
||||
this._ws = ws;
|
||||
try {
|
||||
await this.initialize();
|
||||
this._ws.addEventListener('message', (msg) => {
|
||||
this.parseIncomingMessage(msg);
|
||||
});
|
||||
this._ws.removeEventListener('close', onCloseCallback);
|
||||
this._ws.removeEventListener('error', onErrorCallback);
|
||||
this._ws.addEventListener('close', this.disconnect);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
// In websockets, our error rarely tells us much, as for security reasons
|
||||
// browsers usually only throw Error Code 1006. It's up to those using this
|
||||
// library to state what the problem might be.
|
||||
|
||||
ws.addEventListener("error", onErrorCallback);
|
||||
ws.addEventListener("close", onCloseCallback);
|
||||
});
|
||||
};
|
||||
ws.addEventListener('error', onErrorCallback)
|
||||
ws.addEventListener('close', onCloseCallback);
|
||||
});
|
||||
};
|
||||
|
||||
public disconnect = async (): Promise<void> => {
|
||||
if (!this.Connected) {
|
||||
return;
|
||||
}
|
||||
this._ws!.close();
|
||||
this._ws = undefined;
|
||||
this.emit("disconnect");
|
||||
};
|
||||
public disconnect = async (): Promise<void> => {
|
||||
if (!this.Connected) {
|
||||
return;
|
||||
}
|
||||
this._ws!.close();
|
||||
this._ws = undefined;
|
||||
this.emit('disconnect');
|
||||
};
|
||||
|
||||
public sendMessage(msg: ButtplugMessage) {
|
||||
if (!this.Connected) {
|
||||
throw new Error("ButtplugBrowserWebsocketConnector not connected");
|
||||
}
|
||||
this._ws!.send("[" + msg.toJSON() + "]");
|
||||
}
|
||||
public sendMessage(msg: ButtplugMessage) {
|
||||
if (!this.Connected) {
|
||||
throw new Error('ButtplugBrowserWebsocketConnector not connected');
|
||||
}
|
||||
this._ws!.send('[' + JSON.stringify(msg) + ']');
|
||||
}
|
||||
|
||||
public initialize = async (): Promise<void> => {
|
||||
return Promise.resolve();
|
||||
};
|
||||
public initialize = async (): Promise<void> => {
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
protected parseIncomingMessage(event: MessageEvent) {
|
||||
if (typeof event.data === "string") {
|
||||
const msgs = fromJSON(event.data);
|
||||
this.emit("message", msgs);
|
||||
} else if (event.data instanceof Blob) {
|
||||
// No-op, we only use text message types.
|
||||
}
|
||||
}
|
||||
protected parseIncomingMessage(event: MessageEvent) {
|
||||
if (typeof event.data === 'string') {
|
||||
const msgs: ButtplugMessage[] = JSON.parse(event.data);
|
||||
this.emit('message', msgs);
|
||||
} else if (event.data instanceof Blob) {
|
||||
// No-op, we only use text message types.
|
||||
}
|
||||
}
|
||||
|
||||
protected onReaderLoad(event: Event) {
|
||||
const msgs = fromJSON((event.target as FileReader).result);
|
||||
this.emit("message", msgs);
|
||||
}
|
||||
protected onReaderLoad(event: Event) {
|
||||
const msgs: ButtplugMessage[] = JSON.parse((event.target as FileReader).result as string);
|
||||
this.emit('message', msgs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,60 +6,61 @@
|
||||
* @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved.
|
||||
*/
|
||||
|
||||
import * as Messages from "../core/Messages";
|
||||
import { ButtplugError } from "../core/Exceptions";
|
||||
import * as Messages from '../core/Messages';
|
||||
import { ButtplugError } from '../core/Exceptions';
|
||||
|
||||
export class ButtplugMessageSorter {
|
||||
protected _counter = 1;
|
||||
protected _waitingMsgs: Map<
|
||||
number,
|
||||
[(val: Messages.ButtplugMessage) => void, (err: Error) => void]
|
||||
> = new Map();
|
||||
protected _counter = 1;
|
||||
protected _waitingMsgs: Map<
|
||||
number,
|
||||
[(val: Messages.ButtplugMessage) => void, (err: Error) => void]
|
||||
> = new Map();
|
||||
|
||||
public constructor(private _useCounter: boolean) {}
|
||||
public constructor(private _useCounter: boolean) {}
|
||||
|
||||
// One of the places we should actually return a promise, as we need to store
|
||||
// them while waiting for them to return across the line.
|
||||
// tslint:disable:promise-function-async
|
||||
public PrepareOutgoingMessage(
|
||||
msg: Messages.ButtplugMessage,
|
||||
): Promise<Messages.ButtplugMessage> {
|
||||
if (this._useCounter) {
|
||||
msg.Id = this._counter;
|
||||
// Always increment last, otherwise we might lose sync
|
||||
this._counter += 1;
|
||||
}
|
||||
let res;
|
||||
let rej;
|
||||
const msgPromise = new Promise<Messages.ButtplugMessage>(
|
||||
(resolve, reject) => {
|
||||
res = resolve;
|
||||
rej = reject;
|
||||
},
|
||||
);
|
||||
this._waitingMsgs.set(msg.Id, [res, rej]);
|
||||
return msgPromise;
|
||||
}
|
||||
// One of the places we should actually return a promise, as we need to store
|
||||
// them while waiting for them to return across the line.
|
||||
// tslint:disable:promise-function-async
|
||||
public PrepareOutgoingMessage(
|
||||
msg: Messages.ButtplugMessage
|
||||
): Promise<Messages.ButtplugMessage> {
|
||||
if (this._useCounter) {
|
||||
Messages.setMsgId(msg, this._counter);
|
||||
// Always increment last, otherwise we might lose sync
|
||||
this._counter += 1;
|
||||
}
|
||||
let res;
|
||||
let rej;
|
||||
const msgPromise = new Promise<Messages.ButtplugMessage>(
|
||||
(resolve, reject) => {
|
||||
res = resolve;
|
||||
rej = reject;
|
||||
}
|
||||
);
|
||||
this._waitingMsgs.set(Messages.msgId(msg), [res, rej]);
|
||||
return msgPromise;
|
||||
}
|
||||
|
||||
public ParseIncomingMessages(
|
||||
msgs: Messages.ButtplugMessage[],
|
||||
): Messages.ButtplugMessage[] {
|
||||
const noMatch: Messages.ButtplugMessage[] = [];
|
||||
for (const x of msgs) {
|
||||
if (x.Id !== Messages.SYSTEM_MESSAGE_ID && this._waitingMsgs.has(x.Id)) {
|
||||
const [res, rej] = this._waitingMsgs.get(x.Id)!;
|
||||
// If we've gotten back an error, reject the related promise using a
|
||||
// ButtplugException derived type.
|
||||
if (x.Type === Messages.Error) {
|
||||
rej(ButtplugError.FromError(x as Messages.Error));
|
||||
continue;
|
||||
}
|
||||
res(x);
|
||||
continue;
|
||||
} else {
|
||||
noMatch.push(x);
|
||||
}
|
||||
}
|
||||
return noMatch;
|
||||
}
|
||||
public ParseIncomingMessages(
|
||||
msgs: Messages.ButtplugMessage[]
|
||||
): Messages.ButtplugMessage[] {
|
||||
const noMatch: Messages.ButtplugMessage[] = [];
|
||||
for (const x of msgs) {
|
||||
let id = Messages.msgId(x);
|
||||
if (id !== Messages.SYSTEM_MESSAGE_ID && this._waitingMsgs.has(id)) {
|
||||
const [res, rej] = this._waitingMsgs.get(id)!;
|
||||
// If we've gotten back an error, reject the related promise using a
|
||||
// ButtplugException derived type.
|
||||
if (x.Error !== undefined) {
|
||||
rej(ButtplugError.FromError(x.Error!));
|
||||
continue;
|
||||
}
|
||||
res(x);
|
||||
continue;
|
||||
} else {
|
||||
noMatch.push(x);
|
||||
}
|
||||
}
|
||||
return noMatch;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user