2115 lines
73 KiB
JavaScript
2115 lines
73 KiB
JavaScript
import assert from "node:assert/strict";
|
||
import { readFile } from "node:fs/promises";
|
||
import { after, before, test } from "node:test";
|
||
|
||
import { createElement } from "react";
|
||
import { renderToStaticMarkup } from "react-dom/server";
|
||
import { createServer } from "vite";
|
||
|
||
let server;
|
||
let xgridsK1Manifest;
|
||
let xgridsK1ObservationSources;
|
||
let openObservationSource;
|
||
let shouldRestartObservationSource;
|
||
let consumeCameraLeaseRetry;
|
||
let resetCameraLeaseRetryBudget;
|
||
let cameraTransportRecoveryIsCurrent;
|
||
let cameraTransportCanOpen;
|
||
let cameraPendingQueueCanAccept;
|
||
let cameraTransportCloseRecoveryMessage;
|
||
let cameraPlaybackRecoveryFailureStage;
|
||
let createCameraStartupWatchdog;
|
||
let cameraStartupWatchdogRecoveryMessage;
|
||
let CAMERA_FIRST_MEDIA_TIMEOUT_MS;
|
||
let CAMERA_FIRST_PLAYABLE_FRAME_TIMEOUT_MS;
|
||
let liveCameraPlaybackAuthorityIdentity;
|
||
let initialCameraPlaybackRecoveryState;
|
||
let reduceCameraPlaybackRecovery;
|
||
let cameraTransportCallbackIsCurrent;
|
||
let cameraBrowserTransportIdentity;
|
||
let liveRerunRecoveryAuthorityIdentity;
|
||
let initialObservationWindowRect;
|
||
let FloatingObservationWindow;
|
||
let ObservationTimeline;
|
||
let shouldCaptureWorkspacePointer;
|
||
let normalizeTimelineRange;
|
||
let timelineOffsetSeconds;
|
||
let formatTimelineDuration;
|
||
let normalizeAccumulationSeconds;
|
||
let formatAccumulationDuration;
|
||
let resolveRerunSourceUrl;
|
||
let resolveRecordedBlueprintUrl;
|
||
let fetchRecordedBlueprintRrd;
|
||
let recordedCameraJournalContract;
|
||
let resolveRecordedPerceptionUrl;
|
||
let fetchRecordedPerceptionRrd;
|
||
let resolveRecordedPerceptionViewerSourceUrl;
|
||
let probeRecordedPerceptionViewerSource;
|
||
let resolveRecordedPointColorsUrl;
|
||
let fetchRecordedPointColorsRrd;
|
||
let recordedPointColorKey;
|
||
let isRecordedPlaybackFullyBuffered;
|
||
let recordedObservationSources;
|
||
let selectRecordedMediaEpoch;
|
||
let recordedMediaLocalTime;
|
||
|
||
before(async () => {
|
||
server = await createServer({
|
||
appType: "custom",
|
||
logLevel: "silent",
|
||
server: { middlewareMode: true },
|
||
});
|
||
({ xgridsK1Manifest } = await server.ssrLoadModule(
|
||
"@xgrids-k1/frontend/manifest.ts",
|
||
));
|
||
({ xgridsK1ObservationSources } = await server.ssrLoadModule(
|
||
"@xgrids-k1/frontend/observationSources.ts",
|
||
));
|
||
({ openObservationSource, shouldRestartObservationSource } = await server.ssrLoadModule(
|
||
"/src/core/observation/layoutPolicy.ts",
|
||
));
|
||
({
|
||
consumeCameraLeaseRetry,
|
||
resetCameraLeaseRetryBudget,
|
||
cameraTransportRecoveryIsCurrent,
|
||
cameraTransportCanOpen,
|
||
cameraPendingQueueCanAccept,
|
||
cameraTransportCloseRecoveryMessage,
|
||
cameraPlaybackRecoveryFailureStage,
|
||
createCameraStartupWatchdog,
|
||
cameraStartupWatchdogRecoveryMessage,
|
||
CAMERA_FIRST_MEDIA_TIMEOUT_MS,
|
||
CAMERA_FIRST_PLAYABLE_FRAME_TIMEOUT_MS,
|
||
} = await server.ssrLoadModule("/src/components/MseFmp4WebSocketPlayer.tsx"));
|
||
({
|
||
liveCameraPlaybackAuthorityIdentity,
|
||
initialCameraPlaybackRecoveryState,
|
||
reduceCameraPlaybackRecovery,
|
||
cameraTransportCallbackIsCurrent,
|
||
cameraBrowserTransportIdentity,
|
||
} = await server.ssrLoadModule(
|
||
"/src/core/observation/liveCameraRecovery.ts",
|
||
));
|
||
({
|
||
FloatingObservationWindow,
|
||
initialObservationWindowRect,
|
||
shouldCaptureWorkspacePointer,
|
||
} = await server.ssrLoadModule("/src/components/FloatingObservationWindow.tsx"));
|
||
({ liveRerunRecoveryAuthorityIdentity } = await server.ssrLoadModule(
|
||
"/src/core/observation/liveReceiverWatchdog.ts",
|
||
));
|
||
({
|
||
ObservationTimeline,
|
||
normalizeTimelineRange,
|
||
timelineOffsetSeconds,
|
||
formatTimelineDuration,
|
||
normalizeAccumulationSeconds,
|
||
formatAccumulationDuration,
|
||
} =
|
||
await server.ssrLoadModule("/src/components/ObservationTimeline.tsx"));
|
||
({
|
||
resolveRerunSourceUrl,
|
||
resolveRecordedBlueprintUrl,
|
||
fetchRecordedBlueprintRrd,
|
||
recordedCameraJournalContract,
|
||
resolveRecordedPerceptionUrl,
|
||
fetchRecordedPerceptionRrd,
|
||
resolveRecordedPerceptionViewerSourceUrl,
|
||
probeRecordedPerceptionViewerSource,
|
||
resolveRecordedPointColorsUrl,
|
||
fetchRecordedPointColorsRrd,
|
||
recordedPointColorKey,
|
||
isRecordedPlaybackFullyBuffered,
|
||
} = await server.ssrLoadModule(
|
||
"/src/components/RerunViewport.tsx",
|
||
));
|
||
({ recordedObservationSources } = await server.ssrLoadModule(
|
||
"/src/core/observation/recordedObservationSources.ts",
|
||
));
|
||
({ selectRecordedMediaEpoch, recordedMediaLocalTime } = await server.ssrLoadModule(
|
||
"/src/components/RecordedFmp4Player.tsx",
|
||
));
|
||
});
|
||
|
||
test("follow toggles retain the current recorded camera journal", () => {
|
||
const before = recordedCameraJournalContract({
|
||
activeView: "spatial", viewResetGeneration: 0, planView: false, followTrajectory: false,
|
||
});
|
||
const afterFollowToggle = recordedCameraJournalContract({
|
||
activeView: "spatial", viewResetGeneration: 0, planView: false, followTrajectory: true,
|
||
});
|
||
const afterPlanToggle = recordedCameraJournalContract({
|
||
activeView: "spatial", viewResetGeneration: 0, planView: true, followTrajectory: true,
|
||
});
|
||
const afterReset = recordedCameraJournalContract({
|
||
activeView: "spatial", viewResetGeneration: 1, planView: false, followTrajectory: false,
|
||
});
|
||
|
||
assert.equal(afterFollowToggle, before);
|
||
assert.notEqual(afterPlanToggle, before);
|
||
assert.notEqual(afterReset, before);
|
||
});
|
||
|
||
test("observation camera windows tile from the bottom-left above the live timeline", () => {
|
||
const bounds = { width: 1280, height: 720 };
|
||
const left = initialObservationWindowRect(0, 2, bounds);
|
||
const right = initialObservationWindowRect(1, 2, bounds);
|
||
|
||
assert.equal(left.y, right.y);
|
||
assert.ok(left.x + left.width < right.x);
|
||
assert.equal(left.x, 18);
|
||
assert.ok(right.x + right.width <= bounds.width - 18);
|
||
assert.ok(right.y + right.height <= bounds.height - 64);
|
||
});
|
||
|
||
test("live camera window keeps its player mounted before viewport measurement", () => {
|
||
const source = {
|
||
id: "k1:sensor.camera.right",
|
||
sourceId: "sensor.camera.right",
|
||
label: "Правая камера",
|
||
description: "Live camera",
|
||
modality: "video",
|
||
availability: "streaming",
|
||
transport: "websocket",
|
||
endpointLabel: "Локальный видеопоток",
|
||
previewUrl: null,
|
||
delivery: {
|
||
id: "camera-preview-4",
|
||
kind: "mse-fmp4-websocket",
|
||
url: "/camera-preview/4",
|
||
mediaType: 'video/mp4; codecs="avc1.641028"',
|
||
},
|
||
activation: {
|
||
groupId: "k1:device-session:camera.preview.decoder",
|
||
maxActive: 1,
|
||
selected: true,
|
||
controllable: false,
|
||
},
|
||
binding: {
|
||
deviceId: "k1-a",
|
||
deviceSessionId: "device-session",
|
||
acquisitionId: "acquisition-quick",
|
||
},
|
||
capabilities: {
|
||
defaultVisible: true,
|
||
overlay: true,
|
||
resizable: true,
|
||
timelineMode: "live-only",
|
||
},
|
||
};
|
||
const noop = () => undefined;
|
||
const markup = renderToStaticMarkup(createElement(FloatingObservationWindow, {
|
||
source,
|
||
index: 0,
|
||
count: 1,
|
||
boundsRef: { current: null },
|
||
maximized: false,
|
||
active: true,
|
||
onRectChange: noop,
|
||
onMaximizedChange: noop,
|
||
onActivate: noop,
|
||
onClose: noop,
|
||
}));
|
||
|
||
assert.match(markup, /<video/);
|
||
assert.match(markup, /Правая камера/);
|
||
});
|
||
|
||
test("observation camera windows stack without overlap when the viewport is narrow", () => {
|
||
const bounds = { width: 420, height: 700 };
|
||
const lower = initialObservationWindowRect(0, 2, bounds);
|
||
const upper = initialObservationWindowRect(1, 2, bounds);
|
||
|
||
assert.equal(lower.x, upper.x);
|
||
assert.ok(upper.y + upper.height < lower.y);
|
||
for (const rect of [lower, upper]) {
|
||
assert.ok(rect.x >= 0 && rect.y >= 0);
|
||
assert.ok(rect.x + rect.width <= bounds.width);
|
||
assert.ok(rect.y + rect.height <= bounds.height - 64);
|
||
}
|
||
});
|
||
|
||
test("observation camera tiling remains in bounds on a constrained viewport", () => {
|
||
const bounds = { width: 260, height: 280 };
|
||
const rects = Array.from({ length: 2 }, (_, index) => (
|
||
initialObservationWindowRect(index, 2, bounds)
|
||
));
|
||
|
||
for (const rect of rects) {
|
||
assert.ok(rect.width > 0 && rect.height > 0);
|
||
assert.ok(rect.x >= 0 && rect.y >= 0);
|
||
assert.ok(rect.x + rect.width <= bounds.width);
|
||
assert.ok(rect.y + rect.height <= bounds.height - 64);
|
||
}
|
||
assert.ok(rects[1].y + rects[1].height <= rects[0].y);
|
||
});
|
||
|
||
test("floating observation interaction captures resize and movable header pointers", () => {
|
||
const target = (matches) => ({ closest: (selector) => matches.includes(selector) });
|
||
assert.equal(
|
||
shouldCaptureWorkspacePointer(0, target([".nodedc-workspace-window__resize"])),
|
||
true,
|
||
);
|
||
assert.equal(
|
||
shouldCaptureWorkspacePointer(0, target([".nodedc-workspace-window__head"])),
|
||
true,
|
||
);
|
||
assert.equal(
|
||
shouldCaptureWorkspacePointer(
|
||
0,
|
||
target([".nodedc-workspace-window__head", "button, input, select, textarea, a"]),
|
||
),
|
||
false,
|
||
);
|
||
assert.equal(
|
||
shouldCaptureWorkspacePointer(2, target([".nodedc-workspace-window__resize"])),
|
||
false,
|
||
);
|
||
});
|
||
|
||
test("live camera registers sourceopen before assigning the MSE object URL", async () => {
|
||
const source = await readFile(
|
||
new URL("../src/components/MseFmp4WebSocketPlayer.tsx", import.meta.url),
|
||
"utf8",
|
||
);
|
||
const listenerIndex = source.indexOf(
|
||
'mediaSource.addEventListener("sourceopen", onSourceOpen, { once: true });',
|
||
);
|
||
const sourceAssignmentIndex = source.indexOf("video.src = objectUrl;");
|
||
|
||
assert.ok(listenerIndex >= 0, "sourceopen listener must remain explicit");
|
||
assert.ok(sourceAssignmentIndex >= 0, "MediaSource object URL assignment must remain explicit");
|
||
assert.ok(
|
||
listenerIndex < sourceAssignmentIndex,
|
||
"sourceopen listener must exist before video.src can open the MediaSource",
|
||
);
|
||
});
|
||
|
||
test("live camera reports each browser startup boundary without device actions", async () => {
|
||
const source = await readFile(
|
||
new URL("../src/components/MseFmp4WebSocketPlayer.tsx", import.meta.url),
|
||
"utf8",
|
||
);
|
||
for (const eventCode of [
|
||
"live_camera_player_effect_started",
|
||
"live_camera_playback_authority_missing",
|
||
"live_camera_media_source_open",
|
||
"live_camera_websocket_open",
|
||
]) {
|
||
assert.ok(
|
||
source.includes(`eventCode: "${eventCode}"`),
|
||
`missing bounded player diagnostic ${eventCode}`,
|
||
);
|
||
}
|
||
});
|
||
|
||
test("recorded observation timeline clamps relative seek time without epoch precision in the UI", () => {
|
||
const range = normalizeTimelineRange({
|
||
min: 0,
|
||
max: 12_500_000_000,
|
||
});
|
||
assert.ok(range);
|
||
assert.equal(timelineOffsetSeconds(range, range.min + 2_250_000_000), 2.25);
|
||
assert.equal(timelineOffsetSeconds(range, range.min - 1), 0);
|
||
assert.equal(timelineOffsetSeconds(range, range.max + 1), 12.5);
|
||
assert.equal(formatTimelineDuration(62.125), "01:02.125");
|
||
});
|
||
|
||
test("recorded observation timeline rejects empty and non-finite ranges", () => {
|
||
assert.equal(normalizeTimelineRange(null), null);
|
||
assert.equal(normalizeTimelineRange({ min: 10, max: 10 }), null);
|
||
assert.equal(normalizeTimelineRange({ min: Number.NaN, max: 10 }), null);
|
||
});
|
||
|
||
test("accumulation control normalizes UI values and distinguishes a single frame", () => {
|
||
assert.equal(normalizeAccumulationSeconds(-3), 0);
|
||
assert.equal(normalizeAccumulationSeconds(12.6), 13);
|
||
assert.equal(normalizeAccumulationSeconds(999), 999);
|
||
assert.equal(normalizeAccumulationSeconds(1800), 1800);
|
||
assert.equal(normalizeAccumulationSeconds(9999), 9999);
|
||
assert.equal(normalizeAccumulationSeconds(Number.NaN), 0);
|
||
assert.equal(formatAccumulationDuration(0), "Кадр");
|
||
assert.equal(formatAccumulationDuration(12), "12 с");
|
||
assert.equal(formatAccumulationDuration(1800), "30 мин");
|
||
assert.equal(formatAccumulationDuration(125), "2 мин 5 с");
|
||
});
|
||
|
||
test("spatial timeline renders synchronized accumulation and playback controls", () => {
|
||
const markup = renderToStaticMarkup(createElement(ObservationTimeline, {
|
||
active: true,
|
||
sourceCount: 2,
|
||
mode: "recorded",
|
||
seekable: true,
|
||
rangeNs: { min: 0, max: 20_000_000_000 },
|
||
currentNs: 5_000_000_000,
|
||
accumulationSeconds: 12,
|
||
onAccumulationChange: () => undefined,
|
||
onAccumulationCommit: () => undefined,
|
||
onSeek: () => undefined,
|
||
}));
|
||
|
||
assert.match(markup, /data-accumulation="true"/);
|
||
assert.match(markup, /Накопление/);
|
||
assert.match(markup, /aria-label="Окно накопления облака точек"/);
|
||
assert.match(markup, /aria-valuetext="12 с"/);
|
||
assert.match(markup, /aria-label="Позиция воспроизведения"/);
|
||
assert.equal((markup.match(/type="range"/g) ?? []).length, 2);
|
||
});
|
||
|
||
test("recorded observation timeline uses compact icon transport and one inline rate trigger", () => {
|
||
const idleMarkup = renderToStaticMarkup(createElement(ObservationTimeline, {
|
||
active: true,
|
||
sourceCount: 2,
|
||
mode: "recorded",
|
||
seekable: true,
|
||
rangeNs: { min: 0, max: 20_000_000_000 },
|
||
currentNs: 5_000_000_000,
|
||
playing: false,
|
||
playbackRate: 1,
|
||
onSeek: () => undefined,
|
||
onPlayingChange: () => undefined,
|
||
onPlaybackRateChange: () => undefined,
|
||
}));
|
||
const playingMarkup = renderToStaticMarkup(createElement(ObservationTimeline, {
|
||
active: true,
|
||
sourceCount: 2,
|
||
mode: "recorded",
|
||
seekable: true,
|
||
rangeNs: { min: 0, max: 20_000_000_000 },
|
||
currentNs: 5_000_000_000,
|
||
playing: true,
|
||
playbackRate: 1,
|
||
onSeek: () => undefined,
|
||
onPlayingChange: () => undefined,
|
||
onPlaybackRateChange: () => undefined,
|
||
}));
|
||
|
||
assert.match(idleMarkup, /aria-label="Воспроизвести"/);
|
||
assert.match(idleMarkup, /lucide-play/);
|
||
assert.match(idleMarkup, /fill="currentColor"/);
|
||
assert.match(idleMarkup, /stroke-width="0"/);
|
||
assert.match(playingMarkup, /aria-label="Пауза"/);
|
||
assert.match(playingMarkup, /lucide-square/);
|
||
assert.match(playingMarkup, /fill="currentColor"/);
|
||
assert.match(idleMarkup, /nodedc-select-inline/);
|
||
assert.match(idleMarkup, />1×<\/span>/);
|
||
assert.doesNotMatch(idleMarkup, /nodedc-select-trigger__chevron/);
|
||
assert.doesNotMatch(idleMarkup, />Воспроизвести<|>Пауза</);
|
||
});
|
||
|
||
test("Rerun expands only root-relative session recordings onto the current origin", () => {
|
||
assert.equal(
|
||
resolveRerunSourceUrl(
|
||
" /api/v1/observation-sessions/session-1/recording.rrd ",
|
||
"http://127.0.0.1:5174",
|
||
),
|
||
"http://127.0.0.1:5174/api/v1/observation-sessions/session-1/recording.rrd",
|
||
);
|
||
assert.equal(
|
||
resolveRerunSourceUrl("rerun+http://127.0.0.1:9877/proxy", "http://127.0.0.1:5174"),
|
||
"rerun+http://127.0.0.1:9877/proxy",
|
||
);
|
||
assert.equal(
|
||
resolveRerunSourceUrl("//different-authority.invalid/session.rrd", "http://127.0.0.1:5174"),
|
||
"//different-authority.invalid/session.rrd",
|
||
);
|
||
});
|
||
|
||
test("recorded blueprint endpoint is derived only from canonical same-origin RRD sources", () => {
|
||
assert.equal(
|
||
resolveRecordedBlueprintUrl(
|
||
"/api/v1/observation-sessions/session-1/recording.rrd",
|
||
"http://127.0.0.1:5174",
|
||
),
|
||
"http://127.0.0.1:5174/api/v1/observation-sessions/session-1/blueprint.rrd",
|
||
);
|
||
assert.equal(
|
||
resolveRecordedBlueprintUrl(
|
||
"https://outside.invalid/api/v1/observation-sessions/session-1/recording.rrd",
|
||
"http://127.0.0.1:5174",
|
||
),
|
||
null,
|
||
);
|
||
assert.equal(
|
||
resolveRecordedBlueprintUrl(
|
||
"/api/v1/observation-sessions/../recording.rrd",
|
||
"http://127.0.0.1:5174",
|
||
),
|
||
null,
|
||
);
|
||
assert.equal(
|
||
resolveRecordedBlueprintUrl(
|
||
`/api/v1/laboratory/vegetation-shadow/lab-v1-vegetation-shadow-${"a".repeat(64)}`
|
||
+ "/canonical-replay.rrd",
|
||
"http://127.0.0.1:5174",
|
||
"/api/v1/observation-sessions/session-1/blueprint.rrd",
|
||
),
|
||
"http://127.0.0.1:5174/api/v1/observation-sessions/session-1/blueprint.rrd",
|
||
);
|
||
});
|
||
|
||
test("recorded replay becomes ready only after the complete declared timeline is buffered", () => {
|
||
assert.equal(isRecordedPlaybackFullyBuffered(null, 20), false);
|
||
assert.equal(
|
||
isRecordedPlaybackFullyBuffered({ min: 0, max: 19_500_000_000 }, 20),
|
||
false,
|
||
);
|
||
assert.equal(
|
||
isRecordedPlaybackFullyBuffered({ min: 0, max: 19_999_500_000 }, 20),
|
||
true,
|
||
);
|
||
assert.equal(
|
||
isRecordedPlaybackFullyBuffered({ min: 0, max: 1 }, undefined),
|
||
true,
|
||
);
|
||
assert.equal(
|
||
isRecordedPlaybackFullyBuffered({ min: 0, max: 20_000_000_000 }, Number.NaN),
|
||
false,
|
||
);
|
||
});
|
||
|
||
test("recorded blueprint fetch is bounded, strict and sends only display settings", async () => {
|
||
const calls = [];
|
||
const cameraLimits = [];
|
||
const payload = Uint8Array.from([0x52, 0x52, 0x46, 0x32, 0x01]);
|
||
const result = await fetchRecordedBlueprintRrd(
|
||
"http://127.0.0.1:5174/api/v1/observation-sessions/session-1/blueprint.rrd",
|
||
{
|
||
accumulationSeconds: 24,
|
||
showGrid: false,
|
||
showPoints: true,
|
||
showTrajectory: false,
|
||
pointSize: 4.5,
|
||
colorMode: "height",
|
||
palette: "custom",
|
||
customColor: "#35d7c1",
|
||
},
|
||
{ applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
|
||
{
|
||
origin: "http://127.0.0.1:5174",
|
||
blueprintSessionId: "a".repeat(32),
|
||
activeView: "perception3d",
|
||
viewResetGeneration: 1,
|
||
followTrajectory: true,
|
||
cameraEye: {
|
||
position: [3, 4, 5],
|
||
lookTarget: [1, 2, 0],
|
||
eyeUp: [0, 0, 1],
|
||
},
|
||
eyeRelativeToTracking: true,
|
||
currentTimeNs: 39_215_263_458,
|
||
onCameraMaxOrbitalRadius: value => cameraLimits.push(value),
|
||
unifiedCameraShare: 0.73,
|
||
perceptionLayers: {
|
||
enabled: true,
|
||
detections2d: true,
|
||
segmentation: false,
|
||
cuboids3d: true,
|
||
},
|
||
fetcher: async (input, init) => {
|
||
calls.push({ input: String(input), init, body: JSON.parse(String(init.body)) });
|
||
return new Response(payload, {
|
||
status: 200,
|
||
headers: {
|
||
"Content-Type": "application/vnd.rerun.rrd",
|
||
"X-MissionCore-Camera-Max-Orbital-Radius": "686.024231",
|
||
},
|
||
});
|
||
},
|
||
},
|
||
);
|
||
assert.deepEqual([...result], [...payload]);
|
||
assert.equal(calls[0].init.method, "POST");
|
||
assert.equal(calls[0].init.credentials, "same-origin");
|
||
assert.deepEqual(calls[0].body, {
|
||
application_id: "nodedc_mission_core_recorded",
|
||
recording_id: "recording-001",
|
||
blueprint_session_id: "a".repeat(32),
|
||
accumulation_seconds: 24,
|
||
show_grid: false,
|
||
show_points: true,
|
||
show_trajectory: false,
|
||
point_size: 4.5,
|
||
color_mode: "height",
|
||
palette: "custom",
|
||
custom_color: "#35d7c1",
|
||
active_view: "perception3d",
|
||
view_reset_generation: 1,
|
||
follow_trajectory: true,
|
||
current_time_ns: 39_215_263_458,
|
||
unified_perception: true,
|
||
unified_camera_share: 0.73,
|
||
semantic_layer: null,
|
||
plan_view: false,
|
||
eye_position: [3, 4, 5],
|
||
eye_look_target: [1, 2, 0],
|
||
eye_up: [0, 0, 1],
|
||
eye_relative_to_tracking: true,
|
||
show_camera_image: true,
|
||
show_detections_2d: true,
|
||
show_segmentation: false,
|
||
show_cuboids_3d: true,
|
||
});
|
||
assert.deepEqual(cameraLimits, [686.024231]);
|
||
|
||
await fetchRecordedBlueprintRrd(
|
||
"http://127.0.0.1:5174/api/v1/observation-sessions/session-1/blueprint.rrd",
|
||
{
|
||
accumulationSeconds: 0,
|
||
showGrid: true,
|
||
showPoints: true,
|
||
showTrajectory: true,
|
||
pointSize: 2.5,
|
||
colorMode: "intensity",
|
||
palette: "turbo",
|
||
customColor: "#ffffff",
|
||
},
|
||
{ applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
|
||
{
|
||
origin: "http://127.0.0.1:5174",
|
||
blueprintSessionId: "b".repeat(32),
|
||
perceptionLayers: {
|
||
enabled: true,
|
||
detections2d: false,
|
||
segmentation: false,
|
||
cuboids3d: true,
|
||
},
|
||
fetcher: async (input, init) => {
|
||
calls.push({ input: String(input), init, body: JSON.parse(String(init.body)) });
|
||
return new Response(payload, {
|
||
status: 200,
|
||
headers: { "Content-Type": "application/vnd.rerun.rrd" },
|
||
});
|
||
},
|
||
},
|
||
);
|
||
assert.equal(calls[1].body.unified_perception, false);
|
||
assert.equal(calls[1].body.unified_camera_share, 0.46);
|
||
assert.equal(calls[1].body.show_cuboids_3d, true);
|
||
|
||
await assert.rejects(
|
||
fetchRecordedBlueprintRrd(
|
||
"https://outside.invalid/api/v1/observation-sessions/session-1/blueprint.rrd",
|
||
{
|
||
accumulationSeconds: 24,
|
||
showGrid: false,
|
||
showPoints: true,
|
||
showTrajectory: false,
|
||
pointSize: 4.5,
|
||
colorMode: "height",
|
||
palette: "custom",
|
||
customColor: "#35d7c1",
|
||
},
|
||
{ applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
|
||
{
|
||
origin: "http://127.0.0.1:5174",
|
||
blueprintSessionId: "a".repeat(32),
|
||
fetcher: async () => new Response(payload),
|
||
},
|
||
),
|
||
/Unsafe recorded blueprint request/,
|
||
);
|
||
});
|
||
|
||
test("recorded tracking quantizes the native fractional-nanosecond cursor without accepting invalid times", async () => {
|
||
const bodies = [];
|
||
const request = currentTimeNs => fetchRecordedBlueprintRrd(
|
||
"/api/v1/observation-sessions/session-1/blueprint.rrd",
|
||
{accumulationSeconds: 10, showGrid: true, showPoints: true,
|
||
showTrajectory: true, pointSize: 0.5, colorMode: "intensity",
|
||
palette: "turbo", customColor: "#ffffff"},
|
||
{applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001"},
|
||
{origin: "http://127.0.0.1:8000", blueprintSessionId: "a".repeat(32),
|
||
cameraEye: {position: [3, 4, 5], lookTarget: [1, 2, 0], eyeUp: [0, 0, 1]},
|
||
eyeRelativeToTracking: true, currentTimeNs,
|
||
fetcher: async (_input, init) => {
|
||
bodies.push(JSON.parse(init.body));
|
||
return new Response(new Uint8Array([0x52, 0x52, 0x46, 0x32]), {
|
||
headers: {"Content-Type": "application/vnd.rerun.rrd"},
|
||
});
|
||
}},
|
||
);
|
||
await request(536_460_021_972.65625);
|
||
assert.equal(bodies[0].current_time_ns, 536_460_021_973);
|
||
for (const invalid of [-0.1, NaN, Infinity, Number.MAX_SAFE_INTEGER + 1]) {
|
||
await assert.rejects(request(invalid), /Unsafe recorded blueprint request/);
|
||
}
|
||
assert.equal(bodies.length, 1);
|
||
});
|
||
|
||
test("recorded point colors use one strict same-origin component overlay", async () => {
|
||
const endpoint = resolveRecordedPointColorsUrl(
|
||
"/api/v1/observation-sessions/session-1/recording.rrd",
|
||
"http://127.0.0.1:5174",
|
||
);
|
||
assert.equal(
|
||
endpoint,
|
||
"http://127.0.0.1:5174/api/v1/observation-sessions/session-1/point-colors.rrd",
|
||
);
|
||
assert.equal(
|
||
resolveRecordedPointColorsUrl(
|
||
"https://outside.invalid/api/v1/observation-sessions/session-1/recording.rrd",
|
||
"http://127.0.0.1:5174",
|
||
),
|
||
null,
|
||
);
|
||
assert.equal(
|
||
recordedPointColorKey({
|
||
colorMode: "intensity",
|
||
palette: "turbo",
|
||
customColor: "#112233",
|
||
}),
|
||
"intensity|turbo|-",
|
||
);
|
||
assert.equal(
|
||
recordedPointColorKey({
|
||
colorMode: "class",
|
||
palette: "turbo",
|
||
customColor: "#112233",
|
||
}),
|
||
"class|turbo|#112233",
|
||
);
|
||
|
||
const calls = [];
|
||
const payload = Uint8Array.from([0x52, 0x52, 0x46, 0x32, 0x01]);
|
||
const result = await fetchRecordedPointColorsRrd(
|
||
endpoint,
|
||
{
|
||
colorMode: "distance",
|
||
palette: "viridis",
|
||
customColor: "#35d7c1",
|
||
},
|
||
{ applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
|
||
{
|
||
origin: "http://127.0.0.1:5174",
|
||
fetcher: async (input, init) => {
|
||
calls.push({ input: String(input), init, body: JSON.parse(String(init.body)) });
|
||
return new Response(payload, {
|
||
status: 200,
|
||
headers: {
|
||
"Content-Type": "application/vnd.rerun.rrd",
|
||
"Content-Length": String(payload.byteLength),
|
||
},
|
||
});
|
||
},
|
||
},
|
||
);
|
||
assert.deepEqual([...result], [...payload]);
|
||
assert.deepEqual(calls[0].body, {
|
||
application_id: "nodedc_mission_core_recorded",
|
||
recording_id: "recording-001",
|
||
color_mode: "distance",
|
||
palette: "viridis",
|
||
custom_color: "#35d7c1",
|
||
});
|
||
});
|
||
|
||
test("recorded perception fetch admits one complete same-origin RRD or no layer", async () => {
|
||
const endpoint = resolveRecordedPerceptionUrl(
|
||
"/api/v1/observation-sessions/session-1/recording.rrd",
|
||
"http://127.0.0.1:5174",
|
||
);
|
||
assert.equal(
|
||
endpoint,
|
||
"http://127.0.0.1:5174/api/v1/observation-sessions/session-1/perception.rrd",
|
||
);
|
||
const payload = Uint8Array.from([0x52, 0x52, 0x46, 0x32, 0x01]);
|
||
const progress = [];
|
||
const result = await fetchRecordedPerceptionRrd(
|
||
endpoint,
|
||
{ applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
|
||
{
|
||
origin: "http://127.0.0.1:5174",
|
||
onProgress: (receivedBytes, totalBytes) => {
|
||
progress.push([receivedBytes, totalBytes]);
|
||
},
|
||
fetcher: async (_input, init) => {
|
||
assert.deepEqual(JSON.parse(String(init.body)), {
|
||
application_id: "nodedc_mission_core_recorded",
|
||
recording_id: "recording-001",
|
||
});
|
||
return new Response(payload, {
|
||
status: 200,
|
||
headers: {
|
||
"Content-Type": "application/vnd.rerun.rrd",
|
||
"Content-Length": String(payload.byteLength),
|
||
},
|
||
});
|
||
},
|
||
},
|
||
);
|
||
assert.deepEqual([...result], [...payload]);
|
||
assert.deepEqual(progress[0], [0, payload.byteLength]);
|
||
assert.deepEqual(progress.at(-1), [payload.byteLength, payload.byteLength]);
|
||
|
||
const absent = await fetchRecordedPerceptionRrd(
|
||
endpoint,
|
||
{ applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
|
||
{
|
||
origin: "http://127.0.0.1:5174",
|
||
fetcher: async () => new Response(null, { status: 204 }),
|
||
},
|
||
);
|
||
assert.equal(absent, null);
|
||
});
|
||
|
||
test("LAB perception sidecar is streamed by native Rerun from one generation-bound URL", async () => {
|
||
const endpoint =
|
||
`http://127.0.0.1:5174/api/v1/laboratory/vegetation-shadow/` +
|
||
`lab-v1-vegetation-shadow-${"a".repeat(64)}/canonical-overlay.rrd`;
|
||
const sourceUrl = resolveRecordedPerceptionViewerSourceUrl(
|
||
endpoint,
|
||
{ applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
|
||
"b".repeat(64),
|
||
"http://127.0.0.1:5174",
|
||
);
|
||
assert.equal(
|
||
sourceUrl,
|
||
`${endpoint}?application_id=nodedc_mission_core_recorded` +
|
||
`&recording_id=recording-001&generation=${"b".repeat(64)}`,
|
||
);
|
||
|
||
const overlayGeneration = "c".repeat(64);
|
||
const probe = await probeRecordedPerceptionViewerSource(sourceUrl, {
|
||
origin: "http://127.0.0.1:5174",
|
||
fetcher: async (input, init) => {
|
||
assert.equal(String(input), sourceUrl);
|
||
assert.equal(init.method, "HEAD");
|
||
assert.equal(new Headers(init.headers).get("Range"), null);
|
||
return new Response(null, {
|
||
status: 200,
|
||
headers: {
|
||
"Content-Type": "application/vnd.rerun.rrd",
|
||
"Content-Length": "186058411",
|
||
"ETag": `"${overlayGeneration}"`,
|
||
"X-Rerun-Format": "RRF2",
|
||
},
|
||
});
|
||
},
|
||
});
|
||
assert.deepEqual(probe, {
|
||
sourceUrl: `${sourceUrl}&overlay_generation=${overlayGeneration}`,
|
||
byteLength: 186_058_411,
|
||
});
|
||
});
|
||
|
||
test("LAB native source rejects an unsealed overlay descriptor", async () => {
|
||
const endpoint =
|
||
`http://127.0.0.1:5174/api/v1/laboratory/vegetation-shadow/` +
|
||
`lab-v1-vegetation-shadow-${"a".repeat(64)}/canonical-overlay.rrd`;
|
||
const sourceUrl = resolveRecordedPerceptionViewerSourceUrl(
|
||
endpoint,
|
||
{ applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001" },
|
||
"b".repeat(64),
|
||
"http://127.0.0.1:5174",
|
||
);
|
||
|
||
await assert.rejects(
|
||
probeRecordedPerceptionViewerSource(sourceUrl, {
|
||
origin: "http://127.0.0.1:5174",
|
||
fetcher: async () => new Response(null, {
|
||
status: 200,
|
||
headers: {
|
||
"Content-Type": "application/vnd.rerun.rrd",
|
||
"Content-Length": "186058411",
|
||
"ETag": `"${"c".repeat(64)}"`,
|
||
"X-Rerun-Format": "RRF1",
|
||
},
|
||
}),
|
||
}),
|
||
/Invalid recorded perception viewer response/,
|
||
);
|
||
});
|
||
|
||
test("recorded replay creates an isolated source catalog without live device bindings", () => {
|
||
const sources = recordedObservationSources({
|
||
kind: "rerun-recording",
|
||
sessionId: "session-1",
|
||
sourceUrl: "/api/v1/observation-sessions/session-1/recording.rrd",
|
||
viewerSourceUrl: `/api/v1/observation-sessions/session-1/recording.rrd?generation=${"a".repeat(64)}`,
|
||
mediaType: "application/vnd.rerun.rrd",
|
||
timeline: "session_time",
|
||
timelineStartSeconds: 0,
|
||
timelineEndSeconds: 20,
|
||
seekable: true,
|
||
byteLength: 123,
|
||
sha256: "a".repeat(64),
|
||
playback: { speed: 1, loop: false },
|
||
mediaSources: [{
|
||
id: "recorded.camera.abc123",
|
||
label: "Записанная камера 1",
|
||
modality: "video",
|
||
manifestUrl: "/api/v1/observation-sessions/session-1/media/recorded-video-abc123/manifest",
|
||
manifestGenerationSha256: "c".repeat(64),
|
||
byteLength: 1_024,
|
||
mediaType: "video/mp4",
|
||
timelineStartSeconds: 0.25,
|
||
timelineEndSeconds: 20,
|
||
seekable: true,
|
||
synchronization: "host-arrival-best-effort",
|
||
}],
|
||
});
|
||
assert.deepEqual(sources.map(({ modality }) => modality), ["point-cloud", "video"]);
|
||
assert.equal(sources[1].delivery.kind, "recorded-fmp4-manifest");
|
||
assert.equal(sources[1].delivery.manifestGenerationSha256, "c".repeat(64));
|
||
assert.deepEqual(sources[1].binding, {});
|
||
assert.equal(sources.some(({ provider }) => provider.pluginId.includes("xgrids")), false);
|
||
assert.equal(JSON.stringify(sources).includes("192.168"), false);
|
||
});
|
||
|
||
test("recorded camera epoch selection and shared-clock offset are deterministic", () => {
|
||
const epochs = [
|
||
{ ordinal: 1, timelineStartSeconds: 0.25, timelineEndSeconds: 9 },
|
||
{ ordinal: 2, timelineStartSeconds: 10, timelineEndSeconds: 20 },
|
||
];
|
||
assert.equal(selectRecordedMediaEpoch(epochs, 0), null);
|
||
assert.equal(selectRecordedMediaEpoch(epochs, 0.25).ordinal, 1);
|
||
assert.equal(selectRecordedMediaEpoch(epochs, 9), epochs[0]);
|
||
assert.equal(selectRecordedMediaEpoch(epochs, 9.9), null);
|
||
assert.equal(selectRecordedMediaEpoch(epochs, 10).ordinal, 2);
|
||
assert.equal(recordedMediaLocalTime(10, 12.5), 2.5);
|
||
assert.equal(recordedMediaLocalTime(10, 8), 0);
|
||
assert.equal(recordedMediaLocalTime(10, 30, 4), 4);
|
||
});
|
||
|
||
after(async () => {
|
||
await server?.close();
|
||
});
|
||
|
||
function model() {
|
||
const activeModel = xgridsK1Manifest.spec.models[0];
|
||
assert.ok(activeModel, "XGRIDS plugin must declare at least one model");
|
||
return activeModel;
|
||
}
|
||
|
||
function cameraRow(sourceId, label) {
|
||
return {
|
||
stream_id: `camera.preview.${sourceId.split(".").at(-1)}`,
|
||
source_id: sourceId,
|
||
semantic_channel_id: "camera.preview.live",
|
||
label,
|
||
sensor_kind: "camera",
|
||
modality: "encoded-video",
|
||
availability: "available",
|
||
endpoint_label: "MSE · fMP4",
|
||
activation: {
|
||
group_id: "camera.preview.decoder",
|
||
max_active: 1,
|
||
selected: false,
|
||
controllable: true,
|
||
},
|
||
delivery: null,
|
||
};
|
||
}
|
||
|
||
function connectionSupervisor({ dataAuthoritative = false, dataPlaneState = "idle" } = {}) {
|
||
const observedAt = "2026-08-06T12:00:00Z";
|
||
const target = { ipv4: "192.168.68.52", port: 1883 };
|
||
return {
|
||
schema_version: "missioncore.k1-connection-supervisor/v1",
|
||
revision: 7,
|
||
closed: false,
|
||
intent: {
|
||
intent_id: "intent-001",
|
||
requested_mode: "bridge",
|
||
expected_device_id: "device-k1-001",
|
||
requested_at: observedAt,
|
||
},
|
||
observed: {
|
||
device_network: {
|
||
state: "applied",
|
||
intent_id: "intent-001",
|
||
transport_ref: "ble-k1-001",
|
||
connection_mode: "bridge",
|
||
target,
|
||
source: "ble-read-only-status",
|
||
observed_at: observedAt,
|
||
},
|
||
host_path: {
|
||
epoch: 3,
|
||
available: true,
|
||
fingerprint: "en0:192.168.68.10",
|
||
interface: "en0",
|
||
source_ipv4: "192.168.68.10",
|
||
route_class: "direct",
|
||
reason_code: null,
|
||
observed_at: observedAt,
|
||
},
|
||
endpoint: {
|
||
target,
|
||
tcp_state: "reachable",
|
||
intent_id: "intent-001",
|
||
host_path_epoch: 3,
|
||
reason_code: null,
|
||
observed_at: observedAt,
|
||
},
|
||
device_identity: {
|
||
state: "verified",
|
||
intent_id: "intent-001",
|
||
logical_device_id: "device-k1-001",
|
||
compatibility_profile_id: "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2",
|
||
connection_mode: "bridge",
|
||
source: "mqtt-device-info",
|
||
host_path_epoch: 3,
|
||
observed_at: observedAt,
|
||
},
|
||
control_plane: {
|
||
state: "healthy",
|
||
session_id: "control-session-001",
|
||
host_path_epoch: 3,
|
||
reason_code: null,
|
||
observed_at: observedAt,
|
||
},
|
||
data_plane: {
|
||
state: dataAuthoritative ? "healthy" : dataPlaneState,
|
||
session_id: dataAuthoritative || dataPlaneState !== "idle" ? "data-session-001" : null,
|
||
host_path_epoch: dataAuthoritative || dataPlaneState !== "idle" ? 3 : null,
|
||
reason_code: null,
|
||
observed_at: dataAuthoritative || dataPlaneState !== "idle" ? observedAt : null,
|
||
},
|
||
},
|
||
lease: {
|
||
state: "reachable",
|
||
generation: 4,
|
||
intent_id: "intent-001",
|
||
host_path_epoch: 3,
|
||
connection_mode: "bridge",
|
||
target,
|
||
logical_device_id: "device-k1-001",
|
||
reason_code: null,
|
||
observed_at: observedAt,
|
||
},
|
||
authority: {
|
||
network_mutation_allowed: false,
|
||
control_allowed: true,
|
||
acquisition_start_allowed: true,
|
||
data_ingest_authoritative: dataAuthoritative,
|
||
physical_motion_allowed: false,
|
||
reason_codes: [],
|
||
},
|
||
last_known: null,
|
||
allowed_actions: ["stop-acquisition"],
|
||
};
|
||
}
|
||
|
||
function connectionLifecycle() {
|
||
return {
|
||
schema_version: "missioncore.xgrids-k1-connection-lifecycle/v1",
|
||
revision: 8,
|
||
desired_mode: "bridge",
|
||
configured_mode: "bridge",
|
||
active_mode: "bridge",
|
||
mode_change: {
|
||
state: "ready",
|
||
from: "bridge",
|
||
to: "bridge",
|
||
},
|
||
mode_selection: {
|
||
allowed: false,
|
||
reason_codes: ["connection-mode-selection-acquisition-active"],
|
||
automatic_retry: false,
|
||
},
|
||
active_binding_key: "binding-intent-001-bridge",
|
||
active_binding: {
|
||
binding_key: "binding-intent-001-bridge",
|
||
intent_id: "intent-001",
|
||
transport_ref: "ble-k1-001",
|
||
connection_mode: "bridge",
|
||
target_ipv4: "192.168.68.52",
|
||
target_port: 1883,
|
||
host_path_epoch: 3,
|
||
control_session_id: "control-session-001",
|
||
logical_device_id: "device-k1-001",
|
||
compatibility_profile_id: "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2",
|
||
},
|
||
connection_ready: true,
|
||
ready_to_start: true,
|
||
operation: null,
|
||
allowed_actions: ["stop-acquisition"],
|
||
automatic_retry: false,
|
||
};
|
||
}
|
||
|
||
function declaredState(cameraRows = [
|
||
cameraRow("sensor.camera.left", "K1 · камера слева"),
|
||
cameraRow("sensor.camera.right", "K1 · камера справа"),
|
||
]) {
|
||
const activeModel = model();
|
||
return {
|
||
phase: "connected",
|
||
source_mode: "idle",
|
||
connection_mode: "bridge",
|
||
k1_ip: "192.168.7.10",
|
||
foxglove_ws_url: "ws://192.168.7.10:8765",
|
||
foxglove_viewer_url: "http://192.168.7.10:8765/vendor-viewer",
|
||
compatibility: {
|
||
profile_id: "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2",
|
||
camera_preview: "rtsp://192.168.7.10:8554/vendor-preview",
|
||
},
|
||
device_ref: {
|
||
device_id: "device-k1-001",
|
||
model_id: activeModel.id,
|
||
identity_stability: "stable",
|
||
identity_basis: "hardware-identifier",
|
||
},
|
||
device_session: {
|
||
device_session_id: "device-session-001",
|
||
device_id: "device-k1-001",
|
||
compatibility_profile_id: "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2",
|
||
connectivity: "connected",
|
||
},
|
||
connection_supervisor: connectionSupervisor(),
|
||
connection_lifecycle: connectionLifecycle(),
|
||
application_control_session: {
|
||
verified_control: {
|
||
logical_device_id: "device-k1-001",
|
||
compatibility_profile_id: "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2",
|
||
control_session_id: "control-session-001",
|
||
source: "mqtt-device-info",
|
||
},
|
||
},
|
||
sensor_catalog: {
|
||
schema_version: "missioncore.sensor-catalog/v1alpha2",
|
||
revision: "test-profile",
|
||
streams: [
|
||
{ stream_id: "spatial.point-cloud.live", modality: "point-cloud", availability: "observed" },
|
||
...cameraRows,
|
||
],
|
||
},
|
||
camera_preview: {
|
||
phase: "idle",
|
||
revision: 1,
|
||
generation: 0,
|
||
active_source_id: null,
|
||
delivery: null,
|
||
},
|
||
};
|
||
}
|
||
|
||
function pointCloudStreamingState() {
|
||
const state = declaredState();
|
||
return {
|
||
...state,
|
||
phase: "streaming",
|
||
source_mode: "live",
|
||
connection_supervisor: connectionSupervisor({ dataAuthoritative: true }),
|
||
rerun_grpc_url: "rerun+http://127.0.0.1:9877/proxy",
|
||
acquisition: {
|
||
acquisition_id: "acquisition-001",
|
||
device_id: "device-k1-001",
|
||
device_session_id: "device-session-001",
|
||
compatibility_profile_id: "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2",
|
||
control_mode: "operator-manual",
|
||
requested_streams: ["spatial.point-cloud.live"],
|
||
target_host: "127.0.0.1",
|
||
duration_seconds: 0,
|
||
evidence_policy: "required",
|
||
state: "acquiring",
|
||
state_revision: 3,
|
||
},
|
||
};
|
||
}
|
||
|
||
function cameraStreamingState(sourceId) {
|
||
const state = pointCloudStreamingState();
|
||
const delivery = {
|
||
id: `preview-generation-7:${sourceId}`,
|
||
kind: "mse-fmp4-websocket",
|
||
url: "/api/v1/device-plugins/xgrids-k1/camera-preview/ws?generation=7",
|
||
media_type: 'video/mp4; codecs="avc1.640028"',
|
||
};
|
||
state.sensor_catalog = {
|
||
...state.sensor_catalog,
|
||
streams: state.sensor_catalog.streams.map((stream) =>
|
||
stream.source_id === sourceId
|
||
? {
|
||
...stream,
|
||
availability: "streaming",
|
||
activation: { ...stream.activation, selected: true },
|
||
delivery,
|
||
}
|
||
: stream),
|
||
};
|
||
state.camera_preview = {
|
||
phase: "streaming",
|
||
revision: 4,
|
||
generation: 7,
|
||
active_source_id: sourceId,
|
||
delivery,
|
||
};
|
||
state.connection_recovery = {
|
||
schema_version: "missioncore.xgrids-k1-active-stream-recovery/v1",
|
||
state: "inactive",
|
||
generation: 0,
|
||
acquisition_id: null,
|
||
attempt: 0,
|
||
started_at_utc: null,
|
||
elapsed_ms: null,
|
||
reason_code: null,
|
||
force_finish_allowed: false,
|
||
automatic_read_only_rebind: false,
|
||
automatic_command_retry: false,
|
||
start_performed: false,
|
||
stop_performed: false,
|
||
ble_operation_performed: false,
|
||
network_mutation_performed: false,
|
||
runtime_producer_generation: null,
|
||
camera_recovery: "inactive",
|
||
camera_media_state: "ready",
|
||
camera_media_ready: true,
|
||
camera_epoch: {
|
||
generation: 7,
|
||
init_committed: true,
|
||
init_committed_age_ms: 100,
|
||
first_media_committed: true,
|
||
first_media_committed_age_ms: 80,
|
||
committed_media_segment_count: 2,
|
||
last_media_segment_age_ms: 20,
|
||
},
|
||
};
|
||
return state;
|
||
}
|
||
|
||
function cameraRecoveringState(sourceId, recoveryOverrides = {}, stateOverrides = {}) {
|
||
const state = cameraStreamingState(sourceId);
|
||
return {
|
||
...state,
|
||
snapshot_runtime_id: "runtime-camera-recovery-001",
|
||
snapshot_revision: 29,
|
||
producer_generation: 17,
|
||
phase: "reconnecting",
|
||
connection_supervisor: connectionSupervisor({ dataPlaneState: "lost" }),
|
||
connection_recovery: {
|
||
schema_version: "missioncore.xgrids-k1-active-stream-recovery/v1",
|
||
state: "reconnecting",
|
||
generation: 6,
|
||
acquisition_id: state.acquisition.acquisition_id,
|
||
attempt: 2,
|
||
started_at_utc: "2026-08-11T21:19:59Z",
|
||
elapsed_ms: 16_869,
|
||
reason_code: "host-route-unavailable",
|
||
force_finish_allowed: true,
|
||
automatic_read_only_rebind: true,
|
||
automatic_command_retry: false,
|
||
start_performed: false,
|
||
stop_performed: false,
|
||
ble_operation_performed: false,
|
||
network_mutation_performed: false,
|
||
runtime_producer_generation: 17,
|
||
camera_recovery: "owned",
|
||
camera_media_state: "pending-first-media",
|
||
camera_media_ready: false,
|
||
camera_epoch: {
|
||
generation: 7,
|
||
init_committed: true,
|
||
init_committed_age_ms: 250,
|
||
first_media_committed: false,
|
||
first_media_committed_age_ms: null,
|
||
committed_media_segment_count: 0,
|
||
last_media_segment_age_ms: null,
|
||
},
|
||
...recoveryOverrides,
|
||
},
|
||
...stateOverrides,
|
||
};
|
||
}
|
||
|
||
function collectUrlLikeStrings(value, found = []) {
|
||
if (typeof value === "string") {
|
||
if (value.includes("://")) found.push(value);
|
||
return found;
|
||
}
|
||
if (Array.isArray(value)) {
|
||
for (const item of value) collectUrlLikeStrings(item, found);
|
||
return found;
|
||
}
|
||
if (value && typeof value === "object") {
|
||
for (const item of Object.values(value)) collectUrlLikeStrings(item, found);
|
||
}
|
||
return found;
|
||
}
|
||
|
||
test("K1 maps zero, one or N catalog cameras without model-specific source ids", () => {
|
||
const zero = xgridsK1ObservationSources(declaredState([]), model());
|
||
const one = xgridsK1ObservationSources(
|
||
declaredState([cameraRow("rig.front", "Передняя камера")]),
|
||
model(),
|
||
);
|
||
const many = xgridsK1ObservationSources(declaredState(), model());
|
||
|
||
assert.deepEqual(zero.map(({ modality }) => modality), ["point-cloud"]);
|
||
assert.deepEqual(one.map(({ sourceId }) => sourceId), ["sensor.lidar.primary", "rig.front"]);
|
||
assert.deepEqual(many.map(({ sourceId }) => sourceId), [
|
||
"sensor.lidar.primary",
|
||
"sensor.camera.left",
|
||
"sensor.camera.right",
|
||
]);
|
||
assert.equal(new Set(many.map(({ id }) => id)).size, many.length);
|
||
assert.ok(many.every(({ provider }) => provider.pluginId && provider.modelId));
|
||
assert.ok(many.every(({ binding }) => binding.deviceId === "device-k1-001"));
|
||
});
|
||
|
||
test("descriptor ids remain stable while point cloud and selected camera start streaming", () => {
|
||
const declared = xgridsK1ObservationSources(declaredState(), model());
|
||
const streaming = xgridsK1ObservationSources(
|
||
cameraStreamingState("sensor.camera.left"),
|
||
model(),
|
||
);
|
||
|
||
assert.deepEqual(streaming.map(({ id }) => id), declared.map(({ id }) => id));
|
||
assert.equal(declared[0].availability, "available");
|
||
assert.equal(streaming[0].availability, "streaming");
|
||
assert.equal(streaming[0].previewUrl, "rerun+http://127.0.0.1:9877/proxy");
|
||
});
|
||
|
||
test("legacy selected-device fields cannot attest or stream observation sources", () => {
|
||
const state = cameraStreamingState("sensor.camera.left");
|
||
delete state.connection_supervisor;
|
||
delete state.application_control_session;
|
||
|
||
const sources = xgridsK1ObservationSources(state, model());
|
||
const pointCloud = sources.find(({ modality }) => modality === "point-cloud");
|
||
const left = sources.find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
assert.ok(pointCloud && left);
|
||
assert.equal(pointCloud.availability, "unverified");
|
||
assert.equal(pointCloud.previewUrl, null);
|
||
assert.equal(pointCloud.binding.deviceId, null);
|
||
assert.equal(pointCloud.binding.deviceSessionId, null);
|
||
assert.equal(left.availability, "unverified");
|
||
assert.equal(left.activation.selected, false);
|
||
assert.equal(left.activation.controllable, false);
|
||
assert.equal(left.delivery, null);
|
||
});
|
||
|
||
test("data-plane loss keeps control identity but withdraws all live deliveries", () => {
|
||
const state = cameraStreamingState("sensor.camera.left");
|
||
state.connection_supervisor = connectionSupervisor({ dataPlaneState: "lost" });
|
||
|
||
const sources = xgridsK1ObservationSources(state, model());
|
||
const pointCloud = sources.find(({ modality }) => modality === "point-cloud");
|
||
const left = sources.find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
assert.ok(pointCloud && left);
|
||
assert.equal(pointCloud.availability, "degraded");
|
||
assert.equal(pointCloud.previewUrl, null);
|
||
assert.equal(pointCloud.binding.deviceId, "device-k1-001");
|
||
assert.equal(left.activation.selected, true);
|
||
assert.equal(left.availability, "degraded");
|
||
assert.equal(left.delivery, null);
|
||
});
|
||
|
||
test("only the authoritative selected camera receives browser delivery", () => {
|
||
const sources = xgridsK1ObservationSources(
|
||
cameraStreamingState("sensor.camera.left"),
|
||
model(),
|
||
);
|
||
const cameras = sources.filter(({ modality }) => modality === "video");
|
||
const [left, right] = cameras;
|
||
|
||
assert.equal(cameras.length, 2);
|
||
assert.equal(left.activation.selected, true);
|
||
assert.equal(left.activation.maxActive, 1);
|
||
assert.equal(left.availability, "streaming");
|
||
assert.equal(left.delivery.kind, "mse-fmp4-websocket");
|
||
assert.equal(left.transport, "websocket");
|
||
assert.equal(right.activation.selected, false);
|
||
assert.equal(right.availability, "available");
|
||
assert.equal(right.delivery, null);
|
||
assert.equal(left.activation.groupId, right.activation.groupId);
|
||
assert.match(left.activation.groupId, /device-session-001/);
|
||
});
|
||
|
||
test("ordinary camera delivery requires ready media facts from the exact current epoch", () => {
|
||
const pending = cameraStreamingState("sensor.camera.left");
|
||
pending.connection_recovery = {
|
||
...pending.connection_recovery,
|
||
camera_media_state: "pending-first-media",
|
||
camera_media_ready: false,
|
||
camera_epoch: {
|
||
generation: 7,
|
||
init_committed: true,
|
||
init_committed_age_ms: 20,
|
||
first_media_committed: false,
|
||
first_media_committed_age_ms: null,
|
||
committed_media_segment_count: 0,
|
||
last_media_segment_age_ms: null,
|
||
},
|
||
};
|
||
const staleEpoch = cameraStreamingState("sensor.camera.left");
|
||
staleEpoch.connection_recovery = {
|
||
...staleEpoch.connection_recovery,
|
||
camera_epoch: {
|
||
...staleEpoch.connection_recovery.camera_epoch,
|
||
generation: 6,
|
||
},
|
||
};
|
||
|
||
for (const state of [pending, staleEpoch]) {
|
||
const camera = xgridsK1ObservationSources(state, model()).find(
|
||
({ sourceId }) => sourceId === "sensor.camera.left",
|
||
);
|
||
assert.ok(camera);
|
||
assert.equal(camera.activation.selected, false);
|
||
assert.equal(camera.delivery, null);
|
||
assert.equal(camera.availability, "available");
|
||
assert.equal(liveCameraPlaybackAuthorityIdentity(camera), null);
|
||
}
|
||
});
|
||
|
||
test("switching left to right keeps ids stable and never exposes both deliveries", () => {
|
||
const left = xgridsK1ObservationSources(
|
||
cameraStreamingState("sensor.camera.left"),
|
||
model(),
|
||
).filter(({ modality }) => modality === "video");
|
||
const right = xgridsK1ObservationSources(
|
||
cameraStreamingState("sensor.camera.right"),
|
||
model(),
|
||
).filter(({ modality }) => modality === "video");
|
||
|
||
assert.deepEqual(right.map(({ id }) => id), left.map(({ id }) => id));
|
||
assert.deepEqual(left.filter(({ delivery }) => delivery).map(({ sourceId }) => sourceId), [
|
||
"sensor.camera.left",
|
||
]);
|
||
assert.deepEqual(right.filter(({ delivery }) => delivery).map(({ sourceId }) => sourceId), [
|
||
"sensor.camera.right",
|
||
]);
|
||
});
|
||
|
||
test("camera descriptors never leak vendor RTSP endpoints or device IP addresses", () => {
|
||
const sources = xgridsK1ObservationSources(
|
||
cameraStreamingState("sensor.camera.left"),
|
||
model(),
|
||
);
|
||
const cameras = sources.filter(({ modality }) => modality === "video");
|
||
|
||
assert.ok(cameras.every(({ previewUrl }) => previewUrl === null));
|
||
assert.deepEqual(collectUrlLikeStrings(sources), [
|
||
"rerun+http://127.0.0.1:9877/proxy",
|
||
]);
|
||
const serialized = JSON.stringify(cameras);
|
||
assert.doesNotMatch(serialized, /rtsp:\/\//i);
|
||
assert.doesNotMatch(serialized, /192\.168\.7\.10/);
|
||
assert.doesNotMatch(serialized, /vendor-(?:preview|viewer)/);
|
||
});
|
||
|
||
test("unattested, duplicate and unsafe camera entries fail closed", () => {
|
||
const duplicate = cameraRow("sensor.camera.left", "Duplicate");
|
||
const state = cameraStreamingState("sensor.camera.left");
|
||
state.compatibility.profile_id = null;
|
||
state.device_session.compatibility_profile_id = null;
|
||
state.connection_supervisor.observed.device_identity.state = "mismatch";
|
||
state.application_control_session.verified_control = null;
|
||
state.sensor_catalog.streams.push(duplicate);
|
||
state.camera_preview.delivery = {
|
||
...state.camera_preview.delivery,
|
||
url: "ws://192.168.7.10:9000/leak",
|
||
};
|
||
|
||
const cameras = xgridsK1ObservationSources(state, model()).filter(
|
||
({ modality }) => modality === "video",
|
||
);
|
||
assert.deepEqual(cameras.map(({ sourceId }) => sourceId), ["sensor.camera.right"]);
|
||
assert.equal(cameras[0].availability, "unverified");
|
||
assert.equal(cameras[0].activation.controllable, false);
|
||
assert.equal(cameras[0].delivery, null);
|
||
});
|
||
|
||
test("camera delivery rejects literal and encoded endpoint or credential leaks", () => {
|
||
const unsafeUrls = [
|
||
"/api/preview?upstream=rtsp://camera.local/live",
|
||
"/api/preview?upstream=rtsp%3A%2F%2Fcamera.local%2Flive",
|
||
"/api/preview?upstream=rtsp%253A%252F%252Fcamera.local%252Flive",
|
||
"/api/preview?endpoint=192.0.2.52:8554",
|
||
"/api/preview?endpoint=192%2E168%2E68%2E52",
|
||
"/api/preview?password=not-for-the-browser",
|
||
"/api/preview?%70%61%73%73%77%6f%72%64=not-for-the-browser",
|
||
"/api/preview/camera:secret@device",
|
||
"/%2f%2fevil.example/preview",
|
||
];
|
||
|
||
for (const url of unsafeUrls) {
|
||
const state = cameraStreamingState("sensor.camera.left");
|
||
state.camera_preview.delivery = { ...state.camera_preview.delivery, url };
|
||
state.sensor_catalog.streams = state.sensor_catalog.streams.map((stream) =>
|
||
stream.source_id === "sensor.camera.left"
|
||
? { ...stream, delivery: { ...stream.delivery, url } }
|
||
: stream,
|
||
);
|
||
const left = xgridsK1ObservationSources(state, model()).find(
|
||
({ sourceId }) => sourceId === "sensor.camera.left",
|
||
);
|
||
assert.ok(left, `left camera descriptor missing for ${url}`);
|
||
assert.equal(left.delivery, null, `unsafe delivery escaped for ${url}`);
|
||
}
|
||
});
|
||
|
||
test("authoritative camera transport retries indefinitely with a capped backoff", () => {
|
||
let budget = resetCameraLeaseRetryBudget("delivery-7");
|
||
for (const expectedDelay of [400, 1_000, 2_000, 5_000, 5_000, 5_000]) {
|
||
const retry = consumeCameraLeaseRetry(budget, "delivery-7");
|
||
assert.equal(retry.delay, expectedDelay);
|
||
budget = retry.budget;
|
||
}
|
||
assert.equal(budget.count, 4);
|
||
|
||
budget = resetCameraLeaseRetryBudget("delivery-7");
|
||
const retryAfterManualReset = consumeCameraLeaseRetry(budget, "delivery-7");
|
||
assert.equal(retryAfterManualReset.delay, 400);
|
||
assert.equal(retryAfterManualReset.budget.count, 1);
|
||
});
|
||
|
||
test("camera startup watchdog replaces an open-but-silent MSE or WebSocket", () => {
|
||
const scheduled = new Map();
|
||
const timeouts = [];
|
||
let nextHandle = 1;
|
||
const watchdog = createCameraStartupWatchdog({
|
||
schedule(callback, timeoutMs) {
|
||
const handle = nextHandle;
|
||
nextHandle += 1;
|
||
scheduled.set(handle, { callback, timeoutMs });
|
||
return handle;
|
||
},
|
||
cancel(handle) {
|
||
scheduled.delete(handle);
|
||
},
|
||
onTimeout(stage) {
|
||
timeouts.push(stage);
|
||
},
|
||
});
|
||
|
||
watchdog.armFirstMedia();
|
||
watchdog.armFirstMedia();
|
||
assert.equal(watchdog.pendingStage(), "first-media");
|
||
assert.equal(scheduled.size, 1);
|
||
const [{ callback, timeoutMs }] = scheduled.values();
|
||
assert.equal(timeoutMs, CAMERA_FIRST_MEDIA_TIMEOUT_MS);
|
||
callback();
|
||
|
||
assert.deepEqual(timeouts, ["first-media"]);
|
||
assert.equal(watchdog.pendingStage(), null);
|
||
assert.match(
|
||
cameraStartupWatchdogRecoveryMessage("first-media"),
|
||
/не передаёт медиаданные; восстанавливаем/,
|
||
);
|
||
});
|
||
|
||
test("continuing media extends the first-playable-frame deadline", () => {
|
||
const scheduled = new Map();
|
||
const cancelled = [];
|
||
const timeouts = [];
|
||
let nextHandle = 10;
|
||
const watchdog = createCameraStartupWatchdog({
|
||
schedule(callback, timeoutMs) {
|
||
const handle = nextHandle;
|
||
nextHandle += 1;
|
||
scheduled.set(handle, { callback, timeoutMs });
|
||
return handle;
|
||
},
|
||
cancel(handle) {
|
||
cancelled.push(handle);
|
||
scheduled.delete(handle);
|
||
},
|
||
onTimeout(stage) {
|
||
timeouts.push(stage);
|
||
},
|
||
});
|
||
|
||
watchdog.armFirstMedia();
|
||
watchdog.markMediaReceived();
|
||
const playableHandle = nextHandle - 1;
|
||
assert.deepEqual(cancelled, [10]);
|
||
assert.equal(watchdog.pendingStage(), "first-playable-frame");
|
||
assert.equal(scheduled.get(playableHandle).timeoutMs, CAMERA_FIRST_PLAYABLE_FRAME_TIMEOUT_MS);
|
||
|
||
watchdog.markMediaReceived();
|
||
const extendedPlayableHandle = nextHandle - 1;
|
||
assert.notEqual(extendedPlayableHandle, playableHandle);
|
||
assert.deepEqual(cancelled, [10, playableHandle]);
|
||
assert.equal(
|
||
scheduled.get(extendedPlayableHandle).timeoutMs,
|
||
CAMERA_FIRST_PLAYABLE_FRAME_TIMEOUT_MS,
|
||
);
|
||
scheduled.get(extendedPlayableHandle).callback();
|
||
assert.deepEqual(timeouts, ["first-playable-frame"]);
|
||
assert.match(
|
||
cameraStartupWatchdogRecoveryMessage("first-playable-frame"),
|
||
/первый кадр не воспроизводится; пересоздаём decoder/,
|
||
);
|
||
});
|
||
|
||
test("playing clears the camera startup watchdog", () => {
|
||
const scheduled = new Map();
|
||
const cancelled = [];
|
||
const watchdog = createCameraStartupWatchdog({
|
||
schedule(callback) {
|
||
scheduled.set(21, callback);
|
||
return 21;
|
||
},
|
||
cancel(handle) {
|
||
cancelled.push(handle);
|
||
scheduled.delete(handle);
|
||
},
|
||
onTimeout() {
|
||
assert.fail("a playing transport must not time out");
|
||
},
|
||
});
|
||
|
||
watchdog.armFirstMedia();
|
||
watchdog.markMediaReceived();
|
||
watchdog.markPlaying();
|
||
assert.equal(watchdog.pendingStage(), null);
|
||
assert.deepEqual(cancelled, [21, 21]);
|
||
assert.equal(scheduled.size, 0);
|
||
});
|
||
|
||
test("browser append queue absorbs the complete bounded server backlog", () => {
|
||
assert.equal(
|
||
cameraPendingQueueCanAccept(8 * 1024 * 1024, 64, 1024 * 1024),
|
||
true,
|
||
);
|
||
assert.equal(
|
||
cameraPendingQueueCanAccept(11 * 1024 * 1024, 95, 1024 * 1024),
|
||
true,
|
||
);
|
||
assert.equal(
|
||
cameraPendingQueueCanAccept(12 * 1024 * 1024, 64, 1),
|
||
false,
|
||
);
|
||
assert.equal(
|
||
cameraPendingQueueCanAccept(8 * 1024 * 1024, 96, 1),
|
||
false,
|
||
);
|
||
});
|
||
|
||
test("server slow-reader close is an automatic browser-only camera recovery", () => {
|
||
assert.match(
|
||
cameraTransportCloseRecoveryMessage(4_008),
|
||
/отстал от эфира; восстанавливаем текущую камеру/,
|
||
);
|
||
assert.match(cameraTransportCloseRecoveryMessage(1_008), /переподключаемся/);
|
||
assert.match(cameraTransportCloseRecoveryMessage(1_000), /восстанавливаем/);
|
||
assert.match(cameraTransportCloseRecoveryMessage(1_011), /восстанавливаем/);
|
||
});
|
||
|
||
test("camera transport recovery events keep exact diagnostic causes", () => {
|
||
assert.equal(
|
||
cameraPlaybackRecoveryFailureStage({ type: "heartbeat" }),
|
||
"camera-heartbeat-reopen",
|
||
);
|
||
assert.equal(
|
||
cameraPlaybackRecoveryFailureStage({ type: "document-visible" }),
|
||
"camera-visibility-reopen",
|
||
);
|
||
assert.equal(
|
||
cameraPlaybackRecoveryFailureStage({ type: "network-online" }),
|
||
"camera-network-online-reopen",
|
||
);
|
||
assert.equal(
|
||
cameraPlaybackRecoveryFailureStage({ type: "page-restore", persisted: true }),
|
||
"camera-page-restore-reopen",
|
||
);
|
||
});
|
||
|
||
test("a laptop sleep gap reopens only the exact authoritative live camera transport", () => {
|
||
const source = xgridsK1ObservationSources(
|
||
cameraStreamingState("sensor.camera.right"),
|
||
model(),
|
||
).find(({ sourceId }) => sourceId === "sensor.camera.right");
|
||
assert.ok(source);
|
||
const authority = liveCameraPlaybackAuthorityIdentity(source);
|
||
assert.ok(authority);
|
||
|
||
const initial = initialCameraPlaybackRecoveryState(authority, 1_000);
|
||
const wake = reduceCameraPlaybackRecovery(initial, { type: "heartbeat" }, {
|
||
activeAuthorityIdentity: authority,
|
||
now: 7_000,
|
||
documentVisible: true,
|
||
networkOnline: true,
|
||
transportHealthy: false,
|
||
});
|
||
assert.equal(wake.reopen, true);
|
||
assert.equal(wake.state.lastReopenAt, 7_000);
|
||
|
||
const staleSource = reduceCameraPlaybackRecovery(initial, { type: "heartbeat" }, {
|
||
activeAuthorityIdentity: `${authority}:replaced`,
|
||
now: 7_000,
|
||
documentVisible: true,
|
||
networkOnline: true,
|
||
});
|
||
assert.equal(staleSource.reopen, false);
|
||
});
|
||
|
||
test("visibility, pageshow and online wake burst owns one replacement decoder", () => {
|
||
const authority = "camera-authority-acquisition-1";
|
||
let state = initialCameraPlaybackRecoveryState(authority, 1_000);
|
||
({ state } = reduceCameraPlaybackRecovery(state, { type: "document-hidden" }, {
|
||
activeAuthorityIdentity: authority,
|
||
now: 2_000,
|
||
documentVisible: false,
|
||
networkOnline: true,
|
||
}));
|
||
|
||
const visible = reduceCameraPlaybackRecovery(state, { type: "document-visible" }, {
|
||
activeAuthorityIdentity: authority,
|
||
now: 8_000,
|
||
documentVisible: true,
|
||
networkOnline: true,
|
||
});
|
||
assert.equal(visible.reopen, true);
|
||
state = visible.state;
|
||
|
||
for (const event of [
|
||
{ type: "page-restore", persisted: true },
|
||
{ type: "network-online" },
|
||
{ type: "heartbeat" },
|
||
]) {
|
||
const duplicate = reduceCameraPlaybackRecovery(state, event, {
|
||
activeAuthorityIdentity: authority,
|
||
now: 8_100,
|
||
documentVisible: true,
|
||
networkOnline: true,
|
||
});
|
||
assert.equal(duplicate.reopen, false);
|
||
state = duplicate.state;
|
||
}
|
||
|
||
({ state } = reduceCameraPlaybackRecovery(state, { type: "document-hidden" }, {
|
||
activeAuthorityIdentity: authority,
|
||
now: 12_000,
|
||
documentVisible: false,
|
||
networkOnline: true,
|
||
}));
|
||
const secondWake = reduceCameraPlaybackRecovery(state, { type: "document-visible" }, {
|
||
activeAuthorityIdentity: authority,
|
||
now: 18_000,
|
||
documentVisible: true,
|
||
networkOnline: true,
|
||
});
|
||
assert.equal(secondWake.reopen, true);
|
||
assert.equal(secondWake.state.lastReopenAt, 18_000);
|
||
});
|
||
|
||
test("healthy camera survives a long main-thread heartbeat gap without decoder churn", () => {
|
||
const authority = "camera-authority-acquisition-healthy";
|
||
let state = initialCameraPlaybackRecoveryState(authority, 1_000);
|
||
for (const now of [2_000, 3_000, 4_000, 12_000, 25_000]) {
|
||
const heartbeat = reduceCameraPlaybackRecovery(state, { type: "heartbeat" }, {
|
||
activeAuthorityIdentity: authority,
|
||
now,
|
||
documentVisible: true,
|
||
networkOnline: true,
|
||
transportHealthy: true,
|
||
});
|
||
assert.equal(heartbeat.reopen, false);
|
||
state = heartbeat.state;
|
||
}
|
||
assert.equal(state.lastReopenAt, null);
|
||
});
|
||
|
||
test("callbacks from a replaced camera WebSocket and SourceBuffer are fenced", () => {
|
||
assert.equal(cameraTransportCallbackIsCurrent(4, 4, false), true);
|
||
assert.equal(cameraTransportCallbackIsCurrent(5, 4, false), false);
|
||
assert.equal(cameraTransportCallbackIsCurrent(4, 4, true), false);
|
||
});
|
||
|
||
test("an already-stale document opens zero camera transports on mount", () => {
|
||
let transportOpenCount = 0;
|
||
const uiBuildStaleRef = { current: false };
|
||
const subscribeToAlreadyStaleCoordinator = (listener) => {
|
||
listener();
|
||
return () => undefined;
|
||
};
|
||
|
||
subscribeToAlreadyStaleCoordinator(() => {
|
||
uiBuildStaleRef.current = true;
|
||
});
|
||
if (cameraTransportCanOpen(uiBuildStaleRef.current)) transportOpenCount += 1;
|
||
|
||
assert.equal(uiBuildStaleRef.current, true);
|
||
assert.equal(transportOpenCount, 0);
|
||
});
|
||
|
||
test("only the current acquisition authority may schedule a replacement decoder", () => {
|
||
const authority = "camera-authority-acquisition-current";
|
||
assert.equal(
|
||
cameraTransportRecoveryIsCurrent(authority, authority, 7, 7, false),
|
||
true,
|
||
);
|
||
assert.equal(
|
||
cameraTransportRecoveryIsCurrent(`${authority}:replaced`, authority, 7, 7, false),
|
||
false,
|
||
);
|
||
assert.equal(
|
||
cameraTransportRecoveryIsCurrent(authority, authority, 8, 7, false),
|
||
false,
|
||
);
|
||
assert.equal(
|
||
cameraTransportRecoveryIsCurrent(authority, authority, 7, 7, true),
|
||
false,
|
||
);
|
||
assert.equal(
|
||
cameraTransportRecoveryIsCurrent(null, authority, 7, 7, false),
|
||
false,
|
||
);
|
||
});
|
||
|
||
test("same camera delivery gets a new browser transport owner for a new acquisition", () => {
|
||
const camera = xgridsK1ObservationSources(
|
||
cameraStreamingState("sensor.camera.right"),
|
||
model(),
|
||
).find(({ sourceId }) => sourceId === "sensor.camera.right");
|
||
assert.ok(camera?.delivery && camera.delivery.kind === "mse-fmp4-websocket");
|
||
const previousAuthority = liveCameraPlaybackAuthorityIdentity(camera);
|
||
const nextAuthority = liveCameraPlaybackAuthorityIdentity({
|
||
...camera,
|
||
binding: {
|
||
...camera.binding,
|
||
deviceSessionId: "device-session-002",
|
||
acquisitionId: "acquisition-002",
|
||
},
|
||
});
|
||
assert.ok(previousAuthority && nextAuthority);
|
||
assert.notEqual(nextAuthority, previousAuthority);
|
||
assert.notEqual(
|
||
cameraBrowserTransportIdentity(camera.delivery, nextAuthority),
|
||
cameraBrowserTransportIdentity(camera.delivery, previousAuthority),
|
||
);
|
||
});
|
||
|
||
test("malformed camera exclusivity or media descriptors cannot own wake recovery", () => {
|
||
const camera = xgridsK1ObservationSources(
|
||
cameraStreamingState("sensor.camera.left"),
|
||
model(),
|
||
).find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
assert.ok(camera?.activation && camera.delivery?.kind === "mse-fmp4-websocket");
|
||
|
||
for (const malformed of [
|
||
{ ...camera, activation: { ...camera.activation, groupId: " " } },
|
||
{ ...camera, activation: { ...camera.activation, groupId: null } },
|
||
{ ...camera, activation: { ...camera.activation, maxActive: 2 } },
|
||
{ ...camera, delivery: { ...camera.delivery, mediaType: " " } },
|
||
{ ...camera, delivery: { ...camera.delivery, mediaType: null } },
|
||
{ ...camera, delivery: { ...camera.delivery, mediaType: "video/webm" } },
|
||
]) {
|
||
assert.equal(liveCameraPlaybackAuthorityIdentity(malformed), null);
|
||
}
|
||
});
|
||
|
||
test("point-cloud recovery cannot revive a dead or stale camera delivery", () => {
|
||
const sources = xgridsK1ObservationSources(
|
||
cameraStreamingState("sensor.camera.left"),
|
||
model(),
|
||
);
|
||
const pointCloud = sources.find(({ modality }) => modality === "point-cloud");
|
||
const camera = sources.find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
assert.ok(pointCloud && camera);
|
||
assert.equal(pointCloud.availability, "streaming");
|
||
assert.ok(liveCameraPlaybackAuthorityIdentity(camera));
|
||
|
||
assert.equal(liveCameraPlaybackAuthorityIdentity({
|
||
...camera,
|
||
availability: "error",
|
||
}), null);
|
||
assert.equal(liveCameraPlaybackAuthorityIdentity({
|
||
...camera,
|
||
binding: { ...camera.binding, acquisitionId: "acquisition-replaced" },
|
||
activation: { ...camera.activation, selected: false },
|
||
}), null);
|
||
});
|
||
|
||
test("exact active recovery retains the same camera delivery without controls", () => {
|
||
const healthy = xgridsK1ObservationSources(
|
||
cameraStreamingState("sensor.camera.left"),
|
||
model(),
|
||
);
|
||
const recovering = xgridsK1ObservationSources(
|
||
cameraRecoveringState("sensor.camera.left"),
|
||
model(),
|
||
);
|
||
const healthyPointCloud = healthy.find(({ modality }) => modality === "point-cloud");
|
||
const recoveryPointCloud = recovering.find(({ modality }) => modality === "point-cloud");
|
||
const healthyCamera = healthy.find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
const recoveryCamera = recovering.find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
assert.ok(healthyPointCloud && recoveryPointCloud && healthyCamera && recoveryCamera);
|
||
|
||
assert.equal(recoveryPointCloud.id, healthyPointCloud.id);
|
||
assert.equal(recoveryPointCloud.previewUrl, healthyPointCloud.previewUrl);
|
||
assert.equal(recoveryPointCloud.availability, "connecting");
|
||
assert.equal(recoveryCamera.id, healthyCamera.id);
|
||
assert.deepEqual(recoveryCamera.delivery, healthyCamera.delivery);
|
||
assert.equal(recoveryCamera.activation.selected, true);
|
||
assert.equal(recoveryCamera.activation.controllable, false);
|
||
assert.equal(recoveryCamera.availability, "connecting");
|
||
assert.deepEqual(recoveryCamera.presentationLease, {
|
||
kind: "active-stream-recovery",
|
||
runtimeId: "runtime-camera-recovery-001",
|
||
acquisitionId: "acquisition-001",
|
||
acquisitionStateRevision: 3,
|
||
producerGeneration: 17,
|
||
recoveryGeneration: 6,
|
||
});
|
||
|
||
const healthyAuthority = liveCameraPlaybackAuthorityIdentity(healthyCamera);
|
||
const recoveryAuthority = liveCameraPlaybackAuthorityIdentity(recoveryCamera);
|
||
assert.ok(healthyAuthority && recoveryAuthority);
|
||
assert.notEqual(recoveryAuthority, healthyAuthority);
|
||
assert.equal(
|
||
liveCameraPlaybackAuthorityIdentity(
|
||
xgridsK1ObservationSources(
|
||
cameraRecoveringState("sensor.camera.left"),
|
||
model(),
|
||
).find(({ sourceId }) => sourceId === "sensor.camera.left"),
|
||
),
|
||
recoveryAuthority,
|
||
"repeated snapshots of the same recovery generation keep one browser owner",
|
||
);
|
||
});
|
||
|
||
test("camera recovery stays connecting until the current epoch has durable first media", () => {
|
||
const pending = xgridsK1ObservationSources(
|
||
cameraRecoveringState("sensor.camera.left"),
|
||
model(),
|
||
).find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
const ready = xgridsK1ObservationSources(
|
||
cameraRecoveringState("sensor.camera.left", {
|
||
camera_media_state: "ready",
|
||
camera_media_ready: true,
|
||
camera_epoch: {
|
||
generation: 7,
|
||
init_committed: true,
|
||
init_committed_age_ms: 500,
|
||
first_media_committed: true,
|
||
first_media_committed_age_ms: 100,
|
||
committed_media_segment_count: 1,
|
||
last_media_segment_age_ms: 100,
|
||
},
|
||
}),
|
||
model(),
|
||
).find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
|
||
assert.ok(pending?.delivery && ready?.delivery);
|
||
assert.equal(pending.activation.selected, true);
|
||
assert.equal(pending.availability, "connecting");
|
||
assert.ok(pending.presentationLease);
|
||
assert.equal(ready.activation.selected, true);
|
||
assert.equal(ready.availability, "streaming");
|
||
assert.ok(ready.presentationLease);
|
||
});
|
||
|
||
test("first recovered PCL keeps the exact camera authority until browser media is playable", () => {
|
||
const reconnectingState = cameraRecoveringState("sensor.camera.left");
|
||
const recoveredState = cameraRecoveringState(
|
||
"sensor.camera.left",
|
||
{
|
||
state: "recovered",
|
||
force_finish_allowed: false,
|
||
elapsed_ms: null,
|
||
reason_code: null,
|
||
},
|
||
{
|
||
phase: "live",
|
||
connection_supervisor: connectionSupervisor({ dataAuthoritative: true }),
|
||
},
|
||
);
|
||
recoveredState.camera_preview = {
|
||
...recoveredState.camera_preview,
|
||
phase: "connecting",
|
||
};
|
||
recoveredState.sensor_catalog = {
|
||
...recoveredState.sensor_catalog,
|
||
streams: recoveredState.sensor_catalog.streams.map((stream) =>
|
||
stream.source_id === "sensor.camera.left"
|
||
? { ...stream, availability: "connecting" }
|
||
: stream),
|
||
};
|
||
|
||
const reconnecting = xgridsK1ObservationSources(reconnectingState, model());
|
||
const recovered = xgridsK1ObservationSources(recoveredState, model());
|
||
const reconnectingCamera = reconnecting.find(
|
||
({ sourceId }) => sourceId === "sensor.camera.left",
|
||
);
|
||
const recoveredCamera = recovered.find(
|
||
({ sourceId }) => sourceId === "sensor.camera.left",
|
||
);
|
||
const reconnectingPointCloud = reconnecting.find(
|
||
({ modality }) => modality === "point-cloud",
|
||
);
|
||
const recoveredPointCloud = recovered.find(
|
||
({ modality }) => modality === "point-cloud",
|
||
);
|
||
assert.ok(
|
||
reconnectingCamera
|
||
&& recoveredCamera
|
||
&& reconnectingPointCloud
|
||
&& recoveredPointCloud,
|
||
);
|
||
|
||
assert.equal(recoveredCamera.availability, "connecting");
|
||
assert.equal(recoveredCamera.activation.selected, true);
|
||
assert.equal(recoveredCamera.activation.controllable, true);
|
||
assert.deepEqual(recoveredCamera.delivery, reconnectingCamera.delivery);
|
||
assert.deepEqual(recoveredCamera.presentationLease, reconnectingCamera.presentationLease);
|
||
assert.deepEqual(
|
||
recoveredPointCloud.presentationLease,
|
||
reconnectingPointCloud.presentationLease,
|
||
);
|
||
assert.equal(
|
||
liveCameraPlaybackAuthorityIdentity(recoveredCamera),
|
||
liveCameraPlaybackAuthorityIdentity(reconnectingCamera),
|
||
"reconnecting→recovered must not retire the decoder before first playable frame",
|
||
);
|
||
const spatialSource = {
|
||
id: "acquisition-001",
|
||
url: reconnectingPointCloud.previewUrl,
|
||
label: "Live",
|
||
kind: "rerun-grpc",
|
||
};
|
||
assert.equal(
|
||
liveRerunRecoveryAuthorityIdentity(recoveredPointCloud, spatialSource),
|
||
liveRerunRecoveryAuthorityIdentity(reconnectingPointCloud, spatialSource),
|
||
"the first recovered PCL keeps one exact Rerun browser authority",
|
||
);
|
||
});
|
||
|
||
test("camera recovery fails closed while the exact spatial lease may continue", () => {
|
||
const cameraStreamError = cameraRecoveringState("sensor.camera.left");
|
||
cameraStreamError.sensor_catalog.streams = cameraStreamError.sensor_catalog.streams.map(
|
||
(stream) => stream.source_id === "sensor.camera.left"
|
||
? { ...stream, availability: "error" }
|
||
: stream,
|
||
);
|
||
const mismatchedDelivery = cameraRecoveringState("sensor.camera.left");
|
||
mismatchedDelivery.camera_preview.delivery = {
|
||
...mismatchedDelivery.camera_preview.delivery,
|
||
id: "preview-generation-replaced:sensor.camera.left",
|
||
};
|
||
const missingDelivery = cameraRecoveringState("sensor.camera.left");
|
||
missingDelivery.camera_preview.delivery = null;
|
||
missingDelivery.sensor_catalog.streams = missingDelivery.sensor_catalog.streams.map(
|
||
(stream) => stream.source_id === "sensor.camera.left"
|
||
? { ...stream, delivery: null }
|
||
: stream,
|
||
);
|
||
const cases = [
|
||
cameraRecoveringState("sensor.camera.left", { camera_recovery: "blocked" }),
|
||
cameraRecoveringState("sensor.camera.left", {}, {
|
||
device_session: {
|
||
...cameraStreamingState("sensor.camera.left").device_session,
|
||
device_session_id: "device-session-replaced",
|
||
},
|
||
}),
|
||
cameraRecoveringState("sensor.camera.left", {}, {
|
||
camera_preview: {
|
||
...cameraStreamingState("sensor.camera.left").camera_preview,
|
||
phase: "error",
|
||
},
|
||
}),
|
||
cameraStreamError,
|
||
mismatchedDelivery,
|
||
missingDelivery,
|
||
];
|
||
|
||
for (const state of cases) {
|
||
const sources = xgridsK1ObservationSources(state, model());
|
||
const pointCloud = sources.find(({ modality }) => modality === "point-cloud");
|
||
const camera = sources.find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
assert.ok(pointCloud && camera);
|
||
assert.equal(pointCloud.previewUrl, "rerun+http://127.0.0.1:9877/proxy");
|
||
assert.equal(pointCloud.availability, "connecting");
|
||
assert.equal(camera.activation.selected, false);
|
||
assert.equal(camera.activation.controllable, false);
|
||
assert.equal(camera.delivery, null);
|
||
assert.equal(camera.presentationLease, null);
|
||
assert.equal(liveCameraPlaybackAuthorityIdentity(camera), null);
|
||
}
|
||
});
|
||
|
||
test("stale and terminal recovery states withdraw both retained transports", () => {
|
||
const staleProducer = cameraRecoveringState("sensor.camera.left");
|
||
staleProducer.producer_generation += 1;
|
||
const differentAcquisition = cameraRecoveringState("sensor.camera.left", {
|
||
acquisition_id: "acquisition-stale",
|
||
});
|
||
for (const state of [
|
||
staleProducer,
|
||
differentAcquisition,
|
||
cameraRecoveringState("sensor.camera.left", { state: "blocked" }),
|
||
cameraRecoveringState("sensor.camera.left", { state: "standby" }),
|
||
cameraRecoveringState("sensor.camera.left", { state: "fault" }),
|
||
]) {
|
||
const sources = xgridsK1ObservationSources(state, model());
|
||
const pointCloud = sources.find(({ modality }) => modality === "point-cloud");
|
||
const camera = sources.find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
assert.ok(pointCloud && camera);
|
||
assert.equal(pointCloud.previewUrl, null);
|
||
assert.equal(pointCloud.presentationLease, null);
|
||
assert.equal(camera.activation.selected, false);
|
||
assert.equal(camera.delivery, null);
|
||
assert.equal(camera.presentationLease, null);
|
||
}
|
||
});
|
||
|
||
test("malformed recovery leases cannot own a camera WebSocket or decoder", () => {
|
||
const camera = xgridsK1ObservationSources(
|
||
cameraRecoveringState("sensor.camera.left"),
|
||
model(),
|
||
).find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
assert.ok(camera?.presentationLease);
|
||
|
||
for (const presentationLease of [
|
||
null,
|
||
{ ...camera.presentationLease, runtimeId: " " },
|
||
{ ...camera.presentationLease, acquisitionId: "acquisition-stale" },
|
||
{ ...camera.presentationLease, acquisitionStateRevision: 0 },
|
||
{ ...camera.presentationLease, producerGeneration: 0 },
|
||
{ ...camera.presentationLease, recoveryGeneration: 0 },
|
||
]) {
|
||
assert.equal(
|
||
liveCameraPlaybackAuthorityIdentity({ ...camera, presentationLease }),
|
||
null,
|
||
);
|
||
}
|
||
});
|
||
|
||
test("layout policy evicts only exclusive camera peers", () => {
|
||
const sources = xgridsK1ObservationSources(
|
||
cameraStreamingState("sensor.camera.left"),
|
||
model(),
|
||
);
|
||
const left = sources.find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
const right = sources.find(({ sourceId }) => sourceId === "sensor.camera.right");
|
||
const pointCloud = sources.find(({ modality }) => modality === "point-cloud");
|
||
assert.ok(left && right && pointCloud);
|
||
|
||
const change = openObservationSource(
|
||
[pointCloud.id, left.id],
|
||
right.id,
|
||
sources,
|
||
);
|
||
assert.deepEqual(change.visibleIds, [pointCloud.id, right.id]);
|
||
assert.deepEqual(change.removedIds, [left.id]);
|
||
});
|
||
|
||
test("selected camera without delivery is explicitly restartable", () => {
|
||
const state = cameraStreamingState("sensor.camera.left");
|
||
state.camera_preview = {
|
||
...state.camera_preview,
|
||
phase: "error",
|
||
delivery: null,
|
||
};
|
||
state.sensor_catalog.streams = state.sensor_catalog.streams.map((stream) =>
|
||
stream.source_id === "sensor.camera.left"
|
||
? { ...stream, availability: "error", delivery: null }
|
||
: stream,
|
||
);
|
||
const sources = xgridsK1ObservationSources(state, model());
|
||
const left = sources.find(({ sourceId }) => sourceId === "sensor.camera.left");
|
||
const right = sources.find(({ sourceId }) => sourceId === "sensor.camera.right");
|
||
assert.ok(left && right);
|
||
|
||
assert.equal(left.activation.selected, true);
|
||
assert.equal(left.delivery, null);
|
||
assert.equal(left.availability, "error");
|
||
assert.equal(shouldRestartObservationSource(left), true);
|
||
assert.equal(shouldRestartObservationSource(right), false);
|
||
});
|