react-native-web-serial-api 0.0.3 → 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/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 -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
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
AppState,
|
|
4
|
-
FlatList,
|
|
5
|
-
StyleSheet,
|
|
6
|
-
Text,
|
|
7
|
-
TouchableOpacity,
|
|
8
|
-
View,
|
|
9
|
-
} from 'react-native';
|
|
10
|
-
import type {SerialPort} from 'react-native-web-serial-api';
|
|
11
|
-
import {serial, UsbSerial} from 'react-native-web-serial-api';
|
|
12
|
-
import {AppBar} from '../components/AppBar';
|
|
13
|
-
import {colors} from '../theme';
|
|
14
|
-
|
|
15
|
-
type Props = {
|
|
16
|
-
onSelect: (port: SerialPort) => void;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// One row in the device list: a probed USB-serial port, which may or may not be
|
|
20
|
-
// accessible yet (Android USB permission).
|
|
21
|
-
type DeviceRow = {
|
|
22
|
-
deviceId: number;
|
|
23
|
-
portNumber: number;
|
|
24
|
-
usbVendorId: number;
|
|
25
|
-
usbProductId: number;
|
|
26
|
-
hasPermission: boolean;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function hex4(n: number | undefined): string {
|
|
30
|
-
return (n ?? 0).toString(16).toUpperCase().padStart(4, '0');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// The Web Serial API only exposes VID/PID (not the driver/chip class), so we
|
|
34
|
-
// derive a best-effort label from known USB-serial vendor IDs.
|
|
35
|
-
function chipLabel(vendorId: number | undefined): string {
|
|
36
|
-
switch (vendorId) {
|
|
37
|
-
case 0x0403:
|
|
38
|
-
return 'FTDI';
|
|
39
|
-
case 0x10c4:
|
|
40
|
-
return 'CP210x';
|
|
41
|
-
case 0x1a86:
|
|
42
|
-
return 'CH34x';
|
|
43
|
-
case 0x067b:
|
|
44
|
-
return 'Prolific';
|
|
45
|
-
default:
|
|
46
|
-
return 'USB serial';
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Native (Android) gives us every probed port plus its permission state, so we
|
|
51
|
-
// can show plugged-in-but-unpermitted devices too. On web that lower-level
|
|
52
|
-
// module is unavailable; fall back to serial.getPorts() (already permitted).
|
|
53
|
-
function nativeUsb() {
|
|
54
|
-
try {
|
|
55
|
-
return UsbSerial.getUsbSerial();
|
|
56
|
-
} catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function DevicesScreen({onSelect}: Props) {
|
|
62
|
-
const [rows, setRows] = React.useState<DeviceRow[]>([]);
|
|
63
|
-
const [error, setError] = React.useState<string | null>(null);
|
|
64
|
-
|
|
65
|
-
const refresh = React.useCallback(async () => {
|
|
66
|
-
setError(null);
|
|
67
|
-
try {
|
|
68
|
-
const usb = nativeUsb();
|
|
69
|
-
if (usb) {
|
|
70
|
-
// Full enumeration incl. unpermitted devices.
|
|
71
|
-
setRows((await usb.findAllDrivers()) as DeviceRow[]);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if (!serial) {
|
|
75
|
-
setError('Web Serial API is not available on this platform.');
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
// Web: only already-granted ports are visible; all are permitted.
|
|
79
|
-
const ports = await serial.getPorts();
|
|
80
|
-
setRows(
|
|
81
|
-
ports.map(p => {
|
|
82
|
-
const info = p.getInfo();
|
|
83
|
-
return {
|
|
84
|
-
deviceId: -1,
|
|
85
|
-
portNumber: 0,
|
|
86
|
-
usbVendorId: info.usbVendorId ?? 0,
|
|
87
|
-
usbProductId: info.usbProductId ?? 0,
|
|
88
|
-
hasPermission: true,
|
|
89
|
-
};
|
|
90
|
-
}),
|
|
91
|
-
);
|
|
92
|
-
} catch (e: any) {
|
|
93
|
-
setError(e?.message ?? String(e));
|
|
94
|
-
}
|
|
95
|
-
}, []);
|
|
96
|
-
|
|
97
|
-
React.useEffect(() => {
|
|
98
|
-
refresh();
|
|
99
|
-
if (!serial) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
// Auto-refresh on attach, detach, AND permission-grant (the library emits
|
|
103
|
-
// "connect" for all three), so the list stays current without manual taps.
|
|
104
|
-
serial.addEventListener('connect', refresh);
|
|
105
|
-
serial.addEventListener('disconnect', refresh);
|
|
106
|
-
return () => {
|
|
107
|
-
serial.removeEventListener('connect', refresh);
|
|
108
|
-
serial.removeEventListener('disconnect', refresh);
|
|
109
|
-
};
|
|
110
|
-
}, [refresh]);
|
|
111
|
-
|
|
112
|
-
// Resolve the SerialPort for an already-permitted row and proceed.
|
|
113
|
-
const openPermitted = React.useCallback(
|
|
114
|
-
async (row: DeviceRow) => {
|
|
115
|
-
setError(null);
|
|
116
|
-
try {
|
|
117
|
-
const ports = await serial.getPorts();
|
|
118
|
-
const match =
|
|
119
|
-
ports.find(p => {
|
|
120
|
-
const info = p.getInfo();
|
|
121
|
-
return (
|
|
122
|
-
info.usbVendorId === row.usbVendorId &&
|
|
123
|
-
info.usbProductId === row.usbProductId
|
|
124
|
-
);
|
|
125
|
-
}) ?? ports[0];
|
|
126
|
-
if (match) {
|
|
127
|
-
onSelect(match);
|
|
128
|
-
} else {
|
|
129
|
-
setError('Device is no longer available.');
|
|
130
|
-
}
|
|
131
|
-
} catch (e: any) {
|
|
132
|
-
setError(e?.message ?? String(e));
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
[onSelect],
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
// Tap on an unpermitted row: request Android USB permission. On grant,
|
|
139
|
-
// refresh immediately.
|
|
140
|
-
const grantPermission = React.useCallback(
|
|
141
|
-
async (row: DeviceRow) => {
|
|
142
|
-
setError(null);
|
|
143
|
-
const usb = nativeUsb();
|
|
144
|
-
if (!usb) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
try {
|
|
148
|
-
await usb.requestPermission(row.deviceId);
|
|
149
|
-
} catch (e: any) {
|
|
150
|
-
setError(e?.message ?? String(e));
|
|
151
|
-
}
|
|
152
|
-
refresh();
|
|
153
|
-
},
|
|
154
|
-
[refresh],
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
const requestNew = React.useCallback(async () => {
|
|
158
|
-
setError(null);
|
|
159
|
-
try {
|
|
160
|
-
const port = await serial.requestPort();
|
|
161
|
-
onSelect(port);
|
|
162
|
-
} catch (e: any) {
|
|
163
|
-
// user cancelled the picker, or no device
|
|
164
|
-
setError(e?.message ?? String(e));
|
|
165
|
-
}
|
|
166
|
-
}, [onSelect]);
|
|
167
|
-
|
|
168
|
-
// Refresh device list when app returns to foreground (covers system dialog grants)
|
|
169
|
-
React.useEffect(() => {
|
|
170
|
-
const handleAppStateChange = (state: string) => {
|
|
171
|
-
if (state === 'active') {
|
|
172
|
-
refresh();
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
const subscription = AppState.addEventListener(
|
|
176
|
-
'change',
|
|
177
|
-
handleAppStateChange,
|
|
178
|
-
);
|
|
179
|
-
return () => {
|
|
180
|
-
subscription.remove();
|
|
181
|
-
};
|
|
182
|
-
}, [refresh]);
|
|
183
|
-
|
|
184
|
-
return (
|
|
185
|
-
<View style={styles.container}>
|
|
186
|
-
<AppBar
|
|
187
|
-
title="Simple USB Terminal"
|
|
188
|
-
menu={[
|
|
189
|
-
{key: 'refresh', title: 'Refresh Devices', onPress: refresh},
|
|
190
|
-
{key: 'request', title: 'Connect new device…', onPress: requestNew},
|
|
191
|
-
]}
|
|
192
|
-
/>
|
|
193
|
-
|
|
194
|
-
<View style={styles.header}>
|
|
195
|
-
<Text style={styles.headerText}>USB Devices</Text>
|
|
196
|
-
</View>
|
|
197
|
-
|
|
198
|
-
{error ? <Text style={styles.error}>{error}</Text> : null}
|
|
199
|
-
|
|
200
|
-
<FlatList
|
|
201
|
-
data={rows}
|
|
202
|
-
keyExtractor={(r, i) => `${r.deviceId}:${r.portNumber}:${i}`}
|
|
203
|
-
ListEmptyComponent={
|
|
204
|
-
<Text style={styles.empty}>{'<no USB devices found>'}</Text>
|
|
205
|
-
}
|
|
206
|
-
renderItem={({item}) => (
|
|
207
|
-
<TouchableOpacity
|
|
208
|
-
style={styles.item}
|
|
209
|
-
onPress={() =>
|
|
210
|
-
item.hasPermission ? openPermitted(item) : grantPermission(item)
|
|
211
|
-
}>
|
|
212
|
-
<Text style={[styles.text1, !item.hasPermission && styles.dimmed]}>
|
|
213
|
-
{chipLabel(item.usbVendorId)}
|
|
214
|
-
{item.hasPermission ? '' : ' 🔒 tap to allow'}
|
|
215
|
-
</Text>
|
|
216
|
-
<Text style={[styles.text2, !item.hasPermission && styles.dimmed]}>
|
|
217
|
-
{`Vendor ${hex4(item.usbVendorId)}, Product ${hex4(
|
|
218
|
-
item.usbProductId,
|
|
219
|
-
)}`}
|
|
220
|
-
</Text>
|
|
221
|
-
</TouchableOpacity>
|
|
222
|
-
)}
|
|
223
|
-
/>
|
|
224
|
-
</View>
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const styles = StyleSheet.create({
|
|
229
|
-
container: {flex: 1, backgroundColor: colors.background},
|
|
230
|
-
header: {
|
|
231
|
-
backgroundColor: colors.divider,
|
|
232
|
-
paddingVertical: 12,
|
|
233
|
-
alignItems: 'center',
|
|
234
|
-
},
|
|
235
|
-
headerText: {fontSize: 16, color: colors.text},
|
|
236
|
-
error: {color: '#c62828', padding: 12},
|
|
237
|
-
empty: {
|
|
238
|
-
fontSize: 18,
|
|
239
|
-
textAlign: 'center',
|
|
240
|
-
color: colors.textSecondary,
|
|
241
|
-
marginTop: 24,
|
|
242
|
-
},
|
|
243
|
-
item: {paddingVertical: 8, paddingHorizontal: 12},
|
|
244
|
-
text1: {fontSize: 16, color: colors.text, marginTop: 4, marginHorizontal: 12},
|
|
245
|
-
text2: {
|
|
246
|
-
fontSize: 13,
|
|
247
|
-
color: colors.textSecondary,
|
|
248
|
-
marginHorizontal: 20,
|
|
249
|
-
marginBottom: 4,
|
|
250
|
-
},
|
|
251
|
-
dimmed: {opacity: 0.5},
|
|
252
|
-
});
|