|
| 1 | +// Buttplug Web - Complete Application Example |
| 2 | +// |
| 3 | +// This is a complete, working example that demonstrates the full workflow |
| 4 | +// of a Buttplug application in a browser. If you're new to Buttplug, start here! |
| 5 | +// |
| 6 | +// Prerequisites: |
| 7 | +// 1. Install Intiface Central: https://intiface.com/central |
| 8 | +// 2. Start the server in Intiface Central (click "Start Server") |
| 9 | +// 3. Include Buttplug via CDN in your HTML: |
| 10 | +// <script src="https://cdn.jsdelivr.net/npm/buttplug@4.0.0/dist/web/buttplug.min.js"></script> |
| 11 | +// 4. Call runApplicationExample() from your page |
| 12 | + |
| 13 | +async function runApplicationExample() { |
| 14 | + console.log("==========================================="); |
| 15 | + console.log(" Buttplug Web Application Example"); |
| 16 | + console.log("===========================================\n"); |
| 17 | + |
| 18 | + // Step 1: Create a client |
| 19 | + // The client name identifies your application to the server. |
| 20 | + const client = new Buttplug.ButtplugClient("My Buttplug Application"); |
| 21 | + |
| 22 | + // Step 2: Set up event handlers |
| 23 | + // Always do this BEFORE connecting to avoid missing events. |
| 24 | + client.addListener("deviceadded", (device) => { |
| 25 | + console.log(`[+] Device connected: ${device.name}`); |
| 26 | + }); |
| 27 | + |
| 28 | + client.addListener("deviceremoved", (device) => { |
| 29 | + console.log(`[-] Device disconnected: ${device.name}`); |
| 30 | + }); |
| 31 | + |
| 32 | + client.addListener("disconnect", () => { |
| 33 | + console.log("[!] Server connection lost!"); |
| 34 | + }); |
| 35 | + |
| 36 | + // Step 3: Connect to the server |
| 37 | + console.log("Connecting to Intiface Central..."); |
| 38 | + try { |
| 39 | + const connector = new Buttplug.ButtplugBrowserWebsocketClientConnector( |
| 40 | + "ws://127.0.0.1:12345" |
| 41 | + ); |
| 42 | + await client.connect(connector); |
| 43 | + } catch (e) { |
| 44 | + if (e instanceof Buttplug.ButtplugClientConnectorException) { |
| 45 | + alert( |
| 46 | + "Could not connect to Intiface Central!\n\n" + |
| 47 | + "Make sure Intiface Central is running and the server is started.\n" + |
| 48 | + "Default address: ws://127.0.0.1:12345" |
| 49 | + ); |
| 50 | + console.log("ERROR: Could not connect to Intiface Central!"); |
| 51 | + return; |
| 52 | + } |
| 53 | + throw e; |
| 54 | + } |
| 55 | + console.log("Connected!\n"); |
| 56 | + |
| 57 | + // Step 4: Scan for devices |
| 58 | + console.log("Scanning for devices..."); |
| 59 | + console.log("Turn on your Bluetooth/USB devices now.\n"); |
| 60 | + await client.startScanning(); |
| 61 | + |
| 62 | + // Wait for devices (using alert/confirm for browser interaction) |
| 63 | + alert("Scanning for devices...\n\nTurn on your devices, then click OK when ready."); |
| 64 | + await client.stopScanning(); |
| 65 | + |
| 66 | + // Step 5: Check what devices we found |
| 67 | + const devices = Array.from(client.devices.values()); |
| 68 | + if (devices.length === 0) { |
| 69 | + alert( |
| 70 | + "No devices found!\n\n" + |
| 71 | + "Make sure your device is:\n" + |
| 72 | + "- Turned on\n" + |
| 73 | + "- In pairing/discoverable mode\n" + |
| 74 | + "- Supported by Buttplug (check https://iostindex.com)" |
| 75 | + ); |
| 76 | + console.log("No devices found."); |
| 77 | + await client.disconnect(); |
| 78 | + return; |
| 79 | + } |
| 80 | + |
| 81 | + console.log(`\nFound ${devices.length} device(s):\n`); |
| 82 | + |
| 83 | + // Step 6: Display device capabilities |
| 84 | + for (const device of devices) { |
| 85 | + console.log(` ${device.name}`); |
| 86 | + |
| 87 | + // Check output capabilities |
| 88 | + const outputs = []; |
| 89 | + if (device.hasOutput(Buttplug.OutputType.Vibrate)) outputs.push("Vibrate"); |
| 90 | + if (device.hasOutput(Buttplug.OutputType.Rotate)) outputs.push("Rotate"); |
| 91 | + if (device.hasOutput(Buttplug.OutputType.Oscillate)) outputs.push("Oscillate"); |
| 92 | + if (device.hasOutput(Buttplug.OutputType.Position)) outputs.push("Position"); |
| 93 | + if (device.hasOutput(Buttplug.OutputType.Constrict)) outputs.push("Constrict"); |
| 94 | + |
| 95 | + if (outputs.length > 0) { |
| 96 | + console.log(` Outputs: ${outputs.join(", ")}`); |
| 97 | + } |
| 98 | + |
| 99 | + // Check input capabilities |
| 100 | + const inputs = []; |
| 101 | + if (device.hasInput(Buttplug.InputType.Battery)) inputs.push("Battery"); |
| 102 | + if (device.hasInput(Buttplug.InputType.RSSI)) inputs.push("RSSI"); |
| 103 | + |
| 104 | + if (inputs.length > 0) { |
| 105 | + console.log(` Inputs: ${inputs.join(", ")}`); |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + // Step 7: Interactive device control |
| 110 | + console.log("\n=== Interactive Control ==="); |
| 111 | + console.log("Use the prompts to control devices."); |
| 112 | + |
| 113 | + let running = true; |
| 114 | + while (running) { |
| 115 | + const input = prompt( |
| 116 | + "Commands:\n" + |
| 117 | + " v <0-100> - Vibrate all devices at percentage\n" + |
| 118 | + " s - Stop all devices\n" + |
| 119 | + " b - Read battery levels\n" + |
| 120 | + " q - Quit\n\n" + |
| 121 | + "Enter command:" |
| 122 | + ); |
| 123 | + |
| 124 | + if (input === null) { |
| 125 | + // User clicked Cancel |
| 126 | + running = false; |
| 127 | + continue; |
| 128 | + } |
| 129 | + |
| 130 | + const cmd = input.trim().toLowerCase(); |
| 131 | + |
| 132 | + if (!cmd) continue; |
| 133 | + |
| 134 | + try { |
| 135 | + if (cmd.startsWith("v ")) { |
| 136 | + // Vibrate command |
| 137 | + const percentStr = cmd.slice(2); |
| 138 | + const percent = parseInt(percentStr, 10); |
| 139 | + if (!isNaN(percent) && percent >= 0 && percent <= 100) { |
| 140 | + const intensity = percent / 100.0; |
| 141 | + for (const device of devices) { |
| 142 | + if (device.hasOutput(Buttplug.OutputType.Vibrate)) { |
| 143 | + await device.runOutput(Buttplug.DeviceOutput.Vibrate.percent(intensity)); |
| 144 | + console.log(` ${device.name}: vibrating at ${percent}%`); |
| 145 | + } |
| 146 | + } |
| 147 | + } else { |
| 148 | + alert("Usage: v <0-100>"); |
| 149 | + } |
| 150 | + } else if (cmd === "s") { |
| 151 | + // Stop all devices |
| 152 | + await client.stopAllDevices(); |
| 153 | + console.log(" All devices stopped."); |
| 154 | + } else if (cmd === "b") { |
| 155 | + // Read battery levels |
| 156 | + let batteryInfo = "Battery Levels:\n\n"; |
| 157 | + for (const device of devices) { |
| 158 | + if (device.hasInput(Buttplug.InputType.Battery)) { |
| 159 | + try { |
| 160 | + const battery = await device.battery(); |
| 161 | + const msg = `${device.name}: ${(battery * 100).toFixed(0)}%`; |
| 162 | + console.log(` ${msg}`); |
| 163 | + batteryInfo += msg + "\n"; |
| 164 | + } catch (e) { |
| 165 | + console.log(` ${device.name}: could not read battery`); |
| 166 | + batteryInfo += `${device.name}: could not read battery\n`; |
| 167 | + } |
| 168 | + } else { |
| 169 | + console.log(` ${device.name}: no battery sensor`); |
| 170 | + batteryInfo += `${device.name}: no battery sensor\n`; |
| 171 | + } |
| 172 | + } |
| 173 | + alert(batteryInfo); |
| 174 | + } else if (cmd === "q") { |
| 175 | + running = false; |
| 176 | + } else { |
| 177 | + alert("Unknown command. Use v, s, b, or q."); |
| 178 | + } |
| 179 | + } catch (e) { |
| 180 | + if (e instanceof Buttplug.ButtplugDeviceError) { |
| 181 | + console.log(` Device error: ${e.message}`); |
| 182 | + alert(`Device error: ${e.message}`); |
| 183 | + } else if (e instanceof Buttplug.ButtplugError) { |
| 184 | + console.log(` Error: ${e.message}`); |
| 185 | + alert(`Error: ${e.message}`); |
| 186 | + } else { |
| 187 | + throw e; |
| 188 | + } |
| 189 | + } |
| 190 | + } |
| 191 | + |
| 192 | + // Step 8: Clean up |
| 193 | + console.log("\nStopping devices and disconnecting..."); |
| 194 | + await client.stopAllDevices(); |
| 195 | + await client.disconnect(); |
| 196 | + console.log("Goodbye!"); |
| 197 | +} |
0 commit comments