Type-safe Buttplug v4 client. WebSocket or WASM, Zod-validated.
Intiface Central or in-browser Web Bluetooth. Discovery, I/O, patterns.
bun add @zendrex/buttplug.jsWorks with npm, pnpm, and yarn. Any runtime with WebSocket support (Node 18+, Bun, Deno, browsers). Ships ESM and CJS with .d.ts types.
ButtplugClient: transport, v4 handshake, scanning, device list, reconnectDevice: capability checks and typed output/sensor methods- Zod-validated protocol messages (
src/protocol/schema); types inferred from schemas - Per-feature output ranges (typically
0–100), clamped to server metadata - Event-driven lifecycle (
device.*,connection.*,scan.*,input.reading) PatternEngine(@zendrex/buttplug.js/patterns): presets and custom keyframe tracks
| Approach | Typical use | Requirements |
|---|---|---|
| WebSocket (default) | Desktop apps, servers, scripts | Intiface Central on the same machine or network (ws://127.0.0.1:12345 by default) |
| WASM transport | Browser-only, no desktop server | Chromium, HTTPS or localhost, optional peer buttplug-wasm-blob |
WebSocket mode expects Intiface Central with Start Server enabled. Default endpoint: ws://127.0.0.1:12345 (or ws://localhost:12345).
import { ButtplugClient } from "@zendrex/buttplug.js";
const client = new ButtplugClient("ws://127.0.0.1:12345");
client.on("device.added", async ({ data: { device } }) => {
console.log(device.displayName ?? device.name);
if (device.canOutput("Vibrate")) {
await device.vibrate(0.5);
setTimeout(() => device.stop(), 2000);
}
});
await client.connect();
await client.startScanning();client.on("device.added", async ({ data: { device } }) => {
console.log(`${device.index}: ${device.name}`);
for (const output of device.features.outputs) {
console.log(` ${output.type} (feature ${output.featureIndex})`);
}
});
client.on("device.removed", ({ data: { device } }) => {
console.log(`gone: ${device.name}`);
});await device.vibrate(0.75);
await device.vibrate([
{ index: 0, value: 0.4 },
{ index: 1, value: 1 },
]);
await device.rotate(0.6, { clockwise: false });
await device.position(0.8, { duration: 500 });
await device.stop();All output values are normalized 0–1 floats; the library maps them to each feature's server-reported device range. canOutput / canRead / canSubscribe gate unsupported calls.
const level = await device.readSensor("Battery");
const unsub = await device.subscribeSensor("RSSI", (value) => {
console.log("RSSI", value);
});
await unsub();Pushed sensor updates also arrive on the client as input.reading.
@zendrex/buttplug.js/patterns
import { ButtplugClient } from "@zendrex/buttplug.js";
import { PatternEngine } from "@zendrex/buttplug.js/patterns";
const client = new ButtplugClient("ws://127.0.0.1:12345");
await client.connect();
const engine = new PatternEngine(client);
const id = await engine.play(device, "wave", {
intensity: 0.8,
speed: 1.5,
loop: true,
});
const id2 = await engine.play(device, [
{
featureIndex: 0,
keyframes: [
{ value: 0, duration: 0 },
{ value: 100, duration: 1000, easing: "easeIn" },
{ value: 20, duration: 500, easing: "easeOut" },
],
},
], { loop: 3, intensity: 0.6 });
await engine.stop(id);
engine.stopAll();
engine.dispose();Presets: pulse, wave, ramp_up, ramp_down, heartbeat, surge, stroke. Easings: linear, easeIn, easeOut, easeInOut, step.
bun add @zendrex/buttplug.js buttplug-wasm-blobimport { ButtplugClient } from "@zendrex/buttplug.js";
import { WasmTransport } from "@zendrex/buttplug.js/wasm";
const client = new ButtplugClient(new WasmTransport());
await client.connect();
await client.startScanning();Chromium over HTTPS or localhost only. WASM guide.
const client = new ButtplugClient("ws://127.0.0.1:12345", {
autoReconnect: true,
reconnectDelay: 1000,
maxReconnectDelay: 30_000,
maxReconnectAttempts: 10,
});
client.on("connection.reconnected", () => {
console.log("reconnected");
});On reconnect the client re-handshakes, refreshes the device list, and emits connection.reconnected. PatternEngine stops patterns for devices that disappeared.
Transport. URL string → WebSocketTransport. Custom Transport implementations are accepted in the constructor. WASM: WasmTransport from @zendrex/buttplug.js/wasm.
Validation. Schemas live in src/protocol/schema. Public types are inferred from them.
Devices. Device parses DeviceFeatures from the server descriptor. canOutput, canRead, and canSubscribe gate commands; unsupported calls throw DeviceError with deviceIndex.
Events. Emittery v2 shape: handlers get { data } (and { name, data } when needed). client.on(...) returns an unsubscribe function; client.clearListeners() and client.dispose() tear down.
Patterns. @zendrex/buttplug.js/patterns keeps the scheduler out of the main bundle. ButtplugClient implements PatternEngineClient.
| Import | Provides |
|---|---|
@zendrex/buttplug.js |
ButtplugClient, Device, errors, loggers, WebSocketTransport, protocol types |
@zendrex/buttplug.js/patterns |
PatternEngine, PRESETS, PRESET_NAMES, getPresetInfo, pattern types |
@zendrex/buttplug.js/wasm |
WasmTransport (peer: buttplug-wasm-blob) |
const client = new ButtplugClient(urlOrTransport, options?);
await client.connect();
await client.startScanning();
await client.stopScanning();
await client.stopAll();
await client.disconnect();
client.dispose();
client.connected; // boolean
client.scanning; // boolean
client.devices; // Device[]
client.serverInfo; // ServerInfo | nullConnection: connection.connecting, connection.connected, connection.disconnected, connection.reconnecting, connection.reconnected, connection.error
Devices: device.added, device.removed, device.updated, device.list, device.error
Scan: scan.started, scan.finished
Sensors: input.reading
Options: clientName, requestTimeout, autoPing, autoReconnect, reconnectDelay, maxReconnectDelay, maxReconnectAttempts, logger, verbose
| Method | Output type |
|---|---|
vibrate() |
Vibrate |
rotate() |
Rotate / RotateWithDirection |
oscillate() |
Oscillate |
constrict() |
Constrict |
spray() |
Spray |
temperature() |
Temperature |
led() |
Led |
position() |
Position / HwPositionWithDuration |
stop() |
stops outputs and/or sensor subscriptions |
OUTPUT_TYPES and INPUT_TYPES list protocol feature names the library recognizes.
engine.play(deviceOrIndex, presetName, options?);
engine.play(deviceOrIndex, tracks, options?);
engine.play(deviceOrIndex, descriptor, options?);
engine.stop(patternId);
engine.stopAll();
engine.list();
engine.dispose();All extend ButtplugError:
| Class | When |
|---|---|
ConnectionError |
Transport failure |
HandshakeError |
Handshake rejected |
ProtocolError |
Server error message (code from ErrorCode) |
DeviceError |
Missing capability or invalid pattern target (deviceIndex) |
TimeoutError |
requestTimeout exceeded (operation, timeoutMs) |
formatError(err) stringifies unknown throws safely.
Silent by default. verbose: true → console; explicit logger in options takes precedence. Wire traffic: browser DevTools → Network → WebSocket → Messages.
Site source is in docs/. Local dev:
bun run docsGuides: getting started, devices, commands, events, WASM, patterns.
MIT