Files
OneCAD-Draft/web/ocs-control.js
T
Afonsop d719fb7778 fix: close the review findings on the issue wave
Two reviewers over the #1345/#1348/#1349 wave; no blockers, two
majors, several minors — all fixed.

- La16 was still billed at 4 B/px: the no-conversion special case
  keyed on native_bpp == 4, which La16 also is — but only Rgba8 is a
  real move in into_rgba8, so La16's decode peak is 8. The case now
  keys on the color type, and a table test pins every layout's peak
  (plus GIF's screen+frame 8) so the accounting can't silently drift.
- A control-routed open of a damaged-but-recoverable drawing parked
  the operation behind the RecoveryPrompt modal (hang until the JS
  timeout, silent drop if another op arrived, or false success when a
  human declined). The open op now settles as failed with
  recovery_required and the parse error; the prompt stays for humans,
  automation can retry through the recovery flow.
- json_values_match: integers beyond f64 precision compared by value
  (a u64 could round-trip-match a float); integral pairs now compare
  exactly and mixed integer/float matches only when the float is whole
  and equals the integer.
- A queued web open settles with the explicit queued marker whenever
  the open slot was busy (the old name check made same-named in-flight
  opens look completed with an empty result).
- ocs-control.js resolves  with the API object reference, not
  window.ocsControl (undefined if the bridge already existed when the
  script ran); DATALINK on web says the typed path is superseded by
  the picker instead of silently discarding it; the wipeout depth test
  probes past the page background's rectangle; and the plot-path doc
  comment names the function it actually mirrors.
2026-09-18 14:26:20 +01:00

78 lines
2.8 KiB
JavaScript

// Web automation convenience API (#1349).
//
// The wasm module exposes the control bridge through `window.wasmBindings`
// (ocs_control_submit / ocs_control_take). This module wraps it in promises:
//
// await window.ocsControl.ready resolves once the bridge exists
// window.addEventListener("ocs-ready", …) fired once, at the same moment
// await window.ocsControl.request({...}) one control request -> final reply
//
// Hosts that intend to automate from the start should set
// localStorage["opencadstudio.automation"] = "1" before the page loads so the
// startup donation prompt never appears (see docs/automation/README.md).
(() => {
if (window.ocsControl) return;
const bridge = () => {
const api = window.wasmBindings;
return api && api.ocs_control_submit && api.ocs_control_take ? api : null;
};
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
let readyResolve;
const ready = new Promise(resolve => { readyResolve = resolve; });
const settle = () => {
// apiObject is assigned to window.ocsControl at the END of this IIFE;
// resolve with the local reference so a synchronous bridge (script
// injected late) cannot settle `ready` with undefined.
readyResolve(apiObject);
window.dispatchEvent(new Event("ocs-ready"));
};
const poll = setInterval(() => {
if (bridge()) {
clearInterval(poll);
settle();
}
}, 50);
if (bridge()) {
clearInterval(poll);
settle();
}
// Submit one request and poll take() until the app stores its reply.
async function exchange(api, payload, pollMs, deadline) {
const ticket = api.ocs_control_submit(JSON.stringify(payload));
for (;;) {
const raw = api.ocs_control_take(ticket);
if (raw) return JSON.parse(raw);
if (performance.now() >= deadline) {
throw new Error("ocs-control: timed out: " + JSON.stringify(payload));
}
await sleep(pollMs);
}
}
// One control request. Replies that are still `accepted`/`running` (an
// async open, capture or save) are followed through `op:"operation"` until
// a final status arrives. Resolves with the final reply object; rejects
// only on timeout or a missing bridge.
async function request(payload, options = {}) {
const pollMs = options.pollMs ?? 50;
const deadline = performance.now() + (options.timeoutMs ?? 120000);
const api = bridge();
if (!api) {
throw new Error("ocs-control: bridge not ready (await ocsControl.ready)");
}
let reply = await exchange(api, payload, pollMs, deadline);
while (["accepted", "running"].includes(reply.status) && reply.request_id) {
await sleep(pollMs);
reply = await exchange(
api, {op: "operation", request_id: reply.request_id}, pollMs, deadline);
}
return reply;
}
const apiObject = {ready, request};
window.ocsControl = apiObject;
})();