ive-connect
Version:
A universal haptic device control library for interactive experiences
108 lines (107 loc) • 3.57 kB
JavaScript
"use strict";
/**
* Minimal Buttplug v4 WebSocket transport
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ButtplugWs = void 0;
const PROTOCOL_VERSION_MAJOR = 4;
const PROTOCOL_VERSION_MINOR = 0;
class ButtplugWs {
constructor(onMessage) {
this.ws = null;
this.msgId = 0;
this.pending = new Map();
this.pingTimer = null;
this.onMessage = onMessage;
}
get connected() {
var _a;
return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;
}
async open(url, clientName) {
await new Promise((resolve, reject) => {
try {
this.ws = new WebSocket(url);
}
catch (err) {
reject(err);
return;
}
this.ws.onopen = async () => {
try {
const info = await this.send('RequestServerInfo', {
ClientName: clientName,
ProtocolVersionMajor: PROTOCOL_VERSION_MAJOR,
ProtocolVersionMinor: PROTOCOL_VERSION_MINOR,
});
if (info.MaxPingTime && info.MaxPingTime > 0) {
this.pingTimer = setInterval(() => this.send('Ping', {}).catch(() => { }), Math.floor(info.MaxPingTime / 2));
}
await this.send('RequestDeviceList', {});
resolve();
}
catch (err) {
reject(err);
}
};
this.ws.onerror = () => reject(new Error(`WS failed: ${url}`));
this.ws.onclose = () => this.cleanup();
this.ws.onmessage = (e) => this.handleRaw(e.data);
});
}
async send(type, payload) {
return new Promise((resolve, reject) => {
if (!this.connected) {
reject(new Error('Not connected'));
return;
}
const id = ++this.msgId;
this.pending.set(id, { resolve, reject });
this.ws.send(JSON.stringify([{ [type]: { Id: id, ...payload } }]));
});
}
close() {
var _a;
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN)
this.ws.close();
this.cleanup();
}
handleRaw(data) {
let msgs;
try {
msgs = JSON.parse(data);
}
catch (_a) {
return;
}
for (const msg of msgs) {
const type = Object.keys(msg)[0];
const payload = msg[type];
const id = payload === null || payload === void 0 ? void 0 : payload.Id;
if (id !== undefined && this.pending.has(id)) {
if (type === 'Error') {
this.pending
.get(id)
.reject(new Error(payload.ErrorMessage));
}
else {
this.pending.get(id).resolve(payload);
}
this.pending.delete(id);
}
this.onMessage(type, payload);
}
}
cleanup() {
if (this.pingTimer) {
clearInterval(this.pingTimer);
this.pingTimer = null;
}
for (const p of this.pending.values()) {
p.reject(new Error('Connection closed'));
}
this.pending.clear();
this.ws = null;
}
}
exports.ButtplugWs = ButtplugWs;