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.
- package/README.md +188 -117
- package/TESTING.md +417 -176
- package/android/build.gradle +14 -0
- 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 +1 -1
- package/lib/commonjs/WebSerial.js +110 -26
- package/lib/commonjs/WebSerial.js.map +1 -1
- package/lib/commonjs/index.js +2 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/lib/event-target.js +3 -1
- package/lib/commonjs/lib/event-target.js.map +1 -1
- 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/{virtual-serial.js → in-memory-serial-transport.js} +66 -28
- package/lib/commonjs/testing/in-memory-serial-transport.js.map +1 -0
- package/lib/commonjs/testing/index.js +100 -17
- package/lib/commonjs/testing/index.js.map +1 -1
- 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/{serial-device.js → simulated-device.js} +17 -17
- 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 +3 -3
- 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 +1 -1
- package/lib/typescript/src/WebSerial.d.ts +7 -7
- package/lib/typescript/src/WebSerial.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/lib/event-target.d.ts +2 -0
- package/lib/typescript/src/lib/event-target.d.ts.map +1 -1
- 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/{virtual-serial.d.ts → in-memory-serial-transport.d.ts} +37 -26
- package/lib/typescript/src/testing/in-memory-serial-transport.d.ts.map +1 -0
- package/lib/typescript/src/testing/index.d.ts +18 -8
- package/lib/typescript/src/testing/index.d.ts.map +1 -1
- 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/{serial-device.d.ts → simulated-device.d.ts} +23 -23
- 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 +3 -3
- 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 +21 -3
- package/src/UsbSerial.ts +1 -1
- package/src/WebSerial.ts +134 -35
- package/src/index.ts +4 -1
- package/src/lib/event-target.ts +12 -0
- 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/{virtual-serial.ts → in-memory-serial-transport.ts} +95 -56
- package/src/testing/index.ts +69 -21
- package/src/testing/install-in-memory-serial-transport.ts +65 -0
- package/src/testing/serial-client.ts +313 -0
- package/src/testing/{serial-device.ts → simulated-device.ts} +23 -23
- package/src/testing/test-suite.ts +186 -0
- package/src/transport.ts +3 -3
- 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/lib/commonjs/testing/install.js +0 -54
- package/lib/commonjs/testing/install.js.map +0 -1
- package/lib/commonjs/testing/serial-device.js.map +0 -1
- package/lib/commonjs/testing/virtual-serial.js.map +0 -1
- package/lib/typescript/src/testing/install.d.ts +0 -25
- package/lib/typescript/src/testing/install.d.ts.map +0 -1
- package/lib/typescript/src/testing/serial-device.d.ts.map +0 -1
- package/lib/typescript/src/testing/virtual-serial.d.ts.map +0 -1
- package/src/testing/install.ts +0 -65
|
@@ -0,0 +1,299 @@
|
|
|
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 {
|
|
8
|
+
type ControlMessage,
|
|
9
|
+
type PortInfo,
|
|
10
|
+
parseControlMessage,
|
|
11
|
+
} from './protocol';
|
|
12
|
+
|
|
13
|
+
/** The subset of a `serialport` SerialPort that the bridge uses (callback API). */
|
|
14
|
+
export interface SerialLike {
|
|
15
|
+
write(data: Uint8Array, cb?: (err?: Error | null) => void): void;
|
|
16
|
+
set(
|
|
17
|
+
opts: {dtr?: boolean; rts?: boolean; brk?: boolean},
|
|
18
|
+
cb?: (err?: Error | null) => void,
|
|
19
|
+
): void;
|
|
20
|
+
get(
|
|
21
|
+
cb: (
|
|
22
|
+
err: Error | null,
|
|
23
|
+
signals?: {cts?: boolean; dsr?: boolean; dcd?: boolean; ri?: boolean},
|
|
24
|
+
) => void,
|
|
25
|
+
): void;
|
|
26
|
+
update(opts: {baudRate: number}, cb?: (err?: Error | null) => void): void;
|
|
27
|
+
flush(cb?: (err?: Error | null) => void): void;
|
|
28
|
+
drain(cb?: (err?: Error | null) => void): void;
|
|
29
|
+
close(cb?: (err?: Error | null) => void): void;
|
|
30
|
+
on(event: 'data', listener: (data: Uint8Array) => void): void;
|
|
31
|
+
on(event: 'error', listener: (err: Error) => void): void;
|
|
32
|
+
on(event: 'open' | 'close', listener: () => void): void;
|
|
33
|
+
removeListener(event: string, listener: (...args: never[]) => void): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The subset of a `ws` WebSocket that the bridge uses. */
|
|
37
|
+
export interface WsLike {
|
|
38
|
+
send(data: string | Uint8Array): void;
|
|
39
|
+
on(
|
|
40
|
+
event: 'message',
|
|
41
|
+
listener: (data: unknown, isBinary: boolean) => void,
|
|
42
|
+
): void;
|
|
43
|
+
on(event: 'close', listener: () => void): void;
|
|
44
|
+
on(event: 'error', listener: (err: Error) => void): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type BridgeOptions = {
|
|
48
|
+
/** Whether the port forwards serial→client data before `startReading`. */
|
|
49
|
+
readingByDefault?: boolean;
|
|
50
|
+
/** Optional logger for diagnostics. */
|
|
51
|
+
log?: (message: string) => void;
|
|
52
|
+
/** Optional static metadata or callback exposed via `getPortInfo`. */
|
|
53
|
+
portInfo?: PortInfo | (() => PortInfo | null | undefined);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Normalise any binary payload (Buffer/ArrayBuffer/typed array) to bytes. */
|
|
57
|
+
function toBytes(data: unknown): Uint8Array {
|
|
58
|
+
if (data instanceof Uint8Array) {
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
if (data instanceof ArrayBuffer) {
|
|
62
|
+
return new Uint8Array(data);
|
|
63
|
+
}
|
|
64
|
+
if (ArrayBuffer.isView(data)) {
|
|
65
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(data)) {
|
|
68
|
+
return Uint8Array.from(data as number[]);
|
|
69
|
+
}
|
|
70
|
+
return new Uint8Array(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const errMessage = (e: unknown): string =>
|
|
74
|
+
e instanceof Error ? e.message : String(e);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Wire a single WebSocket connection to a single serial port: binary frames are
|
|
78
|
+
* piped both ways, JSON control frames invoke the corresponding serial method
|
|
79
|
+
* and get a response, and the port's data/error/close are surfaced as frames.
|
|
80
|
+
* Returns a teardown function that detaches the serial listeners.
|
|
81
|
+
*/
|
|
82
|
+
export function attachBridge(
|
|
83
|
+
serial: SerialLike,
|
|
84
|
+
ws: WsLike,
|
|
85
|
+
options: BridgeOptions = {},
|
|
86
|
+
): () => void {
|
|
87
|
+
let reading = options.readingByDefault ?? true;
|
|
88
|
+
|
|
89
|
+
const onData = (data: Uint8Array): void => {
|
|
90
|
+
if (reading) {
|
|
91
|
+
ws.send(toBytes(data));
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const sendEvent = (event: 'open' | 'close' | 'error', error?: string): void =>
|
|
95
|
+
ws.send(JSON.stringify({type: 'event', event, ...(error ? {error} : {})}));
|
|
96
|
+
const onError = (err: Error): void => sendEvent('error', err.message);
|
|
97
|
+
const onOpen = (): void => sendEvent('open');
|
|
98
|
+
const onClose = (): void => sendEvent('close');
|
|
99
|
+
|
|
100
|
+
serial.on('data', onData);
|
|
101
|
+
serial.on('error', onError);
|
|
102
|
+
serial.on('open', onOpen);
|
|
103
|
+
serial.on('close', onClose);
|
|
104
|
+
|
|
105
|
+
const reply = (id: number, error: unknown, result: unknown = null): void =>
|
|
106
|
+
ws.send(
|
|
107
|
+
JSON.stringify({
|
|
108
|
+
type: 'response',
|
|
109
|
+
id,
|
|
110
|
+
error: error ? errMessage(error) : null,
|
|
111
|
+
result,
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const handleCommand = (msg: Extract<ControlMessage, {type: 'command'}>) => {
|
|
116
|
+
const {id, command} = msg;
|
|
117
|
+
const args = (msg.args ?? {}) as Record<string, number | boolean>;
|
|
118
|
+
switch (command) {
|
|
119
|
+
// Note: serialport can change the baud rate live; data-bits/parity/stop-bits
|
|
120
|
+
// are fixed at the rate the bridge process was started with.
|
|
121
|
+
case 'setLineCoding':
|
|
122
|
+
case 'setBaudRate':
|
|
123
|
+
serial.update({baudRate: Number(args.baudRate)}, e => reply(id, e));
|
|
124
|
+
break;
|
|
125
|
+
case 'setSignals': {
|
|
126
|
+
const set: {dtr?: boolean; rts?: boolean; brk?: boolean} = {};
|
|
127
|
+
if (typeof args.dtr === 'boolean') set.dtr = args.dtr;
|
|
128
|
+
if (typeof args.rts === 'boolean') set.rts = args.rts;
|
|
129
|
+
if (typeof args.brk === 'boolean') set.brk = args.brk;
|
|
130
|
+
serial.set(set, e => reply(id, e));
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'getSignals':
|
|
134
|
+
serial.get((e, s) =>
|
|
135
|
+
reply(
|
|
136
|
+
id,
|
|
137
|
+
e,
|
|
138
|
+
s
|
|
139
|
+
? {cts: !!s.cts, dsr: !!s.dsr, dcd: !!s.dcd, ri: !!s.ri}
|
|
140
|
+
: {cts: false, dsr: false, dcd: false, ri: false},
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
break;
|
|
144
|
+
case 'getPortInfo': {
|
|
145
|
+
const info =
|
|
146
|
+
typeof options.portInfo === 'function'
|
|
147
|
+
? options.portInfo()
|
|
148
|
+
: options.portInfo;
|
|
149
|
+
reply(
|
|
150
|
+
id,
|
|
151
|
+
null,
|
|
152
|
+
info
|
|
153
|
+
? {
|
|
154
|
+
usbVendorId: info.usbVendorId,
|
|
155
|
+
usbProductId: info.usbProductId,
|
|
156
|
+
serialNumber: info.serialNumber,
|
|
157
|
+
}
|
|
158
|
+
: null,
|
|
159
|
+
);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'startReading':
|
|
163
|
+
reading = true;
|
|
164
|
+
reply(id, null);
|
|
165
|
+
break;
|
|
166
|
+
case 'stopReading':
|
|
167
|
+
reading = false;
|
|
168
|
+
reply(id, null);
|
|
169
|
+
break;
|
|
170
|
+
case 'flush':
|
|
171
|
+
serial.flush(e => reply(id, e));
|
|
172
|
+
break;
|
|
173
|
+
case 'drain':
|
|
174
|
+
serial.drain(e => reply(id, e));
|
|
175
|
+
break;
|
|
176
|
+
case 'break':
|
|
177
|
+
serial.set({brk: true}, () =>
|
|
178
|
+
setTimeout(
|
|
179
|
+
() => serial.set({brk: false}, e => reply(id, e)),
|
|
180
|
+
Number(args.duration ?? 100),
|
|
181
|
+
),
|
|
182
|
+
);
|
|
183
|
+
break;
|
|
184
|
+
case 'close':
|
|
185
|
+
serial.close(e => reply(id, e));
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
reply(id, new Error(`unknown command: ${command}`));
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
ws.on('message', (data, isBinary) => {
|
|
193
|
+
if (isBinary) {
|
|
194
|
+
serial.write(toBytes(data), err => {
|
|
195
|
+
if (err) {
|
|
196
|
+
options.log?.(`serial write failed: ${errMessage(err)}`);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const text =
|
|
202
|
+
typeof data === 'string' ? data : new TextDecoder().decode(toBytes(data));
|
|
203
|
+
const msg = parseControlMessage(text);
|
|
204
|
+
if (msg?.type === 'command') {
|
|
205
|
+
handleCommand(msg);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const teardown = (): void => {
|
|
210
|
+
serial.removeListener('data', onData as (...a: never[]) => void);
|
|
211
|
+
serial.removeListener('error', onError as (...a: never[]) => void);
|
|
212
|
+
serial.removeListener('open', onOpen as (...a: never[]) => void);
|
|
213
|
+
serial.removeListener('close', onClose as (...a: never[]) => void);
|
|
214
|
+
};
|
|
215
|
+
ws.on('close', teardown);
|
|
216
|
+
ws.on('error', err => options.log?.(`ws error: ${errMessage(err)}`));
|
|
217
|
+
|
|
218
|
+
return teardown;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ── CLI argument parsing (pure, testable) ────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
export type BridgeArgs = {
|
|
224
|
+
port?: string;
|
|
225
|
+
baudRate: number;
|
|
226
|
+
wsPort: number;
|
|
227
|
+
host: string;
|
|
228
|
+
allowRemote: boolean;
|
|
229
|
+
help: boolean;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/** Parse `expose-serial` CLI args + env into a normalised options object. */
|
|
233
|
+
export function parseBridgeArgs(
|
|
234
|
+
argv: readonly string[],
|
|
235
|
+
env: Record<string, string | undefined> = {},
|
|
236
|
+
): BridgeArgs {
|
|
237
|
+
const args: Record<string, string | boolean> = {};
|
|
238
|
+
for (let i = 0; i < argv.length; i++) {
|
|
239
|
+
const a = argv[i];
|
|
240
|
+
const take = () => argv[++i];
|
|
241
|
+
switch (a) {
|
|
242
|
+
case '-p':
|
|
243
|
+
case '--port':
|
|
244
|
+
args.port = take();
|
|
245
|
+
break;
|
|
246
|
+
case '-b':
|
|
247
|
+
case '--baudrate':
|
|
248
|
+
args.baudRate = take();
|
|
249
|
+
break;
|
|
250
|
+
case '-w':
|
|
251
|
+
case '--ws-port':
|
|
252
|
+
args.wsPort = take();
|
|
253
|
+
break;
|
|
254
|
+
case '--host':
|
|
255
|
+
args.host = take();
|
|
256
|
+
break;
|
|
257
|
+
case '--allow-remote':
|
|
258
|
+
args.allowRemote = true;
|
|
259
|
+
break;
|
|
260
|
+
case '-h':
|
|
261
|
+
case '--help':
|
|
262
|
+
args.help = true;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const allowRemote = args.allowRemote === true;
|
|
268
|
+
const host =
|
|
269
|
+
(args.host as string) ??
|
|
270
|
+
env.HOST ??
|
|
271
|
+
(allowRemote ? '0.0.0.0' : '127.0.0.1');
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
port: (args.port as string) ?? env.SERIAL_PORT,
|
|
275
|
+
baudRate: Number(args.baudRate ?? env.BAUD_RATE ?? 115200),
|
|
276
|
+
wsPort: Number(args.wsPort ?? env.WS_PORT ?? 8080),
|
|
277
|
+
host,
|
|
278
|
+
allowRemote,
|
|
279
|
+
help: args.help === true,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export const USAGE = `expose-serial-websocket — bridge a serial port to a WebSocket
|
|
284
|
+
|
|
285
|
+
Usage:
|
|
286
|
+
expose-serial-websocket --port <path> [--baudrate 115200] [--ws-port 8080]
|
|
287
|
+
[--host 127.0.0.1] [--allow-remote]
|
|
288
|
+
|
|
289
|
+
Options:
|
|
290
|
+
-p, --port <path> Serial device (e.g. /dev/ttyUSB0, COM3) [env SERIAL_PORT]
|
|
291
|
+
-b, --baudrate <n> Baud rate (default 115200) [env BAUD_RATE]
|
|
292
|
+
-w, --ws-port <n> WebSocket port (default 8080) [env WS_PORT]
|
|
293
|
+
--host <addr> Listen address (default 127.0.0.1) [env HOST]
|
|
294
|
+
--allow-remote Bind 0.0.0.0 (exposes the port to the network!)
|
|
295
|
+
-h, --help Show this help
|
|
296
|
+
|
|
297
|
+
Connect from the app with:
|
|
298
|
+
new Serial(new WebSocketSerialTransport('ws://localhost:8080'))
|
|
299
|
+
`;
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
|
|
14
|
+
// The bridge core + the in-memory device adapter, so a test can expose a
|
|
15
|
+
// SimulatedDevice simulator over a WebSocket (see testing/exposeSimulatedDevice).
|
|
16
|
+
export type {BridgeOptions, SerialLike, WsLike} from './bridge';
|
|
17
|
+
export {attachBridge} from './bridge';
|
|
18
|
+
export type {
|
|
19
|
+
CommandMessage,
|
|
20
|
+
CommandName,
|
|
21
|
+
ControlMessage,
|
|
22
|
+
EventMessage,
|
|
23
|
+
InputSignals,
|
|
24
|
+
LineCoding,
|
|
25
|
+
Parity,
|
|
26
|
+
PortInfo,
|
|
27
|
+
ResponseMessage,
|
|
28
|
+
} from './protocol';
|
|
29
|
+
export {
|
|
30
|
+
portInfoFromDevice,
|
|
31
|
+
SimulatedDeviceToSerialLike,
|
|
32
|
+
} from './serial-device-bridge';
|
|
33
|
+
export type {
|
|
34
|
+
WebSocketCtor,
|
|
35
|
+
WebSocketLike,
|
|
36
|
+
WebSocketSerialOptions,
|
|
37
|
+
} from './WebSocketSerialTransport';
|
|
38
|
+
export {WebSocketSerialTransport} from './WebSocketSerialTransport';
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
|
|
16
|
+
import type {Parity} from '../WebSerial';
|
|
17
|
+
|
|
18
|
+
export type {Parity} from '../WebSerial';
|
|
19
|
+
|
|
20
|
+
/** Connection parameters for `setLineCoding`. */
|
|
21
|
+
export type LineCoding = {
|
|
22
|
+
baudRate: number;
|
|
23
|
+
dataBits?: number;
|
|
24
|
+
stopBits?: number;
|
|
25
|
+
parity?: Parity;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Input control-signal state returned by `getSignals`. */
|
|
29
|
+
export type InputSignals = {
|
|
30
|
+
cts: boolean;
|
|
31
|
+
dsr: boolean;
|
|
32
|
+
dcd: boolean;
|
|
33
|
+
ri: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Optional serial-port metadata returned by `getPortInfo`. */
|
|
37
|
+
export type PortInfo = {
|
|
38
|
+
usbVendorId?: number;
|
|
39
|
+
usbProductId?: number;
|
|
40
|
+
serialNumber?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Every control command the bridge understands. */
|
|
44
|
+
export type CommandName =
|
|
45
|
+
| 'setLineCoding'
|
|
46
|
+
| 'setBaudRate'
|
|
47
|
+
| 'setSignals'
|
|
48
|
+
| 'getSignals'
|
|
49
|
+
| 'getPortInfo'
|
|
50
|
+
| 'startReading'
|
|
51
|
+
| 'stopReading'
|
|
52
|
+
| 'flush'
|
|
53
|
+
| 'drain'
|
|
54
|
+
| 'break'
|
|
55
|
+
| 'close';
|
|
56
|
+
|
|
57
|
+
/** client → bridge */
|
|
58
|
+
export type CommandMessage = {
|
|
59
|
+
type: 'command';
|
|
60
|
+
id: number;
|
|
61
|
+
command: CommandName;
|
|
62
|
+
args?: Record<string, unknown>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** bridge → client, answering a {@link CommandMessage} with the same `id`. */
|
|
66
|
+
export type ResponseMessage = {
|
|
67
|
+
type: 'response';
|
|
68
|
+
id: number;
|
|
69
|
+
error: string | null;
|
|
70
|
+
result?: unknown;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** An unsolicited port lifecycle event the bridge emits to the client. */
|
|
74
|
+
export type BridgeEventName = 'open' | 'close' | 'error';
|
|
75
|
+
|
|
76
|
+
/** bridge → client */
|
|
77
|
+
export type EventMessage = {
|
|
78
|
+
type: 'event';
|
|
79
|
+
event: BridgeEventName;
|
|
80
|
+
error?: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type ControlMessage = CommandMessage | ResponseMessage | EventMessage;
|
|
84
|
+
|
|
85
|
+
/** Parse a text frame into a {@link ControlMessage}, or null if it isn't one. */
|
|
86
|
+
export function parseControlMessage(text: string): ControlMessage | null {
|
|
87
|
+
let value: unknown;
|
|
88
|
+
try {
|
|
89
|
+
value = JSON.parse(text);
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
if (typeof value !== 'object' || value === null) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const type = (value as {type?: unknown}).type;
|
|
97
|
+
if (type === 'command' || type === 'response' || type === 'event') {
|
|
98
|
+
return value as ControlMessage;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
|
|
22
|
+
import type {
|
|
23
|
+
DeviceHandle,
|
|
24
|
+
InMemorySerialTransport,
|
|
25
|
+
} from '../testing/in-memory-serial-transport';
|
|
26
|
+
import type {DataEvent, ErrorEvent} from '../transport';
|
|
27
|
+
import type {SerialLike} from './bridge';
|
|
28
|
+
import type {PortInfo} from './protocol';
|
|
29
|
+
|
|
30
|
+
/** The bridge's `getPortInfo` metadata, taken from the device's USB identity. */
|
|
31
|
+
export function portInfoFromDevice(device: DeviceHandle): PortInfo {
|
|
32
|
+
return {
|
|
33
|
+
usbVendorId: device.usbVendorId,
|
|
34
|
+
usbProductId: device.usbProductId,
|
|
35
|
+
serialNumber: device.serialNumber,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build a {@link SerialLike} backed by `device` (already registered on
|
|
41
|
+
* `transport` via `addDevice`). One adapter per WebSocket connection; its
|
|
42
|
+
* transport subscriptions are released when the bridge tears down its listeners.
|
|
43
|
+
*/
|
|
44
|
+
export function SimulatedDeviceToSerialLike(
|
|
45
|
+
transport: InMemorySerialTransport,
|
|
46
|
+
device: DeviceHandle,
|
|
47
|
+
): SerialLike {
|
|
48
|
+
const dataListeners = new Set<(data: Uint8Array) => void>();
|
|
49
|
+
const errorListeners = new Set<(err: Error) => void>();
|
|
50
|
+
const openListeners = new Set<() => void>();
|
|
51
|
+
const closeListeners = new Set<() => void>();
|
|
52
|
+
|
|
53
|
+
// The device's `send(...)` surfaces here as a transport data event; forward
|
|
54
|
+
// the bytes of *this* device to the bridge's 'data' listener.
|
|
55
|
+
const dataSub = transport.onData((e: DataEvent) => {
|
|
56
|
+
if (e.deviceId !== device.deviceId) return;
|
|
57
|
+
const bytes = Uint8Array.from(e.data);
|
|
58
|
+
for (const l of dataListeners) l(bytes);
|
|
59
|
+
});
|
|
60
|
+
const errorSub = transport.onError((e: ErrorEvent) => {
|
|
61
|
+
if (e.deviceId !== device.deviceId) return;
|
|
62
|
+
const err = new Error(e.error);
|
|
63
|
+
err.name = e.errorName ?? err.name;
|
|
64
|
+
for (const l of errorListeners) l(err);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const id = () => device.deviceId;
|
|
68
|
+
const port = () => device.portNumber;
|
|
69
|
+
|
|
70
|
+
/** Open the sim on first use (the app's open handshake), else just re-baud. */
|
|
71
|
+
const ensureOpen = async (baudRate: number): Promise<void> => {
|
|
72
|
+
if (device.isOpen) {
|
|
73
|
+
await transport.setParameters(id(), port(), {baudRate});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await transport.open(id(), port(), {baudRate});
|
|
77
|
+
// The device only forwards `send(...)` while "reading"; the bridge's own
|
|
78
|
+
// startReading toggles client forwarding, not the device, so enable it here.
|
|
79
|
+
await transport.startReading(id(), port());
|
|
80
|
+
for (const l of openListeners) l();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const cleanupIfIdle = (): void => {
|
|
84
|
+
if (dataListeners.size === 0 && errorListeners.size === 0) {
|
|
85
|
+
dataSub.remove();
|
|
86
|
+
errorSub.remove();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
write(data, cb) {
|
|
92
|
+
transport.write(id(), port(), Array.from(data)).then(
|
|
93
|
+
() => cb?.(null),
|
|
94
|
+
err => cb?.(err as Error),
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
set(opts, cb) {
|
|
98
|
+
const ops: Array<Promise<void>> = [];
|
|
99
|
+
if (opts.dtr !== undefined)
|
|
100
|
+
ops.push(transport.setDTR(id(), port(), opts.dtr));
|
|
101
|
+
if (opts.rts !== undefined)
|
|
102
|
+
ops.push(transport.setRTS(id(), port(), opts.rts));
|
|
103
|
+
if (opts.brk !== undefined)
|
|
104
|
+
ops.push(transport.setBreak(id(), port(), opts.brk));
|
|
105
|
+
Promise.all(ops).then(
|
|
106
|
+
() => cb?.(null),
|
|
107
|
+
err => cb?.(err as Error),
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
get(cb) {
|
|
111
|
+
Promise.all([
|
|
112
|
+
transport.getCTS(id(), port()),
|
|
113
|
+
transport.getDSR(id(), port()),
|
|
114
|
+
transport.getCD(id(), port()),
|
|
115
|
+
transport.getRI(id(), port()),
|
|
116
|
+
]).then(
|
|
117
|
+
([cts, dsr, dcd, ri]) => cb(null, {cts, dsr, dcd, ri}),
|
|
118
|
+
err => cb(err as Error),
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
update(opts, cb) {
|
|
122
|
+
ensureOpen(opts.baudRate).then(
|
|
123
|
+
() => cb?.(null),
|
|
124
|
+
err => cb?.(err as Error),
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
flush(cb) {
|
|
128
|
+
cb?.(null);
|
|
129
|
+
},
|
|
130
|
+
drain(cb) {
|
|
131
|
+
cb?.(null);
|
|
132
|
+
},
|
|
133
|
+
close(cb) {
|
|
134
|
+
transport.close(id(), port()).then(
|
|
135
|
+
() => {
|
|
136
|
+
for (const l of closeListeners) l();
|
|
137
|
+
cb?.(null);
|
|
138
|
+
},
|
|
139
|
+
err => cb?.(err as Error),
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
on(event: string, listener: (...args: never[]) => void) {
|
|
143
|
+
if (event === 'data')
|
|
144
|
+
dataListeners.add(listener as (d: Uint8Array) => void);
|
|
145
|
+
else if (event === 'error')
|
|
146
|
+
errorListeners.add(listener as (e: Error) => void);
|
|
147
|
+
else if (event === 'open') openListeners.add(listener as () => void);
|
|
148
|
+
else if (event === 'close') closeListeners.add(listener as () => void);
|
|
149
|
+
},
|
|
150
|
+
removeListener(event: string, listener: (...args: never[]) => void) {
|
|
151
|
+
if (event === 'data')
|
|
152
|
+
dataListeners.delete(listener as (d: Uint8Array) => void);
|
|
153
|
+
else if (event === 'error')
|
|
154
|
+
errorListeners.delete(listener as (e: Error) => void);
|
|
155
|
+
else if (event === 'open') openListeners.delete(listener as () => void);
|
|
156
|
+
else if (event === 'close') closeListeners.delete(listener as () => void);
|
|
157
|
+
cleanupIfIdle();
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.installSerialMock = installSerialMock;
|
|
7
|
-
var _UsbSerial = require("../UsbSerial");
|
|
8
|
-
var _serialDevice = require("./serial-device");
|
|
9
|
-
var _virtualSerial = require("./virtual-serial");
|
|
10
|
-
/**
|
|
11
|
-
* installSerialMock — point the library at a virtual device set in one call.
|
|
12
|
-
*
|
|
13
|
-
* Built for E2E: call it once at app startup, behind your own env/build flag, to
|
|
14
|
-
* make `navigator.serial` / this library's `serial` talk to simulated
|
|
15
|
-
* {@link SerialDevice}s instead of real USB hardware while a test driver
|
|
16
|
-
* (Maestro, Detox, …) exercises the app on a device/emulator.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* // index.js (debug/E2E build only)
|
|
20
|
-
* import {installSerialMock, EchoDevice} from 'react-native-web-serial-api/testing';
|
|
21
|
-
* installSerialMock({
|
|
22
|
-
* enabled: process.env.RNWS_SERIAL_MOCK === '1',
|
|
23
|
-
* devices: [new EchoDevice(), new MyThermometer()],
|
|
24
|
-
* });
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/** A device to register: a SerialDevice, optionally with transport options. */
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Build a {@link VirtualSerialTransport} from `devices` and install it globally
|
|
31
|
-
* via {@link setUsbSerial}. SerialDevices default to granted USB permission (so
|
|
32
|
-
* they show up immediately); pass the `{device, options}` form to override.
|
|
33
|
-
* Returns the transport (handy for driving devices in-test), or `null` when
|
|
34
|
-
* disabled.
|
|
35
|
-
*/
|
|
36
|
-
function installSerialMock(options) {
|
|
37
|
-
if (options.enabled === false) return null;
|
|
38
|
-
const transport = new _virtualSerial.VirtualSerialTransport(options.transport);
|
|
39
|
-
for (const entry of options.devices) {
|
|
40
|
-
if (entry instanceof _serialDevice.SerialDevice) {
|
|
41
|
-
transport.addDevice(entry, {
|
|
42
|
-
hasPermission: true
|
|
43
|
-
});
|
|
44
|
-
} else {
|
|
45
|
-
transport.addDevice(entry.device, {
|
|
46
|
-
hasPermission: true,
|
|
47
|
-
...entry.options
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
(0, _UsbSerial.setUsbSerial)(transport);
|
|
52
|
-
return transport;
|
|
53
|
-
}
|
|
54
|
-
//# sourceMappingURL=install.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_UsbSerial","require","_serialDevice","_virtualSerial","installSerialMock","options","enabled","transport","VirtualSerialTransport","entry","devices","SerialDevice","addDevice","hasPermission","device","setUsbSerial"],"sourceRoot":"../../../src","sources":["testing/install.ts"],"mappings":";;;;;;AAgBA,IAAAA,UAAA,GAAAC,OAAA;AACA,IAAAC,aAAA,GAAAD,OAAA;AAKA,IAAAE,cAAA,GAAAF,OAAA;AAtBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AASA;;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASG,iBAAiBA,CAC/BC,OAAiC,EACF;EAC/B,IAAIA,OAAO,CAACC,OAAO,KAAK,KAAK,EAAE,OAAO,IAAI;EAE1C,MAAMC,SAAS,GAAG,IAAIC,qCAAsB,CAACH,OAAO,CAACE,SAAS,CAAC;EAC/D,KAAK,MAAME,KAAK,IAAIJ,OAAO,CAACK,OAAO,EAAE;IACnC,IAAID,KAAK,YAAYE,0BAAY,EAAE;MACjCJ,SAAS,CAACK,SAAS,CAACH,KAAK,EAAE;QAACI,aAAa,EAAE;MAAI,CAAC,CAAC;IACnD,CAAC,MAAM;MACLN,SAAS,CAACK,SAAS,CAACH,KAAK,CAACK,MAAM,EAAE;QAChCD,aAAa,EAAE,IAAI;QACnB,GAAGJ,KAAK,CAACJ;MACX,CAAC,CAAC;IACJ;EACF;EAEA,IAAAU,uBAAY,EAACR,SAAS,CAAC;EACvB,OAAOA,SAAS;AAClB","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["toBytes","data","Array","from","c","charCodeAt","Uint8Array","map","n","SerialDevice","host","_bind","send","raiseError","message","name","setSignals","signals","openOptions","deviceId","portNumber","onOpen","_options","onData","_data","onHostSignals","_signals","onClose","exports","EchoDevice","constructor","identity","usbVendorId","usbProductId","serialNumber","LineDevice","buffer","i","length","String","fromCharCode","nl","indexOf","line","slice","replace","onLine","SilentDevice"],"sourceRoot":"../../../src","sources":["testing/serial-device.ts"],"mappings":"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","ignoreList":[]}
|