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.
Files changed (119) hide show
  1. package/README.md +23 -0
  2. package/TESTING.md +301 -0
  3. package/android/build.gradle +2 -2
  4. package/lib/commonjs/UsbSerial.js +58 -26
  5. package/lib/commonjs/UsbSerial.js.map +1 -1
  6. package/lib/commonjs/WebSerial.js +169 -57
  7. package/lib/commonjs/WebSerial.js.map +1 -1
  8. package/lib/commonjs/index.js +13 -1
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/lib/dom-exception.js +176 -0
  11. package/lib/commonjs/lib/dom-exception.js.map +1 -0
  12. package/lib/commonjs/lib/event-target.js +138 -0
  13. package/lib/commonjs/lib/event-target.js.map +1 -0
  14. package/lib/commonjs/lib/promise.js +23 -0
  15. package/lib/commonjs/lib/promise.js.map +1 -0
  16. package/lib/commonjs/testing/index.js +70 -0
  17. package/lib/commonjs/testing/index.js.map +1 -0
  18. package/lib/commonjs/testing/install.js +54 -0
  19. package/lib/commonjs/testing/install.js.map +1 -0
  20. package/lib/commonjs/testing/serial-device.js +164 -0
  21. package/lib/commonjs/testing/serial-device.js.map +1 -0
  22. package/lib/commonjs/testing/virtual-serial.js +615 -0
  23. package/lib/commonjs/testing/virtual-serial.js.map +1 -0
  24. package/lib/commonjs/transport.js +61 -0
  25. package/lib/commonjs/transport.js.map +1 -0
  26. package/lib/typescript/src/UsbSerial.d.ts +24 -67
  27. package/lib/typescript/src/UsbSerial.d.ts.map +1 -1
  28. package/lib/typescript/src/WebSerial.d.ts +11 -2
  29. package/lib/typescript/src/WebSerial.d.ts.map +1 -1
  30. package/lib/typescript/src/index.d.ts +2 -0
  31. package/lib/typescript/src/index.d.ts.map +1 -1
  32. package/lib/typescript/src/lib/dom-exception.d.ts +100 -0
  33. package/lib/typescript/src/lib/dom-exception.d.ts.map +1 -0
  34. package/lib/typescript/src/lib/event-target.d.ts +53 -0
  35. package/lib/typescript/src/lib/event-target.d.ts.map +1 -0
  36. package/lib/typescript/src/lib/promise.d.ts +11 -0
  37. package/lib/typescript/src/lib/promise.d.ts.map +1 -0
  38. package/lib/typescript/src/testing/index.d.ts +23 -0
  39. package/lib/typescript/src/testing/index.d.ts.map +1 -0
  40. package/lib/typescript/src/testing/install.d.ts +25 -0
  41. package/lib/typescript/src/testing/install.d.ts.map +1 -0
  42. package/lib/typescript/src/testing/serial-device.d.ts +127 -0
  43. package/lib/typescript/src/testing/serial-device.d.ts.map +1 -0
  44. package/lib/typescript/src/testing/virtual-serial.d.ts +205 -0
  45. package/lib/typescript/src/testing/virtual-serial.d.ts.map +1 -0
  46. package/lib/typescript/src/transport.d.ts +131 -0
  47. package/lib/typescript/src/transport.d.ts.map +1 -0
  48. package/package.json +38 -2
  49. package/src/UsbSerial.ts +65 -90
  50. package/src/WebSerial.ts +227 -88
  51. package/src/index.ts +2 -7
  52. package/src/lib/dom-exception.ts +129 -60
  53. package/src/lib/event-target.ts +46 -21
  54. package/src/lib/promise.ts +7 -7
  55. package/src/testing/index.ts +42 -0
  56. package/src/testing/install.ts +65 -0
  57. package/src/testing/serial-device.ts +193 -0
  58. package/src/testing/virtual-serial.ts +801 -0
  59. package/src/transport.ts +200 -0
  60. package/babel.config.js +0 -3
  61. package/biome.json +0 -35
  62. package/example/.watchmanconfig +0 -1
  63. package/example/App.tsx +0 -71
  64. package/example/__tests__/App.test.tsx +0 -16
  65. package/example/__tests__/connectEvents.test.tsx +0 -81
  66. package/example/__tests__/getPorts.test.tsx +0 -140
  67. package/example/android/app/build.gradle +0 -120
  68. package/example/android/app/debug.keystore +0 -0
  69. package/example/android/app/proguard-rules.pro +0 -10
  70. package/example/android/app/src/debug/AndroidManifest.xml +0 -9
  71. package/example/android/app/src/main/AndroidManifest.xml +0 -38
  72. package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +0 -22
  73. package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +0 -41
  74. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -37
  75. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  76. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  77. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  78. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  79. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  80. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  81. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  82. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  83. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  84. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  85. package/example/android/app/src/main/res/values/strings.xml +0 -3
  86. package/example/android/app/src/main/res/values/styles.xml +0 -9
  87. package/example/android/build.gradle +0 -22
  88. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  89. package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  90. package/example/android/gradle.properties +0 -47
  91. package/example/android/gradlew +0 -252
  92. package/example/android/gradlew.bat +0 -94
  93. package/example/android/settings.gradle +0 -6
  94. package/example/app.json +0 -4
  95. package/example/babel.config.js +0 -21
  96. package/example/biome.json +0 -47
  97. package/example/deploy.sh +0 -11
  98. package/example/index.html +0 -26
  99. package/example/index.js +0 -9
  100. package/example/index.web.js +0 -8
  101. package/example/jest.config.js +0 -12
  102. package/example/metro.config.js +0 -58
  103. package/example/package-lock.json +0 -14510
  104. package/example/package.json +0 -48
  105. package/example/react-native.config.js +0 -17
  106. package/example/src/components/AppBar.tsx +0 -73
  107. package/example/src/components/Menu.tsx +0 -90
  108. package/example/src/components/SingleChoiceDialog.tsx +0 -120
  109. package/example/src/screens/ConnectScreen.tsx +0 -195
  110. package/example/src/screens/DevicesScreen.tsx +0 -252
  111. package/example/src/screens/TerminalScreen.tsx +0 -572
  112. package/example/src/settings.ts +0 -43
  113. package/example/src/theme.ts +0 -19
  114. package/example/src/util/TextUtil.ts +0 -129
  115. package/example/tsconfig.json +0 -10
  116. package/example/vite.config.mjs +0 -55
  117. package/scripts/deploy-release.sh +0 -127
  118. package/tsconfig.build.json +0 -7
  119. 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
- });