@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.
Files changed (49) hide show
  1. package/dist/errors/serial-error-code.d.ts +53 -18
  2. package/dist/errors/serial-error-code.d.ts.map +1 -1
  3. package/dist/errors/serial-error-context.d.ts +47 -0
  4. package/dist/errors/serial-error-context.d.ts.map +1 -0
  5. package/dist/errors/serial-error.d.ts +30 -8
  6. package/dist/errors/serial-error.d.ts.map +1 -1
  7. package/dist/index.d.ts +9 -3
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.mjs +1065 -594
  10. package/dist/index.mjs.map +4 -4
  11. package/dist/internal/assert-never.d.ts +2 -0
  12. package/dist/internal/assert-never.d.ts.map +1 -0
  13. package/dist/internal/branded-numbers.d.ts +37 -0
  14. package/dist/internal/branded-numbers.d.ts.map +1 -0
  15. package/dist/session/create-serial-session.d.ts +5 -2
  16. package/dist/session/create-serial-session.d.ts.map +1 -1
  17. package/dist/session/index.d.ts +6 -2
  18. package/dist/session/index.d.ts.map +1 -1
  19. package/dist/session/internal/build-request-options.d.ts +7 -7
  20. package/dist/session/internal/build-request-options.d.ts.map +1 -1
  21. package/dist/session/internal/error-severity.d.ts +38 -0
  22. package/dist/session/internal/error-severity.d.ts.map +1 -0
  23. package/dist/session/internal/line-buffer.d.ts +15 -4
  24. package/dist/session/internal/line-buffer.d.ts.map +1 -1
  25. package/dist/session/internal/receive-pipeline.d.ts +34 -0
  26. package/dist/session/internal/receive-pipeline.d.ts.map +1 -0
  27. package/dist/session/internal/receive-replay-buffer.d.ts +3 -2
  28. package/dist/session/internal/receive-replay-buffer.d.ts.map +1 -1
  29. package/dist/session/internal/session-error-reporter.d.ts +32 -0
  30. package/dist/session/internal/session-error-reporter.d.ts.map +1 -0
  31. package/dist/session/internal/session-lifecycle.d.ts +44 -0
  32. package/dist/session/internal/session-lifecycle.d.ts.map +1 -0
  33. package/dist/session/internal/validate-serial-port-filters.d.ts +12 -0
  34. package/dist/session/internal/validate-serial-port-filters.d.ts.map +1 -0
  35. package/dist/session/serial-session-options.d.ts +95 -49
  36. package/dist/session/serial-session-options.d.ts.map +1 -1
  37. package/dist/session/serial-session-state.d.ts +49 -12
  38. package/dist/session/serial-session-state.d.ts.map +1 -1
  39. package/dist/session/serial-session.d.ts +9 -7
  40. package/dist/session/serial-session.d.ts.map +1 -1
  41. package/dist/session/session-runtime.d.ts +140 -0
  42. package/dist/session/session-runtime.d.ts.map +1 -0
  43. package/dist/terminal/create-terminal-buffer.d.ts +3 -2
  44. package/dist/terminal/create-terminal-buffer.d.ts.map +1 -1
  45. package/dist/types.d.ts +18 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/package.json +1 -1
  48. package/dist/session/session-state-machine.d.ts +0 -40
  49. 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 BehaviorSubject2,
8
+ BehaviorSubject as BehaviorSubject3,
4
9
  distinctUntilChanged,
5
10
  map as map2,
6
- Observable as Observable4,
7
- share,
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 = /* @__PURE__ */ ((SerialErrorCode2) => {
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
- * Creates a new SerialError instance.
18
+ * Browser does not support the Web Serial API.
37
19
  *
38
- * @param code - The error code identifying the type of error
39
- * @param message - A human-readable error message
40
- * @param originalError - The original error that caused this SerialError, if any
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
- constructor(code, message, originalError) {
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
- * Check if the error matches a specific error code.
28
+ * Serial port is not available.
53
29
  *
54
- * This is a convenience method for checking the error code without directly
55
- * comparing the code property.
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
- * @param code - The error code to check against
58
- * @returns `true` if this error's code matches the provided code, `false` otherwise
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
- * @example
61
- * ```typescript
62
- * if (error.is(SerialErrorCode.PORT_NOT_OPEN)) {
63
- * // Handle port not open error
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
- is(code) {
68
- return this.code === code;
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
- ...DEFAULT_TERMINAL_BUFFER_OPTIONS,
234
- ...options
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
- ...DEFAULT_LINE_BUFFER_OPTIONS,
300
- ...options
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
- "OPERATION_CANCELLED" /* OPERATION_CANCELLED */,
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/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) {
405
- return;
406
- }
407
- try {
408
- reader.releaseLock();
409
- } catch {
410
- }
411
- reader = null;
412
- };
413
- const pump = async (stream) => {
414
- reader = stream.getReader();
415
- running = true;
416
- try {
417
- while (!stopped) {
418
- const { done, value } = await reader.read();
419
- if (done) {
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: "READ_FAILED" /* READ_FAILED */,
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
- "CONNECTION_LOST" /* CONNECTION_LOST */,
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 Observable2, defer } from "rxjs";
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 Observable2((subscriber) => {
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
- "INVALID_RECEIVE_REPLAY_OPTIONS" /* INVALID_RECEIVE_REPLAY_OPTIONS */,
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
- "INVALID_RECEIVE_REPLAY_OPTIONS" /* INVALID_RECEIVE_REPLAY_OPTIONS */,
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 merged;
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
- 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"
1411
+ receiveReplay: resolveReceiveReplayOptions(),
1412
+ terminalBuffer: resolveTerminalBufferOptions(),
1413
+ lineBuffer: resolveLineBufferOptions()
580
1414
  };
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;
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
- ...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
1457
+ const resolvedOptions = resolveSerialSessionOptions(options);
1458
+ const controller = createSessionRuntimeController(
1459
+ createInitialRuntime(hasWebSerialSupport())
666
1460
  );
667
- const errorsSubject = new Subject2();
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 lineBuffer = createLineBuffer(resolvedOptions.lineBuffer);
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$ = machine.state$.pipe(
681
- map2((state) => state === SerialSessionState.Connected),
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
- return new Observable4((subscriber) => {
830
- if (disposed) {
831
- subscriber.error(createDisposedError());
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 (disposed) {
1047
- return new Observable4((subscriber) => {
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, "non-fatal", {
1057
- fallbackCode: "WRITE_FAILED" /* WRITE_FAILED */,
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$: machine.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 activePort;
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
- SerialSessionState,
1553
+ SerialSessionStatus,
1554
+ assertNever,
1085
1555
  createSerialSession,
1086
- createTerminalBuffer
1556
+ createTerminalBuffer,
1557
+ resolveSerialSessionOptions
1087
1558
  };
1088
1559
  //# sourceMappingURL=index.mjs.map