342 lines
14 KiB
Markdown
342 lines
14 KiB
Markdown
# 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`
|
|
|
|
```js
|
|
// 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)
|
|
|
|
```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": <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.*
|