import { TRAINER_LAUNCH_REQUEST_EXPORT_KEY } from './constants.js'; import { findExportedConstructor, findInstanceInContainerGraph, formatError, isRecord, } from './runtime.js'; export function hasMissingOptionalServices(state) { return ( !state.gameLifecycleService || !state.trainerVisibilityService || !state.unavailableTitlesService ); } export function hasUnresolvedServices(state) { return ( hasMissingOptionalServices(state) || !state.trainerApiService || !state.trainerService || !state.trainerLaunchRequestCtor ); } const OPTIONAL_SERVICE_SPECS = [ { stateKey: 'unavailableTitlesService', methods: [ 'getUnavailableTitle', 'searchUnavailableTitles', 'getUnavailableTitlesByCorrelationIds', ], label: 'Unavailable titles service', }, { stateKey: 'gameLifecycleService', methods: ['onGameLaunched', 'onGameEnded', 'launch'], label: 'Game lifecycle service', }, { stateKey: 'trainerVisibilityService', methods: ['onDisplayTrainerChanged', 'onVisibleTrainerChanged', 'onRunningTrainerChanged'], label: 'Trainer visibility service', }, { stateKey: 'trainerApiService', methods: [ 'getLatestLocalTrainerForGame', 'getMostCompatibleTrainerForGame', 'getTrainerById', ], label: 'Trainer API service', }, { stateKey: 'trainerService', methods: ['launch', 'endTrainer', 'onNewTrainer', 'onTrainerEnded'], label: 'Trainer service', }, ]; export function resolveOptionalServices(state, container, webpackRequire) { for (const spec of OPTIONAL_SERVICE_SPECS) { if (!state[spec.stateKey]) { state[spec.stateKey] = resolveOptionalService(state, container, webpackRequire, spec); } } if (!state.trainerLaunchRequestCtor) { state.trainerLaunchRequestCtor = getTrainerLaunchRequestCtor(state, webpackRequire); } } export function getInstalledAppsService(state, container, webpackRequire) { const ctor = findExportedConstructor(webpackRequire, (prototype) => { return ( typeof prototype.refreshApps === 'function' && typeof prototype.watchGame === 'function' ); }); if (!ctor) { state.log('warn', 'Installed apps service constructor not found in webpack cache.'); return null; } try { const service = container.get(ctor); const appsCount = isRecord(service.installedApps) ? Object.keys(service.installedApps).length : -1; const catalogGamesCount = isRecord(service.catalog?.games) ? Object.keys(service.catalog.games).length : -1; state.log( 'info', 'Installed apps service resolved.', `ctor=${ctor.name || ''}, installedApps=${appsCount}, catalogGames=${catalogGamesCount}`, ); return service; } catch (error) { state.log( 'warn', 'Failed to resolve installed apps service from Aurelia container.', formatError(error), ); return null; } } export function getStoreRef(state, container, webpackRequire) { const storeCtor = findExportedConstructor(webpackRequire, (prototype) => { return ( typeof prototype.dispatch === 'function' && typeof prototype.registerAction === 'function' && typeof prototype.unregisterAction === 'function' ); }); if (!storeCtor) { state.log('warn', 'Store constructor not found in webpack cache.'); return null; } try { const store = container.get(storeCtor); const storeState = typeof store.state?.getValue === 'function' ? store.state.getValue() : null; const installedAppsCount = isRecord(storeState?.installedApps) ? Object.keys(storeState.installedApps).length : -1; const catalogGamesCount = isRecord(storeState?.catalog?.games) ? Object.keys(storeState.catalog.games).length : -1; state.log( 'info', 'Store resolved via fallback.', `ctor=${storeCtor.name || ''}, installedApps=${installedAppsCount}, catalogGames=${catalogGamesCount}`, ); return store; } catch (error) { state.log('warn', 'Failed to resolve Store from container.', formatError(error)); return null; } } function resolveOptionalService(state, container, webpackRequire, spec) { const matchesMethods = (target) => hasAllMethods(target, spec.methods); const ctor = findExportedConstructor(webpackRequire, matchesMethods); if (ctor) { return getContainerService(state, container, ctor, spec.stateKey, spec.label); } return findFallbackService( state, container, spec.stateKey, `${spec.label} constructor not found in webpack cache.`, matchesMethods, ); } function hasAllMethods(target, methods) { if (!target) { return false; } for (const method of methods) { if (typeof target[method] !== 'function') { return false; } } return true; } function getTrainerLaunchRequestCtor(state, webpackRequire) { const cache = webpackRequire?.c; if (!cache || typeof cache !== 'object') { warnMissingOptionalService( state, 'trainerLaunchRequestCtor', 'Trainer launch request constructor cache is unavailable.', ); return null; } for (const record of Object.values(cache)) { const exports = record?.exports; if (!isRecord(exports)) { continue; } const candidate = exports[TRAINER_LAUNCH_REQUEST_EXPORT_KEY]; if ( typeof candidate === 'function' && typeof exports.ZS === 'function' && typeof exports.jR === 'function' && typeof exports.UY === 'function' ) { clearMissingOptionalServiceWarning(state, 'trainerLaunchRequestCtor'); return candidate; } } warnMissingOptionalService( state, 'trainerLaunchRequestCtor', 'Trainer launch request constructor not found in webpack cache.', ); return null; } function getContainerService(state, container, ctor, warningKey, label) { try { const service = container.get(ctor); clearMissingOptionalServiceWarning(state, warningKey); state.log( 'info', `${label} resolved.`, `ctor=${ctor.name || ''}${service?.runningTrainer ? ', running=yes' : ''}`, ); return service; } catch (error) { state.log( 'warn', `Failed to resolve ${label.toLowerCase()} from Aurelia container.`, formatError(error), ); return null; } } function findFallbackService(state, container, warningKey, missingMessage, predicate) { const fallbackService = findInstanceInContainerGraph(container, predicate); if (fallbackService) { clearMissingOptionalServiceWarning(state, warningKey); state.log('info', `${warningKey} resolved from container graph.`); return fallbackService; } warnMissingOptionalService(state, warningKey, missingMessage); return null; } function warnMissingOptionalService(state, key, message) { if (state.missingOptionalServiceWarnings.has(key)) { return; } state.missingOptionalServiceWarnings.add(key); state.log('warn', message); } function clearMissingOptionalServiceWarning(state, key) { state.missingOptionalServiceWarnings.delete(key); }