ive-connect 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 iveplay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,251 +1,109 @@
1
- # ive-connect
2
-
3
- A universal haptic device control library that provides a consistent interface for managing various haptic devices (Handy, Buttplug, etc.).
4
-
5
- ## Features
6
-
7
- - Unified device control interface
8
- - Support for multiple device types
9
- - Event-based state management
10
- - TypeScript support
11
-
12
- ## Installation
13
-
14
- ```bash
15
- npm install ive-connect
16
- ```
17
-
18
- ## Quick Start
19
-
20
- ```typescript
21
- import { DeviceManager, HandyDevice } from "ive-connect";
22
-
23
- // Create a device manager
24
- const manager = new DeviceManager();
25
-
26
- // Create and register a Handy device
27
- const handyDevice = new HandyDevice({
28
- connectionKey: "your-connection-key",
29
- });
30
- manager.registerDevice(handyDevice);
31
-
32
- // Connect to the device
33
- await handyDevice.connect();
34
-
35
- // Load a script
36
- await handyDevice.loadScript({
37
- type: "funscript",
38
- url: "https://example.com/script.funscript",
39
- });
40
-
41
- // Start playback
42
- await handyDevice.play(0, 1.0, false);
43
-
44
- // Later, stop playback
45
- await handyDevice.stop();
46
-
47
- // Disconnect when done
48
- await handyDevice.disconnect();
49
- ```
50
-
51
- ## Supported Devices
52
-
53
- ### Handy
54
-
55
- ```typescript
56
- import { HandyDevice } from "ive-connect";
57
-
58
- const handy = new HandyDevice({
59
- connectionKey: "your-connection-key",
60
- // Optional custom configuration
61
- baseV3Url: "https://www.handyfeeling.com/api/v3",
62
- baseV2Url: "https://www.handyfeeling.com/api/v2",
63
- applicationId: "YourAppName",
64
- });
65
-
66
- // Connect to the device
67
- await handy.connect();
68
-
69
- // Update configuration
70
- await handy.updateConfig({
71
- offset: -200, // Timing offset in milliseconds
72
- stroke: {
73
- min: 0.1, // Min stroke position (0.0 to 1.0)
74
- max: 0.9, // Max stroke position (0.0 to 1.0)
75
- },
76
- });
77
-
78
- // Listen for events
79
- handy.on("connected", (deviceInfo) => {
80
- console.log("Connected to Handy:", deviceInfo);
81
- });
82
-
83
- handy.on("playbackStateChanged", (state) => {
84
- console.log("Playback state changed:", state.isPlaying);
85
- });
86
- ```
87
-
88
- ### Using the Device Manager
89
-
90
- ```typescript
91
- import { DeviceManager, HandyDevice } from "ive-connect";
92
-
93
- // Create a device manager
94
- const manager = new DeviceManager();
95
-
96
- // Register devices
97
- const handy = new HandyDevice({
98
- connectionKey: "your-connection-key",
99
- });
100
- manager.registerDevice(handy);
101
-
102
- // Connect all devices
103
- await manager.connectAll();
104
-
105
- // Load a script to all connected devices
106
- await manager.loadScriptAll({
107
- type: "funscript",
108
- url: "https://example.com/script.funscript",
109
- });
110
-
111
- // Start playback on all devices
112
- await manager.playAll(0, 1.0, false);
113
-
114
- // Sync time on all devices every second
115
- setInterval(() => {
116
- manager.syncTimeAll(videoElement.currentTime * 1000);
117
- }, 1000);
118
-
119
- // Listen for events from any device
120
- manager.on("device:connected", ({ deviceId, data }) => {
121
- console.log(`Device ${deviceId} connected:`, data);
122
- });
123
-
124
- // Listen for events from a specific device
125
- manager.on(`device:${handy.id}:error`, (error) => {
126
- console.error(`Error from ${handy.id}:`, error);
127
- });
128
- ```
129
-
130
- ## API Reference
131
-
132
- ### Core Classes
133
-
134
- #### `DeviceManager`
135
-
136
- Central manager for all haptic devices.
137
-
138
- - `registerDevice(device: HapticDevice): void` - Register a device
139
- - `unregisterDevice(deviceId: string): void` - Unregister a device
140
- - `getDevices(): HapticDevice[]` - Get all registered devices
141
- - `getDevice(deviceId: string): HapticDevice | undefined` - Get a specific device
142
- - `connectAll(): Promise<Record<string, boolean>>` - Connect all devices
143
- - `disconnectAll(): Promise<Record<string, boolean>>` - Disconnect all devices
144
- - `loadScriptAll(scriptData: ScriptData): Promise<Record<string, boolean>>` - Load script to all devices
145
- - `playAll(timeMs: number, playbackRate?: number, loop?: boolean): Promise<Record<string, boolean>>` - Start playback on all devices
146
- - `stopAll(): Promise<Record<string, boolean>>` - Stop playback on all devices
147
- - `syncTimeAll(timeMs: number): Promise<Record<string, boolean>>` - Sync time on all playing devices
148
-
149
- #### `HandyDevice`
150
-
151
- Implementation of the Handy device.
152
-
153
- - `connect(config?: Partial<HandySettings>): Promise<boolean>` - Connect to the device
154
- - `disconnect(): Promise<boolean>` - Disconnect from the device
155
- - `getConfig(): HandySettings` - Get current configuration
156
- - `updateConfig(config: Partial<HandySettings>): Promise<boolean>` - Update configuration
157
- - `loadScript(scriptData: ScriptData): Promise<boolean>` - Load a script
158
- - `play(timeMs: number, playbackRate?: number, loop?: boolean): Promise<boolean>` - Start playback
159
- - `stop(): Promise<boolean>` - Stop playback
160
- - `syncTime(timeMs: number): Promise<boolean>` - Sync time
161
- - `getDeviceInfo(): DeviceInfo | null` - Get device information
162
-
163
- ### Events
164
-
165
- Devices and the DeviceManager emit various events that you can listen to:
166
-
167
- #### Device Events
168
-
169
- - `error` - Error occurred
170
- - `connected` - Device connected
171
- - `disconnected` - Device disconnected
172
- - `connectionStateChanged` - Connection state changed
173
- - `playbackStateChanged` - Playback state changed
174
- - `scriptLoaded` - Script loaded successfully
175
- - `configChanged` - Configuration changed
176
-
177
- #### DeviceManager Events
178
-
179
- - `deviceAdded` - Device was added to the manager
180
- - `deviceRemoved` - Device was removed from the manager
181
- - `device:{deviceId}:{eventName}` - Event from a specific device
182
- - `device:{eventName}` - Event from any device
183
-
184
- ## Interfaces
185
-
186
- ### `HapticDevice`
187
-
188
- Common interface for all haptic devices.
189
-
190
- ```typescript
191
- interface HapticDevice {
192
- readonly id: string;
193
- readonly name: string;
194
- readonly type: string;
195
- readonly capabilities: DeviceCapability[];
196
- readonly isConnected: boolean;
197
- readonly isPlaying: boolean;
198
-
199
- connect(config?: any): Promise<boolean>;
200
- disconnect(): Promise<boolean>;
201
- getConfig(): DeviceSettings;
202
- updateConfig(config: Partial<DeviceSettings>): Promise<boolean>;
203
- loadScript(scriptData: ScriptData): Promise<boolean>;
204
- play(timeMs: number, playbackRate?: number, loop?: boolean): Promise<boolean>;
205
- stop(): Promise<boolean>;
206
- syncTime(timeMs: number): Promise<boolean>;
207
- getDeviceInfo(): DeviceInfo | null;
208
- on(event: string, callback: (data: any) => void): void;
209
- off(event: string, callback: (data: any) => void): void;
210
- }
211
- ```
212
-
213
- ### `ScriptData`
214
-
215
- Data for scripts to be loaded.
216
-
217
- ```typescript
218
- interface ScriptData {
219
- type: string; // Script type (e.g., "funscript")
220
- url?: string; // URL to script if remote
221
- content?: any; // Script content if loaded directly
222
- }
223
- ```
224
-
225
- ### `DeviceSettings`
226
-
227
- Base interface for device settings.
228
-
229
- ```typescript
230
- interface DeviceSettings {
231
- id: string; // Device identifier
232
- name: string; // Human-readable name
233
- enabled: boolean; // Whether the device is enabled
234
- [key: string]: any; // Additional device-specific settings
235
- }
236
- ```
237
-
238
- ### `HandySettings`
239
-
240
- Handy-specific settings.
241
-
242
- ```typescript
243
- interface HandySettings extends DeviceSettings {
244
- connectionKey: string; // Device connection key
245
- offset: number; // Timing offset in milliseconds
246
- stroke: {
247
- min: number; // Min stroke position (0.0 to 1.0)
248
- max: number; // Max stroke position (0.0 to 1.0)
249
- };
250
- }
251
- ```
1
+ # ive-connect
2
+
3
+ A universal haptic device control library that provides a consistent interface for managing various haptic devices (Handy, Buttplug, etc.).
4
+
5
+ ## Features
6
+
7
+ - Unified device control interface
8
+ - Support for multiple device types
9
+ - Event-based state management
10
+ - TypeScript support
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install ive-connect
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```typescript
21
+ import { DeviceManager, HandyDevice } from "ive-connect";
22
+
23
+ // Create a device manager
24
+ const manager = new DeviceManager();
25
+
26
+ // Create and register a Handy device
27
+ const handyDevice = new HandyDevice({
28
+ connectionKey: "your-connection-key",
29
+ });
30
+ manager.registerDevice(handyDevice);
31
+
32
+ // Connect to the device
33
+ await handyDevice.connect();
34
+
35
+ // Load a script
36
+ await handyDevice.loadScript({
37
+ type: "funscript",
38
+ url: "https://example.com/script.funscript",
39
+ });
40
+
41
+ // Start playback
42
+ await handyDevice.play(0, 1.0, false);
43
+
44
+ // Later, stop playback
45
+ await handyDevice.stop();
46
+
47
+ // Disconnect when done
48
+ await handyDevice.disconnect();
49
+ ```
50
+
51
+ ## Supported Devices
52
+
53
+ ### Handy
54
+
55
+ ```typescript
56
+ import { HandyDevice } from "ive-connect";
57
+
58
+ const handy = new HandyDevice({
59
+ connectionKey: "your-connection-key",
60
+ // Optional custom configuration
61
+ baseV3Url: "https://www.handyfeeling.com/api/v3",
62
+ baseV2Url: "https://www.handyfeeling.com/api/v2",
63
+ applicationId: "YourAppName",
64
+ });
65
+
66
+ // Connect to the device
67
+ await handy.connect();
68
+
69
+ // Update configuration
70
+ await handy.updateConfig({
71
+ offset: -200, // Timing offset in milliseconds
72
+ stroke: {
73
+ min: 0.1, // Min stroke position (0.0 to 1.0)
74
+ max: 0.9, // Max stroke position (0.0 to 1.0)
75
+ },
76
+ });
77
+
78
+ // Listen for events
79
+ handy.on("connected", (deviceInfo) => {
80
+ console.log("Connected to Handy:", deviceInfo);
81
+ });
82
+
83
+ handy.on("playbackStateChanged", (state) => {
84
+ console.log("Playback state changed:", state.isPlaying);
85
+ });
86
+ ```
87
+
88
+ ### Using the Device Manager
89
+
90
+ ```typescript
91
+ import { DeviceManager, HandyDevice } from "ive-connect";
92
+
93
+ // Create a device manager
94
+ const manager = new DeviceManager();
95
+
96
+ // Register devices
97
+ const handy = new HandyDevice({
98
+ connectionKey: "your-connection-key",
99
+ });
100
+ manager.registerDevice(handy);
101
+ ```
102
+
103
+ ## License
104
+
105
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
106
+
107
+ ## Acknowledgments
108
+
109
+ This project uses [buttplug.io](https://buttplug.io) (BSD 3-Clause License) for device communication.
@@ -43,7 +43,7 @@ const buttplug_1 = require("buttplug");
43
43
  const buttplug_2 = require("buttplug");
44
44
  const events_1 = require("../../core/events");
45
45
  const types_1 = require("./types");
46
- const DEBUG_WEBSOCKET = true;
46
+ const DEBUG_WEBSOCKET = false;
47
47
  class ButtplugApi extends events_1.EventEmitter {
48
48
  constructor(clientName = "IVE-Connect") {
49
49
  super();
@@ -411,11 +411,11 @@ class ButtplugDevice extends events_1.EventEmitter {
411
411
  const prevAction = actionIndex > 0
412
412
  ? this._currentScriptActions[actionIndex - 1]
413
413
  : { pos: 0 };
414
- // Calculate duration for linear movement based on time to next action
414
+ // Calculate duration for linear movement based on time with previous action
415
415
  let durationMs = 500; // Default duration if we can't determine
416
416
  if (actionIndex < this._currentScriptActions.length - 1) {
417
- const nextAction = this._currentScriptActions[actionIndex + 1];
418
- durationMs = nextAction.at - action.at;
417
+ const prevAction = this._currentScriptActions[actionIndex - 1];
418
+ durationMs = action.at - prevAction.at;
419
419
  // Enforce a minimum duration to prevent erratic movement
420
420
  durationMs = Math.max(100, durationMs);
421
421
  }
@@ -458,7 +458,12 @@ class ButtplugDevice extends events_1.EventEmitter {
458
458
  high = mid - 1;
459
459
  }
460
460
  }
461
- return bestIndex;
461
+ // When returning bestIndex, we're always getting
462
+ // the last action that has a timestamp <= timeMs
463
+ // Let's return the next action instead
464
+ return bestIndex < this._currentScriptActions.length - 1
465
+ ? bestIndex + 1
466
+ : bestIndex;
462
467
  }
463
468
  /**
464
469
  * Set up event handlers for the Buttplug API
@@ -166,7 +166,7 @@ class HandyApi {
166
166
  const response = await this.request("/hssp/play", {
167
167
  method: "PUT",
168
168
  body: JSON.stringify({
169
- start_time: Math.round(videoTime * 1000),
169
+ start_time: Math.round(videoTime),
170
170
  server_time: this.estimateServerTime(),
171
171
  playback_rate: playbackRate,
172
172
  loop,
@@ -203,7 +203,7 @@ class HandyApi {
203
203
  const response = await this.request("/hssp/synctime", {
204
204
  method: "PUT",
205
205
  body: JSON.stringify({
206
- current_time: Math.round(videoTime * 1000),
206
+ current_time: Math.round(videoTime),
207
207
  server_time: this.estimateServerTime(),
208
208
  filter,
209
209
  }),
@@ -205,8 +205,32 @@ class HandyDevice extends events_1.EventEmitter {
205
205
  let scriptUrl;
206
206
  // Handle script data based on type
207
207
  if (scriptData.url) {
208
- // If URL is provided, use it directly
209
- scriptUrl = scriptData.url;
208
+ // If URL ends with .funscript and it's a direct URL, fetch and upload it
209
+ if (scriptData.url.toLowerCase().endsWith(".funscript")) {
210
+ try {
211
+ const response = await fetch(scriptData.url);
212
+ if (!response.ok) {
213
+ throw new Error(`Failed to fetch funscript: ${response.status}`);
214
+ }
215
+ const funscriptContent = await response.json();
216
+ const blob = new Blob([JSON.stringify(funscriptContent)], {
217
+ type: "application/json",
218
+ });
219
+ const uploadedUrl = await this._api.uploadScript(blob);
220
+ if (!uploadedUrl) {
221
+ throw new Error("Failed to upload funscript");
222
+ }
223
+ scriptUrl = uploadedUrl;
224
+ }
225
+ catch (error) {
226
+ console.error("Error processing funscript URL:", error);
227
+ // Fall back to using the original URL
228
+ scriptUrl = scriptData.url;
229
+ }
230
+ }
231
+ else {
232
+ scriptUrl = scriptData.url;
233
+ }
210
234
  }
211
235
  else if (scriptData.content) {
212
236
  // If content is provided, upload it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ive-connect",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "A universal haptic device control library for interactive experiences",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,7 +10,8 @@
10
10
  "prebuild": "npm run clean",
11
11
  "prepare": "npm run build",
12
12
  "test": "jest",
13
- "lint": "eslint src --ext .ts"
13
+ "lint": "eslint src --ext .ts",
14
+ "release": "release-it"
14
15
  },
15
16
  "keywords": [
16
17
  "haptic",
@@ -19,7 +20,7 @@
19
20
  "device-control",
20
21
  "interactive"
21
22
  ],
22
- "author": "",
23
+ "author": "iveplay",
23
24
  "license": "MIT",
24
25
  "files": [
25
26
  "dist",
@@ -37,6 +38,7 @@
37
38
  "@typescript-eslint/parser": "^8.31.0",
38
39
  "eslint": "^9.25.1",
39
40
  "jest": "^29.7.0",
41
+ "release-it": "^19.0.2",
40
42
  "rimraf": "^6.0.1"
41
43
  },
42
44
  "engines": {