Files
Wand-Enhancer/web-panel/bridge/src/wand/runtime.ts
T
kitbyte a0b3968d33 refactor(web-panel): update architecture + cheat i18n
Reorganize the panel around product capabilities and integrate the
remote i18n feature from origin/master into the new structure.

- Restructure src into capability features (app, trainer, library,
  remote-session, appearance, shared); move the bridge to bridge/src.
- Add lingui-based UI localization and wrap remaining user-facing
  strings (Trans / msg macros); fix the 'END В·' mojibake.
- Port WeMod cheat-metadata i18n: capture the access token from the
  snapshot's renderer in the bridge and fetch localized trainer_meta
  in remote-session.i18n; guarantee snapshot sync on token failure.
- Wire vitest to the app's lingui/preact pipeline (mergeConfig).
2026-06-15 00:10:49 +03:00

195 lines
6.9 KiB
TypeScript

const crypto = require('node:crypto');
const path = require('node:path');
const {
IPC_CHANNEL,
REMOTE_COMMAND_REQUEST_CHANNEL,
REMOTE_COMMAND_RESPONSE_CHANNEL,
REMOTE_COMMAND_RESPONSE_TIMEOUT_MS,
REMOTE_GAME_STATUS_CHANNEL,
REMOTE_INSTALLED_APPS_CHANNEL,
} = require('../constants');
const { writeInstallLog } = require('../logger');
const { ensureBridge } = require('../runtime');
const { installRendererScripts } = require('./renderer-scripts');
const { safeString } = require('../utils');
import type { BridgeOptions, ElectronPort, WebContentsPort } from '../types';
// Reads the signed-in WeMod access token from the renderer's localStorage so the
// panel can request localized cheat metadata from the WeMod API.
const WEMOD_ACCESS_TOKEN_SCRIPT =
'JSON.parse(localStorage.getItem("infinity:globalStore") || "{}")?.token?.accessToken ?? null';
function installWandRuntime(electron: ElectronPort, options: BridgeOptions = {}) {
const runtime = ensureBridge(options);
if (!electron || !electron.ipcMain || !electron.app) {
throw new Error('Electron main-process API is required to install Wand runtime hooks.');
}
const boundRenderers: Set<WebContentsPort> = globalThis.__wandRemoteBridgeBoundRenderers || new Set();
const pendingCommandResponses = globalThis.__wandRemoteBridgePendingCommandResponses || new Map();
globalThis.__wandRemoteBridgeBoundRenderers = boundRenderers;
globalThis.__wandRemoteBridgePendingCommandResponses = pendingCommandResponses;
runtime.setHandler((request) => {
let delivered = false;
for (const sender of Array.from(boundRenderers)) {
try {
if (!sender || sender.isDestroyed()) {
boundRenderers.delete(sender);
continue;
}
sender.send(IPC_CHANNEL.SET_VALUE, request);
delivered = true;
} catch (error) {
boundRenderers.delete(sender);
writeInstallLog('warn', 'Failed to forward set_value to renderer.', error);
}
}
return delivered;
});
runtime.setCommandHandler(async (request) => {
for (const sender of Array.from(boundRenderers)) {
try {
if (!sender || sender.isDestroyed()) {
boundRenderers.delete(sender);
continue;
}
return await dispatchRemoteCommandToRenderer(sender, request, pendingCommandResponses);
} catch (error) {
writeInstallLog('warn', 'Failed to execute remote command in renderer.', error);
}
}
return buildRendererBridgeMissingResponse(request);
});
installIpcHandlers(electron, runtime, boundRenderers, pendingCommandResponses);
installRendererScripts(electron, runtime, {
...options,
panelRoot: options.panelRoot || path.dirname(__dirname),
});
writeInstallLog('info', 'Wand runtime hooks installed.');
return runtime;
}
function installIpcHandlers(electron, runtime, boundRenderers, pendingCommandResponses) {
if (globalThis.__wandRemoteBridgeIpcInstalled) {
return;
}
globalThis.__wandRemoteBridgeIpcInstalled = true;
electron.ipcMain.handle(IPC_CHANNEL.TRAINER_SNAPSHOT, (event, snapshot) => {
void syncSnapshotWithAccessToken(runtime, event?.sender, snapshot);
return true;
});
electron.ipcMain.handle(REMOTE_INSTALLED_APPS_CHANNEL, (_event, snapshot) => {
runtime.syncInstalledApps(snapshot);
return true;
});
electron.ipcMain.handle(REMOTE_GAME_STATUS_CHANNEL, (_event, snapshot) => {
runtime.syncGameStatus(snapshot);
return true;
});
electron.ipcMain.handle(REMOTE_COMMAND_RESPONSE_CHANNEL, (_event, response) => {
const requestId = safeString(response?.requestId);
const pending = requestId ? pendingCommandResponses.get(requestId) : null;
if (!pending) {
return false;
}
pending.resolve(response);
return true;
});
electron.ipcMain.handle(IPC_CHANNEL.VALUE_CHANGED, (_event, change) => {
runtime.valueChanged(change);
return true;
});
electron.ipcMain.handle(IPC_CHANNEL.BIND_HANDLER, (event) => {
if (event && event.sender) {
boundRenderers.add(event.sender);
}
return true;
});
electron.ipcMain.handle(IPC_CHANNEL.REMOTE_URL, () => runtime.remoteUrl);
}
async function syncSnapshotWithAccessToken(runtime, sender, snapshot) {
const accessToken = await readWemodAccessToken(sender);
if (accessToken && snapshot && typeof snapshot === 'object') {
snapshot.accessToken = accessToken;
}
runtime.sync(snapshot);
}
async function readWemodAccessToken(sender) {
if (!sender || typeof sender.executeJavaScript !== 'function' || sender.isDestroyed?.()) {
return null;
}
try {
const token = await sender.executeJavaScript(WEMOD_ACCESS_TOKEN_SCRIPT);
return typeof token === 'string' && token ? token : null;
} catch (error) {
writeInstallLog('warn', 'Failed to read WeMod access token from renderer.', error);
return null;
}
}
function dispatchRemoteCommandToRenderer(sender, request, pendingCommandResponses) {
return new Promise((resolve, reject) => {
const requestId = `remote_command_${typeof crypto.randomUUID === 'function' ? crypto.randomUUID() : Date.now().toString(36)}`;
const timer = setTimeout(() => {
pendingCommandResponses.delete(requestId);
reject(new Error('Renderer remote command timed out.'));
}, REMOTE_COMMAND_RESPONSE_TIMEOUT_MS);
pendingCommandResponses.set(requestId, {
resolve: (response) => {
clearTimeout(timer);
pendingCommandResponses.delete(requestId);
resolve(response);
},
reject: (error) => {
clearTimeout(timer);
pendingCommandResponses.delete(requestId);
reject(error instanceof Error ? error : new Error(String(error)));
},
});
try {
sender.send(REMOTE_COMMAND_REQUEST_CHANNEL, {
...request,
requestId,
});
} catch (error) {
clearTimeout(timer);
pendingCommandResponses.delete(requestId);
reject(error);
}
});
}
function buildRendererBridgeMissingResponse(request) {
return {
ok: false,
action: request?.action === 'stop' ? 'stop' : 'launch',
gameId: typeof request?.gameId === 'string' ? request.gameId : null,
titleId: typeof request?.titleId === 'string' ? request.titleId : null,
error: {
code: 'bridge_not_ready',
message: 'The renderer command bridge is not ready yet.',
},
};
}
module.exports = {
installWandRuntime,
};