import { describe, expect, it } from "bun:test"; import { safeParseChatRealtimeConnectMessage, safeParseChatRealtimeServerEvent, serializeChatRealtimeConnectMessage, serializeChatRealtimeServerEvent, } from "./chat-realtime.ts"; describe("chat realtime protocol", () => { it("builds and parses a versioned JWT registration message", () => { const message = serializeChatRealtimeConnectMessage({ registrationSequence: 1, token: "jwt_token", }); expect(JSON.parse(message)).toEqual({ registrationSequence: 1, type: "chat.connect", version: 1, token: "jwt_token", }); expect(safeParseChatRealtimeConnectMessage(message)).toEqual({ ok: true, value: { registrationSequence: 1, token: "jwt_token", type: "chat.connect", version: 1, }, }); }); it("rejects malformed registration messages and invalid JWT fields", () => { expect(safeParseChatRealtimeConnectMessage("not json")).toEqual({ ok: false, reason: "invalid_json", registrationSequence: null, }); expect(safeParseChatRealtimeConnectMessage(null)).toEqual({ ok: false, reason: "missing_body", registrationSequence: null, }); expect( safeParseChatRealtimeConnectMessage( JSON.stringify({ type: "chat.other", version: 1, token: "jwt" }), ), ).toEqual({ ok: false, reason: "ignored_message_type", registrationSequence: null, }); expect( safeParseChatRealtimeConnectMessage( JSON.stringify({ registrationSequence: 1, type: "chat.connect", version: 2, token: "jwt", }), ), ).toEqual({ ok: false, reason: "unsupported_version", registrationSequence: 1, }); expect( safeParseChatRealtimeConnectMessage( JSON.stringify({ registrationSequence: 1, type: "chat.connect", version: 1, token: " ", }), ), ).toEqual({ ok: false, reason: "invalid_token", registrationSequence: 1, }); expect( safeParseChatRealtimeConnectMessage( JSON.stringify({ registrationSequence: 1, type: "chat.connect", version: 1, token: "x".repeat(16_385), }), ), ).toEqual({ ok: false, reason: "invalid_token", registrationSequence: 1, }); expect( safeParseChatRealtimeConnectMessage( JSON.stringify({ registrationSequence: 0, type: "chat.connect", version: 1, token: "jwt", }), ), ).toEqual({ ok: false, reason: "invalid_registration_sequence", registrationSequence: null, }); expect(() => serializeChatRealtimeConnectMessage({ registrationSequence: 1, token: "\t\n", }), ).toThrow( "token must be a trimmed non-empty string of at most 16384 characters.", ); expect(() => serializeChatRealtimeConnectMessage({ registrationSequence: 1, token: "x".repeat(16_385), }), ).toThrow( "token must be a trimmed non-empty string of at most 16384 characters.", ); expect(() => serializeChatRealtimeConnectMessage({ registrationSequence: 0, token: "jwt", }), ).toThrow("registrationSequence must be a positive safe integer."); expect(() => serializeChatRealtimeConnectMessage({ registrationSequence: 1, token: " jwt", }), ).toThrow( "token must be a trimmed non-empty string of at most 16384 characters.", ); }); it("validates server events before serialization", () => { expect( JSON.parse( serializeChatRealtimeServerEvent({ audienceRevision: "revision_123", registrationExpiresAt: "2026-06-21T01:00:00.000Z", registrationExpiresInMs: 900_000, registrationSequence: 1, type: "chat.connected", version: 1, }), ), ).toEqual({ audienceRevision: "revision_123", registrationExpiresAt: "2026-06-21T01:00:00.000Z", registrationExpiresInMs: 900_000, registrationSequence: 1, type: "chat.connected", version: 1, }); expect(() => serializeChatRealtimeServerEvent({ audienceRevision: "", type: "conversations.sync", version: 1, }), ).toThrow("event must match the chat realtime server protocol."); }); it("parses every supported server event", () => { expect( safeParseChatRealtimeServerEvent( JSON.stringify({ audienceRevision: "revision_123", registrationExpiresAt: "2026-06-21T01:00:00.000Z", registrationExpiresInMs: 900_000, registrationSequence: 1, type: "chat.connected", version: 1, }), ), ).toEqual({ ok: true, value: { audienceRevision: "revision_123", registrationExpiresAt: "2026-06-21T01:00:00.000Z", registrationExpiresInMs: 900_000, registrationSequence: 1, type: "chat.connected", version: 1, }, }); for (const [code, retryable] of [ ["identity_unavailable", true], ["invalid_auth", false], ["invalid_request", false], ["service_unavailable", true], ["superseded", false], ] as const) { expect( safeParseChatRealtimeServerEvent( JSON.stringify({ code, registrationSequence: 1, retryable, type: "chat.rejected", version: 1, }), ), ).toEqual({ ok: true, value: { code, registrationSequence: 1, retryable, type: "chat.rejected", version: 1, }, }); } expect( safeParseChatRealtimeServerEvent( JSON.stringify({ code: "invalid_request", registrationSequence: null, retryable: false, type: "chat.rejected", version: 1, }), ), ).toEqual({ ok: false, reason: "invalid_rejection" }); expect( safeParseChatRealtimeServerEvent( JSON.stringify({ audienceRevision: "revision_123", type: "conversations.sync", version: 1, }), ), ).toEqual({ ok: true, value: { audienceRevision: "revision_123", type: "conversations.sync", version: 1, }, }); }); it("does not project registration fields from sync invalidations", () => { const result = safeParseChatRealtimeServerEvent( JSON.stringify({ audienceRevision: "should-not-be-applied", registrationExpiresAt: "2026-06-21T01:00:00.000Z", registrationSequence: 9, type: "conversations.sync", version: 1, }), ); expect(result).toEqual({ ok: true, value: { audienceRevision: "should-not-be-applied", type: "conversations.sync", version: 1, }, }); }); it("rejects malformed server events, versions, types, and fields", () => { expect(safeParseChatRealtimeServerEvent("not json")).toEqual({ ok: false, reason: "invalid_json", }); expect(safeParseChatRealtimeServerEvent("[]")).toEqual({ ok: false, reason: "invalid_message", }); expect(safeParseChatRealtimeServerEvent(new Uint8Array())).toEqual({ ok: false, reason: "unsupported_data", }); expect( safeParseChatRealtimeServerEvent( JSON.stringify({ type: "conversations.sync", version: 2 }), ), ).toEqual({ ok: false, reason: "unsupported_version" }); expect( safeParseChatRealtimeServerEvent( JSON.stringify({ type: "chat.unknown", version: 1 }), ), ).toEqual({ ok: false, reason: "ignored_message_type" }); for (const audienceRevision of ["", " ", 42, "x".repeat(256)]) { expect( safeParseChatRealtimeServerEvent( JSON.stringify({ audienceRevision, registrationExpiresAt: "2026-06-21T01:00:00.000Z", registrationExpiresInMs: 900_000, registrationSequence: 1, type: "chat.connected", version: 1, }), ), ).toEqual({ ok: false, reason: "invalid_audience_revision" }); } expect( safeParseChatRealtimeServerEvent( JSON.stringify({ audienceRevision: "revision_123", registrationExpiresAt: "2026-06-21T01:00:00Z", registrationExpiresInMs: 900_000, registrationSequence: 1, type: "chat.connected", version: 1, }), ), ).toEqual({ ok: false, reason: "invalid_registration_expiration" }); expect( safeParseChatRealtimeServerEvent( JSON.stringify({ audienceRevision: "revision_123", registrationExpiresAt: "2026-06-21T01:00:00.000Z", registrationExpiresInMs: 900_000, registrationSequence: 0, type: "chat.connected", version: 1, }), ), ).toEqual({ ok: false, reason: "invalid_registration_sequence" }); for (const registrationExpiresInMs of [-1, 0, 1.5]) { expect( safeParseChatRealtimeServerEvent( JSON.stringify({ audienceRevision: "revision_123", registrationExpiresAt: "2026-06-21T01:00:00.000Z", registrationExpiresInMs, registrationSequence: 1, type: "chat.connected", version: 1, }), ), ).toEqual({ ok: false, reason: "invalid_registration_expiration" }); } for (const event of [ { code: "unknown", registrationSequence: 1, retryable: true }, { code: "invalid_auth", registrationSequence: 1, retryable: "false" }, { code: "invalid_auth", registrationSequence: 1, retryable: true }, { code: "service_unavailable", registrationSequence: 1, retryable: false, }, { code: "invalid_auth", registrationSequence: 0, retryable: false }, { code: "invalid_auth", registrationSequence: null, retryable: false }, { code: "invalid_auth", registrationSequence: 1 }, ]) { expect( safeParseChatRealtimeServerEvent( JSON.stringify({ ...event, type: "chat.rejected", version: 1 }), ), ).toEqual({ ok: false, reason: "invalid_rejection" }); } }); });