@gurezo/web-serial-rxjs 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.ja.md +17 -10
  2. package/README.md +21 -12
  3. package/dist/errors/serial-error-code.d.ts +28 -19
  4. package/dist/errors/serial-error-code.d.ts.map +1 -1
  5. package/dist/errors/serial-error-context.d.ts +21 -7
  6. package/dist/errors/serial-error-context.d.ts.map +1 -1
  7. package/dist/errors/serial-error.d.ts +19 -20
  8. package/dist/errors/serial-error.d.ts.map +1 -1
  9. package/dist/index.d.ts +36 -12
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.mjs +215 -43
  12. package/dist/index.mjs.map +3 -3
  13. package/dist/internal/assert-never.d.ts +9 -0
  14. package/dist/internal/assert-never.d.ts.map +1 -1
  15. package/dist/internal/strip-ansi-sequences.d.ts +25 -0
  16. package/dist/internal/strip-ansi-sequences.d.ts.map +1 -0
  17. package/dist/session/create-serial-session.d.ts +1 -1
  18. package/dist/session/create-serial-session.d.ts.map +1 -1
  19. package/dist/session/index.d.ts +2 -1
  20. package/dist/session/index.d.ts.map +1 -1
  21. package/dist/session/internal/validate-serial-port-filters.d.ts.map +1 -1
  22. package/dist/session/is-connected-session-state.d.ts +11 -0
  23. package/dist/session/is-connected-session-state.d.ts.map +1 -0
  24. package/dist/session/normalize-serial-error.d.ts +1 -1
  25. package/dist/session/normalize-serial-error.d.ts.map +1 -1
  26. package/dist/session/serial-session-options.d.ts +41 -34
  27. package/dist/session/serial-session-options.d.ts.map +1 -1
  28. package/dist/session/serial-session-state.d.ts +3 -2
  29. package/dist/session/serial-session-state.d.ts.map +1 -1
  30. package/dist/session/serial-session.d.ts +46 -14
  31. package/dist/session/serial-session.d.ts.map +1 -1
  32. package/dist/terminal/create-terminal-buffer.d.ts +12 -1
  33. package/dist/terminal/create-terminal-buffer.d.ts.map +1 -1
  34. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -17,28 +17,32 @@ var SerialErrorCode = {
17
17
  /**
18
18
  * Browser does not support the Web Serial API.
19
19
  *
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.
20
+ * Emitted on `connect$` when `navigator.serial` is unavailable.
23
21
  *
24
22
  * **Suggested action**: Inform the user to use a supported browser.
23
+ *
24
+ * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}
25
25
  */
26
26
  BROWSER_NOT_SUPPORTED: "BROWSER_NOT_SUPPORTED",
27
27
  /**
28
28
  * Serial port is not available.
29
29
  *
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.
30
+ * **Reserved not emitted in v3.x.** The current implementation uses only
31
+ * `navigator.serial.requestPort`; there is no `getPorts` API path. Scheduled
32
+ * for removal in the next major version.
33
+ *
34
+ * **Suggested action**: Handle port acquisition failures with
35
+ * {@link SerialErrorCode.PORT_OPEN_FAILED} or
36
+ * {@link SerialErrorCode.OPERATION_CANCELLED} instead.
33
37
  *
34
- * **Suggested action**: Check if the port is available or being used by another application.
38
+ * @deprecated Not emitted at runtime in v3.x. Will be removed in the next major version.
39
+ * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}
35
40
  */
36
41
  PORT_NOT_AVAILABLE: "PORT_NOT_AVAILABLE",
37
42
  /**
38
43
  * Failed to open the serial port.
39
44
  *
40
- * This error occurs when the port cannot be opened, typically due to incorrect
41
- * connection parameters, hardware issues, or permission problems.
45
+ * Emitted on `connect$` when `port.open()` rejects.
42
46
  *
43
47
  * **Suggested action**: Verify connection parameters and check hardware connections.
44
48
  */
@@ -46,8 +50,8 @@ var SerialErrorCode = {
46
50
  /**
47
51
  * Serial port is already open.
48
52
  *
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.
53
+ * Emitted on `connect$` when the session is not in `'idle'` or `'error'`
54
+ * (non-fatal; session state is unchanged).
51
55
  *
52
56
  * **Suggested action**: Disconnect the current port before connecting a new one.
53
57
  */
@@ -55,10 +59,10 @@ var SerialErrorCode = {
55
59
  /**
56
60
  * Serial port is not open.
57
61
  *
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.
62
+ * Emitted when `send$` or `disconnect$` is called in an invalid session
63
+ * state (for example before `connect$` completes).
60
64
  *
61
- * **Suggested action**: Call {@link SerialClient.connect} before reading or writing.
65
+ * **Suggested action**: Call {@link SerialSession.connect$} before sending data.
62
66
  */
63
67
  PORT_NOT_OPEN: "PORT_NOT_OPEN",
64
68
  /**
@@ -111,8 +115,12 @@ var SerialErrorCode = {
111
115
  /**
112
116
  * Operation timed out before completion.
113
117
  *
114
- * This error occurs when an operation waits for a condition (for example, prompt
115
- * detection) and the timeout period elapses first.
118
+ * **Reserved not emitted in v3.x.** No timeout / prompt detection /
119
+ * transaction API exists yet. Scheduled for removal in the next major version
120
+ * unless a future API adopts this code.
121
+ *
122
+ * @deprecated Not emitted at runtime in v3.x. Will be removed in the next major version.
123
+ * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}
116
124
  */
117
125
  OPERATION_TIMEOUT: "OPERATION_TIMEOUT",
118
126
  /**
@@ -191,10 +199,11 @@ var SerialErrorCode = {
191
199
  /**
192
200
  * Unknown error occurred.
193
201
  *
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.
202
+ * Emitted as a fallback when dispose or disconnect encounters an error that
203
+ * cannot be classified more specifically. The underlying failure is on
204
+ * {@link SerialError.context | context.cause}.
196
205
  *
197
- * **Suggested action**: Check the error message and originalError for more details.
206
+ * **Suggested action**: Check the error message and `context.cause` for more details.
198
207
  */
199
208
  UNKNOWN: "UNKNOWN"
200
209
  };
@@ -305,6 +314,89 @@ function createNewlineTokenizer(mode) {
305
314
  };
306
315
  }
307
316
 
317
+ // src/internal/strip-ansi-sequences.ts
318
+ var ESC = "\x1B";
319
+ var CSI_FINAL_BYTE = /[@-~]/;
320
+ var BEL = "\x07";
321
+ var OSC_STRING_TERMINATOR = "\x1B\\";
322
+ function findOscTerminator(oscBody) {
323
+ let earliest = null;
324
+ const belIndex = oscBody.indexOf(BEL);
325
+ if (belIndex >= 0) {
326
+ earliest = { index: belIndex, length: 1 };
327
+ }
328
+ const stIndex = oscBody.indexOf(OSC_STRING_TERMINATOR);
329
+ if (stIndex >= 0) {
330
+ const candidate = { index: stIndex, length: OSC_STRING_TERMINATOR.length };
331
+ if (!earliest || stIndex < earliest.index) {
332
+ earliest = candidate;
333
+ }
334
+ }
335
+ return earliest;
336
+ }
337
+ function createAnsiStripper() {
338
+ let pending = "";
339
+ const feed = (chunk) => {
340
+ const input = pending + chunk;
341
+ pending = "";
342
+ let output = "";
343
+ let i = 0;
344
+ while (i < input.length) {
345
+ const escIndex = input.indexOf(ESC, i);
346
+ if (escIndex < 0) {
347
+ output += input.slice(i);
348
+ break;
349
+ }
350
+ output += input.slice(i, escIndex);
351
+ const next = escIndex + 1;
352
+ if (next >= input.length) {
353
+ pending = ESC;
354
+ break;
355
+ }
356
+ const code = input.charCodeAt(next);
357
+ if (code === 91) {
358
+ const csiStart = next + 1;
359
+ let j = csiStart;
360
+ while (j < input.length && !CSI_FINAL_BYTE.test(input.charAt(j))) {
361
+ j++;
362
+ }
363
+ if (j < input.length) {
364
+ i = j + 1;
365
+ continue;
366
+ }
367
+ pending = input.slice(escIndex);
368
+ break;
369
+ }
370
+ if (code === 93) {
371
+ const oscBody = input.slice(next);
372
+ const terminator = findOscTerminator(oscBody);
373
+ if (terminator) {
374
+ i = escIndex + 1 + terminator.index + terminator.length;
375
+ continue;
376
+ }
377
+ pending = input.slice(escIndex);
378
+ break;
379
+ }
380
+ if (next < input.length) {
381
+ i = next + 1;
382
+ continue;
383
+ }
384
+ pending = input.slice(escIndex);
385
+ break;
386
+ }
387
+ return output;
388
+ };
389
+ const flush = () => {
390
+ if (pending.length === 0) {
391
+ return "";
392
+ }
393
+ const remainder = pending;
394
+ pending = "";
395
+ return remainder;
396
+ };
397
+ return { feed, flush };
398
+ }
399
+
308
400
  // src/terminal/create-terminal-buffer.ts
309
401
  function applyTerminalChunk(state, chunk) {
310
402
  const tokenizer = createNewlineTokenizer("terminal");
@@ -373,7 +465,8 @@ function trimTerminalState(state, limits) {
373
465
  }
374
466
  var DEFAULT_TERMINAL_BUFFER_OPTIONS = {
375
467
  maxLines: 1e4,
376
- maxChars: 1048576
468
+ maxChars: 1048576,
469
+ stripAnsi: true
377
470
  };
378
471
  function resolveTerminalBufferLimits(options) {
379
472
  const maxLines = options?.maxLines ?? DEFAULT_TERMINAL_BUFFER_OPTIONS.maxLines;
@@ -389,9 +482,12 @@ var initialTerminalState = {
389
482
  };
390
483
  function createTerminalBuffer(receive$, options) {
391
484
  const limits = resolveTerminalBufferLimits(options);
485
+ const stripAnsi = options?.stripAnsi ?? DEFAULT_TERMINAL_BUFFER_OPTIONS.stripAnsi;
486
+ const ansiStripper = stripAnsi ? createAnsiStripper() : null;
392
487
  const text$ = receive$.pipe(
393
488
  scan((state, chunk) => {
394
- const next = applyTerminalChunk(state, chunk);
489
+ const normalized = ansiStripper !== null ? ansiStripper.feed(chunk) : chunk;
490
+ const next = applyTerminalChunk(state, normalized);
395
491
  return trimTerminalState(next, limits);
396
492
  }, initialTerminalState),
397
493
  map(terminalDisplayText),
@@ -435,17 +531,18 @@ var SerialError = class _SerialError extends Error {
435
531
  *
436
532
  * @param code - The error code identifying the type of error
437
533
  * @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`.
534
+ * @param originalError - The original error that caused this SerialError, if any.
535
+ * @deprecated Prefer passing `{ cause }` via {@link context}. When omitted,
536
+ * cause-bearing codes derive `{ cause }` from this argument for backward
537
+ * compatibility.
538
+ * @param context - Structured metadata for the error code. For cause-bearing
539
+ * codes, pass `{ cause }` here. When omitted, cause-bearing codes derive
540
+ * `{ cause }` from the legacy {@link originalError} argument.
441
541
  */
442
542
  constructor(code, message, originalError, context) {
443
543
  super(message);
444
544
  this.name = "SerialError";
445
545
  this.code = code;
446
- if (originalError !== void 0) {
447
- this.originalError = originalError;
448
- }
449
546
  if (context !== void 0) {
450
547
  this.context = context;
451
548
  } else if (originalError !== void 0 && isCauseContextCode(code)) {
@@ -453,6 +550,14 @@ var SerialError = class _SerialError extends Error {
453
550
  } else {
454
551
  this.context = void 0;
455
552
  }
553
+ if (originalError !== void 0) {
554
+ this.originalError = originalError;
555
+ } else if (isCauseContextCode(code) && this.context !== void 0) {
556
+ const causeContext = this.context;
557
+ if (causeContext.cause instanceof Error) {
558
+ this.originalError = causeContext.cause;
559
+ }
560
+ }
456
561
  if (ErrorWithCaptureStackTrace.captureStackTrace) {
457
562
  ErrorWithCaptureStackTrace.captureStackTrace(this, _SerialError);
458
563
  }
@@ -688,14 +793,16 @@ function normalizeSerialError(error, options) {
688
793
  return new SerialError(
689
794
  SerialErrorCode.OPERATION_CANCELLED,
690
795
  "Port selection was cancelled by the user",
691
- error
796
+ void 0,
797
+ { cause: error }
692
798
  );
693
799
  }
694
800
  const cause = error instanceof Error ? error : new Error(String(error));
695
801
  return new SerialError(
696
802
  options.fallbackCode,
697
803
  `${prefix}: ${cause.message}`,
698
- cause
804
+ void 0,
805
+ { cause }
699
806
  );
700
807
  }
701
808
 
@@ -1303,18 +1410,36 @@ function validateSerialPortFilters(filters) {
1303
1410
  if (!filters || filters.length === 0) {
1304
1411
  return filters;
1305
1412
  }
1306
- for (const filter of filters) {
1413
+ for (let filterIndex = 0; filterIndex < filters.length; filterIndex++) {
1414
+ const filter = filters[filterIndex];
1415
+ if (filter === void 0) {
1416
+ continue;
1417
+ }
1307
1418
  if (!filter.usbVendorId && !filter.usbProductId) {
1308
1419
  throw new SerialError(
1309
1420
  SerialErrorCode.INVALID_FILTER_OPTIONS,
1310
- "Filter must have at least usbVendorId or usbProductId"
1421
+ "Filter must have at least usbVendorId or usbProductId",
1422
+ void 0,
1423
+ {
1424
+ field: "filters",
1425
+ value: filter,
1426
+ constraint: "at-least-one-usb-id",
1427
+ filterIndex
1428
+ }
1311
1429
  );
1312
1430
  }
1313
1431
  if (filter.usbVendorId !== void 0) {
1314
1432
  if (!Number.isInteger(filter.usbVendorId) || filter.usbVendorId < 0 || filter.usbVendorId > 65535) {
1315
1433
  throw new SerialError(
1316
1434
  SerialErrorCode.INVALID_FILTER_OPTIONS,
1317
- `Invalid usbVendorId: ${filter.usbVendorId}. Must be an integer between 0 and 65535.`
1435
+ `Invalid usbVendorId: ${filter.usbVendorId}. Must be an integer between 0 and 65535.`,
1436
+ void 0,
1437
+ {
1438
+ field: "usbVendorId",
1439
+ value: filter.usbVendorId,
1440
+ constraint: "usb-id-0-65535",
1441
+ filterIndex
1442
+ }
1318
1443
  );
1319
1444
  }
1320
1445
  }
@@ -1322,7 +1447,14 @@ function validateSerialPortFilters(filters) {
1322
1447
  if (!Number.isInteger(filter.usbProductId) || filter.usbProductId < 0 || filter.usbProductId > 65535) {
1323
1448
  throw new SerialError(
1324
1449
  SerialErrorCode.INVALID_FILTER_OPTIONS,
1325
- `Invalid usbProductId: ${filter.usbProductId}. Must be an integer between 0 and 65535.`
1450
+ `Invalid usbProductId: ${filter.usbProductId}. Must be an integer between 0 and 65535.`,
1451
+ void 0,
1452
+ {
1453
+ field: "usbProductId",
1454
+ value: filter.usbProductId,
1455
+ constraint: "usb-id-0-65535",
1456
+ filterIndex
1457
+ }
1326
1458
  );
1327
1459
  }
1328
1460
  }
@@ -1347,13 +1479,25 @@ function resolveReceiveReplayOptions(options) {
1347
1479
  if (!Number.isSafeInteger(bufferSize) || bufferSize < 1 || bufferSize > MAX_RECEIVE_REPLAY_BUFFER_SIZE) {
1348
1480
  throw new SerialError(
1349
1481
  SerialErrorCode.INVALID_RECEIVE_REPLAY_OPTIONS,
1350
- `Invalid receiveReplay.bufferSize: ${bufferSize}. Must be a safe integer between 1 and ${MAX_RECEIVE_REPLAY_BUFFER_SIZE}.`
1482
+ `Invalid receiveReplay.bufferSize: ${bufferSize}. Must be a safe integer between 1 and ${MAX_RECEIVE_REPLAY_BUFFER_SIZE}.`,
1483
+ void 0,
1484
+ {
1485
+ field: "receiveReplay.bufferSize",
1486
+ value: bufferSize,
1487
+ constraint: "receive-replay-buffer-size-range"
1488
+ }
1351
1489
  );
1352
1490
  }
1353
1491
  if (!Number.isSafeInteger(maxChars) || maxChars < 0 || maxChars > MAX_RECEIVE_REPLAY_MAX_CHARS) {
1354
1492
  throw new SerialError(
1355
1493
  SerialErrorCode.INVALID_RECEIVE_REPLAY_OPTIONS,
1356
- `Invalid receiveReplay.maxChars: ${maxChars}. Must be a safe integer between 0 and ${MAX_RECEIVE_REPLAY_MAX_CHARS}.`
1494
+ `Invalid receiveReplay.maxChars: ${maxChars}. Must be a safe integer between 0 and ${MAX_RECEIVE_REPLAY_MAX_CHARS}.`,
1495
+ void 0,
1496
+ {
1497
+ field: "receiveReplay.maxChars",
1498
+ value: maxChars,
1499
+ constraint: "receive-replay-max-chars-range"
1500
+ }
1357
1501
  );
1358
1502
  }
1359
1503
  return {
@@ -1371,18 +1515,31 @@ function resolveTerminalBufferOptions(options) {
1371
1515
  if (!Number.isSafeInteger(maxLines) || maxLines < 0) {
1372
1516
  throw new SerialError(
1373
1517
  SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS,
1374
- `Invalid terminalBuffer.maxLines: ${maxLines}. Must be a safe integer >= 0.`
1518
+ `Invalid terminalBuffer.maxLines: ${maxLines}. Must be a safe integer >= 0.`,
1519
+ void 0,
1520
+ {
1521
+ field: "terminalBuffer.maxLines",
1522
+ value: maxLines,
1523
+ constraint: "non-negative-safe-integer"
1524
+ }
1375
1525
  );
1376
1526
  }
1377
1527
  if (!Number.isSafeInteger(maxChars) || maxChars < 0) {
1378
1528
  throw new SerialError(
1379
1529
  SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS,
1380
- `Invalid terminalBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`
1530
+ `Invalid terminalBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`,
1531
+ void 0,
1532
+ {
1533
+ field: "terminalBuffer.maxChars",
1534
+ value: maxChars,
1535
+ constraint: "non-negative-safe-integer"
1536
+ }
1381
1537
  );
1382
1538
  }
1383
1539
  return {
1384
1540
  maxLines: brandMaxLines(maxLines),
1385
- maxChars: brandMaxChars(maxChars)
1541
+ maxChars: brandMaxChars(maxChars),
1542
+ stripAnsi: merged.stripAnsi
1386
1543
  };
1387
1544
  }
1388
1545
  function resolveLineBufferOptions(options) {
@@ -1394,7 +1551,13 @@ function resolveLineBufferOptions(options) {
1394
1551
  if (!Number.isSafeInteger(maxChars) || maxChars < 0) {
1395
1552
  throw new SerialError(
1396
1553
  SerialErrorCode.INVALID_LINE_BUFFER_OPTIONS,
1397
- `Invalid lineBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`
1554
+ `Invalid lineBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`,
1555
+ void 0,
1556
+ {
1557
+ field: "lineBuffer.maxChars",
1558
+ value: maxChars,
1559
+ constraint: "non-negative-safe-integer"
1560
+ }
1398
1561
  );
1399
1562
  }
1400
1563
  return {
@@ -1426,7 +1589,13 @@ function resolveConnectionOptions(options) {
1426
1589
  if (!Number.isSafeInteger(baudRate) || baudRate <= 0) {
1427
1590
  throw new SerialError(
1428
1591
  SerialErrorCode.INVALID_CONNECTION_OPTIONS,
1429
- `Invalid baudRate: ${baudRate}. Must be a safe integer > 0.`
1592
+ `Invalid baudRate: ${baudRate}. Must be a safe integer > 0.`,
1593
+ void 0,
1594
+ {
1595
+ field: "baudRate",
1596
+ value: baudRate,
1597
+ constraint: "positive-safe-integer"
1598
+ }
1430
1599
  );
1431
1600
  }
1432
1601
  return {
@@ -1533,9 +1702,6 @@ function createSerialSession(options) {
1533
1702
  getPortInfo() {
1534
1703
  return portInfoSubject.getValue();
1535
1704
  },
1536
- getCurrentPort() {
1537
- return getRuntimePort(controller.runtime);
1538
- },
1539
1705
  errors$,
1540
1706
  receive$,
1541
1707
  terminalText$,
@@ -1543,6 +1709,11 @@ function createSerialSession(options) {
1543
1709
  lines$
1544
1710
  };
1545
1711
  }
1712
+
1713
+ // src/session/is-connected-session-state.ts
1714
+ function isConnectedSessionState(state) {
1715
+ return state.status === SerialSessionStatus.Connected;
1716
+ }
1546
1717
  export {
1547
1718
  DEFAULT_LINE_BUFFER_OPTIONS,
1548
1719
  DEFAULT_TERMINAL_BUFFER_OPTIONS,
@@ -1554,6 +1725,7 @@ export {
1554
1725
  assertNever,
1555
1726
  createSerialSession,
1556
1727
  createTerminalBuffer,
1728
+ isConnectedSessionState,
1557
1729
  resolveSerialSessionOptions
1558
1730
  };
1559
1731
  //# sourceMappingURL=index.mjs.map