@portalsdk/wire-protocol 0.2.0 → 0.3.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/dist/index.cjs CHANGED
@@ -5,6 +5,7 @@ var PROTOCOL_VERSION = 1;
5
5
  var UPGRADE_PARAMS = {
6
6
  version: "v",
7
7
  token: "token",
8
+ key: "key",
8
9
  leaf: "leaf",
9
10
  meta: "meta",
10
11
  last: "last"
@@ -40,7 +41,7 @@ var isWireMessage = (v) => isRec(v) && isStr(v["id"]) && (v["seq"] === null || i
40
41
  var isChannelInfo = (v) => isRec(v) && isStr(v["id"]) && (v["mode"] === "standard" || v["mode"] === "broadcast") && opt(v["name"], isStr) && opt(v["meta"], isRec);
41
42
  var isCapabilities = (v) => isRec(v) && opt(v["publish"], isBool) && opt(v["sendDirect"], isBool);
42
43
  var isMeInfo = (v) => isRec(v) && isStr(v["id"]) && isBool(v["anon"]) && isRec(v["claims"]) && isCapabilities(v["capabilities"]);
43
- var isParticipant = (v) => isRec(v) && isStr(v["userId"]) && isRec(v["claims"]);
44
+ var isParticipant = (v) => isRec(v) && isStr(v["id"]) && isBool(v["anon"]) && opt(v["username"], isStr) && opt(v["metadata"], isRec);
44
45
  var isReadyPresence = (v) => {
45
46
  if (!isRec(v)) return false;
46
47
  if (v["mode"] === "detailed") {
@@ -60,7 +61,7 @@ var CHANNEL_SERVER_FRAMES = {
60
61
  retract: (v) => isStr(v["id"]) && isNum(v["seq"]) && opt(v["reason"], isStr),
61
62
  presence: (v) => {
62
63
  if (v["mode"] === "detailed") {
63
- return arrayOf(v["joined"], isParticipant) && arrayOf(v["left"], isParticipant) && isNum(v["count"]);
64
+ return arrayOf(v["joined"], isParticipant) && arrayOf(v["left"], isStr) && isNum(v["count"]);
64
65
  }
65
66
  if (v["mode"] === "aggregate") {
66
67
  return isNum(v["count"]) && Array.isArray(v["recent"]);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/parse.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Protocol version carried by every upgrade as `?v=` (§1.1, §6).\n *\n * An unknown version is refused at the upgrade with HTTP 426 `unsupported_version`.\n * Within v1 the protocol evolves additively only; a breaking change bumps this.\n */\nexport const PROTOCOL_VERSION = 1;\n\n/**\n * Upgrade query-parameter names (§1.1). Build upgrade URLs from these rather than\n * string literals, so a rename is a compile error rather than a silent 4xx.\n *\n * - `version` — required on every upgrade; unknown → 426.\n * - `token` — the signed JWT. Identifies the user; the apiKey is resolved from it.\n * - `leaf` — opaque reconnect token; echo back what `ready` gave you, unchanged.\n * - `meta` — initial presence metadata, base64 JSON (standard channels; ≤1KB decoded).\n * - `last` — highest contiguous seq held, sent on reconnect to request replay (§1.4).\n */\nexport const UPGRADE_PARAMS = {\n version: \"v\",\n token: \"token\",\n leaf: \"leaf\",\n meta: \"meta\",\n last: \"last\",\n} as const;\n\n/**\n * Response header carrying the refusal code on a refused upgrade (§1.1).\n *\n * Duplicates `code` from the body so a client behind a body-eating proxy can still\n * tell why the socket never opened.\n */\nexport const PORTAL_ERROR_HEADER = \"x-portal-error\";\n","/**\n * Why an upgrade was refused (§1.1).\n *\n * Refusals happen at the HTTP upgrade — **the socket never opens**. They are therefore\n * disjoint from {@link PublishErrorCode} (HTTP publish rejections) and from the\n * in-session `error` frame, which both presuppose a working connection.\n */\nexport type RefusalCode =\n | \"invalid_token\"\n | \"token_expired\"\n | \"invalid_api_key\"\n | \"not_member\"\n | \"banned\"\n | \"anonymous_not_allowed\"\n | \"unknown_channel\"\n | \"unsupported_version\"\n | \"channel_at_capacity\";\n\n/**\n * The HTTP status each refusal is delivered with (§1.1 table).\n *\n * The mapping is many-to-one: status alone does not identify the cause, so read\n * `code` from the body (or the `x-portal-error` header) rather than branching on status.\n */\nexport const REFUSAL_STATUS = {\n invalid_token: 401,\n token_expired: 401,\n invalid_api_key: 403,\n not_member: 403,\n banned: 403,\n anonymous_not_allowed: 403,\n unknown_channel: 404,\n unsupported_version: 426,\n channel_at_capacity: 429,\n} as const satisfies Record<RefusalCode, number>;\n\nconst REFUSAL_CODES: ReadonlySet<string> = new Set(Object.keys(REFUSAL_STATUS));\n\n/**\n * Whether a value is a refusal code this version knows (§1.1).\n *\n * A refusal body arriving with an unrecognised code is not a refusal this client can\n * reason about — treat it as an opaque failure rather than coercing it.\n */\nexport const isRefusalCode = (value: unknown): value is RefusalCode =>\n typeof value === \"string\" && REFUSAL_CODES.has(value);\n\n/** Body of a refused upgrade (§1.1). */\nexport type RefusalBody = {\n code: RefusalCode;\n reason?: string;\n /**\n * Seconds to wait before retrying. Documented for `channel_at_capacity` (429) only\n * (§1.1); absent on every other refusal.\n */\n retryAfter?: number;\n};\n\n/**\n * Why an HTTP publish was rejected (§3.1).\n *\n * Deliberately NOT merged into {@link RefusalCode}: these arrive on a live connection\n * in response to `POST /v1/channels/{id}/messages`, and a client reacts to them per\n * send, not per connection.\n *\n * `blocked_by_middleware` carries user-visible copy in `reason`.\n */\nexport type PublishErrorCode =\n | \"not_permitted\"\n | \"blocked_by_middleware\"\n | \"content_too_large\"\n | \"rate_limited\";\n\n/**\n * Body of a rejected publish (§3.1).\n *\n * SPEC: §3.1 specifies the shape as `4xx { code, reason? }` but does not map each code\n * to a status, so no status record is exported here (unlike {@link REFUSAL_STATUS}).\n */\nexport type PublishErrorBody = {\n code: PublishErrorCode;\n reason?: string;\n};\n","import type {\n ActivityFrame,\n BatchFrame,\n ChannelClientFrame,\n ChannelReadyFrame,\n ChannelServerFrame,\n Capabilities,\n ChannelInfo,\n DirectFrame,\n ErrorFrame,\n MeInfo,\n PongFrame,\n PresenceFrame,\n ReadyPresence,\n ReassignFrame,\n RetractFrame,\n WirePresenceParticipant,\n} from \"./channel.js\";\nimport type {\n InboxClientFrame,\n InboxCounterFrame,\n InboxEntryFrame,\n InboxEntryWire,\n InboxItemFrame,\n InboxItemWire,\n InboxReadyFrame,\n InboxServerFrame,\n} from \"./inbox.js\";\nimport type {\n AnyFrame,\n ParsedChannelClientFrame,\n ParsedChannelFrame,\n ParsedInboxClientFrame,\n ParsedInboxFrame,\n UnknownFrame,\n} from \"./frames.js\";\nimport type { Mention, WireMessage, WireSender } from \"./message.js\";\n\n// ============================================================\n// Primitives\n// ============================================================\n\ntype Rec = Record<string, unknown>;\n\nconst isRec = (v: unknown): v is Rec =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n\nconst isStr = (v: unknown): v is string => typeof v === \"string\";\nconst isBool = (v: unknown): v is boolean => typeof v === \"boolean\";\n\n/** JSON has no NaN/Infinity, so any non-finite number means a hand-built object. */\nconst isNum = (v: unknown): v is number =>\n typeof v === \"number\" && Number.isFinite(v);\n\n/** An absent optional field is valid; a present one must match. */\nconst opt = <T>(v: unknown, check: (x: unknown) => x is T): boolean =>\n v === undefined || check(v);\n\nconst arrayOf = <T>(v: unknown, check: (x: unknown) => x is T): v is T[] =>\n Array.isArray(v) && v.every(check);\n\nconst isStrRec = (v: unknown): v is Record<string, string> =>\n isRec(v) && Object.values(v).every(isStr);\n\n// ============================================================\n// Shape validators\n//\n// Each validator checks exactly the fields its type declares required — no more, no\n// less. Unknown fields are ignored and survive untouched (§6), but a frame missing a\n// required field is rejected rather than returned as a type that lies about it.\n// ============================================================\n\nconst isMention = (v: unknown): v is Mention => isRec(v) && isStr(v[\"userId\"]);\n\nconst isWireSender = (v: unknown): v is WireSender =>\n isRec(v) && isStr(v[\"id\"]) && isBool(v[\"anon\"]) && opt(v[\"username\"], isStr);\n\nconst isWireMessage = (v: unknown): v is WireMessage =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n (v[\"seq\"] === null || isNum(v[\"seq\"])) &&\n isStr(v[\"type\"]) &&\n isStr(v[\"kind\"]) &&\n \"content\" in v &&\n isWireSender(v[\"sender\"]) &&\n isNum(v[\"timestamp\"]) &&\n opt(v[\"to\"], isStr) &&\n (v[\"mentions\"] === undefined || arrayOf(v[\"mentions\"], isMention)) &&\n isBool(v[\"retracted\"]) &&\n isBool(v[\"ephemeral\"]);\n\nconst isChannelInfo = (v: unknown): v is ChannelInfo =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n (v[\"mode\"] === \"standard\" || v[\"mode\"] === \"broadcast\") &&\n opt(v[\"name\"], isStr) &&\n opt(v[\"meta\"], isRec);\n\nconst isCapabilities = (v: unknown): v is Capabilities =>\n isRec(v) && opt(v[\"publish\"], isBool) && opt(v[\"sendDirect\"], isBool);\n\nconst isMeInfo = (v: unknown): v is MeInfo =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n isBool(v[\"anon\"]) &&\n isRec(v[\"claims\"]) &&\n isCapabilities(v[\"capabilities\"]);\n\nconst isParticipant = (v: unknown): v is WirePresenceParticipant =>\n isRec(v) && isStr(v[\"userId\"]) && isRec(v[\"claims\"]);\n\nconst isReadyPresence = (v: unknown): v is ReadyPresence => {\n if (!isRec(v)) return false;\n if (v[\"mode\"] === \"detailed\") {\n return arrayOf(v[\"participants\"], isParticipant) && isNum(v[\"count\"]);\n }\n if (v[\"mode\"] === \"aggregate\") {\n return isNum(v[\"count\"]) && (v[\"recent\"] === undefined || Array.isArray(v[\"recent\"]));\n }\n return false;\n};\n\nconst isInboxEntryWire = (v: unknown): v is InboxEntryWire =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n opt(v[\"name\"], isStr) &&\n opt(v[\"meta\"], isRec) &&\n opt(v[\"latest\"], isLatest) &&\n isNum(v[\"unread\"]) &&\n isBool(v[\"muted\"]) &&\n isNum(v[\"at\"]);\n\nconst isLatest = (v: unknown): v is InboxEntryWire[\"latest\"] =>\n isRec(v) &&\n isStr(v[\"text\"]) &&\n isRec(v[\"sender\"]) &&\n isStr((v[\"sender\"] as Rec)[\"id\"]) &&\n isNum(v[\"at\"]);\n\nconst isInboxItemWire = (v: unknown): v is InboxItemWire =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n isStr(v[\"type\"]) &&\n opt(v[\"title\"], isStr) &&\n \"data\" in v &&\n opt(v[\"channelId\"], isStr) &&\n isNum(v[\"at\"]) &&\n isBool(v[\"read\"]);\n\n// ============================================================\n// Frame tables — the single source of truth for \"known `t`\"\n// ============================================================\n\ntype Validator = (v: Rec) => boolean;\n\nconst CHANNEL_SERVER_FRAMES: Record<ChannelServerFrame[\"t\"], Validator> = {\n ready: (v) =>\n isChannelInfo(v[\"channel\"]) &&\n isMeInfo(v[\"me\"]) &&\n isNum(v[\"seq\"]) &&\n isStr(v[\"leaf\"]) &&\n isReadyPresence(v[\"presence\"]) &&\n opt(v[\"watermark\"], isNum) &&\n opt(v[\"ext\"], isRec) &&\n opt(v[\"bindings\"], isStrRec),\n batch: (v) => arrayOf(v[\"msgs\"], isWireMessage),\n retract: (v) => isStr(v[\"id\"]) && isNum(v[\"seq\"]) && opt(v[\"reason\"], isStr),\n presence: (v) => {\n if (v[\"mode\"] === \"detailed\") {\n return (\n arrayOf(v[\"joined\"], isParticipant) &&\n arrayOf(v[\"left\"], isParticipant) &&\n isNum(v[\"count\"])\n );\n }\n if (v[\"mode\"] === \"aggregate\") {\n return isNum(v[\"count\"]) && Array.isArray(v[\"recent\"]);\n }\n return false;\n },\n activity: (v) => isStr(v[\"userId\"]) && isStr(v[\"kind\"]) && isNum(v[\"since\"]),\n direct: (v) => isWireMessage(v[\"msg\"]),\n reassign: (v) => isStr(v[\"leaf\"]),\n error: (v) => isStr(v[\"code\"]) && opt(v[\"reason\"], isStr) && opt(v[\"ref\"], isStr),\n pong: () => true,\n};\n\nconst INBOX_SERVER_FRAMES: Record<InboxServerFrame[\"t\"], Validator> = {\n ready: (v) =>\n arrayOf(v[\"entries\"], isInboxEntryWire) &&\n arrayOf(v[\"items\"], isInboxItemWire) &&\n isNum(v[\"counter\"]),\n entry: (v) => isInboxEntryWire(v[\"entry\"]),\n item: (v) => isInboxItemWire(v[\"item\"]),\n counter: (v) => isNum(v[\"n\"]),\n pong: () => true,\n};\n\n// C→S tables. A server (or the test mock) receives these; the same totality contract\n// applies as for the S→C tables. `ping` and `activity` also appear in S→C sets with\n// different shapes — the families are disjoint, so each is validated against its own\n// direction's table.\nconst CHANNEL_CLIENT_FRAMES: Record<ChannelClientFrame[\"t\"], Validator> = {\n ephemeral: (v) => isStr(v[\"cl\"]) && isStr(v[\"type\"]) && \"content\" in v,\n activity: (v) => isStr(v[\"kind\"]),\n watermark: (v) => isNum(v[\"seq\"]),\n meta: (v) => isRec(v[\"metadata\"]),\n ping: () => true,\n};\n\nconst INBOX_CLIENT_FRAMES: Record<InboxClientFrame[\"t\"], Validator> = {\n read: (v) => isStr(v[\"channelId\"]),\n \"item.read\": (v) => isStr(v[\"id\"]),\n \"read.all\": () => true,\n mute: (v) => isStr(v[\"channelId\"]) && isBool(v[\"muted\"]),\n ping: () => true,\n};\n\nconst validatorFor = <T extends string>(\n table: Record<T, Validator>,\n t: string,\n): Validator | undefined =>\n Object.hasOwn(table, t) ? table[t as T] : undefined;\n\n// ============================================================\n// Parse / serialize\n// ============================================================\n\nconst parseJson = (raw: string): unknown => {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return undefined;\n }\n};\n\n/**\n * A frame-shaped value: an object carrying a string `t`. Anything else is not a frame.\n */\nconst asFrameShape = (raw: string): (Rec & { t: string }) | null => {\n const value = parseJson(raw);\n if (!isRec(value) || !isStr(value[\"t\"])) return null;\n return value as Rec & { t: string };\n};\n\n/**\n * Parse a text frame from a **channel** socket (S→C).\n *\n * Total and non-throwing:\n * - malformed JSON, a non-object, or a missing/non-string `t` → `null`\n * - a known `t` whose shape does not match → `null`\n * - an unknown `t` → the frame is returned intact as an {@link UnknownFrame} (§6),\n * because forward compatibility says ignore it, not lose it\n *\n * Unknown *fields* on known frames are preserved, so `parse` → {@link serializeFrame}\n * round-trips without dropping anything.\n */\nexport function parseChannelFrame(raw: string): ParsedChannelFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(CHANNEL_SERVER_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as ChannelServerFrame) : null;\n}\n\n/**\n * Parse a text frame from an **inbox** socket (S→C).\n *\n * Same contract as {@link parseChannelFrame}. The two families are disjoint: an inbox\n * `ready` and a channel `ready` share a `t` but not a shape, so a frame must be parsed\n * with the function matching the socket it arrived on.\n */\nexport function parseInboxFrame(raw: string): ParsedInboxFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(INBOX_SERVER_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as InboxServerFrame) : null;\n}\n\n/**\n * Parse a text frame a client sent on a **channel** socket (C→S).\n *\n * The upstream counterpart of {@link parseChannelFrame}, for a server or test mock\n * receiving the frames a client sends. Same totality contract: `null` on malformed JSON\n * / missing `t` / a known `t` with a bad shape, and an {@link UnknownFrame} passthrough\n * for an unrecognised `t` (§6).\n */\nexport function parseChannelClientFrame(\n raw: string,\n): ParsedChannelClientFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(CHANNEL_CLIENT_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as ChannelClientFrame) : null;\n}\n\n/**\n * Parse a text frame a client sent on an **inbox** socket (C→S).\n *\n * Same contract as {@link parseChannelClientFrame}.\n */\nexport function parseInboxClientFrame(raw: string): ParsedInboxClientFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(INBOX_CLIENT_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as InboxClientFrame) : null;\n}\n\n/**\n * Serialize a frame to a JSON text frame.\n *\n * Primarily for C→S sends. It accepts any frame so that a parsed S→C frame can be\n * re-serialized intact — the round-trip that proves unknown fields survive (§6).\n */\nexport function serializeFrame(frame: AnyFrame): string {\n return JSON.stringify(frame);\n}\n\n// ============================================================\n// Guards\n//\n// These narrow a frame that has already been through parseChannelFrame /\n// parseInboxFrame, which is what makes a `t` check sufficient: the parser has already\n// validated the shape, so narrowing on the discriminator cannot lie.\n// ============================================================\n\n/** True when the parser recognised this channel frame — i.e. it is not an `UnknownFrame`. */\nexport const isKnownChannelFrame = (\n frame: ParsedChannelFrame,\n): frame is ChannelServerFrame => Object.hasOwn(CHANNEL_SERVER_FRAMES, frame.t);\n\n/** True when the parser recognised this inbox frame — i.e. it is not an `UnknownFrame`. */\nexport const isKnownInboxFrame = (\n frame: ParsedInboxFrame,\n): frame is InboxServerFrame => Object.hasOwn(INBOX_SERVER_FRAMES, frame.t);\n\n/** True when the parser recognised this C→S channel frame (not an `UnknownFrame`). */\nexport const isKnownChannelClientFrame = (\n frame: ParsedChannelClientFrame,\n): frame is ChannelClientFrame => Object.hasOwn(CHANNEL_CLIENT_FRAMES, frame.t);\n\n/** True when the parser recognised this C→S inbox frame (not an `UnknownFrame`). */\nexport const isKnownInboxClientFrame = (\n frame: ParsedInboxClientFrame,\n): frame is InboxClientFrame => Object.hasOwn(INBOX_CLIENT_FRAMES, frame.t);\n\n// ── Channel S→C ─────────────────────────────────────────────\n\nexport const isChannelReady = (f: ParsedChannelFrame): f is ChannelReadyFrame =>\n f.t === \"ready\";\nexport const isBatch = (f: ParsedChannelFrame): f is BatchFrame => f.t === \"batch\";\nexport const isRetract = (f: ParsedChannelFrame): f is RetractFrame =>\n f.t === \"retract\";\nexport const isPresence = (f: ParsedChannelFrame): f is PresenceFrame =>\n f.t === \"presence\";\nexport const isActivity = (f: ParsedChannelFrame): f is ActivityFrame =>\n f.t === \"activity\";\nexport const isDirect = (f: ParsedChannelFrame): f is DirectFrame => f.t === \"direct\";\nexport const isReassign = (f: ParsedChannelFrame): f is ReassignFrame =>\n f.t === \"reassign\";\nexport const isError = (f: ParsedChannelFrame): f is ErrorFrame => f.t === \"error\";\nexport const isPong = (f: ParsedChannelFrame): f is PongFrame => f.t === \"pong\";\n\n// ── Inbox S→C ───────────────────────────────────────────────\n\nexport const isInboxReady = (f: ParsedInboxFrame): f is InboxReadyFrame =>\n f.t === \"ready\";\nexport const isInboxEntry = (f: ParsedInboxFrame): f is InboxEntryFrame =>\n f.t === \"entry\";\nexport const isInboxItem = (f: ParsedInboxFrame): f is InboxItemFrame =>\n f.t === \"item\";\nexport const isInboxCounter = (f: ParsedInboxFrame): f is InboxCounterFrame =>\n f.t === \"counter\";\nexport const isInboxPong = (f: ParsedInboxFrame): f is PongFrame => f.t === \"pong\";\n"]}
1
+ {"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/parse.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Protocol version carried by every upgrade as `?v=` (§1.1, §6).\n *\n * An unknown version is refused at the upgrade with HTTP 426 `unsupported_version`.\n * Within v1 the protocol evolves additively only; a breaking change bumps this.\n */\nexport const PROTOCOL_VERSION = 1;\n\n/**\n * Upgrade query-parameter names (§1.1). Build upgrade URLs from these rather than\n * string literals, so a rename is a compile error rather than a silent 4xx.\n *\n * - `version` — required on every upgrade; unknown → 426.\n * - `token` — the signed JWT. Identifies the user.\n * - `key` — the publishable apiKey, identifying the app (§1 credential transport).\n * - `leaf` — opaque reconnect token; echo back what `ready` gave you, unchanged.\n * - `meta` — initial presence metadata, base64 JSON (standard channels; ≤1KB decoded).\n * - `last` — highest contiguous seq held, sent on reconnect to request replay (§1.4).\n */\nexport const UPGRADE_PARAMS = {\n version: \"v\",\n token: \"token\",\n key: \"key\",\n leaf: \"leaf\",\n meta: \"meta\",\n last: \"last\",\n} as const;\n\n/**\n * Response header carrying the refusal code on a refused upgrade (§1.1).\n *\n * Duplicates `code` from the body so a client behind a body-eating proxy can still\n * tell why the socket never opened.\n */\nexport const PORTAL_ERROR_HEADER = \"x-portal-error\";\n","/**\n * Why an upgrade was refused (§1.1).\n *\n * Refusals happen at the HTTP upgrade — **the socket never opens**. They are therefore\n * disjoint from {@link PublishErrorCode} (HTTP publish rejections) and from the\n * in-session `error` frame, which both presuppose a working connection.\n */\nexport type RefusalCode =\n | \"invalid_token\"\n | \"token_expired\"\n | \"invalid_api_key\"\n | \"not_member\"\n | \"banned\"\n | \"anonymous_not_allowed\"\n | \"unknown_channel\"\n | \"unsupported_version\"\n | \"channel_at_capacity\";\n\n/**\n * The HTTP status each refusal is delivered with (§1.1 table).\n *\n * The mapping is many-to-one: status alone does not identify the cause, so read\n * `code` from the body (or the `x-portal-error` header) rather than branching on status.\n */\nexport const REFUSAL_STATUS = {\n invalid_token: 401,\n token_expired: 401,\n invalid_api_key: 403,\n not_member: 403,\n banned: 403,\n anonymous_not_allowed: 403,\n unknown_channel: 404,\n unsupported_version: 426,\n channel_at_capacity: 429,\n} as const satisfies Record<RefusalCode, number>;\n\nconst REFUSAL_CODES: ReadonlySet<string> = new Set(Object.keys(REFUSAL_STATUS));\n\n/**\n * Whether a value is a refusal code this version knows (§1.1).\n *\n * A refusal body arriving with an unrecognised code is not a refusal this client can\n * reason about — treat it as an opaque failure rather than coercing it.\n */\nexport const isRefusalCode = (value: unknown): value is RefusalCode =>\n typeof value === \"string\" && REFUSAL_CODES.has(value);\n\n/** Body of a refused upgrade (§1.1). */\nexport type RefusalBody = {\n code: RefusalCode;\n reason?: string;\n /**\n * Seconds to wait before retrying. Documented for `channel_at_capacity` (429) only\n * (§1.1); absent on every other refusal.\n */\n retryAfter?: number;\n};\n\n/**\n * Why an HTTP publish was rejected (§3.1).\n *\n * Deliberately NOT merged into {@link RefusalCode}: these arrive on a live connection\n * in response to `POST /v1/channels/{id}/messages`, and a client reacts to them per\n * send, not per connection.\n *\n * `blocked_by_middleware` carries user-visible copy in `reason`.\n */\nexport type PublishErrorCode =\n | \"not_permitted\"\n | \"blocked_by_middleware\"\n | \"content_too_large\"\n | \"rate_limited\";\n\n/**\n * Body of a rejected publish (§3.1).\n *\n * SPEC: §3.1 specifies the shape as `4xx { code, reason? }` but does not map each code\n * to a status, so no status record is exported here (unlike {@link REFUSAL_STATUS}).\n */\nexport type PublishErrorBody = {\n code: PublishErrorCode;\n reason?: string;\n};\n","import type {\n ActivityFrame,\n BatchFrame,\n ChannelClientFrame,\n ChannelReadyFrame,\n ChannelServerFrame,\n Capabilities,\n ChannelInfo,\n DirectFrame,\n ErrorFrame,\n MeInfo,\n PongFrame,\n PresenceFrame,\n ReadyPresence,\n ReassignFrame,\n RetractFrame,\n WirePresenceParticipant,\n} from \"./channel.js\";\nimport type {\n InboxClientFrame,\n InboxCounterFrame,\n InboxEntryFrame,\n InboxEntryWire,\n InboxItemFrame,\n InboxItemWire,\n InboxReadyFrame,\n InboxServerFrame,\n} from \"./inbox.js\";\nimport type {\n AnyFrame,\n ParsedChannelClientFrame,\n ParsedChannelFrame,\n ParsedInboxClientFrame,\n ParsedInboxFrame,\n UnknownFrame,\n} from \"./frames.js\";\nimport type { Mention, WireMessage, WireSender } from \"./message.js\";\n\n// ============================================================\n// Primitives\n// ============================================================\n\ntype Rec = Record<string, unknown>;\n\nconst isRec = (v: unknown): v is Rec =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n\nconst isStr = (v: unknown): v is string => typeof v === \"string\";\nconst isBool = (v: unknown): v is boolean => typeof v === \"boolean\";\n\n/** JSON has no NaN/Infinity, so any non-finite number means a hand-built object. */\nconst isNum = (v: unknown): v is number =>\n typeof v === \"number\" && Number.isFinite(v);\n\n/** An absent optional field is valid; a present one must match. */\nconst opt = <T>(v: unknown, check: (x: unknown) => x is T): boolean =>\n v === undefined || check(v);\n\nconst arrayOf = <T>(v: unknown, check: (x: unknown) => x is T): v is T[] =>\n Array.isArray(v) && v.every(check);\n\nconst isStrRec = (v: unknown): v is Record<string, string> =>\n isRec(v) && Object.values(v).every(isStr);\n\n// ============================================================\n// Shape validators\n//\n// Each validator checks exactly the fields its type declares required — no more, no\n// less. Unknown fields are ignored and survive untouched (§6), but a frame missing a\n// required field is rejected rather than returned as a type that lies about it.\n// ============================================================\n\nconst isMention = (v: unknown): v is Mention => isRec(v) && isStr(v[\"userId\"]);\n\nconst isWireSender = (v: unknown): v is WireSender =>\n isRec(v) && isStr(v[\"id\"]) && isBool(v[\"anon\"]) && opt(v[\"username\"], isStr);\n\nconst isWireMessage = (v: unknown): v is WireMessage =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n (v[\"seq\"] === null || isNum(v[\"seq\"])) &&\n isStr(v[\"type\"]) &&\n isStr(v[\"kind\"]) &&\n \"content\" in v &&\n isWireSender(v[\"sender\"]) &&\n isNum(v[\"timestamp\"]) &&\n opt(v[\"to\"], isStr) &&\n (v[\"mentions\"] === undefined || arrayOf(v[\"mentions\"], isMention)) &&\n isBool(v[\"retracted\"]) &&\n isBool(v[\"ephemeral\"]);\n\nconst isChannelInfo = (v: unknown): v is ChannelInfo =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n (v[\"mode\"] === \"standard\" || v[\"mode\"] === \"broadcast\") &&\n opt(v[\"name\"], isStr) &&\n opt(v[\"meta\"], isRec);\n\nconst isCapabilities = (v: unknown): v is Capabilities =>\n isRec(v) && opt(v[\"publish\"], isBool) && opt(v[\"sendDirect\"], isBool);\n\nconst isMeInfo = (v: unknown): v is MeInfo =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n isBool(v[\"anon\"]) &&\n isRec(v[\"claims\"]) &&\n isCapabilities(v[\"capabilities\"]);\n\nconst isParticipant = (v: unknown): v is WirePresenceParticipant =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n isBool(v[\"anon\"]) &&\n opt(v[\"username\"], isStr) &&\n opt(v[\"metadata\"], isRec);\n\nconst isReadyPresence = (v: unknown): v is ReadyPresence => {\n if (!isRec(v)) return false;\n if (v[\"mode\"] === \"detailed\") {\n return arrayOf(v[\"participants\"], isParticipant) && isNum(v[\"count\"]);\n }\n if (v[\"mode\"] === \"aggregate\") {\n return isNum(v[\"count\"]) && (v[\"recent\"] === undefined || Array.isArray(v[\"recent\"]));\n }\n return false;\n};\n\nconst isInboxEntryWire = (v: unknown): v is InboxEntryWire =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n opt(v[\"name\"], isStr) &&\n opt(v[\"meta\"], isRec) &&\n opt(v[\"latest\"], isLatest) &&\n isNum(v[\"unread\"]) &&\n isBool(v[\"muted\"]) &&\n isNum(v[\"at\"]);\n\nconst isLatest = (v: unknown): v is InboxEntryWire[\"latest\"] =>\n isRec(v) &&\n isStr(v[\"text\"]) &&\n isRec(v[\"sender\"]) &&\n isStr((v[\"sender\"] as Rec)[\"id\"]) &&\n isNum(v[\"at\"]);\n\nconst isInboxItemWire = (v: unknown): v is InboxItemWire =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n isStr(v[\"type\"]) &&\n opt(v[\"title\"], isStr) &&\n \"data\" in v &&\n opt(v[\"channelId\"], isStr) &&\n isNum(v[\"at\"]) &&\n isBool(v[\"read\"]);\n\n// ============================================================\n// Frame tables — the single source of truth for \"known `t`\"\n// ============================================================\n\ntype Validator = (v: Rec) => boolean;\n\nconst CHANNEL_SERVER_FRAMES: Record<ChannelServerFrame[\"t\"], Validator> = {\n ready: (v) =>\n isChannelInfo(v[\"channel\"]) &&\n isMeInfo(v[\"me\"]) &&\n isNum(v[\"seq\"]) &&\n isStr(v[\"leaf\"]) &&\n isReadyPresence(v[\"presence\"]) &&\n opt(v[\"watermark\"], isNum) &&\n opt(v[\"ext\"], isRec) &&\n opt(v[\"bindings\"], isStrRec),\n batch: (v) => arrayOf(v[\"msgs\"], isWireMessage),\n retract: (v) => isStr(v[\"id\"]) && isNum(v[\"seq\"]) && opt(v[\"reason\"], isStr),\n presence: (v) => {\n if (v[\"mode\"] === \"detailed\") {\n return (\n arrayOf(v[\"joined\"], isParticipant) &&\n arrayOf(v[\"left\"], isStr) &&\n isNum(v[\"count\"])\n );\n }\n if (v[\"mode\"] === \"aggregate\") {\n return isNum(v[\"count\"]) && Array.isArray(v[\"recent\"]);\n }\n return false;\n },\n activity: (v) => isStr(v[\"userId\"]) && isStr(v[\"kind\"]) && isNum(v[\"since\"]),\n direct: (v) => isWireMessage(v[\"msg\"]),\n reassign: (v) => isStr(v[\"leaf\"]),\n error: (v) => isStr(v[\"code\"]) && opt(v[\"reason\"], isStr) && opt(v[\"ref\"], isStr),\n pong: () => true,\n};\n\nconst INBOX_SERVER_FRAMES: Record<InboxServerFrame[\"t\"], Validator> = {\n ready: (v) =>\n arrayOf(v[\"entries\"], isInboxEntryWire) &&\n arrayOf(v[\"items\"], isInboxItemWire) &&\n isNum(v[\"counter\"]),\n entry: (v) => isInboxEntryWire(v[\"entry\"]),\n item: (v) => isInboxItemWire(v[\"item\"]),\n counter: (v) => isNum(v[\"n\"]),\n pong: () => true,\n};\n\n// C→S tables. A server (or the test mock) receives these; the same totality contract\n// applies as for the S→C tables. `ping` and `activity` also appear in S→C sets with\n// different shapes — the families are disjoint, so each is validated against its own\n// direction's table.\nconst CHANNEL_CLIENT_FRAMES: Record<ChannelClientFrame[\"t\"], Validator> = {\n ephemeral: (v) => isStr(v[\"cl\"]) && isStr(v[\"type\"]) && \"content\" in v,\n activity: (v) => isStr(v[\"kind\"]),\n watermark: (v) => isNum(v[\"seq\"]),\n meta: (v) => isRec(v[\"metadata\"]),\n ping: () => true,\n};\n\nconst INBOX_CLIENT_FRAMES: Record<InboxClientFrame[\"t\"], Validator> = {\n read: (v) => isStr(v[\"channelId\"]),\n \"item.read\": (v) => isStr(v[\"id\"]),\n \"read.all\": () => true,\n mute: (v) => isStr(v[\"channelId\"]) && isBool(v[\"muted\"]),\n ping: () => true,\n};\n\nconst validatorFor = <T extends string>(\n table: Record<T, Validator>,\n t: string,\n): Validator | undefined =>\n Object.hasOwn(table, t) ? table[t as T] : undefined;\n\n// ============================================================\n// Parse / serialize\n// ============================================================\n\nconst parseJson = (raw: string): unknown => {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return undefined;\n }\n};\n\n/**\n * A frame-shaped value: an object carrying a string `t`. Anything else is not a frame.\n */\nconst asFrameShape = (raw: string): (Rec & { t: string }) | null => {\n const value = parseJson(raw);\n if (!isRec(value) || !isStr(value[\"t\"])) return null;\n return value as Rec & { t: string };\n};\n\n/**\n * Parse a text frame from a **channel** socket (S→C).\n *\n * Total and non-throwing:\n * - malformed JSON, a non-object, or a missing/non-string `t` → `null`\n * - a known `t` whose shape does not match → `null`\n * - an unknown `t` → the frame is returned intact as an {@link UnknownFrame} (§6),\n * because forward compatibility says ignore it, not lose it\n *\n * Unknown *fields* on known frames are preserved, so `parse` → {@link serializeFrame}\n * round-trips without dropping anything.\n */\nexport function parseChannelFrame(raw: string): ParsedChannelFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(CHANNEL_SERVER_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as ChannelServerFrame) : null;\n}\n\n/**\n * Parse a text frame from an **inbox** socket (S→C).\n *\n * Same contract as {@link parseChannelFrame}. The two families are disjoint: an inbox\n * `ready` and a channel `ready` share a `t` but not a shape, so a frame must be parsed\n * with the function matching the socket it arrived on.\n */\nexport function parseInboxFrame(raw: string): ParsedInboxFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(INBOX_SERVER_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as InboxServerFrame) : null;\n}\n\n/**\n * Parse a text frame a client sent on a **channel** socket (C→S).\n *\n * The upstream counterpart of {@link parseChannelFrame}, for a server or test mock\n * receiving the frames a client sends. Same totality contract: `null` on malformed JSON\n * / missing `t` / a known `t` with a bad shape, and an {@link UnknownFrame} passthrough\n * for an unrecognised `t` (§6).\n */\nexport function parseChannelClientFrame(\n raw: string,\n): ParsedChannelClientFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(CHANNEL_CLIENT_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as ChannelClientFrame) : null;\n}\n\n/**\n * Parse a text frame a client sent on an **inbox** socket (C→S).\n *\n * Same contract as {@link parseChannelClientFrame}.\n */\nexport function parseInboxClientFrame(raw: string): ParsedInboxClientFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(INBOX_CLIENT_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as InboxClientFrame) : null;\n}\n\n/**\n * Serialize a frame to a JSON text frame.\n *\n * Primarily for C→S sends. It accepts any frame so that a parsed S→C frame can be\n * re-serialized intact — the round-trip that proves unknown fields survive (§6).\n */\nexport function serializeFrame(frame: AnyFrame): string {\n return JSON.stringify(frame);\n}\n\n// ============================================================\n// Guards\n//\n// These narrow a frame that has already been through parseChannelFrame /\n// parseInboxFrame, which is what makes a `t` check sufficient: the parser has already\n// validated the shape, so narrowing on the discriminator cannot lie.\n// ============================================================\n\n/** True when the parser recognised this channel frame — i.e. it is not an `UnknownFrame`. */\nexport const isKnownChannelFrame = (\n frame: ParsedChannelFrame,\n): frame is ChannelServerFrame => Object.hasOwn(CHANNEL_SERVER_FRAMES, frame.t);\n\n/** True when the parser recognised this inbox frame — i.e. it is not an `UnknownFrame`. */\nexport const isKnownInboxFrame = (\n frame: ParsedInboxFrame,\n): frame is InboxServerFrame => Object.hasOwn(INBOX_SERVER_FRAMES, frame.t);\n\n/** True when the parser recognised this C→S channel frame (not an `UnknownFrame`). */\nexport const isKnownChannelClientFrame = (\n frame: ParsedChannelClientFrame,\n): frame is ChannelClientFrame => Object.hasOwn(CHANNEL_CLIENT_FRAMES, frame.t);\n\n/** True when the parser recognised this C→S inbox frame (not an `UnknownFrame`). */\nexport const isKnownInboxClientFrame = (\n frame: ParsedInboxClientFrame,\n): frame is InboxClientFrame => Object.hasOwn(INBOX_CLIENT_FRAMES, frame.t);\n\n// ── Channel S→C ─────────────────────────────────────────────\n\nexport const isChannelReady = (f: ParsedChannelFrame): f is ChannelReadyFrame =>\n f.t === \"ready\";\nexport const isBatch = (f: ParsedChannelFrame): f is BatchFrame => f.t === \"batch\";\nexport const isRetract = (f: ParsedChannelFrame): f is RetractFrame =>\n f.t === \"retract\";\nexport const isPresence = (f: ParsedChannelFrame): f is PresenceFrame =>\n f.t === \"presence\";\nexport const isActivity = (f: ParsedChannelFrame): f is ActivityFrame =>\n f.t === \"activity\";\nexport const isDirect = (f: ParsedChannelFrame): f is DirectFrame => f.t === \"direct\";\nexport const isReassign = (f: ParsedChannelFrame): f is ReassignFrame =>\n f.t === \"reassign\";\nexport const isError = (f: ParsedChannelFrame): f is ErrorFrame => f.t === \"error\";\nexport const isPong = (f: ParsedChannelFrame): f is PongFrame => f.t === \"pong\";\n\n// ── Inbox S→C ───────────────────────────────────────────────\n\nexport const isInboxReady = (f: ParsedInboxFrame): f is InboxReadyFrame =>\n f.t === \"ready\";\nexport const isInboxEntry = (f: ParsedInboxFrame): f is InboxEntryFrame =>\n f.t === \"entry\";\nexport const isInboxItem = (f: ParsedInboxFrame): f is InboxItemFrame =>\n f.t === \"item\";\nexport const isInboxCounter = (f: ParsedInboxFrame): f is InboxCounterFrame =>\n f.t === \"counter\";\nexport const isInboxPong = (f: ParsedInboxFrame): f is PongFrame => f.t === \"pong\";\n"]}
package/dist/index.d.cts CHANGED
@@ -10,7 +10,8 @@ declare const PROTOCOL_VERSION = 1;
10
10
  * string literals, so a rename is a compile error rather than a silent 4xx.
11
11
  *
12
12
  * - `version` — required on every upgrade; unknown → 426.
13
- * - `token` — the signed JWT. Identifies the user; the apiKey is resolved from it.
13
+ * - `token` — the signed JWT. Identifies the user.
14
+ * - `key` — the publishable apiKey, identifying the app (§1 credential transport).
14
15
  * - `leaf` — opaque reconnect token; echo back what `ready` gave you, unchanged.
15
16
  * - `meta` — initial presence metadata, base64 JSON (standard channels; ≤1KB decoded).
16
17
  * - `last` — highest contiguous seq held, sent on reconnect to request replay (§1.4).
@@ -18,6 +19,7 @@ declare const PROTOCOL_VERSION = 1;
18
19
  declare const UPGRADE_PARAMS: {
19
20
  readonly version: "v";
20
21
  readonly token: "token";
22
+ readonly key: "key";
21
23
  readonly leaf: "leaf";
22
24
  readonly meta: "meta";
23
25
  readonly last: "last";
@@ -190,18 +192,17 @@ type MeInfo = {
190
192
  capabilities: Capabilities;
191
193
  };
192
194
  /**
193
- * A participant as it appears **on the wire** (§1.2 snapshot, §2.1 deltas).
195
+ * A participant as it appears **on the wire** (§1.2 snapshot, §2.1 `joined` deltas).
194
196
  *
195
- * SPEC: provisional. `{ userId, claims }` is what the current fixtures prove the wire
196
- * carries (`channel_ready` presence, and the `presence` frame's `joined`), so it is what
197
- * this type says the type follows recorded evidence, never an anticipated change.
198
- *
199
- * This shape may change in a future version of the package; treat `claims` as unstable
200
- * and do not build against it.
197
+ * `username` is present when the participant has one; `metadata` is the session presence
198
+ * metadata (`?meta=` / a `meta` frame). This carries no `claims` — the token's claim bag is
199
+ * the connected user's own (`me.claims` in `ready`), never another participant's.
201
200
  */
202
201
  type WirePresenceParticipant = {
203
- userId: string;
204
- claims: Record<string, unknown>;
202
+ id: string;
203
+ anon: boolean;
204
+ username?: string;
205
+ metadata?: Record<string, unknown>;
205
206
  };
206
207
  /** Presence snapshot on a standard channel, carried by `ready` (§1.2). */
207
208
  type DetailedPresenceSnapshot = {
@@ -284,15 +285,14 @@ type RetractFrame = {
284
285
  /**
285
286
  * Presence delta on a standard channel (§2.1).
286
287
  *
287
- * SPEC: §2.1 elides the `joined`/`left` element shapes as `[...]`. The fixture proves
288
- * `joined` carries {@link WirePresenceParticipant}; its `left` is empty, so `left`'s
289
- * element shape is assumed symmetric and remains **unverified**.
288
+ * `joined` carries full participants; `left` carries bare participant ids (a departing
289
+ * participant is identified, not re-described).
290
290
  */
291
291
  type DetailedPresenceFrame = {
292
292
  t: "presence";
293
293
  mode: "detailed";
294
294
  joined: WirePresenceParticipant[];
295
- left: WirePresenceParticipant[];
295
+ left: string[];
296
296
  count: number;
297
297
  };
298
298
  /**
package/dist/index.d.ts CHANGED
@@ -10,7 +10,8 @@ declare const PROTOCOL_VERSION = 1;
10
10
  * string literals, so a rename is a compile error rather than a silent 4xx.
11
11
  *
12
12
  * - `version` — required on every upgrade; unknown → 426.
13
- * - `token` — the signed JWT. Identifies the user; the apiKey is resolved from it.
13
+ * - `token` — the signed JWT. Identifies the user.
14
+ * - `key` — the publishable apiKey, identifying the app (§1 credential transport).
14
15
  * - `leaf` — opaque reconnect token; echo back what `ready` gave you, unchanged.
15
16
  * - `meta` — initial presence metadata, base64 JSON (standard channels; ≤1KB decoded).
16
17
  * - `last` — highest contiguous seq held, sent on reconnect to request replay (§1.4).
@@ -18,6 +19,7 @@ declare const PROTOCOL_VERSION = 1;
18
19
  declare const UPGRADE_PARAMS: {
19
20
  readonly version: "v";
20
21
  readonly token: "token";
22
+ readonly key: "key";
21
23
  readonly leaf: "leaf";
22
24
  readonly meta: "meta";
23
25
  readonly last: "last";
@@ -190,18 +192,17 @@ type MeInfo = {
190
192
  capabilities: Capabilities;
191
193
  };
192
194
  /**
193
- * A participant as it appears **on the wire** (§1.2 snapshot, §2.1 deltas).
195
+ * A participant as it appears **on the wire** (§1.2 snapshot, §2.1 `joined` deltas).
194
196
  *
195
- * SPEC: provisional. `{ userId, claims }` is what the current fixtures prove the wire
196
- * carries (`channel_ready` presence, and the `presence` frame's `joined`), so it is what
197
- * this type says the type follows recorded evidence, never an anticipated change.
198
- *
199
- * This shape may change in a future version of the package; treat `claims` as unstable
200
- * and do not build against it.
197
+ * `username` is present when the participant has one; `metadata` is the session presence
198
+ * metadata (`?meta=` / a `meta` frame). This carries no `claims` — the token's claim bag is
199
+ * the connected user's own (`me.claims` in `ready`), never another participant's.
201
200
  */
202
201
  type WirePresenceParticipant = {
203
- userId: string;
204
- claims: Record<string, unknown>;
202
+ id: string;
203
+ anon: boolean;
204
+ username?: string;
205
+ metadata?: Record<string, unknown>;
205
206
  };
206
207
  /** Presence snapshot on a standard channel, carried by `ready` (§1.2). */
207
208
  type DetailedPresenceSnapshot = {
@@ -284,15 +285,14 @@ type RetractFrame = {
284
285
  /**
285
286
  * Presence delta on a standard channel (§2.1).
286
287
  *
287
- * SPEC: §2.1 elides the `joined`/`left` element shapes as `[...]`. The fixture proves
288
- * `joined` carries {@link WirePresenceParticipant}; its `left` is empty, so `left`'s
289
- * element shape is assumed symmetric and remains **unverified**.
288
+ * `joined` carries full participants; `left` carries bare participant ids (a departing
289
+ * participant is identified, not re-described).
290
290
  */
291
291
  type DetailedPresenceFrame = {
292
292
  t: "presence";
293
293
  mode: "detailed";
294
294
  joined: WirePresenceParticipant[];
295
- left: WirePresenceParticipant[];
295
+ left: string[];
296
296
  count: number;
297
297
  };
298
298
  /**
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ var PROTOCOL_VERSION = 1;
3
3
  var UPGRADE_PARAMS = {
4
4
  version: "v",
5
5
  token: "token",
6
+ key: "key",
6
7
  leaf: "leaf",
7
8
  meta: "meta",
8
9
  last: "last"
@@ -38,7 +39,7 @@ var isWireMessage = (v) => isRec(v) && isStr(v["id"]) && (v["seq"] === null || i
38
39
  var isChannelInfo = (v) => isRec(v) && isStr(v["id"]) && (v["mode"] === "standard" || v["mode"] === "broadcast") && opt(v["name"], isStr) && opt(v["meta"], isRec);
39
40
  var isCapabilities = (v) => isRec(v) && opt(v["publish"], isBool) && opt(v["sendDirect"], isBool);
40
41
  var isMeInfo = (v) => isRec(v) && isStr(v["id"]) && isBool(v["anon"]) && isRec(v["claims"]) && isCapabilities(v["capabilities"]);
41
- var isParticipant = (v) => isRec(v) && isStr(v["userId"]) && isRec(v["claims"]);
42
+ var isParticipant = (v) => isRec(v) && isStr(v["id"]) && isBool(v["anon"]) && opt(v["username"], isStr) && opt(v["metadata"], isRec);
42
43
  var isReadyPresence = (v) => {
43
44
  if (!isRec(v)) return false;
44
45
  if (v["mode"] === "detailed") {
@@ -58,7 +59,7 @@ var CHANNEL_SERVER_FRAMES = {
58
59
  retract: (v) => isStr(v["id"]) && isNum(v["seq"]) && opt(v["reason"], isStr),
59
60
  presence: (v) => {
60
61
  if (v["mode"] === "detailed") {
61
- return arrayOf(v["joined"], isParticipant) && arrayOf(v["left"], isParticipant) && isNum(v["count"]);
62
+ return arrayOf(v["joined"], isParticipant) && arrayOf(v["left"], isStr) && isNum(v["count"]);
62
63
  }
63
64
  if (v["mode"] === "aggregate") {
64
65
  return isNum(v["count"]) && Array.isArray(v["recent"]);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/parse.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Protocol version carried by every upgrade as `?v=` (§1.1, §6).\n *\n * An unknown version is refused at the upgrade with HTTP 426 `unsupported_version`.\n * Within v1 the protocol evolves additively only; a breaking change bumps this.\n */\nexport const PROTOCOL_VERSION = 1;\n\n/**\n * Upgrade query-parameter names (§1.1). Build upgrade URLs from these rather than\n * string literals, so a rename is a compile error rather than a silent 4xx.\n *\n * - `version` — required on every upgrade; unknown → 426.\n * - `token` — the signed JWT. Identifies the user; the apiKey is resolved from it.\n * - `leaf` — opaque reconnect token; echo back what `ready` gave you, unchanged.\n * - `meta` — initial presence metadata, base64 JSON (standard channels; ≤1KB decoded).\n * - `last` — highest contiguous seq held, sent on reconnect to request replay (§1.4).\n */\nexport const UPGRADE_PARAMS = {\n version: \"v\",\n token: \"token\",\n leaf: \"leaf\",\n meta: \"meta\",\n last: \"last\",\n} as const;\n\n/**\n * Response header carrying the refusal code on a refused upgrade (§1.1).\n *\n * Duplicates `code` from the body so a client behind a body-eating proxy can still\n * tell why the socket never opened.\n */\nexport const PORTAL_ERROR_HEADER = \"x-portal-error\";\n","/**\n * Why an upgrade was refused (§1.1).\n *\n * Refusals happen at the HTTP upgrade — **the socket never opens**. They are therefore\n * disjoint from {@link PublishErrorCode} (HTTP publish rejections) and from the\n * in-session `error` frame, which both presuppose a working connection.\n */\nexport type RefusalCode =\n | \"invalid_token\"\n | \"token_expired\"\n | \"invalid_api_key\"\n | \"not_member\"\n | \"banned\"\n | \"anonymous_not_allowed\"\n | \"unknown_channel\"\n | \"unsupported_version\"\n | \"channel_at_capacity\";\n\n/**\n * The HTTP status each refusal is delivered with (§1.1 table).\n *\n * The mapping is many-to-one: status alone does not identify the cause, so read\n * `code` from the body (or the `x-portal-error` header) rather than branching on status.\n */\nexport const REFUSAL_STATUS = {\n invalid_token: 401,\n token_expired: 401,\n invalid_api_key: 403,\n not_member: 403,\n banned: 403,\n anonymous_not_allowed: 403,\n unknown_channel: 404,\n unsupported_version: 426,\n channel_at_capacity: 429,\n} as const satisfies Record<RefusalCode, number>;\n\nconst REFUSAL_CODES: ReadonlySet<string> = new Set(Object.keys(REFUSAL_STATUS));\n\n/**\n * Whether a value is a refusal code this version knows (§1.1).\n *\n * A refusal body arriving with an unrecognised code is not a refusal this client can\n * reason about — treat it as an opaque failure rather than coercing it.\n */\nexport const isRefusalCode = (value: unknown): value is RefusalCode =>\n typeof value === \"string\" && REFUSAL_CODES.has(value);\n\n/** Body of a refused upgrade (§1.1). */\nexport type RefusalBody = {\n code: RefusalCode;\n reason?: string;\n /**\n * Seconds to wait before retrying. Documented for `channel_at_capacity` (429) only\n * (§1.1); absent on every other refusal.\n */\n retryAfter?: number;\n};\n\n/**\n * Why an HTTP publish was rejected (§3.1).\n *\n * Deliberately NOT merged into {@link RefusalCode}: these arrive on a live connection\n * in response to `POST /v1/channels/{id}/messages`, and a client reacts to them per\n * send, not per connection.\n *\n * `blocked_by_middleware` carries user-visible copy in `reason`.\n */\nexport type PublishErrorCode =\n | \"not_permitted\"\n | \"blocked_by_middleware\"\n | \"content_too_large\"\n | \"rate_limited\";\n\n/**\n * Body of a rejected publish (§3.1).\n *\n * SPEC: §3.1 specifies the shape as `4xx { code, reason? }` but does not map each code\n * to a status, so no status record is exported here (unlike {@link REFUSAL_STATUS}).\n */\nexport type PublishErrorBody = {\n code: PublishErrorCode;\n reason?: string;\n};\n","import type {\n ActivityFrame,\n BatchFrame,\n ChannelClientFrame,\n ChannelReadyFrame,\n ChannelServerFrame,\n Capabilities,\n ChannelInfo,\n DirectFrame,\n ErrorFrame,\n MeInfo,\n PongFrame,\n PresenceFrame,\n ReadyPresence,\n ReassignFrame,\n RetractFrame,\n WirePresenceParticipant,\n} from \"./channel.js\";\nimport type {\n InboxClientFrame,\n InboxCounterFrame,\n InboxEntryFrame,\n InboxEntryWire,\n InboxItemFrame,\n InboxItemWire,\n InboxReadyFrame,\n InboxServerFrame,\n} from \"./inbox.js\";\nimport type {\n AnyFrame,\n ParsedChannelClientFrame,\n ParsedChannelFrame,\n ParsedInboxClientFrame,\n ParsedInboxFrame,\n UnknownFrame,\n} from \"./frames.js\";\nimport type { Mention, WireMessage, WireSender } from \"./message.js\";\n\n// ============================================================\n// Primitives\n// ============================================================\n\ntype Rec = Record<string, unknown>;\n\nconst isRec = (v: unknown): v is Rec =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n\nconst isStr = (v: unknown): v is string => typeof v === \"string\";\nconst isBool = (v: unknown): v is boolean => typeof v === \"boolean\";\n\n/** JSON has no NaN/Infinity, so any non-finite number means a hand-built object. */\nconst isNum = (v: unknown): v is number =>\n typeof v === \"number\" && Number.isFinite(v);\n\n/** An absent optional field is valid; a present one must match. */\nconst opt = <T>(v: unknown, check: (x: unknown) => x is T): boolean =>\n v === undefined || check(v);\n\nconst arrayOf = <T>(v: unknown, check: (x: unknown) => x is T): v is T[] =>\n Array.isArray(v) && v.every(check);\n\nconst isStrRec = (v: unknown): v is Record<string, string> =>\n isRec(v) && Object.values(v).every(isStr);\n\n// ============================================================\n// Shape validators\n//\n// Each validator checks exactly the fields its type declares required — no more, no\n// less. Unknown fields are ignored and survive untouched (§6), but a frame missing a\n// required field is rejected rather than returned as a type that lies about it.\n// ============================================================\n\nconst isMention = (v: unknown): v is Mention => isRec(v) && isStr(v[\"userId\"]);\n\nconst isWireSender = (v: unknown): v is WireSender =>\n isRec(v) && isStr(v[\"id\"]) && isBool(v[\"anon\"]) && opt(v[\"username\"], isStr);\n\nconst isWireMessage = (v: unknown): v is WireMessage =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n (v[\"seq\"] === null || isNum(v[\"seq\"])) &&\n isStr(v[\"type\"]) &&\n isStr(v[\"kind\"]) &&\n \"content\" in v &&\n isWireSender(v[\"sender\"]) &&\n isNum(v[\"timestamp\"]) &&\n opt(v[\"to\"], isStr) &&\n (v[\"mentions\"] === undefined || arrayOf(v[\"mentions\"], isMention)) &&\n isBool(v[\"retracted\"]) &&\n isBool(v[\"ephemeral\"]);\n\nconst isChannelInfo = (v: unknown): v is ChannelInfo =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n (v[\"mode\"] === \"standard\" || v[\"mode\"] === \"broadcast\") &&\n opt(v[\"name\"], isStr) &&\n opt(v[\"meta\"], isRec);\n\nconst isCapabilities = (v: unknown): v is Capabilities =>\n isRec(v) && opt(v[\"publish\"], isBool) && opt(v[\"sendDirect\"], isBool);\n\nconst isMeInfo = (v: unknown): v is MeInfo =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n isBool(v[\"anon\"]) &&\n isRec(v[\"claims\"]) &&\n isCapabilities(v[\"capabilities\"]);\n\nconst isParticipant = (v: unknown): v is WirePresenceParticipant =>\n isRec(v) && isStr(v[\"userId\"]) && isRec(v[\"claims\"]);\n\nconst isReadyPresence = (v: unknown): v is ReadyPresence => {\n if (!isRec(v)) return false;\n if (v[\"mode\"] === \"detailed\") {\n return arrayOf(v[\"participants\"], isParticipant) && isNum(v[\"count\"]);\n }\n if (v[\"mode\"] === \"aggregate\") {\n return isNum(v[\"count\"]) && (v[\"recent\"] === undefined || Array.isArray(v[\"recent\"]));\n }\n return false;\n};\n\nconst isInboxEntryWire = (v: unknown): v is InboxEntryWire =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n opt(v[\"name\"], isStr) &&\n opt(v[\"meta\"], isRec) &&\n opt(v[\"latest\"], isLatest) &&\n isNum(v[\"unread\"]) &&\n isBool(v[\"muted\"]) &&\n isNum(v[\"at\"]);\n\nconst isLatest = (v: unknown): v is InboxEntryWire[\"latest\"] =>\n isRec(v) &&\n isStr(v[\"text\"]) &&\n isRec(v[\"sender\"]) &&\n isStr((v[\"sender\"] as Rec)[\"id\"]) &&\n isNum(v[\"at\"]);\n\nconst isInboxItemWire = (v: unknown): v is InboxItemWire =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n isStr(v[\"type\"]) &&\n opt(v[\"title\"], isStr) &&\n \"data\" in v &&\n opt(v[\"channelId\"], isStr) &&\n isNum(v[\"at\"]) &&\n isBool(v[\"read\"]);\n\n// ============================================================\n// Frame tables — the single source of truth for \"known `t`\"\n// ============================================================\n\ntype Validator = (v: Rec) => boolean;\n\nconst CHANNEL_SERVER_FRAMES: Record<ChannelServerFrame[\"t\"], Validator> = {\n ready: (v) =>\n isChannelInfo(v[\"channel\"]) &&\n isMeInfo(v[\"me\"]) &&\n isNum(v[\"seq\"]) &&\n isStr(v[\"leaf\"]) &&\n isReadyPresence(v[\"presence\"]) &&\n opt(v[\"watermark\"], isNum) &&\n opt(v[\"ext\"], isRec) &&\n opt(v[\"bindings\"], isStrRec),\n batch: (v) => arrayOf(v[\"msgs\"], isWireMessage),\n retract: (v) => isStr(v[\"id\"]) && isNum(v[\"seq\"]) && opt(v[\"reason\"], isStr),\n presence: (v) => {\n if (v[\"mode\"] === \"detailed\") {\n return (\n arrayOf(v[\"joined\"], isParticipant) &&\n arrayOf(v[\"left\"], isParticipant) &&\n isNum(v[\"count\"])\n );\n }\n if (v[\"mode\"] === \"aggregate\") {\n return isNum(v[\"count\"]) && Array.isArray(v[\"recent\"]);\n }\n return false;\n },\n activity: (v) => isStr(v[\"userId\"]) && isStr(v[\"kind\"]) && isNum(v[\"since\"]),\n direct: (v) => isWireMessage(v[\"msg\"]),\n reassign: (v) => isStr(v[\"leaf\"]),\n error: (v) => isStr(v[\"code\"]) && opt(v[\"reason\"], isStr) && opt(v[\"ref\"], isStr),\n pong: () => true,\n};\n\nconst INBOX_SERVER_FRAMES: Record<InboxServerFrame[\"t\"], Validator> = {\n ready: (v) =>\n arrayOf(v[\"entries\"], isInboxEntryWire) &&\n arrayOf(v[\"items\"], isInboxItemWire) &&\n isNum(v[\"counter\"]),\n entry: (v) => isInboxEntryWire(v[\"entry\"]),\n item: (v) => isInboxItemWire(v[\"item\"]),\n counter: (v) => isNum(v[\"n\"]),\n pong: () => true,\n};\n\n// C→S tables. A server (or the test mock) receives these; the same totality contract\n// applies as for the S→C tables. `ping` and `activity` also appear in S→C sets with\n// different shapes — the families are disjoint, so each is validated against its own\n// direction's table.\nconst CHANNEL_CLIENT_FRAMES: Record<ChannelClientFrame[\"t\"], Validator> = {\n ephemeral: (v) => isStr(v[\"cl\"]) && isStr(v[\"type\"]) && \"content\" in v,\n activity: (v) => isStr(v[\"kind\"]),\n watermark: (v) => isNum(v[\"seq\"]),\n meta: (v) => isRec(v[\"metadata\"]),\n ping: () => true,\n};\n\nconst INBOX_CLIENT_FRAMES: Record<InboxClientFrame[\"t\"], Validator> = {\n read: (v) => isStr(v[\"channelId\"]),\n \"item.read\": (v) => isStr(v[\"id\"]),\n \"read.all\": () => true,\n mute: (v) => isStr(v[\"channelId\"]) && isBool(v[\"muted\"]),\n ping: () => true,\n};\n\nconst validatorFor = <T extends string>(\n table: Record<T, Validator>,\n t: string,\n): Validator | undefined =>\n Object.hasOwn(table, t) ? table[t as T] : undefined;\n\n// ============================================================\n// Parse / serialize\n// ============================================================\n\nconst parseJson = (raw: string): unknown => {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return undefined;\n }\n};\n\n/**\n * A frame-shaped value: an object carrying a string `t`. Anything else is not a frame.\n */\nconst asFrameShape = (raw: string): (Rec & { t: string }) | null => {\n const value = parseJson(raw);\n if (!isRec(value) || !isStr(value[\"t\"])) return null;\n return value as Rec & { t: string };\n};\n\n/**\n * Parse a text frame from a **channel** socket (S→C).\n *\n * Total and non-throwing:\n * - malformed JSON, a non-object, or a missing/non-string `t` → `null`\n * - a known `t` whose shape does not match → `null`\n * - an unknown `t` → the frame is returned intact as an {@link UnknownFrame} (§6),\n * because forward compatibility says ignore it, not lose it\n *\n * Unknown *fields* on known frames are preserved, so `parse` → {@link serializeFrame}\n * round-trips without dropping anything.\n */\nexport function parseChannelFrame(raw: string): ParsedChannelFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(CHANNEL_SERVER_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as ChannelServerFrame) : null;\n}\n\n/**\n * Parse a text frame from an **inbox** socket (S→C).\n *\n * Same contract as {@link parseChannelFrame}. The two families are disjoint: an inbox\n * `ready` and a channel `ready` share a `t` but not a shape, so a frame must be parsed\n * with the function matching the socket it arrived on.\n */\nexport function parseInboxFrame(raw: string): ParsedInboxFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(INBOX_SERVER_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as InboxServerFrame) : null;\n}\n\n/**\n * Parse a text frame a client sent on a **channel** socket (C→S).\n *\n * The upstream counterpart of {@link parseChannelFrame}, for a server or test mock\n * receiving the frames a client sends. Same totality contract: `null` on malformed JSON\n * / missing `t` / a known `t` with a bad shape, and an {@link UnknownFrame} passthrough\n * for an unrecognised `t` (§6).\n */\nexport function parseChannelClientFrame(\n raw: string,\n): ParsedChannelClientFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(CHANNEL_CLIENT_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as ChannelClientFrame) : null;\n}\n\n/**\n * Parse a text frame a client sent on an **inbox** socket (C→S).\n *\n * Same contract as {@link parseChannelClientFrame}.\n */\nexport function parseInboxClientFrame(raw: string): ParsedInboxClientFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(INBOX_CLIENT_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as InboxClientFrame) : null;\n}\n\n/**\n * Serialize a frame to a JSON text frame.\n *\n * Primarily for C→S sends. It accepts any frame so that a parsed S→C frame can be\n * re-serialized intact — the round-trip that proves unknown fields survive (§6).\n */\nexport function serializeFrame(frame: AnyFrame): string {\n return JSON.stringify(frame);\n}\n\n// ============================================================\n// Guards\n//\n// These narrow a frame that has already been through parseChannelFrame /\n// parseInboxFrame, which is what makes a `t` check sufficient: the parser has already\n// validated the shape, so narrowing on the discriminator cannot lie.\n// ============================================================\n\n/** True when the parser recognised this channel frame — i.e. it is not an `UnknownFrame`. */\nexport const isKnownChannelFrame = (\n frame: ParsedChannelFrame,\n): frame is ChannelServerFrame => Object.hasOwn(CHANNEL_SERVER_FRAMES, frame.t);\n\n/** True when the parser recognised this inbox frame — i.e. it is not an `UnknownFrame`. */\nexport const isKnownInboxFrame = (\n frame: ParsedInboxFrame,\n): frame is InboxServerFrame => Object.hasOwn(INBOX_SERVER_FRAMES, frame.t);\n\n/** True when the parser recognised this C→S channel frame (not an `UnknownFrame`). */\nexport const isKnownChannelClientFrame = (\n frame: ParsedChannelClientFrame,\n): frame is ChannelClientFrame => Object.hasOwn(CHANNEL_CLIENT_FRAMES, frame.t);\n\n/** True when the parser recognised this C→S inbox frame (not an `UnknownFrame`). */\nexport const isKnownInboxClientFrame = (\n frame: ParsedInboxClientFrame,\n): frame is InboxClientFrame => Object.hasOwn(INBOX_CLIENT_FRAMES, frame.t);\n\n// ── Channel S→C ─────────────────────────────────────────────\n\nexport const isChannelReady = (f: ParsedChannelFrame): f is ChannelReadyFrame =>\n f.t === \"ready\";\nexport const isBatch = (f: ParsedChannelFrame): f is BatchFrame => f.t === \"batch\";\nexport const isRetract = (f: ParsedChannelFrame): f is RetractFrame =>\n f.t === \"retract\";\nexport const isPresence = (f: ParsedChannelFrame): f is PresenceFrame =>\n f.t === \"presence\";\nexport const isActivity = (f: ParsedChannelFrame): f is ActivityFrame =>\n f.t === \"activity\";\nexport const isDirect = (f: ParsedChannelFrame): f is DirectFrame => f.t === \"direct\";\nexport const isReassign = (f: ParsedChannelFrame): f is ReassignFrame =>\n f.t === \"reassign\";\nexport const isError = (f: ParsedChannelFrame): f is ErrorFrame => f.t === \"error\";\nexport const isPong = (f: ParsedChannelFrame): f is PongFrame => f.t === \"pong\";\n\n// ── Inbox S→C ───────────────────────────────────────────────\n\nexport const isInboxReady = (f: ParsedInboxFrame): f is InboxReadyFrame =>\n f.t === \"ready\";\nexport const isInboxEntry = (f: ParsedInboxFrame): f is InboxEntryFrame =>\n f.t === \"entry\";\nexport const isInboxItem = (f: ParsedInboxFrame): f is InboxItemFrame =>\n f.t === \"item\";\nexport const isInboxCounter = (f: ParsedInboxFrame): f is InboxCounterFrame =>\n f.t === \"counter\";\nexport const isInboxPong = (f: ParsedInboxFrame): f is PongFrame => f.t === \"pong\";\n"]}
1
+ {"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/parse.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Protocol version carried by every upgrade as `?v=` (§1.1, §6).\n *\n * An unknown version is refused at the upgrade with HTTP 426 `unsupported_version`.\n * Within v1 the protocol evolves additively only; a breaking change bumps this.\n */\nexport const PROTOCOL_VERSION = 1;\n\n/**\n * Upgrade query-parameter names (§1.1). Build upgrade URLs from these rather than\n * string literals, so a rename is a compile error rather than a silent 4xx.\n *\n * - `version` — required on every upgrade; unknown → 426.\n * - `token` — the signed JWT. Identifies the user.\n * - `key` — the publishable apiKey, identifying the app (§1 credential transport).\n * - `leaf` — opaque reconnect token; echo back what `ready` gave you, unchanged.\n * - `meta` — initial presence metadata, base64 JSON (standard channels; ≤1KB decoded).\n * - `last` — highest contiguous seq held, sent on reconnect to request replay (§1.4).\n */\nexport const UPGRADE_PARAMS = {\n version: \"v\",\n token: \"token\",\n key: \"key\",\n leaf: \"leaf\",\n meta: \"meta\",\n last: \"last\",\n} as const;\n\n/**\n * Response header carrying the refusal code on a refused upgrade (§1.1).\n *\n * Duplicates `code` from the body so a client behind a body-eating proxy can still\n * tell why the socket never opened.\n */\nexport const PORTAL_ERROR_HEADER = \"x-portal-error\";\n","/**\n * Why an upgrade was refused (§1.1).\n *\n * Refusals happen at the HTTP upgrade — **the socket never opens**. They are therefore\n * disjoint from {@link PublishErrorCode} (HTTP publish rejections) and from the\n * in-session `error` frame, which both presuppose a working connection.\n */\nexport type RefusalCode =\n | \"invalid_token\"\n | \"token_expired\"\n | \"invalid_api_key\"\n | \"not_member\"\n | \"banned\"\n | \"anonymous_not_allowed\"\n | \"unknown_channel\"\n | \"unsupported_version\"\n | \"channel_at_capacity\";\n\n/**\n * The HTTP status each refusal is delivered with (§1.1 table).\n *\n * The mapping is many-to-one: status alone does not identify the cause, so read\n * `code` from the body (or the `x-portal-error` header) rather than branching on status.\n */\nexport const REFUSAL_STATUS = {\n invalid_token: 401,\n token_expired: 401,\n invalid_api_key: 403,\n not_member: 403,\n banned: 403,\n anonymous_not_allowed: 403,\n unknown_channel: 404,\n unsupported_version: 426,\n channel_at_capacity: 429,\n} as const satisfies Record<RefusalCode, number>;\n\nconst REFUSAL_CODES: ReadonlySet<string> = new Set(Object.keys(REFUSAL_STATUS));\n\n/**\n * Whether a value is a refusal code this version knows (§1.1).\n *\n * A refusal body arriving with an unrecognised code is not a refusal this client can\n * reason about — treat it as an opaque failure rather than coercing it.\n */\nexport const isRefusalCode = (value: unknown): value is RefusalCode =>\n typeof value === \"string\" && REFUSAL_CODES.has(value);\n\n/** Body of a refused upgrade (§1.1). */\nexport type RefusalBody = {\n code: RefusalCode;\n reason?: string;\n /**\n * Seconds to wait before retrying. Documented for `channel_at_capacity` (429) only\n * (§1.1); absent on every other refusal.\n */\n retryAfter?: number;\n};\n\n/**\n * Why an HTTP publish was rejected (§3.1).\n *\n * Deliberately NOT merged into {@link RefusalCode}: these arrive on a live connection\n * in response to `POST /v1/channels/{id}/messages`, and a client reacts to them per\n * send, not per connection.\n *\n * `blocked_by_middleware` carries user-visible copy in `reason`.\n */\nexport type PublishErrorCode =\n | \"not_permitted\"\n | \"blocked_by_middleware\"\n | \"content_too_large\"\n | \"rate_limited\";\n\n/**\n * Body of a rejected publish (§3.1).\n *\n * SPEC: §3.1 specifies the shape as `4xx { code, reason? }` but does not map each code\n * to a status, so no status record is exported here (unlike {@link REFUSAL_STATUS}).\n */\nexport type PublishErrorBody = {\n code: PublishErrorCode;\n reason?: string;\n};\n","import type {\n ActivityFrame,\n BatchFrame,\n ChannelClientFrame,\n ChannelReadyFrame,\n ChannelServerFrame,\n Capabilities,\n ChannelInfo,\n DirectFrame,\n ErrorFrame,\n MeInfo,\n PongFrame,\n PresenceFrame,\n ReadyPresence,\n ReassignFrame,\n RetractFrame,\n WirePresenceParticipant,\n} from \"./channel.js\";\nimport type {\n InboxClientFrame,\n InboxCounterFrame,\n InboxEntryFrame,\n InboxEntryWire,\n InboxItemFrame,\n InboxItemWire,\n InboxReadyFrame,\n InboxServerFrame,\n} from \"./inbox.js\";\nimport type {\n AnyFrame,\n ParsedChannelClientFrame,\n ParsedChannelFrame,\n ParsedInboxClientFrame,\n ParsedInboxFrame,\n UnknownFrame,\n} from \"./frames.js\";\nimport type { Mention, WireMessage, WireSender } from \"./message.js\";\n\n// ============================================================\n// Primitives\n// ============================================================\n\ntype Rec = Record<string, unknown>;\n\nconst isRec = (v: unknown): v is Rec =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n\nconst isStr = (v: unknown): v is string => typeof v === \"string\";\nconst isBool = (v: unknown): v is boolean => typeof v === \"boolean\";\n\n/** JSON has no NaN/Infinity, so any non-finite number means a hand-built object. */\nconst isNum = (v: unknown): v is number =>\n typeof v === \"number\" && Number.isFinite(v);\n\n/** An absent optional field is valid; a present one must match. */\nconst opt = <T>(v: unknown, check: (x: unknown) => x is T): boolean =>\n v === undefined || check(v);\n\nconst arrayOf = <T>(v: unknown, check: (x: unknown) => x is T): v is T[] =>\n Array.isArray(v) && v.every(check);\n\nconst isStrRec = (v: unknown): v is Record<string, string> =>\n isRec(v) && Object.values(v).every(isStr);\n\n// ============================================================\n// Shape validators\n//\n// Each validator checks exactly the fields its type declares required — no more, no\n// less. Unknown fields are ignored and survive untouched (§6), but a frame missing a\n// required field is rejected rather than returned as a type that lies about it.\n// ============================================================\n\nconst isMention = (v: unknown): v is Mention => isRec(v) && isStr(v[\"userId\"]);\n\nconst isWireSender = (v: unknown): v is WireSender =>\n isRec(v) && isStr(v[\"id\"]) && isBool(v[\"anon\"]) && opt(v[\"username\"], isStr);\n\nconst isWireMessage = (v: unknown): v is WireMessage =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n (v[\"seq\"] === null || isNum(v[\"seq\"])) &&\n isStr(v[\"type\"]) &&\n isStr(v[\"kind\"]) &&\n \"content\" in v &&\n isWireSender(v[\"sender\"]) &&\n isNum(v[\"timestamp\"]) &&\n opt(v[\"to\"], isStr) &&\n (v[\"mentions\"] === undefined || arrayOf(v[\"mentions\"], isMention)) &&\n isBool(v[\"retracted\"]) &&\n isBool(v[\"ephemeral\"]);\n\nconst isChannelInfo = (v: unknown): v is ChannelInfo =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n (v[\"mode\"] === \"standard\" || v[\"mode\"] === \"broadcast\") &&\n opt(v[\"name\"], isStr) &&\n opt(v[\"meta\"], isRec);\n\nconst isCapabilities = (v: unknown): v is Capabilities =>\n isRec(v) && opt(v[\"publish\"], isBool) && opt(v[\"sendDirect\"], isBool);\n\nconst isMeInfo = (v: unknown): v is MeInfo =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n isBool(v[\"anon\"]) &&\n isRec(v[\"claims\"]) &&\n isCapabilities(v[\"capabilities\"]);\n\nconst isParticipant = (v: unknown): v is WirePresenceParticipant =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n isBool(v[\"anon\"]) &&\n opt(v[\"username\"], isStr) &&\n opt(v[\"metadata\"], isRec);\n\nconst isReadyPresence = (v: unknown): v is ReadyPresence => {\n if (!isRec(v)) return false;\n if (v[\"mode\"] === \"detailed\") {\n return arrayOf(v[\"participants\"], isParticipant) && isNum(v[\"count\"]);\n }\n if (v[\"mode\"] === \"aggregate\") {\n return isNum(v[\"count\"]) && (v[\"recent\"] === undefined || Array.isArray(v[\"recent\"]));\n }\n return false;\n};\n\nconst isInboxEntryWire = (v: unknown): v is InboxEntryWire =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n opt(v[\"name\"], isStr) &&\n opt(v[\"meta\"], isRec) &&\n opt(v[\"latest\"], isLatest) &&\n isNum(v[\"unread\"]) &&\n isBool(v[\"muted\"]) &&\n isNum(v[\"at\"]);\n\nconst isLatest = (v: unknown): v is InboxEntryWire[\"latest\"] =>\n isRec(v) &&\n isStr(v[\"text\"]) &&\n isRec(v[\"sender\"]) &&\n isStr((v[\"sender\"] as Rec)[\"id\"]) &&\n isNum(v[\"at\"]);\n\nconst isInboxItemWire = (v: unknown): v is InboxItemWire =>\n isRec(v) &&\n isStr(v[\"id\"]) &&\n isStr(v[\"type\"]) &&\n opt(v[\"title\"], isStr) &&\n \"data\" in v &&\n opt(v[\"channelId\"], isStr) &&\n isNum(v[\"at\"]) &&\n isBool(v[\"read\"]);\n\n// ============================================================\n// Frame tables — the single source of truth for \"known `t`\"\n// ============================================================\n\ntype Validator = (v: Rec) => boolean;\n\nconst CHANNEL_SERVER_FRAMES: Record<ChannelServerFrame[\"t\"], Validator> = {\n ready: (v) =>\n isChannelInfo(v[\"channel\"]) &&\n isMeInfo(v[\"me\"]) &&\n isNum(v[\"seq\"]) &&\n isStr(v[\"leaf\"]) &&\n isReadyPresence(v[\"presence\"]) &&\n opt(v[\"watermark\"], isNum) &&\n opt(v[\"ext\"], isRec) &&\n opt(v[\"bindings\"], isStrRec),\n batch: (v) => arrayOf(v[\"msgs\"], isWireMessage),\n retract: (v) => isStr(v[\"id\"]) && isNum(v[\"seq\"]) && opt(v[\"reason\"], isStr),\n presence: (v) => {\n if (v[\"mode\"] === \"detailed\") {\n return (\n arrayOf(v[\"joined\"], isParticipant) &&\n arrayOf(v[\"left\"], isStr) &&\n isNum(v[\"count\"])\n );\n }\n if (v[\"mode\"] === \"aggregate\") {\n return isNum(v[\"count\"]) && Array.isArray(v[\"recent\"]);\n }\n return false;\n },\n activity: (v) => isStr(v[\"userId\"]) && isStr(v[\"kind\"]) && isNum(v[\"since\"]),\n direct: (v) => isWireMessage(v[\"msg\"]),\n reassign: (v) => isStr(v[\"leaf\"]),\n error: (v) => isStr(v[\"code\"]) && opt(v[\"reason\"], isStr) && opt(v[\"ref\"], isStr),\n pong: () => true,\n};\n\nconst INBOX_SERVER_FRAMES: Record<InboxServerFrame[\"t\"], Validator> = {\n ready: (v) =>\n arrayOf(v[\"entries\"], isInboxEntryWire) &&\n arrayOf(v[\"items\"], isInboxItemWire) &&\n isNum(v[\"counter\"]),\n entry: (v) => isInboxEntryWire(v[\"entry\"]),\n item: (v) => isInboxItemWire(v[\"item\"]),\n counter: (v) => isNum(v[\"n\"]),\n pong: () => true,\n};\n\n// C→S tables. A server (or the test mock) receives these; the same totality contract\n// applies as for the S→C tables. `ping` and `activity` also appear in S→C sets with\n// different shapes — the families are disjoint, so each is validated against its own\n// direction's table.\nconst CHANNEL_CLIENT_FRAMES: Record<ChannelClientFrame[\"t\"], Validator> = {\n ephemeral: (v) => isStr(v[\"cl\"]) && isStr(v[\"type\"]) && \"content\" in v,\n activity: (v) => isStr(v[\"kind\"]),\n watermark: (v) => isNum(v[\"seq\"]),\n meta: (v) => isRec(v[\"metadata\"]),\n ping: () => true,\n};\n\nconst INBOX_CLIENT_FRAMES: Record<InboxClientFrame[\"t\"], Validator> = {\n read: (v) => isStr(v[\"channelId\"]),\n \"item.read\": (v) => isStr(v[\"id\"]),\n \"read.all\": () => true,\n mute: (v) => isStr(v[\"channelId\"]) && isBool(v[\"muted\"]),\n ping: () => true,\n};\n\nconst validatorFor = <T extends string>(\n table: Record<T, Validator>,\n t: string,\n): Validator | undefined =>\n Object.hasOwn(table, t) ? table[t as T] : undefined;\n\n// ============================================================\n// Parse / serialize\n// ============================================================\n\nconst parseJson = (raw: string): unknown => {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return undefined;\n }\n};\n\n/**\n * A frame-shaped value: an object carrying a string `t`. Anything else is not a frame.\n */\nconst asFrameShape = (raw: string): (Rec & { t: string }) | null => {\n const value = parseJson(raw);\n if (!isRec(value) || !isStr(value[\"t\"])) return null;\n return value as Rec & { t: string };\n};\n\n/**\n * Parse a text frame from a **channel** socket (S→C).\n *\n * Total and non-throwing:\n * - malformed JSON, a non-object, or a missing/non-string `t` → `null`\n * - a known `t` whose shape does not match → `null`\n * - an unknown `t` → the frame is returned intact as an {@link UnknownFrame} (§6),\n * because forward compatibility says ignore it, not lose it\n *\n * Unknown *fields* on known frames are preserved, so `parse` → {@link serializeFrame}\n * round-trips without dropping anything.\n */\nexport function parseChannelFrame(raw: string): ParsedChannelFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(CHANNEL_SERVER_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as ChannelServerFrame) : null;\n}\n\n/**\n * Parse a text frame from an **inbox** socket (S→C).\n *\n * Same contract as {@link parseChannelFrame}. The two families are disjoint: an inbox\n * `ready` and a channel `ready` share a `t` but not a shape, so a frame must be parsed\n * with the function matching the socket it arrived on.\n */\nexport function parseInboxFrame(raw: string): ParsedInboxFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(INBOX_SERVER_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as InboxServerFrame) : null;\n}\n\n/**\n * Parse a text frame a client sent on a **channel** socket (C→S).\n *\n * The upstream counterpart of {@link parseChannelFrame}, for a server or test mock\n * receiving the frames a client sends. Same totality contract: `null` on malformed JSON\n * / missing `t` / a known `t` with a bad shape, and an {@link UnknownFrame} passthrough\n * for an unrecognised `t` (§6).\n */\nexport function parseChannelClientFrame(\n raw: string,\n): ParsedChannelClientFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(CHANNEL_CLIENT_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as ChannelClientFrame) : null;\n}\n\n/**\n * Parse a text frame a client sent on an **inbox** socket (C→S).\n *\n * Same contract as {@link parseChannelClientFrame}.\n */\nexport function parseInboxClientFrame(raw: string): ParsedInboxClientFrame | null {\n const frame = asFrameShape(raw);\n if (frame === null) return null;\n\n const validate = validatorFor(INBOX_CLIENT_FRAMES, frame.t);\n if (validate === undefined) return frame as UnknownFrame;\n\n return validate(frame) ? (frame as InboxClientFrame) : null;\n}\n\n/**\n * Serialize a frame to a JSON text frame.\n *\n * Primarily for C→S sends. It accepts any frame so that a parsed S→C frame can be\n * re-serialized intact — the round-trip that proves unknown fields survive (§6).\n */\nexport function serializeFrame(frame: AnyFrame): string {\n return JSON.stringify(frame);\n}\n\n// ============================================================\n// Guards\n//\n// These narrow a frame that has already been through parseChannelFrame /\n// parseInboxFrame, which is what makes a `t` check sufficient: the parser has already\n// validated the shape, so narrowing on the discriminator cannot lie.\n// ============================================================\n\n/** True when the parser recognised this channel frame — i.e. it is not an `UnknownFrame`. */\nexport const isKnownChannelFrame = (\n frame: ParsedChannelFrame,\n): frame is ChannelServerFrame => Object.hasOwn(CHANNEL_SERVER_FRAMES, frame.t);\n\n/** True when the parser recognised this inbox frame — i.e. it is not an `UnknownFrame`. */\nexport const isKnownInboxFrame = (\n frame: ParsedInboxFrame,\n): frame is InboxServerFrame => Object.hasOwn(INBOX_SERVER_FRAMES, frame.t);\n\n/** True when the parser recognised this C→S channel frame (not an `UnknownFrame`). */\nexport const isKnownChannelClientFrame = (\n frame: ParsedChannelClientFrame,\n): frame is ChannelClientFrame => Object.hasOwn(CHANNEL_CLIENT_FRAMES, frame.t);\n\n/** True when the parser recognised this C→S inbox frame (not an `UnknownFrame`). */\nexport const isKnownInboxClientFrame = (\n frame: ParsedInboxClientFrame,\n): frame is InboxClientFrame => Object.hasOwn(INBOX_CLIENT_FRAMES, frame.t);\n\n// ── Channel S→C ─────────────────────────────────────────────\n\nexport const isChannelReady = (f: ParsedChannelFrame): f is ChannelReadyFrame =>\n f.t === \"ready\";\nexport const isBatch = (f: ParsedChannelFrame): f is BatchFrame => f.t === \"batch\";\nexport const isRetract = (f: ParsedChannelFrame): f is RetractFrame =>\n f.t === \"retract\";\nexport const isPresence = (f: ParsedChannelFrame): f is PresenceFrame =>\n f.t === \"presence\";\nexport const isActivity = (f: ParsedChannelFrame): f is ActivityFrame =>\n f.t === \"activity\";\nexport const isDirect = (f: ParsedChannelFrame): f is DirectFrame => f.t === \"direct\";\nexport const isReassign = (f: ParsedChannelFrame): f is ReassignFrame =>\n f.t === \"reassign\";\nexport const isError = (f: ParsedChannelFrame): f is ErrorFrame => f.t === \"error\";\nexport const isPong = (f: ParsedChannelFrame): f is PongFrame => f.t === \"pong\";\n\n// ── Inbox S→C ───────────────────────────────────────────────\n\nexport const isInboxReady = (f: ParsedInboxFrame): f is InboxReadyFrame =>\n f.t === \"ready\";\nexport const isInboxEntry = (f: ParsedInboxFrame): f is InboxEntryFrame =>\n f.t === \"entry\";\nexport const isInboxItem = (f: ParsedInboxFrame): f is InboxItemFrame =>\n f.t === \"item\";\nexport const isInboxCounter = (f: ParsedInboxFrame): f is InboxCounterFrame =>\n f.t === \"counter\";\nexport const isInboxPong = (f: ParsedInboxFrame): f is PongFrame => f.t === \"pong\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portalsdk/wire-protocol",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Wire format types and codecs for the Portal protocol.",
5
5
  "license": "MIT",
6
6
  "type": "module",