Files
ca-archive/content/db-webext.js
ale 54807b9982 v3
Signed-off-by: ale <ale@manalejandro.com>
2026-02-08 22:16:18 +01:00

246 líneas
5.8 KiB
JavaScript

"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 CDN o localmente
// Para producción, incluir sql.js localmente
try {
// Intentar cargar sql.js si no está ya cargado
if (typeof window.initSqlJs === "undefined") {
await this.loadScript("https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/sql-wasm.js");
}
this.SQL = await initSqlJs({
locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/${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;
}