import { CONTAINER_GRAPH_MAX_DEPTH } from "./constants.js" export function isRecord(value) { return typeof value === "object" && value !== null } export function formatError(error) { return error?.stack || String(error) } export async function invokeIpc(state, channel, payload, label, level = "warn") { if (!state.ipcRenderer) { return false } try { await state.ipcRenderer.invoke(channel, payload) return true } catch (error) { state.log(level, `${label} IPC failed.`, formatError(error)) return false } } export function isDiagnosticsDebugEnabled() { return globalThis.__wandInstalledAppsSyncDebug === true } let lastPredicateErrorLogAt = 0 function logContainerGraphPredicateError(error) { if (!isDiagnosticsDebugEnabled()) { return } const now = Date.now() if (now - lastPredicateErrorLogAt < 1000) { return } lastPredicateErrorLogAt = now console.debug( "[wand-installed-apps-sync] container graph predicate threw", formatError(error) ) } export function getRequire() { return ( globalThis.require || (typeof window !== "undefined" ? window.require : null) ) } export function safeString(...values) { for (const value of values) { if (typeof value === "string" && value.trim()) { return value.trim() } } return "" } export function getWebpackRequire() { const chunk = globalThis.webpackChunkWeMod if (!Array.isArray(chunk)) { return null } if (typeof chunk.__wandWebpackRequire === "function") { return chunk.__wandWebpackRequire } let resolvedRequire = null chunk.push([ [`wand-enhancer-${Date.now()}`], {}, (webpackRequire) => { resolvedRequire = webpackRequire }, ]) if (typeof resolvedRequire === "function") { chunk.__wandWebpackRequire = resolvedRequire } return resolvedRequire } export function getAppRoot() { return ( document.getElementById("root") || document.querySelector("[aurelia-app]") || document.querySelector("root") ) } export function hasAppRoot() { return Boolean(getAppRoot()) } let cachedAureliaContainer = null function isUsableContainer(container) { return isRecord(container) && typeof container.get === "function" } export function getAureliaContainer() { if (isUsableContainer(cachedAureliaContainer)) { return cachedAureliaContainer } const resolved = resolveAureliaContainer() if (resolved) { cachedAureliaContainer = resolved } return resolved } function resolveAureliaContainer() { const root = getAppRoot() const rootContainer = getContainerFromSubtree(root) if (rootContainer) { return rootContainer } const bodyContainer = getContainerFromElement(document.body) if (bodyContainer) { return bodyContainer } if (isRecord(globalThis.aurelia) && globalThis.aurelia.container) { return globalThis.aurelia.container } return null } export function summarizeAureliaSubtree(root) { if (!isDiagnosticsDebugEnabled()) { return "(debug-disabled)" } if (!root) { return "root=null" } let elementsWithAu = 0 let controllerEntries = 0 let namedAuEntries = 0 function inspectElement(element) { if (!isRecord(element?.au)) { return } elementsWithAu += 1 if (element.au.controller) { controllerEntries += 1 } for (const [key, value] of Object.entries(element.au)) { if (key !== "controller" && isRecord(value)) { namedAuEntries += 1 } } } inspectElement(root) const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT) let element = walker.nextNode() while (element) { inspectElement(element) element = walker.nextNode() } return `elementsWithAu=${elementsWithAu}, controllerEntries=${controllerEntries}, namedAuEntries=${namedAuEntries}` } export function findExportedConstructor(webpackRequire, predicate) { const cache = webpackRequire?.c if (!cache || typeof cache !== "object") { return null } for (const record of Object.values(cache)) { const exports = record?.exports const candidates = [] if (typeof exports === "function") { candidates.push(exports) } else if (isRecord(exports)) { if (typeof exports.default === "function") { candidates.push(exports.default) } for (const value of Object.values(exports)) { if (typeof value === "function") { candidates.push(value) } } } for (const candidate of candidates) { if (candidate?.prototype && predicate(candidate.prototype)) { return candidate } } } return null } export function findInstanceInContainerGraph( root, predicate, maxDepth = CONTAINER_GRAPH_MAX_DEPTH ) { if (!root) { return null } const seen = new Set() const queue = [{ value: root, depth: 0 }] while (queue.length > 0) { const current = queue.shift() const value = current?.value const depth = current?.depth ?? 0 if (!value || seen.has(value)) { continue } seen.add(value) try { if (predicate(value)) { return value } } catch (error) { logContainerGraphPredicateError(error) } if (depth >= maxDepth) { continue } enqueueNestedValues(queue, seen, value, depth + 1) } return null } export function getBasename(location) { if (typeof location !== "string" || !location.trim()) { return "" } const normalized = location.replace(/\\/g, "/").replace(/\/+$/, "") const leaf = normalized.split("/").filter(Boolean).pop() return leaf ? leaf.trim() : "" } export function toStringId(value) { if (typeof value === "string" && value.trim()) { return value.trim() } if (typeof value === "number" && Number.isFinite(value)) { return String(value) } return null } export function normalizeStringList(value) { if (!Array.isArray(value)) { return [] } return value .filter((entry) => typeof entry === "string" && entry.trim()) .map((entry) => entry.trim()) } export function getPreferredLocale() { return safeString( document.documentElement?.lang, Array.isArray(globalThis.navigator?.languages) ? globalThis.navigator.languages.find( (entry) => typeof entry === "string" && entry.trim() ) : "", globalThis.navigator?.language, "en-US" ) } function getContainerFromAu(au) { if (!isRecord(au)) { return null } if (au.container) { return au.container } const directControllerContainer = au.controller?.container || au.controller?.viewModel?.container if (directControllerContainer) { return directControllerContainer } for (const value of Object.values(au)) { if (!isRecord(value)) { continue } const container = value.container || value.controller?.container || value.viewModel?.container || value.controller?.viewModel?.container if (container) { return container } } return null } function getContainerFromElement(element) { if (!element) { return null } if (element.__aurelia__?.container) { return element.__aurelia__.container } return getContainerFromAu(element.au) } function getContainerFromSubtree(root) { if (!root) { return null } const rootContainer = getContainerFromElement(root) if (rootContainer) { return rootContainer } const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT) let element = walker.nextNode() while (element) { const container = getContainerFromElement(element) if (container) { return container } element = walker.nextNode() } return null } function enqueueNestedValues(queue, seen, value, depth) { if (value instanceof Map) { enqueueIterable(queue, seen, value.values(), depth) return } if (value instanceof Set || Array.isArray(value)) { enqueueIterable(queue, seen, value.values(), depth) return } if (!isRecord(value) && typeof value !== "function") { return } enqueueIterable(queue, seen, Object.values(value), depth) } function enqueueIterable(queue, seen, values, depth) { for (const entry of values) { if ((isRecord(entry) || typeof entry === "function") && !seen.has(entry)) { queue.push({ value: entry, depth }) } } }