mirror of
https://github.com/k1tbyte/Wand-Enhancer.git
synced 2026-09-02 18:00:39 +00:00
391 lines
9.1 KiB
JavaScript
Vendored
391 lines
9.1 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 });
|
|
}
|
|
}
|
|
}
|