Files
2026-09-23 18:59:27 +02:00

14 KiB

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://<host>:<port>/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://<host>:<port>/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 { "<rid>": <timestamp>, ... } — token già importati

Struttura lastImport

// Successo:
{ ok: true, badge: { t: '✓', c: '#4caf50' }, message: '...', coords: '...', created: true/false, token: '...', at: <ts> }

// Errore:
{ ok: false, badge: { t: '✗', c: '#f44336' }, error: '...', token: '...', at: <ts> }

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 { "<rid>": <timestamp> } — 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)

^(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)

^[0-9a-f]{40}$

Accetta solo l'ID a 40 caratteri esadecimali (senza prefisso tipo-community-server).

Regex content.js (TOKEN_SEARCH_RE)

(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

// 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):

{
  "token": "sr-ar-170-98018ad7...",
  "rid": "98018ad7...",
  "type": "sr",
  "community": "ar",
  "server": 170,
  "timestamp": <unix_ts>,
  "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.