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
|
@@ -31,9 +31,12 @@ import com.facebook.react.bridge.BaseActivityEventListener;
|
|
|
31
31
|
|
|
32
32
|
import java.util.ArrayList;
|
|
33
33
|
import java.util.EnumSet;
|
|
34
|
-
import java.util.HashMap;
|
|
35
34
|
import java.util.List;
|
|
36
35
|
import java.util.Map;
|
|
36
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
37
|
+
import java.util.concurrent.ExecutorService;
|
|
38
|
+
import java.util.concurrent.Executors;
|
|
39
|
+
import java.util.concurrent.atomic.AtomicInteger;
|
|
37
40
|
|
|
38
41
|
public class NativeUsbSerialModule extends NativeUsbSerialSpec {
|
|
39
42
|
|
|
@@ -43,17 +46,30 @@ public class NativeUsbSerialModule extends NativeUsbSerialSpec {
|
|
|
43
46
|
|
|
44
47
|
private final UsbManager usbManager;
|
|
45
48
|
|
|
49
|
+
// These maps are mutated from several threads — TurboModule calls (native
|
|
50
|
+
// modules thread), the broadcast receivers (main thread) and the
|
|
51
|
+
// SerialInputOutputManager listener (its own IO thread) — so they must be
|
|
52
|
+
// concurrent to avoid corruption / ConcurrentModificationException.
|
|
46
53
|
// key: "deviceId:portNumber"
|
|
47
|
-
private final Map<String, UsbSerialPort> openPorts = new
|
|
48
|
-
private final Map<String, UsbDeviceConnection> openConnections = new
|
|
49
|
-
private final Map<String, SerialInputOutputManager> ioManagers = new
|
|
54
|
+
private final Map<String, UsbSerialPort> openPorts = new ConcurrentHashMap<>();
|
|
55
|
+
private final Map<String, UsbDeviceConnection> openConnections = new ConcurrentHashMap<>();
|
|
56
|
+
private final Map<String, SerialInputOutputManager> ioManagers = new ConcurrentHashMap<>();
|
|
50
57
|
|
|
51
|
-
// key: requestCode
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
// key: requestCode. requestPermission() is reachable from both the native
|
|
59
|
+
// modules thread (direct JS call) and the main thread (onActivityResult ->
|
|
60
|
+
// requestPermission), so the counter must be atomic to avoid two callers
|
|
61
|
+
// colliding on a request code and clobbering each other's pending promise.
|
|
62
|
+
private final Map<Integer, Promise> pendingPermissions = new ConcurrentHashMap<>();
|
|
63
|
+
private final AtomicInteger nextRequestCode = new AtomicInteger(0);
|
|
64
|
+
|
|
65
|
+
// Resumes blocking USB work (open/setParameters) off the main thread when a
|
|
66
|
+
// permission grant arrives on the broadcast-receiver (main) thread.
|
|
67
|
+
private final ExecutorService backgroundExecutor = Executors.newSingleThreadExecutor();
|
|
54
68
|
|
|
55
69
|
private static final int PORT_PICKER_REQUEST_CODE = 0xAB8465;
|
|
56
|
-
|
|
70
|
+
// Written from showPortPicker (module thread), read/cleared from
|
|
71
|
+
// onActivityResult (main thread) — volatile for cross-thread visibility.
|
|
72
|
+
private volatile Promise pendingPortPickerPromise = null;
|
|
57
73
|
|
|
58
74
|
private final ActivityEventListener activityEventListener = new BaseActivityEventListener() {
|
|
59
75
|
@Override
|
|
@@ -177,7 +193,41 @@ public class NativeUsbSerialModule extends NativeUsbSerialSpec {
|
|
|
177
193
|
IntentFilter usbFilter = new IntentFilter();
|
|
178
194
|
usbFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
|
179
195
|
usbFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
|
|
180
|
-
|
|
196
|
+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
|
|
197
|
+
reactContext.registerReceiver(usbStateReceiver, usbFilter, Context.RECEIVER_NOT_EXPORTED);
|
|
198
|
+
} else {
|
|
199
|
+
reactContext.registerReceiver(usbStateReceiver, usbFilter);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Release everything acquired in the constructor and during the module's
|
|
205
|
+
* life. Without this, every JS reload / context teardown leaks the three
|
|
206
|
+
* registered receivers (kept alive against a dead ReactApplicationContext)
|
|
207
|
+
* and any open USB ports — and stacks duplicate connect/disconnect/data
|
|
208
|
+
* events from the orphaned receivers.
|
|
209
|
+
*/
|
|
210
|
+
@Override
|
|
211
|
+
public void invalidate() {
|
|
212
|
+
ReactApplicationContext ctx = getReactApplicationContext();
|
|
213
|
+
try { ctx.unregisterReceiver(permissionReceiver); } catch (Exception ignored) {}
|
|
214
|
+
try { ctx.unregisterReceiver(usbStateReceiver); } catch (Exception ignored) {}
|
|
215
|
+
try { ctx.removeActivityEventListener(activityEventListener); } catch (Exception ignored) {}
|
|
216
|
+
|
|
217
|
+
for (String key : new ArrayList<>(ioManagers.keySet())) {
|
|
218
|
+
SerialInputOutputManager ioManager = ioManagers.remove(key);
|
|
219
|
+
try { if (ioManager != null) ioManager.stop(); } catch (Exception ignored) {}
|
|
220
|
+
}
|
|
221
|
+
for (String key : new ArrayList<>(openPorts.keySet())) {
|
|
222
|
+
UsbSerialPort port = openPorts.remove(key);
|
|
223
|
+
UsbDeviceConnection connection = openConnections.remove(key);
|
|
224
|
+
try { if (port != null) port.close(); } catch (Exception ignored) {}
|
|
225
|
+
try { if (connection != null) connection.close(); } catch (Exception ignored) {}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
backgroundExecutor.shutdown();
|
|
229
|
+
|
|
230
|
+
super.invalidate();
|
|
181
231
|
}
|
|
182
232
|
|
|
183
233
|
// Helper to avoid implementing all Promise methods in anonymous classes
|
|
@@ -294,7 +344,11 @@ public class NativeUsbSerialModule extends NativeUsbSerialSpec {
|
|
|
294
344
|
public void onResolve(Object value) {
|
|
295
345
|
Boolean granted = (Boolean) value;
|
|
296
346
|
if (granted != null && granted) {
|
|
297
|
-
|
|
347
|
+
// onResolve runs on the permission broadcast receiver's
|
|
348
|
+
// (main) thread; openDevice()/port.open() do blocking USB
|
|
349
|
+
// control transfers, so resume off the main thread.
|
|
350
|
+
backgroundExecutor.execute(() ->
|
|
351
|
+
open(fDeviceId, fPortNumber, fBaudRate, fDataBits, fStopBits, fParity, promise));
|
|
298
352
|
} else {
|
|
299
353
|
promise.reject("PERMISSION_DENIED", "USB permission denied");
|
|
300
354
|
}
|
|
@@ -640,6 +694,7 @@ public class NativeUsbSerialModule extends NativeUsbSerialSpec {
|
|
|
640
694
|
|
|
641
695
|
@Override
|
|
642
696
|
public void requestPermission(double deviceId, Promise promise) {
|
|
697
|
+
int requestCode = -1;
|
|
643
698
|
try {
|
|
644
699
|
UsbSerialDriver driver = findDriver((int) deviceId);
|
|
645
700
|
if (driver == null) {
|
|
@@ -651,7 +706,7 @@ public class NativeUsbSerialModule extends NativeUsbSerialSpec {
|
|
|
651
706
|
return;
|
|
652
707
|
}
|
|
653
708
|
|
|
654
|
-
|
|
709
|
+
requestCode = nextRequestCode.getAndIncrement();
|
|
655
710
|
pendingPermissions.put(requestCode, promise);
|
|
656
711
|
|
|
657
712
|
Intent intent = new Intent(ACTION_USB_PERMISSION);
|
|
@@ -671,6 +726,8 @@ public class NativeUsbSerialModule extends NativeUsbSerialSpec {
|
|
|
671
726
|
);
|
|
672
727
|
usbManager.requestPermission(driver.getDevice(), permissionIntent);
|
|
673
728
|
} catch (Exception e) {
|
|
729
|
+
// Don't leave an orphaned pending promise behind if dispatch failed.
|
|
730
|
+
if (requestCode != -1) pendingPermissions.remove(requestCode);
|
|
674
731
|
promise.reject("REQUEST_PERMISSION_ERROR", e.getMessage(), e);
|
|
675
732
|
}
|
|
676
733
|
}
|
|
@@ -720,6 +777,12 @@ public class NativeUsbSerialModule extends NativeUsbSerialSpec {
|
|
|
720
777
|
} catch (ActivityNotFoundException e) {
|
|
721
778
|
pendingPortPickerPromise = null;
|
|
722
779
|
promise.reject("ACTIVITY_NOT_FOUND", e.getMessage(), e);
|
|
780
|
+
} catch (Exception e) {
|
|
781
|
+
// Any other failure to launch must also clear the pending slot,
|
|
782
|
+
// otherwise the picker is wedged forever (every future call rejects
|
|
783
|
+
// with PICKER_ALREADY_OPEN) and this promise never settles.
|
|
784
|
+
pendingPortPickerPromise = null;
|
|
785
|
+
promise.reject("PICKER_LAUNCH_FAILED", e.getMessage(), e);
|
|
723
786
|
}
|
|
724
787
|
}
|
|
725
788
|
|
|
@@ -96,41 +96,64 @@ public class PortPickerActivity extends AppCompatActivity {
|
|
|
96
96
|
return drivers;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// Guard against mismatched array lengths to avoid ArrayIndexOutOfBoundsException
|
|
100
|
-
int filterCount = Math.min(filterVendorIds.length,
|
|
101
|
-
filterProductIds != null ? filterProductIds.length : 0);
|
|
102
|
-
|
|
103
99
|
List<UsbSerialDriver> filtered = new ArrayList<>();
|
|
104
100
|
for (UsbSerialDriver driver : drivers) {
|
|
105
101
|
int vid = driver.getDevice().getVendorId();
|
|
106
102
|
int pid = driver.getDevice().getProductId();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
boolean pidMatch = filterProductIds[i] == -1 || pid == filterProductIds[i];
|
|
110
|
-
if (vidMatch && pidMatch) {
|
|
111
|
-
filtered.add(driver);
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
103
|
+
if (matchesAnyFilter(vid, pid, filterVendorIds, filterProductIds)) {
|
|
104
|
+
filtered.add(driver);
|
|
114
105
|
}
|
|
115
106
|
}
|
|
116
107
|
return filtered;
|
|
117
108
|
}
|
|
118
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Whether a device with the given vendor/product id matches any of the
|
|
112
|
+
* parallel {@code vendorIds}/{@code productIds} filter arrays. A
|
|
113
|
+
* {@code productId} of -1 is a vendor-only wildcard. Mismatched array
|
|
114
|
+
* lengths are tolerated (the shorter length wins), and empty/no filters
|
|
115
|
+
* match everything. Pure and Android-free so it can be unit tested directly.
|
|
116
|
+
*/
|
|
117
|
+
static boolean matchesAnyFilter(int vid, int pid, int[] vendorIds, int[] productIds) {
|
|
118
|
+
if (vendorIds == null || vendorIds.length == 0) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
// Guard against mismatched array lengths to avoid ArrayIndexOutOfBoundsException
|
|
122
|
+
int filterCount = Math.min(vendorIds.length,
|
|
123
|
+
productIds != null ? productIds.length : 0);
|
|
124
|
+
for (int i = 0; i < filterCount; i++) {
|
|
125
|
+
boolean vidMatch = vid == vendorIds[i];
|
|
126
|
+
boolean pidMatch = productIds[i] == -1 || pid == productIds[i];
|
|
127
|
+
if (vidMatch && pidMatch) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
119
134
|
private void showPickerDialog() {
|
|
120
|
-
|
|
121
|
-
this,
|
|
122
|
-
getFilteredDrivers(),
|
|
123
|
-
resolve(titleSelectPort, DEFAULT_TITLE_SELECT_PORT),
|
|
124
|
-
resolve(titleNoPortsAvailable, DEFAULT_TITLE_NO_PORTS_AVAILABLE),
|
|
125
|
-
resolve(messageNoPortsAvailable, DEFAULT_MESSAGE_NO_PORTS_AVAILABLE)
|
|
126
|
-
);
|
|
127
|
-
dialog.show(getSupportFragmentManager(), TAG_PICKER_DIALOG);
|
|
135
|
+
new PortPickerDialogFragment().show(getSupportFragmentManager(), TAG_PICKER_DIALOG);
|
|
128
136
|
}
|
|
129
137
|
|
|
130
138
|
private static String resolve(String override, String defaultValue) {
|
|
131
139
|
return (override != null && !override.isEmpty()) ? override : defaultValue;
|
|
132
140
|
}
|
|
133
141
|
|
|
142
|
+
// Resolved labels, read by the (possibly recreated) dialog fragment. The
|
|
143
|
+
// backing fields are restored from getIntent() in onCreate, so these survive
|
|
144
|
+
// a configuration change such as rotation.
|
|
145
|
+
String resolvedTitleSelectPort() {
|
|
146
|
+
return resolve(titleSelectPort, DEFAULT_TITLE_SELECT_PORT);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
String resolvedTitleNoPortsAvailable() {
|
|
150
|
+
return resolve(titleNoPortsAvailable, DEFAULT_TITLE_NO_PORTS_AVAILABLE);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
String resolvedMessageNoPortsAvailable() {
|
|
154
|
+
return resolve(messageNoPortsAvailable, DEFAULT_MESSAGE_NO_PORTS_AVAILABLE);
|
|
155
|
+
}
|
|
156
|
+
|
|
134
157
|
/** Called by the DialogFragment when the user selects a port. */
|
|
135
158
|
void onPortSelected(UsbDevice device, int portNumber) {
|
|
136
159
|
Intent result = new Intent();
|
|
@@ -153,42 +176,28 @@ public class PortPickerActivity extends AppCompatActivity {
|
|
|
153
176
|
}
|
|
154
177
|
|
|
155
178
|
// -------------------------------------------------------------------------
|
|
156
|
-
// Static DialogFragment – survives configuration changes such as rotation
|
|
179
|
+
// Static DialogFragment – survives configuration changes such as rotation.
|
|
180
|
+
// It deliberately keeps NO transient state of its own: on every (re)creation
|
|
181
|
+
// it rebuilds the driver list and labels from the host activity, whose own
|
|
182
|
+
// state is restored from its Intent. Stashing the non-Parcelable driver list
|
|
183
|
+
// and an Activity reference on the fragment (as before) loses them on the
|
|
184
|
+
// recreation that rotation triggers, leaving an empty / dead dialog.
|
|
157
185
|
// -------------------------------------------------------------------------
|
|
158
186
|
public static class PortPickerDialogFragment extends DialogFragment {
|
|
159
187
|
|
|
160
|
-
// Not passed via Bundle argument because UsbSerialDriver is not Serializable/Parcelable
|
|
161
|
-
private List<UsbSerialDriver> drivers;
|
|
162
|
-
private PortPickerActivity host;
|
|
163
|
-
private String titleSelectPort;
|
|
164
|
-
private String titleNoPortsAvailable;
|
|
165
|
-
private String messageNoPortsAvailable;
|
|
166
|
-
|
|
167
|
-
static PortPickerDialogFragment newInstance(
|
|
168
|
-
PortPickerActivity host,
|
|
169
|
-
List<UsbSerialDriver> drivers,
|
|
170
|
-
String titleSelectPort,
|
|
171
|
-
String titleNoPortsAvailable,
|
|
172
|
-
String messageNoPortsAvailable) {
|
|
173
|
-
PortPickerDialogFragment f = new PortPickerDialogFragment();
|
|
174
|
-
f.host = host;
|
|
175
|
-
f.drivers = drivers;
|
|
176
|
-
f.titleSelectPort = titleSelectPort;
|
|
177
|
-
f.titleNoPortsAvailable = titleNoPortsAvailable;
|
|
178
|
-
f.messageNoPortsAvailable = messageNoPortsAvailable;
|
|
179
|
-
return f;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
188
|
@Override
|
|
183
189
|
public android.app.Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
190
|
+
PortPickerActivity act = (PortPickerActivity) requireActivity();
|
|
184
191
|
Context ctx = requireContext();
|
|
185
192
|
|
|
193
|
+
List<UsbSerialDriver> drivers = act.getFilteredDrivers();
|
|
194
|
+
|
|
186
195
|
if (drivers == null || drivers.isEmpty()) {
|
|
187
196
|
return new AlertDialog.Builder(ctx)
|
|
188
|
-
.setTitle(
|
|
189
|
-
.setMessage(
|
|
190
|
-
.setPositiveButton(android.R.string.ok, (d, w) ->
|
|
191
|
-
.setOnCancelListener(d ->
|
|
197
|
+
.setTitle(act.resolvedTitleNoPortsAvailable())
|
|
198
|
+
.setMessage(act.resolvedMessageNoPortsAvailable())
|
|
199
|
+
.setPositiveButton(android.R.string.ok, (d, w) -> act.onPickerCancelled())
|
|
200
|
+
.setOnCancelListener(d -> act.onPickerCancelled())
|
|
192
201
|
.create();
|
|
193
202
|
}
|
|
194
203
|
|
|
@@ -215,21 +224,14 @@ public class PortPickerActivity extends AppCompatActivity {
|
|
|
215
224
|
}
|
|
216
225
|
|
|
217
226
|
return new AlertDialog.Builder(ctx)
|
|
218
|
-
.setTitle(
|
|
219
|
-
.setItems(labels.toArray(new String[0]), (d, which) ->
|
|
220
|
-
|
|
221
|
-
host.onPortSelected(
|
|
227
|
+
.setTitle(act.resolvedTitleSelectPort())
|
|
228
|
+
.setItems(labels.toArray(new String[0]), (d, which) ->
|
|
229
|
+
act.onPortSelected(
|
|
222
230
|
flatDrivers.get(which).getDevice(),
|
|
223
|
-
flatPortNumbers.get(which))
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
.setNegativeButton(android.R.string.cancel, (d, w) -> cancelAndFinish())
|
|
227
|
-
.setOnCancelListener(d -> cancelAndFinish())
|
|
231
|
+
flatPortNumbers.get(which)))
|
|
232
|
+
.setNegativeButton(android.R.string.cancel, (d, w) -> act.onPickerCancelled())
|
|
233
|
+
.setOnCancelListener(d -> act.onPickerCancelled())
|
|
228
234
|
.create();
|
|
229
235
|
}
|
|
230
|
-
|
|
231
|
-
private void cancelAndFinish() {
|
|
232
|
-
if (host != null) host.onPickerCancelled();
|
|
233
|
-
}
|
|
234
236
|
}
|
|
235
237
|
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* expose-serial-websocket — bridge a local serial port to a WebSocket so a
|
|
4
|
+
* browser / React Native app can drive it through `WebSocketSerialTransport`.
|
|
5
|
+
*
|
|
6
|
+
* This is a thin Node wrapper: argument parsing and the per-connection wiring
|
|
7
|
+
* live in the package's built `websocket/bridge` module (unit-tested with
|
|
8
|
+
* fakes); here we just supply the real `serialport` and `ws` objects.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
let bridge;
|
|
12
|
+
try {
|
|
13
|
+
bridge = require('../lib/commonjs/websocket/bridge.js');
|
|
14
|
+
} catch (_e) {
|
|
15
|
+
console.error(
|
|
16
|
+
'Could not load the built bridge module. Build the package first ' +
|
|
17
|
+
'(npm run prepare) and try again.',
|
|
18
|
+
);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const {attachBridge, parseBridgeArgs, USAGE} = bridge;
|
|
22
|
+
|
|
23
|
+
function requireOrExit(name) {
|
|
24
|
+
try {
|
|
25
|
+
return require(name);
|
|
26
|
+
} catch (_e) {
|
|
27
|
+
console.error(
|
|
28
|
+
`Missing optional dependency "${name}". Install it on this host:\n` +
|
|
29
|
+
` npm install ${name}\n`,
|
|
30
|
+
);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function main() {
|
|
36
|
+
const args = parseBridgeArgs(process.argv.slice(2), process.env);
|
|
37
|
+
|
|
38
|
+
if (args.help) {
|
|
39
|
+
console.log(USAGE);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (!args.port) {
|
|
43
|
+
console.error('Error: --port <path> is required.\n');
|
|
44
|
+
console.error(USAGE);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const {SerialPort} = requireOrExit('serialport');
|
|
49
|
+
const {WebSocketServer} = requireOrExit('ws');
|
|
50
|
+
|
|
51
|
+
const parseHexMaybe = value => {
|
|
52
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
if (typeof value === 'string') {
|
|
56
|
+
const trimmed = value.trim();
|
|
57
|
+
if (!trimmed) return undefined;
|
|
58
|
+
const base = /^0x/i.test(trimmed) ? 16 : 16;
|
|
59
|
+
const parsed = Number.parseInt(trimmed.replace(/^0x/i, ''), base);
|
|
60
|
+
if (Number.isFinite(parsed)) {
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const normalizeInfo = info => {
|
|
68
|
+
if (!info || typeof info !== 'object') {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const usbVendorId = parseHexMaybe(info.vendorId ?? info.usbVendorId);
|
|
72
|
+
const usbProductId = parseHexMaybe(info.productId ?? info.usbProductId);
|
|
73
|
+
const serialNumber =
|
|
74
|
+
typeof info.serialNumber === 'string' ? info.serialNumber : undefined;
|
|
75
|
+
if (
|
|
76
|
+
usbVendorId === undefined &&
|
|
77
|
+
usbProductId === undefined &&
|
|
78
|
+
serialNumber === undefined
|
|
79
|
+
) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return {usbVendorId, usbProductId, serialNumber};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let cachedPortInfo = null;
|
|
86
|
+
|
|
87
|
+
const refreshPortInfo = async () => {
|
|
88
|
+
try {
|
|
89
|
+
const list = await SerialPort.list();
|
|
90
|
+
const match = list.find(p => p.path === args.port);
|
|
91
|
+
cachedPortInfo = normalizeInfo(match);
|
|
92
|
+
} catch {
|
|
93
|
+
// ignore metadata refresh failures
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const serial = new SerialPort({path: args.port, baudRate: args.baudRate});
|
|
98
|
+
// Kick off metadata discovery in parallel; getPortInfo can still fall back to
|
|
99
|
+
// fields directly exposed by the opened serial instance.
|
|
100
|
+
void refreshPortInfo();
|
|
101
|
+
|
|
102
|
+
const getPortInfo = () => {
|
|
103
|
+
return (
|
|
104
|
+
cachedPortInfo ??
|
|
105
|
+
normalizeInfo({
|
|
106
|
+
vendorId: serial.vendorId,
|
|
107
|
+
productId: serial.productId,
|
|
108
|
+
serialNumber: serial.serialNumber,
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
let serialClosed = false;
|
|
114
|
+
|
|
115
|
+
serial.on('error', err => {
|
|
116
|
+
console.error(`serial error: ${err.message}`);
|
|
117
|
+
if (
|
|
118
|
+
err.message.includes('Disconnected') ||
|
|
119
|
+
err.message.includes('No such file')
|
|
120
|
+
) {
|
|
121
|
+
serialClosed = true;
|
|
122
|
+
if (active) {
|
|
123
|
+
active.close(1011, 'Serial port disconnected');
|
|
124
|
+
active = null;
|
|
125
|
+
}
|
|
126
|
+
process.exit(1); // or attempt to reopen
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
serial.on('close', () => {
|
|
130
|
+
console.error('Serial port closed unexpectedly');
|
|
131
|
+
serialClosed = true;
|
|
132
|
+
if (active) {
|
|
133
|
+
active.close(1011, 'Serial port closed');
|
|
134
|
+
active = null;
|
|
135
|
+
}
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const wss = new WebSocketServer({host: args.host, port: args.wsPort});
|
|
140
|
+
let active = null;
|
|
141
|
+
let detachCurrent = null;
|
|
142
|
+
|
|
143
|
+
wss.on('connection', ws => {
|
|
144
|
+
if (active) {
|
|
145
|
+
ws.close(1013, 'serial port already in use');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (!serial.isOpen || serialClosed) {
|
|
149
|
+
ws.close(1011, 'serial port not available');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
active = ws;
|
|
153
|
+
console.log('client connected');
|
|
154
|
+
const detach = attachBridge(serial, ws, {
|
|
155
|
+
log: m => console.error(m),
|
|
156
|
+
portInfo: getPortInfo,
|
|
157
|
+
});
|
|
158
|
+
detachCurrent = detach;
|
|
159
|
+
|
|
160
|
+
ws.on('close', () => {
|
|
161
|
+
if (detachCurrent) detachCurrent();
|
|
162
|
+
if (active === ws) active = null;
|
|
163
|
+
detachCurrent = null;
|
|
164
|
+
console.log('client disconnected');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
wss.on('listening', () => {
|
|
169
|
+
console.log(
|
|
170
|
+
`Serial port ${args.port} exposed on ws://${args.host}:${args.wsPort}`,
|
|
171
|
+
);
|
|
172
|
+
if (args.host === '0.0.0.0' || args.allowRemote) {
|
|
173
|
+
console.warn(
|
|
174
|
+
'⚠ Bound to a non-localhost address — your serial port is reachable ' +
|
|
175
|
+
'from the network. Only do this on trusted networks.',
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
wss.on('error', err => {
|
|
180
|
+
console.error(`WebSocket server error: ${err.message}`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
let shuttingDown = false;
|
|
185
|
+
const shutdown = () => {
|
|
186
|
+
if (shuttingDown) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
shuttingDown = true;
|
|
190
|
+
console.log('\nshutting down…');
|
|
191
|
+
try {
|
|
192
|
+
wss.close();
|
|
193
|
+
} catch (_e) {}
|
|
194
|
+
try {
|
|
195
|
+
if (serial.isOpen) {
|
|
196
|
+
serial.close();
|
|
197
|
+
}
|
|
198
|
+
} catch (_e) {}
|
|
199
|
+
process.exit(0);
|
|
200
|
+
};
|
|
201
|
+
process.on('SIGINT', shutdown);
|
|
202
|
+
process.on('SIGTERM', shutdown);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
main();
|
|
@@ -3,34 +3,37 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
|
|
6
|
+
Object.defineProperty(exports, "DataBits", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _transport.DataBits;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "Parity", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _transport.Parity;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "StopBits", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _transport.StopBits;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
exports.UsbSerialModule = void 0;
|
|
7
25
|
exports.getUsbSerial = getUsbSerial;
|
|
26
|
+
exports.resetUsbSerial = resetUsbSerial;
|
|
27
|
+
exports.setUsbSerial = setUsbSerial;
|
|
8
28
|
var _reactNative = require("react-native");
|
|
9
29
|
var _NativeUsbSerial = _interopRequireDefault(require("./NativeUsbSerial"));
|
|
30
|
+
var _transport = require("./transport");
|
|
10
31
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
SPACE: 4
|
|
17
|
-
};
|
|
18
|
-
const DataBits = exports.DataBits = {
|
|
19
|
-
FIVE: 5,
|
|
20
|
-
SIX: 6,
|
|
21
|
-
SEVEN: 7,
|
|
22
|
-
EIGHT: 8
|
|
23
|
-
};
|
|
24
|
-
const StopBits = exports.StopBits = {
|
|
25
|
-
ONE: 1,
|
|
26
|
-
ONE_FIVE: 3,
|
|
27
|
-
TWO: 2
|
|
28
|
-
};
|
|
29
|
-
const DEFAULT_OPEN_OPTIONS = {
|
|
30
|
-
dataBits: DataBits.EIGHT,
|
|
31
|
-
stopBits: StopBits.ONE,
|
|
32
|
-
parity: Parity.NONE
|
|
33
|
-
};
|
|
32
|
+
// These types and enums were originally declared in this module and form part
|
|
33
|
+
// of the public `UsbSerial` namespace. They now live in ./transport (a
|
|
34
|
+
// react-native-free module the virtual transport also builds on) and are
|
|
35
|
+
// re-exported here unchanged for backwards compatibility.
|
|
36
|
+
|
|
34
37
|
class UsbSerialModule {
|
|
35
38
|
constructor() {
|
|
36
39
|
if (!_NativeUsbSerial.default) {
|
|
@@ -50,7 +53,7 @@ class UsbSerialModule {
|
|
|
50
53
|
}
|
|
51
54
|
open(deviceId, portNumber, options) {
|
|
52
55
|
const opts = {
|
|
53
|
-
...DEFAULT_OPEN_OPTIONS,
|
|
56
|
+
..._transport.DEFAULT_OPEN_OPTIONS,
|
|
54
57
|
...options
|
|
55
58
|
};
|
|
56
59
|
return this.native.open(deviceId, portNumber, opts.baudRate, opts.dataBits, opts.stopBits, opts.parity);
|
|
@@ -72,7 +75,7 @@ class UsbSerialModule {
|
|
|
72
75
|
}
|
|
73
76
|
setParameters(deviceId, portNumber, options) {
|
|
74
77
|
const opts = {
|
|
75
|
-
...DEFAULT_OPEN_OPTIONS,
|
|
78
|
+
..._transport.DEFAULT_OPEN_OPTIONS,
|
|
76
79
|
...options
|
|
77
80
|
};
|
|
78
81
|
return this.native.setParameters(deviceId, portNumber, opts.baudRate, opts.dataBits, opts.stopBits, opts.parity);
|
|
@@ -140,10 +143,39 @@ class UsbSerialModule {
|
|
|
140
143
|
}
|
|
141
144
|
exports.UsbSerialModule = UsbSerialModule;
|
|
142
145
|
let instance = null;
|
|
146
|
+
let override = null;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resolve the active serial transport. Returns the override set via
|
|
150
|
+
* {@link setUsbSerial} if present (used by tests and by the example's on-device
|
|
151
|
+
* "virtual device" mode); otherwise lazily constructs the real native-backed
|
|
152
|
+
* {@link UsbSerialModule}.
|
|
153
|
+
*/
|
|
143
154
|
function getUsbSerial() {
|
|
155
|
+
if (override) return override;
|
|
144
156
|
if (!instance) {
|
|
145
157
|
instance = new UsbSerialModule();
|
|
146
158
|
}
|
|
147
159
|
return instance;
|
|
148
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Override the transport returned by {@link getUsbSerial}. Pass a
|
|
164
|
+
* {@link SerialTransport} (e.g. an `InMemorySerialTransport`) to make the
|
|
165
|
+
* singleton `serial` instance — and any `new Serial()` created without an
|
|
166
|
+
* explicit transport — talk to it instead of real hardware. Pass `null` to
|
|
167
|
+
* clear the override.
|
|
168
|
+
*
|
|
169
|
+
* Inject before the first `getPorts()` / `requestPort()` / `addEventListener()`
|
|
170
|
+
* call so the lazy initialisation in `Serial` picks it up.
|
|
171
|
+
*/
|
|
172
|
+
function setUsbSerial(transport) {
|
|
173
|
+
override = transport;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Clear any override and drop the cached native instance (test teardown). */
|
|
177
|
+
function resetUsbSerial() {
|
|
178
|
+
override = null;
|
|
179
|
+
instance = null;
|
|
180
|
+
}
|
|
149
181
|
//# sourceMappingURL=UsbSerial.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","_NativeUsbSerial","_interopRequireDefault","e","__esModule","default","
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_NativeUsbSerial","_interopRequireDefault","_transport","e","__esModule","default","UsbSerialModule","constructor","NativeUsbSerial","Error","native","emitter","NativeEventEmitter","NativeModules","findAllDrivers","showPortPicker","filter","labels","requestPermission","deviceId","open","portNumber","options","opts","DEFAULT_OPEN_OPTIONS","baudRate","dataBits","stopBits","parity","close","isOpen","write","data","timeout","startReading","stopReading","setParameters","setDTR","value","setRTS","getDTR","getRTS","getCD","getCTS","getDSR","getRI","getControlLines","getSupportedControlLines","setFlowControl","flowControl","getFlowControl","getSupportedFlowControl","setBreak","purgeHwBuffers","purgeWriteBuffers","purgeReadBuffers","getSerial","onData","listener","addListener","onError","onConnect","onDisconnect","exports","instance","override","getUsbSerial","setUsbSerial","transport","resetUsbSerial"],"sourceRoot":"../../src","sources":["UsbSerial.ts"],"mappings":"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","ignoreList":[]}
|