@ai-matrx/desktop-protocol 0.1.0 → 0.1.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.
- package/CHANGELOG.md +14 -0
- package/README.md +3 -1
- package/dist/client.cjs +42 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.js +42 -1
- package/dist/client.js.map +1 -1
- package/dist/frame.cjs +12 -0
- package/dist/frame.cjs.map +1 -1
- package/dist/frame.d.cts +11 -1
- package/dist/frame.d.ts +11 -1
- package/dist/frame.js +12 -0
- package/dist/frame.js.map +1 -1
- package/dist/index.cjs +67 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -3
- package/dist/index.d.ts +46 -3
- package/dist/index.js +67 -2
- package/dist/index.js.map +1 -1
- package/dist/{registry-DTcTGycI.d.cts → registry-DJbrsZ4u.d.cts} +42 -1
- package/dist/{registry-DTcTGycI.d.ts → registry-DJbrsZ4u.d.ts} +42 -1
- package/package.json +1 -1
package/dist/frame.cjs
CHANGED
|
@@ -20,15 +20,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/frame.ts
|
|
21
21
|
var frame_exports = {};
|
|
22
22
|
__export(frame_exports, {
|
|
23
|
+
BROADCAST_CID: () => BROADCAST_CID,
|
|
23
24
|
FRAME_HEADER_BYTES: () => FRAME_HEADER_BYTES,
|
|
24
25
|
FRAME_STRUCT_FORMAT: () => FRAME_STRUCT_FORMAT,
|
|
25
26
|
FRAME_VERSION: () => FRAME_VERSION,
|
|
26
27
|
FrameChannel: () => FrameChannel,
|
|
27
28
|
FrameFlag: () => FrameFlag,
|
|
28
29
|
assertFrameHeader: () => assertFrameHeader,
|
|
30
|
+
connectionEndFrame: () => connectionEndFrame,
|
|
29
31
|
decodeFrame: () => decodeFrame,
|
|
30
32
|
encodeFrame: () => encodeFrame,
|
|
31
33
|
hasFlag: () => hasFlag,
|
|
34
|
+
isConnectionEnd: () => isConnectionEnd,
|
|
32
35
|
readCid: () => readCid,
|
|
33
36
|
stampCid: () => stampCid
|
|
34
37
|
});
|
|
@@ -129,4 +132,13 @@ function stampCid(frame, cid) {
|
|
|
129
132
|
function hasFlag(header, flag) {
|
|
130
133
|
return (header.flags & flag) !== 0;
|
|
131
134
|
}
|
|
135
|
+
var BROADCAST_CID = 0;
|
|
136
|
+
function connectionEndFrame(cid) {
|
|
137
|
+
if (cid === BROADCAST_CID) throw frameError("a connection end needs a client cid, not the broadcast cid 0");
|
|
138
|
+
return encodeFrame({ version: FRAME_VERSION, flags: FrameFlag.FIN, channel: FrameChannel.ENVELOPE, cid, sid: 0, seq: 0 }, new Uint8Array(0));
|
|
139
|
+
}
|
|
140
|
+
function isConnectionEnd(frame) {
|
|
141
|
+
const h = frame.header;
|
|
142
|
+
return h.channel === FrameChannel.ENVELOPE && hasFlag(h, FrameFlag.FIN) && frame.payload.byteLength === 0 && h.cid !== BROADCAST_CID;
|
|
143
|
+
}
|
|
132
144
|
//# sourceMappingURL=frame.cjs.map
|
package/dist/frame.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/frame.ts","../src/error.ts"],"sourcesContent":["/**\n * `@ai-matrx/desktop-protocol/frame` — the 16-byte binary frame codec. ZOD-FREE by design: the\n * relay Worker's hot path imports only this entry and must stay tiny.\n *\n * off size field notes\n * 0 u8 version = 1\n * 1 u8 flags bit0 FIN (last frame of this stream), bit1 REPLAY (from ring buffer),\n * bits2-7 reserved = 0\n * 2 u8 channel see FrameChannel\n * 3 u8 reserved = 0\n * 4 u32 cid relay connection id. 0 on every client↔relay hop; the relay stamps it on\n * the relay→device hop and strips it on the way back (rewrites 4 bytes in\n * place — the relay never parses payloads).\n * 8 u32 sid stream id, announced by the core in stream{meta:{kind:\"open\",sid}}\n * 12 u32 seq per-stream frame sequence, starts at 1, strictly +1\n *\n * Big-endian. Payload length = message length − 16 (one frame = one WS binary message).\n * Python struct format: \">BBBBIII\". The zod twin (FrameHeader in the root entry, and so the JSON\n * Schema and Pydantic model) is checked against this file by src/frame.test.ts.\n */\nimport { DesktopProtocolError } from \"./error\";\n\nexport const FRAME_HEADER_BYTES = 16 as const;\nexport const FRAME_VERSION = 1 as const;\nexport const FRAME_STRUCT_FORMAT = \">BBBBIII\" as const;\nexport const FrameFlag = { FIN: 0b01, REPLAY: 0b10 } as const;\nexport const FrameChannel = {\n /** core→client: pty output, fs.read_stream bytes */\n DATA: 0,\n /** core→client: stderr of a non-pty process (reserved for exec.spawn in v1.1) */\n STDERR: 1,\n /** client→core: keystrokes / stdin to sid */\n STDIN: 2,\n /** core→client: serialized terminal screen (attach when since_seq is outside the ring) */\n SNAPSHOT: 3,\n /** relay↔device hop only: a JSON envelope carried inside a binary frame so the relay can route\n * by cid without parsing JSON. Never seen by clients. */\n ENVELOPE: 255,\n} as const;\n\nexport type FrameChannelValue = (typeof FrameChannel)[keyof typeof FrameChannel];\n\nexport interface FrameHeader {\n version: typeof FRAME_VERSION;\n /** 0..3 — FrameFlag bits. */\n flags: number;\n channel: FrameChannelValue;\n cid: number;\n sid: number;\n seq: number;\n}\n\nexport interface DecodedFrame {\n header: FrameHeader;\n /** A VIEW into the input buffer (no copy). Copy it if you keep it past the input's lifetime. */\n payload: Uint8Array;\n}\n\nconst U32_MAX = 4_294_967_295;\nconst CHANNELS: ReadonlySet<number> = new Set(Object.values(FrameChannel));\n\nfunction frameError(message: string): DesktopProtocolError {\n return new DesktopProtocolError(\"PROTOCOL_ERROR\", message);\n}\n\nfunction checkU32(name: string, value: number): void {\n if (!Number.isInteger(value) || value < 0 || value > U32_MAX) throw frameError(`frame ${name} is not a u32: ${value}`);\n}\n\n/** Validate a header without zod. Throws DesktopProtocolError(PROTOCOL_ERROR). */\nexport function assertFrameHeader(h: FrameHeader): void {\n if (h.version !== FRAME_VERSION) throw frameError(`unsupported frame version ${String(h.version)}`);\n if (!Number.isInteger(h.flags) || h.flags < 0 || h.flags > 3) throw frameError(`reserved frame flag bits set: ${h.flags}`);\n if (!CHANNELS.has(h.channel)) throw frameError(`unknown frame channel ${String(h.channel)}`);\n checkU32(\"cid\", h.cid);\n checkU32(\"sid\", h.sid);\n checkU32(\"seq\", h.seq);\n}\n\nexport function encodeFrame(h: FrameHeader, payload: Uint8Array): Uint8Array {\n assertFrameHeader(h);\n const out = new Uint8Array(FRAME_HEADER_BYTES + payload.byteLength);\n const v = new DataView(out.buffer);\n v.setUint8(0, h.version);\n v.setUint8(1, h.flags);\n v.setUint8(2, h.channel);\n v.setUint8(3, 0);\n v.setUint32(4, h.cid);\n v.setUint32(8, h.sid);\n v.setUint32(12, h.seq);\n out.set(payload, FRAME_HEADER_BYTES);\n return out;\n}\n\nexport function decodeFrame(buf: ArrayBuffer | Uint8Array): DecodedFrame {\n const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);\n if (bytes.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${bytes.byteLength} bytes`);\n const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n if (v.getUint8(3) !== 0) throw frameError(\"reserved frame byte 3 is not 0\");\n const header = {\n version: v.getUint8(0),\n flags: v.getUint8(1),\n channel: v.getUint8(2),\n cid: v.getUint32(4),\n sid: v.getUint32(8),\n seq: v.getUint32(12),\n } as FrameHeader;\n assertFrameHeader(header);\n return { header, payload: bytes.subarray(FRAME_HEADER_BYTES) };\n}\n\n/** Read only the 4-byte cid (relay routing) without decoding the rest. */\nexport function readCid(frame: Uint8Array): number {\n if (frame.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${frame.byteLength} bytes`);\n return new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(4);\n}\n\n/** Relay hot path: rewrite only the cid IN PLACE, never touch the payload. Returns the same array. */\nexport function stampCid(frame: Uint8Array, cid: number): Uint8Array {\n if (frame.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${frame.byteLength} bytes`);\n checkU32(\"cid\", cid);\n new DataView(frame.buffer, frame.byteOffset, frame.byteLength).setUint32(4, cid);\n return frame;\n}\n\nexport function hasFlag(header: FrameHeader, flag: (typeof FrameFlag)[keyof typeof FrameFlag]): boolean {\n return (header.flags & flag) !== 0;\n}\n","/**\n * The one error type every package entry throws. It carries the wire ErrorCode (SPEC §5), so a\n * caller branches on `code`/`retryable`/`data.reason` and never parses a message. Zod-free on\n * purpose: `./frame` (the relay hot path) throws it without pulling the schema graph.\n */\nimport type { ErrorCodeName, ErrorData } from \"./types\";\n\nexport class DesktopProtocolError extends Error {\n readonly code: ErrorCodeName;\n readonly retryable: boolean;\n readonly data: ErrorData | undefined;\n\n constructor(code: ErrorCodeName, message: string, options: { retryable?: boolean; data?: ErrorData; cause?: unknown } = {}) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = \"DesktopProtocolError\";\n this.code = code;\n this.retryable = options.retryable ?? false;\n this.data = options.data;\n }\n\n /** The wire body (ProtocolErrorBody) — what a core or relay sends for this error. */\n toBody(): { code: ErrorCodeName; message: string; retryable: boolean; data?: ErrorData } {\n return this.data === undefined\n ? { code: this.code, message: this.message, retryable: this.retryable }\n : { code: this.code, message: this.message, retryable: this.retryable, data: this.data };\n }\n}\n\nexport function isDesktopProtocolError(value: unknown): value is DesktopProtocolError {\n return value instanceof DesktopProtocolError || (value instanceof Error && value.name === \"DesktopProtocolError\" && \"code\" in value);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/frame.ts","../src/error.ts"],"sourcesContent":["/**\n * `@ai-matrx/desktop-protocol/frame` — the 16-byte binary frame codec. ZOD-FREE by design: the\n * relay Worker's hot path imports only this entry and must stay tiny.\n *\n * off size field notes\n * 0 u8 version = 1\n * 1 u8 flags bit0 FIN (last frame of this stream), bit1 REPLAY (from ring buffer),\n * bits2-7 reserved = 0\n * 2 u8 channel see FrameChannel\n * 3 u8 reserved = 0\n * 4 u32 cid relay connection id. 0 on every client↔relay hop; the relay stamps it on\n * the relay→device hop and strips it on the way back (rewrites 4 bytes in\n * place — the relay never parses payloads).\n * 8 u32 sid stream id, announced by the core in stream{meta:{kind:\"open\",sid}}\n * 12 u32 seq per-stream frame sequence, starts at 1, strictly +1\n *\n * Big-endian. Payload length = message length − 16 (one frame = one WS binary message).\n * Python struct format: \">BBBBIII\". The zod twin (FrameHeader in the root entry, and so the JSON\n * Schema and Pydantic model) is checked against this file by src/frame.test.ts.\n */\nimport { DesktopProtocolError } from \"./error\";\n\nexport const FRAME_HEADER_BYTES = 16 as const;\nexport const FRAME_VERSION = 1 as const;\nexport const FRAME_STRUCT_FORMAT = \">BBBBIII\" as const;\nexport const FrameFlag = { FIN: 0b01, REPLAY: 0b10 } as const;\nexport const FrameChannel = {\n /** core→client: pty output, fs.read_stream bytes */\n DATA: 0,\n /** core→client: stderr of a non-pty process (reserved for exec.spawn in v1.1) */\n STDERR: 1,\n /** client→core: keystrokes / stdin to sid */\n STDIN: 2,\n /** core→client: serialized terminal screen (attach when since_seq is outside the ring) */\n SNAPSHOT: 3,\n /** relay↔device hop only: a JSON envelope carried inside a binary frame so the relay can route\n * by cid without parsing JSON. Never seen by clients. */\n ENVELOPE: 255,\n} as const;\n\nexport type FrameChannelValue = (typeof FrameChannel)[keyof typeof FrameChannel];\n\nexport interface FrameHeader {\n version: typeof FRAME_VERSION;\n /** 0..3 — FrameFlag bits. */\n flags: number;\n channel: FrameChannelValue;\n cid: number;\n sid: number;\n seq: number;\n}\n\nexport interface DecodedFrame {\n header: FrameHeader;\n /** A VIEW into the input buffer (no copy). Copy it if you keep it past the input's lifetime. */\n payload: Uint8Array;\n}\n\nconst U32_MAX = 4_294_967_295;\nconst CHANNELS: ReadonlySet<number> = new Set(Object.values(FrameChannel));\n\nfunction frameError(message: string): DesktopProtocolError {\n return new DesktopProtocolError(\"PROTOCOL_ERROR\", message);\n}\n\nfunction checkU32(name: string, value: number): void {\n if (!Number.isInteger(value) || value < 0 || value > U32_MAX) throw frameError(`frame ${name} is not a u32: ${value}`);\n}\n\n/** Validate a header without zod. Throws DesktopProtocolError(PROTOCOL_ERROR). */\nexport function assertFrameHeader(h: FrameHeader): void {\n if (h.version !== FRAME_VERSION) throw frameError(`unsupported frame version ${String(h.version)}`);\n if (!Number.isInteger(h.flags) || h.flags < 0 || h.flags > 3) throw frameError(`reserved frame flag bits set: ${h.flags}`);\n if (!CHANNELS.has(h.channel)) throw frameError(`unknown frame channel ${String(h.channel)}`);\n checkU32(\"cid\", h.cid);\n checkU32(\"sid\", h.sid);\n checkU32(\"seq\", h.seq);\n}\n\nexport function encodeFrame(h: FrameHeader, payload: Uint8Array): Uint8Array {\n assertFrameHeader(h);\n const out = new Uint8Array(FRAME_HEADER_BYTES + payload.byteLength);\n const v = new DataView(out.buffer);\n v.setUint8(0, h.version);\n v.setUint8(1, h.flags);\n v.setUint8(2, h.channel);\n v.setUint8(3, 0);\n v.setUint32(4, h.cid);\n v.setUint32(8, h.sid);\n v.setUint32(12, h.seq);\n out.set(payload, FRAME_HEADER_BYTES);\n return out;\n}\n\nexport function decodeFrame(buf: ArrayBuffer | Uint8Array): DecodedFrame {\n const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);\n if (bytes.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${bytes.byteLength} bytes`);\n const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n if (v.getUint8(3) !== 0) throw frameError(\"reserved frame byte 3 is not 0\");\n const header = {\n version: v.getUint8(0),\n flags: v.getUint8(1),\n channel: v.getUint8(2),\n cid: v.getUint32(4),\n sid: v.getUint32(8),\n seq: v.getUint32(12),\n } as FrameHeader;\n assertFrameHeader(header);\n return { header, payload: bytes.subarray(FRAME_HEADER_BYTES) };\n}\n\n/** Read only the 4-byte cid (relay routing) without decoding the rest. */\nexport function readCid(frame: Uint8Array): number {\n if (frame.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${frame.byteLength} bytes`);\n return new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(4);\n}\n\n/** Relay hot path: rewrite only the cid IN PLACE, never touch the payload. Returns the same array. */\nexport function stampCid(frame: Uint8Array, cid: number): Uint8Array {\n if (frame.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${frame.byteLength} bytes`);\n checkU32(\"cid\", cid);\n new DataView(frame.buffer, frame.byteOffset, frame.byteLength).setUint32(4, cid);\n return frame;\n}\n\nexport function hasFlag(header: FrameHeader, flag: (typeof FrameFlag)[keyof typeof FrameFlag]): boolean {\n return (header.flags & flag) !== 0;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Relay ↔ device hop (never seen by clients). Every JSON envelope rides an ENVELOPE frame with\n// flags 0, sid 0, seq 0 and the client's cid; byte frames carry that cid too.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** cid 0 on an ENVELOPE frame from the device: the relay delivers it to every client. */\nexport const BROADCAST_CID = 0 as const;\n\n/**\n * Connection end: an ENVELOPE frame with FIN, sid 0, seq 0 and NO payload, for connection `cid`.\n * Relay → device: client `cid` is gone — cancel its streams, detach its sessions (idempotent; it\n * may arrive more than once). Device → relay: close client `cid`.\n */\nexport function connectionEndFrame(cid: number): Uint8Array {\n if (cid === BROADCAST_CID) throw frameError(\"a connection end needs a client cid, not the broadcast cid 0\");\n return encodeFrame({ version: FRAME_VERSION, flags: FrameFlag.FIN, channel: FrameChannel.ENVELOPE, cid, sid: 0, seq: 0 }, new Uint8Array(0));\n}\n\n/** True for a decoded connection-end frame (see connectionEndFrame). */\nexport function isConnectionEnd(frame: DecodedFrame): boolean {\n const h = frame.header;\n return h.channel === FrameChannel.ENVELOPE && hasFlag(h, FrameFlag.FIN) && frame.payload.byteLength === 0 && h.cid !== BROADCAST_CID;\n}\n","/**\n * The one error type every package entry throws. It carries the wire ErrorCode (SPEC §5), so a\n * caller branches on `code`/`retryable`/`data.reason` and never parses a message. Zod-free on\n * purpose: `./frame` (the relay hot path) throws it without pulling the schema graph.\n */\nimport type { ErrorCodeName, ErrorData } from \"./types\";\n\nexport class DesktopProtocolError extends Error {\n readonly code: ErrorCodeName;\n readonly retryable: boolean;\n readonly data: ErrorData | undefined;\n\n constructor(code: ErrorCodeName, message: string, options: { retryable?: boolean; data?: ErrorData; cause?: unknown } = {}) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = \"DesktopProtocolError\";\n this.code = code;\n this.retryable = options.retryable ?? false;\n this.data = options.data;\n }\n\n /** The wire body (ProtocolErrorBody) — what a core or relay sends for this error. */\n toBody(): { code: ErrorCodeName; message: string; retryable: boolean; data?: ErrorData } {\n return this.data === undefined\n ? { code: this.code, message: this.message, retryable: this.retryable }\n : { code: this.code, message: this.message, retryable: this.retryable, data: this.data };\n }\n}\n\nexport function isDesktopProtocolError(value: unknown): value is DesktopProtocolError {\n return value instanceof DesktopProtocolError || (value instanceof Error && value.name === \"DesktopProtocolError\" && \"code\" in value);\n}\n"],"mappings":"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","names":[]}
|
package/dist/frame.d.cts
CHANGED
|
@@ -42,5 +42,15 @@ declare function readCid(frame: Uint8Array): number;
|
|
|
42
42
|
/** Relay hot path: rewrite only the cid IN PLACE, never touch the payload. Returns the same array. */
|
|
43
43
|
declare function stampCid(frame: Uint8Array, cid: number): Uint8Array;
|
|
44
44
|
declare function hasFlag(header: FrameHeader, flag: (typeof FrameFlag)[keyof typeof FrameFlag]): boolean;
|
|
45
|
+
/** cid 0 on an ENVELOPE frame from the device: the relay delivers it to every client. */
|
|
46
|
+
declare const BROADCAST_CID: 0;
|
|
47
|
+
/**
|
|
48
|
+
* Connection end: an ENVELOPE frame with FIN, sid 0, seq 0 and NO payload, for connection `cid`.
|
|
49
|
+
* Relay → device: client `cid` is gone — cancel its streams, detach its sessions (idempotent; it
|
|
50
|
+
* may arrive more than once). Device → relay: close client `cid`.
|
|
51
|
+
*/
|
|
52
|
+
declare function connectionEndFrame(cid: number): Uint8Array;
|
|
53
|
+
/** True for a decoded connection-end frame (see connectionEndFrame). */
|
|
54
|
+
declare function isConnectionEnd(frame: DecodedFrame): boolean;
|
|
45
55
|
|
|
46
|
-
export { type DecodedFrame, FRAME_HEADER_BYTES, FRAME_STRUCT_FORMAT, FRAME_VERSION, FrameChannel, type FrameChannelValue, FrameFlag, type FrameHeader, assertFrameHeader, decodeFrame, encodeFrame, hasFlag, readCid, stampCid };
|
|
56
|
+
export { BROADCAST_CID, type DecodedFrame, FRAME_HEADER_BYTES, FRAME_STRUCT_FORMAT, FRAME_VERSION, FrameChannel, type FrameChannelValue, FrameFlag, type FrameHeader, assertFrameHeader, connectionEndFrame, decodeFrame, encodeFrame, hasFlag, isConnectionEnd, readCid, stampCid };
|
package/dist/frame.d.ts
CHANGED
|
@@ -42,5 +42,15 @@ declare function readCid(frame: Uint8Array): number;
|
|
|
42
42
|
/** Relay hot path: rewrite only the cid IN PLACE, never touch the payload. Returns the same array. */
|
|
43
43
|
declare function stampCid(frame: Uint8Array, cid: number): Uint8Array;
|
|
44
44
|
declare function hasFlag(header: FrameHeader, flag: (typeof FrameFlag)[keyof typeof FrameFlag]): boolean;
|
|
45
|
+
/** cid 0 on an ENVELOPE frame from the device: the relay delivers it to every client. */
|
|
46
|
+
declare const BROADCAST_CID: 0;
|
|
47
|
+
/**
|
|
48
|
+
* Connection end: an ENVELOPE frame with FIN, sid 0, seq 0 and NO payload, for connection `cid`.
|
|
49
|
+
* Relay → device: client `cid` is gone — cancel its streams, detach its sessions (idempotent; it
|
|
50
|
+
* may arrive more than once). Device → relay: close client `cid`.
|
|
51
|
+
*/
|
|
52
|
+
declare function connectionEndFrame(cid: number): Uint8Array;
|
|
53
|
+
/** True for a decoded connection-end frame (see connectionEndFrame). */
|
|
54
|
+
declare function isConnectionEnd(frame: DecodedFrame): boolean;
|
|
45
55
|
|
|
46
|
-
export { type DecodedFrame, FRAME_HEADER_BYTES, FRAME_STRUCT_FORMAT, FRAME_VERSION, FrameChannel, type FrameChannelValue, FrameFlag, type FrameHeader, assertFrameHeader, decodeFrame, encodeFrame, hasFlag, readCid, stampCid };
|
|
56
|
+
export { BROADCAST_CID, type DecodedFrame, FRAME_HEADER_BYTES, FRAME_STRUCT_FORMAT, FRAME_VERSION, FrameChannel, type FrameChannelValue, FrameFlag, type FrameHeader, assertFrameHeader, connectionEndFrame, decodeFrame, encodeFrame, hasFlag, isConnectionEnd, readCid, stampCid };
|
package/dist/frame.js
CHANGED
|
@@ -93,16 +93,28 @@ function stampCid(frame, cid) {
|
|
|
93
93
|
function hasFlag(header, flag) {
|
|
94
94
|
return (header.flags & flag) !== 0;
|
|
95
95
|
}
|
|
96
|
+
var BROADCAST_CID = 0;
|
|
97
|
+
function connectionEndFrame(cid) {
|
|
98
|
+
if (cid === BROADCAST_CID) throw frameError("a connection end needs a client cid, not the broadcast cid 0");
|
|
99
|
+
return encodeFrame({ version: FRAME_VERSION, flags: FrameFlag.FIN, channel: FrameChannel.ENVELOPE, cid, sid: 0, seq: 0 }, new Uint8Array(0));
|
|
100
|
+
}
|
|
101
|
+
function isConnectionEnd(frame) {
|
|
102
|
+
const h = frame.header;
|
|
103
|
+
return h.channel === FrameChannel.ENVELOPE && hasFlag(h, FrameFlag.FIN) && frame.payload.byteLength === 0 && h.cid !== BROADCAST_CID;
|
|
104
|
+
}
|
|
96
105
|
export {
|
|
106
|
+
BROADCAST_CID,
|
|
97
107
|
FRAME_HEADER_BYTES,
|
|
98
108
|
FRAME_STRUCT_FORMAT,
|
|
99
109
|
FRAME_VERSION,
|
|
100
110
|
FrameChannel,
|
|
101
111
|
FrameFlag,
|
|
102
112
|
assertFrameHeader,
|
|
113
|
+
connectionEndFrame,
|
|
103
114
|
decodeFrame,
|
|
104
115
|
encodeFrame,
|
|
105
116
|
hasFlag,
|
|
117
|
+
isConnectionEnd,
|
|
106
118
|
readCid,
|
|
107
119
|
stampCid
|
|
108
120
|
};
|
package/dist/frame.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/error.ts","../src/frame.ts"],"sourcesContent":["/**\n * The one error type every package entry throws. It carries the wire ErrorCode (SPEC §5), so a\n * caller branches on `code`/`retryable`/`data.reason` and never parses a message. Zod-free on\n * purpose: `./frame` (the relay hot path) throws it without pulling the schema graph.\n */\nimport type { ErrorCodeName, ErrorData } from \"./types\";\n\nexport class DesktopProtocolError extends Error {\n readonly code: ErrorCodeName;\n readonly retryable: boolean;\n readonly data: ErrorData | undefined;\n\n constructor(code: ErrorCodeName, message: string, options: { retryable?: boolean; data?: ErrorData; cause?: unknown } = {}) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = \"DesktopProtocolError\";\n this.code = code;\n this.retryable = options.retryable ?? false;\n this.data = options.data;\n }\n\n /** The wire body (ProtocolErrorBody) — what a core or relay sends for this error. */\n toBody(): { code: ErrorCodeName; message: string; retryable: boolean; data?: ErrorData } {\n return this.data === undefined\n ? { code: this.code, message: this.message, retryable: this.retryable }\n : { code: this.code, message: this.message, retryable: this.retryable, data: this.data };\n }\n}\n\nexport function isDesktopProtocolError(value: unknown): value is DesktopProtocolError {\n return value instanceof DesktopProtocolError || (value instanceof Error && value.name === \"DesktopProtocolError\" && \"code\" in value);\n}\n","/**\n * `@ai-matrx/desktop-protocol/frame` — the 16-byte binary frame codec. ZOD-FREE by design: the\n * relay Worker's hot path imports only this entry and must stay tiny.\n *\n * off size field notes\n * 0 u8 version = 1\n * 1 u8 flags bit0 FIN (last frame of this stream), bit1 REPLAY (from ring buffer),\n * bits2-7 reserved = 0\n * 2 u8 channel see FrameChannel\n * 3 u8 reserved = 0\n * 4 u32 cid relay connection id. 0 on every client↔relay hop; the relay stamps it on\n * the relay→device hop and strips it on the way back (rewrites 4 bytes in\n * place — the relay never parses payloads).\n * 8 u32 sid stream id, announced by the core in stream{meta:{kind:\"open\",sid}}\n * 12 u32 seq per-stream frame sequence, starts at 1, strictly +1\n *\n * Big-endian. Payload length = message length − 16 (one frame = one WS binary message).\n * Python struct format: \">BBBBIII\". The zod twin (FrameHeader in the root entry, and so the JSON\n * Schema and Pydantic model) is checked against this file by src/frame.test.ts.\n */\nimport { DesktopProtocolError } from \"./error\";\n\nexport const FRAME_HEADER_BYTES = 16 as const;\nexport const FRAME_VERSION = 1 as const;\nexport const FRAME_STRUCT_FORMAT = \">BBBBIII\" as const;\nexport const FrameFlag = { FIN: 0b01, REPLAY: 0b10 } as const;\nexport const FrameChannel = {\n /** core→client: pty output, fs.read_stream bytes */\n DATA: 0,\n /** core→client: stderr of a non-pty process (reserved for exec.spawn in v1.1) */\n STDERR: 1,\n /** client→core: keystrokes / stdin to sid */\n STDIN: 2,\n /** core→client: serialized terminal screen (attach when since_seq is outside the ring) */\n SNAPSHOT: 3,\n /** relay↔device hop only: a JSON envelope carried inside a binary frame so the relay can route\n * by cid without parsing JSON. Never seen by clients. */\n ENVELOPE: 255,\n} as const;\n\nexport type FrameChannelValue = (typeof FrameChannel)[keyof typeof FrameChannel];\n\nexport interface FrameHeader {\n version: typeof FRAME_VERSION;\n /** 0..3 — FrameFlag bits. */\n flags: number;\n channel: FrameChannelValue;\n cid: number;\n sid: number;\n seq: number;\n}\n\nexport interface DecodedFrame {\n header: FrameHeader;\n /** A VIEW into the input buffer (no copy). Copy it if you keep it past the input's lifetime. */\n payload: Uint8Array;\n}\n\nconst U32_MAX = 4_294_967_295;\nconst CHANNELS: ReadonlySet<number> = new Set(Object.values(FrameChannel));\n\nfunction frameError(message: string): DesktopProtocolError {\n return new DesktopProtocolError(\"PROTOCOL_ERROR\", message);\n}\n\nfunction checkU32(name: string, value: number): void {\n if (!Number.isInteger(value) || value < 0 || value > U32_MAX) throw frameError(`frame ${name} is not a u32: ${value}`);\n}\n\n/** Validate a header without zod. Throws DesktopProtocolError(PROTOCOL_ERROR). */\nexport function assertFrameHeader(h: FrameHeader): void {\n if (h.version !== FRAME_VERSION) throw frameError(`unsupported frame version ${String(h.version)}`);\n if (!Number.isInteger(h.flags) || h.flags < 0 || h.flags > 3) throw frameError(`reserved frame flag bits set: ${h.flags}`);\n if (!CHANNELS.has(h.channel)) throw frameError(`unknown frame channel ${String(h.channel)}`);\n checkU32(\"cid\", h.cid);\n checkU32(\"sid\", h.sid);\n checkU32(\"seq\", h.seq);\n}\n\nexport function encodeFrame(h: FrameHeader, payload: Uint8Array): Uint8Array {\n assertFrameHeader(h);\n const out = new Uint8Array(FRAME_HEADER_BYTES + payload.byteLength);\n const v = new DataView(out.buffer);\n v.setUint8(0, h.version);\n v.setUint8(1, h.flags);\n v.setUint8(2, h.channel);\n v.setUint8(3, 0);\n v.setUint32(4, h.cid);\n v.setUint32(8, h.sid);\n v.setUint32(12, h.seq);\n out.set(payload, FRAME_HEADER_BYTES);\n return out;\n}\n\nexport function decodeFrame(buf: ArrayBuffer | Uint8Array): DecodedFrame {\n const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);\n if (bytes.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${bytes.byteLength} bytes`);\n const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n if (v.getUint8(3) !== 0) throw frameError(\"reserved frame byte 3 is not 0\");\n const header = {\n version: v.getUint8(0),\n flags: v.getUint8(1),\n channel: v.getUint8(2),\n cid: v.getUint32(4),\n sid: v.getUint32(8),\n seq: v.getUint32(12),\n } as FrameHeader;\n assertFrameHeader(header);\n return { header, payload: bytes.subarray(FRAME_HEADER_BYTES) };\n}\n\n/** Read only the 4-byte cid (relay routing) without decoding the rest. */\nexport function readCid(frame: Uint8Array): number {\n if (frame.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${frame.byteLength} bytes`);\n return new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(4);\n}\n\n/** Relay hot path: rewrite only the cid IN PLACE, never touch the payload. Returns the same array. */\nexport function stampCid(frame: Uint8Array, cid: number): Uint8Array {\n if (frame.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${frame.byteLength} bytes`);\n checkU32(\"cid\", cid);\n new DataView(frame.buffer, frame.byteOffset, frame.byteLength).setUint32(4, cid);\n return frame;\n}\n\nexport function hasFlag(header: FrameHeader, flag: (typeof FrameFlag)[keyof typeof FrameFlag]): boolean {\n return (header.flags & flag) !== 0;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/error.ts","../src/frame.ts"],"sourcesContent":["/**\n * The one error type every package entry throws. It carries the wire ErrorCode (SPEC §5), so a\n * caller branches on `code`/`retryable`/`data.reason` and never parses a message. Zod-free on\n * purpose: `./frame` (the relay hot path) throws it without pulling the schema graph.\n */\nimport type { ErrorCodeName, ErrorData } from \"./types\";\n\nexport class DesktopProtocolError extends Error {\n readonly code: ErrorCodeName;\n readonly retryable: boolean;\n readonly data: ErrorData | undefined;\n\n constructor(code: ErrorCodeName, message: string, options: { retryable?: boolean; data?: ErrorData; cause?: unknown } = {}) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = \"DesktopProtocolError\";\n this.code = code;\n this.retryable = options.retryable ?? false;\n this.data = options.data;\n }\n\n /** The wire body (ProtocolErrorBody) — what a core or relay sends for this error. */\n toBody(): { code: ErrorCodeName; message: string; retryable: boolean; data?: ErrorData } {\n return this.data === undefined\n ? { code: this.code, message: this.message, retryable: this.retryable }\n : { code: this.code, message: this.message, retryable: this.retryable, data: this.data };\n }\n}\n\nexport function isDesktopProtocolError(value: unknown): value is DesktopProtocolError {\n return value instanceof DesktopProtocolError || (value instanceof Error && value.name === \"DesktopProtocolError\" && \"code\" in value);\n}\n","/**\n * `@ai-matrx/desktop-protocol/frame` — the 16-byte binary frame codec. ZOD-FREE by design: the\n * relay Worker's hot path imports only this entry and must stay tiny.\n *\n * off size field notes\n * 0 u8 version = 1\n * 1 u8 flags bit0 FIN (last frame of this stream), bit1 REPLAY (from ring buffer),\n * bits2-7 reserved = 0\n * 2 u8 channel see FrameChannel\n * 3 u8 reserved = 0\n * 4 u32 cid relay connection id. 0 on every client↔relay hop; the relay stamps it on\n * the relay→device hop and strips it on the way back (rewrites 4 bytes in\n * place — the relay never parses payloads).\n * 8 u32 sid stream id, announced by the core in stream{meta:{kind:\"open\",sid}}\n * 12 u32 seq per-stream frame sequence, starts at 1, strictly +1\n *\n * Big-endian. Payload length = message length − 16 (one frame = one WS binary message).\n * Python struct format: \">BBBBIII\". The zod twin (FrameHeader in the root entry, and so the JSON\n * Schema and Pydantic model) is checked against this file by src/frame.test.ts.\n */\nimport { DesktopProtocolError } from \"./error\";\n\nexport const FRAME_HEADER_BYTES = 16 as const;\nexport const FRAME_VERSION = 1 as const;\nexport const FRAME_STRUCT_FORMAT = \">BBBBIII\" as const;\nexport const FrameFlag = { FIN: 0b01, REPLAY: 0b10 } as const;\nexport const FrameChannel = {\n /** core→client: pty output, fs.read_stream bytes */\n DATA: 0,\n /** core→client: stderr of a non-pty process (reserved for exec.spawn in v1.1) */\n STDERR: 1,\n /** client→core: keystrokes / stdin to sid */\n STDIN: 2,\n /** core→client: serialized terminal screen (attach when since_seq is outside the ring) */\n SNAPSHOT: 3,\n /** relay↔device hop only: a JSON envelope carried inside a binary frame so the relay can route\n * by cid without parsing JSON. Never seen by clients. */\n ENVELOPE: 255,\n} as const;\n\nexport type FrameChannelValue = (typeof FrameChannel)[keyof typeof FrameChannel];\n\nexport interface FrameHeader {\n version: typeof FRAME_VERSION;\n /** 0..3 — FrameFlag bits. */\n flags: number;\n channel: FrameChannelValue;\n cid: number;\n sid: number;\n seq: number;\n}\n\nexport interface DecodedFrame {\n header: FrameHeader;\n /** A VIEW into the input buffer (no copy). Copy it if you keep it past the input's lifetime. */\n payload: Uint8Array;\n}\n\nconst U32_MAX = 4_294_967_295;\nconst CHANNELS: ReadonlySet<number> = new Set(Object.values(FrameChannel));\n\nfunction frameError(message: string): DesktopProtocolError {\n return new DesktopProtocolError(\"PROTOCOL_ERROR\", message);\n}\n\nfunction checkU32(name: string, value: number): void {\n if (!Number.isInteger(value) || value < 0 || value > U32_MAX) throw frameError(`frame ${name} is not a u32: ${value}`);\n}\n\n/** Validate a header without zod. Throws DesktopProtocolError(PROTOCOL_ERROR). */\nexport function assertFrameHeader(h: FrameHeader): void {\n if (h.version !== FRAME_VERSION) throw frameError(`unsupported frame version ${String(h.version)}`);\n if (!Number.isInteger(h.flags) || h.flags < 0 || h.flags > 3) throw frameError(`reserved frame flag bits set: ${h.flags}`);\n if (!CHANNELS.has(h.channel)) throw frameError(`unknown frame channel ${String(h.channel)}`);\n checkU32(\"cid\", h.cid);\n checkU32(\"sid\", h.sid);\n checkU32(\"seq\", h.seq);\n}\n\nexport function encodeFrame(h: FrameHeader, payload: Uint8Array): Uint8Array {\n assertFrameHeader(h);\n const out = new Uint8Array(FRAME_HEADER_BYTES + payload.byteLength);\n const v = new DataView(out.buffer);\n v.setUint8(0, h.version);\n v.setUint8(1, h.flags);\n v.setUint8(2, h.channel);\n v.setUint8(3, 0);\n v.setUint32(4, h.cid);\n v.setUint32(8, h.sid);\n v.setUint32(12, h.seq);\n out.set(payload, FRAME_HEADER_BYTES);\n return out;\n}\n\nexport function decodeFrame(buf: ArrayBuffer | Uint8Array): DecodedFrame {\n const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);\n if (bytes.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${bytes.byteLength} bytes`);\n const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n if (v.getUint8(3) !== 0) throw frameError(\"reserved frame byte 3 is not 0\");\n const header = {\n version: v.getUint8(0),\n flags: v.getUint8(1),\n channel: v.getUint8(2),\n cid: v.getUint32(4),\n sid: v.getUint32(8),\n seq: v.getUint32(12),\n } as FrameHeader;\n assertFrameHeader(header);\n return { header, payload: bytes.subarray(FRAME_HEADER_BYTES) };\n}\n\n/** Read only the 4-byte cid (relay routing) without decoding the rest. */\nexport function readCid(frame: Uint8Array): number {\n if (frame.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${frame.byteLength} bytes`);\n return new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(4);\n}\n\n/** Relay hot path: rewrite only the cid IN PLACE, never touch the payload. Returns the same array. */\nexport function stampCid(frame: Uint8Array, cid: number): Uint8Array {\n if (frame.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${frame.byteLength} bytes`);\n checkU32(\"cid\", cid);\n new DataView(frame.buffer, frame.byteOffset, frame.byteLength).setUint32(4, cid);\n return frame;\n}\n\nexport function hasFlag(header: FrameHeader, flag: (typeof FrameFlag)[keyof typeof FrameFlag]): boolean {\n return (header.flags & flag) !== 0;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Relay ↔ device hop (never seen by clients). Every JSON envelope rides an ENVELOPE frame with\n// flags 0, sid 0, seq 0 and the client's cid; byte frames carry that cid too.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** cid 0 on an ENVELOPE frame from the device: the relay delivers it to every client. */\nexport const BROADCAST_CID = 0 as const;\n\n/**\n * Connection end: an ENVELOPE frame with FIN, sid 0, seq 0 and NO payload, for connection `cid`.\n * Relay → device: client `cid` is gone — cancel its streams, detach its sessions (idempotent; it\n * may arrive more than once). Device → relay: close client `cid`.\n */\nexport function connectionEndFrame(cid: number): Uint8Array {\n if (cid === BROADCAST_CID) throw frameError(\"a connection end needs a client cid, not the broadcast cid 0\");\n return encodeFrame({ version: FRAME_VERSION, flags: FrameFlag.FIN, channel: FrameChannel.ENVELOPE, cid, sid: 0, seq: 0 }, new Uint8Array(0));\n}\n\n/** True for a decoded connection-end frame (see connectionEndFrame). */\nexport function isConnectionEnd(frame: DecodedFrame): boolean {\n const h = frame.header;\n return h.channel === FrameChannel.ENVELOPE && hasFlag(h, FrameFlag.FIN) && frame.payload.byteLength === 0 && h.cid !== BROADCAST_CID;\n}\n"],"mappings":";AAOO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,MAAqB,SAAiB,UAAsE,CAAC,GAAG;AAC1H,UAAM,SAAS,QAAQ,UAAU,SAAY,SAAY,EAAE,OAAO,QAAQ,MAAM,CAAC;AACjF,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,OAAO,QAAQ;AAAA,EACtB;AAAA;AAAA,EAGA,SAAyF;AACvF,WAAO,KAAK,SAAS,SACjB,EAAE,MAAM,KAAK,MAAM,SAAS,KAAK,SAAS,WAAW,KAAK,UAAU,IACpE,EAAE,MAAM,KAAK,MAAM,SAAS,KAAK,SAAS,WAAW,KAAK,WAAW,MAAM,KAAK,KAAK;AAAA,EAC3F;AACF;;;ACJO,IAAM,qBAAqB;AAC3B,IAAM,gBAAgB;AACtB,IAAM,sBAAsB;AAC5B,IAAM,YAAY,EAAE,KAAK,GAAM,QAAQ,EAAK;AAC5C,IAAM,eAAe;AAAA;AAAA,EAE1B,MAAM;AAAA;AAAA,EAEN,QAAQ;AAAA;AAAA,EAER,OAAO;AAAA;AAAA,EAEP,UAAU;AAAA;AAAA;AAAA,EAGV,UAAU;AACZ;AAoBA,IAAM,UAAU;AAChB,IAAM,WAAgC,IAAI,IAAI,OAAO,OAAO,YAAY,CAAC;AAEzE,SAAS,WAAW,SAAuC;AACzD,SAAO,IAAI,qBAAqB,kBAAkB,OAAO;AAC3D;AAEA,SAAS,SAAS,MAAc,OAAqB;AACnD,MAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ,QAAS,OAAM,WAAW,SAAS,IAAI,kBAAkB,KAAK,EAAE;AACvH;AAGO,SAAS,kBAAkB,GAAsB;AACtD,MAAI,EAAE,YAAY,cAAe,OAAM,WAAW,6BAA6B,OAAO,EAAE,OAAO,CAAC,EAAE;AAClG,MAAI,CAAC,OAAO,UAAU,EAAE,KAAK,KAAK,EAAE,QAAQ,KAAK,EAAE,QAAQ,EAAG,OAAM,WAAW,iCAAiC,EAAE,KAAK,EAAE;AACzH,MAAI,CAAC,SAAS,IAAI,EAAE,OAAO,EAAG,OAAM,WAAW,yBAAyB,OAAO,EAAE,OAAO,CAAC,EAAE;AAC3F,WAAS,OAAO,EAAE,GAAG;AACrB,WAAS,OAAO,EAAE,GAAG;AACrB,WAAS,OAAO,EAAE,GAAG;AACvB;AAEO,SAAS,YAAY,GAAgB,SAAiC;AAC3E,oBAAkB,CAAC;AACnB,QAAM,MAAM,IAAI,WAAW,qBAAqB,QAAQ,UAAU;AAClE,QAAM,IAAI,IAAI,SAAS,IAAI,MAAM;AACjC,IAAE,SAAS,GAAG,EAAE,OAAO;AACvB,IAAE,SAAS,GAAG,EAAE,KAAK;AACrB,IAAE,SAAS,GAAG,EAAE,OAAO;AACvB,IAAE,SAAS,GAAG,CAAC;AACf,IAAE,UAAU,GAAG,EAAE,GAAG;AACpB,IAAE,UAAU,GAAG,EAAE,GAAG;AACpB,IAAE,UAAU,IAAI,EAAE,GAAG;AACrB,MAAI,IAAI,SAAS,kBAAkB;AACnC,SAAO;AACT;AAEO,SAAS,YAAY,KAA6C;AACvE,QAAM,QAAQ,eAAe,aAAa,MAAM,IAAI,WAAW,GAAG;AAClE,MAAI,MAAM,aAAa,mBAAoB,OAAM,WAAW,gBAAgB,MAAM,UAAU,QAAQ;AACpG,QAAM,IAAI,IAAI,SAAS,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU;AACvE,MAAI,EAAE,SAAS,CAAC,MAAM,EAAG,OAAM,WAAW,gCAAgC;AAC1E,QAAM,SAAS;AAAA,IACb,SAAS,EAAE,SAAS,CAAC;AAAA,IACrB,OAAO,EAAE,SAAS,CAAC;AAAA,IACnB,SAAS,EAAE,SAAS,CAAC;AAAA,IACrB,KAAK,EAAE,UAAU,CAAC;AAAA,IAClB,KAAK,EAAE,UAAU,CAAC;AAAA,IAClB,KAAK,EAAE,UAAU,EAAE;AAAA,EACrB;AACA,oBAAkB,MAAM;AACxB,SAAO,EAAE,QAAQ,SAAS,MAAM,SAAS,kBAAkB,EAAE;AAC/D;AAGO,SAAS,QAAQ,OAA2B;AACjD,MAAI,MAAM,aAAa,mBAAoB,OAAM,WAAW,gBAAgB,MAAM,UAAU,QAAQ;AACpG,SAAO,IAAI,SAAS,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU,EAAE,UAAU,CAAC;AACnF;AAGO,SAAS,SAAS,OAAmB,KAAyB;AACnE,MAAI,MAAM,aAAa,mBAAoB,OAAM,WAAW,gBAAgB,MAAM,UAAU,QAAQ;AACpG,WAAS,OAAO,GAAG;AACnB,MAAI,SAAS,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU,EAAE,UAAU,GAAG,GAAG;AAC/E,SAAO;AACT;AAEO,SAAS,QAAQ,QAAqB,MAA2D;AACtG,UAAQ,OAAO,QAAQ,UAAU;AACnC;AAQO,IAAM,gBAAgB;AAOtB,SAAS,mBAAmB,KAAyB;AAC1D,MAAI,QAAQ,cAAe,OAAM,WAAW,8DAA8D;AAC1G,SAAO,YAAY,EAAE,SAAS,eAAe,OAAO,UAAU,KAAK,SAAS,aAAa,UAAU,KAAK,KAAK,GAAG,KAAK,EAAE,GAAG,IAAI,WAAW,CAAC,CAAC;AAC7I;AAGO,SAAS,gBAAgB,OAA8B;AAC5D,QAAM,IAAI,MAAM;AAChB,SAAO,EAAE,YAAY,aAAa,YAAY,QAAQ,GAAG,UAAU,GAAG,KAAK,MAAM,QAAQ,eAAe,KAAK,EAAE,QAAQ;AACzH;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -21,10 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
AbsPath: () => AbsPath,
|
|
24
|
+
BROADCAST_CID: () => BROADCAST_CID,
|
|
24
25
|
CancelMsg: () => CancelMsg,
|
|
25
26
|
ClientToCoreMsg: () => ClientToCoreMsg,
|
|
26
27
|
ClientType: () => ClientType,
|
|
27
28
|
CloseCode: () => CloseCode,
|
|
29
|
+
CloseCodeValue: () => CloseCodeValue,
|
|
28
30
|
CoreToClientMsg: () => CoreToClientMsg,
|
|
29
31
|
CreditMsg: () => CreditMsg,
|
|
30
32
|
DEFAULT_MAX_FRAME_PAYLOAD_BYTES: () => DEFAULT_MAX_FRAME_PAYLOAD_BYTES,
|
|
@@ -104,10 +106,16 @@ __export(src_exports, {
|
|
|
104
106
|
ProtocolErrorBody: () => ProtocolErrorBody,
|
|
105
107
|
ProtocolVersion: () => ProtocolVersion,
|
|
106
108
|
PtyInfo: () => PtyInfo,
|
|
109
|
+
RELAY_CALL_HELLO_ID: () => RELAY_CALL_HELLO_ID,
|
|
110
|
+
RELAY_CALL_REQUEST_ID: () => RELAY_CALL_REQUEST_ID,
|
|
107
111
|
RelayAuthExpiringEvent: () => RelayAuthExpiringEvent,
|
|
112
|
+
RelayCallEnvelope: () => RelayCallEnvelope,
|
|
113
|
+
RelayCallHello: () => RelayCallHello,
|
|
108
114
|
RelayCallRequest: () => RelayCallRequest,
|
|
109
115
|
RelayCallResponse: () => RelayCallResponse,
|
|
110
116
|
RelayDeviceStatusEvent: () => RelayDeviceStatusEvent,
|
|
117
|
+
RelayHopConnectionEnd: () => RelayHopConnectionEnd,
|
|
118
|
+
RelayHopEnvelopeHeader: () => RelayHopEnvelopeHeader,
|
|
111
119
|
RelayMsg: () => RelayMsg,
|
|
112
120
|
RelayStatusResponse: () => RelayStatusResponse,
|
|
113
121
|
RequestId: () => RequestId,
|
|
@@ -131,10 +139,12 @@ __export(src_exports, {
|
|
|
131
139
|
SysinfoGetResult: () => SysinfoGetResult,
|
|
132
140
|
Welcome: () => Welcome,
|
|
133
141
|
assertFrameHeader: () => assertFrameHeader,
|
|
142
|
+
connectionEndFrame: () => connectionEndFrame,
|
|
134
143
|
decodeFrame: () => decodeFrame,
|
|
135
144
|
encodeFrame: () => encodeFrame,
|
|
136
145
|
epochMs: () => epochMs,
|
|
137
146
|
hasFlag: () => hasFlag,
|
|
147
|
+
isConnectionEnd: () => isConnectionEnd,
|
|
138
148
|
isDesktopProtocolError: () => isDesktopProtocolError,
|
|
139
149
|
nonNegInt: () => nonNegInt,
|
|
140
150
|
readCid: () => readCid,
|
|
@@ -329,6 +339,15 @@ function stampCid(frame, cid) {
|
|
|
329
339
|
function hasFlag(header, flag) {
|
|
330
340
|
return (header.flags & flag) !== 0;
|
|
331
341
|
}
|
|
342
|
+
var BROADCAST_CID = 0;
|
|
343
|
+
function connectionEndFrame(cid) {
|
|
344
|
+
if (cid === BROADCAST_CID) throw frameError("a connection end needs a client cid, not the broadcast cid 0");
|
|
345
|
+
return encodeFrame({ version: FRAME_VERSION, flags: FrameFlag.FIN, channel: FrameChannel.ENVELOPE, cid, sid: 0, seq: 0 }, new Uint8Array(0));
|
|
346
|
+
}
|
|
347
|
+
function isConnectionEnd(frame) {
|
|
348
|
+
const h = frame.header;
|
|
349
|
+
return h.channel === FrameChannel.ENVELOPE && hasFlag(h, FrameFlag.FIN) && frame.payload.byteLength === 0 && h.cid !== BROADCAST_CID;
|
|
350
|
+
}
|
|
332
351
|
|
|
333
352
|
// src/schema/frame.ts
|
|
334
353
|
var FrameHeader = import_zod3.z.strictObject({
|
|
@@ -339,6 +358,22 @@ var FrameHeader = import_zod3.z.strictObject({
|
|
|
339
358
|
sid: u32,
|
|
340
359
|
seq: u32
|
|
341
360
|
});
|
|
361
|
+
var RelayHopEnvelopeHeader = import_zod3.z.strictObject({
|
|
362
|
+
version: import_zod3.z.literal(FRAME_VERSION),
|
|
363
|
+
flags: import_zod3.z.literal(0),
|
|
364
|
+
channel: import_zod3.z.literal(255),
|
|
365
|
+
cid: u32,
|
|
366
|
+
sid: import_zod3.z.literal(0),
|
|
367
|
+
seq: import_zod3.z.literal(0)
|
|
368
|
+
});
|
|
369
|
+
var RelayHopConnectionEnd = import_zod3.z.strictObject({
|
|
370
|
+
version: import_zod3.z.literal(FRAME_VERSION),
|
|
371
|
+
flags: import_zod3.z.literal(1),
|
|
372
|
+
channel: import_zod3.z.literal(255),
|
|
373
|
+
cid: import_zod3.z.int().min(1).max(4294967295),
|
|
374
|
+
sid: import_zod3.z.literal(0),
|
|
375
|
+
seq: import_zod3.z.literal(0)
|
|
376
|
+
});
|
|
342
377
|
var DEFAULT_WINDOW_BYTES = 262144;
|
|
343
378
|
var DEFAULT_MAX_FRAME_PAYLOAD_BYTES = 65536;
|
|
344
379
|
var DEFAULT_MAX_MESSAGE_BYTES = 8388608;
|
|
@@ -423,7 +458,15 @@ var CoreToClientMsg = import_zod4.z.discriminatedUnion("type", [ResponseMsg, Err
|
|
|
423
458
|
var CloseCode = {
|
|
424
459
|
NORMAL: 1e3,
|
|
425
460
|
GOING_AWAY: 1001,
|
|
426
|
-
// relay
|
|
461
|
+
// app quit; relay: the device was silent for 75 s — reconnect
|
|
462
|
+
/** Never sent by anyone: what a socket reports when it drops without a close frame. A relay
|
|
463
|
+
* deploy (every Durable Object restarts) arrives as this — reconnect immediately. */
|
|
464
|
+
ABNORMAL: 1006,
|
|
465
|
+
/** Relay → clients: the device (re)connected, so the core session they spoke to is gone —
|
|
466
|
+
* reconnect, hello, and reattach with since_seq. */
|
|
467
|
+
DEVICE_RECONNECTED: 1012,
|
|
468
|
+
TRY_AGAIN_LATER: 1013,
|
|
469
|
+
// relay: signing keys or the device registry unreachable — retry
|
|
427
470
|
AUTH_INVALID: 4001,
|
|
428
471
|
AUTH_FORBIDDEN: 4003,
|
|
429
472
|
DEVICE_NOT_FOUND: 4004,
|
|
@@ -436,6 +479,7 @@ var CloseCode = {
|
|
|
436
479
|
PROTOCOL_ERROR: 4400,
|
|
437
480
|
UPGRADE_REQUIRED: 4426
|
|
438
481
|
};
|
|
482
|
+
var CloseCodeValue = import_zod4.z.literal([1e3, 1001, 1006, 1012, 1013, 4001, 4003, 4004, 4008, 4009, 4010, 4400, 4426]);
|
|
439
483
|
|
|
440
484
|
// src/schema/sysinfo.ts
|
|
441
485
|
var import_zod5 = require("zod");
|
|
@@ -743,6 +787,22 @@ var RelayCallResponse = import_zod9.z.discriminatedUnion("ok", [
|
|
|
743
787
|
import_zod9.z.strictObject({ ok: import_zod9.z.literal(true), result: import_zod9.z.unknown() }),
|
|
744
788
|
import_zod9.z.strictObject({ ok: import_zod9.z.literal(false), error: ProtocolErrorBody })
|
|
745
789
|
]);
|
|
790
|
+
var RELAY_CALL_HELLO_ID = "hello";
|
|
791
|
+
var RELAY_CALL_REQUEST_ID = "call";
|
|
792
|
+
var RelayCallHello = import_zod9.z.strictObject({
|
|
793
|
+
type: import_zod9.z.literal("hello"),
|
|
794
|
+
id: import_zod9.z.literal(RELAY_CALL_HELLO_ID),
|
|
795
|
+
protocolVersion: ProtocolVersion,
|
|
796
|
+
clientType: import_zod9.z.literal("server"),
|
|
797
|
+
clientVersion: import_zod9.z.string().min(1).max(32)
|
|
798
|
+
});
|
|
799
|
+
var RelayCallEnvelope = import_zod9.z.strictObject({
|
|
800
|
+
id: import_zod9.z.literal(RELAY_CALL_REQUEST_ID),
|
|
801
|
+
type: import_zod9.z.literal("request"),
|
|
802
|
+
cap: import_zod9.z.string().min(1).max(32),
|
|
803
|
+
op: import_zod9.z.string().min(1).max(64),
|
|
804
|
+
params: import_zod9.z.unknown()
|
|
805
|
+
});
|
|
746
806
|
var RelayStatusResponse = RelayDeviceStatusEvent;
|
|
747
807
|
|
|
748
808
|
// src/schema/registry.ts
|
|
@@ -871,6 +931,11 @@ var NAMED_SCHEMAS = {
|
|
|
871
931
|
RelayDeviceStatusEvent,
|
|
872
932
|
RelayAuthExpiringEvent,
|
|
873
933
|
RelayCallRequest,
|
|
874
|
-
RelayCallResponse
|
|
934
|
+
RelayCallResponse,
|
|
935
|
+
RelayCallHello,
|
|
936
|
+
RelayCallEnvelope,
|
|
937
|
+
RelayHopEnvelopeHeader,
|
|
938
|
+
RelayHopConnectionEnd,
|
|
939
|
+
CloseCodeValue
|
|
875
940
|
};
|
|
876
941
|
//# sourceMappingURL=index.cjs.map
|