ive-connect
Version:
A universal haptic device control library for interactive experiences
271 lines (270 loc) • 9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeviceManager = void 0;
/**
* Device Manager
*
* Central manager for all haptic devices.
* Handles unified script loading and distribution to devices.
*/
const events_1 = require("./events");
const script_loader_1 = require("./script-loader");
/**
* Device Manager class
* Handles registration and control of multiple haptic devices
*/
class DeviceManager extends events_1.EventEmitter {
constructor() {
super(...arguments);
this.devices = new Map();
this.currentFunscript = null;
this.currentScriptOptions = null;
}
/**
* Register a device with the manager
* @param device Device to register
*/
registerDevice(device) {
var _a;
if (this.devices.has(device.id)) {
return;
}
this.devices.set(device.id, device);
this.setupDeviceEventForwarding(device);
this.emit("deviceAdded", device);
// If we have a script loaded, prepare it on the new device
if (this.currentFunscript) {
device
.prepareScript(this.currentFunscript, (_a = this.currentScriptOptions) !== null && _a !== void 0 ? _a : undefined)
.catch((error) => {
console.error(`Error preparing script on newly registered device ${device.id}:`, error);
});
}
}
/**
* Unregister a device from the manager
* @param deviceId Device ID to unregister
*/
unregisterDevice(deviceId) {
const device = this.devices.get(deviceId);
if (device) {
this.devices.delete(deviceId);
this.emit("deviceRemoved", device);
}
}
/**
* Get all registered devices
*/
getDevices() {
return Array.from(this.devices.values());
}
/**
* Get a specific device by ID
* @param deviceId Device ID to retrieve
*/
getDevice(deviceId) {
return this.devices.get(deviceId);
}
/**
* Get the currently loaded funscript
*/
getCurrentFunscript() {
return this.currentFunscript;
}
/**
* Connect to all registered devices
* @returns Object with success status for each device
*/
async connectAll() {
const results = {};
for (const [id, device] of this.devices.entries()) {
try {
results[id] = await device.connect();
}
catch (error) {
console.error(`Error connecting device ${id}:`, error);
results[id] = false;
}
}
return results;
}
/**
* Disconnect from all registered devices
* @returns Object with success status for each device
*/
async disconnectAll() {
const results = {};
for (const [id, device] of this.devices.entries()) {
try {
results[id] = await device.disconnect();
}
catch (error) {
console.error(`Error disconnecting device ${id}:`, error);
results[id] = false;
}
}
return results;
}
/**
* Load a script - fetches, parses, and prepares on all connected devices
*
* This is the main entry point for loading scripts. It:
* 1. Fetches and parses the script (once, centrally)
* 2. Applies any transformations (inversion, sorting)
* 3. Distributes to all connected devices
* 4. Returns the funscript along with per-device results
*
* @param scriptData Script data to load (URL or content)
* @param options Options for script loading (e.g., invertScript)
* @returns ScriptLoadResult with funscript and per-device status
*/
async loadScript(scriptData, options) {
// Step 1: Fetch and parse the script centrally
const loadResult = await (0, script_loader_1.loadScript)(scriptData, options);
if (!loadResult.success || !loadResult.funscript) {
return {
success: false,
funscript: null,
error: loadResult.error,
devices: {},
};
}
// Store the loaded script
this.currentFunscript = loadResult.funscript;
this.currentScriptOptions = options !== null && options !== void 0 ? options : null;
// Step 2: Prepare on all connected devices
const deviceResults = {};
for (const [id, device] of this.devices.entries()) {
// Only prepare on connected devices (or buttplug which manages its own connection)
if (device.isConnected || device.id === "buttplug") {
try {
const result = await device.prepareScript(loadResult.funscript, options);
deviceResults[id] = result;
}
catch (error) {
console.error(`Error preparing script on device ${id}:`, error);
deviceResults[id] = {
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
else {
deviceResults[id] = {
success: false,
error: "Device not connected",
};
}
}
// Emit event
this.emit("scriptLoaded", {
funscript: loadResult.funscript,
devices: deviceResults,
});
return {
success: true,
funscript: loadResult.funscript,
devices: deviceResults,
};
}
/**
* Start playback on all connected devices
* @param timeMs Current time in milliseconds
* @param playbackRate Playback rate (1.0 = normal speed)
* @param loop Whether to loop the script
* @returns Object with success status for each device
*/
async playAll(timeMs, playbackRate = 1.0, loop = false) {
const results = {};
for (const [id, device] of this.devices.entries()) {
if (device.isConnected) {
try {
results[id] = await device.play(timeMs, playbackRate, loop);
}
catch (error) {
console.error(`Error playing on device ${id}:`, error);
results[id] = false;
}
}
else {
results[id] = false;
}
}
return results;
}
/**
* Stop playback on all connected devices
* @returns Object with success status for each device
*/
async stopAll() {
const results = {};
for (const [id, device] of this.devices.entries()) {
if (device.isConnected) {
try {
results[id] = await device.stop();
}
catch (error) {
console.error(`Error stopping device ${id}:`, error);
results[id] = false;
}
}
else {
results[id] = false;
}
}
return results;
}
/**
* Synchronize time on all connected and playing devices
* @param timeMs Current time in milliseconds
* @param filter Time filter for synchronization
* @returns Object with success status for each device
*/
async syncTimeAll(timeMs, filter = 0.5) {
const results = {};
for (const [id, device] of this.devices.entries()) {
if (device.isConnected && device.isPlaying) {
try {
results[id] = await device.syncTime(timeMs, filter);
}
catch (error) {
console.error(`Error syncing time on device ${id}:`, error);
results[id] = false;
}
}
else {
results[id] = false;
}
}
return results;
}
/**
* Clear the currently loaded script
*/
clearScript() {
this.currentFunscript = null;
this.currentScriptOptions = null;
}
/**
* Set up event forwarding from a device to the manager
* @param device Device to forward events from
*/
setupDeviceEventForwarding(device) {
const eventsToForward = [
"error",
"connected",
"disconnected",
"connectionStateChanged",
"playbackStateChanged",
"scriptLoaded",
"configChanged",
];
for (const eventName of eventsToForward) {
device.on(eventName, (data) => {
this.emit(`device:${device.id}:${eventName}`, data);
this.emit(`device:${eventName}`, { deviceId: device.id, data });
});
}
}
}
exports.DeviceManager = DeviceManager;