|
| 1 | +## PhysicalChannel |
| 2 | + |
| 3 | +`PhysicalChannel<T>` is the interface every concrete transport implements. |
| 4 | +`T` is the message payload type --- either `string`, `Uint8Array`, or the |
| 5 | +union of both. |
| 6 | + |
| 7 | +```ts |
| 8 | +interface PhysicalChannel<T extends string | Uint8Array> { |
| 9 | + // Reactive surface |
| 10 | + state$: BehaviorSubject<ChannelState>; |
| 11 | + open$: Observable<PhysicalChannel<T>>; |
| 12 | + close$: Observable<[self: PhysicalChannel<T>, event: CloseEventBase]>; |
| 13 | + error$: Observable<Error>; |
| 14 | + message$: Observable<T>; |
| 15 | + |
| 16 | + // Imperative surface |
| 17 | + closed: boolean; |
| 18 | + onmessage?: (data: T, isUtf8: boolean) => void; |
| 19 | + onclose?: (code: number, reason: string, wasClean: boolean) => void; |
| 20 | + |
| 21 | + isOpen(): boolean; |
| 22 | + send(data: T): number; // returns bytes buffered, -1 if not ready |
| 23 | + send$(data: T): Observable<number>; // waits for open, then sends |
| 24 | + close(code?, reason?): void; |
| 25 | + buffer(): number; // bytes currently buffered out |
| 26 | +} |
| 27 | +``` |
| 28 | + |
| 29 | + |
| 30 | +## States |
| 31 | + |
| 32 | +The `ChannelState` enum reflects the connection lifecycle: |
| 33 | + |
| 34 | +| Value | Meaning | |
| 35 | +|---|---| |
| 36 | +| `CONNECTING` | Initial; not yet open | |
| 37 | +| `OPEN` | Ready to send/receive | |
| 38 | +| `CLOSED` | Terminal; cannot be reopened | |
| 39 | + |
| 40 | +`state$` is a `BehaviorSubject`, so subscribers get the current state |
| 41 | +immediately. `open$` and `close$` are `ReplaySubject(1)` --- subscribing |
| 42 | +after the event still fires the callback. |
| 43 | + |
| 44 | + |
| 45 | +## Two ways to subscribe |
| 46 | + |
| 47 | +`message$` (observable) and `onmessage` (callback) deliver the same data. |
| 48 | +Pick whichever fits the consumer: |
| 49 | + |
| 50 | +```ts |
| 51 | +// Callback flavor |
| 52 | +channel.onmessage = (data, isUtf8) => handle(data); |
| 53 | + |
| 54 | +// RxJS flavor |
| 55 | +channel.message$.subscribe((data) => handle(data)); |
| 56 | +``` |
| 57 | + |
| 58 | +Same applies to close: `onclose` and `close$` both fire once on disconnect. |
| 59 | + |
| 60 | + |
| 61 | +## `send()` vs `send$()` |
| 62 | + |
| 63 | +- `send(data)` is **fire-and-forget**: writes immediately if open, returns |
| 64 | + the number of bytes now buffered. Returns `-1` if the channel is not |
| 65 | + ready --- useful as a quick liveness check. |
| 66 | +- `send$(data)` is **wait-then-send**: returns an Observable that defers |
| 67 | + until `open$` fires. Errors if the channel closes first. |
| 68 | + |
| 69 | +```ts |
| 70 | +// Fire only if connected right now |
| 71 | +if (channel.isOpen()) channel.send(message); |
| 72 | + |
| 73 | +// Queue until the channel opens (e.g. immediately after construction) |
| 74 | +channel.send$(message).subscribe(); |
| 75 | +``` |
| 76 | + |
| 77 | + |
| 78 | +## Close event |
| 79 | + |
| 80 | +Both `close$` and `onclose` carry a `CloseEventBase`: |
| 81 | + |
| 82 | +| Field | Description | |
| 83 | +|---|---| |
| 84 | +| `code` | Numeric close code (e.g. 1000 for normal closure) | |
| 85 | +| `reason` | Free-form reason text | |
| 86 | +| `wasClean` | Whether the close handshake completed | |
| 87 | + |
| 88 | +`code` follows WebSocket conventions where applicable but every transport |
| 89 | +chooses its own scheme for non-WebSocket scenarios. |
0 commit comments