@av-pi-studio/client 0.0.1

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.
@@ -0,0 +1,62 @@
1
+ import { encodeTerminalFrame } from "@av-pi-studio/protocol";
2
+ export class TerminalStreamRouter {
3
+ daemon;
4
+ subscribers = new Map();
5
+ detach = null;
6
+ constructor(daemon) {
7
+ this.daemon = daemon;
8
+ }
9
+ /** Begin routing inbound terminal frames. Idempotent. */
10
+ start() {
11
+ if (this.detach)
12
+ return;
13
+ this.detach = this.daemon.onTerminalFrame((frame) => this.dispatch(frame));
14
+ }
15
+ /** Stop routing inbound frames (subscribers retained). */
16
+ stop() {
17
+ this.detach?.();
18
+ this.detach = null;
19
+ }
20
+ /** Register (or replace) the subscriber for a slot. Returns an unsubscribe fn. */
21
+ subscribeSlot(slot, subscriber) {
22
+ this.subscribers.set(slot, subscriber);
23
+ return () => {
24
+ if (this.subscribers.get(slot) === subscriber)
25
+ this.subscribers.delete(slot);
26
+ };
27
+ }
28
+ /** True iff a subscriber is registered for the slot. */
29
+ hasSlot(slot) {
30
+ return this.subscribers.has(slot);
31
+ }
32
+ // ─── Outbound ─────────────────────────────────────────────────────────────
33
+ /** Send raw input bytes to a slot's PTY (opcode `Input = 0x02`). */
34
+ sendInput(slot, data) {
35
+ this.daemon.sendBinary(encodeTerminalFrame({ opcode: "Input", slot, data }));
36
+ }
37
+ /** Send a resize (opcode `Resize = 0x03`, JSON `{ rows, cols }` payload). */
38
+ sendResize(slot, rows, cols) {
39
+ this.daemon.sendBinary(encodeTerminalFrame({ opcode: "Resize", slot, rows, cols }));
40
+ }
41
+ // ─── Inbound dispatch ───────────────────────────────────────────────────────
42
+ dispatch(frame) {
43
+ const subscriber = this.subscribers.get(frame.slot);
44
+ if (!subscriber)
45
+ return; // no subscriber for this slot — drop
46
+ switch (frame.opcode) {
47
+ case "Output":
48
+ subscriber.onOutput?.(frame.data);
49
+ return;
50
+ case "Snapshot":
51
+ subscriber.onSnapshot?.(frame.data);
52
+ return;
53
+ case "Restore":
54
+ subscriber.onRestore?.(frame.data);
55
+ return;
56
+ default:
57
+ // Input/Resize are outbound-only; ignore if echoed back.
58
+ return;
59
+ }
60
+ }
61
+ }
62
+ //# sourceMappingURL=terminal-stream-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-stream-router.js","sourceRoot":"","sources":["../src/terminal-stream-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAsB,MAAM,wBAAwB,CAAC;AAuBjF,MAAM,OAAO,oBAAoB;IAIF;IAHZ,WAAW,GAAG,IAAI,GAAG,EAAkC,CAAC;IACjE,MAAM,GAAwB,IAAI,CAAC;IAE3C,YAA6B,MAAoB;QAApB,WAAM,GAAN,MAAM,CAAc;IAAG,CAAC;IAErD,yDAAyD;IACzD,KAAK;QACH,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,0DAA0D;IAC1D,IAAI;QACF,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAChB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,kFAAkF;IAClF,aAAa,CAAC,IAAY,EAAE,UAAkC;QAC5D,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACvC,OAAO,GAAG,EAAE;YACV,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,UAAU;gBAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC/E,CAAC,CAAC;IACJ,CAAC;IAED,wDAAwD;IACxD,OAAO,CAAC,IAAY;QAClB,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,6EAA6E;IAE7E,oEAAoE;IACpE,SAAS,CAAC,IAAY,EAAE,IAAgB;QACtC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/E,CAAC;IAED,6EAA6E;IAC7E,UAAU,CAAC,IAAY,EAAE,IAAY,EAAE,IAAY;QACjD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACtF,CAAC;IAED,+EAA+E;IAEvE,QAAQ,CAAC,KAAoB;QACnC,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpD,IAAI,CAAC,UAAU;YAAE,OAAO,CAAC,qCAAqC;QAC9D,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC;YACrB,KAAK,QAAQ;gBACX,UAAU,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAClC,OAAO;YACT,KAAK,UAAU;gBACb,UAAU,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACpC,OAAO;YACT,KAAK,SAAS;gBACZ,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACnC,OAAO;YACT;gBACE,yDAAyD;gBACzD,OAAO;QACX,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Pluggable transport abstraction — direct WebSocket and (future) relay E2EE share this API.
3
+ * See architecture/client-app-runtime.md § Layered client library.
4
+ */
5
+ export type TransportMessageHandler = (data: string | ArrayBuffer | Blob) => void;
6
+ export type TransportCloseHandler = (code: number, reason: string) => void;
7
+ export type TransportErrorHandler = (error: unknown) => void;
8
+ export interface Transport {
9
+ /** Initiate the connection. Resolves when the socket is open (raw, pre-handshake). */
10
+ connect(url: string): Promise<void>;
11
+ /** Send a text frame. */
12
+ sendText(data: string): void;
13
+ /** Send a binary frame. */
14
+ sendBinary(data: Uint8Array): void;
15
+ /** Close the transport cleanly. */
16
+ close(code?: number, reason?: string): void;
17
+ /** Whether the transport is currently open and writeable. */
18
+ readonly isOpen: boolean;
19
+ onMessage: TransportMessageHandler | null;
20
+ onClose: TransportCloseHandler | null;
21
+ onError: TransportErrorHandler | null;
22
+ }
23
+ type AnyWebSocket = {
24
+ readyState: number;
25
+ send(data: string | ArrayBuffer | Uint8Array): void;
26
+ close(code?: number, reason?: string): void;
27
+ onopen: ((ev: unknown) => void) | null;
28
+ onclose: ((ev: {
29
+ code: number;
30
+ reason: string;
31
+ }) => void) | null;
32
+ onerror: ((ev: unknown) => void) | null;
33
+ onmessage: ((ev: {
34
+ data: unknown;
35
+ }) => void) | null;
36
+ };
37
+ type WsFactory = (url: string, protocols?: string[]) => AnyWebSocket;
38
+ /**
39
+ * Create a direct WebSocket transport using the supplied factory.
40
+ * Default factory uses the global `WebSocket` (browser/RN/Node 22+) or throws.
41
+ */
42
+ export declare function createWebSocketTransport(factory?: WsFactory): Transport;
43
+ export {};
44
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;AAClF,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AAC3E,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;AAE7D,MAAM,WAAW,SAAS;IACxB,sFAAsF;IACtF,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,yBAAyB;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,2BAA2B;IAC3B,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACnC,mCAAmC;IACnC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAEzB,SAAS,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACtC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACvC;AAID,KAAK,YAAY,GAAG;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,IAAI,CAAC;IACpD,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACvC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACjE,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CACrD,CAAC;AAEF,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,YAAY,CAAC;AASrE;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CA4DvE"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Pluggable transport abstraction — direct WebSocket and (future) relay E2EE share this API.
3
+ * See architecture/client-app-runtime.md § Layered client library.
4
+ */
5
+ // Node `ws` compatibility: ws closes have reason as Buffer, not string.
6
+ function reasonString(reason) {
7
+ if (typeof reason === "string")
8
+ return reason;
9
+ if (reason instanceof Uint8Array || Buffer.isBuffer(reason))
10
+ return reason.toString("utf8");
11
+ return String(reason ?? "");
12
+ }
13
+ /**
14
+ * Create a direct WebSocket transport using the supplied factory.
15
+ * Default factory uses the global `WebSocket` (browser/RN/Node 22+) or throws.
16
+ */
17
+ export function createWebSocketTransport(factory) {
18
+ let ws = null;
19
+ const self = {
20
+ onMessage: null,
21
+ onClose: null,
22
+ onError: null,
23
+ get isOpen() {
24
+ return ws !== null && ws.readyState === 1; // OPEN
25
+ },
26
+ connect(url) {
27
+ return new Promise((resolve, reject) => {
28
+ const create = factory ?? (() => new WebSocket(url));
29
+ try {
30
+ ws = factory ? create(url) : new WebSocket(url);
31
+ }
32
+ catch (err) {
33
+ reject(err);
34
+ return;
35
+ }
36
+ ws.onopen = () => resolve();
37
+ ws.onerror = (ev) => {
38
+ if (ws?.readyState !== 1) {
39
+ reject(ev);
40
+ }
41
+ self.onError?.(ev);
42
+ };
43
+ ws.onclose = (ev) => {
44
+ self.onClose?.(ev.code, reasonString(ev.reason));
45
+ };
46
+ ws.onmessage = (ev) => {
47
+ const { data } = ev;
48
+ if (data instanceof ArrayBuffer || data instanceof Uint8Array) {
49
+ self.onMessage?.(data);
50
+ }
51
+ else if (typeof data === "string") {
52
+ self.onMessage?.(data);
53
+ }
54
+ else if (typeof Blob !== "undefined" && data instanceof Blob) {
55
+ // Browser default `binaryType` is "blob" — DaemonClient.handleIncoming already
56
+ // reads Blob payloads asynchronously via `.arrayBuffer()`, so forward it as-is
57
+ // instead of silently dropping every binary (terminal/file-transfer) frame.
58
+ self.onMessage?.(data);
59
+ }
60
+ else if (Buffer.isBuffer(data)) {
61
+ // Node `ws` may deliver Buffer
62
+ self.onMessage?.(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength));
63
+ }
64
+ };
65
+ });
66
+ },
67
+ sendText(data) {
68
+ ws?.send(data);
69
+ },
70
+ sendBinary(data) {
71
+ ws?.send(data);
72
+ },
73
+ close(code = 1000, reason = "") {
74
+ ws?.close(code, reason);
75
+ ws = null;
76
+ },
77
+ };
78
+ return self;
79
+ }
80
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@av-pi-studio/client",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsc -b",
18
+ "clean": "rm -rf dist *.tsbuildinfo"
19
+ },
20
+ "dependencies": {
21
+ "@av-pi-studio/protocol": "^0.0.1"
22
+ }
23
+ }