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.
Files changed (120) hide show
  1. package/README.md +23 -0
  2. package/TESTING.md +301 -0
  3. package/android/build.gradle +2 -2
  4. package/android/src/main/java/dev/webserialapi/NativeUsbSerialModule.java +7 -1
  5. package/lib/commonjs/UsbSerial.js +58 -26
  6. package/lib/commonjs/UsbSerial.js.map +1 -1
  7. package/lib/commonjs/WebSerial.js +169 -57
  8. package/lib/commonjs/WebSerial.js.map +1 -1
  9. package/lib/commonjs/index.js +13 -1
  10. package/lib/commonjs/index.js.map +1 -1
  11. package/lib/commonjs/lib/dom-exception.js +176 -0
  12. package/lib/commonjs/lib/dom-exception.js.map +1 -0
  13. package/lib/commonjs/lib/event-target.js +138 -0
  14. package/lib/commonjs/lib/event-target.js.map +1 -0
  15. package/lib/commonjs/lib/promise.js +23 -0
  16. package/lib/commonjs/lib/promise.js.map +1 -0
  17. package/lib/commonjs/testing/index.js +70 -0
  18. package/lib/commonjs/testing/index.js.map +1 -0
  19. package/lib/commonjs/testing/install.js +54 -0
  20. package/lib/commonjs/testing/install.js.map +1 -0
  21. package/lib/commonjs/testing/serial-device.js +164 -0
  22. package/lib/commonjs/testing/serial-device.js.map +1 -0
  23. package/lib/commonjs/testing/virtual-serial.js +615 -0
  24. package/lib/commonjs/testing/virtual-serial.js.map +1 -0
  25. package/lib/commonjs/transport.js +61 -0
  26. package/lib/commonjs/transport.js.map +1 -0
  27. package/lib/typescript/src/UsbSerial.d.ts +24 -67
  28. package/lib/typescript/src/UsbSerial.d.ts.map +1 -1
  29. package/lib/typescript/src/WebSerial.d.ts +11 -2
  30. package/lib/typescript/src/WebSerial.d.ts.map +1 -1
  31. package/lib/typescript/src/index.d.ts +2 -0
  32. package/lib/typescript/src/index.d.ts.map +1 -1
  33. package/lib/typescript/src/lib/dom-exception.d.ts +100 -0
  34. package/lib/typescript/src/lib/dom-exception.d.ts.map +1 -0
  35. package/lib/typescript/src/lib/event-target.d.ts +53 -0
  36. package/lib/typescript/src/lib/event-target.d.ts.map +1 -0
  37. package/lib/typescript/src/lib/promise.d.ts +11 -0
  38. package/lib/typescript/src/lib/promise.d.ts.map +1 -0
  39. package/lib/typescript/src/testing/index.d.ts +23 -0
  40. package/lib/typescript/src/testing/index.d.ts.map +1 -0
  41. package/lib/typescript/src/testing/install.d.ts +25 -0
  42. package/lib/typescript/src/testing/install.d.ts.map +1 -0
  43. package/lib/typescript/src/testing/serial-device.d.ts +127 -0
  44. package/lib/typescript/src/testing/serial-device.d.ts.map +1 -0
  45. package/lib/typescript/src/testing/virtual-serial.d.ts +205 -0
  46. package/lib/typescript/src/testing/virtual-serial.d.ts.map +1 -0
  47. package/lib/typescript/src/transport.d.ts +131 -0
  48. package/lib/typescript/src/transport.d.ts.map +1 -0
  49. package/package.json +38 -2
  50. package/src/UsbSerial.ts +65 -90
  51. package/src/WebSerial.ts +227 -88
  52. package/src/index.ts +2 -7
  53. package/src/lib/dom-exception.ts +129 -60
  54. package/src/lib/event-target.ts +46 -21
  55. package/src/lib/promise.ts +7 -7
  56. package/src/testing/index.ts +42 -0
  57. package/src/testing/install.ts +65 -0
  58. package/src/testing/serial-device.ts +193 -0
  59. package/src/testing/virtual-serial.ts +801 -0
  60. package/src/transport.ts +200 -0
  61. package/babel.config.js +0 -3
  62. package/biome.json +0 -35
  63. package/example/.watchmanconfig +0 -1
  64. package/example/App.tsx +0 -71
  65. package/example/__tests__/App.test.tsx +0 -16
  66. package/example/__tests__/connectEvents.test.tsx +0 -81
  67. package/example/__tests__/getPorts.test.tsx +0 -140
  68. package/example/android/app/build.gradle +0 -120
  69. package/example/android/app/debug.keystore +0 -0
  70. package/example/android/app/proguard-rules.pro +0 -10
  71. package/example/android/app/src/debug/AndroidManifest.xml +0 -9
  72. package/example/android/app/src/main/AndroidManifest.xml +0 -38
  73. package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +0 -22
  74. package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +0 -41
  75. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -37
  76. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  77. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  78. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  79. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  80. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  81. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  82. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  83. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  84. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  85. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  86. package/example/android/app/src/main/res/values/strings.xml +0 -3
  87. package/example/android/app/src/main/res/values/styles.xml +0 -9
  88. package/example/android/build.gradle +0 -22
  89. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  90. package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  91. package/example/android/gradle.properties +0 -47
  92. package/example/android/gradlew +0 -252
  93. package/example/android/gradlew.bat +0 -94
  94. package/example/android/settings.gradle +0 -6
  95. package/example/app.json +0 -4
  96. package/example/babel.config.js +0 -21
  97. package/example/biome.json +0 -47
  98. package/example/deploy.sh +0 -11
  99. package/example/index.html +0 -26
  100. package/example/index.js +0 -9
  101. package/example/index.web.js +0 -8
  102. package/example/jest.config.js +0 -12
  103. package/example/metro.config.js +0 -58
  104. package/example/package-lock.json +0 -14510
  105. package/example/package.json +0 -48
  106. package/example/react-native.config.js +0 -17
  107. package/example/src/components/AppBar.tsx +0 -73
  108. package/example/src/components/Menu.tsx +0 -90
  109. package/example/src/components/SingleChoiceDialog.tsx +0 -120
  110. package/example/src/screens/ConnectScreen.tsx +0 -195
  111. package/example/src/screens/DevicesScreen.tsx +0 -248
  112. package/example/src/screens/TerminalScreen.tsx +0 -564
  113. package/example/src/settings.ts +0 -43
  114. package/example/src/theme.ts +0 -19
  115. package/example/src/util/TextUtil.ts +0 -129
  116. package/example/tsconfig.json +0 -10
  117. package/example/vite.config.mjs +0 -55
  118. package/scripts/deploy-release.sh +0 -127
  119. package/tsconfig.build.json +0 -7
  120. package/tsconfig.json +0 -20
@@ -1,248 +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, the
139
- // library emits "connect" -> refresh() runs -> the row becomes permitted.
140
- const grantPermission = React.useCallback(async (row: DeviceRow) => {
141
- setError(null);
142
- const usb = nativeUsb();
143
- if (!usb) {
144
- return;
145
- }
146
- try {
147
- await usb.requestPermission(row.deviceId);
148
- } catch (e: any) {
149
- setError(e?.message ?? String(e));
150
- } finally {
151
- // Always refresh after permission attempt (fixes stale state)
152
- refresh();
153
- }
154
- }, [refresh]);
155
-
156
- const requestNew = React.useCallback(async () => {
157
- setError(null);
158
- try {
159
- const port = await serial.requestPort();
160
- onSelect(port);
161
- } catch (e: any) {
162
- // user cancelled the picker, or no device
163
- setError(e?.message ?? String(e));
164
- }
165
- }, [onSelect]);
166
-
167
- // Refresh device list when app returns to foreground (covers system dialog grants)
168
- React.useEffect(() => {
169
- const handleAppStateChange = (state: string) => {
170
- if (state === 'active') {
171
- refresh();
172
- }
173
- };
174
- const subscription = AppState.addEventListener('change', handleAppStateChange);
175
- return () => {
176
- subscription.remove();
177
- };
178
- }, [refresh]);
179
-
180
- return (
181
- <View style={styles.container}>
182
- <AppBar
183
- title="Simple USB Terminal"
184
- menu={[
185
- {key: 'refresh', title: 'Refresh Devices', onPress: refresh},
186
- {key: 'request', title: 'Connect new device…', onPress: requestNew},
187
- ]}
188
- />
189
-
190
- <View style={styles.header}>
191
- <Text style={styles.headerText}>USB Devices</Text>
192
- </View>
193
-
194
- {error ? <Text style={styles.error}>{error}</Text> : null}
195
-
196
- <FlatList
197
- data={rows}
198
- keyExtractor={(r, i) => `${r.deviceId}:${r.portNumber}:${i}`}
199
- ListEmptyComponent={
200
- <Text style={styles.empty}>{'<no USB devices found>'}</Text>
201
- }
202
- renderItem={({item}) => (
203
- <TouchableOpacity
204
- style={styles.item}
205
- onPress={() =>
206
- item.hasPermission ? openPermitted(item) : grantPermission(item)
207
- }>
208
- <Text style={[styles.text1, !item.hasPermission && styles.dimmed]}>
209
- {chipLabel(item.usbVendorId)}
210
- {item.hasPermission ? '' : ' 🔒 tap to allow'}
211
- </Text>
212
- <Text style={[styles.text2, !item.hasPermission && styles.dimmed]}>
213
- {`Vendor ${hex4(item.usbVendorId)}, Product ${hex4(
214
- item.usbProductId,
215
- )}`}
216
- </Text>
217
- </TouchableOpacity>
218
- )}
219
- />
220
- </View>
221
- );
222
- }
223
-
224
- const styles = StyleSheet.create({
225
- container: {flex: 1, backgroundColor: colors.background},
226
- header: {
227
- backgroundColor: colors.divider,
228
- paddingVertical: 12,
229
- alignItems: 'center',
230
- },
231
- headerText: {fontSize: 16, color: colors.text},
232
- error: {color: '#c62828', padding: 12},
233
- empty: {
234
- fontSize: 18,
235
- textAlign: 'center',
236
- color: colors.textSecondary,
237
- marginTop: 24,
238
- },
239
- item: {paddingVertical: 8, paddingHorizontal: 12},
240
- text1: {fontSize: 16, color: colors.text, marginTop: 4, marginHorizontal: 12},
241
- text2: {
242
- fontSize: 13,
243
- color: colors.textSecondary,
244
- marginHorizontal: 20,
245
- marginBottom: 4,
246
- },
247
- dimmed: {opacity: 0.5},
248
- });