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.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/android/build.gradle +62 -0
- package/android/gradle.properties +6 -0
- package/android/src/main/AndroidManifest.xml +21 -0
- package/android/src/main/java/dev/webserialapi/NativeUsbSerialModule.java +704 -0
- package/android/src/main/java/dev/webserialapi/NativeUsbSerialPackage.java +46 -0
- package/android/src/main/java/dev/webserialapi/PortPickerActivity.java +235 -0
- package/android/src/main/java/dev/webserialapi/UsbDetachReceiver.java +37 -0
- package/android/src/main/res/xml/device_filter.xml +13 -0
- package/babel.config.js +3 -0
- package/biome.json +35 -0
- package/example/.watchmanconfig +1 -0
- package/example/App.tsx +71 -0
- package/example/__tests__/App.test.tsx +16 -0
- package/example/android/app/build.gradle +120 -0
- package/example/android/app/debug.keystore +0 -0
- package/example/android/app/proguard-rules.pro +10 -0
- package/example/android/app/src/debug/AndroidManifest.xml +9 -0
- package/example/android/app/src/main/AndroidManifest.xml +38 -0
- package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +22 -0
- package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +41 -0
- package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- 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 +3 -0
- package/example/android/app/src/main/res/values/styles.xml +9 -0
- package/example/android/build.gradle +22 -0
- package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/example/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/example/android/gradle.properties +47 -0
- package/example/android/gradlew +252 -0
- package/example/android/gradlew.bat +94 -0
- package/example/android/settings.gradle +6 -0
- package/example/app.json +4 -0
- package/example/babel.config.js +21 -0
- package/example/biome.json +47 -0
- package/example/deploy.sh +11 -0
- package/example/index.html +26 -0
- package/example/index.js +9 -0
- package/example/index.web.js +8 -0
- package/example/jest.config.js +12 -0
- package/example/metro.config.js +58 -0
- package/example/package-lock.json +14510 -0
- package/example/package.json +48 -0
- package/example/react-native.config.js +17 -0
- package/example/src/components/AppBar.tsx +73 -0
- package/example/src/components/Menu.tsx +90 -0
- package/example/src/components/SingleChoiceDialog.tsx +120 -0
- package/example/src/screens/ConnectScreen.tsx +195 -0
- package/example/src/screens/DevicesScreen.tsx +141 -0
- package/example/src/screens/TerminalScreen.tsx +564 -0
- package/example/src/settings.ts +43 -0
- package/example/src/theme.ts +19 -0
- package/example/src/util/TextUtil.ts +129 -0
- package/example/tsconfig.json +10 -0
- package/example/vite.config.mjs +55 -0
- package/lib/commonjs/NativeUsbSerial.js +11 -0
- package/lib/commonjs/NativeUsbSerial.js.map +1 -0
- package/lib/commonjs/NativeUsbSerial.web.js +12 -0
- package/lib/commonjs/NativeUsbSerial.web.js.map +1 -0
- package/lib/commonjs/UsbSerial.js +149 -0
- package/lib/commonjs/UsbSerial.js.map +1 -0
- package/lib/commonjs/WebSerial.js +852 -0
- package/lib/commonjs/WebSerial.js.map +1 -0
- package/lib/commonjs/index.js +44 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/serial.android.js +13 -0
- package/lib/commonjs/serial.android.js.map +1 -0
- package/lib/commonjs/serial.js +13 -0
- package/lib/commonjs/serial.js.map +1 -0
- package/lib/commonjs/serial.web.js +11 -0
- package/lib/commonjs/serial.web.js.map +1 -0
- package/lib/typescript/src/NativeUsbSerial.d.ts +51 -0
- package/lib/typescript/src/NativeUsbSerial.d.ts.map +1 -0
- package/lib/typescript/src/NativeUsbSerial.web.d.ts +3 -0
- package/lib/typescript/src/NativeUsbSerial.web.d.ts.map +1 -0
- package/lib/typescript/src/UsbSerial.d.ts +97 -0
- package/lib/typescript/src/UsbSerial.d.ts.map +1 -0
- package/lib/typescript/src/WebSerial.d.ts +236 -0
- package/lib/typescript/src/WebSerial.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +7 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/serial.android.d.ts +2 -0
- package/lib/typescript/src/serial.android.d.ts.map +1 -0
- package/lib/typescript/src/serial.d.ts +2 -0
- package/lib/typescript/src/serial.d.ts.map +1 -0
- package/lib/typescript/src/serial.web.d.ts +4 -0
- package/lib/typescript/src/serial.web.d.ts.map +1 -0
- package/package.json +78 -0
- package/react-native.config.js +9 -0
- package/scripts/deploy-release.sh +127 -0
- package/src/NativeUsbSerial.ts +124 -0
- package/src/NativeUsbSerial.web.ts +5 -0
- package/src/UsbSerial.ts +305 -0
- package/src/WebSerial.ts +1084 -0
- package/src/index.ts +23 -0
- package/src/lib/dom-exception.ts +161 -0
- package/src/lib/event-target.ts +170 -0
- package/src/lib/promise.ts +19 -0
- package/src/serial.android.ts +1 -0
- package/src/serial.ts +1 -0
- package/src/serial.web.ts +6 -0
- package/tsconfig.build.json +7 -0
- 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
|