Files
yawn/vendor/fxnode/test/protocol.test.ts
T

880 lines
29 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
PROTOCOL_VERSION,
validCommandReceipt,
validCompositionReceipt,
validRequest,
validWorkerMessage,
} from "@lib/browser/protocol.js";
import {
decodeFxNodeActionOptions,
decodeFxNodeAddNodeParams,
decodeFxNodeCamera,
decodeFxNodeInput,
decodeFxNodeResourceAuthorization,
decodeFxNodeResourceData,
decodeFxNodeViewport,
} from "@lib/browser/host-decode.js";
import { fxNodeDevicePixels } from "@lib/browser/view-limits.js";
const viewport = { width: 1, height: 1, dpr: 1 };
const viewId = "internal-view";
test("protocol v2 messages are rejected", () => {
assert.equal(validRequest({ protocol: 2, type: "state.get", id: "request" }), false);
assert.equal(
validWorkerMessage({ protocol: 2, type: "view.node-menu.result", viewId, requestId: "request", open: false }),
false,
);
});
test("old unprefixed view message names are rejected", () => {
for (const type of [
"viewport",
"input",
"node.add",
"selection.remove",
"selection.mute",
"resource.set",
"pointer.flush",
"frame.consumed",
])
assert.equal(validRequest({ protocol: PROTOCOL_VERSION, type, viewId }), false);
for (const type of ["frame", "selection.host", "node-menu.result", "resource.open"])
assert.equal(validWorkerMessage({ protocol: PROTOCOL_VERSION, type, viewId }), false);
});
test("view protocol is internally scoped and camera decoding is hostile-safe", () => {
const attach = {
protocol: PROTOCOL_VERSION,
type: "view.attach",
id: "request",
viewId: "internal-view",
viewport,
camera: { center: { x: 3, y: -4 }, zoom: 2 },
};
assert.equal(validRequest(attach), true);
assert.equal(validRequest({ ...attach, viewId: undefined }), false);
assert.equal(validRequest({ ...attach, camera: undefined }), false);
assert.equal(validRequest({ ...attach, callerViewId: "public" }), false);
for (const id of [undefined, "", 1, "x".repeat(513)]) assert.equal(validRequest({ ...attach, viewId: id }), false);
assert.equal(validRequest({ ...attach, extra: true }), false);
assert.equal(validRequest({ protocol: PROTOCOL_VERSION, type: "view.detach", id: "request", viewId }), true);
assert.equal(
validRequest({ protocol: PROTOCOL_VERSION, type: "view.detach", id: "request", viewId, extra: true }),
false,
);
assert.deepEqual(decodeFxNodeCamera(attach.camera), attach.camera);
const frozen = Object.freeze({ center: Object.freeze({ x: 3, y: -4 }), zoom: 0.1 });
assert.deepEqual(decodeFxNodeCamera(frozen), frozen);
assert.deepEqual(decodeFxNodeCamera({ center: { x: 0, y: 0 }, zoom: 4 }), {
center: { x: 0, y: 0 },
zoom: 4,
});
for (const value of [NaN, Infinity, -Infinity]) {
assert.throws(() => decodeFxNodeCamera({ center: { x: value, y: 0 }, zoom: 1 }), TypeError);
assert.throws(() => decodeFxNodeCamera({ center: { x: 0, y: 0 }, zoom: value }), TypeError);
}
assert.throws(
() => decodeFxNodeCamera({ center: { x: 0, y: 0 }, zoom: 1, extra: true } as never),
new TypeError("Invalid FxNode camera"),
);
assert.throws(
() => decodeFxNodeCamera({ center: { x: 0, y: 0 }, zoom: 1, [Symbol()]: true } as never),
new TypeError("Invalid FxNode camera"),
);
let cameraReads = 0;
const accessorCamera = Object.defineProperty({ center: { x: 0, y: 0 } }, "zoom", {
enumerable: true,
get() {
cameraReads++;
return 1;
},
});
assert.throws(() => decodeFxNodeCamera(accessorCamera as never), new TypeError("Invalid FxNode camera"));
assert.equal(cameraReads, 0);
const center = { x: 7, y: 8 };
const detachedCamera = decodeFxNodeCamera({ center, zoom: 1 });
center.x = 99;
assert.deepEqual(detachedCamera, { center: { x: 7, y: 8 }, zoom: 1 });
for (const zoom of [0.099, 4.001])
assert.throws(() => decodeFxNodeCamera({ center: { x: 0, y: 0 }, zoom }), RangeError);
const nested = Proxy.revocable({ x: 0, y: 0 }, {});
nested.revoke();
assert.throws(() => decodeFxNodeCamera({ center: nested.proxy, zoom: 1 }), new TypeError("Invalid FxNode camera"));
const revoked = Proxy.revocable({}, {});
revoked.revoke();
assert.throws(() => decodeFxNodeCamera(revoked.proxy as never), new TypeError("Invalid FxNode camera"));
});
test("view.frame worker messages are exact and view IDs are bounded", () => {
const previous = globalThis.ImageBitmap;
class TestImageBitmap {
readonly width = 1;
readonly height = 1;
}
Object.defineProperty(globalThis, "ImageBitmap", { configurable: true, value: TestImageBitmap });
try {
const frame = {
protocol: PROTOCOL_VERSION,
type: "view.frame",
viewId,
bitmap: new TestImageBitmap(),
renderId: 0,
frameId: 0,
hostGeneration: 0,
surfaceGeneration: 0,
deviceWidth: 1,
deviceHeight: 1,
host: { colorPickerOpen: false },
};
assert.equal(validWorkerMessage(frame), true);
for (const candidate of [
{ ...frame, bitmap: {} },
{ ...frame, frameId: -1 },
{ ...frame, renderId: 0.5 },
{ ...frame, hostGeneration: Number.MAX_SAFE_INTEGER + 1 },
{ ...frame, surfaceGeneration: -1 },
{ ...frame, deviceWidth: 0 },
{ ...frame, deviceHeight: 8193 },
{ ...frame, host: { colorPickerOpen: false, extra: true } },
{ ...frame, extra: true },
])
assert.equal(validWorkerMessage(candidate), false);
for (const invalidViewId of [undefined, "", 1, "x".repeat(513)]) {
const candidate = { ...frame, viewId: invalidViewId };
if (invalidViewId === undefined) delete (candidate as { viewId?: unknown }).viewId;
assert.equal(validWorkerMessage(candidate), false);
}
} finally {
if (previous === undefined) delete (globalThis as { ImageBitmap?: unknown }).ImageBitmap;
else Object.defineProperty(globalThis, "ImageBitmap", { configurable: true, value: previous });
}
});
test("input and viewport decoder tables cover every variant and boundary", () => {
const m0 = { alt: false, control: false, meta: false, shift: false },
m15 = { alt: true, control: true, meta: true, shift: true };
const cases = [
[
{ kind: "focus", phase: "focus" },
{ kind: "focus", phase: "focus" },
],
[
{ kind: "outside-pointer", button: -1 },
{ kind: "outside-pointer", button: -1 },
],
[
{
kind: "pointer",
phase: "move",
pointerId: 1,
pointerType: "pen",
position: { x: -2, y: -3 },
button: -1,
buttons: 0,
modifiers: m0,
},
{
kind: "pointer",
phase: "move",
pointerId: 1,
pointerType: "pen",
position: { x: -2, y: -3 },
button: -1,
buttons: 0,
modifiers: 0,
},
],
[
{ kind: "wheel", position: { x: -1, y: -2 }, delta: { x: -3, y: 4 }, modifiers: m15 },
{ kind: "wheel", position: { x: -1, y: -2 }, delta: { x: -3, y: 4 }, modifiers: 15 },
],
[
{ kind: "key", phase: "up", key: "A", code: "KeyA", repeat: false, modifiers: m15 },
{ kind: "key", phase: "up", key: "A", code: "KeyA", repeat: false, modifiers: 15 },
],
] as const;
for (const [input, expected] of cases) assert.deepEqual(decodeFxNodeInput(input as never), expected);
for (const value of [
{ width: 0, height: 8192, dpr: Number.MIN_VALUE },
{ width: 8192, height: 0, dpr: 1 },
{ width: 4096, height: 4096, dpr: 1 },
])
assert.deepEqual(decodeFxNodeViewport(value), value);
assert.deepEqual(decodeFxNodeViewport({ width: 0, height: 0, dpr: 4 }), { width: 0, height: 0, dpr: 4 });
assert.deepEqual(fxNodeDevicePixels(0, 0, 1), { width: 1, height: 1 });
assert.deepEqual(fxNodeDevicePixels(0.49, 0.5, 1), { width: 1, height: 1 });
assert.deepEqual(fxNodeDevicePixels(1.5, 2.5, 1), { width: 2, height: 3 });
assert.equal(fxNodeDevicePixels(4097, 4096, 1), undefined);
for (const value of [
{ width: -1, height: 1, dpr: 1 },
{ width: 8193, height: 0, dpr: 1 },
{ width: 4097, height: 4096, dpr: 1 },
{ width: 1, height: 1, dpr: 0 },
])
assert.throws(() => decodeFxNodeViewport(value), RangeError);
});
test("protocol tables reject malformed pointer fences", () => {
const base = { protocol: PROTOCOL_VERSION, type: "view.pointer.flush", viewId },
move = {
kind: "pointer",
phase: "move",
pointerId: 1,
pointerType: "mouse",
position: { x: 0, y: 0 },
button: 0,
buttons: 0,
modifiers: 0,
};
assert.equal(
validRequest({ ...base, pointerFence: { generation: 0, before: { sequence: 1, hostGeneration: 2, event: move } } }),
true,
);
for (const pointerFence of [
{},
{ generation: 0.5 },
{ generation: 0, extra: true },
{ generation: 0, before: {} },
{ generation: 0, before: { sequence: 1, event: move } },
{ generation: 0, before: { sequence: 1, hostGeneration: -1, event: move } },
{ generation: 0, before: { sequence: 1, hostGeneration: 0, event: { ...move, phase: "down" } } },
{ generation: 0, before: { sequence: 1, hostGeneration: 0, event: { ...move, position: { x: Infinity, y: 0 } } } },
])
assert.equal(validRequest({ ...base, pointerFence }), false);
});
test("host generations are required, exact, and nonnegative", () => {
const event = { kind: "focus", phase: "focus" },
input = { protocol: PROTOCOL_VERSION, type: "view.input", viewId, event, hostGeneration: 0 },
viewportRequest = {
protocol: PROTOCOL_VERSION,
type: "view.viewport",
id: "resize",
viewId,
viewport,
expectedSurfaceGeneration: 1,
hostGeneration: 0,
};
for (const value of [input, viewportRequest]) assert.equal(validRequest(value), true);
for (const generation of [undefined, -1, 0.5]) {
const candidate = { ...input, ...(generation === undefined ? {} : { hostGeneration: generation }) };
if (generation === undefined) delete (candidate as { hostGeneration?: unknown }).hostGeneration;
assert.equal(validRequest(candidate), false);
}
assert.equal(validRequest({ ...input, extra: true }), false);
assert.equal(validRequest({ ...viewportRequest, hostGeneration: -1 }), false);
assert.equal(validRequest({ ...viewportRequest, pointerFence: { generation: 1 } }), true);
assert.equal(validRequest({ ...viewportRequest, pointerFence: { generation: 0.5 } }), false);
for (const expectedSurfaceGeneration of [-1, 0.5, Number.MAX_SAFE_INTEGER + 1])
assert.equal(validRequest({ ...viewportRequest, expectedSurfaceGeneration }), false);
const render = { protocol: PROTOCOL_VERSION, type: "view.render", viewId, renderId: 1, hostGeneration: 0 };
assert.equal(validRequest(render), true);
for (const renderId of [-1, 0.5, Number.MAX_SAFE_INTEGER + 1])
assert.equal(validRequest({ ...render, renderId }), false);
const consumed = { protocol: PROTOCOL_VERSION, type: "view.frame.consumed", viewId, frameId: 0 };
assert.equal(validRequest(consumed), true);
assert.equal(validRequest({ ...consumed, extra: true }), false);
for (const frameId of [-1, 0.5, Number.MAX_SAFE_INTEGER + 1])
assert.equal(validRequest({ ...consumed, frameId }), false);
});
test("resource open correlation is exact on input and worker messages", () => {
const event = {
kind: "pointer",
phase: "down",
pointerId: 1,
pointerType: "mouse",
position: { x: 2, y: 3 },
button: 0,
buttons: 1,
modifiers: 0,
},
input = {
protocol: PROTOCOL_VERSION,
type: "view.input",
viewId,
event,
hostGeneration: 0,
resourceOpenRequestId: "open-1",
},
resource = {
id: "image",
kind: "image",
title: "Image",
openTitle: "Open image",
accept: ["image/png"],
maxBytes: 1024,
maxWidth: 4,
maxHeight: 4,
maxPixels: 16,
},
open = {
protocol: PROTOCOL_VERSION,
type: "view.resource.open",
viewId,
requestId: "open-1",
authorization: { viewId, token: "node:parameter:image", graphVersion: 2, compositionRevision: 3 },
resource,
};
assert.equal(validRequest(input), true);
assert.equal(validWorkerMessage(open), true);
assert.equal(validWorkerMessage({ ...open, authorization: { ...open.authorization, viewId: "other" } }), true);
assert.equal(validWorkerMessage({ ...open, authorization: { ...open.authorization, extra: true } }), false);
assert.equal(validWorkerMessage({ ...open, authorization: { ...open.authorization, viewId: "" } }), false);
for (const requestId of ["", "x".repeat(513), 1]) {
assert.equal(validRequest({ ...input, resourceOpenRequestId: requestId }), false);
assert.equal(validWorkerMessage({ ...open, requestId }), false);
}
assert.equal(validRequest({ ...input, extra: true }), false);
assert.equal(validWorkerMessage({ ...open, bounds: { x: 0, y: 0, width: 1, height: 1 } }), false);
assert.equal(validWorkerMessage({ ...open, authorization: { ...open.authorization, extra: true } }), false);
assert.equal(validWorkerMessage({ ...open, resource: { ...resource, accept: ["image/png"], extra: true } }), false);
});
test("public host values decode once without getters and detach nested input", () => {
let reads = 0;
const getter = Object.defineProperty({ kind: "focus" }, "phase", {
enumerable: true,
get() {
reads++;
return "focus";
},
});
assert.throws(() => decodeFxNodeInput(getter as never), new TypeError("Invalid FxNode input"));
assert.equal(reads, 0);
const position = { x: 1, y: 2 },
mods = { alt: true, control: false, meta: true, shift: false },
input = {
kind: "pointer",
phase: "down",
pointerId: 1,
pointerType: "mouse",
position,
button: 0,
buttons: 1,
modifiers: mods,
} as const,
wire = decodeFxNodeInput(input);
position.x = 9;
mods.control = true;
assert.deepEqual(wire, {
kind: "pointer",
phase: "down",
pointerId: 1,
pointerType: "mouse",
position: { x: 1, y: 2 },
button: 0,
buttons: 1,
modifiers: 5,
});
assert.throws(
() => decodeFxNodeInput({ ...input, [Symbol()]: true } as never),
new TypeError("Invalid FxNode input"),
);
assert.throws(
() => decodeFxNodeViewport({ width: Infinity, height: 1, dpr: 1 }),
new TypeError("Invalid FxNode viewport"),
);
assert.throws(
() => decodeFxNodeViewport({ width: 8192, height: 8192, dpr: 1 }),
new RangeError("FxNode viewport is outside supported bounds"),
);
});
test("public action values reject accessors, symbols, and hostile proxies", () => {
assert.deepEqual(decodeFxNodeActionOptions({}), { kind: "current" });
let reads = 0;
const options = Object.defineProperty({}, "expectedVersion", {
enumerable: true,
get() {
reads++;
return 1;
},
});
const params = Object.defineProperty({ typeId: "test", viewPosition: { x: 1, y: 2 } }, "nodeId", {
enumerable: true,
get() {
reads++;
return "node";
},
});
assert.throws(() => decodeFxNodeActionOptions(options), new TypeError("Invalid action options"));
assert.throws(() => decodeFxNodeAddNodeParams(params), new TypeError("Invalid add-node parameters"));
assert.equal(reads, 0);
assert.throws(
() => decodeFxNodeAddNodeParams({ typeId: "test", viewPosition: { x: 1, y: 2 }, [Symbol()]: true }),
new TypeError("Invalid add-node parameters"),
);
const revoked = Proxy.revocable({}, {});
revoked.revoke();
assert.throws(() => decodeFxNodeActionOptions(revoked.proxy), new TypeError("Invalid action options"));
assert.throws(() => decodeFxNodeAddNodeParams(revoked.proxy), new TypeError("Invalid add-node parameters"));
});
test("protocol validators are total and bound request and node type ids", () => {
const throwing = new Proxy(
{},
{
get() {
throw new Error("hostile");
},
ownKeys() {
throw new Error("hostile");
},
},
);
const target = {};
const revoked = Proxy.revocable(target, {});
revoked.revoke();
for (const value of [throwing, revoked.proxy]) {
assert.doesNotThrow(() => validRequest(value));
assert.equal(validRequest(value), false);
assert.doesNotThrow(() => validWorkerMessage(value));
assert.equal(validWorkerMessage(value), false);
}
assert.equal(validRequest({ protocol: PROTOCOL_VERSION, type: "state.get", id: "x".repeat(513) }), false);
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "node.add-at-view",
id: "r",
nodeId: "n",
nodeType: "x".repeat(129),
viewPosition: { x: 0, y: 0 },
}),
false,
);
// The live command wire format retains its historical 512-code-unit node type bound.
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "command",
id: "r",
expected: { kind: "current" },
command: { type: "node.add", nodeId: "n", nodeType: "x".repeat(129), position: { x: 0, y: 0 } },
}),
true,
);
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "command",
id: "r",
expected: { kind: "current" },
command: { type: "node.add", nodeId: "n", nodeType: "x".repeat(513), position: { x: 0, y: 0 } },
}),
false,
);
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "command",
id: "r",
expected: { kind: "current" },
command: Object.create({ type: "undo" }),
}),
false,
);
assert.equal(
validRequest({ protocol: PROTOCOL_VERSION, type: "load", id: "r", data: {}, expected: { kind: "current" } }),
true,
);
assert.equal(
validRequest({ protocol: PROTOCOL_VERSION, type: "load", id: "r", layout: {}, expected: { kind: "current" } }),
false,
);
assert.equal(validRequest({ protocol: PROTOCOL_VERSION, type: "save.data", id: "r" }), true);
assert.equal(validRequest({ protocol: PROTOCOL_VERSION, type: "save.data", id: "r", extra: true }), false);
assert.equal(validRequest(Object.create({ protocol: PROTOCOL_VERSION, type: "state.get", id: "r" })), false);
assert.equal(validRequest({ protocol: PROTOCOL_VERSION, type: "snapshot", id: "r" }), false);
assert.equal(validRequest({ protocol: PROTOCOL_VERSION, type: "state.get", id: "r" }), true);
let inspected = 0;
const opaque = Object.defineProperty({}, "nodes", {
enumerable: true,
get() {
inspected++;
throw new Error("payload inspected");
},
});
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "state.set",
id: "r",
state: opaque,
expected: { kind: "current" },
}),
true,
);
assert.equal(inspected, 0);
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "state.set",
id: "r",
state: {},
expected: { kind: "current" },
extra: true,
}),
false,
);
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "view.resource.set",
viewId,
id: "r",
authorization: { viewId, token: "t", graphVersion: 2, compositionRevision: 0 },
resource: { name: "x.png", mime: "image/png", bytes: new ArrayBuffer(1) },
expected: { kind: "current" },
}),
true,
);
assert.equal(
validRequest(
Object.assign(Object.create({ event: { kind: "focus", phase: "focus" } }), {
protocol: PROTOCOL_VERSION,
type: "view.input",
viewId,
}),
),
false,
);
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "view.pointer.flush",
viewId,
pointerFence: Object.create({ generation: 0 }),
}),
false,
);
});
test("action requests, selection projections, menu results, and receipts are exact", () => {
const current = { kind: "current" as const };
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "view.node.add",
viewId,
id: "r",
nodeId: "n",
typeId: "t",
viewPosition: { x: 1, y: 2 },
expected: current,
}),
true,
);
assert.equal(
validRequest({ protocol: PROTOCOL_VERSION, type: "view.selection.remove", viewId, id: "r", expected: current }),
true,
);
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "view.selection.remove",
viewId,
id: "r",
expected: current,
value: false,
}),
false,
);
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "view.selection.mute",
viewId,
id: "r",
expected: current,
value: false,
}),
true,
);
assert.equal(
validRequest({ protocol: PROTOCOL_VERSION, type: "view.selection.mute", viewId, id: "r", expected: current }),
false,
);
const selection = (nodeCount: number, linkCount: number, canRemove: boolean) => ({
protocol: PROTOCOL_VERSION,
type: "view.selection.host",
viewId,
projection: { nodeCount, linkCount, canRemove, mute: { enabled: false } },
});
assert.equal(validWorkerMessage(selection(0, 0, false)), true);
assert.equal(validWorkerMessage(selection(1, 0, true)), true);
assert.equal(validWorkerMessage(selection(0, 0, true)), false);
assert.equal(validWorkerMessage(selection(1, 0, false)), false);
assert.equal(
validWorkerMessage({
protocol: PROTOCOL_VERSION,
type: "view.node-menu.result",
viewId,
requestId: "r",
open: false,
}),
true,
);
assert.equal(
validWorkerMessage({
protocol: PROTOCOL_VERSION,
type: "view.node-menu.result",
viewId,
requestId: "r",
open: true,
compositionRevision: 0,
viewPosition: { x: 1, y: 2 },
}),
true,
);
assert.equal(
validWorkerMessage({
protocol: PROTOCOL_VERSION,
type: "view.node-menu.result",
viewId,
requestId: "r",
open: false,
viewPosition: { x: 1, y: 2 },
}),
false,
);
assert.equal(validCommandReceipt({ status: "noop", version: 0 }), true);
assert.equal(validCommandReceipt({ status: "committed", version: 1 }), true);
assert.equal(validCommandReceipt({ status: "noop", version: 0, extra: true }), false);
});
test("resource DTO and wire decoding is exact, bounded, and hostile-safe", () => {
const authorization = { viewId, token: "control", graphVersion: 2, compositionRevision: 3 },
bytes = new ArrayBuffer(4),
resource = { name: "pixel.png", mime: "image/png", bytes };
assert.deepEqual(decodeFxNodeResourceAuthorization(authorization), authorization);
assert.throws(
() => decodeFxNodeResourceAuthorization({ ...authorization, viewId: "" }),
new TypeError("Invalid resource authorization"),
);
assert.throws(
() => decodeFxNodeResourceAuthorization({ ...authorization, extra: true }),
new TypeError("Invalid resource authorization"),
);
assert.equal(decodeFxNodeResourceData(resource).bytes, bytes);
let reads = 0;
const accessor = Object.defineProperty({ token: "control", graphVersion: 2 }, "compositionRevision", {
enumerable: true,
get() {
reads++;
return 3;
},
});
assert.throws(() => decodeFxNodeResourceAuthorization(accessor), new TypeError("Invalid resource authorization"));
assert.equal(reads, 0);
assert.throws(
() => decodeFxNodeResourceData({ ...resource, [Symbol()]: true }),
new TypeError("Invalid resource data"),
);
assert.throws(
() => decodeFxNodeResourceData({ ...resource, bytes: new ArrayBuffer(0) }),
new RangeError("Resource data is outside supported bounds"),
);
const request = {
protocol: PROTOCOL_VERSION,
type: "view.resource.set",
viewId,
id: "r",
authorization,
resource,
expected: { kind: "exact", version: 2 },
pointerFence: { generation: 1 },
};
assert.equal(validRequest(request), true);
assert.equal(validRequest({ ...request, authorization: { ...authorization, extra: true } }), false);
assert.equal(validRequest({ ...request, resource: { ...resource, name: "bad\nname" } }), false);
assert.equal(validRequest({ ...request, expected: { kind: "exact", version: -1 } }), false);
const nested = Proxy.revocable({ x: 1, y: 2 }, {});
nested.revoke();
assert.throws(
() => decodeFxNodeAddNodeParams({ typeId: "test", viewPosition: nested.proxy }),
new TypeError("Invalid add-node parameters"),
);
const bytesProxy = Proxy.revocable(new ArrayBuffer(4), {});
bytesProxy.revoke();
assert.throws(
() => decodeFxNodeResourceData({ ...resource, bytes: bytesProxy.proxy }),
new TypeError("Invalid resource data"),
);
});
test("malformed composition init remains recognizable protocol", () => {
const init = {
protocol: PROTOCOL_VERSION,
type: "init",
id: "init",
applicationId: "app",
applicationVersion: 1,
resources: {},
historyLimit: 0,
};
assert.equal(validRequest(init), true);
for (const presentation of [
{ viewport },
{ camera: { center: { x: 0, y: 0 }, zoom: 1 } },
{ pointerLane: new SharedArrayBuffer(32) },
])
assert.equal(validRequest({ ...init, ...presentation }), false);
assert.equal(
validRequest({
protocol: PROTOCOL_VERSION,
type: "init",
id: "init",
composition: {},
layout: null,
historyLimit: 0,
}),
false,
);
assert.equal(
validWorkerMessage({
protocol: PROTOCOL_VERSION,
type: "response",
id: "init",
ok: false,
error: {
code: "composition.invalid",
message: "Invalid composition",
issues: [{ code: "composition.schema", path: "/", message: "invalid" }],
},
}),
true,
);
assert.equal(
validWorkerMessage({
protocol: PROTOCOL_VERSION,
type: "response",
id: "init",
ok: false,
error: { code: "link.endpoint", message: "Missing endpoint", path: "/links/stale" },
}),
true,
);
assert.equal(
validWorkerMessage({
protocol: PROTOCOL_VERSION,
type: "response",
id: "init",
ok: false,
error: { code: "link.endpoint", message: "Missing endpoint", path: "/" + "x".repeat(513) },
}),
false,
);
assert.equal(
validWorkerMessage(Object.create({ protocol: PROTOCOL_VERSION, type: "response", id: "init", ok: true })),
false,
);
assert.equal(
validWorkerMessage({
protocol: PROTOCOL_VERSION,
type: "response",
id: "init",
ok: false,
error: Object.create({ code: "worker.error", message: "inherited" }),
}),
false,
);
});
test("live composition protocol validates updates, revisions, receipts and events", () => {
const request = (update: unknown, expected: unknown = { kind: "current" }) => ({
protocol: PROTOCOL_VERSION,
type: "composition.update",
id: "request",
expected,
update,
});
for (const update of [
{ kind: "composition.load", composition: {} },
{ kind: "theme.set", theme: {} },
{ kind: "compatibility.set", compatibility: { wildcardInputTypes: [] } },
{ kind: "socket.compose", id: "signal", definition: {} },
{ kind: "socket.remove", id: "signal" },
{ kind: "node.compose", id: "source", definition: {} },
{ kind: "node.remove", id: "source" },
])
assert.equal(validRequest(request(update)), true);
assert.equal(validRequest(request({ kind: "composition.load", composition: {}, extra: true })), false);
for (const expected of [
{ kind: "exact", revision: -1 },
{ kind: "exact", revision: 0.5 },
{ kind: "exact", revision: Number.MAX_SAFE_INTEGER + 1 },
])
assert.equal(validRequest(request({ kind: "node.remove", id: "source" }, expected)), false);
for (const id of ["", "x".repeat(129), "bad\nvalue", "__proto__", "prototype", "constructor"])
assert.equal(validRequest(request({ kind: "node.remove", id })), false);
assert.equal(validRequest({ ...request({ kind: "node.remove", id: "source" }), extra: true }), false);
assert.equal(validRequest(request({ kind: "node.remove", id: "source", extra: true })), false);
assert.equal(
validCompositionReceipt({
status: "committed",
revision: 1,
graphVersion: 0,
graphChanged: false,
historyReset: true,
}),
true,
);
assert.equal(
validCompositionReceipt({ status: "noop", revision: 0, graphVersion: 0, graphChanged: false, historyReset: false }),
true,
);
assert.equal(
validCompositionReceipt(
Object.create({ status: "noop", revision: 0, graphVersion: 0, graphChanged: false, historyReset: false }),
),
false,
);
assert.equal(
validCompositionReceipt({ status: "noop", revision: 0, graphVersion: 0, graphChanged: true, historyReset: false }),
false,
);
assert.equal(
validCompositionReceipt({
status: "committed",
revision: 1,
graphVersion: 0,
graphChanged: false,
historyReset: false,
}),
false,
);
assert.equal(
validWorkerMessage({
protocol: PROTOCOL_VERSION,
type: "composition.event",
envelope: {
baseRevision: 0,
revision: 1,
change: { kind: "node.compose", id: "source" },
baseGraphVersion: 2,
graphVersion: 3,
graphChanged: true,
historyReset: true,
},
}),
true,
);
assert.equal(
validWorkerMessage({
protocol: PROTOCOL_VERSION,
type: "composition.event",
envelope: {
baseRevision: 0,
revision: 2,
change: { kind: "theme.set" },
baseGraphVersion: 2,
graphVersion: 2,
graphChanged: false,
historyReset: true,
},
}),
false,
);
});