react-native-web-serial-api 0.0.1

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 (114) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/android/build.gradle +62 -0
  4. package/android/gradle.properties +6 -0
  5. package/android/src/main/AndroidManifest.xml +21 -0
  6. package/android/src/main/java/dev/webserialapi/NativeUsbSerialModule.java +704 -0
  7. package/android/src/main/java/dev/webserialapi/NativeUsbSerialPackage.java +46 -0
  8. package/android/src/main/java/dev/webserialapi/PortPickerActivity.java +235 -0
  9. package/android/src/main/java/dev/webserialapi/UsbDetachReceiver.java +37 -0
  10. package/android/src/main/res/xml/device_filter.xml +13 -0
  11. package/babel.config.js +3 -0
  12. package/biome.json +35 -0
  13. package/example/.watchmanconfig +1 -0
  14. package/example/App.tsx +71 -0
  15. package/example/__tests__/App.test.tsx +16 -0
  16. package/example/android/app/build.gradle +120 -0
  17. package/example/android/app/debug.keystore +0 -0
  18. package/example/android/app/proguard-rules.pro +10 -0
  19. package/example/android/app/src/debug/AndroidManifest.xml +9 -0
  20. package/example/android/app/src/main/AndroidManifest.xml +38 -0
  21. package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +22 -0
  22. package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +41 -0
  23. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  24. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  25. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  26. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  27. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  28. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  29. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  30. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  31. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  32. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  33. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  34. package/example/android/app/src/main/res/values/strings.xml +3 -0
  35. package/example/android/app/src/main/res/values/styles.xml +9 -0
  36. package/example/android/build.gradle +22 -0
  37. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  38. package/example/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  39. package/example/android/gradle.properties +47 -0
  40. package/example/android/gradlew +252 -0
  41. package/example/android/gradlew.bat +94 -0
  42. package/example/android/settings.gradle +6 -0
  43. package/example/app.json +4 -0
  44. package/example/babel.config.js +21 -0
  45. package/example/biome.json +47 -0
  46. package/example/deploy.sh +11 -0
  47. package/example/index.html +26 -0
  48. package/example/index.js +9 -0
  49. package/example/index.web.js +8 -0
  50. package/example/jest.config.js +12 -0
  51. package/example/metro.config.js +58 -0
  52. package/example/package-lock.json +14510 -0
  53. package/example/package.json +48 -0
  54. package/example/react-native.config.js +17 -0
  55. package/example/src/components/AppBar.tsx +73 -0
  56. package/example/src/components/Menu.tsx +90 -0
  57. package/example/src/components/SingleChoiceDialog.tsx +120 -0
  58. package/example/src/screens/ConnectScreen.tsx +195 -0
  59. package/example/src/screens/DevicesScreen.tsx +141 -0
  60. package/example/src/screens/TerminalScreen.tsx +564 -0
  61. package/example/src/settings.ts +43 -0
  62. package/example/src/theme.ts +19 -0
  63. package/example/src/util/TextUtil.ts +129 -0
  64. package/example/tsconfig.json +10 -0
  65. package/example/vite.config.mjs +55 -0
  66. package/lib/commonjs/NativeUsbSerial.js +11 -0
  67. package/lib/commonjs/NativeUsbSerial.js.map +1 -0
  68. package/lib/commonjs/NativeUsbSerial.web.js +12 -0
  69. package/lib/commonjs/NativeUsbSerial.web.js.map +1 -0
  70. package/lib/commonjs/UsbSerial.js +149 -0
  71. package/lib/commonjs/UsbSerial.js.map +1 -0
  72. package/lib/commonjs/WebSerial.js +852 -0
  73. package/lib/commonjs/WebSerial.js.map +1 -0
  74. package/lib/commonjs/index.js +44 -0
  75. package/lib/commonjs/index.js.map +1 -0
  76. package/lib/commonjs/package.json +1 -0
  77. package/lib/commonjs/serial.android.js +13 -0
  78. package/lib/commonjs/serial.android.js.map +1 -0
  79. package/lib/commonjs/serial.js +13 -0
  80. package/lib/commonjs/serial.js.map +1 -0
  81. package/lib/commonjs/serial.web.js +11 -0
  82. package/lib/commonjs/serial.web.js.map +1 -0
  83. package/lib/typescript/src/NativeUsbSerial.d.ts +51 -0
  84. package/lib/typescript/src/NativeUsbSerial.d.ts.map +1 -0
  85. package/lib/typescript/src/NativeUsbSerial.web.d.ts +3 -0
  86. package/lib/typescript/src/NativeUsbSerial.web.d.ts.map +1 -0
  87. package/lib/typescript/src/UsbSerial.d.ts +97 -0
  88. package/lib/typescript/src/UsbSerial.d.ts.map +1 -0
  89. package/lib/typescript/src/WebSerial.d.ts +236 -0
  90. package/lib/typescript/src/WebSerial.d.ts.map +1 -0
  91. package/lib/typescript/src/index.d.ts +7 -0
  92. package/lib/typescript/src/index.d.ts.map +1 -0
  93. package/lib/typescript/src/serial.android.d.ts +2 -0
  94. package/lib/typescript/src/serial.android.d.ts.map +1 -0
  95. package/lib/typescript/src/serial.d.ts +2 -0
  96. package/lib/typescript/src/serial.d.ts.map +1 -0
  97. package/lib/typescript/src/serial.web.d.ts +4 -0
  98. package/lib/typescript/src/serial.web.d.ts.map +1 -0
  99. package/package.json +78 -0
  100. package/react-native.config.js +9 -0
  101. package/scripts/deploy-release.sh +127 -0
  102. package/src/NativeUsbSerial.ts +124 -0
  103. package/src/NativeUsbSerial.web.ts +5 -0
  104. package/src/UsbSerial.ts +305 -0
  105. package/src/WebSerial.ts +1084 -0
  106. package/src/index.ts +23 -0
  107. package/src/lib/dom-exception.ts +161 -0
  108. package/src/lib/event-target.ts +170 -0
  109. package/src/lib/promise.ts +19 -0
  110. package/src/serial.android.ts +1 -0
  111. package/src/serial.ts +1 -0
  112. package/src/serial.web.ts +6 -0
  113. package/tsconfig.build.json +7 -0
  114. package/tsconfig.json +20 -0
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "react-native-web-serial-api",
3
+ "version": "0.0.1",
4
+ "description": "W3C Web Serial API (navigator.serial) for React Native on Android, backed by a USB-serial TurboModule (built on mik3y/usb-serial-for-android).",
5
+ "main": "lib/commonjs/index.js",
6
+ "react-native": "lib/commonjs/index.js",
7
+ "source": "src/index.ts",
8
+ "types": "lib/typescript/src/index.d.ts",
9
+ "scripts": {
10
+ "prepare": "bob build",
11
+ "clean": "node -e \"require('fs').rm(\\\"./lib\\\", { recursive: true }, () => {console.log(\\\"'lib' folder deleted\\\")})\"",
12
+ "typecheck": "tsc --noEmit",
13
+ "lint": "biome check src",
14
+ "format": "biome check --write src"
15
+ },
16
+ "keywords": [
17
+ "react-native",
18
+ "android",
19
+ "web-serial",
20
+ "web serial api",
21
+ "serial",
22
+ "usb",
23
+ "usb-serial",
24
+ "turbomodule",
25
+ "navigator.serial",
26
+ "ftdi",
27
+ "cp210x",
28
+ "ch340",
29
+ "pl2303",
30
+ "cdc-acm"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/uzlopak/react-native-web-serial-api.git"
35
+ },
36
+ "author": "Aras Abbasi <arasabbasi@gmail.com>",
37
+ "license": "MIT",
38
+ "codegenConfig": {
39
+ "name": "WebSerialApiSpec",
40
+ "type": "modules",
41
+ "jsSrcsDir": "src",
42
+ "android": {
43
+ "javaPackageName": "dev.webserialapi"
44
+ }
45
+ },
46
+ "dependencies": {
47
+ "web-streams-polyfill": "^4.3.0"
48
+ },
49
+ "peerDependencies": {
50
+ "react": "*",
51
+ "react-native": "*"
52
+ },
53
+ "devDependencies": {
54
+ "@biomejs/biome": "2.4.16",
55
+ "@react-native/babel-preset": "0.85.3",
56
+ "@types/react": "^19.2.6",
57
+ "react": "19.2.3",
58
+ "react-native": "0.85.3",
59
+ "react-native-builder-bob": "^0.41.0",
60
+ "typescript": "6.0.3"
61
+ },
62
+ "react-native-builder-bob": {
63
+ "source": "src",
64
+ "output": "lib",
65
+ "targets": [
66
+ "commonjs",
67
+ [
68
+ "typescript",
69
+ {
70
+ "project": "tsconfig.build.json"
71
+ }
72
+ ]
73
+ ]
74
+ },
75
+ "engines": {
76
+ "node": ">=22"
77
+ }
78
+ }
@@ -0,0 +1,9 @@
1
+ // Consumed by the React Native CLI in host apps. This package is Android-only,
2
+ // so opt out of iOS autolinking (there is no podspec / iOS implementation).
3
+ module.exports = {
4
+ dependency: {
5
+ platforms: {
6
+ ios: null,
7
+ },
8
+ },
9
+ };
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Build and install a standalone RELEASE build of the example app.
4
+ #
5
+ # A release build embeds the JS bundle (assets/index.android.bundle) into the
6
+ # APK, so the app runs WITHOUT Metro / a dev server. The JS bundler runs once
7
+ # at build time; after install the app is fully self-contained.
8
+ #
9
+ # Usage:
10
+ # ./scripts/deploy-release.sh # build, install on the connected device, launch
11
+ # ./scripts/deploy-release.sh --build # build the APK only (no install)
12
+ # ./scripts/deploy-release.sh --no-launch
13
+ # DEVICE=192.168.1.50:5555 ./scripts/deploy-release.sh # target a specific adb device
14
+ #
15
+ # Environment overrides (auto-detected if unset):
16
+ # JAVA_HOME - JDK 17 (RN 0.85 / Gradle 8.13 require JDK 17-21)
17
+ # ANDROID_HOME - Android SDK location
18
+ # NODE_BIN - directory containing a Node >= 20 binary (RN 0.85 metro needs it)
19
+ #
20
+ set -euo pipefail
21
+
22
+ APP_ID="dev.react_native_web_serial_api.example"
23
+ MAIN_ACTIVITY="${APP_ID}/.MainActivity"
24
+
25
+ # Resolve paths relative to this script so it works from any CWD.
26
+ # This script lives in <repo>/scripts; the buildable app is in <repo>/example
27
+ # (the repo-root android/ is the library module and has no gradlew).
28
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
29
+ REPO_ROOT="$(dirname "$SCRIPT_DIR")"
30
+ EXAMPLE_DIR="$REPO_ROOT/example"
31
+ ANDROID_DIR="$EXAMPLE_DIR/android"
32
+ APK="$ANDROID_DIR/app/build/outputs/apk/release/app-release.apk"
33
+
34
+ # --- arg parsing ---------------------------------------------------------
35
+ DO_INSTALL=1
36
+ DO_LAUNCH=1
37
+ for arg in "$@"; do
38
+ case "$arg" in
39
+ --build) DO_INSTALL=0; DO_LAUNCH=0 ;;
40
+ --no-launch) DO_LAUNCH=0 ;;
41
+ -h|--help) grep '^#' "$0" | grep -v '^#!' | sed 's/^# \{0,1\}//'; exit 0 ;;
42
+ *) echo "Unknown option: $arg" >&2; exit 1 ;;
43
+ esac
44
+ done
45
+
46
+ # --- toolchain auto-detection -------------------------------------------
47
+ # JDK 17 (only override if the current java isn't already 17-21).
48
+ if [ -z "${JAVA_HOME:-}" ]; then
49
+ for d in /usr/lib/jvm/java-17-openjdk-amd64 /usr/lib/jvm/java-21-openjdk-amd64 \
50
+ /usr/lib/jvm/temurin-17-jdk-amd64; do
51
+ [ -d "$d" ] && export JAVA_HOME="$d" && break
52
+ done
53
+ fi
54
+ [ -n "${JAVA_HOME:-}" ] && echo "JAVA_HOME=$JAVA_HOME"
55
+
56
+ # Android SDK.
57
+ if [ -z "${ANDROID_HOME:-}" ]; then
58
+ for d in "$HOME/Android/Sdk" "$HOME/Library/Android/sdk" "${ANDROID_SDK_ROOT:-}"; do
59
+ [ -n "$d" ] && [ -d "$d" ] && export ANDROID_HOME="$d" && break
60
+ done
61
+ fi
62
+ if [ -z "${ANDROID_HOME:-}" ]; then
63
+ echo "ERROR: Android SDK not found. Set ANDROID_HOME." >&2
64
+ exit 1
65
+ fi
66
+ echo "ANDROID_HOME=$ANDROID_HOME"
67
+ export PATH="$ANDROID_HOME/platform-tools:$PATH"
68
+
69
+ # Node >= 20 (RN 0.85 metro-config uses Array.prototype.toReversed()).
70
+ need_node() { node -e 'process.exit(typeof [].toReversed==="function"?0:1)' 2>/dev/null; }
71
+ if [ -n "${NODE_BIN:-}" ]; then
72
+ export PATH="$NODE_BIN:$PATH"
73
+ fi
74
+ if ! command -v node >/dev/null 2>&1 || ! need_node; then
75
+ # Try the newest nvm-managed Node >= 20.
76
+ if [ -d "$HOME/.nvm/versions/node" ]; then
77
+ for v in $(ls -1 "$HOME/.nvm/versions/node" | sort -Vr); do
78
+ if "$HOME/.nvm/versions/node/$v/bin/node" -e 'process.exit(typeof [].toReversed==="function"?0:1)' 2>/dev/null; then
79
+ export PATH="$HOME/.nvm/versions/node/$v/bin:$PATH"
80
+ break
81
+ fi
82
+ done
83
+ fi
84
+ fi
85
+ if ! need_node; then
86
+ echo "ERROR: need Node >= 20 (found $(node -v 2>/dev/null || echo none)). Set NODE_BIN." >&2
87
+ exit 1
88
+ fi
89
+ echo "node=$(node -v)"
90
+
91
+ # --- pick adb device -----------------------------------------------------
92
+ adb_target=()
93
+ if [ -n "${DEVICE:-}" ]; then
94
+ adb connect "$DEVICE" >/dev/null 2>&1 || true
95
+ adb_target=(-s "$DEVICE")
96
+ fi
97
+
98
+ # --- build ---------------------------------------------------------------
99
+ echo "==> Building release APK (embeds JS bundle)…"
100
+ ( cd "$ANDROID_DIR" && ./gradlew :app:assembleRelease )
101
+
102
+ if [ ! -f "$APK" ]; then
103
+ echo "ERROR: APK not found at $APK" >&2
104
+ exit 1
105
+ fi
106
+ echo "==> Built: $APK ($(du -h "$APK" | cut -f1))"
107
+
108
+ [ "$DO_INSTALL" -eq 1 ] || { echo "Build-only mode; done."; exit 0; }
109
+
110
+ # --- install -------------------------------------------------------------
111
+ echo "==> Installing on device…"
112
+ # Capture output instead of piping into `grep -q`: under `set -o pipefail`,
113
+ # grep closing the pipe early can make a successful install report failure.
114
+ install_out="$(adb "${adb_target[@]}" install -r "$APK" 2>&1)" || true
115
+ echo "$install_out"
116
+ if ! grep -q "Success" <<<"$install_out"; then
117
+ echo "==> Reinstall failed (likely signature mismatch); uninstalling and retrying…"
118
+ adb "${adb_target[@]}" uninstall "$APP_ID" >/dev/null 2>&1 || true
119
+ adb "${adb_target[@]}" install "$APK"
120
+ fi
121
+
122
+ [ "$DO_LAUNCH" -eq 1 ] || { echo "Installed (not launched)."; exit 0; }
123
+
124
+ # --- launch --------------------------------------------------------------
125
+ echo "==> Launching $MAIN_ACTIVITY…"
126
+ adb "${adb_target[@]}" shell am start -n "$MAIN_ACTIVITY" >/dev/null
127
+ echo "==> Done. The app runs standalone (no Metro needed)."
@@ -0,0 +1,124 @@
1
+ import type {TurboModule} from 'react-native';
2
+ import {TurboModuleRegistry} from 'react-native';
3
+
4
+ // UsbSerialPort.ControlLine enum
5
+ export type ControlLine = 'RTS' | 'CTS' | 'DTR' | 'DSR' | 'CD' | 'RI';
6
+
7
+ // UsbSerialPort.FlowControl enum
8
+ export type FlowControl =
9
+ | 'NONE'
10
+ | 'RTS_CTS'
11
+ | 'DTR_DSR'
12
+ | 'XON_XOFF'
13
+ | 'XON_XOFF_INLINE';
14
+
15
+ export type PortId = {
16
+ deviceId: number;
17
+ portNumber: number;
18
+ usbVendorId: number;
19
+ usbProductId: number;
20
+ };
21
+
22
+ export type PortPickerLabels = {
23
+ titleSelectPort?: string;
24
+ titleNoPortsAvailable?: string;
25
+ messageNoPortsAvailable?: string;
26
+ };
27
+
28
+ export type PortFilter = {
29
+ usbVendorId?: number;
30
+ usbProductId?: number;
31
+ };
32
+
33
+ export interface Spec extends TurboModule {
34
+ // UsbSerialProber
35
+ findAllDrivers(): Promise<ReadonlyArray<PortId>>;
36
+
37
+ // Port picker dialog — shows available ports filtered by filters, requests permission for selected port
38
+ showPortPicker(
39
+ filters: ReadonlyArray<PortFilter>,
40
+ labels?: PortPickerLabels,
41
+ ): Promise<PortId>;
42
+
43
+ // UsbSerialPort lifecycle
44
+ open(
45
+ deviceId: number,
46
+ portNumber: number,
47
+ baudRate: number,
48
+ dataBits: number,
49
+ stopBits: number,
50
+ parity: number,
51
+ ): Promise<void>;
52
+ close(deviceId: number, portNumber: number): Promise<void>;
53
+ isOpen(deviceId: number, portNumber: number): boolean;
54
+
55
+ // Read/Write
56
+ write(
57
+ deviceId: number,
58
+ portNumber: number,
59
+ data: ReadonlyArray<number>,
60
+ timeout: number,
61
+ ): Promise<void>;
62
+ startReading(deviceId: number, portNumber: number): Promise<void>;
63
+ stopReading(deviceId: number, portNumber: number): Promise<void>;
64
+
65
+ // Parameters
66
+ setParameters(
67
+ deviceId: number,
68
+ portNumber: number,
69
+ baudRate: number,
70
+ dataBits: number,
71
+ stopBits: number,
72
+ parity: number,
73
+ ): Promise<void>;
74
+
75
+ // Control lines
76
+ setDTR(deviceId: number, portNumber: number, value: boolean): Promise<void>;
77
+ setRTS(deviceId: number, portNumber: number, value: boolean): Promise<void>;
78
+ getDTR(deviceId: number, portNumber: number): Promise<boolean>;
79
+ getRTS(deviceId: number, portNumber: number): Promise<boolean>;
80
+ getCD(deviceId: number, portNumber: number): Promise<boolean>;
81
+ getCTS(deviceId: number, portNumber: number): Promise<boolean>;
82
+ getDSR(deviceId: number, portNumber: number): Promise<boolean>;
83
+ getRI(deviceId: number, portNumber: number): Promise<boolean>;
84
+
85
+ getControlLines(
86
+ deviceId: number,
87
+ portNumber: number,
88
+ ): Promise<ReadonlyArray<string>>;
89
+ getSupportedControlLines(
90
+ deviceId: number,
91
+ portNumber: number,
92
+ ): Promise<ReadonlyArray<string>>;
93
+
94
+ // Flow control
95
+ setFlowControl(
96
+ deviceId: number,
97
+ portNumber: number,
98
+ flowControl: string,
99
+ ): Promise<void>;
100
+ getFlowControl(deviceId: number, portNumber: number): Promise<string>;
101
+ getSupportedFlowControl(
102
+ deviceId: number,
103
+ portNumber: number,
104
+ ): Promise<ReadonlyArray<string>>;
105
+
106
+ // Misc
107
+ setBreak(deviceId: number, portNumber: number, value: boolean): Promise<void>;
108
+ purgeHwBuffers(
109
+ deviceId: number,
110
+ portNumber: number,
111
+ purgeWriteBuffers: boolean,
112
+ purgeReadBuffers: boolean,
113
+ ): Promise<void>;
114
+ getSerial(deviceId: number, portNumber: number): Promise<string>;
115
+
116
+ // USB permission
117
+ requestPermission(deviceId: number): Promise<boolean>;
118
+
119
+ // NativeEventEmitter
120
+ addListener(eventName: string): void;
121
+ removeListeners(count: number): void;
122
+ }
123
+
124
+ export default TurboModuleRegistry.get<Spec>('NativeUsbSerial');
@@ -0,0 +1,5 @@
1
+ // On web there is no native USB-serial TurboModule. serial.web.ts delegates to
2
+ // the browser's native navigator.serial, and UsbSerial.getUsbSerial() treats a
3
+ // null module as "not available" — so this stub simply avoids touching
4
+ // TurboModuleRegistry (which does not exist under react-native-web).
5
+ export default null;
@@ -0,0 +1,305 @@
1
+ import {NativeEventEmitter, NativeModules} from 'react-native';
2
+ import NativeUsbSerial from './NativeUsbSerial';
3
+
4
+ export type ControlLine = 'RTS' | 'CTS' | 'DTR' | 'DSR' | 'CD' | 'RI';
5
+ export type FlowControl =
6
+ | 'NONE'
7
+ | 'RTS_CTS'
8
+ | 'DTR_DSR'
9
+ | 'XON_XOFF'
10
+ | 'XON_XOFF_INLINE';
11
+
12
+ export type PortFilter = {
13
+ usbVendorId?: number;
14
+ usbProductId?: number;
15
+ };
16
+
17
+ export type PortPickerLabels = {
18
+ titleSelectPort?: string;
19
+ titleNoPortsAvailable?: string;
20
+ messageNoPortsAvailable?: string;
21
+ };
22
+
23
+ export type PortId = {
24
+ deviceId: number;
25
+ portNumber: number;
26
+ usbVendorId: number;
27
+ usbProductId: number;
28
+ };
29
+
30
+ export type DataEvent = {
31
+ deviceId: number;
32
+ portNumber: number;
33
+ data: number[];
34
+ };
35
+
36
+ export type ErrorEvent = {
37
+ deviceId: number;
38
+ portNumber: number;
39
+ error: string;
40
+ };
41
+
42
+ export type ConnectEvent = {
43
+ deviceId: number;
44
+ usbVendorId: number;
45
+ usbProductId: number;
46
+ };
47
+
48
+ export const Parity = {
49
+ NONE: 0,
50
+ ODD: 1,
51
+ EVEN: 2,
52
+ MARK: 3,
53
+ SPACE: 4,
54
+ } as const;
55
+
56
+ export const DataBits = {
57
+ FIVE: 5,
58
+ SIX: 6,
59
+ SEVEN: 7,
60
+ EIGHT: 8,
61
+ } as const;
62
+
63
+ export const StopBits = {
64
+ ONE: 1,
65
+ ONE_FIVE: 3,
66
+ TWO: 2,
67
+ } as const;
68
+
69
+ export type OpenOptions = {
70
+ baudRate: number;
71
+ dataBits?: number;
72
+ stopBits?: number;
73
+ parity?: number;
74
+ };
75
+
76
+ const DEFAULT_OPEN_OPTIONS: Required<Omit<OpenOptions, 'baudRate'>> = {
77
+ dataBits: DataBits.EIGHT,
78
+ stopBits: StopBits.ONE,
79
+ parity: Parity.NONE,
80
+ };
81
+
82
+ type Subscription = {remove: () => void};
83
+
84
+ export class UsbSerialModule {
85
+ private readonly native: NonNullable<typeof NativeUsbSerial>;
86
+ private readonly emitter: NativeEventEmitter;
87
+
88
+ constructor() {
89
+ if (!NativeUsbSerial) {
90
+ throw new Error('NativeUsbSerial is not available');
91
+ }
92
+ this.native = NativeUsbSerial;
93
+ this.emitter = new NativeEventEmitter(NativeModules.NativeUsbSerial);
94
+ }
95
+
96
+ findAllDrivers(): Promise<ReadonlyArray<PortId>> {
97
+ return this.native.findAllDrivers() as Promise<ReadonlyArray<PortId>>;
98
+ }
99
+
100
+ showPortPicker(
101
+ filter: Readonly<PortFilter[]>,
102
+ labels?: PortPickerLabels,
103
+ ): Promise<PortId> {
104
+ return this.native.showPortPicker(filter, labels) as Promise<PortId>;
105
+ }
106
+
107
+ requestPermission(deviceId: number): Promise<boolean> {
108
+ return this.native.requestPermission(deviceId);
109
+ }
110
+
111
+ open(
112
+ deviceId: number,
113
+ portNumber: number,
114
+ options: OpenOptions,
115
+ ): Promise<void> {
116
+ const opts = {...DEFAULT_OPEN_OPTIONS, ...options};
117
+ return this.native.open(
118
+ deviceId,
119
+ portNumber,
120
+ opts.baudRate,
121
+ opts.dataBits,
122
+ opts.stopBits,
123
+ opts.parity,
124
+ );
125
+ }
126
+
127
+ close(deviceId: number, portNumber: number): Promise<void> {
128
+ return this.native.close(deviceId, portNumber);
129
+ }
130
+
131
+ isOpen(deviceId: number, portNumber: number): boolean {
132
+ return this.native.isOpen(deviceId, portNumber);
133
+ }
134
+
135
+ write(
136
+ deviceId: number,
137
+ portNumber: number,
138
+ data: number[],
139
+ timeout: number = 2000,
140
+ ): Promise<void> {
141
+ return this.native.write(deviceId, portNumber, data, timeout);
142
+ }
143
+
144
+ startReading(deviceId: number, portNumber: number): Promise<void> {
145
+ return this.native.startReading(deviceId, portNumber);
146
+ }
147
+
148
+ stopReading(deviceId: number, portNumber: number): Promise<void> {
149
+ return this.native.stopReading(deviceId, portNumber);
150
+ }
151
+
152
+ setParameters(
153
+ deviceId: number,
154
+ portNumber: number,
155
+ options: OpenOptions,
156
+ ): Promise<void> {
157
+ const opts = {...DEFAULT_OPEN_OPTIONS, ...options};
158
+ return this.native.setParameters(
159
+ deviceId,
160
+ portNumber,
161
+ opts.baudRate,
162
+ opts.dataBits,
163
+ opts.stopBits,
164
+ opts.parity,
165
+ );
166
+ }
167
+
168
+ setDTR(deviceId: number, portNumber: number, value: boolean): Promise<void> {
169
+ return this.native.setDTR(deviceId, portNumber, value);
170
+ }
171
+
172
+ setRTS(deviceId: number, portNumber: number, value: boolean): Promise<void> {
173
+ return this.native.setRTS(deviceId, portNumber, value);
174
+ }
175
+
176
+ getDTR(deviceId: number, portNumber: number): Promise<boolean> {
177
+ return this.native.getDTR(deviceId, portNumber);
178
+ }
179
+
180
+ getRTS(deviceId: number, portNumber: number): Promise<boolean> {
181
+ return this.native.getRTS(deviceId, portNumber);
182
+ }
183
+
184
+ getCD(deviceId: number, portNumber: number): Promise<boolean> {
185
+ return this.native.getCD(deviceId, portNumber);
186
+ }
187
+
188
+ getCTS(deviceId: number, portNumber: number): Promise<boolean> {
189
+ return this.native.getCTS(deviceId, portNumber);
190
+ }
191
+
192
+ getDSR(deviceId: number, portNumber: number): Promise<boolean> {
193
+ return this.native.getDSR(deviceId, portNumber);
194
+ }
195
+
196
+ getRI(deviceId: number, portNumber: number): Promise<boolean> {
197
+ return this.native.getRI(deviceId, portNumber);
198
+ }
199
+
200
+ getControlLines(
201
+ deviceId: number,
202
+ portNumber: number,
203
+ ): Promise<ControlLine[]> {
204
+ return this.native.getControlLines(deviceId, portNumber) as Promise<
205
+ ControlLine[]
206
+ >;
207
+ }
208
+
209
+ getSupportedControlLines(
210
+ deviceId: number,
211
+ portNumber: number,
212
+ ): Promise<ControlLine[]> {
213
+ return this.native.getSupportedControlLines(
214
+ deviceId,
215
+ portNumber,
216
+ ) as Promise<ControlLine[]>;
217
+ }
218
+
219
+ setFlowControl(
220
+ deviceId: number,
221
+ portNumber: number,
222
+ flowControl: FlowControl,
223
+ ): Promise<void> {
224
+ return this.native.setFlowControl(deviceId, portNumber, flowControl);
225
+ }
226
+
227
+ getFlowControl(deviceId: number, portNumber: number): Promise<FlowControl> {
228
+ return this.native.getFlowControl(
229
+ deviceId,
230
+ portNumber,
231
+ ) as Promise<FlowControl>;
232
+ }
233
+
234
+ getSupportedFlowControl(
235
+ deviceId: number,
236
+ portNumber: number,
237
+ ): Promise<FlowControl[]> {
238
+ return this.native.getSupportedFlowControl(deviceId, portNumber) as Promise<
239
+ FlowControl[]
240
+ >;
241
+ }
242
+
243
+ setBreak(
244
+ deviceId: number,
245
+ portNumber: number,
246
+ value: boolean,
247
+ ): Promise<void> {
248
+ return this.native.setBreak(deviceId, portNumber, value);
249
+ }
250
+
251
+ purgeHwBuffers(
252
+ deviceId: number,
253
+ portNumber: number,
254
+ purgeWriteBuffers: boolean,
255
+ purgeReadBuffers: boolean,
256
+ ): Promise<void> {
257
+ return this.native.purgeHwBuffers(
258
+ deviceId,
259
+ portNumber,
260
+ purgeWriteBuffers,
261
+ purgeReadBuffers,
262
+ );
263
+ }
264
+
265
+ getSerial(deviceId: number, portNumber: number): Promise<string> {
266
+ return this.native.getSerial(deviceId, portNumber);
267
+ }
268
+
269
+ onData(listener: (event: DataEvent) => void): Subscription {
270
+ return this.emitter.addListener(
271
+ 'data',
272
+ listener as (...args: readonly Record<string, unknown>[]) => unknown,
273
+ );
274
+ }
275
+
276
+ onError(listener: (event: ErrorEvent) => void): Subscription {
277
+ return this.emitter.addListener(
278
+ 'error',
279
+ listener as (...args: readonly Record<string, unknown>[]) => unknown,
280
+ );
281
+ }
282
+
283
+ onConnect(listener: (event: ConnectEvent) => void): Subscription {
284
+ return this.emitter.addListener(
285
+ 'connect',
286
+ listener as (...args: readonly Record<string, unknown>[]) => unknown,
287
+ );
288
+ }
289
+
290
+ onDisconnect(listener: (event: ConnectEvent) => void): Subscription {
291
+ return this.emitter.addListener(
292
+ 'disconnect',
293
+ listener as (...args: readonly Record<string, unknown>[]) => unknown,
294
+ );
295
+ }
296
+ }
297
+
298
+ let instance: UsbSerialModule | null = null;
299
+
300
+ export function getUsbSerial(): UsbSerialModule {
301
+ if (!instance) {
302
+ instance = new UsbSerialModule();
303
+ }
304
+ return instance;
305
+ }