Public invitations could stop after an early tunnel-process exit, and connection progress and Viewer status did not consistently describe the current attempt or Host state. This maintenance release adds bounded startup retries, aligns enhanced NAT retry feedback across entry paths, and fixes camera-preview and room-entry layout. The existing route owner and published contracts remain in place. Four-attempt progress is advertised and opted into; older readers retain supported messages. Interaction/chat experiments are outside this PR. Validation: npm run check (74 files, 1072 tests and production build), npm run check:go (including Windows capture compile), documentation/repository hygiene, 10 production Viewer-entry discovery cases, and a real public-link gate with remote HTTP/WebSocket access and shutdown cleanup all passed. Existing device/network evidence limits remain documented. ## Release notes ### 中文 这是一次维护更新,重点改善公网邀请启动、连接重试反馈和分享状态提示,并修正两处界面布局问题。 - 公网邀请服务在连接就绪前意外退出时,会自动重试,最多重试 3 次;所有尝试仍共用 30 秒启动预算。 - 增强穿透连接在首次尝试失败后最多重试 3 次,重试进度显示为 1/3、2/3、3/3。邀请链接和房间号加入都支持新版进度提示。 - 修正观众状态提示:房主离线时不再误报“没有可用的媒体线路”,服务器连接恢复与媒体恢复也会分别呈现。 - 修正电视内房间号输入界面的居中位置,以及摄像头选择预览被裁切的问题。 - 尚未确认来源声音状态时,不再提前提示“未分享来源声音”。 本次保持现有版本兼容;新版重试进度需要页面与服务端均支持。额外重试不能保证所有网络都能直连。 ### English This maintenance update improves public invitation startup, connection retry feedback, and sharing status messages, with two layout fixes. - If the public invitation service exits before connecting, Piik retries up to three times within the existing shared 30-second startup budget. - Enhanced NAT traversal now allows up to three retries after the initial attempt, shown as 1/3, 2/3, and 3/3. Both invitation links and room-code entry support the updated progress display. - Viewer status now distinguishes an offline host from an unavailable media route, and reports server reconnection separately from media recovery. - Fixed the alignment of room-code entry inside the TV and cropping in camera-selection previews. - The missing-source-audio notice now waits until the source's audio availability is known. Existing version compatibility is preserved. Updated retry progress requires support from both the page and server. Additional retries cannot guarantee a direct connection on every network.
1707 lines
47 KiB
TypeScript
1707 lines
47 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
DEFAULT_QUALITY_SETTINGS,
|
|
DEFAULT_ROUTE_POLICY,
|
|
DEFAULT_VIEWER_DISPLAY_NAME,
|
|
MAX_DISPLAY_NAME_CODE_POINTS,
|
|
MAX_MEDIA_ROUTE_REVISION,
|
|
MAX_VIEWER_QUALITY_EVIDENCE_BYTES,
|
|
MAX_VIEWER_PASSWORD_LENGTH,
|
|
MAX_VIEWERS_PER_ROOM_LIMIT,
|
|
SIGNALING_PROTOCOL,
|
|
clientMessageSchema,
|
|
createRoomRequestSchema,
|
|
decodeClientMessage,
|
|
normalizeDisplayName,
|
|
participantRouteAssignmentSchema,
|
|
preparedRouteCandidateSchema,
|
|
replaceRoomRequestSchema,
|
|
roomAccessUpdateRequestSchema,
|
|
roomAccessUpdateResponseSchema,
|
|
runtimeCapabilitiesSchema,
|
|
serverMessageSchema,
|
|
viewerPasswordSchema,
|
|
} from "../src/shared/protocol.js";
|
|
|
|
const token = "a".repeat(43);
|
|
const roomId = "1234";
|
|
const viewerGrant = `${"b".repeat(21)}g`;
|
|
const qualitySettings = {
|
|
resolution: "1080p",
|
|
maxFramerate: 60,
|
|
maxBitrate: 8_000_000,
|
|
degradationPreference: "maintain-resolution",
|
|
} as const;
|
|
|
|
describe("runtime capabilities", () => {
|
|
it("keeps progress opt-in optional and strict for either signaling role", () => {
|
|
for (const role of ["host", "viewer"] as const) {
|
|
const identity = { type: "authenticate", protocol: SIGNALING_PROTOCOL, roomId,
|
|
clientId: "client_12345678", role, ...(role === "host" ? { token } : {}) };
|
|
expect(clientMessageSchema.safeParse(identity).success).toBe(true);
|
|
for (const connectionAttemptProgress4 of [true, false, null, 4, "true"]) {
|
|
expect(clientMessageSchema.safeParse({ ...identity, connectionAttemptProgress4 }).success)
|
|
.toBe(connectionAttemptProgress4 === true);
|
|
}
|
|
}
|
|
});
|
|
it("defaults missing capabilities off and ignores unknown descriptors", () => {
|
|
expect(runtimeCapabilitiesSchema.parse({})).toEqual({ sfu: false, natPrediction: false });
|
|
expect(runtimeCapabilitiesSchema.parse({ sfu: true, extra: { enabled: true } }))
|
|
.toEqual({ sfu: true, natPrediction: false });
|
|
expect(runtimeCapabilitiesSchema.parse({ natPrediction: true, SFU: true }))
|
|
.toEqual({ sfu: false, natPrediction: true });
|
|
expect(runtimeCapabilitiesSchema.parse({ sfu: true, natPrediction: true }))
|
|
.toEqual({ sfu: true, natPrediction: true });
|
|
expect(runtimeCapabilitiesSchema.parse({ connectionAttemptProgress4: true }))
|
|
.toEqual({ sfu: false, natPrediction: false, connectionAttemptProgress4: true });
|
|
});
|
|
|
|
it("rejects malformed known capabilities and non-object responses", () => {
|
|
for (const value of [null, [], true, { sfu: null }, { sfu: "true" },
|
|
{ natPrediction: null }, { natPrediction: 1 },
|
|
{ connectionAttemptProgress4: null }, { connectionAttemptProgress4: 4 }]) {
|
|
expect(runtimeCapabilitiesSchema.safeParse(value).success).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
const qualitySettingsWithAudio = {
|
|
...qualitySettings,
|
|
screenAudioQuality: "music",
|
|
} as const;
|
|
|
|
const legacyCodecQualitySettings = {
|
|
...qualitySettings,
|
|
videoCodec: "vp8",
|
|
} as const;
|
|
|
|
const qualityEvidence = {
|
|
type: "viewer-quality-evidence",
|
|
guard: {
|
|
connectionId: "connection_12345678",
|
|
routeRevision: 0,
|
|
presentationEpoch: 0,
|
|
},
|
|
sequence: 0,
|
|
windowMs: 2_000,
|
|
metrics: {
|
|
natTraversalPath: "ordinary",
|
|
width: 1_920,
|
|
height: 1_080,
|
|
framesPerSecond: 59.8,
|
|
bitrateKbps: 7_500,
|
|
packetsReceivedDelta: 1_500,
|
|
packetsLostDelta: 2,
|
|
rttMs: 18,
|
|
jitterMs: 3.5,
|
|
framesDecodedDelta: 120,
|
|
framesDroppedDelta: 1,
|
|
decodeMsPerFrame: 2.4,
|
|
freezeCountDelta: 0,
|
|
freezeDurationMsDelta: 0,
|
|
pauseCountDelta: 0,
|
|
pauseDurationMsDelta: 0,
|
|
codec: "video/H264",
|
|
codecProfile: "profile-level-id=42e01f",
|
|
codecParameters:
|
|
"packetization-mode=1; level-asymmetry-allowed=1",
|
|
audioBitrateKbps: 192,
|
|
audioPacketLossPercent: 0.2,
|
|
audioJitterMs: 2.5,
|
|
audioVideoPlayoutDeltaMs: -12.5,
|
|
videoJitterBufferDelayMs: 24,
|
|
audioJitterBufferDelayMs: 18,
|
|
audioConcealedSamplesPercent: 1,
|
|
audioConcealmentEventsDelta: 3,
|
|
audioCodec: "audio/opus",
|
|
},
|
|
} as const;
|
|
|
|
describe("client signaling protocol", () => {
|
|
it("uses the current strict signaling generation", () => {
|
|
expect(SIGNALING_PROTOCOL).toBe("piik-v23");
|
|
});
|
|
|
|
it("keeps signaling challenges strict and sequence-only", () => {
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "signaling-challenge",
|
|
sequence: Number.MAX_SAFE_INTEGER,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "signaling-challenge-response",
|
|
sequence: 0,
|
|
}).success,
|
|
).toBe(true);
|
|
for (const invalid of [
|
|
{ type: "signaling-challenge", sequence: -1 },
|
|
{ type: "signaling-challenge", sequence: 1.5 },
|
|
{
|
|
type: "signaling-challenge",
|
|
sequence: Number.MAX_SAFE_INTEGER + 1,
|
|
},
|
|
{ type: "signaling-challenge", sequence: 1, roomId },
|
|
{ type: "signaling-challenge-response", sequence: "1" },
|
|
{ type: "signaling-challenge-response", sequence: 1, sessionId: token },
|
|
]) {
|
|
expect(
|
|
(invalid.type === "signaling-challenge"
|
|
? clientMessageSchema
|
|
: serverMessageSchema
|
|
).safeParse(invalid).success,
|
|
).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("accepts only an empty route diagnostic request", () => {
|
|
expect(
|
|
clientMessageSchema.safeParse({ type: "request-route-diagnostic" })
|
|
.success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "request-route-diagnostic",
|
|
roomId,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("keeps Viewer route status as a strict crossed union", () => {
|
|
expect(
|
|
serverMessageSchema.parse({
|
|
type: "route-status",
|
|
revision: 3,
|
|
state: "waiting",
|
|
reason: "sfu-admission",
|
|
}),
|
|
).toEqual({
|
|
type: "route-status",
|
|
revision: 3,
|
|
state: "waiting",
|
|
reason: "sfu-admission",
|
|
});
|
|
expect(
|
|
serverMessageSchema.parse({
|
|
type: "route-status",
|
|
revision: 4,
|
|
state: "failed",
|
|
reason: "route-exhausted",
|
|
}),
|
|
).toEqual({
|
|
type: "route-status",
|
|
revision: 4,
|
|
state: "failed",
|
|
reason: "route-exhausted",
|
|
});
|
|
for (const invalid of [
|
|
{
|
|
type: "route-status",
|
|
revision: 3,
|
|
state: "waiting",
|
|
reason: "route-exhausted",
|
|
},
|
|
{
|
|
type: "route-status",
|
|
revision: 3,
|
|
state: "failed",
|
|
reason: "sfu-admission",
|
|
},
|
|
{
|
|
type: "route-status",
|
|
revision: 3,
|
|
state: "waiting",
|
|
reason: "sfu-admission",
|
|
retryAfterMs: 1_000,
|
|
},
|
|
{
|
|
type: "route-status",
|
|
revision: 3,
|
|
state: "waiting",
|
|
reason: "sfu-admission",
|
|
peerId: "private-peer-id",
|
|
},
|
|
]) {
|
|
expect(serverMessageSchema.safeParse(invalid).success).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("accepts an atomic room creation profile", () => {
|
|
expect(
|
|
createRoomRequestSchema.parse({ codeEntryPolicy: "open" }),
|
|
).toEqual({ codeEntryPolicy: "open" });
|
|
expect(
|
|
createRoomRequestSchema.parse({
|
|
codeEntryPolicy: "private",
|
|
roomPassword: "room-password",
|
|
}),
|
|
).toEqual({
|
|
codeEntryPolicy: "private",
|
|
roomPassword: "room-password",
|
|
});
|
|
expect(
|
|
createRoomRequestSchema.parse({ codeEntryPolicy: "private" }),
|
|
).toEqual({ codeEntryPolicy: "private" });
|
|
expect(
|
|
createRoomRequestSchema.parse({
|
|
codeEntryPolicy: "open",
|
|
preferredRoomId: "4321",
|
|
}),
|
|
).toEqual({ codeEntryPolicy: "open", preferredRoomId: "4321" });
|
|
expect(
|
|
createRoomRequestSchema.safeParse({
|
|
codeEntryPolicy: "open",
|
|
preferredRoomId: "0432",
|
|
}).success,
|
|
).toBe(false);
|
|
for (const codeEntryPolicy of [
|
|
"password",
|
|
"private-link",
|
|
"public-watch",
|
|
1,
|
|
]) {
|
|
expect(
|
|
createRoomRequestSchema.safeParse({
|
|
codeEntryPolicy,
|
|
}).success,
|
|
).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("accepts only a replacement creation profile", () => {
|
|
expect(
|
|
replaceRoomRequestSchema.parse({
|
|
codeEntryPolicy: "private",
|
|
roomPassword: "room-password",
|
|
}),
|
|
).toEqual({
|
|
codeEntryPolicy: "private",
|
|
roomPassword: "room-password",
|
|
});
|
|
expect(
|
|
replaceRoomRequestSchema.safeParse({
|
|
codeEntryPolicy: "open",
|
|
preferredRoomId: "4321",
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("rejects the removed all-room ICE refresh request", () => {
|
|
expect(clientMessageSchema.safeParse({ type: "refresh-ice" }).success).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it("accepts a bounded authentication message", () => {
|
|
expect(DEFAULT_ROUTE_POLICY).toEqual({
|
|
peerOnly: false,
|
|
topologyOptimization: true,
|
|
natPrediction: false,
|
|
});
|
|
expect(
|
|
decodeClientMessage(
|
|
JSON.stringify({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "viewer",
|
|
clientId: "client_12345678",
|
|
}),
|
|
),
|
|
).toMatchObject({ type: "authenticate", role: "viewer" });
|
|
expect(
|
|
decodeClientMessage(
|
|
JSON.stringify({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "viewer",
|
|
viewerGrant,
|
|
clientId: "client_12345678",
|
|
}),
|
|
),
|
|
).toMatchObject({ viewerGrant });
|
|
expect(
|
|
decodeClientMessage(
|
|
JSON.stringify({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "host",
|
|
token,
|
|
clientId: "host_client_12345678",
|
|
shareGeneration: "share_generation_12345678",
|
|
qualitySettings: qualitySettingsWithAudio,
|
|
}),
|
|
),
|
|
).toMatchObject({
|
|
role: "host",
|
|
shareGeneration: "share_generation_12345678",
|
|
qualitySettings: qualitySettingsWithAudio,
|
|
routePolicy: {
|
|
peerOnly: false,
|
|
topologyOptimization: true,
|
|
natPrediction: false,
|
|
},
|
|
});
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "viewer",
|
|
clientId: "client_12345678",
|
|
capabilities: { peerIceTurn: true },
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
roomId,
|
|
role: "viewer",
|
|
clientId: "client_12345678",
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: "invalid-protocol",
|
|
roomId,
|
|
role: "viewer",
|
|
clientId: "client_12345678",
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("normalizes display names and rejects misleading Unicode boundaries", () => {
|
|
expect(normalizeDisplayName(" Cafe\u0301\u00a0朋友 ")).toBe("Café 朋友");
|
|
expect(normalizeDisplayName("玩家 👩💻")).toBe("玩家 👩💻");
|
|
expect(normalizeDisplayName("名".repeat(MAX_DISPLAY_NAME_CODE_POINTS))).toBe(
|
|
"名".repeat(MAX_DISPLAY_NAME_CODE_POINTS),
|
|
);
|
|
expect(
|
|
normalizeDisplayName("名".repeat(MAX_DISPLAY_NAME_CODE_POINTS + 1)),
|
|
).toBeNull();
|
|
for (const invalid of ["a\nb", "a\u202eb", "a\ufeffb", "a\ud800b"]) {
|
|
expect(normalizeDisplayName(invalid)).toBeNull();
|
|
}
|
|
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "viewer",
|
|
clientId: "client_12345678",
|
|
displayName: "小明",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "host",
|
|
token,
|
|
clientId: "client_12345678",
|
|
viewerPresence: true,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "set-display-name",
|
|
displayName: " Cafe\u0301 ",
|
|
}).success,
|
|
).toBe(false);
|
|
expect(DEFAULT_VIEWER_DISPLAY_NAME).toBe("观众");
|
|
});
|
|
|
|
it("accepts only canonical bounded Viewer grants and access actions", () => {
|
|
for (const malformedGrant of [
|
|
"b".repeat(21),
|
|
"b".repeat(23),
|
|
`${"b".repeat(21)}=`,
|
|
`${"b".repeat(21)}b`,
|
|
`g1.${roomId}.1787076000.${"b".repeat(43)}`,
|
|
]) {
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "viewer",
|
|
viewerGrant: malformedGrant,
|
|
clientId: "client_12345678",
|
|
}).success,
|
|
).toBe(false);
|
|
}
|
|
for (const policy of ["open", "private"] as const) {
|
|
expect(
|
|
roomAccessUpdateRequestSchema.safeParse({
|
|
action: "set-code-entry-policy",
|
|
policy,
|
|
}).success,
|
|
).toBe(true);
|
|
}
|
|
expect(
|
|
roomAccessUpdateRequestSchema.safeParse({
|
|
action: "rotate-viewer-grant",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
roomAccessUpdateRequestSchema.safeParse({
|
|
action: "revoke-viewer-grant",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
roomAccessUpdateRequestSchema.safeParse({
|
|
action: "set-code-entry-policy",
|
|
policy: "disabled",
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
roomAccessUpdateRequestSchema.safeParse({
|
|
action: "set-code-entry-policy",
|
|
policy: "password",
|
|
}).success,
|
|
).toBe(false);
|
|
for (const removedMessage of [
|
|
{ type: "set-code-entry-policy", policy: "open" },
|
|
{ type: "rotate-viewer-grant" },
|
|
{ type: "revoke-viewer-grant" },
|
|
{ type: "set-viewer-password", password: "room-password" },
|
|
]) {
|
|
expect(clientMessageSchema.safeParse(removedMessage).success).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("accepts simple bounded Viewer passwords and access responses", () => {
|
|
expect(viewerPasswordSchema.safeParse("x").success).toBe(true);
|
|
expect(viewerPasswordSchema.safeParse("simple-password").success).toBe(true);
|
|
for (const invalid of [
|
|
"",
|
|
"contains space",
|
|
"line\nbreak",
|
|
"x".repeat(MAX_VIEWER_PASSWORD_LENGTH + 1),
|
|
]) {
|
|
expect(viewerPasswordSchema.safeParse(invalid).success).toBe(false);
|
|
}
|
|
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "viewer",
|
|
clientId: "client_12345678",
|
|
viewerPassword: "easy-password",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
roomAccessUpdateRequestSchema.safeParse({
|
|
action: "set-viewer-password",
|
|
password: null,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
roomAccessUpdateResponseSchema.safeParse({
|
|
type: "viewer-password-updated",
|
|
enabled: true,
|
|
}).success,
|
|
).toBe(true);
|
|
});
|
|
|
|
it("rejects unknown fields and malformed tokens", () => {
|
|
const result = clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "host",
|
|
token: "short",
|
|
clientId: "client_12345678",
|
|
admin: true,
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it("requires a host token and rejects viewer tokens or malformed room codes", () => {
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "host",
|
|
token,
|
|
clientId: "client_12345678",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "viewer",
|
|
token,
|
|
clientId: "client_12345678",
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId: "1234",
|
|
role: "viewer",
|
|
clientId: "client_12345678",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId: "0123",
|
|
role: "viewer",
|
|
clientId: "client_12345678",
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId: "1".repeat(5),
|
|
role: "viewer",
|
|
clientId: "client_12345678",
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("bounds SDP before routing it", () => {
|
|
const result = clientMessageSchema.safeParse({
|
|
type: "signal",
|
|
targetPeerId: "viewer_12345678",
|
|
payload: {
|
|
kind: "description",
|
|
connectionId: "connection_12345678",
|
|
description: {
|
|
type: "offer",
|
|
sdp: "v".repeat(48 * 1024 + 1),
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it("requires viewers to choose ICE restart or peer rebuild explicitly", () => {
|
|
const restartRequest = {
|
|
type: "restart-request",
|
|
connectionId: "connection_12345678",
|
|
rebuild: true,
|
|
};
|
|
|
|
expect(clientMessageSchema.safeParse(restartRequest).success).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
...restartRequest,
|
|
targetPeerId: "parent_12345678",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: restartRequest.type,
|
|
connectionId: restartRequest.connectionId,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("accepts explicit sharing-stop and abandonment messages", () => {
|
|
expect(clientMessageSchema.safeParse({ type: "stop-sharing" }).success).toBe(
|
|
true,
|
|
);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "stop-sharing",
|
|
shareGeneration: "share_generation_12345678",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "authenticate",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
roomId,
|
|
role: "host",
|
|
token,
|
|
clientId: "client_12345678",
|
|
shareGeneration: "share_generation_12345678",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(clientMessageSchema.safeParse({ type: "abandon-room" }).success).toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
it("keeps intentional pause updates strict and generation-bound", () => {
|
|
const pause = {
|
|
type: "set-sharing-paused",
|
|
shareGeneration: "share_generation_12345678",
|
|
paused: true,
|
|
};
|
|
expect(clientMessageSchema.safeParse(pause).success).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({ ...pause, paused: false }).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({ ...pause, shareGeneration: undefined })
|
|
.success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({ ...pause, paused: "true" }).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({ ...pause, unexpected: 1 }).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "host-status",
|
|
online: true,
|
|
paused: true,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
serverMessageSchema.safeParse({ type: "host-status", online: true })
|
|
.success,
|
|
).toBe(false);
|
|
const authoritativePause = {
|
|
type: "pause-sharing-source",
|
|
shareGeneration: "share_generation_12345678",
|
|
};
|
|
expect(serverMessageSchema.safeParse(authoritativePause).success).toBe(true);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...authoritativePause,
|
|
unexpected: null,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("accepts only strict, bounded quality settings", () => {
|
|
expect(DEFAULT_QUALITY_SETTINGS).not.toHaveProperty("videoCodec");
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "set-quality-settings",
|
|
qualitySettings,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "set-quality-settings",
|
|
qualitySettings: qualitySettingsWithAudio,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "set-quality-settings",
|
|
qualitySettings: { ...qualitySettings, resolution: "480p" },
|
|
}).success,
|
|
).toBe(true);
|
|
for (const screenAudioQuality of [
|
|
"saver",
|
|
"music",
|
|
"very-high",
|
|
] as const) {
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "set-quality-settings",
|
|
qualitySettings: { ...qualitySettings, screenAudioQuality },
|
|
}).success,
|
|
).toBe(true);
|
|
}
|
|
for (const invalid of [
|
|
{ ...qualitySettings, resolution: "2160p" },
|
|
{ ...qualitySettings, maxFramerate: 14 },
|
|
{ ...qualitySettings, maxFramerate: 61 },
|
|
{ ...qualitySettings, maxFramerate: 30.5 },
|
|
{ ...qualitySettings, maxBitrate: 1_999_999 },
|
|
{ ...qualitySettings, maxBitrate: 12_000_001 },
|
|
{ ...qualitySettings, maxBitrate: 5_000_000.5 },
|
|
{ ...qualitySettings, degradationPreference: "automatic" },
|
|
legacyCodecQualitySettings,
|
|
{ ...qualitySettings, screenAudioQuality: "lossless" },
|
|
{ ...qualitySettings, screenAudioQuality: 960_000 },
|
|
{ ...qualitySettings, codec: "video/VP9" },
|
|
]) {
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "set-quality-settings",
|
|
qualitySettings: invalid,
|
|
}).success,
|
|
).toBe(false);
|
|
}
|
|
const { maxFramerate: _missing, ...missingField } = qualitySettings;
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "set-quality-settings",
|
|
qualitySettings: missingField,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("accepts only a bounded browser relay capacity", () => {
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "relay-capacity",
|
|
downstreamEdges: 0,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "relay-capacity",
|
|
downstreamEdges: 1,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "relay-capacity",
|
|
downstreamEdges: 2,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "relay-capacity",
|
|
downstreamEdges: 3,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "relay-capacity",
|
|
downstreamEdges: 4,
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "relay-capacity",
|
|
downstreamEdges: 1,
|
|
score: 100,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("accepts only strict, bounded viewer quality evidence", () => {
|
|
expect(clientMessageSchema.safeParse(qualityEvidence).success).toBe(true);
|
|
const { natTraversalPath: _path, ...legacyMetrics } =
|
|
qualityEvidence.metrics;
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
...qualityEvidence,
|
|
metrics: legacyMetrics,
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
...qualityEvidence,
|
|
metrics: {
|
|
...qualityEvidence.metrics,
|
|
freezeDurationMsDelta: 12_000,
|
|
pauseDurationMsDelta: 6_000,
|
|
},
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
...qualityEvidence,
|
|
metrics: {
|
|
...qualityEvidence.metrics,
|
|
freezeCountDelta: null,
|
|
freezeDurationMsDelta: null,
|
|
pauseCountDelta: null,
|
|
pauseDurationMsDelta: null,
|
|
},
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
Buffer.byteLength(JSON.stringify(qualityEvidence), "utf8"),
|
|
).toBeLessThanOrEqual(MAX_VIEWER_QUALITY_EVIDENCE_BYTES);
|
|
|
|
for (const invalid of [
|
|
{ ...qualityEvidence, roomId },
|
|
{
|
|
...qualityEvidence,
|
|
guard: { ...qualityEvidence.guard, peerId: "viewer_12345678" },
|
|
},
|
|
{ ...qualityEvidence, windowMs: 999 },
|
|
{ ...qualityEvidence, sequence: -1 },
|
|
{
|
|
...qualityEvidence,
|
|
metrics: { ...qualityEvidence.metrics, width: null },
|
|
},
|
|
{
|
|
...qualityEvidence,
|
|
metrics: { ...qualityEvidence.metrics, bitrateKbps: Number.POSITIVE_INFINITY },
|
|
},
|
|
{
|
|
...qualityEvidence,
|
|
metrics: { ...qualityEvidence.metrics, freezeCountDelta: -1 },
|
|
},
|
|
{
|
|
...qualityEvidence,
|
|
metrics: {
|
|
...qualityEvidence.metrics,
|
|
freezeCountDelta: Number.MAX_SAFE_INTEGER + 1,
|
|
},
|
|
},
|
|
{
|
|
...qualityEvidence,
|
|
metrics: {
|
|
...qualityEvidence.metrics,
|
|
freezeDurationMsDelta: Number.MAX_SAFE_INTEGER + 1,
|
|
},
|
|
},
|
|
{
|
|
...qualityEvidence,
|
|
metrics: { ...qualityEvidence.metrics, pauseCountDelta: -1 },
|
|
},
|
|
{
|
|
...qualityEvidence,
|
|
metrics: {
|
|
...qualityEvidence.metrics,
|
|
pauseDurationMsDelta: Number.MAX_SAFE_INTEGER + 1,
|
|
},
|
|
},
|
|
{
|
|
...qualityEvidence,
|
|
metrics: {
|
|
...qualityEvidence.metrics,
|
|
codecParameters: "sprop-parameter-sets=deadbeef",
|
|
},
|
|
},
|
|
{
|
|
...qualityEvidence,
|
|
metrics: {
|
|
...qualityEvidence.metrics,
|
|
codecProfile: "device-id=deadbeef",
|
|
},
|
|
},
|
|
{
|
|
...qualityEvidence,
|
|
metrics: {
|
|
...qualityEvidence.metrics,
|
|
scalabilityMode: "DEVICE_ABC123",
|
|
},
|
|
},
|
|
{
|
|
...qualityEvidence,
|
|
metrics: {
|
|
...qualityEvidence.metrics,
|
|
decoderImplementation: "device-specific-decoder",
|
|
},
|
|
},
|
|
]) {
|
|
expect(clientMessageSchema.safeParse(invalid).success).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("accepts only native sender quality state with exact known identity", () => {
|
|
const sender = {
|
|
type: "sender-quality-evidence",
|
|
childPeerId: "viewer_12345678",
|
|
connectionId: "connection_12345678",
|
|
rtpStatsId: "rtp-stats-1",
|
|
trackIdentifier: "track-1",
|
|
sampleTimestampMs: 2_000,
|
|
routeRevision: 3,
|
|
state: "degraded",
|
|
diagnostics: {
|
|
natTraversalPath: "ordinary",
|
|
reason: "bandwidth",
|
|
framesPerSecond: 24,
|
|
bitrateKbps: 1_500,
|
|
},
|
|
} as const;
|
|
expect(clientMessageSchema.safeParse(sender).success).toBe(true);
|
|
const { natTraversalPath: _senderPath, ...legacyDiagnostics } =
|
|
sender.diagnostics;
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
...sender,
|
|
diagnostics: legacyDiagnostics,
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
...sender,
|
|
state: "healthy",
|
|
rtpStatsId: null,
|
|
diagnostics: { ...sender.diagnostics, reason: "none" },
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
...sender,
|
|
sampleTimestampMs: null,
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "sfu-publisher-quality-evidence",
|
|
publicationGeneration: "publication_generation_12345678",
|
|
routeRevision: 4,
|
|
state: "healthy",
|
|
sampleTimestampMs: 2_000,
|
|
diagnostics: {
|
|
natTraversalPath: "ordinary",
|
|
reason: "none",
|
|
framesPerSecond: 30,
|
|
bitrateKbps: 2_000,
|
|
},
|
|
}).success,
|
|
).toBe(true);
|
|
});
|
|
|
|
it.each([
|
|
["video/VP8", null, "max-fr=60; max-fs=8160"],
|
|
["video/VP9", "profile-id=2", "max-fs=8160"],
|
|
["video/AV1", "profile=1", "level-idx=31; tier=0"],
|
|
["video/unknown", null, null],
|
|
])(
|
|
"accepts canonical viewer codec evidence for %s",
|
|
(codec, codecProfile, codecParameters) => {
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
...qualityEvidence,
|
|
metrics: {
|
|
...qualityEvidence.metrics,
|
|
codec,
|
|
codecProfile,
|
|
codecParameters,
|
|
},
|
|
}).success,
|
|
).toBe(true);
|
|
},
|
|
);
|
|
|
|
it("accepts bounded route acknowledgements and failures", () => {
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "route-ready",
|
|
revision: 7,
|
|
phase: "prepare",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "route-ready",
|
|
revision: 7,
|
|
phase: "prepare",
|
|
qualityApproved: true,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "route-ready",
|
|
revision: 7,
|
|
phase: "prepare",
|
|
qualityApproved: false,
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "route-transport-connected",
|
|
revision: 7,
|
|
connectionId: "connection_12345678",
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "route-media-unavailable",
|
|
revision: 7,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "route-failed",
|
|
revision: 7,
|
|
phase: "active",
|
|
connectionId: null,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "refresh-sfu",
|
|
revision: MAX_MEDIA_ROUTE_REVISION,
|
|
}).success,
|
|
).toBe(true);
|
|
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "route-ready",
|
|
revision: -1,
|
|
phase: "prepare",
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "route-transport-connected",
|
|
revision: 7,
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "route-media-unavailable",
|
|
revision: -1,
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "route-failed",
|
|
revision: 7,
|
|
phase: "active",
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
clientMessageSchema.safeParse({
|
|
type: "refresh-sfu",
|
|
revision: 1.5,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("server signaling protocol", () => {
|
|
it("keeps route status pairings revision-fenced and strict", () => {
|
|
for (const message of [
|
|
{
|
|
type: "route-status",
|
|
revision: 3,
|
|
state: "waiting",
|
|
reason: "sfu-admission",
|
|
},
|
|
{
|
|
type: "route-status",
|
|
revision: 4,
|
|
state: "failed",
|
|
reason: "route-exhausted",
|
|
},
|
|
]) {
|
|
expect(serverMessageSchema.safeParse(message).success).toBe(true);
|
|
}
|
|
for (const message of [
|
|
{
|
|
type: "route-status",
|
|
revision: 3,
|
|
state: "waiting",
|
|
reason: "route-exhausted",
|
|
},
|
|
{
|
|
type: "route-status",
|
|
revision: 3,
|
|
state: "failed",
|
|
reason: "sfu-admission",
|
|
},
|
|
{
|
|
type: "route-status",
|
|
revision: 3,
|
|
state: "failed",
|
|
reason: "route-exhausted",
|
|
retry: true,
|
|
},
|
|
]) {
|
|
expect(serverMessageSchema.safeParse(message).success).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("accepts only the privacy-safe route snapshot payload", () => {
|
|
const message = {
|
|
type: "route-diagnostic-snapshot",
|
|
snapshot: {
|
|
children: [
|
|
{
|
|
ordinal: 1,
|
|
parent: { kind: "host" },
|
|
effectiveCapacity: 2,
|
|
childCount: 1,
|
|
demandAgeMs: 100,
|
|
queueWaitMs: 10,
|
|
candidateStartMs: 20,
|
|
firstDecodedFrameMs: 80,
|
|
finalMs: 80,
|
|
finalRoute: "direct",
|
|
rejectionBucket: "none",
|
|
quality: {
|
|
eligibleWindows: 3,
|
|
eligibleDurationMs: 6_000,
|
|
freezeWindows: 1,
|
|
freezeCount: 1,
|
|
freezeDurationMs: 250,
|
|
pauseCount: 0,
|
|
pauseDurationMs: 0,
|
|
},
|
|
},
|
|
{
|
|
ordinal: 2,
|
|
parent: { kind: "viewer", ordinal: 1 },
|
|
effectiveCapacity: 0,
|
|
childCount: 0,
|
|
demandAgeMs: 90,
|
|
queueWaitMs: null,
|
|
candidateStartMs: null,
|
|
firstDecodedFrameMs: null,
|
|
finalMs: null,
|
|
finalRoute: "waiting",
|
|
rejectionBucket: "sfu-admission",
|
|
quality: null,
|
|
},
|
|
],
|
|
operation: {
|
|
childOrdinal: 2,
|
|
reason: "join",
|
|
stage: "admission",
|
|
cursor: 1,
|
|
candidateCount: 2,
|
|
},
|
|
},
|
|
} as const;
|
|
expect(serverMessageSchema.safeParse(message).success).toBe(true);
|
|
for (const invalid of [
|
|
{
|
|
...message,
|
|
snapshot: { ...message.snapshot, peerId: "viewer_private_12345678" },
|
|
},
|
|
{
|
|
...message,
|
|
snapshot: {
|
|
...message.snapshot,
|
|
children: [
|
|
{
|
|
...message.snapshot.children[0],
|
|
connectionId: "connection_private_12345678",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
...message,
|
|
snapshot: {
|
|
...message.snapshot,
|
|
children: [
|
|
{
|
|
...message.snapshot.children[0],
|
|
quality: {
|
|
...message.snapshot.children[0].quality,
|
|
presentationEpoch: 1,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
...message,
|
|
snapshot: {
|
|
...message.snapshot,
|
|
children: [
|
|
{
|
|
...message.snapshot.children[0],
|
|
quality: {
|
|
...message.snapshot.children[0].quality,
|
|
freezeCount: -1,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
...message,
|
|
snapshot: {
|
|
...message.snapshot,
|
|
children: [
|
|
message.snapshot.children[0],
|
|
{
|
|
...message.snapshot.children[1],
|
|
parent: { kind: "viewer", ordinal: 2 },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
...message,
|
|
snapshot: {
|
|
...message.snapshot,
|
|
operation: {
|
|
...message.snapshot.operation,
|
|
rejectionBucket: "raw-error",
|
|
},
|
|
},
|
|
},
|
|
]) {
|
|
expect(serverMessageSchema.safeParse(invalid).success).toBe(false);
|
|
}
|
|
});
|
|
it("accepts only STUN URLs in the authenticated peer ICE config", () => {
|
|
for (const url of ["stun:stun.test:80/", "stun:stun.test:3478/"]) {
|
|
expect(serverMessageSchema.safeParse({
|
|
...authenticatedMessage(20),
|
|
iceConfig: { iceServers: [{ urls: url }], natPredictionStunUrls: [] },
|
|
}).success).toBe(false);
|
|
}
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "ice-config",
|
|
iceConfig: { iceServers: [] },
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...authenticatedMessage(8),
|
|
iceConfig: {
|
|
iceServers: [
|
|
{
|
|
urls: "turn:relay.example.test:3478?transport=udp",
|
|
username: `1787076000:${"a".repeat(32)}`,
|
|
credential: "credential",
|
|
},
|
|
],
|
|
},
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...authenticatedMessage(8),
|
|
iceConfig: {
|
|
iceServers: [{ urls: "turn:relay.example.test:3478?transport=udp" }],
|
|
},
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...authenticatedMessage(8),
|
|
iceConfig: {
|
|
iceServers: [{ urls: "stuns:stun.example.test:5349" }],
|
|
},
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
function authenticatedMessage(maxViewers: number) {
|
|
return {
|
|
type: "authenticated",
|
|
protocol: SIGNALING_PROTOCOL,
|
|
role: "host",
|
|
peerId: "host_12345678",
|
|
maxViewers,
|
|
endpointMediaCopyCapacity: 2,
|
|
hostOnline: true,
|
|
connectionId: null,
|
|
mediaMode: "peer-assisted",
|
|
shareGeneration: "share_generation_12345678",
|
|
routeRevision: 0,
|
|
routeAssignment: {
|
|
upstream: { kind: "none" },
|
|
childPeerIds: [],
|
|
sfuPublicationGeneration: null,
|
|
},
|
|
qualitySettings,
|
|
routePolicy: DEFAULT_ROUTE_POLICY,
|
|
codeEntryPolicy: "open",
|
|
viewerPasswordEnabled: false,
|
|
viewerAuthorizationGeneration: "viewer_generation_12345678",
|
|
iceConfig: {
|
|
iceServers: [],
|
|
natPredictionStunUrls: [],
|
|
},
|
|
};
|
|
}
|
|
|
|
it("scopes password configuration state to authenticated Hosts", () => {
|
|
const host = authenticatedMessage(8) as Record<string, unknown>;
|
|
expect(serverMessageSchema.safeParse(host).success).toBe(true);
|
|
delete host.viewerPasswordEnabled;
|
|
expect(serverMessageSchema.safeParse(host).success).toBe(false);
|
|
|
|
const viewer = {
|
|
...authenticatedMessage(8),
|
|
role: "viewer",
|
|
peerId: "viewer_12345678",
|
|
} as Record<string, unknown>;
|
|
delete viewer.viewerPasswordEnabled;
|
|
expect(serverMessageSchema.safeParse(viewer).success).toBe(true);
|
|
viewer.viewerPasswordEnabled = true;
|
|
expect(serverMessageSchema.safeParse(viewer).success).toBe(false);
|
|
});
|
|
|
|
it("requires the v20 NAT observation configuration", () => {
|
|
const message = authenticatedMessage(8);
|
|
const { natPredictionStunUrls: _urls, ...legacyIceConfig } =
|
|
message.iceConfig;
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...message,
|
|
iceConfig: legacyIceConfig,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("accepts dynamic viewer limits within the protocol boundary", () => {
|
|
expect(MAX_VIEWERS_PER_ROOM_LIMIT).toBe(20);
|
|
expect(serverMessageSchema.safeParse(authenticatedMessage(1)).success).toBe(
|
|
true,
|
|
);
|
|
expect(serverMessageSchema.safeParse(authenticatedMessage(8)).success).toBe(
|
|
true,
|
|
);
|
|
expect(
|
|
serverMessageSchema.safeParse(
|
|
authenticatedMessage(MAX_VIEWERS_PER_ROOM_LIMIT),
|
|
).success,
|
|
).toBe(true);
|
|
});
|
|
|
|
it("requires the authenticated endpoint capacity to be 1, 2, or 3", () => {
|
|
for (const endpointMediaCopyCapacity of [1, 2, 3]) {
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...authenticatedMessage(8),
|
|
endpointMediaCopyCapacity,
|
|
}).success,
|
|
).toBe(true);
|
|
}
|
|
for (const endpointMediaCopyCapacity of [undefined, 0, 4, 1.5]) {
|
|
const message = authenticatedMessage(8) as Record<string, unknown>;
|
|
if (endpointMediaCopyCapacity === undefined) {
|
|
delete message.endpointMediaCopyCapacity;
|
|
} else {
|
|
message.endpointMediaCopyCapacity = endpointMediaCopyCapacity;
|
|
}
|
|
expect(serverMessageSchema.safeParse(message).success).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("accepts a strict, unique and bounded Viewer presence snapshot", () => {
|
|
const viewer = {
|
|
role: "viewer",
|
|
peerId: "viewer_12345678",
|
|
displayName: "小明",
|
|
upstream: { kind: "peer", peerId: "viewer_parent_12345678" },
|
|
} as const;
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "viewer-presence",
|
|
viewers: [
|
|
{
|
|
...viewer,
|
|
upstream: { kind: "sfu" },
|
|
mediaReady: true,
|
|
},
|
|
],
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "viewer-presence",
|
|
viewers: [{ ...viewer, mediaReady: false }],
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "viewer-presence",
|
|
viewers: [viewer, viewer],
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "viewer-presence",
|
|
viewers: [{ ...viewer, mediaTopology: "peer-relay" }],
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "viewer-presence",
|
|
viewers: [{ ...viewer, ip: "203.0.113.1" }],
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "viewer-presence",
|
|
viewers: [
|
|
viewer,
|
|
{
|
|
role: "host",
|
|
peerId: "host_12345678",
|
|
displayName: "分享者",
|
|
upstream: { kind: "none" },
|
|
},
|
|
],
|
|
}).success,
|
|
).toBe(true);
|
|
});
|
|
|
|
it("requires a complete bounded peer-assisted assignment", () => {
|
|
const peerAssisted = {
|
|
...authenticatedMessage(8),
|
|
mediaMode: "peer-assisted",
|
|
shareGeneration: "share_generation_12345678",
|
|
routeRevision: 0,
|
|
routeAssignment: {
|
|
upstream: { kind: "none" },
|
|
childPeerIds: ["viewer_12345678", "viewer_87654321"],
|
|
sfuPublicationGeneration: null,
|
|
},
|
|
qualitySettings,
|
|
routePolicy: DEFAULT_ROUTE_POLICY,
|
|
};
|
|
|
|
expect(serverMessageSchema.safeParse(peerAssisted).success).toBe(true);
|
|
const incomplete = {
|
|
...authenticatedMessage(8),
|
|
} as Record<string, unknown>;
|
|
delete incomplete.routeAssignment;
|
|
expect(serverMessageSchema.safeParse(incomplete).success).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...peerAssisted,
|
|
routeAssignment: {
|
|
...peerAssisted.routeAssignment,
|
|
childPeerIds: [
|
|
"viewer_12345678",
|
|
"viewer_87654321",
|
|
"viewer_third_1234",
|
|
"viewer_overflow_1",
|
|
],
|
|
},
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...peerAssisted,
|
|
qualitySettings: { ...qualitySettings, maxBitrate: 20_000_000 },
|
|
}).success,
|
|
).toBe(false);
|
|
const missingMode = {
|
|
...authenticatedMessage(8),
|
|
} as Record<string, unknown>;
|
|
delete missingMode.mediaMode;
|
|
expect(serverMessageSchema.safeParse(missingMode).success).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "quality-settings",
|
|
qualitySettings: {
|
|
resolution: "720p",
|
|
maxFramerate: 30,
|
|
maxBitrate: 3_000_000,
|
|
degradationPreference: "balanced",
|
|
},
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "quality-settings",
|
|
qualitySettings: { ...qualitySettings, maxFramerate: 0 },
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("accepts only bounded connection-attempt progress on a prepared route", () => {
|
|
const candidate = {
|
|
childPeerId: "child_12345678", connectionId: "connection_12345678",
|
|
transport: "direct", qualityProbe: false,
|
|
};
|
|
for (const total of [3, 4]) {
|
|
for (let current = 1; current <= total; current++) {
|
|
expect(preparedRouteCandidateSchema.safeParse({
|
|
...candidate, connectionAttempt: { current, total },
|
|
}).success).toBe(true);
|
|
}
|
|
}
|
|
for (const connectionAttempt of [
|
|
{ current: 0, total: 3 }, { current: 4, total: 3 },
|
|
{ current: 5, total: 4 }, { current: 1, total: 5 }, { current: 1.5, total: 3 },
|
|
]) {
|
|
expect(preparedRouteCandidateSchema.safeParse({
|
|
...candidate, connectionAttempt,
|
|
}).success).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("keeps route messages strict alongside the single authenticated mode", () => {
|
|
const assignment = {
|
|
upstream: { kind: "peer", peerId: "parent_12345678" },
|
|
childPeerIds: ["child_12345678", "child_87654321"],
|
|
sfuPublicationGeneration: null,
|
|
};
|
|
|
|
expect(participantRouteAssignmentSchema.safeParse(assignment).success).toBe(
|
|
true,
|
|
);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "route-update",
|
|
revision: 9,
|
|
phase: "prepare",
|
|
assignment,
|
|
candidate: {
|
|
childPeerId: "child_12345678",
|
|
connectionId: "connection_12345678",
|
|
transport: "direct",
|
|
qualityProbe: false,
|
|
},
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "route-update",
|
|
revision: 10,
|
|
phase: "prepare",
|
|
assignment,
|
|
candidate: {
|
|
childPeerId: "child_12345678",
|
|
connectionId: "connection_12345678",
|
|
transport: "sfu",
|
|
qualityProbe: true,
|
|
},
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "route-update",
|
|
revision: 10,
|
|
phase: "prepare",
|
|
assignment,
|
|
candidate: {
|
|
childPeerId: "child_12345678",
|
|
connectionId: "connection_12345678",
|
|
transport: "direct",
|
|
qualityProbe: false,
|
|
unexpected: null,
|
|
},
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "sfu-config",
|
|
revision: 9,
|
|
publicationGeneration: "publication_12345678",
|
|
connectionId: "connection_12345678",
|
|
}).success,
|
|
).toBe(true);
|
|
|
|
expect(
|
|
participantRouteAssignmentSchema.safeParse({
|
|
...assignment,
|
|
childPeerIds: [
|
|
"child_12345678",
|
|
"child_87654321",
|
|
"child_third_1234",
|
|
"child_overflow_1",
|
|
],
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
participantRouteAssignmentSchema.safeParse({
|
|
...assignment,
|
|
childPeerIds: ["child_12345678", "child_12345678"],
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "route-update",
|
|
revision: 9,
|
|
phase: "prepare",
|
|
assignment,
|
|
roomAssignments: [],
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
type: "sfu-config",
|
|
revision: 9,
|
|
publicationGeneration: "publication_12345678",
|
|
connectionId: "",
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...authenticatedMessage(8),
|
|
routeRevision: MAX_MEDIA_ROUTE_REVISION + 1,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("accepts every bounded hybrid upstream shape", () => {
|
|
for (const upstream of [
|
|
{ kind: "none" },
|
|
{ kind: "peer", peerId: "parent_12345678" },
|
|
{ kind: "sfu" },
|
|
]) {
|
|
expect(
|
|
participantRouteAssignmentSchema.safeParse({
|
|
upstream,
|
|
childPeerIds: [],
|
|
sfuPublicationGeneration:
|
|
upstream.kind === "peer" ? null : "generation_12345678",
|
|
}).success,
|
|
).toBe(true);
|
|
}
|
|
expect(
|
|
participantRouteAssignmentSchema.safeParse({
|
|
upstream: { kind: "sfu" },
|
|
childPeerIds: [],
|
|
sfuPublicationGeneration: null,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("accepts only canonical server-derived viewer evidence envelopes", () => {
|
|
const forwarded = {
|
|
...qualityEvidence,
|
|
viewerPeerId: "viewer_12345678",
|
|
upstream: { kind: "peer", peerId: "host_12345678" },
|
|
};
|
|
expect(serverMessageSchema.safeParse(forwarded).success).toBe(true);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...forwarded,
|
|
metrics: {
|
|
...forwarded.metrics,
|
|
freezeDurationMsDelta: 12_000,
|
|
pauseDurationMsDelta: 6_000,
|
|
},
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...forwarded,
|
|
upstream: { kind: "sfu" },
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
Buffer.byteLength(JSON.stringify(forwarded), "utf8"),
|
|
).toBeLessThanOrEqual(MAX_VIEWER_QUALITY_EVIDENCE_BYTES);
|
|
expect(
|
|
serverMessageSchema.safeParse({ ...forwarded, roomId }).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...qualityEvidence,
|
|
viewerPeerId: "viewer_12345678",
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
serverMessageSchema.safeParse({
|
|
...forwarded,
|
|
upstream: { kind: "none" },
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it.each([0, 1.5, MAX_VIEWERS_PER_ROOM_LIMIT + 1])(
|
|
"rejects viewer limit %s outside the protocol boundary",
|
|
(maxViewers) => {
|
|
expect(
|
|
serverMessageSchema.safeParse(authenticatedMessage(maxViewers)).success,
|
|
).toBe(false);
|
|
},
|
|
);
|
|
|
|
});
|