mirror of
https://github.com/luanslimadev/Wand-Enhancer.git
synced 2026-08-28 23:01:14 +00:00
179 lines
6.2 KiB
JavaScript
179 lines
6.2 KiB
JavaScript
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.cjs');
|
|
const { writeInstallLog } = require('./logger.cjs');
|
|
const { ensureBridge } = require('./runtime.cjs');
|
|
const { installRendererScripts } = require('./renderer-scripts.cjs');
|
|
const { safeString } = require('./utils.cjs');
|
|
|
|
function installWandRuntime(electron, options = {}) {
|
|
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 = 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) => {
|
|
try {
|
|
const allWindows = electron.BrowserWindow.getAllWindows();
|
|
const win = allWindows[0];
|
|
win.webContents
|
|
.executeJavaScript(
|
|
`JSON.parse(localStorage.getItem("infinity:globalStore") || "{}")?.token?.accessToken`
|
|
)
|
|
.then((accessToken) => {
|
|
snapshot.accessToken = accessToken;
|
|
runtime.sync(snapshot);
|
|
});
|
|
} catch (e) {
|
|
runtime.sync(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);
|
|
}
|
|
|
|
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,
|
|
};
|