react-native-web-serial-api 0.0.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -104
- package/TESTING.md +542 -0
- package/android/build.gradle +16 -2
- package/android/src/main/java/dev/webserialapi/NativeUsbSerialModule.java +74 -11
- package/android/src/main/java/dev/webserialapi/PortPickerActivity.java +61 -59
- package/bin/expose-serial.js +205 -0
- package/lib/commonjs/UsbSerial.js +58 -26
- package/lib/commonjs/UsbSerial.js.map +1 -1
- package/lib/commonjs/WebSerial.js +273 -77
- package/lib/commonjs/WebSerial.js.map +1 -1
- package/lib/commonjs/index.js +15 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/lib/dom-exception.js +176 -0
- package/lib/commonjs/lib/dom-exception.js.map +1 -0
- package/lib/commonjs/lib/event-target.js +140 -0
- package/lib/commonjs/lib/event-target.js.map +1 -0
- package/lib/commonjs/lib/promise.js +23 -0
- package/lib/commonjs/lib/promise.js.map +1 -0
- package/lib/commonjs/lib/web-streams.js +42 -0
- package/lib/commonjs/lib/web-streams.js.map +1 -0
- package/lib/commonjs/testing/device-fixture.js +70 -0
- package/lib/commonjs/testing/device-fixture.js.map +1 -0
- package/lib/commonjs/testing/expose.js +91 -0
- package/lib/commonjs/testing/expose.js.map +1 -0
- package/lib/commonjs/testing/harness.js +98 -0
- package/lib/commonjs/testing/harness.js.map +1 -0
- package/lib/commonjs/testing/in-memory-serial-transport.js +653 -0
- package/lib/commonjs/testing/in-memory-serial-transport.js.map +1 -0
- package/lib/commonjs/testing/index.js +153 -0
- package/lib/commonjs/testing/index.js.map +1 -0
- package/lib/commonjs/testing/install-in-memory-serial-transport.js +54 -0
- package/lib/commonjs/testing/install-in-memory-serial-transport.js.map +1 -0
- package/lib/commonjs/testing/serial-client.js +277 -0
- package/lib/commonjs/testing/serial-client.js.map +1 -0
- package/lib/commonjs/testing/simulated-device.js +164 -0
- package/lib/commonjs/testing/simulated-device.js.map +1 -0
- package/lib/commonjs/testing/test-suite.js +142 -0
- package/lib/commonjs/testing/test-suite.js.map +1 -0
- package/lib/commonjs/transport.js +61 -0
- package/lib/commonjs/transport.js.map +1 -0
- package/lib/commonjs/websocket/WebSocketSerialTransport.js +659 -0
- package/lib/commonjs/websocket/WebSocketSerialTransport.js.map +1 -0
- package/lib/commonjs/websocket/bridge.js +234 -0
- package/lib/commonjs/websocket/bridge.js.map +1 -0
- package/lib/commonjs/websocket/index.js +33 -0
- package/lib/commonjs/websocket/index.js.map +1 -0
- package/lib/commonjs/websocket/protocol.js +55 -0
- package/lib/commonjs/websocket/protocol.js.map +1 -0
- package/lib/commonjs/websocket/serial-device-bridge.js +130 -0
- package/lib/commonjs/websocket/serial-device-bridge.js.map +1 -0
- package/lib/typescript/src/UsbSerial.d.ts +24 -67
- package/lib/typescript/src/UsbSerial.d.ts.map +1 -1
- package/lib/typescript/src/WebSerial.d.ts +16 -7
- package/lib/typescript/src/WebSerial.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/lib/dom-exception.d.ts +100 -0
- package/lib/typescript/src/lib/dom-exception.d.ts.map +1 -0
- package/lib/typescript/src/lib/event-target.d.ts +55 -0
- package/lib/typescript/src/lib/event-target.d.ts.map +1 -0
- package/lib/typescript/src/lib/promise.d.ts +11 -0
- package/lib/typescript/src/lib/promise.d.ts.map +1 -0
- package/lib/typescript/src/lib/web-streams.d.ts +9 -0
- package/lib/typescript/src/lib/web-streams.d.ts.map +1 -0
- package/lib/typescript/src/testing/device-fixture.d.ts +70 -0
- package/lib/typescript/src/testing/device-fixture.d.ts.map +1 -0
- package/lib/typescript/src/testing/expose.d.ts +71 -0
- package/lib/typescript/src/testing/expose.d.ts.map +1 -0
- package/lib/typescript/src/testing/harness.d.ts +34 -0
- package/lib/typescript/src/testing/harness.d.ts.map +1 -0
- package/lib/typescript/src/testing/in-memory-serial-transport.d.ts +216 -0
- package/lib/typescript/src/testing/in-memory-serial-transport.d.ts.map +1 -0
- package/lib/typescript/src/testing/index.d.ts +33 -0
- package/lib/typescript/src/testing/index.d.ts.map +1 -0
- package/lib/typescript/src/testing/install-in-memory-serial-transport.d.ts +25 -0
- package/lib/typescript/src/testing/install-in-memory-serial-transport.d.ts.map +1 -0
- package/lib/typescript/src/testing/serial-client.d.ts +62 -0
- package/lib/typescript/src/testing/serial-client.d.ts.map +1 -0
- package/lib/typescript/src/testing/simulated-device.d.ts +127 -0
- package/lib/typescript/src/testing/simulated-device.d.ts.map +1 -0
- package/lib/typescript/src/testing/test-suite.d.ts +75 -0
- package/lib/typescript/src/testing/test-suite.d.ts.map +1 -0
- package/lib/typescript/src/transport.d.ts +131 -0
- package/lib/typescript/src/transport.d.ts.map +1 -0
- package/lib/typescript/src/websocket/WebSocketSerialTransport.d.ts +111 -0
- package/lib/typescript/src/websocket/WebSocketSerialTransport.d.ts.map +1 -0
- package/lib/typescript/src/websocket/bridge.d.ts +66 -0
- package/lib/typescript/src/websocket/bridge.d.ts.map +1 -0
- package/lib/typescript/src/websocket/index.d.ts +19 -0
- package/lib/typescript/src/websocket/index.d.ts.map +1 -0
- package/lib/typescript/src/websocket/protocol.d.ts +64 -0
- package/lib/typescript/src/websocket/protocol.d.ts.map +1 -0
- package/lib/typescript/src/websocket/serial-device-bridge.d.ts +32 -0
- package/lib/typescript/src/websocket/serial-device-bridge.d.ts.map +1 -0
- package/package.json +57 -3
- package/src/UsbSerial.ts +65 -90
- package/src/WebSerial.ts +351 -113
- package/src/index.ts +6 -8
- package/src/lib/dom-exception.ts +129 -60
- package/src/lib/event-target.ts +58 -21
- package/src/lib/promise.ts +7 -7
- package/src/lib/web-streams.ts +43 -0
- package/src/testing/device-fixture.ts +150 -0
- package/src/testing/expose.ts +147 -0
- package/src/testing/harness.ts +124 -0
- package/src/testing/in-memory-serial-transport.ts +840 -0
- package/src/testing/index.ts +90 -0
- package/src/testing/install-in-memory-serial-transport.ts +65 -0
- package/src/testing/serial-client.ts +313 -0
- package/src/testing/simulated-device.ts +193 -0
- package/src/testing/test-suite.ts +186 -0
- package/src/transport.ts +200 -0
- package/src/websocket/WebSocketSerialTransport.ts +796 -0
- package/src/websocket/bridge.ts +299 -0
- package/src/websocket/index.ts +38 -0
- package/src/websocket/protocol.ts +101 -0
- package/src/websocket/serial-device-bridge.ts +160 -0
- package/babel.config.js +0 -3
- package/biome.json +0 -35
- package/example/.watchmanconfig +0 -1
- package/example/App.tsx +0 -71
- package/example/__tests__/App.test.tsx +0 -16
- package/example/__tests__/connectEvents.test.tsx +0 -81
- package/example/__tests__/getPorts.test.tsx +0 -140
- package/example/android/app/build.gradle +0 -120
- package/example/android/app/debug.keystore +0 -0
- package/example/android/app/proguard-rules.pro +0 -10
- package/example/android/app/src/debug/AndroidManifest.xml +0 -9
- package/example/android/app/src/main/AndroidManifest.xml +0 -38
- package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +0 -22
- package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +0 -41
- package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -37
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/values/strings.xml +0 -3
- package/example/android/app/src/main/res/values/styles.xml +0 -9
- package/example/android/build.gradle +0 -22
- package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
- package/example/android/gradle.properties +0 -47
- package/example/android/gradlew +0 -252
- package/example/android/gradlew.bat +0 -94
- package/example/android/settings.gradle +0 -6
- package/example/app.json +0 -4
- package/example/babel.config.js +0 -21
- package/example/biome.json +0 -47
- package/example/deploy.sh +0 -11
- package/example/index.html +0 -26
- package/example/index.js +0 -9
- package/example/index.web.js +0 -8
- package/example/jest.config.js +0 -12
- package/example/metro.config.js +0 -58
- package/example/package-lock.json +0 -14510
- package/example/package.json +0 -48
- package/example/react-native.config.js +0 -17
- package/example/src/components/AppBar.tsx +0 -73
- package/example/src/components/Menu.tsx +0 -90
- package/example/src/components/SingleChoiceDialog.tsx +0 -120
- package/example/src/screens/ConnectScreen.tsx +0 -195
- package/example/src/screens/DevicesScreen.tsx +0 -252
- package/example/src/screens/TerminalScreen.tsx +0 -572
- package/example/src/settings.ts +0 -43
- package/example/src/theme.ts +0 -19
- package/example/src/util/TextUtil.ts +0 -129
- package/example/tsconfig.json +0 -10
- package/example/vite.config.mjs +0 -55
- package/scripts/deploy-release.sh +0 -127
- package/tsconfig.build.json +0 -7
- package/tsconfig.json +0 -20
package/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
|
|
26
|
-
|
|
27
|
-
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**.
|
|
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.
|
|
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.
|
|
44
32
|
|
|
45
|
-
|
|
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,127 +56,235 @@ async function run() {
|
|
|
70
56
|
}
|
|
71
57
|
```
|
|
72
58
|
|
|
73
|
-
|
|
59
|
+
## Android setup
|
|
74
60
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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.
|
|
62
|
+
|
|
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>
|
|
79
74
|
```
|
|
80
75
|
|
|
81
|
-
|
|
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`.
|
|
82
100
|
|
|
83
101
|
```ts
|
|
84
|
-
serial
|
|
85
|
-
serial.addEventListener('disconnect', () => console.log('device detached'));
|
|
102
|
+
import {serial} from 'react-native-web-serial-api';
|
|
86
103
|
|
|
87
|
-
|
|
104
|
+
const ports = await serial.getPorts();
|
|
88
105
|
```
|
|
89
106
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
accessible — and shows up in `getPorts()` — once permission is re-granted).
|
|
94
|
-
Simply subscribing with `serial.addEventListener('connect', …)` is enough to
|
|
95
|
-
receive these; you don't need to call `getPorts()` first. A common pattern is to
|
|
96
|
-
re-run `getPorts()` on every `connect`/`disconnect` to keep a device list fresh.
|
|
107
|
+
### Permissions
|
|
108
|
+
|
|
109
|
+
There are two different permission concepts:
|
|
97
110
|
|
|
98
|
-
|
|
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.
|
|
113
|
+
|
|
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.
|
|
99
115
|
|
|
100
116
|
```ts
|
|
101
117
|
const ports = await serial.getPorts();
|
|
118
|
+
const port = await serial.requestPort();
|
|
102
119
|
```
|
|
103
120
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
There are two distinct notions of "permission" in play, and they behave
|
|
107
|
-
differently on Android than in the browser:
|
|
108
|
-
|
|
109
|
-
- **Web Serial permission grant** — `serial.requestPort()`. In the browser this
|
|
110
|
-
records a site-level grant for the chosen port; on Android it shows a native
|
|
111
|
-
picker and requests the Android USB permission for the selected device. This
|
|
112
|
-
is the mechanism for gaining access to a device you don't have access to yet,
|
|
113
|
-
and it is **unchanged** by anything below.
|
|
114
|
-
- **Native Android USB permission** — `UsbManager` permission for a device.
|
|
115
|
-
This can be granted **outside** the app entirely: when you plug a device in,
|
|
116
|
-
Android may show its own _"Open <app> to handle this USB device? / use by
|
|
117
|
-
default for this device"_ dialog, or the app may have been launched via a
|
|
118
|
-
`USB_DEVICE_ATTACHED` intent filter. In those cases the app already holds USB
|
|
119
|
-
permission without ever calling `requestPort()`.
|
|
120
|
-
|
|
121
|
-
**`serial.getPorts()` in Android/native mode** returns **every probed
|
|
122
|
-
USB-serial port the app can currently access through Android USB permission** —
|
|
123
|
-
regardless of how that permission was obtained. So a device granted via the
|
|
124
|
-
system attach dialog appears in `getPorts()` even though `requestPort()` was
|
|
125
|
-
never called for it. Probed devices the app does **not** yet have permission for
|
|
126
|
-
are excluded; use `requestPort()` to gain access to those.
|
|
121
|
+
### Connect and disconnect events
|
|
127
122
|
|
|
128
123
|
```ts
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
serial.addEventListener('connect', () => console.log('device attached'));
|
|
125
|
+
serial.addEventListener('disconnect', () => console.log('device detached'));
|
|
131
126
|
|
|
132
|
-
|
|
133
|
-
|
|
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();
|
|
134
138
|
```
|
|
135
139
|
|
|
136
|
-
|
|
137
|
-
the site via `requestPort()` (the browser's persistent permission store) — the
|
|
138
|
-
native "attach dialog" notion does not apply.
|
|
140
|
+
### Browser-only option note
|
|
139
141
|
|
|
140
|
-
|
|
141
|
-
> spec's `getPorts()` ("ports the site has been granted access to"). On Android
|
|
142
|
-
> the unit of access is the OS-level USB permission, so a device the OS has
|
|
143
|
-
> already authorized for the app is, by definition, one the app has been
|
|
144
|
-
> granted access to.
|
|
142
|
+
In Android USB mode, `allowedBluetoothServiceClassIds` is not supported by `requestPort()` and will throw a `TypeError` if provided.
|
|
145
143
|
|
|
146
|
-
## API
|
|
144
|
+
## API reference
|
|
147
145
|
|
|
148
146
|
The package exposes:
|
|
149
147
|
|
|
150
148
|
| Export | Description |
|
|
151
149
|
| --- | --- |
|
|
152
|
-
| `serial` | A ready-to-use `Serial` instance
|
|
150
|
+
| `serial` | A ready-to-use `Serial` instance. |
|
|
153
151
|
| `Serial`, `SerialPort` | The Web Serial API classes. |
|
|
154
152
|
| `UsbSerial` | Lower-level access to the raw USB-serial TurboModule (Android only). |
|
|
155
|
-
| `Event`, `EventTarget` |
|
|
153
|
+
| `Event`, `EventTarget` | Polyfill implementations used only when the runtime does not already provide these globals. |
|
|
156
154
|
| Types | `SerialOptions`, `SerialOutputSignals`, `SerialInputSignals`, `SerialPortInfo`, `SerialPortFilter`, `SerialPortRequestOptions`. |
|
|
157
155
|
|
|
158
156
|
## Example app
|
|
159
157
|
|
|
160
|
-
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
|
|
161
170
|
|
|
162
171
|
```sh
|
|
163
|
-
# install the library's build tooling
|
|
164
172
|
npm install
|
|
165
|
-
|
|
166
|
-
# install and run the example on Android
|
|
167
173
|
cd example
|
|
168
174
|
npm install
|
|
169
175
|
npm run android
|
|
170
176
|
```
|
|
171
177
|
|
|
172
|
-
Because
|
|
178
|
+
Because the example uses the Web Serial API, a few details differ from SimpleUsbTerminal:
|
|
173
179
|
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
- 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.
|
|
177
183
|
|
|
178
184
|
### Run the example in the browser
|
|
179
185
|
|
|
180
|
-
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.
|
|
181
187
|
|
|
182
188
|
```sh
|
|
183
189
|
cd example
|
|
184
190
|
npm install
|
|
185
|
-
npm run web
|
|
186
|
-
# npm run web:build
|
|
191
|
+
npm run web
|
|
192
|
+
# npm run web:build
|
|
193
|
+
```
|
|
194
|
+
|
|
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.
|
|
196
|
+
|
|
197
|
+
## Testing and simulation
|
|
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.
|
|
200
|
+
|
|
201
|
+
### Fast in-memory test
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import {Serial} from 'react-native-web-serial-api';
|
|
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);
|
|
187
217
|
```
|
|
188
218
|
|
|
189
|
-
|
|
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());
|
|
239
|
+
|
|
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();
|
|
245
|
+
```
|
|
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
|
+
|
|
255
|
+
```sh
|
|
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'));
|
|
272
|
+
```
|
|
273
|
+
|
|
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.
|
|
190
284
|
|
|
191
285
|
## How it works
|
|
192
286
|
|
|
193
|
-
The JavaScript layer
|
|
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.
|
|
194
288
|
|
|
195
289
|
## License
|
|
196
290
|
|