webserial-core
Version:
A strongly-typed, event-driven, abstract TypeScript library for the Web Serial API with custom parsers, command queue, handshake validation, and auto-reconnect.
409 lines (392 loc) • 14.9 kB
HTML
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Web Bluetooth — webserial-core demo</title>
<style>
:root {
--accent: #a855f7;
--accent-dark: #9333ea;
--accent-glow: rgba(168, 85, 247, 0.12);
--accent-badge: #faf5ff;
}
</style>
<script
type="module"
crossorigin
src="/demos/assets/web-bluetooth-yFc46qqb.js"
></script>
<link
rel="modulepreload"
crossorigin
href="/demos/assets/demo-shared-BVQKG6NC.js"
/>
<link
rel="stylesheet"
crossorigin
href="/demos/assets/demo-shared-DLFsukQx.css"
/>
</head>
<body>
<!-- ── Topbar ──────────────────────────────────────────── -->
<header class="topbar">
<button class="icon-btn" id="menu-btn" title="Toggle sidebar">
<i data-lucide="menu"></i>
</button>
<a class="topbar-brand" href="/demos/web-serial.html">
<span class="brand-icon"><i data-lucide="bluetooth"></i></span>
<span class="brand-name">webserial-core</span>
<span class="brand-ver">v2</span>
</a>
<span class="provider-pill">Bluetooth</span>
<span class="flex-1"></span>
<nav class="topbar-nav">
<a class="nav-item" href="/demos/web-serial.html"
><i data-lucide="zap"></i><span class="nav-lbl"> Web Serial</span></a
>
<a class="nav-item active" href="/demos/web-bluetooth.html"
><i data-lucide="bluetooth"></i
><span class="nav-lbl"> Bluetooth</span></a
>
<a class="nav-item" href="/demos/web-usb.html"
><i data-lucide="usb"></i><span class="nav-lbl"> WebUSB</span></a
>
<a class="nav-item" href="/demos/websocket.html"
><i data-lucide="globe"></i><span class="nav-lbl"> WebSocket</span></a
>
</nav>
<div class="topbar-actions">
<div class="status-pill">
<div class="status-dot" id="status-dot"></div>
<span id="status-text">Disconnected</span>
</div>
<button class="icon-btn" id="theme-btn" title="Toggle theme">🌙</button>
<button
class="icon-btn code-toggle"
id="code-toggle-btn"
title="Toggle code panel"
>
<i data-lucide="code-2"></i>
</button>
<button class="btn btn-connect" id="btn-connect">Pair Device</button>
<button class="btn btn-disconnect" id="btn-disconnect" disabled>
Disconnect
</button>
</div>
</header>
<!-- ── App layout ───────────────────────────────────────── -->
<div class="app-layout">
<!-- ── Sidebar ─────────────────────────────────────────── -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-scroll">
<!-- BLE info notice -->
<div class="sb-section">
<div class="notice">
Uses <strong>Nordic UART Service (NUS)</strong> over GATT.
Compatible: nRF52, ESP32 BLE UART, or any NUS device
(<code>6e400001-…</code>).<br />
Requires <strong>HTTPS</strong> or <code>localhost</code> +
Chromium with Web Bluetooth enabled.
</div>
</div>
<!-- Connection settings (limited for BLE) -->
<div class="sb-section">
<div class="sb-title">Connection</div>
<div class="field-row">
<div class="field">
<label for="cfg-bufsize">Buffer Size</label>
<input
type="number"
id="cfg-bufsize"
value="255"
min="64"
max="65536"
/>
</div>
<div class="field">
<label for="cfg-timeout">Cmd Timeout (ms)</label>
<input type="number" id="cfg-timeout" value="3000" min="0" />
</div>
</div>
<div class="field">
<label for="cfg-handshake">Handshake Timeout (ms)</label>
<input type="number" id="cfg-handshake" value="2000" min="0" />
</div>
<div class="notice" style="margin-top: 6px">
<strong>Baud rate</strong> is not used over BLE — link speed is
negotiated at the GATT level.
</div>
</div>
<!-- Message format -->
<div class="sb-section">
<div class="sb-title">Message Format</div>
<div class="field">
<label for="cfg-delim">Delimiter</label>
<input
type="text"
id="cfg-delim"
value="\n"
placeholder="\n \r\n | etc."
/>
</div>
<div class="field-row">
<div class="field">
<label for="cfg-prepend">Prepend</label>
<input
type="text"
id="cfg-prepend"
value=""
placeholder="e.g. STX"
/>
</div>
<div class="field">
<label for="cfg-append">Append</label>
<input
type="text"
id="cfg-append"
value="\n"
placeholder="e.g. \n"
/>
</div>
</div>
</div>
<!-- Handshake -->
<div class="sb-section">
<div class="sb-title">Handshake on Connect</div>
<div class="notice" style="margin-bottom: 6px">
Sent automatically right after the GATT link opens. Leave
<em>Command</em> empty to skip entirely.
</div>
<div class="field-row">
<div class="field">
<label for="cfg-hs-cmd">Command to send</label>
<input
type="text"
id="cfg-hs-cmd"
value=""
placeholder="e.g. PING\n or FF 01 A3"
/>
</div>
<div class="field" style="flex: 0 0 62px">
<label for="cfg-hs-cmd-mode">Mode</label>
<select id="cfg-hs-cmd-mode">
<option value="text" selected>TXT</option>
<option value="hex">HEX</option>
</select>
</div>
</div>
<div class="field-row">
<div class="field">
<label for="cfg-hs-expect"
>Expected <small>(empty = no check)</small></label
>
<input
type="text"
id="cfg-hs-expect"
value=""
placeholder="e.g. PONG or OK"
/>
</div>
<div class="field" style="flex: 0 0 62px">
<label for="cfg-hs-expect-mode">Mode</label>
<select id="cfg-hs-expect-mode">
<option value="text" selected>TXT</option>
<option value="hex">HEX</option>
</select>
</div>
</div>
</div>
<!-- Saved Commands -->
<div class="sb-section">
<div class="sb-title">Saved Commands</div>
<div class="notice" style="margin-bottom: 6px">
Pre-defined commands to send. Use <em>HEX</em> for raw bytes.
</div>
<div class="field-row">
<div class="field" style="flex: 0 0 72px">
<label for="cmd-name">Name</label>
<input type="text" id="cmd-name" placeholder="LED ON" />
</div>
<div class="field">
<label for="cmd-value">Command</label>
<input type="text" id="cmd-value" placeholder="LED_ON\n" />
</div>
<div class="field" style="flex: 0 0 62px">
<label for="cmd-mode">Mode</label>
<select id="cmd-mode">
<option value="text" selected>TXT</option>
<option value="hex">HEX</option>
</select>
</div>
</div>
<button
class="btn btn-ghost"
id="cmd-add"
style="width: 100%; margin-top: 3px"
>
<i data-lucide="plus"></i> Add Command
</button>
<div class="chip-list" id="cmd-list"></div>
</div>
<!-- Data Listeners -->
<div class="sb-section">
<div class="sb-title">Data Listeners</div>
<div class="notice" style="margin-bottom: 6px">
Patterns for <code>on('serial:data')</code> — skeleton code in
download.
</div>
<div class="field-row">
<div class="field" style="flex: 0 0 64px">
<label for="lst-name">Name</label>
<input type="text" id="lst-name" placeholder="Temp" />
</div>
<div class="field">
<label for="lst-pattern">Pattern</label>
<input type="text" id="lst-pattern" placeholder="TEMP:" />
</div>
<div class="field" style="flex: 0 0 62px">
<label for="lst-match">Match</label>
<select id="lst-match">
<option value="exact">exact</option>
<option value="contains" selected>has</option>
<option value="startsWith">starts</option>
<option value="hex">hex</option>
</select>
</div>
</div>
<button
class="btn btn-ghost"
id="lst-add"
style="width: 100%; margin-top: 3px"
>
<i data-lucide="plus"></i> Add Listener
</button>
<div class="chip-list" id="lst-list"></div>
</div>
<!-- Download -->
<div class="sb-section">
<div class="sb-title">Download Code</div>
<div class="field">
<label for="dl-name">Class / File name</label>
<input
type="text"
id="dl-name"
value="MyBleDevice"
placeholder="MyBleDevice"
/>
</div>
<div class="dl-lang-row">
<label class="dl-opt"
><input type="radio" name="dl-lang" value="ts" checked />
TypeScript</label
>
<label class="dl-opt"
><input type="radio" name="dl-lang" value="js" />
JavaScript</label
>
</div>
<div class="dl-type-row">
<label class="dl-opt"
><input type="radio" name="dl-type" value="file" /> Standalone
file</label
>
<label class="dl-opt"
><input type="radio" name="dl-type" value="project" checked />
Full project (ZIP)</label
>
</div>
<div class="dl-btns">
<button class="btn btn-dl" id="dl-btn">
<i data-lucide="download"></i> Download
</button>
<button
class="btn btn-ghost"
id="cfg-export-btn"
title="Export configuration as JSON"
>
<i data-lucide="share"></i> Export config
</button>
<label
class="btn btn-ghost"
title="Import configuration from JSON"
>
<i data-lucide="upload"></i> Import config
<input
type="file"
id="cfg-import-input"
accept=".json"
style="display: none"
/>
</label>
</div>
</div>
</div>
<!-- /sidebar-scroll -->
</aside>
<div class="sidebar-resize-handle" id="sidebar-resize-handle"></div>
<!-- ── Chat console ─────────────────────────────────── -->
<main class="console-area">
<div class="console-hdr">
<div class="status-pill">
<div class="status-dot" id="console-dot"></div>
<span id="console-text">Pair a Bluetooth device to begin</span>
</div>
<button
class="btn btn-ghost"
id="clear-btn"
style="margin-left: auto; font-size: 0.7rem; padding: 4px 10px"
>
Clear
</button>
</div>
<div class="messages" id="messages">
<div class="empty-state">
<div class="empty-icon">📡</div>
<span>Pair a BLE device to begin</span>
</div>
</div>
<div class="input-bar">
<button class="btn btn-mode" id="mode-toggle">TXT</button>
<input
class="msg-input"
id="input-send"
type="text"
placeholder="Type a command, e.g. LED_ON"
disabled
/>
<button class="btn btn-send" id="btn-send" disabled>Send</button>
</div>
</main>
<!-- ── Code panel ────────────────────────────────────── -->
<div class="resize-handle" id="resize-handle"></div>
<aside class="code-panel" id="code-panel">
<div class="cp-hdr">
<div class="cp-title">
<span>Code Preview</span>
<span class="cp-tab" id="code-tab">device.ts</span>
</div>
<div class="cp-actions">
<button class="cp-btn" id="copy-btn">Copy</button>
</div>
</div>
<pre class="code-view" id="code-view"></pre>
</aside>
</div>
<!-- /app-layout -->
<footer class="app-footer">
© 2025
<a
href="https://github.com/danidoble"
style="color: inherit; text-decoration: none"
>@danidoble</a
>
· webserial-core v2
</footer>
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
<script>
lucide.createIcons();
</script>
</body>
</html>