@gurezo/web-serial-rxjs 2.3.6 → 3.0.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.ja.md +1 -1
- package/README.md +1 -1
- package/dist/errors/serial-error-code.d.ts +92 -14
- package/dist/errors/serial-error-code.d.ts.map +1 -1
- package/dist/errors/serial-error-context.d.ts +47 -0
- package/dist/errors/serial-error-context.d.ts.map +1 -0
- package/dist/errors/serial-error.d.ts +30 -8
- package/dist/errors/serial-error.d.ts.map +1 -1
- package/dist/index.d.ts +13 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1327 -493
- package/dist/index.mjs.map +4 -4
- package/dist/internal/assert-never.d.ts +2 -0
- package/dist/internal/assert-never.d.ts.map +1 -0
- package/dist/internal/branded-numbers.d.ts +37 -0
- package/dist/internal/branded-numbers.d.ts.map +1 -0
- package/dist/internal/newline-tokenizer.d.ts +38 -0
- package/dist/internal/newline-tokenizer.d.ts.map +1 -0
- package/dist/session/create-serial-session.d.ts +7 -3
- package/dist/session/create-serial-session.d.ts.map +1 -1
- package/dist/session/index.d.ts +7 -2
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/internal/build-request-options.d.ts +7 -7
- package/dist/session/internal/build-request-options.d.ts.map +1 -1
- package/dist/session/internal/error-severity.d.ts +38 -0
- package/dist/session/internal/error-severity.d.ts.map +1 -0
- package/dist/session/internal/line-buffer.d.ts +37 -4
- package/dist/session/internal/line-buffer.d.ts.map +1 -1
- package/dist/session/internal/receive-pipeline.d.ts +34 -0
- package/dist/session/internal/receive-pipeline.d.ts.map +1 -0
- package/dist/session/internal/receive-replay-buffer.d.ts +38 -0
- package/dist/session/internal/receive-replay-buffer.d.ts.map +1 -0
- package/dist/session/internal/session-error-reporter.d.ts +32 -0
- package/dist/session/internal/session-error-reporter.d.ts.map +1 -0
- package/dist/session/internal/session-lifecycle.d.ts +44 -0
- package/dist/session/internal/session-lifecycle.d.ts.map +1 -0
- package/dist/session/internal/validate-serial-port-filters.d.ts +12 -0
- package/dist/session/internal/validate-serial-port-filters.d.ts.map +1 -0
- package/dist/session/read-pump.d.ts +9 -1
- package/dist/session/read-pump.d.ts.map +1 -1
- package/dist/session/serial-session-options.d.ts +133 -46
- package/dist/session/serial-session-options.d.ts.map +1 -1
- package/dist/session/serial-session-state.d.ts +50 -12
- package/dist/session/serial-session-state.d.ts.map +1 -1
- package/dist/session/serial-session.d.ts +44 -9
- package/dist/session/serial-session.d.ts.map +1 -1
- package/dist/session/session-runtime.d.ts +140 -0
- package/dist/session/session-runtime.d.ts.map +1 -0
- package/dist/terminal/create-terminal-buffer.d.ts +48 -1
- package/dist/terminal/create-terminal-buffer.d.ts.map +1 -1
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +2 -1
- package/dist/index.js +0 -724
- package/dist/session/session-state-machine.d.ts +0 -39
- package/dist/session/session-state-machine.d.ts.map +0 -1
package/dist/index.js
DELETED
|
@@ -1,724 +0,0 @@
|
|
|
1
|
-
// packages/web-serial-rxjs/src/session/create-serial-session.ts
|
|
2
|
-
import {
|
|
3
|
-
BehaviorSubject as BehaviorSubject2,
|
|
4
|
-
distinctUntilChanged,
|
|
5
|
-
map as map2,
|
|
6
|
-
Observable as Observable3,
|
|
7
|
-
ReplaySubject,
|
|
8
|
-
share,
|
|
9
|
-
Subject,
|
|
10
|
-
switchMap
|
|
11
|
-
} from "rxjs";
|
|
12
|
-
|
|
13
|
-
// packages/web-serial-rxjs/src/errors/serial-error-code.ts
|
|
14
|
-
var SerialErrorCode = /* @__PURE__ */ ((SerialErrorCode2) => {
|
|
15
|
-
SerialErrorCode2["BROWSER_NOT_SUPPORTED"] = "BROWSER_NOT_SUPPORTED";
|
|
16
|
-
SerialErrorCode2["PORT_NOT_AVAILABLE"] = "PORT_NOT_AVAILABLE";
|
|
17
|
-
SerialErrorCode2["PORT_OPEN_FAILED"] = "PORT_OPEN_FAILED";
|
|
18
|
-
SerialErrorCode2["PORT_ALREADY_OPEN"] = "PORT_ALREADY_OPEN";
|
|
19
|
-
SerialErrorCode2["PORT_NOT_OPEN"] = "PORT_NOT_OPEN";
|
|
20
|
-
SerialErrorCode2["READ_FAILED"] = "READ_FAILED";
|
|
21
|
-
SerialErrorCode2["WRITE_FAILED"] = "WRITE_FAILED";
|
|
22
|
-
SerialErrorCode2["CONNECTION_LOST"] = "CONNECTION_LOST";
|
|
23
|
-
SerialErrorCode2["INVALID_FILTER_OPTIONS"] = "INVALID_FILTER_OPTIONS";
|
|
24
|
-
SerialErrorCode2["OPERATION_CANCELLED"] = "OPERATION_CANCELLED";
|
|
25
|
-
SerialErrorCode2["OPERATION_TIMEOUT"] = "OPERATION_TIMEOUT";
|
|
26
|
-
SerialErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
27
|
-
return SerialErrorCode2;
|
|
28
|
-
})(SerialErrorCode || {});
|
|
29
|
-
|
|
30
|
-
// packages/web-serial-rxjs/src/errors/serial-error.ts
|
|
31
|
-
var SerialError = class _SerialError extends Error {
|
|
32
|
-
/**
|
|
33
|
-
* Creates a new SerialError instance.
|
|
34
|
-
*
|
|
35
|
-
* @param code - The error code identifying the type of error
|
|
36
|
-
* @param message - A human-readable error message
|
|
37
|
-
* @param originalError - The original error that caused this SerialError, if any
|
|
38
|
-
*/
|
|
39
|
-
constructor(code, message, originalError) {
|
|
40
|
-
super(message);
|
|
41
|
-
this.name = "SerialError";
|
|
42
|
-
this.code = code;
|
|
43
|
-
this.originalError = originalError;
|
|
44
|
-
if (Error.captureStackTrace) {
|
|
45
|
-
Error.captureStackTrace(this, _SerialError);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Check if the error matches a specific error code.
|
|
50
|
-
*
|
|
51
|
-
* This is a convenience method for checking the error code without directly
|
|
52
|
-
* comparing the code property.
|
|
53
|
-
*
|
|
54
|
-
* @param code - The error code to check against
|
|
55
|
-
* @returns `true` if this error's code matches the provided code, `false` otherwise
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* ```typescript
|
|
59
|
-
* if (error.is(SerialErrorCode.PORT_NOT_OPEN)) {
|
|
60
|
-
* // Handle port not open error
|
|
61
|
-
* }
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
is(code) {
|
|
65
|
-
return this.code === code;
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// packages/web-serial-rxjs/src/terminal/create-terminal-buffer.ts
|
|
70
|
-
import { map, scan, shareReplay } from "rxjs";
|
|
71
|
-
function applyTerminalChunk(state, chunk) {
|
|
72
|
-
let { completed, currentLine } = state;
|
|
73
|
-
const len = chunk.length;
|
|
74
|
-
for (let i = 0; i < len; i++) {
|
|
75
|
-
const c = chunk.charAt(i);
|
|
76
|
-
if (c === "\r") {
|
|
77
|
-
const next = i + 1 < len ? chunk.charAt(i + 1) : "";
|
|
78
|
-
if (next === "\n") {
|
|
79
|
-
completed += currentLine + "\n";
|
|
80
|
-
currentLine = "";
|
|
81
|
-
i++;
|
|
82
|
-
} else {
|
|
83
|
-
currentLine = "";
|
|
84
|
-
}
|
|
85
|
-
} else if (c === "\n") {
|
|
86
|
-
completed += currentLine + "\n";
|
|
87
|
-
currentLine = "";
|
|
88
|
-
} else {
|
|
89
|
-
currentLine += c;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return { completed, currentLine };
|
|
93
|
-
}
|
|
94
|
-
function terminalDisplayText(state) {
|
|
95
|
-
return state.completed + state.currentLine;
|
|
96
|
-
}
|
|
97
|
-
var initialTerminalState = {
|
|
98
|
-
completed: "",
|
|
99
|
-
currentLine: ""
|
|
100
|
-
};
|
|
101
|
-
function createTerminalBuffer(receive$) {
|
|
102
|
-
const text$ = receive$.pipe(
|
|
103
|
-
scan(
|
|
104
|
-
(state, chunk) => applyTerminalChunk(state, chunk),
|
|
105
|
-
initialTerminalState
|
|
106
|
-
),
|
|
107
|
-
map(terminalDisplayText),
|
|
108
|
-
shareReplay({ bufferSize: 1, refCount: true })
|
|
109
|
-
);
|
|
110
|
-
return { text$ };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// packages/web-serial-rxjs/src/session/internal/build-request-options.ts
|
|
114
|
-
function buildRequestOptions(options) {
|
|
115
|
-
if (!options || !options.filters || options.filters.length === 0) {
|
|
116
|
-
return void 0;
|
|
117
|
-
}
|
|
118
|
-
for (const filter of options.filters) {
|
|
119
|
-
if (!filter.usbVendorId && !filter.usbProductId) {
|
|
120
|
-
throw new SerialError(
|
|
121
|
-
"INVALID_FILTER_OPTIONS" /* INVALID_FILTER_OPTIONS */,
|
|
122
|
-
"Filter must have at least usbVendorId or usbProductId"
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
if (filter.usbVendorId !== void 0) {
|
|
126
|
-
if (!Number.isInteger(filter.usbVendorId) || filter.usbVendorId < 0 || filter.usbVendorId > 65535) {
|
|
127
|
-
throw new SerialError(
|
|
128
|
-
"INVALID_FILTER_OPTIONS" /* INVALID_FILTER_OPTIONS */,
|
|
129
|
-
`Invalid usbVendorId: ${filter.usbVendorId}. Must be an integer between 0 and 65535.`
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (filter.usbProductId !== void 0) {
|
|
134
|
-
if (!Number.isInteger(filter.usbProductId) || filter.usbProductId < 0 || filter.usbProductId > 65535) {
|
|
135
|
-
throw new SerialError(
|
|
136
|
-
"INVALID_FILTER_OPTIONS" /* INVALID_FILTER_OPTIONS */,
|
|
137
|
-
`Invalid usbProductId: ${filter.usbProductId}. Must be an integer between 0 and 65535.`
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
filters: options.filters
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// packages/web-serial-rxjs/src/session/internal/has-web-serial-support.ts
|
|
148
|
-
function hasWebSerialSupport() {
|
|
149
|
-
return typeof navigator !== "undefined" && "serial" in navigator && navigator.serial !== void 0 && navigator.serial !== null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// packages/web-serial-rxjs/src/session/internal/line-buffer.ts
|
|
153
|
-
function createLineBuffer() {
|
|
154
|
-
let buffer = "";
|
|
155
|
-
const clear = () => {
|
|
156
|
-
buffer = "";
|
|
157
|
-
};
|
|
158
|
-
const feed = (chunk) => {
|
|
159
|
-
buffer += chunk;
|
|
160
|
-
const out = [];
|
|
161
|
-
for (; ; ) {
|
|
162
|
-
const crlf = buffer.indexOf("\r\n");
|
|
163
|
-
if (crlf >= 0) {
|
|
164
|
-
out.push(buffer.slice(0, crlf));
|
|
165
|
-
buffer = buffer.slice(crlf + 2);
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
const cr = buffer.indexOf("\r");
|
|
169
|
-
if (cr >= 0 && cr + 1 < buffer.length && buffer[cr + 1] !== "\n") {
|
|
170
|
-
out.push(buffer.slice(0, cr));
|
|
171
|
-
buffer = buffer.slice(cr + 1);
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
const nl = buffer.indexOf("\n");
|
|
175
|
-
if (nl >= 0) {
|
|
176
|
-
out.push(buffer.slice(0, nl));
|
|
177
|
-
buffer = buffer.slice(nl + 1);
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
if (cr >= 0 && cr + 1 === buffer.length) {
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
return out;
|
|
186
|
-
};
|
|
187
|
-
return { feed, clear };
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// packages/web-serial-rxjs/src/session/normalize-serial-error.ts
|
|
191
|
-
var DEFAULT_MESSAGE_PREFIX = "Serial operation failed";
|
|
192
|
-
var isDomExceptionWithName = (error, name) => typeof DOMException !== "undefined" && error instanceof DOMException && error.name === name;
|
|
193
|
-
function normalizeSerialError(error, options) {
|
|
194
|
-
if (error instanceof SerialError) {
|
|
195
|
-
return error;
|
|
196
|
-
}
|
|
197
|
-
const prefix = options.messagePrefix ?? DEFAULT_MESSAGE_PREFIX;
|
|
198
|
-
if (isDomExceptionWithName(error, "NotFoundError")) {
|
|
199
|
-
return new SerialError(
|
|
200
|
-
"OPERATION_CANCELLED" /* OPERATION_CANCELLED */,
|
|
201
|
-
"Port selection was cancelled by the user",
|
|
202
|
-
error
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
const cause = error instanceof Error ? error : new Error(String(error));
|
|
206
|
-
return new SerialError(
|
|
207
|
-
options.fallbackCode,
|
|
208
|
-
`${prefix}: ${cause.message}`,
|
|
209
|
-
cause
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// packages/web-serial-rxjs/src/session/read-pump.ts
|
|
214
|
-
function createReadPump(port, { onChunk, onError }) {
|
|
215
|
-
let reader = null;
|
|
216
|
-
let running = false;
|
|
217
|
-
let stopped = false;
|
|
218
|
-
const decoder = new TextDecoder(void 0, { fatal: false });
|
|
219
|
-
const releaseReader = () => {
|
|
220
|
-
if (!reader) {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
try {
|
|
224
|
-
reader.releaseLock();
|
|
225
|
-
} catch {
|
|
226
|
-
}
|
|
227
|
-
reader = null;
|
|
228
|
-
};
|
|
229
|
-
const pump = async (stream) => {
|
|
230
|
-
reader = stream.getReader();
|
|
231
|
-
running = true;
|
|
232
|
-
try {
|
|
233
|
-
while (!stopped) {
|
|
234
|
-
const { done, value } = await reader.read();
|
|
235
|
-
if (done) {
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
if (value && value.byteLength > 0) {
|
|
239
|
-
const text = decoder.decode(value, { stream: true });
|
|
240
|
-
if (text.length > 0) {
|
|
241
|
-
onChunk(text);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
if (!stopped) {
|
|
246
|
-
const tail = decoder.decode();
|
|
247
|
-
if (tail.length > 0) {
|
|
248
|
-
onChunk(tail);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
if (!stopped) {
|
|
253
|
-
onError(
|
|
254
|
-
normalizeSerialError(error, {
|
|
255
|
-
fallbackCode: "READ_FAILED" /* READ_FAILED */,
|
|
256
|
-
messagePrefix: "Read pump failed"
|
|
257
|
-
})
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
} finally {
|
|
261
|
-
running = false;
|
|
262
|
-
releaseReader();
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
return {
|
|
266
|
-
start() {
|
|
267
|
-
if (running || stopped) {
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
const stream = port.readable;
|
|
271
|
-
if (!stream) {
|
|
272
|
-
stopped = true;
|
|
273
|
-
onError(
|
|
274
|
-
new SerialError(
|
|
275
|
-
"CONNECTION_LOST" /* CONNECTION_LOST */,
|
|
276
|
-
"Read pump failed: port.readable is not available"
|
|
277
|
-
)
|
|
278
|
-
);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
void pump(stream);
|
|
282
|
-
},
|
|
283
|
-
async stop() {
|
|
284
|
-
if (stopped) {
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
stopped = true;
|
|
288
|
-
if (!reader) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
try {
|
|
292
|
-
await reader.cancel();
|
|
293
|
-
} catch {
|
|
294
|
-
} finally {
|
|
295
|
-
releaseReader();
|
|
296
|
-
}
|
|
297
|
-
},
|
|
298
|
-
get isRunning() {
|
|
299
|
-
return running;
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// packages/web-serial-rxjs/src/session/send-queue.ts
|
|
305
|
-
import { Observable, defer } from "rxjs";
|
|
306
|
-
function createSendQueue() {
|
|
307
|
-
let chain = Promise.resolve();
|
|
308
|
-
return {
|
|
309
|
-
enqueue(operation) {
|
|
310
|
-
return defer(
|
|
311
|
-
() => new Observable((subscriber) => {
|
|
312
|
-
let cancelled = false;
|
|
313
|
-
const run = async () => {
|
|
314
|
-
try {
|
|
315
|
-
const value = await operation();
|
|
316
|
-
if (!cancelled) {
|
|
317
|
-
subscriber.next(value);
|
|
318
|
-
subscriber.complete();
|
|
319
|
-
}
|
|
320
|
-
} catch (error) {
|
|
321
|
-
if (!cancelled) {
|
|
322
|
-
subscriber.error(error);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
const scheduled = chain.then(run, run);
|
|
327
|
-
chain = scheduled.then(
|
|
328
|
-
() => void 0,
|
|
329
|
-
() => void 0
|
|
330
|
-
);
|
|
331
|
-
return () => {
|
|
332
|
-
cancelled = true;
|
|
333
|
-
};
|
|
334
|
-
})
|
|
335
|
-
);
|
|
336
|
-
},
|
|
337
|
-
clear() {
|
|
338
|
-
chain = Promise.resolve();
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// packages/web-serial-rxjs/src/session/serial-session-options.ts
|
|
344
|
-
var DEFAULT_RECEIVE_REPLAY = {
|
|
345
|
-
enabled: false,
|
|
346
|
-
bufferSize: 512
|
|
347
|
-
};
|
|
348
|
-
var DEFAULT_SERIAL_SESSION_OPTIONS = {
|
|
349
|
-
baudRate: 9600,
|
|
350
|
-
dataBits: 8,
|
|
351
|
-
stopBits: 1,
|
|
352
|
-
parity: "none",
|
|
353
|
-
bufferSize: 255,
|
|
354
|
-
flowControl: "none",
|
|
355
|
-
filters: void 0,
|
|
356
|
-
receiveReplay: { ...DEFAULT_RECEIVE_REPLAY }
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
// packages/web-serial-rxjs/src/session/serial-session-state.ts
|
|
360
|
-
var SerialSessionState = {
|
|
361
|
-
Idle: "idle",
|
|
362
|
-
Connecting: "connecting",
|
|
363
|
-
Connected: "connected",
|
|
364
|
-
Disconnecting: "disconnecting",
|
|
365
|
-
Unsupported: "unsupported",
|
|
366
|
-
Error: "error"
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
// packages/web-serial-rxjs/src/session/session-state-machine.ts
|
|
370
|
-
import { BehaviorSubject } from "rxjs";
|
|
371
|
-
var S = SerialSessionState;
|
|
372
|
-
var ALLOWED_TRANSITIONS = {
|
|
373
|
-
[S.Idle]: [S.Connecting, S.Error],
|
|
374
|
-
[S.Connecting]: [S.Connected, S.Error, S.Idle],
|
|
375
|
-
[S.Connected]: [S.Disconnecting, S.Error],
|
|
376
|
-
[S.Disconnecting]: [S.Idle, S.Error],
|
|
377
|
-
[S.Error]: [S.Idle, S.Connecting],
|
|
378
|
-
[S.Unsupported]: []
|
|
379
|
-
};
|
|
380
|
-
var SessionStateMachine = class {
|
|
381
|
-
constructor(initial = SerialSessionState.Idle) {
|
|
382
|
-
this.subject = new BehaviorSubject(initial);
|
|
383
|
-
}
|
|
384
|
-
get current() {
|
|
385
|
-
return this.subject.getValue();
|
|
386
|
-
}
|
|
387
|
-
get state$() {
|
|
388
|
-
return this.subject.asObservable();
|
|
389
|
-
}
|
|
390
|
-
toConnecting() {
|
|
391
|
-
return this.transition(S.Connecting);
|
|
392
|
-
}
|
|
393
|
-
toConnected() {
|
|
394
|
-
return this.transition(S.Connected);
|
|
395
|
-
}
|
|
396
|
-
toDisconnecting() {
|
|
397
|
-
return this.transition(S.Disconnecting);
|
|
398
|
-
}
|
|
399
|
-
toIdle() {
|
|
400
|
-
return this.transition(S.Idle);
|
|
401
|
-
}
|
|
402
|
-
toError() {
|
|
403
|
-
return this.transition(S.Error);
|
|
404
|
-
}
|
|
405
|
-
toUnsupported() {
|
|
406
|
-
return this.transition(S.Unsupported);
|
|
407
|
-
}
|
|
408
|
-
complete() {
|
|
409
|
-
this.subject.complete();
|
|
410
|
-
}
|
|
411
|
-
transition(next) {
|
|
412
|
-
const current = this.subject.getValue();
|
|
413
|
-
if (current === next) {
|
|
414
|
-
return false;
|
|
415
|
-
}
|
|
416
|
-
const allowed = ALLOWED_TRANSITIONS[current];
|
|
417
|
-
if (!allowed.includes(next)) {
|
|
418
|
-
if (typeof console !== "undefined" && console.warn) {
|
|
419
|
-
console.warn(
|
|
420
|
-
`[web-serial-rxjs] Ignoring invalid SerialSession transition ${current} -> ${next}`
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
return false;
|
|
424
|
-
}
|
|
425
|
-
this.subject.next(next);
|
|
426
|
-
return true;
|
|
427
|
-
}
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
// packages/web-serial-rxjs/src/session/create-serial-session.ts
|
|
431
|
-
function createSerialSession(options) {
|
|
432
|
-
const resolvedOptions = {
|
|
433
|
-
...DEFAULT_SERIAL_SESSION_OPTIONS,
|
|
434
|
-
...options,
|
|
435
|
-
filters: options?.filters,
|
|
436
|
-
receiveReplay: {
|
|
437
|
-
...DEFAULT_SERIAL_SESSION_OPTIONS.receiveReplay,
|
|
438
|
-
...options?.receiveReplay
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
const supported = hasWebSerialSupport();
|
|
442
|
-
const machine = new SessionStateMachine(
|
|
443
|
-
supported ? SerialSessionState.Idle : SerialSessionState.Unsupported
|
|
444
|
-
);
|
|
445
|
-
const errorsSubject = new Subject();
|
|
446
|
-
const receiveSubject = new Subject();
|
|
447
|
-
const linesSubject = new Subject();
|
|
448
|
-
const sendQueue = createSendQueue();
|
|
449
|
-
const textEncoder = new TextEncoder();
|
|
450
|
-
const lineBuffer = createLineBuffer();
|
|
451
|
-
const errors$ = errorsSubject.asObservable();
|
|
452
|
-
const receive$ = receiveSubject.asObservable();
|
|
453
|
-
const lines$ = linesSubject.asObservable();
|
|
454
|
-
const terminalText$ = createTerminalBuffer(receive$).text$;
|
|
455
|
-
const isConnected$ = machine.state$.pipe(
|
|
456
|
-
map2((state) => state === SerialSessionState.Connected),
|
|
457
|
-
distinctUntilChanged()
|
|
458
|
-
);
|
|
459
|
-
const portInfoSubject = new BehaviorSubject2(null);
|
|
460
|
-
const portInfo$ = portInfoSubject.asObservable();
|
|
461
|
-
const receiveReplayStream$ = resolvedOptions.receiveReplay.enabled ? new BehaviorSubject2(receive$) : null;
|
|
462
|
-
let activeReceiveReplay = null;
|
|
463
|
-
const clearLiveReceiveReplay = () => {
|
|
464
|
-
if (receiveReplayStream$) {
|
|
465
|
-
if (activeReceiveReplay) {
|
|
466
|
-
activeReceiveReplay.complete();
|
|
467
|
-
activeReceiveReplay = null;
|
|
468
|
-
}
|
|
469
|
-
receiveReplayStream$.next(receive$);
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
const startLiveReceiveReplay = () => {
|
|
473
|
-
if (!receiveReplayStream$) {
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
if (activeReceiveReplay) {
|
|
477
|
-
activeReceiveReplay.complete();
|
|
478
|
-
activeReceiveReplay = null;
|
|
479
|
-
}
|
|
480
|
-
const rs = new ReplaySubject(resolvedOptions.receiveReplay.bufferSize);
|
|
481
|
-
activeReceiveReplay = rs;
|
|
482
|
-
receiveReplayStream$.next(rs.asObservable());
|
|
483
|
-
};
|
|
484
|
-
const receiveReplay$ = receiveReplayStream$ ? receiveReplayStream$.pipe(switchMap((inner) => inner), share()) : receive$;
|
|
485
|
-
let activePort = null;
|
|
486
|
-
let activePump = null;
|
|
487
|
-
const setActivePort = (port) => {
|
|
488
|
-
activePort = port;
|
|
489
|
-
portInfoSubject.next(port ? port.getInfo() : null);
|
|
490
|
-
};
|
|
491
|
-
const teardownPump = async () => {
|
|
492
|
-
clearLiveReceiveReplay();
|
|
493
|
-
const pump = activePump;
|
|
494
|
-
activePump = null;
|
|
495
|
-
lineBuffer.clear();
|
|
496
|
-
if (pump) {
|
|
497
|
-
await pump.stop();
|
|
498
|
-
}
|
|
499
|
-
};
|
|
500
|
-
const closePortSafely = async (port) => {
|
|
501
|
-
if (!port) {
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
try {
|
|
505
|
-
await port.close();
|
|
506
|
-
} catch {
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
const reportError = (error, severity, options2) => {
|
|
510
|
-
const serialError = normalizeSerialError(error, options2);
|
|
511
|
-
errorsSubject.next(serialError);
|
|
512
|
-
if (severity === "fatal") {
|
|
513
|
-
machine.toError();
|
|
514
|
-
sendQueue.clear();
|
|
515
|
-
const portToClose = activePort;
|
|
516
|
-
setActivePort(null);
|
|
517
|
-
void teardownPump().then(() => closePortSafely(portToClose));
|
|
518
|
-
}
|
|
519
|
-
return serialError;
|
|
520
|
-
};
|
|
521
|
-
const writeToPort = async (payload) => {
|
|
522
|
-
const port = activePort;
|
|
523
|
-
if (machine.current !== SerialSessionState.Connected || !port || !port.writable) {
|
|
524
|
-
throw new SerialError(
|
|
525
|
-
"PORT_NOT_OPEN" /* PORT_NOT_OPEN */,
|
|
526
|
-
"Cannot send data while session is not connected"
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
const writer = port.writable.getWriter();
|
|
530
|
-
try {
|
|
531
|
-
await writer.write(payload);
|
|
532
|
-
} finally {
|
|
533
|
-
try {
|
|
534
|
-
writer.releaseLock();
|
|
535
|
-
} catch {
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
return {
|
|
540
|
-
isBrowserSupported() {
|
|
541
|
-
return hasWebSerialSupport();
|
|
542
|
-
},
|
|
543
|
-
connect$() {
|
|
544
|
-
return new Observable3((subscriber) => {
|
|
545
|
-
if (!hasWebSerialSupport()) {
|
|
546
|
-
const error = reportError(
|
|
547
|
-
new SerialError(
|
|
548
|
-
"BROWSER_NOT_SUPPORTED" /* BROWSER_NOT_SUPPORTED */,
|
|
549
|
-
"Web Serial API is not supported in this environment"
|
|
550
|
-
),
|
|
551
|
-
"non-fatal",
|
|
552
|
-
{ fallbackCode: "BROWSER_NOT_SUPPORTED" /* BROWSER_NOT_SUPPORTED */ }
|
|
553
|
-
);
|
|
554
|
-
subscriber.error(error);
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
const current = machine.current;
|
|
558
|
-
if (current !== SerialSessionState.Idle && current !== SerialSessionState.Error) {
|
|
559
|
-
const error = reportError(
|
|
560
|
-
new SerialError(
|
|
561
|
-
"PORT_ALREADY_OPEN" /* PORT_ALREADY_OPEN */,
|
|
562
|
-
`Cannot connect while session state is '${current}'`
|
|
563
|
-
),
|
|
564
|
-
"non-fatal",
|
|
565
|
-
{ fallbackCode: "PORT_ALREADY_OPEN" /* PORT_ALREADY_OPEN */ }
|
|
566
|
-
);
|
|
567
|
-
subscriber.error(error);
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
let cancelled = false;
|
|
571
|
-
machine.toConnecting();
|
|
572
|
-
const run = async () => {
|
|
573
|
-
let selectedPort = null;
|
|
574
|
-
try {
|
|
575
|
-
selectedPort = await navigator.serial.requestPort(
|
|
576
|
-
buildRequestOptions(resolvedOptions)
|
|
577
|
-
);
|
|
578
|
-
await selectedPort.open({
|
|
579
|
-
baudRate: resolvedOptions.baudRate,
|
|
580
|
-
dataBits: resolvedOptions.dataBits,
|
|
581
|
-
stopBits: resolvedOptions.stopBits,
|
|
582
|
-
parity: resolvedOptions.parity,
|
|
583
|
-
bufferSize: resolvedOptions.bufferSize,
|
|
584
|
-
flowControl: resolvedOptions.flowControl
|
|
585
|
-
});
|
|
586
|
-
} catch (error) {
|
|
587
|
-
if (selectedPort) {
|
|
588
|
-
await closePortSafely(selectedPort);
|
|
589
|
-
}
|
|
590
|
-
const serialError = reportError(error, "fatal", {
|
|
591
|
-
fallbackCode: "PORT_OPEN_FAILED" /* PORT_OPEN_FAILED */,
|
|
592
|
-
messagePrefix: "Failed to open port"
|
|
593
|
-
});
|
|
594
|
-
if (!cancelled) {
|
|
595
|
-
subscriber.error(serialError);
|
|
596
|
-
}
|
|
597
|
-
return;
|
|
598
|
-
}
|
|
599
|
-
if (cancelled) {
|
|
600
|
-
await closePortSafely(selectedPort);
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
setActivePort(selectedPort);
|
|
604
|
-
lineBuffer.clear();
|
|
605
|
-
if (resolvedOptions.receiveReplay.enabled) {
|
|
606
|
-
startLiveReceiveReplay();
|
|
607
|
-
}
|
|
608
|
-
activePump = createReadPump(selectedPort, {
|
|
609
|
-
onChunk: (text) => {
|
|
610
|
-
receiveSubject.next(text);
|
|
611
|
-
if (activeReceiveReplay) {
|
|
612
|
-
activeReceiveReplay.next(text);
|
|
613
|
-
}
|
|
614
|
-
for (const line of lineBuffer.feed(text)) {
|
|
615
|
-
linesSubject.next(line);
|
|
616
|
-
}
|
|
617
|
-
},
|
|
618
|
-
onError: (pumpError) => reportError(pumpError, "fatal", {
|
|
619
|
-
fallbackCode: "READ_FAILED" /* READ_FAILED */,
|
|
620
|
-
messagePrefix: "Read pump failed"
|
|
621
|
-
})
|
|
622
|
-
});
|
|
623
|
-
activePump.start();
|
|
624
|
-
sendQueue.clear();
|
|
625
|
-
machine.toConnected();
|
|
626
|
-
subscriber.next();
|
|
627
|
-
subscriber.complete();
|
|
628
|
-
};
|
|
629
|
-
void run();
|
|
630
|
-
return () => {
|
|
631
|
-
cancelled = true;
|
|
632
|
-
};
|
|
633
|
-
});
|
|
634
|
-
},
|
|
635
|
-
disconnect$() {
|
|
636
|
-
return new Observable3((subscriber) => {
|
|
637
|
-
const current = machine.current;
|
|
638
|
-
if (current === SerialSessionState.Idle || current === SerialSessionState.Unsupported) {
|
|
639
|
-
subscriber.next();
|
|
640
|
-
subscriber.complete();
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
if (current !== SerialSessionState.Connected && current !== SerialSessionState.Error) {
|
|
644
|
-
const error = reportError(
|
|
645
|
-
new SerialError(
|
|
646
|
-
"PORT_NOT_OPEN" /* PORT_NOT_OPEN */,
|
|
647
|
-
`Cannot disconnect while session state is '${current}'`
|
|
648
|
-
),
|
|
649
|
-
"non-fatal",
|
|
650
|
-
{ fallbackCode: "PORT_NOT_OPEN" /* PORT_NOT_OPEN */ }
|
|
651
|
-
);
|
|
652
|
-
subscriber.error(error);
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
machine.toDisconnecting();
|
|
656
|
-
sendQueue.clear();
|
|
657
|
-
const portToClose = activePort;
|
|
658
|
-
const run = async () => {
|
|
659
|
-
try {
|
|
660
|
-
await teardownPump();
|
|
661
|
-
if (portToClose) {
|
|
662
|
-
try {
|
|
663
|
-
await portToClose.close();
|
|
664
|
-
} catch (error) {
|
|
665
|
-
setActivePort(null);
|
|
666
|
-
const serialError = reportError(error, "fatal", {
|
|
667
|
-
fallbackCode: "CONNECTION_LOST" /* CONNECTION_LOST */,
|
|
668
|
-
messagePrefix: "Failed to close port"
|
|
669
|
-
});
|
|
670
|
-
subscriber.error(serialError);
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
setActivePort(null);
|
|
675
|
-
machine.toIdle();
|
|
676
|
-
subscriber.next();
|
|
677
|
-
subscriber.complete();
|
|
678
|
-
} catch (error) {
|
|
679
|
-
const serialError = reportError(error, "fatal", {
|
|
680
|
-
fallbackCode: "UNKNOWN" /* UNKNOWN */,
|
|
681
|
-
messagePrefix: "Unexpected disconnect failure"
|
|
682
|
-
});
|
|
683
|
-
subscriber.error(serialError);
|
|
684
|
-
}
|
|
685
|
-
};
|
|
686
|
-
void run();
|
|
687
|
-
});
|
|
688
|
-
},
|
|
689
|
-
send$(data) {
|
|
690
|
-
return sendQueue.enqueue(async () => {
|
|
691
|
-
const payload = typeof data === "string" ? textEncoder.encode(data) : data;
|
|
692
|
-
try {
|
|
693
|
-
await writeToPort(payload);
|
|
694
|
-
} catch (error) {
|
|
695
|
-
throw reportError(error, "non-fatal", {
|
|
696
|
-
fallbackCode: "WRITE_FAILED" /* WRITE_FAILED */,
|
|
697
|
-
messagePrefix: "Failed to write data"
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
});
|
|
701
|
-
},
|
|
702
|
-
state$: machine.state$,
|
|
703
|
-
isConnected$,
|
|
704
|
-
portInfo$,
|
|
705
|
-
getPortInfo() {
|
|
706
|
-
return portInfoSubject.getValue();
|
|
707
|
-
},
|
|
708
|
-
getCurrentPort() {
|
|
709
|
-
return activePort;
|
|
710
|
-
},
|
|
711
|
-
errors$,
|
|
712
|
-
receive$,
|
|
713
|
-
terminalText$,
|
|
714
|
-
receiveReplay$,
|
|
715
|
-
lines$
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
export {
|
|
719
|
-
SerialError,
|
|
720
|
-
SerialErrorCode,
|
|
721
|
-
SerialSessionState,
|
|
722
|
-
createSerialSession,
|
|
723
|
-
createTerminalBuffer
|
|
724
|
-
};
|