react-native-web-serial-api 0.0.2 → 0.1.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 +23 -0
- package/TESTING.md +301 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/dev/webserialapi/NativeUsbSerialModule.java +7 -1
- package/lib/commonjs/UsbSerial.js +58 -26
- package/lib/commonjs/UsbSerial.js.map +1 -1
- package/lib/commonjs/WebSerial.js +169 -57
- package/lib/commonjs/WebSerial.js.map +1 -1
- package/lib/commonjs/index.js +13 -1
- 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 +138 -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/testing/index.js +70 -0
- package/lib/commonjs/testing/index.js.map +1 -0
- package/lib/commonjs/testing/install.js +54 -0
- package/lib/commonjs/testing/install.js.map +1 -0
- package/lib/commonjs/testing/serial-device.js +164 -0
- package/lib/commonjs/testing/serial-device.js.map +1 -0
- package/lib/commonjs/testing/virtual-serial.js +615 -0
- package/lib/commonjs/testing/virtual-serial.js.map +1 -0
- package/lib/commonjs/transport.js +61 -0
- package/lib/commonjs/transport.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 +11 -2
- package/lib/typescript/src/WebSerial.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -0
- 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 +53 -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/testing/index.d.ts +23 -0
- package/lib/typescript/src/testing/index.d.ts.map +1 -0
- package/lib/typescript/src/testing/install.d.ts +25 -0
- package/lib/typescript/src/testing/install.d.ts.map +1 -0
- package/lib/typescript/src/testing/serial-device.d.ts +127 -0
- package/lib/typescript/src/testing/serial-device.d.ts.map +1 -0
- package/lib/typescript/src/testing/virtual-serial.d.ts +205 -0
- package/lib/typescript/src/testing/virtual-serial.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/package.json +38 -2
- package/src/UsbSerial.ts +65 -90
- package/src/WebSerial.ts +227 -88
- package/src/index.ts +2 -7
- package/src/lib/dom-exception.ts +129 -60
- package/src/lib/event-target.ts +46 -21
- package/src/lib/promise.ts +7 -7
- package/src/testing/index.ts +42 -0
- package/src/testing/install.ts +65 -0
- package/src/testing/serial-device.ts +193 -0
- package/src/testing/virtual-serial.ts +801 -0
- package/src/transport.ts +200 -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 -248
- package/example/src/screens/TerminalScreen.tsx +0 -564
- 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/README.md
CHANGED
|
@@ -70,6 +70,9 @@ async function run() {
|
|
|
70
70
|
}
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
In Android USB mode, `allowedBluetoothServiceClassIds` is not supported by
|
|
74
|
+
`requestPort()` and will throw a `TypeError` if provided.
|
|
75
|
+
|
|
73
76
|
### Control & status signals
|
|
74
77
|
|
|
75
78
|
```ts
|
|
@@ -192,6 +195,26 @@ npm run web # dev server at http://localhost:5173
|
|
|
192
195
|
|
|
193
196
|
The JavaScript layer (`src/`) implements the Web Serial API on top of a thin TurboModule (`NativeUsbSerial`) whose native Android implementation (`android/src/main/java/dev/webserialapi/`) wraps `usb-serial-for-android`. Reads/writes are bridged to `ReadableStream`/`WritableStream` via [`web-streams-polyfill`](https://github.com/MattiasBuelens/web-streams-polyfill).
|
|
194
197
|
|
|
198
|
+
## Testing
|
|
199
|
+
|
|
200
|
+
The hardware layer sits behind a single injectable `SerialTransport` interface, so you can test against a pure-JS **virtual serial device** instead of real USB hardware — in Jest *and* live on a device/emulator/browser. The same conformance suite runs in both places, and the example app has a **Self Test** screen plus a **Virtual device (demo)** mode.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import {Serial} from 'react-native-web-serial-api';
|
|
204
|
+
import {VirtualSerialTransport, EchoDevice} from 'react-native-web-serial-api/testing';
|
|
205
|
+
|
|
206
|
+
const transport = new VirtualSerialTransport();
|
|
207
|
+
transport.addDevice(new EchoDevice(), {hasPermission: true});
|
|
208
|
+
const serial = new Serial(transport); // no native module, no hardware
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```sh
|
|
212
|
+
npm test # unit + WPT conformance suites
|
|
213
|
+
npm run test:coverage # coverage report (HTML in coverage/lcov-report)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
See **[TESTING.md](TESTING.md)** for the full guide (authoring a `SerialDevice`, the conformance/WPT suites, on-device E2E, and coverage).
|
|
217
|
+
|
|
195
218
|
## License
|
|
196
219
|
|
|
197
220
|
MIT © Aras Abbasi
|
package/TESTING.md
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
This library is designed to be testable **without USB hardware** — both in a
|
|
4
|
+
test runner (Jest, Vitest, …) and live on a device, an emulator, or in the
|
|
5
|
+
browser. One idea makes that possible: every path to the hardware goes through a
|
|
6
|
+
single interface, [`SerialTransport`](src/transport.ts), and you can swap in a
|
|
7
|
+
pure-JavaScript implementation.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Serial / SerialPort ──depends on──► SerialTransport
|
|
11
|
+
▲ ▲
|
|
12
|
+
UsbSerialModule │ │ VirtualSerialTransport
|
|
13
|
+
(real, native) (in-memory, no native deps)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Because `VirtualSerialTransport` has **no `react-native` dependency**, the same
|
|
17
|
+
code runs under Node/Jest, on a real Android device, and on the web.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## The transport seam
|
|
22
|
+
|
|
23
|
+
`Serial` resolves its transport in three ways, in order of precedence:
|
|
24
|
+
|
|
25
|
+
1. **Constructor injection** — `new Serial(transport)` (best for isolated tests).
|
|
26
|
+
2. **Global override** — `setUsbSerial(transport)` affects the singleton `serial`
|
|
27
|
+
and any `new Serial()` created without an explicit transport.
|
|
28
|
+
3. **Default** — the real `UsbSerialModule` backed by the native TurboModule.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {Serial, setUsbSerial, resetUsbSerial} from 'react-native-web-serial-api';
|
|
32
|
+
import {VirtualSerialTransport} from 'react-native-web-serial-api/testing';
|
|
33
|
+
|
|
34
|
+
// (1) explicit — recommended in unit tests
|
|
35
|
+
const transport = new VirtualSerialTransport();
|
|
36
|
+
const serial = new Serial(transport);
|
|
37
|
+
|
|
38
|
+
// (2) global — redirects the singleton `serial` too. Call it before the first
|
|
39
|
+
// getPorts()/requestPort()/addEventListener(). resetUsbSerial() restores default.
|
|
40
|
+
setUsbSerial(transport);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The testing utilities live in the **`react-native-web-serial-api/testing`**
|
|
44
|
+
subpath, so they stay out of your production bundle.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## VirtualSerialTransport
|
|
49
|
+
|
|
50
|
+
An in-memory transport backing one or more simulated devices. You register a
|
|
51
|
+
[`SerialDevice`](#simulating-a-whole-device-serialdevice) and the transport
|
|
52
|
+
reads its USB identity; `options` carries the transport-side knobs.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import {VirtualSerialTransport, EchoDevice} from 'react-native-web-serial-api/testing';
|
|
56
|
+
|
|
57
|
+
const transport = new VirtualSerialTransport({
|
|
58
|
+
latencyMs: 0, // 0 = resolve on a microtask (deterministic for Jest)
|
|
59
|
+
autoGrantPermission: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const device = transport.addDevice(
|
|
63
|
+
new EchoDevice({usbVendorId: 0x0403, usbProductId: 0x6001, serialNumber: 'DEMO-1'}),
|
|
64
|
+
{
|
|
65
|
+
hasPermission: true, // false → hidden from getPorts() until requestPort()
|
|
66
|
+
loopbackSignals: true, // DTR→DSR+DCD, RTS→CTS, so getSignals reflects setSignals
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`EchoDevice` (loopback) and `SilentDevice` (accepts writes, sends nothing) are
|
|
72
|
+
built in; for anything richer, write a `SerialDevice` (next section).
|
|
73
|
+
|
|
74
|
+
### Driving a device from a test/UI
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
device.push([0x01, 0x02]); // inbound bytes as if the device sent them
|
|
78
|
+
device.emitError('cable fault'); // raise a read error on the readable stream
|
|
79
|
+
device.attach(); // (re)attach → fires "connect" (new deviceId)
|
|
80
|
+
device.detach(); // detach → fires "disconnect"
|
|
81
|
+
device.loseDevice(); // unplug while open: errors the stream, disconnects
|
|
82
|
+
device.failNext('open'); // make the next open()/write()/… reject once
|
|
83
|
+
device.written; // number[][] — everything the host wrote
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`transport.selectNextPort(device)` / `transport.rejectNextPortPicker()` script
|
|
87
|
+
what the next `requestPort()` returns.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Simulating a whole device (`SerialDevice`)
|
|
92
|
+
|
|
93
|
+
To model a *whole* peripheral — a stateful protocol that greets on open, streams
|
|
94
|
+
over time, reacts to control signals, and raises typed errors — extend
|
|
95
|
+
**`SerialDevice`** and override the lifecycle hooks:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import {SerialDevice, VirtualSerialTransport} from 'react-native-web-serial-api/testing';
|
|
99
|
+
import {Serial} from 'react-native-web-serial-api';
|
|
100
|
+
|
|
101
|
+
class Thermometer extends SerialDevice {
|
|
102
|
+
usbVendorId = 0x0403;
|
|
103
|
+
usbProductId = 0x6001;
|
|
104
|
+
#timer?: ReturnType<typeof setInterval>;
|
|
105
|
+
|
|
106
|
+
onOpen() { // host opened the port
|
|
107
|
+
this.send('READY\r\n');
|
|
108
|
+
this.#timer = setInterval(() => this.send(`temp=${20 + Math.random()}C\r\n`), 1000);
|
|
109
|
+
}
|
|
110
|
+
onData(bytes: Uint8Array) { // host wrote to the device
|
|
111
|
+
if (String.fromCharCode(...bytes).trim() === 'ID?') this.send('ACME-TEMP\r\n');
|
|
112
|
+
}
|
|
113
|
+
onHostSignals(s) { /* DTR/RTS/break changed */ }
|
|
114
|
+
onClose() { clearInterval(this.#timer); }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const transport = new VirtualSerialTransport();
|
|
118
|
+
transport.addDevice(new Thermometer(), {hasPermission: true});
|
|
119
|
+
const serial = new Serial(transport);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Hooks: `onOpen(options)`, `onData(data)`, `onHostSignals(signals)`, `onClose()`
|
|
123
|
+
(all optional, may be async). Helpers: `this.send(bytes|string)`,
|
|
124
|
+
`this.raiseError(message, name?)` (e.g. `'BreakError'`),
|
|
125
|
+
`this.setSignals({dataCarrierDetect, clearToSend, ringIndicator, dataSetReady})`,
|
|
126
|
+
`this.openOptions`. **`EchoDevice`** (loopback), **`SilentDevice`**, and
|
|
127
|
+
**`LineDevice`** (buffers to `\n`, calls `onLine(line)`) are built in.
|
|
128
|
+
`addDevice(device, options?)` reads the device's
|
|
129
|
+
`usbVendorId`/`usbProductId`/`serialNumber`; `options` carries the transport
|
|
130
|
+
knobs (`hasPermission`, `portNumber`, …).
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Writing unit tests (Jest)
|
|
135
|
+
|
|
136
|
+
No native mocks required — inject the transport and exercise the real
|
|
137
|
+
`Serial`/`SerialPort` logic and streams:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import {Serial} from 'react-native-web-serial-api';
|
|
141
|
+
import {VirtualSerialTransport} from 'react-native-web-serial-api/testing';
|
|
142
|
+
|
|
143
|
+
it('echoes bytes through the streams', async () => {
|
|
144
|
+
const transport = new VirtualSerialTransport();
|
|
145
|
+
transport.addDevice({usbVendorId: 0x0403, usbProductId: 0x6001, hasPermission: true});
|
|
146
|
+
const serial = new Serial(transport);
|
|
147
|
+
|
|
148
|
+
const [port] = await serial.getPorts();
|
|
149
|
+
await port.open({baudRate: 115200});
|
|
150
|
+
|
|
151
|
+
const reader = port.readable!.getReader();
|
|
152
|
+
const writer = port.writable!.getWriter();
|
|
153
|
+
await writer.write(Uint8Array.from([1, 2, 3]));
|
|
154
|
+
expect(Array.from((await reader.read()).value!)).toEqual([1, 2, 3]);
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
See [`src/__tests__/`](src/__tests__) for the library's own suites. Note: the
|
|
159
|
+
seam imports `react-native`, so a Jest setup needs the RN preset — see this
|
|
160
|
+
repo's [`jest.config.js`](jest.config.js).
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## The conformance suite (one suite, two runtimes)
|
|
165
|
+
|
|
166
|
+
[`src/__tests__/conformance-suite.ts`](src/__tests__/conformance-suite.ts) exports
|
|
167
|
+
`serialConformanceTests` — self-contained cases (each builds its own
|
|
168
|
+
`Serial` + `VirtualSerialTransport`) with built-in assertions and **no
|
|
169
|
+
test-runner dependency**. It is **test-only code**: it is excluded from the
|
|
170
|
+
build and from the published npm package (it imports the shipped testing
|
|
171
|
+
utilities, not the other way round), so it adds nothing to consumers' bundles.
|
|
172
|
+
The very same array runs:
|
|
173
|
+
|
|
174
|
+
- **under Jest** — [`src/__tests__/conformance.test.ts`](src/__tests__/conformance.test.ts):
|
|
175
|
+
```ts
|
|
176
|
+
import {serialConformanceTests} from './conformance-suite';
|
|
177
|
+
for (const t of serialConformanceTests) it(t.name, () => t.run());
|
|
178
|
+
```
|
|
179
|
+
- **on a device** — via `runSerialConformance()`, which returns a structured
|
|
180
|
+
pass/fail result per test (it never throws). The example app's Self-Test
|
|
181
|
+
screen imports it directly from the test folder (a dev-only import — see
|
|
182
|
+
[`example/src/screens/SelfTestScreen.tsx`](example/src/screens/SelfTestScreen.tsx)).
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import {runSerialConformance, runRealDeviceSmokeTest} from './conformance-suite';
|
|
186
|
+
|
|
187
|
+
const results = await runSerialConformance(); // virtual, full suite
|
|
188
|
+
const smoke = await runRealDeviceSmokeTest(serial); // small, safe, real device
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## WPT spec compliance
|
|
194
|
+
|
|
195
|
+
The `WPT …` cases **at the end of the conformance suite**
|
|
196
|
+
([`src/__tests__/conformance-suite.ts`](src/__tests__/conformance-suite.ts)) are ports of the
|
|
197
|
+
**official Web Platform Tests** for the Web Serial API (vendored in
|
|
198
|
+
`tmp/serial/`), so the spec's own test logic runs against our polyfill via the
|
|
199
|
+
virtual loopback device — proof of W3C compliance, not just our own assertions.
|
|
200
|
+
They live in the conformance suite rather than a separate Jest-only file, so the
|
|
201
|
+
same spec tests run **under Jest *and* on-device** (Self Test screen), not just
|
|
202
|
+
in a browser. They cover loopback read/write (small + large, repeated),
|
|
203
|
+
`readable.cancel()` discarding buffered data, hardware flow-control
|
|
204
|
+
back-pressure, typed `BreakError`/`BufferOverrunError`, large PRNG-stream
|
|
205
|
+
integrity, disconnect during a pending read/write, and the interface (IDL) shape.
|
|
206
|
+
|
|
207
|
+
Porting the spec faithfully originally surfaced five real gaps in the polyfill
|
|
208
|
+
(typed `BreakError`/`BufferOverrunError` on the readable; disconnect rejecting
|
|
209
|
+
the pending read/write with `NetworkError`; the `disconnect` event `target`; and
|
|
210
|
+
a leaked subscription on `readable.cancel()`). All are now **fixed** in
|
|
211
|
+
`WebSerial.ts`, and these cases pass as regression guards. The browser-security
|
|
212
|
+
WPT files (permission prompts, secure-context, `requestPort()` user-gesture
|
|
213
|
+
gating) don't apply to a React Native polyfill and are intentionally not ported;
|
|
214
|
+
the PRNG-stream length is scaled down from the upstream 10 MB so the on-device
|
|
215
|
+
Self Test stays fast.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## On-device testing (example app)
|
|
220
|
+
|
|
221
|
+
The [example app](example) ships two ways to test on real hardware (or none):
|
|
222
|
+
|
|
223
|
+
- **Self Test screen** (overflow menu → *Self test…*) runs the conformance suite
|
|
224
|
+
in-app and shows pass/fail, plus a *Run on connected device* smoke test. This
|
|
225
|
+
works on Android, web, and in an emulator with **no device attached**.
|
|
226
|
+
- **Virtual device (demo)** toggle (overflow menu) injects a
|
|
227
|
+
`VirtualSerialTransport` (an FTDI `EchoDevice` + a CP210x `SensorDevice`, both
|
|
228
|
+
authored as `SerialDevice`s — see [example/src/devices/](example/src/devices))
|
|
229
|
+
so the whole Devices → Connect → Terminal flow runs hardware-free.
|
|
230
|
+
|
|
231
|
+
> **Platform note:** demo mode redirects the app's live serial, which only works
|
|
232
|
+
> on Android (where `serial` is this library's polyfill). On web `serial` is the
|
|
233
|
+
> browser's native `navigator.serial`, which the library cannot inject into — but
|
|
234
|
+
> the Self-Test screen still works everywhere because it builds its own
|
|
235
|
+
> `new Serial(virtualTransport)`.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## E2E in the emulator
|
|
240
|
+
|
|
241
|
+
The same `SerialDevice` mocks let you run **UI E2E tests** against an app with no
|
|
242
|
+
USB hardware. Install the mock once at startup behind your own flag:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
// index.js — debug/E2E build only
|
|
246
|
+
import {installSerialMock, EchoDevice} from 'react-native-web-serial-api/testing';
|
|
247
|
+
import {MyThermometer} from './devices/MyThermometer';
|
|
248
|
+
|
|
249
|
+
installSerialMock({
|
|
250
|
+
enabled: process.env.RNWS_SERIAL_MOCK === '1', // your own gate
|
|
251
|
+
devices: [new EchoDevice(), new MyThermometer()],
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`installSerialMock` builds a `VirtualSerialTransport` and calls `setUsbSerial`, so
|
|
256
|
+
`navigator.serial` now talks to your simulated devices.
|
|
257
|
+
|
|
258
|
+
The example app ships a **Maestro** suite ([example/.maestro/](example/.maestro))
|
|
259
|
+
that drives the real UI against the in-app mock (via the demo toggle):
|
|
260
|
+
|
|
261
|
+
```sh
|
|
262
|
+
# start an Android emulator, then:
|
|
263
|
+
npm --prefix example run android # build + install the debug app (Metro)
|
|
264
|
+
npm --prefix example run e2e # maestro test .maestro
|
|
265
|
+
|
|
266
|
+
# from the repo root: run host Jest first, then emulator E2E
|
|
267
|
+
npm run test:host+emulator
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
- `demo-echo.yaml` — enable demo mode → connect to the FTDI echo device → send a
|
|
271
|
+
line in the Terminal → assert it round-trips.
|
|
272
|
+
- `self-test.yaml` — open *Self test* → run the conformance suite → assert green.
|
|
273
|
+
|
|
274
|
+
This isn't wired into GitHub CI (it needs an emulator); run it locally or in an
|
|
275
|
+
emulator-equipped job. [Detox](https://wix.github.io/Detox/) works the same way —
|
|
276
|
+
the mock is what makes either runner hardware-free.
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Running the tests
|
|
281
|
+
|
|
282
|
+
```sh
|
|
283
|
+
npm test # library unit + conformance suite
|
|
284
|
+
npm run test:watch # watch mode
|
|
285
|
+
npm run typecheck
|
|
286
|
+
npm run lint
|
|
287
|
+
|
|
288
|
+
npm test --prefix example # example app tests
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
CI runs all of the above plus a build check on every push/PR — see
|
|
292
|
+
[`.github/workflows/ci.yml`](.github/workflows/ci.yml).
|
|
293
|
+
|
|
294
|
+
## Coverage
|
|
295
|
+
|
|
296
|
+
```sh
|
|
297
|
+
npm run test:coverage
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Prints a per-file table + summary and writes a browsable HTML report to
|
|
301
|
+
`coverage/lcov-report/index.html` (config in [`jest.config.js`](jest.config.js)).
|
package/android/build.gradle
CHANGED
|
@@ -31,7 +31,7 @@ def getExtOrIntegerDefault(name) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
android {
|
|
34
|
-
namespace "dev.webserialapi"
|
|
34
|
+
namespace = "dev.webserialapi"
|
|
35
35
|
|
|
36
36
|
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
37
37
|
|
|
@@ -50,7 +50,7 @@ repositories {
|
|
|
50
50
|
mavenCentral()
|
|
51
51
|
google()
|
|
52
52
|
// usb-serial-for-android is published on JitPack
|
|
53
|
-
maven { url "https://jitpack.io" }
|
|
53
|
+
maven { url = "https://jitpack.io" }
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
dependencies {
|
|
@@ -657,11 +657,17 @@ public class NativeUsbSerialModule extends NativeUsbSerialSpec {
|
|
|
657
657
|
Intent intent = new Intent(ACTION_USB_PERMISSION);
|
|
658
658
|
intent.putExtra(EXTRA_REQUEST_CODE, requestCode);
|
|
659
659
|
|
|
660
|
+
intent.setPackage(getReactApplicationContext().getPackageName());
|
|
661
|
+
|
|
662
|
+
int flags = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S
|
|
663
|
+
? PendingIntent.FLAG_MUTABLE
|
|
664
|
+
: 0;
|
|
665
|
+
|
|
660
666
|
PendingIntent permissionIntent = PendingIntent.getBroadcast(
|
|
661
667
|
getReactApplicationContext(),
|
|
662
668
|
requestCode,
|
|
663
669
|
intent,
|
|
664
|
-
|
|
670
|
+
flags
|
|
665
671
|
);
|
|
666
672
|
usbManager.requestPermission(driver.getDevice(), permissionIntent);
|
|
667
673
|
} catch (Exception e) {
|
|
@@ -3,34 +3,37 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
|
|
6
|
+
Object.defineProperty(exports, "DataBits", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _transport.DataBits;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "Parity", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _transport.Parity;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "StopBits", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _transport.StopBits;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
exports.UsbSerialModule = void 0;
|
|
7
25
|
exports.getUsbSerial = getUsbSerial;
|
|
26
|
+
exports.resetUsbSerial = resetUsbSerial;
|
|
27
|
+
exports.setUsbSerial = setUsbSerial;
|
|
8
28
|
var _reactNative = require("react-native");
|
|
9
29
|
var _NativeUsbSerial = _interopRequireDefault(require("./NativeUsbSerial"));
|
|
30
|
+
var _transport = require("./transport");
|
|
10
31
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
SPACE: 4
|
|
17
|
-
};
|
|
18
|
-
const DataBits = exports.DataBits = {
|
|
19
|
-
FIVE: 5,
|
|
20
|
-
SIX: 6,
|
|
21
|
-
SEVEN: 7,
|
|
22
|
-
EIGHT: 8
|
|
23
|
-
};
|
|
24
|
-
const StopBits = exports.StopBits = {
|
|
25
|
-
ONE: 1,
|
|
26
|
-
ONE_FIVE: 3,
|
|
27
|
-
TWO: 2
|
|
28
|
-
};
|
|
29
|
-
const DEFAULT_OPEN_OPTIONS = {
|
|
30
|
-
dataBits: DataBits.EIGHT,
|
|
31
|
-
stopBits: StopBits.ONE,
|
|
32
|
-
parity: Parity.NONE
|
|
33
|
-
};
|
|
32
|
+
// These types and enums were originally declared in this module and form part
|
|
33
|
+
// of the public `UsbSerial` namespace. They now live in ./transport (a
|
|
34
|
+
// react-native-free module the virtual transport also builds on) and are
|
|
35
|
+
// re-exported here unchanged for backwards compatibility.
|
|
36
|
+
|
|
34
37
|
class UsbSerialModule {
|
|
35
38
|
constructor() {
|
|
36
39
|
if (!_NativeUsbSerial.default) {
|
|
@@ -50,7 +53,7 @@ class UsbSerialModule {
|
|
|
50
53
|
}
|
|
51
54
|
open(deviceId, portNumber, options) {
|
|
52
55
|
const opts = {
|
|
53
|
-
...DEFAULT_OPEN_OPTIONS,
|
|
56
|
+
..._transport.DEFAULT_OPEN_OPTIONS,
|
|
54
57
|
...options
|
|
55
58
|
};
|
|
56
59
|
return this.native.open(deviceId, portNumber, opts.baudRate, opts.dataBits, opts.stopBits, opts.parity);
|
|
@@ -72,7 +75,7 @@ class UsbSerialModule {
|
|
|
72
75
|
}
|
|
73
76
|
setParameters(deviceId, portNumber, options) {
|
|
74
77
|
const opts = {
|
|
75
|
-
...DEFAULT_OPEN_OPTIONS,
|
|
78
|
+
..._transport.DEFAULT_OPEN_OPTIONS,
|
|
76
79
|
...options
|
|
77
80
|
};
|
|
78
81
|
return this.native.setParameters(deviceId, portNumber, opts.baudRate, opts.dataBits, opts.stopBits, opts.parity);
|
|
@@ -140,10 +143,39 @@ class UsbSerialModule {
|
|
|
140
143
|
}
|
|
141
144
|
exports.UsbSerialModule = UsbSerialModule;
|
|
142
145
|
let instance = null;
|
|
146
|
+
let override = null;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resolve the active serial transport. Returns the override set via
|
|
150
|
+
* {@link setUsbSerial} if present (used by tests and by the example's on-device
|
|
151
|
+
* "virtual device" mode); otherwise lazily constructs the real native-backed
|
|
152
|
+
* {@link UsbSerialModule}.
|
|
153
|
+
*/
|
|
143
154
|
function getUsbSerial() {
|
|
155
|
+
if (override) return override;
|
|
144
156
|
if (!instance) {
|
|
145
157
|
instance = new UsbSerialModule();
|
|
146
158
|
}
|
|
147
159
|
return instance;
|
|
148
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Override the transport returned by {@link getUsbSerial}. Pass a
|
|
164
|
+
* {@link SerialTransport} (e.g. a `VirtualSerialTransport`) to make the
|
|
165
|
+
* singleton `serial` instance — and any `new Serial()` created without an
|
|
166
|
+
* explicit transport — talk to it instead of real hardware. Pass `null` to
|
|
167
|
+
* clear the override.
|
|
168
|
+
*
|
|
169
|
+
* Inject before the first `getPorts()` / `requestPort()` / `addEventListener()`
|
|
170
|
+
* call so the lazy initialisation in `Serial` picks it up.
|
|
171
|
+
*/
|
|
172
|
+
function setUsbSerial(transport) {
|
|
173
|
+
override = transport;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Clear any override and drop the cached native instance (test teardown). */
|
|
177
|
+
function resetUsbSerial() {
|
|
178
|
+
override = null;
|
|
179
|
+
instance = null;
|
|
180
|
+
}
|
|
149
181
|
//# sourceMappingURL=UsbSerial.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","_NativeUsbSerial","_interopRequireDefault","e","__esModule","default","
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_NativeUsbSerial","_interopRequireDefault","_transport","e","__esModule","default","UsbSerialModule","constructor","NativeUsbSerial","Error","native","emitter","NativeEventEmitter","NativeModules","findAllDrivers","showPortPicker","filter","labels","requestPermission","deviceId","open","portNumber","options","opts","DEFAULT_OPEN_OPTIONS","baudRate","dataBits","stopBits","parity","close","isOpen","write","data","timeout","startReading","stopReading","setParameters","setDTR","value","setRTS","getDTR","getRTS","getCD","getCTS","getDSR","getRI","getControlLines","getSupportedControlLines","setFlowControl","flowControl","getFlowControl","getSupportedFlowControl","setBreak","purgeHwBuffers","purgeWriteBuffers","purgeReadBuffers","getSerial","onData","listener","addListener","onError","onConnect","onDisconnect","exports","instance","override","getUsbSerial","setUsbSerial","transport","resetUsbSerial"],"sourceRoot":"../../src","sources":["UsbSerial.ts"],"mappings":"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","ignoreList":[]}
|