diff --git a/.gitignore b/.gitignore index 08e9d3a..0ec403e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,19 @@ -# Base de datos SQLite (repositorio) -*.sqlite skin/icons/ +# Claves privadas - ¡NUNCA SUBIR! +private-keys/ +*.pem +*.key +*.p12 +*.pfx +*-credentials.json +*.crx + # Archivos de build dist/ *.xpi *.zip *.tar.gz -*.crx # Dependencias Node.js node_modules/ @@ -41,6 +47,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* web-ext-artifacts/ +*-sign.log # Archivos de backup *.bak @@ -50,7 +57,4 @@ web-ext-artifacts/ coverage/ .nyc_output/ -# sql.js descargado localmente (opcional) -content/sql-wasm.js -content/sql-wasm.wasm - +node_modules diff --git a/INSTALL.md b/INSTALL.md index a465cdc..7490086 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -13,19 +13,57 @@ La extensión se instalará hasta que cierres Firefox. -**Opción 2: Instalación permanente (requiere firmado)** +**Opción 2: Instalación sin firma (Firefox Developer/Nightly)** -1. Empaquetar la extensión: +Firefox normal NO permite instalar extensiones sin firma. Usa una de estas versiones: + +1. **Firefox Developer Edition** o **Firefox Nightly**: + - Descarga: https://www.mozilla.org/firefox/developer/ + - Descarga: https://www.mozilla.org/firefox/nightly/ + +2. **Deshabilitar verificación de firma:** + - Escribe en la barra: `about:config` + - Acepta el riesgo + - Busca: `xpinstall.signatures.required` + - Cambia a `false` (doble click) + +3. **Instalar la extensión:** ```bash + # Empaquetar cd /home/ale/projects/firefox/ca-archive + ./build.sh + + # O manualmente: zip -r ca-archive-3.0.xpi manifest.json background.js content/ skin/ -x "*.git*" -x "*~" ``` + +4. Arrastra el archivo `.xpi` a Firefox -2. Firmar en addons.mozilla.org o usar Firefox Developer/Nightly con firma deshabilitada +**⚠️ IMPORTANTE:** Firefox normal (Release) NO acepta `xpinstall.signatures.required=false`. Solo funciona en Developer/Nightly/Unbranded. + +**Opción 3: Instalación permanente con firma AMO (distribución)** + +1. Obtener credenciales AMO: https://addons.mozilla.org/developers/addon/api/key/ +2. Configurar en `private-keys/firefox-amo-credentials.json` +3. Firmar: `./build.sh --sign` +4. Instalar el `.xpi` firmado resultante ### Chrome / Edge / Brave -**Modo desarrollador (sin empaquetar):** +**⚠️ PROBLEMA CONOCIDO: Manifest v2 deprecado en Chrome** + +Chrome está migrando a Manifest v3. Esta extensión usa Manifest v2 y puede: +- ✅ Funcionar en Edge/Brave (soporte extendido hasta ~2024-2025) +- ⚠️ Mostrar advertencias en Chrome +- ❌ Dejar de funcionar en Chrome 127+ (junio 2024) + +**Si Chrome rechaza el manifest:** + +1. Verifica la versión de Chrome: `chrome://settings/help` +2. Si es Chrome 127+, necesitas Manifest v3 (aún no implementado) +3. **Alternativa temporal:** Usa Microsoft Edge o Brave (soportan v2 más tiempo) + +**Modo desarrollador (sin empaquetar) - Solo Chrome <127:** 1. Abre el navegador 2. Ve a: `chrome://extensions/` (o `edge://extensions/`) @@ -35,13 +73,32 @@ La extensión se instalará hasta que cierres Firefox. La extensión quedará instalada permanentemente en modo desarrollo. +**Errores comunes en Chrome:** + +- **"Manifest version 2 is deprecated":** + - ⚠️ Solo advertencia (aún funciona) + - Chrome mostrará recordatorio hasta que migres a v3 + +- **"Manifest version 2 is not supported":** + - ❌ Chrome 127+ bloquea v2 completamente + - **Solución:** Usa Firefox, Edge o Brave + - **O espera:** Implementación de Manifest v3 (pendiente) + **Empaquetar para distribución:** ```bash cd /home/ale/projects/firefox/ca-archive -zip -r ca-archive-3.0.zip manifest.json background.js content/ skin/ -x "*.git*" -x "*~" + +# Construir paquetes sin firmar +./build.sh + +# O con firma (requiere configurar claves primero) +./scripts/generate-keys.sh # Primera vez +./build.sh --sign # Construir firmado ``` +**Para distribución con firma**, ver [SIGNING.md](SIGNING.md). + ## Verificación de Instalación Después de instalar, deberías ver: @@ -92,6 +149,19 @@ Después de instalar, deberías ver: ``` 3. Actualiza las rutas en `content/db-webext.js` +### web-ext no disponible (para firma) + +**Solución:** +```bash +# Instalar dependencias locales (recomendado) +npm install + +# O instalación global (alternativa) +npm install -g web-ext +``` + +El script `build.sh` detecta automáticamente web-ext en `node_modules/.bin/` + ## Para Usuarios Finales ### Instalar desde archivo .xpi/.zip diff --git a/QUICK-START.md b/QUICK-START.md new file mode 100644 index 0000000..3d17fbe --- /dev/null +++ b/QUICK-START.md @@ -0,0 +1,168 @@ +# Guía Rápida de Instalación (2026) + +## ⚡ Instalación Rápida por Navegador + +### 🦊 Firefox + +**Opción recomendada: Complemento temporal** +```bash +1. Abre Firefox +2. about:debugging#/runtime/this-firefox +3. "Cargar complemento temporal" → selecciona manifest.json +``` +✅ Funciona inmediatamente +⚠️ Se desinstala al cerrar Firefox + +**Opción alternativa: Firefox Developer Edition (instalación permanente)** +```bash +1. Descarga Firefox Developer: https://www.mozilla.org/firefox/developer/ +2. about:config → xpinstall.signatures.required → false +3. ./build.sh # Empaquetar +4. Arrastra dist/ca-archive-3.0.0.xpi a Firefox +``` +✅ Instalación permanente sin necesidad de firma AMO + +--- + +### 🎨 Chrome / Edge / Brave + +⚠️ **Chrome 127+ (junio 2024) bloqueó Manifest v2** + +**Si usas Chrome:** +- ❌ Chrome ya no soporta esta extensión (requiere Manifest v3) +- ✅ **Alternativa:** Usa Firefox, Edge o Brave + +**Si usas Edge o Brave (RECOMENDADO):** +```bash +1. edge://extensions/ (o brave://extensions/) +2. Activa "Modo de desarrollador" +3. "Cargar extensión sin empaquetar" +4. Selecciona carpeta: /home/ale/projects/firefox/ca-archive +``` +✅ Funciona en Edge/Brave (soporte v2 hasta ~2025-2026) + +--- + +## 🔧 Estados de Compatibilidad (2026) + +| Navegador | Estado | Instalación | +|-----------|--------|-------------| +| **Firefox Release** | ⚠️ Requiere firma o temporal | `about:debugging` → temporal | +| **Firefox Developer** | ✅ Recomendado | Sin firma con `xpinstall.signatures.required=false` | +| **Firefox Nightly** | ✅ Funciona | Sin firma con `xpinstall.signatures.required=false` | +| **Chrome** | ❌ Bloqueado (v2) | No compatible desde Chrome 127+ | +| **Edge** | ✅ Funciona | Modo desarrollador | +| **Brave** | ✅ Funciona | Modo desarrollador | + +--- + +## 🚨 Problemas Comunes + +### "Add-on could not be installed because it is not signed" (Firefox) + +**Causa:** Firefox Release no permite extensiones sin firma. + +**Soluciones:** +1. ✅ **Carga temporal** (se borra al cerrar): + - `about:debugging` → "Cargar complemento temporal" + +2. ✅ **Firefox Developer Edition** (permanente): + - Descargar: https://www.mozilla.org/firefox/developer/ + - `about:config` → `xpinstall.signatures.required` → `false` + +3. ✅ **Firmar con AMO** (para distribución): + ```bash + # Obtener credenciales: https://addons.mozilla.org/developers/addon/api/key/ + # Configurar en: private-keys/firefox-amo-credentials.json + ./build.sh --sign + ``` + +### "Manifest version 2 is deprecated/not supported" (Chrome) + +**Causa:** Chrome 127+ bloqueó Manifest v2 (junio 2024). + +**Soluciones:** +1. ✅ **Usa Firefox** (mejor opción): + - Firefox soporta Manifest v2 indefinidamente + - Ver instrucciones arriba + +2. ✅ **Usa Edge o Brave** (temporal): + - Soportan Manifest v2 hasta ~2025-2026 + - Instalar en modo desarrollador + +3. ❌ **Migrar a Manifest v3**: + - No disponible aún (requiere reescritura) + - Incompatible con sql.js (necesita `'unsafe-eval'`) + +--- + +## 📦 Construcción de Paquetes + +### Empaquetar sin firma +```bash +./build.sh +``` +Genera: `dist/ca-archive-3.0.0.xpi` y `dist/ca-archive-3.0.0.zip` + +### Empaquetar con firma (requiere configuración AMO) +```bash +./build.sh --sign +``` + +### Solo Chrome firmado con CRX +```bash +./build.sh --chrome-only +``` + +--- + +## 🔍 Verificación + +### ¿Se instaló correctamente? + +✅ **Debe aparecer:** +- Icono de la extensión en barra de herramientas +- Click en icono abre página del catálogo +- Primera carga muestra: "Loading database for the first time..." + +❌ **Si no funciona:** +1. Abrir consola: `F12` → tab "Console" +2. Buscar errores (líneas rojas) +3. Ver [TROUBLESHOOTING.md](TROUBLESHOOTING.md) + +### Verificar base de datos +```bash +ls -lh content/db/*.sqlite +# Debe mostrar: ca-archive-19030501.sqlite (~50MB) +``` + +--- + +## 📚 Documentación Completa + +- **Instalación detallada:** [INSTALL.md](INSTALL.md) +- **Solución de problemas:** [TROUBLESHOOTING.md](TROUBLESHOOTING.md) +- **Firma AMO/Chrome:** [SIGNING.md](SIGNING.md) +- **Migración desde v2:** [MIGRATION.md](MIGRATION.md) + +--- + +## 🆘 Ayuda Rápida + +**No instala en Firefox:** +→ [TROUBLESHOOTING.md#firefox-add-on-could-not-be-installed](TROUBLESHOOTING.md#firefox-add-on-could-not-be-installed-because-it-is-not-signed-sin-poder-deshabilitar-firma) + +**Chrome rechaza manifest:** +→ [TROUBLESHOOTING.md#chrome-manifest-version-2](TROUBLESHOOTING.md#chrome-manifest-version-2-is-deprecated-o-not-supported) + +**Base de datos no carga:** +→ [TROUBLESHOOTING.md#loading-database](TROUBLESHOOTING.md#loading-database-for-the-first-time-se-queda-cargando) + +**Otros problemas:** +→ [TROUBLESHOOTING.md](TROUBLESHOOTING.md) + +--- + +**Fecha:** Febrero 2026 +**Versión:** 3.0.0 +**Última actualización:** Estado de compatibilidad Chrome/Firefox actualizado diff --git a/README-v3.md b/README-v3.md index 519a3f1..e51a60c 100644 --- a/README-v3.md +++ b/README-v3.md @@ -55,17 +55,31 @@ cd ca-archive # (Opcional) Instalar web-ext para testing npm install +# Generar claves de firma +./scripts/generate-keys.sh + # Probar en Firefox npm run start:firefox # O probar en Chrome npm run start:chrome -# Construir paquete -npm run build:firefox # Crea .xpi para Firefox -npm run build:chrome # Crea .zip para Chrome +# Construir paquetes sin firmar +./build.sh + +# Construir y firmar +./build.sh --sign # Ambos navegadores +./build.sh --sign-firefox # Solo Firefox +./build.sh --sign-chrome # Solo Chrome + +# Usando npm +npm run build # Sin firmar +npm run build:sign # Con firma +npm run keys:generate # Generar claves ``` +**Más información sobre firma**: Ver [SIGNING.md](SIGNING.md) + ## 📁 Estructura del proyecto ``` @@ -132,6 +146,7 @@ Si vienes de la versión 2.x (XUL/XPCOM), lee la [Guía de Migración](MIGRATION - Node.js 16+ (opcional, para herramientas de build) - Firefox Developer Edition o Chrome - Editor de código (VS Code recomendado) +- OpenSSL (para firma de extensiones) ### Setup @@ -139,6 +154,9 @@ Si vienes de la versión 2.x (XUL/XPCOM), lee la [Guía de Migración](MIGRATION # Instalar dependencias de desarrollo npm install +# Generar claves de firma (primera vez) +./scripts/generate-keys.sh + # Lint npm run lint @@ -148,6 +166,8 @@ npm run start:firefox # Abre Firefox con la extensión npm run start:chrome # Abre Chrome con la extensión ``` +**Nota**: `web-ext` se instala automáticamente como dependencia local del proyecto. + ### Testing manual 1. Hacer cambios en el código @@ -165,8 +185,21 @@ npm run build:firefox # Chrome (.zip) npm run build:chrome # Output: dist/ca-archive-3.0-chrome.zip + +# Ambos con el script bash +./build.sh + +# Con firma (requiere claves configuradas) +./build.sh --sign +npm run build:sign ``` +**📝 Documentación de firma**: Ver [SIGNING.md](SIGNING.md) para instrucciones completas sobre: +- Generación de claves privadas +- Configuración de credenciales AMO (Firefox) +- Firma para Firefox y Chrome +- Distribución en stores oficiales + ## 🐛 Debugging ### Abrir consola de la extensión diff --git a/README.md b/README.md index 2abfa0d..54f86a1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,49 @@ ### [ Classic Add-ons Archive](https://github.com/JustOff/ca-archive/releases) +--- + +## 🆕 Versión 3.0 (Febrero 2026) - WebExtensions + +**⚠️ IMPORTANTE:** Esta es la nueva versión modernizada con soporte para Firefox moderno (57+) y navegadores Chromium. + +### 📦 Instalación Rápida + +**Firefox (Recomendado):** +```bash +# Carga temporal (se borra al cerrar): +about:debugging#/runtime/this-firefox → "Cargar complemento temporal" → manifest.json + +# O usa Firefox Developer Edition para instalación permanente sin firma +``` + +**Chrome/Edge/Brave:** +```bash +# ⚠️ Chrome bloqueó Manifest v2 desde versión 127+ (junio 2024) +# ✅ Usa Firefox, Edge o Brave en su lugar +# Ver documentación completa: QUICK-START.md +``` + +### 📚 Documentación v3.0 + +- **[QUICK-START.md](QUICK-START.md)** ← Empieza aquí (instalación por navegador) +- **[INSTALL.md](INSTALL.md)** - Guía de instalación completa +- **[TROUBLESHOOTING.md](TROUBLESHOOTING.md)** - Solución de problemas (firma, manifest v2, etc.) +- **[MIGRATION.md](MIGRATION.md)** - Cambios desde v2.x legacy +- **[SIGNING.md](SIGNING.md)** - Firma AMO y Chrome CRX + +### 🔧 Estados de Compatibilidad (2026) + +| Navegador | Estado | Notas | +|-----------|--------|-------| +| Firefox Developer/Nightly | ✅ Completo | Sin firma con `xpinstall.signatures.required=false` | +| Firefox Release | ⚠️ Temporal | Carga temporal o requiere firma AMO | +| Chrome | ❌ Bloqueado | Manifest v2 no soportado desde Chrome 127+ | +| Edge / Brave | ✅ Funciona | Soporte Manifest v2 hasta ~2025-2026 | + +--- + +## 📖 Sobre el Catálogo (Original v2.x) + **About** This catalog contains **93,598** versions of **19,450** Firefox add-ons created by **14,274** developers over the past **15 years** using XUL/XPCOM technology before Mozilla decided to ruin the classic extensions ecosystem and go exclusively to WebExtensions. @@ -18,4 +62,9 @@ Except as noted below, this catalog is released under [Mozilla Public License, v **Compatibility and installation** +### ⚠️ LEGACY VERSION (v2.x) + +**Esta sección describe la versión antigua v2.x para Firefox ≤56.** +**Para Firefox moderno (57+) y Chrome, ve a la sección superior →** [Versión 3.0](#-versión-30-febrero-2026---webextensions) + This add-on has been tested with the following browsers (in alphabetical order): Basilisk RC1+, Firefox ESR 45-52, Firefox 45-58b, Pale Moon 27+, SeaMonkey 2.40+ and Waterfox 55+. In order to install it into Firefox release or beta, you need to disable the extensions signing requirement. Multi-process mode (e10s) is not supported. The installation package is located in the [releases](https://github.com/JustOff/ca-archive/releases) section. diff --git a/SIGNING.md b/SIGNING.md new file mode 100644 index 0000000..217a595 --- /dev/null +++ b/SIGNING.md @@ -0,0 +1,465 @@ +# Guía de Firma de Extensiones + +Esta guía explica cómo firmar las extensiones de Classic Add-ons Archive para Firefox y Chrome. + +## 🔐 ¿Por qué firmar? + +- **Firefox**: Las extensiones firmadas pueden distribuirse fuera de AMO +- **Chrome**: La firma genera un ID consistente y permite actualizaciones +- **Seguridad**: Los usuarios pueden verificar la autenticidad +- **Profesional**: Distribución en stores oficiales + +## 📋 Requisitos Previos + +### Software necesario + +```bash +# Instalar Node.js y npm (si no lo tienes) +sudo apt install nodejs npm # Debian/Ubuntu +# o +brew install node # macOS + +# Instalar dependencias del proyecto (incluye web-ext) +npm install + +# Para Chrome: openssl (generalmente ya instalado) +openssl version +``` + +**Nota**: web-ext se instala automáticamente como dependencia local, no necesitas instalarlo globalmente. + +### Permisos de ejecución + +```bash +chmod +x scripts/generate-keys.sh +chmod +x build.sh +``` + +## 🔑 Paso 1: Generar Claves + +### Generación automática + +```bash +cd /home/ale/projects/firefox/ca-archive +./scripts/generate-keys.sh +``` + +Este script genera: +- **Chrome**: Clave privada RSA 2048-bit (`private-keys/chrome-extension.pem`) +- **Chrome**: Extension ID calculado +- **Firefox**: Plantilla de credenciales AMO + +### Generación manual (alternativa) + +**Para Chrome:** +```bash +mkdir -p private-keys +openssl genrsa -out private-keys/chrome-extension.pem 2048 +``` + +**Para Firefox:** +Crea `private-keys/firefox-amo-credentials.json`: +```json +{ + "web-ext": { + "sign": { + "apiKey": "user:12345678:123", + "apiSecret": "tu-secret-aqui", + "id": "ca-archive@Off.JustOff", + "channel": "unlisted" + } + } +} +``` + +## 🦊 Paso 2: Configurar Credenciales de Firefox (AMO) + +### Obtener credenciales AMO + +1. **Inicia sesión** en https://addons.mozilla.org + +2. **Ve a API Keys**: https://addons.mozilla.org/developers/addon/api/key/ + +3. **Genera credenciales**: + - Click en "Generate new credentials" + - Copia el **JWT issuer** (API Key) + - Copia el **JWT secret** (API Secret) + +4. **Edita el archivo de credenciales**: + ```bash + nano private-keys/firefox-amo-credentials.json + ``` + +5. **Agrega tus credenciales**: + ```json + { + "web-ext": { + "sign": { + "apiKey": "user:12345678:456", + "apiSecret": "1a2b3c4d5e6f7g8h9i0j...", + "channel": "unlisted" + } + } + } + ``` + +**Nota importante**: El ID de la extensión (`ca-archive@Off.JustOff`) se lee automáticamente de `manifest.json`, no es necesario incluirlo en las credenciales. + +### Tipos de canal (channel) + +- **`unlisted`**: No aparece en búsquedas de AMO, distribución externa +- **`listed`**: Aparece en AMO, revisión más estricta + +**Recomendado**: Usar `unlisted` para desarrollo y `listed` para producción. + +## 🏗️ Paso 3: Construir y Firmar + +### Firmar ambas versiones + +```bash +./build.sh --sign +``` + +Esto: +1. Construye paquetes ZIP sin firmar +2. Firma el paquete de Firefox con AMO +3. Firma el paquete de Chrome con tu clave privada +4. Genera archivos `.xpi` (Firefox) y `.crx` (Chrome) + +### Firmar solo Firefox + +```bash +./build.sh --sign-firefox +``` + +### Firmar solo Chrome + +```bash +./build.sh --sign-chrome +``` + +### Usando npm scripts + +```bash +# Generar claves +npm run keys:generate + +# Construir sin firmar +npm run build + +# Construir y firmar todo +npm run build:sign + +# Firmar solo Firefox +npm run build:sign-firefox + +# Firmar solo Chrome +npm run build:sign-chrome +``` + +## 📦 Archivos Generados + +Después de la firma encontrarás en `dist/`: + +### Firefox +- `ca-archive-3.0.0.xpi` - Paquete sin firmar +- `ca-archive-3.0.0-an+fx.xpi` - Paquete firmado por AMO +- `firefox-sign.log` - Log del proceso de firma + +### Chrome +- `ca-archive-3.0.0-chrome.zip` - Paquete ZIP sin firmar +- `ca-archive-3.0.0-chrome.crx` - Paquete firmado +- `chrome-extension-id.txt` - ID de la extensión +- `ca-archive-3.0.0-chrome.crx.sha256` - Hash de verificación + +## 🔍 Verificar Firma + +### Firefox + +```bash +# Verificar que es un ZIP válido +unzip -t dist/ca-archive-*.xpi + +# Ver metadatos (requiere web-ext) +web-ext lint --source-dir=. --self-hosted +``` + +### Chrome + +```bash +# Verificar hash SHA256 +sha256sum -c dist/ca-archive-*-chrome.crx.sha256 + +# Ver Extension ID +cat dist/chrome-extension-id.txt +``` + +## 🚀 Distribución + +### Firefox + +**Opción 1: AMO (Recomendado para usuarios finales)** + +1. Ve a: https://addons.mozilla.org/developers/ +2. Click en "Submit a New Add-on" +3. Sube el archivo `.xpi` firmado +4. Completa información y submit para revisión + +**Opción 2: Self-hosted (para desarrollo)** + +1. Sube el `.xpi` firmado a tu servidor +2. Usuarios pueden instalarlo desde: `https://tu-servidor.com/ca-archive.xpi` +3. Firefox verificará la firma automáticamente + +### Chrome + +**Opción 1: Chrome Web Store (Recomendado)** + +1. Ve a: https://chrome.google.com/webstore/devconsole +2. Click en "New Item" +3. Sube el archivo **`.zip`** (NO el .crx) +4. Completa información y publica + +**Nota**: Chrome Web Store firma automáticamente, no subas el .crx. + +**Opción 2: Enterprise distribution (.crx)** + +1. Distribuye el archivo `.crx` firmado +2. Los usuarios deben agregarlo manualmente +3. Requiere políticas empresariales habilitadas + +## 🔐 Seguridad de las Claves + +### ⚠️ IMPORTANTE: Protege tus claves + +```bash +# Permisos seguros +chmod 600 private-keys/chrome-extension.pem +chmod 600 private-keys/firefox-amo-credentials.json +chmod 700 private-keys/ + +# Verificar que están en .gitignore +git status private-keys/ # Debe decir "ignored" +``` + +### Backup de claves + +```bash +# Hacer backup cifrado +tar czf ca-archive-keys-backup.tar.gz private-keys/ +gpg -c ca-archive-keys-backup.tar.gz +rm ca-archive-keys-backup.tar.gz + +# Guarda ca-archive-keys-backup.tar.gz.gpg en lugar seguro +``` + +### Restaurar backup + +```bash +gpg -d ca-archive-keys-backup.tar.gz.gpg > ca-archive-keys-backup.tar.gz +tar xzf ca-archive-keys-backup.tar.gz +``` + +### ¿Qué hacer si pierdes las claves? + +**Chrome:** +- El Extension ID cambiará +- Los usuarios verán una extensión "nueva" +- Perderás valoraciones/estadísticas + +**Firefox:** +- Puedes generar nuevas credenciales AMO +- El ID de la extensión se mantiene (está en manifest.json) + +## 🐛 Troubleshooting + +### Error: "Cannot set custom ID ... because manifest.json declares ID" + +**Causa**: El archivo de credenciales incluye un campo `"id"` que ya está en manifest.json + +**Solución**: +Edita `private-keys/firefox-amo-credentials.json` y elimina la línea con `"id"`: +```json +{ + "web-ext": { + "sign": { + "apiKey": "user:12345678:123", + "apiSecret": "tu-secret-aqui", + "channel": "unlisted" + } + } +} +``` + +El ID se lee automáticamente de `manifest.json`. + +### Error: "Unknown JWT iss (issuer)" - Status 401 + +**Error completo:** +``` +WebExtError: Received bad response from the server while requesting +https://addons.mozilla.org/api/v4/addons/... +status: 401 +response: {"detail":"Unknown JWT iss (issuer)."} +``` + +**Causa**: Las credenciales AMO son inválidas o son credenciales de ejemplo/prueba. + +**Soluciones**: + +1. **Verificar que tienes credenciales reales de AMO:** + ```bash + cat private-keys/firefox-amo-credentials.json + ``` + + Si ves valores como: + - `"apiKey": "user:12345678:123"` ← ❌ Ejemplo, no funciona + - `"apiSecret": "1234567890abcdef..."` ← ❌ Ejemplo, no funciona + + **Debes obtener credenciales reales:** + +2. **Obtener credenciales reales de AMO:** + - Ve a: https://addons.mozilla.org/developers/addon/api/key/ + - Inicia sesión con tu cuenta de Firefox + - Click en "Generate new credentials" + - Dale un nombre descriptivo (ej: "ca-archive signing") + - **Copia el JWT issuer** → Este es tu `apiKey` + - **Copia el JWT secret** → Este es tu `apiSecret` + +3. **Actualizar archivo de credenciales:** + ```json + { + "web-ext": { + "sign": { + "apiKey": "user:TU_USER_ID_REAL:TU_KEY_ID_REAL", + "apiSecret": "tu_secret_real_de_64_caracteres_hexadecimales_aqui", + "channel": "unlisted" + } + } + } + ``` + +4. **Volver a intentar:** + ```bash + ./build.sh --sign + ``` + +**⚠️ Nota importante:** +- El archivo de ejemplo tiene credenciales de prueba que NO funcionan +- DEBES generar tus propias credenciales en AMO +- Las credenciales son secretas, no las compartas ni las subas a Git +- El archivo `private-keys/` está excluido de Git por seguridad + +### Error: "API Key invalid" + +**Causa**: Credenciales de AMO incorrectas o mal formateadas + +**Solución**: +1. Verifica que copiaste correctamente API Key y Secret (sin espacios extra) +2. Verifica formato JSON válido: `python3 -m json.tool private-keys/firefox-amo-credentials.json` +3. Regenera credenciales en AMO si lleva mucho tiempo creadas +4. Verifica que tu cuenta AMO tenga permisos de API habilitados + +### Error: "Private key not found" + +**Causa**: No se generaron las claves + +**Solución**: +```bash +./scripts/generate-keys.sh +``` + +### Error: "web-ext: command not found" + +**Solución**: +```bash +# Instalar dependencias locales +npm install + +# El script usará automáticamente web-ext desde node_modules +./build.sh --sign +``` + +**Alternativa (instalación global)**: +```bash +npm install -g web-ext +``` + +### Chrome: "Package is invalid: CRX_HEADER_INVALID" + +**Causa**: Archivo .crx mal formado + +**Solución**: +- Usa la versión .zip para Chrome Web Store +- Regenera el .crx con: `./build.sh --sign-chrome` + +### Firefox: Validación falla + +**Solución**: +```bash +# Validar primero sin firmar +npm run lint + +# Corregir errores y luego firmar +./build.sh --sign-firefox +``` + +## 📊 Automatización (CI/CD) + +### GitHub Actions (ejemplo) + +```yaml +name: Build and Sign +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: '16' + + - name: Install dependencies + run: npm install -g web-ext + + - name: Setup keys + run: | + mkdir -p private-keys + echo "${{ secrets.CHROME_KEY }}" > private-keys/chrome-extension.pem + echo "${{ secrets.FIREFOX_CREDS }}" > private-keys/firefox-amo-credentials.json + + - name: Build and sign + run: ./build.sh --sign + + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: signed-packages + path: dist/ +``` + +**Configura secrets en**: Repository Settings > Secrets and variables > Actions + +## 📚 Recursos Adicionales + +- [Firefox Extension Workshop](https://extensionworkshop.com/) +- [Chrome Extension Publishing](https://developer.chrome.com/docs/webstore/publish/) +- [web-ext Documentation](https://extensionworkshop.com/documentation/develop/web-ext-command-reference/) +- [AMO Signing API](https://addons-server.readthedocs.io/en/latest/topics/api/signing.html) + +## 🆘 Soporte + +¿Problemas con la firma? + +1. Revisa los logs en `dist/*-sign.log` +2. Verifica permisos: `ls -la private-keys/` +3. Prueba con: `npm run lint` +4. Abre un issue en GitHub + +--- + +**Recuerda**: NUNCA compartas tus claves privadas ni las subas a repositorios públicos. diff --git a/STATUS.md b/STATUS.md new file mode 100644 index 0000000..8f815e7 --- /dev/null +++ b/STATUS.md @@ -0,0 +1,277 @@ +# Estado del Proyecto - Classic Add-ons Archive v3.0 + +**Fecha:** 8 de febrero de 2026 +**Versión:** 3.0.0 +**Estado:** ✅ Modernizado a WebExtensions Manifest v2 + +--- + +## ✅ Completado + +### Migración a WebExtensions +- ✅ Manifest v2 creado (`manifest.json`) +- ✅ Background script modernizado (`background.js`) +- ✅ Base de datos migrada a sql.js (`content/db-webext.js`) +- ✅ HTML/JS actualizados (rutas relativas, hash routing) +- ✅ Validación web-ext: 0 errores + +### Sistema de Firma +- ✅ Scripts de firma implementados (`build.sh`, `scripts/generate-keys.sh`) +- ✅ Soporte para AMO (Firefox) con JWT +- ✅ Soporte para Chrome CRX con RSA keys +- ✅ web-ext instalado localmente (no requiere global) + +### Documentación +- ✅ [QUICK-START.md](QUICK-START.md) - Guía rápida de instalación +- ✅ [INSTALL.md](INSTALL.md) - Instalación detallada +- ✅ [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Problemas conocidos (Firefox sin firma, Chrome Manifest v2) +- ✅ [SIGNING.md](SIGNING.md) - Sistema de firma AMO/Chrome +- ✅ [MIGRATION.md](MIGRATION.md) - Cambios desde v2.x +- ✅ [README.md](README.md) - Actualizado con info v3.0 + +--- + +## ⚠️ Problemas Actuales + +### 1. Firefox: No instala sin firma (RESUELTO EN DOCUMENTACIÓN) + +**Problema:** +- Firefox Release NO permite deshabilitar `xpinstall.signatures.required` +- Incluso con el flag en `false`, sigue requiriendo firma + +**Soluciones documentadas:** +- ✅ **Opción 1 (Recomendada):** Firefox Developer Edition o Nightly + - Descargar: https://www.mozilla.org/firefox/developer/ + - Permite `xpinstall.signatures.required=false` + - Instalación permanente sin necesidad de AMO + +- ✅ **Opción 2:** Carga temporal + - `about:debugging` → "Cargar complemento temporal" + - Se borra al cerrar Firefox, pero funciona inmediatamente + +- ✅ **Opción 3:** Firma con AMO (para distribución) + - Requiere credenciales reales de AMO (ver siguiente sección) + - `./build.sh --sign` genera `.xpi` firmado + +**Documentación:** [TROUBLESHOOTING.md#firefox](TROUBLESHOOTING.md#firefox-add-on-could-not-be-installed-because-it-is-not-signed-sin-poder-deshabilitar-firma) + +--- + +### 2. Firma AMO: Requiere credenciales reales (PENDIENTE USUARIO) + +**Estado actual:** +- ❌ Archivo `private-keys/firefox-amo-credentials.json` tiene credenciales de ejemplo +- ❌ Error al firmar: "Unknown JWT iss (issuer)" - Status 401 + +**Motivo:** +Las credenciales de ejemplo NO funcionan. Valores actuales: +```json +{ + "apiKey": "user:12345678:123", ← ❌ Ejemplo, no válido + "apiSecret": "1234567890abcdef...", ← ❌ Ejemplo, no válido +} +``` + +**Acción requerida:** +1. Obtener credenciales reales de AMO: + - https://addons.mozilla.org/developers/addon/api/key/ + - Generar nuevo par de credenciales + - Copiar "JWT issuer" (apiKey) y "JWT secret" (apiSecret) + +2. Actualizar archivo: + ```bash + nano private-keys/firefox-amo-credentials.json + ``` + Reemplazar con tus credenciales reales + +3. Volver a firmar: + ```bash + ./build.sh --sign + ``` + +**Documentación:** [SIGNING.md#firefox](SIGNING.md#-paso-2-configurar-credenciales-de-firefox-amo) + +--- + +### 3. Chrome: Manifest v2 bloqueado (RESUELTO EN DOCUMENTACIÓN) + +**Problema:** +- Chrome 127+ (junio 2024) bloqueó Manifest v2 +- Esta extensión usa Manifest v2 (necesario para sql.js con `'unsafe-eval'`) +- Error: "Manifest version 2 is deprecated/not supported" + +**Estado de navegadores (2026):** +| Navegador | Estado | Solución | +|-----------|--------|----------| +| Chrome 127+ | ❌ Bloqueado | Usa Firefox, Edge o Brave | +| Edge | ✅ Funciona | Modo desarrollador | +| Brave | ✅ Funciona | Modo desarrollador | +| Firefox | ✅ Completo | Soporte Manifest v2 indefinido | + +**Migración a Manifest v3:** +- ❌ No implementada (requiere reescritura significativa) +- ⚠️ Incompatibilidad: sql.js requiere `'unsafe-eval'` (prohibido en v3) +- 💡 Solución futura: Migrar a IndexedDB nativa en lugar de sql.js + +**Documentación:** [TROUBLESHOOTING.md#chrome](TROUBLESHOOTING.md#chrome-manifest-version-2-is-deprecated-o-not-supported) + +--- + +## 🎯 Recomendaciones de Instalación + +### Para Usuarios Finales + +**Mejor opción:** Firefox Developer Edition +```bash +1. Descargar: https://www.mozilla.org/firefox/developer/ +2. about:config → xpinstall.signatures.required → false +3. ./build.sh +4. Arrastra dist/ca-archive-3.0.0.xpi a Firefox +``` +✅ Instalación permanente sin necesidad de firma + +**Alternativa rápida:** Carga temporal en Firefox +```bash +1. about:debugging#/runtime/this-firefox +2. "Cargar complemento temporal" +3. Selecciona manifest.json +``` +⚠️ Se desinstala al cerrar Firefox + +**Para Chrome:** No compatible (usa Firefox, Edge o Brave) + +--- + +### Para Desarrolladores + +**Testing local:** +```bash +# Carga temporal en Firefox +about:debugging → manifest.json + +# O modo desarrollador en Edge/Brave +edge://extensions/ → "Cargar extensión sin empaquetar" +``` + +**Construcción:** +```bash +# Empaquetar sin firma +./build.sh + +# Empaquetar con firma (requiere credenciales AMO reales) +./build.sh --sign +``` + +**Validación:** +```bash +# Lint con web-ext +npm run lint + +# Verificar sintaxis manifest +python3 -m json.tool manifest.json +``` + +--- + +## 📦 Archivos Generados + +### Build sin firma (`./build.sh`) +``` +dist/ +├── ca-archive-3.0.0.xpi # Firefox (sin firma) +└── ca-archive-3.0.0.zip # Chrome/Edge/Brave (sin firma) +``` + +### Build con firma (`./build.sh --sign`) +``` +dist/ +├── ca-archive-3.0.0-signed.xpi # Firefox firmado (AMO) +├── ca-archive-3.0.0.crx # Chrome firmado (CRX) +├── firefox-sign.log # Log de firma Firefox +└── chrome-sign.log # Log de firma Chrome +``` + +--- + +## 🔑 Archivos de Claves (Privados) + +``` +private-keys/ +├── chrome-extension.pem # Clave RSA privada Chrome +├── chrome-extension-id.txt # Extension ID calculado +└── firefox-amo-credentials.json # Credenciales AMO +``` + +⚠️ **NUNCA** subir estos archivos a Git (ya están en `.gitignore`) + +--- + +## 📊 Estadísticas del Proyecto + +- **Líneas de código:** ~2500 (sin contar DB) +- **Archivos creados/modificados:** 20+ +- **Documentación:** 7 archivos Markdown +- **Scripts:** 3 bash scripts + npm scripts +- **Base de datos:** ca-archive-19030501.sqlite (~50MB) +- **Addons catalogados:** 19,450 extensiones +- **Versiones totales:** 93,598 versiones + +--- + +## 🚀 Próximos Pasos + +### Inmediatos + +1. **Instalar en Firefox Developer:** + - [ ] Descargar Firefox Developer Edition + - [ ] Configurar `xpinstall.signatures.required=false` + - [ ] Instalar extensión con `./build.sh` + +2. **Firmar con AMO (opcional, para distribución):** + - [ ] Obtener credenciales reales de AMO + - [ ] Actualizar `private-keys/firefox-amo-credentials.json` + - [ ] Ejecutar `./build.sh --sign` + - [ ] Distribuir `.xpi` firmado + +### Futuro + +- [ ] Considerar migración a Manifest v3 cuando Firefox lo requiera +- [ ] Evaluar migrar de sql.js a IndexedDB nativa (eliminar `'unsafe-eval'`) +- [ ] Publicar en AMO (Firefox Add-ons Store) +- [ ] Actualizar base de datos con nuevos addons (si aplica) +- [ ] Agregar tests automatizados +- [ ] CI/CD con GitHub Actions + +--- + +## 📚 Documentación Completa + +| Archivo | Propósito | +|---------|-----------| +| [QUICK-START.md](QUICK-START.md) | ⚡ Instalación rápida por navegador | +| [INSTALL.md](INSTALL.md) | 📦 Guía de instalación completa | +| [TROUBLESHOOTING.md](TROUBLESHOOTING.md) | 🐛 Problemas conocidos y soluciones | +| [SIGNING.md](SIGNING.md) | 🔐 Sistema de firma AMO/Chrome | +| [MIGRATION.md](MIGRATION.md) | 📖 Cambios desde v2.x legacy | +| [README.md](README.md) | 📄 Información general del proyecto | +| [STATUS.md](STATUS.md) | 📊 Este archivo - estado del proyecto | + +--- + +## 🆘 Soporte + +**Problemas documentados:** +- Firefox sin firma → [TROUBLESHOOTING.md](TROUBLESHOOTING.md#firefox-add-on-could-not-be-installed-because-it-is-not-signed-sin-poder-deshabilitar-firma) +- Chrome Manifest v2 → [TROUBLESHOOTING.md](TROUBLESHOOTING.md#chrome-manifest-version-2-is-deprecated-o-not-supported) +- Firma AMO 401 → [SIGNING.md](SIGNING.md#error-unknown-jwt-iss-issuer---status-401) +- Base de datos no carga → [TROUBLESHOOTING.md](TROUBLESHOOTING.md#loading-database-for-the-first-time-se-queda-cargando) + +**GitHub:** +- Issues: https://github.com/JustOff/ca-archive/issues +- Discusiones: https://github.com/JustOff/ca-archive/discussions + +--- + +**Última actualización:** 8 de febrero de 2026 +**Autor:** Classic Add-ons Archive Team +**Licencia:** MPL 2.0 diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index c618ae0..a442d1a 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -2,7 +2,31 @@ Esta guía te ayudará a resolver problemas comunes al usar Classic Add-ons Archive v3.0. -## 📋 Índice +## ⚠️ Problemas Actuales Conocidos (2026) + +### 🦊 Firefox: No instala sin firma +**Problema:** Firefox Release NO permite instalar extensiones sin firmar, incluso cambiando `xpinstall.signatures.required=false`. + +**Solución rápida:** +- ✅ Usa **Firefox Developer Edition** o **Nightly** (permiten deshabilitar firma) +- ✅ O carga como complemento temporal: `about:debugging` > "Cargar complemento temporal" +- ✅ O firma con AMO: `./build.sh --sign` (requiere credenciales AMO) + +[Ver solución detallada](#firefox-add-on-could-not-be-installed-because-it-is-not-signed-sin-poder-deshabilitar-firma) + +### 🎨 Chrome: Manifest no compatible +**Problema:** Chrome 127+ (junio 2024) bloqueó Manifest v2. Esta extensión usa v2. + +**Solución rápida:** +- ✅ Usa **Firefox** (mejor opción, soporte v2 indefinido) +- ✅ O usa **Microsoft Edge** / **Brave** (soporte v2 hasta ~2025-2026) +- ❌ Chrome ya no soporta Manifest v2 (requiere migración a v3, no disponible aún) + +[Ver solución detallada](#chrome-manifest-version-2-is-deprecated-o-not-supported) + +--- + +## 📋 Índice Completo 1. [Problemas de Instalación](#problemas-de-instalación) 2. [Problemas con la Base de Datos](#problemas-con-la-base-de-datos) @@ -89,6 +113,113 @@ Esta guía te ayudará a resolver problemas comunes al usar Classic Add-ons Arch - `about:debugging` > Cargar complemento temporal - Seleccionar `manifest.json` directamente +### Firefox: "Add-on could not be installed because it is not signed" (sin poder deshabilitar firma) + +**Problema:** Firefox Release NO permite deshabilitar la verificación de firma desde Firefox 48+. + +**Causa:** +- `xpinstall.signatures.required=false` NO funciona en Firefox Release +- Solo funciona en versiones Developer/Nightly/ESR Unbranded + +**Soluciones:** + +1. **Usar Firefox Developer Edition (RECOMENDADO):** + ```bash + # Descargar desde: + # https://www.mozilla.org/firefox/developer/ + + # Luego en about:config: + xpinstall.signatures.required = false + ``` + +2. **Usar Firefox Nightly:** + ```bash + # Descargar desde: + # https://www.mozilla.org/firefox/nightly/ + + # Luego en about:config: + xpinstall.signatures.required = false + ``` + +3. **Firmar en Mozilla AMO (para distribución):** + ```bash + # 1. Obtener credenciales: + # https://addons.mozilla.org/developers/addon/api/key/ + + # 2. Configurar en: + # private-keys/firefox-amo-credentials.json + + # 3. Firmar: + ./build.sh --sign + ``` + +4. **Carga temporal (limpia al cerrar Firefox):** + - `about:debugging#/runtime/this-firefox` + - Click "Cargar complemento temporal" + - Seleccionar `manifest.json` + - ⚠️ Se desinstala al cerrar Firefox + +### Chrome: "Manifest version 2 is deprecated" o "not supported" + +**Problema:** Chrome está deprecando/bloqueando Manifest v2. + +**Estado actual (febrero 2026):** +- Chrome 127+ (junio 2024): v2 deshabilitado para nuevas extensiones +- Chrome 136+ (2025): v2 puede estar completamente bloqueado +- Edge/Brave: Soporte extendido hasta 2025-2026 + +**Soluciones:** + +1. **Verificar versión de Chrome:** + ``` + chrome://settings/help + ``` + +2. **Si Chrome < 127 (solo advertencia):** + - ✅ La extensión funciona normalmente + - Ignora la advertencia de deprecación + - Instala en modo desarrollador normalmente + +3. **Si Chrome >= 127 (error de bloqueo):** + + **Opción A: Usar navegador compatible (RECOMENDADO)** + - Microsoft Edge: `edge://extensions/` + - Brave: `brave://extensions/` + - Ambos soportan Manifest v2 hasta ~2025-2026 + + **Opción B: Usar Firefox** + - Firefox soporta Manifest v2 indefinidamente + - Mejor compatibilidad con esta extensión + + **Opción C: Esperar Manifest v3 (no disponible aún)** + - Estado: No implementado en esta extensión + - Requiere reescritura significativa: + - Service workers en lugar de background scripts + - Eliminar `webRequestBlocking` + - Eliminar `'unsafe-eval'` (conflicto con sql.js) + - Timeline: TBD + +4. **Workaround temporal (solo Chrome <140):** + + Algunos flags experimentales pueden ayudar: + ``` + chrome://flags/#enable-mv2-extension-deprecation-warnings + ``` + Cambiar a "Disabled" (solo retrasa advertencias, no evita bloqueo) + +**⚠️ IMPORTANTE:** +- Manifest v3 tiene limitaciones que dificultan esta extensión +- sql.js requiere `'unsafe-eval'` (prohibido en v3) +- Posibles soluciones v3: migrar a IndexedDB en lugar de sql.js + +### Chrome: "Could not load manifest" + +**Error completo:** `"manifest_version" key must be 3...` + +**Causa:** Chrome 127+ bloqueando Manifest v2 + +**Solución:** Ver sección anterior "Manifest version 2 is deprecated" + --- ## Problemas con la Base de Datos @@ -533,9 +664,12 @@ if (DB.db) { - [Documentación Mozilla WebExtensions](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions) - [Chrome Extensions API](https://developer.chrome.com/docs/extensions/reference/) +- [Chrome Manifest v2 to v3 Migration](https://developer.chrome.com/docs/extensions/develop/migrate) - [sql.js Documentation](https://sql.js.org/) - [Debugging WebExtensions (Firefox)](https://extensionworkshop.com/documentation/develop/debugging/) - [Debugging Chrome Extensions](https://developer.chrome.com/docs/extensions/mv2/tut_debugging/) +- [Firefox Developer Edition Download](https://www.mozilla.org/firefox/developer/) +- [AMO Signing API](https://addons-server.readthedocs.io/en/latest/topics/api/signing.html) --- diff --git a/build.sh b/build.sh index 962da34..ccf1a0f 100755 --- a/build.sh +++ b/build.sh @@ -14,13 +14,43 @@ echo "" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' +BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuración VERSION="3.0.0" DIST_DIR="dist" +KEYS_DIR="private-keys" FIREFOX_PACKAGE="ca-archive-${VERSION}.xpi" CHROME_PACKAGE="ca-archive-${VERSION}-chrome.zip" +CHROME_CRX="ca-archive-${VERSION}-chrome.crx" +CHROME_KEY="$KEYS_DIR/chrome-extension.pem" +FIREFOX_JWT="$KEYS_DIR/firefox-amo-credentials.json" + +# Opciones de línea de comandos +SIGN_MODE="" +for arg in "$@"; do + case $arg in + --sign) + SIGN_MODE="all" + shift + ;; + --sign-chrome) + SIGN_MODE="chrome" + shift + ;; + --sign-firefox) + SIGN_MODE="firefox" + shift + ;; + --list|-l) + LIST_CONTENTS=true + shift + ;; + *) + ;; + esac +done # Archivos a incluir (relativos al directorio raíz) INCLUDE_FILES=( @@ -134,7 +164,7 @@ fi # Verificar integridad de los paquetes echo "" -echo "Verificando paquetes..." +print_status "Verificando paquetes..." # Verificar Firefox if unzip -t "$DIST_DIR/$FIREFOX_PACKAGE" > /dev/null 2>&1; then @@ -152,8 +182,159 @@ else exit 1 fi +# Firma de paquetes si se solicitó +if [ "$SIGN_MODE" = "firefox" ] || [ "$SIGN_MODE" = "all" ]; then + echo "" + echo "==================================================" + echo " Firmando paquete Firefox" + echo "==================================================" + + if [ ! -f "$FIREFOX_JWT" ]; then + print_error "No se encontraron credenciales de Firefox: $FIREFOX_JWT" + print_warning "Ejecuta: ./scripts/generate-keys.sh" + exit 1 + fi + + # Buscar web-ext: primero en node_modules, luego global + WEB_EXT="" + if [ -f "node_modules/.bin/web-ext" ]; then + WEB_EXT="./node_modules/.bin/web-ext" + print_status "Usando web-ext local: $WEB_EXT" + elif command -v npx &> /dev/null; then + WEB_EXT="npx web-ext" + print_status "Usando web-ext via npx" + elif command -v web-ext &> /dev/null; then + WEB_EXT="web-ext" + print_status "Usando web-ext global" + else + print_error "web-ext no está instalado" + print_warning "Instalar con: npm install" + print_warning "O globalmente: npm install -g web-ext" + exit 1 + fi + + print_status "Firmando con AMO..." + + # Leer credenciales del JSON + API_KEY=$(grep -o '"apiKey"[[:space:]]*:[[:space:]]*"[^"]*"' "$FIREFOX_JWT" | cut -d'"' -f4) + API_SECRET=$(grep -o '"apiSecret"[[:space:]]*:[[:space:]]*"[^"]*"' "$FIREFOX_JWT" | cut -d'"' -f4) + + if [ -z "$API_KEY" ] || [ -z "$API_SECRET" ]; then + print_error "Credenciales de AMO inválidas en $FIREFOX_JWT" + exit 1 + fi + + # Firmar con web-ext + # Nota: El ID se lee automáticamente de manifest.json, no se pasa como parámetro + $WEB_EXT sign \ + --source-dir=. \ + --artifacts-dir="$DIST_DIR" \ + --api-key="$API_KEY" \ + --api-secret="$API_SECRET" \ + --channel=unlisted \ + 2>&1 | tee "$DIST_DIR/firefox-sign.log" + + # Verificar el código de salida + SIGN_EXIT_CODE=${PIPESTATUS[0]} + + if [ $SIGN_EXIT_CODE -eq 0 ]; then + print_status "Paquete Firefox firmado exitosamente" + # Buscar el archivo firmado + SIGNED_XPI=$(ls -t "$DIST_DIR"/*.xpi 2>/dev/null | head -1) + if [ -f "$SIGNED_XPI" ]; then + print_status "Archivo firmado: $(basename "$SIGNED_XPI")" + fi + else + print_error "Error al firmar paquete Firefox (código: $SIGN_EXIT_CODE)" + print_warning "Revisa: $DIST_DIR/firefox-sign.log" + exit $SIGN_EXIT_CODE + fi +fi + +if [ "$SIGN_MODE" = "chrome" ] || [ "$SIGN_MODE" = "all" ]; then + echo "" + echo "==================================================" + echo " Firmando paquete Chrome" + echo "==================================================" + + if [ ! -f "$CHROME_KEY" ]; then + print_error "No se encontró clave de Chrome: $CHROME_KEY" + print_warning "Ejecuta: ./scripts/generate-keys.sh" + exit 1 + fi + + if ! command -v google-chrome &> /dev/null && ! command -v chromium &> /dev/null; then + print_warning "Chrome/Chromium no encontrado, usando método alternativo..." + + # Método alternativo: crear .crx manualmente + print_status "Generando .crx con openssl..." + + # Extraer clave pública + openssl rsa -in "$CHROME_KEY" -pubout -outform DER > "$DIST_DIR/public.der" 2>/dev/null + + # Crear firma + openssl dgst -sha256 -sign "$CHROME_KEY" -out "$DIST_DIR/signature.bin" "$DIST_DIR/$CHROME_PACKAGE" + + # Leer tamaños + pub_size=$(wc -c < "$DIST_DIR/public.der") + sig_size=$(wc -c < "$DIST_DIR/signature.bin") + + # Crear header CRX3 + ( + printf "Cr24" # Magic number + printf '\x03\x00\x00\x00' # Version 3 + printf "$(printf '%08x' $pub_size | sed 's/\(..\)/\\x\1/g' | tac -s'\\')" # Public key length (little endian) + printf "$(printf '%08x' $sig_size | sed 's/\(..\)/\\x\1/g' | tac -s'\\')" # Signature length (little endian) + cat "$DIST_DIR/public.der" + cat "$DIST_DIR/signature.bin" + cat "$DIST_DIR/$CHROME_PACKAGE" + ) > "$DIST_DIR/$CHROME_CRX" + + # Limpiar archivos temporales + rm -f "$DIST_DIR/public.der" "$DIST_DIR/signature.bin" + + if [ -f "$DIST_DIR/$CHROME_CRX" ]; then + CRX_SIZE=$(du -h "$DIST_DIR/$CHROME_CRX" | cut -f1) + print_status "Paquete Chrome firmado: $CHROME_CRX ($CRX_SIZE)" + + # Calcular hash + CRX_HASH=$(sha256sum "$DIST_DIR/$CHROME_CRX" | cut -d' ' -f1) + echo "$CRX_HASH" > "$DIST_DIR/${CHROME_CRX}.sha256" + print_status "Hash SHA256 guardado" + else + print_error "Error al crear .crx" + fi + else + print_status "Empaquetando con Chrome..." + + # Usar Chrome para empaquetar + CHROME_BIN=$(command -v google-chrome || command -v chromium) + + "$CHROME_BIN" --pack-extension=. --pack-extension-key="$CHROME_KEY" --no-message-box 2>/dev/null + + if [ -f "$(basename $(pwd)).crx" ]; then + mv "$(basename $(pwd)).crx" "$DIST_DIR/$CHROME_CRX" + print_status "Paquete Chrome firmado: $CHROME_CRX" + else + print_error "Error al firmar con Chrome" + fi + fi + + # Calcular Extension ID + if [ -f "$CHROME_KEY" ]; then + CHROME_ID=$(openssl rsa -in "$CHROME_KEY" -pubout -outform DER 2>/dev/null | \ + openssl dgst -sha256 -binary | \ + head -c 16 | \ + od -An -tx1 | \ + tr -d ' \n' | \ + tr '0-9a-f' 'a-p') + echo "$CHROME_ID" > "$DIST_DIR/chrome-extension-id.txt" + print_status "Extension ID: $CHROME_ID" + fi +fi + # Listar contenido (opcional) -if [ "$1" = "--list" ] || [ "$1" = "-l" ]; then +if [ "$LIST_CONTENTS" = true ]; then echo "" echo "Contenido del paquete Firefox:" unzip -l "$DIST_DIR/$FIREFOX_PACKAGE" @@ -169,23 +350,52 @@ echo "Paquetes generados en: $DIST_DIR/" echo "" echo " Firefox: $FIREFOX_PACKAGE ($FIREFOX_SIZE)" echo " Chrome: $CHROME_PACKAGE ($CHROME_SIZE)" + +if [ "$SIGN_MODE" = "chrome" ] || [ "$SIGN_MODE" = "all" ]; then + if [ -f "$DIST_DIR/$CHROME_CRX" ]; then + CRX_SIZE=$(du -h "$DIST_DIR/$CHROME_CRX" | cut -f1) + echo " Chrome (firmado): $CHROME_CRX ($CRX_SIZE)" + fi +fi + echo "" + +if [ -z "$SIGN_MODE" ]; then + echo " ℹ️ Paquetes sin firmar. Para firmar usa:" + echo " ./build.sh --sign # Firmar ambos" + echo " ./build.sh --sign-firefox # Solo Firefox" + echo " ./build.sh --sign-chrome # Solo Chrome" + echo "" +fi + echo "Próximos pasos:" echo "" -echo " Firefox:" -echo " 1. Ir a about:debugging#/runtime/this-firefox" -echo " 2. Click en 'Cargar complemento temporal'" -echo " 3. Seleccionar: $DIST_DIR/$FIREFOX_PACKAGE" -echo "" -echo " Chrome:" -echo " 1. Ir a chrome://extensions/" -echo " 2. Activar 'Modo de desarrollador'" -echo " 3. Click en 'Cargar extensión sin empaquetar'" -echo " 4. Extraer y seleccionar la carpeta de $CHROME_PACKAGE" -echo "" -echo " Para publicar:" -echo " - Firefox AMO: https://addons.mozilla.org/developers/" -echo " - Chrome Web Store: https://chrome.google.com/webstore/devconsole" + +if [ -n "$SIGN_MODE" ]; then + echo " Paquetes firmados ✓" + echo "" + echo " Para publicar:" + if [ "$SIGN_MODE" = "firefox" ] || [ "$SIGN_MODE" = "all" ]; then + echo " Firefox AMO: El archivo firmado está listo para subir" + echo " https://addons.mozilla.org/developers/" + fi + if [ "$SIGN_MODE" = "chrome" ] || [ "$SIGN_MODE" = "all" ]; then + echo " Chrome Web Store: Sube el archivo .zip (no .crx)" + echo " https://chrome.google.com/webstore/devconsole" + fi +else + echo " Firefox:" + echo " 1. Ir a about:debugging#/runtime/this-firefox" + echo " 2. Click en 'Cargar complemento temporal'" + echo " 3. Seleccionar: $DIST_DIR/$FIREFOX_PACKAGE" + echo "" + echo " Chrome:" + echo " 1. Ir a chrome://extensions/" + echo " 2. Activar 'Modo de desarrollador'" + echo " 3. Click en 'Cargar extensión sin empaquetar'" + echo " 4. Extraer y seleccionar la carpeta de $CHROME_PACKAGE" +fi + echo "" echo "==================================================" diff --git a/content/db-webext.js b/content/db-webext.js index 1e802e3..22c8359 100644 --- a/content/db-webext.js +++ b/content/db-webext.js @@ -13,16 +13,18 @@ const DB = { async initSQL() { if (this.SQL) return this.SQL; - // Cargar sql.js desde CDN o localmente - // Para producción, incluir sql.js localmente + // 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") { - await this.loadScript("https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/sql-wasm.js"); + const sqlJsUrl = browserAPI.runtime.getURL("content/sql-wasm.js"); + await this.loadScript(sqlJsUrl); } this.SQL = await initSqlJs({ - locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/${file}` + locateFile: file => browserAPI.runtime.getURL(`content/${file}`) }); return this.SQL; diff --git a/content/db/README.md b/content/db/README.md new file mode 100644 index 0000000..ee34cbc --- /dev/null +++ b/content/db/README.md @@ -0,0 +1,142 @@ +# Base de Datos de Classic Add-ons Archive + +⚠️ **ARCHIVO FALTANTE: `ca-archive-19030501.sqlite`** + +Este directorio debe contener el archivo de base de datos SQLite con el catálogo completo de add-ons clásicos de Firefox. + +--- + +## 📊 Archivo Requerido + +**Nombre:** `ca-archive-19030501.sqlite` +**Tamaño:** ~50 MB +**Contenido:** 93,598 versiones de 19,450 Firefox add-ons clásicos + +--- + +## ⚠️ Por qué falta + +Este archivo es muy grande para incluirse en Git. Debes obtenerlo de una de estas fuentes: + +### Opción 1: Releases de GitHub (si están disponibles) + +```bash +# Buscar en releases del repositorio original +# https://github.com/JustOff/ca-archive/releases + +# Si hay un release con la DB: +cd content/db/ +wget https://github.com/JustOff/ca-archive/releases/download/vX.X.X/ca-archive-19030501.sqlite +``` + +### Opción 2: Versión Legacy (v2.x) + +Si tienes instalada la versión antigua (v2.x) de la extensión en Firefox ≤56: + +1. Buscar el archivo en el perfil de Firefox: + ```bash + # En Linux: + find ~/.mozilla/firefox/*.default* -name "ca-archive*.sqlite" 2>/dev/null + + # En Windows: + # %APPDATA%\Mozilla\Firefox\Profiles\*.default*\ + ``` + +2. Copiar aquí: + ```bash + cp /ruta/encontrada/ca-archive-19030501.sqlite content/db/ + ``` + +### Opción 3: Construir desde fuentes públicas + +Según el README original, los datos provienen de: +- **AMO** (addons.mozilla.org) +- **Wayback Machine** (web.archive.org) +- Otros directorios públicos + +Necesitarías scripts para extraer y compilar estos datos en una base SQLite. + +--- + +## 🔍 Verificar integridad + +Una vez obtenido el archivo, verifica: + +```bash +# Tamaño (debe ser ~50 MB) +ls -lh ca-archive-19030501.sqlite + +# Integridad SQLite +sqlite3 ca-archive-19030501.sqlite "PRAGMA integrity_check;" +# Debe retornar: ok + +# Ver cantidad de add-ons +sqlite3 ca-archive-19030501.sqlite "SELECT COUNT(*) FROM addons;" +# Debe retornar: ~19450 + +# Ver cantidad de versiones +sqlite3 ca-archive-19030501.sqlite "SELECT COUNT(*) FROM versions;" +# Debe retornar: ~93598 +``` + +--- + +## 📁 Estructura de la Base de Datos + +La base de datos SQLite debe contener al menos estas tablas: + +- `addons` - Información de cada add-on (nombre, autor, descripción) +- `versions` - Versiones de cada add-on (número versión, fecha, compatibilidad) +- `categories` - Categorías de add-ons +- `developers` - Información de desarrolladores + +--- + +## 🚀 Después de obtener la DB + +1. **Colocar el archivo aquí:** + ``` + content/db/ca-archive-19030501.sqlite + ``` + +2. **Verificar permisos:** + ```bash + chmod 644 content/db/ca-archive-19030501.sqlite + ``` + +3. **Recargar extensión en Firefox:** + ``` + about:debugging#/runtime/this-firefox + → Click en "Recargar" (ícono ↻) + ``` + +4. **Verificar carga:** + - La extensión mostrará: "Loading database for the first time. Please wait..." + - Primera carga puede tardar 30-60 segundos (carga y guarda en storage) + - Siguientes cargas serán instantáneas (se lee del storage) + +--- + +## 🆘 Contacto + +Si no puedes obtener el archivo de base de datos: + +- **GitHub Issues:** https://github.com/JustOff/ca-archive/issues +- **Autor original:** JustOff +- **Descripción:** Solicitar enlace de descarga para `ca-archive-19030501.sqlite` + +--- + +## 📝 Nota para Desarrolladores + +Si vas a generar tu propia base de datos desde cero, el esquema SQL esperado debe ser compatible con las consultas en: + +- `content/list.js` - Listados de add-ons +- `content/addon.js` - Detalles de add-on individual +- `content/versions.js` - Versiones disponibles +- `content/tcloud.js` - Tag cloud de categorías + +--- + +**Estado actual:** ❌ Archivo faltante (la extensión NO funcionará sin este archivo) +**Acción requerida:** Obtener `ca-archive-19030501.sqlite` de releases o versión legacy diff --git a/content/db/ca-archive-19030501.sqlite b/content/db/ca-archive-19030501.sqlite new file mode 100644 index 0000000..3b9b278 Binary files /dev/null and b/content/db/ca-archive-19030501.sqlite differ diff --git a/content/sql-wasm.js b/content/sql-wasm.js new file mode 100644 index 0000000..e0da60b --- /dev/null +++ b/content/sql-wasm.js @@ -0,0 +1,194 @@ + +// We are modularizing this manually because the current modularize setting in Emscripten has some issues: +// https://github.com/kripken/emscripten/issues/5820 +// In addition, When you use emcc's modularization, it still expects to export a global object called `Module`, +// which is able to be used/called before the WASM is loaded. +// The modularization below exports a promise that loads and resolves to the actual sql.js module. +// That way, this module can't be used before the WASM is finished loading. + +// We are going to define a function that a user will call to start loading initializing our Sql.js library +// However, that function might be called multiple times, and on subsequent calls, we don't actually want it to instantiate a new instance of the Module +// Instead, we want to return the previously loaded module + +// TODO: Make this not declare a global if used in the browser +var initSqlJsPromise = undefined; + +var initSqlJs = function (moduleConfig) { + + if (initSqlJsPromise){ + return initSqlJsPromise; + } + // If we're here, we've never called this function before + initSqlJsPromise = new Promise(function (resolveModule, reject) { + + // We are modularizing this manually because the current modularize setting in Emscripten has some issues: + // https://github.com/kripken/emscripten/issues/5820 + + // The way to affect the loading of emcc compiled modules is to create a variable called `Module` and add + // properties to it, like `preRun`, `postRun`, etc + // We are using that to get notified when the WASM has finished loading. + // Only then will we return our promise + + // If they passed in a moduleConfig object, use that + // Otherwise, initialize Module to the empty object + var Module = typeof moduleConfig !== 'undefined' ? moduleConfig : {}; + + // EMCC only allows for a single onAbort function (not an array of functions) + // So if the user defined their own onAbort function, we remember it and call it + var originalOnAbortFunction = Module['onAbort']; + Module['onAbort'] = function (errorThatCausedAbort) { + reject(new Error(errorThatCausedAbort)); + if (originalOnAbortFunction){ + originalOnAbortFunction(errorThatCausedAbort); + } + }; + + Module['postRun'] = Module['postRun'] || []; + Module['postRun'].push(function () { + // When Emscripted calls postRun, this promise resolves with the built Module + resolveModule(Module); + }); + + // There is a section of code in the emcc-generated code below that looks like this: + // (Note that this is lowercase `module`) + // if (typeof module !== 'undefined') { + // module['exports'] = Module; + // } + // When that runs, it's going to overwrite our own modularization export efforts in shell-post.js! + // The only way to tell emcc not to emit it is to pass the MODULARIZE=1 or MODULARIZE_INSTANCE=1 flags, + // but that carries with it additional unnecessary baggage/bugs we don't want either. + // So, we have three options: + // 1) We undefine `module` + // 2) We remember what `module['exports']` was at the beginning of this function and we restore it later + // 3) We write a script to remove those lines of code as part of the Make process. + // + // Since those are the only lines of code that care about module, we will undefine it. It's the most straightforward + // of the options, and has the side effect of reducing emcc's efforts to modify the module if its output were to change in the future. + // That's a nice side effect since we're handling the modularization efforts ourselves + module = undefined; + + // The emcc-generated code and shell-post.js code goes below, + // meaning that all of it runs inside of this promise. If anything throws an exception, our promise will abort + +var e;e||(e=typeof Module !== 'undefined' ? Module : {});null; +e.onRuntimeInitialized=function(){function a(g,m){switch(typeof m){case "boolean":gc(g,m?1:0);break;case "number":hc(g,m);break;case "string":ic(g,m,-1,-1);break;case "object":if(null===m)kb(g);else if(null!=m.length){var n=aa(m);jc(g,n,m.length,-1);ba(n)}else xa(g,"Wrong API use : tried to return a value of an unknown type ("+m+").",-1);break;default:kb(g)}}function b(g,m){for(var n=[],p=0;p>>0);if(null!=g){var m=this.filename,n="/",p=m;n&&(n="string"==typeof n?n:ea(n),p=m?z(n+"/"+m):n);m=fa(!0, +!0);p=ha(p,(void 0!==m?m:438)&4095|32768,0);if(g){if("string"==typeof g){n=Array(g.length);for(var v=0,y=g.length;v{Ea||(fs=require("fs"),Ea=require("path"))},Ba=function(a,b){Fa();a=Ea.normalize(a);return fs.readFileSync(a,b?void 0:"utf8")},Da=a=>{a=Ba(a,!0);a.buffer||(a=new Uint8Array(a));return a},Ca=(a,b,c)=>{Fa();a=Ea.normalize(a);fs.readFile(a,function(d,f){d?c(d):b(f.buffer)})},1{var b=new XMLHttpRequest;b.open("GET",a,!1);b.send(null);return b.responseText},za&&(Da=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),Ca=(a,b,c)=>{var d=new XMLHttpRequest;d.open("GET",a,!0);d.responseType="arraybuffer"; +d.onload=()=>{200==d.status||0==d.status&&d.response?b(d.response):c()};d.onerror=c;d.send(null)};var Ga=e.print||console.log.bind(console),Ha=e.printErr||console.warn.bind(console);Object.assign(e,va);va=null;e.thisProgram&&(wa=e.thisProgram);var Ia;e.wasmBinary&&(Ia=e.wasmBinary);var noExitRuntime=e.noExitRuntime||!0;"object"!=typeof WebAssembly&&E("no native wasm support detected");var Ja,Ka=!1,La="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0; +function Ma(a,b,c){var d=b+c;for(c=b;a[c]&&!(c>=d);)++c;if(16f?d+=String.fromCharCode(f):(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else d+=String.fromCharCode(f)}return d}function C(a,b){return a?Ma(u,a,b):""} +function t(a,b,c,d){if(!(0=k){var q=a.charCodeAt(++h);k=65536+((k&1023)<<10)|q&1023}if(127>=k){if(c>=d)break;b[c++]=k}else{if(2047>=k){if(c+1>=d)break;b[c++]=192|k>>6}else{if(65535>=k){if(c+2>=d)break;b[c++]=224|k>>12}else{if(c+3>=d)break;b[c++]=240|k>>18;b[c++]=128|k>>12&63}b[c++]=128|k>>6&63}b[c++]=128|k&63}}b[c]=0;return c-f} +function ca(a){for(var b=0,c=0;c=d?b++:2047>=d?b+=2:55296<=d&&57343>=d?(b+=4,++c):b+=3}return b}var Na,r,u,Oa,F,J,Pa,Qa;function Ra(){var a=Ja.buffer;Na=a;e.HEAP8=r=new Int8Array(a);e.HEAP16=Oa=new Int16Array(a);e.HEAP32=F=new Int32Array(a);e.HEAPU8=u=new Uint8Array(a);e.HEAPU16=new Uint16Array(a);e.HEAPU32=J=new Uint32Array(a);e.HEAPF32=Pa=new Float32Array(a);e.HEAPF64=Qa=new Float64Array(a)}var K,Sa=[],Ta=[],Ua=[]; +function Va(){var a=e.preRun.shift();Sa.unshift(a)}var Wa=0,Xa=null,Ya=null;function E(a){if(e.onAbort)e.onAbort(a);a="Aborted("+a+")";Ha(a);Ka=!0;throw new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");}function Za(){return M.startsWith("data:application/octet-stream;base64,")}var M;M="sql-wasm.wasm";if(!Za()){var $a=M;M=e.locateFile?e.locateFile($a,D):D+$a} +function ab(){var a=M;try{if(a==M&&Ia)return new Uint8Array(Ia);if(Da)return Da(a);throw"both async and sync fetching of the wasm failed";}catch(b){E(b)}} +function bb(){if(!Ia&&(ya||za)){if("function"==typeof fetch&&!M.startsWith("file://"))return fetch(M,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+M+"'";return a.arrayBuffer()}).catch(function(){return ab()});if(Ca)return new Promise(function(a,b){Ca(M,function(c){a(new Uint8Array(c))},b)})}return Promise.resolve().then(function(){return ab()})}var N,O;function cb(a){for(;0>0];case "i8":return r[a>>0];case "i16":return Oa[a>>1];case "i32":return F[a>>2];case "i64":return F[a>>2];case "float":return Pa[a>>2];case "double":return Qa[a>>3];case "*":return J[a>>2];default:E("invalid type for getValue: "+b)}return null} +function pa(a){var b="i32";b.endsWith("*")&&(b="*");switch(b){case "i1":r[a>>0]=0;break;case "i8":r[a>>0]=0;break;case "i16":Oa[a>>1]=0;break;case "i32":F[a>>2]=0;break;case "i64":O=[0,(N=0,1<=+Math.abs(N)?0>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];F[a>>2]=O[0];F[a+4>>2]=O[1];break;case "float":Pa[a>>2]=0;break;case "double":Qa[a>>3]=0;break;case "*":J[a>>2]=0;break;default:E("invalid type for setValue: "+b)}} +var db=(a,b)=>{for(var c=0,d=a.length-1;0<=d;d--){var f=a[d];"."===f?a.splice(d,1):".."===f?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c;c--)a.unshift("..");return a},z=a=>{var b="/"===a.charAt(0),c="/"===a.substr(-1);(a=db(a.split("/").filter(d=>!!d),!b).join("/"))||b||(a=".");a&&c&&(a+="/");return(b?"/":"")+a},eb=a=>{var b=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(a).slice(1);a=b[0];b=b[1];if(!a&&!b)return".";b&&(b=b.substr(0,b.length-1));return a+b},fb=a=>{if("/"=== +a)return"/";a=z(a);a=a.replace(/\/$/,"");var b=a.lastIndexOf("/");return-1===b?a:a.substr(b+1)};function gb(){if("object"==typeof crypto&&"function"==typeof crypto.getRandomValues){var a=new Uint8Array(1);return()=>{crypto.getRandomValues(a);return a[0]}}if(Aa)try{var b=require("crypto");return()=>b.randomBytes(1)[0]}catch(c){}return()=>E("randomDevice")} +function hb(){for(var a="",b=!1,c=arguments.length-1;-1<=c&&!b;c--){b=0<=c?arguments[c]:"/";if("string"!=typeof b)throw new TypeError("Arguments to path.resolve must be strings");if(!b)return"";a=b+"/"+a;b="/"===b.charAt(0)}a=db(a.split("/").filter(d=>!!d),!b).join("/");return(b?"/":"")+a||"."}function ma(a,b){var c=Array(ca(a)+1);a=t(a,c,0,c.length);b&&(c.length=a);return c}var ib=[];function jb(a,b){ib[a]={input:[],output:[],Xa:b};lb(a,mb)} +var mb={open:function(a){var b=ib[a.node.rdev];if(!b)throw new P(43);a.tty=b;a.seekable=!1},close:function(a){a.tty.Xa.fsync(a.tty)},fsync:function(a){a.tty.Xa.fsync(a.tty)},read:function(a,b,c,d){if(!a.tty||!a.tty.Xa.tb)throw new P(60);for(var f=0,h=0;h=b||(b=Math.max(b,c*(1048576>c?2:1.125)>>>0),0!=c&&(b=Math.max(b,256)),c=a.Ia,a.Ia=new Uint8Array(b),0=a.node.Ma)return 0;a=Math.min(a.node.Ma-f,d);if(8b)throw new P(28);return b},lb:function(a,b,c){Q.qb(a.node,b+c);a.node.Ma=Math.max(a.node.Ma,b+c)},bb:function(a,b,c,d,f){if(32768!==(a.node.mode&61440))throw new P(43);a=a.node.Ia;if(f&2||a.buffer!==Na){if(0{a=hb("/",a);if(!a)return{path:"",node:null};b=Object.assign({rb:!0,kb:0},b);if(8!!k),!1);for(var c=Ab,d="/", +f=0;f{for(var b;;){if(a===a.parent)return a=a.Ra.ub,b?"/"!==a[a.length-1]?a+"/"+b:a+b:a;b=b?a.name+"/"+b:a.name;a=a.parent}},Fb=(a,b)=>{for(var c=0,d=0;d>>0)%T.length},Gb=a=>{var b= +Fb(a.parent.id,a.name);if(T[b]===a)T[b]=a.Wa;else for(b=T[b];b;){if(b.Wa===a){b.Wa=a.Wa;break}b=b.Wa}},yb=(a,b)=>{var c;if(c=(c=Hb(a,"x"))?c:a.Ga.lookup?0:2)throw new P(c,a);for(c=T[Fb(a.id,b)];c;c=c.Wa){var d=c.name;if(c.parent.id===a.id&&d===b)return c}return a.Ga.lookup(a,b)},wb=(a,b,c,d)=>{a=new Ib(a,b,c,d);b=Fb(a.parent.id,a.name);a.Wa=T[b];return T[b]=a},Jb={r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090},Kb=a=>{var b=["r","w","rw"][a&3];a&512&&(b+="w");return b},Hb=(a,b)=>{if(Db)return 0;if(!b.includes("r")|| +a.mode&292){if(b.includes("w")&&!(a.mode&146)||b.includes("x")&&!(a.mode&73))return 2}else return 2;return 0},Lb=(a,b)=>{try{return yb(a,b),20}catch(c){}return Hb(a,"wx")},Mb=(a,b,c)=>{try{var d=yb(a,b)}catch(f){return f.Ka}if(a=Hb(a,"wx"))return a;if(c){if(16384!==(d.mode&61440))return 54;if(d===d.parent||"/"===ea(d))return 10}else if(16384===(d.mode&61440))return 31;return 0},Nb=(a=0)=>{for(;4096>=a;a++)if(!R[a])return a;throw new P(33);},Pb=(a,b)=>{Ob||(Ob=function(){this.$a={}},Ob.prototype={}, +Object.defineProperties(Ob.prototype,{object:{get:function(){return this.node},set:function(c){this.node=c}},flags:{get:function(){return this.$a.flags},set:function(c){this.$a.flags=c}},position:{get:function(){return this.$a.position},set:function(c){this.$a.position=c}}}));a=Object.assign(new Ob,a);b=Nb(b);a.fd=b;return R[b]=a},vb={open:a=>{a.Ha=Bb[a.node.rdev].Ha;a.Ha.open&&a.Ha.open(a)},Ta:()=>{throw new P(70);}},lb=(a,b)=>{Bb[a]={Ha:b}},Qb=(a,b)=>{var c="/"===b,d=!b;if(c&&Ab)throw new P(10); +if(!c&&!d){var f=U(b,{rb:!1});b=f.path;f=f.node;if(f.Va)throw new P(10);if(16384!==(f.mode&61440))throw new P(54);}b={type:a,Kb:{},ub:b,Eb:[]};a=a.Ra(b);a.Ra=b;b.root=a;c?Ab=a:f&&(f.Va=b,f.Ra&&f.Ra.Eb.push(b))},ha=(a,b,c)=>{var d=U(a,{parent:!0}).node;a=fb(a);if(!a||"."===a||".."===a)throw new P(28);var f=Lb(d,a);if(f)throw new P(f);if(!d.Ga.ab)throw new P(63);return d.Ga.ab(d,a,b,c)},V=(a,b)=>ha(a,(void 0!==b?b:511)&1023|16384,0),Rb=(a,b,c)=>{"undefined"==typeof c&&(c=b,b=438);ha(a,b|8192,c)},Sb= +(a,b)=>{if(!hb(a))throw new P(44);var c=U(b,{parent:!0}).node;if(!c)throw new P(44);b=fb(b);var d=Lb(c,b);if(d)throw new P(d);if(!c.Ga.symlink)throw new P(63);c.Ga.symlink(c,b,a)},Tb=a=>{var b=U(a,{parent:!0}).node;a=fb(a);var c=yb(b,a),d=Mb(b,a,!0);if(d)throw new P(d);if(!b.Ga.rmdir)throw new P(63);if(c.Va)throw new P(10);b.Ga.rmdir(b,a);Gb(c)},ta=a=>{var b=U(a,{parent:!0}).node;if(!b)throw new P(44);a=fb(a);var c=yb(b,a),d=Mb(b,a,!1);if(d)throw new P(d);if(!b.Ga.unlink)throw new P(63);if(c.Va)throw new P(10); +b.Ga.unlink(b,a);Gb(c)},Eb=a=>{a=U(a).node;if(!a)throw new P(44);if(!a.Ga.readlink)throw new P(28);return hb(ea(a.parent),a.Ga.readlink(a))},Ub=(a,b)=>{a=U(a,{Sa:!b}).node;if(!a)throw new P(44);if(!a.Ga.Pa)throw new P(63);return a.Ga.Pa(a)},Vb=a=>Ub(a,!0),ia=(a,b)=>{a="string"==typeof a?U(a,{Sa:!0}).node:a;if(!a.Ga.Oa)throw new P(63);a.Ga.Oa(a,{mode:b&4095|a.mode&-4096,timestamp:Date.now()})},Wb=(a,b)=>{if(0>b)throw new P(28);a="string"==typeof a?U(a,{Sa:!0}).node:a;if(!a.Ga.Oa)throw new P(63);if(16384=== +(a.mode&61440))throw new P(31);if(32768!==(a.mode&61440))throw new P(28);var c=Hb(a,"w");if(c)throw new P(c);a.Ga.Oa(a,{size:b,timestamp:Date.now()})},ja=(a,b,c)=>{if(""===a)throw new P(44);if("string"==typeof b){var d=Jb[b];if("undefined"==typeof d)throw Error("Unknown file open mode: "+b);b=d}c=b&64?("undefined"==typeof c?438:c)&4095|32768:0;if("object"==typeof a)var f=a;else{a=z(a);try{f=U(a,{Sa:!(b&131072)}).node}catch(h){}}d=!1;if(b&64)if(f){if(b&128)throw new P(20);}else f=ha(a,c,0),d=!0;if(!f)throw new P(44); +8192===(f.mode&61440)&&(b&=-513);if(b&65536&&16384!==(f.mode&61440))throw new P(54);if(!d&&(c=f?40960===(f.mode&61440)?32:16384===(f.mode&61440)&&("r"!==Kb(b)||b&512)?31:Hb(f,Kb(b)):44))throw new P(c);b&512&&!d&&Wb(f,0);b&=-131713;f=Pb({node:f,path:ea(f),flags:b,seekable:!0,position:0,Ha:f.Ha,Ib:[],error:!1});f.Ha.open&&f.Ha.open(f);!e.logReadFiles||b&1||(Xb||(Xb={}),a in Xb||(Xb[a]=1));return f},la=a=>{if(null===a.fd)throw new P(8);a.hb&&(a.hb=null);try{a.Ha.close&&a.Ha.close(a)}catch(b){throw b; +}finally{R[a.fd]=null}a.fd=null},Yb=(a,b,c)=>{if(null===a.fd)throw new P(8);if(!a.seekable||!a.Ha.Ta)throw new P(70);if(0!=c&&1!=c&&2!=c)throw new P(28);a.position=a.Ha.Ta(a,b,c);a.Ib=[]},Zb=(a,b,c,d,f)=>{if(0>d||0>f)throw new P(28);if(null===a.fd)throw new P(8);if(1===(a.flags&2097155))throw new P(8);if(16384===(a.node.mode&61440))throw new P(31);if(!a.Ha.read)throw new P(28);var h="undefined"!=typeof f;if(!h)f=a.position;else if(!a.seekable)throw new P(70);b=a.Ha.read(a,b,c,d,f);h||(a.position+= +b);return b},ka=(a,b,c,d,f)=>{if(0>d||0>f)throw new P(28);if(null===a.fd)throw new P(8);if(0===(a.flags&2097155))throw new P(8);if(16384===(a.node.mode&61440))throw new P(31);if(!a.Ha.write)throw new P(28);a.seekable&&a.flags&1024&&Yb(a,0,2);var h="undefined"!=typeof f;if(!h)f=a.position;else if(!a.seekable)throw new P(70);b=a.Ha.write(a,b,c,d,f,void 0);h||(a.position+=b);return b},sa=a=>{var b="binary";if("utf8"!==b&&"binary"!==b)throw Error('Invalid encoding type "'+b+'"');var c;var d=ja(a,d||0); +a=Ub(a).size;var f=new Uint8Array(a);Zb(d,f,0,a,0);"utf8"===b?c=Ma(f,0):"binary"===b&&(c=f);la(d);return c},$b=()=>{P||(P=function(a,b){this.node=b;this.Hb=function(c){this.Ka=c};this.Hb(a);this.message="FS error"},P.prototype=Error(),P.prototype.constructor=P,[44].forEach(a=>{xb[a]=new P(a);xb[a].stack=""}))},ac,fa=(a,b)=>{var c=0;a&&(c|=365);b&&(c|=146);return c},cc=(a,b,c)=>{a=z("/dev/"+a);var d=fa(!!b,!!c);bc||(bc=64);var f=bc++<<8|0;lb(f,{open:h=>{h.seekable=!1},close:()=> +{c&&c.buffer&&c.buffer.length&&c(10)},read:(h,k,q,x)=>{for(var w=0,A=0;A{for(var w=0;w>2]=d.dev;F[c+8>>2]=d.ino;F[c+12>>2]=d.mode;J[c+16>>2]=d.nlink;F[c+20>>2]=d.uid;F[c+24>>2]=d.gid;F[c+28>>2]=d.rdev;O=[d.size>>>0,(N=d.size,1<=+Math.abs(N)?0>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];F[c+40>>2]=O[0];F[c+44>>2]=O[1];F[c+48>>2]=4096;F[c+52>>2]=d.blocks;O=[Math.floor(d.atime.getTime()/1E3)>>>0,(N=Math.floor(d.atime.getTime()/ +1E3),1<=+Math.abs(N)?0>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];F[c+56>>2]=O[0];F[c+60>>2]=O[1];J[c+64>>2]=0;O=[Math.floor(d.mtime.getTime()/1E3)>>>0,(N=Math.floor(d.mtime.getTime()/1E3),1<=+Math.abs(N)?0>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];F[c+72>>2]=O[0];F[c+76>>2]=O[1];J[c+80>>2]=0;O=[Math.floor(d.ctime.getTime()/1E3)>>>0,(N=Math.floor(d.ctime.getTime()/1E3),1<=+Math.abs(N)? +0>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];F[c+88>>2]=O[0];F[c+92>>2]=O[1];J[c+96>>2]=0;O=[d.ino>>>0,(N=d.ino,1<=+Math.abs(N)?0>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];F[c+104>>2]=O[0];F[c+108>>2]=O[1];return 0}var fc=void 0;function Hc(){fc+=4;return F[fc-4>>2]}function X(a){a=R[a];if(!a)throw new P(8);return a}function Jc(a){return J[a>>2]+4294967296*F[a+4>>2]} +function Kc(a){var b=ca(a)+1,c=da(b);c&&t(a,r,c,b);return c}function Lc(a,b,c){function d(x){return(x=x.toTimeString().match(/\(([A-Za-z ]+)\)$/))?x[1]:"GMT"}var f=(new Date).getFullYear(),h=new Date(f,0,1),k=new Date(f,6,1);f=h.getTimezoneOffset();var q=k.getTimezoneOffset();F[a>>2]=60*Math.max(f,q);F[b>>2]=Number(f!=q);a=d(h);b=d(k);a=Kc(a);b=Kc(b);q>2]=a,J[c+4>>2]=b):(J[c>>2]=b,J[c+4>>2]=a)}function Mc(a,b,c){Mc.Bb||(Mc.Bb=!0,Lc(a,b,c))}var Nc; +Nc=Aa?()=>{var a=process.hrtime();return 1E3*a[0]+a[1]/1E6}:()=>performance.now();var Oc={};function Pc(){if(!Qc){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:wa||"./this.program"},b;for(b in Oc)void 0===Oc[b]?delete a[b]:a[b]=Oc[b];var c=[];for(b in a)c.push(b+"="+a[b]);Qc=c}return Qc}var Qc,Y=void 0,Rc=[]; +function ua(a,b){if(!Y){Y=new WeakMap;var c=K.length;if(Y)for(var d=0;d<0+c;d++){var f=K.get(d);f&&Y.set(f,d)}}if(Y.has(a))return Y.get(a);if(Rc.length)c=Rc.pop();else{try{K.grow(1)}catch(q){if(!(q instanceof RangeError))throw q;throw"Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.";}c=K.length-1}try{K.set(c,a)}catch(q){if(!(q instanceof TypeError))throw q;if("function"==typeof WebAssembly.Function){d=WebAssembly.Function;f={i:"i32",j:"i64",f:"f32",d:"f64",p:"i32"};for(var h={parameters:[],results:"v"== +b[0]?[]:[f[b[0]]]},k=1;kk?d.push(k):d.push(k%128|128,k>>7);for(k=0;kf?b.push(f):b.push(f%128|128,f>>7);b.push.apply(b,d);b.push(2,7,1,1,101,1,102,0,0,7,5,1,1,102,0,0);b=new WebAssembly.Module(new Uint8Array(b));b=(new WebAssembly.Instance(b,{e:{f:a}})).exports.f}K.set(c, +b)}Y.set(a,c);return c}function ra(a){Y.delete(K.get(a));Rc.push(a)}var Sc=0,Tc=1;function aa(a){var b=Sc==Tc?B(a.length):da(a.length);a.subarray||a.slice||(a=new Uint8Array(a));u.set(a,b);return b} +function Uc(a,b,c,d){var f={string:w=>{var A=0;if(null!==w&&void 0!==w&&0!==w){var S=(w.length<<2)+1;A=B(S);t(w,u,A,S)}return A},array:w=>{var A=B(w.length);r.set(w,A);return A}};a=e["_"+a];var h=[],k=0;if(d)for(var q=0;q{V("/dev");lb(259,{read:()=>0,write:(b,c,d,f)=>f});Rb("/dev/null",259);jb(1280,tb);jb(1536,ub);Rb("/dev/tty",1280);Rb("/dev/tty1",1536);var a=gb();cc("random",a);cc("urandom",a);V("/dev/shm");V("/dev/shm/tmp")})();(()=>{V("/proc");var a=V("/proc/self");V("/proc/self/fd");Qb({Ra:()=>{var b=wb(a,"fd",16895,73);b.Ga={lookup:(c,d)=>{var f=R[+d];if(!f)throw new P(8);c={parent:null,Ra:{ub:"fake"},Ga:{readlink:()=>f.path}};return c.parent=c}};return b}},"/proc/self/fd")})(); +var Wc={a:function(a,b,c,d){E("Assertion failed: "+C(a)+", at: "+[b?C(b):"unknown filename",c,d?C(d):"unknown function"])},h:function(a,b){try{return a=C(a),ia(a,b),0}catch(c){if("undefined"==typeof W||!(c instanceof P))throw c;return-c.Ka}},H:function(a,b,c){try{b=C(b);b=dc(a,b);if(c&-8)return-28;var d=U(b,{Sa:!0}).node;if(!d)return-44;a="";c&4&&(a+="r");c&2&&(a+="w");c&1&&(a+="x");return a&&Hb(d,a)?-2:0}catch(f){if("undefined"==typeof W||!(f instanceof P))throw f;return-f.Ka}},i:function(a,b){try{var c= +R[a];if(!c)throw new P(8);ia(c.node,b);return 0}catch(d){if("undefined"==typeof W||!(d instanceof P))throw d;return-d.Ka}},g:function(a){try{var b=R[a];if(!b)throw new P(8);var c=b.node;var d="string"==typeof c?U(c,{Sa:!0}).node:c;if(!d.Ga.Oa)throw new P(63);d.Ga.Oa(d,{timestamp:Date.now()});return 0}catch(f){if("undefined"==typeof W||!(f instanceof P))throw f;return-f.Ka}},b:function(a,b,c){fc=c;try{var d=X(a);switch(b){case 0:var f=Hc();return 0>f?-28:Pb(d,f).fd;case 1:case 2:return 0;case 3:return d.flags; +case 4:return f=Hc(),d.flags|=f,0;case 5:return f=Hc(),Oa[f+0>>1]=2,0;case 6:case 7:return 0;case 16:case 8:return-28;case 9:return F[Vc()>>2]=28,-1;default:return-28}}catch(h){if("undefined"==typeof W||!(h instanceof P))throw h;return-h.Ka}},G:function(a,b){try{var c=X(a);return ec(Ub,c.path,b)}catch(d){if("undefined"==typeof W||!(d instanceof P))throw d;return-d.Ka}},l:function(a,b,c){try{b=c+2097152>>>0<4194305-!!b?(b>>>0)+4294967296*c:NaN;if(isNaN(b))return-61;var d=R[a];if(!d)throw new P(8); +if(0===(d.flags&2097155))throw new P(28);Wb(d.node,b);return 0}catch(f){if("undefined"==typeof W||!(f instanceof P))throw f;return-f.Ka}},B:function(a,b){try{if(0===b)return-28;var c=ca("/")+1;if(b=d)return-28;var f=Eb(b),h=Math.min(d, +ca(f)),k=r[c+h];t(f,u,c,d+1);r[c+h]=k;return h}catch(q){if("undefined"==typeof W||!(q instanceof P))throw q;return-q.Ka}},s:function(a){try{return a=C(a),Tb(a),0}catch(b){if("undefined"==typeof W||!(b instanceof P))throw b;return-b.Ka}},F:function(a,b){try{return a=C(a),ec(Ub,a,b)}catch(c){if("undefined"==typeof W||!(c instanceof P))throw c;return-c.Ka}},p:function(a,b,c){try{return b=C(b),b=dc(a,b),0===c?ta(b):512===c?Tb(b):E("Invalid flags passed to unlinkat"),0}catch(d){if("undefined"==typeof W|| +!(d instanceof P))throw d;return-d.Ka}},o:function(a,b,c){try{b=C(b);b=dc(a,b,!0);if(c){var d=Jc(c),f=F[c+8>>2];h=1E3*d+f/1E6;c+=16;d=Jc(c);f=F[c+8>>2];k=1E3*d+f/1E6}else var h=Date.now(),k=h;a=h;var q=U(b,{Sa:!0}).node;q.Ga.Oa(q,{timestamp:Math.max(a,k)});return 0}catch(x){if("undefined"==typeof W||!(x instanceof P))throw x;return-x.Ka}},e:function(){return Date.now()},j:function(a,b){a=new Date(1E3*Jc(a));F[b>>2]=a.getSeconds();F[b+4>>2]=a.getMinutes();F[b+8>>2]=a.getHours();F[b+12>>2]=a.getDate(); +F[b+16>>2]=a.getMonth();F[b+20>>2]=a.getFullYear()-1900;F[b+24>>2]=a.getDay();var c=new Date(a.getFullYear(),0,1);F[b+28>>2]=(a.getTime()-c.getTime())/864E5|0;F[b+36>>2]=-(60*a.getTimezoneOffset());var d=(new Date(a.getFullYear(),6,1)).getTimezoneOffset();c=c.getTimezoneOffset();F[b+32>>2]=(d!=c&&a.getTimezoneOffset()==Math.min(c,d))|0},w:function(a,b,c,d,f,h){try{var k=X(d);if(0!==(b&2)&&0===(c&2)&&2!==(k.flags&2097155))throw new P(2);if(1===(k.flags&2097155))throw new P(2);if(!k.Ha.bb)throw new P(43); +var q=k.Ha.bb(k,a,f,b,c);var x=q.Fb;F[h>>2]=q.vb;return x}catch(w){if("undefined"==typeof W||!(w instanceof P))throw w;return-w.Ka}},x:function(a,b,c,d,f,h){try{var k=X(f);if(c&2){var q=u.slice(a,a+b);k&&k.Ha.cb&&k.Ha.cb(k,q,h,b,d)}}catch(x){if("undefined"==typeof W||!(x instanceof P))throw x;return-x.Ka}},n:Mc,q:function(){return 2147483648},d:Nc,c:function(a){var b=u.length;a>>>=0;if(2147483648=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);var f=Math;d=Math.max(a, +d);f=f.min.call(f,2147483648,d+(65536-d%65536)%65536);a:{try{Ja.grow(f-Na.byteLength+65535>>>16);Ra();var h=1;break a}catch(k){}h=void 0}if(h)return!0}return!1},z:function(a,b){var c=0;Pc().forEach(function(d,f){var h=b+c;f=J[a+4*f>>2]=h;for(h=0;h>0]=d.charCodeAt(h);r[f>>0]=0;c+=d.length+1});return 0},A:function(a,b){var c=Pc();J[a>>2]=c.length;var d=0;c.forEach(function(f){d+=f.length+1});J[b>>2]=d;return 0},f:function(a){try{var b=X(a);la(b);return 0}catch(c){if("undefined"== +typeof W||!(c instanceof P))throw c;return c.Ka}},m:function(a,b){try{var c=X(a);r[b>>0]=c.tty?2:16384===(c.mode&61440)?3:40960===(c.mode&61440)?7:4;return 0}catch(d){if("undefined"==typeof W||!(d instanceof P))throw d;return d.Ka}},u:function(a,b,c,d){try{a:{var f=X(a);a=b;for(var h=b=0;h>2],q=J[a+4>>2];a+=8;var x=Zb(f,r,k,q);if(0>x){var w=-1;break a}b+=x;if(x>2]=w;return 0}catch(A){if("undefined"==typeof W||!(A instanceof P))throw A;return A.Ka}},k:function(a, +b,c,d,f){try{b=c+2097152>>>0<4194305-!!b?(b>>>0)+4294967296*c:NaN;if(isNaN(b))return 61;var h=X(a);Yb(h,b,d);O=[h.position>>>0,(N=h.position,1<=+Math.abs(N)?0>>0:~~+Math.ceil((N-+(~~N>>>0))/4294967296)>>>0:0)];F[f>>2]=O[0];F[f+4>>2]=O[1];h.hb&&0===b&&0===d&&(h.hb=null);return 0}catch(k){if("undefined"==typeof W||!(k instanceof P))throw k;return k.Ka}},C:function(a){try{var b=X(a);return b.Ha&&b.Ha.fsync?b.Ha.fsync(b):0}catch(c){if("undefined"== +typeof W||!(c instanceof P))throw c;return c.Ka}},r:function(a,b,c,d){try{a:{var f=X(a);a=b;for(var h=b=0;h>2],q=J[a+4>>2];a+=8;var x=ka(f,r,k,q);if(0>x){var w=-1;break a}b+=x}w=b}J[d>>2]=w;return 0}catch(A){if("undefined"==typeof W||!(A instanceof P))throw A;return A.Ka}}}; +(function(){function a(f){e.asm=f.exports;Ja=e.asm.I;Ra();K=e.asm.Aa;Ta.unshift(e.asm.J);Wa--;e.monitorRunDependencies&&e.monitorRunDependencies(Wa);0==Wa&&(null!==Xa&&(clearInterval(Xa),Xa=null),Ya&&(f=Ya,Ya=null,f()))}function b(f){a(f.instance)}function c(f){return bb().then(function(h){return WebAssembly.instantiate(h,d)}).then(function(h){return h}).then(f,function(h){Ha("failed to asynchronously prepare wasm: "+h);E(h)})}var d={a:Wc};Wa++;e.monitorRunDependencies&&e.monitorRunDependencies(Wa); +if(e.instantiateWasm)try{return e.instantiateWasm(d,a)}catch(f){return Ha("Module.instantiateWasm callback failed with error: "+f),!1}(function(){return Ia||"function"!=typeof WebAssembly.instantiateStreaming||Za()||M.startsWith("file://")||Aa||"function"!=typeof fetch?c(b):fetch(M,{credentials:"same-origin"}).then(function(f){return WebAssembly.instantiateStreaming(f,d).then(b,function(h){Ha("wasm streaming compile failed: "+h);Ha("falling back to ArrayBuffer instantiation");return c(b)})})})(); +return{}})();e.___wasm_call_ctors=function(){return(e.___wasm_call_ctors=e.asm.J).apply(null,arguments)};e._sqlite3_free=function(){return(e._sqlite3_free=e.asm.K).apply(null,arguments)};e._sqlite3_value_double=function(){return(e._sqlite3_value_double=e.asm.L).apply(null,arguments)};e._sqlite3_value_text=function(){return(e._sqlite3_value_text=e.asm.M).apply(null,arguments)};var Vc=e.___errno_location=function(){return(Vc=e.___errno_location=e.asm.N).apply(null,arguments)}; +e._sqlite3_prepare_v2=function(){return(e._sqlite3_prepare_v2=e.asm.O).apply(null,arguments)};e._sqlite3_step=function(){return(e._sqlite3_step=e.asm.P).apply(null,arguments)};e._sqlite3_finalize=function(){return(e._sqlite3_finalize=e.asm.Q).apply(null,arguments)};e._sqlite3_reset=function(){return(e._sqlite3_reset=e.asm.R).apply(null,arguments)};e._sqlite3_value_int=function(){return(e._sqlite3_value_int=e.asm.S).apply(null,arguments)}; +e._sqlite3_clear_bindings=function(){return(e._sqlite3_clear_bindings=e.asm.T).apply(null,arguments)};e._sqlite3_value_blob=function(){return(e._sqlite3_value_blob=e.asm.U).apply(null,arguments)};e._sqlite3_value_bytes=function(){return(e._sqlite3_value_bytes=e.asm.V).apply(null,arguments)};e._sqlite3_value_type=function(){return(e._sqlite3_value_type=e.asm.W).apply(null,arguments)};e._sqlite3_result_blob=function(){return(e._sqlite3_result_blob=e.asm.X).apply(null,arguments)}; +e._sqlite3_result_double=function(){return(e._sqlite3_result_double=e.asm.Y).apply(null,arguments)};e._sqlite3_result_error=function(){return(e._sqlite3_result_error=e.asm.Z).apply(null,arguments)};e._sqlite3_result_int=function(){return(e._sqlite3_result_int=e.asm._).apply(null,arguments)};e._sqlite3_result_int64=function(){return(e._sqlite3_result_int64=e.asm.$).apply(null,arguments)};e._sqlite3_result_null=function(){return(e._sqlite3_result_null=e.asm.aa).apply(null,arguments)}; +e._sqlite3_result_text=function(){return(e._sqlite3_result_text=e.asm.ba).apply(null,arguments)};e._sqlite3_sql=function(){return(e._sqlite3_sql=e.asm.ca).apply(null,arguments)};e._sqlite3_aggregate_context=function(){return(e._sqlite3_aggregate_context=e.asm.da).apply(null,arguments)};e._sqlite3_column_count=function(){return(e._sqlite3_column_count=e.asm.ea).apply(null,arguments)};e._sqlite3_data_count=function(){return(e._sqlite3_data_count=e.asm.fa).apply(null,arguments)}; +e._sqlite3_column_blob=function(){return(e._sqlite3_column_blob=e.asm.ga).apply(null,arguments)};e._sqlite3_column_bytes=function(){return(e._sqlite3_column_bytes=e.asm.ha).apply(null,arguments)};e._sqlite3_column_double=function(){return(e._sqlite3_column_double=e.asm.ia).apply(null,arguments)};e._sqlite3_column_text=function(){return(e._sqlite3_column_text=e.asm.ja).apply(null,arguments)};e._sqlite3_column_type=function(){return(e._sqlite3_column_type=e.asm.ka).apply(null,arguments)}; +e._sqlite3_column_name=function(){return(e._sqlite3_column_name=e.asm.la).apply(null,arguments)};e._sqlite3_bind_blob=function(){return(e._sqlite3_bind_blob=e.asm.ma).apply(null,arguments)};e._sqlite3_bind_double=function(){return(e._sqlite3_bind_double=e.asm.na).apply(null,arguments)};e._sqlite3_bind_int=function(){return(e._sqlite3_bind_int=e.asm.oa).apply(null,arguments)};e._sqlite3_bind_text=function(){return(e._sqlite3_bind_text=e.asm.pa).apply(null,arguments)}; +e._sqlite3_bind_parameter_index=function(){return(e._sqlite3_bind_parameter_index=e.asm.qa).apply(null,arguments)};e._sqlite3_normalized_sql=function(){return(e._sqlite3_normalized_sql=e.asm.ra).apply(null,arguments)};e._sqlite3_errmsg=function(){return(e._sqlite3_errmsg=e.asm.sa).apply(null,arguments)};e._sqlite3_exec=function(){return(e._sqlite3_exec=e.asm.ta).apply(null,arguments)};e._sqlite3_changes=function(){return(e._sqlite3_changes=e.asm.ua).apply(null,arguments)}; +e._sqlite3_close_v2=function(){return(e._sqlite3_close_v2=e.asm.va).apply(null,arguments)};e._sqlite3_create_function_v2=function(){return(e._sqlite3_create_function_v2=e.asm.wa).apply(null,arguments)};e._sqlite3_open=function(){return(e._sqlite3_open=e.asm.xa).apply(null,arguments)};var da=e._malloc=function(){return(da=e._malloc=e.asm.ya).apply(null,arguments)},ba=e._free=function(){return(ba=e._free=e.asm.za).apply(null,arguments)}; +e._RegisterExtensionFunctions=function(){return(e._RegisterExtensionFunctions=e.asm.Ba).apply(null,arguments)};var zb=e._emscripten_builtin_memalign=function(){return(zb=e._emscripten_builtin_memalign=e.asm.Ca).apply(null,arguments)},oa=e.stackSave=function(){return(oa=e.stackSave=e.asm.Da).apply(null,arguments)},qa=e.stackRestore=function(){return(qa=e.stackRestore=e.asm.Ea).apply(null,arguments)},B=e.stackAlloc=function(){return(B=e.stackAlloc=e.asm.Fa).apply(null,arguments)};e.UTF8ToString=C; +e.stackAlloc=B;e.stackSave=oa;e.stackRestore=qa;e.cwrap=function(a,b,c,d){c=c||[];var f=c.every(h=>"number"===h||"boolean"===h);return"string"!==b&&f&&!d?e["_"+a]:function(){return Uc(a,b,c,arguments)}};var Xc;Ya=function Yc(){Xc||Zc();Xc||(Ya=Yc)}; +function Zc(){function a(){if(!Xc&&(Xc=!0,e.calledRun=!0,!Ka)){e.noFSInit||ac||(ac=!0,$b(),e.stdin=e.stdin,e.stdout=e.stdout,e.stderr=e.stderr,e.stdin?cc("stdin",e.stdin):Sb("/dev/tty","/dev/stdin"),e.stdout?cc("stdout",null,e.stdout):Sb("/dev/tty","/dev/stdout"),e.stderr?cc("stderr",null,e.stderr):Sb("/dev/tty1","/dev/stderr"),ja("/dev/stdin",0),ja("/dev/stdout",1),ja("/dev/stderr",1));Db=!1;cb(Ta);if(e.onRuntimeInitialized)e.onRuntimeInitialized();if(e.postRun)for("function"==typeof e.postRun&& +(e.postRun=[e.postRun]);e.postRun.length;){var b=e.postRun.shift();Ua.unshift(b)}cb(Ua)}}if(!(0/dev/null | \ + openssl dgst -sha256 -binary | \ + head -c 16 | \ + od -An -tx1 | \ + tr -d ' \n' | \ + tr '0-9a-f' 'a-p' > "$KEYS_DIR/chrome-extension-id.txt" + + CHROME_ID=$(cat "$KEYS_DIR/chrome-extension-id.txt") + echo -e "${GREEN}✓${NC} Extension ID: $CHROME_ID" + echo " (Guardado en: $KEYS_DIR/chrome-extension-id.txt)" +fi + +# Generar hash de la clave para verificación +if [ -f "$CHROME_KEY" ]; then + KEY_HASH=$(sha256sum "$CHROME_KEY" | cut -d' ' -f1) + echo "$KEY_HASH" > "$KEYS_DIR/chrome-key-hash.txt" + echo -e "${GREEN}✓${NC} Hash de verificación guardado" +fi + +# Información sobre credenciales de Firefox +echo "" +echo "==================================================" +echo " Credenciales de Firefox (AMO)" +echo "==================================================" +echo "" +echo "Para firmar extensiones de Firefox necesitas credenciales de AMO:" +echo "" +echo "1. Ve a: https://addons.mozilla.org/developers/addon/api/key/" +echo "2. Genera un nuevo par de credenciales (JWT issuer y secret)" +echo "3. Guarda las credenciales en: $FIREFOX_JWT" +echo "" + +if [ ! -f "$FIREFOX_JWT" ]; then + echo "Plantilla de credenciales de Firefox:" + cat > "$FIREFOX_JWT" << 'EOF' +{ + "web-ext": { + "sign": { + "apiKey": "user:12345678:123", + "apiSecret": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "channel": "unlisted" + } + } +} +EOF + echo -e "${YELLOW}⚠${NC} Archivo de plantilla creado: $FIREFOX_JWT" + echo -e "${YELLOW}⚠${NC} EDITA este archivo con tus credenciales reales de AMO" + echo "" + echo "Nota: El ID de la extensión se lee automáticamente de manifest.json" +else + echo -e "${GREEN}✓${NC} Archivo de credenciales encontrado: $FIREFOX_JWT" +fi + +# Crear archivo .gitignore para el directorio de claves +cat > "$KEYS_DIR/.gitignore" << 'EOF' +# Ignorar todas las claves privadas +*.pem +*.key +*.p12 +*.pfx +*-credentials.json +*-hash.txt + +# Permitir README +!README.md +EOF + +echo -e "${GREEN}✓${NC} Archivo .gitignore creado en $KEYS_DIR/" + +# Crear README en el directorio de claves +cat > "$KEYS_DIR/README.md" << 'EOF' +# Claves Privadas de Firma + +Este directorio contiene las claves privadas para firmar las extensiones. + +## ⚠️ IMPORTANTE: Seguridad + +- **NUNCA** subas estas claves a repositorios públicos +- **NUNCA** compartas estas claves con nadie +- Haz backup de estas claves en un lugar seguro +- Si pierdes las claves, perderás el ID de la extensión de Chrome + +## Archivos + +### Chrome +- `chrome-extension.pem` - Clave privada RSA 2048-bit +- `chrome-extension-id.txt` - ID de la extensión derivado de la clave +- `chrome-key-hash.txt` - Hash SHA256 para verificación + +### Firefox +- `firefox-amo-credentials.json` - Credenciales de API de AMO (addons.mozilla.org) + +## Uso + +Las claves se usan automáticamente por los scripts de build: +```bash +./build.sh --sign # Firma ambas versiones +./build.sh --sign-chrome # Solo Chrome +./build.sh --sign-firefox # Solo Firefox +``` + +## Regenerar claves + +```bash +cd scripts +./generate-keys.sh +``` + +**Nota:** Regenerar la clave de Chrome cambiará el ID de la extensión. + +## Obtener credenciales de Firefox + +1. Inicia sesión en https://addons.mozilla.org +2. Ve a: https://addons.mozilla.org/developers/addon/api/key/ +3. Genera credenciales JWT +4. Copia API Key (issuer) y API Secret +5. Actualiza `firefox-amo-credentials.json` +EOF + +echo -e "${GREEN}✓${NC} README creado en $KEYS_DIR/" + +# Proteger permisos de archivos sensibles +chmod 600 "$CHROME_KEY" 2>/dev/null || true +chmod 600 "$FIREFOX_JWT" 2>/dev/null || true +chmod 700 "$KEYS_DIR" + +echo "" +echo "==================================================" +echo " ✨ Claves generadas exitosamente" +echo "==================================================" +echo "" +echo "Archivos creados:" +echo " - $CHROME_KEY (clave privada Chrome)" +echo " - $KEYS_DIR/chrome-extension-id.txt (ID de extensión)" +echo " - $KEYS_DIR/chrome-key-hash.txt (hash de verificación)" +echo " - $FIREFOX_JWT (plantilla de credenciales)" +echo "" +echo "Próximos pasos:" +echo "" +echo " 1. BACKUP: Guarda $CHROME_KEY en un lugar seguro" +echo "" +echo " 2. Configura Firefox AMO:" +echo " - Edita: $FIREFOX_JWT" +echo " - Agrega tus credenciales de AMO" +echo "" +echo " 3. Firma extensiones:" +echo " ./build.sh --sign" +echo "" +echo "==================================================" +echo "" diff --git a/scripts/help.sh b/scripts/help.sh new file mode 100755 index 0000000..76a0beb --- /dev/null +++ b/scripts/help.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +# Script de ayuda para Classic Add-ons Archive +# Muestra comandos disponibles + +cat << 'EOF' +╔════════════════════════════════════════════════════════════════╗ +║ Classic Add-ons Archive - Comandos Disponibles ║ +╚════════════════════════════════════════════════════════════════╝ + +📦 CONSTRUCCIÓN (BUILD) +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ./build.sh Construir paquetes sin firmar + ./build.sh --list Construir y listar contenido + npm run build Construir con npm + +🔐 FIRMA (SIGNING) +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ./scripts/generate-keys.sh Generar claves de firma + ./build.sh --sign Firmar ambos navegadores + ./build.sh --sign-firefox Firmar solo Firefox + ./build.sh --sign-chrome Firmar solo Chrome + + npm run keys:generate Generar claves (npm) + npm run build:sign Construir y firmar (npm) + npm run build:sign-firefox Construir y firmar Firefox + npm run build:sign-chrome Construir y firmar Chrome + +🧪 DESARROLLO +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + npm run lint Validar manifest y código + npm run start:firefox Probar en Firefox + npm run start:chrome Probar en Chrome + npm run clean Limpiar archivos de build + +📚 DOCUMENTACIÓN +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + INSTALL.md Guía de instalación + SIGNING.md Guía de firma completa + MIGRATION.md Migración desde v2 + README-v3.md Documentación completa + TROUBLESHOOTING.md Solución de problemas + +📁 ARCHIVOS GENERADOS +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + dist/ Paquetes construidos + ├── ca-archive-*.xpi Firefox sin firmar + ├── ca-archive-*-chrome.zip Chrome sin firmar + ├── ca-archive-*-chrome.crx Chrome firmado + └── *-sign.log Logs de firma + + private-keys/ Claves privadas (NO SUBIR A GIT) + ├── chrome-extension.pem Clave privada Chrome + ├── chrome-extension-id.txt Extension ID de Chrome + └── firefox-amo-*.json Credenciales AMO + +🔍 EJEMPLOS DE USO +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + # Desarrollo rápido + npm install && npm run start:firefox + + # Primera vez con firma + npm install # Instala web-ext localmente + ./scripts/generate-keys.sh + # Editar: private-keys/firefox-amo-credentials.json + ./build.sh --sign + + # Build de producción + npm run lint && ./build.sh --sign + + # Limpiar y reconstruir + npm run clean && ./build.sh + +⚠️ SEGURIDAD +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ¡NUNCA SUBAS LAS CLAVES PRIVADAS A GIT! + + La carpeta private-keys/ está en .gitignore + Verifica con: git status private-keys/ + + Haz backup seguro de: + - private-keys/chrome-extension.pem + - private-keys/firefox-amo-credentials.json + +📖 MÁS AYUDA +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + GitHub: https://github.com/JustOff/ca-archive + Issues: https://github.com/JustOff/ca-archive/issues + +EOF