# Analisi Estensione Chrome — OGame Spy Auto-Importer > **Versione:** 2.1.0 > **Manifest:** MV3 > **File:** `/opt/ogame_db/ogame-importer/` --- ## 0. Changelog v2.1.0 — fix auto-import L'auto-import **non funzionava** (bisognava sempre premere il bottone). Cause: 1. **`content.js` leggeva la clipboard con `navigator.clipboard.readText()` da content script.** In un content script valgono i permessi *della pagina*, non `clipboardRead` dell'estensione: sulla pagina OGame la lettura veniva rifiutata e l'errore era inghiottito in silenzio (`catch { return; }`). Il popup funzionava perché è una pagina dell'estensione. 2. **Filtro troppo stretto:** `isRelevantPage()` richiedeva `component=messages|spy` nell'URL; il content script girava solo nel frame principale (no `all_frames`). 3. **Retry inutile:** il polling usciva subito se il testo non era cambiato, quindi il "secondo tentativo" non avveniva mai. **Soluzione:** non si legge più la clipboard. Il testo viene intercettato *quando OGame lo copia*: - `inject.js` (nuovo, **MAIN world**, `document_start`) aggancia `navigator.clipboard.writeText` e lo notifica al content script via `postMessage` → copre il pulsante **API/Copia** di OGame. - `content.js` ascolta l'evento DOM **`copy`** → copre Ctrl+C, tasto destro e `document.execCommand('copy')`. - `manifest.json`: `content.js` + `inject.js` con `all_frames: true`. Nessun polling, nessun permesso clipboard richiesto a runtime, funziona anche su Brave. ### v2.1.1 — fix "Server non raggiungibile" sull'auto-import Con il service worker MV3 **freddo**, l'import automatico falliva spesso con "Server non raggiungibile", mentre il bottone manuale funzionava sempre. Causa: il listener `onMessage` è registrato prima che `init()` finisca di leggere `chrome.storage`, quindi `settings` era ancora `{host:'localhost', port:8899}` e la fetch andava all'host sbagliato. Il popup, aprendosi, manda un `getState` che dà tempo al worker di inizializzarsi: per questo il manuale era affidabile. Fix: `await settingsReady` all'inizio di ogni handler; l'errore di rete ora ritorna `status:'unreachable'` (ritentabile dal retry già presente in `content.js`). --- ## 1. Architettura L'estensione segue il pattern standard MV3 con 3 componenti: ``` ┌─────────────────────────────────────────────────────────┐ │ Popup (popup.html + popup.js) │ │ ── UI impostazioni + import manuale fallback │ └────────────────────┬────────────────────────────────────┘ │ chrome.runtime.sendMessage ▼ ┌─────────────────────────────────────────────────────────┐ │ Background (background.js) — Service Worker │ │ ── Logica import, deduplicazione, notifiche, badge │ └────────────────────┬────────────────────────────────────┘ │ POST /api/reports → Backend Flask ▼ ┌─────────────────────────────────────────────────────────┐ │ Content Script (content.js) — isolated world │ │ ── Evento DOM 'copy' + messaggi da inject.js │ └────────────────────┬────────────────────────────────────┘ │ ▲ window.postMessage │ │ ┌────────────────────▼────────────────────────────────────┐ │ inject.js (MAIN world) │ │ ── Hook di navigator.clipboard.writeText │ └─────────────────────────────────────────────────────────┘ ``` ### Relazioni tra file | File | Dipende da | Parla con | |---|---|---| | `manifest.json` | — | definisce tutto (content scripts: `inject.js` MAIN world + `content.js`) | | `inject.js` | — | `window.postMessage` (a `content.js`) | | `background.js` | `chrome.storage` | `chrome.runtime.sendMessage` (da content/popup), `fetch` (backend) | | `content.js` | `chrome.storage` | evento `copy` (DOM), `window` message (da `inject.js`), `chrome.runtime.sendMessage` (a background) | | `popup.html` | `popup.js` | — | | `popup.js` | `chrome.storage`, `chrome.runtime` | `chrome.runtime.sendMessage` (a background), `fetch` (backend) | --- ## 2. Flusso dati dettagliato ### 2.1 Auto-import (flusso principale) ``` 1. Utente su pagina OGame → apre rapporto di spionaggio 2. Clicca "API" → writeText(token) (oppure Ctrl+C → evento 'copy') 3a. inject.js (MAIN world): writeText agganciato → window.postMessage(token) 3b. content.js: listener 'copy' → e.clipboardData.getData('text') 4. content.js estrae il token con regex: /(cr|sr|rr|mr)-[a-z]{2}-\d{1,3}-[0-9a-f]{40}/i 5. Invia a background: { type: 'capture', token: 'sr-ar-170-...' } 6. background.js: a. Controlla SEEN (token già importato?) → se sì, ritorna 'known' b. Controlla debounce (stesso token negli ultimi 6s?) → se sì, ritorna 'dup' c. Controlla se importing già in corso → ritorna 'busy' d. POST http://:/api/reports { token } e. Se OK + created → salva in SEEN, badge ✓ verde, notifica f. Se OK + !created → badge ✓ verde, "già presente" (no notifica) g. Se 429 → badge ! arancione, notifica "proxy occupato" h. Se errore → badge ✗ rosso, notifica errore ``` ### 2.2 Import manuale (fallback) ``` 1. Utente apre popup → incolla token nel textarea 2. Clicca "Importa ora" 3. popup.js valida formato token 4. Invia a background: { type: 'importManual', token: '...' } 5. background.js: stessa logica di cui sopra, ma: - NON salta per SEEN (importa sempre, anche se già visto) - Mostra errore nel popup invece di notifica ``` ### 2.3 Verifica connessione ``` 1. Utente clicca "Verifica connessione" nel popup 2. popup.js: GET http://:/api/bootstrap 3. Mostra stato: nome piattaforma, universo, galassie ``` --- ## 3. Stato e memoria ### chrome.storage.local — chiavi usate | Chiave | Tipo | Contenuto | |---|---|---| | `settings` | object | `{ host, port, autoImport, notifications }` | | `lastImport` | object | Ultimo esito import (badge, messaggio, timestamp) | | `seenTokens` | object | `{ "": , ... }` — token già importati | ### Struttura `lastImport` ```js // Successo: { ok: true, badge: { t: '✓', c: '#4caf50' }, message: '...', coords: '...', created: true/false, token: '...', at: } // Errore: { ok: false, badge: { t: '✗', c: '#f44336' }, error: '...', token: '...', at: } ``` ### Variabili di stato in background.js (in memoria) | Variabile | Tipo | Scopo | |---|---|---| | `settings` | object | Configurazione corrente (sync da storage) | | `importing` | boolean | Lock: un solo import alla volta | | `lastImport` | object | Ultimo esito (sync con storage) | | `debounce` | object | `{ rid, at }` — anti-doppio invio ravvicinato | | `failNotified` | boolean | Una sola notifica per errore (anti-spam) | | `seen` | object | `{ "": }` — token già importati | ### Gestione SEEN (token già importati) - **Max voci:** 500 - **TTL:** 60 giorni (voce eliminata se `now - timestamp > 60 giorni`) - **Pruning:** quando supera 500, elimina le vecchie (metà di 500 = 250) - **Comportamento:** token già in SEEN → import skip (status `known`), nessuna notifica - **Nota:** per import manuale (`source === 'manual'`) il controllo SEEN è disattivato --- ## 4. Regex e formati token ### Pattern principale (TOKEN_RE) ```regex ^(cr|sr|rr|mr)-([a-z]{2})-(\d{1,3})-([0-9a-f]{40})$ ``` | Gruppo | Significato | Esempio | |---|---|---| | 1 | Tipo report | `cr`, `sr`, `rr`, `mr` | | 2 | Community | `ar` | | 3 | Server number | `170` | | 4 | Report ID (40 hex) | `98018ad723cb4fdba047f78728fb831cd28b3ca5` | ### Pattern fallback (RID_ONLY_RE) ```regex ^[0-9a-f]{40}$ ``` Accetta solo l'ID a 40 caratteri esadecimali (senza prefisso tipo-community-server). ### Regex content.js (TOKEN_SEARCH_RE) ```regex (cr|sr|rr|mr)-[a-z]{2}-\d{1,3}-[0-9a-f]{40} ``` Senza `^` e `$` — cerca il token **dentro** il testo copiato (la selezione può contenere altro). --- ## 5. API Backend usate dall'estensione | Endpoint | Metodo | Dati | Scopo | |---|---|---|---| | `/api/reports` | POST | `{ token: "sr-ar-170-..." }` | Importa un rapporto | | `/api/bootstrap` | GET | — | Verifica connessione (solo popup) | ### Risposte `/api/reports` ```json // Successo - nuovo: { ok: true, created: true, coords: "3|456|789", report: { defender_name: "...", defender_planet_name: "..." } } // Successo - già presente: { ok: true, created: false } // Errore: { ok: false, error: "..." } ``` --- ## 6. Comportamenti edge case ### 6.1 Background non pronto / service worker sospeso - **Content script:** `chrome.runtime.sendMessage` fallisce → status `'unreachable'` → retry singolo dopo 1s - **Popup:** `chrome.runtime.sendMessage` fallisce → mostra "Ricarica l'estensione" - **MV3:** il service worker può essere ucciso da Chrome dopo inattività → al prossimo messaggio si riattiva ### 6.2 Doppio invio (stesso token copiato due volte) - **Debounce:** 6 secondi — se stesso token in <6s → status `'dup'`, nessun POST - **SEEN:** dopo 6s, se il token è in SEEN → status `'known'`, nessun POST ### 6.3 Server occupato (HTTP 429) - Proxy community: ~10 richieste/minuto - Badge `!` arancione - Notifica una sola volta (`failNotified` flag) - Il token **non** viene aggiunto a SEEN → l'utente può ritentare ### 6.4 Server irraggiungibile - Timeout fetch: 15 secondi - Badge `!` arancione - Notifica una sola volta - Nessuno retry automatico (MV3: il service worker può essere sospeso) ### 6.5 Auto-import disattivato - Se `autoImport === false`, content script ignora tutti gli eventi `copy` - Badge spento (`''`) - Il popup può comunque fare import manuale --- ## 7. Considerazioni per future modifiche ### Punti di forza (da preservare) - Design minimalista: nessun polling, nessun permesso invasivo - Deduplicazione robusta (SEEN + debounce) - Anti-spam notifiche (`failNotified`, solo `created` per notifica) - MV3 compliant, service worker pulito - Separazione chiara dei ruoli (content = cattura, background = import, popup = config) ### Aree di miglioramento / decisioni da prendere 1. **SEEN store:** 500 voci / 60 giorni. Con più universi o utenti attivi, potrebbe saturare. Valutare aumento limiti o strategia di pruning più aggressiva. 2. **Service worker lifecycle:** in MV3 il SW può essere ucciso in qualsiasi momento. Il `importing` lock è in memoria → si resetta. Questo è accettabile perché: - Il content script ha un retry singolo - Il SEEN previene duplicati - L'import manuale nel popup è sempre disponibile 3. **Configurazione:** host/port di default `localhost:8899`. Se l'utente cambia IP, deve aggiornare manualmente. Potrebbe servire un meccanismo di rilevamento automatico (mDNS, ecc.). 4. **Content script:** il flag `sentToken` è per pagina (si resetta al reload). Questo significa che se l'utente ricarica la pagina e copia di nuovo lo stesso token, viene reinviato. Il SEEN nel background gestisce la deduplicazione, quindi è accettabile. 5. **Popup:** non c'è storico degli import — solo l'ultimo. Potrebbe essere utile un log. 6. **Errori HTTP generici:** il popup mostra `Risposta del server: HTTP 500` ma non il messaggio esatto del backend. Potrebbe essere migliorato. 7. **Manifest:** nessun `action.default_title` → il popup non ha tooltip. Aggiungere `title` per chiarezza. 8. **Versione:** hardcoded in manifest e popup. Unificare in un unico punto. ### Costanti da tenere in sync tra file | Costante | background.js | content.js | popup.js | |---|---|---|---| | `TOKEN_RE` | ✓ | ✓ (variante) | ✓ | | `TOKEN_SEARCH_RE` | — | ✓ | — | | `DEFAULTS.host` | ✓ | — | ✓ | | `DEFAULTS.port` | ✓ | — | ✓ | | `DEFAULTS.autoImport` | ✓ | ✓ | ✓ | | `DEFAULTS.notifications` | ✓ | — | ✓ | | `SETTINGS_KEY` | ✓ | — | ✓ | | `SEEN_MAX` | ✓ | — | — | | `SEEN_TTL_MS` | ✓ | — | — | ### Struttura dati da conoscere **Report importato (salvato da backend):** ```json { "token": "sr-ar-170-98018ad7...", "rid": "98018ad7...", "type": "sr", "community": "ar", "server": 170, "timestamp": , "report": { ... dati completi dal proxy ... } } ``` --- ## 8. Riferimenti incrociati | Componente | File | Porta | |---|---|---| | Backend Flask | `/opt/ogame_db/app.py` | 8899 | | Aggiornamento dati | `/opt/ogame_db/update_data.py` | — | | Gestione rapporti | `/opt/ogame_db/report_store.py` | — | | Nomi tecnici | `/opt/ogame_db/technames.py` | — | | Frontend web | `/opt/ogame_db/static/` | — | | Dati | `/opt/ogame_db/data/` | — | | Documentazione | `/opt/ogame_db/DOCUMENTAZIONE.md` | — | --- *Documento generato per riferimento sviluppo. Ultimo aggiornamento: v2.1.0.*