// Extracted from newOS4 index.html — ModuleManager class ModuleManager { constructor(terminal) { this.terminal = terminal; this.modules = {}; this.moduleRegistry = []; this.loadFromStorage(); } loadFromStorage() { try { const saved = localStorage.getItem('webx11_modules'); if (saved) { this.moduleRegistry = JSON.parse(saved); } } catch (e) { console.error('Module load error:', e); } } saveToStorage() { try { localStorage.setItem('webx11_modules', JSON.stringify(this.moduleRegistry)); } catch (e) { console.error('Module save error:', e); } } async loadModuleFromURL(url) { try { this.terminal.print(`Loading module from ${url}...`, 'info'); const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); const code = await response.text(); const ModuleClass = this.createModuleClass(code, url); if (ModuleClass) { const instance = new ModuleClass(); this.registerModule(instance, url); if (instance.init) { await instance.init(); } this.terminal.print(`Module loaded successfully from ${url}`, 'success'); return true; } } catch (error) { this.terminal.print(`Failed to load module: ${error.message}`, 'error'); return false; } } createModuleClass(code, url) { try { // Strip leading comments to avoid ASI: `return\n/**` → `return; /**` const cleanCode = code.replace(/^\s*(?:\/\/[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*\s*/, ''); const classMatch = cleanCode.match(/class\s+(\w+)\s+(extends\s+\S+)?\s*\{/); if (classMatch) { const className = classMatch[1]; const mod = new Function('return ' + cleanCode + '\nreturn ' + className)(); return mod; } throw new Error('No valid module class found'); } catch (e) { this.terminal.print(`Module parse error: ${e.message}`, 'error'); return null; } } registerModule(instance, url = null) { const name = instance.constructor.name; this.modules[name] = instance; instance.terminal = this.terminal; if (instance.registerCommands) { instance.registerCommands(); } // Only persist modules loaded from absolute URLs (not boot-time ext modules) if (url && url.startsWith('http') && !this.moduleRegistry.find(m => m.name === name)) { this.moduleRegistry.push({ name: name, url: url, loaded: new Date().toISOString(), enabled: true }); this.saveToStorage(); } console.log(`Module registered: ${name}`); } unregisterModule(name) { if (this.modules[name]) { delete this.modules[name]; this.moduleRegistry = this.moduleRegistry.filter(m => m.name !== name); this.saveToStorage(); return true; } return false; } loadModuleFromCode(code, url) { try { this.terminal.print('Loading module from editor...', 'info'); const ModuleClass = this.createModuleClass(code, url); if (ModuleClass) { const instance = new ModuleClass(); this.registerModule(instance, url); if (instance.init) { instance.init(); } this.terminal.print(`Module loaded from editor: ${instance.constructor.name}`, 'success'); return true; } } catch (error) { this.terminal.print(`Failed to load module from code: ${error.message}`, 'error'); return false; } } getModuleList() { const allNames = new Set([...this.moduleRegistry.map(m => m.name), ...Object.keys(this.modules)]); return Array.from(allNames).map(name => ({ name, url: (this.moduleRegistry.find(m => m.name === name) || {}).url || null, loaded: this.modules[name] !== undefined })); } clearAll() { this.modules = {}; this.moduleRegistry = []; this.saveToStorage(); } getModuleRequirements(name) { const mod = this.modules[name]; if (mod && typeof mod.requirements === 'function') { return mod.requirements(); } return null; } checkAllRequirements() { const results = {}; for (const modName in this.modules) { const mod = this.modules[modName]; if (typeof mod.requirements === 'function') { results[modName] = mod.requirements(); } } return results; } getAllCommands() { const commands = {}; for (const modName in this.modules) { const mod = this.modules[modName]; if (mod.commands) { for (const cmd in mod.commands) { commands[cmd] = modName; } } } return commands; } getAllHelp() { const help = {}; for (const modName in this.modules) { const mod = this.modules[modName]; if (typeof mod.getHelpInfo === 'function') { const modHelp = mod.getHelpInfo(); if (modHelp) Object.assign(help, modHelp); } else if (mod.commands) { for (const cmd in mod.commands) { if (!help[cmd]) help[cmd] = cmd + ' — (no description)'; } } } return help; } }