react-native-web-serial-api 0.1.0 → 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.
Files changed (112) hide show
  1. package/README.md +188 -117
  2. package/TESTING.md +417 -176
  3. package/android/build.gradle +14 -0
  4. package/android/src/main/java/dev/webserialapi/NativeUsbSerialModule.java +74 -11
  5. package/android/src/main/java/dev/webserialapi/PortPickerActivity.java +61 -59
  6. package/bin/expose-serial.js +205 -0
  7. package/lib/commonjs/UsbSerial.js +1 -1
  8. package/lib/commonjs/WebSerial.js +110 -26
  9. package/lib/commonjs/WebSerial.js.map +1 -1
  10. package/lib/commonjs/index.js +2 -2
  11. package/lib/commonjs/index.js.map +1 -1
  12. package/lib/commonjs/lib/event-target.js +3 -1
  13. package/lib/commonjs/lib/event-target.js.map +1 -1
  14. package/lib/commonjs/lib/web-streams.js +42 -0
  15. package/lib/commonjs/lib/web-streams.js.map +1 -0
  16. package/lib/commonjs/testing/device-fixture.js +70 -0
  17. package/lib/commonjs/testing/device-fixture.js.map +1 -0
  18. package/lib/commonjs/testing/expose.js +91 -0
  19. package/lib/commonjs/testing/expose.js.map +1 -0
  20. package/lib/commonjs/testing/harness.js +98 -0
  21. package/lib/commonjs/testing/harness.js.map +1 -0
  22. package/lib/commonjs/testing/{virtual-serial.js → in-memory-serial-transport.js} +66 -28
  23. package/lib/commonjs/testing/in-memory-serial-transport.js.map +1 -0
  24. package/lib/commonjs/testing/index.js +100 -17
  25. package/lib/commonjs/testing/index.js.map +1 -1
  26. package/lib/commonjs/testing/install-in-memory-serial-transport.js +54 -0
  27. package/lib/commonjs/testing/install-in-memory-serial-transport.js.map +1 -0
  28. package/lib/commonjs/testing/serial-client.js +277 -0
  29. package/lib/commonjs/testing/serial-client.js.map +1 -0
  30. package/lib/commonjs/testing/{serial-device.js → simulated-device.js} +17 -17
  31. package/lib/commonjs/testing/simulated-device.js.map +1 -0
  32. package/lib/commonjs/testing/test-suite.js +142 -0
  33. package/lib/commonjs/testing/test-suite.js.map +1 -0
  34. package/lib/commonjs/transport.js +3 -3
  35. package/lib/commonjs/websocket/WebSocketSerialTransport.js +659 -0
  36. package/lib/commonjs/websocket/WebSocketSerialTransport.js.map +1 -0
  37. package/lib/commonjs/websocket/bridge.js +234 -0
  38. package/lib/commonjs/websocket/bridge.js.map +1 -0
  39. package/lib/commonjs/websocket/index.js +33 -0
  40. package/lib/commonjs/websocket/index.js.map +1 -0
  41. package/lib/commonjs/websocket/protocol.js +55 -0
  42. package/lib/commonjs/websocket/protocol.js.map +1 -0
  43. package/lib/commonjs/websocket/serial-device-bridge.js +130 -0
  44. package/lib/commonjs/websocket/serial-device-bridge.js.map +1 -0
  45. package/lib/typescript/src/UsbSerial.d.ts +1 -1
  46. package/lib/typescript/src/WebSerial.d.ts +7 -7
  47. package/lib/typescript/src/WebSerial.d.ts.map +1 -1
  48. package/lib/typescript/src/index.d.ts +1 -1
  49. package/lib/typescript/src/index.d.ts.map +1 -1
  50. package/lib/typescript/src/lib/event-target.d.ts +2 -0
  51. package/lib/typescript/src/lib/event-target.d.ts.map +1 -1
  52. package/lib/typescript/src/lib/web-streams.d.ts +9 -0
  53. package/lib/typescript/src/lib/web-streams.d.ts.map +1 -0
  54. package/lib/typescript/src/testing/device-fixture.d.ts +70 -0
  55. package/lib/typescript/src/testing/device-fixture.d.ts.map +1 -0
  56. package/lib/typescript/src/testing/expose.d.ts +71 -0
  57. package/lib/typescript/src/testing/expose.d.ts.map +1 -0
  58. package/lib/typescript/src/testing/harness.d.ts +34 -0
  59. package/lib/typescript/src/testing/harness.d.ts.map +1 -0
  60. package/lib/typescript/src/testing/{virtual-serial.d.ts → in-memory-serial-transport.d.ts} +37 -26
  61. package/lib/typescript/src/testing/in-memory-serial-transport.d.ts.map +1 -0
  62. package/lib/typescript/src/testing/index.d.ts +18 -8
  63. package/lib/typescript/src/testing/index.d.ts.map +1 -1
  64. package/lib/typescript/src/testing/install-in-memory-serial-transport.d.ts +25 -0
  65. package/lib/typescript/src/testing/install-in-memory-serial-transport.d.ts.map +1 -0
  66. package/lib/typescript/src/testing/serial-client.d.ts +62 -0
  67. package/lib/typescript/src/testing/serial-client.d.ts.map +1 -0
  68. package/lib/typescript/src/testing/{serial-device.d.ts → simulated-device.d.ts} +23 -23
  69. package/lib/typescript/src/testing/simulated-device.d.ts.map +1 -0
  70. package/lib/typescript/src/testing/test-suite.d.ts +75 -0
  71. package/lib/typescript/src/testing/test-suite.d.ts.map +1 -0
  72. package/lib/typescript/src/transport.d.ts +3 -3
  73. package/lib/typescript/src/websocket/WebSocketSerialTransport.d.ts +111 -0
  74. package/lib/typescript/src/websocket/WebSocketSerialTransport.d.ts.map +1 -0
  75. package/lib/typescript/src/websocket/bridge.d.ts +66 -0
  76. package/lib/typescript/src/websocket/bridge.d.ts.map +1 -0
  77. package/lib/typescript/src/websocket/index.d.ts +19 -0
  78. package/lib/typescript/src/websocket/index.d.ts.map +1 -0
  79. package/lib/typescript/src/websocket/protocol.d.ts +64 -0
  80. package/lib/typescript/src/websocket/protocol.d.ts.map +1 -0
  81. package/lib/typescript/src/websocket/serial-device-bridge.d.ts +32 -0
  82. package/lib/typescript/src/websocket/serial-device-bridge.d.ts.map +1 -0
  83. package/package.json +21 -3
  84. package/src/UsbSerial.ts +1 -1
  85. package/src/WebSerial.ts +134 -35
  86. package/src/index.ts +4 -1
  87. package/src/lib/event-target.ts +12 -0
  88. package/src/lib/web-streams.ts +43 -0
  89. package/src/testing/device-fixture.ts +150 -0
  90. package/src/testing/expose.ts +147 -0
  91. package/src/testing/harness.ts +124 -0
  92. package/src/testing/{virtual-serial.ts → in-memory-serial-transport.ts} +95 -56
  93. package/src/testing/index.ts +69 -21
  94. package/src/testing/install-in-memory-serial-transport.ts +65 -0
  95. package/src/testing/serial-client.ts +313 -0
  96. package/src/testing/{serial-device.ts → simulated-device.ts} +23 -23
  97. package/src/testing/test-suite.ts +186 -0
  98. package/src/transport.ts +3 -3
  99. package/src/websocket/WebSocketSerialTransport.ts +796 -0
  100. package/src/websocket/bridge.ts +299 -0
  101. package/src/websocket/index.ts +38 -0
  102. package/src/websocket/protocol.ts +101 -0
  103. package/src/websocket/serial-device-bridge.ts +160 -0
  104. package/lib/commonjs/testing/install.js +0 -54
  105. package/lib/commonjs/testing/install.js.map +0 -1
  106. package/lib/commonjs/testing/serial-device.js.map +0 -1
  107. package/lib/commonjs/testing/virtual-serial.js.map +0 -1
  108. package/lib/typescript/src/testing/install.d.ts +0 -25
  109. package/lib/typescript/src/testing/install.d.ts.map +0 -1
  110. package/lib/typescript/src/testing/serial-device.d.ts.map +0 -1
  111. package/lib/typescript/src/testing/virtual-serial.d.ts.map +0 -1
  112. package/src/testing/install.ts +0 -65
@@ -1,15 +1,15 @@
1
1
  /**
2
- * SerialDevice — author a complete simulated serial peripheral.
2
+ * SimulatedDevice — author a complete simulated serial peripheral.
3
3
  *
4
4
  * Extend the class and override the lifecycle hooks to model a real device's
5
5
  * whole behaviour (firmware/protocol): react to `open()`, to bytes the host
6
6
  * writes, to control-signal changes, and stream data back over time. It is
7
- * hosted by a {@link VirtualSerialTransport} ({@link ./virtual-serial}) and is
7
+ * hosted by an {@link InMemorySerialTransport} ({@link ./virtual-serial}) and is
8
8
  * free of any `react-native` dependency, so the same device runs under Jest,
9
9
  * in a browser, and inside a React Native app on a device/emulator (for E2E).
10
10
  *
11
11
  * @example
12
- * class Thermometer extends SerialDevice {
12
+ * class Thermometer extends SimulatedDevice {
13
13
  * usbVendorId = 0x0403;
14
14
  * usbProductId = 0x6001;
15
15
  * #timer?: ReturnType<typeof setInterval>;
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import type { OpenOptions } from '../transport';
27
27
  /** The negotiated connection parameters (native parity code: 0 none/1 odd/2 even). */
28
- export type SerialDeviceOpenOptions = Required<OpenOptions>;
28
+ export type SimulatedDeviceOpenOptions = Required<OpenOptions>;
29
29
  /** Device-asserted input signals — what the host reads via getSignals(). */
30
30
  export type SerialInputSignals = {
31
31
  dataCarrierDetect?: boolean;
@@ -34,21 +34,21 @@ export type SerialInputSignals = {
34
34
  dataSetReady?: boolean;
35
35
  };
36
36
  /** Host-asserted output signals (DTR/RTS/break) the device observes. */
37
- export type SerialHostSignals = {
37
+ export type HostSignals = {
38
38
  dataTerminalReady: boolean;
39
39
  requestToSend: boolean;
40
40
  break: boolean;
41
41
  };
42
42
  /**
43
- * The handle a {@link SerialDevice} uses to talk back to the host. Provided by
44
- * the transport; you normally use the `protected` helpers on `SerialDevice`
43
+ * The handle a {@link SimulatedDevice} uses to talk back to the host. Provided by
44
+ * the transport; you normally use the `protected` helpers on `SimulatedDevice`
45
45
  * rather than this directly.
46
46
  */
47
- export interface SerialDeviceHost {
47
+ export interface SimulatedDeviceHost {
48
48
  readonly deviceId: number;
49
49
  readonly portNumber: number;
50
50
  readonly isOpen: boolean;
51
- readonly openOptions: SerialDeviceOpenOptions | null;
51
+ readonly openOptions: SimulatedDeviceOpenOptions | null;
52
52
  send(bytes: number[]): void;
53
53
  raiseError(message: string, name?: string): void;
54
54
  setSignals(signals: SerialInputSignals): void;
@@ -60,7 +60,7 @@ export declare function toBytes(data: number[] | Uint8Array | string): number[];
60
60
  * care about (all default to no-ops and may be async) and use the `protected`
61
61
  * helpers to drive the host.
62
62
  */
63
- export declare abstract class SerialDevice {
63
+ export declare abstract class SimulatedDevice {
64
64
  #private;
65
65
  /** USB Vendor ID this device reports for enumeration. */
66
66
  abstract readonly usbVendorId: number;
@@ -68,8 +68,8 @@ export declare abstract class SerialDevice {
68
68
  abstract readonly usbProductId: number;
69
69
  /** Optional USB serial number. */
70
70
  readonly serialNumber?: string;
71
- /** @internal Bind the transport host (called by VirtualSerialTransport). */
72
- _bind(host: SerialDeviceHost): void;
71
+ /** @internal Bind the transport host (called by InMemorySerialTransport). */
72
+ _bind(host: SimulatedDeviceHost): void;
73
73
  /** Send bytes to the host (they appear on `port.readable`). */
74
74
  protected send(data: number[] | Uint8Array | string): void;
75
75
  /**
@@ -81,47 +81,47 @@ export declare abstract class SerialDevice {
81
81
  /** Set device-asserted input signals (DCD/CTS/RI/DSR) the host can read. */
82
82
  protected setSignals(signals: SerialInputSignals): void;
83
83
  /** The parameters the host opened the port with, or null when closed. */
84
- protected get openOptions(): SerialDeviceOpenOptions | null;
84
+ protected get openOptions(): SimulatedDeviceOpenOptions | null;
85
85
  protected get deviceId(): number;
86
86
  protected get portNumber(): number;
87
87
  /** The host opened the port. */
88
- onOpen(_options: SerialDeviceOpenOptions): void | Promise<void>;
88
+ onOpen(_options: SimulatedDeviceOpenOptions): void | Promise<void>;
89
89
  /** The host wrote bytes to the device. */
90
90
  onData(_data: Uint8Array): void | Promise<void>;
91
91
  /** The host changed DTR/RTS/break. */
92
- onHostSignals(_signals: SerialHostSignals): void | Promise<void>;
92
+ onHostSignals(_signals: HostSignals): void | Promise<void>;
93
93
  /** The host closed the port. */
94
94
  onClose(): void | Promise<void>;
95
95
  }
96
- /** Optional USB identity for the built-in devices. */
97
- export type DeviceIdentity = {
96
+ /** Optional USB identity for built-in devices. */
97
+ export type SimulatedDeviceIdentity = {
98
98
  usbVendorId?: number;
99
99
  usbProductId?: number;
100
100
  serialNumber?: string;
101
101
  };
102
102
  /** A loopback device: every byte written is echoed straight back. */
103
- export declare class EchoDevice extends SerialDevice {
103
+ export declare class LoopbackDevice extends SimulatedDevice {
104
104
  readonly usbVendorId: number;
105
105
  readonly usbProductId: number;
106
106
  readonly serialNumber?: string;
107
- constructor(identity?: DeviceIdentity);
107
+ constructor(identity?: SimulatedDeviceIdentity);
108
108
  onData(data: Uint8Array): void;
109
109
  }
110
110
  /**
111
111
  * Base for a line-oriented command/response device: buffers incoming bytes and
112
112
  * calls {@link onLine} for each `\n`-terminated line (trailing CR/LF stripped).
113
113
  */
114
- export declare abstract class LineDevice extends SerialDevice {
114
+ export declare abstract class LineBufferedDevice extends SimulatedDevice {
115
115
  #private;
116
116
  /** Handle one line received from the host. */
117
117
  abstract onLine(line: string): void | Promise<void>;
118
118
  onData(data: Uint8Array): void;
119
119
  }
120
120
  /** A device that accepts writes but never sends anything back. */
121
- export declare class SilentDevice extends SerialDevice {
121
+ export declare class SinkDevice extends SimulatedDevice {
122
122
  readonly usbVendorId: number;
123
123
  readonly usbProductId: number;
124
124
  readonly serialNumber?: string;
125
- constructor(identity?: DeviceIdentity);
125
+ constructor(identity?: SimulatedDeviceIdentity);
126
126
  }
127
- //# sourceMappingURL=serial-device.d.ts.map
127
+ //# sourceMappingURL=simulated-device.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simulated-device.d.ts","sourceRoot":"","sources":["../../../../src/testing/simulated-device.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * A runtime-agnostic suite runner for serial device tests. Write a set of
3
+ * `SerialTest`s once and run them anywhere a {@link SerialPort} exists:
4
+ *
5
+ * - in Jest against a {@link DeviceHandle} simulator (in-memory),
6
+ * - on a device / emulator over a real or WebSocket-backed port, and
7
+ * - against BOTH, then {@link compareTestResults} to prove the device matches
8
+ * the simulator case-for-case.
9
+ *
10
+ * Each test receives an opened, host-side {@link SerialClient}; by default one
11
+ * client is shared across the whole suite (open once, close at the end), which
12
+ * matches how a request/response protocol session behaves. Pass `shared: false`
13
+ * to open and close a fresh client per test. The runner never throws — it
14
+ * collects a {@link SerialTestResult} per case — so it is safe to drive an
15
+ * on-device Self-Test screen.
16
+ *
17
+ * @example One suite, two transports, compared
18
+ * const sim = await runTestSuite(myTests, await virtualPort());
19
+ * const dev = await runTestSuite(myTests, realPort);
20
+ * const rows = compareTestResults(sim, dev); // a row passes when both agree
21
+ */
22
+ import type { SerialOptions, SerialPort } from '../WebSerial';
23
+ import { SerialClient } from './serial-client';
24
+ export type SerialTestResult = {
25
+ name: string;
26
+ passed: boolean;
27
+ error?: string;
28
+ durationMs: number;
29
+ };
30
+ /** One test case. `run` receives an already-opened client (typically per the
31
+ * suite's {@link TestClient}; `SerialClient` by default). */
32
+ export type SerialTest<C = SerialClient> = {
33
+ name: string;
34
+ run(client: C): Promise<void>;
35
+ };
36
+ /** Progress hooks so a UI can render results live as each test completes. */
37
+ export type SerialTestProgress = {
38
+ /** Called just before a test starts running. */
39
+ onStart?: (name: string, index: number, total: number) => void;
40
+ /** Called after each test completes (pass or fail). */
41
+ onResult?: (result: SerialTestResult) => void;
42
+ };
43
+ /**
44
+ * How to build (and tear down) the per-suite client from a port. Provide this to
45
+ * run a higher-level protocol client (e.g. an HCI / NMEA framer built on a
46
+ * {@link SerialClient}) instead of the raw client. `connect` must also open the
47
+ * port; `disconnect` must release it (a `SerialClient.close()` does both).
48
+ */
49
+ export type TestClient<C> = {
50
+ connect(port: SerialPort): Promise<C>;
51
+ disconnect(client: C): Promise<void>;
52
+ };
53
+ export type RunTestSuiteOptions<C = SerialClient> = {
54
+ /** `SerialOptions` for the default client's `open()`. Default {baudRate: 115200}. */
55
+ open?: SerialOptions;
56
+ /** Open/close a fresh client per test instead of sharing one. Default false. */
57
+ shared?: boolean;
58
+ /** Build a protocol client over the port (defaults to an opened SerialClient). */
59
+ client?: TestClient<C>;
60
+ progress?: SerialTestProgress;
61
+ };
62
+ /**
63
+ * Run `tests` against an already-acquired `port`, collecting one result per
64
+ * case. With the default (shared) client the port is opened once and closed at
65
+ * the end; with `shared: false` each test gets a fresh open/close. Never throws.
66
+ */
67
+ export declare function runTestSuite<C = SerialClient>(tests: SerialTest<C>[], port: SerialPort, options?: RunTestSuiteOptions<C>): Promise<SerialTestResult[]>;
68
+ /**
69
+ * Compare two runs of the same suite case-by-case. A row passes when both runs
70
+ * agree (both passed or both failed), so the `candidate` is judged equivalent to
71
+ * the `reference` rather than judged on its own. Cases the candidate produced
72
+ * that the reference never ran are surfaced as failing `candidate: …` rows.
73
+ */
74
+ export declare function compareTestResults(reference: SerialTestResult[], candidate: SerialTestResult[]): SerialTestResult[];
75
+ //# sourceMappingURL=test-suite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-suite.d.ts","sourceRoot":"","sources":["../../../../src/testing/test-suite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAE,UAAU,EAAC,MAAM,cAAc,CAAC;AAE5D,OAAO,EAAC,YAAY,EAAC,MAAM,iBAAiB,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;6DAC6D;AAC7D,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,YAAY,IAAI;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B,CAAC;AAEF,6EAA6E;AAC7E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,gDAAgD;IAChD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/D,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC/C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;IAC1B,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACtC,UAAU,CAAC,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,mBAAmB,CAAC,CAAC,GAAG,YAAY,IAAI;IAClD,qFAAqF;IACrF,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,gFAAgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kFAAkF;IAClF,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACvB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,CAAC;AAaF;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,CAAC,GAAG,YAAY,EACjD,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EACtB,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,mBAAmB,CAAC,CAAC,CAAM,GACnC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAyD7B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,SAAS,EAAE,gBAAgB,EAAE,GAC5B,gBAAgB,EAAE,CA4BpB"}
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Hardware-transport seam for the Web Serial polyfill.
2
+ * Hardware-transport abstraction for the Web Serial polyfill.
3
3
  *
4
4
  * `SerialTransport` is the single interface that the `Serial`/`SerialPort`
5
5
  * classes depend on to talk to "the device". The production implementation
6
6
  * (`UsbSerialModule`, backed by the `NativeUsbSerial` TurboModule — see
7
7
  * {@link ./UsbSerial}) and the in-memory test/dev double
8
- * (`VirtualSerialTransport` — see {@link ./testing/virtual-serial}) both
8
+ * (`InMemorySerialTransport` — see {@link ./testing/in-memory-serial-transport}) both
9
9
  * implement it.
10
10
  *
11
11
  * This module is intentionally free of any `react-native` import. That is what
@@ -91,7 +91,7 @@ export type Subscription = {
91
91
  * The contract every serial transport must satisfy. It mirrors the JS-friendly
92
92
  * surface of `UsbSerialModule` exactly, so `UsbSerialModule implements
93
93
  * SerialTransport` is a faithful 1:1 and any conforming double (e.g.
94
- * `VirtualSerialTransport`) is a drop-in replacement.
94
+ * `InMemorySerialTransport`) is a drop-in replacement.
95
95
  *
96
96
  * Ports are addressed by the pair `(deviceId, portNumber)`. Inbound bytes,
97
97
  * read errors and device attach/detach arrive through the `on*` subscriptions.
@@ -0,0 +1,111 @@
1
+ /**
2
+ * A {@link SerialTransport} that talks to a remote serial port over a WebSocket
3
+ * bridge (run `expose-serial-websocket` on the host — see `bin/expose-serial.js`).
4
+ *
5
+ * Because it implements the same transport contract as the native `UsbSerialModule` and the
6
+ * in-memory `InMemorySerialTransport`, it's a drop-in: the whole `Serial` /
7
+ * `SerialPort` polyfill (streams, signals, reconnect, the conformance suite)
8
+ * works on top of it unchanged.
9
+ *
10
+ * **Resilience.** The socket is supervised: an unexpected drop (server restart,
11
+ * network blip) is handled transparently — the transport reconnects with
12
+ * exponential backoff and *restores the session* (line coding, control signals,
13
+ * and read state) so the app's open `SerialPort` keeps working. A transient
14
+ * drop is therefore invisible to the polyfill (reads pause and resume; writes
15
+ * issued during the gap wait for the reconnection). Only a real remote
16
+ * disconnect (the host's serial port going away) or giving up after
17
+ * `maxReconnectAttempts` surfaces as a serial disconnect.
18
+ *
19
+ * @example
20
+ * import {Serial} from 'react-native-web-serial-api';
21
+ * import {WebSocketSerialTransport} from 'react-native-web-serial-api/websocket';
22
+ *
23
+ * const serial = new Serial(new WebSocketSerialTransport('ws://localhost:8080'));
24
+ * const [port] = await serial.getPorts();
25
+ * await port.open({baudRate: 115200});
26
+ */
27
+ import type { ConnectEvent, ControlLine, DataEvent, ErrorEvent, FlowControl, OpenOptions, PortFilter, PortId, PortPickerLabels, SerialTransport, Subscription } from '../transport';
28
+ /** Minimal structural WebSocket shape (browser & React Native both provide it). */
29
+ export interface WebSocketLike {
30
+ binaryType: string;
31
+ send(data: string | ArrayBufferLike | ArrayBufferView): void;
32
+ close(): void;
33
+ addEventListener(type: string, listener: (event: {
34
+ data?: unknown;
35
+ }) => void): void;
36
+ }
37
+ export type WebSocketCtor = new (url: string) => WebSocketLike;
38
+ export type WebSocketConnectionState = 'connecting' | 'open' | 'reconnecting' | 'suspended' | 'closed';
39
+ export type WebSocketSerialOptions = {
40
+ /** USB identity to report from getInfo() (cosmetic; default 0/0). */
41
+ usbVendorId?: number;
42
+ usbProductId?: number;
43
+ /** Serial number returned by getSerial(). */
44
+ serialNumber?: string;
45
+ /** WebSocket implementation (defaults to the global). Injectable for tests. */
46
+ WebSocket?: WebSocketCtor;
47
+ /** Auto-reconnect when the socket drops unexpectedly. Default `true`. */
48
+ reconnect?: boolean;
49
+ /** Initial reconnect backoff in ms; doubles each attempt. Default 250. */
50
+ reconnectInitialDelayMs?: number;
51
+ /** Cap on the reconnect backoff in ms. Default 10000. */
52
+ reconnectMaxDelayMs?: number;
53
+ /** Give up (surface a disconnect) after this many consecutive failures. Default Infinity. */
54
+ maxReconnectAttempts?: number;
55
+ /** How long write()/commands wait for a (re)connection before failing. Default 10000. */
56
+ connectTimeoutMs?: number;
57
+ /** How long a control command waits for its response before failing. Default 10000. */
58
+ commandTimeoutMs?: number;
59
+ /** Notified before each reconnect attempt (1-based attempt + the chosen delay). */
60
+ onReconnecting?: (attempt: number, delayMs: number) => void;
61
+ /** Notified once (re)connected; `reconnected` is false only for the first connect. */
62
+ onConnected?: (info: {
63
+ reconnected: boolean;
64
+ }) => void;
65
+ /** Notified when the transport is permanently closed (gave up, or `disconnect()`). */
66
+ onClosed?: (reason: string) => void;
67
+ };
68
+ type Listener<E> = (event: E) => void;
69
+ export declare class WebSocketSerialTransport implements SerialTransport {
70
+ #private;
71
+ constructor(url: string, options?: WebSocketSerialOptions);
72
+ /** Current connection state (advisory; the polyfill keeps working across reconnects). */
73
+ get connectionState(): WebSocketConnectionState;
74
+ findAllDrivers(): Promise<ReadonlyArray<PortId>>;
75
+ showPortPicker(_filter: ReadonlyArray<PortFilter>, _labels?: PortPickerLabels): Promise<PortId>;
76
+ requestPermission(_deviceId: number): Promise<boolean>;
77
+ open(_deviceId: number, _portNumber: number, options: OpenOptions): Promise<void>;
78
+ close(_deviceId: number, _portNumber: number): Promise<void>;
79
+ isOpen(_deviceId: number, _portNumber: number): boolean;
80
+ write(_deviceId: number, _portNumber: number, data: number[], _timeout?: number): Promise<void>;
81
+ startReading(_deviceId: number, _portNumber: number): Promise<void>;
82
+ stopReading(_deviceId: number, _portNumber: number): Promise<void>;
83
+ setParameters(_deviceId: number, _portNumber: number, options: OpenOptions): Promise<void>;
84
+ setDTR(_d: number, _p: number, value: boolean): Promise<void>;
85
+ setRTS(_d: number, _p: number, value: boolean): Promise<void>;
86
+ getDTR(_d: number, _p: number): Promise<boolean>;
87
+ getRTS(_d: number, _p: number): Promise<boolean>;
88
+ getCD(_d: number, _p: number): Promise<boolean>;
89
+ getCTS(_d: number, _p: number): Promise<boolean>;
90
+ getDSR(_d: number, _p: number): Promise<boolean>;
91
+ getRI(_d: number, _p: number): Promise<boolean>;
92
+ getControlLines(_d: number, _p: number): Promise<ControlLine[]>;
93
+ getSupportedControlLines(_d: number, _p: number): Promise<ControlLine[]>;
94
+ setFlowControl(_d: number, _p: number, _flowControl: FlowControl): Promise<void>;
95
+ getFlowControl(_d: number, _p: number): Promise<FlowControl>;
96
+ getSupportedFlowControl(_d: number, _p: number): Promise<FlowControl[]>;
97
+ setBreak(_d: number, _p: number, value: boolean): Promise<void>;
98
+ purgeHwBuffers(_d: number, _p: number, _purgeWrite: boolean, _purgeRead: boolean): Promise<void>;
99
+ getSerial(_d: number, _p: number): Promise<string>;
100
+ onData(listener: Listener<DataEvent>): Subscription;
101
+ onError(listener: Listener<ErrorEvent>): Subscription;
102
+ onConnect(listener: Listener<ConnectEvent>): Subscription;
103
+ onDisconnect(listener: Listener<ConnectEvent>): Subscription;
104
+ /**
105
+ * Permanently close the transport (and the remote port session). Cancels any
106
+ * pending reconnect and stops auto-reconnecting.
107
+ */
108
+ disconnect(): void;
109
+ }
110
+ export {};
111
+ //# sourceMappingURL=WebSocketSerialTransport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketSerialTransport.d.ts","sourceRoot":"","sources":["../../../../src/websocket/WebSocketSerialTransport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Server-side core of the WebSocket serial bridge — deliberately free of any
3
+ * `serialport`/`ws`/Node imports so it is unit-testable with fakes. The thin
4
+ * `bin/expose-serial.js` wrapper supplies the real `serialport` port and `ws`
5
+ * socket objects and calls {@link attachBridge} for each connection.
6
+ */
7
+ import { type PortInfo } from './protocol';
8
+ /** The subset of a `serialport` SerialPort that the bridge uses (callback API). */
9
+ export interface SerialLike {
10
+ write(data: Uint8Array, cb?: (err?: Error | null) => void): void;
11
+ set(opts: {
12
+ dtr?: boolean;
13
+ rts?: boolean;
14
+ brk?: boolean;
15
+ }, cb?: (err?: Error | null) => void): void;
16
+ get(cb: (err: Error | null, signals?: {
17
+ cts?: boolean;
18
+ dsr?: boolean;
19
+ dcd?: boolean;
20
+ ri?: boolean;
21
+ }) => void): void;
22
+ update(opts: {
23
+ baudRate: number;
24
+ }, cb?: (err?: Error | null) => void): void;
25
+ flush(cb?: (err?: Error | null) => void): void;
26
+ drain(cb?: (err?: Error | null) => void): void;
27
+ close(cb?: (err?: Error | null) => void): void;
28
+ on(event: 'data', listener: (data: Uint8Array) => void): void;
29
+ on(event: 'error', listener: (err: Error) => void): void;
30
+ on(event: 'open' | 'close', listener: () => void): void;
31
+ removeListener(event: string, listener: (...args: never[]) => void): void;
32
+ }
33
+ /** The subset of a `ws` WebSocket that the bridge uses. */
34
+ export interface WsLike {
35
+ send(data: string | Uint8Array): void;
36
+ on(event: 'message', listener: (data: unknown, isBinary: boolean) => void): void;
37
+ on(event: 'close', listener: () => void): void;
38
+ on(event: 'error', listener: (err: Error) => void): void;
39
+ }
40
+ export type BridgeOptions = {
41
+ /** Whether the port forwards serial→client data before `startReading`. */
42
+ readingByDefault?: boolean;
43
+ /** Optional logger for diagnostics. */
44
+ log?: (message: string) => void;
45
+ /** Optional static metadata or callback exposed via `getPortInfo`. */
46
+ portInfo?: PortInfo | (() => PortInfo | null | undefined);
47
+ };
48
+ /**
49
+ * Wire a single WebSocket connection to a single serial port: binary frames are
50
+ * piped both ways, JSON control frames invoke the corresponding serial method
51
+ * and get a response, and the port's data/error/close are surfaced as frames.
52
+ * Returns a teardown function that detaches the serial listeners.
53
+ */
54
+ export declare function attachBridge(serial: SerialLike, ws: WsLike, options?: BridgeOptions): () => void;
55
+ export type BridgeArgs = {
56
+ port?: string;
57
+ baudRate: number;
58
+ wsPort: number;
59
+ host: string;
60
+ allowRemote: boolean;
61
+ help: boolean;
62
+ };
63
+ /** Parse `expose-serial` CLI args + env into a normalised options object. */
64
+ export declare function parseBridgeArgs(argv: readonly string[], env?: Record<string, string | undefined>): BridgeArgs;
65
+ export declare const USAGE = "expose-serial-websocket \u2014 bridge a serial port to a WebSocket\n\nUsage:\n expose-serial-websocket --port <path> [--baudrate 115200] [--ws-port 8080]\n [--host 127.0.0.1] [--allow-remote]\n\nOptions:\n -p, --port <path> Serial device (e.g. /dev/ttyUSB0, COM3) [env SERIAL_PORT]\n -b, --baudrate <n> Baud rate (default 115200) [env BAUD_RATE]\n -w, --ws-port <n> WebSocket port (default 8080) [env WS_PORT]\n --host <addr> Listen address (default 127.0.0.1) [env HOST]\n --allow-remote Bind 0.0.0.0 (exposes the port to the network!)\n -h, --help Show this help\n\nConnect from the app with:\n new Serial(new WebSocketSerialTransport('ws://localhost:8080'))\n";
66
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../../../src/websocket/bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Remote-serial-over-WebSocket entry point.
3
+ *
4
+ * import {WebSocketSerialTransport}
5
+ * from 'react-native-web-serial-api/websocket';
6
+ *
7
+ * Client side: `WebSocketSerialTransport` connects an app to a remote serial
8
+ * port. Server side: {@link attachBridge} pipes a WebSocket to a serial port,
9
+ * and {@link SimulatedDeviceToSerialLike} lets that "serial port" be an in-memory
10
+ * {@link SimulatedDevice} simulator (used by `testing/exposeSimulatedDevice`). The
11
+ * wire protocol is in {@link ./protocol}; the bridge core in {@link ./bridge}.
12
+ */
13
+ export type { BridgeOptions, SerialLike, WsLike } from './bridge';
14
+ export { attachBridge } from './bridge';
15
+ export type { CommandMessage, CommandName, ControlMessage, EventMessage, InputSignals, LineCoding, Parity, PortInfo, ResponseMessage, } from './protocol';
16
+ export { portInfoFromDevice, SimulatedDeviceToSerialLike, } from './serial-device-bridge';
17
+ export type { WebSocketCtor, WebSocketLike, WebSocketSerialOptions, } from './WebSocketSerialTransport';
18
+ export { WebSocketSerialTransport } from './WebSocketSerialTransport';
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/websocket/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,YAAY,EAAC,aAAa,EAAE,UAAU,EAAE,MAAM,EAAC,MAAM,UAAU,CAAC;AAChE,OAAO,EAAC,YAAY,EAAC,MAAM,UAAU,CAAC;AACtC,YAAY,EACV,cAAc,EACd,WAAW,EACX,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,MAAM,EACN,QAAQ,EACR,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,aAAa,EACb,aAAa,EACb,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAC,wBAAwB,EAAC,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Wire protocol shared by the WebSocket serial bridge (Node, see
3
+ * {@link ./bridge}) and the client transport ({@link ./WebSocketSerialTransport}).
4
+ *
5
+ * A single WebSocket carries two kinds of frame:
6
+ * - **binary** frames are raw serial bytes (client→bridge = data to write to the
7
+ * port; bridge→client = data read from the port), and
8
+ * - **text** frames are JSON control messages (this module).
9
+ *
10
+ * Control flow is request/response: the client tags each {@link CommandMessage}
11
+ * with an incrementing `id` and the bridge replies with a {@link ResponseMessage}
12
+ * carrying the same `id`. The bridge may also push unsolicited
13
+ * {@link EventMessage}s (port open/close/error).
14
+ */
15
+ import type { Parity } from '../WebSerial';
16
+ export type { Parity } from '../WebSerial';
17
+ /** Connection parameters for `setLineCoding`. */
18
+ export type LineCoding = {
19
+ baudRate: number;
20
+ dataBits?: number;
21
+ stopBits?: number;
22
+ parity?: Parity;
23
+ };
24
+ /** Input control-signal state returned by `getSignals`. */
25
+ export type InputSignals = {
26
+ cts: boolean;
27
+ dsr: boolean;
28
+ dcd: boolean;
29
+ ri: boolean;
30
+ };
31
+ /** Optional serial-port metadata returned by `getPortInfo`. */
32
+ export type PortInfo = {
33
+ usbVendorId?: number;
34
+ usbProductId?: number;
35
+ serialNumber?: string;
36
+ };
37
+ /** Every control command the bridge understands. */
38
+ export type CommandName = 'setLineCoding' | 'setBaudRate' | 'setSignals' | 'getSignals' | 'getPortInfo' | 'startReading' | 'stopReading' | 'flush' | 'drain' | 'break' | 'close';
39
+ /** client → bridge */
40
+ export type CommandMessage = {
41
+ type: 'command';
42
+ id: number;
43
+ command: CommandName;
44
+ args?: Record<string, unknown>;
45
+ };
46
+ /** bridge → client, answering a {@link CommandMessage} with the same `id`. */
47
+ export type ResponseMessage = {
48
+ type: 'response';
49
+ id: number;
50
+ error: string | null;
51
+ result?: unknown;
52
+ };
53
+ /** An unsolicited port lifecycle event the bridge emits to the client. */
54
+ export type BridgeEventName = 'open' | 'close' | 'error';
55
+ /** bridge → client */
56
+ export type EventMessage = {
57
+ type: 'event';
58
+ event: BridgeEventName;
59
+ error?: string;
60
+ };
61
+ export type ControlMessage = CommandMessage | ResponseMessage | EventMessage;
62
+ /** Parse a text frame into a {@link ControlMessage}, or null if it isn't one. */
63
+ export declare function parseControlMessage(text: string): ControlMessage | null;
64
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../../src/websocket/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,cAAc,CAAC;AAEzC,YAAY,EAAC,MAAM,EAAC,MAAM,cAAc,CAAC;AAEzC,iDAAiD;AACjD,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;CACb,CAAC;AAEF,+DAA+D;AAC/D,MAAM,MAAM,QAAQ,GAAG;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,WAAW,GACnB,eAAe,GACf,aAAa,GACb,YAAY,GACZ,YAAY,GACZ,aAAa,GACb,cAAc,GACd,aAAa,GACb,OAAO,GACP,OAAO,GACP,OAAO,GACP,OAAO,CAAC;AAEZ,sBAAsB;AACtB,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAEzD,sBAAsB;AACtB,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,eAAe,GAAG,YAAY,CAAC;AAE7E,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAevE"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Adapt an in-memory {@link DeviceHandle} simulator to the
3
+ * `serialport`-style {@link SerialLike} that {@link attachBridge} consumes, so a
4
+ * test can expose a *simulated* device over a WebSocket and have a real app
5
+ * connect to it with `new WebSocketSerialTransport(url)`. This is what makes the
6
+ * *same* device-driving test run in Jest (in-memory) and on a device/emulator
7
+ * (over the socket).
8
+ *
9
+ * Pure — no `ws`/`serialport`/Node imports — so it unit-tests with the same
10
+ * `FakeWs` + `attachBridge` fakes the bridge core uses. The lazy-`ws` server
11
+ * wrapper lives in `react-native-web-serial-api/testing` (`exposeSimulatedDevice`).
12
+ *
13
+ * Mapping (host app ⇄ simulator):
14
+ * - client binary frame → `write` → `device.onData`,
15
+ * - `setLineCoding`/`setBaudRate` → `update` → opens the sim + starts reading,
16
+ * - `setSignals` → `set` → `device.onHostSignals`,
17
+ * - `getSignals` → `get` → the device's asserted input signals,
18
+ * - `device.send(...)` → `'data'` → client binary frame,
19
+ * - `getPortInfo` → {@link portInfoFromDevice}.
20
+ */
21
+ import type { DeviceHandle, InMemorySerialTransport } from '../testing/in-memory-serial-transport';
22
+ import type { SerialLike } from './bridge';
23
+ import type { PortInfo } from './protocol';
24
+ /** The bridge's `getPortInfo` metadata, taken from the device's USB identity. */
25
+ export declare function portInfoFromDevice(device: DeviceHandle): PortInfo;
26
+ /**
27
+ * Build a {@link SerialLike} backed by `device` (already registered on
28
+ * `transport` via `addDevice`). One adapter per WebSocket connection; its
29
+ * transport subscriptions are released when the bridge tears down its listeners.
30
+ */
31
+ export declare function SimulatedDeviceToSerialLike(transport: InMemorySerialTransport, device: DeviceHandle): SerialLike;
32
+ //# sourceMappingURL=serial-device-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serial-device-bridge.d.ts","sourceRoot":"","sources":["../../../../src/websocket/serial-device-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,uBAAuB,EACxB,MAAM,uCAAuC,CAAC;AAE/C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAC;AAEzC,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,QAAQ,CAMjE;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,uBAAuB,EAClC,MAAM,EAAE,YAAY,GACnB,UAAU,CAiHZ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-web-serial-api",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
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
5
  "main": "lib/commonjs/index.js",
6
6
  "react-native": "lib/commonjs/index.js",
@@ -19,15 +19,26 @@
19
19
  "react-native": "./lib/commonjs/testing/index.js",
20
20
  "default": "./lib/commonjs/testing/index.js"
21
21
  },
22
+ "./websocket": {
23
+ "source": "./src/websocket/index.ts",
24
+ "types": "./lib/typescript/src/websocket/index.d.ts",
25
+ "react-native": "./lib/commonjs/websocket/index.js",
26
+ "default": "./lib/commonjs/websocket/index.js"
27
+ },
22
28
  "./package.json": "./package.json"
23
29
  },
30
+ "bin": {
31
+ "expose-serial-websocket": "bin/expose-serial.js"
32
+ },
24
33
  "files": [
25
34
  "src",
26
35
  "lib",
27
36
  "android",
37
+ "bin",
28
38
  "react-native.config.js",
29
39
  "TESTING.md",
30
40
  "!android/build",
41
+ "!android/src/test",
31
42
  "!**/__tests__",
32
43
  "!**/__fixtures__",
33
44
  "!**/__mocks__",
@@ -37,11 +48,12 @@
37
48
  "prepare": "bob build",
38
49
  "clean": "node -e \"require('fs').rm(\\\"./lib\\\", { recursive: true }, () => {console.log(\\\"'lib' folder deleted\\\")})\"",
39
50
  "typecheck": "tsc --noEmit",
40
- "lint": "biome check src",
41
- "format": "biome check --write src",
51
+ "lint": "biome check",
52
+ "format": "biome check --write",
42
53
  "test": "jest",
43
54
  "test:watch": "jest --watch",
44
55
  "test:coverage": "jest --coverage",
56
+ "test:android": "cd example/android && ./gradlew :react-native-web-serial-api:testDebugUnitTest",
45
57
  "test:emulator:e2e": "npm --prefix example run e2e",
46
58
  "test:host+emulator": "node ./node_modules/jest/bin/jest.js && npm --prefix example run e2e"
47
59
  },
@@ -78,6 +90,10 @@
78
90
  "dependencies": {
79
91
  "web-streams-polyfill": "^4.3.0"
80
92
  },
93
+ "optionalDependencies": {
94
+ "serialport": "^12.0.0",
95
+ "ws": "^8.18.0"
96
+ },
81
97
  "peerDependencies": {
82
98
  "react": "*",
83
99
  "react-native": "*"
@@ -87,7 +103,9 @@
87
103
  "@react-native/babel-preset": "0.85.3",
88
104
  "@react-native/jest-preset": "0.85.3",
89
105
  "@types/jest": "^29.5.14",
106
+ "@types/node": "^25.9.2",
90
107
  "@types/react": "^19.2.6",
108
+ "@types/ws": "^8.18.1",
91
109
  "babel-jest": "29.6.3",
92
110
  "jest": "29.6.3",
93
111
  "react": "19.2.3",