Files
Wand-Enhancer-k1tbyte/web-panel/bridge/scripts/default/installed-apps-sync/remote-commands.js
T

225 lines
6.9 KiB
JavaScript
Vendored

import {
COMMAND_RESPONSE_CHANNEL,
REMOTE_COMMAND_LAUNCH,
REMOTE_COMMAND_STOP,
REMOTE_COMMAND_TRIGGER,
REMOTE_STOP_EVENT,
} from './constants.js';
import { clearTrainerSnapshot, syncGameStatus } from './game-status.js';
import {
getInstalledVersionsForGame,
rankInstalledAppCandidates,
resolveInstalledData,
} from './installed-data.js';
import {
formatError,
getPreferredLocale,
invokeIpc,
isRecord,
safeString,
toStringId,
} from './runtime.js';
export function handleRemoteCommandRequest(state, _event, request) {
void (async () => {
let response;
if (request?.action === REMOTE_COMMAND_LAUNCH) {
response = await executeRemoteLaunchCommand(state, request);
} else if (request?.action === REMOTE_COMMAND_STOP) {
response = await executeRemoteStopCommand(state, request);
} else {
response = buildCommandResponse(request, false, {
code: 'invalid_command',
message: 'Unknown remote command.',
});
}
await sendRemoteCommandResponse(state, response);
})();
}
function buildCommandResponse(request, ok, error = null) {
const response = {
requestId: safeString(request?.requestId),
ok,
action:
request?.action === REMOTE_COMMAND_STOP ? REMOTE_COMMAND_STOP : REMOTE_COMMAND_LAUNCH,
gameId: toStringId(request?.gameId),
titleId: toStringId(request?.titleId),
};
if (!error) {
return response;
}
return {
...response,
error,
};
}
async function executeRemoteLaunchCommand(state, request) {
const gameId = toStringId(request?.gameId);
if (!gameId) {
return buildCommandResponse(request, false, {
code: 'invalid_game',
message: 'A game id is required to launch a trainer.',
});
}
if (!state.resolveRemoteCommandServices()) {
return buildCommandResponse(request, false, {
code: 'bridge_not_ready',
message: 'The Wand renderer container is not ready yet.',
});
}
if (!state.trainerService) {
return buildCommandResponse(request, false, {
code: 'trainer_service_missing',
message: 'The Wand trainer service is not available yet.',
});
}
if (!state.trainerLaunchRequestCtor) {
return buildCommandResponse(request, false, {
code: 'trainer_launch_missing',
message: 'The Wand trainer launch request constructor is not available yet.',
});
}
const data = resolveInstalledData(state);
if (!data) {
return buildCommandResponse(request, false, {
code: 'installations_missing',
message: 'Installed game data is not available yet.',
});
}
const launchInfo = getLaunchInfoForGame(gameId, data);
if (!isRecord(launchInfo.app)) {
return buildCommandResponse(request, false, {
code: 'game_not_installed',
message: 'Wand could not resolve a preferred installation for this game.',
});
}
const trainerInfo = await resolveTrainerInfoForGame(state, gameId, data);
if (!trainerInfo) {
return buildCommandResponse(request, false, {
code: 'trainer_not_found',
message: 'Wand could not find a compatible trainer for this game.',
});
}
try {
const launchRequest = new state.trainerLaunchRequestCtor(
trainerInfo,
launchInfo.app,
launchInfo.version,
REMOTE_COMMAND_TRIGGER,
);
await state.trainerService.launch(launchRequest);
state.queueSync(true);
state.queueFollowUpSync();
void syncGameStatus(state, true);
return buildCommandResponse(request, true);
} catch (error) {
state.log('warn', 'Remote trainer launch failed.', formatError(error));
return buildCommandResponse(request, false, {
code: 'launch_failed',
message: 'Failed to launch the trainer.',
});
}
}
async function executeRemoteStopCommand(state, request) {
if (!state.resolveRemoteCommandServices()) {
return buildCommandResponse(request, false, {
code: 'bridge_not_ready',
message: 'The Wand renderer container is not ready yet.',
});
}
if (!state.trainerService || typeof state.trainerService.endTrainer !== 'function') {
return buildCommandResponse(request, false, {
code: 'trainer_service_missing',
message: 'The Wand trainer service is not available yet.',
});
}
if (!state.trainerService.trainer && state.currentRunningTrainer.state !== 'running') {
return buildCommandResponse(request, false, {
code: 'no_active_trainer',
message: 'No trainer is running right now.',
});
}
try {
await state.trainerService.endTrainer();
clearTrainerSnapshot(state, REMOTE_STOP_EVENT, true);
return buildCommandResponse(request, true);
} catch (error) {
state.log('warn', 'Remote trainer stop failed.', formatError(error));
return buildCommandResponse(request, false, {
code: 'stop_failed',
message: 'Failed to stop the running trainer.',
});
}
}
function getLaunchInfoForGame(gameId, data) {
const versions = Array.isArray(data?.installedGameVersions?.[gameId])
? data.installedGameVersions[gameId]
: [];
const game = isRecord(data?.catalog?.games?.[gameId]) ? data.catalog.games[gameId] : null;
const top = rankInstalledAppCandidates(data?.rawInstalledApps ?? {}, game, versions)[0];
return top ? { app: top.app, version: top.version } : { app: null, version: null };
}
async function resolveTrainerInfoForGame(state, gameId, data) {
if (!state.trainerApiService) {
return null;
}
try {
const localTrainer = unwrapTrainerInfo(
await state.trainerApiService.getLatestLocalTrainerForGame(gameId),
);
if (localTrainer) {
return localTrainer;
}
} catch (error) {
state.log('warn', 'Local trainer lookup failed.', formatError(error));
}
try {
return unwrapTrainerInfo(
await state.trainerApiService.getMostCompatibleTrainerForGame(
gameId,
getPreferredLocale(),
getInstalledVersionsForGame(gameId, data),
false,
),
);
} catch (error) {
state.log('warn', 'Compatible trainer lookup failed.', formatError(error));
return null;
}
}
function unwrapTrainerInfo(value) {
if (isRecord(value?.trainer)) {
return value.trainer;
}
return isRecord(value) ? value : null;
}
async function sendRemoteCommandResponse(state, response) {
await invokeIpc(state, COMMAND_RESPONSE_CHANNEL, response, 'Remote command response');
}