Readonlyerrors$Primary error channel.
All SerialError instances produced by the session (connect / read / write / close) are multiplexed here. This is the main channel, not a supplementary one.
Every emission is the exact same instance that is also surfaced to
the relevant call-site subscriber (for example connect$().subscribe
receives the same SerialError that errors$ emits for that
failure), so a single subscription is enough to observe the full
error history without double-normalisation.
Fatal failures (connect / read / close) additionally drive state$
to 'error' and tear down the live pump + port; non-fatal failures
(currently only send$ write failures) are multiplexed here without
mutating state$, on the assumption that a real connection loss is
detected by the read pump on the next tick.
Readonlyistrue when state$ is 'connected', false for all other states.
Derived from state$ with distinctUntilChanged so UIs can bind
connect/disabled flags without reimplementing the comparison.
Readonlylines$Decoded text split into complete lines using \n, \r\n, and
lone interior \r (see implementation). Intended for logs,
newline-framed command responses, and parsers—not for mirroring raw
terminal output where \r must be preserved for progress/redraw. For
rendering terminal text, prefer terminalText$.
A trailing fragment without a line terminator is buffered until a later chunk completes a line, or discarded on disconnect. It is not subscription-lazy: the same framing runs whenever the read pump is active, independent of subscribers.
ReadonlyportThe active port’s SerialPort.getInfo snapshot, or null when no
port is open (including SerialSessionState.Idle,
SerialSessionState.Error, and SerialSessionState.Unsupported).
Emits the current value on subscribe. Use with state$ to know when the value is valid for your UI.
Readonlyreceive$Incoming data from the serial port as UTF-8 decoded text.
The stream is driven by the read pump started by connect$ and is
decoded internally with a streaming TextDecoder, so multi-byte
characters split across chunks are joined correctly. It is not
subscription-lazy: emissions happen regardless of whether a consumer
is currently subscribed, so late subscribers see only new data.
Emits raw decoder chunks (not line-aligned): carriage returns and
other control characters from the peer are preserved. Use this for
terminal-like mirrors, progress output that relies on \r, or raw
inspection. Do not drive those UIs from lines$, which may
split on interior \r and break redraw semantics.
For newline-framed protocols, logs, or line-by-line parsing, prefer
lines$ or derive custom framing from receive$.
ReadonlyreceiveSame source data as receive$ but, when
SerialSessionOptions.receiveReplay has enabled: true, it uses a
replay buffer per open connection so new subscribers can receive the
last N decoded text chunks from that connection. When receive replay
is off (default), this is the same hot stream as receive$.
Does not change lines$ (line framing is not replayed here).
Readonlystate$Reactive session lifecycle state.
Replays the current state on subscribe. UIs should drive entirely from this stream instead of reconstructing their own BehaviorSubject.
ReadonlyterminalTerminal-display oriented cumulative text derived from receive$.
This stream collapses carriage-return redraws (\r) and keeps normal
newline behavior (\n, \r\n) so apps can bind terminal-like output
directly without wrapping createTerminalBuffer in every consumer.
Equivalent behavior:
createTerminalBuffer(receive$).text$
Open a serial port and start the internal read pump.
Returns an Observable that completes when the port is fully opened and
the read pump is running. Subscribing to receive$ before calling
connect$ is safe: emissions simply start after the pump is active.
An Observable that completes on successful connection.
Close the active serial port and stop the internal read pump.
Safe to call when already disconnected.
An Observable that completes when the port is fully closed.
The underlying SerialPort while connected, or null otherwise.
Avoid calling port.close() or replacing streams yourself; that conflicts
with session lifecycle. Prefer getPortInfo for identification.
Synchronous read of the last portInfo$ value.
The same as SerialPort.getInfo for the open port, or
null when not connected.
Synchronous feature detection for the Web Serial API.
Never throws; intended for UI branching before calling connect$.
true when navigator.serial is available.
Enqueue data for ordered transmission.
Writes are serialized internally through a FIFO send queue so that
concurrent send$ calls are delivered to the port in call order,
regardless of how quickly each subscriber runs. String payloads are
UTF-8 encoded via a shared TextEncoder; Uint8Array payloads are
passed through unchanged. Write failures are normalized into
SerialError with SerialErrorCode.WRITE_FAILED and
multiplexed on SerialSession.errors$ in addition to being
surfaced to the subscriber, so a single subscription is enough to
observe every I/O error. Calling send$ while the session is not in
'connected' state fails fast with
SerialErrorCode.PORT_NOT_OPEN.
The returned Observable completes once the enqueued payload has been flushed to the underlying writer.
Text (UTF-8 encoded) or raw bytes to send.
An Observable that completes when the payload is written.
v2 public API for interacting with the Web Serial API through a minimal, session-oriented surface.
The session is intentionally slim so that apps (Angular, Vue, React, etc.) can drive their UI purely from
state$+isConnected$+receive$+terminalText$+lines$+errors$and never have to rebuild BehaviorSubjects, manage a read loop, or serialize writes themselves.All imperative Web Serial work (open / read loop / write / close) is encapsulated by the implementation. Only Observables are exposed.
Example
See