react-native-web-serial-api 0.1.0 → 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 +188 -117
- package/TESTING.md +417 -176
- package/android/build.gradle +14 -0
- 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 +1 -1
- package/lib/commonjs/WebSerial.js +110 -26
- package/lib/commonjs/WebSerial.js.map +1 -1
- package/lib/commonjs/index.js +2 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/lib/event-target.js +3 -1
- package/lib/commonjs/lib/event-target.js.map +1 -1
- 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/{virtual-serial.js → in-memory-serial-transport.js} +66 -28
- package/lib/commonjs/testing/in-memory-serial-transport.js.map +1 -0
- package/lib/commonjs/testing/index.js +100 -17
- package/lib/commonjs/testing/index.js.map +1 -1
- 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/{serial-device.js → simulated-device.js} +17 -17
- 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 +3 -3
- 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 +1 -1
- package/lib/typescript/src/WebSerial.d.ts +7 -7
- package/lib/typescript/src/WebSerial.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/lib/event-target.d.ts +2 -0
- package/lib/typescript/src/lib/event-target.d.ts.map +1 -1
- 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/{virtual-serial.d.ts → in-memory-serial-transport.d.ts} +37 -26
- package/lib/typescript/src/testing/in-memory-serial-transport.d.ts.map +1 -0
- package/lib/typescript/src/testing/index.d.ts +18 -8
- package/lib/typescript/src/testing/index.d.ts.map +1 -1
- 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/{serial-device.d.ts → simulated-device.d.ts} +23 -23
- 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 +3 -3
- 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 +21 -3
- package/src/UsbSerial.ts +1 -1
- package/src/WebSerial.ts +134 -35
- package/src/index.ts +4 -1
- package/src/lib/event-target.ts +12 -0
- 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/{virtual-serial.ts → in-memory-serial-transport.ts} +95 -56
- package/src/testing/index.ts +69 -21
- package/src/testing/install-in-memory-serial-transport.ts +65 -0
- package/src/testing/serial-client.ts +313 -0
- package/src/testing/{serial-device.ts → simulated-device.ts} +23 -23
- package/src/testing/test-suite.ts +186 -0
- package/src/transport.ts +3 -3
- 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/lib/commonjs/testing/install.js +0 -54
- package/lib/commonjs/testing/install.js.map +0 -1
- package/lib/commonjs/testing/serial-device.js.map +0 -1
- package/lib/commonjs/testing/virtual-serial.js.map +0 -1
- package/lib/typescript/src/testing/install.d.ts +0 -25
- package/lib/typescript/src/testing/install.d.ts.map +0 -1
- package/lib/typescript/src/testing/serial-device.d.ts.map +0 -1
- package/lib/typescript/src/testing/virtual-serial.d.ts.map +0 -1
- package/src/testing/install.ts +0 -65
package/README.md
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
# react-native-web-serial-api
|
|
2
2
|
|
|
3
|
-
> The [W3C Web Serial API](https://wicg.github.io/serial/) (`navigator.serial`) for
|
|
3
|
+
> The [W3C Web Serial API](https://wicg.github.io/serial/) (`navigator.serial`) for React Native on Android, backed by a USB-serial TurboModule built on top of [`mik3y/usb-serial-for-android`](https://github.com/mik3y/usb-serial-for-android).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use the same API you already know from the browser - `serial.requestPort()`, `port.open()`, `port.readable`, `port.writable`, `getPorts()`, `setSignals()`, and `getSignals()` - in a React Native app that talks to USB serial devices.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- New Architecture **TurboModule**
|
|
9
|
-
- Native port-picker dialog + USB permission handling
|
|
10
|
-
- Backed by Web Streams (`ReadableStream` / `WritableStream`)
|
|
11
|
-
- Drop-in for code written against the browser Web Serial API (on web it transparently uses the native `navigator.serial`)
|
|
7
|
+
## At a glance
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
- Spec-style `Serial` / `SerialPort` implementation
|
|
10
|
+
- New Architecture TurboModule
|
|
11
|
+
- Native port picker and Android USB permission handling
|
|
12
|
+
- Web Streams under the hood (`ReadableStream` / `WritableStream`)
|
|
13
|
+
- Works with browser-style code on web by delegating to the native `navigator.serial`
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Platform support
|
|
16
|
+
|
|
17
|
+
| Platform | Support | Notes |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| Android | Yes | Native USB-serial support through the TurboModule. |
|
|
20
|
+
| Web | Yes | Delegates to the browser's native `navigator.serial`. |
|
|
21
|
+
| iOS | No | Generic USB-serial access is not available, so autolinking is disabled. |
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
16
24
|
|
|
17
25
|
```sh
|
|
18
26
|
npm install react-native-web-serial-api
|
|
@@ -20,49 +28,27 @@ npm install react-native-web-serial-api
|
|
|
20
28
|
yarn add react-native-web-serial-api
|
|
21
29
|
```
|
|
22
30
|
|
|
23
|
-
This is a New Architecture library
|
|
24
|
-
|
|
25
|
-
### Android setup
|
|
31
|
+
This is a New Architecture library. Make sure your app has the New Architecture enabled. No manual linking is required - the module is autolinked.
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
If you want your app to be **launched automatically when a matching device is plugged in**, add an intent filter to your launcher activity in `android/app/src/main/AndroidManifest.xml`:
|
|
30
|
-
|
|
31
|
-
```xml
|
|
32
|
-
<activity android:name=".MainActivity" ...>
|
|
33
|
-
<intent-filter>
|
|
34
|
-
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
|
35
|
-
</intent-filter>
|
|
36
|
-
<!-- The device_filter resource is provided by the library -->
|
|
37
|
-
<meta-data
|
|
38
|
-
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
|
39
|
-
android:resource="@xml/device_filter" />
|
|
40
|
-
</activity>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
The bundled `@xml/device_filter` matches the common USB-serial chips (CDC-ACM, FTDI `0x0403`, CP210x `0x10C4`, CH34x `0x1A86`, PL2303 `0x067B`). Provide your own `res/xml/device_filter.xml` to override it.
|
|
44
|
-
|
|
45
|
-
## Usage
|
|
33
|
+
### Minimal usage
|
|
46
34
|
|
|
47
35
|
```ts
|
|
48
36
|
import {serial} from 'react-native-web-serial-api';
|
|
49
37
|
|
|
50
38
|
async function run() {
|
|
51
|
-
//
|
|
39
|
+
// Must be called from a user gesture on web.
|
|
52
40
|
const port = await serial.requestPort({
|
|
53
|
-
filters: [{usbVendorId: 0x0403}], // optional
|
|
41
|
+
filters: [{usbVendorId: 0x0403}], // optional, for example FTDI only
|
|
54
42
|
});
|
|
55
43
|
|
|
56
44
|
await port.open({baudRate: 115200, dataBits: 8, stopBits: 1, parity: 'none'});
|
|
57
45
|
|
|
58
|
-
// Write
|
|
59
46
|
const writer = port.writable.getWriter();
|
|
60
47
|
await writer.write(new TextEncoder().encode('Hello\n'));
|
|
61
48
|
writer.releaseLock();
|
|
62
49
|
|
|
63
|
-
// Read
|
|
64
50
|
const reader = port.readable.getReader();
|
|
65
|
-
const {value} = await reader.read();
|
|
51
|
+
const {value} = await reader.read();
|
|
66
52
|
console.log(value);
|
|
67
53
|
reader.releaseLock();
|
|
68
54
|
|
|
@@ -70,150 +56,235 @@ async function run() {
|
|
|
70
56
|
}
|
|
71
57
|
```
|
|
72
58
|
|
|
73
|
-
|
|
74
|
-
`requestPort()` and will throw a `TypeError` if provided.
|
|
59
|
+
## Android setup
|
|
75
60
|
|
|
76
|
-
|
|
61
|
+
The library ships its own `AndroidManifest.xml` that declares the port picker activity, the detach receiver, and the `android.hardware.usb.host` feature, so usually no extra configuration is needed.
|
|
77
62
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
63
|
+
If you want your app to launch automatically when a matching device is plugged in, add an intent filter to your launcher activity in `android/app/src/main/AndroidManifest.xml`:
|
|
64
|
+
|
|
65
|
+
```xml
|
|
66
|
+
<activity android:name=".MainActivity" ...>
|
|
67
|
+
<intent-filter>
|
|
68
|
+
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
|
69
|
+
</intent-filter>
|
|
70
|
+
<meta-data
|
|
71
|
+
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
|
72
|
+
android:resource="@xml/device_filter" />
|
|
73
|
+
</activity>
|
|
82
74
|
```
|
|
83
75
|
|
|
84
|
-
|
|
76
|
+
The bundled `@xml/device_filter` matches common USB-serial chips:
|
|
77
|
+
|
|
78
|
+
- CDC-ACM
|
|
79
|
+
- FTDI `0x0403`
|
|
80
|
+
- CP210x `0x10C4`
|
|
81
|
+
- CH34x `0x1A86`
|
|
82
|
+
- PL2303 `0x067B`
|
|
83
|
+
|
|
84
|
+
Provide your own `res/xml/device_filter.xml` to override it.
|
|
85
|
+
|
|
86
|
+
## Which API should I use?
|
|
87
|
+
|
|
88
|
+
| Use case | Start with | Why |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| Real hardware in your app | `Serial` / `SerialPort` | The browser-style Web Serial API you already know. |
|
|
91
|
+
| Quick in-memory smoke tests | `InMemorySerialTransport` + `LoopbackDevice` | Fastest way to exercise bytes without hardware. |
|
|
92
|
+
| Protocol tests against a simulated peripheral | `SimulatedDevice` + `createDeviceFixture` + `SerialClient` | Gives you both sides of the conversation in one test. |
|
|
93
|
+
| App-to-app or emulator-to-host testing | `exposeSimulatedDevice` + `WebSocketSerialTransport` | Runs the same simulated device behind a real WebSocket bridge. |
|
|
94
|
+
|
|
95
|
+
## Core concepts
|
|
96
|
+
|
|
97
|
+
### `serial`
|
|
98
|
+
|
|
99
|
+
The package exports a ready-to-use `serial` singleton, which is the equivalent of `navigator.serial`.
|
|
85
100
|
|
|
86
101
|
```ts
|
|
87
|
-
serial
|
|
88
|
-
serial.addEventListener('disconnect', () => console.log('device detached'));
|
|
102
|
+
import {serial} from 'react-native-web-serial-api';
|
|
89
103
|
|
|
90
|
-
|
|
104
|
+
const ports = await serial.getPorts();
|
|
91
105
|
```
|
|
92
106
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
re-run `getPorts()` on every `connect`/`disconnect` to keep a device list fresh.
|
|
107
|
+
### Permissions
|
|
108
|
+
|
|
109
|
+
There are two different permission concepts:
|
|
110
|
+
|
|
111
|
+
- `serial.requestPort()` is the Web Serial permission flow. In Android mode it shows the native picker and requests Android USB permission for the selected device.
|
|
112
|
+
- Android USB permission can also be granted outside the app, for example through the system attach dialog or an `USB_DEVICE_ATTACHED` intent filter.
|
|
100
113
|
|
|
101
|
-
|
|
114
|
+
On Android, `serial.getPorts()` returns the devices the app can currently access through Android USB permission, regardless of how that permission was obtained. On web, `getPorts()` returns ports previously granted by the site in the browser's persistent permission store.
|
|
102
115
|
|
|
103
116
|
```ts
|
|
104
117
|
const ports = await serial.getPorts();
|
|
118
|
+
const port = await serial.requestPort();
|
|
105
119
|
```
|
|
106
120
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
There are two distinct notions of "permission" in play, and they behave
|
|
110
|
-
differently on Android than in the browser:
|
|
111
|
-
|
|
112
|
-
- **Web Serial permission grant** — `serial.requestPort()`. In the browser this
|
|
113
|
-
records a site-level grant for the chosen port; on Android it shows a native
|
|
114
|
-
picker and requests the Android USB permission for the selected device. This
|
|
115
|
-
is the mechanism for gaining access to a device you don't have access to yet,
|
|
116
|
-
and it is **unchanged** by anything below.
|
|
117
|
-
- **Native Android USB permission** — `UsbManager` permission for a device.
|
|
118
|
-
This can be granted **outside** the app entirely: when you plug a device in,
|
|
119
|
-
Android may show its own _"Open <app> to handle this USB device? / use by
|
|
120
|
-
default for this device"_ dialog, or the app may have been launched via a
|
|
121
|
-
`USB_DEVICE_ATTACHED` intent filter. In those cases the app already holds USB
|
|
122
|
-
permission without ever calling `requestPort()`.
|
|
123
|
-
|
|
124
|
-
**`serial.getPorts()` in Android/native mode** returns **every probed
|
|
125
|
-
USB-serial port the app can currently access through Android USB permission** —
|
|
126
|
-
regardless of how that permission was obtained. So a device granted via the
|
|
127
|
-
system attach dialog appears in `getPorts()` even though `requestPort()` was
|
|
128
|
-
never called for it. Probed devices the app does **not** yet have permission for
|
|
129
|
-
are excluded; use `requestPort()` to gain access to those.
|
|
121
|
+
### Connect and disconnect events
|
|
130
122
|
|
|
131
123
|
```ts
|
|
132
|
-
|
|
133
|
-
|
|
124
|
+
serial.addEventListener('connect', () => console.log('device attached'));
|
|
125
|
+
serial.addEventListener('disconnect', () => console.log('device detached'));
|
|
134
126
|
|
|
135
|
-
|
|
136
|
-
|
|
127
|
+
port.addEventListener('disconnect', () => console.log('this port went away'));
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
On Android, `serial` fires `connect` when a USB device is attached and when the app is granted USB permission for a device. A common pattern is to re-run `getPorts()` on every `connect` / `disconnect` so your device list stays fresh.
|
|
131
|
+
|
|
132
|
+
### Control and status signals
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
await port.setSignals({dataTerminalReady: true, requestToSend: false});
|
|
136
|
+
const {clearToSend, dataCarrierDetect, ringIndicator, dataSetReady} =
|
|
137
|
+
await port.getSignals();
|
|
137
138
|
```
|
|
138
139
|
|
|
139
|
-
|
|
140
|
-
the site via `requestPort()` (the browser's persistent permission store) — the
|
|
141
|
-
native "attach dialog" notion does not apply.
|
|
140
|
+
### Browser-only option note
|
|
142
141
|
|
|
143
|
-
|
|
144
|
-
> spec's `getPorts()` ("ports the site has been granted access to"). On Android
|
|
145
|
-
> the unit of access is the OS-level USB permission, so a device the OS has
|
|
146
|
-
> already authorized for the app is, by definition, one the app has been
|
|
147
|
-
> granted access to.
|
|
142
|
+
In Android USB mode, `allowedBluetoothServiceClassIds` is not supported by `requestPort()` and will throw a `TypeError` if provided.
|
|
148
143
|
|
|
149
|
-
## API
|
|
144
|
+
## API reference
|
|
150
145
|
|
|
151
146
|
The package exposes:
|
|
152
147
|
|
|
153
148
|
| Export | Description |
|
|
154
149
|
| --- | --- |
|
|
155
|
-
| `serial` | A ready-to-use `Serial` instance
|
|
150
|
+
| `serial` | A ready-to-use `Serial` instance. |
|
|
156
151
|
| `Serial`, `SerialPort` | The Web Serial API classes. |
|
|
157
152
|
| `UsbSerial` | Lower-level access to the raw USB-serial TurboModule (Android only). |
|
|
158
|
-
| `Event`, `EventTarget` |
|
|
153
|
+
| `Event`, `EventTarget` | Polyfill implementations used only when the runtime does not already provide these globals. |
|
|
159
154
|
| Types | `SerialOptions`, `SerialOutputSignals`, `SerialInputSignals`, `SerialPortInfo`, `SerialPortFilter`, `SerialPortRequestOptions`. |
|
|
160
155
|
|
|
161
156
|
## Example app
|
|
162
157
|
|
|
163
|
-
The [`example/`](./example) app is a React Native port of [SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal) built
|
|
158
|
+
The [`example/`](./example) app is a React Native port of [SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal) built on this package's Web Serial API. It includes:
|
|
159
|
+
|
|
160
|
+
- a Devices screen with baud-rate selection
|
|
161
|
+
- a Terminal screen with colored send / receive logs
|
|
162
|
+
- HEX mode
|
|
163
|
+
- newline selection
|
|
164
|
+
- clear
|
|
165
|
+
- control lines with RTS / DTR toggles
|
|
166
|
+
- flow control
|
|
167
|
+
- Send BREAK
|
|
168
|
+
|
|
169
|
+
### Run the example on Android
|
|
164
170
|
|
|
165
171
|
```sh
|
|
166
|
-
# install the library's build tooling
|
|
167
172
|
npm install
|
|
168
|
-
|
|
169
|
-
# install and run the example on Android
|
|
170
173
|
cd example
|
|
171
174
|
npm install
|
|
172
175
|
npm run android
|
|
173
176
|
```
|
|
174
177
|
|
|
175
|
-
Because
|
|
178
|
+
Because the example uses the Web Serial API, a few details differ from SimpleUsbTerminal:
|
|
176
179
|
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
- The Android
|
|
180
|
+
- Driver / chip name is not exposed by the Web Serial API. Rows show `Vendor/Product` plus a best-effort chip label from known vendor IDs.
|
|
181
|
+
- Flow control is limited to `None` and `Hardware (RTS-CTS)`. XON/XOFF and DTR/DSR are not in the Web Serial spec. Changing it reconnects the port.
|
|
182
|
+
- The Android foreground-service notification is omitted because it is service plumbing unrelated to serial I/O.
|
|
180
183
|
|
|
181
184
|
### Run the example in the browser
|
|
182
185
|
|
|
183
|
-
The example also runs as a web app via [`react-native-web`](https://necolas.github.io/react-native-web/)
|
|
186
|
+
The example also runs as a web app via [`react-native-web`](https://necolas.github.io/react-native-web/) and [Vite](https://vite.dev/). On web, the package delegates to the browser's native `navigator.serial`, so the same `App.tsx` talks to real serial hardware through browser permissions.
|
|
184
187
|
|
|
185
188
|
```sh
|
|
186
189
|
cd example
|
|
187
190
|
npm install
|
|
188
|
-
npm run web
|
|
189
|
-
# npm run web:build
|
|
191
|
+
npm run web
|
|
192
|
+
# npm run web:build
|
|
190
193
|
```
|
|
191
194
|
|
|
192
|
-
|
|
195
|
+
Web Serial works in Chromium-based browsers over a secure context. `http://localhost` counts. `requestPort()` must be called from a user gesture, and the example's Request port button handles that.
|
|
193
196
|
|
|
194
|
-
##
|
|
195
|
-
|
|
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).
|
|
197
|
+
## Testing and simulation
|
|
197
198
|
|
|
198
|
-
|
|
199
|
+
The transport layer lets you test without USB hardware, whether you want a quick loopback check, a stateful simulated peripheral, or a full WebSocket E2E path. The example app also includes a Self Test screen and a Virtual device (demo) mode.
|
|
199
200
|
|
|
200
|
-
|
|
201
|
+
### Fast in-memory test
|
|
201
202
|
|
|
202
203
|
```ts
|
|
203
204
|
import {Serial} from 'react-native-web-serial-api';
|
|
204
|
-
import {
|
|
205
|
+
import {
|
|
206
|
+
InMemorySerialTransport,
|
|
207
|
+
LoopbackDevice,
|
|
208
|
+
} from 'react-native-web-serial-api/testing';
|
|
209
|
+
|
|
210
|
+
const transport = new InMemorySerialTransport();
|
|
211
|
+
transport.addDevice(
|
|
212
|
+
new LoopbackDevice({usbVendorId: 0x0403, usbProductId: 0x6001}),
|
|
213
|
+
{hasPermission: true},
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const serial = new Serial(transport);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Host-side protocol test
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import {createDeviceFixture, SimulatedDevice} from 'react-native-web-serial-api/testing';
|
|
223
|
+
|
|
224
|
+
class Thermometer extends SimulatedDevice {
|
|
225
|
+
readonly usbVendorId = 0x10c4;
|
|
226
|
+
readonly usbProductId = 0xea60;
|
|
227
|
+
|
|
228
|
+
onOpen() {
|
|
229
|
+
this.send('READY\r\n');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
emitTemperature(value: number) {
|
|
233
|
+
this.send(`temp=${value}\r\n`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const {client, simulatedDevice, whenOpened} =
|
|
238
|
+
await createDeviceFixture(new Thermometer());
|
|
205
239
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
240
|
+
await client.open({baudRate: 115200});
|
|
241
|
+
await whenOpened();
|
|
242
|
+
simulatedDevice.emitTemperature(21.5);
|
|
243
|
+
expect(await client.readLine()).toBe('temp=21.5');
|
|
244
|
+
await client.close();
|
|
209
245
|
```
|
|
210
246
|
|
|
247
|
+
For the full guide to `SerialClient`, `createDeviceFixture`, fault injection, `runTestSuite`, `compareTestResults`, `exposeSimulatedDevice`, and the conformance suites, see [TESTING.md](TESTING.md).
|
|
248
|
+
|
|
249
|
+
## Remote serial over WebSocket
|
|
250
|
+
|
|
251
|
+
You can drive a real serial port plugged into another machine. This is useful for developing in a Chromium browser or an Android emulator that cannot see the USB device directly, or for remote debugging.
|
|
252
|
+
|
|
253
|
+
On the host machine, run the bundled CLI:
|
|
254
|
+
|
|
211
255
|
```sh
|
|
212
|
-
|
|
213
|
-
|
|
256
|
+
npx -p react-native-web-serial-api expose-serial-websocket \
|
|
257
|
+
--port /dev/ttyUSB0 --baudrate 115200
|
|
258
|
+
# add --allow-remote to bind 0.0.0.0
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
In the app:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import {Serial} from 'react-native-web-serial-api';
|
|
265
|
+
import {WebSocketSerialTransport} from 'react-native-web-serial-api/websocket';
|
|
266
|
+
|
|
267
|
+
const serial = new Serial(new WebSocketSerialTransport('ws://localhost:8080'));
|
|
268
|
+
const [port] = await serial.getPorts();
|
|
269
|
+
await port.open({baudRate: 115200});
|
|
270
|
+
const writer = port.writable!.getWriter();
|
|
271
|
+
await writer.write(new TextEncoder().encode('Hello serial!\n'));
|
|
214
272
|
```
|
|
215
273
|
|
|
216
|
-
|
|
274
|
+
The example app includes this under Devices -> menu -> Remote serial (WebSocket).
|
|
275
|
+
|
|
276
|
+
The WebSocket carries raw serial bytes as binary frames and a small JSON control protocol as text frames (`setLineCoding`, `setSignals` / `getSignals`, `startReading` / `stopReading`, `flush`, `break`, and so on). By default the bridge binds to `localhost`. Use `--allow-remote` only on trusted networks.
|
|
277
|
+
|
|
278
|
+
## Troubleshooting
|
|
279
|
+
|
|
280
|
+
- If Android never shows your device, confirm USB host support and the device filter.
|
|
281
|
+
- If `requestPort()` does nothing on web, make sure it is called from a button tap or other user gesture.
|
|
282
|
+
- If the app works on web but not Android, check that New Architecture is enabled and the device has Android USB permission.
|
|
283
|
+
- If `getPorts()` is empty on Android, unplug and replug the device, then grant permission again if Android revoked it.
|
|
284
|
+
|
|
285
|
+
## How it works
|
|
286
|
+
|
|
287
|
+
The JavaScript layer in `src/` implements the Web Serial API on top of a thin TurboModule (`NativeUsbSerial`) whose native Android implementation in `android/src/main/java/dev/webserialapi/` wraps `usb-serial-for-android`. Reads and writes are bridged to `ReadableStream` / `WritableStream` via [`web-streams-polyfill`](https://github.com/MattiasBuelens/web-streams-polyfill) when the runtime does not already provide Web Streams globals.
|
|
217
288
|
|
|
218
289
|
## License
|
|
219
290
|
|