@gurezo/web-serial-rxjs 2.0.3 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +42 -0
- package/README.md +42 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +116 -9
- package/dist/index.mjs +116 -9
- package/dist/index.mjs.map +4 -4
- package/dist/session/create-serial-session.d.ts.map +1 -1
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/serial-session-options.d.ts +31 -1
- package/dist/session/serial-session-options.d.ts.map +1 -1
- package/dist/session/serial-session.d.ts +55 -8
- package/dist/session/serial-session.d.ts.map +1 -1
- package/dist/terminal/create-terminal-buffer.d.ts +30 -0
- package/dist/terminal/create-terminal-buffer.d.ts.map +1 -0
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -12,6 +12,48 @@ Web Serial API は **Chromium 系**のブラウザ(**Chrome** 89+、**Edge** 8
|
|
|
12
12
|
|
|
13
13
|
`connect$` の前の feature detection には `SerialSession.isBrowserSupported()`(同期的に `boolean`)を使います。
|
|
14
14
|
|
|
15
|
+
## 接続中のポート情報(デバイス識別)
|
|
16
|
+
|
|
17
|
+
`connect$` 成功後は `getPortInfo()` または `portInfo$` で `SerialPort.getInfo()` と同じスナップショット(例: 利用可能な場合の USB ベンダ/プロダクト ID)を取得できます。未接続時は `null` です。`getCurrentPort()` は接続中のみ内部の `SerialPort` を返します。`close()` は直接呼ばず、ライフサイクルは `disconnect$` に任せてください。
|
|
18
|
+
|
|
19
|
+
## 受信の replay(`receive$` と `receiveReplay$`)
|
|
20
|
+
|
|
21
|
+
`receive$` は **non-replay** のままです。購読後に届くチャンクだけが見えます。接続ごとに直近 *N* 件のデコード済みテキスト**チャンク**(read pump の 1 回の `onChunk` あたり 1 件。文字数ではありません)を遅延購読者にも渡したい場合は、`createSerialSession` に `receiveReplay: { enabled: true, bufferSize: 512 }` を指定し、`receiveReplay$` を購読します。`bufferSize` を大きくするとメモリ負荷が増えます。receive replay が **無効**(既定)のとき、`receiveReplay$` は `receive$` と同じ hot ストリームです。`lines$` の行分割に replay は付きません。生チャンクの `receiveReplay$` のみが対象です。
|
|
22
|
+
|
|
23
|
+
## `receive$` と `lines$`
|
|
24
|
+
|
|
25
|
+
購読するストリームはユースケースに合わせて選んでください。**`lines$`** をターミナル表示に使うと `\r` が失われ再描画できず、シェル出力(例: `ls -la` の整形)が崩れます。詳細は [概要](https://github.com/gurezo/web-serial-rxjs/blob/main/packages/web-serial-rxjs/docs/OVERVIEW.ja.md) を参照してください。
|
|
26
|
+
|
|
27
|
+
### `receive$`(raw ストリーム)
|
|
28
|
+
|
|
29
|
+
- UTF-8 の**デコードチャンク**をそのまま届く順に(行揃えではありません)。
|
|
30
|
+
- `\r` や行途中の断片など制御文字も保持します。
|
|
31
|
+
- **ターミナル表示**、**プロンプト判定**、自前の**バッファ**/スクロールバック、raw を前提にした処理に使います。
|
|
32
|
+
|
|
33
|
+
### `lines$`(行単位のイベント)
|
|
34
|
+
|
|
35
|
+
- **完了した行**だけを emit(`\n` / `\r\n` / 実装どおり内部の `\r`)。
|
|
36
|
+
- **ログ出力**、**行単位の解析**、改行フレームのプロトコル向き。
|
|
37
|
+
- 対話 CLI の**画面ミラーには不向き**です(`\r` での上書き表示の意味が落ちます)。
|
|
38
|
+
|
|
39
|
+
### 避ける/推奨する書き方
|
|
40
|
+
|
|
41
|
+
画面にそのまま足していく用途で **`lines$`** の文字列を連結すると、再描画情報が欠けレイアウトが崩れます。**避けてください**。
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
session.lines$.subscribe((line) => {
|
|
45
|
+
output += line + '\n';
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
ミラーやシェル風バッファには **`receive$`** のチャンクを連結します。**推奨**です。
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
session.receive$.subscribe((chunk) => {
|
|
53
|
+
output += chunk;
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
15
57
|
## インストール
|
|
16
58
|
|
|
17
59
|
```bash
|
package/README.md
CHANGED
|
@@ -12,6 +12,48 @@ The Web Serial API is only available in **Chromium-based** browsers: **Chrome**
|
|
|
12
12
|
|
|
13
13
|
`SerialSession.isBrowserSupported()` returns a synchronous `boolean` for feature detection before `connect$`.
|
|
14
14
|
|
|
15
|
+
## Port info (device identification)
|
|
16
|
+
|
|
17
|
+
After a successful `connect$`, use `getPortInfo()` or subscribe to `portInfo$` for the `SerialPort.getInfo()` snapshot (e.g. USB vendor/product IDs when the device exposes them). Both yield `null` when disconnected. `getCurrentPort()` returns the underlying `SerialPort` while connected; do not call `close()` on it—use `disconnect$` for lifecycle.
|
|
18
|
+
|
|
19
|
+
## Receive replay (`receive$` vs `receiveReplay$`)
|
|
20
|
+
|
|
21
|
+
`receive$` is **non-replay**: late subscribers only see chunks emitted after they subscribe. To retain the last *N* decoded text **chunks** per open connection (same bytes as `receive$`, e.g. for boot logs), pass `receiveReplay: { enabled: true, bufferSize: 512 }` to `createSerialSession` and subscribe to `receiveReplay$`. Larger `bufferSize` uses more memory. When receive replay is **off** (default), `receiveReplay$` is the same hot stream as `receive$`. This option does not add replay to `lines$`—only raw decoder chunks on `receiveReplay$`.
|
|
22
|
+
|
|
23
|
+
## `receive$` vs `lines$`
|
|
24
|
+
|
|
25
|
+
Pick the stream that matches your use case. Using **`lines$`** for a terminal mirror drops `\r` and redraw behaviour, which breaks shells and tools that rely on carriage-return updates ([overview](https://github.com/gurezo/web-serial-rxjs/blob/main/packages/web-serial-rxjs/docs/OVERVIEW.md)).
|
|
26
|
+
|
|
27
|
+
### `receive$` (raw stream)
|
|
28
|
+
|
|
29
|
+
- UTF-8 **decoder chunks** as they arrive—not line-aligned.
|
|
30
|
+
- Preserves `\r`, partial lines, and other control characters.
|
|
31
|
+
- Use for: **terminal display**, **prompt detection**, **buffering** / scrollback you control, and other **raw-stream** handling.
|
|
32
|
+
|
|
33
|
+
### `lines$` (line-delimited events)
|
|
34
|
+
|
|
35
|
+
- Emits **complete lines** (`\n`, `\r\n`, interior `\r` per implementation).
|
|
36
|
+
- Use for **logs**, **structured parsing**, and protocols framed on newlines.
|
|
37
|
+
- **Not suitable** for mirroring interactive CLI output when peers use `\r` for in-place redraws—you lose those semantics.
|
|
38
|
+
|
|
39
|
+
### Avoid / Prefer
|
|
40
|
+
|
|
41
|
+
**Avoid**—appending **`lines$`** strings for a terminal-style view hides redraws and corrupts layouts.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
session.lines$.subscribe((line) => {
|
|
45
|
+
output += line + '\n';
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Prefer**—concatenate **chunks** from **`receive$`** for mirrors and shell-style buffers.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
session.receive$.subscribe((chunk) => {
|
|
53
|
+
output += chunk;
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
15
57
|
## Installation
|
|
16
58
|
|
|
17
59
|
```bash
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* state, read loops, or write queues themselves.
|
|
15
15
|
*
|
|
16
16
|
* - {@link createSerialSession} - factory for a {@link SerialSession}
|
|
17
|
+
* - {@link createTerminalBuffer} - terminal-style display text from {@link SerialSession.receive$}
|
|
17
18
|
* - {@link SerialSession} - the runtime interface
|
|
18
19
|
* - {@link SerialSessionOptions} - connection options
|
|
19
20
|
* - {@link SerialSessionState} - `state$` payload values (const + type)
|
|
@@ -49,7 +50,9 @@
|
|
|
49
50
|
* ```
|
|
50
51
|
*/
|
|
51
52
|
export { createSerialSession, SerialSessionState } from './session';
|
|
52
|
-
export type { SerialSession, SerialSessionOptions } from './session';
|
|
53
|
+
export type { SerialSession, SerialSessionOptions, SerialSessionReceiveReplayOptions, } from './session';
|
|
53
54
|
export { SerialError } from './errors/serial-error';
|
|
54
55
|
export { SerialErrorCode } from './errors/serial-error-code';
|
|
56
|
+
export { createTerminalBuffer } from './terminal/create-terminal-buffer';
|
|
57
|
+
export type { TerminalBuffer } from './terminal/create-terminal-buffer';
|
|
55
58
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACpE,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,iCAAiC,GAClC,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,YAAY,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
// packages/web-serial-rxjs/src/session/create-serial-session.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BehaviorSubject as BehaviorSubject2,
|
|
4
|
+
distinctUntilChanged,
|
|
5
|
+
map,
|
|
6
|
+
Observable as Observable3,
|
|
7
|
+
ReplaySubject,
|
|
8
|
+
share,
|
|
9
|
+
Subject,
|
|
10
|
+
switchMap
|
|
11
|
+
} from "rxjs";
|
|
3
12
|
|
|
4
13
|
// packages/web-serial-rxjs/src/errors/serial-error-code.ts
|
|
5
14
|
var SerialErrorCode = /* @__PURE__ */ ((SerialErrorCode2) => {
|
|
@@ -288,6 +297,10 @@ function createSendQueue() {
|
|
|
288
297
|
}
|
|
289
298
|
|
|
290
299
|
// packages/web-serial-rxjs/src/session/serial-session-options.ts
|
|
300
|
+
var DEFAULT_RECEIVE_REPLAY = {
|
|
301
|
+
enabled: false,
|
|
302
|
+
bufferSize: 512
|
|
303
|
+
};
|
|
291
304
|
var DEFAULT_SERIAL_SESSION_OPTIONS = {
|
|
292
305
|
baudRate: 9600,
|
|
293
306
|
dataBits: 8,
|
|
@@ -295,7 +308,8 @@ var DEFAULT_SERIAL_SESSION_OPTIONS = {
|
|
|
295
308
|
parity: "none",
|
|
296
309
|
bufferSize: 255,
|
|
297
310
|
flowControl: "none",
|
|
298
|
-
filters: void 0
|
|
311
|
+
filters: void 0,
|
|
312
|
+
receiveReplay: { ...DEFAULT_RECEIVE_REPLAY }
|
|
299
313
|
};
|
|
300
314
|
|
|
301
315
|
// packages/web-serial-rxjs/src/session/serial-session-state.ts
|
|
@@ -374,7 +388,11 @@ function createSerialSession(options) {
|
|
|
374
388
|
const resolvedOptions = {
|
|
375
389
|
...DEFAULT_SERIAL_SESSION_OPTIONS,
|
|
376
390
|
...options,
|
|
377
|
-
filters: options?.filters
|
|
391
|
+
filters: options?.filters,
|
|
392
|
+
receiveReplay: {
|
|
393
|
+
...DEFAULT_SERIAL_SESSION_OPTIONS.receiveReplay,
|
|
394
|
+
...options?.receiveReplay
|
|
395
|
+
}
|
|
378
396
|
};
|
|
379
397
|
const supported = hasWebSerialSupport();
|
|
380
398
|
const machine = new SessionStateMachine(
|
|
@@ -393,9 +411,40 @@ function createSerialSession(options) {
|
|
|
393
411
|
map((state) => state === SerialSessionState.Connected),
|
|
394
412
|
distinctUntilChanged()
|
|
395
413
|
);
|
|
414
|
+
const portInfoSubject = new BehaviorSubject2(null);
|
|
415
|
+
const portInfo$ = portInfoSubject.asObservable();
|
|
416
|
+
const receiveReplayStream$ = resolvedOptions.receiveReplay.enabled ? new BehaviorSubject2(receive$) : null;
|
|
417
|
+
let activeReceiveReplay = null;
|
|
418
|
+
const clearLiveReceiveReplay = () => {
|
|
419
|
+
if (receiveReplayStream$) {
|
|
420
|
+
if (activeReceiveReplay) {
|
|
421
|
+
activeReceiveReplay.complete();
|
|
422
|
+
activeReceiveReplay = null;
|
|
423
|
+
}
|
|
424
|
+
receiveReplayStream$.next(receive$);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
const startLiveReceiveReplay = () => {
|
|
428
|
+
if (!receiveReplayStream$) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (activeReceiveReplay) {
|
|
432
|
+
activeReceiveReplay.complete();
|
|
433
|
+
activeReceiveReplay = null;
|
|
434
|
+
}
|
|
435
|
+
const rs = new ReplaySubject(resolvedOptions.receiveReplay.bufferSize);
|
|
436
|
+
activeReceiveReplay = rs;
|
|
437
|
+
receiveReplayStream$.next(rs.asObservable());
|
|
438
|
+
};
|
|
439
|
+
const receiveReplay$ = receiveReplayStream$ ? receiveReplayStream$.pipe(switchMap((inner) => inner), share()) : receive$;
|
|
396
440
|
let activePort = null;
|
|
397
441
|
let activePump = null;
|
|
442
|
+
const setActivePort = (port) => {
|
|
443
|
+
activePort = port;
|
|
444
|
+
portInfoSubject.next(port ? port.getInfo() : null);
|
|
445
|
+
};
|
|
398
446
|
const teardownPump = async () => {
|
|
447
|
+
clearLiveReceiveReplay();
|
|
399
448
|
const pump = activePump;
|
|
400
449
|
activePump = null;
|
|
401
450
|
lineBuffer.clear();
|
|
@@ -419,7 +468,7 @@ function createSerialSession(options) {
|
|
|
419
468
|
machine.toError();
|
|
420
469
|
sendQueue.clear();
|
|
421
470
|
const portToClose = activePort;
|
|
422
|
-
|
|
471
|
+
setActivePort(null);
|
|
423
472
|
void teardownPump().then(() => closePortSafely(portToClose));
|
|
424
473
|
}
|
|
425
474
|
return serialError;
|
|
@@ -493,7 +542,6 @@ function createSerialSession(options) {
|
|
|
493
542
|
if (selectedPort) {
|
|
494
543
|
await closePortSafely(selectedPort);
|
|
495
544
|
}
|
|
496
|
-
activePort = null;
|
|
497
545
|
const serialError = reportError(error, "fatal", {
|
|
498
546
|
fallbackCode: "PORT_OPEN_FAILED" /* PORT_OPEN_FAILED */,
|
|
499
547
|
messagePrefix: "Failed to open port"
|
|
@@ -507,11 +555,17 @@ function createSerialSession(options) {
|
|
|
507
555
|
await closePortSafely(selectedPort);
|
|
508
556
|
return;
|
|
509
557
|
}
|
|
510
|
-
|
|
558
|
+
setActivePort(selectedPort);
|
|
511
559
|
lineBuffer.clear();
|
|
560
|
+
if (resolvedOptions.receiveReplay.enabled) {
|
|
561
|
+
startLiveReceiveReplay();
|
|
562
|
+
}
|
|
512
563
|
activePump = createReadPump(selectedPort, {
|
|
513
564
|
onChunk: (text) => {
|
|
514
565
|
receiveSubject.next(text);
|
|
566
|
+
if (activeReceiveReplay) {
|
|
567
|
+
activeReceiveReplay.next(text);
|
|
568
|
+
}
|
|
515
569
|
for (const line of lineBuffer.feed(text)) {
|
|
516
570
|
linesSubject.next(line);
|
|
517
571
|
}
|
|
@@ -563,7 +617,7 @@ function createSerialSession(options) {
|
|
|
563
617
|
try {
|
|
564
618
|
await portToClose.close();
|
|
565
619
|
} catch (error) {
|
|
566
|
-
|
|
620
|
+
setActivePort(null);
|
|
567
621
|
const serialError = reportError(error, "fatal", {
|
|
568
622
|
fallbackCode: "CONNECTION_LOST" /* CONNECTION_LOST */,
|
|
569
623
|
messagePrefix: "Failed to close port"
|
|
@@ -572,7 +626,7 @@ function createSerialSession(options) {
|
|
|
572
626
|
return;
|
|
573
627
|
}
|
|
574
628
|
}
|
|
575
|
-
|
|
629
|
+
setActivePort(null);
|
|
576
630
|
machine.toIdle();
|
|
577
631
|
subscriber.next();
|
|
578
632
|
subscriber.complete();
|
|
@@ -602,14 +656,67 @@ function createSerialSession(options) {
|
|
|
602
656
|
},
|
|
603
657
|
state$: machine.state$,
|
|
604
658
|
isConnected$,
|
|
659
|
+
portInfo$,
|
|
660
|
+
getPortInfo() {
|
|
661
|
+
return portInfoSubject.getValue();
|
|
662
|
+
},
|
|
663
|
+
getCurrentPort() {
|
|
664
|
+
return activePort;
|
|
665
|
+
},
|
|
605
666
|
errors$,
|
|
606
667
|
receive$,
|
|
668
|
+
receiveReplay$,
|
|
607
669
|
lines$
|
|
608
670
|
};
|
|
609
671
|
}
|
|
672
|
+
|
|
673
|
+
// packages/web-serial-rxjs/src/terminal/create-terminal-buffer.ts
|
|
674
|
+
import { map as map2, scan, shareReplay } from "rxjs";
|
|
675
|
+
function applyTerminalChunk(state, chunk) {
|
|
676
|
+
let { completed, currentLine } = state;
|
|
677
|
+
const len = chunk.length;
|
|
678
|
+
for (let i = 0; i < len; i++) {
|
|
679
|
+
const c = chunk.charAt(i);
|
|
680
|
+
if (c === "\r") {
|
|
681
|
+
const next = i + 1 < len ? chunk.charAt(i + 1) : "";
|
|
682
|
+
if (next === "\n") {
|
|
683
|
+
completed += currentLine + "\n";
|
|
684
|
+
currentLine = "";
|
|
685
|
+
i++;
|
|
686
|
+
} else {
|
|
687
|
+
currentLine = "";
|
|
688
|
+
}
|
|
689
|
+
} else if (c === "\n") {
|
|
690
|
+
completed += currentLine + "\n";
|
|
691
|
+
currentLine = "";
|
|
692
|
+
} else {
|
|
693
|
+
currentLine += c;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return { completed, currentLine };
|
|
697
|
+
}
|
|
698
|
+
function terminalDisplayText(state) {
|
|
699
|
+
return state.completed + state.currentLine;
|
|
700
|
+
}
|
|
701
|
+
var initialTerminalState = {
|
|
702
|
+
completed: "",
|
|
703
|
+
currentLine: ""
|
|
704
|
+
};
|
|
705
|
+
function createTerminalBuffer(receive$) {
|
|
706
|
+
const text$ = receive$.pipe(
|
|
707
|
+
scan(
|
|
708
|
+
(state, chunk) => applyTerminalChunk(state, chunk),
|
|
709
|
+
initialTerminalState
|
|
710
|
+
),
|
|
711
|
+
map2(terminalDisplayText),
|
|
712
|
+
shareReplay({ bufferSize: 1, refCount: true })
|
|
713
|
+
);
|
|
714
|
+
return { text$ };
|
|
715
|
+
}
|
|
610
716
|
export {
|
|
611
717
|
SerialError,
|
|
612
718
|
SerialErrorCode,
|
|
613
719
|
SerialSessionState,
|
|
614
|
-
createSerialSession
|
|
720
|
+
createSerialSession,
|
|
721
|
+
createTerminalBuffer
|
|
615
722
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
// src/session/create-serial-session.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BehaviorSubject as BehaviorSubject2,
|
|
4
|
+
distinctUntilChanged,
|
|
5
|
+
map,
|
|
6
|
+
Observable as Observable3,
|
|
7
|
+
ReplaySubject,
|
|
8
|
+
share,
|
|
9
|
+
Subject,
|
|
10
|
+
switchMap
|
|
11
|
+
} from "rxjs";
|
|
3
12
|
|
|
4
13
|
// src/errors/serial-error-code.ts
|
|
5
14
|
var SerialErrorCode = /* @__PURE__ */ ((SerialErrorCode2) => {
|
|
@@ -288,6 +297,10 @@ function createSendQueue() {
|
|
|
288
297
|
}
|
|
289
298
|
|
|
290
299
|
// src/session/serial-session-options.ts
|
|
300
|
+
var DEFAULT_RECEIVE_REPLAY = {
|
|
301
|
+
enabled: false,
|
|
302
|
+
bufferSize: 512
|
|
303
|
+
};
|
|
291
304
|
var DEFAULT_SERIAL_SESSION_OPTIONS = {
|
|
292
305
|
baudRate: 9600,
|
|
293
306
|
dataBits: 8,
|
|
@@ -295,7 +308,8 @@ var DEFAULT_SERIAL_SESSION_OPTIONS = {
|
|
|
295
308
|
parity: "none",
|
|
296
309
|
bufferSize: 255,
|
|
297
310
|
flowControl: "none",
|
|
298
|
-
filters: void 0
|
|
311
|
+
filters: void 0,
|
|
312
|
+
receiveReplay: { ...DEFAULT_RECEIVE_REPLAY }
|
|
299
313
|
};
|
|
300
314
|
|
|
301
315
|
// src/session/serial-session-state.ts
|
|
@@ -374,7 +388,11 @@ function createSerialSession(options) {
|
|
|
374
388
|
const resolvedOptions = {
|
|
375
389
|
...DEFAULT_SERIAL_SESSION_OPTIONS,
|
|
376
390
|
...options,
|
|
377
|
-
filters: options?.filters
|
|
391
|
+
filters: options?.filters,
|
|
392
|
+
receiveReplay: {
|
|
393
|
+
...DEFAULT_SERIAL_SESSION_OPTIONS.receiveReplay,
|
|
394
|
+
...options?.receiveReplay
|
|
395
|
+
}
|
|
378
396
|
};
|
|
379
397
|
const supported = hasWebSerialSupport();
|
|
380
398
|
const machine = new SessionStateMachine(
|
|
@@ -393,9 +411,40 @@ function createSerialSession(options) {
|
|
|
393
411
|
map((state) => state === SerialSessionState.Connected),
|
|
394
412
|
distinctUntilChanged()
|
|
395
413
|
);
|
|
414
|
+
const portInfoSubject = new BehaviorSubject2(null);
|
|
415
|
+
const portInfo$ = portInfoSubject.asObservable();
|
|
416
|
+
const receiveReplayStream$ = resolvedOptions.receiveReplay.enabled ? new BehaviorSubject2(receive$) : null;
|
|
417
|
+
let activeReceiveReplay = null;
|
|
418
|
+
const clearLiveReceiveReplay = () => {
|
|
419
|
+
if (receiveReplayStream$) {
|
|
420
|
+
if (activeReceiveReplay) {
|
|
421
|
+
activeReceiveReplay.complete();
|
|
422
|
+
activeReceiveReplay = null;
|
|
423
|
+
}
|
|
424
|
+
receiveReplayStream$.next(receive$);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
const startLiveReceiveReplay = () => {
|
|
428
|
+
if (!receiveReplayStream$) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (activeReceiveReplay) {
|
|
432
|
+
activeReceiveReplay.complete();
|
|
433
|
+
activeReceiveReplay = null;
|
|
434
|
+
}
|
|
435
|
+
const rs = new ReplaySubject(resolvedOptions.receiveReplay.bufferSize);
|
|
436
|
+
activeReceiveReplay = rs;
|
|
437
|
+
receiveReplayStream$.next(rs.asObservable());
|
|
438
|
+
};
|
|
439
|
+
const receiveReplay$ = receiveReplayStream$ ? receiveReplayStream$.pipe(switchMap((inner) => inner), share()) : receive$;
|
|
396
440
|
let activePort = null;
|
|
397
441
|
let activePump = null;
|
|
442
|
+
const setActivePort = (port) => {
|
|
443
|
+
activePort = port;
|
|
444
|
+
portInfoSubject.next(port ? port.getInfo() : null);
|
|
445
|
+
};
|
|
398
446
|
const teardownPump = async () => {
|
|
447
|
+
clearLiveReceiveReplay();
|
|
399
448
|
const pump = activePump;
|
|
400
449
|
activePump = null;
|
|
401
450
|
lineBuffer.clear();
|
|
@@ -419,7 +468,7 @@ function createSerialSession(options) {
|
|
|
419
468
|
machine.toError();
|
|
420
469
|
sendQueue.clear();
|
|
421
470
|
const portToClose = activePort;
|
|
422
|
-
|
|
471
|
+
setActivePort(null);
|
|
423
472
|
void teardownPump().then(() => closePortSafely(portToClose));
|
|
424
473
|
}
|
|
425
474
|
return serialError;
|
|
@@ -493,7 +542,6 @@ function createSerialSession(options) {
|
|
|
493
542
|
if (selectedPort) {
|
|
494
543
|
await closePortSafely(selectedPort);
|
|
495
544
|
}
|
|
496
|
-
activePort = null;
|
|
497
545
|
const serialError = reportError(error, "fatal", {
|
|
498
546
|
fallbackCode: "PORT_OPEN_FAILED" /* PORT_OPEN_FAILED */,
|
|
499
547
|
messagePrefix: "Failed to open port"
|
|
@@ -507,11 +555,17 @@ function createSerialSession(options) {
|
|
|
507
555
|
await closePortSafely(selectedPort);
|
|
508
556
|
return;
|
|
509
557
|
}
|
|
510
|
-
|
|
558
|
+
setActivePort(selectedPort);
|
|
511
559
|
lineBuffer.clear();
|
|
560
|
+
if (resolvedOptions.receiveReplay.enabled) {
|
|
561
|
+
startLiveReceiveReplay();
|
|
562
|
+
}
|
|
512
563
|
activePump = createReadPump(selectedPort, {
|
|
513
564
|
onChunk: (text) => {
|
|
514
565
|
receiveSubject.next(text);
|
|
566
|
+
if (activeReceiveReplay) {
|
|
567
|
+
activeReceiveReplay.next(text);
|
|
568
|
+
}
|
|
515
569
|
for (const line of lineBuffer.feed(text)) {
|
|
516
570
|
linesSubject.next(line);
|
|
517
571
|
}
|
|
@@ -563,7 +617,7 @@ function createSerialSession(options) {
|
|
|
563
617
|
try {
|
|
564
618
|
await portToClose.close();
|
|
565
619
|
} catch (error) {
|
|
566
|
-
|
|
620
|
+
setActivePort(null);
|
|
567
621
|
const serialError = reportError(error, "fatal", {
|
|
568
622
|
fallbackCode: "CONNECTION_LOST" /* CONNECTION_LOST */,
|
|
569
623
|
messagePrefix: "Failed to close port"
|
|
@@ -572,7 +626,7 @@ function createSerialSession(options) {
|
|
|
572
626
|
return;
|
|
573
627
|
}
|
|
574
628
|
}
|
|
575
|
-
|
|
629
|
+
setActivePort(null);
|
|
576
630
|
machine.toIdle();
|
|
577
631
|
subscriber.next();
|
|
578
632
|
subscriber.complete();
|
|
@@ -602,15 +656,68 @@ function createSerialSession(options) {
|
|
|
602
656
|
},
|
|
603
657
|
state$: machine.state$,
|
|
604
658
|
isConnected$,
|
|
659
|
+
portInfo$,
|
|
660
|
+
getPortInfo() {
|
|
661
|
+
return portInfoSubject.getValue();
|
|
662
|
+
},
|
|
663
|
+
getCurrentPort() {
|
|
664
|
+
return activePort;
|
|
665
|
+
},
|
|
605
666
|
errors$,
|
|
606
667
|
receive$,
|
|
668
|
+
receiveReplay$,
|
|
607
669
|
lines$
|
|
608
670
|
};
|
|
609
671
|
}
|
|
672
|
+
|
|
673
|
+
// src/terminal/create-terminal-buffer.ts
|
|
674
|
+
import { map as map2, scan, shareReplay } from "rxjs";
|
|
675
|
+
function applyTerminalChunk(state, chunk) {
|
|
676
|
+
let { completed, currentLine } = state;
|
|
677
|
+
const len = chunk.length;
|
|
678
|
+
for (let i = 0; i < len; i++) {
|
|
679
|
+
const c = chunk.charAt(i);
|
|
680
|
+
if (c === "\r") {
|
|
681
|
+
const next = i + 1 < len ? chunk.charAt(i + 1) : "";
|
|
682
|
+
if (next === "\n") {
|
|
683
|
+
completed += currentLine + "\n";
|
|
684
|
+
currentLine = "";
|
|
685
|
+
i++;
|
|
686
|
+
} else {
|
|
687
|
+
currentLine = "";
|
|
688
|
+
}
|
|
689
|
+
} else if (c === "\n") {
|
|
690
|
+
completed += currentLine + "\n";
|
|
691
|
+
currentLine = "";
|
|
692
|
+
} else {
|
|
693
|
+
currentLine += c;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return { completed, currentLine };
|
|
697
|
+
}
|
|
698
|
+
function terminalDisplayText(state) {
|
|
699
|
+
return state.completed + state.currentLine;
|
|
700
|
+
}
|
|
701
|
+
var initialTerminalState = {
|
|
702
|
+
completed: "",
|
|
703
|
+
currentLine: ""
|
|
704
|
+
};
|
|
705
|
+
function createTerminalBuffer(receive$) {
|
|
706
|
+
const text$ = receive$.pipe(
|
|
707
|
+
scan(
|
|
708
|
+
(state, chunk) => applyTerminalChunk(state, chunk),
|
|
709
|
+
initialTerminalState
|
|
710
|
+
),
|
|
711
|
+
map2(terminalDisplayText),
|
|
712
|
+
shareReplay({ bufferSize: 1, refCount: true })
|
|
713
|
+
);
|
|
714
|
+
return { text$ };
|
|
715
|
+
}
|
|
610
716
|
export {
|
|
611
717
|
SerialError,
|
|
612
718
|
SerialErrorCode,
|
|
613
719
|
SerialSessionState,
|
|
614
|
-
createSerialSession
|
|
720
|
+
createSerialSession,
|
|
721
|
+
createTerminalBuffer
|
|
615
722
|
};
|
|
616
723
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/session/create-serial-session.ts", "../src/errors/serial-error-code.ts", "../src/errors/serial-error.ts", "../src/session/internal/build-request-options.ts", "../src/session/internal/has-web-serial-support.ts", "../src/session/internal/line-buffer.ts", "../src/session/normalize-serial-error.ts", "../src/session/read-pump.ts", "../src/session/send-queue.ts", "../src/session/serial-session-options.ts", "../src/session/serial-session-state.ts", "../src/session/session-state-machine.ts"],
|
|
4
|
-
"sourcesContent": ["import { distinctUntilChanged, map, Observable, Subject } from 'rxjs';\nimport { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport { buildRequestOptions } from './internal/build-request-options';\nimport { hasWebSerialSupport } from './internal/has-web-serial-support';\nimport { createLineBuffer } from './internal/line-buffer';\nimport {\n normalizeSerialError,\n type NormalizeSerialErrorOptions,\n} from './normalize-serial-error';\nimport { createReadPump, type ReadPump } from './read-pump';\nimport { createSendQueue } from './send-queue';\nimport type { SerialSession } from './serial-session';\nimport {\n DEFAULT_SERIAL_SESSION_OPTIONS,\n type SerialSessionOptions,\n} from './serial-session-options';\nimport { SerialSessionState } from './serial-session-state';\nimport { SessionStateMachine } from './session-state-machine';\n\n/**\n * Internal error classification used by the single `reportError` entry\n * point. `'fatal'` errors drive `state$` into `'error'` and tear down\n * the live session (pump stop + port close); `'non-fatal'` errors are\n * only multiplexed on `errors$` without mutating session state - this\n * matches the Issue #199 design note that write failures must not\n * implicitly disconnect the session.\n *\n * @internal\n */\ntype ReportErrorSeverity = 'fatal' | 'non-fatal';\n\n/**\n * Create a v2 {@link SerialSession}.\n *\n * This release wires the internal read pump (#202) and the internal send\n * queue (#203) into the session so that `connect$`, `disconnect$`,\n * `receive$`, and `send$` all operate end-to-end. Error handling is\n * centralised through a single `reportError` helper (#204) so every\n * failure path normalises through {@link normalizeSerialError} and emits\n * on the one `errors$` channel.\n *\n * Key behaviors:\n *\n * - `isBrowserSupported()` returns whether `navigator.serial` is available.\n * - `state$` replays the current lifecycle state driven by\n * {@link SessionStateMachine}.\n * - `connect$()` opens a user-selected port, starts the internal read pump,\n * and transitions `idle -> connecting -> connected`.\n * - `disconnect$()` stops the read pump, closes the port, and transitions\n * `connected -> disconnecting -> idle`.\n * - `receive$` emits UTF-8 decoded text chunks pushed by the pump. It is\n * **not** subscription-lazy - the pump is started by `connect$` and\n * decoded text is multicast to all subscribers; late subscribers see only\n * new data.\n * - `lines$` emits the same decoded stream split into line-terminated\n * segments (`\\n`, `\\r\\n`); a trailing line without a terminator is\n * buffered. It is also not subscription-lazy relative to the pump.\n * - `send$` enqueues each payload on an internal FIFO queue so concurrent\n * subscribers are written to the port in call order. String payloads are\n * UTF-8 encoded through a shared `TextEncoder`.\n * - `errors$` multiplexes every {@link SerialError} produced by the\n * session. Connect / read / close failures are treated as fatal and\n * also drive `state$` to `'error'`; write failures are non-fatal and\n * do not mutate `state$` because a real connection loss will be\n * observed by the read pump on the next tick anyway.\n *\n * @param options - Session options. Only `filters` is consulted by\n * `connect$` today (forwarded to `navigator.serial.requestPort`); the\n * remaining fields are passed to `port.open` using defaults when omitted.\n * @returns A {@link SerialSession} instance.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/202 | Issue #202}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/203 | Issue #203}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/204 | Issue #204}\n */\nexport function createSerialSession(\n options?: SerialSessionOptions,\n): SerialSession {\n const resolvedOptions = {\n ...DEFAULT_SERIAL_SESSION_OPTIONS,\n ...options,\n filters: options?.filters,\n };\n\n const supported = hasWebSerialSupport();\n const machine = new SessionStateMachine(\n supported ? SerialSessionState.Idle : SerialSessionState.Unsupported,\n );\n const errorsSubject = new Subject<SerialError>();\n const receiveSubject = new Subject<string>();\n const linesSubject = new Subject<string>();\n const sendQueue = createSendQueue();\n const textEncoder = new TextEncoder();\n const lineBuffer = createLineBuffer();\n\n const errors$ = errorsSubject.asObservable();\n const receive$ = receiveSubject.asObservable();\n const lines$ = linesSubject.asObservable();\n\n const isConnected$ = machine.state$.pipe(\n map((state) => state === SerialSessionState.Connected),\n distinctUntilChanged(),\n );\n\n let activePort: SerialPort | null = null;\n let activePump: ReadPump | null = null;\n\n const teardownPump = async (): Promise<void> => {\n const pump = activePump;\n activePump = null;\n lineBuffer.clear();\n if (pump) {\n await pump.stop();\n }\n };\n\n const closePortSafely = async (port: SerialPort | null): Promise<void> => {\n if (!port) {\n return;\n }\n try {\n await port.close();\n } catch {\n // The read pump may already have errored the stream, which makes\n // close() reject. We ignore it here because disconnect$ has a\n // dedicated error path for close failures initiated by the user.\n }\n };\n\n /**\n * Single entry point for every error that should reach `errors$`.\n *\n * Responsibilities:\n *\n * 1. Normalise the input through {@link normalizeSerialError} so every\n * emission is a well-formed {@link SerialError}.\n * 2. Multiplex the normalised error on `errors$`.\n * 3. For fatal severities, drive `state$` to `'error'`, clear the send\n * queue so pending writes fail fast, and tear down the live pump +\n * port off the hot path.\n *\n * Returning the normalised error keeps call sites terse: they can hand\n * the result straight to `subscriber.error(...)` without re-normalising.\n */\n const reportError = (\n error: unknown,\n severity: ReportErrorSeverity,\n options: NormalizeSerialErrorOptions,\n ): SerialError => {\n const serialError = normalizeSerialError(error, options);\n errorsSubject.next(serialError);\n if (severity === 'fatal') {\n machine.toError();\n sendQueue.clear();\n const portToClose = activePort;\n activePort = null;\n void teardownPump().then(() => closePortSafely(portToClose));\n }\n return serialError;\n };\n\n const writeToPort = async (payload: Uint8Array): Promise<void> => {\n const port = activePort;\n if (machine.current !== SerialSessionState.Connected || !port || !port.writable) {\n throw new SerialError(\n SerialErrorCode.PORT_NOT_OPEN,\n 'Cannot send data while session is not connected',\n );\n }\n const writer = port.writable.getWriter();\n try {\n await writer.write(payload);\n } finally {\n try {\n writer.releaseLock();\n } catch {\n // releaseLock throws when the stream is already errored; the real\n // failure is surfaced through the write() rejection above so we\n // intentionally swallow this secondary error.\n }\n }\n };\n\n return {\n isBrowserSupported(): boolean {\n return hasWebSerialSupport();\n },\n connect$(): Observable<void> {\n return new Observable<void>((subscriber) => {\n if (!hasWebSerialSupport()) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.BROWSER_NOT_SUPPORTED,\n 'Web Serial API is not supported in this environment',\n ),\n 'non-fatal',\n { fallbackCode: SerialErrorCode.BROWSER_NOT_SUPPORTED },\n );\n subscriber.error(error);\n return;\n }\n\n const current = machine.current;\n if (\n current !== SerialSessionState.Idle &&\n current !== SerialSessionState.Error\n ) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.PORT_ALREADY_OPEN,\n `Cannot connect while session state is '${current}'`,\n ),\n 'non-fatal',\n { fallbackCode: SerialErrorCode.PORT_ALREADY_OPEN },\n );\n subscriber.error(error);\n return;\n }\n\n let cancelled = false;\n machine.toConnecting();\n\n const run = async (): Promise<void> => {\n let selectedPort: SerialPort | null = null;\n try {\n selectedPort = await navigator.serial.requestPort(\n buildRequestOptions(resolvedOptions),\n );\n await selectedPort.open({\n baudRate: resolvedOptions.baudRate,\n dataBits: resolvedOptions.dataBits,\n stopBits: resolvedOptions.stopBits,\n parity: resolvedOptions.parity,\n bufferSize: resolvedOptions.bufferSize,\n flowControl: resolvedOptions.flowControl,\n });\n } catch (error) {\n if (selectedPort) {\n await closePortSafely(selectedPort);\n }\n activePort = null;\n const serialError = reportError(error, 'fatal', {\n fallbackCode: SerialErrorCode.PORT_OPEN_FAILED,\n messagePrefix: 'Failed to open port',\n });\n if (!cancelled) {\n subscriber.error(serialError);\n }\n return;\n }\n\n if (cancelled) {\n await closePortSafely(selectedPort);\n return;\n }\n\n activePort = selectedPort;\n lineBuffer.clear();\n activePump = createReadPump(selectedPort, {\n onChunk: (text) => {\n receiveSubject.next(text);\n for (const line of lineBuffer.feed(text)) {\n linesSubject.next(line);\n }\n },\n onError: (pumpError) =>\n reportError(pumpError, 'fatal', {\n fallbackCode: SerialErrorCode.READ_FAILED,\n messagePrefix: 'Read pump failed',\n }),\n });\n activePump.start();\n sendQueue.clear();\n machine.toConnected();\n subscriber.next();\n subscriber.complete();\n };\n\n void run();\n\n return () => {\n cancelled = true;\n };\n });\n },\n disconnect$(): Observable<void> {\n return new Observable<void>((subscriber) => {\n const current = machine.current;\n\n if (\n current === SerialSessionState.Idle ||\n current === SerialSessionState.Unsupported\n ) {\n subscriber.next();\n subscriber.complete();\n return;\n }\n\n if (\n current !== SerialSessionState.Connected &&\n current !== SerialSessionState.Error\n ) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.PORT_NOT_OPEN,\n `Cannot disconnect while session state is '${current}'`,\n ),\n 'non-fatal',\n { fallbackCode: SerialErrorCode.PORT_NOT_OPEN },\n );\n subscriber.error(error);\n return;\n }\n\n machine.toDisconnecting();\n sendQueue.clear();\n const portToClose = activePort;\n\n const run = async (): Promise<void> => {\n try {\n await teardownPump();\n if (portToClose) {\n try {\n await portToClose.close();\n } catch (error) {\n activePort = null;\n const serialError = reportError(error, 'fatal', {\n fallbackCode: SerialErrorCode.CONNECTION_LOST,\n messagePrefix: 'Failed to close port',\n });\n subscriber.error(serialError);\n return;\n }\n }\n activePort = null;\n machine.toIdle();\n subscriber.next();\n subscriber.complete();\n } catch (error) {\n const serialError = reportError(error, 'fatal', {\n fallbackCode: SerialErrorCode.UNKNOWN,\n messagePrefix: 'Unexpected disconnect failure',\n });\n subscriber.error(serialError);\n }\n };\n\n void run();\n });\n },\n send$(data: string | Uint8Array): Observable<void> {\n return sendQueue.enqueue(async () => {\n const payload =\n typeof data === 'string' ? textEncoder.encode(data) : data;\n try {\n await writeToPort(payload);\n } catch (error) {\n throw reportError(error, 'non-fatal', {\n fallbackCode: SerialErrorCode.WRITE_FAILED,\n messagePrefix: 'Failed to write data',\n });\n }\n });\n },\n state$: machine.state$,\n isConnected$,\n errors$,\n receive$,\n lines$,\n };\n}\n", "/**\n * Error codes for serial port operations.\n *\n * These codes identify specific error conditions that can occur when working with\n * serial ports. Each error code corresponds to a specific failure scenario, making\n * it easier to handle errors programmatically.\n *\n * @example\n * ```typescript\n * try {\n * await client.connect().toPromise();\n * } catch (error) {\n * if (error instanceof SerialError) {\n * switch (error.code) {\n * case SerialErrorCode.BROWSER_NOT_SUPPORTED:\n * console.error('Please use a Chromium-based browser');\n * break;\n * case SerialErrorCode.OPERATION_CANCELLED:\n * console.log('User cancelled port selection');\n * break;\n * // ... handle other error codes\n * }\n * }\n * }\n * ```\n */\nexport enum SerialErrorCode {\n /**\n * Browser does not support the Web Serial API.\n *\n * This error occurs when attempting to use serial port functionality in a browser\n * that doesn't support the Web Serial API. Only Chromium-based browsers (Chrome,\n * Edge, Opera) support this API.\n *\n * **Suggested action**: Inform the user to use a supported browser.\n */\n BROWSER_NOT_SUPPORTED = 'BROWSER_NOT_SUPPORTED',\n\n /**\n * Serial port is not available.\n *\n * This error occurs when a requested port cannot be accessed, such as when\n * getting previously granted ports fails or when the port is already in use\n * by another application.\n *\n * **Suggested action**: Check if the port is available or being used by another application.\n */\n PORT_NOT_AVAILABLE = 'PORT_NOT_AVAILABLE',\n\n /**\n * Failed to open the serial port.\n *\n * This error occurs when the port cannot be opened, typically due to incorrect\n * connection parameters, hardware issues, or permission problems.\n *\n * **Suggested action**: Verify connection parameters and check hardware connections.\n */\n PORT_OPEN_FAILED = 'PORT_OPEN_FAILED',\n\n /**\n * Serial port is already open.\n *\n * This error occurs when attempting to open a port that is already connected.\n * Only one connection can be active at a time per SerialClient instance.\n *\n * **Suggested action**: Disconnect the current port before connecting a new one.\n */\n PORT_ALREADY_OPEN = 'PORT_ALREADY_OPEN',\n\n /**\n * Serial port is not open.\n *\n * This error occurs when attempting to read from or write to a port that hasn't\n * been opened yet. The port must be connected before performing I/O operations.\n *\n * **Suggested action**: Call {@link SerialClient.connect} before reading or writing.\n */\n PORT_NOT_OPEN = 'PORT_NOT_OPEN',\n\n /**\n * Failed to read from the serial port.\n *\n * This error occurs when reading data from the port fails, typically due to\n * connection loss, hardware issues, or stream errors.\n *\n * **Suggested action**: Check the connection and hardware, then retry the read operation.\n */\n READ_FAILED = 'READ_FAILED',\n\n /**\n * Failed to write to the serial port.\n *\n * This error occurs when writing data to the port fails, typically due to\n * connection loss, hardware issues, or stream errors.\n *\n * **Suggested action**: Check the connection and hardware, then retry the write operation.\n */\n WRITE_FAILED = 'WRITE_FAILED',\n\n /**\n * Serial port connection was lost.\n *\n * This error occurs when the connection to the serial port is unexpectedly\n * terminated, such as when the device is disconnected or the port is closed\n * by another process.\n *\n * **Suggested action**: Check the physical connection and reconnect if needed.\n */\n CONNECTION_LOST = 'CONNECTION_LOST',\n\n /**\n * Invalid filter options provided.\n *\n * This error occurs when port filter options are invalid, such as when\n * filter values are out of range or missing required fields.\n *\n * **Suggested action**: Verify filter options match the expected format and value ranges.\n */\n INVALID_FILTER_OPTIONS = 'INVALID_FILTER_OPTIONS',\n\n /**\n * Operation was cancelled by the user.\n *\n * This error occurs when the user cancels a port selection dialog or aborts\n * an operation before it completes.\n *\n * **Suggested action**: This is a normal condition - no action required, but you may want\n * to inform the user that the operation was cancelled.\n */\n OPERATION_CANCELLED = 'OPERATION_CANCELLED',\n\n /**\n * Operation timed out before completion.\n *\n * This error occurs when an operation waits for a condition (for example, prompt\n * detection) and the timeout period elapses first.\n */\n OPERATION_TIMEOUT = 'OPERATION_TIMEOUT',\n\n /**\n * Unknown error occurred.\n *\n * This error code is used for errors that don't fit into any other category.\n * The original error details may be available in the error's message or originalError property.\n *\n * **Suggested action**: Check the error message and originalError for more details.\n */\n UNKNOWN = 'UNKNOWN',\n}\n", "import { SerialErrorCode } from './serial-error-code';\n\n// Re-export SerialErrorCode for convenience\nexport { SerialErrorCode };\n\n/**\n * Custom error class for serial port operations.\n *\n * This error class extends the standard Error class and includes additional information\n * about the type of error that occurred. It provides an error code for programmatic\n * error handling and may include the original error that caused the failure.\n *\n * @example\n * ```typescript\n * try {\n * await client.connect().toPromise();\n * } catch (error) {\n * if (error instanceof SerialError) {\n * console.error(`Error code: ${error.code}`);\n * console.error(`Message: ${error.message}`);\n * if (error.originalError) {\n * console.error(`Original error:`, error.originalError);\n * }\n *\n * // Check specific error code\n * if (error.is(SerialErrorCode.BROWSER_NOT_SUPPORTED)) {\n * // Handle browser not supported\n * }\n * }\n * }\n * ```\n */\nexport class SerialError extends Error {\n /**\n * The error code identifying the type of error that occurred.\n *\n * Use this code to programmatically handle specific error conditions.\n *\n * @see {@link SerialErrorCode} for all available error codes\n */\n public readonly code: SerialErrorCode;\n\n /**\n * The original error that caused this SerialError, if available.\n *\n * This property contains the underlying error (e.g., DOMException, TypeError)\n * that was wrapped in this SerialError. It may be undefined if no original error exists.\n */\n public readonly originalError?: Error;\n\n /**\n * Creates a new SerialError instance.\n *\n * @param code - The error code identifying the type of error\n * @param message - A human-readable error message\n * @param originalError - The original error that caused this SerialError, if any\n */\n constructor(code: SerialErrorCode, message: string, originalError?: Error) {\n super(message);\n this.name = 'SerialError';\n this.code = code;\n this.originalError = originalError;\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if ((Error as any).captureStackTrace) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (Error as any).captureStackTrace(this, SerialError);\n }\n }\n\n /**\n * Check if the error matches a specific error code.\n *\n * This is a convenience method for checking the error code without directly\n * comparing the code property.\n *\n * @param code - The error code to check against\n * @returns `true` if this error's code matches the provided code, `false` otherwise\n *\n * @example\n * ```typescript\n * if (error.is(SerialErrorCode.PORT_NOT_OPEN)) {\n * // Handle port not open error\n * }\n * ```\n */\n public is(code: SerialErrorCode): boolean {\n return this.code === code;\n }\n}\n", "import { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\nimport type { SerialSessionOptions } from '../serial-session-options';\n\n/**\n * Build {@link SerialPortRequestOptions} from {@link SerialSessionOptions}.\n *\n * Converts the `filters` field of {@link SerialSessionOptions} into the\n * shape expected by `navigator.serial.requestPort` and validates USB\n * vendor / product identifiers. Returns `undefined` when no filters are\n * supplied so the browser shows all available ports.\n *\n * @param options - The session options supplied by the caller.\n * @returns The request options, or `undefined` when no filters are set.\n * @throws {@link SerialError} with {@link SerialErrorCode.INVALID_FILTER_OPTIONS}\n * when a filter is empty or contains out-of-range IDs.\n *\n * @internal\n */\nexport function buildRequestOptions(\n options?: SerialSessionOptions,\n): SerialPortRequestOptions | undefined {\n if (!options || !options.filters || options.filters.length === 0) {\n return undefined;\n }\n\n for (const filter of options.filters) {\n if (!filter.usbVendorId && !filter.usbProductId) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n 'Filter must have at least usbVendorId or usbProductId',\n );\n }\n\n if (filter.usbVendorId !== undefined) {\n if (\n !Number.isInteger(filter.usbVendorId) ||\n filter.usbVendorId < 0 ||\n filter.usbVendorId > 0xffff\n ) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n `Invalid usbVendorId: ${filter.usbVendorId}. Must be an integer between 0 and 65535.`,\n );\n }\n }\n\n if (filter.usbProductId !== undefined) {\n if (\n !Number.isInteger(filter.usbProductId) ||\n filter.usbProductId < 0 ||\n filter.usbProductId > 0xffff\n ) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n `Invalid usbProductId: ${filter.usbProductId}. Must be an integer between 0 and 65535.`,\n );\n }\n }\n }\n\n return {\n filters: options.filters,\n };\n}\n", "/**\n * Internal feature detection for the Web Serial API.\n *\n * This helper is intentionally kept package-private: the v2 public API\n * exposes browser support only through {@link SerialSession.isBrowserSupported}.\n *\n * @returns `true` when `navigator.serial` is available.\n *\n * @internal\n */\nexport function hasWebSerialSupport(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n 'serial' in navigator &&\n navigator.serial !== undefined &&\n navigator.serial !== null\n );\n}\n", "/**\n * Streaming UTF-16 text to newline-delimited lines for {@link createSerialSession}.\n * Supports `\\r\\n` and `\\n` per #237; a lone `\\r` that is not the last character\n * in the buffer is treated as a line end (compatibility with some devices). A\n * trailing `\\r` is retained until a following chunk disambiguates `\\r` vs\n * `\\r\\n`.\n *\n * @internal\n */\nexport function createLineBuffer(): {\n feed(chunk: string): string[];\n clear(): void;\n} {\n let buffer = '';\n\n const clear = (): void => {\n buffer = '';\n };\n\n const feed = (chunk: string): string[] => {\n buffer += chunk;\n const out: string[] = [];\n\n for (;;) {\n const crlf = buffer.indexOf('\\r\\n');\n if (crlf >= 0) {\n out.push(buffer.slice(0, crlf));\n buffer = buffer.slice(crlf + 2);\n continue;\n }\n\n // Lone \\r (not the last character) is a line end so we must not let\n // a later \\n in the same buffer be matched first (e.g. \"a\\rb\\n\").\n const cr = buffer.indexOf('\\r');\n if (cr >= 0 && cr + 1 < buffer.length && buffer[cr + 1] !== '\\n') {\n out.push(buffer.slice(0, cr));\n buffer = buffer.slice(cr + 1);\n continue;\n }\n\n const nl = buffer.indexOf('\\n');\n if (nl >= 0) {\n out.push(buffer.slice(0, nl));\n buffer = buffer.slice(nl + 1);\n continue;\n }\n\n if (cr >= 0 && cr + 1 === buffer.length) {\n break;\n }\n\n break;\n }\n\n return out;\n };\n\n return { feed, clear };\n}\n", "import { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\n\n/**\n * Default human-readable prefix attached to normalized errors when the\n * caller does not supply one. Kept short so downstream messages remain\n * readable (e.g. `\"Serial operation failed: <cause>\"`).\n *\n * @internal\n */\nconst DEFAULT_MESSAGE_PREFIX = 'Serial operation failed';\n\n/**\n * Options accepted by {@link normalizeSerialError}.\n *\n * @internal\n */\nexport interface NormalizeSerialErrorOptions {\n /**\n * Error code assigned when the input cannot be classified more\n * specifically (for example a generic `Error` thrown from `port.open`).\n *\n * Most call sites in the session pipeline know which lifecycle phase\n * they are in (connect / read / write / close) and therefore pick a\n * phase-specific fallback such as {@link SerialErrorCode.PORT_OPEN_FAILED}\n * or {@link SerialErrorCode.WRITE_FAILED}.\n */\n fallbackCode: SerialErrorCode;\n /**\n * Prefix used when the input has to be wrapped. Ignored when the input\n * is already a {@link SerialError} so we never rewrite a well-formed\n * message the caller has already chosen.\n */\n messagePrefix?: string;\n}\n\nconst isDomExceptionWithName = (\n error: unknown,\n name: string,\n): error is DOMException =>\n typeof DOMException !== 'undefined' &&\n error instanceof DOMException &&\n error.name === name;\n\n/**\n * Normalize an arbitrary thrown value into a {@link SerialError}.\n *\n * This helper is the single entry point used by every v2 session\n * component (session factory, read pump, send queue) to coerce raw\n * platform errors into the library's error type. Centralising the\n * mapping here satisfies Issue #204's completion criterion that\n * \"error handling lives in one place\" and removes the duplicated\n * `toError` / `normalizeError` helpers previously scattered across\n * session modules.\n *\n * Mapping rules applied, in order:\n *\n * 1. `SerialError` instances pass through unchanged so a caller that has\n * already classified the failure (e.g. `PORT_NOT_OPEN` on a pre-open\n * `send$`) is not rewrapped.\n * 2. `DOMException('NotFoundError')` is mapped to\n * {@link SerialErrorCode.OPERATION_CANCELLED}. Chromium raises this\n * when the user dismisses the `requestPort` dialog; it is a normal\n * control-flow event, not a hard failure.\n * 3. Any other value is wrapped as a {@link SerialError} with\n * {@link NormalizeSerialErrorOptions.fallbackCode}, preserving the\n * original error as `originalError` for debugging.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/204 | Issue #204}\n */\nexport function normalizeSerialError(\n error: unknown,\n options: NormalizeSerialErrorOptions,\n): SerialError {\n if (error instanceof SerialError) {\n return error;\n }\n\n const prefix = options.messagePrefix ?? DEFAULT_MESSAGE_PREFIX;\n\n if (isDomExceptionWithName(error, 'NotFoundError')) {\n return new SerialError(\n SerialErrorCode.OPERATION_CANCELLED,\n 'Port selection was cancelled by the user',\n error,\n );\n }\n\n const cause = error instanceof Error ? error : new Error(String(error));\n return new SerialError(\n options.fallbackCode,\n `${prefix}: ${cause.message}`,\n cause,\n );\n}\n", "import { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport { normalizeSerialError } from './normalize-serial-error';\n\n/**\n * Callback invoked for every decoded chunk the read pump produces.\n *\n * The text is decoded from the raw bytes using a shared `TextDecoder` with\n * `{ stream: true }`, so multi-byte characters that straddle chunk\n * boundaries are joined correctly before reaching the callback.\n *\n * @internal\n */\nexport type ReadPumpChunkHandler = (text: string) => void;\n\n/**\n * Callback invoked when the read pump cannot continue.\n *\n * The provided error is always a {@link SerialError}; raw platform errors\n * are normalized by the pump before they reach the caller so consumers only\n * ever observe the library's error type.\n *\n * @internal\n */\nexport type ReadPumpErrorHandler = (error: SerialError) => void;\n\n/**\n * Options accepted by {@link createReadPump}.\n *\n * @internal\n */\nexport interface ReadPumpOptions {\n onChunk: ReadPumpChunkHandler;\n onError: ReadPumpErrorHandler;\n}\n\n/**\n * Handle returned by {@link createReadPump}.\n *\n * @internal\n */\nexport interface ReadPump {\n /**\n * Start reading from the associated `SerialPort.readable` stream.\n *\n * Subsequent calls are ignored while the pump is already running.\n */\n start(): void;\n /**\n * Stop the read loop and release the underlying reader lock.\n *\n * Safe to call multiple times or before `start()`.\n */\n stop(): Promise<void>;\n /**\n * `true` while the internal loop is actively reading.\n */\n readonly isRunning: boolean;\n}\n\n/**\n * Create an internal read pump for a {@link SerialPort}.\n *\n * The pump is an implementation detail of the v2 `SerialSession` API: it\n * owns a single `TextDecoder` (with `stream: true`), drives a\n * `reader.read()` loop against `port.readable`, and forwards decoded text\n * to the provided sink. It is **not** subscription-lazy - the `SerialSession`\n * starts the pump as soon as `connect$` succeeds so that `receive$`\n * subscribers never miss data because of subscription timing.\n *\n * Errors are normalized into {@link SerialError} with\n * {@link SerialErrorCode.READ_FAILED} for read-side failures and\n * {@link SerialErrorCode.CONNECTION_LOST} for missing readable streams, so\n * the session can forward them to `errors$` without rewrapping.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/202 | Issue #202}\n */\nexport function createReadPump(\n port: SerialPort,\n { onChunk, onError }: ReadPumpOptions,\n): ReadPump {\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n let running = false;\n let stopped = false;\n const decoder = new TextDecoder(undefined, { fatal: false });\n\n const releaseReader = (): void => {\n if (!reader) {\n return;\n }\n try {\n reader.releaseLock();\n } catch {\n // releaseLock may throw when the reader is already detached; ignore.\n }\n reader = null;\n };\n\n const pump = async (stream: ReadableStream<Uint8Array>): Promise<void> => {\n reader = stream.getReader();\n running = true;\n try {\n while (!stopped) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n if (value && value.byteLength > 0) {\n const text = decoder.decode(value, { stream: true });\n if (text.length > 0) {\n onChunk(text);\n }\n }\n }\n if (!stopped) {\n const tail = decoder.decode();\n if (tail.length > 0) {\n onChunk(tail);\n }\n }\n } catch (error) {\n if (!stopped) {\n onError(\n normalizeSerialError(error, {\n fallbackCode: SerialErrorCode.READ_FAILED,\n messagePrefix: 'Read pump failed',\n }),\n );\n }\n } finally {\n running = false;\n releaseReader();\n }\n };\n\n return {\n start(): void {\n if (running || stopped) {\n return;\n }\n const stream = port.readable;\n if (!stream) {\n stopped = true;\n onError(\n new SerialError(\n SerialErrorCode.CONNECTION_LOST,\n 'Read pump failed: port.readable is not available',\n ),\n );\n return;\n }\n void pump(stream);\n },\n async stop(): Promise<void> {\n if (stopped) {\n return;\n }\n stopped = true;\n if (!reader) {\n return;\n }\n try {\n await reader.cancel();\n } catch {\n // Cancel can reject when the stream is already errored; ignore so\n // the caller's disconnect flow is not derailed by a read failure.\n } finally {\n releaseReader();\n }\n },\n get isRunning(): boolean {\n return running;\n },\n };\n}\n", "import { Observable, defer } from 'rxjs';\n\n/**\n * A single enqueued unit of work. Operations are awaited sequentially so\n * that `send$` guarantees call order even when the caller fires multiple\n * observables concurrently.\n *\n * @internal\n */\nexport type SendQueueOperation<T> = () => Promise<T>;\n\n/**\n * Internal helper returned by {@link createSendQueue}.\n *\n * The queue is intentionally not an RxJS operator because Issue #199 calls\n * out explicitly that `send$` must not be re-implemented with `mergeMap`\n * (or any other concurrency-altering operator). A chained `Promise<void>`\n * is the simplest way to guarantee strict FIFO ordering while still\n * surfacing per-operation completion back to the caller.\n *\n * @internal\n */\nexport interface SendQueue {\n /**\n * Schedule an operation to run after all previously enqueued operations\n * have settled (resolved or rejected). The returned Observable completes\n * with the operation's resolved value, or errors with its rejection.\n *\n * Subscribing is what actually schedules the work - the queue is driven\n * by `defer`, so late/never-subscribed Observables never enqueue.\n */\n enqueue<T>(operation: SendQueueOperation<T>): Observable<T>;\n /**\n * Reset the internal promise chain. Existing in-flight operations keep\n * running because we do not cancel native promises, but no newly\n * enqueued work will wait for them. Use this when the session is torn\n * down so a fresh `connect$` starts with a clean chain.\n */\n clear(): void;\n}\n\n/**\n * Create an internal send queue that serialises `send$` writes.\n *\n * Ordering model:\n *\n * - Each `enqueue` call appends its operation to a single `Promise<void>`\n * chain.\n * - A failure in one operation does not poison the chain - the next\n * operation still runs (the chain is advanced with a `.then(() => {},\n * () => {})` safety tail).\n * - Unsubscribing before the operation resolves suppresses `next` /\n * `complete` / `error` for that subscriber; the underlying promise is\n * still awaited so later operations continue to respect call order.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/203 | Issue #203}\n */\nexport function createSendQueue(): SendQueue {\n let chain: Promise<void> = Promise.resolve();\n\n return {\n enqueue<T>(operation: SendQueueOperation<T>): Observable<T> {\n return defer(\n () =>\n new Observable<T>((subscriber) => {\n let cancelled = false;\n\n const run = async (): Promise<void> => {\n try {\n const value = await operation();\n if (!cancelled) {\n subscriber.next(value);\n subscriber.complete();\n }\n } catch (error) {\n if (!cancelled) {\n subscriber.error(error);\n }\n }\n };\n\n const scheduled = chain.then(run, run);\n chain = scheduled.then(\n () => undefined,\n () => undefined,\n );\n\n return () => {\n cancelled = true;\n };\n }),\n );\n },\n clear(): void {\n chain = Promise.resolve();\n },\n };\n}\n", "/**\n * Options for creating a {@link SerialSession} via {@link createSerialSession}.\n *\n * These options configure the serial port connection parameters used when\n * calling `port.open` and `navigator.serial.requestPort`. All properties\n * are optional; omitted fields fall back to {@link DEFAULT_SERIAL_SESSION_OPTIONS}.\n *\n * @example\n * ```typescript\n * const session = createSerialSession({\n * baudRate: 115200,\n * dataBits: 8,\n * stopBits: 1,\n * parity: 'none',\n * flowControl: 'none',\n * filters: [{ usbVendorId: 0x1234, usbProductId: 0x5678 }],\n * });\n * ```\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/200 | Issue #200}\n */\nexport interface SerialSessionOptions {\n /**\n * Baud rate for the serial port connection (bits per second).\n *\n * Common values include 9600, 19200, 38400, 57600, 115200, etc.\n * Must match the baud rate configured on the connected device.\n *\n * @default 9600\n */\n baudRate?: number;\n\n /**\n * Number of data bits per character (7 or 8).\n *\n * @default 8\n */\n dataBits?: 7 | 8;\n\n /**\n * Number of stop bits (1 or 2).\n *\n * @default 1\n */\n stopBits?: 1 | 2;\n\n /**\n * Parity checking mode.\n *\n * @default 'none'\n */\n parity?: 'none' | 'even' | 'odd';\n\n /**\n * Buffer size for the underlying read stream, in bytes.\n *\n * @default 255\n */\n bufferSize?: number;\n\n /**\n * Flow control mode.\n *\n * @default 'none'\n */\n flowControl?: 'none' | 'hardware';\n\n /**\n * Filters for port selection when requesting a port.\n *\n * When specified, the port selection dialog will only show devices\n * matching these filters. Each filter can specify `usbVendorId` and/or\n * `usbProductId`.\n */\n filters?: SerialPortFilter[];\n}\n\n/**\n * Default values applied to omitted {@link SerialSessionOptions} fields.\n *\n * @internal\n */\nexport const DEFAULT_SERIAL_SESSION_OPTIONS: Required<\n Omit<SerialSessionOptions, 'filters'>\n> & { filters?: SerialPortFilter[] } = {\n baudRate: 9600,\n dataBits: 8,\n stopBits: 1,\n parity: 'none',\n bufferSize: 255,\n flowControl: 'none',\n filters: undefined,\n};\n", "/**\n * Reactive lifecycle state for a {@link SerialSession}.\n *\n * This is the v2 API counterpart of the legacy `SerialState` used by\n * `SerialClient`. The runtime values are the same flat strings v1\n * consumers used for UI switches; the {@link SerialSessionState} const\n * object is the canonical source of those literals so call sites can\n * avoid string typos and get IDE completion.\n *\n * Lifecycle transitions:\n *\n * ```\n * idle -> connecting -> connected -> disconnecting -> idle\n * \\-> error\n * (any) -> unsupported (when Web Serial API is unavailable)\n * (any) -> error (when an unrecoverable failure occurs)\n * ```\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/200 | Issue #200}\n */\nexport const SerialSessionState = {\n Idle: 'idle',\n Connecting: 'connecting',\n Connected: 'connected',\n Disconnecting: 'disconnecting',\n Unsupported: 'unsupported',\n Error: 'error',\n} as const;\n\n/**\n * String union of allowed {@link SerialSessionState} runtime values\n * (same set as the values on the {@link SerialSessionState} object).\n */\nexport type SerialSessionState =\n (typeof SerialSessionState)[keyof typeof SerialSessionState];\n", "import { BehaviorSubject, Observable } from 'rxjs';\nimport { SerialSessionState } from './serial-session-state';\n\n/**\n * Allowed transitions for the internal SerialSession state machine.\n *\n * Keys are the source state, values are the set of states that the source\n * is allowed to transition into. Transitions missing from this map are\n * treated as invalid and silently rejected (with a `console.warn`) so that\n * a logic bug in one sub-issue cannot corrupt `state$` for downstream\n * consumers.\n *\n * Lifecycle model (matches {@link SerialSessionState}):\n *\n * ```\n * idle -> connecting -> connected -> disconnecting -> idle\n * \\-> error\n * error -> idle (reset / retry)\n * unsupported (terminal; entered only at construction time)\n * ```\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/201 | Issue #201}\n */\nconst S = SerialSessionState;\n\nconst ALLOWED_TRANSITIONS: Readonly<\n Record<SerialSessionState, readonly SerialSessionState[]>\n> = {\n [S.Idle]: [S.Connecting, S.Error],\n [S.Connecting]: [S.Connected, S.Error, S.Idle],\n [S.Connected]: [S.Disconnecting, S.Error],\n [S.Disconnecting]: [S.Idle, S.Error],\n [S.Error]: [S.Idle, S.Connecting],\n [S.Unsupported]: [],\n};\n\n/**\n * Internal state machine that backs {@link SerialSession.state$}.\n *\n * The machine is deliberately kept as an internal module (not exported\n * from the package) because the public surface is only the Observable.\n * Sub-issues of #199 (read pump / send queue / errors) drive transitions\n * through the dedicated `to*` methods below instead of mutating a shared\n * `BehaviorSubject` directly.\n *\n * Design notes:\n *\n * - Invalid transitions are silently rejected so that the `state$` stream\n * never emits a state that violates the lifecycle contract. A\n * `console.warn` is emitted in development builds to aid debugging.\n * - `unsupported` is terminal: once entered (during construction when\n * `navigator.serial` is missing), the machine refuses every further\n * transition.\n * - `state$` is derived from a {@link BehaviorSubject} so late subscribers\n * still receive the current state on subscription.\n *\n * @internal\n */\nexport class SessionStateMachine {\n private readonly subject: BehaviorSubject<SerialSessionState>;\n\n constructor(initial: SerialSessionState = SerialSessionState.Idle) {\n this.subject = new BehaviorSubject<SerialSessionState>(initial);\n }\n\n get current(): SerialSessionState {\n return this.subject.getValue();\n }\n\n get state$(): Observable<SerialSessionState> {\n return this.subject.asObservable();\n }\n\n toConnecting(): boolean {\n return this.transition(S.Connecting);\n }\n\n toConnected(): boolean {\n return this.transition(S.Connected);\n }\n\n toDisconnecting(): boolean {\n return this.transition(S.Disconnecting);\n }\n\n toIdle(): boolean {\n return this.transition(S.Idle);\n }\n\n toError(): boolean {\n return this.transition(S.Error);\n }\n\n toUnsupported(): boolean {\n return this.transition(S.Unsupported);\n }\n\n complete(): void {\n this.subject.complete();\n }\n\n private transition(next: SerialSessionState): boolean {\n const current = this.subject.getValue();\n\n if (current === next) {\n return false;\n }\n\n const allowed = ALLOWED_TRANSITIONS[current];\n if (!allowed.includes(next)) {\n if (typeof console !== 'undefined' && console.warn) {\n console.warn(\n `[web-serial-rxjs] Ignoring invalid SerialSession transition ${current} -> ${next}`,\n );\n }\n return false;\n }\n\n this.subject.next(next);\n return true;\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["Observable", "SerialErrorCode", "options", "Observable"]
|
|
3
|
+
"sources": ["../src/session/create-serial-session.ts", "../src/errors/serial-error-code.ts", "../src/errors/serial-error.ts", "../src/session/internal/build-request-options.ts", "../src/session/internal/has-web-serial-support.ts", "../src/session/internal/line-buffer.ts", "../src/session/normalize-serial-error.ts", "../src/session/read-pump.ts", "../src/session/send-queue.ts", "../src/session/serial-session-options.ts", "../src/session/serial-session-state.ts", "../src/session/session-state-machine.ts", "../src/terminal/create-terminal-buffer.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n BehaviorSubject,\n distinctUntilChanged,\n map,\n Observable,\n ReplaySubject,\n share,\n Subject,\n switchMap,\n} from 'rxjs';\nimport { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport { buildRequestOptions } from './internal/build-request-options';\nimport { hasWebSerialSupport } from './internal/has-web-serial-support';\nimport { createLineBuffer } from './internal/line-buffer';\nimport {\n normalizeSerialError,\n type NormalizeSerialErrorOptions,\n} from './normalize-serial-error';\nimport { createReadPump, type ReadPump } from './read-pump';\nimport { createSendQueue } from './send-queue';\nimport type { SerialSession } from './serial-session';\nimport {\n DEFAULT_SERIAL_SESSION_OPTIONS,\n type SerialSessionOptions,\n} from './serial-session-options';\nimport { SerialSessionState } from './serial-session-state';\nimport { SessionStateMachine } from './session-state-machine';\n\n/**\n * Internal error classification used by the single `reportError` entry\n * point. `'fatal'` errors drive `state$` into `'error'` and tear down\n * the live session (pump stop + port close); `'non-fatal'` errors are\n * only multiplexed on `errors$` without mutating session state - this\n * matches the Issue #199 design note that write failures must not\n * implicitly disconnect the session.\n *\n * @internal\n */\ntype ReportErrorSeverity = 'fatal' | 'non-fatal';\n\n/**\n * Create a v2 {@link SerialSession}.\n *\n * This release wires the internal read pump (#202) and the internal send\n * queue (#203) into the session so that `connect$`, `disconnect$`,\n * `receive$`, and `send$` all operate end-to-end. Error handling is\n * centralised through a single `reportError` helper (#204) so every\n * failure path normalises through {@link normalizeSerialError} and emits\n * on the one `errors$` channel.\n *\n * Key behaviors:\n *\n * - `isBrowserSupported()` returns whether `navigator.serial` is available.\n * - `state$` replays the current lifecycle state driven by\n * {@link SessionStateMachine}.\n * - `connect$()` opens a user-selected port, starts the internal read pump,\n * and transitions `idle -> connecting -> connected`.\n * - `disconnect$()` stops the read pump, closes the port, and transitions\n * `connected -> disconnecting -> idle`.\n * - `receive$` emits UTF-8 decoded text chunks pushed by the pump. It is\n * **not** subscription-lazy - the pump is started by `connect$` and\n * decoded text is multicast to all subscribers; late subscribers see only\n * new data.\n * - `lines$` emits the same decoded stream split into line-terminated\n * segments (`\\n`, `\\r\\n`); a trailing line without a terminator is\n * buffered. It is also not subscription-lazy relative to the pump.\n * - `send$` enqueues each payload on an internal FIFO queue so concurrent\n * subscribers are written to the port in call order. String payloads are\n * UTF-8 encoded through a shared `TextEncoder`.\n * - `errors$` multiplexes every {@link SerialError} produced by the\n * session. Connect / read / close failures are treated as fatal and\n * also drive `state$` to `'error'`; write failures are non-fatal and\n * do not mutate `state$` because a real connection loss will be\n * observed by the read pump on the next tick anyway.\n *\n * @param options - Session options. Only `filters` is consulted by\n * `connect$` today (forwarded to `navigator.serial.requestPort`); the\n * remaining fields are passed to `port.open` using defaults when omitted.\n * @returns A {@link SerialSession} instance.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/202 | Issue #202}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/203 | Issue #203}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/204 | Issue #204}\n */\nexport function createSerialSession(\n options?: SerialSessionOptions,\n): SerialSession {\n const resolvedOptions = {\n ...DEFAULT_SERIAL_SESSION_OPTIONS,\n ...options,\n filters: options?.filters,\n receiveReplay: {\n ...DEFAULT_SERIAL_SESSION_OPTIONS.receiveReplay,\n ...options?.receiveReplay,\n },\n };\n\n const supported = hasWebSerialSupport();\n const machine = new SessionStateMachine(\n supported ? SerialSessionState.Idle : SerialSessionState.Unsupported,\n );\n const errorsSubject = new Subject<SerialError>();\n const receiveSubject = new Subject<string>();\n const linesSubject = new Subject<string>();\n const sendQueue = createSendQueue();\n const textEncoder = new TextEncoder();\n const lineBuffer = createLineBuffer();\n\n const errors$ = errorsSubject.asObservable();\n const receive$ = receiveSubject.asObservable();\n const lines$ = linesSubject.asObservable();\n\n const isConnected$ = machine.state$.pipe(\n map((state) => state === SerialSessionState.Connected),\n distinctUntilChanged(),\n );\n\n const portInfoSubject = new BehaviorSubject<SerialPortInfo | null>(null);\n const portInfo$ = portInfoSubject.asObservable();\n\n const receiveReplayStream$ = resolvedOptions.receiveReplay.enabled\n ? new BehaviorSubject<Observable<string>>(receive$)\n : null;\n let activeReceiveReplay: ReplaySubject<string> | null = null;\n\n const clearLiveReceiveReplay = (): void => {\n if (receiveReplayStream$) {\n if (activeReceiveReplay) {\n activeReceiveReplay.complete();\n activeReceiveReplay = null;\n }\n receiveReplayStream$.next(receive$);\n }\n };\n\n const startLiveReceiveReplay = (): void => {\n if (!receiveReplayStream$) {\n return;\n }\n if (activeReceiveReplay) {\n activeReceiveReplay.complete();\n activeReceiveReplay = null;\n }\n const rs = new ReplaySubject<string>(resolvedOptions.receiveReplay.bufferSize);\n activeReceiveReplay = rs;\n receiveReplayStream$.next(rs.asObservable());\n };\n\n const receiveReplay$ = receiveReplayStream$\n ? receiveReplayStream$.pipe(switchMap((inner) => inner), share())\n : receive$;\n\n let activePort: SerialPort | null = null;\n let activePump: ReadPump | null = null;\n\n const setActivePort = (port: SerialPort | null): void => {\n activePort = port;\n portInfoSubject.next(port ? port.getInfo() : null);\n };\n\n const teardownPump = async (): Promise<void> => {\n clearLiveReceiveReplay();\n const pump = activePump;\n activePump = null;\n lineBuffer.clear();\n if (pump) {\n await pump.stop();\n }\n };\n\n const closePortSafely = async (port: SerialPort | null): Promise<void> => {\n if (!port) {\n return;\n }\n try {\n await port.close();\n } catch {\n // The read pump may already have errored the stream, which makes\n // close() reject. We ignore it here because disconnect$ has a\n // dedicated error path for close failures initiated by the user.\n }\n };\n\n /**\n * Single entry point for every error that should reach `errors$`.\n *\n * Responsibilities:\n *\n * 1. Normalise the input through {@link normalizeSerialError} so every\n * emission is a well-formed {@link SerialError}.\n * 2. Multiplex the normalised error on `errors$`.\n * 3. For fatal severities, drive `state$` to `'error'`, clear the send\n * queue so pending writes fail fast, and tear down the live pump +\n * port off the hot path.\n *\n * Returning the normalised error keeps call sites terse: they can hand\n * the result straight to `subscriber.error(...)` without re-normalising.\n */\n const reportError = (\n error: unknown,\n severity: ReportErrorSeverity,\n options: NormalizeSerialErrorOptions,\n ): SerialError => {\n const serialError = normalizeSerialError(error, options);\n errorsSubject.next(serialError);\n if (severity === 'fatal') {\n machine.toError();\n sendQueue.clear();\n const portToClose = activePort;\n setActivePort(null);\n void teardownPump().then(() => closePortSafely(portToClose));\n }\n return serialError;\n };\n\n const writeToPort = async (payload: Uint8Array): Promise<void> => {\n const port = activePort;\n if (machine.current !== SerialSessionState.Connected || !port || !port.writable) {\n throw new SerialError(\n SerialErrorCode.PORT_NOT_OPEN,\n 'Cannot send data while session is not connected',\n );\n }\n const writer = port.writable.getWriter();\n try {\n await writer.write(payload);\n } finally {\n try {\n writer.releaseLock();\n } catch {\n // releaseLock throws when the stream is already errored; the real\n // failure is surfaced through the write() rejection above so we\n // intentionally swallow this secondary error.\n }\n }\n };\n\n return {\n isBrowserSupported(): boolean {\n return hasWebSerialSupport();\n },\n connect$(): Observable<void> {\n return new Observable<void>((subscriber) => {\n if (!hasWebSerialSupport()) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.BROWSER_NOT_SUPPORTED,\n 'Web Serial API is not supported in this environment',\n ),\n 'non-fatal',\n { fallbackCode: SerialErrorCode.BROWSER_NOT_SUPPORTED },\n );\n subscriber.error(error);\n return;\n }\n\n const current = machine.current;\n if (\n current !== SerialSessionState.Idle &&\n current !== SerialSessionState.Error\n ) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.PORT_ALREADY_OPEN,\n `Cannot connect while session state is '${current}'`,\n ),\n 'non-fatal',\n { fallbackCode: SerialErrorCode.PORT_ALREADY_OPEN },\n );\n subscriber.error(error);\n return;\n }\n\n let cancelled = false;\n machine.toConnecting();\n\n const run = async (): Promise<void> => {\n let selectedPort: SerialPort | null = null;\n try {\n selectedPort = await navigator.serial.requestPort(\n buildRequestOptions(resolvedOptions),\n );\n await selectedPort.open({\n baudRate: resolvedOptions.baudRate,\n dataBits: resolvedOptions.dataBits,\n stopBits: resolvedOptions.stopBits,\n parity: resolvedOptions.parity,\n bufferSize: resolvedOptions.bufferSize,\n flowControl: resolvedOptions.flowControl,\n });\n } catch (error) {\n if (selectedPort) {\n await closePortSafely(selectedPort);\n }\n const serialError = reportError(error, 'fatal', {\n fallbackCode: SerialErrorCode.PORT_OPEN_FAILED,\n messagePrefix: 'Failed to open port',\n });\n if (!cancelled) {\n subscriber.error(serialError);\n }\n return;\n }\n\n if (cancelled) {\n await closePortSafely(selectedPort);\n return;\n }\n\n setActivePort(selectedPort);\n lineBuffer.clear();\n if (resolvedOptions.receiveReplay.enabled) {\n startLiveReceiveReplay();\n }\n activePump = createReadPump(selectedPort, {\n onChunk: (text) => {\n receiveSubject.next(text);\n if (activeReceiveReplay) {\n activeReceiveReplay.next(text);\n }\n for (const line of lineBuffer.feed(text)) {\n linesSubject.next(line);\n }\n },\n onError: (pumpError) =>\n reportError(pumpError, 'fatal', {\n fallbackCode: SerialErrorCode.READ_FAILED,\n messagePrefix: 'Read pump failed',\n }),\n });\n activePump.start();\n sendQueue.clear();\n machine.toConnected();\n subscriber.next();\n subscriber.complete();\n };\n\n void run();\n\n return () => {\n cancelled = true;\n };\n });\n },\n disconnect$(): Observable<void> {\n return new Observable<void>((subscriber) => {\n const current = machine.current;\n\n if (\n current === SerialSessionState.Idle ||\n current === SerialSessionState.Unsupported\n ) {\n subscriber.next();\n subscriber.complete();\n return;\n }\n\n if (\n current !== SerialSessionState.Connected &&\n current !== SerialSessionState.Error\n ) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.PORT_NOT_OPEN,\n `Cannot disconnect while session state is '${current}'`,\n ),\n 'non-fatal',\n { fallbackCode: SerialErrorCode.PORT_NOT_OPEN },\n );\n subscriber.error(error);\n return;\n }\n\n machine.toDisconnecting();\n sendQueue.clear();\n const portToClose = activePort;\n\n const run = async (): Promise<void> => {\n try {\n await teardownPump();\n if (portToClose) {\n try {\n await portToClose.close();\n } catch (error) {\n setActivePort(null);\n const serialError = reportError(error, 'fatal', {\n fallbackCode: SerialErrorCode.CONNECTION_LOST,\n messagePrefix: 'Failed to close port',\n });\n subscriber.error(serialError);\n return;\n }\n }\n setActivePort(null);\n machine.toIdle();\n subscriber.next();\n subscriber.complete();\n } catch (error) {\n const serialError = reportError(error, 'fatal', {\n fallbackCode: SerialErrorCode.UNKNOWN,\n messagePrefix: 'Unexpected disconnect failure',\n });\n subscriber.error(serialError);\n }\n };\n\n void run();\n });\n },\n send$(data: string | Uint8Array): Observable<void> {\n return sendQueue.enqueue(async () => {\n const payload =\n typeof data === 'string' ? textEncoder.encode(data) : data;\n try {\n await writeToPort(payload);\n } catch (error) {\n throw reportError(error, 'non-fatal', {\n fallbackCode: SerialErrorCode.WRITE_FAILED,\n messagePrefix: 'Failed to write data',\n });\n }\n });\n },\n state$: machine.state$,\n isConnected$,\n portInfo$,\n getPortInfo(): SerialPortInfo | null {\n return portInfoSubject.getValue();\n },\n getCurrentPort(): SerialPort | null {\n return activePort;\n },\n errors$,\n receive$,\n receiveReplay$,\n lines$,\n };\n}\n", "/**\n * Error codes for serial port operations.\n *\n * These codes identify specific error conditions that can occur when working with\n * serial ports. Each error code corresponds to a specific failure scenario, making\n * it easier to handle errors programmatically.\n *\n * @example\n * ```typescript\n * try {\n * await client.connect().toPromise();\n * } catch (error) {\n * if (error instanceof SerialError) {\n * switch (error.code) {\n * case SerialErrorCode.BROWSER_NOT_SUPPORTED:\n * console.error('Please use a Chromium-based browser');\n * break;\n * case SerialErrorCode.OPERATION_CANCELLED:\n * console.log('User cancelled port selection');\n * break;\n * // ... handle other error codes\n * }\n * }\n * }\n * ```\n */\nexport enum SerialErrorCode {\n /**\n * Browser does not support the Web Serial API.\n *\n * This error occurs when attempting to use serial port functionality in a browser\n * that doesn't support the Web Serial API. Only Chromium-based browsers (Chrome,\n * Edge, Opera) support this API.\n *\n * **Suggested action**: Inform the user to use a supported browser.\n */\n BROWSER_NOT_SUPPORTED = 'BROWSER_NOT_SUPPORTED',\n\n /**\n * Serial port is not available.\n *\n * This error occurs when a requested port cannot be accessed, such as when\n * getting previously granted ports fails or when the port is already in use\n * by another application.\n *\n * **Suggested action**: Check if the port is available or being used by another application.\n */\n PORT_NOT_AVAILABLE = 'PORT_NOT_AVAILABLE',\n\n /**\n * Failed to open the serial port.\n *\n * This error occurs when the port cannot be opened, typically due to incorrect\n * connection parameters, hardware issues, or permission problems.\n *\n * **Suggested action**: Verify connection parameters and check hardware connections.\n */\n PORT_OPEN_FAILED = 'PORT_OPEN_FAILED',\n\n /**\n * Serial port is already open.\n *\n * This error occurs when attempting to open a port that is already connected.\n * Only one connection can be active at a time per SerialClient instance.\n *\n * **Suggested action**: Disconnect the current port before connecting a new one.\n */\n PORT_ALREADY_OPEN = 'PORT_ALREADY_OPEN',\n\n /**\n * Serial port is not open.\n *\n * This error occurs when attempting to read from or write to a port that hasn't\n * been opened yet. The port must be connected before performing I/O operations.\n *\n * **Suggested action**: Call {@link SerialClient.connect} before reading or writing.\n */\n PORT_NOT_OPEN = 'PORT_NOT_OPEN',\n\n /**\n * Failed to read from the serial port.\n *\n * This error occurs when reading data from the port fails, typically due to\n * connection loss, hardware issues, or stream errors.\n *\n * **Suggested action**: Check the connection and hardware, then retry the read operation.\n */\n READ_FAILED = 'READ_FAILED',\n\n /**\n * Failed to write to the serial port.\n *\n * This error occurs when writing data to the port fails, typically due to\n * connection loss, hardware issues, or stream errors.\n *\n * **Suggested action**: Check the connection and hardware, then retry the write operation.\n */\n WRITE_FAILED = 'WRITE_FAILED',\n\n /**\n * Serial port connection was lost.\n *\n * This error occurs when the connection to the serial port is unexpectedly\n * terminated, such as when the device is disconnected or the port is closed\n * by another process.\n *\n * **Suggested action**: Check the physical connection and reconnect if needed.\n */\n CONNECTION_LOST = 'CONNECTION_LOST',\n\n /**\n * Invalid filter options provided.\n *\n * This error occurs when port filter options are invalid, such as when\n * filter values are out of range or missing required fields.\n *\n * **Suggested action**: Verify filter options match the expected format and value ranges.\n */\n INVALID_FILTER_OPTIONS = 'INVALID_FILTER_OPTIONS',\n\n /**\n * Operation was cancelled by the user.\n *\n * This error occurs when the user cancels a port selection dialog or aborts\n * an operation before it completes.\n *\n * **Suggested action**: This is a normal condition - no action required, but you may want\n * to inform the user that the operation was cancelled.\n */\n OPERATION_CANCELLED = 'OPERATION_CANCELLED',\n\n /**\n * Operation timed out before completion.\n *\n * This error occurs when an operation waits for a condition (for example, prompt\n * detection) and the timeout period elapses first.\n */\n OPERATION_TIMEOUT = 'OPERATION_TIMEOUT',\n\n /**\n * Unknown error occurred.\n *\n * This error code is used for errors that don't fit into any other category.\n * The original error details may be available in the error's message or originalError property.\n *\n * **Suggested action**: Check the error message and originalError for more details.\n */\n UNKNOWN = 'UNKNOWN',\n}\n", "import { SerialErrorCode } from './serial-error-code';\n\n// Re-export SerialErrorCode for convenience\nexport { SerialErrorCode };\n\n/**\n * Custom error class for serial port operations.\n *\n * This error class extends the standard Error class and includes additional information\n * about the type of error that occurred. It provides an error code for programmatic\n * error handling and may include the original error that caused the failure.\n *\n * @example\n * ```typescript\n * try {\n * await client.connect().toPromise();\n * } catch (error) {\n * if (error instanceof SerialError) {\n * console.error(`Error code: ${error.code}`);\n * console.error(`Message: ${error.message}`);\n * if (error.originalError) {\n * console.error(`Original error:`, error.originalError);\n * }\n *\n * // Check specific error code\n * if (error.is(SerialErrorCode.BROWSER_NOT_SUPPORTED)) {\n * // Handle browser not supported\n * }\n * }\n * }\n * ```\n */\nexport class SerialError extends Error {\n /**\n * The error code identifying the type of error that occurred.\n *\n * Use this code to programmatically handle specific error conditions.\n *\n * @see {@link SerialErrorCode} for all available error codes\n */\n public readonly code: SerialErrorCode;\n\n /**\n * The original error that caused this SerialError, if available.\n *\n * This property contains the underlying error (e.g., DOMException, TypeError)\n * that was wrapped in this SerialError. It may be undefined if no original error exists.\n */\n public readonly originalError?: Error;\n\n /**\n * Creates a new SerialError instance.\n *\n * @param code - The error code identifying the type of error\n * @param message - A human-readable error message\n * @param originalError - The original error that caused this SerialError, if any\n */\n constructor(code: SerialErrorCode, message: string, originalError?: Error) {\n super(message);\n this.name = 'SerialError';\n this.code = code;\n this.originalError = originalError;\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if ((Error as any).captureStackTrace) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (Error as any).captureStackTrace(this, SerialError);\n }\n }\n\n /**\n * Check if the error matches a specific error code.\n *\n * This is a convenience method for checking the error code without directly\n * comparing the code property.\n *\n * @param code - The error code to check against\n * @returns `true` if this error's code matches the provided code, `false` otherwise\n *\n * @example\n * ```typescript\n * if (error.is(SerialErrorCode.PORT_NOT_OPEN)) {\n * // Handle port not open error\n * }\n * ```\n */\n public is(code: SerialErrorCode): boolean {\n return this.code === code;\n }\n}\n", "import { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\nimport type { SerialSessionOptions } from '../serial-session-options';\n\n/**\n * Build {@link SerialPortRequestOptions} from {@link SerialSessionOptions}.\n *\n * Converts the `filters` field of {@link SerialSessionOptions} into the\n * shape expected by `navigator.serial.requestPort` and validates USB\n * vendor / product identifiers. Returns `undefined` when no filters are\n * supplied so the browser shows all available ports.\n *\n * @param options - The session options supplied by the caller.\n * @returns The request options, or `undefined` when no filters are set.\n * @throws {@link SerialError} with {@link SerialErrorCode.INVALID_FILTER_OPTIONS}\n * when a filter is empty or contains out-of-range IDs.\n *\n * @internal\n */\nexport function buildRequestOptions(\n options?: SerialSessionOptions,\n): SerialPortRequestOptions | undefined {\n if (!options || !options.filters || options.filters.length === 0) {\n return undefined;\n }\n\n for (const filter of options.filters) {\n if (!filter.usbVendorId && !filter.usbProductId) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n 'Filter must have at least usbVendorId or usbProductId',\n );\n }\n\n if (filter.usbVendorId !== undefined) {\n if (\n !Number.isInteger(filter.usbVendorId) ||\n filter.usbVendorId < 0 ||\n filter.usbVendorId > 0xffff\n ) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n `Invalid usbVendorId: ${filter.usbVendorId}. Must be an integer between 0 and 65535.`,\n );\n }\n }\n\n if (filter.usbProductId !== undefined) {\n if (\n !Number.isInteger(filter.usbProductId) ||\n filter.usbProductId < 0 ||\n filter.usbProductId > 0xffff\n ) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n `Invalid usbProductId: ${filter.usbProductId}. Must be an integer between 0 and 65535.`,\n );\n }\n }\n }\n\n return {\n filters: options.filters,\n };\n}\n", "/**\n * Internal feature detection for the Web Serial API.\n *\n * This helper is intentionally kept package-private: the v2 public API\n * exposes browser support only through {@link SerialSession.isBrowserSupported}.\n *\n * @returns `true` when `navigator.serial` is available.\n *\n * @internal\n */\nexport function hasWebSerialSupport(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n 'serial' in navigator &&\n navigator.serial !== undefined &&\n navigator.serial !== null\n );\n}\n", "/**\n * Streaming UTF-16 text to newline-delimited lines for {@link createSerialSession}.\n * Supports `\\r\\n` and `\\n` per #237; a lone `\\r` that is not the last character\n * in the buffer is treated as a line end (compatibility with some devices). A\n * trailing `\\r` is retained until a following chunk disambiguates `\\r` vs\n * `\\r\\n`.\n *\n * @internal\n */\nexport function createLineBuffer(): {\n feed(chunk: string): string[];\n clear(): void;\n} {\n let buffer = '';\n\n const clear = (): void => {\n buffer = '';\n };\n\n const feed = (chunk: string): string[] => {\n buffer += chunk;\n const out: string[] = [];\n\n for (;;) {\n const crlf = buffer.indexOf('\\r\\n');\n if (crlf >= 0) {\n out.push(buffer.slice(0, crlf));\n buffer = buffer.slice(crlf + 2);\n continue;\n }\n\n // Lone \\r (not the last character) is a line end so we must not let\n // a later \\n in the same buffer be matched first (e.g. \"a\\rb\\n\").\n const cr = buffer.indexOf('\\r');\n if (cr >= 0 && cr + 1 < buffer.length && buffer[cr + 1] !== '\\n') {\n out.push(buffer.slice(0, cr));\n buffer = buffer.slice(cr + 1);\n continue;\n }\n\n const nl = buffer.indexOf('\\n');\n if (nl >= 0) {\n out.push(buffer.slice(0, nl));\n buffer = buffer.slice(nl + 1);\n continue;\n }\n\n if (cr >= 0 && cr + 1 === buffer.length) {\n break;\n }\n\n break;\n }\n\n return out;\n };\n\n return { feed, clear };\n}\n", "import { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\n\n/**\n * Default human-readable prefix attached to normalized errors when the\n * caller does not supply one. Kept short so downstream messages remain\n * readable (e.g. `\"Serial operation failed: <cause>\"`).\n *\n * @internal\n */\nconst DEFAULT_MESSAGE_PREFIX = 'Serial operation failed';\n\n/**\n * Options accepted by {@link normalizeSerialError}.\n *\n * @internal\n */\nexport interface NormalizeSerialErrorOptions {\n /**\n * Error code assigned when the input cannot be classified more\n * specifically (for example a generic `Error` thrown from `port.open`).\n *\n * Most call sites in the session pipeline know which lifecycle phase\n * they are in (connect / read / write / close) and therefore pick a\n * phase-specific fallback such as {@link SerialErrorCode.PORT_OPEN_FAILED}\n * or {@link SerialErrorCode.WRITE_FAILED}.\n */\n fallbackCode: SerialErrorCode;\n /**\n * Prefix used when the input has to be wrapped. Ignored when the input\n * is already a {@link SerialError} so we never rewrite a well-formed\n * message the caller has already chosen.\n */\n messagePrefix?: string;\n}\n\nconst isDomExceptionWithName = (\n error: unknown,\n name: string,\n): error is DOMException =>\n typeof DOMException !== 'undefined' &&\n error instanceof DOMException &&\n error.name === name;\n\n/**\n * Normalize an arbitrary thrown value into a {@link SerialError}.\n *\n * This helper is the single entry point used by every v2 session\n * component (session factory, read pump, send queue) to coerce raw\n * platform errors into the library's error type. Centralising the\n * mapping here satisfies Issue #204's completion criterion that\n * \"error handling lives in one place\" and removes the duplicated\n * `toError` / `normalizeError` helpers previously scattered across\n * session modules.\n *\n * Mapping rules applied, in order:\n *\n * 1. `SerialError` instances pass through unchanged so a caller that has\n * already classified the failure (e.g. `PORT_NOT_OPEN` on a pre-open\n * `send$`) is not rewrapped.\n * 2. `DOMException('NotFoundError')` is mapped to\n * {@link SerialErrorCode.OPERATION_CANCELLED}. Chromium raises this\n * when the user dismisses the `requestPort` dialog; it is a normal\n * control-flow event, not a hard failure.\n * 3. Any other value is wrapped as a {@link SerialError} with\n * {@link NormalizeSerialErrorOptions.fallbackCode}, preserving the\n * original error as `originalError` for debugging.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/204 | Issue #204}\n */\nexport function normalizeSerialError(\n error: unknown,\n options: NormalizeSerialErrorOptions,\n): SerialError {\n if (error instanceof SerialError) {\n return error;\n }\n\n const prefix = options.messagePrefix ?? DEFAULT_MESSAGE_PREFIX;\n\n if (isDomExceptionWithName(error, 'NotFoundError')) {\n return new SerialError(\n SerialErrorCode.OPERATION_CANCELLED,\n 'Port selection was cancelled by the user',\n error,\n );\n }\n\n const cause = error instanceof Error ? error : new Error(String(error));\n return new SerialError(\n options.fallbackCode,\n `${prefix}: ${cause.message}`,\n cause,\n );\n}\n", "import { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport { normalizeSerialError } from './normalize-serial-error';\n\n/**\n * Callback invoked for every decoded chunk the read pump produces.\n *\n * The text is decoded from the raw bytes using a shared `TextDecoder` with\n * `{ stream: true }`, so multi-byte characters that straddle chunk\n * boundaries are joined correctly before reaching the callback.\n *\n * @internal\n */\nexport type ReadPumpChunkHandler = (text: string) => void;\n\n/**\n * Callback invoked when the read pump cannot continue.\n *\n * The provided error is always a {@link SerialError}; raw platform errors\n * are normalized by the pump before they reach the caller so consumers only\n * ever observe the library's error type.\n *\n * @internal\n */\nexport type ReadPumpErrorHandler = (error: SerialError) => void;\n\n/**\n * Options accepted by {@link createReadPump}.\n *\n * @internal\n */\nexport interface ReadPumpOptions {\n onChunk: ReadPumpChunkHandler;\n onError: ReadPumpErrorHandler;\n}\n\n/**\n * Handle returned by {@link createReadPump}.\n *\n * @internal\n */\nexport interface ReadPump {\n /**\n * Start reading from the associated `SerialPort.readable` stream.\n *\n * Subsequent calls are ignored while the pump is already running.\n */\n start(): void;\n /**\n * Stop the read loop and release the underlying reader lock.\n *\n * Safe to call multiple times or before `start()`.\n */\n stop(): Promise<void>;\n /**\n * `true` while the internal loop is actively reading.\n */\n readonly isRunning: boolean;\n}\n\n/**\n * Create an internal read pump for a {@link SerialPort}.\n *\n * The pump is an implementation detail of the v2 `SerialSession` API: it\n * owns a single `TextDecoder` (with `stream: true`), drives a\n * `reader.read()` loop against `port.readable`, and forwards decoded text\n * to the provided sink. It is **not** subscription-lazy - the `SerialSession`\n * starts the pump as soon as `connect$` succeeds so that `receive$`\n * subscribers never miss data because of subscription timing.\n *\n * Errors are normalized into {@link SerialError} with\n * {@link SerialErrorCode.READ_FAILED} for read-side failures and\n * {@link SerialErrorCode.CONNECTION_LOST} for missing readable streams, so\n * the session can forward them to `errors$` without rewrapping.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/202 | Issue #202}\n */\nexport function createReadPump(\n port: SerialPort,\n { onChunk, onError }: ReadPumpOptions,\n): ReadPump {\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n let running = false;\n let stopped = false;\n const decoder = new TextDecoder(undefined, { fatal: false });\n\n const releaseReader = (): void => {\n if (!reader) {\n return;\n }\n try {\n reader.releaseLock();\n } catch {\n // releaseLock may throw when the reader is already detached; ignore.\n }\n reader = null;\n };\n\n const pump = async (stream: ReadableStream<Uint8Array>): Promise<void> => {\n reader = stream.getReader();\n running = true;\n try {\n while (!stopped) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n if (value && value.byteLength > 0) {\n const text = decoder.decode(value, { stream: true });\n if (text.length > 0) {\n onChunk(text);\n }\n }\n }\n if (!stopped) {\n const tail = decoder.decode();\n if (tail.length > 0) {\n onChunk(tail);\n }\n }\n } catch (error) {\n if (!stopped) {\n onError(\n normalizeSerialError(error, {\n fallbackCode: SerialErrorCode.READ_FAILED,\n messagePrefix: 'Read pump failed',\n }),\n );\n }\n } finally {\n running = false;\n releaseReader();\n }\n };\n\n return {\n start(): void {\n if (running || stopped) {\n return;\n }\n const stream = port.readable;\n if (!stream) {\n stopped = true;\n onError(\n new SerialError(\n SerialErrorCode.CONNECTION_LOST,\n 'Read pump failed: port.readable is not available',\n ),\n );\n return;\n }\n void pump(stream);\n },\n async stop(): Promise<void> {\n if (stopped) {\n return;\n }\n stopped = true;\n if (!reader) {\n return;\n }\n try {\n await reader.cancel();\n } catch {\n // Cancel can reject when the stream is already errored; ignore so\n // the caller's disconnect flow is not derailed by a read failure.\n } finally {\n releaseReader();\n }\n },\n get isRunning(): boolean {\n return running;\n },\n };\n}\n", "import { Observable, defer } from 'rxjs';\n\n/**\n * A single enqueued unit of work. Operations are awaited sequentially so\n * that `send$` guarantees call order even when the caller fires multiple\n * observables concurrently.\n *\n * @internal\n */\nexport type SendQueueOperation<T> = () => Promise<T>;\n\n/**\n * Internal helper returned by {@link createSendQueue}.\n *\n * The queue is intentionally not an RxJS operator because Issue #199 calls\n * out explicitly that `send$` must not be re-implemented with `mergeMap`\n * (or any other concurrency-altering operator). A chained `Promise<void>`\n * is the simplest way to guarantee strict FIFO ordering while still\n * surfacing per-operation completion back to the caller.\n *\n * @internal\n */\nexport interface SendQueue {\n /**\n * Schedule an operation to run after all previously enqueued operations\n * have settled (resolved or rejected). The returned Observable completes\n * with the operation's resolved value, or errors with its rejection.\n *\n * Subscribing is what actually schedules the work - the queue is driven\n * by `defer`, so late/never-subscribed Observables never enqueue.\n */\n enqueue<T>(operation: SendQueueOperation<T>): Observable<T>;\n /**\n * Reset the internal promise chain. Existing in-flight operations keep\n * running because we do not cancel native promises, but no newly\n * enqueued work will wait for them. Use this when the session is torn\n * down so a fresh `connect$` starts with a clean chain.\n */\n clear(): void;\n}\n\n/**\n * Create an internal send queue that serialises `send$` writes.\n *\n * Ordering model:\n *\n * - Each `enqueue` call appends its operation to a single `Promise<void>`\n * chain.\n * - A failure in one operation does not poison the chain - the next\n * operation still runs (the chain is advanced with a `.then(() => {},\n * () => {})` safety tail).\n * - Unsubscribing before the operation resolves suppresses `next` /\n * `complete` / `error` for that subscriber; the underlying promise is\n * still awaited so later operations continue to respect call order.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/203 | Issue #203}\n */\nexport function createSendQueue(): SendQueue {\n let chain: Promise<void> = Promise.resolve();\n\n return {\n enqueue<T>(operation: SendQueueOperation<T>): Observable<T> {\n return defer(\n () =>\n new Observable<T>((subscriber) => {\n let cancelled = false;\n\n const run = async (): Promise<void> => {\n try {\n const value = await operation();\n if (!cancelled) {\n subscriber.next(value);\n subscriber.complete();\n }\n } catch (error) {\n if (!cancelled) {\n subscriber.error(error);\n }\n }\n };\n\n const scheduled = chain.then(run, run);\n chain = scheduled.then(\n () => undefined,\n () => undefined,\n );\n\n return () => {\n cancelled = true;\n };\n }),\n );\n },\n clear(): void {\n chain = Promise.resolve();\n },\n };\n}\n", "/**\n * Options for creating a {@link SerialSession} via {@link createSerialSession}.\n *\n * These options configure the serial port connection parameters used when\n * calling `port.open` and `navigator.serial.requestPort`. All properties\n * are optional; omitted fields fall back to {@link DEFAULT_SERIAL_SESSION_OPTIONS}.\n *\n * @example\n * ```typescript\n * const session = createSerialSession({\n * baudRate: 115200,\n * dataBits: 8,\n * stopBits: 1,\n * parity: 'none',\n * flowControl: 'none',\n * filters: [{ usbVendorId: 0x1234, usbProductId: 0x5678 }],\n * });\n * ```\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/200 | Issue #200}\n */\nexport interface SerialSessionOptions {\n /**\n * Baud rate for the serial port connection (bits per second).\n *\n * Common values include 9600, 19200, 38400, 57600, 115200, etc.\n * Must match the baud rate configured on the connected device.\n *\n * @default 9600\n */\n baudRate?: number;\n\n /**\n * Number of data bits per character (7 or 8).\n *\n * @default 8\n */\n dataBits?: 7 | 8;\n\n /**\n * Number of stop bits (1 or 2).\n *\n * @default 1\n */\n stopBits?: 1 | 2;\n\n /**\n * Parity checking mode.\n *\n * @default 'none'\n */\n parity?: 'none' | 'even' | 'odd';\n\n /**\n * Buffer size for the underlying read stream, in bytes.\n *\n * @default 255\n */\n bufferSize?: number;\n\n /**\n * Flow control mode.\n *\n * @default 'none'\n */\n flowControl?: 'none' | 'hardware';\n\n /**\n * Filters for port selection when requesting a port.\n *\n * When specified, the port selection dialog will only show devices\n * matching these filters. Each filter can specify `usbVendorId` and/or\n * `usbProductId`.\n */\n filters?: SerialPortFilter[];\n\n /**\n * Optional receive replay: retain recent decoded text **chunks** so late\n * subscribers to {@link SerialSession.receiveReplay$} can read buffered\n * data while a connection is active. Does not change {@link SerialSession.receive$}.\n *\n * @default `{ enabled: false, bufferSize: 512 }` (see {@link DEFAULT_SERIAL_SESSION_OPTIONS})\n */\n receiveReplay?: SerialSessionReceiveReplayOptions;\n}\n\n/**\n * Options for {@link SerialSessionOptions.receiveReplay}.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/265 | Issue #265}\n */\nexport interface SerialSessionReceiveReplayOptions {\n /**\n * When `true`, the session uses a replay buffer for {@link SerialSession.receiveReplay$}\n * for each open connection. When `false` (default), `receiveReplay$` is\n * the same hot stream as {@link SerialSession.receive$} (no chunk replay).\n */\n enabled?: boolean;\n\n /**\n * Retains the last **N** decoded text chunks (one emission per `onChunk`\n * from the read pump) in the replay buffer. Not the character count. Higher\n * `bufferSize` uses more memory.\n *\n * @default 512\n */\n bufferSize?: number;\n}\n\nconst DEFAULT_RECEIVE_REPLAY: Required<SerialSessionReceiveReplayOptions> = {\n enabled: false,\n bufferSize: 512,\n};\n\n/**\n * Default values applied to omitted {@link SerialSessionOptions} fields.\n *\n * @internal\n */\nexport const DEFAULT_SERIAL_SESSION_OPTIONS: Required<\n Omit<SerialSessionOptions, 'filters' | 'receiveReplay'>\n> & { filters?: SerialPortFilter[]; receiveReplay: Required<SerialSessionReceiveReplayOptions> } = {\n baudRate: 9600,\n dataBits: 8,\n stopBits: 1,\n parity: 'none',\n bufferSize: 255,\n flowControl: 'none',\n filters: undefined,\n receiveReplay: { ...DEFAULT_RECEIVE_REPLAY },\n};\n", "/**\n * Reactive lifecycle state for a {@link SerialSession}.\n *\n * This is the v2 API counterpart of the legacy `SerialState` used by\n * `SerialClient`. The runtime values are the same flat strings v1\n * consumers used for UI switches; the {@link SerialSessionState} const\n * object is the canonical source of those literals so call sites can\n * avoid string typos and get IDE completion.\n *\n * Lifecycle transitions:\n *\n * ```\n * idle -> connecting -> connected -> disconnecting -> idle\n * \\-> error\n * (any) -> unsupported (when Web Serial API is unavailable)\n * (any) -> error (when an unrecoverable failure occurs)\n * ```\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/200 | Issue #200}\n */\nexport const SerialSessionState = {\n Idle: 'idle',\n Connecting: 'connecting',\n Connected: 'connected',\n Disconnecting: 'disconnecting',\n Unsupported: 'unsupported',\n Error: 'error',\n} as const;\n\n/**\n * String union of allowed {@link SerialSessionState} runtime values\n * (same set as the values on the {@link SerialSessionState} object).\n */\nexport type SerialSessionState =\n (typeof SerialSessionState)[keyof typeof SerialSessionState];\n", "import { BehaviorSubject, Observable } from 'rxjs';\nimport { SerialSessionState } from './serial-session-state';\n\n/**\n * Allowed transitions for the internal SerialSession state machine.\n *\n * Keys are the source state, values are the set of states that the source\n * is allowed to transition into. Transitions missing from this map are\n * treated as invalid and silently rejected (with a `console.warn`) so that\n * a logic bug in one sub-issue cannot corrupt `state$` for downstream\n * consumers.\n *\n * Lifecycle model (matches {@link SerialSessionState}):\n *\n * ```\n * idle -> connecting -> connected -> disconnecting -> idle\n * \\-> error\n * error -> idle (reset / retry)\n * unsupported (terminal; entered only at construction time)\n * ```\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/201 | Issue #201}\n */\nconst S = SerialSessionState;\n\nconst ALLOWED_TRANSITIONS: Readonly<\n Record<SerialSessionState, readonly SerialSessionState[]>\n> = {\n [S.Idle]: [S.Connecting, S.Error],\n [S.Connecting]: [S.Connected, S.Error, S.Idle],\n [S.Connected]: [S.Disconnecting, S.Error],\n [S.Disconnecting]: [S.Idle, S.Error],\n [S.Error]: [S.Idle, S.Connecting],\n [S.Unsupported]: [],\n};\n\n/**\n * Internal state machine that backs {@link SerialSession.state$}.\n *\n * The machine is deliberately kept as an internal module (not exported\n * from the package) because the public surface is only the Observable.\n * Sub-issues of #199 (read pump / send queue / errors) drive transitions\n * through the dedicated `to*` methods below instead of mutating a shared\n * `BehaviorSubject` directly.\n *\n * Design notes:\n *\n * - Invalid transitions are silently rejected so that the `state$` stream\n * never emits a state that violates the lifecycle contract. A\n * `console.warn` is emitted in development builds to aid debugging.\n * - `unsupported` is terminal: once entered (during construction when\n * `navigator.serial` is missing), the machine refuses every further\n * transition.\n * - `state$` is derived from a {@link BehaviorSubject} so late subscribers\n * still receive the current state on subscription.\n *\n * @internal\n */\nexport class SessionStateMachine {\n private readonly subject: BehaviorSubject<SerialSessionState>;\n\n constructor(initial: SerialSessionState = SerialSessionState.Idle) {\n this.subject = new BehaviorSubject<SerialSessionState>(initial);\n }\n\n get current(): SerialSessionState {\n return this.subject.getValue();\n }\n\n get state$(): Observable<SerialSessionState> {\n return this.subject.asObservable();\n }\n\n toConnecting(): boolean {\n return this.transition(S.Connecting);\n }\n\n toConnected(): boolean {\n return this.transition(S.Connected);\n }\n\n toDisconnecting(): boolean {\n return this.transition(S.Disconnecting);\n }\n\n toIdle(): boolean {\n return this.transition(S.Idle);\n }\n\n toError(): boolean {\n return this.transition(S.Error);\n }\n\n toUnsupported(): boolean {\n return this.transition(S.Unsupported);\n }\n\n complete(): void {\n this.subject.complete();\n }\n\n private transition(next: SerialSessionState): boolean {\n const current = this.subject.getValue();\n\n if (current === next) {\n return false;\n }\n\n const allowed = ALLOWED_TRANSITIONS[current];\n if (!allowed.includes(next)) {\n if (typeof console !== 'undefined' && console.warn) {\n console.warn(\n `[web-serial-rxjs] Ignoring invalid SerialSession transition ${current} -> ${next}`,\n );\n }\n return false;\n }\n\n this.subject.next(next);\n return true;\n }\n}\n", "import { type Observable, map, scan, shareReplay } from 'rxjs';\n\n/** @internal Folded state between {@link createTerminalBuffer} emissions. */\nexport interface TerminalBufferState {\n completed: string;\n currentLine: string;\n}\n\n/**\n * Applies one raw decoder chunk to terminal display state.\n * Handles `\\r\\n` and lone `\\n` as line endings, and lone `\\r` as\n * carriage return (clear current line for redraw). Does not interpret ANSI escapes.\n *\n * @internal Exported for unit tests.\n */\nexport function applyTerminalChunk(\n state: TerminalBufferState,\n chunk: string,\n): TerminalBufferState {\n let { completed, currentLine } = state;\n const len = chunk.length;\n\n for (let i = 0; i < len; i++) {\n const c = chunk.charAt(i);\n if (c === '\\r') {\n const next = i + 1 < len ? chunk.charAt(i + 1) : '';\n if (next === '\\n') {\n completed += currentLine + '\\n';\n currentLine = '';\n i++;\n } else {\n currentLine = '';\n }\n } else if (c === '\\n') {\n completed += currentLine + '\\n';\n currentLine = '';\n } else {\n currentLine += c;\n }\n }\n\n return { completed, currentLine };\n}\n\n/** @internal */\nexport function terminalDisplayText(state: TerminalBufferState): string {\n return state.completed + state.currentLine;\n}\n\nexport interface TerminalBuffer {\n /**\n * Cumulative text suitable for terminal-style display: completed lines plus\n * the current line, with `\\r` redraws collapsed per Issue #275.\n */\n readonly text$: Observable<string>;\n}\n\nconst initialTerminalState: TerminalBufferState = {\n completed: '',\n currentLine: '',\n};\n\n/**\n * Builds a terminal-oriented text stream from {@link SerialSession.receive$} (or any\n * `Observable<string>` of decoded chunks). Uses internal buffering so callers need not\n * implement carriage-return collapse themselves.\n */\nexport function createTerminalBuffer(\n receive$: Observable<string>,\n): TerminalBuffer {\n const text$ = receive$.pipe(\n scan(\n (state, chunk: string) => applyTerminalChunk(state, chunk),\n initialTerminalState,\n ),\n map(terminalDisplayText),\n shareReplay({ bufferSize: 1, refCount: true }),\n );\n\n return { text$ };\n}\n"],
|
|
5
|
+
"mappings": ";AAAA;AAAA,EACE,mBAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACiBA,IAAK,kBAAL,kBAAKC,qBAAL;AAUL,EAAAA,iBAAA,2BAAwB;AAWxB,EAAAA,iBAAA,wBAAqB;AAUrB,EAAAA,iBAAA,sBAAmB;AAUnB,EAAAA,iBAAA,uBAAoB;AAUpB,EAAAA,iBAAA,mBAAgB;AAUhB,EAAAA,iBAAA,iBAAc;AAUd,EAAAA,iBAAA,kBAAe;AAWf,EAAAA,iBAAA,qBAAkB;AAUlB,EAAAA,iBAAA,4BAAyB;AAWzB,EAAAA,iBAAA,yBAAsB;AAQtB,EAAAA,iBAAA,uBAAoB;AAUpB,EAAAA,iBAAA,aAAU;AAzHA,SAAAA;AAAA,GAAA;;;ACML,IAAM,cAAN,MAAM,qBAAoB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBrC,YAAY,MAAuB,SAAiB,eAAuB;AACzE,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,gBAAgB;AAIrB,QAAK,MAAc,mBAAmB;AAEpC,MAAC,MAAc,kBAAkB,MAAM,YAAW;AAAA,IACpD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBO,GAAG,MAAgC;AACxC,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;;;ACvEO,SAAS,oBACd,SACsC;AACtC,MAAI,CAAC,WAAW,CAAC,QAAQ,WAAW,QAAQ,QAAQ,WAAW,GAAG;AAChE,WAAO;AAAA,EACT;AAEA,aAAW,UAAU,QAAQ,SAAS;AACpC,QAAI,CAAC,OAAO,eAAe,CAAC,OAAO,cAAc;AAC/C,YAAM,IAAI;AAAA;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,gBAAgB,QAAW;AACpC,UACE,CAAC,OAAO,UAAU,OAAO,WAAW,KACpC,OAAO,cAAc,KACrB,OAAO,cAAc,OACrB;AACA,cAAM,IAAI;AAAA;AAAA,UAER,wBAAwB,OAAO,WAAW;AAAA,QAC5C;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,iBAAiB,QAAW;AACrC,UACE,CAAC,OAAO,UAAU,OAAO,YAAY,KACrC,OAAO,eAAe,KACtB,OAAO,eAAe,OACtB;AACA,cAAM,IAAI;AAAA;AAAA,UAER,yBAAyB,OAAO,YAAY;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS,QAAQ;AAAA,EACnB;AACF;;;ACtDO,SAAS,sBAA+B;AAC7C,SACE,OAAO,cAAc,eACrB,YAAY,aACZ,UAAU,WAAW,UACrB,UAAU,WAAW;AAEzB;;;ACRO,SAAS,mBAGd;AACA,MAAI,SAAS;AAEb,QAAM,QAAQ,MAAY;AACxB,aAAS;AAAA,EACX;AAEA,QAAM,OAAO,CAAC,UAA4B;AACxC,cAAU;AACV,UAAM,MAAgB,CAAC;AAEvB,eAAS;AACP,YAAM,OAAO,OAAO,QAAQ,MAAM;AAClC,UAAI,QAAQ,GAAG;AACb,YAAI,KAAK,OAAO,MAAM,GAAG,IAAI,CAAC;AAC9B,iBAAS,OAAO,MAAM,OAAO,CAAC;AAC9B;AAAA,MACF;AAIA,YAAM,KAAK,OAAO,QAAQ,IAAI;AAC9B,UAAI,MAAM,KAAK,KAAK,IAAI,OAAO,UAAU,OAAO,KAAK,CAAC,MAAM,MAAM;AAChE,YAAI,KAAK,OAAO,MAAM,GAAG,EAAE,CAAC;AAC5B,iBAAS,OAAO,MAAM,KAAK,CAAC;AAC5B;AAAA,MACF;AAEA,YAAM,KAAK,OAAO,QAAQ,IAAI;AAC9B,UAAI,MAAM,GAAG;AACX,YAAI,KAAK,OAAO,MAAM,GAAG,EAAE,CAAC;AAC5B,iBAAS,OAAO,MAAM,KAAK,CAAC;AAC5B;AAAA,MACF;AAEA,UAAI,MAAM,KAAK,KAAK,MAAM,OAAO,QAAQ;AACvC;AAAA,MACF;AAEA;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,MAAM,MAAM;AACvB;;;AChDA,IAAM,yBAAyB;AA0B/B,IAAM,yBAAyB,CAC7B,OACA,SAEA,OAAO,iBAAiB,eACxB,iBAAiB,gBACjB,MAAM,SAAS;AA8BV,SAAS,qBACd,OACA,SACa;AACb,MAAI,iBAAiB,aAAa;AAChC,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,QAAQ,iBAAiB;AAExC,MAAI,uBAAuB,OAAO,eAAe,GAAG;AAClD,WAAO,IAAI;AAAA;AAAA,MAET;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACtE,SAAO,IAAI;AAAA,IACT,QAAQ;AAAA,IACR,GAAG,MAAM,KAAK,MAAM,OAAO;AAAA,IAC3B;AAAA,EACF;AACF;;;ACjBO,SAAS,eACd,MACA,EAAE,SAAS,QAAQ,GACT;AACV,MAAI,SAAyD;AAC7D,MAAI,UAAU;AACd,MAAI,UAAU;AACd,QAAM,UAAU,IAAI,YAAY,QAAW,EAAE,OAAO,MAAM,CAAC;AAE3D,QAAM,gBAAgB,MAAY;AAChC,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AACA,QAAI;AACF,aAAO,YAAY;AAAA,IACrB,QAAQ;AAAA,IAER;AACA,aAAS;AAAA,EACX;AAEA,QAAM,OAAO,OAAO,WAAsD;AACxE,aAAS,OAAO,UAAU;AAC1B,cAAU;AACV,QAAI;AACF,aAAO,CAAC,SAAS;AACf,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,MAAM;AACR;AAAA,QACF;AACA,YAAI,SAAS,MAAM,aAAa,GAAG;AACjC,gBAAM,OAAO,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AACnD,cAAI,KAAK,SAAS,GAAG;AACnB,oBAAQ,IAAI;AAAA,UACd;AAAA,QACF;AAAA,MACF;AACA,UAAI,CAAC,SAAS;AACZ,cAAM,OAAO,QAAQ,OAAO;AAC5B,YAAI,KAAK,SAAS,GAAG;AACnB,kBAAQ,IAAI;AAAA,QACd;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,UAAI,CAAC,SAAS;AACZ;AAAA,UACE,qBAAqB,OAAO;AAAA,YAC1B;AAAA,YACA,eAAe;AAAA,UACjB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,UAAE;AACA,gBAAU;AACV,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAc;AACZ,UAAI,WAAW,SAAS;AACtB;AAAA,MACF;AACA,YAAM,SAAS,KAAK;AACpB,UAAI,CAAC,QAAQ;AACX,kBAAU;AACV;AAAA,UACE,IAAI;AAAA;AAAA,YAEF;AAAA,UACF;AAAA,QACF;AACA;AAAA,MACF;AACA,WAAK,KAAK,MAAM;AAAA,IAClB;AAAA,IACA,MAAM,OAAsB;AAC1B,UAAI,SAAS;AACX;AAAA,MACF;AACA,gBAAU;AACV,UAAI,CAAC,QAAQ;AACX;AAAA,MACF;AACA,UAAI;AACF,cAAM,OAAO,OAAO;AAAA,MACtB,QAAQ;AAAA,MAGR,UAAE;AACA,sBAAc;AAAA,MAChB;AAAA,IACF;AAAA,IACA,IAAI,YAAqB;AACvB,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AChLA,SAAS,YAAY,aAAa;AA2D3B,SAAS,kBAA6B;AAC3C,MAAI,QAAuB,QAAQ,QAAQ;AAE3C,SAAO;AAAA,IACL,QAAW,WAAiD;AAC1D,aAAO;AAAA,QACL,MACE,IAAI,WAAc,CAAC,eAAe;AAChC,cAAI,YAAY;AAEhB,gBAAM,MAAM,YAA2B;AACrC,gBAAI;AACF,oBAAM,QAAQ,MAAM,UAAU;AAC9B,kBAAI,CAAC,WAAW;AACd,2BAAW,KAAK,KAAK;AACrB,2BAAW,SAAS;AAAA,cACtB;AAAA,YACF,SAAS,OAAO;AACd,kBAAI,CAAC,WAAW;AACd,2BAAW,MAAM,KAAK;AAAA,cACxB;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,YAAY,MAAM,KAAK,KAAK,GAAG;AACrC,kBAAQ,UAAU;AAAA,YAChB,MAAM;AAAA,YACN,MAAM;AAAA,UACR;AAEA,iBAAO,MAAM;AACX,wBAAY;AAAA,UACd;AAAA,QACF,CAAC;AAAA,MACL;AAAA,IACF;AAAA,IACA,QAAc;AACZ,cAAQ,QAAQ,QAAQ;AAAA,IAC1B;AAAA,EACF;AACF;;;ACWA,IAAM,yBAAsE;AAAA,EAC1E,SAAS;AAAA,EACT,YAAY;AACd;AAOO,IAAM,iCAEsF;AAAA,EACjG,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,SAAS;AAAA,EACT,eAAe,EAAE,GAAG,uBAAuB;AAC7C;;;AC9GO,IAAM,qBAAqB;AAAA,EAChC,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,eAAe;AAAA,EACf,aAAa;AAAA,EACb,OAAO;AACT;;;AC5BA,SAAS,uBAAmC;AAwB5C,IAAM,IAAI;AAEV,IAAM,sBAEF;AAAA,EACF,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,YAAY,EAAE,KAAK;AAAA,EAChC,CAAC,EAAE,UAAU,GAAG,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI;AAAA,EAC7C,CAAC,EAAE,SAAS,GAAG,CAAC,EAAE,eAAe,EAAE,KAAK;AAAA,EACxC,CAAC,EAAE,aAAa,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK;AAAA,EACnC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,MAAM,EAAE,UAAU;AAAA,EAChC,CAAC,EAAE,WAAW,GAAG,CAAC;AACpB;AAwBO,IAAM,sBAAN,MAA0B;AAAA,EAG/B,YAAY,UAA8B,mBAAmB,MAAM;AACjE,SAAK,UAAU,IAAI,gBAAoC,OAAO;AAAA,EAChE;AAAA,EAEA,IAAI,UAA8B;AAChC,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC/B;AAAA,EAEA,IAAI,SAAyC;AAC3C,WAAO,KAAK,QAAQ,aAAa;AAAA,EACnC;AAAA,EAEA,eAAwB;AACtB,WAAO,KAAK,WAAW,EAAE,UAAU;AAAA,EACrC;AAAA,EAEA,cAAuB;AACrB,WAAO,KAAK,WAAW,EAAE,SAAS;AAAA,EACpC;AAAA,EAEA,kBAA2B;AACzB,WAAO,KAAK,WAAW,EAAE,aAAa;AAAA,EACxC;AAAA,EAEA,SAAkB;AAChB,WAAO,KAAK,WAAW,EAAE,IAAI;AAAA,EAC/B;AAAA,EAEA,UAAmB;AACjB,WAAO,KAAK,WAAW,EAAE,KAAK;AAAA,EAChC;AAAA,EAEA,gBAAyB;AACvB,WAAO,KAAK,WAAW,EAAE,WAAW;AAAA,EACtC;AAAA,EAEA,WAAiB;AACf,SAAK,QAAQ,SAAS;AAAA,EACxB;AAAA,EAEQ,WAAW,MAAmC;AACpD,UAAM,UAAU,KAAK,QAAQ,SAAS;AAEtC,QAAI,YAAY,MAAM;AACpB,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,oBAAoB,OAAO;AAC3C,QAAI,CAAC,QAAQ,SAAS,IAAI,GAAG;AAC3B,UAAI,OAAO,YAAY,eAAe,QAAQ,MAAM;AAClD,gBAAQ;AAAA,UACN,+DAA+D,OAAO,OAAO,IAAI;AAAA,QACnF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,SAAK,QAAQ,KAAK,IAAI;AACtB,WAAO;AAAA,EACT;AACF;;;AXpCO,SAAS,oBACd,SACe;AACf,QAAM,kBAAkB;AAAA,IACtB,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS,SAAS;AAAA,IAClB,eAAe;AAAA,MACb,GAAG,+BAA+B;AAAA,MAClC,GAAG,SAAS;AAAA,IACd;AAAA,EACF;AAEA,QAAM,YAAY,oBAAoB;AACtC,QAAM,UAAU,IAAI;AAAA,IAClB,YAAY,mBAAmB,OAAO,mBAAmB;AAAA,EAC3D;AACA,QAAM,gBAAgB,IAAI,QAAqB;AAC/C,QAAM,iBAAiB,IAAI,QAAgB;AAC3C,QAAM,eAAe,IAAI,QAAgB;AACzC,QAAM,YAAY,gBAAgB;AAClC,QAAM,cAAc,IAAI,YAAY;AACpC,QAAM,aAAa,iBAAiB;AAEpC,QAAM,UAAU,cAAc,aAAa;AAC3C,QAAM,WAAW,eAAe,aAAa;AAC7C,QAAM,SAAS,aAAa,aAAa;AAEzC,QAAM,eAAe,QAAQ,OAAO;AAAA,IAClC,IAAI,CAAC,UAAU,UAAU,mBAAmB,SAAS;AAAA,IACrD,qBAAqB;AAAA,EACvB;AAEA,QAAM,kBAAkB,IAAIC,iBAAuC,IAAI;AACvE,QAAM,YAAY,gBAAgB,aAAa;AAE/C,QAAM,uBAAuB,gBAAgB,cAAc,UACvD,IAAIA,iBAAoC,QAAQ,IAChD;AACJ,MAAI,sBAAoD;AAExD,QAAM,yBAAyB,MAAY;AACzC,QAAI,sBAAsB;AACxB,UAAI,qBAAqB;AACvB,4BAAoB,SAAS;AAC7B,8BAAsB;AAAA,MACxB;AACA,2BAAqB,KAAK,QAAQ;AAAA,IACpC;AAAA,EACF;AAEA,QAAM,yBAAyB,MAAY;AACzC,QAAI,CAAC,sBAAsB;AACzB;AAAA,IACF;AACA,QAAI,qBAAqB;AACvB,0BAAoB,SAAS;AAC7B,4BAAsB;AAAA,IACxB;AACA,UAAM,KAAK,IAAI,cAAsB,gBAAgB,cAAc,UAAU;AAC7E,0BAAsB;AACtB,yBAAqB,KAAK,GAAG,aAAa,CAAC;AAAA,EAC7C;AAEA,QAAM,iBAAiB,uBACnB,qBAAqB,KAAK,UAAU,CAAC,UAAU,KAAK,GAAG,MAAM,CAAC,IAC9D;AAEJ,MAAI,aAAgC;AACpC,MAAI,aAA8B;AAElC,QAAM,gBAAgB,CAAC,SAAkC;AACvD,iBAAa;AACb,oBAAgB,KAAK,OAAO,KAAK,QAAQ,IAAI,IAAI;AAAA,EACnD;AAEA,QAAM,eAAe,YAA2B;AAC9C,2BAAuB;AACvB,UAAM,OAAO;AACb,iBAAa;AACb,eAAW,MAAM;AACjB,QAAI,MAAM;AACR,YAAM,KAAK,KAAK;AAAA,IAClB;AAAA,EACF;AAEA,QAAM,kBAAkB,OAAO,SAA2C;AACxE,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AACA,QAAI;AACF,YAAM,KAAK,MAAM;AAAA,IACnB,QAAQ;AAAA,IAIR;AAAA,EACF;AAiBA,QAAM,cAAc,CAClB,OACA,UACAC,aACgB;AAChB,UAAM,cAAc,qBAAqB,OAAOA,QAAO;AACvD,kBAAc,KAAK,WAAW;AAC9B,QAAI,aAAa,SAAS;AACxB,cAAQ,QAAQ;AAChB,gBAAU,MAAM;AAChB,YAAM,cAAc;AACpB,oBAAc,IAAI;AAClB,WAAK,aAAa,EAAE,KAAK,MAAM,gBAAgB,WAAW,CAAC;AAAA,IAC7D;AACA,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,OAAO,YAAuC;AAChE,UAAM,OAAO;AACb,QAAI,QAAQ,YAAY,mBAAmB,aAAa,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC/E,YAAM,IAAI;AAAA;AAAA,QAER;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,KAAK,SAAS,UAAU;AACvC,QAAI;AACF,YAAM,OAAO,MAAM,OAAO;AAAA,IAC5B,UAAE;AACA,UAAI;AACF,eAAO,YAAY;AAAA,MACrB,QAAQ;AAAA,MAIR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,qBAA8B;AAC5B,aAAO,oBAAoB;AAAA,IAC7B;AAAA,IACA,WAA6B;AAC3B,aAAO,IAAIC,YAAiB,CAAC,eAAe;AAC1C,YAAI,CAAC,oBAAoB,GAAG;AAC1B,gBAAM,QAAQ;AAAA,YACZ,IAAI;AAAA;AAAA,cAEF;AAAA,YACF;AAAA,YACA;AAAA,YACA,EAAE,kEAAoD;AAAA,UACxD;AACA,qBAAW,MAAM,KAAK;AACtB;AAAA,QACF;AAEA,cAAM,UAAU,QAAQ;AACxB,YACE,YAAY,mBAAmB,QAC/B,YAAY,mBAAmB,OAC/B;AACA,gBAAM,QAAQ;AAAA,YACZ,IAAI;AAAA;AAAA,cAEF,0CAA0C,OAAO;AAAA,YACnD;AAAA,YACA;AAAA,YACA,EAAE,0DAAgD;AAAA,UACpD;AACA,qBAAW,MAAM,KAAK;AACtB;AAAA,QACF;AAEA,YAAI,YAAY;AAChB,gBAAQ,aAAa;AAErB,cAAM,MAAM,YAA2B;AACrC,cAAI,eAAkC;AACtC,cAAI;AACF,2BAAe,MAAM,UAAU,OAAO;AAAA,cACpC,oBAAoB,eAAe;AAAA,YACrC;AACA,kBAAM,aAAa,KAAK;AAAA,cACtB,UAAU,gBAAgB;AAAA,cAC1B,UAAU,gBAAgB;AAAA,cAC1B,UAAU,gBAAgB;AAAA,cAC1B,QAAQ,gBAAgB;AAAA,cACxB,YAAY,gBAAgB;AAAA,cAC5B,aAAa,gBAAgB;AAAA,YAC/B,CAAC;AAAA,UACH,SAAS,OAAO;AACd,gBAAI,cAAc;AAChB,oBAAM,gBAAgB,YAAY;AAAA,YACpC;AACA,kBAAM,cAAc,YAAY,OAAO,SAAS;AAAA,cAC9C;AAAA,cACA,eAAe;AAAA,YACjB,CAAC;AACD,gBAAI,CAAC,WAAW;AACd,yBAAW,MAAM,WAAW;AAAA,YAC9B;AACA;AAAA,UACF;AAEA,cAAI,WAAW;AACb,kBAAM,gBAAgB,YAAY;AAClC;AAAA,UACF;AAEA,wBAAc,YAAY;AAC1B,qBAAW,MAAM;AACjB,cAAI,gBAAgB,cAAc,SAAS;AACzC,mCAAuB;AAAA,UACzB;AACA,uBAAa,eAAe,cAAc;AAAA,YACxC,SAAS,CAAC,SAAS;AACjB,6BAAe,KAAK,IAAI;AACxB,kBAAI,qBAAqB;AACvB,oCAAoB,KAAK,IAAI;AAAA,cAC/B;AACA,yBAAW,QAAQ,WAAW,KAAK,IAAI,GAAG;AACxC,6BAAa,KAAK,IAAI;AAAA,cACxB;AAAA,YACF;AAAA,YACA,SAAS,CAAC,cACR,YAAY,WAAW,SAAS;AAAA,cAC9B;AAAA,cACA,eAAe;AAAA,YACjB,CAAC;AAAA,UACL,CAAC;AACD,qBAAW,MAAM;AACjB,oBAAU,MAAM;AAChB,kBAAQ,YAAY;AACpB,qBAAW,KAAK;AAChB,qBAAW,SAAS;AAAA,QACtB;AAEA,aAAK,IAAI;AAET,eAAO,MAAM;AACX,sBAAY;AAAA,QACd;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,cAAgC;AAC9B,aAAO,IAAIA,YAAiB,CAAC,eAAe;AAC1C,cAAM,UAAU,QAAQ;AAExB,YACE,YAAY,mBAAmB,QAC/B,YAAY,mBAAmB,aAC/B;AACA,qBAAW,KAAK;AAChB,qBAAW,SAAS;AACpB;AAAA,QACF;AAEA,YACE,YAAY,mBAAmB,aAC/B,YAAY,mBAAmB,OAC/B;AACA,gBAAM,QAAQ;AAAA,YACZ,IAAI;AAAA;AAAA,cAEF,6CAA6C,OAAO;AAAA,YACtD;AAAA,YACA;AAAA,YACA,EAAE,kDAA4C;AAAA,UAChD;AACA,qBAAW,MAAM,KAAK;AACtB;AAAA,QACF;AAEA,gBAAQ,gBAAgB;AACxB,kBAAU,MAAM;AAChB,cAAM,cAAc;AAEpB,cAAM,MAAM,YAA2B;AACrC,cAAI;AACF,kBAAM,aAAa;AACnB,gBAAI,aAAa;AACf,kBAAI;AACF,sBAAM,YAAY,MAAM;AAAA,cAC1B,SAAS,OAAO;AACd,8BAAc,IAAI;AAClB,sBAAM,cAAc,YAAY,OAAO,SAAS;AAAA,kBAC9C;AAAA,kBACA,eAAe;AAAA,gBACjB,CAAC;AACD,2BAAW,MAAM,WAAW;AAC5B;AAAA,cACF;AAAA,YACF;AACA,0BAAc,IAAI;AAClB,oBAAQ,OAAO;AACf,uBAAW,KAAK;AAChB,uBAAW,SAAS;AAAA,UACtB,SAAS,OAAO;AACd,kBAAM,cAAc,YAAY,OAAO,SAAS;AAAA,cAC9C;AAAA,cACA,eAAe;AAAA,YACjB,CAAC;AACD,uBAAW,MAAM,WAAW;AAAA,UAC9B;AAAA,QACF;AAEA,aAAK,IAAI;AAAA,MACX,CAAC;AAAA,IACH;AAAA,IACA,MAAM,MAA6C;AACjD,aAAO,UAAU,QAAQ,YAAY;AACnC,cAAM,UACJ,OAAO,SAAS,WAAW,YAAY,OAAO,IAAI,IAAI;AACxD,YAAI;AACF,gBAAM,YAAY,OAAO;AAAA,QAC3B,SAAS,OAAO;AACd,gBAAM,YAAY,OAAO,aAAa;AAAA,YACpC;AAAA,YACA,eAAe;AAAA,UACjB,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,QAAQ,QAAQ;AAAA,IAChB;AAAA,IACA;AAAA,IACA,cAAqC;AACnC,aAAO,gBAAgB,SAAS;AAAA,IAClC;AAAA,IACA,iBAAoC;AAClC,aAAO;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AYvbA,SAA0B,OAAAC,MAAK,MAAM,mBAAmB;AAejD,SAAS,mBACd,OACA,OACqB;AACrB,MAAI,EAAE,WAAW,YAAY,IAAI;AACjC,QAAM,MAAM,MAAM;AAElB,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAM,IAAI,MAAM,OAAO,CAAC;AACxB,QAAI,MAAM,MAAM;AACd,YAAM,OAAO,IAAI,IAAI,MAAM,MAAM,OAAO,IAAI,CAAC,IAAI;AACjD,UAAI,SAAS,MAAM;AACjB,qBAAa,cAAc;AAC3B,sBAAc;AACd;AAAA,MACF,OAAO;AACL,sBAAc;AAAA,MAChB;AAAA,IACF,WAAW,MAAM,MAAM;AACrB,mBAAa,cAAc;AAC3B,oBAAc;AAAA,IAChB,OAAO;AACL,qBAAe;AAAA,IACjB;AAAA,EACF;AAEA,SAAO,EAAE,WAAW,YAAY;AAClC;AAGO,SAAS,oBAAoB,OAAoC;AACtE,SAAO,MAAM,YAAY,MAAM;AACjC;AAUA,IAAM,uBAA4C;AAAA,EAChD,WAAW;AAAA,EACX,aAAa;AACf;AAOO,SAAS,qBACd,UACgB;AAChB,QAAM,QAAQ,SAAS;AAAA,IACrB;AAAA,MACE,CAAC,OAAO,UAAkB,mBAAmB,OAAO,KAAK;AAAA,MACzD;AAAA,IACF;AAAA,IACAA,KAAI,mBAAmB;AAAA,IACvB,YAAY,EAAE,YAAY,GAAG,UAAU,KAAK,CAAC;AAAA,EAC/C;AAEA,SAAO,EAAE,MAAM;AACjB;",
|
|
6
|
+
"names": ["BehaviorSubject", "Observable", "SerialErrorCode", "BehaviorSubject", "options", "Observable", "map"]
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-serial-session.d.ts","sourceRoot":"","sources":["../../src/session/create-serial-session.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create-serial-session.d.ts","sourceRoot":"","sources":["../../src/session/create-serial-session.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAgBlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,aAAa,CA+Vf"}
|
package/dist/session/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createSerialSession } from './create-serial-session';
|
|
2
2
|
export type { SerialSession } from './serial-session';
|
|
3
|
-
export type { SerialSessionOptions } from './serial-session-options';
|
|
3
|
+
export type { SerialSessionOptions, SerialSessionReceiveReplayOptions, } from './serial-session-options';
|
|
4
4
|
export { SerialSessionState } from './serial-session-state';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EACV,oBAAoB,EACpB,iCAAiC,GAClC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -68,13 +68,43 @@ export interface SerialSessionOptions {
|
|
|
68
68
|
* `usbProductId`.
|
|
69
69
|
*/
|
|
70
70
|
filters?: SerialPortFilter[];
|
|
71
|
+
/**
|
|
72
|
+
* Optional receive replay: retain recent decoded text **chunks** so late
|
|
73
|
+
* subscribers to {@link SerialSession.receiveReplay$} can read buffered
|
|
74
|
+
* data while a connection is active. Does not change {@link SerialSession.receive$}.
|
|
75
|
+
*
|
|
76
|
+
* @default `{ enabled: false, bufferSize: 512 }` (see {@link DEFAULT_SERIAL_SESSION_OPTIONS})
|
|
77
|
+
*/
|
|
78
|
+
receiveReplay?: SerialSessionReceiveReplayOptions;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Options for {@link SerialSessionOptions.receiveReplay}.
|
|
82
|
+
*
|
|
83
|
+
* @see {@link https://github.com/gurezo/web-serial-rxjs/issues/265 | Issue #265}
|
|
84
|
+
*/
|
|
85
|
+
export interface SerialSessionReceiveReplayOptions {
|
|
86
|
+
/**
|
|
87
|
+
* When `true`, the session uses a replay buffer for {@link SerialSession.receiveReplay$}
|
|
88
|
+
* for each open connection. When `false` (default), `receiveReplay$` is
|
|
89
|
+
* the same hot stream as {@link SerialSession.receive$} (no chunk replay).
|
|
90
|
+
*/
|
|
91
|
+
enabled?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Retains the last **N** decoded text chunks (one emission per `onChunk`
|
|
94
|
+
* from the read pump) in the replay buffer. Not the character count. Higher
|
|
95
|
+
* `bufferSize` uses more memory.
|
|
96
|
+
*
|
|
97
|
+
* @default 512
|
|
98
|
+
*/
|
|
99
|
+
bufferSize?: number;
|
|
71
100
|
}
|
|
72
101
|
/**
|
|
73
102
|
* Default values applied to omitted {@link SerialSessionOptions} fields.
|
|
74
103
|
*
|
|
75
104
|
* @internal
|
|
76
105
|
*/
|
|
77
|
-
export declare const DEFAULT_SERIAL_SESSION_OPTIONS: Required<Omit<SerialSessionOptions, 'filters'>> & {
|
|
106
|
+
export declare const DEFAULT_SERIAL_SESSION_OPTIONS: Required<Omit<SerialSessionOptions, 'filters' | 'receiveReplay'>> & {
|
|
78
107
|
filters?: SerialPortFilter[];
|
|
108
|
+
receiveReplay: Required<SerialSessionReceiveReplayOptions>;
|
|
79
109
|
};
|
|
80
110
|
//# sourceMappingURL=serial-session-options.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serial-session-options.d.ts","sourceRoot":"","sources":["../../src/session/serial-session-options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IAEjC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAElC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"serial-session-options.d.ts","sourceRoot":"","sources":["../../src/session/serial-session-options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IAEjC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAElC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAE7B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,iCAAiC,CAAC;CACnD;AAED;;;;GAIG;AACH,MAAM,WAAW,iCAAiC;IAChD;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,EAAE,QAAQ,CACnD,IAAI,CAAC,oBAAoB,EAAE,SAAS,GAAG,eAAe,CAAC,CACxD,GAAG;IAAE,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAAC,aAAa,EAAE,QAAQ,CAAC,iCAAiC,CAAC,CAAA;CAS7F,CAAC"}
|
|
@@ -70,6 +70,29 @@ export interface SerialSession {
|
|
|
70
70
|
* connect/disabled flags without reimplementing the comparison.
|
|
71
71
|
*/
|
|
72
72
|
readonly isConnected$: Observable<boolean>;
|
|
73
|
+
/**
|
|
74
|
+
* The active port’s {@link SerialPort.getInfo} snapshot, or `null` when no
|
|
75
|
+
* port is open (including {@link SerialSessionState.Idle},
|
|
76
|
+
* {@link SerialSessionState.Error}, and {@link SerialSessionState.Unsupported}).
|
|
77
|
+
*
|
|
78
|
+
* Emits the current value on subscribe. Use with {@link state$} to know when
|
|
79
|
+
* the value is valid for your UI.
|
|
80
|
+
*/
|
|
81
|
+
readonly portInfo$: Observable<SerialPortInfo | null>;
|
|
82
|
+
/**
|
|
83
|
+
* Synchronous read of the last {@link portInfo$} value.
|
|
84
|
+
*
|
|
85
|
+
* @returns The same as {@link SerialPort.getInfo} for the open port, or
|
|
86
|
+
* `null` when not connected.
|
|
87
|
+
*/
|
|
88
|
+
getPortInfo(): SerialPortInfo | null;
|
|
89
|
+
/**
|
|
90
|
+
* The underlying `SerialPort` while connected, or `null` otherwise.
|
|
91
|
+
*
|
|
92
|
+
* Avoid calling `port.close()` or replacing streams yourself; that conflicts
|
|
93
|
+
* with session lifecycle. Prefer {@link getPortInfo} for identification.
|
|
94
|
+
*/
|
|
95
|
+
getCurrentPort(): SerialPort | null;
|
|
73
96
|
/**
|
|
74
97
|
* Primary error channel.
|
|
75
98
|
*
|
|
@@ -101,18 +124,42 @@ export interface SerialSession {
|
|
|
101
124
|
* subscription-lazy: emissions happen regardless of whether a consumer
|
|
102
125
|
* is currently subscribed, so late subscribers see only new data.
|
|
103
126
|
*
|
|
104
|
-
* Emits **raw decoder chunks** (not line-aligned)
|
|
105
|
-
*
|
|
106
|
-
*
|
|
127
|
+
* Emits **raw decoder chunks** (not line-aligned): carriage returns and
|
|
128
|
+
* other control characters from the peer are preserved. Use this for
|
|
129
|
+
* terminal-like mirrors, progress output that relies on `\r`, or raw
|
|
130
|
+
* inspection. Do **not** drive those UIs from {@link lines$}, which may
|
|
131
|
+
* split on interior `\r` and break redraw semantics.
|
|
132
|
+
*
|
|
133
|
+
* For newline-framed protocols, logs, or line-by-line parsing, prefer
|
|
134
|
+
* {@link lines$} or derive custom framing from `receive$`.
|
|
135
|
+
*
|
|
136
|
+
* @see {@link https://github.com/gurezo/web-serial-rxjs/issues/273 | Issue #273}
|
|
107
137
|
*/
|
|
108
138
|
readonly receive$: Observable<string>;
|
|
139
|
+
/**
|
|
140
|
+
* Same source data as {@link receive$} but, when
|
|
141
|
+
* {@link SerialSessionOptions.receiveReplay} has `enabled: true`, it uses a
|
|
142
|
+
* replay buffer **per open connection** so new subscribers can receive the
|
|
143
|
+
* last N decoded text **chunks** from that connection. When receive replay
|
|
144
|
+
* is off (default), this is the same hot stream as {@link receive$}.
|
|
145
|
+
*
|
|
146
|
+
* Does not change {@link lines$} (line framing is not replayed here).
|
|
147
|
+
*
|
|
148
|
+
* @see {@link https://github.com/gurezo/web-serial-rxjs/issues/265 | Issue #265}
|
|
149
|
+
*/
|
|
150
|
+
readonly receiveReplay$: Observable<string>;
|
|
109
151
|
/**
|
|
110
152
|
* Decoded text split into **complete lines** using `\n`, `\r\n`, and
|
|
111
|
-
* lone interior `\r` (see implementation).
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
153
|
+
* lone interior `\r` (see implementation). Intended for **logs**,
|
|
154
|
+
* newline-framed command responses, and parsers—not for mirroring raw
|
|
155
|
+
* terminal output where `\r` must be preserved for progress/redraw.
|
|
156
|
+
*
|
|
157
|
+
* A trailing fragment without a line terminator is buffered until a later
|
|
158
|
+
* chunk completes a line, or discarded on disconnect. It is **not**
|
|
159
|
+
* subscription-lazy: the same framing runs whenever the read pump is active,
|
|
160
|
+
* independent of subscribers.
|
|
161
|
+
*
|
|
162
|
+
* @see {@link https://github.com/gurezo/web-serial-rxjs/issues/273 | Issue #273}
|
|
116
163
|
*/
|
|
117
164
|
readonly lines$: Observable<string>;
|
|
118
165
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serial-session.d.ts","sourceRoot":"","sources":["../../src/session/serial-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;OAMG;IACH,kBAAkB,IAAI,OAAO,CAAC;IAE9B;;;;;;;;OAQG;IACH,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IAE7B;;;;;;OAMG;IACH,WAAW,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IAEhC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAEhD;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAE3C;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAE1C
|
|
1
|
+
{"version":3,"file":"serial-session.d.ts","sourceRoot":"","sources":["../../src/session/serial-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;OAMG;IACH,kBAAkB,IAAI,OAAO,CAAC;IAE9B;;;;;;;;OAQG;IACH,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IAE7B;;;;;;OAMG;IACH,WAAW,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IAEhC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAEhD;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAE3C;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAEtD;;;;;OAKG;IACH,WAAW,IAAI,cAAc,GAAG,IAAI,CAAC;IAErC;;;;;OAKG;IACH,cAAc,IAAI,UAAU,GAAG,IAAI,CAAC;IAEpC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAE1C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAEtC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAE5C;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAEpC;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;CACpD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Observable } from 'rxjs';
|
|
2
|
+
/** @internal Folded state between {@link createTerminalBuffer} emissions. */
|
|
3
|
+
export interface TerminalBufferState {
|
|
4
|
+
completed: string;
|
|
5
|
+
currentLine: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Applies one raw decoder chunk to terminal display state.
|
|
9
|
+
* Handles `\r\n` and lone `\n` as line endings, and lone `\r` as
|
|
10
|
+
* carriage return (clear current line for redraw). Does not interpret ANSI escapes.
|
|
11
|
+
*
|
|
12
|
+
* @internal Exported for unit tests.
|
|
13
|
+
*/
|
|
14
|
+
export declare function applyTerminalChunk(state: TerminalBufferState, chunk: string): TerminalBufferState;
|
|
15
|
+
/** @internal */
|
|
16
|
+
export declare function terminalDisplayText(state: TerminalBufferState): string;
|
|
17
|
+
export interface TerminalBuffer {
|
|
18
|
+
/**
|
|
19
|
+
* Cumulative text suitable for terminal-style display: completed lines plus
|
|
20
|
+
* the current line, with `\r` redraws collapsed per Issue #275.
|
|
21
|
+
*/
|
|
22
|
+
readonly text$: Observable<string>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Builds a terminal-oriented text stream from {@link SerialSession.receive$} (or any
|
|
26
|
+
* `Observable<string>` of decoded chunks). Uses internal buffering so callers need not
|
|
27
|
+
* implement carriage-return collapse themselves.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createTerminalBuffer(receive$: Observable<string>): TerminalBuffer;
|
|
30
|
+
//# sourceMappingURL=create-terminal-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-terminal-buffer.d.ts","sourceRoot":"","sources":["../../src/terminal/create-terminal-buffer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAA0B,MAAM,MAAM,CAAC;AAE/D,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,mBAAmB,EAC1B,KAAK,EAAE,MAAM,GACZ,mBAAmB,CAwBrB;AAED,gBAAgB;AAChB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CAEtE;AAED,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;CACpC;AAOD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,GAC3B,cAAc,CAWhB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gurezo/web-serial-rxjs",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "RxJS-based utilities for the Web Serial API, usable from Angular, React, Svelte, and Vanilla JavaScript/TypeScript.",
|
|
5
5
|
"author": "Akihiko Kigure <akihiko.kigure@gmail.com>",
|
|
6
6
|
"license": "MIT",
|