"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) { // Limpiar solo contenido dinámico, mantener cabecera Array.from(page.children).forEach(child => { if (!child.classList.contains('amo-header')) child.remove(); }); 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); let currentRow = null; const columnNames = stmt.getColumnNames(); const paramsObj = {}; let paramsBound = false; return { params: paramsObj, executeStep() { // Bind parameters only once before first execution if (!paramsBound && Object.keys(paramsObj).length > 0) { // Convert params object to sql.js format (add ':' prefix to keys) const boundParams = {}; for (const key in paramsObj) { boundParams[':' + key] = paramsObj[key]; } stmt.bind(boundParams); paramsBound = true; } const hasRow = stmt.step(); if (hasRow) { const values = stmt.get(); currentRow = {}; columnNames.forEach((name, index) => { currentRow[name] = values[index]; }); } return hasRow; }, reset() { stmt.reset(); currentRow = null; paramsBound = false; }, finalize() { stmt.free(); currentRow = null; }, get row() { return currentRow || {}; }, getString(index) { return currentRow ? Object.values(currentRow)[index] : null; }, getInt32(index) { return currentRow ? Object.values(currentRow)[index] : 0; }, getDouble(index) { return currentRow ? Object.values(currentRow)[index] : 0.0; } }; } }; // Hacer disponible globalmente para compatibilidad if (typeof window !== "undefined") { window.DB = DB; }