react-native-web-serial-api 0.0.3 → 0.2.0
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/README.md +198 -104
- package/TESTING.md +542 -0
- package/android/build.gradle +16 -2
- package/android/src/main/java/dev/webserialapi/NativeUsbSerialModule.java +74 -11
- package/android/src/main/java/dev/webserialapi/PortPickerActivity.java +61 -59
- package/bin/expose-serial.js +205 -0
- package/lib/commonjs/UsbSerial.js +58 -26
- package/lib/commonjs/UsbSerial.js.map +1 -1
- package/lib/commonjs/WebSerial.js +273 -77
- package/lib/commonjs/WebSerial.js.map +1 -1
- package/lib/commonjs/index.js +15 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/lib/dom-exception.js +176 -0
- package/lib/commonjs/lib/dom-exception.js.map +1 -0
- package/lib/commonjs/lib/event-target.js +140 -0
- package/lib/commonjs/lib/event-target.js.map +1 -0
- package/lib/commonjs/lib/promise.js +23 -0
- package/lib/commonjs/lib/promise.js.map +1 -0
- package/lib/commonjs/lib/web-streams.js +42 -0
- package/lib/commonjs/lib/web-streams.js.map +1 -0
- package/lib/commonjs/testing/device-fixture.js +70 -0
- package/lib/commonjs/testing/device-fixture.js.map +1 -0
- package/lib/commonjs/testing/expose.js +91 -0
- package/lib/commonjs/testing/expose.js.map +1 -0
- package/lib/commonjs/testing/harness.js +98 -0
- package/lib/commonjs/testing/harness.js.map +1 -0
- package/lib/commonjs/testing/in-memory-serial-transport.js +653 -0
- package/lib/commonjs/testing/in-memory-serial-transport.js.map +1 -0
- package/lib/commonjs/testing/index.js +153 -0
- package/lib/commonjs/testing/index.js.map +1 -0
- package/lib/commonjs/testing/install-in-memory-serial-transport.js +54 -0
- package/lib/commonjs/testing/install-in-memory-serial-transport.js.map +1 -0
- package/lib/commonjs/testing/serial-client.js +277 -0
- package/lib/commonjs/testing/serial-client.js.map +1 -0
- package/lib/commonjs/testing/simulated-device.js +164 -0
- package/lib/commonjs/testing/simulated-device.js.map +1 -0
- package/lib/commonjs/testing/test-suite.js +142 -0
- package/lib/commonjs/testing/test-suite.js.map +1 -0
- package/lib/commonjs/transport.js +61 -0
- package/lib/commonjs/transport.js.map +1 -0
- package/lib/commonjs/websocket/WebSocketSerialTransport.js +659 -0
- package/lib/commonjs/websocket/WebSocketSerialTransport.js.map +1 -0
- package/lib/commonjs/websocket/bridge.js +234 -0
- package/lib/commonjs/websocket/bridge.js.map +1 -0
- package/lib/commonjs/websocket/index.js +33 -0
- package/lib/commonjs/websocket/index.js.map +1 -0
- package/lib/commonjs/websocket/protocol.js +55 -0
- package/lib/commonjs/websocket/protocol.js.map +1 -0
- package/lib/commonjs/websocket/serial-device-bridge.js +130 -0
- package/lib/commonjs/websocket/serial-device-bridge.js.map +1 -0
- package/lib/typescript/src/UsbSerial.d.ts +24 -67
- package/lib/typescript/src/UsbSerial.d.ts.map +1 -1
- package/lib/typescript/src/WebSerial.d.ts +16 -7
- package/lib/typescript/src/WebSerial.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/lib/dom-exception.d.ts +100 -0
- package/lib/typescript/src/lib/dom-exception.d.ts.map +1 -0
- package/lib/typescript/src/lib/event-target.d.ts +55 -0
- package/lib/typescript/src/lib/event-target.d.ts.map +1 -0
- package/lib/typescript/src/lib/promise.d.ts +11 -0
- package/lib/typescript/src/lib/promise.d.ts.map +1 -0
- package/lib/typescript/src/lib/web-streams.d.ts +9 -0
- package/lib/typescript/src/lib/web-streams.d.ts.map +1 -0
- package/lib/typescript/src/testing/device-fixture.d.ts +70 -0
- package/lib/typescript/src/testing/device-fixture.d.ts.map +1 -0
- package/lib/typescript/src/testing/expose.d.ts +71 -0
- package/lib/typescript/src/testing/expose.d.ts.map +1 -0
- package/lib/typescript/src/testing/harness.d.ts +34 -0
- package/lib/typescript/src/testing/harness.d.ts.map +1 -0
- package/lib/typescript/src/testing/in-memory-serial-transport.d.ts +216 -0
- package/lib/typescript/src/testing/in-memory-serial-transport.d.ts.map +1 -0
- package/lib/typescript/src/testing/index.d.ts +33 -0
- package/lib/typescript/src/testing/index.d.ts.map +1 -0
- package/lib/typescript/src/testing/install-in-memory-serial-transport.d.ts +25 -0
- package/lib/typescript/src/testing/install-in-memory-serial-transport.d.ts.map +1 -0
- package/lib/typescript/src/testing/serial-client.d.ts +62 -0
- package/lib/typescript/src/testing/serial-client.d.ts.map +1 -0
- package/lib/typescript/src/testing/simulated-device.d.ts +127 -0
- package/lib/typescript/src/testing/simulated-device.d.ts.map +1 -0
- package/lib/typescript/src/testing/test-suite.d.ts +75 -0
- package/lib/typescript/src/testing/test-suite.d.ts.map +1 -0
- package/lib/typescript/src/transport.d.ts +131 -0
- package/lib/typescript/src/transport.d.ts.map +1 -0
- package/lib/typescript/src/websocket/WebSocketSerialTransport.d.ts +111 -0
- package/lib/typescript/src/websocket/WebSocketSerialTransport.d.ts.map +1 -0
- package/lib/typescript/src/websocket/bridge.d.ts +66 -0
- package/lib/typescript/src/websocket/bridge.d.ts.map +1 -0
- package/lib/typescript/src/websocket/index.d.ts +19 -0
- package/lib/typescript/src/websocket/index.d.ts.map +1 -0
- package/lib/typescript/src/websocket/protocol.d.ts +64 -0
- package/lib/typescript/src/websocket/protocol.d.ts.map +1 -0
- package/lib/typescript/src/websocket/serial-device-bridge.d.ts +32 -0
- package/lib/typescript/src/websocket/serial-device-bridge.d.ts.map +1 -0
- package/package.json +57 -3
- package/src/UsbSerial.ts +65 -90
- package/src/WebSerial.ts +351 -113
- package/src/index.ts +6 -8
- package/src/lib/dom-exception.ts +129 -60
- package/src/lib/event-target.ts +58 -21
- package/src/lib/promise.ts +7 -7
- package/src/lib/web-streams.ts +43 -0
- package/src/testing/device-fixture.ts +150 -0
- package/src/testing/expose.ts +147 -0
- package/src/testing/harness.ts +124 -0
- package/src/testing/in-memory-serial-transport.ts +840 -0
- package/src/testing/index.ts +90 -0
- package/src/testing/install-in-memory-serial-transport.ts +65 -0
- package/src/testing/serial-client.ts +313 -0
- package/src/testing/simulated-device.ts +193 -0
- package/src/testing/test-suite.ts +186 -0
- package/src/transport.ts +200 -0
- package/src/websocket/WebSocketSerialTransport.ts +796 -0
- package/src/websocket/bridge.ts +299 -0
- package/src/websocket/index.ts +38 -0
- package/src/websocket/protocol.ts +101 -0
- package/src/websocket/serial-device-bridge.ts +160 -0
- package/babel.config.js +0 -3
- package/biome.json +0 -35
- package/example/.watchmanconfig +0 -1
- package/example/App.tsx +0 -71
- package/example/__tests__/App.test.tsx +0 -16
- package/example/__tests__/connectEvents.test.tsx +0 -81
- package/example/__tests__/getPorts.test.tsx +0 -140
- package/example/android/app/build.gradle +0 -120
- package/example/android/app/debug.keystore +0 -0
- package/example/android/app/proguard-rules.pro +0 -10
- package/example/android/app/src/debug/AndroidManifest.xml +0 -9
- package/example/android/app/src/main/AndroidManifest.xml +0 -38
- package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +0 -22
- package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +0 -41
- package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -37
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/values/strings.xml +0 -3
- package/example/android/app/src/main/res/values/styles.xml +0 -9
- package/example/android/build.gradle +0 -22
- package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
- package/example/android/gradle.properties +0 -47
- package/example/android/gradlew +0 -252
- package/example/android/gradlew.bat +0 -94
- package/example/android/settings.gradle +0 -6
- package/example/app.json +0 -4
- package/example/babel.config.js +0 -21
- package/example/biome.json +0 -47
- package/example/deploy.sh +0 -11
- package/example/index.html +0 -26
- package/example/index.js +0 -9
- package/example/index.web.js +0 -8
- package/example/jest.config.js +0 -12
- package/example/metro.config.js +0 -58
- package/example/package-lock.json +0 -14510
- package/example/package.json +0 -48
- package/example/react-native.config.js +0 -17
- package/example/src/components/AppBar.tsx +0 -73
- package/example/src/components/Menu.tsx +0 -90
- package/example/src/components/SingleChoiceDialog.tsx +0 -120
- package/example/src/screens/ConnectScreen.tsx +0 -195
- package/example/src/screens/DevicesScreen.tsx +0 -252
- package/example/src/screens/TerminalScreen.tsx +0 -572
- package/example/src/settings.ts +0 -43
- package/example/src/theme.ts +0 -19
- package/example/src/util/TextUtil.ts +0 -129
- package/example/tsconfig.json +0 -10
- package/example/vite.config.mjs +0 -55
- package/scripts/deploy-release.sh +0 -127
- package/tsconfig.build.json +0 -7
- package/tsconfig.json +0 -20
package/src/WebSerial.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ByteLengthQueuingStrategy,
|
|
3
|
-
ReadableStream,
|
|
4
|
-
WritableStream,
|
|
5
|
-
} from 'web-streams-polyfill';
|
|
6
1
|
import {DOMException} from './lib/dom-exception';
|
|
7
|
-
import {Event, EventTarget} from './lib/event-target';
|
|
2
|
+
import {Event, EventTarget, setEventParent} from './lib/event-target';
|
|
8
3
|
import {createDeferredPromise} from './lib/promise';
|
|
4
|
+
import {
|
|
5
|
+
ByteLengthQueuingStrategyImpl as ByteLengthQueuingStrategy,
|
|
6
|
+
ReadableStreamImpl as ReadableStream,
|
|
7
|
+
WritableStreamImpl as WritableStream,
|
|
8
|
+
} from './lib/web-streams';
|
|
9
9
|
import type {
|
|
10
10
|
ConnectEvent,
|
|
11
11
|
DataEvent,
|
|
12
12
|
ErrorEvent,
|
|
13
13
|
OpenOptions,
|
|
14
14
|
PortId,
|
|
15
|
-
|
|
16
|
-
} from './
|
|
15
|
+
SerialTransport,
|
|
16
|
+
} from './transport';
|
|
17
17
|
import {getUsbSerial} from './UsbSerial';
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -23,14 +23,14 @@ import {getUsbSerial} from './UsbSerial';
|
|
|
23
23
|
* even - Data word plus parity bit has even parity.
|
|
24
24
|
* odd - Data word plus parity bit has odd parity.
|
|
25
25
|
*/
|
|
26
|
-
type
|
|
26
|
+
export type Parity = 'none' | 'even' | 'odd';
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* @see https://wicg.github.io/serial/#dom-flowcontroltype
|
|
30
30
|
* none - No flow control is enabled.
|
|
31
31
|
* hardware - Hardware flow control using the RTS and CTS signals is enabled.
|
|
32
32
|
*/
|
|
33
|
-
type
|
|
33
|
+
type FlowControl = 'none' | 'hardware';
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* @see https://wicg.github.io/serial/#dom-serialoptions
|
|
@@ -60,7 +60,7 @@ export type SerialOptions = {
|
|
|
60
60
|
/**
|
|
61
61
|
* The parity mode.
|
|
62
62
|
*/
|
|
63
|
-
parity?:
|
|
63
|
+
parity?: Parity;
|
|
64
64
|
/**
|
|
65
65
|
* A positive, non-zero value indicating the size of the read and write
|
|
66
66
|
* buffers that should be created.
|
|
@@ -69,7 +69,7 @@ export type SerialOptions = {
|
|
|
69
69
|
/**
|
|
70
70
|
* The flow control mode.
|
|
71
71
|
*/
|
|
72
|
-
flowControl?:
|
|
72
|
+
flowControl?: FlowControl;
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
/**
|
|
@@ -171,24 +171,27 @@ type PortInternals = {
|
|
|
171
171
|
getVid(): number | undefined;
|
|
172
172
|
getPid(): number | undefined;
|
|
173
173
|
getPortNumber(): number;
|
|
174
|
-
|
|
174
|
+
isForgotten(): boolean;
|
|
175
175
|
setDeviceId(id: number): void;
|
|
176
176
|
/** Reset to a closed, re-openable state after the device is physically lost. */
|
|
177
177
|
handleDeviceLost(): void;
|
|
178
|
+
/** Reset to a closed, re-openable state after a clean physical detach. */
|
|
179
|
+
handleDeviceDetached(): void;
|
|
178
180
|
};
|
|
179
181
|
const portInternals = new WeakMap<SerialPort, PortInternals>();
|
|
180
182
|
|
|
181
183
|
const kDefaultDataBits = 8;
|
|
182
184
|
const kDefaultStopBits = 1;
|
|
183
|
-
const kDefaultParity:
|
|
185
|
+
const kDefaultParity: Parity = 'none';
|
|
184
186
|
const kDefaultBufferSize = 255;
|
|
185
|
-
const kDefaultFlowControl:
|
|
187
|
+
const kDefaultFlowControl: FlowControl = 'none';
|
|
186
188
|
|
|
187
189
|
const kAcceptableDataBits = [7, 8] as const;
|
|
188
190
|
const kAcceptableStopBits = [1, 2] as const;
|
|
189
|
-
const kAcceptableParity:
|
|
191
|
+
const kAcceptableParity: Parity[] = ['none', 'even', 'odd'];
|
|
192
|
+
const kAcceptableFlowControl: FlowControl[] = ['none', 'hardware'];
|
|
190
193
|
|
|
191
|
-
function parityToNative(parity:
|
|
194
|
+
function parityToNative(parity: Parity): number {
|
|
192
195
|
switch (parity) {
|
|
193
196
|
case 'odd':
|
|
194
197
|
return 1;
|
|
@@ -199,8 +202,32 @@ function parityToNative(parity: ParityType): number {
|
|
|
199
202
|
}
|
|
200
203
|
}
|
|
201
204
|
|
|
202
|
-
|
|
203
|
-
|
|
205
|
+
const FLOW_CONTROL_NATIVE: Readonly<Record<FlowControl, 'RTS_CTS' | 'NONE'>> = {
|
|
206
|
+
none: 'NONE',
|
|
207
|
+
hardware: 'RTS_CTS',
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
function flowControlToNative(flowControl: FlowControl): 'RTS_CTS' | 'NONE' {
|
|
211
|
+
return FLOW_CONTROL_NATIVE[flowControl];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Normalise a writable chunk to a plain byte array. The Web Serial writable
|
|
216
|
+
* accepts any BufferSource — a bare `ArrayBuffer` or any `ArrayBufferView`
|
|
217
|
+
* (`DataView`, typed arrays, including views with a non-zero `byteOffset`). A
|
|
218
|
+
* naive `Array.from(chunk)` only works for the iterable typed arrays and
|
|
219
|
+
* silently yields `[]` for an `ArrayBuffer`/`DataView`, so handle each shape.
|
|
220
|
+
*/
|
|
221
|
+
function bufferSourceToBytes(chunk: ArrayBufferView | ArrayBuffer): number[] {
|
|
222
|
+
if (chunk instanceof Uint8Array) {
|
|
223
|
+
return Array.from(chunk);
|
|
224
|
+
}
|
|
225
|
+
if (ArrayBuffer.isView(chunk)) {
|
|
226
|
+
return Array.from(
|
|
227
|
+
new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return Array.from(new Uint8Array(chunk as ArrayBuffer));
|
|
204
231
|
}
|
|
205
232
|
|
|
206
233
|
/**
|
|
@@ -239,24 +266,60 @@ export class SerialPort extends EventTarget {
|
|
|
239
266
|
#pendingClosePromise: ReturnType<typeof createDeferredPromise<void>> | null =
|
|
240
267
|
null; // [[pendingClosePromise]] = null
|
|
241
268
|
|
|
242
|
-
#usb:
|
|
269
|
+
#usb: SerialTransport;
|
|
243
270
|
#deviceId: number;
|
|
244
271
|
#portNumber: number;
|
|
245
272
|
#usbVendorId?: number;
|
|
246
273
|
#usbProductId?: number;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
#outputSignals: Required<SerialOutputSignals> = {
|
|
250
|
-
dataTerminalReady: false,
|
|
251
|
-
requestToSend: false,
|
|
252
|
-
break: false,
|
|
253
|
-
};
|
|
274
|
+
// Baud rate of the current open(), used to size the native write timeout.
|
|
275
|
+
#baudRate: number = 0;
|
|
254
276
|
|
|
255
277
|
#dataSubscription: {remove: () => void} | null = null;
|
|
256
278
|
#errorSubscription: {remove: () => void} | null = null;
|
|
257
279
|
|
|
280
|
+
// The live stream controllers, captured in the readable/writable getters, so
|
|
281
|
+
// a device loss can error the in-flight read/write with a "NetworkError".
|
|
282
|
+
#readableController: ReadableStreamDefaultController<Uint8Array> | null =
|
|
283
|
+
null;
|
|
284
|
+
#writableController: WritableStreamDefaultController | null = null;
|
|
285
|
+
#forgetRequested: boolean = false;
|
|
286
|
+
|
|
287
|
+
#resetToClosedState(state: 'closed' | 'forgotten' = 'closed'): void {
|
|
288
|
+
this.#dataSubscription?.remove();
|
|
289
|
+
this.#errorSubscription?.remove();
|
|
290
|
+
this.#dataSubscription = null;
|
|
291
|
+
this.#errorSubscription = null;
|
|
292
|
+
this.#readableController = null;
|
|
293
|
+
this.#writableController = null;
|
|
294
|
+
|
|
295
|
+
this.#readable = null;
|
|
296
|
+
this.#writable = null;
|
|
297
|
+
this.#readFatal = false;
|
|
298
|
+
this.#writeFatal = false;
|
|
299
|
+
this.#pendingClosePromise = null;
|
|
300
|
+
this.#bufferSize = undefined;
|
|
301
|
+
this.#baudRate = 0;
|
|
302
|
+
this.#state = state;
|
|
303
|
+
this.#connected = false;
|
|
304
|
+
this.#forgetRequested = state === 'forgotten';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Compute a native write timeout (ms) large enough to actually drain `length`
|
|
309
|
+
* bytes at the negotiated baud rate. A fixed 2 s timeout spuriously fails
|
|
310
|
+
* large writes on slow links; this scales with the payload while keeping a 2 s
|
|
311
|
+
* floor. ~10 bits per byte (start + 8 data + stop) with a 2× safety margin.
|
|
312
|
+
*/
|
|
313
|
+
#writeTimeoutFor(length: number): number {
|
|
314
|
+
const kMinTimeoutMs = 2000;
|
|
315
|
+
/* istanbul ignore next — baudRate is always > 0 when the port is open */
|
|
316
|
+
const baud = this.#baudRate > 0 ? this.#baudRate : 9600;
|
|
317
|
+
const estimatedMs = Math.ceil((length * 10 * 1000) / baud) * 2;
|
|
318
|
+
return Math.max(kMinTimeoutMs, estimatedMs);
|
|
319
|
+
}
|
|
320
|
+
|
|
258
321
|
constructor(
|
|
259
|
-
usb:
|
|
322
|
+
usb: SerialTransport,
|
|
260
323
|
deviceId: number,
|
|
261
324
|
portNumber: number,
|
|
262
325
|
usbVendorId: number,
|
|
@@ -275,12 +338,13 @@ export class SerialPort extends EventTarget {
|
|
|
275
338
|
getVid: () => this.#usbVendorId,
|
|
276
339
|
getPid: () => this.#usbProductId,
|
|
277
340
|
getPortNumber: () => this.#portNumber,
|
|
278
|
-
|
|
341
|
+
isForgotten: () => this.#state === 'forgotten',
|
|
279
342
|
setDeviceId: (id: number) => {
|
|
280
343
|
this.#deviceId = id;
|
|
281
344
|
serialPortDeviceIds.set(this, id);
|
|
282
345
|
},
|
|
283
346
|
handleDeviceLost: () => this.#handleDeviceLost(),
|
|
347
|
+
handleDeviceDetached: () => this.#handleDeviceDetached(),
|
|
284
348
|
});
|
|
285
349
|
}
|
|
286
350
|
|
|
@@ -291,35 +355,75 @@ export class SerialPort extends EventTarget {
|
|
|
291
355
|
* After this, a later open() (e.g. when the device is re-attached) succeeds.
|
|
292
356
|
*/
|
|
293
357
|
#handleDeviceLost(): void {
|
|
294
|
-
|
|
358
|
+
const wasForgotten = this.#state === 'forgotten';
|
|
359
|
+
|
|
360
|
+
// Reject any in-flight read/write with a "NetworkError" (per spec), erroring
|
|
361
|
+
// the stream controllers directly. We must NOT cancel()/abort() the streams:
|
|
362
|
+
// cancel() rejects on a reader-locked stream (leaving the read orphaned) and
|
|
363
|
+
// both would try to invoke the OS on the now-gone device.
|
|
364
|
+
const lost = new DOMException('The device has been lost.', 'NetworkError');
|
|
295
365
|
if (this.#readable) {
|
|
296
366
|
this.#readFatal = true;
|
|
297
367
|
try {
|
|
298
|
-
|
|
299
|
-
this.#readable.cancel().catch(() => {});
|
|
368
|
+
this.#readableController?.error(lost);
|
|
300
369
|
} catch {}
|
|
301
370
|
}
|
|
302
371
|
if (this.#writable) {
|
|
303
372
|
this.#writeFatal = true;
|
|
304
373
|
try {
|
|
305
|
-
this.#
|
|
374
|
+
this.#writableController?.error(lost);
|
|
306
375
|
} catch {}
|
|
307
376
|
}
|
|
308
|
-
this.#
|
|
309
|
-
this.#errorSubscription?.remove();
|
|
310
|
-
this.#dataSubscription = null;
|
|
311
|
-
this.#errorSubscription = null;
|
|
377
|
+
this.#resetToClosedState(wasForgotten ? 'forgotten' : 'closed');
|
|
312
378
|
|
|
379
|
+
this.dispatchEvent(new Event('disconnect'));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Reset this port to a closed, re-openable state after a clean physical
|
|
384
|
+
* detach. Unlike device loss, buffered readable bytes should still be
|
|
385
|
+
* observable before the stream ends.
|
|
386
|
+
*/
|
|
387
|
+
#handleDeviceDetached(): void {
|
|
388
|
+
const wasForgotten = this.#state === 'forgotten';
|
|
389
|
+
const readable = this.#readable;
|
|
390
|
+
const writable = this.#writable;
|
|
391
|
+
|
|
392
|
+
// Keep the public event and state transition synchronous so existing
|
|
393
|
+
// callers observe the detach immediately, but defer the stream teardown by
|
|
394
|
+
// one microtask to let already-queued data drain first.
|
|
395
|
+
this.#state = wasForgotten ? 'forgotten' : 'closed';
|
|
396
|
+
this.#connected = false;
|
|
313
397
|
this.#readable = null;
|
|
314
398
|
this.#writable = null;
|
|
315
|
-
this.#readFatal = false;
|
|
316
|
-
this.#writeFatal = false;
|
|
317
|
-
this.#pendingClosePromise = null;
|
|
318
|
-
this.#bufferSize = undefined;
|
|
319
|
-
this.#state = 'closed';
|
|
320
|
-
this.#connected = false;
|
|
321
|
-
|
|
322
399
|
this.dispatchEvent(new Event('disconnect'));
|
|
400
|
+
|
|
401
|
+
queueMicrotask(() => {
|
|
402
|
+
// Close the readable stream so any bytes already queued can still be
|
|
403
|
+
// drained by the consumer before EOF.
|
|
404
|
+
try {
|
|
405
|
+
if (readable) this.#readableController?.close();
|
|
406
|
+
} catch {}
|
|
407
|
+
|
|
408
|
+
// A detached device can no longer accept writes, so reject any writable
|
|
409
|
+
// traffic and clear local state.
|
|
410
|
+
const lost = new DOMException(
|
|
411
|
+
'The device has been lost.',
|
|
412
|
+
'NetworkError',
|
|
413
|
+
);
|
|
414
|
+
if (writable) {
|
|
415
|
+
this.#writeFatal = true;
|
|
416
|
+
try {
|
|
417
|
+
this.#writableController?.error(lost);
|
|
418
|
+
} catch {}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Mirror the stream-closing bookkeeping without treating this as a hard
|
|
422
|
+
// read fatal error.
|
|
423
|
+
this.#handleClosingReadableStream();
|
|
424
|
+
this.#handleClosingWritableStream();
|
|
425
|
+
this.#resetToClosedState(wasForgotten ? 'forgotten' : 'closed');
|
|
426
|
+
});
|
|
323
427
|
}
|
|
324
428
|
|
|
325
429
|
/**
|
|
@@ -394,12 +498,21 @@ export class SerialPort extends EventTarget {
|
|
|
394
498
|
const stream = new ReadableStream<Uint8Array>(
|
|
395
499
|
{
|
|
396
500
|
start(controller) {
|
|
501
|
+
self.#readableController = controller;
|
|
397
502
|
self.#dataSubscription = self.#usb.onData((event: DataEvent) => {
|
|
398
503
|
if (
|
|
399
504
|
event.deviceId === deviceId &&
|
|
400
505
|
event.portNumber === portNumber
|
|
401
506
|
) {
|
|
402
|
-
|
|
507
|
+
try {
|
|
508
|
+
controller.enqueue(new Uint8Array(event.data));
|
|
509
|
+
} catch {
|
|
510
|
+
// The stream may already be closing/closed — e.g. a data event
|
|
511
|
+
// racing cancel()/close(), where the stream is closed but this
|
|
512
|
+
// subscription is not yet torn down. Dropping the chunk is
|
|
513
|
+
// correct (the consumer has stopped reading) and avoids throwing
|
|
514
|
+
// out of the transport's event-dispatch loop.
|
|
515
|
+
}
|
|
403
516
|
}
|
|
404
517
|
});
|
|
405
518
|
|
|
@@ -409,7 +522,15 @@ export class SerialPort extends EventTarget {
|
|
|
409
522
|
event.portNumber === portNumber
|
|
410
523
|
) {
|
|
411
524
|
self.#readFatal = true; // Set this.[[readFatal]] to true.
|
|
412
|
-
|
|
525
|
+
// Surface the spec error type (BreakError, BufferOverrunError,
|
|
526
|
+
// FramingError, ParityError, …) when the transport reports one;
|
|
527
|
+
// fall back to NetworkError otherwise.
|
|
528
|
+
controller.error(
|
|
529
|
+
new DOMException(
|
|
530
|
+
event.error,
|
|
531
|
+
event.errorName ?? 'NetworkError',
|
|
532
|
+
),
|
|
533
|
+
);
|
|
413
534
|
self.#handleClosingReadableStream();
|
|
414
535
|
}
|
|
415
536
|
});
|
|
@@ -456,9 +577,18 @@ export class SerialPort extends EventTarget {
|
|
|
456
577
|
|
|
457
578
|
const stream = new WritableStream<Uint8Array>(
|
|
458
579
|
{
|
|
580
|
+
start(controller) {
|
|
581
|
+
self.#writableController = controller;
|
|
582
|
+
},
|
|
459
583
|
async write(chunk) {
|
|
584
|
+
const bytes = bufferSourceToBytes(chunk);
|
|
460
585
|
try {
|
|
461
|
-
await self.#usb.write(
|
|
586
|
+
await self.#usb.write(
|
|
587
|
+
deviceId,
|
|
588
|
+
portNumber,
|
|
589
|
+
bytes,
|
|
590
|
+
self.#writeTimeoutFor(bytes.length),
|
|
591
|
+
);
|
|
462
592
|
} catch (e) {
|
|
463
593
|
// If the port was disconnected, set this.[[writeFatal]] to true.
|
|
464
594
|
self.#writeFatal = true;
|
|
@@ -541,7 +671,7 @@ export class SerialPort extends EventTarget {
|
|
|
541
671
|
|
|
542
672
|
// 3. If options["baudRate"] is 0, reject promise with a TypeError and
|
|
543
673
|
// return promise.
|
|
544
|
-
if (options.baudRate
|
|
674
|
+
if (!Number.isFinite(options.baudRate) || options.baudRate <= 0) {
|
|
545
675
|
throw new TypeError('baudRate must be a positive, non-zero value.');
|
|
546
676
|
}
|
|
547
677
|
|
|
@@ -562,7 +692,7 @@ export class SerialPort extends EventTarget {
|
|
|
562
692
|
// 6. If options["bufferSize"] is 0, reject promise with a TypeError and
|
|
563
693
|
// return promise.
|
|
564
694
|
const bufferSize = options.bufferSize ?? kDefaultBufferSize;
|
|
565
|
-
if (bufferSize
|
|
695
|
+
if (!Number.isFinite(bufferSize) || bufferSize <= 0) {
|
|
566
696
|
throw new TypeError('bufferSize must be a positive, non-zero value.');
|
|
567
697
|
}
|
|
568
698
|
|
|
@@ -574,6 +704,11 @@ export class SerialPort extends EventTarget {
|
|
|
574
704
|
}
|
|
575
705
|
|
|
576
706
|
const flowControl = options.flowControl ?? kDefaultFlowControl;
|
|
707
|
+
if (!kAcceptableFlowControl.includes(flowControl)) {
|
|
708
|
+
throw new TypeError(
|
|
709
|
+
`flowControl must be one of: ${kAcceptableFlowControl.join(', ')}.`,
|
|
710
|
+
);
|
|
711
|
+
}
|
|
577
712
|
|
|
578
713
|
// 8. Set this.[[state]] to "opening".
|
|
579
714
|
this.#state = 'opening';
|
|
@@ -599,18 +734,60 @@ export class SerialPort extends EventTarget {
|
|
|
599
734
|
);
|
|
600
735
|
}
|
|
601
736
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
this.#
|
|
605
|
-
|
|
606
|
-
|
|
737
|
+
try {
|
|
738
|
+
if (flowControl !== 'none') {
|
|
739
|
+
await this.#usb.setFlowControl(
|
|
740
|
+
this.#deviceId,
|
|
741
|
+
this.#portNumber,
|
|
742
|
+
flowControlToNative(flowControl),
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
await this.#usb.startReading(this.#deviceId, this.#portNumber);
|
|
747
|
+
} catch (e) {
|
|
748
|
+
// If post-open setup fails (flow-control/read pump), best-effort close
|
|
749
|
+
// and reset local state so a subsequent open() can succeed cleanly.
|
|
750
|
+
try {
|
|
751
|
+
await this.#usb.close(this.#deviceId, this.#portNumber);
|
|
752
|
+
} catch {
|
|
753
|
+
// ignore
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
this.#resetToClosedState(this.#forgetRequested ? 'forgotten' : 'closed');
|
|
757
|
+
|
|
758
|
+
throw new DOMException(
|
|
759
|
+
`Failed to open serial port: ${(e as Error).message}`,
|
|
760
|
+
'NetworkError',
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Guard against lifecycle races (e.g. forget() called while open() is
|
|
765
|
+
// still awaiting native setup). Do not revive a forgotten/changed state.
|
|
766
|
+
if (this.#state !== 'opening') {
|
|
767
|
+
try {
|
|
768
|
+
await this.#usb.close(this.#deviceId, this.#portNumber);
|
|
769
|
+
} catch {
|
|
770
|
+
// ignore
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (this.#state === 'forgotten' || this.#state === 'forgetting') {
|
|
774
|
+
this.#resetToClosedState('forgotten');
|
|
775
|
+
throw new DOMException(
|
|
776
|
+
'The port has been forgotten while opening.',
|
|
777
|
+
'InvalidStateError',
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
this.#resetToClosedState();
|
|
782
|
+
throw new DOMException(
|
|
783
|
+
'Failed to open serial port: state changed while opening.',
|
|
784
|
+
'NetworkError',
|
|
607
785
|
);
|
|
608
786
|
}
|
|
609
787
|
|
|
610
788
|
this.#state = 'opened'; // 9.3. Set this.[[state]] to "opened".
|
|
611
789
|
this.#bufferSize = bufferSize; // 9.4. Set this.[[bufferSize]] to options["bufferSize"].
|
|
612
|
-
|
|
613
|
-
await this.#usb.startReading(this.#deviceId, this.#portNumber);
|
|
790
|
+
this.#baudRate = options.baudRate;
|
|
614
791
|
|
|
615
792
|
// When the port becomes logically connected:
|
|
616
793
|
this.#connected = true; // 2. Set port.[[connected]] to true.
|
|
@@ -618,6 +795,13 @@ export class SerialPort extends EventTarget {
|
|
|
618
795
|
// device presence (attach/detach), dispatched by Serial from the native
|
|
619
796
|
// USB state events — not logical open()/close(). So we do not dispatch them
|
|
620
797
|
// here; that also avoids re-entrancy with auto-reconnect listeners.
|
|
798
|
+
|
|
799
|
+
// Materialise the readable eagerly so its data subscription is live the
|
|
800
|
+
// instant the native read pump (startReading, above) begins emitting. The
|
|
801
|
+
// device may transmit immediately on open; without an active subscription
|
|
802
|
+
// those first bytes would be delivered to no listener and silently lost in
|
|
803
|
+
// the window between open() resolving and the first port.readable access.
|
|
804
|
+
void this.readable;
|
|
621
805
|
}
|
|
622
806
|
|
|
623
807
|
/**
|
|
@@ -676,18 +860,10 @@ export class SerialPort extends EventTarget {
|
|
|
676
860
|
// ignore
|
|
677
861
|
}
|
|
678
862
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
this.#
|
|
683
|
-
|
|
684
|
-
this.#state = 'closed'; // 10.1.2. Set this.[[state]] to "closed".
|
|
685
|
-
this.#readFatal = false; // 10.1.3. Set this.[[readFatal]] and this.[[writeFatal]] to false.
|
|
686
|
-
this.#writeFatal = false; // 10.1.3. (continued)
|
|
687
|
-
this.#pendingClosePromise = null; // 10.1.4. Set this.[[pendingClosePromise]] to null.
|
|
688
|
-
|
|
689
|
-
// When the port is no longer logically connected:
|
|
690
|
-
this.#connected = false; // 2. Set port.[[connected]] to false.
|
|
863
|
+
// 10.1.2-10.1.4 and logical disconnect bookkeeping.
|
|
864
|
+
// If forget() was requested while close() was in flight, preserve
|
|
865
|
+
// forgotten semantics instead of reviving the port to "closed".
|
|
866
|
+
this.#resetToClosedState(this.#forgetRequested ? 'forgotten' : 'closed');
|
|
691
867
|
// (No port-level "disconnect" dispatch here — that event signals physical
|
|
692
868
|
// detach, fired by Serial from the native USB state events. See open().)
|
|
693
869
|
}
|
|
@@ -698,6 +874,14 @@ export class SerialPort extends EventTarget {
|
|
|
698
874
|
async forget(): Promise<void> {
|
|
699
875
|
// The forget() method steps are:
|
|
700
876
|
|
|
877
|
+
this.#forgetRequested = true;
|
|
878
|
+
|
|
879
|
+
// Forgetting an open port must not leave active streams/native resources
|
|
880
|
+
// behind. Close first so the instance becomes cleanly unusable afterwards.
|
|
881
|
+
if (this.#state === 'opened') {
|
|
882
|
+
await this.close();
|
|
883
|
+
}
|
|
884
|
+
|
|
701
885
|
// 2.1. Set this.[[state]] to "forgetting".
|
|
702
886
|
this.#state = 'forgetting';
|
|
703
887
|
// 2.2. Remove this from the sequence of serial ports on the system which
|
|
@@ -729,9 +913,6 @@ export class SerialPort extends EventTarget {
|
|
|
729
913
|
throw new TypeError('At least one signal must be specified.');
|
|
730
914
|
}
|
|
731
915
|
|
|
732
|
-
// Merge with current output signal state for partial updates
|
|
733
|
-
this.#outputSignals = {...this.#outputSignals, ...signals};
|
|
734
|
-
|
|
735
916
|
const promises: Promise<void>[] = [];
|
|
736
917
|
|
|
737
918
|
// 4.1. If signals["dataTerminalReady"] is present, invoke the operating
|
|
@@ -824,6 +1005,17 @@ export class SerialPort extends EventTarget {
|
|
|
824
1005
|
#handleClosingReadableStream(): void {
|
|
825
1006
|
// To handle closing the readable stream perform the following steps:
|
|
826
1007
|
|
|
1008
|
+
// Drop the native data/error subscription tied to this readable. Without
|
|
1009
|
+
// this a later readable (after cancel() + re-acquire) would coexist with the
|
|
1010
|
+
// old subscription, which then enqueues into the cancelled controller and
|
|
1011
|
+
// throws. close()/handleDeviceLost() also clear these; doing it here covers
|
|
1012
|
+
// a standalone readable.cancel().
|
|
1013
|
+
this.#dataSubscription?.remove();
|
|
1014
|
+
this.#errorSubscription?.remove();
|
|
1015
|
+
this.#dataSubscription = null;
|
|
1016
|
+
this.#errorSubscription = null;
|
|
1017
|
+
this.#readableController = null;
|
|
1018
|
+
|
|
827
1019
|
// 1. Set this.[[readable]] to null.
|
|
828
1020
|
this.#readable = null;
|
|
829
1021
|
|
|
@@ -840,6 +1032,8 @@ export class SerialPort extends EventTarget {
|
|
|
840
1032
|
#handleClosingWritableStream(): void {
|
|
841
1033
|
// To handle closing the writable stream perform the following steps:
|
|
842
1034
|
|
|
1035
|
+
this.#writableController = null;
|
|
1036
|
+
|
|
843
1037
|
// 1. Set this.[[writable]] to null.
|
|
844
1038
|
this.#writable = null;
|
|
845
1039
|
|
|
@@ -860,9 +1054,23 @@ export class Serial extends EventTarget {
|
|
|
860
1054
|
disconnect: null as ((event: Event) => void) | null,
|
|
861
1055
|
};
|
|
862
1056
|
|
|
863
|
-
#usb:
|
|
1057
|
+
#usb: SerialTransport | null = null;
|
|
864
1058
|
#knownPorts: Map<string, SerialPort> = new Map();
|
|
865
1059
|
#initialized = false;
|
|
1060
|
+
#injected: SerialTransport | null;
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* @param transport Optional transport override. When provided, this `Serial`
|
|
1064
|
+
* talks to it instead of the global native module — the transport override used by tests
|
|
1065
|
+
* and the virtual serial device harness (`new Serial(new InMemorySerialTransport())`).
|
|
1066
|
+
* Omit it for the normal native-backed instance; the singleton `serial`
|
|
1067
|
+
* export is created this way and can still be redirected globally via
|
|
1068
|
+
* `setUsbSerial()`.
|
|
1069
|
+
*/
|
|
1070
|
+
constructor(transport?: SerialTransport) {
|
|
1071
|
+
super();
|
|
1072
|
+
this.#injected = transport ?? null;
|
|
1073
|
+
}
|
|
866
1074
|
|
|
867
1075
|
/**
|
|
868
1076
|
* Subscribing to "connect"/"disconnect" must wire up the native USB state
|
|
@@ -880,42 +1088,49 @@ export class Serial extends EventTarget {
|
|
|
880
1088
|
* Deferring native access out of the module-import path avoids touching the
|
|
881
1089
|
* TurboModule before the runtime is ready.
|
|
882
1090
|
*/
|
|
883
|
-
#ensureInit():
|
|
1091
|
+
#ensureInit(): SerialTransport | null {
|
|
884
1092
|
if (this.#initialized) return this.#usb;
|
|
885
1093
|
this.#initialized = true;
|
|
886
1094
|
try {
|
|
887
|
-
this.#usb = getUsbSerial();
|
|
1095
|
+
this.#usb = this.#injected ?? getUsbSerial();
|
|
888
1096
|
|
|
889
1097
|
// A USB device was attached. Android assigns a NEW deviceId on every
|
|
890
1098
|
// attach, so match previously-known ports of the same physical device by
|
|
891
1099
|
// VID/PID, update their deviceId, re-key them, and fire "connect" on the
|
|
892
1100
|
// same SerialPort instance (W3C spec model: the port is reused).
|
|
893
1101
|
this.#usb.onConnect((event: ConnectEvent) => {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
internals.
|
|
900
|
-
internals.
|
|
901
|
-
|
|
902
|
-
internals.
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1102
|
+
// Native connect events expose VID/PID but not a stable unique
|
|
1103
|
+
// identifier. To avoid mis-associating identical devices, remap only
|
|
1104
|
+
// when there is exactly one disconnected candidate.
|
|
1105
|
+
const candidates = Array.from(this.#knownPorts.entries()).filter(
|
|
1106
|
+
([, port]) => {
|
|
1107
|
+
const internals = portInternals.get(port)!;
|
|
1108
|
+
if (internals.getVid() !== event.usbVendorId) return false;
|
|
1109
|
+
if (internals.getPid() !== event.usbProductId) return false;
|
|
1110
|
+
if (internals.isForgotten()) return false;
|
|
1111
|
+
return !port.connected;
|
|
1112
|
+
},
|
|
1113
|
+
);
|
|
1114
|
+
|
|
1115
|
+
if (candidates.length === 1) {
|
|
1116
|
+
const [key, port] = candidates[0];
|
|
1117
|
+
const internals = portInternals.get(port)!;
|
|
1118
|
+
internals.setDeviceId(event.deviceId);
|
|
1119
|
+
const newKey = this.#portKey(
|
|
1120
|
+
event.deviceId,
|
|
1121
|
+
internals.getPortNumber(),
|
|
1122
|
+
);
|
|
1123
|
+
this.#knownPorts.delete(key);
|
|
1124
|
+
this.#knownPorts.set(newKey, port);
|
|
1125
|
+
// Dispatched on the port; it bubbles to this Serial (event.target
|
|
1126
|
+
// stays the port, per spec — see setEventParent below).
|
|
1127
|
+
port.dispatchEvent(new Event('connect'));
|
|
1128
|
+
return;
|
|
914
1129
|
}
|
|
1130
|
+
|
|
1131
|
+
// No known port matched (or matching is ambiguous): fire a Serial-level
|
|
1132
|
+
// "connect" so listeners refresh; getPorts() surfaces the new port.
|
|
915
1133
|
this.dispatchEvent(new Event('connect'));
|
|
916
|
-
// If we matched no known port this is a brand-new device; getPorts()
|
|
917
|
-
// will surface it on the next refresh.
|
|
918
|
-
void matched;
|
|
919
1134
|
});
|
|
920
1135
|
|
|
921
1136
|
// A USB device was detached. Reset the matching port(s) to a closed,
|
|
@@ -923,13 +1138,22 @@ export class Serial extends EventTarget {
|
|
|
923
1138
|
// SerialPort instance so a re-attach can reuse it. handleDeviceLost()
|
|
924
1139
|
// dispatches "disconnect" on the port.
|
|
925
1140
|
this.#usb.onDisconnect((event: ConnectEvent) => {
|
|
1141
|
+
const wasLost = (event as ConnectEvent & {lost?: boolean}).lost ?? true;
|
|
926
1142
|
const prefix = `${event.deviceId}:`;
|
|
1143
|
+
let matched = false;
|
|
927
1144
|
for (const [key, port] of [...this.#knownPorts.entries()]) {
|
|
928
1145
|
if (key.startsWith(prefix)) {
|
|
929
|
-
|
|
1146
|
+
// Physical loss tears down the stream immediately; a clean detach
|
|
1147
|
+
// lets any already-buffered bytes drain before EOF.
|
|
1148
|
+
const internals = portInternals.get(port);
|
|
1149
|
+
if (wasLost) internals?.handleDeviceLost();
|
|
1150
|
+
else internals?.handleDeviceDetached();
|
|
1151
|
+
matched = true;
|
|
930
1152
|
}
|
|
931
1153
|
}
|
|
932
|
-
|
|
1154
|
+
// No known port matched (e.g. a device never opened): fire a
|
|
1155
|
+
// Serial-level "disconnect" so listeners can still refresh.
|
|
1156
|
+
if (!matched) this.dispatchEvent(new Event('disconnect'));
|
|
933
1157
|
});
|
|
934
1158
|
} catch {
|
|
935
1159
|
this.#usb = null;
|
|
@@ -1007,11 +1231,18 @@ export class Serial extends EventTarget {
|
|
|
1007
1231
|
} of portIds) {
|
|
1008
1232
|
if (!hasPermission) continue;
|
|
1009
1233
|
const key = this.#portKey(deviceId, portNumber);
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1234
|
+
const known = this.#knownPorts.get(key);
|
|
1235
|
+
if (!known || portInternals.get(known)?.isForgotten()) {
|
|
1236
|
+
const port = new SerialPort(
|
|
1237
|
+
usb,
|
|
1238
|
+
deviceId,
|
|
1239
|
+
portNumber,
|
|
1240
|
+
usbVendorId,
|
|
1241
|
+
usbProductId,
|
|
1014
1242
|
);
|
|
1243
|
+
// The port's connect/disconnect events bubble to this Serial.
|
|
1244
|
+
setEventParent(port, this);
|
|
1245
|
+
this.#knownPorts.set(key, port);
|
|
1015
1246
|
}
|
|
1016
1247
|
ports.push(this.#knownPorts.get(key)!);
|
|
1017
1248
|
}
|
|
@@ -1036,6 +1267,12 @@ export class Serial extends EventTarget {
|
|
|
1036
1267
|
);
|
|
1037
1268
|
}
|
|
1038
1269
|
|
|
1270
|
+
if ((options.allowedBluetoothServiceClassIds?.length ?? 0) > 0) {
|
|
1271
|
+
throw new TypeError(
|
|
1272
|
+
'allowedBluetoothServiceClassIds is not supported in Android USB mode.',
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1039
1276
|
// 4. If options["filters"] is present, then for each filter in
|
|
1040
1277
|
// options["filters"] run the following steps:
|
|
1041
1278
|
if (options.filters) {
|
|
@@ -1083,17 +1320,18 @@ export class Serial extends EventTarget {
|
|
|
1083
1320
|
|
|
1084
1321
|
// 5.6. Let port be a SerialPort representing the port chosen by the user.
|
|
1085
1322
|
const key = `${portId.deviceId}:${portId.portNumber}`;
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
portId.usbProductId,
|
|
1095
|
-
),
|
|
1323
|
+
const known = this.#knownPorts.get(key);
|
|
1324
|
+
if (!known || portInternals.get(known)?.isForgotten()) {
|
|
1325
|
+
const port = new SerialPort(
|
|
1326
|
+
usb,
|
|
1327
|
+
portId.deviceId,
|
|
1328
|
+
portId.portNumber,
|
|
1329
|
+
portId.usbVendorId,
|
|
1330
|
+
portId.usbProductId,
|
|
1096
1331
|
);
|
|
1332
|
+
// The port's connect/disconnect events bubble to this Serial.
|
|
1333
|
+
setEventParent(port, this);
|
|
1334
|
+
this.#knownPorts.set(key, port);
|
|
1097
1335
|
}
|
|
1098
1336
|
|
|
1099
1337
|
// 5.7. Resolve promise with port.
|