@gurezo/web-serial-rxjs 2.4.0 → 3.1.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 +17 -10
- package/README.md +21 -12
- package/dist/errors/serial-error-code.d.ts +81 -37
- package/dist/errors/serial-error-code.d.ts.map +1 -1
- package/dist/errors/serial-error-context.d.ts +61 -0
- package/dist/errors/serial-error-context.d.ts.map +1 -0
- package/dist/errors/serial-error.d.ts +42 -21
- package/dist/errors/serial-error.d.ts.map +1 -1
- package/dist/index.d.ts +42 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1278 -635
- package/dist/index.mjs.map +4 -4
- package/dist/internal/assert-never.d.ts +11 -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/strip-ansi-sequences.d.ts +25 -0
- package/dist/internal/strip-ansi-sequences.d.ts.map +1 -0
- package/dist/session/create-serial-session.d.ts +6 -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 +15 -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 +3 -2
- package/dist/session/internal/receive-replay-buffer.d.ts.map +1 -1
- 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/is-connected-session-state.d.ts +11 -0
- package/dist/session/is-connected-session-state.d.ts.map +1 -0
- package/dist/session/normalize-serial-error.d.ts +1 -1
- package/dist/session/normalize-serial-error.d.ts.map +1 -1
- package/dist/session/serial-session-options.d.ts +118 -65
- 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 +52 -18
- 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 +15 -3
- 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 +1 -1
- package/dist/session/session-state-machine.d.ts +0 -40
- package/dist/session/session-state-machine.d.ts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,77 +1,233 @@
|
|
|
1
|
+
// src/internal/assert-never.ts
|
|
2
|
+
function assertNever(value) {
|
|
3
|
+
throw new Error(`Unexpected value: ${String(value)}`);
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
// src/session/create-serial-session.ts
|
|
2
7
|
import {
|
|
3
|
-
BehaviorSubject as
|
|
8
|
+
BehaviorSubject as BehaviorSubject3,
|
|
4
9
|
distinctUntilChanged,
|
|
5
10
|
map as map2,
|
|
6
|
-
Observable as
|
|
7
|
-
|
|
8
|
-
Subject as Subject2,
|
|
9
|
-
switchMap
|
|
11
|
+
Observable as Observable5,
|
|
12
|
+
Subject as Subject3
|
|
10
13
|
} from "rxjs";
|
|
11
14
|
|
|
12
15
|
// src/errors/serial-error-code.ts
|
|
13
|
-
var SerialErrorCode =
|
|
14
|
-
SerialErrorCode2["BROWSER_NOT_SUPPORTED"] = "BROWSER_NOT_SUPPORTED";
|
|
15
|
-
SerialErrorCode2["PORT_NOT_AVAILABLE"] = "PORT_NOT_AVAILABLE";
|
|
16
|
-
SerialErrorCode2["PORT_OPEN_FAILED"] = "PORT_OPEN_FAILED";
|
|
17
|
-
SerialErrorCode2["PORT_ALREADY_OPEN"] = "PORT_ALREADY_OPEN";
|
|
18
|
-
SerialErrorCode2["PORT_NOT_OPEN"] = "PORT_NOT_OPEN";
|
|
19
|
-
SerialErrorCode2["READ_FAILED"] = "READ_FAILED";
|
|
20
|
-
SerialErrorCode2["WRITE_FAILED"] = "WRITE_FAILED";
|
|
21
|
-
SerialErrorCode2["CONNECTION_LOST"] = "CONNECTION_LOST";
|
|
22
|
-
SerialErrorCode2["INVALID_FILTER_OPTIONS"] = "INVALID_FILTER_OPTIONS";
|
|
23
|
-
SerialErrorCode2["OPERATION_CANCELLED"] = "OPERATION_CANCELLED";
|
|
24
|
-
SerialErrorCode2["OPERATION_TIMEOUT"] = "OPERATION_TIMEOUT";
|
|
25
|
-
SerialErrorCode2["LINE_BUFFER_OVERFLOW"] = "LINE_BUFFER_OVERFLOW";
|
|
26
|
-
SerialErrorCode2["INVALID_RECEIVE_REPLAY_OPTIONS"] = "INVALID_RECEIVE_REPLAY_OPTIONS";
|
|
27
|
-
SerialErrorCode2["RECEIVE_REPLAY_BUFFER_OVERFLOW"] = "RECEIVE_REPLAY_BUFFER_OVERFLOW";
|
|
28
|
-
SerialErrorCode2["SESSION_DISPOSED"] = "SESSION_DISPOSED";
|
|
29
|
-
SerialErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
30
|
-
return SerialErrorCode2;
|
|
31
|
-
})(SerialErrorCode || {});
|
|
32
|
-
|
|
33
|
-
// src/errors/serial-error.ts
|
|
34
|
-
var SerialError = class _SerialError extends Error {
|
|
16
|
+
var SerialErrorCode = {
|
|
35
17
|
/**
|
|
36
|
-
*
|
|
18
|
+
* Browser does not support the Web Serial API.
|
|
37
19
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
20
|
+
* Emitted on `connect$` when `navigator.serial` is unavailable.
|
|
21
|
+
*
|
|
22
|
+
* **Suggested action**: Inform the user to use a supported browser.
|
|
23
|
+
*
|
|
24
|
+
* @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}
|
|
41
25
|
*/
|
|
42
|
-
|
|
43
|
-
super(message);
|
|
44
|
-
this.name = "SerialError";
|
|
45
|
-
this.code = code;
|
|
46
|
-
this.originalError = originalError;
|
|
47
|
-
if (Error.captureStackTrace) {
|
|
48
|
-
Error.captureStackTrace(this, _SerialError);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
26
|
+
BROWSER_NOT_SUPPORTED: "BROWSER_NOT_SUPPORTED",
|
|
51
27
|
/**
|
|
52
|
-
*
|
|
28
|
+
* Serial port is not available.
|
|
53
29
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
30
|
+
* **Reserved — not emitted in v3.x.** The current implementation uses only
|
|
31
|
+
* `navigator.serial.requestPort`; there is no `getPorts` API path. Scheduled
|
|
32
|
+
* for removal in the next major version.
|
|
56
33
|
*
|
|
57
|
-
*
|
|
58
|
-
* @
|
|
34
|
+
* **Suggested action**: Handle port acquisition failures with
|
|
35
|
+
* {@link SerialErrorCode.PORT_OPEN_FAILED} or
|
|
36
|
+
* {@link SerialErrorCode.OPERATION_CANCELLED} instead.
|
|
59
37
|
*
|
|
60
|
-
* @
|
|
61
|
-
*
|
|
62
|
-
* if (error.is(SerialErrorCode.PORT_NOT_OPEN)) {
|
|
63
|
-
* // Handle port not open error
|
|
64
|
-
* }
|
|
65
|
-
* ```
|
|
38
|
+
* @deprecated Not emitted at runtime in v3.x. Will be removed in the next major version.
|
|
39
|
+
* @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}
|
|
66
40
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
41
|
+
PORT_NOT_AVAILABLE: "PORT_NOT_AVAILABLE",
|
|
42
|
+
/**
|
|
43
|
+
* Failed to open the serial port.
|
|
44
|
+
*
|
|
45
|
+
* Emitted on `connect$` when `port.open()` rejects.
|
|
46
|
+
*
|
|
47
|
+
* **Suggested action**: Verify connection parameters and check hardware connections.
|
|
48
|
+
*/
|
|
49
|
+
PORT_OPEN_FAILED: "PORT_OPEN_FAILED",
|
|
50
|
+
/**
|
|
51
|
+
* Serial port is already open.
|
|
52
|
+
*
|
|
53
|
+
* Emitted on `connect$` when the session is not in `'idle'` or `'error'`
|
|
54
|
+
* (non-fatal; session state is unchanged).
|
|
55
|
+
*
|
|
56
|
+
* **Suggested action**: Disconnect the current port before connecting a new one.
|
|
57
|
+
*/
|
|
58
|
+
PORT_ALREADY_OPEN: "PORT_ALREADY_OPEN",
|
|
59
|
+
/**
|
|
60
|
+
* Serial port is not open.
|
|
61
|
+
*
|
|
62
|
+
* Emitted when `send$` or `disconnect$` is called in an invalid session
|
|
63
|
+
* state (for example before `connect$` completes).
|
|
64
|
+
*
|
|
65
|
+
* **Suggested action**: Call {@link SerialSession.connect$} before sending data.
|
|
66
|
+
*/
|
|
67
|
+
PORT_NOT_OPEN: "PORT_NOT_OPEN",
|
|
68
|
+
/**
|
|
69
|
+
* Failed to read from the serial port.
|
|
70
|
+
*
|
|
71
|
+
* This error occurs when reading data from the port fails, typically due to
|
|
72
|
+
* connection loss, hardware issues, or stream errors.
|
|
73
|
+
*
|
|
74
|
+
* **Suggested action**: Check the connection and hardware, then retry the read operation.
|
|
75
|
+
*/
|
|
76
|
+
READ_FAILED: "READ_FAILED",
|
|
77
|
+
/**
|
|
78
|
+
* Failed to write to the serial port.
|
|
79
|
+
*
|
|
80
|
+
* This error occurs when writing data to the port fails, typically due to
|
|
81
|
+
* connection loss, hardware issues, or stream errors.
|
|
82
|
+
*
|
|
83
|
+
* **Suggested action**: Check the connection and hardware, then retry the write operation.
|
|
84
|
+
*/
|
|
85
|
+
WRITE_FAILED: "WRITE_FAILED",
|
|
86
|
+
/**
|
|
87
|
+
* Serial port connection was lost.
|
|
88
|
+
*
|
|
89
|
+
* This error occurs when the connection to the serial port is unexpectedly
|
|
90
|
+
* terminated, such as when the device is disconnected or the port is closed
|
|
91
|
+
* by another process.
|
|
92
|
+
*
|
|
93
|
+
* **Suggested action**: Check the physical connection and reconnect if needed.
|
|
94
|
+
*/
|
|
95
|
+
CONNECTION_LOST: "CONNECTION_LOST",
|
|
96
|
+
/**
|
|
97
|
+
* Invalid filter options provided.
|
|
98
|
+
*
|
|
99
|
+
* This error occurs when port filter options are invalid, such as when
|
|
100
|
+
* filter values are out of range or missing required fields.
|
|
101
|
+
*
|
|
102
|
+
* **Suggested action**: Verify filter options match the expected format and value ranges.
|
|
103
|
+
*/
|
|
104
|
+
INVALID_FILTER_OPTIONS: "INVALID_FILTER_OPTIONS",
|
|
105
|
+
/**
|
|
106
|
+
* Operation was cancelled by the user.
|
|
107
|
+
*
|
|
108
|
+
* This error occurs when the user cancels a port selection dialog or aborts
|
|
109
|
+
* an operation before it completes.
|
|
110
|
+
*
|
|
111
|
+
* **Suggested action**: This is a normal condition - no action required, but you may want
|
|
112
|
+
* to inform the user that the operation was cancelled.
|
|
113
|
+
*/
|
|
114
|
+
OPERATION_CANCELLED: "OPERATION_CANCELLED",
|
|
115
|
+
/**
|
|
116
|
+
* Operation timed out before completion.
|
|
117
|
+
*
|
|
118
|
+
* **Reserved — not emitted in v3.x.** No timeout / prompt detection /
|
|
119
|
+
* transaction API exists yet. Scheduled for removal in the next major version
|
|
120
|
+
* unless a future API adopts this code.
|
|
121
|
+
*
|
|
122
|
+
* @deprecated Not emitted at runtime in v3.x. Will be removed in the next major version.
|
|
123
|
+
* @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}
|
|
124
|
+
*/
|
|
125
|
+
OPERATION_TIMEOUT: "OPERATION_TIMEOUT",
|
|
126
|
+
/**
|
|
127
|
+
* Internal line buffer exceeded its configured size limit.
|
|
128
|
+
*
|
|
129
|
+
* This error occurs when the incomplete line tail held by the {@link SerialSession.lines$}
|
|
130
|
+
* framing buffer grows beyond {@link SerialSessionOptions.lineBuffer} `maxChars`.
|
|
131
|
+
* Leading characters are discarded to bound memory; the session remains connected.
|
|
132
|
+
*
|
|
133
|
+
* **Suggested action**: Increase `lineBuffer.maxChars`, handle framing on `receive$`,
|
|
134
|
+
* or ensure the device sends line terminators.
|
|
135
|
+
*/
|
|
136
|
+
LINE_BUFFER_OVERFLOW: "LINE_BUFFER_OVERFLOW",
|
|
137
|
+
/**
|
|
138
|
+
* Invalid receive replay options provided.
|
|
139
|
+
*
|
|
140
|
+
* This error occurs when {@link SerialSessionOptions.receiveReplay} values are
|
|
141
|
+
* out of range, such as a non-integer `bufferSize` or `maxChars`.
|
|
142
|
+
*
|
|
143
|
+
* **Suggested action**: Verify `receiveReplay` options match the documented
|
|
144
|
+
* ranges and value types.
|
|
145
|
+
*/
|
|
146
|
+
INVALID_RECEIVE_REPLAY_OPTIONS: "INVALID_RECEIVE_REPLAY_OPTIONS",
|
|
147
|
+
/**
|
|
148
|
+
* Invalid terminal buffer options provided.
|
|
149
|
+
*
|
|
150
|
+
* This error occurs when {@link SerialSessionOptions.terminalBuffer} values are
|
|
151
|
+
* out of range, such as a negative or non-integer `maxLines` or `maxChars`.
|
|
152
|
+
*
|
|
153
|
+
* **Suggested action**: Verify `terminalBuffer` options match the documented
|
|
154
|
+
* ranges and value types.
|
|
155
|
+
*/
|
|
156
|
+
INVALID_TERMINAL_BUFFER_OPTIONS: "INVALID_TERMINAL_BUFFER_OPTIONS",
|
|
157
|
+
/**
|
|
158
|
+
* Invalid line buffer options provided.
|
|
159
|
+
*
|
|
160
|
+
* This error occurs when {@link SerialSessionOptions.lineBuffer} values are
|
|
161
|
+
* out of range, such as a negative or non-integer `maxChars`.
|
|
162
|
+
*
|
|
163
|
+
* **Suggested action**: Verify `lineBuffer` options match the documented
|
|
164
|
+
* ranges and value types.
|
|
165
|
+
*/
|
|
166
|
+
INVALID_LINE_BUFFER_OPTIONS: "INVALID_LINE_BUFFER_OPTIONS",
|
|
167
|
+
/**
|
|
168
|
+
* Invalid connection options provided.
|
|
169
|
+
*
|
|
170
|
+
* This error occurs when connection fields such as `baudRate` are out of
|
|
171
|
+
* range at session creation time.
|
|
172
|
+
*
|
|
173
|
+
* **Suggested action**: Verify connection options match the documented
|
|
174
|
+
* ranges and value types.
|
|
175
|
+
*/
|
|
176
|
+
INVALID_CONNECTION_OPTIONS: "INVALID_CONNECTION_OPTIONS",
|
|
177
|
+
/**
|
|
178
|
+
* Receive replay buffer exceeded its configured character limit.
|
|
179
|
+
*
|
|
180
|
+
* This error occurs when buffered chunks on {@link SerialSession.receiveReplay$}
|
|
181
|
+
* exceed {@link SerialSessionOptions.receiveReplay} `maxChars`. Oldest chunks
|
|
182
|
+
* are discarded to bound memory; the session remains connected.
|
|
183
|
+
*
|
|
184
|
+
* **Suggested action**: Increase `receiveReplay.maxChars`, reduce chunk size at
|
|
185
|
+
* the source, or subscribe earlier to avoid relying on a large replay buffer.
|
|
186
|
+
*/
|
|
187
|
+
RECEIVE_REPLAY_BUFFER_OVERFLOW: "RECEIVE_REPLAY_BUFFER_OVERFLOW",
|
|
188
|
+
/**
|
|
189
|
+
* Session has been disposed and can no longer be used.
|
|
190
|
+
*
|
|
191
|
+
* This error occurs when calling {@link SerialSession.connect$} or
|
|
192
|
+
* {@link SerialSession.send$} after {@link SerialSession.dispose$} has
|
|
193
|
+
* completed. Create a new session instead of reusing a disposed instance.
|
|
194
|
+
*
|
|
195
|
+
* **Suggested action**: Call {@link SerialSession.dispose$} only when
|
|
196
|
+
* permanently tearing down a session, then create a new one if needed.
|
|
197
|
+
*/
|
|
198
|
+
SESSION_DISPOSED: "SESSION_DISPOSED",
|
|
199
|
+
/**
|
|
200
|
+
* Unknown error occurred.
|
|
201
|
+
*
|
|
202
|
+
* Emitted as a fallback when dispose or disconnect encounters an error that
|
|
203
|
+
* cannot be classified more specifically. The underlying failure is on
|
|
204
|
+
* {@link SerialError.context | context.cause}.
|
|
205
|
+
*
|
|
206
|
+
* **Suggested action**: Check the error message and `context.cause` for more details.
|
|
207
|
+
*/
|
|
208
|
+
UNKNOWN: "UNKNOWN"
|
|
70
209
|
};
|
|
71
210
|
|
|
72
211
|
// src/terminal/create-terminal-buffer.ts
|
|
73
212
|
import { map, scan, shareReplay } from "rxjs";
|
|
74
213
|
|
|
214
|
+
// src/internal/branded-numbers.ts
|
|
215
|
+
function brandBaudRate(value) {
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
function brandSerialPortBufferSize(value) {
|
|
219
|
+
return value;
|
|
220
|
+
}
|
|
221
|
+
function brandReceiveReplayBufferSize(value) {
|
|
222
|
+
return value;
|
|
223
|
+
}
|
|
224
|
+
function brandMaxChars(value) {
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
function brandMaxLines(value) {
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
|
|
75
231
|
// src/internal/newline-tokenizer.ts
|
|
76
232
|
function createNewlineTokenizer(mode) {
|
|
77
233
|
let pending = "";
|
|
@@ -158,6 +314,89 @@ function createNewlineTokenizer(mode) {
|
|
|
158
314
|
};
|
|
159
315
|
}
|
|
160
316
|
|
|
317
|
+
// src/internal/strip-ansi-sequences.ts
|
|
318
|
+
var ESC = "\x1B";
|
|
319
|
+
var CSI_FINAL_BYTE = /[@-~]/;
|
|
320
|
+
var BEL = "\x07";
|
|
321
|
+
var OSC_STRING_TERMINATOR = "\x1B\\";
|
|
322
|
+
function findOscTerminator(oscBody) {
|
|
323
|
+
let earliest = null;
|
|
324
|
+
const belIndex = oscBody.indexOf(BEL);
|
|
325
|
+
if (belIndex >= 0) {
|
|
326
|
+
earliest = { index: belIndex, length: 1 };
|
|
327
|
+
}
|
|
328
|
+
const stIndex = oscBody.indexOf(OSC_STRING_TERMINATOR);
|
|
329
|
+
if (stIndex >= 0) {
|
|
330
|
+
const candidate = { index: stIndex, length: OSC_STRING_TERMINATOR.length };
|
|
331
|
+
if (!earliest || stIndex < earliest.index) {
|
|
332
|
+
earliest = candidate;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return earliest;
|
|
336
|
+
}
|
|
337
|
+
function createAnsiStripper() {
|
|
338
|
+
let pending = "";
|
|
339
|
+
const feed = (chunk) => {
|
|
340
|
+
const input = pending + chunk;
|
|
341
|
+
pending = "";
|
|
342
|
+
let output = "";
|
|
343
|
+
let i = 0;
|
|
344
|
+
while (i < input.length) {
|
|
345
|
+
const escIndex = input.indexOf(ESC, i);
|
|
346
|
+
if (escIndex < 0) {
|
|
347
|
+
output += input.slice(i);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
output += input.slice(i, escIndex);
|
|
351
|
+
const next = escIndex + 1;
|
|
352
|
+
if (next >= input.length) {
|
|
353
|
+
pending = ESC;
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
const code = input.charCodeAt(next);
|
|
357
|
+
if (code === 91) {
|
|
358
|
+
const csiStart = next + 1;
|
|
359
|
+
let j = csiStart;
|
|
360
|
+
while (j < input.length && !CSI_FINAL_BYTE.test(input.charAt(j))) {
|
|
361
|
+
j++;
|
|
362
|
+
}
|
|
363
|
+
if (j < input.length) {
|
|
364
|
+
i = j + 1;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
pending = input.slice(escIndex);
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
if (code === 93) {
|
|
371
|
+
const oscBody = input.slice(next);
|
|
372
|
+
const terminator = findOscTerminator(oscBody);
|
|
373
|
+
if (terminator) {
|
|
374
|
+
i = escIndex + 1 + terminator.index + terminator.length;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
pending = input.slice(escIndex);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
if (next < input.length) {
|
|
381
|
+
i = next + 1;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
pending = input.slice(escIndex);
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
return output;
|
|
388
|
+
};
|
|
389
|
+
const flush = () => {
|
|
390
|
+
if (pending.length === 0) {
|
|
391
|
+
return "";
|
|
392
|
+
}
|
|
393
|
+
const remainder = pending;
|
|
394
|
+
pending = "";
|
|
395
|
+
return remainder;
|
|
396
|
+
};
|
|
397
|
+
return { feed, flush };
|
|
398
|
+
}
|
|
399
|
+
|
|
161
400
|
// src/terminal/create-terminal-buffer.ts
|
|
162
401
|
function applyTerminalChunk(state, chunk) {
|
|
163
402
|
const tokenizer = createNewlineTokenizer("terminal");
|
|
@@ -226,12 +465,15 @@ function trimTerminalState(state, limits) {
|
|
|
226
465
|
}
|
|
227
466
|
var DEFAULT_TERMINAL_BUFFER_OPTIONS = {
|
|
228
467
|
maxLines: 1e4,
|
|
229
|
-
maxChars: 1048576
|
|
468
|
+
maxChars: 1048576,
|
|
469
|
+
stripAnsi: true
|
|
230
470
|
};
|
|
231
471
|
function resolveTerminalBufferLimits(options) {
|
|
472
|
+
const maxLines = options?.maxLines ?? DEFAULT_TERMINAL_BUFFER_OPTIONS.maxLines;
|
|
473
|
+
const maxChars = options?.maxChars ?? DEFAULT_TERMINAL_BUFFER_OPTIONS.maxChars;
|
|
232
474
|
return {
|
|
233
|
-
|
|
234
|
-
|
|
475
|
+
maxLines: brandMaxLines(maxLines),
|
|
476
|
+
maxChars: brandMaxChars(maxChars)
|
|
235
477
|
};
|
|
236
478
|
}
|
|
237
479
|
var initialTerminalState = {
|
|
@@ -240,9 +482,12 @@ var initialTerminalState = {
|
|
|
240
482
|
};
|
|
241
483
|
function createTerminalBuffer(receive$, options) {
|
|
242
484
|
const limits = resolveTerminalBufferLimits(options);
|
|
485
|
+
const stripAnsi = options?.stripAnsi ?? DEFAULT_TERMINAL_BUFFER_OPTIONS.stripAnsi;
|
|
486
|
+
const ansiStripper = stripAnsi ? createAnsiStripper() : null;
|
|
243
487
|
const text$ = receive$.pipe(
|
|
244
488
|
scan((state, chunk) => {
|
|
245
|
-
const
|
|
489
|
+
const normalized = ansiStripper !== null ? ansiStripper.feed(chunk) : chunk;
|
|
490
|
+
const next = applyTerminalChunk(state, normalized);
|
|
246
491
|
return trimTerminalState(next, limits);
|
|
247
492
|
}, initialTerminalState),
|
|
248
493
|
map(terminalDisplayText),
|
|
@@ -251,44 +496,95 @@ function createTerminalBuffer(receive$, options) {
|
|
|
251
496
|
return { text$ };
|
|
252
497
|
}
|
|
253
498
|
|
|
254
|
-
// src/session/internal/
|
|
255
|
-
function
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
499
|
+
// src/session/internal/has-web-serial-support.ts
|
|
500
|
+
function hasWebSerialSupport() {
|
|
501
|
+
return typeof navigator !== "undefined" && "serial" in navigator && navigator.serial !== void 0 && navigator.serial !== null;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/session/internal/receive-pipeline.ts
|
|
505
|
+
import {
|
|
506
|
+
BehaviorSubject,
|
|
507
|
+
share,
|
|
508
|
+
Subject as Subject2,
|
|
509
|
+
switchMap
|
|
510
|
+
} from "rxjs";
|
|
511
|
+
|
|
512
|
+
// src/errors/serial-error-context.ts
|
|
513
|
+
var CAUSE_CONTEXT_CODES = /* @__PURE__ */ new Set([
|
|
514
|
+
SerialErrorCode.PORT_NOT_AVAILABLE,
|
|
515
|
+
SerialErrorCode.PORT_OPEN_FAILED,
|
|
516
|
+
SerialErrorCode.READ_FAILED,
|
|
517
|
+
SerialErrorCode.WRITE_FAILED,
|
|
518
|
+
SerialErrorCode.CONNECTION_LOST,
|
|
519
|
+
SerialErrorCode.OPERATION_CANCELLED,
|
|
520
|
+
SerialErrorCode.UNKNOWN
|
|
521
|
+
]);
|
|
522
|
+
function isCauseContextCode(code) {
|
|
523
|
+
return CAUSE_CONTEXT_CODES.has(code);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/errors/serial-error.ts
|
|
527
|
+
var ErrorWithCaptureStackTrace = Error;
|
|
528
|
+
var SerialError = class _SerialError extends Error {
|
|
529
|
+
/**
|
|
530
|
+
* Creates a new SerialError instance.
|
|
531
|
+
*
|
|
532
|
+
* @param code - The error code identifying the type of error
|
|
533
|
+
* @param message - A human-readable error message
|
|
534
|
+
* @param originalError - The original error that caused this SerialError, if any.
|
|
535
|
+
* @deprecated Prefer passing `{ cause }` via {@link context}. When omitted,
|
|
536
|
+
* cause-bearing codes derive `{ cause }` from this argument for backward
|
|
537
|
+
* compatibility.
|
|
538
|
+
* @param context - Structured metadata for the error code. For cause-bearing
|
|
539
|
+
* codes, pass `{ cause }` here. When omitted, cause-bearing codes derive
|
|
540
|
+
* `{ cause }` from the legacy {@link originalError} argument.
|
|
541
|
+
*/
|
|
542
|
+
constructor(code, message, originalError, context) {
|
|
543
|
+
super(message);
|
|
544
|
+
this.name = "SerialError";
|
|
545
|
+
this.code = code;
|
|
546
|
+
if (context !== void 0) {
|
|
547
|
+
this.context = context;
|
|
548
|
+
} else if (originalError !== void 0 && isCauseContextCode(code)) {
|
|
549
|
+
this.context = { cause: originalError };
|
|
550
|
+
} else {
|
|
551
|
+
this.context = void 0;
|
|
265
552
|
}
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
553
|
+
if (originalError !== void 0) {
|
|
554
|
+
this.originalError = originalError;
|
|
555
|
+
} else if (isCauseContextCode(code) && this.context !== void 0) {
|
|
556
|
+
const causeContext = this.context;
|
|
557
|
+
if (causeContext.cause instanceof Error) {
|
|
558
|
+
this.originalError = causeContext.cause;
|
|
272
559
|
}
|
|
273
560
|
}
|
|
274
|
-
if (
|
|
275
|
-
|
|
276
|
-
throw new SerialError(
|
|
277
|
-
"INVALID_FILTER_OPTIONS" /* INVALID_FILTER_OPTIONS */,
|
|
278
|
-
`Invalid usbProductId: ${filter.usbProductId}. Must be an integer between 0 and 65535.`
|
|
279
|
-
);
|
|
280
|
-
}
|
|
561
|
+
if (ErrorWithCaptureStackTrace.captureStackTrace) {
|
|
562
|
+
ErrorWithCaptureStackTrace.captureStackTrace(this, _SerialError);
|
|
281
563
|
}
|
|
282
564
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
565
|
+
/**
|
|
566
|
+
* Check if the error matches a specific error code.
|
|
567
|
+
*
|
|
568
|
+
* This is a convenience method for checking the error code without directly
|
|
569
|
+
* comparing the code property. When this method returns `true`, TypeScript
|
|
570
|
+
* narrows `this.code` and `this.context` to the shapes defined for the
|
|
571
|
+
* provided `code` argument.
|
|
572
|
+
*
|
|
573
|
+
* @param code - The error code to check against
|
|
574
|
+
* @returns Type predicate: `true` if this error's code matches the provided
|
|
575
|
+
* code (and `this.code` / `this.context` are narrowed), `false` otherwise
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* ```typescript
|
|
579
|
+
* if (error.is(SerialErrorCode.LINE_BUFFER_OVERFLOW)) {
|
|
580
|
+
* // error.code and error.context.maxChars are narrowed
|
|
581
|
+
* }
|
|
582
|
+
* ```
|
|
583
|
+
*/
|
|
584
|
+
is(code) {
|
|
585
|
+
return this.code === code;
|
|
586
|
+
}
|
|
587
|
+
};
|
|
292
588
|
|
|
293
589
|
// src/session/internal/line-buffer.ts
|
|
294
590
|
var DEFAULT_LINE_BUFFER_OPTIONS = {
|
|
@@ -296,8 +592,9 @@ var DEFAULT_LINE_BUFFER_OPTIONS = {
|
|
|
296
592
|
};
|
|
297
593
|
function createLineBuffer(options) {
|
|
298
594
|
const limits = {
|
|
299
|
-
|
|
300
|
-
|
|
595
|
+
maxChars: brandMaxChars(
|
|
596
|
+
options?.maxChars ?? DEFAULT_LINE_BUFFER_OPTIONS.maxChars
|
|
597
|
+
)
|
|
301
598
|
};
|
|
302
599
|
const tokenizer = createNewlineTokenizer("line");
|
|
303
600
|
const clear = () => {
|
|
@@ -371,45 +668,347 @@ function createReceiveReplayBuffer(options) {
|
|
|
371
668
|
return { next, asObservable, complete };
|
|
372
669
|
}
|
|
373
670
|
|
|
374
|
-
// src/session/
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// src/session/read-pump.ts
|
|
398
|
-
function createReadPump(port, { onChunk, onError, onDone }) {
|
|
399
|
-
let reader = null;
|
|
400
|
-
let running = false;
|
|
401
|
-
let stopped = false;
|
|
402
|
-
const decoder = new TextDecoder(void 0, { fatal: false });
|
|
403
|
-
const releaseReader = () => {
|
|
404
|
-
if (!reader) {
|
|
671
|
+
// src/session/internal/receive-pipeline.ts
|
|
672
|
+
function createReceivePipeline(deps) {
|
|
673
|
+
const { resolvedOptions, reportError } = deps;
|
|
674
|
+
const receiveSubject = new Subject2();
|
|
675
|
+
const linesSubject = new Subject2();
|
|
676
|
+
const lineBuffer = createLineBuffer(resolvedOptions.lineBuffer);
|
|
677
|
+
const receive$ = receiveSubject.asObservable();
|
|
678
|
+
const lines$ = linesSubject.asObservable();
|
|
679
|
+
const receiveReplayStream$ = resolvedOptions.receiveReplay.enabled ? new BehaviorSubject(receive$) : null;
|
|
680
|
+
let activeReceiveReplay = null;
|
|
681
|
+
const clearReplay = () => {
|
|
682
|
+
if (receiveReplayStream$) {
|
|
683
|
+
if (activeReceiveReplay) {
|
|
684
|
+
activeReceiveReplay.complete();
|
|
685
|
+
activeReceiveReplay = null;
|
|
686
|
+
}
|
|
687
|
+
receiveReplayStream$.next(receive$);
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
const startLiveReceiveReplay = () => {
|
|
691
|
+
if (!receiveReplayStream$) {
|
|
405
692
|
return;
|
|
406
693
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
694
|
+
if (activeReceiveReplay) {
|
|
695
|
+
activeReceiveReplay.complete();
|
|
696
|
+
activeReceiveReplay = null;
|
|
410
697
|
}
|
|
411
|
-
|
|
412
|
-
|
|
698
|
+
const buffer = createReceiveReplayBuffer({
|
|
699
|
+
bufferSize: resolvedOptions.receiveReplay.bufferSize,
|
|
700
|
+
maxChars: resolvedOptions.receiveReplay.maxChars
|
|
701
|
+
});
|
|
702
|
+
activeReceiveReplay = buffer;
|
|
703
|
+
receiveReplayStream$.next(buffer.asObservable());
|
|
704
|
+
};
|
|
705
|
+
const receiveReplay$ = receiveReplayStream$ ? receiveReplayStream$.pipe(switchMap((inner) => inner), share()) : receive$;
|
|
706
|
+
const clearLineBuffer = () => {
|
|
707
|
+
lineBuffer.clear();
|
|
708
|
+
};
|
|
709
|
+
const handleChunk = (text) => {
|
|
710
|
+
receiveSubject.next(text);
|
|
711
|
+
if (activeReceiveReplay) {
|
|
712
|
+
const { overflowed: overflowed2 } = activeReceiveReplay.next(text);
|
|
713
|
+
if (overflowed2) {
|
|
714
|
+
reportError(
|
|
715
|
+
new SerialError(
|
|
716
|
+
SerialErrorCode.RECEIVE_REPLAY_BUFFER_OVERFLOW,
|
|
717
|
+
"Receive replay buffer exceeded configured limits; oldest chunks were discarded",
|
|
718
|
+
void 0,
|
|
719
|
+
{
|
|
720
|
+
maxChars: resolvedOptions.receiveReplay.maxChars,
|
|
721
|
+
bufferSize: resolvedOptions.receiveReplay.bufferSize
|
|
722
|
+
}
|
|
723
|
+
),
|
|
724
|
+
{
|
|
725
|
+
fallbackCode: SerialErrorCode.RECEIVE_REPLAY_BUFFER_OVERFLOW
|
|
726
|
+
}
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
const { lines, overflowed } = lineBuffer.feed(text);
|
|
731
|
+
if (overflowed) {
|
|
732
|
+
reportError(
|
|
733
|
+
new SerialError(
|
|
734
|
+
SerialErrorCode.LINE_BUFFER_OVERFLOW,
|
|
735
|
+
"Line buffer exceeded maxChars; leading data was discarded",
|
|
736
|
+
void 0,
|
|
737
|
+
{ maxChars: resolvedOptions.lineBuffer.maxChars }
|
|
738
|
+
),
|
|
739
|
+
{ fallbackCode: SerialErrorCode.LINE_BUFFER_OVERFLOW }
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
for (const line of lines) {
|
|
743
|
+
linesSubject.next(line);
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
const complete = () => {
|
|
747
|
+
receiveSubject.complete();
|
|
748
|
+
linesSubject.complete();
|
|
749
|
+
receiveReplayStream$?.complete();
|
|
750
|
+
};
|
|
751
|
+
return {
|
|
752
|
+
receive$,
|
|
753
|
+
lines$,
|
|
754
|
+
receiveReplay$,
|
|
755
|
+
clearReplay,
|
|
756
|
+
startLiveReceiveReplay,
|
|
757
|
+
clearLineBuffer,
|
|
758
|
+
handleChunk,
|
|
759
|
+
complete
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// src/session/internal/error-severity.ts
|
|
764
|
+
var ERROR_SEVERITY = {
|
|
765
|
+
[SerialErrorCode.READ_FAILED]: "fatal",
|
|
766
|
+
[SerialErrorCode.CONNECTION_LOST]: "fatal",
|
|
767
|
+
[SerialErrorCode.PORT_OPEN_FAILED]: "fatal",
|
|
768
|
+
[SerialErrorCode.OPERATION_CANCELLED]: "fatal",
|
|
769
|
+
[SerialErrorCode.UNKNOWN]: "fatal",
|
|
770
|
+
[SerialErrorCode.LINE_BUFFER_OVERFLOW]: "non-fatal",
|
|
771
|
+
[SerialErrorCode.RECEIVE_REPLAY_BUFFER_OVERFLOW]: "non-fatal",
|
|
772
|
+
[SerialErrorCode.BROWSER_NOT_SUPPORTED]: "non-fatal",
|
|
773
|
+
[SerialErrorCode.PORT_ALREADY_OPEN]: "non-fatal",
|
|
774
|
+
[SerialErrorCode.PORT_NOT_OPEN]: "non-fatal",
|
|
775
|
+
[SerialErrorCode.WRITE_FAILED]: "non-fatal"
|
|
776
|
+
};
|
|
777
|
+
function resolveErrorSeverity(code) {
|
|
778
|
+
if (code in ERROR_SEVERITY) {
|
|
779
|
+
return ERROR_SEVERITY[code];
|
|
780
|
+
}
|
|
781
|
+
return "fatal";
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/session/normalize-serial-error.ts
|
|
785
|
+
var DEFAULT_MESSAGE_PREFIX = "Serial operation failed";
|
|
786
|
+
var isDomExceptionWithName = (error, name) => typeof DOMException !== "undefined" && error instanceof DOMException && error.name === name;
|
|
787
|
+
function normalizeSerialError(error, options) {
|
|
788
|
+
if (error instanceof SerialError) {
|
|
789
|
+
return error;
|
|
790
|
+
}
|
|
791
|
+
const prefix = options.messagePrefix ?? DEFAULT_MESSAGE_PREFIX;
|
|
792
|
+
if (isDomExceptionWithName(error, "NotFoundError")) {
|
|
793
|
+
return new SerialError(
|
|
794
|
+
SerialErrorCode.OPERATION_CANCELLED,
|
|
795
|
+
"Port selection was cancelled by the user",
|
|
796
|
+
void 0,
|
|
797
|
+
{ cause: error }
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
801
|
+
return new SerialError(
|
|
802
|
+
options.fallbackCode,
|
|
803
|
+
`${prefix}: ${cause.message}`,
|
|
804
|
+
void 0,
|
|
805
|
+
{ cause }
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/session/session-runtime.ts
|
|
810
|
+
import { BehaviorSubject as BehaviorSubject2 } from "rxjs";
|
|
811
|
+
|
|
812
|
+
// src/session/serial-session-state.ts
|
|
813
|
+
var SerialSessionStatus = {
|
|
814
|
+
Idle: "idle",
|
|
815
|
+
Connecting: "connecting",
|
|
816
|
+
Connected: "connected",
|
|
817
|
+
Disconnecting: "disconnecting",
|
|
818
|
+
Unsupported: "unsupported",
|
|
819
|
+
Error: "error",
|
|
820
|
+
Disposed: "disposed"
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
// src/session/session-runtime.ts
|
|
824
|
+
var S = SerialSessionStatus;
|
|
825
|
+
var ALLOWED_TRANSITIONS = {
|
|
826
|
+
[S.Idle]: [S.Connecting, S.Error, S.Disposed],
|
|
827
|
+
[S.Connecting]: [S.Connected, S.Error, S.Idle, S.Disposed],
|
|
828
|
+
[S.Connected]: [S.Disconnecting, S.Error, S.Disposed],
|
|
829
|
+
[S.Disconnecting]: [S.Idle, S.Error, S.Disposed],
|
|
830
|
+
[S.Error]: [S.Idle, S.Connecting, S.Disposed],
|
|
831
|
+
[S.Unsupported]: [S.Disposed],
|
|
832
|
+
[S.Disposed]: []
|
|
833
|
+
};
|
|
834
|
+
function runtimeToSessionStatus(runtime) {
|
|
835
|
+
return runtime.status;
|
|
836
|
+
}
|
|
837
|
+
function runtimeToPublicState(runtime) {
|
|
838
|
+
switch (runtime.status) {
|
|
839
|
+
case S.Idle:
|
|
840
|
+
return { status: S.Idle };
|
|
841
|
+
case S.Connecting:
|
|
842
|
+
return { status: S.Connecting };
|
|
843
|
+
case S.Connected:
|
|
844
|
+
return { status: S.Connected, portInfo: runtime.port.getInfo() };
|
|
845
|
+
case S.Disconnecting:
|
|
846
|
+
return { status: S.Disconnecting };
|
|
847
|
+
case S.Unsupported:
|
|
848
|
+
return { status: S.Unsupported };
|
|
849
|
+
case S.Error:
|
|
850
|
+
return { status: S.Error, error: runtime.error };
|
|
851
|
+
case S.Disposed:
|
|
852
|
+
return { status: S.Disposed };
|
|
853
|
+
default:
|
|
854
|
+
return assertNeverRuntime(runtime);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
function isValidTransition(from, to) {
|
|
858
|
+
if (from === to) {
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
return ALLOWED_TRANSITIONS[from].includes(
|
|
862
|
+
to
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
function createIdleRuntime() {
|
|
866
|
+
return { status: S.Idle };
|
|
867
|
+
}
|
|
868
|
+
function createConnectingRuntime(cancel) {
|
|
869
|
+
return { status: S.Connecting, cancel };
|
|
870
|
+
}
|
|
871
|
+
function createConnectedRuntime(port, pump) {
|
|
872
|
+
return { status: S.Connected, port, pump };
|
|
873
|
+
}
|
|
874
|
+
function createDisconnectingRuntime(port) {
|
|
875
|
+
return { status: S.Disconnecting, port };
|
|
876
|
+
}
|
|
877
|
+
function createErrorRuntime(error) {
|
|
878
|
+
return { status: S.Error, error };
|
|
879
|
+
}
|
|
880
|
+
function createUnsupportedRuntime() {
|
|
881
|
+
return { status: S.Unsupported };
|
|
882
|
+
}
|
|
883
|
+
function createDisposedRuntime() {
|
|
884
|
+
return { status: S.Disposed };
|
|
885
|
+
}
|
|
886
|
+
function createInitialRuntime(supported) {
|
|
887
|
+
return supported ? createIdleRuntime() : createUnsupportedRuntime();
|
|
888
|
+
}
|
|
889
|
+
function getRuntimePort(runtime) {
|
|
890
|
+
switch (runtime.status) {
|
|
891
|
+
case S.Connected:
|
|
892
|
+
return runtime.port;
|
|
893
|
+
case S.Disconnecting:
|
|
894
|
+
return runtime.port;
|
|
895
|
+
default:
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
function getRuntimePump(runtime) {
|
|
900
|
+
if (runtime.status === S.Connected) {
|
|
901
|
+
return runtime.pump;
|
|
902
|
+
}
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
function createSessionRuntimeController(initial) {
|
|
906
|
+
let runtime = initial;
|
|
907
|
+
const subject = new BehaviorSubject2(
|
|
908
|
+
runtimeToPublicState(initial)
|
|
909
|
+
);
|
|
910
|
+
const transition = (next) => {
|
|
911
|
+
const from = runtimeToSessionStatus(runtime);
|
|
912
|
+
const to = runtimeToSessionStatus(next);
|
|
913
|
+
if (from === to) {
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
if (!isValidTransition(from, to)) {
|
|
917
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
918
|
+
console.warn(
|
|
919
|
+
`[web-serial-rxjs] Ignoring invalid SerialSession transition ${from} -> ${to}`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
runtime = next;
|
|
925
|
+
subject.next(runtimeToPublicState(next));
|
|
926
|
+
return true;
|
|
927
|
+
};
|
|
928
|
+
return {
|
|
929
|
+
get runtime() {
|
|
930
|
+
return runtime;
|
|
931
|
+
},
|
|
932
|
+
get status() {
|
|
933
|
+
return runtimeToSessionStatus(runtime);
|
|
934
|
+
},
|
|
935
|
+
get state$() {
|
|
936
|
+
return subject.asObservable();
|
|
937
|
+
},
|
|
938
|
+
transition,
|
|
939
|
+
complete() {
|
|
940
|
+
subject.complete();
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
function assertNeverRuntime(value) {
|
|
945
|
+
return assertNever(value);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// src/session/internal/session-error-reporter.ts
|
|
949
|
+
function createSessionErrorReporter(deps) {
|
|
950
|
+
const {
|
|
951
|
+
controller,
|
|
952
|
+
errorsSubject,
|
|
953
|
+
sendQueue,
|
|
954
|
+
isDisposed,
|
|
955
|
+
updatePortInfo,
|
|
956
|
+
teardownPump,
|
|
957
|
+
closePortSafely
|
|
958
|
+
} = deps;
|
|
959
|
+
const createDisposedError = () => new SerialError(
|
|
960
|
+
SerialErrorCode.SESSION_DISPOSED,
|
|
961
|
+
"SerialSession has been disposed"
|
|
962
|
+
);
|
|
963
|
+
const reportError = (error, options) => {
|
|
964
|
+
const serialError = normalizeSerialError(error, options);
|
|
965
|
+
if (isDisposed()) {
|
|
966
|
+
return serialError;
|
|
967
|
+
}
|
|
968
|
+
errorsSubject.next(serialError);
|
|
969
|
+
if (resolveErrorSeverity(serialError.code) === "fatal") {
|
|
970
|
+
const runtime = controller.runtime;
|
|
971
|
+
const portToClose = getRuntimePort(runtime);
|
|
972
|
+
const pump = getRuntimePump(runtime);
|
|
973
|
+
controller.transition(createErrorRuntime(serialError));
|
|
974
|
+
sendQueue.clear();
|
|
975
|
+
updatePortInfo(null);
|
|
976
|
+
void teardownPump(pump).then(() => closePortSafely(portToClose));
|
|
977
|
+
}
|
|
978
|
+
return serialError;
|
|
979
|
+
};
|
|
980
|
+
return { reportError, createDisposedError };
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/session/internal/session-lifecycle.ts
|
|
984
|
+
import { Observable as Observable3 } from "rxjs";
|
|
985
|
+
|
|
986
|
+
// src/session/internal/build-request-options.ts
|
|
987
|
+
function buildRequestOptions(options) {
|
|
988
|
+
if (!options?.filters || options.filters.length === 0) {
|
|
989
|
+
return void 0;
|
|
990
|
+
}
|
|
991
|
+
return {
|
|
992
|
+
filters: options.filters
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// src/session/read-pump.ts
|
|
997
|
+
function createReadPump(port, { onChunk, onError, onDone }) {
|
|
998
|
+
let reader = null;
|
|
999
|
+
let running = false;
|
|
1000
|
+
let stopped = false;
|
|
1001
|
+
const decoder = new TextDecoder(void 0, { fatal: false });
|
|
1002
|
+
const releaseReader = () => {
|
|
1003
|
+
if (!reader) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
try {
|
|
1007
|
+
reader.releaseLock();
|
|
1008
|
+
} catch {
|
|
1009
|
+
}
|
|
1010
|
+
reader = null;
|
|
1011
|
+
};
|
|
413
1012
|
const pump = async (stream) => {
|
|
414
1013
|
reader = stream.getReader();
|
|
415
1014
|
running = true;
|
|
@@ -439,7 +1038,7 @@ function createReadPump(port, { onChunk, onError, onDone }) {
|
|
|
439
1038
|
if (!stopped) {
|
|
440
1039
|
onError(
|
|
441
1040
|
normalizeSerialError(error, {
|
|
442
|
-
fallbackCode:
|
|
1041
|
+
fallbackCode: SerialErrorCode.READ_FAILED,
|
|
443
1042
|
messagePrefix: "Read pump failed"
|
|
444
1043
|
})
|
|
445
1044
|
);
|
|
@@ -459,7 +1058,7 @@ function createReadPump(port, { onChunk, onError, onDone }) {
|
|
|
459
1058
|
stopped = true;
|
|
460
1059
|
onError(
|
|
461
1060
|
new SerialError(
|
|
462
|
-
|
|
1061
|
+
SerialErrorCode.CONNECTION_LOST,
|
|
463
1062
|
"Read pump failed: port.readable is not available"
|
|
464
1063
|
)
|
|
465
1064
|
);
|
|
@@ -488,272 +1087,75 @@ function createReadPump(port, { onChunk, onError, onDone }) {
|
|
|
488
1087
|
};
|
|
489
1088
|
}
|
|
490
1089
|
|
|
491
|
-
// src/session/
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
} catch (error) {
|
|
508
|
-
if (!cancelled) {
|
|
509
|
-
subscriber.error(error);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
const scheduled = chain.then(run, run);
|
|
514
|
-
chain = scheduled.then(
|
|
515
|
-
() => void 0,
|
|
516
|
-
() => void 0
|
|
517
|
-
);
|
|
518
|
-
return () => {
|
|
519
|
-
cancelled = true;
|
|
520
|
-
};
|
|
521
|
-
})
|
|
522
|
-
);
|
|
523
|
-
},
|
|
524
|
-
clear() {
|
|
525
|
-
chain = Promise.resolve();
|
|
526
|
-
}
|
|
1090
|
+
// src/session/internal/session-lifecycle.ts
|
|
1091
|
+
function createSessionLifecycle(deps) {
|
|
1092
|
+
const {
|
|
1093
|
+
controller,
|
|
1094
|
+
resolvedOptions,
|
|
1095
|
+
sendQueue,
|
|
1096
|
+
receivePipeline,
|
|
1097
|
+
portInfoSubject,
|
|
1098
|
+
errorsSubject,
|
|
1099
|
+
isDisposed,
|
|
1100
|
+
reportError,
|
|
1101
|
+
createDisposedError
|
|
1102
|
+
} = deps;
|
|
1103
|
+
const updatePortInfo = (port) => {
|
|
1104
|
+
portInfoSubject.next(port ? port.getInfo() : null);
|
|
527
1105
|
};
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
enabled: false,
|
|
535
|
-
bufferSize: 512,
|
|
536
|
-
maxChars: 0
|
|
537
|
-
};
|
|
538
|
-
function resolveReceiveReplayOptions(options) {
|
|
539
|
-
const merged = {
|
|
540
|
-
...DEFAULT_RECEIVE_REPLAY,
|
|
541
|
-
...options
|
|
1106
|
+
const teardownPump = async (pump) => {
|
|
1107
|
+
receivePipeline.clearReplay();
|
|
1108
|
+
receivePipeline.clearLineBuffer();
|
|
1109
|
+
if (pump) {
|
|
1110
|
+
await pump.stop();
|
|
1111
|
+
}
|
|
542
1112
|
};
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
"INVALID_RECEIVE_REPLAY_OPTIONS" /* INVALID_RECEIVE_REPLAY_OPTIONS */,
|
|
547
|
-
`Invalid receiveReplay.bufferSize: ${bufferSize}. Must be a safe integer between 1 and ${MAX_RECEIVE_REPLAY_BUFFER_SIZE}.`
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
if (!Number.isSafeInteger(maxChars) || maxChars < 0 || maxChars > MAX_RECEIVE_REPLAY_MAX_CHARS) {
|
|
551
|
-
throw new SerialError(
|
|
552
|
-
"INVALID_RECEIVE_REPLAY_OPTIONS" /* INVALID_RECEIVE_REPLAY_OPTIONS */,
|
|
553
|
-
`Invalid receiveReplay.maxChars: ${maxChars}. Must be a safe integer between 0 and ${MAX_RECEIVE_REPLAY_MAX_CHARS}.`
|
|
554
|
-
);
|
|
555
|
-
}
|
|
556
|
-
return merged;
|
|
557
|
-
}
|
|
558
|
-
var DEFAULT_SERIAL_SESSION_OPTIONS = {
|
|
559
|
-
baudRate: 9600,
|
|
560
|
-
dataBits: 8,
|
|
561
|
-
stopBits: 1,
|
|
562
|
-
parity: "none",
|
|
563
|
-
bufferSize: 255,
|
|
564
|
-
flowControl: "none",
|
|
565
|
-
filters: void 0,
|
|
566
|
-
receiveReplay: { ...DEFAULT_RECEIVE_REPLAY },
|
|
567
|
-
terminalBuffer: { ...DEFAULT_TERMINAL_BUFFER_OPTIONS },
|
|
568
|
-
lineBuffer: { ...DEFAULT_LINE_BUFFER_OPTIONS }
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
// src/session/serial-session-state.ts
|
|
572
|
-
var SerialSessionState = {
|
|
573
|
-
Idle: "idle",
|
|
574
|
-
Connecting: "connecting",
|
|
575
|
-
Connected: "connected",
|
|
576
|
-
Disconnecting: "disconnecting",
|
|
577
|
-
Unsupported: "unsupported",
|
|
578
|
-
Error: "error",
|
|
579
|
-
Disposed: "disposed"
|
|
580
|
-
};
|
|
581
|
-
|
|
582
|
-
// src/session/session-state-machine.ts
|
|
583
|
-
import { BehaviorSubject } from "rxjs";
|
|
584
|
-
var S = SerialSessionState;
|
|
585
|
-
var ALLOWED_TRANSITIONS = {
|
|
586
|
-
[S.Idle]: [S.Connecting, S.Error, S.Disposed],
|
|
587
|
-
[S.Connecting]: [S.Connected, S.Error, S.Idle, S.Disposed],
|
|
588
|
-
[S.Connected]: [S.Disconnecting, S.Error, S.Disposed],
|
|
589
|
-
[S.Disconnecting]: [S.Idle, S.Error, S.Disposed],
|
|
590
|
-
[S.Error]: [S.Idle, S.Connecting, S.Disposed],
|
|
591
|
-
[S.Unsupported]: [S.Disposed],
|
|
592
|
-
[S.Disposed]: []
|
|
593
|
-
};
|
|
594
|
-
var SessionStateMachine = class {
|
|
595
|
-
constructor(initial = SerialSessionState.Idle) {
|
|
596
|
-
this.subject = new BehaviorSubject(initial);
|
|
597
|
-
}
|
|
598
|
-
get current() {
|
|
599
|
-
return this.subject.getValue();
|
|
600
|
-
}
|
|
601
|
-
get state$() {
|
|
602
|
-
return this.subject.asObservable();
|
|
603
|
-
}
|
|
604
|
-
toConnecting() {
|
|
605
|
-
return this.transition(S.Connecting);
|
|
606
|
-
}
|
|
607
|
-
toConnected() {
|
|
608
|
-
return this.transition(S.Connected);
|
|
609
|
-
}
|
|
610
|
-
toDisconnecting() {
|
|
611
|
-
return this.transition(S.Disconnecting);
|
|
612
|
-
}
|
|
613
|
-
toIdle() {
|
|
614
|
-
return this.transition(S.Idle);
|
|
615
|
-
}
|
|
616
|
-
toError() {
|
|
617
|
-
return this.transition(S.Error);
|
|
618
|
-
}
|
|
619
|
-
toUnsupported() {
|
|
620
|
-
return this.transition(S.Unsupported);
|
|
621
|
-
}
|
|
622
|
-
toDisposed() {
|
|
623
|
-
return this.transition(S.Disposed);
|
|
624
|
-
}
|
|
625
|
-
complete() {
|
|
626
|
-
this.subject.complete();
|
|
627
|
-
}
|
|
628
|
-
transition(next) {
|
|
629
|
-
const current = this.subject.getValue();
|
|
630
|
-
if (current === next) {
|
|
631
|
-
return false;
|
|
1113
|
+
const closePortSafely = async (port) => {
|
|
1114
|
+
if (!port) {
|
|
1115
|
+
return;
|
|
632
1116
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
console.warn(
|
|
637
|
-
`[web-serial-rxjs] Ignoring invalid SerialSession transition ${current} -> ${next}`
|
|
638
|
-
);
|
|
639
|
-
}
|
|
640
|
-
return false;
|
|
641
|
-
}
|
|
642
|
-
this.subject.next(next);
|
|
643
|
-
return true;
|
|
644
|
-
}
|
|
645
|
-
};
|
|
646
|
-
|
|
647
|
-
// src/session/create-serial-session.ts
|
|
648
|
-
function createSerialSession(options) {
|
|
649
|
-
const resolvedOptions = {
|
|
650
|
-
...DEFAULT_SERIAL_SESSION_OPTIONS,
|
|
651
|
-
...options,
|
|
652
|
-
filters: options?.filters,
|
|
653
|
-
receiveReplay: resolveReceiveReplayOptions(options?.receiveReplay),
|
|
654
|
-
terminalBuffer: {
|
|
655
|
-
...DEFAULT_SERIAL_SESSION_OPTIONS.terminalBuffer,
|
|
656
|
-
...options?.terminalBuffer
|
|
657
|
-
},
|
|
658
|
-
lineBuffer: {
|
|
659
|
-
...DEFAULT_SERIAL_SESSION_OPTIONS.lineBuffer,
|
|
660
|
-
...options?.lineBuffer
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
const supported = hasWebSerialSupport();
|
|
664
|
-
const machine = new SessionStateMachine(
|
|
665
|
-
supported ? SerialSessionState.Idle : SerialSessionState.Unsupported
|
|
666
|
-
);
|
|
667
|
-
const errorsSubject = new Subject2();
|
|
668
|
-
const receiveSubject = new Subject2();
|
|
669
|
-
const linesSubject = new Subject2();
|
|
670
|
-
const sendQueue = createSendQueue();
|
|
671
|
-
const textEncoder = new TextEncoder();
|
|
672
|
-
const lineBuffer = createLineBuffer(resolvedOptions.lineBuffer);
|
|
673
|
-
const errors$ = errorsSubject.asObservable();
|
|
674
|
-
const receive$ = receiveSubject.asObservable();
|
|
675
|
-
const lines$ = linesSubject.asObservable();
|
|
676
|
-
const terminalText$ = createTerminalBuffer(
|
|
677
|
-
receive$,
|
|
678
|
-
resolvedOptions.terminalBuffer
|
|
679
|
-
).text$;
|
|
680
|
-
const isConnected$ = machine.state$.pipe(
|
|
681
|
-
map2((state) => state === SerialSessionState.Connected),
|
|
682
|
-
distinctUntilChanged()
|
|
683
|
-
);
|
|
684
|
-
const portInfoSubject = new BehaviorSubject2(null);
|
|
685
|
-
const portInfo$ = portInfoSubject.asObservable();
|
|
686
|
-
const receiveReplayStream$ = resolvedOptions.receiveReplay.enabled ? new BehaviorSubject2(receive$) : null;
|
|
687
|
-
let activeReceiveReplay = null;
|
|
688
|
-
const clearLiveReceiveReplay = () => {
|
|
689
|
-
if (receiveReplayStream$) {
|
|
690
|
-
if (activeReceiveReplay) {
|
|
691
|
-
activeReceiveReplay.complete();
|
|
692
|
-
activeReceiveReplay = null;
|
|
693
|
-
}
|
|
694
|
-
receiveReplayStream$.next(receive$);
|
|
695
|
-
}
|
|
696
|
-
};
|
|
697
|
-
const startLiveReceiveReplay = () => {
|
|
698
|
-
if (!receiveReplayStream$) {
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
if (activeReceiveReplay) {
|
|
702
|
-
activeReceiveReplay.complete();
|
|
703
|
-
activeReceiveReplay = null;
|
|
1117
|
+
try {
|
|
1118
|
+
await port.close();
|
|
1119
|
+
} catch {
|
|
704
1120
|
}
|
|
705
|
-
const buffer = createReceiveReplayBuffer({
|
|
706
|
-
bufferSize: resolvedOptions.receiveReplay.bufferSize,
|
|
707
|
-
maxChars: resolvedOptions.receiveReplay.maxChars
|
|
708
|
-
});
|
|
709
|
-
activeReceiveReplay = buffer;
|
|
710
|
-
receiveReplayStream$.next(buffer.asObservable());
|
|
711
1121
|
};
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
let activeConnectCancel = null;
|
|
716
|
-
let disposed = false;
|
|
717
|
-
const createDisposedError = () => new SerialError(
|
|
718
|
-
"SESSION_DISPOSED" /* SESSION_DISPOSED */,
|
|
719
|
-
"SerialSession has been disposed"
|
|
720
|
-
);
|
|
721
|
-
const completeSession = async () => {
|
|
722
|
-
const current = machine.current;
|
|
723
|
-
if (current === SerialSessionState.Connecting) {
|
|
724
|
-
activeConnectCancel?.();
|
|
1122
|
+
const teardownFromSnapshot = async (snapshot) => {
|
|
1123
|
+
if (snapshot.status === SerialSessionStatus.Connecting) {
|
|
1124
|
+
snapshot.cancel();
|
|
725
1125
|
}
|
|
726
1126
|
sendQueue.clear();
|
|
727
|
-
if (
|
|
728
|
-
const portToClose =
|
|
729
|
-
|
|
1127
|
+
if (snapshot.status === SerialSessionStatus.Connected || snapshot.status === SerialSessionStatus.Disconnecting || snapshot.status === SerialSessionStatus.Error) {
|
|
1128
|
+
const portToClose = getRuntimePort(snapshot);
|
|
1129
|
+
const pump = getRuntimePump(snapshot);
|
|
1130
|
+
await teardownPump(pump);
|
|
730
1131
|
await closePortSafely(portToClose);
|
|
731
|
-
|
|
1132
|
+
updatePortInfo(null);
|
|
732
1133
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1134
|
+
receivePipeline.clearLineBuffer();
|
|
1135
|
+
};
|
|
1136
|
+
const completeSubjects = () => {
|
|
1137
|
+
controller.complete();
|
|
736
1138
|
errorsSubject.complete();
|
|
737
|
-
|
|
738
|
-
linesSubject.complete();
|
|
1139
|
+
receivePipeline.complete();
|
|
739
1140
|
portInfoSubject.complete();
|
|
740
|
-
receiveReplayStream$?.complete();
|
|
741
1141
|
};
|
|
742
|
-
const dispose$ = () => new
|
|
743
|
-
if (
|
|
1142
|
+
const dispose$ = () => new Observable3((subscriber) => {
|
|
1143
|
+
if (isDisposed()) {
|
|
744
1144
|
subscriber.next();
|
|
745
1145
|
subscriber.complete();
|
|
746
1146
|
return;
|
|
747
1147
|
}
|
|
748
|
-
|
|
1148
|
+
const snapshot = controller.runtime;
|
|
1149
|
+
controller.transition(createDisposedRuntime());
|
|
749
1150
|
const run = async () => {
|
|
750
1151
|
try {
|
|
751
|
-
await
|
|
1152
|
+
await teardownFromSnapshot(snapshot);
|
|
1153
|
+
completeSubjects();
|
|
752
1154
|
subscriber.next();
|
|
753
1155
|
subscriber.complete();
|
|
754
1156
|
} catch (error) {
|
|
755
1157
|
const serialError = normalizeSerialError(error, {
|
|
756
|
-
fallbackCode:
|
|
1158
|
+
fallbackCode: SerialErrorCode.UNKNOWN,
|
|
757
1159
|
messagePrefix: "Unexpected dispose failure"
|
|
758
1160
|
});
|
|
759
1161
|
subscriber.error(serialError);
|
|
@@ -761,57 +1163,15 @@ function createSerialSession(options) {
|
|
|
761
1163
|
};
|
|
762
1164
|
void run();
|
|
763
1165
|
});
|
|
764
|
-
const clearActiveConnectCancel = (cancel) => {
|
|
765
|
-
if (activeConnectCancel === cancel) {
|
|
766
|
-
activeConnectCancel = null;
|
|
767
|
-
}
|
|
768
|
-
};
|
|
769
|
-
const setActivePort = (port) => {
|
|
770
|
-
activePort = port;
|
|
771
|
-
portInfoSubject.next(port ? port.getInfo() : null);
|
|
772
|
-
};
|
|
773
|
-
const teardownPump = async () => {
|
|
774
|
-
clearLiveReceiveReplay();
|
|
775
|
-
const pump = activePump;
|
|
776
|
-
activePump = null;
|
|
777
|
-
lineBuffer.clear();
|
|
778
|
-
if (pump) {
|
|
779
|
-
await pump.stop();
|
|
780
|
-
}
|
|
781
|
-
};
|
|
782
|
-
const closePortSafely = async (port) => {
|
|
783
|
-
if (!port) {
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
try {
|
|
787
|
-
await port.close();
|
|
788
|
-
} catch {
|
|
789
|
-
}
|
|
790
|
-
};
|
|
791
|
-
const reportError = (error, severity, options2) => {
|
|
792
|
-
const serialError = normalizeSerialError(error, options2);
|
|
793
|
-
if (disposed) {
|
|
794
|
-
return serialError;
|
|
795
|
-
}
|
|
796
|
-
errorsSubject.next(serialError);
|
|
797
|
-
if (severity === "fatal") {
|
|
798
|
-
machine.toError();
|
|
799
|
-
sendQueue.clear();
|
|
800
|
-
const portToClose = activePort;
|
|
801
|
-
setActivePort(null);
|
|
802
|
-
void teardownPump().then(() => closePortSafely(portToClose));
|
|
803
|
-
}
|
|
804
|
-
return serialError;
|
|
805
|
-
};
|
|
806
1166
|
const writeToPort = async (payload) => {
|
|
807
|
-
const
|
|
808
|
-
if (
|
|
1167
|
+
const runtime = controller.runtime;
|
|
1168
|
+
if (runtime.status !== SerialSessionStatus.Connected || !runtime.port.writable) {
|
|
809
1169
|
throw new SerialError(
|
|
810
|
-
|
|
1170
|
+
SerialErrorCode.PORT_NOT_OPEN,
|
|
811
1171
|
"Cannot send data while session is not connected"
|
|
812
1172
|
);
|
|
813
1173
|
}
|
|
814
|
-
const writer = port.writable.getWriter();
|
|
1174
|
+
const writer = runtime.port.writable.getWriter();
|
|
815
1175
|
try {
|
|
816
1176
|
await writer.write(payload);
|
|
817
1177
|
} finally {
|
|
@@ -821,254 +1181,527 @@ function createSerialSession(options) {
|
|
|
821
1181
|
}
|
|
822
1182
|
}
|
|
823
1183
|
};
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1184
|
+
const connect$ = () => new Observable3((subscriber) => {
|
|
1185
|
+
if (isDisposed()) {
|
|
1186
|
+
subscriber.error(createDisposedError());
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
if (!hasWebSerialSupport()) {
|
|
1190
|
+
const error = reportError(
|
|
1191
|
+
new SerialError(
|
|
1192
|
+
SerialErrorCode.BROWSER_NOT_SUPPORTED,
|
|
1193
|
+
"Web Serial API is not supported in this environment"
|
|
1194
|
+
),
|
|
1195
|
+
{ fallbackCode: SerialErrorCode.BROWSER_NOT_SUPPORTED }
|
|
1196
|
+
);
|
|
1197
|
+
subscriber.error(error);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
const current = controller.status;
|
|
1201
|
+
if (current !== SerialSessionStatus.Idle && current !== SerialSessionStatus.Error) {
|
|
1202
|
+
const error = reportError(
|
|
1203
|
+
new SerialError(
|
|
1204
|
+
SerialErrorCode.PORT_ALREADY_OPEN,
|
|
1205
|
+
`Cannot connect while session state is '${current}'`
|
|
1206
|
+
),
|
|
1207
|
+
{ fallbackCode: SerialErrorCode.PORT_ALREADY_OPEN }
|
|
1208
|
+
);
|
|
1209
|
+
subscriber.error(error);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
let cancelled = false;
|
|
1213
|
+
const cancelInFlightConnect = () => {
|
|
1214
|
+
cancelled = true;
|
|
1215
|
+
if (controller.status === SerialSessionStatus.Connecting) {
|
|
1216
|
+
controller.transition(createIdleRuntime());
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
controller.transition(createConnectingRuntime(cancelInFlightConnect));
|
|
1220
|
+
const run = async () => {
|
|
1221
|
+
let selectedPort = null;
|
|
1222
|
+
try {
|
|
1223
|
+
selectedPort = await navigator.serial.requestPort(
|
|
1224
|
+
buildRequestOptions(resolvedOptions)
|
|
1225
|
+
);
|
|
1226
|
+
await selectedPort.open({
|
|
1227
|
+
baudRate: resolvedOptions.baudRate,
|
|
1228
|
+
dataBits: resolvedOptions.dataBits,
|
|
1229
|
+
stopBits: resolvedOptions.stopBits,
|
|
1230
|
+
parity: resolvedOptions.parity,
|
|
1231
|
+
bufferSize: resolvedOptions.bufferSize,
|
|
1232
|
+
flowControl: resolvedOptions.flowControl
|
|
1233
|
+
});
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
if (selectedPort) {
|
|
1236
|
+
await closePortSafely(selectedPort);
|
|
833
1237
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
"non-fatal",
|
|
841
|
-
{ fallbackCode: "BROWSER_NOT_SUPPORTED" /* BROWSER_NOT_SUPPORTED */ }
|
|
842
|
-
);
|
|
843
|
-
subscriber.error(error);
|
|
844
|
-
return;
|
|
1238
|
+
const serialError = reportError(error, {
|
|
1239
|
+
fallbackCode: SerialErrorCode.PORT_OPEN_FAILED,
|
|
1240
|
+
messagePrefix: "Failed to open port"
|
|
1241
|
+
});
|
|
1242
|
+
if (!cancelled) {
|
|
1243
|
+
subscriber.error(serialError);
|
|
845
1244
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (cancelled || isDisposed()) {
|
|
1248
|
+
await closePortSafely(selectedPort);
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
receivePipeline.clearLineBuffer();
|
|
1252
|
+
if (resolvedOptions.receiveReplay.enabled) {
|
|
1253
|
+
receivePipeline.startLiveReceiveReplay();
|
|
1254
|
+
}
|
|
1255
|
+
const pump = createReadPump(selectedPort, {
|
|
1256
|
+
onChunk: receivePipeline.handleChunk,
|
|
1257
|
+
onError: (pumpError) => reportError(pumpError, {
|
|
1258
|
+
fallbackCode: SerialErrorCode.READ_FAILED,
|
|
1259
|
+
messagePrefix: "Read pump failed"
|
|
1260
|
+
}),
|
|
1261
|
+
onDone: () => {
|
|
1262
|
+
if (controller.status !== SerialSessionStatus.Connected) {
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
reportError(
|
|
849
1266
|
new SerialError(
|
|
850
|
-
|
|
851
|
-
|
|
1267
|
+
SerialErrorCode.CONNECTION_LOST,
|
|
1268
|
+
"Read pump ended unexpectedly: stream closed while connected"
|
|
852
1269
|
),
|
|
853
|
-
|
|
854
|
-
|
|
1270
|
+
{
|
|
1271
|
+
fallbackCode: SerialErrorCode.CONNECTION_LOST
|
|
1272
|
+
}
|
|
855
1273
|
);
|
|
856
|
-
subscriber.error(error);
|
|
857
|
-
return;
|
|
858
1274
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1275
|
+
});
|
|
1276
|
+
pump.start();
|
|
1277
|
+
sendQueue.clear();
|
|
1278
|
+
if (isDisposed()) {
|
|
1279
|
+
await teardownPump(pump);
|
|
1280
|
+
await closePortSafely(selectedPort);
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
controller.transition(createConnectedRuntime(selectedPort, pump));
|
|
1284
|
+
updatePortInfo(selectedPort);
|
|
1285
|
+
subscriber.next();
|
|
1286
|
+
subscriber.complete();
|
|
1287
|
+
};
|
|
1288
|
+
void run();
|
|
1289
|
+
return () => {
|
|
1290
|
+
cancelInFlightConnect();
|
|
1291
|
+
};
|
|
1292
|
+
});
|
|
1293
|
+
const disconnect$ = () => new Observable3((subscriber) => {
|
|
1294
|
+
if (isDisposed()) {
|
|
1295
|
+
subscriber.next();
|
|
1296
|
+
subscriber.complete();
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
const runtime = controller.runtime;
|
|
1300
|
+
if (runtime.status === SerialSessionStatus.Idle || runtime.status === SerialSessionStatus.Unsupported || runtime.status === SerialSessionStatus.Disconnecting) {
|
|
1301
|
+
subscriber.next();
|
|
1302
|
+
subscriber.complete();
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
if (runtime.status === SerialSessionStatus.Connecting) {
|
|
1306
|
+
runtime.cancel();
|
|
1307
|
+
subscriber.next();
|
|
1308
|
+
subscriber.complete();
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
if (runtime.status !== SerialSessionStatus.Connected && runtime.status !== SerialSessionStatus.Error) {
|
|
1312
|
+
const error = reportError(
|
|
1313
|
+
new SerialError(
|
|
1314
|
+
SerialErrorCode.PORT_NOT_OPEN,
|
|
1315
|
+
`Cannot disconnect while session state is '${runtime.status}'`
|
|
1316
|
+
),
|
|
1317
|
+
{ fallbackCode: SerialErrorCode.PORT_NOT_OPEN }
|
|
1318
|
+
);
|
|
1319
|
+
subscriber.error(error);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
const portToClose = getRuntimePort(runtime);
|
|
1323
|
+
controller.transition(createDisconnectingRuntime(portToClose));
|
|
1324
|
+
sendQueue.clear();
|
|
1325
|
+
const run = async () => {
|
|
1326
|
+
try {
|
|
1327
|
+
const pump = getRuntimePump(controller.runtime);
|
|
1328
|
+
await teardownPump(pump);
|
|
1329
|
+
if (portToClose) {
|
|
870
1330
|
try {
|
|
871
|
-
|
|
872
|
-
buildRequestOptions(resolvedOptions)
|
|
873
|
-
);
|
|
874
|
-
await selectedPort.open({
|
|
875
|
-
baudRate: resolvedOptions.baudRate,
|
|
876
|
-
dataBits: resolvedOptions.dataBits,
|
|
877
|
-
stopBits: resolvedOptions.stopBits,
|
|
878
|
-
parity: resolvedOptions.parity,
|
|
879
|
-
bufferSize: resolvedOptions.bufferSize,
|
|
880
|
-
flowControl: resolvedOptions.flowControl
|
|
881
|
-
});
|
|
1331
|
+
await portToClose.close();
|
|
882
1332
|
} catch (error) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
fallbackCode: "PORT_OPEN_FAILED" /* PORT_OPEN_FAILED */,
|
|
888
|
-
messagePrefix: "Failed to open port"
|
|
1333
|
+
updatePortInfo(null);
|
|
1334
|
+
const serialError = reportError(error, {
|
|
1335
|
+
fallbackCode: SerialErrorCode.CONNECTION_LOST,
|
|
1336
|
+
messagePrefix: "Failed to close port"
|
|
889
1337
|
});
|
|
890
|
-
|
|
891
|
-
subscriber.error(serialError);
|
|
892
|
-
}
|
|
893
|
-
return;
|
|
894
|
-
}
|
|
895
|
-
if (cancelled || disposed) {
|
|
896
|
-
await closePortSafely(selectedPort);
|
|
1338
|
+
subscriber.error(serialError);
|
|
897
1339
|
return;
|
|
898
1340
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1341
|
+
}
|
|
1342
|
+
updatePortInfo(null);
|
|
1343
|
+
if (!isDisposed()) {
|
|
1344
|
+
controller.transition(createIdleRuntime());
|
|
1345
|
+
}
|
|
1346
|
+
subscriber.next();
|
|
1347
|
+
subscriber.complete();
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
const serialError = reportError(error, {
|
|
1350
|
+
fallbackCode: SerialErrorCode.UNKNOWN,
|
|
1351
|
+
messagePrefix: "Unexpected disconnect failure"
|
|
1352
|
+
});
|
|
1353
|
+
subscriber.error(serialError);
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
void run();
|
|
1357
|
+
});
|
|
1358
|
+
return {
|
|
1359
|
+
connect$,
|
|
1360
|
+
disconnect$,
|
|
1361
|
+
dispose$,
|
|
1362
|
+
writeToPort,
|
|
1363
|
+
teardownPump,
|
|
1364
|
+
closePortSafely,
|
|
1365
|
+
updatePortInfo
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// src/session/send-queue.ts
|
|
1370
|
+
import { Observable as Observable4, defer } from "rxjs";
|
|
1371
|
+
function createSendQueue() {
|
|
1372
|
+
let chain = Promise.resolve();
|
|
1373
|
+
return {
|
|
1374
|
+
enqueue(operation) {
|
|
1375
|
+
return defer(
|
|
1376
|
+
() => new Observable4((subscriber) => {
|
|
1377
|
+
let cancelled = false;
|
|
1378
|
+
const run = async () => {
|
|
1379
|
+
try {
|
|
1380
|
+
const value = await operation();
|
|
1381
|
+
if (!cancelled) {
|
|
1382
|
+
subscriber.next(value);
|
|
1383
|
+
subscriber.complete();
|
|
935
1384
|
}
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
messagePrefix: "Read pump failed"
|
|
940
|
-
}),
|
|
941
|
-
onDone: () => {
|
|
942
|
-
if (machine.current !== SerialSessionState.Connected) {
|
|
943
|
-
return;
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
if (!cancelled) {
|
|
1387
|
+
subscriber.error(error);
|
|
944
1388
|
}
|
|
945
|
-
reportError(
|
|
946
|
-
new SerialError(
|
|
947
|
-
"CONNECTION_LOST" /* CONNECTION_LOST */,
|
|
948
|
-
"Read pump ended unexpectedly: stream closed while connected"
|
|
949
|
-
),
|
|
950
|
-
"fatal",
|
|
951
|
-
{
|
|
952
|
-
fallbackCode: "CONNECTION_LOST" /* CONNECTION_LOST */
|
|
953
|
-
}
|
|
954
|
-
);
|
|
955
1389
|
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
await closePortSafely(selectedPort);
|
|
962
|
-
setActivePort(null);
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
machine.toConnected();
|
|
966
|
-
subscriber.next();
|
|
967
|
-
subscriber.complete();
|
|
968
|
-
};
|
|
969
|
-
void run();
|
|
970
|
-
return () => {
|
|
971
|
-
clearActiveConnectCancel(cancelInFlightConnect);
|
|
972
|
-
cancelInFlightConnect();
|
|
973
|
-
};
|
|
974
|
-
});
|
|
975
|
-
},
|
|
976
|
-
disconnect$() {
|
|
977
|
-
return new Observable4((subscriber) => {
|
|
978
|
-
if (disposed) {
|
|
979
|
-
subscriber.next();
|
|
980
|
-
subscriber.complete();
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
const current = machine.current;
|
|
984
|
-
if (current === SerialSessionState.Idle || current === SerialSessionState.Unsupported || current === SerialSessionState.Disconnecting) {
|
|
985
|
-
subscriber.next();
|
|
986
|
-
subscriber.complete();
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
989
|
-
if (current === SerialSessionState.Connecting) {
|
|
990
|
-
activeConnectCancel?.();
|
|
991
|
-
subscriber.next();
|
|
992
|
-
subscriber.complete();
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
if (current !== SerialSessionState.Connected && current !== SerialSessionState.Error) {
|
|
996
|
-
const error = reportError(
|
|
997
|
-
new SerialError(
|
|
998
|
-
"PORT_NOT_OPEN" /* PORT_NOT_OPEN */,
|
|
999
|
-
`Cannot disconnect while session state is '${current}'`
|
|
1000
|
-
),
|
|
1001
|
-
"non-fatal",
|
|
1002
|
-
{ fallbackCode: "PORT_NOT_OPEN" /* PORT_NOT_OPEN */ }
|
|
1390
|
+
};
|
|
1391
|
+
const scheduled = chain.then(run, run);
|
|
1392
|
+
chain = scheduled.then(
|
|
1393
|
+
() => void 0,
|
|
1394
|
+
() => void 0
|
|
1003
1395
|
);
|
|
1004
|
-
|
|
1005
|
-
|
|
1396
|
+
return () => {
|
|
1397
|
+
cancelled = true;
|
|
1398
|
+
};
|
|
1399
|
+
})
|
|
1400
|
+
);
|
|
1401
|
+
},
|
|
1402
|
+
clear() {
|
|
1403
|
+
chain = Promise.resolve();
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// src/session/internal/validate-serial-port-filters.ts
|
|
1409
|
+
function validateSerialPortFilters(filters) {
|
|
1410
|
+
if (!filters || filters.length === 0) {
|
|
1411
|
+
return filters;
|
|
1412
|
+
}
|
|
1413
|
+
for (let filterIndex = 0; filterIndex < filters.length; filterIndex++) {
|
|
1414
|
+
const filter = filters[filterIndex];
|
|
1415
|
+
if (filter === void 0) {
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
if (!filter.usbVendorId && !filter.usbProductId) {
|
|
1419
|
+
throw new SerialError(
|
|
1420
|
+
SerialErrorCode.INVALID_FILTER_OPTIONS,
|
|
1421
|
+
"Filter must have at least usbVendorId or usbProductId",
|
|
1422
|
+
void 0,
|
|
1423
|
+
{
|
|
1424
|
+
field: "filters",
|
|
1425
|
+
value: filter,
|
|
1426
|
+
constraint: "at-least-one-usb-id",
|
|
1427
|
+
filterIndex
|
|
1006
1428
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
messagePrefix: "Failed to close port"
|
|
1021
|
-
});
|
|
1022
|
-
subscriber.error(serialError);
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
setActivePort(null);
|
|
1027
|
-
if (!disposed) {
|
|
1028
|
-
machine.toIdle();
|
|
1029
|
-
}
|
|
1030
|
-
subscriber.next();
|
|
1031
|
-
subscriber.complete();
|
|
1032
|
-
} catch (error) {
|
|
1033
|
-
const serialError = reportError(error, "fatal", {
|
|
1034
|
-
fallbackCode: "UNKNOWN" /* UNKNOWN */,
|
|
1035
|
-
messagePrefix: "Unexpected disconnect failure"
|
|
1036
|
-
});
|
|
1037
|
-
subscriber.error(serialError);
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
if (filter.usbVendorId !== void 0) {
|
|
1432
|
+
if (!Number.isInteger(filter.usbVendorId) || filter.usbVendorId < 0 || filter.usbVendorId > 65535) {
|
|
1433
|
+
throw new SerialError(
|
|
1434
|
+
SerialErrorCode.INVALID_FILTER_OPTIONS,
|
|
1435
|
+
`Invalid usbVendorId: ${filter.usbVendorId}. Must be an integer between 0 and 65535.`,
|
|
1436
|
+
void 0,
|
|
1437
|
+
{
|
|
1438
|
+
field: "usbVendorId",
|
|
1439
|
+
value: filter.usbVendorId,
|
|
1440
|
+
constraint: "usb-id-0-65535",
|
|
1441
|
+
filterIndex
|
|
1038
1442
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
if (filter.usbProductId !== void 0) {
|
|
1447
|
+
if (!Number.isInteger(filter.usbProductId) || filter.usbProductId < 0 || filter.usbProductId > 65535) {
|
|
1448
|
+
throw new SerialError(
|
|
1449
|
+
SerialErrorCode.INVALID_FILTER_OPTIONS,
|
|
1450
|
+
`Invalid usbProductId: ${filter.usbProductId}. Must be an integer between 0 and 65535.`,
|
|
1451
|
+
void 0,
|
|
1452
|
+
{
|
|
1453
|
+
field: "usbProductId",
|
|
1454
|
+
value: filter.usbProductId,
|
|
1455
|
+
constraint: "usb-id-0-65535",
|
|
1456
|
+
filterIndex
|
|
1457
|
+
}
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
return filters;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// src/session/serial-session-options.ts
|
|
1466
|
+
var MAX_RECEIVE_REPLAY_BUFFER_SIZE = 65536;
|
|
1467
|
+
var MAX_RECEIVE_REPLAY_MAX_CHARS = 1048576;
|
|
1468
|
+
var DEFAULT_RECEIVE_REPLAY = {
|
|
1469
|
+
enabled: false,
|
|
1470
|
+
bufferSize: 512,
|
|
1471
|
+
maxChars: 0
|
|
1472
|
+
};
|
|
1473
|
+
function resolveReceiveReplayOptions(options) {
|
|
1474
|
+
const merged = {
|
|
1475
|
+
...DEFAULT_RECEIVE_REPLAY,
|
|
1476
|
+
...options
|
|
1477
|
+
};
|
|
1478
|
+
const { bufferSize, maxChars } = merged;
|
|
1479
|
+
if (!Number.isSafeInteger(bufferSize) || bufferSize < 1 || bufferSize > MAX_RECEIVE_REPLAY_BUFFER_SIZE) {
|
|
1480
|
+
throw new SerialError(
|
|
1481
|
+
SerialErrorCode.INVALID_RECEIVE_REPLAY_OPTIONS,
|
|
1482
|
+
`Invalid receiveReplay.bufferSize: ${bufferSize}. Must be a safe integer between 1 and ${MAX_RECEIVE_REPLAY_BUFFER_SIZE}.`,
|
|
1483
|
+
void 0,
|
|
1484
|
+
{
|
|
1485
|
+
field: "receiveReplay.bufferSize",
|
|
1486
|
+
value: bufferSize,
|
|
1487
|
+
constraint: "receive-replay-buffer-size-range"
|
|
1488
|
+
}
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
if (!Number.isSafeInteger(maxChars) || maxChars < 0 || maxChars > MAX_RECEIVE_REPLAY_MAX_CHARS) {
|
|
1492
|
+
throw new SerialError(
|
|
1493
|
+
SerialErrorCode.INVALID_RECEIVE_REPLAY_OPTIONS,
|
|
1494
|
+
`Invalid receiveReplay.maxChars: ${maxChars}. Must be a safe integer between 0 and ${MAX_RECEIVE_REPLAY_MAX_CHARS}.`,
|
|
1495
|
+
void 0,
|
|
1496
|
+
{
|
|
1497
|
+
field: "receiveReplay.maxChars",
|
|
1498
|
+
value: maxChars,
|
|
1499
|
+
constraint: "receive-replay-max-chars-range"
|
|
1500
|
+
}
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
return {
|
|
1504
|
+
enabled: merged.enabled,
|
|
1505
|
+
bufferSize: brandReceiveReplayBufferSize(bufferSize),
|
|
1506
|
+
maxChars: brandMaxChars(maxChars)
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
function resolveTerminalBufferOptions(options) {
|
|
1510
|
+
const merged = {
|
|
1511
|
+
...DEFAULT_TERMINAL_BUFFER_OPTIONS,
|
|
1512
|
+
...options
|
|
1513
|
+
};
|
|
1514
|
+
const { maxLines, maxChars } = merged;
|
|
1515
|
+
if (!Number.isSafeInteger(maxLines) || maxLines < 0) {
|
|
1516
|
+
throw new SerialError(
|
|
1517
|
+
SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS,
|
|
1518
|
+
`Invalid terminalBuffer.maxLines: ${maxLines}. Must be a safe integer >= 0.`,
|
|
1519
|
+
void 0,
|
|
1520
|
+
{
|
|
1521
|
+
field: "terminalBuffer.maxLines",
|
|
1522
|
+
value: maxLines,
|
|
1523
|
+
constraint: "non-negative-safe-integer"
|
|
1524
|
+
}
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
if (!Number.isSafeInteger(maxChars) || maxChars < 0) {
|
|
1528
|
+
throw new SerialError(
|
|
1529
|
+
SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS,
|
|
1530
|
+
`Invalid terminalBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`,
|
|
1531
|
+
void 0,
|
|
1532
|
+
{
|
|
1533
|
+
field: "terminalBuffer.maxChars",
|
|
1534
|
+
value: maxChars,
|
|
1535
|
+
constraint: "non-negative-safe-integer"
|
|
1536
|
+
}
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
return {
|
|
1540
|
+
maxLines: brandMaxLines(maxLines),
|
|
1541
|
+
maxChars: brandMaxChars(maxChars),
|
|
1542
|
+
stripAnsi: merged.stripAnsi
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
function resolveLineBufferOptions(options) {
|
|
1546
|
+
const merged = {
|
|
1547
|
+
...DEFAULT_LINE_BUFFER_OPTIONS,
|
|
1548
|
+
...options
|
|
1549
|
+
};
|
|
1550
|
+
const { maxChars } = merged;
|
|
1551
|
+
if (!Number.isSafeInteger(maxChars) || maxChars < 0) {
|
|
1552
|
+
throw new SerialError(
|
|
1553
|
+
SerialErrorCode.INVALID_LINE_BUFFER_OPTIONS,
|
|
1554
|
+
`Invalid lineBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`,
|
|
1555
|
+
void 0,
|
|
1556
|
+
{
|
|
1557
|
+
field: "lineBuffer.maxChars",
|
|
1558
|
+
value: maxChars,
|
|
1559
|
+
constraint: "non-negative-safe-integer"
|
|
1560
|
+
}
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
return {
|
|
1564
|
+
maxChars: brandMaxChars(maxChars)
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
var DEFAULT_SERIAL_SESSION_OPTIONS = {
|
|
1568
|
+
baudRate: brandBaudRate(9600),
|
|
1569
|
+
dataBits: 8,
|
|
1570
|
+
stopBits: 1,
|
|
1571
|
+
parity: "none",
|
|
1572
|
+
bufferSize: brandSerialPortBufferSize(255),
|
|
1573
|
+
flowControl: "none",
|
|
1574
|
+
receiveReplay: resolveReceiveReplayOptions(),
|
|
1575
|
+
terminalBuffer: resolveTerminalBufferOptions(),
|
|
1576
|
+
lineBuffer: resolveLineBufferOptions()
|
|
1577
|
+
};
|
|
1578
|
+
function resolveConnectionOptions(options) {
|
|
1579
|
+
const merged = {
|
|
1580
|
+
baudRate: DEFAULT_SERIAL_SESSION_OPTIONS.baudRate,
|
|
1581
|
+
dataBits: DEFAULT_SERIAL_SESSION_OPTIONS.dataBits,
|
|
1582
|
+
stopBits: DEFAULT_SERIAL_SESSION_OPTIONS.stopBits,
|
|
1583
|
+
parity: DEFAULT_SERIAL_SESSION_OPTIONS.parity,
|
|
1584
|
+
bufferSize: DEFAULT_SERIAL_SESSION_OPTIONS.bufferSize,
|
|
1585
|
+
flowControl: DEFAULT_SERIAL_SESSION_OPTIONS.flowControl,
|
|
1586
|
+
...options
|
|
1587
|
+
};
|
|
1588
|
+
const { baudRate, bufferSize } = merged;
|
|
1589
|
+
if (!Number.isSafeInteger(baudRate) || baudRate <= 0) {
|
|
1590
|
+
throw new SerialError(
|
|
1591
|
+
SerialErrorCode.INVALID_CONNECTION_OPTIONS,
|
|
1592
|
+
`Invalid baudRate: ${baudRate}. Must be a safe integer > 0.`,
|
|
1593
|
+
void 0,
|
|
1594
|
+
{
|
|
1595
|
+
field: "baudRate",
|
|
1596
|
+
value: baudRate,
|
|
1597
|
+
constraint: "positive-safe-integer"
|
|
1598
|
+
}
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
return {
|
|
1602
|
+
dataBits: merged.dataBits ?? DEFAULT_SERIAL_SESSION_OPTIONS.dataBits,
|
|
1603
|
+
stopBits: merged.stopBits ?? DEFAULT_SERIAL_SESSION_OPTIONS.stopBits,
|
|
1604
|
+
parity: merged.parity ?? DEFAULT_SERIAL_SESSION_OPTIONS.parity,
|
|
1605
|
+
flowControl: merged.flowControl ?? DEFAULT_SERIAL_SESSION_OPTIONS.flowControl,
|
|
1606
|
+
baudRate: brandBaudRate(baudRate),
|
|
1607
|
+
bufferSize: brandSerialPortBufferSize(
|
|
1608
|
+
bufferSize ?? DEFAULT_SERIAL_SESSION_OPTIONS.bufferSize
|
|
1609
|
+
)
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
function resolveSerialSessionOptions(options) {
|
|
1613
|
+
const connection = resolveConnectionOptions(options);
|
|
1614
|
+
const filters = validateSerialPortFilters(options?.filters);
|
|
1615
|
+
return {
|
|
1616
|
+
...connection,
|
|
1617
|
+
...filters !== void 0 ? { filters } : {},
|
|
1618
|
+
receiveReplay: resolveReceiveReplayOptions(options?.receiveReplay),
|
|
1619
|
+
terminalBuffer: resolveTerminalBufferOptions(options?.terminalBuffer),
|
|
1620
|
+
lineBuffer: resolveLineBufferOptions(options?.lineBuffer)
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// src/session/create-serial-session.ts
|
|
1625
|
+
function createSerialSession(options) {
|
|
1626
|
+
const resolvedOptions = resolveSerialSessionOptions(options);
|
|
1627
|
+
const controller = createSessionRuntimeController(
|
|
1628
|
+
createInitialRuntime(hasWebSerialSupport())
|
|
1629
|
+
);
|
|
1630
|
+
const errorsSubject = new Subject3();
|
|
1631
|
+
const sendQueue = createSendQueue();
|
|
1632
|
+
const textEncoder = new TextEncoder();
|
|
1633
|
+
const portInfoSubject = new BehaviorSubject3(null);
|
|
1634
|
+
const isDisposed = () => controller.status === SerialSessionStatus.Disposed;
|
|
1635
|
+
const errorReporterRef = {};
|
|
1636
|
+
const receivePipeline = createReceivePipeline({
|
|
1637
|
+
resolvedOptions,
|
|
1638
|
+
reportError: (error, options2) => errorReporterRef.reportError(error, options2)
|
|
1639
|
+
});
|
|
1640
|
+
const lifecycle = createSessionLifecycle({
|
|
1641
|
+
controller,
|
|
1642
|
+
resolvedOptions,
|
|
1643
|
+
sendQueue,
|
|
1644
|
+
receivePipeline,
|
|
1645
|
+
portInfoSubject,
|
|
1646
|
+
errorsSubject,
|
|
1647
|
+
isDisposed,
|
|
1648
|
+
reportError: (error, options2) => errorReporterRef.reportError(error, options2),
|
|
1649
|
+
createDisposedError: () => errorReporterRef.createDisposedError()
|
|
1650
|
+
});
|
|
1651
|
+
const { reportError, createDisposedError } = createSessionErrorReporter({
|
|
1652
|
+
controller,
|
|
1653
|
+
errorsSubject,
|
|
1654
|
+
sendQueue,
|
|
1655
|
+
isDisposed,
|
|
1656
|
+
updatePortInfo: lifecycle.updatePortInfo,
|
|
1657
|
+
teardownPump: lifecycle.teardownPump,
|
|
1658
|
+
closePortSafely: lifecycle.closePortSafely
|
|
1659
|
+
});
|
|
1660
|
+
errorReporterRef.reportError = reportError;
|
|
1661
|
+
errorReporterRef.createDisposedError = createDisposedError;
|
|
1662
|
+
const { receive$, lines$, receiveReplay$ } = receivePipeline;
|
|
1663
|
+
const errors$ = errorsSubject.asObservable();
|
|
1664
|
+
const terminalText$ = createTerminalBuffer(
|
|
1665
|
+
receive$,
|
|
1666
|
+
resolvedOptions.terminalBuffer
|
|
1667
|
+
).text$;
|
|
1668
|
+
const isConnected$ = controller.state$.pipe(
|
|
1669
|
+
map2((state) => state.status === SerialSessionStatus.Connected),
|
|
1670
|
+
distinctUntilChanged()
|
|
1671
|
+
);
|
|
1672
|
+
const portInfo$ = portInfoSubject.asObservable();
|
|
1673
|
+
return {
|
|
1674
|
+
isBrowserSupported() {
|
|
1675
|
+
return hasWebSerialSupport();
|
|
1042
1676
|
},
|
|
1043
|
-
|
|
1044
|
-
|
|
1677
|
+
connect$: lifecycle.connect$,
|
|
1678
|
+
disconnect$: lifecycle.disconnect$,
|
|
1679
|
+
dispose$: lifecycle.dispose$,
|
|
1680
|
+
destroy$: lifecycle.dispose$,
|
|
1045
1681
|
send$(data) {
|
|
1046
|
-
if (
|
|
1047
|
-
return new
|
|
1682
|
+
if (isDisposed()) {
|
|
1683
|
+
return new Observable5((subscriber) => {
|
|
1048
1684
|
subscriber.error(createDisposedError());
|
|
1049
1685
|
});
|
|
1050
1686
|
}
|
|
1051
1687
|
return sendQueue.enqueue(async () => {
|
|
1052
1688
|
const payload = typeof data === "string" ? textEncoder.encode(data) : data;
|
|
1053
1689
|
try {
|
|
1054
|
-
await writeToPort(payload);
|
|
1690
|
+
await lifecycle.writeToPort(payload);
|
|
1055
1691
|
} catch (error) {
|
|
1056
|
-
throw reportError(error,
|
|
1057
|
-
fallbackCode:
|
|
1692
|
+
throw reportError(error, {
|
|
1693
|
+
fallbackCode: SerialErrorCode.WRITE_FAILED,
|
|
1058
1694
|
messagePrefix: "Failed to write data"
|
|
1059
1695
|
});
|
|
1060
1696
|
}
|
|
1061
1697
|
});
|
|
1062
1698
|
},
|
|
1063
|
-
state$:
|
|
1699
|
+
state$: controller.state$,
|
|
1064
1700
|
isConnected$,
|
|
1065
1701
|
portInfo$,
|
|
1066
1702
|
getPortInfo() {
|
|
1067
1703
|
return portInfoSubject.getValue();
|
|
1068
1704
|
},
|
|
1069
|
-
getCurrentPort() {
|
|
1070
|
-
return activePort;
|
|
1071
|
-
},
|
|
1072
1705
|
errors$,
|
|
1073
1706
|
receive$,
|
|
1074
1707
|
terminalText$,
|
|
@@ -1076,13 +1709,23 @@ function createSerialSession(options) {
|
|
|
1076
1709
|
lines$
|
|
1077
1710
|
};
|
|
1078
1711
|
}
|
|
1712
|
+
|
|
1713
|
+
// src/session/is-connected-session-state.ts
|
|
1714
|
+
function isConnectedSessionState(state) {
|
|
1715
|
+
return state.status === SerialSessionStatus.Connected;
|
|
1716
|
+
}
|
|
1079
1717
|
export {
|
|
1080
1718
|
DEFAULT_LINE_BUFFER_OPTIONS,
|
|
1081
1719
|
DEFAULT_TERMINAL_BUFFER_OPTIONS,
|
|
1720
|
+
MAX_RECEIVE_REPLAY_BUFFER_SIZE,
|
|
1721
|
+
MAX_RECEIVE_REPLAY_MAX_CHARS,
|
|
1082
1722
|
SerialError,
|
|
1083
1723
|
SerialErrorCode,
|
|
1084
|
-
|
|
1724
|
+
SerialSessionStatus,
|
|
1725
|
+
assertNever,
|
|
1085
1726
|
createSerialSession,
|
|
1086
|
-
createTerminalBuffer
|
|
1727
|
+
createTerminalBuffer,
|
|
1728
|
+
isConnectedSessionState,
|
|
1729
|
+
resolveSerialSessionOptions
|
|
1087
1730
|
};
|
|
1088
1731
|
//# sourceMappingURL=index.mjs.map
|