UNPKG

ive-connect

Version:

A universal haptic device control library for interactive experiences

378 lines (377 loc) 13.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.AutoblowDevice = void 0; /** * Autoblow Device Implementation * * Implements the HapticDevice interface for Autoblow devices (Ultra and Vacuglide) * Focused on sync script playback functionality */ const device_interface_1 = require("../../core/device-interface"); const events_1 = require("../../core/events"); /** * Default Autoblow configuration */ const DEFAULT_CONFIG = { id: 'autoblow', name: 'Autoblow', enabled: true, deviceToken: '', offset: 0, }; /** * Autoblow device implementation */ class AutoblowDevice extends events_1.EventEmitter { constructor(config) { super(); this._connectionState = device_interface_1.ConnectionState.DISCONNECTED; this._deviceInfo = null; this._device = null; this._deviceType = null; this._isPlaying = false; this._scriptPrepared = false; this.id = 'autoblow'; this.name = 'Autoblow'; this.type = 'autoblow'; this.capabilities = [ device_interface_1.DeviceCapability.LINEAR, device_interface_1.DeviceCapability.STROKE, ]; this._config = { ...DEFAULT_CONFIG }; if (config) { Object.assign(this._config, config); } } /** * Get connected state */ get isConnected() { return this._connectionState === device_interface_1.ConnectionState.CONNECTED; } /** * Get playing state */ get isPlaying() { return this._isPlaying; } /** * Get the device type (ultra or vacuglide) */ get deviceType() { return this._deviceType; } /** * Connect to the Autoblow device */ async connect(config) { try { if (config) { await this.updateConfig(config); } if (!this._config.deviceToken || this._config.deviceToken.length < 5) { this.emit('error', 'Device token must be at least 5 characters'); return false; } this._connectionState = device_interface_1.ConnectionState.CONNECTING; this.emit('connectionStateChanged', this._connectionState); // Dynamically import the SDK let sdk; try { sdk = await Promise.resolve().then(() => __importStar(require('@xsense/autoblow-sdk'))); } catch (error) { this.emit('error', 'Failed to load Autoblow SDK. Make sure @xsense/autoblow-sdk is installed.'); this._connectionState = device_interface_1.ConnectionState.DISCONNECTED; this.emit('connectionStateChanged', this._connectionState); return false; } // Initialize device connection const result = await sdk.deviceInit(this._config.deviceToken); // Store the appropriate device reference if (result.ultra) { this._device = result.ultra; this._deviceType = 'autoblow-ultra'; } else if (result.vacuglide) { this._device = result.vacuglide; this._deviceType = 'vacuglide'; } else { throw new Error('No device returned from SDK'); } this._deviceInfo = result.deviceInfo; this._connectionState = device_interface_1.ConnectionState.CONNECTED; this.emit('connectionStateChanged', this._connectionState); this.emit('connected', this.getDeviceInfo()); return true; } catch (error) { console.error('Autoblow: Error connecting to device:', error); this._connectionState = device_interface_1.ConnectionState.DISCONNECTED; this.emit('connectionStateChanged', this._connectionState); this.emit('error', `Connection error: ${error instanceof Error ? error.message : String(error)}`); return false; } } /** * Disconnect from the device */ async disconnect() { try { if (this._isPlaying) { await this.stop(); } this._device = null; this._deviceInfo = null; this._deviceType = null; this._isPlaying = false; this._scriptPrepared = false; this._connectionState = device_interface_1.ConnectionState.DISCONNECTED; this.emit('connectionStateChanged', this._connectionState); this.emit('disconnected'); return true; } catch (error) { console.error('Autoblow: Error disconnecting:', error); this._connectionState = device_interface_1.ConnectionState.DISCONNECTED; this.emit('connectionStateChanged', this._connectionState); this.emit('disconnected'); return true; } } /** * Get current configuration */ getConfig() { return { ...this._config }; } /** * Update configuration */ async updateConfig(config) { if (config.deviceToken !== undefined) { this._config.deviceToken = config.deviceToken; } if (config.offset !== undefined) { this._config.offset = config.offset; // Apply offset to device if connected if (this.isConnected && this._device) { try { await this._device.syncScriptOffset(config.offset); } catch (error) { console.error('Autoblow: Error setting offset:', error); } } } if (config.name !== undefined) { this._config.name = config.name; } if (config.enabled !== undefined) { this._config.enabled = config.enabled; } this.emit('configChanged', this._config); return true; } /** * Prepare a script for playback (upload to device) * The funscript is already parsed - we just need to upload it * * @param funscript The parsed funscript content */ async prepareScript(funscript) { if (!this.isConnected || !this._device) { return { success: false, error: 'Device not connected' }; } try { // Validate funscript format if (!funscript.actions || !Array.isArray(funscript.actions)) { return { success: false, error: 'Invalid script format: Missing actions array', }; } // Convert to Autoblow SDK format and upload const sdkFunscript = { actions: funscript.actions.map((action) => ({ at: action.at, pos: action.pos, })), }; await this._device.syncScriptUploadFunscriptFile(sdkFunscript); this._scriptPrepared = true; this.emit('scriptLoaded', { type: 'funscript', actions: funscript.actions.length, }); return { success: true }; } catch (error) { console.error('Autoblow: Error preparing script:', error); return { success: false, error: `Script preparation error: ${error instanceof Error ? error.message : String(error)}`, }; } } /** * Start playback at the specified time */ async play(timeMs, _playbackRate = 1.0, _loop = false) { if (!this.isConnected || !this._device) { this.emit('error', 'Cannot play: Device not connected'); return false; } if (!this._scriptPrepared) { this.emit('error', 'Cannot play: No script prepared'); return false; } try { // Apply offset before starting if (this._config.offset !== 0) { await this._device.syncScriptOffset(this._config.offset); } await this._device.syncScriptStart(timeMs); this._isPlaying = true; this.emit('playbackStateChanged', { isPlaying: this._isPlaying, timeMs, }); return true; } catch (error) { console.error('Autoblow: Error starting playback:', error); this._isPlaying = false; this.emit('error', `Playback error: ${error instanceof Error ? error.message : String(error)}`); this.emit('playbackStateChanged', { isPlaying: false }); return false; } } /** * Stop playback */ async stop() { if (!this.isConnected || !this._device) { this.emit('error', 'Cannot stop: Device not connected'); return false; } try { await this._device.syncScriptStop(); this._isPlaying = false; this.emit('playbackStateChanged', { isPlaying: false }); return true; } catch (error) { console.error('Autoblow: Error stopping playback:', error); this._isPlaying = false; this.emit('error', `Stop error: ${error instanceof Error ? error.message : String(error)}`); this.emit('playbackStateChanged', { isPlaying: false }); return false; } } /** * Sync time - Autoblow handles this via syncScriptStart * We restart playback at the new time position */ async syncTime(timeMs, _filter) { if (!this.isConnected || !this._isPlaying || !this._device) { return false; } try { // Autoblow doesn't have a direct sync method, restart at new position await this._device.syncScriptStart(timeMs); return true; } catch (error) { console.error('Autoblow: Error syncing time:', error); return false; } } /** * Set the sync script offset */ async setOffset(offsetMs) { if (!this.isConnected || !this._device) { this.emit('error', 'Cannot set offset: Device not connected'); return false; } try { await this._device.syncScriptOffset(offsetMs); this._config.offset = offsetMs; this.emit('configChanged', this._config); return true; } catch (error) { console.error('Autoblow: Error setting offset:', error); return false; } } /** * Get device state */ async getState() { if (!this.isConnected || !this._device) { return null; } try { return await this._device.getState(); } catch (error) { console.error('Autoblow: Error getting state:', error); return null; } } /** * Get device information */ getDeviceInfo() { if (!this._deviceInfo) return null; return { id: this.id, name: this.name, type: this.type, deviceType: this._deviceType, firmware: String(this._deviceInfo.firmwareVersion), firmwareBranch: this._deviceInfo.firmwareBranch, hardware: this._deviceInfo.hardwareVersion, firmwareStatus: this._deviceInfo.firmwareStatus, mac: this._deviceInfo.mac, }; } } exports.AutoblowDevice = AutoblowDevice;