← Files ModRetro Chromatic PluginARCHIVED FILE
dist/device-capture/native-client.js
73.2 KB · Oct 3, 2026 · 06:38 UTC
/// <reference lib="dom" />
import { DEVICE_COLORS, DEVICE_COLOR_KEY, mountControlTooltips } from "../web-annotations/view.js";
import { requestDiagnosticAnnotation } from "../web-annotations/native.js";
import { mountPreviewErrorDialog } from "../web-annotations/error-dialog.js";
import { NATIVE_CAPTURE_VIEW } from "./native-page.js";
import { mountDevicePicker } from "./device-picker.js";
import { createTimingDiagnostics } from "./native-timing-diagnostics.js";
/** Diagnostic only: bounded browser-clock observations, never image content. */
class BrowserStageTrace {
key;
identity;
buckets = Array(60);
highWater = {};
beatAt;
activity;
lastPublished = -Infinity;
lastValidAtMs;
clockAnomalyCount = 0;
lastClockAnomaly = null;
lastEventAtMs = null;
lastEvent = null;
lastAtMs = {};
inFlight = null;
endedAtMs;
endReason;
startedAtMs;
constructor(key, identity, startedAtMs, activity) {
this.key = key;
this.identity = identity;
const valid = Number.isFinite(startedAtMs) && startedAtMs >= 0 && Number.isSafeInteger(Math.floor(startedAtMs));
this.startedAtMs = valid ? startedAtMs : 0;
this.beatAt = this.startedAtMs;
this.lastValidAtMs = this.startedAtMs;
this.activity = activity;
if (!valid)
this.anomaly(!Number.isFinite(startedAtMs) ? "nonfinite" : startedAtMs < 0 ? "negative" : "unsafe-index");
}
anomaly(reason) { this.clockAnomalyCount = Math.min(1000000, this.clockAnomalyCount + 1); this.lastClockAnomaly = reason; }
validTime(now) { if (!Number.isFinite(now)) {
this.anomaly("nonfinite");
return false;
} if (now < 0) {
this.anomaly("negative");
return false;
} if (now < this.lastValidAtMs) {
this.anomaly("backward");
return false;
} if (!Number.isSafeInteger(Math.floor(now)) || !Number.isSafeInteger(Math.floor((now - this.startedAtMs) / 1000))) {
this.anomaly("unsafe-index");
return false;
} this.lastValidAtMs = now; return true; }
get lastValidTime() { return this.lastValidAtMs; }
get isEnded() { return this.endedAtMs !== undefined; }
index(now) { return Math.max(0, Math.floor((now - this.startedAtMs) / 1000)); }
bucket(index) { const slot = index % 60; let value = this.buckets[slot]; if (!value || value.index !== index) {
value = { index, activeObservedMs: 0, pausedObservedMs: 0, hiddenObservedMs: 0, controlObservedMs: 0, unavailableObservedMs: 0, events: {}, stages: {} };
this.buckets[slot] = value;
} return value; }
// A delayed event loop is unknown coverage, not proof of a quiet interval.
heartbeat(now, activity) {
if (this.endedAtMs !== undefined || !this.validTime(now))
return;
if (now - this.beatAt <= 500) {
let at = this.beatAt;
while (at < now) {
const index = this.index(at), end = Math.min(now, this.startedAtMs + (index + 1) * 1000);
if (!(end > at)) {
this.anomaly("unsafe-index");
break;
}
const bucket = this.bucket(index), delta = end - at;
if (this.activity === "active")
bucket.activeObservedMs += delta;
else {
bucket.pausedObservedMs += delta;
if (this.activity === "hidden")
bucket.hiddenObservedMs += delta;
else if (this.activity === "control")
bucket.controlObservedMs += delta;
else
bucket.unavailableObservedMs += delta;
}
at = end;
}
}
this.beatAt = now;
this.activity = activity;
}
event(name, now) { if (this.endedAtMs !== undefined || !this.validTime(now))
return; const b = this.bucket(this.index(now)); b.events[name] = Math.min(1000000, (b.events[name] ?? 0) + 1); this.lastEvent = name; this.lastEventAtMs = now; this.lastAtMs[name] = now; }
stage(name, identity, now, duration) {
if (this.endedAtMs !== undefined || !this.validTime(now))
return false;
const sequence = identity.previewSequence ?? identity.frameId, previous = this.highWater[name];
if (previous !== undefined && sequence <= previous)
return false;
this.highWater[name] = sequence;
const b = this.bucket(this.index(now)), frame = { frameId: identity.frameId, ...(identity.previewSequence === undefined ? {} : { previewSequence: identity.previewSequence }) };
const validDuration = duration !== undefined && Number.isFinite(duration) && duration >= 0 ? duration : null;
const existing = b.stages[name];
if (existing) {
existing.count = Math.min(1000000, existing.count + 1);
existing.last = frame;
existing.lastAtMs = now;
if (validDuration !== null)
existing.maxDurationMs = Math.max(existing.maxDurationMs ?? 0, validDuration);
}
else
b.stages[name] = { count: 1, first: frame, last: frame, lastAtMs: now, maxDurationMs: validDuration };
this.lastEvent = name;
this.lastEventAtMs = now;
this.lastAtMs[name] = now;
return true;
}
anchor(identity, service, browser, nativeTiming) { if (this.endedAtMs !== undefined || !this.validTime(browser))
return; const b = this.bucket(this.index(browser)), correlatable = this.identity.connectionId !== null || this.identity.sourceGeneration !== null, anchor = { frameId: identity.frameId, ...(identity.previewSequence === undefined ? {} : { previewSequence: identity.previewSequence }), serviceCachePublishedAtMs: correlatable && service !== null && Number.isFinite(service) && service >= 0 ? service : null, browserSubmittedAtMs: browser, nativeTiming }; if (b.anchors)
b.anchors.last = anchor;
else
b.anchors = { first: anchor, last: anchor }; }
phase(value, now) { if (this.endedAtMs !== undefined || !this.validTime(now))
return; this.inFlight = value ? { phase: value, startedAtMs: now } : null; }
end(reason, now) { if (this.endedAtMs !== undefined)
return; this.endedAtMs = this.validTime(now) ? now : this.lastValidAtMs; this.endReason = reason; }
snapshot(now, force = false, previousPublication = -Infinity) {
if (!this.validTime(now))
now = this.lastValidAtMs;
if (!force && now - Math.max(this.lastPublished, previousPublication) < 1000)
return;
this.lastPublished = now;
const through = this.endedAtMs ?? now, index = this.index(through), first = Math.max(0, index - 59), rows = [];
for (let i = first; i <= index; i++) {
const found = this.buckets[i % 60], bucket = found?.index === i ? found : undefined, durationMs = Math.max(0, Math.min(1000, through - (this.startedAtMs + i * 1000))), activeObservedMs = Math.min(durationMs, bucket?.activeObservedMs ?? 0), pausedObservedMs = Math.min(durationMs - activeObservedMs, bucket?.pausedObservedMs ?? 0);
rows.push({ index: i, startOffsetMs: i * 1000, durationMs, partial: durationMs < 1000, coverage: { activeObservedMs, pausedObservedMs, hiddenObservedMs: bucket?.hiddenObservedMs ?? 0, controlObservedMs: bucket?.controlObservedMs ?? 0, unavailableObservedMs: bucket?.unavailableObservedMs ?? 0, unknownMs: Math.max(0, durationMs - activeObservedMs - pausedObservedMs) }, events: bucket ? { ...bucket.events } : null, stages: bucket ? { ...bucket.stages } : null, ...(bucket?.anchors ? { anchors: { first: { ...bucket.anchors.first }, last: { ...bucket.anchors.last } } } : {}) });
}
const connectionContinuity = this.identity.connectionId !== null ? (this.identity.sourceGeneration !== null ? "connection-id-and-source-generation" : "connection-id") : (this.identity.sourceGeneration !== null ? "source-generation" : "unverified");
const result = { version: 1, scope: "observed browser events; unknown coverage and paused intervals are not source inactivity; RAF is an opportunity, not scanout", clock: "browser-performance-now", coverageMethod: "inferred between render heartbeats at most 500ms apart; longer gaps are unknown, not proof of inactivity", eventCounting: "classifications may overlap and are not exclusive request outcomes", retentionScope: "up to 60 one-second buckets including the current partial bucket", clockAnomalyCount: this.clockAnomalyCount, lastClockAnomaly: this.lastClockAnomaly, anchorClock: "service-performance-now; native system-uptime; same response only, clocks not aligned; legacy continuity may be unverified", connectionContinuity, durationAttribution: "maxima belong to the bucket where the stage completed, including operations started earlier", durationScope: { delivered: "fetch start to body; includes service wait", decoded: "decode start to accepted decode", submitted: "accepted decode to DOM submission", rafOpportunities: "submission to RAF callback" }, ...this.identity, startedAtMs: this.startedAtMs, observedThroughMs: through, endedAtMs: this.endedAtMs ?? null, endReason: this.endReason ?? null, activity: this.activity, inFlight: this.inFlight, lastEvent: this.lastEvent, lastEventAtMs: this.lastEventAtMs, lastAtMs: { ...this.lastAtMs }, windowSeconds: 60, evictedBuckets: first, serializationOmittedBuckets: 0, buckets: rows };
let json = JSON.stringify(result);
while (json.length > 65536 && result.buckets.length > 1) {
result.buckets.shift();
result.serializationOmittedBuckets++;
json = JSON.stringify(result);
}
return json;
}
}
/** Mount the same explicit-consent device controls standalone or inside the current game preview. */
export function mountNativeCapture(root, options = {}) {
const scope = options.embedded ? root.shadowRoot ?? root.attachShadow({ mode: "open" }) : root;
if (options.embedded)
scope.innerHTML = NATIVE_CAPTURE_VIEW;
// Move the actual controls, retaining one set of handlers and state. A separate
// shadow host keeps their existing styles without leaking them into the player.
const toolbar = options.embedded && options.toolbarHost ? scope.querySelector(".toolbar") : undefined;
const toolbarScope = toolbar ? options.toolbarHost.shadowRoot ?? options.toolbarHost.attachShadow({ mode: "open" }) : undefined;
if (toolbarScope && toolbar) {
const layout = document.createElement("style");
layout.textContent = ":host{display:block}.toolbar{position:relative;bottom:auto;width:auto;height:44px;margin:0;white-space:nowrap}.feed-title{display:none}";
toolbarScope.replaceChildren(...Array.from(scope.querySelectorAll("style"), style => style.cloneNode(true)), layout, toolbar);
}
// The shared host overlays notices above Install without changing its position.
const footer = options.embedded && options.footerHost ? scope.querySelector(".capture-notices") : undefined;
const footerScope = footer ? options.footerHost.shadowRoot ?? options.footerHost.attachShadow({ mode: "open" }) : undefined;
if (footerScope && footer) {
footer.append(scope.querySelector("#saved"));
footerScope.replaceChildren(...Array.from(scope.querySelectorAll("style"), style => style.cloneNode(true)), footer);
}
const element = (id) => (scope.querySelector(`#${id}`) ?? toolbar?.querySelector(`#${id}`) ?? footer?.querySelector(`#${id}`));
const base = options.base ?? new URL("../", import.meta.url), app = element("capture-app"), frame = element("video");
const camera = element("camera"), enable = element("capture-enable");
const picker = mountDevicePicker(scope, id => { camera.value = id; camera.dispatchEvent(new Event("change")); });
const errorDialog = mountPreviewErrorDialog(element("capture-app")), settings = element("settings-dialog");
// Counters are unique validated HTTP frames, successful decodes, DOM submissions,
// and latest-submission RAF opportunities, not source frames or display scanout.
if (options.embedded)
element("device-shell").src = new URL("assets/devices/chromatic-codex.webp", base).href;
let state = { active: false }, pending = 0, statusPolling = false, settingsRequest = 0, inventoryId = "", lastCapture = "", lastFrame, frameUrl, lastError = "", toastTimer;
let frameGeneration = 0, framePolling = false, frameFallback = false, frameTimer, frameController, pageActive = true, statusRetryAt = 0;
let statusController, statusRefreshPending = false, statusRecoveryActive = false;
let measurement, frameRaf, rafFrame;
let stageTrace, stageTracePublishedAt = -Infinity;
const timingDiagnostic = window.location?.hash === "#chromatic-timing-v2";
let diagnosticRequests = 0;
let pendingEnable = false, listingDevices = false, discoveryFailed = false, revoking = false, visible = true, disposed = false, displayedError = "", connectionMessage = "";
let frameError = "", videoOptionsKey = "";
const identityErrorMessage = "The device video could not be verified. Reconnect your Chromatic.";
let displayDevices;
let activeStateKey = "", dismissedStateKey = "", activeRecoveryPath = "", dismissedRecoveryPath = "";
if (footer) {
for (const id of ["state", "view-feedback", "recovery", "screenshot-recovery"]) {
const notice = element(id);
notice.setAttribute("role", "button");
notice.setAttribute("aria-live", "polite");
notice.title = "Dismiss message";
notice.tabIndex = 0;
const dismiss = () => { if (id === "state")
dismissedStateKey = activeStateKey;
else if (id === "recovery")
dismissedRecoveryPath = activeRecoveryPath; notice.textContent = ""; if (id === "recovery" || id === "screenshot-recovery")
notice.hidden = true; };
notice.addEventListener("click", dismiss);
notice.addEventListener("keydown", event => { if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
dismiss();
} });
}
}
let uncertainRequest;
const blocked = () => !!uncertainRequest || !!pending || !!state.busy || !!state.revoked || !!state.closing || state.nativeFailure?.devicesReleased === false || !!state.recording && (state.recording.uncertain === true || ["starting", "recording", "finishing"].includes(state.recording.state));
const knownOperation = () => !!pending || !!uncertainRequest || ["connected", "connecting"].includes(state.observation?.connection ?? "") || !!state.recording || !!state.nativeFailure;
function requestResolved(next) {
const request = uncertainRequest;
if (!request || next.sessionId !== request.before.sessionId || next.generation !== request.before.generation || next.busy)
return false;
const { action, before, fields } = request;
if (action === "start_recording")
return !!next.recording && next.recording.captureId !== before.recording?.captureId;
if (action === "screenshot")
return next.lastCapture?.kind === "screenshot" && next.lastCapture.id !== before.lastCapture?.id;
if (action === "connect") {
const selected = fields.selection;
return next.observation?.connection === "connected" && next.observation.selection?.videoDeviceId === selected?.videoId && next.observation.selection?.audioDeviceId === selected?.audioId;
}
if (action === "enable")
return next.enabled === true && (before.enabled !== true || fields.audio === true && before.observation?.permissions?.audio !== "authorized" && next.observation?.permissions?.audio === "authorized");
if (action === "disable")
return next.enabled === false;
if (action === "disconnect" || action === "stop_recording")
return (action === "stop_recording" || next.observation?.connection === "disconnected") && (!before.recording || next.recording?.captureId === before.recording.captureId && ["complete", "partial", "failed"].includes(next.recording.state) && !next.recording.uncertain);
return false;
}
function showError(error, requested = true) {
if (disposed)
return;
const text = error instanceof Error ? error.message : String(error);
if (!requested && !knownOperation()) {
connectionMessage = "The device view is disconnected. Retry when you want to connect.";
render();
return;
}
lastError = text;
if (visible && text !== displayedError) {
displayedError = text;
errorDialog.show("device-capture", text);
}
}
async function request(action, fields = {}) {
if (uncertainRequest)
throw new Error("The previous device action is unconfirmed. Check its status before another action.");
if (pending && action !== "disable")
throw new Error("A capture operation is still finishing.");
if (action === "disable" && revoking)
throw new Error("Capture disable is already pending.");
if (!state.active)
throw new Error("This capture session is closed.");
const before = state;
let acknowledged = false;
noteTrace("controlStart");
invalidateFrame(false);
pending++;
if (action === "disable")
revoking = true;
if (action === "list_devices") {
listingDevices = true;
discoveryFailed = false;
}
render();
try {
const response = await fetch(new URL("control", base), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sessionId: state.sessionId, generation: state.generation, action, ...fields }), credentials: "omit", cache: "no-store", signal: AbortSignal.timeout(65000) });
const value = await response.json();
if (value.status && !disposed) {
accept(value.status);
acknowledged = true;
}
if (!response.ok && response.status >= 400 && response.status < 500)
acknowledged = true;
if (!response.ok || !value.success)
throw new Error(value.error ?? "The capture action could not finish.");
if (!acknowledged)
throw new Error("The device action returned no status.");
return value;
}
catch (error) {
if (action === "list_devices")
discoveryFailed = true;
if (!acknowledged && !["list_devices", "permission_status", "open_settings", "live_frame"].includes(action)) {
uncertainRequest = { action, before, fields };
throw new Error("The device action is unconfirmed because its reply was lost. Check the original status; do not retry the action.");
}
throw error;
}
finally {
pending--;
noteTrace("controlEnd");
if (action === "disable")
revoking = false;
if (action === "list_devices")
listingDevices = false;
render();
void pollStatus();
scheduleFrame();
}
}
function run(action) { void action().catch(error => showError(error)); }
function settingsTab(name, focus = false) { picker.close(); for (const kind of ["connection", "recording"]) {
const tab = element(`settings-${kind}`);
tab.setAttribute("aria-selected", String(kind === name));
tab.tabIndex = kind === name ? 0 : -1;
element(`settings-${kind}-panel`).hidden = kind !== name;
if (focus && kind === name)
tab.focus();
} }
function openSettings(tab = "connection") { settingsTab(tab); if (!settings.open)
settings.showModal(); element("settings-title").focus({ preventScroll: true }); }
function render() {
if (disposed)
return;
options.onChange?.(blocked());
publishMeasurement();
publishStageTrace();
const permissionRequired = state.permissionRequired !== false, audioSupported = state.audioSupported !== false;
const needsEnable = permissionRequired && (!state.enabled || !!state.observation?.permissions && state.observation.permissions.audio !== "authorized");
element("capture-permission-help").hidden = !needsEnable;
element("capture-device-field").hidden = needsEnable;
element("permission-status").hidden = !permissionRequired;
const connection = state.observation?.connection ?? "disconnected", connected = connection === "connected" && !!state.enabled, recording = !!state.recording && ["starting", "recording", "finishing"].includes(state.recording.state), busy = !!pending || !!state.busy || !!uncertainRequest;
const discovering = !!state.enabled && !needsEnable && !discoveryFailed && (pendingEnable || listingDevices);
const uncertain = !!uncertainRequest || state.recording?.uncertain === true || state.nativeFailure?.devicesReleased === false;
element("record").hidden = recording && !uncertain;
element("record").disabled = busy || uncertain || !connected || !state.enabled;
element("stop").hidden = !recording || uncertain;
element("stop").disabled = !!pending || !!state.busy;
element("stop").setAttribute("aria-label", "Stop recording");
const recovery = element("recovery"), recoveryPath = state.recording?.retainedPath ?? "";
if (recoveryPath !== activeRecoveryPath) {
activeRecoveryPath = recoveryPath;
dismissedRecoveryPath = "";
}
recovery.hidden = !recoveryPath || recoveryPath === dismissedRecoveryPath;
recovery.textContent = "A partial recording was saved. Ask Codex to recover it.";
element("device-empty").hidden = connected;
element("device-empty-message").textContent = discovering ? "Finding your Chromatic…" : !state.enabled ? "Device capture is off." : "Connect your Chromatic to see its live view.";
element("empty-settings").textContent = !permissionRequired ? "Connect" : !state.enabled ? "Enable capture…" : "Connection settings";
element("empty-settings").hidden = discovering;
element("empty-settings").disabled = busy;
const inventoryFresh = !!state.devices && (state.devicesListedAt === undefined || Date.now() - state.devicesListedAt < 60000);
const selectedVideo = displayDevices?.videoDevices.find(device => device.deviceId === camera.value), matches = matchingAudio(selectedVideo);
const soleDevice = displayDevices?.videoDevices.length === 1 && !!selectedVideo, singleDevice = element("single-device");
singleDevice.hidden = !soleDevice;
const singleLabel = soleDevice ? selectedVideo.label : "";
if (singleDevice.textContent !== singleLabel)
singleDevice.textContent = singleLabel;
enable.hidden = !needsEnable || connected || recording;
enable.disabled = busy || !state.active || !state.supported || pendingEnable;
element("connect").hidden = connected || recording || needsEnable;
element("connect").disabled = busy || discovering || needsEnable || !state.enabled || !camera.value || !inventoryFresh || (audioSupported && matches.length !== 1);
element("disconnect").hidden = !connected && !recording;
element("disconnect").disabled = busy;
element("screenshot").disabled = busy || !connected;
element("refresh").disabled = busy || connected || recording || !state.enabled || needsEnable;
camera.disabled = busy || discovering || connected || recording || !state.enabled || needsEnable;
picker.render(displayDevices?.videoDevices ?? [], camera.value, camera.disabled, !!soleDevice);
if (discovering && !displayDevices?.videoDevices.length)
element("device-picker-value").textContent = "Finding your Chromatic…";
element("duration").disabled = recording || uncertain;
if (!connected) {
frame.hidden = true;
lastFrame = undefined;
frameError = "";
if (frameUrl) {
URL.revokeObjectURL(frameUrl);
frameUrl = undefined;
frame.removeAttribute("src");
}
}
const elapsed = state.recording ? Math.max(0, Math.min(state.recording.durationMs, state.recording.durationMs - (Date.parse(state.recording.deadline) - Date.now()))) : 0;
const label = uncertain ? ""
: recording ? `● REC ${Math.floor(elapsed / 60000)}:${String(Math.floor(elapsed / 1000) % 60).padStart(2, "0")}`
: connectionMessage ? connectionMessage : !state.active ? "Device view disconnected — reopen it to connect."
: connection === "failed" ? "Connection interrupted — check Settings."
: connection === "connecting" ? "Connecting to your Chromatic…"
: "";
const stateKey = recording ? "recording" : label;
if (stateKey !== activeStateKey) {
activeStateKey = stateKey;
dismissedStateKey = "";
}
element("state").textContent = stateKey === dismissedStateKey ? "" : label;
element("state").dataset.recording = String(recording);
element("connection-hint").textContent = connectionMessage || (!state.active ? "Reopen the device view to connect." : !state.supported ? "Device capture is not supported on this platform." : discovering || discoveryFailed ? "" : state.enabled && state.devices && !state.devices.videoDevices.length ? "Connect and power on your Chromatic, then refresh." : state.enabled && state.devices && !inventoryFresh ? "Refresh devices to connect." : "");
element("audio-hint").textContent = state.enabled && !busy && audioSupported && selectedVideo && matches.length !== 1 ? (matches.length ? "Multiple matching USB audio inputs found. Check the connection, then refresh." : "USB audio not found. Check the connection, then refresh.") : "";
element("guidance").textContent = audioSupported ? "" : "USB audio is not available on Linux.";
element("permission-status").textContent = pendingEnable && needsEnable ? "Waiting for macOS permission…" : "";
const source = state.observation?.source;
element("details").textContent = source ? `${source.width} × ${source.height} · ${source.videoSamples} native video samples` : "";
const sound = element("sound");
sound.disabled = true;
sound.dataset.tooltip = audioSupported ? "Live audio playback is not available. USB audio is included in recordings." : "USB audio capture is not supported on Linux in this build.";
sound.setAttribute("aria-label", sound.dataset.tooltip);
element("annotate").disabled = !connected;
}
function matchingAudio(video) {
return video?.player ? state.devices?.audioDevices.filter(device => device.player === video.player) ?? [] : [];
}
function accept(value) {
if (disposed)
return;
if (requestResolved(value))
uncertainRequest = undefined;
if (value.sessionId !== state.sessionId || value.generation !== state.generation)
discoveryFailed = false;
if (frameStateIdentity(value) !== frameStateIdentity(state))
invalidateFrame(true);
const sameSession = value.active && value.enabled && value.sessionId === state.sessionId && value.generation === state.generation;
if (value.devices)
displayDevices = value.devices;
else if (!sameSession || !(value.busy || pending))
displayDevices = undefined;
inventoryId = value.devices?.inventoryId ?? "";
const videos = displayDevices?.videoDevices ?? [], previous = camera.value;
const nextOptionsKey = JSON.stringify(videos.map(device => [device.deviceId, device.label]));
if (nextOptionsKey !== videoOptionsKey) {
videoOptionsKey = nextOptionsKey;
camera.replaceChildren(new Option("Select device", ""), ...videos.map(d => new Option(d.label, d.deviceId)));
}
camera.value = videos.some(d => d.deviceId === previous) ? previous : videos.length === 1 ? videos[0].deviceId : "";
if (measurement && measurement.key !== measurementKey(value))
endMeasurement(measurement, "connection-change");
state = value;
syncStageTrace();
connectionMessage = "";
if (visible && (value.settingsRequest ?? 0) > settingsRequest) {
settingsRequest = value.settingsRequest;
openSettings(value.settingsTab);
}
if (value.lastCapture && value.lastCapture.id !== lastCapture) {
lastCapture = value.lastCapture.id;
const saved = element("saved");
saved.hidden = false;
const link = document.createElement("a");
link.href = new URL(`captures/${value.lastCapture.id}`, base).href;
link.textContent = value.lastCapture.kind === "video" ? "Download clip" : "View image";
link.target = "_blank";
link.rel = "noopener";
const copy = document.createElement("button");
copy.textContent = "Copy path";
const path = value.lastCapture.path;
copy.addEventListener("click", () => { void navigator.clipboard.writeText(path).catch(showError); });
saved.replaceChildren(document.createTextNode(value.lastCapture.kind === "video" ? "Recording saved. " : "Screenshot saved. "), link, document.createTextNode(" · "), copy);
clearTimeout(toastTimer);
toastTimer = setTimeout(() => { saved.hidden = true; }, 8000);
}
const identityError = !!value.frameIdentityFailure && value.active && value.enabled && value.observation?.connection === "connected";
if (identityError)
frameError = "";
if (!identityError && displayedError === identityErrorMessage) {
errorDialog.close();
displayedError = "";
if (lastError === identityErrorMessage)
lastError = "";
}
if (value.error)
showError(value.error, false);
else if (identityError && !uncertainRequest)
showError(identityErrorMessage, false);
if (!value.error && !identityError && !uncertainRequest && !frameError) {
lastError = "";
displayedError = "";
}
render();
scheduleFrame();
}
function displayActive() { return !disposed && visible && pageActive && document.visibilityState !== "hidden"; }
function frameStateIdentity(value) { return JSON.stringify([value.active, value.sessionId, value.generation, value.enabled, value.busy, value.revoked, value.closing, value.observation?.sessionId, value.observation?.connection, value.observation?.selection?.videoDeviceId, value.observation?.latestFrame?.connectionId ?? value.latestFrame?.connectionId, value.observation?.sourceGeneration ?? value.latestFrame?.sourceGeneration, !!value.latestFrame, value.frameIdentityFailure?.code]); }
function canReadFrame() { return displayActive() && state.active && state.enabled && state.observation?.connection === "connected" && !!state.latestFrame && !pending && !state.busy && !uncertainRequest && !state.revoked && !state.closing && !state.frameReleaseFailure && !state.frameIdentityFailure && state.nativeFailure?.devicesReleased !== false; }
function measurementKey(value) { return JSON.stringify([value.sessionId, value.generation, value.observation?.sessionId ?? value.latestFrame?.sessionId, value.observation?.connection, value.observation?.latestFrame?.connectionId ?? value.latestFrame?.connectionId, value.observation?.sourceGeneration ?? value.latestFrame?.sourceGeneration]); }
// The service cache may be empty without changing the native connection. Its
// native observation, not the cached frame, is the continuity authority.
function traceKey(value) { return JSON.stringify([value.sessionId, value.generation, value.observation?.sessionId, value.observation?.connection, value.observation?.latestFrame?.connectionId ?? null, value.observation?.sourceGeneration ?? null]); }
function traceActivity() { if (!displayActive())
return "hidden"; if (pending || state.busy || uncertainRequest || statusRecoveryActive)
return "control"; return canReadFrame() ? "active" : "unavailable"; }
function withTrace(trace, work) { if (!trace || trace !== stageTrace)
return; try {
work(trace);
}
catch { /* Diagnostic failures cannot interrupt the feed. */ } }
function noteTrace(event) { if (!stageTrace)
return; withTrace(stageTrace, value => value.event(event, performance.now())); }
function finishStageTrace(reason, previous = false) { if (!stageTrace)
return; withTrace(stageTrace, value => { const now = performance.now(); value.heartbeat(now, traceActivity()); value.end(reason, now); const json = value.snapshot(value.lastValidTime, true); if (json !== undefined) {
if (previous)
frame.dataset.captureStageTraceEnded = json;
else
frame.dataset.captureStageTrace = json;
stageTracePublishedAt = value.lastValidTime;
} }); }
function syncStageTrace() {
try {
if (state.diagnosticTraceEnabled !== true) {
stageTrace = undefined;
delete frame.dataset.captureStageTrace;
delete frame.dataset.captureStageTraceEnded;
return;
}
const key = traceKey(state), connected = state.active && state.enabled && !state.revoked && !state.closing && state.observation?.connection === "connected" && !!state.observation.latestFrame;
if (stageTrace && (!connected || stageTrace.key !== key || stageTrace.isEnded && connected)) {
if (!stageTrace.isEnded || connected)
finishStageTrace("connection-change", connected);
if (connected) {
stageTrace = undefined;
delete frame.dataset.captureStageTrace;
}
}
if (!connected || stageTrace || disposed)
return;
const uuid = (value) => typeof value === "string" && /^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/i.test(value) ? value : null;
const number = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
stageTrace = new BrowserStageTrace(key, { serviceSessionId: uuid(state.sessionId), serviceGeneration: number(state.generation), nativeSessionId: uuid(state.observation?.sessionId), connectionId: uuid(state.observation?.latestFrame?.connectionId), sourceGeneration: number(state.observation?.sourceGeneration) }, performance.now(), traceActivity());
}
catch { /* Do not make diagnostics a prerequisite for display. */ }
}
function publishStageTrace() { if (!stageTrace)
return; withTrace(stageTrace, value => { value.heartbeat(performance.now(), traceActivity()); const now = value.lastValidTime, json = value.snapshot(now, false, stageTracePublishedAt); if (json !== undefined) {
frame.dataset.captureStageTrace = json;
stageTracePublishedAt = now;
} }); }
function endMeasurement(value, reason, now = performance.now()) {
if (value.endedAtMs !== undefined)
return;
if (rafFrame?.measurement === value && rafFrame.clockSample) {
value.diagnostics?.missRaf();
rafFrame.clockSample = undefined;
}
value.endedAtMs = Math.min(value.startedAtMs + 60000, Math.max(value.startedAtMs, now));
value.endReason = reason;
}
function measurementOpen(value, now = performance.now()) {
if (now - value.startedAtMs >= 60000)
endMeasurement(value, "window-complete", now);
return value.endedAtMs === undefined;
}
function frameMeasurement() {
const key = measurementKey(state);
const distribution = () => ({ buckets: Array(60).fill(0), intervalsMs: [], observedIntervals: 0, longestGapMs: null });
if (!measurement || measurement.key !== key) {
const startedAtMs = performance.now();
let diagnostics;
if (timingDiagnostic)
try {
diagnostics = createTimingDiagnostics(startedAtMs);
}
catch { /* Diagnostic setup must not stop the live view. */ }
measurement = { key, serviceSessionId: state.sessionId, serviceGeneration: state.generation, nativeSessionId: state.observation?.sessionId ?? state.latestFrame?.sessionId, connectionId: state.observation?.latestFrame?.connectionId ?? state.latestFrame?.connectionId, sourceGeneration: state.observation?.sourceGeneration ?? state.latestFrame?.sourceGeneration, startedAtMs, counts: { delivered: 0, decoded: 0, submitted: 0, rafOpportunities: 0, duplicates: 0, errors: 0, stale: 0 }, firstAtMs: {}, lastAtMs: {}, sequences: {}, unobservedPreviewSequences: null, sequenceGapCounterSaturated: false, distributions: { delivered: distribution(), decoded: distribution(), submitted: distribution(), rafOpportunities: distribution() }, diagnostics };
}
return measurement;
}
function countFrame(value, counter, now = performance.now()) {
if (!measurementOpen(value, now))
return false;
// One fixed window, scalar high-water marks, and bounded timing samples only.
value.counts[counter] = Math.min(100000, value.counts[counter] + 1);
if (value.counts[counter] === 100000)
endMeasurement(value, "counter-limit", now);
return true;
}
function countUniqueFrame(value, stage, identity, now = performance.now()) {
const sequence = identity.previewSequence ?? identity.frameId, previous = value.sequences[stage];
if (previous !== undefined && sequence <= previous)
return;
value.sequences[stage] = sequence;
if (countFrame(value, stage, now)) {
const samples = value.distributions[stage], bucket = Math.min(59, Math.floor(Math.max(0, now - value.startedAtMs) / 1000)), last = value.lastAtMs[stage];
samples.buckets[bucket] = samples.buckets[bucket] + 1;
if (last !== undefined) {
const interval = now - last;
samples.observedIntervals++;
samples.longestGapMs = Math.max(samples.longestGapMs ?? 0, interval);
if (samples.intervalsMs.length < 4096)
samples.intervalsMs.push(interval);
}
value.firstAtMs[stage] ??= now;
value.lastAtMs[stage] = now;
}
}
function summarizeDistributions(value, now) {
const observedThroughMs = value.endedAtMs ?? now, elapsedMs = Math.max(0, observedThroughMs - value.startedAtMs), completeSeconds = Math.min(60, Math.floor(elapsedMs / 1000));
if (value.summary && (value.summary.finalized || value.endedAtMs === undefined && now - value.summary.calculatedAtMs < 1000))
return value.summary;
const percentile = (sorted, fraction) => sorted[Math.max(0, Math.ceil(sorted.length * fraction) - 1)] ?? null;
const stage = (name) => {
const samples = value.distributions[name], rates = samples.buckets.slice(0, completeSeconds).sort((a, b) => a - b), intervals = samples.intervalsMs.slice().sort((a, b) => a - b);
return { oneSecondRatesFps: { min: rates[0] ?? null, mean: rates.length ? rates.reduce((sum, count) => sum + count, 0) / rates.length : null, p50: percentile(rates, 0.5), p95: percentile(rates, 0.95), p99: percentile(rates, 0.99), max: rates.at(-1) ?? null },
...(completeSeconds < 60 ? { partialSecond: { durationMs: elapsedMs - completeSeconds * 1000, uniqueCount: samples.buckets[completeSeconds] } } : {}),
interframe: { observedIntervals: samples.observedIntervals, retainedIntervals: intervals.length, intervalLimitReached: intervals.length === 4096, omittedIntervals: samples.observedIntervals - intervals.length, p50Ms: percentile(intervals, 0.5), p95Ms: percentile(intervals, 0.95), p99Ms: percentile(intervals, 0.99), longestGapMs: samples.longestGapMs } };
};
// Sorting is limited to once per second, plus the final window snapshot.
return value.summary = { calculatedAtMs: now, observedThroughMs, elapsedMs, finalized: value.endedAtMs !== undefined, partialWindow: elapsedMs < 60000, completeSeconds, counterLimitReached: value.endReason === "counter-limit", stages: { delivered: stage("delivered"), decoded: stage("decoded"), submitted: stage("submitted"), rafOpportunities: stage("rafOpportunities") } };
}
function publishMeasurement() {
if (!measurement)
return;
const value = measurement, now = performance.now();
measurementOpen(value, now);
const elapsedMs = Math.max(0, (value.endedAtMs ?? now) - value.startedAtMs);
// Browser elapsed time includes view/control pauses. Native uptime and source
// PTS stay separate; an RAF callback is not proof that a display showed a frame.
frame.dataset.captureMetrics = JSON.stringify({ version: 1, clock: "browser-performance-now", clockAlignment: "not-aligned-to-native", rafMeaning: "callback-opportunity-not-physical-presentation", windowLimitMs: 60000, counterLimit: 100000, intervalLimitPerStage: 4096, percentileMethod: "nearest-rank", intervalScope: "between-same-connection-unique-events; quantiles use retained intervals", sequenceGapScope: "encoded preview sequences not observed between delivered frames; includes upstream and view pauses, not source drops; first and last censored", unobservedPreviewSequences: value.unobservedPreviewSequences, sequenceGapCounterSaturated: value.sequenceGapCounterSaturated, serviceSessionId: value.serviceSessionId, serviceGeneration: value.serviceGeneration, nativeSessionId: value.nativeSessionId, connectionId: value.connectionId, sourceGeneration: value.sourceGeneration, sequenceBasis: value.sequenceBasis, startedAtMs: value.startedAtMs, endedAtMs: value.endedAtMs, endReason: value.endReason, elapsedMs, counts: value.counts, firstAtMs: value.firstAtMs, lastAtMs: value.lastAtMs, distribution: summarizeDistributions(value, now) });
if (value.diagnostics)
frame.dataset.captureDiagnostics = JSON.stringify({ ...value.diagnostics.summary(now, value.endedAtMs), serviceSessionId: value.serviceSessionId, serviceGeneration: value.serviceGeneration, nativeSessionId: value.nativeSessionId, connectionId: value.connectionId, sourceGeneration: value.sourceGeneration });
else
delete frame.dataset.captureDiagnostics;
}
function observeRaf(value, identity, generation, submittedAt, trace, clockSample) {
if (!measurementOpen(value) && (!trace || trace !== stageTrace)) {
if (clockSample)
value.diagnostics?.missRaf();
return;
}
if (rafFrame?.clockSample)
rafFrame.measurement.diagnostics?.missRaf();
rafFrame = { measurement: value, identity, generation, submittedAt, trace, clockSample };
if (frameRaf !== undefined)
return;
frameRaf = requestAnimationFrame(() => { frameRaf = undefined; const pending = rafFrame; rafFrame = undefined; if (pending && pending.generation === frameGeneration && canReadFrame()) {
const now = performance.now();
if (measurementOpen(pending.measurement)) {
countUniqueFrame(pending.measurement, "rafOpportunities", pending.identity);
if (pending.clockSample)
pending.measurement.diagnostics?.observeRaf(pending.clockSample, now, Date.now());
publishMeasurement();
}
else if (pending.clockSample)
pending.measurement.diagnostics?.missRaf();
if (pending.trace)
withTrace(pending.trace, trace => trace.stage("rafOpportunities", pending.identity, now, now - pending.submittedAt));
}
else if (pending?.clockSample)
pending.measurement.diagnostics?.missRaf(); });
}
function invalidateFrame(clear) { frameGeneration++; frameFallback = false; clearTimeout(frameTimer); frameTimer = undefined; frameController?.abort(); if (frameRaf !== undefined)
cancelAnimationFrame(frameRaf); frameRaf = undefined; if (rafFrame?.clockSample)
rafFrame.measurement.diagnostics?.missRaf(); rafFrame = undefined; lastFrame = undefined; if (clear) {
frame.hidden = true;
if (frameUrl)
URL.revokeObjectURL(frameUrl);
frameUrl = undefined;
frame.removeAttribute("src");
for (const name of ["nativeSessionId", "frameId", "frameSha256", "sourcePts", "transferMs", "transferScope", "serviceWaitMs", "decodeMs", "connectionId", "previewSequence", "sourceGeneration", "callbackToPublishMs"])
delete frame.dataset[name];
} publishMeasurement(); }
function scheduleFrame(delay = 0) { if (frameTimer !== undefined || framePolling || statusRecoveryActive || !canReadFrame())
return; frameTimer = setTimeout(() => { frameTimer = undefined; void pollFrame(); }, delay); }
function frameIdentity(response) {
const header = (name) => response.headers.get(`X-Chromatic-${name}`);
const number = (name, integer = false, minimum = 0) => { const raw = header(name), value = raw === null ? NaN : Number(raw); if (raw === null || !raw.trim() || !Number.isFinite(value) || value < minimum || integer && !Number.isSafeInteger(value))
throw new Error("The native preview frame metadata was invalid."); return value; };
const uuid = (name, optional = false) => { const value = header(name); if (value === null && optional)
return undefined; if (value === null || !/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/i.test(value))
throw new Error("The native preview frame identity was invalid."); return value; };
const sha256 = header("Frame-Sha256");
if (!sha256 || !/^[a-f0-9]{64}$/.test(sha256))
throw new Error("The native preview frame checksum was missing or invalid.");
const identity = { nativeSessionId: uuid("Native-Session-Id"), frameId: number("Frame-Id", true), sha256, sourcePTS: number("Source-Pts"), width: number("Frame-Width", true, 1), height: number("Frame-Height", true, 1) };
if (identity.width > 4096 || identity.height > 4096 || identity.width * identity.height > 8_388_608)
throw new Error("The native preview dimensions exceeded their limit.");
identity.connectionId = uuid("Connection-Id", true);
if (header("Preview-Sequence") !== null)
identity.previewSequence = number("Preview-Sequence", true, 1);
if (header("Source-Generation") !== null)
identity.sourceGeneration = number("Source-Generation", true);
const timing = ["Callback-Received-At-Ms", "Encode-Started-At-Ms", "Encode-Finished-At-Ms", "Published-At-Ms"];
if (header("Timing-Clock") !== null || timing.some(name => header(name) !== null)) {
if (header("Timing-Clock") !== "system-uptime")
throw new Error("The native preview clock was not recognized.");
const values = timing.map(name => number(name));
if (values.some((value, index) => index > 0 && value < values[index - 1]))
throw new Error("The native preview timing was out of order.");
[identity.callbackMs, identity.encodeStartedMs, identity.encodeFinishedMs, identity.publishedMs] = values;
identity.callbackToPublishMs = values[3] - values[0];
}
const detail = ["Preview-Queued-At-Ms", "Hash-Finished-At-Ms", "File-Written-At-Ms"];
if (detail.some(name => header(name) !== null)) {
if (detail.some(name => header(name) === null) || identity.callbackMs === undefined || identity.encodeStartedMs === undefined || identity.encodeFinishedMs === undefined || identity.publishedMs === undefined)
throw new Error("The native preview timing was incomplete.");
const [queued, hash, written] = detail.map(name => number(name));
if (queued < identity.callbackMs || queued > identity.encodeStartedMs || hash < identity.encodeFinishedMs || written < hash || written > identity.publishedMs)
throw new Error("The native preview timing was out of order.");
identity.previewQueuedMs = queued;
identity.hashFinishedMs = hash;
identity.fileWrittenMs = written;
}
if (header("Read-Duration-Ms") !== null)
identity.readMs = number("Read-Duration-Ms");
if (header("Wait-Duration-Ms") !== null)
identity.waitMs = number("Wait-Duration-Ms");
return identity;
}
async function pollStatus(force = false) {
if (!displayActive())
return;
// An aborted request keeps the slot until it settles; coalesce resets into one refresh.
if (force) {
const changed = !statusRecoveryActive;
statusRecoveryActive = true;
if (changed)
publishStageTrace();
statusRefreshPending = true;
if (statusPolling)
statusController?.abort();
}
if (statusPolling || pending || !statusRefreshPending && performance.now() < statusRetryAt)
return;
const recovery = statusRefreshPending;
statusRefreshPending = false;
statusPolling = true;
const generation = frameGeneration, controller = new AbortController();
statusController = controller;
let accepted = false;
const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(5000)]);
try {
const response = await fetch(new URL("status", base), { credentials: "omit", cache: "no-store", signal });
if (!response.ok)
throw new Error("The local capture session is not responding.");
const next = await response.json();
if (signal.aborted)
throw signal.reason;
if (generation === frameGeneration && !pending && displayActive()) {
statusRetryAt = 0;
accept(next);
accepted = true;
}
}
catch (error) {
if (!controller.signal.aborted && generation === frameGeneration && displayActive()) {
statusRetryAt = performance.now() + 1000;
showError(error, false);
}
}
finally {
statusPolling = false;
statusController = undefined;
if (recovery) {
if (!accepted && generation !== frameGeneration && !disposed)
statusRefreshPending = true;
else {
statusRecoveryActive = false;
publishStageTrace();
scheduleFrame(500);
}
}
if (statusRefreshPending && displayActive() && !pending)
void pollStatus();
}
}
async function pollFrame() {
if (framePolling || !canReadFrame())
return;
framePolling = true;
const generation = frameGeneration, controller = new AbortController(), metrics = frameMeasurement(), trace = stageTrace;
frameController = controller;
publishMeasurement();
let next, image, failed = false, noChange = false, capacity = false, settled = false, handled = false, stale = false, errorCounted = false, timedOut = false, timedResponse = false, timeout;
const clockAttempted = !!metrics.diagnostics && measurementOpen(metrics) && diagnosticRequests++ % 10 === 0;
if (clockAttempted)
metrics.diagnostics?.noteTimedRequest();
const countStale = () => { if (!stale) {
stale = true;
countFrame(metrics, "stale");
if (trace)
withTrace(trace, value => value.event("stale", performance.now()));
} };
const countError = () => { if (!errorCounted) {
errorCounted = true;
countFrame(metrics, "errors");
if (trace)
withTrace(trace, value => value.event("error", performance.now()));
} };
controller.signal.addEventListener("abort", () => { if (!timedOut)
countStale(); image?.removeAttribute("src"); if (next) {
URL.revokeObjectURL(next);
next = undefined;
} }, { once: true });
const current = () => { const valid = generation === frameGeneration && !controller.signal.aborted && canReadFrame(); if (!valid && !timedOut)
countStale(); return valid; };
const work = (async () => {
const wallStartedAt = Date.now(), fetchStarted = performance.now();
if (trace)
withTrace(trace, value => { value.event("request", fetchStarted); value.phase("fetch", fetchStarted); });
// Only advance the cursor after a frame was actually submitted. A failed
// decode must be able to retry the same verified frame on the next request.
const fallback = frameFallback;
frameFallback = false;
const after = !fallback && lastFrame && { nativeSessionId: lastFrame.nativeSessionId, frameId: lastFrame.frameId, sha256: lastFrame.sha256, connectionId: lastFrame.connectionId, sourceGeneration: lastFrame.sourceGeneration, previewSequence: lastFrame.previewSequence };
const route = fallback ? (clockAttempted ? "live-frame-timed" : "live-frame") : (clockAttempted ? "live-frame-next-timed" : "live-frame-next");
const response = await fetch(new URL(route, base), { credentials: "omit", cache: "no-store", signal: controller.signal, ...(after ? { headers: { "X-Chromatic-After-Identity": JSON.stringify(after) } } : {}) });
const headersAt = performance.now();
if (trace)
withTrace(trace, value => value.event(response.status === 200 ? "http200" : response.status === 204 ? "http204" : response.status === 404 ? "http404" : response.status === 409 ? "http409" : response.status === 429 ? "http429" : "httpOther", headersAt));
if (clockAttempted && measurementOpen(metrics, headersAt)) {
timedResponse = true;
const raw = response.headers.get("X-Chromatic-Clock-Sample-Ms"), hasClock = raw !== null && !!raw.trim() && Number.isFinite(Number(raw)) && Number(raw) >= 0, unavailable = response.headers.get("X-Chromatic-Clock-Unavailable-Reason");
const reason = unavailable === "busy" || unavailable === "unsupported" || unavailable === "invalid" || unavailable === "error" ? unavailable : undefined;
metrics.diagnostics?.noteTimedResponse(hasClock, hasClock ? undefined : reason);
}
if (!current())
return;
if (response.status === 204) {
noChange = true;
return;
}
if (response.status === 409) {
if (!statusRecoveryActive) {
invalidateFrame(true);
void pollStatus(true);
}
failed = true;
return;
}
if (response.status === 429) {
capacity = true;
frameFallback = true;
return;
}
if (response.status === 404) {
countError();
if (!statusRecoveryActive) {
invalidateFrame(true);
void pollStatus(true);
}
failed = true;
return;
}
if (!response.ok)
throw new Error("The native preview frame could not be read.");
const identity = frameIdentity(response), expectedNative = state.observation?.sessionId ?? state.latestFrame?.sessionId, expectedConnection = state.observation?.latestFrame?.connectionId ?? state.latestFrame?.connectionId, expectedSource = state.observation?.sourceGeneration ?? state.latestFrame?.sourceGeneration;
if (!expectedNative || identity.nativeSessionId !== expectedNative || expectedConnection !== undefined && identity.connectionId !== expectedConnection || expectedSource !== undefined && identity.sourceGeneration !== expectedSource)
throw new Error("The native preview frame belongs to a different connection.");
const blob = await response.blob();
const bodyAt = performance.now();
if (!current())
return;
if (!blob.size || blob.size > 1024 * 1024 || blob.type !== "image/jpeg")
throw new Error("The native preview frame was invalid.");
const length = response.headers.get("Content-Length");
if (length !== null && Number(length) !== blob.size)
throw new Error("The native preview frame was incomplete.");
const basis = identity.previewSequence === undefined ? "source-frame-id" : "preview-sequence", incoming = identity.previewSequence ?? identity.frameId;
if (metrics.sequenceBasis && metrics.sequenceBasis !== basis)
throw new Error("The native preview sequence basis changed within its connection.");
metrics.sequenceBasis = basis;
const previous = metrics.latest?.previewSequence ?? metrics.latest?.frameId;
if (previous !== undefined && incoming < previous) {
countStale();
return;
}
if (previous === incoming) {
if (metrics.latest.sha256 !== identity.sha256)
throw new Error("The native preview frame identity changed its bytes.");
countFrame(metrics, "duplicates");
}
else {
if (basis === "preview-sequence" && measurementOpen(metrics)) {
const gaps = (metrics.unobservedPreviewSequences ?? 0) + (previous === undefined ? 0 : incoming - previous - 1);
metrics.unobservedPreviewSequences = Math.min(100000, gaps);
if (gaps >= 100000)
metrics.sequenceGapCounterSaturated = true;
}
metrics.latest = identity;
countUniqueFrame(metrics, "delivered", identity);
if (trace)
withTrace(trace, value => value.stage("delivered", identity, bodyAt, bodyAt - fetchStarted));
}
publishMeasurement();
if (lastFrame && (lastFrame.previewSequence ?? lastFrame.frameId) === incoming)
return;
const transferMs = bodyAt - fetchStarted;
next = URL.createObjectURL(blob);
image = new Image();
image.src = next;
const decodeStarted = performance.now();
if (trace)
withTrace(trace, value => value.phase("decode", decodeStarted));
await image.decode();
const decodedAt = performance.now();
if (image.naturalWidth !== identity.width || image.naturalHeight !== identity.height)
throw new Error("The native preview decoded dimensions did not match.");
countUniqueFrame(metrics, "decoded", identity);
if (!current())
return;
if (trace)
withTrace(trace, value => value.stage("decoded", identity, decodedAt, decodedAt - decodeStarted));
const old = frameUrl;
frame.src = next;
const submittedAt = performance.now();
frame.hidden = false;
frameUrl = next;
next = undefined;
lastFrame = identity;
frameError = "";
frame.dataset.nativeSessionId = identity.nativeSessionId;
frame.dataset.frameId = String(identity.frameId);
frame.dataset.frameSha256 = identity.sha256;
frame.dataset.sourcePts = String(identity.sourcePTS);
frame.dataset.transferMs = String(transferMs);
frame.dataset.transferScope = identity.waitMs === undefined ? "fetch-to-body-immediate-request" : "fetch-to-body-includes-service-wait";
if (identity.waitMs === undefined)
delete frame.dataset.serviceWaitMs;
else
frame.dataset.serviceWaitMs = String(identity.waitMs);
frame.dataset.decodeMs = String(performance.now() - decodeStarted);
for (const name of ["connectionId", "previewSequence", "sourceGeneration", "callbackToPublishMs"]) {
if (identity[name] === undefined)
delete frame.dataset[name];
else
frame.dataset[name] = String(identity[name]);
}
const previousSubmission = metrics.sequences.submitted;
countUniqueFrame(metrics, "submitted", identity);
if (trace)
withTrace(trace, value => {
if (!value.stage("submitted", identity, submittedAt, submittedAt - decodedAt))
return;
const raw = response.headers.get("X-Chromatic-Cache-Published-At-Ms"), service = raw !== null && raw.trim() ? Number(raw) : null;
const nativeTiming = identity.callbackMs !== undefined && identity.encodeStartedMs !== undefined && identity.encodeFinishedMs !== undefined && identity.publishedMs !== undefined ? { clock: "system-uptime", callbackReceivedAtMs: identity.callbackMs, ...(identity.previewQueuedMs === undefined ? {} : { previewQueuedAtMs: identity.previewQueuedMs }), encodeStartedAtMs: identity.encodeStartedMs, encodeFinishedAtMs: identity.encodeFinishedMs, ...(identity.hashFinishedMs === undefined ? {} : { hashFinishedAtMs: identity.hashFinishedMs }), ...(identity.fileWrittenMs === undefined ? {} : { fileWrittenAtMs: identity.fileWrittenMs }), publishedAtMs: identity.publishedMs } : null;
value.anchor(identity, service, submittedAt, nativeTiming);
});
let clockSample;
if (metrics.diagnostics && measurementOpen(metrics, submittedAt) && (previousSubmission === undefined || incoming > previousSubmission)) {
const rawClock = response.headers.get("X-Chromatic-Clock-Sample-Ms"), clock = rawClock === null || !rawClock.trim() ? undefined : Number(rawClock);
try {
clockSample = metrics.diagnostics.observe(image, { startedAt: fetchStarted, headersAt, bodyAt, decodeStartedAt: decodeStarted, decodedAt, submittedAt, wallStartedAt, wallSubmittedAt: Date.now(), clockSampleMs: clockAttempted && Number.isFinite(clock) ? clock : undefined, readMs: identity.readMs, waitMs: identity.waitMs, callbackMs: identity.callbackMs, previewQueuedMs: identity.previewQueuedMs, encodeStartedMs: identity.encodeStartedMs, encodeFinishedMs: identity.encodeFinishedMs, hashFinishedMs: identity.hashFinishedMs, fileWrittenMs: identity.fileWrittenMs, publishedMs: identity.publishedMs, clockAttempted });
}
catch { /* Diagnostics cannot interrupt capture or cleanup. */ }
}
observeRaf(metrics, identity, generation, submittedAt, trace, clockSample);
if (!state.error) {
lastError = "";
displayedError = "";
}
if (old)
URL.revokeObjectURL(old);
})();
const finish = () => { clearTimeout(timeout); if (clockAttempted && !timedResponse && measurementOpen(metrics))
metrics.diagnostics?.noteTimedNoResponse(); if (next)
URL.revokeObjectURL(next); if (frameController === controller)
frameController = undefined; framePolling = false; if (trace)
withTrace(trace, value => value.phase(null, performance.now())); publishMeasurement(); scheduleFrame(failed ? 500 : capacity ? 100 : noChange ? 25 : 0); };
// A timed-out decoder may settle later. Keep the single in-flight latch until
// it does, so a stuck decoder cannot accumulate replacement requests.
void work.then(() => { settled = true; if (handled)
finish(); }, () => { settled = true; if (handled)
finish(); });
try {
await Promise.race([work, new Promise((_, reject) => { timeout = setTimeout(() => { timedOut = true; countError(); if (trace)
withTrace(trace, value => value.event("timeout", performance.now())); controller.abort(); image?.removeAttribute("src"); reject(new Error("The native preview frame timed out.")); }, 5000); })]);
}
catch (error) {
failed = true;
if (generation === frameGeneration) {
countError();
if (displayActive() && !pending) {
frameError = error instanceof Error ? error.message : String(error);
showError(error, false);
}
}
else
countStale();
}
finally {
handled = true;
if (settled)
finish();
}
}
element("refresh").addEventListener("click", () => run(() => request("list_devices")));
enable.addEventListener("click", () => { if (!state.active || pendingEnable || state.enabled && state.observation?.permissions?.audio === "authorized")
return; pendingEnable = true; void (async () => { await request("enable", { audio: true }); if (state.enabled && !disposed) {
await request("list_devices");
if (state.devices?.videoDevices.length === 1) {
camera.value = state.devices.videoDevices[0].deviceId;
await connectSelected();
settings.close();
}
} })().catch(showError).finally(() => { pendingEnable = false; render(); }); });
camera.addEventListener("change", () => render());
async function connectSelected() { const video = state.devices?.videoDevices.find(d => d.deviceId === camera.value), matches = matchingAudio(video); if (!state.devices || state.devicesListedAt !== undefined && Date.now() - state.devicesListedAt >= 60000)
throw new Error("Refresh devices and use the exact selection from the current list."); if (!video)
throw new Error("Select a Chromatic from the current device list."); if (state.audioSupported !== false && matches.length !== 1)
throw new Error("Matching USB audio could not be resolved. Check the connection and refresh devices."); const sound = matches[0]; return request("connect", { inventoryId, selection: { videoId: video.deviceId, videoLabel: video.label, ...(state.audioSupported !== false && sound ? { audioId: sound.deviceId, audioLabel: sound.label } : {}) } }); }
element("connect").addEventListener("click", () => run(connectSelected));
for (const [id, action] of [["disconnect", "disconnect"], ["screenshot", "screenshot"]])
element(id).addEventListener("click", () => run(() => request(action)));
element("stop").addEventListener("click", () => { if (!uncertainRequest && !state.recording?.uncertain && state.nativeFailure?.devicesReleased !== false && state.recording && ["starting", "recording", "finishing"].includes(state.recording.state))
run(() => request("stop_recording")); });
element("empty-settings").addEventListener("click", () => {
if (state.permissionRequired !== false && !state.enabled) {
openSettings();
return;
}
run(async () => {
await request("list_devices");
const devices = state.devices?.videoDevices ?? [];
if (devices.length !== 1) {
openSettings();
if (!devices.length)
throw new Error("No supported Chromatic video device was found. Check USB, power, and access to the video device.");
return;
}
camera.value = devices[0].deviceId;
await connectSelected();
});
});
element("record").addEventListener("click", () => run(() => request("start_recording", { durationMs: Number(element("duration").value) * 1000 })));
for (const host of [app, ...(toolbar ? [toolbar] : []), ...(footer ? [footer] : [])])
for (const button of Array.from(host.querySelectorAll("button[title]")))
button.dataset.tooltip = button.title;
const disposeTooltips = mountControlTooltips(app), disposeToolbarTooltips = toolbar ? mountControlTooltips(toolbar) : undefined, disposeFooterTooltips = footer ? mountControlTooltips(footer) : undefined;
let selectedColor = DEVICE_COLORS[0], colorGeneration = 0;
function paint() { if (visible && !disposed) {
document.body.style.setProperty("--cgv-background", selectedColor.background);
document.body.style.setProperty("--cgv-ink", selectedColor.ink);
} for (const color of DEVICE_COLORS)
element(`skin-${color.id}`).setAttribute("aria-pressed", String(color.id === selectedColor.id)); }
async function chooseColor(color) { const generation = ++colorGeneration; if (color.id === selectedColor.id)
return; const image = document.createElement("img"); image.className = "shell"; image.id = "device-shell"; image.alt = ""; image.setAttribute("aria-hidden", "true"); image.draggable = false; image.src = new URL(`assets/devices/chromatic-${color.id}.webp`, base).href; try {
await image.decode();
if (generation !== colorGeneration)
return;
element("device-shell").replaceWith(image);
selectedColor = color;
paint();
try {
localStorage.setItem(DEVICE_COLOR_KEY, color.id);
}
catch { /* Optional preference only. */ }
}
catch {
element("view-feedback").textContent = "That color could not load. Try another color.";
} }
for (const color of DEVICE_COLORS)
element(`skin-${color.id}`).addEventListener("click", () => { void chooseColor(color); });
paint();
try {
const stored = DEVICE_COLORS.find(c => c.id === localStorage.getItem(DEVICE_COLOR_KEY));
if (stored)
void chooseColor(stored);
}
catch { /* Optional preference only. */ }
element("annotate").addEventListener("click", () => { const result = requestDiagnosticAnnotation(frame, { Source: "Native Chromatic live feed", Request: "Help review this device view. Native video does not authenticate the installed ROM." }); element("view-feedback").textContent = result.accepted ? "Ready to send. Send the annotation to ask Codex about this view." : "Use the browser's annotation button to share this view with Codex."; });
element("settings-open").addEventListener("click", () => openSettings());
element("settings-close").addEventListener("click", () => settings.close());
settings.addEventListener("close", () => { picker.close(); element("settings-open").focus({ preventScroll: true }); });
for (const kind of ["connection", "recording"]) {
const tab = element(`settings-${kind}`);
tab.addEventListener("click", () => settingsTab(kind));
tab.addEventListener("keydown", event => { if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) {
event.preventDefault();
settingsTab(event.key === "Home" ? "connection" : event.key === "End" ? "recording" : kind === "connection" ? "recording" : "connection", true);
} });
}
// Hiding or closing this display must not cancel a native recording.
const pagehide = () => { pageActive = false; invalidateFrame(true); noteTrace("visibilityHidden"); publishStageTrace(); };
const pageshow = () => { pageActive = true; noteTrace(displayActive() ? "visibilityVisible" : "visibilityHidden"); publishStageTrace(); void pollStatus(); scheduleFrame(); };
const visibility = () => { invalidateFrame(true); noteTrace(displayActive() ? "visibilityVisible" : "visibilityHidden"); publishStageTrace(); if (displayActive()) {
void pollStatus();
scheduleFrame();
} };
window.addEventListener("pagehide", pagehide);
window.addEventListener("pageshow", pageshow);
document.addEventListener("visibilitychange", visibility);
render();
void pollStatus();
const pollTimer = setInterval(() => { void pollStatus(); }, 500), renderTimer = setInterval(render, 250);
return {
canLeave: () => !blocked(),
setVisible(value) { const changed = visible !== value; if (changed)
invalidateFrame(true); visible = value; if (changed) {
noteTrace(displayActive() ? "visibilityVisible" : "visibilityHidden");
publishStageTrace();
} if (!value) {
errorDialog.close();
settings.close();
displayedError = "";
}
else {
paint();
try {
const stored = DEVICE_COLORS.find(c => c.id === localStorage.getItem(DEVICE_COLOR_KEY));
if (stored)
void chooseColor(stored);
}
catch { /* Optional preference only. */ }
if (lastError)
showError(lastError);
void pollStatus();
scheduleFrame();
} },
dispose() { disposed = true; invalidateFrame(true); finishStageTrace("disposed"); if (measurement)
endMeasurement(measurement, "disposed"); publishMeasurement(); picker.dispose(); colorGeneration++; clearInterval(pollTimer); clearInterval(renderTimer); clearTimeout(toastTimer); window.removeEventListener("pagehide", pagehide); window.removeEventListener("pageshow", pageshow); document.removeEventListener("visibilitychange", visibility); disposeTooltips(); disposeToolbarTooltips?.(); disposeFooterTooltips?.(); toolbarScope?.replaceChildren(); footerScope?.replaceChildren(); errorDialog.dispose(); },
};
}
//# sourceMappingURL=native-client.js.mapSHA-256: e2522b5442d3f95d746e59bd1bce51f5c5ac5755a0dbdd9088ad4f1130e66648