mirror of
https://github.com/k1tbyte/Wand-Enhancer.git
synced 2026-08-29 22:01:18 +00:00
f2e88e9247
ipcRenderer.invoke rejects asynchronously, so marking the bridge bound before awaiting left set-value dead for the session while the log reported success. Compare installed-app snapshots structurally instead of by an explicit field list, which had already drifted from the bridge copy and hid location changes.
397 lines
8.2 KiB
JavaScript
Vendored
397 lines
8.2 KiB
JavaScript
Vendored
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 })
|
|
}
|
|
}
|
|
}
|