fix: address presentation sync review findings

This commit is contained in:
lda
2026-07-14 11:42:14 +07:00 Verified
parent e42561a117
commit a16c652884
4 changed files with 98 additions and 0 deletions
@@ -6,6 +6,7 @@ export {
decodeCreateSessionRequest,
decodeJoinSessionRequest,
decodeServerSyncMessage,
decodeSessionGrant,
isCanonicalPresentationHash,
normalizeJoinCode,
} from "./protocol.js";
@@ -7,6 +7,7 @@ import {
decodeCreateSessionRequest,
decodeJoinSessionRequest,
decodeServerSyncMessage,
decodeSessionGrant,
isCanonicalPresentationHash,
normalizeJoinCode,
} from "./protocol.js";
@@ -237,4 +238,68 @@ describe("presentation sync protocol", () => {
error: "invalid_message",
});
});
it("decodes a valid session grant", () => {
const grant = {
sessionId: "session-1",
code: "ABCD7X",
connectionToken: "token-1",
websocketPath: "/api/presentation-sync/ws",
snapshot: { hash: "#scene/thesis/title", revision: 0 },
};
expect(decodeSessionGrant(JSON.stringify(grant))).toEqual({
ok: true,
value: grant,
});
});
it.each([
{
snapshot: { hash: "#unknown/title", revision: 0 },
},
{
snapshot: { hash: "#scene/thesis/title", revision: -1 },
},
{
connectionToken: "",
},
{
connectionToken: "x".repeat(129),
},
{
websocketPath: "/api/presentation-sync/other",
},
])("rejects invalid session grant field %#", (override) => {
const grant = {
sessionId: "session-1",
code: "ABCD7X",
connectionToken: "token-1",
websocketPath: "/api/presentation-sync/ws",
snapshot: { hash: "#scene/thesis/title", revision: 0 },
...override,
};
expect(decodeSessionGrant(JSON.stringify(grant))).toEqual({
ok: false,
error: "invalid_message",
});
});
it("returns stable errors for malformed session grants", () => {
expect(decodeSessionGrant("not json")).toEqual({
ok: false,
error: "invalid_json",
});
expect(
decodeSessionGrant(
JSON.stringify({
sessionId: "session-1",
code: "ABCD7X",
connectionToken: "token-1",
websocketPath: "/api/presentation-sync/ws",
}),
),
).toEqual({ ok: false, error: "invalid_message" });
});
});
@@ -160,6 +160,21 @@ const JoinSessionRequestSchema = Schema.Struct({
),
});
const SessionCredentialSchema = Schema.String.pipe(
Schema.minLength(1),
Schema.maxLength(MAX_MESSAGE_ID_LENGTH),
);
const SessionCodeSchema = Schema.String.pipe(
Schema.filter((value) => value.length === JOIN_CODE_LENGTH),
);
const SessionGrantSchema = Schema.Struct({
sessionId: SessionCredentialSchema,
code: SessionCodeSchema,
connectionToken: SessionCredentialSchema,
websocketPath: Schema.Literal("/api/presentation-sync/ws"),
snapshot: SnapshotSchema,
});
const parseJson = (input: string): DecodeResult<unknown> => {
// Measure UTF-8 bytes before parsing so websocket limits match transport size.
if (new TextEncoder().encode(input).byteLength > MAX_SYNC_MESSAGE_BYTES) {
@@ -235,3 +250,7 @@ export const decodeJoinSessionRequest = (
return { ok: false, error: "invalid_message" };
}
};
export const decodeSessionGrant = (
input: string,
): DecodeResult<SessionGrant> => decodeSchema(input, SessionGrantSchema);