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.
Files changed (177) hide show
  1. package/README.md +198 -104
  2. package/TESTING.md +542 -0
  3. package/android/build.gradle +16 -2
  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 +58 -26
  8. package/lib/commonjs/UsbSerial.js.map +1 -1
  9. package/lib/commonjs/WebSerial.js +273 -77
  10. package/lib/commonjs/WebSerial.js.map +1 -1
  11. package/lib/commonjs/index.js +15 -3
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/commonjs/lib/dom-exception.js +176 -0
  14. package/lib/commonjs/lib/dom-exception.js.map +1 -0
  15. package/lib/commonjs/lib/event-target.js +140 -0
  16. package/lib/commonjs/lib/event-target.js.map +1 -0
  17. package/lib/commonjs/lib/promise.js +23 -0
  18. package/lib/commonjs/lib/promise.js.map +1 -0
  19. package/lib/commonjs/lib/web-streams.js +42 -0
  20. package/lib/commonjs/lib/web-streams.js.map +1 -0
  21. package/lib/commonjs/testing/device-fixture.js +70 -0
  22. package/lib/commonjs/testing/device-fixture.js.map +1 -0
  23. package/lib/commonjs/testing/expose.js +91 -0
  24. package/lib/commonjs/testing/expose.js.map +1 -0
  25. package/lib/commonjs/testing/harness.js +98 -0
  26. package/lib/commonjs/testing/harness.js.map +1 -0
  27. package/lib/commonjs/testing/in-memory-serial-transport.js +653 -0
  28. package/lib/commonjs/testing/in-memory-serial-transport.js.map +1 -0
  29. package/lib/commonjs/testing/index.js +153 -0
  30. package/lib/commonjs/testing/index.js.map +1 -0
  31. package/lib/commonjs/testing/install-in-memory-serial-transport.js +54 -0
  32. package/lib/commonjs/testing/install-in-memory-serial-transport.js.map +1 -0
  33. package/lib/commonjs/testing/serial-client.js +277 -0
  34. package/lib/commonjs/testing/serial-client.js.map +1 -0
  35. package/lib/commonjs/testing/simulated-device.js +164 -0
  36. package/lib/commonjs/testing/simulated-device.js.map +1 -0
  37. package/lib/commonjs/testing/test-suite.js +142 -0
  38. package/lib/commonjs/testing/test-suite.js.map +1 -0
  39. package/lib/commonjs/transport.js +61 -0
  40. package/lib/commonjs/transport.js.map +1 -0
  41. package/lib/commonjs/websocket/WebSocketSerialTransport.js +659 -0
  42. package/lib/commonjs/websocket/WebSocketSerialTransport.js.map +1 -0
  43. package/lib/commonjs/websocket/bridge.js +234 -0
  44. package/lib/commonjs/websocket/bridge.js.map +1 -0
  45. package/lib/commonjs/websocket/index.js +33 -0
  46. package/lib/commonjs/websocket/index.js.map +1 -0
  47. package/lib/commonjs/websocket/protocol.js +55 -0
  48. package/lib/commonjs/websocket/protocol.js.map +1 -0
  49. package/lib/commonjs/websocket/serial-device-bridge.js +130 -0
  50. package/lib/commonjs/websocket/serial-device-bridge.js.map +1 -0
  51. package/lib/typescript/src/UsbSerial.d.ts +24 -67
  52. package/lib/typescript/src/UsbSerial.d.ts.map +1 -1
  53. package/lib/typescript/src/WebSerial.d.ts +16 -7
  54. package/lib/typescript/src/WebSerial.d.ts.map +1 -1
  55. package/lib/typescript/src/index.d.ts +3 -1
  56. package/lib/typescript/src/index.d.ts.map +1 -1
  57. package/lib/typescript/src/lib/dom-exception.d.ts +100 -0
  58. package/lib/typescript/src/lib/dom-exception.d.ts.map +1 -0
  59. package/lib/typescript/src/lib/event-target.d.ts +55 -0
  60. package/lib/typescript/src/lib/event-target.d.ts.map +1 -0
  61. package/lib/typescript/src/lib/promise.d.ts +11 -0
  62. package/lib/typescript/src/lib/promise.d.ts.map +1 -0
  63. package/lib/typescript/src/lib/web-streams.d.ts +9 -0
  64. package/lib/typescript/src/lib/web-streams.d.ts.map +1 -0
  65. package/lib/typescript/src/testing/device-fixture.d.ts +70 -0
  66. package/lib/typescript/src/testing/device-fixture.d.ts.map +1 -0
  67. package/lib/typescript/src/testing/expose.d.ts +71 -0
  68. package/lib/typescript/src/testing/expose.d.ts.map +1 -0
  69. package/lib/typescript/src/testing/harness.d.ts +34 -0
  70. package/lib/typescript/src/testing/harness.d.ts.map +1 -0
  71. package/lib/typescript/src/testing/in-memory-serial-transport.d.ts +216 -0
  72. package/lib/typescript/src/testing/in-memory-serial-transport.d.ts.map +1 -0
  73. package/lib/typescript/src/testing/index.d.ts +33 -0
  74. package/lib/typescript/src/testing/index.d.ts.map +1 -0
  75. package/lib/typescript/src/testing/install-in-memory-serial-transport.d.ts +25 -0
  76. package/lib/typescript/src/testing/install-in-memory-serial-transport.d.ts.map +1 -0
  77. package/lib/typescript/src/testing/serial-client.d.ts +62 -0
  78. package/lib/typescript/src/testing/serial-client.d.ts.map +1 -0
  79. package/lib/typescript/src/testing/simulated-device.d.ts +127 -0
  80. package/lib/typescript/src/testing/simulated-device.d.ts.map +1 -0
  81. package/lib/typescript/src/testing/test-suite.d.ts +75 -0
  82. package/lib/typescript/src/testing/test-suite.d.ts.map +1 -0
  83. package/lib/typescript/src/transport.d.ts +131 -0
  84. package/lib/typescript/src/transport.d.ts.map +1 -0
  85. package/lib/typescript/src/websocket/WebSocketSerialTransport.d.ts +111 -0
  86. package/lib/typescript/src/websocket/WebSocketSerialTransport.d.ts.map +1 -0
  87. package/lib/typescript/src/websocket/bridge.d.ts +66 -0
  88. package/lib/typescript/src/websocket/bridge.d.ts.map +1 -0
  89. package/lib/typescript/src/websocket/index.d.ts +19 -0
  90. package/lib/typescript/src/websocket/index.d.ts.map +1 -0
  91. package/lib/typescript/src/websocket/protocol.d.ts +64 -0
  92. package/lib/typescript/src/websocket/protocol.d.ts.map +1 -0
  93. package/lib/typescript/src/websocket/serial-device-bridge.d.ts +32 -0
  94. package/lib/typescript/src/websocket/serial-device-bridge.d.ts.map +1 -0
  95. package/package.json +57 -3
  96. package/src/UsbSerial.ts +65 -90
  97. package/src/WebSerial.ts +351 -113
  98. package/src/index.ts +6 -8
  99. package/src/lib/dom-exception.ts +129 -60
  100. package/src/lib/event-target.ts +58 -21
  101. package/src/lib/promise.ts +7 -7
  102. package/src/lib/web-streams.ts +43 -0
  103. package/src/testing/device-fixture.ts +150 -0
  104. package/src/testing/expose.ts +147 -0
  105. package/src/testing/harness.ts +124 -0
  106. package/src/testing/in-memory-serial-transport.ts +840 -0
  107. package/src/testing/index.ts +90 -0
  108. package/src/testing/install-in-memory-serial-transport.ts +65 -0
  109. package/src/testing/serial-client.ts +313 -0
  110. package/src/testing/simulated-device.ts +193 -0
  111. package/src/testing/test-suite.ts +186 -0
  112. package/src/transport.ts +200 -0
  113. package/src/websocket/WebSocketSerialTransport.ts +796 -0
  114. package/src/websocket/bridge.ts +299 -0
  115. package/src/websocket/index.ts +38 -0
  116. package/src/websocket/protocol.ts +101 -0
  117. package/src/websocket/serial-device-bridge.ts +160 -0
  118. package/babel.config.js +0 -3
  119. package/biome.json +0 -35
  120. package/example/.watchmanconfig +0 -1
  121. package/example/App.tsx +0 -71
  122. package/example/__tests__/App.test.tsx +0 -16
  123. package/example/__tests__/connectEvents.test.tsx +0 -81
  124. package/example/__tests__/getPorts.test.tsx +0 -140
  125. package/example/android/app/build.gradle +0 -120
  126. package/example/android/app/debug.keystore +0 -0
  127. package/example/android/app/proguard-rules.pro +0 -10
  128. package/example/android/app/src/debug/AndroidManifest.xml +0 -9
  129. package/example/android/app/src/main/AndroidManifest.xml +0 -38
  130. package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +0 -22
  131. package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +0 -41
  132. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -37
  133. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  134. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  135. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  136. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  137. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  138. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  139. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  140. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  141. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  142. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  143. package/example/android/app/src/main/res/values/strings.xml +0 -3
  144. package/example/android/app/src/main/res/values/styles.xml +0 -9
  145. package/example/android/build.gradle +0 -22
  146. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  147. package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  148. package/example/android/gradle.properties +0 -47
  149. package/example/android/gradlew +0 -252
  150. package/example/android/gradlew.bat +0 -94
  151. package/example/android/settings.gradle +0 -6
  152. package/example/app.json +0 -4
  153. package/example/babel.config.js +0 -21
  154. package/example/biome.json +0 -47
  155. package/example/deploy.sh +0 -11
  156. package/example/index.html +0 -26
  157. package/example/index.js +0 -9
  158. package/example/index.web.js +0 -8
  159. package/example/jest.config.js +0 -12
  160. package/example/metro.config.js +0 -58
  161. package/example/package-lock.json +0 -14510
  162. package/example/package.json +0 -48
  163. package/example/react-native.config.js +0 -17
  164. package/example/src/components/AppBar.tsx +0 -73
  165. package/example/src/components/Menu.tsx +0 -90
  166. package/example/src/components/SingleChoiceDialog.tsx +0 -120
  167. package/example/src/screens/ConnectScreen.tsx +0 -195
  168. package/example/src/screens/DevicesScreen.tsx +0 -252
  169. package/example/src/screens/TerminalScreen.tsx +0 -572
  170. package/example/src/settings.ts +0 -43
  171. package/example/src/theme.ts +0 -19
  172. package/example/src/util/TextUtil.ts +0 -129
  173. package/example/tsconfig.json +0 -10
  174. package/example/vite.config.mjs +0 -55
  175. package/scripts/deploy-release.sh +0 -127
  176. package/tsconfig.build.json +0 -7
  177. package/tsconfig.json +0 -20
@@ -0,0 +1,147 @@
1
+ /**
2
+ * `exposeSimulatedDevice` — run a {@link SimulatedDevice} simulator behind a WebSocket
3
+ * server so a real app (on a device/emulator) can connect to it with
4
+ * `new Serial(new WebSocketSerialTransport(url))` and drive the *same* simulated
5
+ * peripheral your Jest tests drive. The test process keeps the
6
+ * {@link DeviceHandle} handle, so it can inject frames / move the GPS and
7
+ * `await whenOpened()` for the app to connect — turning an in-memory device
8
+ * suite into an on-device E2E without changing the device code.
9
+ *
10
+ * `ws` is an *optional* dependency: it is loaded lazily (and only in Node), via
11
+ * an indirect require so app bundlers never pull it into a mobile bundle. Pass
12
+ * `options.WebSocketServer` to inject your own (e.g. `import {WebSocketServer}
13
+ * from 'ws'`) and skip the lazy load entirely.
14
+ *
15
+ * @example
16
+ * import {WebSocketServer} from 'ws';
17
+ * const ex = exposeSimulatedDevice(new WMBusGateway('iU891A-XL'), {
18
+ * port: 8090,
19
+ * WebSocketServer,
20
+ * });
21
+ * // …app connects to ex.url…
22
+ * await ex.whenOpened();
23
+ * ex.simulatedDevice.addMeter(meter);
24
+ * meter.sendTelegram(); // the app receives the 0x20 telegram event
25
+ * await ex.close();
26
+ */
27
+
28
+ import {
29
+ attachBridge,
30
+ portInfoFromDevice,
31
+ SimulatedDeviceToSerialLike,
32
+ type WsLike,
33
+ } from '../websocket';
34
+ import type {DeviceOptions} from './in-memory-serial-transport';
35
+ import {
36
+ type DeviceHandle,
37
+ InMemorySerialTransport,
38
+ } from './in-memory-serial-transport';
39
+ import type {
40
+ SimulatedDevice,
41
+ SimulatedDeviceOpenOptions,
42
+ } from './simulated-device';
43
+
44
+ /** The `ws` WebSocketServer surface this helper uses. */
45
+ export type WebSocketServerLike = {
46
+ on(event: 'connection', listener: (socket: WsLike) => void): void;
47
+ close(cb?: () => void): void;
48
+ };
49
+
50
+ /** A `ws`-compatible `WebSocketServer` constructor. */
51
+ export type WebSocketServerCtor = new (options: {
52
+ port: number;
53
+ host?: string;
54
+ }) => WebSocketServerLike;
55
+
56
+ export type ExposeSimulatedDeviceOptions = {
57
+ /** TCP port for the WebSocket server. */
58
+ port: number;
59
+ /** Listen address. Defaults to `localhost`. Use `0.0.0.0` for an emulator. */
60
+ host?: string;
61
+ /** Inject a `ws`-compatible `WebSocketServer` (skips the lazy `require('ws')`). */
62
+ WebSocketServer?: WebSocketServerCtor;
63
+ /** Forward device→client data before the app sends startReading. Default true. */
64
+ readingByDefault?: boolean;
65
+ /** Diagnostics logger. */
66
+ log?: (message: string) => void;
67
+ /** Transport-side device options (hasPermission defaults to true). */
68
+ device?: DeviceOptions;
69
+ };
70
+
71
+ export type ExposedDevice<D extends SimulatedDevice = SimulatedDevice> = {
72
+ /** The URL the app connects to, e.g. `ws://localhost:8090`. */
73
+ url: string;
74
+ transport: InMemorySerialTransport;
75
+ /** The transport-side handle: push/emitError/whenOpened/whenClosed/… */
76
+ device: DeviceHandle;
77
+ /** The concrete device simulator, typed (drive it from the test). */
78
+ simulatedDevice: D;
79
+ /** Resolve when the app opens the port (now if already open). */
80
+ whenOpened(): Promise<SimulatedDeviceOpenOptions>;
81
+ /** Resolve when the app closes the port (now if not open). */
82
+ whenClosed(): Promise<void>;
83
+ /** Stop the WebSocket server. */
84
+ close(): Promise<void>;
85
+ };
86
+
87
+ /** Resolve a `ws` WebSocketServer lazily, in Node only, invisibly to bundlers. */
88
+ function loadWebSocketServer(): WebSocketServerCtor {
89
+ const specifier = 'ws';
90
+ const nodeRequire =
91
+ // @ts-ignore
92
+ typeof module !== 'undefined' &&
93
+ // @ts-ignore
94
+ typeof (module as {require?: unknown}).require === 'function'
95
+ ? // @ts-ignore
96
+ (module as {require: (id: string) => unknown}).require.bind(module)
97
+ : /* istanbul ignore next */ undefined;
98
+ /* istanbul ignore next — only reachable in non-Node bundled environments */
99
+ if (!nodeRequire) {
100
+ throw new Error(
101
+ "exposeSimulatedDevice could not load 'ws'. Pass options.WebSocketServer, " +
102
+ 'or run it in a Node process with the optional `ws` package installed.',
103
+ );
104
+ }
105
+ const ws = nodeRequire(specifier) as {
106
+ WebSocketServer?: WebSocketServerCtor;
107
+ Server?: WebSocketServerCtor;
108
+ };
109
+ const Ctor = ws.WebSocketServer ?? ws.Server;
110
+ if (!Ctor) {
111
+ throw new Error("the 'ws' package did not export a WebSocketServer.");
112
+ }
113
+ return Ctor;
114
+ }
115
+
116
+ export function exposeSimulatedDevice<D extends SimulatedDevice>(
117
+ simulatedDevice: D,
118
+ options: ExposeSimulatedDeviceOptions,
119
+ ): ExposedDevice<D> {
120
+ const transport = new InMemorySerialTransport();
121
+ const device = transport.addDevice(simulatedDevice, {
122
+ hasPermission: true,
123
+ ...options.device,
124
+ });
125
+
126
+ const Ctor = options.WebSocketServer ?? loadWebSocketServer();
127
+ const server = new Ctor({port: options.port, host: options.host});
128
+ server.on('connection', socket => {
129
+ const serial = SimulatedDeviceToSerialLike(transport, device);
130
+ attachBridge(serial, socket, {
131
+ portInfo: portInfoFromDevice(device),
132
+ readingByDefault: options.readingByDefault,
133
+ log: options.log,
134
+ });
135
+ });
136
+
137
+ const host = options.host ?? 'localhost';
138
+ return {
139
+ url: `ws://${host}:${options.port}`,
140
+ transport,
141
+ device,
142
+ simulatedDevice,
143
+ whenOpened: () => device.whenOpened(),
144
+ whenClosed: () => device.whenClosed(),
145
+ close: () => new Promise<void>(resolve => server.close(() => resolve())),
146
+ };
147
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Tiny, dependency-free test helpers for driving serial devices — shared by the
3
+ * library's own conformance suite, the {@link SerialClient}, and consumers' app
4
+ * tests. Free of `jest` and `react-native`, so they run under any test runner
5
+ * and on a real device (e.g. an on-device Self-Test screen).
6
+ */
7
+
8
+ /** Throw `message` if `condition` is falsy. */
9
+ export function assert(condition: unknown, message: string): asserts condition {
10
+ if (!condition) throw new Error(message);
11
+ }
12
+
13
+ /** Throw if `actual !== expected`, including both values in the message. */
14
+ export function assertEqual<T>(actual: T, expected: T, message: string): void {
15
+ if (actual !== expected) {
16
+ throw new Error(
17
+ `${message} (expected ${String(expected)}, got ${String(actual)})`,
18
+ );
19
+ }
20
+ }
21
+
22
+ /** Byte-for-byte equality of two sequences. */
23
+ export function bytesEqual(
24
+ a: ArrayLike<number>,
25
+ b: ArrayLike<number>,
26
+ ): boolean {
27
+ if (a.length !== b.length) return false;
28
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
29
+ return true;
30
+ }
31
+
32
+ /** What {@link assertRejects} may additionally check about the thrown error. */
33
+ export type RejectionExpectation = {
34
+ name?: string;
35
+ type?: new (...args: never[]) => Error;
36
+ message?: string | RegExp;
37
+ };
38
+
39
+ /** Assert `fn()` rejects (optionally matching the error name/type/message). */
40
+ export async function assertRejects(
41
+ fn: () => Promise<unknown>,
42
+ message: string,
43
+ expected?: RejectionExpectation,
44
+ ): Promise<void> {
45
+ try {
46
+ await fn();
47
+ } catch (e) {
48
+ const err = e as Error;
49
+ if (expected?.name && err.name !== expected.name) {
50
+ throw new Error(
51
+ `${message}: expected error "${expected.name}" but got "${err.name}"`,
52
+ );
53
+ }
54
+ if (expected?.type && !(err instanceof expected.type)) {
55
+ throw new Error(`${message}: expected a ${expected.type.name}`);
56
+ }
57
+ if (expected?.message !== undefined) {
58
+ const ok =
59
+ expected.message instanceof RegExp
60
+ ? expected.message.test(err.message)
61
+ : err.message.includes(expected.message);
62
+ if (!ok) {
63
+ throw new Error(
64
+ `${message}: expected the error message to match ${String(expected.message)}`,
65
+ );
66
+ }
67
+ }
68
+ return;
69
+ }
70
+ throw new Error(`${message}: expected a rejection but none occurred`);
71
+ }
72
+
73
+ /** Normalise any thrown value into a `"Name: message"` string. */
74
+ export function errorMessage(e: unknown): string {
75
+ return e instanceof Error ? `${e.name}: ${e.message}` : String(e);
76
+ }
77
+
78
+ /** Reject if `promise` doesn't settle within `ms`; `label` names it on timeout. */
79
+ export function withTimeout<T>(
80
+ promise: Promise<T>,
81
+ ms: number,
82
+ label: string,
83
+ ): Promise<T> {
84
+ return new Promise<T>((resolve, reject) => {
85
+ const timer = setTimeout(
86
+ () => reject(new Error(`timed out: ${label}`)),
87
+ ms,
88
+ );
89
+ promise.then(
90
+ v => {
91
+ clearTimeout(timer);
92
+ resolve(v);
93
+ },
94
+ e => {
95
+ clearTimeout(timer);
96
+ reject(e);
97
+ },
98
+ );
99
+ });
100
+ }
101
+
102
+ /** The slice of `ReadableStreamDefaultReader` the helpers need. */
103
+ export type ByteReader = {
104
+ read(): Promise<{done: boolean; value?: Uint8Array}>;
105
+ };
106
+
107
+ /** Read exactly `count` bytes from a reader (accumulating chunks), with a timeout. */
108
+ export async function readBytes(
109
+ reader: ByteReader,
110
+ count: number,
111
+ timeoutMs = 2000,
112
+ ): Promise<number[]> {
113
+ const out: number[] = [];
114
+ while (out.length < count) {
115
+ const {done, value} = await withTimeout(
116
+ reader.read(),
117
+ timeoutMs,
118
+ `reading ${count} bytes (got ${out.length})`,
119
+ );
120
+ if (done) break;
121
+ if (value) out.push(...value);
122
+ }
123
+ return out;
124
+ }