Skip to content

Commit ed91823

Browse files
qdotclaude
andcommitted
feat(wasm): split into buttplug-wasm-blob and buttplug-wasm packages
Restructures the wasm/ directory as an npm workspace with two packages: - buttplug-wasm-blob: WASM server binary + typed FFI wrapper. No external deps. Third-party client libraries can use this directly. - buttplug-wasm (packages/connector): thin connector for the official buttplug JS client. Depends on buttplug-wasm-blob + buttplug. This lets alternative JS implementations (e.g., zendrex/buttplug.js) consume the WASM server without pulling in our specific client library. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e287411 commit ed91823

17 files changed

Lines changed: 385 additions & 176 deletions

File tree

.github/workflows/main.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ jobs:
9999
node-version: '20'
100100
- name: Build WASM crate
101101
run: wasm-pack build --dev crates/buttplug_wasm --target web
102-
- name: Install TS wrapper dependencies
102+
- name: Install workspace dependencies
103103
run: cd wasm && npm install
104-
- name: Build TS wrapper
105-
run: cd wasm && npm run build:web
104+
- name: Build packages
105+
run: cd wasm && npm run build:blob && npm run build:connector

crates/buttplug_wasm/README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ This is the Rust FFI layer that compiles Buttplug to WebAssembly via `wasm-bindg
55
## What this crate does
66

77
- Exposes Buttplug server functionality to JavaScript/TypeScript through wasm-bindgen
8-
- Produces WASM output consumed by the [`buttplug-wasm`](https://www.npmjs.com/package/buttplug-wasm) npm package
8+
- Produces WASM output consumed by the [`buttplug-wasm-blob`](https://www.npmjs.com/package/buttplug-wasm-blob) npm package
99
- Includes the WebBluetooth hardware manager for browser-native device discovery
1010

1111
## For users
1212

13-
Install the `buttplug-wasm` npm package instead:
13+
Install one of the npm packages instead:
1414

15-
```bash
16-
npm install buttplug-wasm
17-
```
15+
- **Using the official buttplug JS client?** `npm install buttplug-wasm buttplug`
16+
- **Building your own client?** `npm install buttplug-wasm-blob`
1817

1918
## Building (for contributors)
2019

@@ -26,4 +25,4 @@ This outputs to `pkg/` (gitignored). The output is consumed by `../../wasm/` dur
2625

2726
## Why `publish = false`?
2827

29-
This crate is not useful on its own — it's a thin FFI shim over `buttplug_server`. The publishable artifact is the npm package that bundles the compiled WASM binary with a TypeScript connector class.
28+
This crate is not useful on its own — it's a thin FFI shim over `buttplug_server`. The publishable artifacts are the `buttplug-wasm-blob` and `buttplug-wasm` npm packages in `../../wasm/packages/`.

wasm/.npmignore

Lines changed: 0 additions & 6 deletions
This file was deleted.

wasm/README.md

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,37 @@
1-
# buttplug-wasm
1+
# Buttplug WASM Packages
22

3-
Buttplug WASM connector for running an embedded Buttplug server directly in the browser via WebAssembly and Web Bluetooth.
3+
This workspace contains the npm packages for running a Buttplug server in the browser via WebAssembly.
44

5-
## Installation
5+
## Packages
6+
7+
| Package | Description |
8+
|---------|-------------|
9+
| [`buttplug-wasm-blob`](packages/blob/) | WASM server binary + typed FFI wrapper. Use this if you're building your own connector for a third-party buttplug client library. |
10+
| [`buttplug-wasm`](packages/connector/) | Ready-made connector for the official [`buttplug`](https://www.npmjs.com/package/buttplug) JS client library. |
11+
12+
## Quick Start
13+
14+
If you're using the official buttplug JS client:
615

716
```bash
817
npm install buttplug-wasm buttplug
918
```
1019

11-
## Usage
12-
1320
```typescript
1421
import { ButtplugClient } from 'buttplug';
1522
import { ButtplugWasmClientConnector } from 'buttplug-wasm';
1623

17-
// Optional: enable debug logging
18-
await ButtplugWasmClientConnector.activateLogging("debug");
19-
2024
const connector = new ButtplugWasmClientConnector();
2125
const client = new ButtplugClient("My App");
22-
2326
await client.connect(connector);
24-
await client.startScanning();
25-
26-
client.on("deviceadded", (device) => {
27-
console.log(`Device connected: ${device.Name}`);
28-
});
2927
```
3028

31-
## How it works
29+
## Building
3230

33-
This package bundles a full Buttplug server compiled to WebAssembly. When you create a `ButtplugWasmClientConnector`, it:
34-
35-
1. Loads and initializes the WASM module (lazy, on first `connect()`)
36-
2. Creates an embedded Buttplug server instance
37-
3. Communicates with the server via JSON message passing over the WASM boundary
38-
4. Uses Web Bluetooth for device discovery and communication
39-
40-
No external server process needed — everything runs in-browser.
41-
42-
## Requirements
43-
44-
- A browser with Web Bluetooth support (Chrome, Edge, Opera)
45-
- HTTPS context (Web Bluetooth requires secure origins)
46-
- The `buttplug` npm package (peer dependency for `ButtplugClient`)
31+
```bash
32+
npm install
33+
npm run build # builds wasm-pack + both packages
34+
```
4735

4836
## License
4937

wasm/examples/basic/vite.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ function wasmEnvPlugin(): Plugin {
2121
export default defineConfig({
2222
resolve: {
2323
alias: {
24-
"buttplug-wasm": fileURLToPath(new URL("../../src/index.ts", import.meta.url)),
24+
"buttplug-wasm": fileURLToPath(new URL("../../packages/connector/src/index.ts", import.meta.url)),
25+
"buttplug-wasm-blob": fileURLToPath(new URL("../../packages/blob/src/index.ts", import.meta.url)),
2526
"@wasm": fileURLToPath(new URL("../../../crates/buttplug_wasm/pkg", import.meta.url)),
2627
},
2728
},
2829
optimizeDeps: {
29-
exclude: ["buttplug-wasm"],
30+
exclude: ["buttplug-wasm", "buttplug-wasm-blob"],
3031
},
3132
server: {
3233
fs: {

wasm/package.json

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,13 @@
11
{
2-
"name": "buttplug-wasm",
3-
"version": "3.0.0",
4-
"description": "Buttplug WASM connector for running an embedded Buttplug server in the browser",
5-
"license": "BSD-3-Clause",
6-
"author": "Nonpolynomial Labs, LLC <kyle@nonpolynomial.com>",
7-
"homepage": "https://buttplug.io",
8-
"repository": {
9-
"type": "git",
10-
"url": "https://github.com/buttplugio/buttplug.git",
11-
"directory": "wasm"
12-
},
13-
"bugs": {
14-
"url": "https://github.com/buttplugio/buttplug/issues"
15-
},
16-
"keywords": [
17-
"buttplug",
18-
"haptics",
19-
"teledildonics",
20-
"wasm",
21-
"webbluetooth",
22-
"bluetooth",
23-
"browser"
24-
],
25-
"type": "module",
26-
"main": "./dist/buttplug-wasm.mjs",
27-
"module": "./dist/buttplug-wasm.mjs",
28-
"types": "./dist/index.d.ts",
29-
"exports": {
30-
".": {
31-
"types": "./dist/index.d.ts",
32-
"import": "./dist/buttplug-wasm.mjs",
33-
"default": "./dist/buttplug-wasm.mjs"
34-
}
35-
},
36-
"files": [
37-
"dist"
38-
],
2+
"private": true,
393
"workspaces": [
40-
"examples/basic"
4+
"packages/*",
5+
"examples/*"
416
],
42-
"engines": {
43-
"node": ">=18"
44-
},
457
"scripts": {
468
"build:wasm": "cd ../crates/buttplug_wasm && wasm-pack build --target web",
47-
"build:web": "vite build",
48-
"build": "npm run build:wasm && npm run build:web",
49-
"prepublishOnly": "npm run build"
50-
},
51-
"dependencies": {
52-
"buttplug": "^3.2.1",
53-
"eventemitter3": "^5.0.1"
54-
},
55-
"devDependencies": {
56-
"typescript": "^5.3.3",
57-
"vite": "^5.1.4",
58-
"vite-plugin-dts": "^3.7.3",
59-
"vite-plugin-top-level-await": "^1.4.1",
60-
"vite-plugin-wasm": "^3.3.0"
9+
"build:blob": "npm run build -w buttplug-wasm-blob",
10+
"build:connector": "npm run build -w buttplug-wasm",
11+
"build": "npm run build:wasm && npm run build:blob && npm run build:connector"
6112
}
6213
}

wasm/packages/blob/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# buttplug-wasm-blob
2+
3+
Buttplug WASM server binary with a typed FFI wrapper. This package bundles a complete Buttplug server compiled to WebAssembly with Web Bluetooth support.
4+
5+
## Who is this for?
6+
7+
- **Building your own buttplug client?** Use this package directly and write your own connector.
8+
- **Using the official buttplug JS client?** Install [`buttplug-wasm`](https://www.npmjs.com/package/buttplug-wasm) instead, which provides a ready-made connector.
9+
10+
## Installation
11+
12+
```bash
13+
npm install buttplug-wasm-blob
14+
```
15+
16+
## API
17+
18+
```typescript
19+
import {
20+
loadButtplugWasm,
21+
createServer,
22+
freeServer,
23+
sendMessage,
24+
activateLogging,
25+
} from 'buttplug-wasm-blob';
26+
27+
// Load the WASM module (call once, lazy-loaded)
28+
await loadButtplugWasm();
29+
30+
// Optional: enable debug logging
31+
activateLogging("debug");
32+
33+
// Create a server — the callback receives server events as JSON bytes
34+
const handle = createServer((msg: Uint8Array) => {
35+
const json = new TextDecoder().decode(msg);
36+
console.log("Server event:", JSON.parse(json));
37+
});
38+
39+
// Send a message to the server — wrap as JSON array bytes
40+
const request = JSON.stringify({ RequestServerInfo: { Id: 1, ClientName: "MyApp", ProtocolVersionMajor: 4, ProtocolVersionMinor: 0 } });
41+
sendMessage(handle, new TextEncoder().encode('[' + request + ']'), (response: Uint8Array) => {
42+
console.log("Response:", JSON.parse(new TextDecoder().decode(response)));
43+
});
44+
45+
// Clean up
46+
freeServer(handle);
47+
```
48+
49+
## Message Format
50+
51+
All messages are JSON-encoded Buttplug protocol v4 messages, wrapped in arrays and passed as `Uint8Array` (UTF-8 bytes). See the [Buttplug protocol spec](https://buttplug-spec.docs.buttplug.io/) for message formats.
52+
53+
## Requirements
54+
55+
- A browser with Web Bluetooth support (Chrome, Edge, Opera)
56+
- HTTPS context (Web Bluetooth requires secure origins)
57+
58+
## License
59+
60+
BSD-3-Clause

wasm/packages/blob/package.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "buttplug-wasm-blob",
3+
"version": "3.0.0",
4+
"description": "Buttplug WASM server binary with typed FFI wrapper",
5+
"license": "BSD-3-Clause",
6+
"author": "Nonpolynomial Labs, LLC <kyle@nonpolynomial.com>",
7+
"homepage": "https://buttplug.io",
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/buttplugio/buttplug.git",
11+
"directory": "wasm/packages/blob"
12+
},
13+
"bugs": {
14+
"url": "https://github.com/buttplugio/buttplug/issues"
15+
},
16+
"keywords": [
17+
"buttplug",
18+
"wasm",
19+
"webbluetooth",
20+
"teledildonics"
21+
],
22+
"type": "module",
23+
"main": "./dist/buttplug-wasm-blob.mjs",
24+
"module": "./dist/buttplug-wasm-blob.mjs",
25+
"types": "./dist/src/index.d.ts",
26+
"exports": {
27+
".": {
28+
"types": "./dist/src/index.d.ts",
29+
"import": "./dist/buttplug-wasm-blob.mjs"
30+
}
31+
},
32+
"files": [
33+
"dist"
34+
],
35+
"engines": {
36+
"node": ">=18"
37+
},
38+
"scripts": {
39+
"build": "vite build",
40+
"prepublishOnly": "npm run build"
41+
},
42+
"devDependencies": {
43+
"typescript": "^6.0.3",
44+
"vite": "^8.0.13",
45+
"vite-plugin-dts": "^5.0.0",
46+
"vite-plugin-top-level-await": "^1.6.0",
47+
"vite-plugin-wasm": "^3.6.0"
48+
}
49+
}

wasm/packages/blob/src/index.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export type ButtplugServerCallback = (msg: Uint8Array) => void;
2+
export type ButtplugServerHandle = number;
3+
4+
let wasmInstance: any;
5+
6+
export async function loadButtplugWasm(): Promise<void> {
7+
if (wasmInstance == undefined) {
8+
const wasm = await import('@wasm/buttplug_wasm.js');
9+
await wasm.default();
10+
wasmInstance = wasm;
11+
}
12+
}
13+
14+
export function createServer(callback: ButtplugServerCallback): ButtplugServerHandle {
15+
return wasmInstance.buttplug_create_embedded_wasm_server(callback);
16+
}
17+
18+
export function freeServer(handle: ButtplugServerHandle): void {
19+
wasmInstance.buttplug_free_embedded_wasm_server(handle);
20+
}
21+
22+
export function sendMessage(
23+
handle: ButtplugServerHandle,
24+
msg: Uint8Array,
25+
callback: ButtplugServerCallback,
26+
): void {
27+
wasmInstance.buttplug_client_send_json_message(handle, msg, callback);
28+
}
29+
30+
export function activateLogging(level: string = "debug"): void {
31+
wasmInstance.buttplug_activate_env_logger(level);
32+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"esModuleInterop": true,
1212
"allowSyntheticDefaultImports": true,
1313
"paths": {
14-
"@wasm/*": ["../crates/buttplug_wasm/pkg/*"]
14+
"@wasm/*": ["../../../crates/buttplug_wasm/pkg/*"]
1515
}
1616
},
1717
"include": ["src/**/*"],

0 commit comments

Comments
 (0)