@gurezo/web-serial-rxjs 2.4.0 → 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/dist/errors/serial-error-code.d.ts +53 -18
- 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 +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1065 -594
- 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/session/create-serial-session.d.ts +5 -2
- package/dist/session/create-serial-session.d.ts.map +1 -1
- package/dist/session/index.d.ts +6 -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/serial-session-options.d.ts +95 -49
- package/dist/session/serial-session-options.d.ts.map +1 -1
- package/dist/session/serial-session-state.d.ts +49 -12
- package/dist/session/serial-session-state.d.ts.map +1 -1
- package/dist/session/serial-session.d.ts +9 -7
- 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 +3 -2
- 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,224 @@
|
|
|
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
|
+
* This error occurs when attempting to use serial port functionality in a browser
|
|
21
|
+
* that doesn't support the Web Serial API. Supported desktop browsers are Chrome,
|
|
22
|
+
* Edge, Opera, and Firefox 151+. Safari and mobile browsers are not supported.
|
|
23
|
+
*
|
|
24
|
+
* **Suggested action**: Inform the user to use a supported browser.
|
|
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
|
-
* This
|
|
55
|
-
*
|
|
30
|
+
* This error occurs when a requested port cannot be accessed, such as when
|
|
31
|
+
* getting previously granted ports fails or when the port is already in use
|
|
32
|
+
* by another application.
|
|
56
33
|
*
|
|
57
|
-
*
|
|
58
|
-
|
|
34
|
+
* **Suggested action**: Check if the port is available or being used by another application.
|
|
35
|
+
*/
|
|
36
|
+
PORT_NOT_AVAILABLE: "PORT_NOT_AVAILABLE",
|
|
37
|
+
/**
|
|
38
|
+
* Failed to open the serial port.
|
|
59
39
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* }
|
|
65
|
-
* ```
|
|
40
|
+
* This error occurs when the port cannot be opened, typically due to incorrect
|
|
41
|
+
* connection parameters, hardware issues, or permission problems.
|
|
42
|
+
*
|
|
43
|
+
* **Suggested action**: Verify connection parameters and check hardware connections.
|
|
66
44
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
45
|
+
PORT_OPEN_FAILED: "PORT_OPEN_FAILED",
|
|
46
|
+
/**
|
|
47
|
+
* Serial port is already open.
|
|
48
|
+
*
|
|
49
|
+
* This error occurs when attempting to open a port that is already connected.
|
|
50
|
+
* Only one connection can be active at a time per SerialClient instance.
|
|
51
|
+
*
|
|
52
|
+
* **Suggested action**: Disconnect the current port before connecting a new one.
|
|
53
|
+
*/
|
|
54
|
+
PORT_ALREADY_OPEN: "PORT_ALREADY_OPEN",
|
|
55
|
+
/**
|
|
56
|
+
* Serial port is not open.
|
|
57
|
+
*
|
|
58
|
+
* This error occurs when attempting to read from or write to a port that hasn't
|
|
59
|
+
* been opened yet. The port must be connected before performing I/O operations.
|
|
60
|
+
*
|
|
61
|
+
* **Suggested action**: Call {@link SerialClient.connect} before reading or writing.
|
|
62
|
+
*/
|
|
63
|
+
PORT_NOT_OPEN: "PORT_NOT_OPEN",
|
|
64
|
+
/**
|
|
65
|
+
* Failed to read from the serial port.
|
|
66
|
+
*
|
|
67
|
+
* This error occurs when reading data from the port fails, typically due to
|
|
68
|
+
* connection loss, hardware issues, or stream errors.
|
|
69
|
+
*
|
|
70
|
+
* **Suggested action**: Check the connection and hardware, then retry the read operation.
|
|
71
|
+
*/
|
|
72
|
+
READ_FAILED: "READ_FAILED",
|
|
73
|
+
/**
|
|
74
|
+
* Failed to write to the serial port.
|
|
75
|
+
*
|
|
76
|
+
* This error occurs when writing data to the port fails, typically due to
|
|
77
|
+
* connection loss, hardware issues, or stream errors.
|
|
78
|
+
*
|
|
79
|
+
* **Suggested action**: Check the connection and hardware, then retry the write operation.
|
|
80
|
+
*/
|
|
81
|
+
WRITE_FAILED: "WRITE_FAILED",
|
|
82
|
+
/**
|
|
83
|
+
* Serial port connection was lost.
|
|
84
|
+
*
|
|
85
|
+
* This error occurs when the connection to the serial port is unexpectedly
|
|
86
|
+
* terminated, such as when the device is disconnected or the port is closed
|
|
87
|
+
* by another process.
|
|
88
|
+
*
|
|
89
|
+
* **Suggested action**: Check the physical connection and reconnect if needed.
|
|
90
|
+
*/
|
|
91
|
+
CONNECTION_LOST: "CONNECTION_LOST",
|
|
92
|
+
/**
|
|
93
|
+
* Invalid filter options provided.
|
|
94
|
+
*
|
|
95
|
+
* This error occurs when port filter options are invalid, such as when
|
|
96
|
+
* filter values are out of range or missing required fields.
|
|
97
|
+
*
|
|
98
|
+
* **Suggested action**: Verify filter options match the expected format and value ranges.
|
|
99
|
+
*/
|
|
100
|
+
INVALID_FILTER_OPTIONS: "INVALID_FILTER_OPTIONS",
|
|
101
|
+
/**
|
|
102
|
+
* Operation was cancelled by the user.
|
|
103
|
+
*
|
|
104
|
+
* This error occurs when the user cancels a port selection dialog or aborts
|
|
105
|
+
* an operation before it completes.
|
|
106
|
+
*
|
|
107
|
+
* **Suggested action**: This is a normal condition - no action required, but you may want
|
|
108
|
+
* to inform the user that the operation was cancelled.
|
|
109
|
+
*/
|
|
110
|
+
OPERATION_CANCELLED: "OPERATION_CANCELLED",
|
|
111
|
+
/**
|
|
112
|
+
* Operation timed out before completion.
|
|
113
|
+
*
|
|
114
|
+
* This error occurs when an operation waits for a condition (for example, prompt
|
|
115
|
+
* detection) and the timeout period elapses first.
|
|
116
|
+
*/
|
|
117
|
+
OPERATION_TIMEOUT: "OPERATION_TIMEOUT",
|
|
118
|
+
/**
|
|
119
|
+
* Internal line buffer exceeded its configured size limit.
|
|
120
|
+
*
|
|
121
|
+
* This error occurs when the incomplete line tail held by the {@link SerialSession.lines$}
|
|
122
|
+
* framing buffer grows beyond {@link SerialSessionOptions.lineBuffer} `maxChars`.
|
|
123
|
+
* Leading characters are discarded to bound memory; the session remains connected.
|
|
124
|
+
*
|
|
125
|
+
* **Suggested action**: Increase `lineBuffer.maxChars`, handle framing on `receive$`,
|
|
126
|
+
* or ensure the device sends line terminators.
|
|
127
|
+
*/
|
|
128
|
+
LINE_BUFFER_OVERFLOW: "LINE_BUFFER_OVERFLOW",
|
|
129
|
+
/**
|
|
130
|
+
* Invalid receive replay options provided.
|
|
131
|
+
*
|
|
132
|
+
* This error occurs when {@link SerialSessionOptions.receiveReplay} values are
|
|
133
|
+
* out of range, such as a non-integer `bufferSize` or `maxChars`.
|
|
134
|
+
*
|
|
135
|
+
* **Suggested action**: Verify `receiveReplay` options match the documented
|
|
136
|
+
* ranges and value types.
|
|
137
|
+
*/
|
|
138
|
+
INVALID_RECEIVE_REPLAY_OPTIONS: "INVALID_RECEIVE_REPLAY_OPTIONS",
|
|
139
|
+
/**
|
|
140
|
+
* Invalid terminal buffer options provided.
|
|
141
|
+
*
|
|
142
|
+
* This error occurs when {@link SerialSessionOptions.terminalBuffer} values are
|
|
143
|
+
* out of range, such as a negative or non-integer `maxLines` or `maxChars`.
|
|
144
|
+
*
|
|
145
|
+
* **Suggested action**: Verify `terminalBuffer` options match the documented
|
|
146
|
+
* ranges and value types.
|
|
147
|
+
*/
|
|
148
|
+
INVALID_TERMINAL_BUFFER_OPTIONS: "INVALID_TERMINAL_BUFFER_OPTIONS",
|
|
149
|
+
/**
|
|
150
|
+
* Invalid line buffer options provided.
|
|
151
|
+
*
|
|
152
|
+
* This error occurs when {@link SerialSessionOptions.lineBuffer} values are
|
|
153
|
+
* out of range, such as a negative or non-integer `maxChars`.
|
|
154
|
+
*
|
|
155
|
+
* **Suggested action**: Verify `lineBuffer` options match the documented
|
|
156
|
+
* ranges and value types.
|
|
157
|
+
*/
|
|
158
|
+
INVALID_LINE_BUFFER_OPTIONS: "INVALID_LINE_BUFFER_OPTIONS",
|
|
159
|
+
/**
|
|
160
|
+
* Invalid connection options provided.
|
|
161
|
+
*
|
|
162
|
+
* This error occurs when connection fields such as `baudRate` are out of
|
|
163
|
+
* range at session creation time.
|
|
164
|
+
*
|
|
165
|
+
* **Suggested action**: Verify connection options match the documented
|
|
166
|
+
* ranges and value types.
|
|
167
|
+
*/
|
|
168
|
+
INVALID_CONNECTION_OPTIONS: "INVALID_CONNECTION_OPTIONS",
|
|
169
|
+
/**
|
|
170
|
+
* Receive replay buffer exceeded its configured character limit.
|
|
171
|
+
*
|
|
172
|
+
* This error occurs when buffered chunks on {@link SerialSession.receiveReplay$}
|
|
173
|
+
* exceed {@link SerialSessionOptions.receiveReplay} `maxChars`. Oldest chunks
|
|
174
|
+
* are discarded to bound memory; the session remains connected.
|
|
175
|
+
*
|
|
176
|
+
* **Suggested action**: Increase `receiveReplay.maxChars`, reduce chunk size at
|
|
177
|
+
* the source, or subscribe earlier to avoid relying on a large replay buffer.
|
|
178
|
+
*/
|
|
179
|
+
RECEIVE_REPLAY_BUFFER_OVERFLOW: "RECEIVE_REPLAY_BUFFER_OVERFLOW",
|
|
180
|
+
/**
|
|
181
|
+
* Session has been disposed and can no longer be used.
|
|
182
|
+
*
|
|
183
|
+
* This error occurs when calling {@link SerialSession.connect$} or
|
|
184
|
+
* {@link SerialSession.send$} after {@link SerialSession.dispose$} has
|
|
185
|
+
* completed. Create a new session instead of reusing a disposed instance.
|
|
186
|
+
*
|
|
187
|
+
* **Suggested action**: Call {@link SerialSession.dispose$} only when
|
|
188
|
+
* permanently tearing down a session, then create a new one if needed.
|
|
189
|
+
*/
|
|
190
|
+
SESSION_DISPOSED: "SESSION_DISPOSED",
|
|
191
|
+
/**
|
|
192
|
+
* Unknown error occurred.
|
|
193
|
+
*
|
|
194
|
+
* This error code is used for errors that don't fit into any other category.
|
|
195
|
+
* The original error details may be available in the error's message or originalError property.
|
|
196
|
+
*
|
|
197
|
+
* **Suggested action**: Check the error message and originalError for more details.
|
|
198
|
+
*/
|
|
199
|
+
UNKNOWN: "UNKNOWN"
|
|
70
200
|
};
|
|
71
201
|
|
|
72
202
|
// src/terminal/create-terminal-buffer.ts
|
|
73
203
|
import { map, scan, shareReplay } from "rxjs";
|
|
74
204
|
|
|
205
|
+
// src/internal/branded-numbers.ts
|
|
206
|
+
function brandBaudRate(value) {
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
function brandSerialPortBufferSize(value) {
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
212
|
+
function brandReceiveReplayBufferSize(value) {
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
215
|
+
function brandMaxChars(value) {
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
function brandMaxLines(value) {
|
|
219
|
+
return value;
|
|
220
|
+
}
|
|
221
|
+
|
|
75
222
|
// src/internal/newline-tokenizer.ts
|
|
76
223
|
function createNewlineTokenizer(mode) {
|
|
77
224
|
let pending = "";
|
|
@@ -229,9 +376,11 @@ var DEFAULT_TERMINAL_BUFFER_OPTIONS = {
|
|
|
229
376
|
maxChars: 1048576
|
|
230
377
|
};
|
|
231
378
|
function resolveTerminalBufferLimits(options) {
|
|
379
|
+
const maxLines = options?.maxLines ?? DEFAULT_TERMINAL_BUFFER_OPTIONS.maxLines;
|
|
380
|
+
const maxChars = options?.maxChars ?? DEFAULT_TERMINAL_BUFFER_OPTIONS.maxChars;
|
|
232
381
|
return {
|
|
233
|
-
|
|
234
|
-
|
|
382
|
+
maxLines: brandMaxLines(maxLines),
|
|
383
|
+
maxChars: brandMaxChars(maxChars)
|
|
235
384
|
};
|
|
236
385
|
}
|
|
237
386
|
var initialTerminalState = {
|
|
@@ -251,53 +400,96 @@ function createTerminalBuffer(receive$, options) {
|
|
|
251
400
|
return { text$ };
|
|
252
401
|
}
|
|
253
402
|
|
|
254
|
-
// src/session/internal/build-request-options.ts
|
|
255
|
-
function buildRequestOptions(options) {
|
|
256
|
-
if (!options || !options.filters || options.filters.length === 0) {
|
|
257
|
-
return void 0;
|
|
258
|
-
}
|
|
259
|
-
for (const filter of options.filters) {
|
|
260
|
-
if (!filter.usbVendorId && !filter.usbProductId) {
|
|
261
|
-
throw new SerialError(
|
|
262
|
-
"INVALID_FILTER_OPTIONS" /* INVALID_FILTER_OPTIONS */,
|
|
263
|
-
"Filter must have at least usbVendorId or usbProductId"
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
if (filter.usbVendorId !== void 0) {
|
|
267
|
-
if (!Number.isInteger(filter.usbVendorId) || filter.usbVendorId < 0 || filter.usbVendorId > 65535) {
|
|
268
|
-
throw new SerialError(
|
|
269
|
-
"INVALID_FILTER_OPTIONS" /* INVALID_FILTER_OPTIONS */,
|
|
270
|
-
`Invalid usbVendorId: ${filter.usbVendorId}. Must be an integer between 0 and 65535.`
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
if (filter.usbProductId !== void 0) {
|
|
275
|
-
if (!Number.isInteger(filter.usbProductId) || filter.usbProductId < 0 || filter.usbProductId > 65535) {
|
|
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
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return {
|
|
284
|
-
filters: options.filters
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
403
|
// src/session/internal/has-web-serial-support.ts
|
|
289
404
|
function hasWebSerialSupport() {
|
|
290
405
|
return typeof navigator !== "undefined" && "serial" in navigator && navigator.serial !== void 0 && navigator.serial !== null;
|
|
291
406
|
}
|
|
292
407
|
|
|
408
|
+
// src/session/internal/receive-pipeline.ts
|
|
409
|
+
import {
|
|
410
|
+
BehaviorSubject,
|
|
411
|
+
share,
|
|
412
|
+
Subject as Subject2,
|
|
413
|
+
switchMap
|
|
414
|
+
} from "rxjs";
|
|
415
|
+
|
|
416
|
+
// src/errors/serial-error-context.ts
|
|
417
|
+
var CAUSE_CONTEXT_CODES = /* @__PURE__ */ new Set([
|
|
418
|
+
SerialErrorCode.PORT_NOT_AVAILABLE,
|
|
419
|
+
SerialErrorCode.PORT_OPEN_FAILED,
|
|
420
|
+
SerialErrorCode.READ_FAILED,
|
|
421
|
+
SerialErrorCode.WRITE_FAILED,
|
|
422
|
+
SerialErrorCode.CONNECTION_LOST,
|
|
423
|
+
SerialErrorCode.OPERATION_CANCELLED,
|
|
424
|
+
SerialErrorCode.UNKNOWN
|
|
425
|
+
]);
|
|
426
|
+
function isCauseContextCode(code) {
|
|
427
|
+
return CAUSE_CONTEXT_CODES.has(code);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/errors/serial-error.ts
|
|
431
|
+
var ErrorWithCaptureStackTrace = Error;
|
|
432
|
+
var SerialError = class _SerialError extends Error {
|
|
433
|
+
/**
|
|
434
|
+
* Creates a new SerialError instance.
|
|
435
|
+
*
|
|
436
|
+
* @param code - The error code identifying the type of error
|
|
437
|
+
* @param message - A human-readable error message
|
|
438
|
+
* @param originalError - The original error that caused this SerialError, if any
|
|
439
|
+
* @param context - Structured metadata for the error code. When omitted, cause-bearing
|
|
440
|
+
* codes derive `{ cause }` from `originalError`.
|
|
441
|
+
*/
|
|
442
|
+
constructor(code, message, originalError, context) {
|
|
443
|
+
super(message);
|
|
444
|
+
this.name = "SerialError";
|
|
445
|
+
this.code = code;
|
|
446
|
+
if (originalError !== void 0) {
|
|
447
|
+
this.originalError = originalError;
|
|
448
|
+
}
|
|
449
|
+
if (context !== void 0) {
|
|
450
|
+
this.context = context;
|
|
451
|
+
} else if (originalError !== void 0 && isCauseContextCode(code)) {
|
|
452
|
+
this.context = { cause: originalError };
|
|
453
|
+
} else {
|
|
454
|
+
this.context = void 0;
|
|
455
|
+
}
|
|
456
|
+
if (ErrorWithCaptureStackTrace.captureStackTrace) {
|
|
457
|
+
ErrorWithCaptureStackTrace.captureStackTrace(this, _SerialError);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Check if the error matches a specific error code.
|
|
462
|
+
*
|
|
463
|
+
* This is a convenience method for checking the error code without directly
|
|
464
|
+
* comparing the code property. When this method returns `true`, TypeScript
|
|
465
|
+
* narrows `this.code` and `this.context` to the shapes defined for the
|
|
466
|
+
* provided `code` argument.
|
|
467
|
+
*
|
|
468
|
+
* @param code - The error code to check against
|
|
469
|
+
* @returns Type predicate: `true` if this error's code matches the provided
|
|
470
|
+
* code (and `this.code` / `this.context` are narrowed), `false` otherwise
|
|
471
|
+
*
|
|
472
|
+
* @example
|
|
473
|
+
* ```typescript
|
|
474
|
+
* if (error.is(SerialErrorCode.LINE_BUFFER_OVERFLOW)) {
|
|
475
|
+
* // error.code and error.context.maxChars are narrowed
|
|
476
|
+
* }
|
|
477
|
+
* ```
|
|
478
|
+
*/
|
|
479
|
+
is(code) {
|
|
480
|
+
return this.code === code;
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
|
|
293
484
|
// src/session/internal/line-buffer.ts
|
|
294
485
|
var DEFAULT_LINE_BUFFER_OPTIONS = {
|
|
295
486
|
maxChars: 1048576
|
|
296
487
|
};
|
|
297
488
|
function createLineBuffer(options) {
|
|
298
489
|
const limits = {
|
|
299
|
-
|
|
300
|
-
|
|
490
|
+
maxChars: brandMaxChars(
|
|
491
|
+
options?.maxChars ?? DEFAULT_LINE_BUFFER_OPTIONS.maxChars
|
|
492
|
+
)
|
|
301
493
|
};
|
|
302
494
|
const tokenizer = createNewlineTokenizer("line");
|
|
303
495
|
const clear = () => {
|
|
@@ -371,6 +563,119 @@ function createReceiveReplayBuffer(options) {
|
|
|
371
563
|
return { next, asObservable, complete };
|
|
372
564
|
}
|
|
373
565
|
|
|
566
|
+
// src/session/internal/receive-pipeline.ts
|
|
567
|
+
function createReceivePipeline(deps) {
|
|
568
|
+
const { resolvedOptions, reportError } = deps;
|
|
569
|
+
const receiveSubject = new Subject2();
|
|
570
|
+
const linesSubject = new Subject2();
|
|
571
|
+
const lineBuffer = createLineBuffer(resolvedOptions.lineBuffer);
|
|
572
|
+
const receive$ = receiveSubject.asObservable();
|
|
573
|
+
const lines$ = linesSubject.asObservable();
|
|
574
|
+
const receiveReplayStream$ = resolvedOptions.receiveReplay.enabled ? new BehaviorSubject(receive$) : null;
|
|
575
|
+
let activeReceiveReplay = null;
|
|
576
|
+
const clearReplay = () => {
|
|
577
|
+
if (receiveReplayStream$) {
|
|
578
|
+
if (activeReceiveReplay) {
|
|
579
|
+
activeReceiveReplay.complete();
|
|
580
|
+
activeReceiveReplay = null;
|
|
581
|
+
}
|
|
582
|
+
receiveReplayStream$.next(receive$);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
const startLiveReceiveReplay = () => {
|
|
586
|
+
if (!receiveReplayStream$) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
if (activeReceiveReplay) {
|
|
590
|
+
activeReceiveReplay.complete();
|
|
591
|
+
activeReceiveReplay = null;
|
|
592
|
+
}
|
|
593
|
+
const buffer = createReceiveReplayBuffer({
|
|
594
|
+
bufferSize: resolvedOptions.receiveReplay.bufferSize,
|
|
595
|
+
maxChars: resolvedOptions.receiveReplay.maxChars
|
|
596
|
+
});
|
|
597
|
+
activeReceiveReplay = buffer;
|
|
598
|
+
receiveReplayStream$.next(buffer.asObservable());
|
|
599
|
+
};
|
|
600
|
+
const receiveReplay$ = receiveReplayStream$ ? receiveReplayStream$.pipe(switchMap((inner) => inner), share()) : receive$;
|
|
601
|
+
const clearLineBuffer = () => {
|
|
602
|
+
lineBuffer.clear();
|
|
603
|
+
};
|
|
604
|
+
const handleChunk = (text) => {
|
|
605
|
+
receiveSubject.next(text);
|
|
606
|
+
if (activeReceiveReplay) {
|
|
607
|
+
const { overflowed: overflowed2 } = activeReceiveReplay.next(text);
|
|
608
|
+
if (overflowed2) {
|
|
609
|
+
reportError(
|
|
610
|
+
new SerialError(
|
|
611
|
+
SerialErrorCode.RECEIVE_REPLAY_BUFFER_OVERFLOW,
|
|
612
|
+
"Receive replay buffer exceeded configured limits; oldest chunks were discarded",
|
|
613
|
+
void 0,
|
|
614
|
+
{
|
|
615
|
+
maxChars: resolvedOptions.receiveReplay.maxChars,
|
|
616
|
+
bufferSize: resolvedOptions.receiveReplay.bufferSize
|
|
617
|
+
}
|
|
618
|
+
),
|
|
619
|
+
{
|
|
620
|
+
fallbackCode: SerialErrorCode.RECEIVE_REPLAY_BUFFER_OVERFLOW
|
|
621
|
+
}
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const { lines, overflowed } = lineBuffer.feed(text);
|
|
626
|
+
if (overflowed) {
|
|
627
|
+
reportError(
|
|
628
|
+
new SerialError(
|
|
629
|
+
SerialErrorCode.LINE_BUFFER_OVERFLOW,
|
|
630
|
+
"Line buffer exceeded maxChars; leading data was discarded",
|
|
631
|
+
void 0,
|
|
632
|
+
{ maxChars: resolvedOptions.lineBuffer.maxChars }
|
|
633
|
+
),
|
|
634
|
+
{ fallbackCode: SerialErrorCode.LINE_BUFFER_OVERFLOW }
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
for (const line of lines) {
|
|
638
|
+
linesSubject.next(line);
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
const complete = () => {
|
|
642
|
+
receiveSubject.complete();
|
|
643
|
+
linesSubject.complete();
|
|
644
|
+
receiveReplayStream$?.complete();
|
|
645
|
+
};
|
|
646
|
+
return {
|
|
647
|
+
receive$,
|
|
648
|
+
lines$,
|
|
649
|
+
receiveReplay$,
|
|
650
|
+
clearReplay,
|
|
651
|
+
startLiveReceiveReplay,
|
|
652
|
+
clearLineBuffer,
|
|
653
|
+
handleChunk,
|
|
654
|
+
complete
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/session/internal/error-severity.ts
|
|
659
|
+
var ERROR_SEVERITY = {
|
|
660
|
+
[SerialErrorCode.READ_FAILED]: "fatal",
|
|
661
|
+
[SerialErrorCode.CONNECTION_LOST]: "fatal",
|
|
662
|
+
[SerialErrorCode.PORT_OPEN_FAILED]: "fatal",
|
|
663
|
+
[SerialErrorCode.OPERATION_CANCELLED]: "fatal",
|
|
664
|
+
[SerialErrorCode.UNKNOWN]: "fatal",
|
|
665
|
+
[SerialErrorCode.LINE_BUFFER_OVERFLOW]: "non-fatal",
|
|
666
|
+
[SerialErrorCode.RECEIVE_REPLAY_BUFFER_OVERFLOW]: "non-fatal",
|
|
667
|
+
[SerialErrorCode.BROWSER_NOT_SUPPORTED]: "non-fatal",
|
|
668
|
+
[SerialErrorCode.PORT_ALREADY_OPEN]: "non-fatal",
|
|
669
|
+
[SerialErrorCode.PORT_NOT_OPEN]: "non-fatal",
|
|
670
|
+
[SerialErrorCode.WRITE_FAILED]: "non-fatal"
|
|
671
|
+
};
|
|
672
|
+
function resolveErrorSeverity(code) {
|
|
673
|
+
if (code in ERROR_SEVERITY) {
|
|
674
|
+
return ERROR_SEVERITY[code];
|
|
675
|
+
}
|
|
676
|
+
return "fatal";
|
|
677
|
+
}
|
|
678
|
+
|
|
374
679
|
// src/session/normalize-serial-error.ts
|
|
375
680
|
var DEFAULT_MESSAGE_PREFIX = "Serial operation failed";
|
|
376
681
|
var isDomExceptionWithName = (error, name) => typeof DOMException !== "undefined" && error instanceof DOMException && error.name === name;
|
|
@@ -381,7 +686,7 @@ function normalizeSerialError(error, options) {
|
|
|
381
686
|
const prefix = options.messagePrefix ?? DEFAULT_MESSAGE_PREFIX;
|
|
382
687
|
if (isDomExceptionWithName(error, "NotFoundError")) {
|
|
383
688
|
return new SerialError(
|
|
384
|
-
|
|
689
|
+
SerialErrorCode.OPERATION_CANCELLED,
|
|
385
690
|
"Port selection was cancelled by the user",
|
|
386
691
|
error
|
|
387
692
|
);
|
|
@@ -394,29 +699,216 @@ function normalizeSerialError(error, options) {
|
|
|
394
699
|
);
|
|
395
700
|
}
|
|
396
701
|
|
|
397
|
-
// src/session/
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
702
|
+
// src/session/session-runtime.ts
|
|
703
|
+
import { BehaviorSubject as BehaviorSubject2 } from "rxjs";
|
|
704
|
+
|
|
705
|
+
// src/session/serial-session-state.ts
|
|
706
|
+
var SerialSessionStatus = {
|
|
707
|
+
Idle: "idle",
|
|
708
|
+
Connecting: "connecting",
|
|
709
|
+
Connected: "connected",
|
|
710
|
+
Disconnecting: "disconnecting",
|
|
711
|
+
Unsupported: "unsupported",
|
|
712
|
+
Error: "error",
|
|
713
|
+
Disposed: "disposed"
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
// src/session/session-runtime.ts
|
|
717
|
+
var S = SerialSessionStatus;
|
|
718
|
+
var ALLOWED_TRANSITIONS = {
|
|
719
|
+
[S.Idle]: [S.Connecting, S.Error, S.Disposed],
|
|
720
|
+
[S.Connecting]: [S.Connected, S.Error, S.Idle, S.Disposed],
|
|
721
|
+
[S.Connected]: [S.Disconnecting, S.Error, S.Disposed],
|
|
722
|
+
[S.Disconnecting]: [S.Idle, S.Error, S.Disposed],
|
|
723
|
+
[S.Error]: [S.Idle, S.Connecting, S.Disposed],
|
|
724
|
+
[S.Unsupported]: [S.Disposed],
|
|
725
|
+
[S.Disposed]: []
|
|
726
|
+
};
|
|
727
|
+
function runtimeToSessionStatus(runtime) {
|
|
728
|
+
return runtime.status;
|
|
729
|
+
}
|
|
730
|
+
function runtimeToPublicState(runtime) {
|
|
731
|
+
switch (runtime.status) {
|
|
732
|
+
case S.Idle:
|
|
733
|
+
return { status: S.Idle };
|
|
734
|
+
case S.Connecting:
|
|
735
|
+
return { status: S.Connecting };
|
|
736
|
+
case S.Connected:
|
|
737
|
+
return { status: S.Connected, portInfo: runtime.port.getInfo() };
|
|
738
|
+
case S.Disconnecting:
|
|
739
|
+
return { status: S.Disconnecting };
|
|
740
|
+
case S.Unsupported:
|
|
741
|
+
return { status: S.Unsupported };
|
|
742
|
+
case S.Error:
|
|
743
|
+
return { status: S.Error, error: runtime.error };
|
|
744
|
+
case S.Disposed:
|
|
745
|
+
return { status: S.Disposed };
|
|
746
|
+
default:
|
|
747
|
+
return assertNeverRuntime(runtime);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
function isValidTransition(from, to) {
|
|
751
|
+
if (from === to) {
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
return ALLOWED_TRANSITIONS[from].includes(
|
|
755
|
+
to
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
function createIdleRuntime() {
|
|
759
|
+
return { status: S.Idle };
|
|
760
|
+
}
|
|
761
|
+
function createConnectingRuntime(cancel) {
|
|
762
|
+
return { status: S.Connecting, cancel };
|
|
763
|
+
}
|
|
764
|
+
function createConnectedRuntime(port, pump) {
|
|
765
|
+
return { status: S.Connected, port, pump };
|
|
766
|
+
}
|
|
767
|
+
function createDisconnectingRuntime(port) {
|
|
768
|
+
return { status: S.Disconnecting, port };
|
|
769
|
+
}
|
|
770
|
+
function createErrorRuntime(error) {
|
|
771
|
+
return { status: S.Error, error };
|
|
772
|
+
}
|
|
773
|
+
function createUnsupportedRuntime() {
|
|
774
|
+
return { status: S.Unsupported };
|
|
775
|
+
}
|
|
776
|
+
function createDisposedRuntime() {
|
|
777
|
+
return { status: S.Disposed };
|
|
778
|
+
}
|
|
779
|
+
function createInitialRuntime(supported) {
|
|
780
|
+
return supported ? createIdleRuntime() : createUnsupportedRuntime();
|
|
781
|
+
}
|
|
782
|
+
function getRuntimePort(runtime) {
|
|
783
|
+
switch (runtime.status) {
|
|
784
|
+
case S.Connected:
|
|
785
|
+
return runtime.port;
|
|
786
|
+
case S.Disconnecting:
|
|
787
|
+
return runtime.port;
|
|
788
|
+
default:
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
function getRuntimePump(runtime) {
|
|
793
|
+
if (runtime.status === S.Connected) {
|
|
794
|
+
return runtime.pump;
|
|
795
|
+
}
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
function createSessionRuntimeController(initial) {
|
|
799
|
+
let runtime = initial;
|
|
800
|
+
const subject = new BehaviorSubject2(
|
|
801
|
+
runtimeToPublicState(initial)
|
|
802
|
+
);
|
|
803
|
+
const transition = (next) => {
|
|
804
|
+
const from = runtimeToSessionStatus(runtime);
|
|
805
|
+
const to = runtimeToSessionStatus(next);
|
|
806
|
+
if (from === to) {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
if (!isValidTransition(from, to)) {
|
|
810
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
811
|
+
console.warn(
|
|
812
|
+
`[web-serial-rxjs] Ignoring invalid SerialSession transition ${from} -> ${to}`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
runtime = next;
|
|
818
|
+
subject.next(runtimeToPublicState(next));
|
|
819
|
+
return true;
|
|
820
|
+
};
|
|
821
|
+
return {
|
|
822
|
+
get runtime() {
|
|
823
|
+
return runtime;
|
|
824
|
+
},
|
|
825
|
+
get status() {
|
|
826
|
+
return runtimeToSessionStatus(runtime);
|
|
827
|
+
},
|
|
828
|
+
get state$() {
|
|
829
|
+
return subject.asObservable();
|
|
830
|
+
},
|
|
831
|
+
transition,
|
|
832
|
+
complete() {
|
|
833
|
+
subject.complete();
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function assertNeverRuntime(value) {
|
|
838
|
+
return assertNever(value);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/session/internal/session-error-reporter.ts
|
|
842
|
+
function createSessionErrorReporter(deps) {
|
|
843
|
+
const {
|
|
844
|
+
controller,
|
|
845
|
+
errorsSubject,
|
|
846
|
+
sendQueue,
|
|
847
|
+
isDisposed,
|
|
848
|
+
updatePortInfo,
|
|
849
|
+
teardownPump,
|
|
850
|
+
closePortSafely
|
|
851
|
+
} = deps;
|
|
852
|
+
const createDisposedError = () => new SerialError(
|
|
853
|
+
SerialErrorCode.SESSION_DISPOSED,
|
|
854
|
+
"SerialSession has been disposed"
|
|
855
|
+
);
|
|
856
|
+
const reportError = (error, options) => {
|
|
857
|
+
const serialError = normalizeSerialError(error, options);
|
|
858
|
+
if (isDisposed()) {
|
|
859
|
+
return serialError;
|
|
860
|
+
}
|
|
861
|
+
errorsSubject.next(serialError);
|
|
862
|
+
if (resolveErrorSeverity(serialError.code) === "fatal") {
|
|
863
|
+
const runtime = controller.runtime;
|
|
864
|
+
const portToClose = getRuntimePort(runtime);
|
|
865
|
+
const pump = getRuntimePump(runtime);
|
|
866
|
+
controller.transition(createErrorRuntime(serialError));
|
|
867
|
+
sendQueue.clear();
|
|
868
|
+
updatePortInfo(null);
|
|
869
|
+
void teardownPump(pump).then(() => closePortSafely(portToClose));
|
|
870
|
+
}
|
|
871
|
+
return serialError;
|
|
872
|
+
};
|
|
873
|
+
return { reportError, createDisposedError };
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// src/session/internal/session-lifecycle.ts
|
|
877
|
+
import { Observable as Observable3 } from "rxjs";
|
|
878
|
+
|
|
879
|
+
// src/session/internal/build-request-options.ts
|
|
880
|
+
function buildRequestOptions(options) {
|
|
881
|
+
if (!options?.filters || options.filters.length === 0) {
|
|
882
|
+
return void 0;
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
filters: options.filters
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/session/read-pump.ts
|
|
890
|
+
function createReadPump(port, { onChunk, onError, onDone }) {
|
|
891
|
+
let reader = null;
|
|
892
|
+
let running = false;
|
|
893
|
+
let stopped = false;
|
|
894
|
+
const decoder = new TextDecoder(void 0, { fatal: false });
|
|
895
|
+
const releaseReader = () => {
|
|
896
|
+
if (!reader) {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
try {
|
|
900
|
+
reader.releaseLock();
|
|
901
|
+
} catch {
|
|
902
|
+
}
|
|
903
|
+
reader = null;
|
|
904
|
+
};
|
|
905
|
+
const pump = async (stream) => {
|
|
906
|
+
reader = stream.getReader();
|
|
907
|
+
running = true;
|
|
908
|
+
try {
|
|
909
|
+
while (!stopped) {
|
|
910
|
+
const { done, value } = await reader.read();
|
|
911
|
+
if (done) {
|
|
420
912
|
if (!stopped) {
|
|
421
913
|
onDone?.();
|
|
422
914
|
}
|
|
@@ -439,7 +931,7 @@ function createReadPump(port, { onChunk, onError, onDone }) {
|
|
|
439
931
|
if (!stopped) {
|
|
440
932
|
onError(
|
|
441
933
|
normalizeSerialError(error, {
|
|
442
|
-
fallbackCode:
|
|
934
|
+
fallbackCode: SerialErrorCode.READ_FAILED,
|
|
443
935
|
messagePrefix: "Read pump failed"
|
|
444
936
|
})
|
|
445
937
|
);
|
|
@@ -459,7 +951,7 @@ function createReadPump(port, { onChunk, onError, onDone }) {
|
|
|
459
951
|
stopped = true;
|
|
460
952
|
onError(
|
|
461
953
|
new SerialError(
|
|
462
|
-
|
|
954
|
+
SerialErrorCode.CONNECTION_LOST,
|
|
463
955
|
"Read pump failed: port.readable is not available"
|
|
464
956
|
)
|
|
465
957
|
);
|
|
@@ -488,14 +980,293 @@ function createReadPump(port, { onChunk, onError, onDone }) {
|
|
|
488
980
|
};
|
|
489
981
|
}
|
|
490
982
|
|
|
983
|
+
// src/session/internal/session-lifecycle.ts
|
|
984
|
+
function createSessionLifecycle(deps) {
|
|
985
|
+
const {
|
|
986
|
+
controller,
|
|
987
|
+
resolvedOptions,
|
|
988
|
+
sendQueue,
|
|
989
|
+
receivePipeline,
|
|
990
|
+
portInfoSubject,
|
|
991
|
+
errorsSubject,
|
|
992
|
+
isDisposed,
|
|
993
|
+
reportError,
|
|
994
|
+
createDisposedError
|
|
995
|
+
} = deps;
|
|
996
|
+
const updatePortInfo = (port) => {
|
|
997
|
+
portInfoSubject.next(port ? port.getInfo() : null);
|
|
998
|
+
};
|
|
999
|
+
const teardownPump = async (pump) => {
|
|
1000
|
+
receivePipeline.clearReplay();
|
|
1001
|
+
receivePipeline.clearLineBuffer();
|
|
1002
|
+
if (pump) {
|
|
1003
|
+
await pump.stop();
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
const closePortSafely = async (port) => {
|
|
1007
|
+
if (!port) {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
try {
|
|
1011
|
+
await port.close();
|
|
1012
|
+
} catch {
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
const teardownFromSnapshot = async (snapshot) => {
|
|
1016
|
+
if (snapshot.status === SerialSessionStatus.Connecting) {
|
|
1017
|
+
snapshot.cancel();
|
|
1018
|
+
}
|
|
1019
|
+
sendQueue.clear();
|
|
1020
|
+
if (snapshot.status === SerialSessionStatus.Connected || snapshot.status === SerialSessionStatus.Disconnecting || snapshot.status === SerialSessionStatus.Error) {
|
|
1021
|
+
const portToClose = getRuntimePort(snapshot);
|
|
1022
|
+
const pump = getRuntimePump(snapshot);
|
|
1023
|
+
await teardownPump(pump);
|
|
1024
|
+
await closePortSafely(portToClose);
|
|
1025
|
+
updatePortInfo(null);
|
|
1026
|
+
}
|
|
1027
|
+
receivePipeline.clearLineBuffer();
|
|
1028
|
+
};
|
|
1029
|
+
const completeSubjects = () => {
|
|
1030
|
+
controller.complete();
|
|
1031
|
+
errorsSubject.complete();
|
|
1032
|
+
receivePipeline.complete();
|
|
1033
|
+
portInfoSubject.complete();
|
|
1034
|
+
};
|
|
1035
|
+
const dispose$ = () => new Observable3((subscriber) => {
|
|
1036
|
+
if (isDisposed()) {
|
|
1037
|
+
subscriber.next();
|
|
1038
|
+
subscriber.complete();
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const snapshot = controller.runtime;
|
|
1042
|
+
controller.transition(createDisposedRuntime());
|
|
1043
|
+
const run = async () => {
|
|
1044
|
+
try {
|
|
1045
|
+
await teardownFromSnapshot(snapshot);
|
|
1046
|
+
completeSubjects();
|
|
1047
|
+
subscriber.next();
|
|
1048
|
+
subscriber.complete();
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
const serialError = normalizeSerialError(error, {
|
|
1051
|
+
fallbackCode: SerialErrorCode.UNKNOWN,
|
|
1052
|
+
messagePrefix: "Unexpected dispose failure"
|
|
1053
|
+
});
|
|
1054
|
+
subscriber.error(serialError);
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
void run();
|
|
1058
|
+
});
|
|
1059
|
+
const writeToPort = async (payload) => {
|
|
1060
|
+
const runtime = controller.runtime;
|
|
1061
|
+
if (runtime.status !== SerialSessionStatus.Connected || !runtime.port.writable) {
|
|
1062
|
+
throw new SerialError(
|
|
1063
|
+
SerialErrorCode.PORT_NOT_OPEN,
|
|
1064
|
+
"Cannot send data while session is not connected"
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
const writer = runtime.port.writable.getWriter();
|
|
1068
|
+
try {
|
|
1069
|
+
await writer.write(payload);
|
|
1070
|
+
} finally {
|
|
1071
|
+
try {
|
|
1072
|
+
writer.releaseLock();
|
|
1073
|
+
} catch {
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
const connect$ = () => new Observable3((subscriber) => {
|
|
1078
|
+
if (isDisposed()) {
|
|
1079
|
+
subscriber.error(createDisposedError());
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
if (!hasWebSerialSupport()) {
|
|
1083
|
+
const error = reportError(
|
|
1084
|
+
new SerialError(
|
|
1085
|
+
SerialErrorCode.BROWSER_NOT_SUPPORTED,
|
|
1086
|
+
"Web Serial API is not supported in this environment"
|
|
1087
|
+
),
|
|
1088
|
+
{ fallbackCode: SerialErrorCode.BROWSER_NOT_SUPPORTED }
|
|
1089
|
+
);
|
|
1090
|
+
subscriber.error(error);
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
const current = controller.status;
|
|
1094
|
+
if (current !== SerialSessionStatus.Idle && current !== SerialSessionStatus.Error) {
|
|
1095
|
+
const error = reportError(
|
|
1096
|
+
new SerialError(
|
|
1097
|
+
SerialErrorCode.PORT_ALREADY_OPEN,
|
|
1098
|
+
`Cannot connect while session state is '${current}'`
|
|
1099
|
+
),
|
|
1100
|
+
{ fallbackCode: SerialErrorCode.PORT_ALREADY_OPEN }
|
|
1101
|
+
);
|
|
1102
|
+
subscriber.error(error);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
let cancelled = false;
|
|
1106
|
+
const cancelInFlightConnect = () => {
|
|
1107
|
+
cancelled = true;
|
|
1108
|
+
if (controller.status === SerialSessionStatus.Connecting) {
|
|
1109
|
+
controller.transition(createIdleRuntime());
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
controller.transition(createConnectingRuntime(cancelInFlightConnect));
|
|
1113
|
+
const run = async () => {
|
|
1114
|
+
let selectedPort = null;
|
|
1115
|
+
try {
|
|
1116
|
+
selectedPort = await navigator.serial.requestPort(
|
|
1117
|
+
buildRequestOptions(resolvedOptions)
|
|
1118
|
+
);
|
|
1119
|
+
await selectedPort.open({
|
|
1120
|
+
baudRate: resolvedOptions.baudRate,
|
|
1121
|
+
dataBits: resolvedOptions.dataBits,
|
|
1122
|
+
stopBits: resolvedOptions.stopBits,
|
|
1123
|
+
parity: resolvedOptions.parity,
|
|
1124
|
+
bufferSize: resolvedOptions.bufferSize,
|
|
1125
|
+
flowControl: resolvedOptions.flowControl
|
|
1126
|
+
});
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
if (selectedPort) {
|
|
1129
|
+
await closePortSafely(selectedPort);
|
|
1130
|
+
}
|
|
1131
|
+
const serialError = reportError(error, {
|
|
1132
|
+
fallbackCode: SerialErrorCode.PORT_OPEN_FAILED,
|
|
1133
|
+
messagePrefix: "Failed to open port"
|
|
1134
|
+
});
|
|
1135
|
+
if (!cancelled) {
|
|
1136
|
+
subscriber.error(serialError);
|
|
1137
|
+
}
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
if (cancelled || isDisposed()) {
|
|
1141
|
+
await closePortSafely(selectedPort);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
receivePipeline.clearLineBuffer();
|
|
1145
|
+
if (resolvedOptions.receiveReplay.enabled) {
|
|
1146
|
+
receivePipeline.startLiveReceiveReplay();
|
|
1147
|
+
}
|
|
1148
|
+
const pump = createReadPump(selectedPort, {
|
|
1149
|
+
onChunk: receivePipeline.handleChunk,
|
|
1150
|
+
onError: (pumpError) => reportError(pumpError, {
|
|
1151
|
+
fallbackCode: SerialErrorCode.READ_FAILED,
|
|
1152
|
+
messagePrefix: "Read pump failed"
|
|
1153
|
+
}),
|
|
1154
|
+
onDone: () => {
|
|
1155
|
+
if (controller.status !== SerialSessionStatus.Connected) {
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
reportError(
|
|
1159
|
+
new SerialError(
|
|
1160
|
+
SerialErrorCode.CONNECTION_LOST,
|
|
1161
|
+
"Read pump ended unexpectedly: stream closed while connected"
|
|
1162
|
+
),
|
|
1163
|
+
{
|
|
1164
|
+
fallbackCode: SerialErrorCode.CONNECTION_LOST
|
|
1165
|
+
}
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
pump.start();
|
|
1170
|
+
sendQueue.clear();
|
|
1171
|
+
if (isDisposed()) {
|
|
1172
|
+
await teardownPump(pump);
|
|
1173
|
+
await closePortSafely(selectedPort);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
controller.transition(createConnectedRuntime(selectedPort, pump));
|
|
1177
|
+
updatePortInfo(selectedPort);
|
|
1178
|
+
subscriber.next();
|
|
1179
|
+
subscriber.complete();
|
|
1180
|
+
};
|
|
1181
|
+
void run();
|
|
1182
|
+
return () => {
|
|
1183
|
+
cancelInFlightConnect();
|
|
1184
|
+
};
|
|
1185
|
+
});
|
|
1186
|
+
const disconnect$ = () => new Observable3((subscriber) => {
|
|
1187
|
+
if (isDisposed()) {
|
|
1188
|
+
subscriber.next();
|
|
1189
|
+
subscriber.complete();
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
const runtime = controller.runtime;
|
|
1193
|
+
if (runtime.status === SerialSessionStatus.Idle || runtime.status === SerialSessionStatus.Unsupported || runtime.status === SerialSessionStatus.Disconnecting) {
|
|
1194
|
+
subscriber.next();
|
|
1195
|
+
subscriber.complete();
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
if (runtime.status === SerialSessionStatus.Connecting) {
|
|
1199
|
+
runtime.cancel();
|
|
1200
|
+
subscriber.next();
|
|
1201
|
+
subscriber.complete();
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
if (runtime.status !== SerialSessionStatus.Connected && runtime.status !== SerialSessionStatus.Error) {
|
|
1205
|
+
const error = reportError(
|
|
1206
|
+
new SerialError(
|
|
1207
|
+
SerialErrorCode.PORT_NOT_OPEN,
|
|
1208
|
+
`Cannot disconnect while session state is '${runtime.status}'`
|
|
1209
|
+
),
|
|
1210
|
+
{ fallbackCode: SerialErrorCode.PORT_NOT_OPEN }
|
|
1211
|
+
);
|
|
1212
|
+
subscriber.error(error);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
const portToClose = getRuntimePort(runtime);
|
|
1216
|
+
controller.transition(createDisconnectingRuntime(portToClose));
|
|
1217
|
+
sendQueue.clear();
|
|
1218
|
+
const run = async () => {
|
|
1219
|
+
try {
|
|
1220
|
+
const pump = getRuntimePump(controller.runtime);
|
|
1221
|
+
await teardownPump(pump);
|
|
1222
|
+
if (portToClose) {
|
|
1223
|
+
try {
|
|
1224
|
+
await portToClose.close();
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
updatePortInfo(null);
|
|
1227
|
+
const serialError = reportError(error, {
|
|
1228
|
+
fallbackCode: SerialErrorCode.CONNECTION_LOST,
|
|
1229
|
+
messagePrefix: "Failed to close port"
|
|
1230
|
+
});
|
|
1231
|
+
subscriber.error(serialError);
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
updatePortInfo(null);
|
|
1236
|
+
if (!isDisposed()) {
|
|
1237
|
+
controller.transition(createIdleRuntime());
|
|
1238
|
+
}
|
|
1239
|
+
subscriber.next();
|
|
1240
|
+
subscriber.complete();
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
const serialError = reportError(error, {
|
|
1243
|
+
fallbackCode: SerialErrorCode.UNKNOWN,
|
|
1244
|
+
messagePrefix: "Unexpected disconnect failure"
|
|
1245
|
+
});
|
|
1246
|
+
subscriber.error(serialError);
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
void run();
|
|
1250
|
+
});
|
|
1251
|
+
return {
|
|
1252
|
+
connect$,
|
|
1253
|
+
disconnect$,
|
|
1254
|
+
dispose$,
|
|
1255
|
+
writeToPort,
|
|
1256
|
+
teardownPump,
|
|
1257
|
+
closePortSafely,
|
|
1258
|
+
updatePortInfo
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
|
|
491
1262
|
// src/session/send-queue.ts
|
|
492
|
-
import { Observable as
|
|
1263
|
+
import { Observable as Observable4, defer } from "rxjs";
|
|
493
1264
|
function createSendQueue() {
|
|
494
1265
|
let chain = Promise.resolve();
|
|
495
1266
|
return {
|
|
496
1267
|
enqueue(operation) {
|
|
497
1268
|
return defer(
|
|
498
|
-
() => new
|
|
1269
|
+
() => new Observable4((subscriber) => {
|
|
499
1270
|
let cancelled = false;
|
|
500
1271
|
const run = async () => {
|
|
501
1272
|
try {
|
|
@@ -527,6 +1298,38 @@ function createSendQueue() {
|
|
|
527
1298
|
};
|
|
528
1299
|
}
|
|
529
1300
|
|
|
1301
|
+
// src/session/internal/validate-serial-port-filters.ts
|
|
1302
|
+
function validateSerialPortFilters(filters) {
|
|
1303
|
+
if (!filters || filters.length === 0) {
|
|
1304
|
+
return filters;
|
|
1305
|
+
}
|
|
1306
|
+
for (const filter of filters) {
|
|
1307
|
+
if (!filter.usbVendorId && !filter.usbProductId) {
|
|
1308
|
+
throw new SerialError(
|
|
1309
|
+
SerialErrorCode.INVALID_FILTER_OPTIONS,
|
|
1310
|
+
"Filter must have at least usbVendorId or usbProductId"
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
if (filter.usbVendorId !== void 0) {
|
|
1314
|
+
if (!Number.isInteger(filter.usbVendorId) || filter.usbVendorId < 0 || filter.usbVendorId > 65535) {
|
|
1315
|
+
throw new SerialError(
|
|
1316
|
+
SerialErrorCode.INVALID_FILTER_OPTIONS,
|
|
1317
|
+
`Invalid usbVendorId: ${filter.usbVendorId}. Must be an integer between 0 and 65535.`
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
if (filter.usbProductId !== void 0) {
|
|
1322
|
+
if (!Number.isInteger(filter.usbProductId) || filter.usbProductId < 0 || filter.usbProductId > 65535) {
|
|
1323
|
+
throw new SerialError(
|
|
1324
|
+
SerialErrorCode.INVALID_FILTER_OPTIONS,
|
|
1325
|
+
`Invalid usbProductId: ${filter.usbProductId}. Must be an integer between 0 and 65535.`
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
return filters;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
530
1333
|
// src/session/serial-session-options.ts
|
|
531
1334
|
var MAX_RECEIVE_REPLAY_BUFFER_SIZE = 65536;
|
|
532
1335
|
var MAX_RECEIVE_REPLAY_MAX_CHARS = 1048576;
|
|
@@ -543,531 +1346,195 @@ function resolveReceiveReplayOptions(options) {
|
|
|
543
1346
|
const { bufferSize, maxChars } = merged;
|
|
544
1347
|
if (!Number.isSafeInteger(bufferSize) || bufferSize < 1 || bufferSize > MAX_RECEIVE_REPLAY_BUFFER_SIZE) {
|
|
545
1348
|
throw new SerialError(
|
|
546
|
-
|
|
1349
|
+
SerialErrorCode.INVALID_RECEIVE_REPLAY_OPTIONS,
|
|
547
1350
|
`Invalid receiveReplay.bufferSize: ${bufferSize}. Must be a safe integer between 1 and ${MAX_RECEIVE_REPLAY_BUFFER_SIZE}.`
|
|
548
1351
|
);
|
|
549
1352
|
}
|
|
550
1353
|
if (!Number.isSafeInteger(maxChars) || maxChars < 0 || maxChars > MAX_RECEIVE_REPLAY_MAX_CHARS) {
|
|
551
1354
|
throw new SerialError(
|
|
552
|
-
|
|
1355
|
+
SerialErrorCode.INVALID_RECEIVE_REPLAY_OPTIONS,
|
|
553
1356
|
`Invalid receiveReplay.maxChars: ${maxChars}. Must be a safe integer between 0 and ${MAX_RECEIVE_REPLAY_MAX_CHARS}.`
|
|
554
1357
|
);
|
|
555
1358
|
}
|
|
556
|
-
return
|
|
1359
|
+
return {
|
|
1360
|
+
enabled: merged.enabled,
|
|
1361
|
+
bufferSize: brandReceiveReplayBufferSize(bufferSize),
|
|
1362
|
+
maxChars: brandMaxChars(maxChars)
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
function resolveTerminalBufferOptions(options) {
|
|
1366
|
+
const merged = {
|
|
1367
|
+
...DEFAULT_TERMINAL_BUFFER_OPTIONS,
|
|
1368
|
+
...options
|
|
1369
|
+
};
|
|
1370
|
+
const { maxLines, maxChars } = merged;
|
|
1371
|
+
if (!Number.isSafeInteger(maxLines) || maxLines < 0) {
|
|
1372
|
+
throw new SerialError(
|
|
1373
|
+
SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS,
|
|
1374
|
+
`Invalid terminalBuffer.maxLines: ${maxLines}. Must be a safe integer >= 0.`
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
if (!Number.isSafeInteger(maxChars) || maxChars < 0) {
|
|
1378
|
+
throw new SerialError(
|
|
1379
|
+
SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS,
|
|
1380
|
+
`Invalid terminalBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
return {
|
|
1384
|
+
maxLines: brandMaxLines(maxLines),
|
|
1385
|
+
maxChars: brandMaxChars(maxChars)
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
function resolveLineBufferOptions(options) {
|
|
1389
|
+
const merged = {
|
|
1390
|
+
...DEFAULT_LINE_BUFFER_OPTIONS,
|
|
1391
|
+
...options
|
|
1392
|
+
};
|
|
1393
|
+
const { maxChars } = merged;
|
|
1394
|
+
if (!Number.isSafeInteger(maxChars) || maxChars < 0) {
|
|
1395
|
+
throw new SerialError(
|
|
1396
|
+
SerialErrorCode.INVALID_LINE_BUFFER_OPTIONS,
|
|
1397
|
+
`Invalid lineBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
return {
|
|
1401
|
+
maxChars: brandMaxChars(maxChars)
|
|
1402
|
+
};
|
|
557
1403
|
}
|
|
558
1404
|
var DEFAULT_SERIAL_SESSION_OPTIONS = {
|
|
559
|
-
baudRate: 9600,
|
|
1405
|
+
baudRate: brandBaudRate(9600),
|
|
560
1406
|
dataBits: 8,
|
|
561
1407
|
stopBits: 1,
|
|
562
1408
|
parity: "none",
|
|
563
|
-
bufferSize: 255,
|
|
1409
|
+
bufferSize: brandSerialPortBufferSize(255),
|
|
564
1410
|
flowControl: "none",
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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"
|
|
1411
|
+
receiveReplay: resolveReceiveReplayOptions(),
|
|
1412
|
+
terminalBuffer: resolveTerminalBufferOptions(),
|
|
1413
|
+
lineBuffer: resolveLineBufferOptions()
|
|
580
1414
|
};
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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;
|
|
632
|
-
}
|
|
633
|
-
const allowed = ALLOWED_TRANSITIONS[current];
|
|
634
|
-
if (!allowed.includes(next)) {
|
|
635
|
-
if (typeof console !== "undefined" && console.warn) {
|
|
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;
|
|
1415
|
+
function resolveConnectionOptions(options) {
|
|
1416
|
+
const merged = {
|
|
1417
|
+
baudRate: DEFAULT_SERIAL_SESSION_OPTIONS.baudRate,
|
|
1418
|
+
dataBits: DEFAULT_SERIAL_SESSION_OPTIONS.dataBits,
|
|
1419
|
+
stopBits: DEFAULT_SERIAL_SESSION_OPTIONS.stopBits,
|
|
1420
|
+
parity: DEFAULT_SERIAL_SESSION_OPTIONS.parity,
|
|
1421
|
+
bufferSize: DEFAULT_SERIAL_SESSION_OPTIONS.bufferSize,
|
|
1422
|
+
flowControl: DEFAULT_SERIAL_SESSION_OPTIONS.flowControl,
|
|
1423
|
+
...options
|
|
1424
|
+
};
|
|
1425
|
+
const { baudRate, bufferSize } = merged;
|
|
1426
|
+
if (!Number.isSafeInteger(baudRate) || baudRate <= 0) {
|
|
1427
|
+
throw new SerialError(
|
|
1428
|
+
SerialErrorCode.INVALID_CONNECTION_OPTIONS,
|
|
1429
|
+
`Invalid baudRate: ${baudRate}. Must be a safe integer > 0.`
|
|
1430
|
+
);
|
|
644
1431
|
}
|
|
645
|
-
|
|
1432
|
+
return {
|
|
1433
|
+
dataBits: merged.dataBits ?? DEFAULT_SERIAL_SESSION_OPTIONS.dataBits,
|
|
1434
|
+
stopBits: merged.stopBits ?? DEFAULT_SERIAL_SESSION_OPTIONS.stopBits,
|
|
1435
|
+
parity: merged.parity ?? DEFAULT_SERIAL_SESSION_OPTIONS.parity,
|
|
1436
|
+
flowControl: merged.flowControl ?? DEFAULT_SERIAL_SESSION_OPTIONS.flowControl,
|
|
1437
|
+
baudRate: brandBaudRate(baudRate),
|
|
1438
|
+
bufferSize: brandSerialPortBufferSize(
|
|
1439
|
+
bufferSize ?? DEFAULT_SERIAL_SESSION_OPTIONS.bufferSize
|
|
1440
|
+
)
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
function resolveSerialSessionOptions(options) {
|
|
1444
|
+
const connection = resolveConnectionOptions(options);
|
|
1445
|
+
const filters = validateSerialPortFilters(options?.filters);
|
|
1446
|
+
return {
|
|
1447
|
+
...connection,
|
|
1448
|
+
...filters !== void 0 ? { filters } : {},
|
|
1449
|
+
receiveReplay: resolveReceiveReplayOptions(options?.receiveReplay),
|
|
1450
|
+
terminalBuffer: resolveTerminalBufferOptions(options?.terminalBuffer),
|
|
1451
|
+
lineBuffer: resolveLineBufferOptions(options?.lineBuffer)
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
646
1454
|
|
|
647
1455
|
// src/session/create-serial-session.ts
|
|
648
1456
|
function createSerialSession(options) {
|
|
649
|
-
const resolvedOptions =
|
|
650
|
-
|
|
651
|
-
|
|
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
|
|
1457
|
+
const resolvedOptions = resolveSerialSessionOptions(options);
|
|
1458
|
+
const controller = createSessionRuntimeController(
|
|
1459
|
+
createInitialRuntime(hasWebSerialSupport())
|
|
666
1460
|
);
|
|
667
|
-
const errorsSubject = new
|
|
668
|
-
const receiveSubject = new Subject2();
|
|
669
|
-
const linesSubject = new Subject2();
|
|
1461
|
+
const errorsSubject = new Subject3();
|
|
670
1462
|
const sendQueue = createSendQueue();
|
|
671
1463
|
const textEncoder = new TextEncoder();
|
|
672
|
-
const
|
|
1464
|
+
const portInfoSubject = new BehaviorSubject3(null);
|
|
1465
|
+
const isDisposed = () => controller.status === SerialSessionStatus.Disposed;
|
|
1466
|
+
const errorReporterRef = {};
|
|
1467
|
+
const receivePipeline = createReceivePipeline({
|
|
1468
|
+
resolvedOptions,
|
|
1469
|
+
reportError: (error, options2) => errorReporterRef.reportError(error, options2)
|
|
1470
|
+
});
|
|
1471
|
+
const lifecycle = createSessionLifecycle({
|
|
1472
|
+
controller,
|
|
1473
|
+
resolvedOptions,
|
|
1474
|
+
sendQueue,
|
|
1475
|
+
receivePipeline,
|
|
1476
|
+
portInfoSubject,
|
|
1477
|
+
errorsSubject,
|
|
1478
|
+
isDisposed,
|
|
1479
|
+
reportError: (error, options2) => errorReporterRef.reportError(error, options2),
|
|
1480
|
+
createDisposedError: () => errorReporterRef.createDisposedError()
|
|
1481
|
+
});
|
|
1482
|
+
const { reportError, createDisposedError } = createSessionErrorReporter({
|
|
1483
|
+
controller,
|
|
1484
|
+
errorsSubject,
|
|
1485
|
+
sendQueue,
|
|
1486
|
+
isDisposed,
|
|
1487
|
+
updatePortInfo: lifecycle.updatePortInfo,
|
|
1488
|
+
teardownPump: lifecycle.teardownPump,
|
|
1489
|
+
closePortSafely: lifecycle.closePortSafely
|
|
1490
|
+
});
|
|
1491
|
+
errorReporterRef.reportError = reportError;
|
|
1492
|
+
errorReporterRef.createDisposedError = createDisposedError;
|
|
1493
|
+
const { receive$, lines$, receiveReplay$ } = receivePipeline;
|
|
673
1494
|
const errors$ = errorsSubject.asObservable();
|
|
674
|
-
const receive$ = receiveSubject.asObservable();
|
|
675
|
-
const lines$ = linesSubject.asObservable();
|
|
676
1495
|
const terminalText$ = createTerminalBuffer(
|
|
677
1496
|
receive$,
|
|
678
1497
|
resolvedOptions.terminalBuffer
|
|
679
1498
|
).text$;
|
|
680
|
-
const isConnected$ =
|
|
681
|
-
map2((state) => state ===
|
|
1499
|
+
const isConnected$ = controller.state$.pipe(
|
|
1500
|
+
map2((state) => state.status === SerialSessionStatus.Connected),
|
|
682
1501
|
distinctUntilChanged()
|
|
683
1502
|
);
|
|
684
|
-
const portInfoSubject = new BehaviorSubject2(null);
|
|
685
1503
|
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;
|
|
704
|
-
}
|
|
705
|
-
const buffer = createReceiveReplayBuffer({
|
|
706
|
-
bufferSize: resolvedOptions.receiveReplay.bufferSize,
|
|
707
|
-
maxChars: resolvedOptions.receiveReplay.maxChars
|
|
708
|
-
});
|
|
709
|
-
activeReceiveReplay = buffer;
|
|
710
|
-
receiveReplayStream$.next(buffer.asObservable());
|
|
711
|
-
};
|
|
712
|
-
const receiveReplay$ = receiveReplayStream$ ? receiveReplayStream$.pipe(switchMap((inner) => inner), share()) : receive$;
|
|
713
|
-
let activePort = null;
|
|
714
|
-
let activePump = null;
|
|
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?.();
|
|
725
|
-
}
|
|
726
|
-
sendQueue.clear();
|
|
727
|
-
if (current === SerialSessionState.Connected || current === SerialSessionState.Error || current === SerialSessionState.Disconnecting) {
|
|
728
|
-
const portToClose = activePort;
|
|
729
|
-
await teardownPump();
|
|
730
|
-
await closePortSafely(portToClose);
|
|
731
|
-
setActivePort(null);
|
|
732
|
-
}
|
|
733
|
-
lineBuffer.clear();
|
|
734
|
-
machine.toDisposed();
|
|
735
|
-
machine.complete();
|
|
736
|
-
errorsSubject.complete();
|
|
737
|
-
receiveSubject.complete();
|
|
738
|
-
linesSubject.complete();
|
|
739
|
-
portInfoSubject.complete();
|
|
740
|
-
receiveReplayStream$?.complete();
|
|
741
|
-
};
|
|
742
|
-
const dispose$ = () => new Observable4((subscriber) => {
|
|
743
|
-
if (disposed) {
|
|
744
|
-
subscriber.next();
|
|
745
|
-
subscriber.complete();
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
disposed = true;
|
|
749
|
-
const run = async () => {
|
|
750
|
-
try {
|
|
751
|
-
await completeSession();
|
|
752
|
-
subscriber.next();
|
|
753
|
-
subscriber.complete();
|
|
754
|
-
} catch (error) {
|
|
755
|
-
const serialError = normalizeSerialError(error, {
|
|
756
|
-
fallbackCode: "UNKNOWN" /* UNKNOWN */,
|
|
757
|
-
messagePrefix: "Unexpected dispose failure"
|
|
758
|
-
});
|
|
759
|
-
subscriber.error(serialError);
|
|
760
|
-
}
|
|
761
|
-
};
|
|
762
|
-
void run();
|
|
763
|
-
});
|
|
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
|
-
const writeToPort = async (payload) => {
|
|
807
|
-
const port = activePort;
|
|
808
|
-
if (machine.current !== SerialSessionState.Connected || !port || !port.writable) {
|
|
809
|
-
throw new SerialError(
|
|
810
|
-
"PORT_NOT_OPEN" /* PORT_NOT_OPEN */,
|
|
811
|
-
"Cannot send data while session is not connected"
|
|
812
|
-
);
|
|
813
|
-
}
|
|
814
|
-
const writer = port.writable.getWriter();
|
|
815
|
-
try {
|
|
816
|
-
await writer.write(payload);
|
|
817
|
-
} finally {
|
|
818
|
-
try {
|
|
819
|
-
writer.releaseLock();
|
|
820
|
-
} catch {
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
};
|
|
824
1504
|
return {
|
|
825
1505
|
isBrowserSupported() {
|
|
826
1506
|
return hasWebSerialSupport();
|
|
827
1507
|
},
|
|
828
|
-
connect
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
if (!hasWebSerialSupport()) {
|
|
835
|
-
const error = reportError(
|
|
836
|
-
new SerialError(
|
|
837
|
-
"BROWSER_NOT_SUPPORTED" /* BROWSER_NOT_SUPPORTED */,
|
|
838
|
-
"Web Serial API is not supported in this environment"
|
|
839
|
-
),
|
|
840
|
-
"non-fatal",
|
|
841
|
-
{ fallbackCode: "BROWSER_NOT_SUPPORTED" /* BROWSER_NOT_SUPPORTED */ }
|
|
842
|
-
);
|
|
843
|
-
subscriber.error(error);
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
const current = machine.current;
|
|
847
|
-
if (current !== SerialSessionState.Idle && current !== SerialSessionState.Error) {
|
|
848
|
-
const error = reportError(
|
|
849
|
-
new SerialError(
|
|
850
|
-
"PORT_ALREADY_OPEN" /* PORT_ALREADY_OPEN */,
|
|
851
|
-
`Cannot connect while session state is '${current}'`
|
|
852
|
-
),
|
|
853
|
-
"non-fatal",
|
|
854
|
-
{ fallbackCode: "PORT_ALREADY_OPEN" /* PORT_ALREADY_OPEN */ }
|
|
855
|
-
);
|
|
856
|
-
subscriber.error(error);
|
|
857
|
-
return;
|
|
858
|
-
}
|
|
859
|
-
let cancelled = false;
|
|
860
|
-
const cancelInFlightConnect = () => {
|
|
861
|
-
cancelled = true;
|
|
862
|
-
if (machine.current === SerialSessionState.Connecting) {
|
|
863
|
-
machine.toIdle();
|
|
864
|
-
}
|
|
865
|
-
};
|
|
866
|
-
activeConnectCancel = cancelInFlightConnect;
|
|
867
|
-
machine.toConnecting();
|
|
868
|
-
const run = async () => {
|
|
869
|
-
let selectedPort = null;
|
|
870
|
-
try {
|
|
871
|
-
selectedPort = await navigator.serial.requestPort(
|
|
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
|
-
});
|
|
882
|
-
} catch (error) {
|
|
883
|
-
if (selectedPort) {
|
|
884
|
-
await closePortSafely(selectedPort);
|
|
885
|
-
}
|
|
886
|
-
const serialError = reportError(error, "fatal", {
|
|
887
|
-
fallbackCode: "PORT_OPEN_FAILED" /* PORT_OPEN_FAILED */,
|
|
888
|
-
messagePrefix: "Failed to open port"
|
|
889
|
-
});
|
|
890
|
-
if (!cancelled) {
|
|
891
|
-
subscriber.error(serialError);
|
|
892
|
-
}
|
|
893
|
-
return;
|
|
894
|
-
}
|
|
895
|
-
if (cancelled || disposed) {
|
|
896
|
-
await closePortSafely(selectedPort);
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
setActivePort(selectedPort);
|
|
900
|
-
lineBuffer.clear();
|
|
901
|
-
if (resolvedOptions.receiveReplay.enabled) {
|
|
902
|
-
startLiveReceiveReplay();
|
|
903
|
-
}
|
|
904
|
-
activePump = createReadPump(selectedPort, {
|
|
905
|
-
onChunk: (text) => {
|
|
906
|
-
receiveSubject.next(text);
|
|
907
|
-
if (activeReceiveReplay) {
|
|
908
|
-
const { overflowed: overflowed2 } = activeReceiveReplay.next(text);
|
|
909
|
-
if (overflowed2) {
|
|
910
|
-
reportError(
|
|
911
|
-
new SerialError(
|
|
912
|
-
"RECEIVE_REPLAY_BUFFER_OVERFLOW" /* RECEIVE_REPLAY_BUFFER_OVERFLOW */,
|
|
913
|
-
`Receive replay buffer exceeded configured limits; oldest chunks were discarded`
|
|
914
|
-
),
|
|
915
|
-
"non-fatal",
|
|
916
|
-
{
|
|
917
|
-
fallbackCode: "RECEIVE_REPLAY_BUFFER_OVERFLOW" /* RECEIVE_REPLAY_BUFFER_OVERFLOW */
|
|
918
|
-
}
|
|
919
|
-
);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
const { lines, overflowed } = lineBuffer.feed(text);
|
|
923
|
-
if (overflowed) {
|
|
924
|
-
reportError(
|
|
925
|
-
new SerialError(
|
|
926
|
-
"LINE_BUFFER_OVERFLOW" /* LINE_BUFFER_OVERFLOW */,
|
|
927
|
-
`Line buffer exceeded maxChars (${resolvedOptions.lineBuffer.maxChars}); leading data was discarded`
|
|
928
|
-
),
|
|
929
|
-
"non-fatal",
|
|
930
|
-
{ fallbackCode: "LINE_BUFFER_OVERFLOW" /* LINE_BUFFER_OVERFLOW */ }
|
|
931
|
-
);
|
|
932
|
-
}
|
|
933
|
-
for (const line of lines) {
|
|
934
|
-
linesSubject.next(line);
|
|
935
|
-
}
|
|
936
|
-
},
|
|
937
|
-
onError: (pumpError) => reportError(pumpError, "fatal", {
|
|
938
|
-
fallbackCode: "READ_FAILED" /* READ_FAILED */,
|
|
939
|
-
messagePrefix: "Read pump failed"
|
|
940
|
-
}),
|
|
941
|
-
onDone: () => {
|
|
942
|
-
if (machine.current !== SerialSessionState.Connected) {
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
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
|
-
}
|
|
956
|
-
});
|
|
957
|
-
activePump.start();
|
|
958
|
-
sendQueue.clear();
|
|
959
|
-
if (disposed) {
|
|
960
|
-
await teardownPump();
|
|
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 */ }
|
|
1003
|
-
);
|
|
1004
|
-
subscriber.error(error);
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
machine.toDisconnecting();
|
|
1008
|
-
sendQueue.clear();
|
|
1009
|
-
const portToClose = activePort;
|
|
1010
|
-
const run = async () => {
|
|
1011
|
-
try {
|
|
1012
|
-
await teardownPump();
|
|
1013
|
-
if (portToClose) {
|
|
1014
|
-
try {
|
|
1015
|
-
await portToClose.close();
|
|
1016
|
-
} catch (error) {
|
|
1017
|
-
setActivePort(null);
|
|
1018
|
-
const serialError = reportError(error, "fatal", {
|
|
1019
|
-
fallbackCode: "CONNECTION_LOST" /* CONNECTION_LOST */,
|
|
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);
|
|
1038
|
-
}
|
|
1039
|
-
};
|
|
1040
|
-
void run();
|
|
1041
|
-
});
|
|
1042
|
-
},
|
|
1043
|
-
dispose$,
|
|
1044
|
-
destroy$: dispose$,
|
|
1508
|
+
connect$: lifecycle.connect$,
|
|
1509
|
+
disconnect$: lifecycle.disconnect$,
|
|
1510
|
+
dispose$: lifecycle.dispose$,
|
|
1511
|
+
destroy$: lifecycle.dispose$,
|
|
1045
1512
|
send$(data) {
|
|
1046
|
-
if (
|
|
1047
|
-
return new
|
|
1513
|
+
if (isDisposed()) {
|
|
1514
|
+
return new Observable5((subscriber) => {
|
|
1048
1515
|
subscriber.error(createDisposedError());
|
|
1049
1516
|
});
|
|
1050
1517
|
}
|
|
1051
1518
|
return sendQueue.enqueue(async () => {
|
|
1052
1519
|
const payload = typeof data === "string" ? textEncoder.encode(data) : data;
|
|
1053
1520
|
try {
|
|
1054
|
-
await writeToPort(payload);
|
|
1521
|
+
await lifecycle.writeToPort(payload);
|
|
1055
1522
|
} catch (error) {
|
|
1056
|
-
throw reportError(error,
|
|
1057
|
-
fallbackCode:
|
|
1523
|
+
throw reportError(error, {
|
|
1524
|
+
fallbackCode: SerialErrorCode.WRITE_FAILED,
|
|
1058
1525
|
messagePrefix: "Failed to write data"
|
|
1059
1526
|
});
|
|
1060
1527
|
}
|
|
1061
1528
|
});
|
|
1062
1529
|
},
|
|
1063
|
-
state$:
|
|
1530
|
+
state$: controller.state$,
|
|
1064
1531
|
isConnected$,
|
|
1065
1532
|
portInfo$,
|
|
1066
1533
|
getPortInfo() {
|
|
1067
1534
|
return portInfoSubject.getValue();
|
|
1068
1535
|
},
|
|
1069
1536
|
getCurrentPort() {
|
|
1070
|
-
return
|
|
1537
|
+
return getRuntimePort(controller.runtime);
|
|
1071
1538
|
},
|
|
1072
1539
|
errors$,
|
|
1073
1540
|
receive$,
|
|
@@ -1079,10 +1546,14 @@ function createSerialSession(options) {
|
|
|
1079
1546
|
export {
|
|
1080
1547
|
DEFAULT_LINE_BUFFER_OPTIONS,
|
|
1081
1548
|
DEFAULT_TERMINAL_BUFFER_OPTIONS,
|
|
1549
|
+
MAX_RECEIVE_REPLAY_BUFFER_SIZE,
|
|
1550
|
+
MAX_RECEIVE_REPLAY_MAX_CHARS,
|
|
1082
1551
|
SerialError,
|
|
1083
1552
|
SerialErrorCode,
|
|
1084
|
-
|
|
1553
|
+
SerialSessionStatus,
|
|
1554
|
+
assertNever,
|
|
1085
1555
|
createSerialSession,
|
|
1086
|
-
createTerminalBuffer
|
|
1556
|
+
createTerminalBuffer,
|
|
1557
|
+
resolveSerialSessionOptions
|
|
1087
1558
|
};
|
|
1088
1559
|
//# sourceMappingURL=index.mjs.map
|