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:
content.jsleggeva la clipboard connavigator.clipboard.readText()da content script. In un content script valgono i permessi della pagina, nonclipboardReaddell'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.- Filtro troppo stretto:
isRelevantPage()richiedevacomponent=messages|spynell'URL; il content script girava solo nel frame principale (noall_frames). - 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) aggancianavigator.clipboard.writeTexte lo notifica al content script viapostMessage→ copre il pulsante API/Copia di OGame.content.jsascolta l'evento DOMcopy→ copre Ctrl+C, tasto destro edocument.execCommand('copy').manifest.json:content.js+inject.jsconall_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.sendMessagefallisce → status'unreachable'→ retry singolo dopo 1s - Popup:
chrome.runtime.sendMessagefallisce → 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 (
failNotifiedflag) - 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 eventicopy - 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, solocreatedper notifica) - MV3 compliant, service worker pulito
- Separazione chiara dei ruoli (content = cattura, background = import, popup = config)
Aree di miglioramento / decisioni da prendere
-
SEEN store: 500 voci / 60 giorni. Con più universi o utenti attivi, potrebbe saturare. Valutare aumento limiti o strategia di pruning più aggressiva.
-
Service worker lifecycle: in MV3 il SW può essere ucciso in qualsiasi momento. Il
importinglock è 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
-
Configurazione: host/port di default
localhost:8899. Se l'utente cambia IP, deve aggiornare manualmente. Potrebbe servire un meccanismo di rilevamento automatico (mDNS, ecc.). -
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. -
Popup: non c'è storico degli import — solo l'ultimo. Potrebbe essere utile un log.
-
Errori HTTP generici: il popup mostra
Risposta del server: HTTP 500ma non il messaggio esatto del backend. Potrebbe essere migliorato. -
Manifest: nessun
action.default_title→ il popup non ha tooltip. Aggiungeretitleper chiarezza. -
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.