react-native-web-serial-api 0.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.
Files changed (114) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/android/build.gradle +62 -0
  4. package/android/gradle.properties +6 -0
  5. package/android/src/main/AndroidManifest.xml +21 -0
  6. package/android/src/main/java/dev/webserialapi/NativeUsbSerialModule.java +704 -0
  7. package/android/src/main/java/dev/webserialapi/NativeUsbSerialPackage.java +46 -0
  8. package/android/src/main/java/dev/webserialapi/PortPickerActivity.java +235 -0
  9. package/android/src/main/java/dev/webserialapi/UsbDetachReceiver.java +37 -0
  10. package/android/src/main/res/xml/device_filter.xml +13 -0
  11. package/babel.config.js +3 -0
  12. package/biome.json +35 -0
  13. package/example/.watchmanconfig +1 -0
  14. package/example/App.tsx +71 -0
  15. package/example/__tests__/App.test.tsx +16 -0
  16. package/example/android/app/build.gradle +120 -0
  17. package/example/android/app/debug.keystore +0 -0
  18. package/example/android/app/proguard-rules.pro +10 -0
  19. package/example/android/app/src/debug/AndroidManifest.xml +9 -0
  20. package/example/android/app/src/main/AndroidManifest.xml +38 -0
  21. package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +22 -0
  22. package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +41 -0
  23. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  24. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  25. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  26. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  27. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  28. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  29. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  30. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  31. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  32. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  33. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  34. package/example/android/app/src/main/res/values/strings.xml +3 -0
  35. package/example/android/app/src/main/res/values/styles.xml +9 -0
  36. package/example/android/build.gradle +22 -0
  37. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  38. package/example/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  39. package/example/android/gradle.properties +47 -0
  40. package/example/android/gradlew +252 -0
  41. package/example/android/gradlew.bat +94 -0
  42. package/example/android/settings.gradle +6 -0
  43. package/example/app.json +4 -0
  44. package/example/babel.config.js +21 -0
  45. package/example/biome.json +47 -0
  46. package/example/deploy.sh +11 -0
  47. package/example/index.html +26 -0
  48. package/example/index.js +9 -0
  49. package/example/index.web.js +8 -0
  50. package/example/jest.config.js +12 -0
  51. package/example/metro.config.js +58 -0
  52. package/example/package-lock.json +14510 -0
  53. package/example/package.json +48 -0
  54. package/example/react-native.config.js +17 -0
  55. package/example/src/components/AppBar.tsx +73 -0
  56. package/example/src/components/Menu.tsx +90 -0
  57. package/example/src/components/SingleChoiceDialog.tsx +120 -0
  58. package/example/src/screens/ConnectScreen.tsx +195 -0
  59. package/example/src/screens/DevicesScreen.tsx +141 -0
  60. package/example/src/screens/TerminalScreen.tsx +564 -0
  61. package/example/src/settings.ts +43 -0
  62. package/example/src/theme.ts +19 -0
  63. package/example/src/util/TextUtil.ts +129 -0
  64. package/example/tsconfig.json +10 -0
  65. package/example/vite.config.mjs +55 -0
  66. package/lib/commonjs/NativeUsbSerial.js +11 -0
  67. package/lib/commonjs/NativeUsbSerial.js.map +1 -0
  68. package/lib/commonjs/NativeUsbSerial.web.js +12 -0
  69. package/lib/commonjs/NativeUsbSerial.web.js.map +1 -0
  70. package/lib/commonjs/UsbSerial.js +149 -0
  71. package/lib/commonjs/UsbSerial.js.map +1 -0
  72. package/lib/commonjs/WebSerial.js +852 -0
  73. package/lib/commonjs/WebSerial.js.map +1 -0
  74. package/lib/commonjs/index.js +44 -0
  75. package/lib/commonjs/index.js.map +1 -0
  76. package/lib/commonjs/package.json +1 -0
  77. package/lib/commonjs/serial.android.js +13 -0
  78. package/lib/commonjs/serial.android.js.map +1 -0
  79. package/lib/commonjs/serial.js +13 -0
  80. package/lib/commonjs/serial.js.map +1 -0
  81. package/lib/commonjs/serial.web.js +11 -0
  82. package/lib/commonjs/serial.web.js.map +1 -0
  83. package/lib/typescript/src/NativeUsbSerial.d.ts +51 -0
  84. package/lib/typescript/src/NativeUsbSerial.d.ts.map +1 -0
  85. package/lib/typescript/src/NativeUsbSerial.web.d.ts +3 -0
  86. package/lib/typescript/src/NativeUsbSerial.web.d.ts.map +1 -0
  87. package/lib/typescript/src/UsbSerial.d.ts +97 -0
  88. package/lib/typescript/src/UsbSerial.d.ts.map +1 -0
  89. package/lib/typescript/src/WebSerial.d.ts +236 -0
  90. package/lib/typescript/src/WebSerial.d.ts.map +1 -0
  91. package/lib/typescript/src/index.d.ts +7 -0
  92. package/lib/typescript/src/index.d.ts.map +1 -0
  93. package/lib/typescript/src/serial.android.d.ts +2 -0
  94. package/lib/typescript/src/serial.android.d.ts.map +1 -0
  95. package/lib/typescript/src/serial.d.ts +2 -0
  96. package/lib/typescript/src/serial.d.ts.map +1 -0
  97. package/lib/typescript/src/serial.web.d.ts +4 -0
  98. package/lib/typescript/src/serial.web.d.ts.map +1 -0
  99. package/package.json +78 -0
  100. package/react-native.config.js +9 -0
  101. package/scripts/deploy-release.sh +127 -0
  102. package/src/NativeUsbSerial.ts +124 -0
  103. package/src/NativeUsbSerial.web.ts +5 -0
  104. package/src/UsbSerial.ts +305 -0
  105. package/src/WebSerial.ts +1084 -0
  106. package/src/index.ts +23 -0
  107. package/src/lib/dom-exception.ts +161 -0
  108. package/src/lib/event-target.ts +170 -0
  109. package/src/lib/promise.ts +19 -0
  110. package/src/serial.android.ts +1 -0
  111. package/src/serial.ts +1 -0
  112. package/src/serial.web.ts +6 -0
  113. package/tsconfig.build.json +7 -0
  114. package/tsconfig.json +20 -0
@@ -0,0 +1,852 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.serial = exports.SerialPort = exports.Serial = void 0;
7
+ var _webStreamsPolyfill = require("web-streams-polyfill");
8
+ var _domException = require("./lib/dom-exception");
9
+ var _eventTarget = require("./lib/event-target");
10
+ var _promise = require("./lib/promise");
11
+ var _UsbSerial = require("./UsbSerial");
12
+ /**
13
+ * @see https://wicg.github.io/serial/#dom-paritytype
14
+ *
15
+ * none - No parity bit is sent for each data word.
16
+ * even - Data word plus parity bit has even parity.
17
+ * odd - Data word plus parity bit has odd parity.
18
+ */
19
+
20
+ /**
21
+ * @see https://wicg.github.io/serial/#dom-flowcontroltype
22
+ * none - No flow control is enabled.
23
+ * hardware - Hardware flow control using the RTS and CTS signals is enabled.
24
+ */
25
+
26
+ /**
27
+ * @see https://wicg.github.io/serial/#dom-serialoptions
28
+ */
29
+
30
+ /**
31
+ * @see https://wicg.github.io/serial/#dom-serialoutputsignals
32
+ */
33
+
34
+ /**
35
+ * @see https://wicg.github.io/serial/#dom-serialinputsignals
36
+ */
37
+
38
+ /**
39
+ * @see https://wicg.github.io/serial/#serialportinfo-dictionary
40
+ */
41
+
42
+ /**
43
+ * @see https://wicg.github.io/serial/#dom-serialportfilter
44
+ */
45
+
46
+ /**
47
+ * @see https://wicg.github.io/serial/#dom-serialportrequestoptions
48
+ */
49
+
50
+ // Maps SerialPort instances to their deviceId — avoids exposing deviceId on
51
+ // the public type while still allowing Serial to access it for requestPermission
52
+ const serialPortDeviceIds = new WeakMap();
53
+
54
+ // "Friend" channel: lets Serial match/update/reset a SerialPort across USB
55
+ // detach + re-attach (Android assigns a new deviceId on every attach) without
56
+ // widening the public SerialPort API. Each port registers a set of closures
57
+ // over its #private fields in its constructor.
58
+
59
+ const portInternals = new WeakMap();
60
+ const kDefaultDataBits = 8;
61
+ const kDefaultStopBits = 1;
62
+ const kDefaultParity = 'none';
63
+ const kDefaultBufferSize = 255;
64
+ const kDefaultFlowControl = 'none';
65
+ const kAcceptableDataBits = [7, 8];
66
+ const kAcceptableStopBits = [1, 2];
67
+ const kAcceptableParity = ['none', 'even', 'odd'];
68
+ function parityToNative(parity) {
69
+ switch (parity) {
70
+ case 'odd':
71
+ return 1;
72
+ case 'even':
73
+ return 2;
74
+ default:
75
+ return 0;
76
+ }
77
+ }
78
+ function flowControlToNative(flowControl) {
79
+ return flowControl === 'hardware' ? 'RTS_CTS' : 'NONE';
80
+ }
81
+
82
+ /**
83
+ * Methods on this interface typically complete asynchronously, queuing work on
84
+ * the serial port task source.
85
+ *
86
+ * The get the parent algorithm for SerialPort returns the same Serial instance
87
+ * that is returned by the SerialPort's relevant global object's Navigator
88
+ * object's serial getter.
89
+ *
90
+ * Instances of SerialPort are created with the internal slots described in the
91
+ * following table:
92
+ *
93
+ * @see https://wicg.github.io/serial/#serialport-interface
94
+ */
95
+ class SerialPort extends _eventTarget.EventTarget {
96
+ #events = {
97
+ connect: null,
98
+ disconnect: null
99
+ };
100
+
101
+ // Internal slots
102
+ #state = 'closed'; // [[state]] = "closed"
103
+ #bufferSize = undefined; // [[bufferSize]] = undefined
104
+ #connected = false; // [[connected]] = false
105
+ #readable = null; // [[readable]] = null
106
+ #readFatal = false; // [[readFatal]] = false
107
+ #writable = null; // [[writable]] = null
108
+ #writeFatal = false; // [[writeFatal]] = false
109
+ #pendingClosePromise = null; // [[pendingClosePromise]] = null
110
+
111
+ #usb;
112
+ #deviceId;
113
+ #portNumber;
114
+ #usbVendorId;
115
+ #usbProductId;
116
+
117
+ // Tracks the current state of output signals for partial updates
118
+ #outputSignals = {
119
+ dataTerminalReady: false,
120
+ requestToSend: false,
121
+ break: false
122
+ };
123
+ #dataSubscription = null;
124
+ #errorSubscription = null;
125
+ constructor(usb, deviceId, portNumber, usbVendorId, usbProductId) {
126
+ super();
127
+ this.#usb = usb;
128
+ this.#deviceId = deviceId;
129
+ this.#portNumber = portNumber;
130
+ serialPortDeviceIds.set(this, deviceId);
131
+ this.#usbVendorId = usbVendorId;
132
+ this.#usbProductId = usbProductId;
133
+
134
+ // Expose a private control channel to Serial (see portInternals).
135
+ portInternals.set(this, {
136
+ getVid: () => this.#usbVendorId,
137
+ getPid: () => this.#usbProductId,
138
+ getPortNumber: () => this.#portNumber,
139
+ getDeviceId: () => this.#deviceId,
140
+ setDeviceId: id => {
141
+ this.#deviceId = id;
142
+ serialPortDeviceIds.set(this, id);
143
+ },
144
+ handleDeviceLost: () => this.#handleDeviceLost()
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Reset this port to a closed, re-openable state after the underlying USB
150
+ * device has been physically removed. Unlike close(), this must NOT touch the
151
+ * (now-gone) device — doing so would throw "USB get_status request failed".
152
+ * After this, a later open() (e.g. when the device is re-attached) succeeds.
153
+ */
154
+ #handleDeviceLost() {
155
+ // Tear down read/write streams without invoking the OS on the dead device.
156
+ if (this.#readable) {
157
+ this.#readFatal = true;
158
+ try {
159
+ // No active reader is required for cancel(); errors are non-fatal here.
160
+ this.#readable.cancel().catch(() => {});
161
+ } catch {}
162
+ }
163
+ if (this.#writable) {
164
+ this.#writeFatal = true;
165
+ try {
166
+ this.#writable.abort().catch(() => {});
167
+ } catch {}
168
+ }
169
+ this.#dataSubscription?.remove();
170
+ this.#errorSubscription?.remove();
171
+ this.#dataSubscription = null;
172
+ this.#errorSubscription = null;
173
+ this.#readable = null;
174
+ this.#writable = null;
175
+ this.#readFatal = false;
176
+ this.#writeFatal = false;
177
+ this.#pendingClosePromise = null;
178
+ this.#bufferSize = undefined;
179
+ this.#state = 'closed';
180
+ this.#connected = false;
181
+ this.dispatchEvent(new _eventTarget.Event('disconnect'));
182
+ }
183
+
184
+ /**
185
+ * @see https://wicg.github.io/serial/#dom-serialport-onconnect
186
+ */
187
+ get onconnect() {
188
+ return this.#events.connect;
189
+ }
190
+ set onconnect(fn) {
191
+ if (this.#events.connect) {
192
+ this.removeEventListener('connect', this.#events.connect);
193
+ }
194
+ if (fn !== null) {
195
+ this.addEventListener('connect', fn);
196
+ this.#events.connect = fn;
197
+ } else {
198
+ this.#events.connect = null;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * @see https://wicg.github.io/serial/#dom-serialport-ondisconnect
204
+ */
205
+ get ondisconnect() {
206
+ return this.#events.disconnect;
207
+ }
208
+ set ondisconnect(fn) {
209
+ if (this.#events.disconnect) {
210
+ this.removeEventListener('disconnect', this.#events.disconnect);
211
+ }
212
+ if (fn !== null) {
213
+ this.addEventListener('disconnect', fn);
214
+ this.#events.disconnect = fn;
215
+ } else {
216
+ this.#events.disconnect = null;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * @see https://wicg.github.io/serial/#dom-serialport-connected
222
+ */
223
+ get connected() {
224
+ // The connected getter steps are:
225
+
226
+ // 1. Return this.[[connected]].
227
+ return this.#connected;
228
+ }
229
+
230
+ /**
231
+ * @see https://wicg.github.io/serial/#dom-serialport-readable
232
+ */
233
+ get readable() {
234
+ // The readable getter steps are:
235
+
236
+ // 1. If this.[[readable]] is not null, return this.[[readable]].
237
+ if (this.#readable !== null) return this.#readable;
238
+
239
+ // 2. If this.[[state]] is not "opened", return null.
240
+ if (this.#state !== 'opened') return null;
241
+
242
+ // 3. If this.[[readFatal]] is true, return null.
243
+ if (this.#readFatal) return null;
244
+
245
+ // 4. Let stream be a new ReadableStream.
246
+ const self = this;
247
+ const deviceId = this.#deviceId;
248
+ const portNumber = this.#portNumber;
249
+ const bufferSize = this.#bufferSize;
250
+ const stream = new _webStreamsPolyfill.ReadableStream({
251
+ start(controller) {
252
+ self.#dataSubscription = self.#usb.onData(event => {
253
+ if (event.deviceId === deviceId && event.portNumber === portNumber) {
254
+ controller.enqueue(new Uint8Array(event.data));
255
+ }
256
+ });
257
+ self.#errorSubscription = self.#usb.onError(event => {
258
+ if (event.deviceId === deviceId && event.portNumber === portNumber) {
259
+ self.#readFatal = true; // Set this.[[readFatal]] to true.
260
+ controller.error(new _domException.DOMException(event.error, 'NetworkError'));
261
+ self.#handleClosingReadableStream();
262
+ }
263
+ });
264
+ },
265
+ async cancel() {
266
+ // cancelAlgorithm: Invoke the operating system to discard the contents
267
+ // of all software and hardware receive buffers for the port.
268
+ await self.#usb.purgeHwBuffers(deviceId, portNumber, false, true).catch(() => {});
269
+ self.#handleClosingReadableStream();
270
+ }
271
+ }, new _webStreamsPolyfill.ByteLengthQueuingStrategy({
272
+ highWaterMark: bufferSize
273
+ }));
274
+
275
+ // Set this.[[readable]] to stream.
276
+ this.#readable = stream;
277
+
278
+ // Return stream.
279
+ return stream;
280
+ }
281
+
282
+ /**
283
+ * @see https://wicg.github.io/serial/#dom-serialport-writable
284
+ */
285
+ get writable() {
286
+ // The writable getter steps are:
287
+
288
+ // 1. If this.[[writable]] is not null, return this.[[writable]].
289
+ if (this.#writable !== null) return this.#writable;
290
+
291
+ // 2. If this.[[state]] is not "opened", return null.
292
+ if (this.#state !== 'opened') return null;
293
+
294
+ // 3. If this.[[writeFatal]] is true, return null.
295
+ if (this.#writeFatal) return null;
296
+
297
+ // 4. Let stream be a new WritableStream.
298
+ const self = this;
299
+ const deviceId = this.#deviceId;
300
+ const portNumber = this.#portNumber;
301
+ const bufferSize = this.#bufferSize;
302
+ const stream = new _webStreamsPolyfill.WritableStream({
303
+ async write(chunk) {
304
+ try {
305
+ await self.#usb.write(deviceId, portNumber, Array.from(chunk));
306
+ } catch (e) {
307
+ // If the port was disconnected, set this.[[writeFatal]] to true.
308
+ self.#writeFatal = true;
309
+ self.#handleClosingWritableStream();
310
+ throw new _domException.DOMException(`Write failed: ${e.message}`, 'NetworkError');
311
+ }
312
+ },
313
+ async abort() {
314
+ // abortAlgorithm: Invoke the operating system to discard the contents
315
+ // of all software and hardware transmit buffers for the port.
316
+ await self.#usb.purgeHwBuffers(deviceId, portNumber, true, false).catch(() => {});
317
+ self.#handleClosingWritableStream();
318
+ },
319
+ async close() {
320
+ // closeAlgorithm: Invoke the operating system to flush the contents
321
+ // of all software and hardware transmit buffers for the port.
322
+ self.#handleClosingWritableStream();
323
+ }
324
+ }, new _webStreamsPolyfill.ByteLengthQueuingStrategy({
325
+ highWaterMark: bufferSize
326
+ }));
327
+
328
+ // Set this.[[writable]] to stream.
329
+ this.#writable = stream;
330
+
331
+ // Return stream.
332
+ return stream;
333
+ }
334
+
335
+ /**
336
+ * @see https://wicg.github.io/serial/#dom-serialport-getinfo
337
+ */
338
+ getInfo() {
339
+ // The getInfo() method steps are:
340
+
341
+ // 1. Let info be an empty ordered map.
342
+ const info = Object.create(null);
343
+
344
+ // 2. If the port is part of a USB device, perform the following steps:
345
+ if (this.#usbVendorId !== undefined) {
346
+ // 1. Set info["usbVendorId"] to the vendor ID of the device.
347
+ info.usbVendorId = this.#usbVendorId;
348
+ // 2. Set info["usbProductId"] to the product ID of the device.
349
+ info.usbProductId = this.#usbProductId;
350
+ }
351
+
352
+ // 3. If the port is a service on a Bluetooth device, perform the following steps:
353
+ // 1. Set info["bluetoothServiceClassId"] to the service class UUID of the Bluetooth service.
354
+
355
+ // 4. Return info.
356
+ return info;
357
+ }
358
+
359
+ /**
360
+ * Before communicating on a serial port it must be opened. Opening the port
361
+ * allows the site to specify the necessary parameters which control how data
362
+ * is transmitted and received. Developers should check the documentation for
363
+ * the device they are connecting to for the appropriate parameters.
364
+ *
365
+ * await port.open({ baudRate: 115200 });
366
+ *
367
+ * Once open() has resolved the readable and writable attributes can be
368
+ * accessed to get the ReadableStream and WritableStream instances for
369
+ * receiving data from and sending data to the connected device.
370
+ * @see https://wicg.github.io/serial/#dom-serialport-open
371
+ */
372
+ async open(options) {
373
+ // The open() method steps are:
374
+
375
+ // 2. If this.[[state]] is not "closed", reject promise with an
376
+ // "InvalidStateError" DOMException and return promise.
377
+ if (this.#state !== 'closed') {
378
+ throw new _domException.DOMException('The port is already open.', 'InvalidStateError');
379
+ }
380
+
381
+ // 3. If options["baudRate"] is 0, reject promise with a TypeError and
382
+ // return promise.
383
+ if (options.baudRate === 0) {
384
+ throw new TypeError('baudRate must be a positive, non-zero value.');
385
+ }
386
+
387
+ // 4. If options["dataBits"] is not 7 or 8, reject promise with a TypeError
388
+ // and return promise.
389
+ const dataBits = options.dataBits ?? kDefaultDataBits;
390
+ if (!kAcceptableDataBits.includes(dataBits)) {
391
+ throw new TypeError('dataBits must be 7 or 8.');
392
+ }
393
+
394
+ // 5. If options["stopBits"] is not 1 or 2, reject promise with a TypeError
395
+ // and return promise.
396
+ const stopBits = options.stopBits ?? kDefaultStopBits;
397
+ if (!kAcceptableStopBits.includes(stopBits)) {
398
+ throw new TypeError('stopBits must be 1 or 2.');
399
+ }
400
+
401
+ // 6. If options["bufferSize"] is 0, reject promise with a TypeError and
402
+ // return promise.
403
+ const bufferSize = options.bufferSize ?? kDefaultBufferSize;
404
+ if (bufferSize === 0) {
405
+ throw new TypeError('bufferSize must be a positive, non-zero value.');
406
+ }
407
+ const parity = options.parity ?? kDefaultParity;
408
+ if (!kAcceptableParity.includes(parity)) {
409
+ throw new TypeError(`parity must be one of: ${kAcceptableParity.join(', ')}.`);
410
+ }
411
+ const flowControl = options.flowControl ?? kDefaultFlowControl;
412
+
413
+ // 8. Set this.[[state]] to "opening".
414
+ this.#state = 'opening';
415
+
416
+ // 9.1. Invoke the operating system to open the serial port using the
417
+ // connection parameters.
418
+ const nativeOptions = {
419
+ baudRate: options.baudRate,
420
+ dataBits,
421
+ stopBits,
422
+ parity: parityToNative(parity)
423
+ };
424
+ try {
425
+ await this.#usb.open(this.#deviceId, this.#portNumber, nativeOptions);
426
+ } catch (e) {
427
+ // 9.2. If this fails for any reason, reject promise with a "NetworkError"
428
+ // DOMException and abort these steps.
429
+ this.#state = 'closed';
430
+ throw new _domException.DOMException(`Failed to open serial port: ${e.message}`, 'NetworkError');
431
+ }
432
+ if (flowControl !== 'none') {
433
+ await this.#usb.setFlowControl(this.#deviceId, this.#portNumber, flowControlToNative(flowControl));
434
+ }
435
+ this.#state = 'opened'; // 9.3. Set this.[[state]] to "opened".
436
+ this.#bufferSize = bufferSize; // 9.4. Set this.[[bufferSize]] to options["bufferSize"].
437
+
438
+ await this.#usb.startReading(this.#deviceId, this.#portNumber);
439
+
440
+ // When the port becomes logically connected:
441
+ this.#connected = true; // 2. Set port.[[connected]] to true.
442
+ // NOTE: the port-level "connect"/"disconnect" events represent physical
443
+ // device presence (attach/detach), dispatched by Serial from the native
444
+ // USB state events — not logical open()/close(). So we do not dispatch them
445
+ // here; that also avoids re-entrancy with auto-reconnect listeners.
446
+ }
447
+
448
+ /**
449
+ * @see https://wicg.github.io/serial/#dom-serialport-close
450
+ */
451
+ async close() {
452
+ // The close() method steps are:
453
+
454
+ // 2. If this.[[state]] is not "opened", reject promise with an
455
+ // "InvalidStateError" DOMException and return promise.
456
+ if (this.#state !== 'opened') {
457
+ throw new _domException.DOMException('The port is not open.', 'InvalidStateError');
458
+ }
459
+
460
+ // 3. Let cancelPromise be the result of invoking cancel on this.[[readable]]
461
+ // or a promise resolved with undefined if this.[[readable]] is null.
462
+ const cancelPromise = this.#readable ? this.#readable.cancel().catch(() => {}) : Promise.resolve();
463
+
464
+ // 4. Let abortPromise be the result of invoking abort on this.[[writable]]
465
+ // or a promise resolved with undefined if this.[[writable]] is null.
466
+ const abortPromise = this.#writable ? this.#writable.abort().catch(() => {}) : Promise.resolve();
467
+
468
+ // 5. Let pendingClosePromise be a new promise.
469
+ const pendingClosePromise = (0, _promise.createDeferredPromise)();
470
+
471
+ // 6. If this.[[readable]] and this.[[writable]] are null, resolve
472
+ // pendingClosePromise with undefined.
473
+ if (this.#readable === null && this.#writable === null) {
474
+ pendingClosePromise.resolve();
475
+ } else {
476
+ // 7. Set this.[[pendingClosePromise]] to pendingClosePromise.
477
+ this.#pendingClosePromise = pendingClosePromise;
478
+ }
479
+
480
+ // 9. Set this.[[state]] to "closing".
481
+ this.#state = 'closing';
482
+
483
+ // 8. Let combinedPromise be the result of getting a promise to wait for all
484
+ // with «cancelPromise, abortPromise, pendingClosePromise».
485
+ // 10. React to combinedPromise.
486
+ await Promise.all([cancelPromise, abortPromise, pendingClosePromise.promise]);
487
+
488
+ // 10.1.1. Invoke the operating system to close the serial port and release
489
+ // any associated resources.
490
+ try {
491
+ await this.#usb.close(this.#deviceId, this.#portNumber);
492
+ } catch {
493
+ // ignore
494
+ }
495
+ this.#dataSubscription?.remove();
496
+ this.#errorSubscription?.remove();
497
+ this.#dataSubscription = null;
498
+ this.#errorSubscription = null;
499
+ this.#state = 'closed'; // 10.1.2. Set this.[[state]] to "closed".
500
+ this.#readFatal = false; // 10.1.3. Set this.[[readFatal]] and this.[[writeFatal]] to false.
501
+ this.#writeFatal = false; // 10.1.3. (continued)
502
+ this.#pendingClosePromise = null; // 10.1.4. Set this.[[pendingClosePromise]] to null.
503
+
504
+ // When the port is no longer logically connected:
505
+ this.#connected = false; // 2. Set port.[[connected]] to false.
506
+ // (No port-level "disconnect" dispatch here — that event signals physical
507
+ // detach, fired by Serial from the native USB state events. See open().)
508
+ }
509
+
510
+ /**
511
+ * @see https://wicg.github.io/serial/#dom-serialport-forget
512
+ */
513
+ async forget() {
514
+ // The forget() method steps are:
515
+
516
+ // 2.1. Set this.[[state]] to "forgetting".
517
+ this.#state = 'forgetting';
518
+ // 2.2. Remove this from the sequence of serial ports on the system which
519
+ // the user has allowed the site to access.
520
+ // (no persistent permissions to revoke in this implementation)
521
+ // 2.3. Set this.[[state]] to "forgotten".
522
+ this.#state = 'forgotten';
523
+ }
524
+
525
+ /**
526
+ * @see https://wicg.github.io/serial/#dom-serialport-setsignals
527
+ */
528
+ async setSignals(signals = {}) {
529
+ // The setSignals() method steps are:
530
+
531
+ // 2. If this.[[state]] is not "opened", reject promise with an
532
+ // "InvalidStateError" DOMException and return promise.
533
+ if (this.#state !== 'opened') {
534
+ throw new _domException.DOMException('The port is not open.', 'InvalidStateError');
535
+ }
536
+
537
+ // 3. If all of the specified members of signals are not present, reject
538
+ // promise with a TypeError and return promise.
539
+ if (signals.dataTerminalReady === undefined && signals.requestToSend === undefined && signals.break === undefined) {
540
+ throw new TypeError('At least one signal must be specified.');
541
+ }
542
+
543
+ // Merge with current output signal state for partial updates
544
+ this.#outputSignals = {
545
+ ...this.#outputSignals,
546
+ ...signals
547
+ };
548
+ const promises = [];
549
+
550
+ // 4.1. If signals["dataTerminalReady"] is present, invoke the operating
551
+ // system to either assert (if true) or deassert (if false) the "DTR" signal.
552
+ if (signals.dataTerminalReady !== undefined) {
553
+ promises.push(this.#usb.setDTR(this.#deviceId, this.#portNumber, signals.dataTerminalReady));
554
+ }
555
+
556
+ // 4.2. If signals["requestToSend"] is present, invoke the operating system
557
+ // to either assert (if true) or deassert (if false) the "RTS" signal.
558
+ if (signals.requestToSend !== undefined) {
559
+ promises.push(this.#usb.setRTS(this.#deviceId, this.#portNumber, signals.requestToSend));
560
+ }
561
+
562
+ // 4.3. If signals["break"] is present, invoke the operating system to
563
+ // either assert (if true) or deassert (if false) the "break" signal.
564
+ if (signals.break !== undefined) {
565
+ promises.push(this.#usb.setBreak(this.#deviceId, this.#portNumber, signals.break));
566
+ }
567
+ try {
568
+ await Promise.all(promises);
569
+ } catch (e) {
570
+ // 4.4. If the operating system fails to change the state of any of these
571
+ // signals for any reason, reject promise with a "NetworkError" DOMException.
572
+ throw new _domException.DOMException(`Failed to set signals: ${e.message}`, 'NetworkError');
573
+ }
574
+ }
575
+
576
+ /**
577
+ * @see https://wicg.github.io/serial/#dom-serialport-getsignals
578
+ */
579
+ async getSignals() {
580
+ // The getSignals() method steps are:
581
+
582
+ // 2. If this.[[state]] is not "opened", reject promise with an
583
+ // "InvalidStateError" DOMException and return promise.
584
+ if (this.#state !== 'opened') {
585
+ throw new _domException.DOMException('The port is not open.', 'InvalidStateError');
586
+ }
587
+
588
+ // 3. Query the operating system for the status of the control signals that
589
+ // may be asserted by the device connected to the serial port.
590
+ try {
591
+ const [dcd, cts, ri, dsr] = await Promise.all([this.#usb.getCD(this.#deviceId, this.#portNumber),
592
+ // 3.3. Let dataCarrierDetect be true if the "DCD" signal has been asserted by the device, and false otherwise.
593
+ this.#usb.getCTS(this.#deviceId, this.#portNumber),
594
+ // 3.4. Let clearToSend be true if the "CTS" signal has been asserted by the device, and false otherwise.
595
+ this.#usb.getRI(this.#deviceId, this.#portNumber),
596
+ // 3.5. Let ringIndicator be true if the "RI" signal has been asserted by the device, and false otherwise.
597
+ this.#usb.getDSR(this.#deviceId, this.#portNumber) // 3.6. Let dataSetReady be true if the "DSR" signal has been asserted by the device, and false otherwise.
598
+ ]);
599
+
600
+ // 3.7. Let signals be the ordered map «[ "dataCarrierDetect" → dataCarrierDetect, "clearToSend" → clearToSend, "ringIndicator" → ringIndicator, "dataSetReady" → dataSetReady ]».
601
+ return {
602
+ dataCarrierDetect: dcd,
603
+ clearToSend: cts,
604
+ ringIndicator: ri,
605
+ dataSetReady: dsr
606
+ };
607
+ } catch (e) {
608
+ // 3.2. If the operating system fails to determine the status of these
609
+ // signals for any reason, reject promise with a "NetworkError" DOMException
610
+ // and abort these steps.
611
+ throw new _domException.DOMException(`Failed to get signals: ${e.message}`, 'NetworkError');
612
+ }
613
+ }
614
+
615
+ /**
616
+ * @see https://wicg.github.io/serial/#dfn-handle-closing-the-readable-stream
617
+ */
618
+ #handleClosingReadableStream() {
619
+ // To handle closing the readable stream perform the following steps:
620
+
621
+ // 1. Set this.[[readable]] to null.
622
+ this.#readable = null;
623
+
624
+ // 2. If this.[[writable]] is null and this.[[pendingClosePromise]] is not
625
+ // null, resolve this.[[pendingClosePromise]] with undefined.
626
+ if (this.#writable === null && this.#pendingClosePromise !== null) {
627
+ this.#pendingClosePromise.resolve();
628
+ }
629
+ }
630
+
631
+ /**
632
+ * @see https://wicg.github.io/serial/#dfn-handle-closing-the-writable-stream
633
+ */
634
+ #handleClosingWritableStream() {
635
+ // To handle closing the writable stream perform the following steps:
636
+
637
+ // 1. Set this.[[writable]] to null.
638
+ this.#writable = null;
639
+
640
+ // 2. If this.[[readable]] is null and this.[[pendingClosePromise]] is not
641
+ // null, resolve this.[[pendingClosePromise]] with undefined.
642
+ if (this.#readable === null && this.#pendingClosePromise !== null) {
643
+ this.#pendingClosePromise.resolve();
644
+ }
645
+ }
646
+ }
647
+
648
+ /**
649
+ * @see https://wicg.github.io/serial/#serial-interface
650
+ */
651
+ exports.SerialPort = SerialPort;
652
+ class Serial extends _eventTarget.EventTarget {
653
+ #events = {
654
+ connect: null,
655
+ disconnect: null
656
+ };
657
+ #usb = null;
658
+ #knownPorts = new Map();
659
+ #initialized = false;
660
+
661
+ /**
662
+ * Lazily acquire the native USB-serial module and wire connect/disconnect
663
+ * listeners on first use (getPorts/requestPort) rather than at construction.
664
+ * Deferring native access out of the module-import path avoids touching the
665
+ * TurboModule before the runtime is ready.
666
+ */
667
+ #ensureInit() {
668
+ if (this.#initialized) return this.#usb;
669
+ this.#initialized = true;
670
+ try {
671
+ this.#usb = (0, _UsbSerial.getUsbSerial)();
672
+
673
+ // A USB device was attached. Android assigns a NEW deviceId on every
674
+ // attach, so match previously-known ports of the same physical device by
675
+ // VID/PID, update their deviceId, re-key them, and fire "connect" on the
676
+ // same SerialPort instance (W3C spec model: the port is reused).
677
+ this.#usb.onConnect(event => {
678
+ let matched = false;
679
+ for (const [key, port] of [...this.#knownPorts.entries()]) {
680
+ const internals = portInternals.get(port);
681
+ if (!internals) continue;
682
+ if (internals.getVid() === event.usbVendorId && internals.getPid() === event.usbProductId) {
683
+ internals.setDeviceId(event.deviceId);
684
+ const newKey = this.#portKey(event.deviceId, internals.getPortNumber());
685
+ if (newKey !== key) {
686
+ this.#knownPorts.delete(key);
687
+ this.#knownPorts.set(newKey, port);
688
+ }
689
+ port.dispatchEvent(new _eventTarget.Event('connect'));
690
+ matched = true;
691
+ }
692
+ }
693
+ this.dispatchEvent(new _eventTarget.Event('connect'));
694
+ // If we matched no known port this is a brand-new device; getPorts()
695
+ // will surface it on the next refresh.
696
+ void matched;
697
+ });
698
+
699
+ // A USB device was detached. Reset the matching port(s) to a closed,
700
+ // re-openable state (without touching the gone device) but KEEP the
701
+ // SerialPort instance so a re-attach can reuse it. handleDeviceLost()
702
+ // dispatches "disconnect" on the port.
703
+ this.#usb.onDisconnect(event => {
704
+ const prefix = `${event.deviceId}:`;
705
+ for (const [key, port] of [...this.#knownPorts.entries()]) {
706
+ if (key.startsWith(prefix)) {
707
+ portInternals.get(port)?.handleDeviceLost();
708
+ }
709
+ }
710
+ this.dispatchEvent(new _eventTarget.Event('disconnect'));
711
+ });
712
+ } catch {
713
+ this.#usb = null;
714
+ }
715
+ return this.#usb;
716
+ }
717
+ /**
718
+ * @see https://wicg.github.io/serial/#dom-serial-onconnect
719
+ */
720
+ get onconnect() {
721
+ return this.#events.connect;
722
+ }
723
+ set onconnect(fn) {
724
+ if (this.#events.connect) {
725
+ this.removeEventListener('connect', this.#events.connect);
726
+ }
727
+ if (fn !== null) {
728
+ this.addEventListener('connect', fn);
729
+ this.#events.connect = fn;
730
+ } else {
731
+ this.#events.connect = null;
732
+ }
733
+ }
734
+
735
+ /**
736
+ * @see https://wicg.github.io/serial/#dom-serial-ondisconnect
737
+ */
738
+ get ondisconnect() {
739
+ return this.#events.disconnect;
740
+ }
741
+ set ondisconnect(fn) {
742
+ if (this.#events.disconnect) {
743
+ this.removeEventListener('disconnect', this.#events.disconnect);
744
+ }
745
+ if (fn !== null) {
746
+ this.addEventListener('disconnect', fn);
747
+ this.#events.disconnect = fn;
748
+ } else {
749
+ this.#events.disconnect = null;
750
+ }
751
+ }
752
+ #portKey(deviceId, portNumber) {
753
+ return `${deviceId}:${portNumber}`;
754
+ }
755
+
756
+ /**
757
+ * @see https://wicg.github.io/serial/#dom-serial-getports
758
+ */
759
+ async getPorts() {
760
+ // The getPorts() method steps are:
761
+
762
+ const usb = this.#ensureInit();
763
+ if (!usb) return []; // Native module not available
764
+
765
+ // 3.1. Let availablePorts be the sequence of available serial ports which
766
+ // the user has allowed the site to access as the result of a previous call
767
+ // to requestPort().
768
+ const portIds = await usb.findAllDrivers();
769
+
770
+ // 3.2. Let ports be the sequence of the SerialPorts representing the ports
771
+ // in availablePorts.
772
+ const ports = [];
773
+ for (const {
774
+ deviceId,
775
+ portNumber,
776
+ usbVendorId,
777
+ usbProductId
778
+ } of portIds) {
779
+ const key = this.#portKey(deviceId, portNumber);
780
+ if (!this.#knownPorts.has(key)) {
781
+ this.#knownPorts.set(key, new SerialPort(usb, deviceId, portNumber, usbVendorId, usbProductId));
782
+ }
783
+ ports.push(this.#knownPorts.get(key));
784
+ }
785
+
786
+ // 3.3. Resolve promise with ports.
787
+ return ports;
788
+ }
789
+
790
+ /**
791
+ * @see https://wicg.github.io/serial/#dom-serial-requestport
792
+ */
793
+ async requestPort(options = {}) {
794
+ // The requestPort() method steps are:
795
+
796
+ const usb = this.#ensureInit();
797
+ if (!usb) {
798
+ throw new _domException.DOMException('NativeUsbSerial is not available.', 'NotFoundError');
799
+ }
800
+
801
+ // 4. If options["filters"] is present, then for each filter in
802
+ // options["filters"] run the following steps:
803
+ if (options.filters) {
804
+ for (const filter of options.filters) {
805
+ if (filter.bluetoothServiceClassId !== undefined) {
806
+ // 4.1.1. If filter["usbVendorId"] is present, reject promise with a
807
+ // TypeError and return promise.
808
+ if (filter.usbVendorId !== undefined) {
809
+ throw new TypeError('A filter cannot specify both bluetoothServiceClassId and usbVendorId.');
810
+ }
811
+ // 4.1.2. If filter["usbProductId"] is present, reject promise with a
812
+ // TypeError and return promise.
813
+ if (filter.usbProductId !== undefined) {
814
+ throw new TypeError('A filter cannot specify both bluetoothServiceClassId and usbProductId.');
815
+ }
816
+ } else if (filter.usbVendorId === undefined) {
817
+ // 4.2. If filter["usbVendorId"] is not present, reject promise with a
818
+ // TypeError and return promise.
819
+ throw new TypeError('A filter must specify usbVendorId if usbProductId is specified, or must not be empty.');
820
+ }
821
+ }
822
+ }
823
+
824
+ // 5.4. Prompt the user to grant the site access to a serial port —
825
+ // shows a native Android dialog with available ports filtered by
826
+ // options["filters"] and requests USB permission for the selected port.
827
+ const nativeFilters = (options.filters ?? []).filter(f => f.usbVendorId !== undefined).map(f => ({
828
+ usbVendorId: f.usbVendorId,
829
+ usbProductId: f.usbProductId
830
+ }));
831
+ let portId;
832
+ try {
833
+ portId = await usb.showPortPicker(nativeFilters);
834
+ } catch {
835
+ // 5.5. If the user does not choose a port, reject promise with a
836
+ // "NotFoundError" DOMException and abort these steps.
837
+ throw new _domException.DOMException('No port selected by the user.', 'NotFoundError');
838
+ }
839
+
840
+ // 5.6. Let port be a SerialPort representing the port chosen by the user.
841
+ const key = `${portId.deviceId}:${portId.portNumber}`;
842
+ if (!this.#knownPorts.has(key)) {
843
+ this.#knownPorts.set(key, new SerialPort(usb, portId.deviceId, portId.portNumber, portId.usbVendorId, portId.usbProductId));
844
+ }
845
+
846
+ // 5.7. Resolve promise with port.
847
+ return this.#knownPorts.get(key);
848
+ }
849
+ }
850
+ exports.Serial = Serial;
851
+ const serial = exports.serial = new Serial();
852
+ //# sourceMappingURL=WebSerial.js.map