"use strict"; // Módulo de base de datos para WebExtensions // Usa sql.js (SQLite compilado a JavaScript/WebAssembly) const DB = { db: null, SQL: null, /** * Inicializa sql.js (debe ser llamado primero) */ async initSQL() { if (this.SQL) return this.SQL; // Cargar sql.js desde archivos locales de la extensión try { const browserAPI = typeof browser !== "undefined" ? browser : chrome; // Intentar cargar sql.js si no está ya cargado if (typeof window.initSqlJs === "undefined") { const sqlJsUrl = browserAPI.runtime.getURL("content/sql-wasm.js"); await this.loadScript(sqlJsUrl); } this.SQL = await initSqlJs({ locateFile: file => browserAPI.runtime.getURL(`content/${file}`) }); return this.SQL; } catch (e) { console.error("Failed to initialize sql.js:", e); throw new Error("Could not load SQL engine"); } }, /** * Cargar script dinámicamente */ loadScript(src) { return new Promise((resolve, reject) => { const script = document.createElement("script"); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); }, /** * Abre o crea la base de datos * @param {string} dbname - Nombre del archivo de base de datos * @param {Document} document - Documento HTML * @returns {boolean} - true si se abrió correctamente */ async openDB(dbname, document) { try { await this.initSQL(); // Intentar cargar la base de datos desde el storage de la extensión const dbData = await this.loadDBFromStorage(dbname); if (dbData) { // Cargar base de datos existente this.db = new this.SQL.Database(new Uint8Array(dbData)); console.log("Database loaded from storage"); } else { // Intentar descargar la base de datos empaquetada await this.downloadAndStoreDB(dbname, document); } return true; } catch (e) { console.error("Error opening database:", e); this.showMessage(document, "Error loading database. Please check the console for details.", "db-warning" ); return false; } }, /** * Carga la base de datos desde el storage local de la extensión */ async loadDBFromStorage(dbname) { try { const browserAPI = typeof browser !== "undefined" ? browser : chrome; const result = await browserAPI.storage.local.get(dbname); return result[dbname]; } catch (e) { console.warn("Could not load DB from storage:", e); return null; } }, /** * Descarga y almacena la base de datos */ async downloadAndStoreDB(dbname, document) { this.showMessage(document, "Loading database for the first time. Please wait...", "db-warning ok" ); try { // Intentar cargar desde la extensión const browserAPI = typeof browser !== "undefined" ? browser : chrome; const dbUrl = browserAPI.runtime.getURL(`content/db/${dbname}`); const response = await fetch(dbUrl); if (!response.ok) { throw new Error(`Failed to fetch database: ${response.status}`); } const arrayBuffer = await response.arrayBuffer(); this.db = new this.SQL.Database(new Uint8Array(arrayBuffer)); // Guardar en storage para uso futuro await this.saveDBToStorage(dbname, arrayBuffer); this.showMessage(document, "Database loaded successfully!", "db-warning ok" ); console.log("Database downloaded and stored"); } catch (e) { console.error("Error downloading database:", e); this.showMessage(document, "Failed to load database. Please reinstall the extension.", "db-warning bad" ); throw e; } }, /** * Guarda la base de datos en el storage local */ async saveDBToStorage(dbname, arrayBuffer) { try { const browserAPI = typeof browser !== "undefined" ? browser : chrome; // Convertir ArrayBuffer a Array para storage const uint8Array = new Uint8Array(arrayBuffer); const array = Array.from(uint8Array); // Chrome tiene límite de storage, considerar usar chunks para DBs grandes // Por ahora, intentar guardar directamente await browserAPI.storage.local.set({ [dbname]: array }); console.log("Database saved to storage"); } catch (e) { console.warn("Could not save DB to storage (may be too large):", e); // No es crítico si falla, se recargará la próxima vez } }, /** * Muestra un mensaje en la página */ showMessage(document, msg, style) { const div = document.createElement("div"); div.className = style; div.appendChild(document.createTextNode(msg)); const page = document.getElementById("page"); if (page) { page.appendChild(div); } }, /** * Cierra la base de datos */ closeDB() { if (this.db) { this.db.close(); this.db = null; } }, /** * Ejecuta una consulta SQL */ execute(sql, params = []) { if (!this.db) { throw new Error("Database not opened"); } return this.db.exec(sql, params); }, /** * Ejecuta una consulta y retorna la primera fila */ executeRow(sql, params = []) { const results = this.execute(sql, params); if (results.length > 0 && results[0].values.length > 0) { return results[0].values[0]; } return null; }, /** * Prepara una statement (compatible con API antigua) */ createStatement(sql) { if (!this.db) { throw new Error("Database not opened"); } // Crear un objeto que emule la API de mozIStorageStatement const stmt = this.db.prepare(sql); return { params: {}, executeStep() { return stmt.step(); }, reset() { stmt.reset(); }, finalize() { stmt.free(); }, row: new Proxy({}, { get(target, prop) { return stmt.get()[stmt.getColumnNames().indexOf(prop)]; } }), getString(index) { return stmt.get()[index]; }, getInt32(index) { return stmt.get()[index]; }, getDouble(index) { return stmt.get()[index]; } }; } }; // Hacer disponible globalmente para compatibilidad if (typeof window !== "undefined") { window.DB = DB; }