Files
sexy/packages/buttplug/dist/index.js
Sebastian Krüger 24f53983d7
Some checks failed
Build and Push Backend Image / build (push) Successful in 47s
Build and Push Buttplug Image / build (push) Has been cancelled
Build and Push Frontend Image / build (push) Has been cancelled
fix: switch buttplug WASM to --target web for browser compatibility
--target bundler generates static WASM ESM imports that only work
through a bundler (vite-plugin-wasm). --target web generates fetch-based
WASM loading via import.meta.url which browsers handle natively.

- Change wasm-pack build target from bundler to web
- Call wasmModule.default() (init) after import in maybeLoadWasm
- Restore rollupOptions.external so WASM stays a separate fetch
- Removes the need for vite-plugin-wasm in any consumer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 14:07:14 +01:00

1395 lines
40 KiB
JavaScript

function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var eventemitter3 = {exports: {}};
var hasRequiredEventemitter3;
function requireEventemitter3 () {
if (hasRequiredEventemitter3) return eventemitter3.exports;
hasRequiredEventemitter3 = 1;
(function (module) {
var has = Object.prototype.hasOwnProperty
, prefix = '~';
/**
* Constructor to create a storage for our `EE` objects.
* An `Events` instance is a plain object whose properties are event names.
*
* @constructor
* @private
*/
function Events() {}
//
// We try to not inherit from `Object.prototype`. In some engines creating an
// instance in this way is faster than calling `Object.create(null)` directly.
// If `Object.create(null)` is not supported we prefix the event names with a
// character to make sure that the built-in object properties are not
// overridden or used as an attack vector.
//
if (Object.create) {
Events.prototype = Object.create(null);
//
// This hack is needed because the `__proto__` property is still inherited in
// some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.
//
if (!new Events().__proto__) prefix = false;
}
/**
* Representation of a single event listener.
*
* @param {Function} fn The listener function.
* @param {*} context The context to invoke the listener with.
* @param {Boolean} [once=false] Specify if the listener is a one-time listener.
* @constructor
* @private
*/
function EE(fn, context, once) {
this.fn = fn;
this.context = context;
this.once = once || false;
}
/**
* Add a listener for a given event.
*
* @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
* @param {(String|Symbol)} event The event name.
* @param {Function} fn The listener function.
* @param {*} context The context to invoke the listener with.
* @param {Boolean} once Specify if the listener is a one-time listener.
* @returns {EventEmitter}
* @private
*/
function addListener(emitter, event, fn, context, once) {
if (typeof fn !== 'function') {
throw new TypeError('The listener must be a function');
}
var listener = new EE(fn, context || emitter, once)
, evt = prefix ? prefix + event : event;
if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;
else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);
else emitter._events[evt] = [emitter._events[evt], listener];
return emitter;
}
/**
* Clear event by name.
*
* @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
* @param {(String|Symbol)} evt The Event name.
* @private
*/
function clearEvent(emitter, evt) {
if (--emitter._eventsCount === 0) emitter._events = new Events();
else delete emitter._events[evt];
}
/**
* Minimal `EventEmitter` interface that is molded against the Node.js
* `EventEmitter` interface.
*
* @constructor
* @public
*/
function EventEmitter() {
this._events = new Events();
this._eventsCount = 0;
}
/**
* Return an array listing the events for which the emitter has registered
* listeners.
*
* @returns {Array}
* @public
*/
EventEmitter.prototype.eventNames = function eventNames() {
var names = []
, events
, name;
if (this._eventsCount === 0) return names;
for (name in (events = this._events)) {
if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
}
if (Object.getOwnPropertySymbols) {
return names.concat(Object.getOwnPropertySymbols(events));
}
return names;
};
/**
* Return the listeners registered for a given event.
*
* @param {(String|Symbol)} event The event name.
* @returns {Array} The registered listeners.
* @public
*/
EventEmitter.prototype.listeners = function listeners(event) {
var evt = prefix ? prefix + event : event
, handlers = this._events[evt];
if (!handlers) return [];
if (handlers.fn) return [handlers.fn];
for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
ee[i] = handlers[i].fn;
}
return ee;
};
/**
* Return the number of listeners listening to a given event.
*
* @param {(String|Symbol)} event The event name.
* @returns {Number} The number of listeners.
* @public
*/
EventEmitter.prototype.listenerCount = function listenerCount(event) {
var evt = prefix ? prefix + event : event
, listeners = this._events[evt];
if (!listeners) return 0;
if (listeners.fn) return 1;
return listeners.length;
};
/**
* Calls each of the listeners registered for a given event.
*
* @param {(String|Symbol)} event The event name.
* @returns {Boolean} `true` if the event had listeners, else `false`.
* @public
*/
EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
var evt = prefix ? prefix + event : event;
if (!this._events[evt]) return false;
var listeners = this._events[evt]
, len = arguments.length
, args
, i;
if (listeners.fn) {
if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);
switch (len) {
case 1: return listeners.fn.call(listeners.context), true;
case 2: return listeners.fn.call(listeners.context, a1), true;
case 3: return listeners.fn.call(listeners.context, a1, a2), true;
case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;
case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
}
for (i = 1, args = new Array(len -1); i < len; i++) {
args[i - 1] = arguments[i];
}
listeners.fn.apply(listeners.context, args);
} else {
var length = listeners.length
, j;
for (i = 0; i < length; i++) {
if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);
switch (len) {
case 1: listeners[i].fn.call(listeners[i].context); break;
case 2: listeners[i].fn.call(listeners[i].context, a1); break;
case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;
case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;
default:
if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {
args[j - 1] = arguments[j];
}
listeners[i].fn.apply(listeners[i].context, args);
}
}
}
return true;
};
/**
* Add a listener for a given event.
*
* @param {(String|Symbol)} event The event name.
* @param {Function} fn The listener function.
* @param {*} [context=this] The context to invoke the listener with.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.on = function on(event, fn, context) {
return addListener(this, event, fn, context, false);
};
/**
* Add a one-time listener for a given event.
*
* @param {(String|Symbol)} event The event name.
* @param {Function} fn The listener function.
* @param {*} [context=this] The context to invoke the listener with.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.once = function once(event, fn, context) {
return addListener(this, event, fn, context, true);
};
/**
* Remove the listeners of a given event.
*
* @param {(String|Symbol)} event The event name.
* @param {Function} fn Only remove the listeners that match this function.
* @param {*} context Only remove the listeners that have this context.
* @param {Boolean} once Only remove one-time listeners.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
var evt = prefix ? prefix + event : event;
if (!this._events[evt]) return this;
if (!fn) {
clearEvent(this, evt);
return this;
}
var listeners = this._events[evt];
if (listeners.fn) {
if (
listeners.fn === fn &&
(!once || listeners.once) &&
(!context || listeners.context === context)
) {
clearEvent(this, evt);
}
} else {
for (var i = 0, events = [], length = listeners.length; i < length; i++) {
if (
listeners[i].fn !== fn ||
(once && !listeners[i].once) ||
(context && listeners[i].context !== context)
) {
events.push(listeners[i]);
}
}
//
// Reset the array, or remove it completely if we have no more listeners.
//
if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;
else clearEvent(this, evt);
}
return this;
};
/**
* Remove all listeners, or those of the specified event.
*
* @param {(String|Symbol)} [event] The event name.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
var evt;
if (event) {
evt = prefix ? prefix + event : event;
if (this._events[evt]) clearEvent(this, evt);
} else {
this._events = new Events();
this._eventsCount = 0;
}
return this;
};
//
// Alias methods names because people roll like that.
//
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
//
// Expose the prefix.
//
EventEmitter.prefixed = prefix;
//
// Allow `EventEmitter` to be imported as module namespace.
//
EventEmitter.EventEmitter = EventEmitter;
//
// Expose the module.
//
{
module.exports = EventEmitter;
}
} (eventemitter3));
return eventemitter3.exports;
}
var eventemitter3Exports = requireEventemitter3();
const EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);
var ButtplugLogLevel = /* @__PURE__ */ ((ButtplugLogLevel2) => {
ButtplugLogLevel2[ButtplugLogLevel2["Off"] = 0] = "Off";
ButtplugLogLevel2[ButtplugLogLevel2["Error"] = 1] = "Error";
ButtplugLogLevel2[ButtplugLogLevel2["Warn"] = 2] = "Warn";
ButtplugLogLevel2[ButtplugLogLevel2["Info"] = 3] = "Info";
ButtplugLogLevel2[ButtplugLogLevel2["Debug"] = 4] = "Debug";
ButtplugLogLevel2[ButtplugLogLevel2["Trace"] = 5] = "Trace";
return ButtplugLogLevel2;
})(ButtplugLogLevel || {});
class LogMessage {
/** Timestamp for the log message */
timestamp;
/** Log Message */
logMessage;
/** Log Level */
logLevel;
/**
* @param logMessage Log message.
* @param logLevel: Log severity level.
*/
constructor(logMessage, logLevel) {
const a = /* @__PURE__ */ new Date();
const hour = a.getHours();
const min = a.getMinutes();
const sec = a.getSeconds();
this.timestamp = `${hour}:${min}:${sec}`;
this.logMessage = logMessage;
this.logLevel = logLevel;
}
/**
* Returns the log message.
*/
get Message() {
return this.logMessage;
}
/**
* Returns the log message level.
*/
get LogLevel() {
return this.logLevel;
}
/**
* Returns the log message timestamp.
*/
get Timestamp() {
return this.timestamp;
}
/**
* Returns a formatted string with timestamp, level, and message.
*/
get FormattedMessage() {
return `${ButtplugLogLevel[this.logLevel]} : ${this.timestamp} : ${this.logMessage}`;
}
}
class ButtplugLogger extends EventEmitter {
/** Singleton instance for the logger */
static sLogger = void 0;
/** Sets maximum log level to log to console */
maximumConsoleLogLevel = 0 /* Off */;
/** Sets maximum log level for all log messages */
maximumEventLogLevel = 0 /* Off */;
/**
* Returns the stored static instance of the logger, creating one if it
* doesn't currently exist.
*/
static get Logger() {
if (ButtplugLogger.sLogger === void 0) {
ButtplugLogger.sLogger = new ButtplugLogger();
}
return this.sLogger;
}
/**
* Constructor. Can only be called internally since we regulate ButtplugLogger
* ownership.
*/
constructor() {
super();
}
/**
* Set the maximum log level to output to console.
*/
get MaximumConsoleLogLevel() {
return this.maximumConsoleLogLevel;
}
/**
* Get the maximum log level to output to console.
*/
set MaximumConsoleLogLevel(buttplugLogLevel) {
this.maximumConsoleLogLevel = buttplugLogLevel;
}
/**
* Set the global maximum log level
*/
get MaximumEventLogLevel() {
return this.maximumEventLogLevel;
}
/**
* Get the global maximum log level
*/
set MaximumEventLogLevel(logLevel) {
this.maximumEventLogLevel = logLevel;
}
/**
* Log new message at Error level.
*/
Error(msg) {
this.AddLogMessage(msg, 1 /* Error */);
}
/**
* Log new message at Warn level.
*/
Warn(msg) {
this.AddLogMessage(msg, 2 /* Warn */);
}
/**
* Log new message at Info level.
*/
Info(msg) {
this.AddLogMessage(msg, 3 /* Info */);
}
/**
* Log new message at Debug level.
*/
Debug(msg) {
this.AddLogMessage(msg, 4 /* Debug */);
}
/**
* Log new message at Trace level.
*/
Trace(msg) {
this.AddLogMessage(msg, 5 /* Trace */);
}
/**
* Checks to see if message should be logged, and if so, adds message to the
* log buffer. May also print message and emit event.
*/
AddLogMessage(msg, level) {
if (level > this.maximumEventLogLevel && level > this.maximumConsoleLogLevel) {
return;
}
const logMsg = new LogMessage(msg, level);
if (level <= this.maximumConsoleLogLevel) {
console.log(logMsg.FormattedMessage);
}
if (level <= this.maximumEventLogLevel) {
this.emit("log", logMsg);
}
}
}
class ButtplugError extends Error {
get ErrorClass() {
return this.errorClass;
}
get InnerError() {
return this.innerError;
}
get Id() {
return this.messageId;
}
get ErrorMessage() {
return {
Error: {
Id: this.Id,
ErrorCode: this.ErrorClass,
ErrorMessage: this.message
}
};
}
static LogAndError(constructor, logger, message, id = SYSTEM_MESSAGE_ID) {
logger.Error(message);
return new constructor(message, id);
}
static FromError(error) {
switch (error.ErrorCode) {
case ErrorClass.ERROR_DEVICE:
return new ButtplugDeviceError(error.ErrorMessage, error.Id);
case ErrorClass.ERROR_INIT:
return new ButtplugInitError(error.ErrorMessage, error.Id);
case ErrorClass.ERROR_UNKNOWN:
return new ButtplugUnknownError(error.ErrorMessage, error.Id);
case ErrorClass.ERROR_PING:
return new ButtplugPingError(error.ErrorMessage, error.Id);
case ErrorClass.ERROR_MSG:
return new ButtplugMessageError(error.ErrorMessage, error.Id);
default:
throw new Error(`Message type ${error.ErrorCode} not handled`);
}
}
errorClass = ErrorClass.ERROR_UNKNOWN;
innerError;
messageId;
constructor(message, errorClass, id = SYSTEM_MESSAGE_ID, inner) {
super(message);
this.errorClass = errorClass;
this.innerError = inner;
this.messageId = id;
}
}
class ButtplugInitError extends ButtplugError {
constructor(message, id = SYSTEM_MESSAGE_ID) {
super(message, ErrorClass.ERROR_INIT, id);
}
}
class ButtplugDeviceError extends ButtplugError {
constructor(message, id = SYSTEM_MESSAGE_ID) {
super(message, ErrorClass.ERROR_DEVICE, id);
}
}
class ButtplugMessageError extends ButtplugError {
constructor(message, id = SYSTEM_MESSAGE_ID) {
super(message, ErrorClass.ERROR_MSG, id);
}
}
class ButtplugPingError extends ButtplugError {
constructor(message, id = SYSTEM_MESSAGE_ID) {
super(message, ErrorClass.ERROR_PING, id);
}
}
class ButtplugUnknownError extends ButtplugError {
constructor(message, id = SYSTEM_MESSAGE_ID) {
super(message, ErrorClass.ERROR_UNKNOWN, id);
}
}
const SYSTEM_MESSAGE_ID = 0;
const DEFAULT_MESSAGE_ID = 1;
const MAX_ID = 4294967295;
const MESSAGE_SPEC_VERSION_MAJOR = 4;
const MESSAGE_SPEC_VERSION_MINOR = 0;
function msgId(msg) {
for (const [_, entry] of Object.entries(msg)) {
if (entry != void 0) {
return entry.Id;
}
}
throw new ButtplugMessageError(`Message ${msg} does not have an ID.`);
}
function setMsgId(msg, id) {
for (const [_, entry] of Object.entries(msg)) {
if (entry != void 0) {
entry.Id = id;
return;
}
}
throw new ButtplugMessageError(`Message ${msg} does not have an ID.`);
}
var ErrorClass = /* @__PURE__ */ ((ErrorClass2) => {
ErrorClass2[ErrorClass2["ERROR_UNKNOWN"] = 0] = "ERROR_UNKNOWN";
ErrorClass2[ErrorClass2["ERROR_INIT"] = 1] = "ERROR_INIT";
ErrorClass2[ErrorClass2["ERROR_PING"] = 2] = "ERROR_PING";
ErrorClass2[ErrorClass2["ERROR_MSG"] = 3] = "ERROR_MSG";
ErrorClass2[ErrorClass2["ERROR_DEVICE"] = 4] = "ERROR_DEVICE";
return ErrorClass2;
})(ErrorClass || {});
var OutputType = /* @__PURE__ */ ((OutputType2) => {
OutputType2["Unknown"] = "Unknown";
OutputType2["Vibrate"] = "Vibrate";
OutputType2["Rotate"] = "Rotate";
OutputType2["Oscillate"] = "Oscillate";
OutputType2["Constrict"] = "Constrict";
OutputType2["Inflate"] = "Inflate";
OutputType2["Position"] = "Position";
OutputType2["HwPositionWithDuration"] = "HwPositionWithDuration";
OutputType2["Temperature"] = "Temperature";
OutputType2["Spray"] = "Spray";
OutputType2["Led"] = "Led";
return OutputType2;
})(OutputType || {});
var InputType = /* @__PURE__ */ ((InputType2) => {
InputType2["Unknown"] = "Unknown";
InputType2["Battery"] = "Battery";
InputType2["RSSI"] = "RSSI";
InputType2["Button"] = "Button";
InputType2["Pressure"] = "Pressure";
return InputType2;
})(InputType || {});
var InputCommandType = /* @__PURE__ */ ((InputCommandType2) => {
InputCommandType2["Read"] = "Read";
InputCommandType2["Subscribe"] = "Subscribe";
InputCommandType2["Unsubscribe"] = "Unsubscribe";
return InputCommandType2;
})(InputCommandType || {});
class ButtplugClientDeviceFeature {
constructor(_deviceIndex, _deviceName, _feature, _sendClosure) {
this._deviceIndex = _deviceIndex;
this._deviceName = _deviceName;
this._feature = _feature;
this._sendClosure = _sendClosure;
}
send = async (msg) => {
return await this._sendClosure(msg);
};
sendMsgExpectOk = async (msg) => {
const response = await this.send(msg);
if (response.Ok !== void 0) {
return;
} else if (response.Error !== void 0) {
throw ButtplugError.FromError(response);
} else {
throw new ButtplugMessageError("Expected Ok or Error, and didn't get either!");
}
};
isOutputValid(type) {
if (this._feature.Output !== void 0 && !Object.prototype.hasOwnProperty.call(this._feature.Output, type)) {
throw new ButtplugDeviceError(
`Feature index ${this._feature.FeatureIndex} does not support type ${type} for device ${this._deviceName}`
);
}
}
isInputValid(type) {
if (this._feature.Input !== void 0 && !Object.prototype.hasOwnProperty.call(this._feature.Input, type)) {
throw new ButtplugDeviceError(
`Feature index ${this._feature.FeatureIndex} does not support type ${type} for device ${this._deviceName}`
);
}
}
async sendOutputCmd(command) {
this.isOutputValid(command.outputType);
if (command.value === void 0) {
throw new ButtplugDeviceError(`${command.outputType} requires value defined`);
}
const type = command.outputType;
let duration = void 0;
if (type == OutputType.HwPositionWithDuration) {
if (command.duration === void 0) {
throw new ButtplugDeviceError("PositionWithDuration requires duration defined");
}
duration = command.duration;
}
let value;
const p = command.value;
if (p.percent === void 0) {
value = command.value.steps;
} else {
value = Math.ceil(this._feature.Output[type].Value[1] * p.percent);
}
const newCommand = { Value: value, Duration: duration };
const outCommand = {};
outCommand[type.toString()] = newCommand;
const cmd = {
OutputCmd: {
Id: 1,
DeviceIndex: this._deviceIndex,
FeatureIndex: this._feature.FeatureIndex,
Command: outCommand
}
};
await this.sendMsgExpectOk(cmd);
}
get featureDescriptor() {
return this._feature.FeatureDescription;
}
get featureIndex() {
return this._feature.FeatureIndex;
}
get outputTypes() {
if (this._feature.Output === void 0) return [];
return Object.keys(this._feature.Output);
}
get inputTypes() {
if (this._feature.Input === void 0) return [];
return Object.keys(this._feature.Input);
}
outputMaxValue(type) {
if (this._feature.Output === void 0 || this._feature.Output[type] === void 0) {
return 0;
}
const val = this._feature.Output[type].Value;
if (Array.isArray(val)) {
return val[val.length - 1];
}
return val;
}
hasOutput(type) {
if (this._feature.Output !== void 0) {
return Object.prototype.hasOwnProperty.call(this._feature.Output, type.toString());
}
return false;
}
hasInput(type) {
if (this._feature.Input !== void 0) {
return Object.prototype.hasOwnProperty.call(this._feature.Input, type.toString());
}
return false;
}
async runOutput(cmd) {
if (this._feature.Output !== void 0 && Object.prototype.hasOwnProperty.call(this._feature.Output, cmd.outputType.toString())) {
return this.sendOutputCmd(cmd);
}
throw new ButtplugDeviceError(`Output type ${cmd.outputType} not supported by feature.`);
}
async runInput(inputType, inputCommand) {
this.isInputValid(inputType);
const inputAttributes = this._feature.Input[inputType];
console.log(this._feature.Input);
if (inputCommand === InputCommandType.Unsubscribe && !inputAttributes.Command.includes(InputCommandType.Subscribe) && !inputAttributes.Command.includes(inputCommand)) {
throw new ButtplugDeviceError(`${inputType} does not support command ${inputCommand}`);
}
const cmd = {
InputCmd: {
Id: 1,
DeviceIndex: this._deviceIndex,
FeatureIndex: this._feature.FeatureIndex,
Type: inputType,
Command: inputCommand
}
};
if (inputCommand == InputCommandType.Read) {
const response = await this.send(cmd);
if (response.InputReading !== void 0) {
return response.InputReading;
} else if (response.Error !== void 0) {
throw ButtplugError.FromError(response);
} else {
throw new ButtplugMessageError("Expected InputReading or Error, and didn't get either!");
}
} else {
console.log(`Sending subscribe message: ${JSON.stringify(cmd)}`);
await this.sendMsgExpectOk(cmd);
console.log("Got back ok?");
}
}
}
class ButtplugClientDevice extends EventEmitter {
/**
* @param _index Index of the device, as created by the device manager.
* @param _name Name of the device.
* @param allowedMsgs Buttplug messages the device can receive.
*/
constructor(_deviceInfo, _sendClosure) {
super();
this._deviceInfo = _deviceInfo;
this._sendClosure = _sendClosure;
this._features = new Map(
Object.entries(_deviceInfo.DeviceFeatures).map(([index, v]) => [
parseInt(index),
new ButtplugClientDeviceFeature(
_deviceInfo.DeviceIndex,
_deviceInfo.DeviceName,
v,
_sendClosure
)
])
);
}
_features;
/**
* Return the name of the device.
*/
get name() {
return this._deviceInfo.DeviceName;
}
/**
* Return the user set name of the device.
*/
get displayName() {
return this._deviceInfo.DeviceDisplayName;
}
/**
* Return the index of the device.
*/
get index() {
return this._deviceInfo.DeviceIndex;
}
/**
* Return the index of the device.
*/
get messageTimingGap() {
return this._deviceInfo.DeviceMessageTimingGap;
}
get features() {
return this._features;
}
static fromMsg(msg, sendClosure) {
return new ButtplugClientDevice(msg, sendClosure);
}
async send(msg) {
return await this._sendClosure(msg);
}
sendMsgExpectOk = async (msg) => {
const response = await this.send(msg);
if (response.Ok !== void 0) {
return;
} else if (response.Error !== void 0) {
throw ButtplugError.FromError(response);
} else ;
};
isOutputValid(featureIndex, type) {
if (!Object.prototype.hasOwnProperty.call(
this._deviceInfo.DeviceFeatures,
featureIndex.toString()
)) {
throw new ButtplugDeviceError(
`Feature index ${featureIndex} does not exist for device ${this.name}`
);
}
if (this._deviceInfo.DeviceFeatures[featureIndex.toString()].Outputs !== void 0 && !Object.prototype.hasOwnProperty.call(
this._deviceInfo.DeviceFeatures[featureIndex.toString()].Outputs,
type
)) {
throw new ButtplugDeviceError(
`Feature index ${featureIndex} does not support type ${type} for device ${this.name}`
);
}
}
hasOutput(type) {
return this._features.values().filter((f) => f.hasOutput(type)).toArray().length > 0;
}
hasInput(type) {
return this._features.values().filter((f) => f.hasInput(type)).toArray().length > 0;
}
async runOutput(cmd) {
const p = [];
for (const f of this._features.values()) {
if (f.hasOutput(cmd.outputType)) {
p.push(f.runOutput(cmd));
}
}
if (p.length == 0) {
return Promise.reject(`No features with output type ${cmd.outputType}`);
}
await Promise.all(p);
}
async stop() {
await this.sendMsgExpectOk({
StopCmd: {
Id: 1,
DeviceIndex: this.index,
FeatureIndex: void 0,
Inputs: true,
Outputs: true
}
});
}
async battery() {
for (const f of this._features.values()) {
if (f.hasInput(InputType.Battery)) {
const response = await f.runInput(
InputType.Battery,
InputCommandType.Read
);
if (response === void 0) {
throw new ButtplugMessageError("Got incorrect message back.");
}
if (response.Reading[InputType.Battery] === void 0) {
throw new ButtplugMessageError("Got reading with no Battery info.");
}
return response.Reading[InputType.Battery].Value;
}
}
throw new ButtplugDeviceError(`No battery present on this device.`);
}
emitDisconnected() {
this.emit("deviceremoved");
}
}
class ButtplugMessageSorter {
constructor(_useCounter) {
this._useCounter = _useCounter;
}
_counter = 1;
_waitingMsgs = /* @__PURE__ */ new Map();
// 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
PrepareOutgoingMessage(msg) {
if (this._useCounter) {
setMsgId(msg, this._counter);
this._counter += 1;
}
let res;
let rej;
const msgPromise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
this._waitingMsgs.set(msgId(msg), [res, rej]);
return msgPromise;
}
ParseIncomingMessages(msgs) {
const noMatch = [];
for (const x of msgs) {
const id = msgId(x);
if (id !== SYSTEM_MESSAGE_ID && this._waitingMsgs.has(id)) {
const [res, rej] = this._waitingMsgs.get(id);
this._waitingMsgs.delete(id);
if (x.Error !== void 0) {
rej(ButtplugError.FromError(x.Error));
continue;
}
res(x);
continue;
} else {
noMatch.push(x);
}
}
return noMatch;
}
}
class ButtplugClientConnectorException extends ButtplugError {
constructor(message) {
super(message, ErrorClass.ERROR_UNKNOWN);
}
}
class ButtplugClient extends EventEmitter {
_pingTimer = null;
_connector = null;
_devices = /* @__PURE__ */ new Map();
_clientName;
_logger = ButtplugLogger.Logger;
_isScanning = false;
_sorter = new ButtplugMessageSorter(true);
constructor(clientName = "Generic Buttplug Client") {
super();
this._clientName = clientName;
this._logger.Debug(`ButtplugClient: Client ${clientName} created.`);
}
get connected() {
return this._connector !== null && this._connector.Connected;
}
get devices() {
this.checkConnector();
return this._devices;
}
get isScanning() {
return this._isScanning;
}
connect = async (connector) => {
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();
};
disconnect = async () => {
this._logger.Debug("ButtplugClient: Disconnect called");
this._devices.clear();
this.checkConnector();
await this.shutdownConnection();
await this._connector.disconnect();
};
startScanning = async () => {
this._logger.Debug("ButtplugClient: StartScanning called");
this._isScanning = true;
await this.sendMsgExpectOk({ StartScanning: { Id: 1 } });
};
stopScanning = async () => {
this._logger.Debug("ButtplugClient: StopScanning called");
this._isScanning = false;
await this.sendMsgExpectOk({ StopScanning: { Id: 1 } });
};
stopAllDevices = async () => {
this._logger.Debug("ButtplugClient: StopAllDevices");
await this.sendMsgExpectOk({
StopCmd: {
Id: 1,
DeviceIndex: void 0,
FeatureIndex: void 0,
Inputs: true,
Outputs: true
}
});
};
disconnectHandler = () => {
this._logger.Info("ButtplugClient: Disconnect event receieved.");
this.emit("disconnect");
};
parseMessages = (msgs) => {
const leftoverMsgs = this._sorter.ParseIncomingMessages(msgs);
for (const x of leftoverMsgs) {
if (x.DeviceList !== void 0) {
this.parseDeviceList(x.DeviceList);
break;
} else if (x.ScanningFinished !== void 0) {
this._isScanning = false;
this.emit("scanningfinished", x);
} else if (x.InputReading !== void 0) {
this.emit("inputreading", x);
} else {
console.log(`Unhandled message: ${x}`);
}
}
};
initializeConnection = async () => {
this.checkConnector();
const msg = await this.sendMessage({
RequestServerInfo: {
ClientName: this._clientName,
Id: 1,
ProtocolVersionMajor: MESSAGE_SPEC_VERSION_MAJOR,
ProtocolVersionMinor: MESSAGE_SPEC_VERSION_MINOR
}
});
if (msg.ServerInfo !== void 0) {
const serverinfo = msg;
this._logger.Info(`ButtplugClient: Connected to Server ${serverinfo.ServerName}`);
serverinfo.MaxPingTime;
await this.requestDeviceList();
return true;
} else if (msg.Error !== void 0) {
await this._connector.disconnect();
const err = msg.Error;
throw ButtplugError.LogAndError(
ButtplugInitError,
this._logger,
`Cannot connect to server. ${err.ErrorMessage}`
);
}
return false;
};
parseDeviceList = (list) => {
for (const [_, d] of Object.entries(list.Devices)) {
if (!this._devices.has(d.DeviceIndex)) {
const device = ButtplugClientDevice.fromMsg(d, this.sendMessageClosure);
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}`);
}
}
for (const [index, device] of this._devices.entries()) {
if (!Object.prototype.hasOwnProperty.call(list.Devices, index.toString())) {
this._devices.delete(index);
this.emit("deviceremoved", device);
}
}
};
requestDeviceList = async () => {
this.checkConnector();
this._logger.Debug("ButtplugClient: ReceiveDeviceList called");
const response = await this.sendMessage({
RequestDeviceList: { Id: 1 }
});
this.parseDeviceList(response.DeviceList);
};
shutdownConnection = async () => {
await this.stopAllDevices();
if (this._pingTimer !== null) {
clearInterval(this._pingTimer);
this._pingTimer = null;
}
};
async sendMessage(msg) {
this.checkConnector();
const p = this._sorter.PrepareOutgoingMessage(msg);
await this._connector.send(msg);
return await p;
}
checkConnector() {
if (!this.connected) {
throw new ButtplugClientConnectorException("ButtplugClient not connected");
}
}
sendMsgExpectOk = async (msg) => {
const response = await this.sendMessage(msg);
if (response.Ok !== void 0) {
return;
} else if (response.Error !== void 0) {
throw ButtplugError.FromError(response);
} else {
throw ButtplugError.LogAndError(
ButtplugMessageError,
this._logger,
`Message ${response} not handled by SendMsgExpectOk`
);
}
};
sendMessageClosure = async (msg) => {
return await this.sendMessage(msg);
};
}
class ButtplugBrowserWebsocketConnector extends EventEmitter {
constructor(_url) {
super();
this._url = _url;
}
_ws;
_websocketConstructor = null;
get Connected() {
return this._ws !== void 0;
}
connect = async () => {
return new Promise((resolve, reject) => {
const ws = new (this._websocketConstructor ?? WebSocket)(this._url);
const onErrorCallback = (event) => {
reject(event);
};
const onCloseCallback = (event) => 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);
}
});
ws.addEventListener("error", onErrorCallback);
ws.addEventListener("close", onCloseCallback);
});
};
disconnect = async () => {
if (!this.Connected) {
return;
}
this._ws.close();
this._ws = void 0;
this.emit("disconnect");
};
sendMessage(msg) {
if (!this.Connected) {
throw new Error("ButtplugBrowserWebsocketConnector not connected");
}
this._ws.send("[" + JSON.stringify(msg) + "]");
}
initialize = async () => {
return Promise.resolve();
};
parseIncomingMessage(event) {
if (typeof event.data === "string") {
const msgs = JSON.parse(event.data);
this.emit("message", msgs);
} else if (event.data instanceof Blob) ;
}
onReaderLoad(event) {
const msgs = JSON.parse(event.target.result);
this.emit("message", msgs);
}
}
class ButtplugBrowserWebsocketClientConnector extends ButtplugBrowserWebsocketConnector {
send = (msg) => {
if (!this.Connected) {
throw new Error("ButtplugClient not connected");
}
this.sendMessage(msg);
};
}
var browser;
var hasRequiredBrowser;
function requireBrowser () {
if (hasRequiredBrowser) return browser;
hasRequiredBrowser = 1;
browser = function () {
throw new Error(
'ws does not work in the browser. Browser clients must use the native ' +
'WebSocket object'
);
};
return browser;
}
var browserExports = requireBrowser();
class ButtplugNodeWebsocketClientConnector extends ButtplugBrowserWebsocketClientConnector {
_websocketConstructor = browserExports.WebSocket;
}
class PercentOrSteps {
_percent;
_steps;
get percent() {
return this._percent;
}
get steps() {
return this._steps;
}
static createSteps(s) {
const v = new PercentOrSteps();
v._steps = s;
return v;
}
static createPercent(p) {
if (p < 0 || p > 1) {
throw new ButtplugDeviceError(`Percent value ${p} is not in the range 0.0 <= x <= 1.0`);
}
const v = new PercentOrSteps();
v._percent = p;
return v;
}
}
class DeviceOutputCommand {
constructor(_outputType, _value, _duration) {
this._outputType = _outputType;
this._value = _value;
this._duration = _duration;
}
get outputType() {
return this._outputType;
}
get value() {
return this._value;
}
get duration() {
return this._duration;
}
}
class DeviceOutputValueConstructor {
constructor(_outputType) {
this._outputType = _outputType;
}
steps(steps) {
return new DeviceOutputCommand(this._outputType, PercentOrSteps.createSteps(steps), void 0);
}
percent(percent) {
return new DeviceOutputCommand(
this._outputType,
PercentOrSteps.createPercent(percent),
void 0
);
}
}
class DeviceOutputPositionWithDurationConstructor {
steps(steps, duration) {
return new DeviceOutputCommand(
OutputType.Position,
PercentOrSteps.createSteps(steps),
duration
);
}
percent(percent, duration) {
return new DeviceOutputCommand(
OutputType.HwPositionWithDuration,
PercentOrSteps.createPercent(percent),
duration
);
}
}
class DeviceOutput {
constructor() {
}
static get Vibrate() {
return new DeviceOutputValueConstructor(OutputType.Vibrate);
}
static get Rotate() {
return new DeviceOutputValueConstructor(OutputType.Rotate);
}
static get Oscillate() {
return new DeviceOutputValueConstructor(OutputType.Oscillate);
}
static get Constrict() {
return new DeviceOutputValueConstructor(OutputType.Constrict);
}
static get Inflate() {
return new DeviceOutputValueConstructor(OutputType.Inflate);
}
static get Temperature() {
return new DeviceOutputValueConstructor(OutputType.Temperature);
}
static get Led() {
return new DeviceOutputValueConstructor(OutputType.Led);
}
static get Spray() {
return new DeviceOutputValueConstructor(OutputType.Spray);
}
static get Position() {
return new DeviceOutputValueConstructor(OutputType.Position);
}
static get PositionWithDuration() {
return new DeviceOutputPositionWithDurationConstructor();
}
}
class ButtplugWasmClientConnector extends EventEmitter {
static _loggingActivated = false;
static wasmInstance;
_connected = false;
client;
serverPtr;
constructor() {
super();
}
get Connected() {
return this._connected;
}
static maybeLoadWasm = async () => {
if (ButtplugWasmClientConnector.wasmInstance == void 0) {
const wasmModule = await import('../wasm/index.js');
await wasmModule.default();
ButtplugWasmClientConnector.wasmInstance = wasmModule;
}
};
static activateLogging = async (logLevel = "debug") => {
await ButtplugWasmClientConnector.maybeLoadWasm();
if (this._loggingActivated) {
console.log("Logging already activated, ignoring.");
return;
}
console.log("Turning on logging.");
ButtplugWasmClientConnector.wasmInstance.buttplug_activate_env_logger(logLevel);
};
initialize = async () => {
};
connect = async () => {
await ButtplugWasmClientConnector.maybeLoadWasm();
this.client = ButtplugWasmClientConnector.wasmInstance.buttplug_create_embedded_wasm_server(
(msgs) => {
this.emitMessage(msgs);
},
this.serverPtr
);
this._connected = true;
};
disconnect = async () => {
};
send = (msg) => {
ButtplugWasmClientConnector.wasmInstance.buttplug_client_send_json_message(
this.client,
new TextEncoder().encode("[" + JSON.stringify(msg) + "]"),
(output) => {
this.emitMessage(output);
}
);
};
emitMessage = (msg) => {
const str = new TextDecoder().decode(msg);
const msgs = JSON.parse(str);
this.emit("message", msgs);
};
}
export { ButtplugBrowserWebsocketClientConnector, ButtplugClient, ButtplugClientConnectorException, ButtplugClientDevice, ButtplugClientDeviceFeature, ButtplugDeviceError, ButtplugError, ButtplugInitError, ButtplugLogLevel, ButtplugLogger, ButtplugMessageError, ButtplugMessageSorter, ButtplugNodeWebsocketClientConnector, ButtplugPingError, ButtplugUnknownError, ButtplugWasmClientConnector, DEFAULT_MESSAGE_ID, DeviceOutput, DeviceOutputCommand, DeviceOutputPositionWithDurationConstructor, DeviceOutputValueConstructor, ErrorClass, InputCommandType, InputType, LogMessage, MAX_ID, MESSAGE_SPEC_VERSION_MAJOR, MESSAGE_SPEC_VERSION_MINOR, OutputType, SYSTEM_MESSAGE_ID, msgId, setMsgId };