// 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;
}
}