Files
linuxcncWebWASM/web/worker/opfs-worker-bridge.mjs

80 lines
3.5 KiB
JavaScript

import {
clearPowerOffCheckpoint,
clearUiState,
loadPowerOffCheckpoint,
loadUiState,
savePowerOffCheckpoint,
saveUiState,
} from "./opfs-ui-state.mjs";
import { DEFAULT_MACHINE, machineDefinition } from "../workspace/machine-definitions.mjs";
const UI_COMMANDS = new Set(["UI_STATE_SAVE", "UI_STATE_LOAD", "UI_STATE_CLEAR"]);
function object(value) {
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
}
function resultEnvelope(input, payload) {
return {
schema_version: 1,
kind: "UI_STATE_RESULT",
context_id: input.context_id,
session_id: input.session_id,
seq: input.seq,
operation_id: input.operation_id ?? null,
payload: { ...payload, command: input.kind, operation_id: input.operation_id ?? null },
};
}
export function isUiStateCommand(input) {
return UI_COMMANDS.has(input?.kind);
}
export async function handleWorkerMessage(session, input) {
if (isUiStateCommand(input)) {
// UI cache is a Worker storage service, not a LinuxCNC command. It never
// consumes the WASM protocol sequence or changes task/motion authority.
const payload = object(input.payload);
const options = { contextId: input.context_id, key: payload.key ?? session.machineDefinition?.id ?? DEFAULT_MACHINE.id };
const result = input.kind === "UI_STATE_SAVE"
? await saveUiState({ ...options, state: payload.state })
: input.kind === "UI_STATE_LOAD"
? await loadUiState(options)
: await clearUiState(options);
return resultEnvelope(input, result);
}
let request = input;
let recoveryNotice = null;
const configuration = object(input.payload).configuration;
const requestedMachine = machineDefinition(configuration?.entrypoint);
const key = requestedMachine?.id ?? session.machineDefinition?.id ?? DEFAULT_MACHINE.id;
// Automatic restoration belongs to a new Worker session. Explicit INI
// changes and machine switches must not be overwritten by saved state.
if (input?.kind === "LOAD_WORKSPACE" && !session.machineDefinition && (!configuration?.entrypoint || requestedMachine) && !object(input.payload).recovery_checkpoint) {
const persisted = await loadPowerOffCheckpoint({ contextId: input.context_id, key });
if (persisted.status === "verified" && persisted.checkpoint) {
try {
if (configuration && session.core && typeof session.validateRecoveryGeometry === "function") session.validateRecoveryGeometry(persisted.checkpoint.state, configuration);
request = { ...input, payload: { ...object(input.payload), recovery_checkpoint: persisted.checkpoint } };
} catch (error) {
recoveryNotice = { status: "ignored", code: String(error?.message ?? error), reason: "saved_checkpoint_incompatible_with_current_configuration" };
}
}
}
const response = session.accept(request);
const payload = object(response?.payload);
if (recoveryNotice) payload.recovery_persistence = recoveryNotice;
if (input?.kind === "POWER" && response?.kind === "COMMAND_RESULT" && payload.status === "verified") {
if (payload.active === false && payload.recovery_checkpoint) {
const persisted = await savePowerOffCheckpoint({ contextId: input.context_id, checkpoint: payload.recovery_checkpoint, key });
payload.recovery_persistence = persisted;
} else if (payload.active === true) {
payload.recovery_persistence = await clearPowerOffCheckpoint({ contextId: input.context_id, key });
}
}
return response;
}
export { UI_COMMANDS };