webserial-core 2.0.0 → 2.0.1
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/dist/demo-style.css +255 -0
- package/dist/demos/assets/demo-shared-DLFsukQx.css +1 -0
- package/dist/demos/assets/demo-shared-DnvFynUr.js +272 -0
- package/dist/demos/assets/web-bluetooth-VVwTClLx.js +1 -0
- package/dist/demos/assets/web-serial-CsBUUDvz.js +1 -0
- package/dist/demos/assets/web-usb-D7sSHjku.js +1 -0
- package/dist/demos/assets/websocket-DREvCVt-.js +1 -0
- package/dist/demos/web-bluetooth.html +408 -0
- package/dist/demos/web-serial.html +483 -0
- package/dist/demos/web-usb.html +526 -0
- package/dist/demos/websocket.html +498 -0
- package/dist/dist/webserial-core.umd.js +1 -0
- package/dist/favicon.svg +10 -0
- package/dist/images/cover.svg +145 -0
- package/dist/images/icons/adapters.svg +13 -0
- package/dist/images/icons/events.svg +4 -0
- package/dist/images/icons/parsers.svg +7 -0
- package/dist/images/icons/queue.svg +8 -0
- package/dist/images/icons/reconnect.svg +7 -0
- package/dist/images/icons/typed.svg +5 -0
- package/dist/images/logo.svg +10 -0
- package/dist/robots.txt +4 -0
- package/dist/types/index.d.ts +6 -30
- package/package.json +5 -4
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
|
+
<title>WebSocket — webserial-core demo</title>
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--accent: #f97316;
|
|
11
|
+
--accent-dark: #ea580c;
|
|
12
|
+
--accent-glow: rgba(249, 115, 22, 0.12);
|
|
13
|
+
--accent-badge: #fff7ed;
|
|
14
|
+
}
|
|
15
|
+
</style>
|
|
16
|
+
<script
|
|
17
|
+
type="module"
|
|
18
|
+
crossorigin
|
|
19
|
+
src="/demos/assets/websocket-DREvCVt-.js"
|
|
20
|
+
></script>
|
|
21
|
+
<link
|
|
22
|
+
rel="modulepreload"
|
|
23
|
+
crossorigin
|
|
24
|
+
href="/demos/assets/demo-shared-DnvFynUr.js"
|
|
25
|
+
/>
|
|
26
|
+
<link
|
|
27
|
+
rel="stylesheet"
|
|
28
|
+
crossorigin
|
|
29
|
+
href="/demos/assets/demo-shared-DLFsukQx.css"
|
|
30
|
+
/>
|
|
31
|
+
</head>
|
|
32
|
+
<body>
|
|
33
|
+
<!-- ── Topbar ──────────────────────────────────────────── -->
|
|
34
|
+
<header class="topbar">
|
|
35
|
+
<button class="icon-btn" id="menu-btn" title="Toggle sidebar">
|
|
36
|
+
<i data-lucide="menu"></i>
|
|
37
|
+
</button>
|
|
38
|
+
<a class="topbar-brand" href="/demos/web-serial.html">
|
|
39
|
+
<span class="brand-icon"><i data-lucide="globe"></i></span>
|
|
40
|
+
<span class="brand-name">webserial-core</span>
|
|
41
|
+
<span class="brand-ver">v2</span>
|
|
42
|
+
</a>
|
|
43
|
+
<span class="provider-pill">WebSocket</span>
|
|
44
|
+
<span class="flex-1"></span>
|
|
45
|
+
|
|
46
|
+
<nav class="topbar-nav">
|
|
47
|
+
<a class="nav-item" href="/demos/web-serial.html"
|
|
48
|
+
><i data-lucide="zap"></i><span class="nav-lbl"> Web Serial</span></a
|
|
49
|
+
>
|
|
50
|
+
<a class="nav-item" href="/demos/web-bluetooth.html"
|
|
51
|
+
><i data-lucide="bluetooth"></i
|
|
52
|
+
><span class="nav-lbl"> Bluetooth</span></a
|
|
53
|
+
>
|
|
54
|
+
<a class="nav-item" href="/demos/web-usb.html"
|
|
55
|
+
><i data-lucide="usb"></i><span class="nav-lbl"> WebUSB</span></a
|
|
56
|
+
>
|
|
57
|
+
<a class="nav-item active" href="/demos/websocket.html"
|
|
58
|
+
><i data-lucide="globe"></i><span class="nav-lbl"> WebSocket</span></a
|
|
59
|
+
>
|
|
60
|
+
</nav>
|
|
61
|
+
|
|
62
|
+
<div class="topbar-actions">
|
|
63
|
+
<div class="status-pill">
|
|
64
|
+
<div class="status-dot" id="status-dot"></div>
|
|
65
|
+
<span id="status-text">Disconnected</span>
|
|
66
|
+
</div>
|
|
67
|
+
<button class="icon-btn" id="theme-btn" title="Toggle theme">🌙</button>
|
|
68
|
+
<button
|
|
69
|
+
class="icon-btn code-toggle"
|
|
70
|
+
id="code-toggle-btn"
|
|
71
|
+
title="Toggle code panel"
|
|
72
|
+
>
|
|
73
|
+
<i data-lucide="code-2"></i>
|
|
74
|
+
</button>
|
|
75
|
+
<button class="btn btn-connect" id="btn-connect">Connect</button>
|
|
76
|
+
<button class="btn btn-disconnect" id="btn-disconnect" disabled>
|
|
77
|
+
Disconnect
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
</header>
|
|
81
|
+
|
|
82
|
+
<!-- ── App layout ───────────────────────────────────────── -->
|
|
83
|
+
<div class="app-layout">
|
|
84
|
+
<!-- ── Sidebar ─────────────────────────────────────────── -->
|
|
85
|
+
<aside class="sidebar" id="sidebar">
|
|
86
|
+
<div class="sidebar-scroll">
|
|
87
|
+
<!-- Info notice -->
|
|
88
|
+
<div class="sb-section">
|
|
89
|
+
<div class="notice">
|
|
90
|
+
Requires the <strong>Node.js bridge server</strong> running
|
|
91
|
+
locally:<br />
|
|
92
|
+
<code>cd demos/websocket && node server.js</code><br />
|
|
93
|
+
The bridge relays serial I/O over WebSocket JSON messages.
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- WebSocket URL -->
|
|
98
|
+
<div class="sb-section">
|
|
99
|
+
<div class="sb-title">Bridge URL</div>
|
|
100
|
+
<div class="field">
|
|
101
|
+
<label for="cfg-wsurl">WebSocket URL</label>
|
|
102
|
+
<input
|
|
103
|
+
type="url"
|
|
104
|
+
id="cfg-wsurl"
|
|
105
|
+
value="ws://localhost:8080"
|
|
106
|
+
placeholder="ws://localhost:8080"
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<!-- Connection settings -->
|
|
112
|
+
<div class="sb-section">
|
|
113
|
+
<div class="sb-title">Connection</div>
|
|
114
|
+
|
|
115
|
+
<div class="field">
|
|
116
|
+
<label for="cfg-baud">Baud Rate</label>
|
|
117
|
+
<select id="cfg-baud">
|
|
118
|
+
<option>300</option>
|
|
119
|
+
<option>600</option>
|
|
120
|
+
<option>1200</option>
|
|
121
|
+
<option>2400</option>
|
|
122
|
+
<option>4800</option>
|
|
123
|
+
<option value="9600" selected>9600</option>
|
|
124
|
+
<option>14400</option>
|
|
125
|
+
<option>19200</option>
|
|
126
|
+
<option>38400</option>
|
|
127
|
+
<option>57600</option>
|
|
128
|
+
<option>115200</option>
|
|
129
|
+
<option>230400</option>
|
|
130
|
+
<option>250000</option>
|
|
131
|
+
<option>500000</option>
|
|
132
|
+
<option>1000000</option>
|
|
133
|
+
</select>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div class="field-row">
|
|
137
|
+
<div class="field">
|
|
138
|
+
<label for="cfg-databits">Data Bits</label>
|
|
139
|
+
<select id="cfg-databits">
|
|
140
|
+
<option value="7">7</option>
|
|
141
|
+
<option value="8" selected>8</option>
|
|
142
|
+
</select>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="field">
|
|
145
|
+
<label for="cfg-stopbits">Stop Bits</label>
|
|
146
|
+
<select id="cfg-stopbits">
|
|
147
|
+
<option value="1" selected>1</option>
|
|
148
|
+
<option value="2">2</option>
|
|
149
|
+
</select>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="field-row">
|
|
154
|
+
<div class="field">
|
|
155
|
+
<label for="cfg-parity">Parity</label>
|
|
156
|
+
<select id="cfg-parity">
|
|
157
|
+
<option value="none" selected>None</option>
|
|
158
|
+
<option value="even">Even</option>
|
|
159
|
+
<option value="odd">Odd</option>
|
|
160
|
+
</select>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="field">
|
|
163
|
+
<label for="cfg-flow">Flow Control</label>
|
|
164
|
+
<select id="cfg-flow">
|
|
165
|
+
<option value="none" selected>None</option>
|
|
166
|
+
<option value="hardware">Hardware</option>
|
|
167
|
+
</select>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div class="field-row">
|
|
172
|
+
<div class="field">
|
|
173
|
+
<label for="cfg-bufsize">Buffer Size</label>
|
|
174
|
+
<input
|
|
175
|
+
type="number"
|
|
176
|
+
id="cfg-bufsize"
|
|
177
|
+
value="255"
|
|
178
|
+
min="64"
|
|
179
|
+
max="65536"
|
|
180
|
+
/>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="field">
|
|
183
|
+
<label for="cfg-timeout">Cmd Timeout (ms)</label>
|
|
184
|
+
<input type="number" id="cfg-timeout" value="3000" min="0" />
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div class="field-row">
|
|
189
|
+
<div class="field">
|
|
190
|
+
<label for="cfg-handshake">Handshake (ms)</label>
|
|
191
|
+
<input type="number" id="cfg-handshake" value="2000" min="0" />
|
|
192
|
+
</div>
|
|
193
|
+
<div class="field">
|
|
194
|
+
<label for="cfg-reconnect-ms">Reconnect (ms)</label>
|
|
195
|
+
<input
|
|
196
|
+
type="number"
|
|
197
|
+
id="cfg-reconnect-ms"
|
|
198
|
+
value="1500"
|
|
199
|
+
min="500"
|
|
200
|
+
/>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div class="field-toggle">
|
|
205
|
+
<label for="cfg-autoreconnect">Auto Reconnect</label>
|
|
206
|
+
<label class="sw">
|
|
207
|
+
<input type="checkbox" id="cfg-autoreconnect" checked />
|
|
208
|
+
<span class="sw-knob"></span>
|
|
209
|
+
</label>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<!-- Message format -->
|
|
214
|
+
<div class="sb-section">
|
|
215
|
+
<div class="sb-title">Message Format</div>
|
|
216
|
+
<div class="field">
|
|
217
|
+
<label for="cfg-delim">Delimiter</label>
|
|
218
|
+
<input
|
|
219
|
+
type="text"
|
|
220
|
+
id="cfg-delim"
|
|
221
|
+
value="\n"
|
|
222
|
+
placeholder="\n \r\n | etc."
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="field-row">
|
|
226
|
+
<div class="field">
|
|
227
|
+
<label for="cfg-prepend">Prepend</label>
|
|
228
|
+
<input
|
|
229
|
+
type="text"
|
|
230
|
+
id="cfg-prepend"
|
|
231
|
+
value=""
|
|
232
|
+
placeholder="e.g. STX"
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
<div class="field">
|
|
236
|
+
<label for="cfg-append">Append</label>
|
|
237
|
+
<input
|
|
238
|
+
type="text"
|
|
239
|
+
id="cfg-append"
|
|
240
|
+
value="\n"
|
|
241
|
+
placeholder="e.g. \n"
|
|
242
|
+
/>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<!-- Handshake -->
|
|
248
|
+
<div class="sb-section">
|
|
249
|
+
<div class="sb-title">Handshake on Connect</div>
|
|
250
|
+
<div class="notice" style="margin-bottom: 6px">
|
|
251
|
+
Sent automatically right after the WebSocket bridge connects.
|
|
252
|
+
Leave
|
|
253
|
+
<em>Command</em> empty to skip entirely.
|
|
254
|
+
</div>
|
|
255
|
+
<div class="field-row">
|
|
256
|
+
<div class="field">
|
|
257
|
+
<label for="cfg-hs-cmd">Command to send</label>
|
|
258
|
+
<input
|
|
259
|
+
type="text"
|
|
260
|
+
id="cfg-hs-cmd"
|
|
261
|
+
value=""
|
|
262
|
+
placeholder="e.g. PING\n or FF 01 A3"
|
|
263
|
+
/>
|
|
264
|
+
</div>
|
|
265
|
+
<div class="field" style="flex: 0 0 62px">
|
|
266
|
+
<label for="cfg-hs-cmd-mode">Mode</label>
|
|
267
|
+
<select id="cfg-hs-cmd-mode">
|
|
268
|
+
<option value="text" selected>TXT</option>
|
|
269
|
+
<option value="hex">HEX</option>
|
|
270
|
+
</select>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="field-row">
|
|
274
|
+
<div class="field">
|
|
275
|
+
<label for="cfg-hs-expect"
|
|
276
|
+
>Expected <small>(empty = no check)</small></label
|
|
277
|
+
>
|
|
278
|
+
<input
|
|
279
|
+
type="text"
|
|
280
|
+
id="cfg-hs-expect"
|
|
281
|
+
value=""
|
|
282
|
+
placeholder="e.g. PONG or OK"
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="field" style="flex: 0 0 62px">
|
|
286
|
+
<label for="cfg-hs-expect-mode">Mode</label>
|
|
287
|
+
<select id="cfg-hs-expect-mode">
|
|
288
|
+
<option value="text" selected>TXT</option>
|
|
289
|
+
<option value="hex">HEX</option>
|
|
290
|
+
</select>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<!-- Saved Commands -->
|
|
296
|
+
<div class="sb-section">
|
|
297
|
+
<div class="sb-title">Saved Commands</div>
|
|
298
|
+
<div class="notice" style="margin-bottom: 6px">
|
|
299
|
+
Pre-defined commands to send. Use <em>HEX</em> for raw bytes.
|
|
300
|
+
</div>
|
|
301
|
+
<div class="field-row">
|
|
302
|
+
<div class="field" style="flex: 0 0 72px">
|
|
303
|
+
<label for="cmd-name">Name</label>
|
|
304
|
+
<input type="text" id="cmd-name" placeholder="LED ON" />
|
|
305
|
+
</div>
|
|
306
|
+
<div class="field">
|
|
307
|
+
<label for="cmd-value">Command</label>
|
|
308
|
+
<input type="text" id="cmd-value" placeholder="LED_ON\n" />
|
|
309
|
+
</div>
|
|
310
|
+
<div class="field" style="flex: 0 0 62px">
|
|
311
|
+
<label for="cmd-mode">Mode</label>
|
|
312
|
+
<select id="cmd-mode">
|
|
313
|
+
<option value="text" selected>TXT</option>
|
|
314
|
+
<option value="hex">HEX</option>
|
|
315
|
+
</select>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
<button
|
|
319
|
+
class="btn btn-ghost"
|
|
320
|
+
id="cmd-add"
|
|
321
|
+
style="width: 100%; margin-top: 3px"
|
|
322
|
+
>
|
|
323
|
+
<i data-lucide="plus"></i> Add Command
|
|
324
|
+
</button>
|
|
325
|
+
<div class="chip-list" id="cmd-list"></div>
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
<!-- Data Listeners -->
|
|
329
|
+
<div class="sb-section">
|
|
330
|
+
<div class="sb-title">Data Listeners</div>
|
|
331
|
+
<div class="notice" style="margin-bottom: 6px">
|
|
332
|
+
Patterns for <code>on('serial:data')</code> — skeleton code in
|
|
333
|
+
download.
|
|
334
|
+
</div>
|
|
335
|
+
<div class="field-row">
|
|
336
|
+
<div class="field" style="flex: 0 0 64px">
|
|
337
|
+
<label for="lst-name">Name</label>
|
|
338
|
+
<input type="text" id="lst-name" placeholder="Temp" />
|
|
339
|
+
</div>
|
|
340
|
+
<div class="field">
|
|
341
|
+
<label for="lst-pattern">Pattern</label>
|
|
342
|
+
<input type="text" id="lst-pattern" placeholder="TEMP:" />
|
|
343
|
+
</div>
|
|
344
|
+
<div class="field" style="flex: 0 0 62px">
|
|
345
|
+
<label for="lst-match">Match</label>
|
|
346
|
+
<select id="lst-match">
|
|
347
|
+
<option value="exact">exact</option>
|
|
348
|
+
<option value="contains" selected>has</option>
|
|
349
|
+
<option value="startsWith">starts</option>
|
|
350
|
+
<option value="hex">hex</option>
|
|
351
|
+
</select>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
<button
|
|
355
|
+
class="btn btn-ghost"
|
|
356
|
+
id="lst-add"
|
|
357
|
+
style="width: 100%; margin-top: 3px"
|
|
358
|
+
>
|
|
359
|
+
<i data-lucide="plus"></i> Add Listener
|
|
360
|
+
</button>
|
|
361
|
+
<div class="chip-list" id="lst-list"></div>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<!-- Download -->
|
|
365
|
+
<div class="sb-section">
|
|
366
|
+
<div class="sb-title">Download Code</div>
|
|
367
|
+
<div class="field">
|
|
368
|
+
<label for="dl-name">Class / File name</label>
|
|
369
|
+
<input
|
|
370
|
+
type="text"
|
|
371
|
+
id="dl-name"
|
|
372
|
+
value="MyWsDevice"
|
|
373
|
+
placeholder="MyWsDevice"
|
|
374
|
+
/>
|
|
375
|
+
</div>
|
|
376
|
+
<div class="dl-lang-row">
|
|
377
|
+
<label class="dl-opt"
|
|
378
|
+
><input type="radio" name="dl-lang" value="ts" checked />
|
|
379
|
+
TypeScript</label
|
|
380
|
+
>
|
|
381
|
+
<label class="dl-opt"
|
|
382
|
+
><input type="radio" name="dl-lang" value="js" />
|
|
383
|
+
JavaScript</label
|
|
384
|
+
>
|
|
385
|
+
</div>
|
|
386
|
+
<div class="dl-type-row">
|
|
387
|
+
<label class="dl-opt"
|
|
388
|
+
><input type="radio" name="dl-type" value="file" checked />
|
|
389
|
+
Standalone file</label
|
|
390
|
+
>
|
|
391
|
+
<label class="dl-opt"
|
|
392
|
+
><input type="radio" name="dl-type" value="project" /> Full
|
|
393
|
+
project (ZIP)</label
|
|
394
|
+
>
|
|
395
|
+
</div>
|
|
396
|
+
<div class="dl-btns">
|
|
397
|
+
<button class="btn btn-dl" id="dl-btn">
|
|
398
|
+
<i data-lucide="download"></i> Download
|
|
399
|
+
</button>
|
|
400
|
+
<button
|
|
401
|
+
class="btn btn-ghost"
|
|
402
|
+
id="cfg-export-btn"
|
|
403
|
+
title="Export configuration as JSON"
|
|
404
|
+
>
|
|
405
|
+
<i data-lucide="share"></i> Export config
|
|
406
|
+
</button>
|
|
407
|
+
<label
|
|
408
|
+
class="btn btn-ghost"
|
|
409
|
+
title="Import configuration from JSON"
|
|
410
|
+
>
|
|
411
|
+
<i data-lucide="upload"></i> Import config
|
|
412
|
+
<input
|
|
413
|
+
type="file"
|
|
414
|
+
id="cfg-import-input"
|
|
415
|
+
accept=".json"
|
|
416
|
+
style="display: none"
|
|
417
|
+
/>
|
|
418
|
+
</label>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
<!-- /sidebar-scroll -->
|
|
423
|
+
</aside>
|
|
424
|
+
<div class="sidebar-resize-handle" id="sidebar-resize-handle"></div>
|
|
425
|
+
|
|
426
|
+
<!-- ── Chat console ─────────────────────────────────── -->
|
|
427
|
+
<main class="console-area">
|
|
428
|
+
<div class="console-hdr">
|
|
429
|
+
<div class="status-pill">
|
|
430
|
+
<div class="status-dot" id="console-dot"></div>
|
|
431
|
+
<span id="console-text"
|
|
432
|
+
>Start the Node.js bridge, then click Connect</span
|
|
433
|
+
>
|
|
434
|
+
</div>
|
|
435
|
+
<button
|
|
436
|
+
class="btn btn-ghost"
|
|
437
|
+
id="clear-btn"
|
|
438
|
+
style="margin-left: auto; font-size: 0.7rem; padding: 4px 10px"
|
|
439
|
+
>
|
|
440
|
+
Clear
|
|
441
|
+
</button>
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
<div class="messages" id="messages">
|
|
445
|
+
<div class="empty-state">
|
|
446
|
+
<div class="empty-icon">🌐</div>
|
|
447
|
+
<span
|
|
448
|
+
>Run <strong>node server.js</strong> in demos/websocket/ then
|
|
449
|
+
connect</span
|
|
450
|
+
>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
<div class="input-bar">
|
|
455
|
+
<button class="btn btn-mode" id="mode-toggle">TXT</button>
|
|
456
|
+
<input
|
|
457
|
+
class="msg-input"
|
|
458
|
+
id="input-send"
|
|
459
|
+
type="text"
|
|
460
|
+
placeholder="Type a command, e.g. LED_ON"
|
|
461
|
+
disabled
|
|
462
|
+
/>
|
|
463
|
+
<button class="btn btn-send" id="btn-send" disabled>Send</button>
|
|
464
|
+
</div>
|
|
465
|
+
</main>
|
|
466
|
+
|
|
467
|
+
<!-- ── Code panel ────────────────────────────────────── -->
|
|
468
|
+
<div class="resize-handle" id="resize-handle"></div>
|
|
469
|
+
<aside class="code-panel" id="code-panel">
|
|
470
|
+
<div class="cp-hdr">
|
|
471
|
+
<div class="cp-title">
|
|
472
|
+
<span>Code Preview</span>
|
|
473
|
+
<span class="cp-tab" id="code-tab">device.ts</span>
|
|
474
|
+
</div>
|
|
475
|
+
<div class="cp-actions">
|
|
476
|
+
<button class="cp-btn" id="copy-btn">Copy</button>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
<pre class="code-view" id="code-view"></pre>
|
|
480
|
+
</aside>
|
|
481
|
+
</div>
|
|
482
|
+
<!-- /app-layout -->
|
|
483
|
+
|
|
484
|
+
<footer class="app-footer">
|
|
485
|
+
© 2025
|
|
486
|
+
<a
|
|
487
|
+
href="https://github.com/danidoble"
|
|
488
|
+
style="color: inherit; text-decoration: none"
|
|
489
|
+
>@danidoble</a
|
|
490
|
+
>
|
|
491
|
+
· webserial-core v2
|
|
492
|
+
</footer>
|
|
493
|
+
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
|
494
|
+
<script>
|
|
495
|
+
lucide.createIcons();
|
|
496
|
+
</script>
|
|
497
|
+
</body>
|
|
498
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.WebSerialCore={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class{listeners={};on(e,t){return this.listeners[e]||(this.listeners[e]=new Set),this.listeners[e].add(t),this}off(e,t){return this.listeners[e]&&this.listeners[e].delete(t),this}emit(e,...t){let n=this.listeners[e];if(!n||n.size===0)return!1;for(let e of n)e(...t);return!0}},n=class{static instances=new Set;static portInstanceMap=new WeakMap;static getInstances(){return Array.from(this.instances)}static register(e){this.instances.add(e)}static unregister(e){this.instances.delete(e)}static isPortInUse(e,t){let n=this.portInstanceMap.get(e);return n!==void 0&&n!==t}static lockPort(e,t){this.portInstanceMap.set(e,t)}static unlockPort(e){this.portInstanceMap.delete(e)}},r=class{queue=[];isProcessing=!1;isPaused=!0;timeoutId=null;commandTimeout;onSend;onTimeout;constructor(e){this.commandTimeout=e.commandTimeout,this.onSend=e.onSend,this.onTimeout=e.onTimeout}get queueSize(){return this.queue.length}enqueue(e){this.queue.push(e),this.tryProcessNext()}advance(){this.clearCommandTimeout(),this.isProcessing=!1,this.tryProcessNext()}pause(){this.isPaused=!0,this.clearCommandTimeout(),this.isProcessing=!1}resume(){this.isPaused=!1,this.tryProcessNext()}clear(){this.queue=[],this.clearCommandTimeout(),this.isProcessing=!1}snapshot(){return[...this.queue]}restore(e){this.queue=[...e,...this.queue]}tryProcessNext(){if(this.isPaused||this.isProcessing||this.queue.length===0)return;this.isProcessing=!0;let e=this.queue.shift();this.commandTimeout>0&&(this.timeoutId=setTimeout(()=>{this.timeoutId=null,this.onTimeout(e),this.advance()},this.commandTimeout)),this.onSend(e).catch(()=>{this.advance()})}clearCommandTimeout(){this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}},i=class e extends Error{constructor(t){super(t),this.name=`SerialPortConflictError`,Object.setPrototypeOf(this,e.prototype)}},a=class e extends Error{constructor(t){super(t),this.name=`SerialPermissionError`,Object.setPrototypeOf(this,e.prototype)}},o=class e extends Error{constructor(t){super(t),this.name=`SerialTimeoutError`,Object.setPrototypeOf(this,e.prototype)}},s=class e extends Error{constructor(t){super(t),this.name=`SerialReadError`,Object.setPrototypeOf(this,e.prototype)}},c=class e extends Error{constructor(t){super(t),this.name=`SerialWriteError`,Object.setPrototypeOf(this,e.prototype)}},l=class e extends t{port=null;reader=null;writer=null;queue;options;isConnecting=!1;abortController=null;userInitiatedDisconnect=!1;reconnectTimerId=null;isHandshaking=!1;static customProvider=null;static polyfillOptions;constructor(e){super(),this.options={baudRate:e.baudRate,dataBits:e.dataBits??8,stopBits:e.stopBits??1,parity:e.parity??`none`,bufferSize:e.bufferSize??255,flowControl:e.flowControl??`none`,filters:e.filters??[],commandTimeout:e.commandTimeout??0,parser:e.parser,autoReconnect:e.autoReconnect??!1,autoReconnectInterval:e.autoReconnectInterval??1500,handshakeTimeout:e.handshakeTimeout??2e3,provider:e.provider,polyfillOptions:e.polyfillOptions},this.queue=new r({commandTimeout:this.options.commandTimeout,onSend:async e=>{await this.writeToPort(e),this.emit(`serial:sent`,e,this)},onTimeout:e=>{this.emit(`serial:timeout`,e,this)}}),this.on(`serial:data`,()=>{this.queue.advance()}),n.register(this)}async handshake(){return!0}async connect(){if(!this.isConnecting&&!this.port){this.isConnecting=!0,this.emit(`serial:connecting`,this);try{let t=this.getSerial();if(!t)throw Error(`Web Serial API is not supported in this browser. Use AbstractSerialDevice.setProvider() to set a WebUSB polyfill.`);if(this.port=await this.findAndValidatePort(),!this.port){let n;try{n=await t.requestPort({filters:this.options.filters},this.options.polyfillOptions??e.polyfillOptions)}catch(e){throw e instanceof DOMException&&(e.name===`NotFoundError`||e.name===`SecurityError`||e.name===`AbortError`)?new a(e instanceof Error?e.message:String(e)):e instanceof Error?e:Error(String(e))}if(!await this.openAndHandshake(n))throw Error(`Handshake failed: the selected device did not respond correctly.`);this.port=n}this.abortController=new AbortController,this.queue.resume(),this.emit(`serial:connected`,this)}catch(e){if(e instanceof a?this.emit(`serial:need-permission`,this):this.emit(`serial:error`,e instanceof Error?e:Error(String(e)),this),this.port){n.unlockPort(this.port);try{await this.port.close()}catch{}this.port=null}throw e}finally{this.isConnecting=!1}}}async disconnect(){this.port&&(this.userInitiatedDisconnect=!0,this.stopReconnecting(),await this.cleanupPort())}async cleanupPort(){if(this.port){this.queue.pause(),this.abortController?.abort(),this.abortController=null;try{let e=this.reader,t=this.writer;if(this.reader=null,this.writer=null,e){try{await e.cancel()}catch{}try{e.releaseLock()}catch{}}if(t){try{await t.close()}catch{}try{t.releaseLock()}catch{}}try{await this.port.close()}catch{}}catch(e){this.emit(`serial:error`,e instanceof Error?e:Error(String(e)),this)}finally{this.port&&n.unlockPort(this.port),this.port=null,this.options.parser?.reset?.(),this.emit(`serial:disconnected`,this),!this.userInitiatedDisconnect&&this.options.autoReconnect&&this.startReconnecting(),this.userInitiatedDisconnect=!1}}}async forget(){await this.disconnect(),this.port&&typeof this.port.forget==`function`&&await this.port.forget(),n.unregister(this)}async send(e){let t;t=typeof e==`string`?new TextEncoder().encode(e):e,t.length>0&&this.queue.enqueue(t)}clearQueue(){this.queue.clear(),this.emit(`serial:queue-empty`,this)}async writeToPort(e){if(!this.port||!this.port.writable)throw new c(`Port not writable.`);this.writer=this.port.writable.getWriter();try{await this.writer.write(e)}catch(e){throw new c(e instanceof Error?e.message:String(e))}finally{this.writer.releaseLock(),this.writer=null}}async readLoop(){if(!(!this.port||!this.port.readable)&&!this.reader){this.reader=this.port.readable.getReader();try{for(;;){let{value:e,done:t}=await this.reader.read();if(t)break;e&&(this.options.parser?this.options.parser.parse(e,e=>{this.emit(`serial:data`,e,this)}):this.emit(`serial:data`,e,this))}}catch(e){if(this.port)throw new s(e instanceof Error?e.message:String(e))}finally{if(this.reader){try{this.reader.releaseLock()}catch{}this.reader=null}}}}async openAndHandshake(e){let t=this;if(n.isPortInUse(e,t))return!1;n.lockPort(e,t);try{await e.open({baudRate:this.options.baudRate,dataBits:this.options.dataBits,stopBits:this.options.stopBits,parity:this.options.parity,bufferSize:this.options.bufferSize,flowControl:this.options.flowControl})}catch(t){throw n.unlockPort(e),t instanceof Error?t:Error(String(t))}this.port=e,this.abortController=new AbortController;let r=this.queue.snapshot();this.isHandshaking=!0,this.readLoop().catch(e=>{!this.isHandshaking&&this.port&&(this.emit(`serial:error`,e,this),this.cleanupPort())}),this.queue.resume();try{let t=await this.runHandshakeWithTimeout();return this.isHandshaking=!1,t?(this.queue.pause(),this.queue.clear(),this.queue.restore(r),this.options.parser?.reset?.(),!0):(await this.teardownHandshake(e,r),!1)}catch{return this.isHandshaking=!1,await this.teardownHandshake(e,r),!1}}async teardownHandshake(e,t){this.queue.pause(),this.queue.clear(),this.queue.restore(t),await this.stopReader(),this.port=null,this.abortController=null,this.options.parser?.reset?.();try{await e.close()}catch{}n.unlockPort(e)}async stopReader(){let e=this.reader;if(this.reader=null,e){try{await e.cancel()}catch{}try{e.releaseLock()}catch{}}}async runHandshakeWithTimeout(){let e=this.options.handshakeTimeout??2e3;return Promise.race([this.handshake(),new Promise(t=>setTimeout(()=>t(!1),e))])}async findAndValidatePort(){let t=this.getSerial();if(!t)return null;let r=await t.getPorts(this.options.polyfillOptions??e.polyfillOptions);if(r.length===0)return null;let i=this.options.filters??[],a=this;for(let e of r)if(!n.isPortInUse(e,a)){if(i.length>0){let t=e.getInfo();if(!i.some(e=>{let n=e.usbVendorId===void 0||e.usbVendorId===t.usbVendorId,r=e.usbProductId===void 0||e.usbProductId===t.usbProductId;return n&&r}))continue}try{if(await this.openAndHandshake(e))return e}catch{}}return null}startReconnecting(){this.reconnectTimerId||=(this.emit(`serial:reconnecting`,this),setInterval(async()=>{if(this.port||this.isConnecting){this.stopReconnecting();return}try{let e=await this.findAndValidatePort();e&&(this.stopReconnecting(),await this.reconnect(e))}catch{}},this.options.autoReconnectInterval))}stopReconnecting(){this.reconnectTimerId&&=(clearInterval(this.reconnectTimerId),null)}async reconnect(e){if(!(this.isConnecting||this.port)){this.isConnecting=!0,this.emit(`serial:connecting`,this);try{this.port=e,this.abortController=new AbortController,this.queue.resume(),this.emit(`serial:connected`,this)}catch(e){this.emit(`serial:error`,e instanceof Error?e:Error(String(e)),this),this.port&&=(n.unlockPort(this.port),null),this.options.autoReconnect&&this.startReconnecting()}finally{this.isConnecting=!1}}}static getInstances(){return n.getInstances()}static async connectAll(){let e=n.getInstances();for(let t of e)try{await t.connect()}catch{}}static setProvider(t,n){e.customProvider=t,e.polyfillOptions=n}getSerial(){return this.options.provider?this.options.provider:e.customProvider?e.customProvider:typeof navigator<`u`&&navigator.serial?navigator.serial:null}};function u(e){if(e<=0)throw Error(`FixedLengthParser: length must be greater than 0`);let t=new Uint8Array;return{parse(n,r){let i=new Uint8Array(t.length+n.length);for(i.set(t),i.set(n,t.length),t=i;t.length>=e;)r(t.slice(0,e)),t=t.slice(e)},reset(){t=new Uint8Array}}}function d(e){let t=``,n=new TextDecoder;return{parse(r,i){t+=n.decode(r,{stream:!0});let a;for(;(a=t.indexOf(e))!==-1;)i(t.slice(0,a)),t=t.slice(a+e.length)},reset(){t=``,n=new TextDecoder}}}function f(){return{parse(e,t){t(e)},reset(){}}}var p=32,m=34,h=0,g=30,_=3,v=7,y=1,b=0,x=771,S=768,C=255,w=8,T=`none`,E=1,D=[16,8,7,6,5],O=[1,2],k=[`none`,`even`,`odd`],A=[`none`,`odd`,`even`],j=[1,1.5,2],M={usbControlInterfaceClass:2,usbTransferInterfaceClass:10,protocol:void 0};function N(e,t){let n=e.configurations[0];if(!n)return null;for(let e of n.interfaces)if(e.alternates[0]?.interfaceClass===t)return e;return null}function P(e,t){let n=e.configurations[0];if(!n)return null;for(let e of n.interfaces){let n=e.alternates[0];if(!n||n.interfaceClass!==t)continue;let r=n.endpoints.some(e=>e.direction===`in`),i=n.endpoints.some(e=>e.direction===`out`);if(r&&i)return e}return null}function F(e,t){let n=e.alternates[0];if(n){for(let e of n.endpoints)if(e.direction===t)return e}throw TypeError(`Interface ${e.interfaceNumber} does not have an ${t} endpoint.`)}function I(e,t){return t===2?`cdc_acm`:e.vendorId===4292?`cp210x`:`none`}var L=class{device_;endpoint_;onError_;constructor(e,t,n){this.device_=e,this.endpoint_=t,this.onError_=n}pull(e){(async()=>{let t=this.endpoint_.packetSize;try{let n=await this.device_.transferIn(this.endpoint_.endpointNumber,t);if(n.status!==`ok`){e.error(`USB error: ${n.status}`),this.onError_();return}if(n.data?.buffer&&n.data.byteLength>0){let t=new Uint8Array(n.data.buffer,n.data.byteOffset,n.data.byteLength);t.length>0&&e.enqueue(t)}}catch(t){e.error(String(t)),this.onError_()}})()}},R=class{device_;endpoint_;onError_;constructor(e,t,n){this.device_=e,this.endpoint_=t,this.onError_=n}async write(e,t){try{let n=await this.device_.transferOut(this.endpoint_.endpointNumber,e.buffer);n.status!==`ok`&&(t.error(n.status),this.onError_())}catch(e){t.error(String(e)),this.onError_()}}},z=class{device_;protocol_;controlInterface_;transferInterface_;inEndpoint_;outEndpoint_;serialOptions_;readable_=null;writable_=null;cdcOutputSignals_={dataTerminalReady:!1,requestToSend:!1,break:!1};constructor(e,t){this.device_=e;let n={...M,...t};this.protocol_=n.protocol??I(e,n.usbControlInterfaceClass);let r=n.usbControlInterfaceClass,i=n.usbTransferInterfaceClass;if(r===i){let t=P(e,i);if(!t)throw TypeError(`Unable to find interface with class ${i} that has both IN and OUT endpoints.`);this.controlInterface_=t,this.transferInterface_=t}else{let t=N(e,r);if(!t)throw TypeError(`Unable to find control interface with class ${r}.`);let n=P(e,i)??N(e,i);if(!n)throw TypeError(`Unable to find transfer interface with class ${i}.`);this.controlInterface_=t,this.transferInterface_=n}this.inEndpoint_=F(this.transferInterface_,`in`),this.outEndpoint_=F(this.transferInterface_,`out`)}get readable(){return!this.readable_&&this.device_.opened&&(this.readable_=new ReadableStream(new L(this.device_,this.inEndpoint_,()=>{this.readable_=null}),{highWaterMark:this.serialOptions_?.bufferSize??C})),this.readable_}get writable(){return!this.writable_&&this.device_.opened&&(this.writable_=new WritableStream(new R(this.device_,this.outEndpoint_,()=>{this.writable_=null}),new ByteLengthQueuingStrategy({highWaterMark:this.serialOptions_?.bufferSize??C}))),this.writable_}async open(e){this.serialOptions_=e,this.validateOptions();try{switch(await this.device_.open(),this.device_.configuration===null&&await this.device_.selectConfiguration(1),await this.device_.claimInterface(this.controlInterface_.interfaceNumber),this.controlInterface_!==this.transferInterface_&&await this.device_.claimInterface(this.transferInterface_.interfaceNumber),this.protocol_){case`cdc_acm`:await this.cdcInit();break;case`cp210x`:await this.cp210xInit();break;case`none`:break}}catch(e){throw this.device_.opened&&await this.device_.close(),Error(`Error setting up device: `+(e instanceof Error?e.message:String(e)),{cause:e})}}async close(){let e=[];if(this.readable_&&e.push(this.readable_.cancel()),this.writable_&&e.push(this.writable_.abort()),await Promise.all(e),this.readable_=null,this.writable_=null,this.device_.opened){switch(this.protocol_){case`cdc_acm`:await this.cdcSetSignals({dataTerminalReady:!1,requestToSend:!1});break;case`cp210x`:await this.cp210xDeinit();break}await this.device_.close()}}async forget(){return this.device_.forget()}getInfo(){return{usbVendorId:this.device_.vendorId,usbProductId:this.device_.productId}}async cdcInit(){await this.cdcSetLineCoding(),await this.cdcSetSignals({dataTerminalReady:!0})}async cdcSetSignals(e){if(this.cdcOutputSignals_={...this.cdcOutputSignals_,...e},e.dataTerminalReady!==void 0||e.requestToSend!==void 0){let e=(this.cdcOutputSignals_.dataTerminalReady?1:0)|(this.cdcOutputSignals_.requestToSend?2:0);await this.device_.controlTransferOut({requestType:`class`,recipient:`interface`,request:m,value:e,index:this.controlInterface_.interfaceNumber})}}async cdcSetLineCoding(){let e=new ArrayBuffer(7),t=new DataView(e);if(t.setUint32(0,this.serialOptions_.baudRate,!0),t.setUint8(4,j.indexOf(this.serialOptions_.stopBits??E)),t.setUint8(5,A.indexOf(this.serialOptions_.parity??T)),t.setUint8(6,this.serialOptions_.dataBits??w),(await this.device_.controlTransferOut({requestType:`class`,recipient:`interface`,request:p,value:0,index:this.controlInterface_.interfaceNumber},e)).status!==`ok`)throw new DOMException(`Failed to set line coding.`,`NetworkError`)}async cp210xInit(){let e=this.controlInterface_.interfaceNumber;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:h,value:y,index:e});let t=new ArrayBuffer(4);new DataView(t).setUint32(0,this.serialOptions_.baudRate,!0),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:g,value:0,index:e},t);let n=this.serialOptions_.dataBits??w,r={none:0,odd:16,even:32}[this.serialOptions_.parity??T]??0,i=({1:0,2:2}[this.serialOptions_.stopBits??E]??0)<<8|r|n;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:_,value:i,index:e}),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:v,value:x,index:e})}async cp210xDeinit(){let e=this.controlInterface_.interfaceNumber;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:v,value:S,index:e}),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:h,value:b,index:e})}validateOptions(){if(this.serialOptions_.baudRate%1!=0)throw RangeError(`Invalid baud rate: ${this.serialOptions_.baudRate}`);if(this.serialOptions_.dataBits!==void 0&&!D.includes(this.serialOptions_.dataBits))throw RangeError(`Invalid dataBits: ${this.serialOptions_.dataBits}`);if(this.serialOptions_.stopBits!==void 0&&!O.includes(this.serialOptions_.stopBits))throw RangeError(`Invalid stopBits: ${this.serialOptions_.stopBits}`);if(this.serialOptions_.parity!==void 0&&!k.includes(this.serialOptions_.parity))throw RangeError(`Invalid parity: ${this.serialOptions_.parity}`)}},B=class{options_;constructor(e){this.options_={...M,...e}}async requestPort(e,t){let n={...this.options_,...t},r=[];if(e?.filters&&e.filters.length>0)for(let t of e.filters){let e={};t.usbVendorId!==void 0&&(e.vendorId=t.usbVendorId),t.usbProductId!==void 0&&(e.productId=t.usbProductId),n.usbControlInterfaceClass!==void 0&&n.usbControlInterfaceClass!==255?e.classCode=n.usbControlInterfaceClass:e.vendorId===void 0&&e.productId===void 0&&(e.classCode=n.usbControlInterfaceClass??2),r.push(e)}else r.push({classCode:n.usbControlInterfaceClass??2});return new z(await navigator.usb.requestDevice({filters:r}),n)}async getPorts(e){let t={...this.options_,...e},n=await navigator.usb.getDevices(),r=[];for(let e of n)try{let n=new z(e,t);r.push(n)}catch{}return r}},V=`6e400001-b5a3-f393-e0a9-e50e24dcca9e`,H=`6e400003-b5a3-f393-e0a9-e50e24dcca9e`,U=`6e400002-b5a3-f393-e0a9-e50e24dcca9e`,W=20,G=10;function K(e){let t=null,n=null,r=null;return{get readable(){return t},get writable(){return n},getInfo(){return{}},async open(){if(!e.gatt)throw Error(`GATT not available on this Bluetooth device.`);r=await e.gatt.connect();let i=await r.getPrimaryService(V),a=await i.getCharacteristic(H),o=await i.getCharacteristic(U);await a.startNotifications(),t=new ReadableStream({start(e){a.addEventListener(`characteristicvaluechanged`,t=>{let n=t.target.value.buffer;e.enqueue(new Uint8Array(n))})}}),n=new WritableStream({async write(e){for(let t=0;t<e.length;t+=W){let n=e.slice(t,t+W);await o.writeValueWithoutResponse(n),t+W<e.length&&await new Promise(e=>setTimeout(e,G))}}})},async close(){r?.connected&&r.disconnect(),t=null,n=null}}}function q(){return{async requestPort(){if(!navigator.bluetooth)throw Error(`Web Bluetooth API is not supported in this browser. Use Chrome on Android, macOS, or ChromeOS.`);return K(await navigator.bluetooth.requestDevice({filters:[{services:[V]}]}))},async getPorts(){return[]}}}function J(e){return new Promise((t,n)=>{e.addEventListener(`open`,()=>t(),{once:!0}),e.addEventListener(`error`,e=>n(e),{once:!0})})}function Y(e,t){return new Promise(n=>{let r=i=>{let a=JSON.parse(i.data);a.type===t&&(e.removeEventListener(`message`,r),n(a.payload))};e.addEventListener(`message`,r)})}function X(e,t){let n=null,r=null;return{get readable(){return n},get writable(){return r},getInfo(){return{usbVendorId:t.vendorId,usbProductId:t.productId}},async open(i){e.send(JSON.stringify({type:`open`,path:t.path,baudRate:i.baudRate,dataBits:i.dataBits,stopBits:i.stopBits,parity:i.parity,parser:{type:`delimiter`,value:`\\n`}})),await Y(e,`opened`);let a=[],o=null,s=!1;function c(e){let t=JSON.parse(e.data);if(t.type===`data`&&t.bytes){let e=new Uint8Array(t.bytes);o?o.enqueue(e):a.push(e)}t.type===`closed`&&(s=!0,o&&o.close())}e.addEventListener(`message`,c),n=new ReadableStream({start(e){o=e;for(let t of a)e.enqueue(t);a.length=0,s&&e.close()},cancel(){e.removeEventListener(`message`,c),o=null}}),r=new WritableStream({write(t){e.send(JSON.stringify({type:`write`,bytes:Array.from(t)}))}})},async close(){e.send(JSON.stringify({type:`close`})),n=null,r=null,e.close()}}}function Z(e){return{async requestPort(t){let n=new WebSocket(e);await J(n),n.send(JSON.stringify({type:`list-ports`,filters:t?.filters??[]}));let r=(await Y(n,`port-list`))[0];if(!r)throw Error(`No ports available on the bridge server. Make sure the Node.js server is running and a device is connected.`);return X(n,r)},async getPorts(){let t=new WebSocket(e);return await J(t),t.send(JSON.stringify({type:`list-ports`,filters:[]})),(await Y(t,`port-list`)).map(e=>X(t,e))}}}e.AbstractSerialDevice=l,e.CommandQueue=r,e.SerialEventEmitter=t,e.SerialPermissionError=a,e.SerialPortConflictError=i,e.SerialReadError=s,e.SerialRegistry=n,e.SerialTimeoutError=o,e.SerialWriteError=c,e.WebUsbProvider=B,e.createBluetoothProvider=q,e.createWebSocketProvider=Z,e.delimiter=d,e.fixedLength=u,e.raw=f});
|
package/dist/favicon.svg
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="40" height="40" fill="none">
|
|
2
|
+
<!-- Dark circle background -->
|
|
3
|
+
<circle cx="20" cy="20" r="19" fill="#1a1527"/>
|
|
4
|
+
<!-- Left angle bracket -->
|
|
5
|
+
<polyline points="10,13 6,20 10,27" stroke="#a78bfa" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
6
|
+
<!-- Right angle bracket -->
|
|
7
|
+
<polyline points="30,13 34,20 30,27" stroke="#22c55e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8
|
+
<!-- Serial data line (dashes) -->
|
|
9
|
+
<line x1="14" y1="20" x2="26" y2="20" stroke="#a78bfa" stroke-width="2" stroke-dasharray="3 2" stroke-linecap="round"/>
|
|
10
|
+
</svg>
|