/** * CyberSim OS - Main Bootstrapper & Simulation Lifecycle Orchestrator (Phase 2.5) * * Coordinates deployment branding, runtime localization, immersive login, * learner personalization, and scenario lifecycle. */ import { globalEventBus } from './engine/event_bus.js'; import { ConsequenceEngine } from './engine/consequence.js'; import { ScenarioState } from './engine/scenario_state.js'; import { ConditionEvaluator } from './engine/condition_evaluator.js'; import { EventScheduler } from './engine/event_scheduler.js'; import { ActionDispatcher } from './engine/action_dispatcher.js'; import { calculateScenarioFingerprint } from './scenario/fingerprint.js'; import { ScenarioLoader, getScenarioUrl } from './scenario/loader.js'; import { validateSchema } from './scenario/schema.js'; import { validateScenario } from './scenario/validator.js'; import { ScenarioDiagnostics, isDevelopmentMode } from './scenario/diagnostics.js'; import { WindowManager } from './core/window_manager.js'; import { DesktopShell } from './core/desktop.js'; import { NotificationService } from './core/notifications.js'; import { i18n } from './core/i18n.js'; import { branding } from './core/branding.js'; import { LoginScreen } from './core/login.js'; import { InlookApp } from './apps/inlook.js'; import { NavigatorApp } from './apps/navigator.js'; import { FilesApp } from './apps/files.js'; import { DocViewerApp } from './apps/docviewer.js'; import { SecurityCenterApp } from './apps/security_center.js'; import { BehavioralScorer } from './scoring/scorer.js'; import { AfterActionReport } from './scoring/aar.js'; import { CertificateGenerator } from './cert/cert_generator.js'; export class CyberSimEngine { constructor() { this.scenario = null; this.scenarioFingerprint = null; this.eventBus = globalEventBus; this.scenarioState = null; this.eventScheduler = null; this.diagnostics = null; this.currentLearnerFirstName = null; } showLoadingScreen(message = 'Loading scenario...') { const desktop = document.getElementById('desktop'); if (!desktop) return; desktop.innerHTML = `
CyberSim OS
${message}
`; } showErrorScreen(errors, warnings) { const desktop = document.getElementById('desktop'); if (!desktop) return; const errorHtml = errors.map(e => `
[${e.field}] ${e.message} ${e.objectId ? ` (${e.objectId})` : ''} ${e.expected ? `
Expected: ${e.expected}
` : ''}
` ).join(''); const warningHtml = warnings.length > 0 ? warnings.slice(0, 10).map(w => `
[${w.field}] ${w.message}
` ).join('') : ''; desktop.innerHTML = `
⚠ Scenario Validation Failed
${errors.length} error(s), ${warnings.length} warning(s)
${errorHtml}
${warningHtml ? `
Warnings:
${warningHtml}
` : ''}
`; const reloadBtn = desktop.querySelector('#btn-reload-error'); if (reloadBtn) { reloadBtn.addEventListener('click', () => this.showLogin()); } } async start() { console.log('[CyberSim OS] Booting enterprise simulation platform...'); // 1. Load deployment branding await branding.load(); branding.applyBrandingStyles(); // 2. Initialize i18n const locConfig = branding.getLocalizationConfig(); i18n.defaultLocale = locConfig.defaultLocale || 'en'; i18n.enabledLocales = locConfig.enabledLocales || ['en', 'es']; await i18n.loadLocale(i18n.defaultLocale); await i18n.loadLocale('es'); // Preload demonstration locale // Check for query parameters bypass (e.g. ?autostart=true) const params = (typeof window !== 'undefined' && window.location) ? new URLSearchParams(window.location.search) : null; const directScenario = params ? params.get('scenario') : null; const autoStart = params ? params.get('autostart') === 'true' : false; if (directScenario && autoStart) { await this.launchScenario({ firstName: params.get('name') || 'Chris', scenarioPath: directScenario, locale: params.get('locale') || i18n.defaultLocale }); } else { this.showLogin(); } } showLogin() { const desktopEl = document.getElementById('desktop'); const taskbarEl = document.getElementById('taskbar'); const startMenuEl = document.getElementById('start-menu'); // Hide desktop shell during login if (taskbarEl) taskbarEl.style.display = 'none'; if (startMenuEl) startMenuEl.style.display = 'none'; const login = new LoginScreen({ containerElement: desktopEl, onConnect: (connectData) => this.launchScenario(connectData) }); login.render(); } async launchScenario({ firstName, scenarioId, scenarioPath, locale }) { this.currentLearnerFirstName = firstName; // Set active locale await i18n.setLocale(locale || i18n.defaultLocale); // Show loading screen this.showLoadingScreen(i18n.t('desktop.loading')); const scenarioUrl = scenarioPath || getScenarioUrl(); console.log(`[CyberSim OS] Loading scenario from: ${scenarioUrl} (Locale: ${locale})`); const loadResult = await ScenarioLoader.load(scenarioUrl, { locale }); if (!loadResult.scenario) { console.error('[CyberSim OS] Failed to load scenario:', loadResult.errors); this.showErrorScreen(loadResult.errors, loadResult.warnings); return; } // Clone scenario to allow learner personalization this.scenario = JSON.parse(JSON.stringify(loadResult.scenario)); if (loadResult.scenario._canonicalScenario) { this.scenario._canonicalScenario = loadResult.scenario._canonicalScenario; } // Personalize learner state with first name if (!this.scenario.learner) { this.scenario.learner = { name: firstName, role: 'Employee', email: `${firstName.toLowerCase()}@example.internal` }; } this.scenario.learner.firstName = firstName; // Personalize full name with entered first name if (this.scenario.learner.name) { const parts = this.scenario.learner.name.split(' '); const lastName = parts.length > 1 ? parts.slice(1).join(' ') : ''; this.scenario.learner.name = lastName ? `${firstName} ${lastName}` : firstName; } // Validate schema const schemaResult = validateSchema(this.scenario); const validationResult = validateScenario(this.scenario); const allErrors = [...loadResult.errors, ...schemaResult.errors, ...validationResult.errors]; const allWarnings = [...loadResult.warnings, ...schemaResult.warnings, ...validationResult.warnings]; if (allErrors.length > 0) { console.error(`[CyberSim OS] Scenario validation failed with ${allErrors.length} error(s)`); this.showErrorScreen(allErrors, allWarnings); return; } // Calculate canonical SHA-256 fingerprint this.scenarioFingerprint = await calculateScenarioFingerprint(this.scenario); console.log(`[CyberSim OS] Scenario Fingerprint (SHA-256): ${this.scenarioFingerprint}`); // Diagnostics in dev mode if (isDevelopmentMode()) { this.diagnostics = new ScenarioDiagnostics({ scenario: this.scenario, schemaResult, validationResult }); this.diagnostics.logToConsole(); } // Initialize runtime scenario state this.scenarioState = new ScenarioState(this.scenario, { seed: this.scenario.seed }); // Initialize UI await this.initializeUI(); // Show taskbar & start menu const taskbarEl = document.getElementById('taskbar'); const startMenuEl = document.getElementById('start-menu'); if (taskbarEl) taskbarEl.style.display = 'flex'; if (startMenuEl) startMenuEl.style.display = 'flex'; // Start event scheduler const conditionEvaluator = new ConditionEvaluator(this.scenarioState); const actionDispatcher = new ActionDispatcher({ scenarioState: this.scenarioState, scenario: this.scenario, eventBus: this.eventBus, notifications: this.notifications, apps: {} }); actionDispatcher.registerApps({ inlook: this.inlook, navigator: this.navigator, files: this.files, docViewer: this.docViewer, securityCenter: this.securityCenter }); actionDispatcher.registerEndScenarioCallback(() => this.finishScenario()); this.eventScheduler = new EventScheduler( this.scenario, this.scenarioState, conditionEvaluator, actionDispatcher, this.eventBus ); this.consequences = new ConsequenceEngine(this.eventBus); this.eventBus.on('*', (entry) => { this.scenarioState.recordAction(entry); }); this.eventBus.emit('SCENARIO_STARTED', { target: this.scenario.id, details: { title: this.scenario.title, fingerprint: this.scenarioFingerprint } }); this.eventScheduler.start(500); if (this.diagnostics) { this.diagnostics.setFingerprint(this.scenarioFingerprint); this.diagnostics.setSeed(this.scenarioState.seed); this.diagnostics.showOverlay(); } window.CyberSimOS = { engine: this, navigator: this.navigator, inlook: this.inlook, files: this.files, securityCenter: this.securityCenter, docViewer: this.docViewer, diagnostics: this.diagnostics, state: this.scenarioState, finishSimulation: () => this.finishScenario() }; } async initializeUI() { const desktopEl = document.getElementById('desktop'); const taskbarAppsEl = document.getElementById('taskbar-apps'); const startMenuEl = document.getElementById('start-menu'); const startBtnEl = document.getElementById('start-btn'); const clockEl = document.getElementById('taskbar-clock'); desktopEl.innerHTML = '
'; this.notifications = new NotificationService(); this.wm = new WindowManager(desktopEl, taskbarAppsEl); this.docViewer = new DocViewerApp({ windowManager: this.wm, eventBus: this.eventBus, scenario: this.scenario }); this.files = new FilesApp({ windowManager: this.wm, eventBus: this.eventBus, notifications: this.notifications, scenario: this.scenario, onOpenFile: (file) => this.docViewer.openDocument(file) }); this.navigator = new NavigatorApp({ windowManager: this.wm, eventBus: this.eventBus, notifications: this.notifications, scenario: this.scenario }); this.securityCenter = new SecurityCenterApp({ windowManager: this.wm, eventBus: this.eventBus, scenario: this.scenario }); this.inlook = new InlookApp({ windowManager: this.wm, eventBus: this.eventBus, notifications: this.notifications, scenario: this.scenario, onNavigateUrl: (url) => this.navigator.launch(url), onOpenDoc: (file) => this.docViewer.openDocument(file), onFileDownloaded: (file) => this.files.addFile(file) }); this.desktop = new DesktopShell({ desktopElement: desktopEl, startMenuElement: startMenuEl, startBtnElement: startBtnEl, clockElement: clockEl, eventBus: this.eventBus, onFinishScenario: () => this.finishScenario() }); // Populate Start Menu user info this._populateStartMenu(); const registeredApps = [ { id: 'inlook', name: i18n.t('apps.inlook'), iconSvg: this.inlook.getIconSvg(), launch: () => this.inlook.launch() }, { id: 'navigator', name: i18n.t('apps.navigator'), iconSvg: this.navigator.getIconSvg(), launch: () => this.navigator.launch() }, { id: 'files', name: i18n.t('apps.files'), iconSvg: this.files.getIconSvg(), launch: () => this.files.launch() }, { id: 'security_center', name: i18n.t('apps.securityCenter'), iconSvg: this.securityCenter.getIconSvg(), launch: () => this.securityCenter.launch() }, { id: 'docviewer', name: i18n.t('apps.docViewer'), iconSvg: this.docViewer.getIconSvg(), launch: () => { const firstDoc = this.scenario.files && this.scenario.files[0]; if (firstDoc) this.docViewer.openDocument(firstDoc); } }, { id: 'verify_cert', name: i18n.t('apps.verifyCert'), iconSvg: ``, launch: () => { window.open('verify.html', '_blank'); } } ]; this.desktop.renderDesktopIcons(registeredApps); } _populateStartMenu() { const s = this.scenario; if (!s) return; const avatarEl = document.getElementById('start-user-avatar'); const nameEl = document.getElementById('start-user-name'); const roleEl = document.getElementById('start-user-role'); const objectivesEl = document.getElementById('start-objectives'); const orgNameEl = document.getElementById('taskbar-org-name'); const networkEl = document.getElementById('taskbar-network-icon'); const org = branding.getOrganization(); const orgName = (s.organizations && s.organizations[0]) ? s.organizations[0].name : org.name; if (s.learner && avatarEl) { const initials = s.learner.name.split(' ').map(n => n[0]).join(''); avatarEl.textContent = initials; } if (s.learner && nameEl) nameEl.textContent = s.learner.name; const roleLine = s.learner ? `${s.learner.role} \u2022 ${orgName}` : orgName; if (roleEl) roleEl.textContent = roleLine; if (orgNameEl) orgNameEl.textContent = org.shortName || orgName; if (networkEl) networkEl.title = `${i18n.t('desktop.networkProtected')} (${orgName})`; if (s.objectives && objectivesEl) { const items = s.objectives.map(obj => { const text = typeof obj === 'string' ? obj : obj.text; return `\u2022 ${text}`; }); objectivesEl.innerHTML = items.join('
'); } } finishScenario() { if (this.eventScheduler) { this.eventScheduler.stop(); } this.eventBus.emit('SCENARIO_FINISHED', { target: this.scenario.id }); const logs = this.eventBus.getLogs(); const scorer = new BehavioralScorer(this.scenario, logs, this.scenarioState); const scoreResult = scorer.evaluate(); const aar = new AfterActionReport({ scenario: this.scenario, scoreResult, onClaimCertificate: () => { const certGen = new CertificateGenerator(this.scenario, scoreResult, this.scenarioFingerprint); const learnerName = this.scenario.learner ? this.scenario.learner.name : 'Learner'; certGen.showCertificateModal(learnerName); }, onRestart: () => { this.showLogin(); } }); aar.show(); } } // Boot on DOM Ready document.addEventListener('DOMContentLoaded', () => { const sim = new CyberSimEngine(); sim.start(); });