{
const frame = document.getElementById("codex-visualization");
if (!(frame instanceof HTMLIFrameElement)) {
return;
}
const isShare = document.documentElement.classList.contains(
"visualization-share-root",
);
let viewportHeight = globalThis.innerHeight;
let mobileModalActive = false;
let mobilePickerHistoryOwned = false;
let mobilePickerHistoryKind = "municipality";
let mobilePickerHistoryDepth = null;
let mobilePickerHistoryPending = false;
const mobilePickerHistoryKey = "__m360MunicipalityPicker";
const mobilePickerKindKey = "__m360Picker";
const mobilePickerDepthKey = "__m360PickerDepth";
const isMobilePickerHistoryState = (value) =>
value != null &&
typeof value === "object" &&
(value[mobilePickerHistoryKey] === true ||
value[mobilePickerKindKey] === "municipality" ||
value[mobilePickerKindKey] === "period" ||
value[mobilePickerKindKey] === "invoice" ||
value[mobilePickerKindKey] === "focus" ||
value[mobilePickerKindKey] === "display");
const getMobilePickerHistoryKind = (value) =>
["period", "invoice", "focus", "display"].includes(value?.[mobilePickerKindKey])
? value[mobilePickerKindKey]
: "municipality";
const getMobilePickerHistoryDepth = (value) =>
Number.isInteger(value?.[mobilePickerDepthKey]) ? value[mobilePickerDepthKey] : null;
const stripMobilePickerHistoryState = () => {
if (!isMobilePickerHistoryState(globalThis.history.state)) {
return;
}
const state = { ...globalThis.history.state };
delete state[mobilePickerHistoryKey];
delete state[mobilePickerKindKey];
delete state[mobilePickerDepthKey];
globalThis.history.replaceState(
Object.keys(state).length > 0 ? state : null,
"",
globalThis.location.href,
);
};
const sendMobilePickerHistory = (open, picker = mobilePickerHistoryKind, depth = mobilePickerHistoryDepth) => {
channel.port1.postMessage({ type: "mobile-picker-history", open, picker, depth });
};
const openMobilePickerHistory = (picker = "municipality", depth = null) => {
if (mobilePickerHistoryOwned || mobilePickerHistoryPending) {
return;
}
const currentState = globalThis.history.state;
const nextState =
currentState != null && typeof currentState === "object" && !Array.isArray(currentState)
? { ...currentState, [mobilePickerKindKey]: picker, ...(Number.isInteger(depth) ? { [mobilePickerDepthKey]: depth } : {}) }
: { [mobilePickerKindKey]: picker, ...(Number.isInteger(depth) ? { [mobilePickerDepthKey]: depth } : {}) };
globalThis.history.pushState(nextState, "", globalThis.location.href);
mobilePickerHistoryOwned = true;
mobilePickerHistoryKind = picker;
mobilePickerHistoryDepth = depth;
};
const closeMobilePickerHistory = () => {
if (!mobilePickerHistoryOwned || mobilePickerHistoryPending) {
return;
}
if (!isMobilePickerHistoryState(globalThis.history.state)) {
mobilePickerHistoryOwned = false;
return;
}
mobilePickerHistoryPending = true;
globalThis.history.back();
};
let sizeFingerprint = null;
let sizeWriteTimeout;
let lastSize = null;
let pendingSize = null;
const storageKey = `codex:visualization-widget-state-v2:${JSON.stringify([
globalThis.location.pathname,
globalThis.location.search,
])}`;
const sizeStorageKey = `${storageKey}:share-size-v1`;
const parseState = (serializedState) => {
if (
typeof serializedState !== "string" ||
new TextEncoder().encode(serializedState).length > 16 * 1024
) {
throw new TypeError(
"Widget state must be a JSON object of at most 16 KiB",
);
}
/** @type {unknown} */
const state = JSON.parse(serializedState);
if (
state == null ||
typeof state !== "object" ||
Array.isArray(state) ||
("imageIds" in state &&
(!Array.isArray(state.imageIds) || state.imageIds.length > 0))
) {
throw new TypeError("Widget state must be a JSON object");
}
const storedState = { modelContent: null, privateContent: null, ...state };
const canonicalState = JSON.stringify(
storedState,
(_key, /** @type {unknown} */ value) => {
if (typeof value === "number" && !Number.isFinite(value)) {
throw new TypeError("Widget state must contain finite numbers");
}
return value;
},
);
if (new TextEncoder().encode(canonicalState).length > 16 * 1024) {
throw new TypeError(
"Widget state must be a JSON object of at most 16 KiB",
);
}
return storedState;
};
let storage = null;
let state = null;
try {
const localStorage = globalThis.localStorage;
const probeKey = `${storageKey}:probe`;
localStorage.setItem(probeKey, "1");
localStorage.removeItem(probeKey);
storage = localStorage;
const savedState = storage.getItem(storageKey);
if (savedState != null) {
try {
state = parseState(savedState);
} catch {
// Invalid saved state does not disable future writes.
}
}
} catch {
storage = null;
}
const persistSize = () => {
globalThis.clearTimeout(sizeWriteTimeout);
if (pendingSize == null) {
return;
}
try {
storage?.setItem(sizeStorageKey, pendingSize);
} catch {
// A denied or full cache must not affect the visualization.
}
pendingSize = null;
};
if (isShare) {
globalThis.addEventListener("pagehide", persistSize);
}
const initialGlobals = {
widgetState: state,
statePersistence: storage == null ? "none" : "local",
};
const channel = new MessageChannel();
channel.port1.addEventListener("message", (event) => {
const data = event.data;
if (typeof data !== "object" || data == null) {
return;
}
if (data.type === "widget-state-write") {
if (!Number.isSafeInteger(data.id) || data.id <= 0) {
return;
}
let ok = false;
try {
if (storage != null) {
const state = parseState(data.state);
storage.setItem(storageKey, JSON.stringify(state));
ok = true;
}
} catch {
// Storage denial, quota limits, and invalid state return a failed receipt.
}
channel.port1.postMessage({
type: "widget-state-result",
id: data.id,
ok,
});
return;
}
if (data.type === "height") {
const height = data.height;
if (typeof data.mobileModal === "boolean") {
mobileModalActive = data.mobileModal;
}
if (
typeof height !== "number" ||
!Number.isFinite(height) ||
height < 0
) {
return;
}
if (!isShare && globalThis.matchMedia("(max-width: 600px)").matches) {
frame.style.height = Math.ceil(globalThis.innerHeight) + "px";
return;
}
if (mobileModalActive && globalThis.matchMedia("(max-width: 600px)").matches) {
frame.style.height = Math.ceil(globalThis.innerHeight) + "px";
return;
}
if (height <= 10_000) {
const width = isShare && storage != null ? frame.clientWidth : null;
frame.style.height = Math.ceil(height) + "px";
if (isShare && storage != null) {
const size = JSON.stringify({
fingerprint: sizeFingerprint,
width,
viewportHeight: globalThis.innerHeight,
dark: globalThis.matchMedia("(prefers-color-scheme: dark)").matches,
scale: globalThis.devicePixelRatio,
height: Math.ceil(height),
});
if (size !== lastSize) {
lastSize = size;
pendingSize = size;
globalThis.clearTimeout(sizeWriteTimeout);
sizeWriteTimeout = globalThis.setTimeout(persistSize, 100);
}
}
}
if (isShare) {
// Keep unusually tall content accessible without accepting an unbounded height.
if (height > 10_000 && Number.parseFloat(frame.style.height) === 0) {
frame.style.removeProperty("height");
}
frame.scrolling = height > 10_000 ? "auto" : "no";
document.documentElement.classList.add("visualization-share-ready");
}
return;
}
if (data.type === "mobile-modal") {
const active = data.active === true;
mobileModalActive = active;
if (active && data.mobile === true) {
openMobilePickerHistory(
["period", "invoice", "focus", "display"].includes(data.picker) ? data.picker : "municipality",
Number.isInteger(data.depth) ? data.depth : null,
);
} else if (!active) {
closeMobilePickerHistory();
}
if (mobileModalActive && globalThis.matchMedia("(max-width: 600px)").matches) {
frame.style.height = Math.ceil(globalThis.innerHeight) + "px";
} else if (!mobileModalActive) {
frame.style.removeProperty("height");
channel.port1.postMessage({ type: "measure" });
}
return;
}
if (data.type === "scroll-to") {
const top = data.top;
if (
typeof top === "number" &&
Number.isFinite(top) &&
top >= 0 &&
globalThis.navigator.userActivation?.isActive === true
) {
globalThis.scrollTo({
top: Math.ceil(
globalThis.scrollY + frame.getBoundingClientRect().top + top,
),
});
}
return;
}
if (data.type === "open-external") {
const rawHref = data.href;
if (typeof rawHref !== "string") {
return;
}
let href;
try {
const url = new URL(rawHref, document.baseURI);
if (url.protocol !== "https:") {
return;
}
href = url.href;
} catch {
return;
}
if (globalThis.navigator.userActivation?.isActive === true) {
const anchor = document.createElement("a");
anchor.href = href;
anchor.rel = "noopener noreferrer";
anchor.target = "_blank";
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
}
return;
}
if (data.type === "follow-up") {
const prompt = data.prompt;
if (
typeof prompt !== "string" ||
globalThis.navigator.userActivation?.isActive !== true
) {
return;
}
const title = data.title;
globalThis.prompt(typeof title === "string" ? title : prompt, prompt);
}
});
channel.port1.start();
stripMobilePickerHistoryState();
const srcdoc = frame.dataset.srcdoc;
if (srcdoc == null) {
return;
}
globalThis.addEventListener("resize", () => {
const height = Number.parseFloat(frame.style.height);
if (!isShare && globalThis.matchMedia("(max-width: 600px)").matches) {
frame.style.height = Math.ceil(globalThis.innerHeight) + "px";
} else if (mobileModalActive && globalThis.matchMedia("(max-width: 600px)").matches) {
frame.style.height = Math.ceil(globalThis.innerHeight) + "px";
} else if (!isShare && (!Number.isFinite(height) || height <= viewportHeight)) {
frame.style.removeProperty("height");
}
viewportHeight = globalThis.innerHeight;
channel.port1.postMessage({ type: "measure" });
});
globalThis.addEventListener("popstate", () => {
mobilePickerHistoryPending = false;
if (isMobilePickerHistoryState(globalThis.history.state)) {
mobilePickerHistoryOwned = true;
mobilePickerHistoryKind = getMobilePickerHistoryKind(globalThis.history.state);
mobilePickerHistoryDepth = getMobilePickerHistoryDepth(globalThis.history.state);
sendMobilePickerHistory(true, mobilePickerHistoryKind, mobilePickerHistoryDepth);
return;
}
if (mobilePickerHistoryOwned) {
mobilePickerHistoryOwned = false;
mobileModalActive = false;
sendMobilePickerHistory(false, mobilePickerHistoryKind, mobilePickerHistoryDepth);
}
});
frame.addEventListener(
"load",
() => {
frame.contentWindow?.postMessage(
{ type: "codex-visualization-initialize" },
"*",
[channel.port2],
);
},
{ once: true },
);
const frameHtml = replaceWidgetStateScript(srcdoc, initialGlobals);
if (isShare && storage != null) {
// Fingerprint the rendered document, including restored widget state, without
// storing its content. This is a layout cache, not a security identity.
let hash = 5381;
for (let index = 0; index < frameHtml.length; index += 1) {
hash = (hash * 33 + frameHtml.charCodeAt(index)) % 4_294_967_296;
}
sizeFingerprint = hash.toString(36);
try {
const size = JSON.parse(storage.getItem(sizeStorageKey) ?? "null");
if (
size?.fingerprint === sizeFingerprint &&
size.width === frame.clientWidth &&
size.viewportHeight === globalThis.innerHeight &&
size.dark ===
globalThis.matchMedia("(prefers-color-scheme: dark)").matches &&
size.scale === globalThis.devicePixelRatio &&
typeof size.height === "number" &&
Number.isFinite(size.height) &&
size.height >= 0 &&
size.height <= 10_000
) {
frame.style.height = size.height + "px";
}
} catch {
// Ignore missing or invalid cache entries and measure the live frame.
}
}
frame.srcdoc = frameHtml;
delete frame.dataset.srcdoc;
})();