A new start
This commit is contained in:
276
packages/buttplug/src/client/Client.ts
Normal file
276
packages/buttplug/src/client/Client.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
/*!
|
||||
* Buttplug JS Source Code File - Visit https://buttplug.io for more info about
|
||||
* the project. Licensed under the BSD 3-Clause license. See LICENSE file in the
|
||||
* project root for full license information.
|
||||
*
|
||||
* @copyright Copyright (c) Nonpolynomial Labs LLC. All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
import { ButtplugLogger } from "../core/Logging";
|
||||
import { EventEmitter } from "eventemitter3";
|
||||
import { ButtplugClientDevice } from "./ButtplugClientDevice";
|
||||
import { IButtplugClientConnector } from "./IButtplugClientConnector";
|
||||
import { ButtplugMessageSorter } from "../utils/ButtplugMessageSorter";
|
||||
|
||||
import * as Messages from "../core/Messages";
|
||||
import {
|
||||
ButtplugDeviceError,
|
||||
ButtplugError,
|
||||
ButtplugInitError,
|
||||
ButtplugMessageError,
|
||||
} from "../core/Exceptions";
|
||||
import { ButtplugClientConnectorException } from "./ButtplugClientConnectorException";
|
||||
import { getMessageClassFromMessage } from "../core/MessageUtils";
|
||||
|
||||
export class ButtplugClient extends EventEmitter {
|
||||
protected _pingTimer: NodeJS.Timeout | null = null;
|
||||
protected _connector: IButtplugClientConnector | null = null;
|
||||
protected _devices: Map<number, ButtplugClientDevice> = new Map();
|
||||
protected _clientName: string;
|
||||
protected _logger = ButtplugLogger.Logger;
|
||||
protected _isScanning = false;
|
||||
private _sorter: ButtplugMessageSorter = new ButtplugMessageSorter(true);
|
||||
|
||||
constructor(clientName = "Generic Buttplug Client") {
|
||||
super();
|
||||
this._clientName = clientName;
|
||||
this._logger.Debug(`ButtplugClient: Client ${clientName} created.`);
|
||||
}
|
||||
|
||||
public get connected(): boolean {
|
||||
return this._connector !== null && this._connector.Connected;
|
||||
}
|
||||
|
||||
public get devices(): ButtplugClientDevice[] {
|
||||
// While this function doesn't actually send a message, if we don't have a
|
||||
// connector, we shouldn't have devices.
|
||||
this.checkConnector();
|
||||
const devices: ButtplugClientDevice[] = [];
|
||||
this._devices.forEach((d) => {
|
||||
devices.push(d);
|
||||
});
|
||||
return devices;
|
||||
}
|
||||
|
||||
public get isScanning(): boolean {
|
||||
return this._isScanning;
|
||||
}
|
||||
|
||||
public connect = async (connector: IButtplugClientConnector) => {
|
||||
this._logger.Info(
|
||||
`ButtplugClient: Connecting using ${connector.constructor.name}`,
|
||||
);
|
||||
await connector.connect();
|
||||
this._connector = connector;
|
||||
this._connector.addListener("message", this.parseMessages);
|
||||
this._connector.addListener("disconnect", this.disconnectHandler);
|
||||
await this.initializeConnection();
|
||||
};
|
||||
|
||||
public disconnect = async () => {
|
||||
this._logger.Debug("ButtplugClient: Disconnect called");
|
||||
this.checkConnector();
|
||||
await this.shutdownConnection();
|
||||
await this._connector!.disconnect();
|
||||
};
|
||||
|
||||
public startScanning = async () => {
|
||||
this._logger.Debug("ButtplugClient: StartScanning called");
|
||||
this._isScanning = true;
|
||||
await this.sendMsgExpectOk(new Messages.StartScanning());
|
||||
};
|
||||
|
||||
public stopScanning = async () => {
|
||||
this._logger.Debug("ButtplugClient: StopScanning called");
|
||||
this._isScanning = false;
|
||||
await this.sendMsgExpectOk(new Messages.StopScanning());
|
||||
};
|
||||
|
||||
public stopAllDevices = async () => {
|
||||
this._logger.Debug("ButtplugClient: StopAllDevices");
|
||||
await this.sendMsgExpectOk(new Messages.StopAllDevices());
|
||||
};
|
||||
|
||||
private async sendDeviceMessage(
|
||||
device: ButtplugClientDevice,
|
||||
deviceMsg: Messages.ButtplugDeviceMessage,
|
||||
): Promise<Messages.ButtplugMessage> {
|
||||
this.checkConnector();
|
||||
const dev = this._devices.get(device.index);
|
||||
if (dev === undefined) {
|
||||
throw ButtplugError.LogAndError(
|
||||
ButtplugDeviceError,
|
||||
this._logger,
|
||||
`Device ${device.index} not available.`,
|
||||
);
|
||||
}
|
||||
deviceMsg.DeviceIndex = device.index;
|
||||
return await this.sendMessage(deviceMsg);
|
||||
}
|
||||
|
||||
protected disconnectHandler = () => {
|
||||
this._logger.Info("ButtplugClient: Disconnect event receieved.");
|
||||
this.emit("disconnect");
|
||||
};
|
||||
|
||||
protected parseMessages = (msgs: Messages.ButtplugMessage[]) => {
|
||||
const leftoverMsgs = this._sorter.ParseIncomingMessages(msgs);
|
||||
for (const x of leftoverMsgs) {
|
||||
switch (getMessageClassFromMessage(x)) {
|
||||
case Messages.DeviceAdded: {
|
||||
const addedMsg = x as Messages.DeviceAdded;
|
||||
const addedDevice = ButtplugClientDevice.fromMsg(
|
||||
addedMsg,
|
||||
this.sendDeviceMessageClosure,
|
||||
);
|
||||
this._devices.set(addedMsg.DeviceIndex, addedDevice);
|
||||
this.emit("deviceadded", addedMsg, addedDevice);
|
||||
break;
|
||||
}
|
||||
case Messages.DeviceRemoved: {
|
||||
const removedMsg = x as Messages.DeviceRemoved;
|
||||
if (this._devices.has(removedMsg.DeviceIndex)) {
|
||||
const removedDevice = this._devices.get(removedMsg.DeviceIndex);
|
||||
removedDevice?.emitDisconnected();
|
||||
this._devices.delete(removedMsg.DeviceIndex);
|
||||
this.emit("deviceremoved", removedMsg, removedDevice);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Messages.ScanningFinished:
|
||||
this._isScanning = false;
|
||||
this.emit("scanningfinished", x);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected initializeConnection = async (): Promise<boolean> => {
|
||||
this.checkConnector();
|
||||
const msg = await this.sendMessage(
|
||||
new Messages.RequestServerInfo(
|
||||
this._clientName,
|
||||
Messages.MESSAGE_SPEC_VERSION,
|
||||
),
|
||||
);
|
||||
switch (getMessageClassFromMessage(msg)) {
|
||||
case Messages.ServerInfo: {
|
||||
const serverinfo = msg as Messages.ServerInfo;
|
||||
this._logger.Info(
|
||||
`ButtplugClient: Connected to Server ${serverinfo.ServerName}`,
|
||||
);
|
||||
// TODO: maybe store server name, do something with message template version?
|
||||
const ping = serverinfo.MaxPingTime;
|
||||
if (serverinfo.MessageVersion < Messages.MESSAGE_SPEC_VERSION) {
|
||||
// Disconnect and throw an exception explaining the version mismatch problem.
|
||||
await this._connector!.disconnect();
|
||||
throw ButtplugError.LogAndError(
|
||||
ButtplugInitError,
|
||||
this._logger,
|
||||
`Server protocol version ${serverinfo.MessageVersion} is older than client protocol version ${Messages.MESSAGE_SPEC_VERSION}. Please update server.`,
|
||||
);
|
||||
}
|
||||
if (ping > 0) {
|
||||
/*
|
||||
this._pingTimer = setInterval(async () => {
|
||||
// If we've disconnected, stop trying to ping the server.
|
||||
if (!this.Connected) {
|
||||
await this.ShutdownConnection();
|
||||
return;
|
||||
}
|
||||
await this.SendMessage(new Messages.Ping());
|
||||
} , Math.round(ping / 2));
|
||||
*/
|
||||
}
|
||||
await this.requestDeviceList();
|
||||
return true;
|
||||
}
|
||||
case Messages.Error: {
|
||||
// Disconnect and throw an exception with the error message we got back.
|
||||
// This will usually only error out if we have a version mismatch that the
|
||||
// server has detected.
|
||||
await this._connector!.disconnect();
|
||||
const err = msg as Messages.Error;
|
||||
throw ButtplugError.LogAndError(
|
||||
ButtplugInitError,
|
||||
this._logger,
|
||||
`Cannot connect to server. ${err.ErrorMessage}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
protected requestDeviceList = async () => {
|
||||
this.checkConnector();
|
||||
this._logger.Debug("ButtplugClient: ReceiveDeviceList called");
|
||||
const deviceList = (await this.sendMessage(
|
||||
new Messages.RequestDeviceList(),
|
||||
)) as Messages.DeviceList;
|
||||
deviceList.Devices.forEach((d) => {
|
||||
if (!this._devices.has(d.DeviceIndex)) {
|
||||
const device = ButtplugClientDevice.fromMsg(
|
||||
d,
|
||||
this.sendDeviceMessageClosure,
|
||||
);
|
||||
this._logger.Debug(`ButtplugClient: Adding Device: ${device}`);
|
||||
this._devices.set(d.DeviceIndex, device);
|
||||
this.emit("deviceadded", device);
|
||||
} else {
|
||||
this._logger.Debug(`ButtplugClient: Device already added: ${d}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
protected shutdownConnection = async () => {
|
||||
await this.stopAllDevices();
|
||||
if (this._pingTimer !== null) {
|
||||
clearInterval(this._pingTimer);
|
||||
this._pingTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
protected async sendMessage(
|
||||
msg: Messages.ButtplugMessage,
|
||||
): Promise<Messages.ButtplugMessage> {
|
||||
this.checkConnector();
|
||||
const p = this._sorter.PrepareOutgoingMessage(msg);
|
||||
await this._connector!.send(msg);
|
||||
return await p;
|
||||
}
|
||||
|
||||
protected checkConnector() {
|
||||
if (!this.connected) {
|
||||
throw new ButtplugClientConnectorException(
|
||||
"ButtplugClient not connected",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected sendMsgExpectOk = async (
|
||||
msg: Messages.ButtplugMessage,
|
||||
): Promise<void> => {
|
||||
const response = await this.sendMessage(msg);
|
||||
switch (getMessageClassFromMessage(response)) {
|
||||
case Messages.Ok:
|
||||
return;
|
||||
case Messages.Error:
|
||||
throw ButtplugError.FromError(response as Messages.Error);
|
||||
default:
|
||||
throw ButtplugError.LogAndError(
|
||||
ButtplugMessageError,
|
||||
this._logger,
|
||||
`Message type ${getMessageClassFromMessage(response)!.constructor} not handled by SendMsgExpectOk`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
protected sendDeviceMessageClosure = async (
|
||||
device: ButtplugClientDevice,
|
||||
msg: Messages.ButtplugDeviceMessage,
|
||||
): Promise<Messages.ButtplugMessage> => {
|
||||
return await this.sendDeviceMessage(device, msg);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user