# Relay Extension + Python Receiver — Documentație Completă

Sistem de captură + dispatch real-time pentru Amazon Relay loadboard. Compus din 2 componente: extensie Chrome MV3 + receiver Python (FastAPI + WebSocket).

**Data:** 2026-05-25

---

## 1. Arhitectura generală

```
┌─────────────────────────────────────────────────────────────────┐
│                          BROWSER                                │
│                                                                 │
│  Tab Amazon Relay (relay.amazon.com / mock relay-api.myvio.eu)  │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────┐ │
│  │  PAGE world      │  │  ISOLATED world  │  │  SERVICE     │ │
│  │                  │  │                  │  │  WORKER      │ │
│  │  injected.js     │←→│ content_script   │←→│ background   │ │
│  │  (fetch/XHR      │  │ (bridge IPC)     │  │ .js (WS conn │ │
│  │   monkey-patch,  │  │                  │  │  + polling)  │ │
│  │   UI inject,     │  │                  │  │              │ │
│  │   book click)    │  │                  │  │              │ │
│  └──────────────────┘  └──────────────────┘  └──────┬───────┘ │
└──────────────────────────────────────────────────────┼─────────┘
                                                       │ WebSocket
                                                       ↓
┌─────────────────────────────────────────────────────────────────┐
│                  PYTHON RECEIVER (FastAPI)                      │
│                  HOST: 192.168.0.31:9001 (typical)              │
│                                                                 │
│  ws://...:9001/ws/stream                                        │
│    ├─ EXT_CLIENTS   (extensii care PUSH-uiesc capturile)        │
│    └─ DISPATCHERS   (pagini browser care RECV broadcasts)       │
│                                                                 │
│  HTTP /admin/* (stats, insights, raw-ring, reports, ...)        │
│                                                                 │
│  Memorie:                                                       │
│    POOL{}            — tururi active (deduplicated)             │
│    LAST_SEEN{}       — TTL per tour (grace period cleanup)      │
│    POLL_HISTORY      — last 200 captures metadata               │
│    LATENCIES         — last 100 ext→recv ms                     │
│    LATENCIES_OUT     — last 100 recv→UI ms (browser reports)    │
│    RAW_RING          — last N raw JSON bodies (configurable)    │
│    reports_db (SQLite) — book history, snapshots, events        │
└──────────────────────┬──────────────────────────────────────────┘
                       │ WS broadcast
                       ↓
                ┌─────────────────────────────────────┐
                │  BOOKER-ADMIN UI (relay_tours,      │
                │  relay_ws_sources, mock_tuning)     │
                │  Conexiuni multiple dispatchers     │
                └─────────────────────────────────────┘
```

---

## 2. Extensie Chrome MV3

### 2.1 Structura fișierelor

| Fișier | World | Rol |
|---|---|---|
| `manifest.json` | — | Declarații extensiei (perms, content_scripts, SW) |
| `background.js` | Service Worker | WS connection persistent, polling scheduler, message router |
| `content_script.js` | ISOLATED | Bridge între page world și service worker (acces `chrome.*` API) |
| `injected.js` | PAGE | Monkey-patch fetch/XHR, capture body+headers, UI inject, book click |
| `popup.html` + `popup.js` | popup | Setări utilizator (rate, jitter, target host, pull mode, etc) |
| `book_unlock.json` | filesystem | **Safety gate**: dacă lipsește/false → toate book-urile blocate |
| `assets/audio/*.mp3` | — | Sunete pentru new/price/booked alerts |

### 2.2 Flow capture (passive)

1. **Pagina Amazon JS** face `fetch('/api/loadboard/search', {...})`
2. **`injected.js` window.fetch override** prinde apelul:
   - Pre-cache: clone request → salvează body + headers (incluzând `x-csrf-token`)
   - Apoi `await origFetch.apply(this, args)` → cererea reală merge la Amazon
   - Măsoară `fetch_ms = performance.now() - t0` (local, fără telemetrie)
   - Clone response → `send()`
3. **`send()`** trimite `window.postMessage({__relay_capture: true, body, ts, fetch_ms, ...}, "*")`
4. **`content_script.js`** ascultă `window.message` → forward la background via `chrome.runtime.sendMessage`
5. **`background.js`** trimite payload prin WebSocket la receiver (sau cozează dacă WS offline)

### 2.3 Flow polling (active)

1. **`background.js` timer** (rate configurabil din popup) trimite `trigger_active_fetch` la fiecare tab eligibil
2. **`content_script.js`** forward via `postMessage({__relay_active: true, pull_via_ui_click, ...})`
3. **`injected.js`** handler:
   - **`pull_via_ui_click=true` (DEFAULT, Rocket Relay style)**: găsește butonul Refresh prin `#utility-bar .refresh-and-chat-box button` + SVG path filter `M20.128 2`, apoi `btn.click()` SIMPLU (NU MouseEvent dispatchEvent) — React Amazon răspunde corect. **NU există fallback la fetch direct.**
   - **`pull_via_ui_click=false`**: doFetch direct la URL-ul cached
4. Răspunsul revine via flow passive (mock/Amazon face fetch ca răspuns la click → capture obișnuit)

### 2.4 Flow book (DOM click style)

Toate cele 10 gates trebuie să treacă, altfel book-ul e blocat:

1. `book_unlock.json` exists + `enabled: true`
2. Popup toggle "Book ENABLED" = ON
3. `BOOK_HARDCODED_CONFIG.enabled = true` (în code)
4. Tour ID în lock (`__relay_book_in_progress`)
5. Card există în DOM (verificat la click time)
6. URL fetch verify (URL conține EXACT tour_id-ul din lock)
7. `dom_click_mode = true`
8. `fetch_url_verify = true`
9. **LEVEL 4 fetch global block**: orice `/api/loadboard/{tid}/{v}/option/{oid}/majorVersion/{mv}` URL care nu trece gate-urile → fake response cu `errorCode: work_opportunity_not_available` (cod nativ Amazon, fără string custom suspect)
10. **LEVEL 4 XHR global block**: idem pentru XMLHttpRequest

### 2.5 Configurare popup

| Setting | Default | Effect |
|---|---|---|
| `enabled` | true | Master toggle — OFF = nimic nu se trimite la receiver |
| `active_mode` | false | ON = polling automat la `poll_rate` |
| `poll_rate` | 2.0s | Interval bază între polls |
| `poll_jitter` | 0 | ± random pentru anti-bot rate signature |
| `pull_via_ui_click` | **true** | Click pe butonul Refresh (telemetrie nativă) — NU există fallback fetch |
| `poll_only_when_active` | false | Polluiază doar tab activ + fereastra focusată |
| `target_host` | auto | Filtru tab-uri (relay.amazon.es/de/it/fr/uk/com, relay-api.myvio.eu) |
| `override_result_size` | 0 | Force `resultSize` în body (0 = lasă natural) |
| `auto_paginate` | true | Aggregate pages prin nextItemToken până la 20 pagini |
| `alerts_enabled` | false | Move-to-top + highlight CSS pe tururi noi |
| `alerts_audio_on` | true | Sunete (new.mp3, price.mp3, successbook.mp3) |
| `simulation_mode` | false | Dry-run book: highlight 15s + sunet, NU click real |
| `fast_book_button_enabled` | false | Injectează buton "🚀 Fast Book" pe fiecare card |
| `book_user_enabled` | false | Toggle în popup pentru book autorizat |

---

## 3. Python receiver

### 3.1 Structura fișierelor

| Fișier | Rol |
|---|---|
| `receiver.py` | App FastAPI principală — WS + HTTP endpoints |
| `config.py` | Env vars + .env loader (no external deps) |
| `book_authorization.py` | Gate file pentru book live (lazy import) |
| `book_unlock.json` | Status unlock per receiver (separate de extension) |
| `reports_db.py` | SQLite local pentru book history, snapshots, event log |
| `captures/*.json` | Optional disk dump (dacă `SAVE_RAW_ENABLED=true`) |

### 3.2 Endpoints HTTP

| Endpoint | Returns | Folosit pentru |
|---|---|---|
| `GET /` | HTML dashboard | Quick view în browser |
| `GET /admin/stats` | tenant, target, pool_size, ws_clients, ext_clients, uptime, counters, **memory** (RSS, ring bytes) | Stats overview |
| `GET /admin/insights` | recent_polls (last 30), avg_latency_ms, avg_latency_out_ms, **recent_latency_out_ms**, polls_per_minute, **consecutive_fails**, **fail_auto_stop_threshold**, latest_pool | UI charts |
| `GET /admin/raw-ring/list` | Metadata pentru toate item-urile din ring (fara body-uri) | Lista UI tab Raw |
| `GET /admin/raw-ring/zip?limit=N` | ZIP cu ultimele N JSON-uri + `_manifest.json` | Download bulk export |
| `GET /admin/raw-ring/{idx}` | Body complet pt un item | Preview UI |
| `GET /admin/last-response` | Ultimul response captat | Quick inspection |
| `GET /admin/reports/book-history` | DB SQLite — istoricul book-urilor |  |
| `GET /admin/reports/snapshots` | DB SQLite — snapshots orare |  |
| `GET /admin/reports/events` | DB SQLite — event log (info/warn/error) |  |

### 3.3 Endpoints WebSocket

| Path | Roluri |
|---|---|
| `WS /ws/stream` | Multiplex — ext (push captures) sau dispatcher (recv broadcasts) după primul mesaj `identify` |

**Mesaje ext→receiver:**
- `loadboard_response` — capture complet (url, status, body, ts, fetch_ms, source)
- `ext_status` — status raportat de extensie (poll rate, active flag, errors)
- `book_result` — rezultat book pentru audit

**Mesaje receiver→dispatcher:**
- `add` / `update` — tour nou sau version bump
- `gone` — tour expirat (TTL grace period)
- `ext_status` — broadcast la dispatcheri (UI)

**Mesaje dispatcher→receiver:**
- `identify` — declarare rol
- `latency_report` — UI raportează latency `Date.now() - msg.__server_ts`
- `ext_command` — control remote (pause/resume polling, change rate)

### 3.4 Variabile de mediu (config.py)

| Env Var | Default | Effect |
|---|---|---|
| `RECEIVER_PORT` | 9001 | Port HTTP+WS |
| `RECEIVER_HOST` | 0.0.0.0 | Bind addr |
| `GRACE_PERIOD_S` | 1.5 | Cât timp un tur nevăzut rămâne în POOL înainte de TTL cleanup |
| `POOL_MAX_SIZE` | 0 | LRU cap pe POOL (0 = nelimitat) |
| `EXT_MAX_MSG_BYTES` | 10MB | Limită mesaj WS de la extensie |
| `BOOK_UNLOCK_FILE` | `./book_unlock.json` | Path la gate file |
| `BOOK_AUDIT_ENABLED` | true | Log book attempts în SQLite |
| `SAVE_RAW_ENABLED` | false | Disk dump capturi în `./captures/` |
| `SAVE_RAW_DIR` | `./captures` | Folder pentru dump |
| `SAVE_RAW_MAX_FILES` | 5000 | FIFO rotation |
| **`RAW_RING_SIZE`** | **50** | Ring buffer JSON-uri raw în RAM (1000 = ~140MB) |
| **`FAIL_AUTO_STOP_THRESHOLD`** | **0** | Auto-stop receiver după N fails consecutive (0 = OFF) |

### 3.5 Safety: book_unlock.json

```json
{
  "enabled": false,
  "token": "",
  "note": "Schimba enabled: true + reload pentru a permite book-uri"
}
```

Dacă fișierul **lipsește** SAU `enabled !== true` → toate book paths blocate (gate redundant cu cel din extension). Token-ul opțional e dovadă suplimentară.

---

## 4. Ce poate detecta Amazon — analiză honestă

### 4.1 Ce VEDE Amazon (telemetria lor)

| Detectabil | Cum | Mitigare |
|---|---|---|
| **Cererile HTTP la `/api/loadboard/search`** | Standard logging server-side | Inevitabil — userul oricum face cererea via UI. Rate matters: 0.5s × 5 tabs = ~10 req/sec validat empiric pe B_PLUS |
| **Headers normale** (cookie, csrf, UA) | Server log | Pass-through, neschimbate de extensie |
| **Click events native pe butoane** (Reservar, Confirm, Refresh) | Pendo, csa.ContentInteraction, optimus | TRUSTED click events emitate de browser cand userul/extensia face btn.click() — telemetria primește event-ul. Asta e BUN pentru noi: arată activitate de user normal. |
| **`window.fetch` wrapped** | Teoretic detectabil prin `fetch.toString()` | În practică, anti-bot prod nu face asta. Rocket Relay rulează ani de zile fără probleme. |
| **Pagina rămâne deschisă mult timp** | Time-on-page metrics | Polluare doar pe tab activ + focusat (`poll_only_when_active`) reduce signature |

### 4.2 Ce NU VEDE Amazon

| Invizibil | De ce |
|---|---|
| **Mesajele postMessage interne** (`__relay_capture: true`) | Doar dacă au `window.addEventListener('message', ...)` cu filtrare exact pe flag — nu fac asta în prod |
| **`performance.now()` măsurători locale** | Funcție stdlib, nu generează rețea |
| **Body-ul captat de extensie** | Rămâne pe device + WebSocket spre LAN-ul tău |
| **WebSocket-ul către receiver (`ws://192.168.0.31:9001/`)** | LAN privat, neaccesibil din amazon.com origin |
| **Modificările DOM** (move-to-top, CSS classes adăugate) | Doar dacă au MutationObserver activ — în prod nu observă tot DOM-ul |
| **Click-uri programatice `btn.click()`** vs trusted clicks | Native event listeners se trigerează identic. Doar dacă React onClick verifică `event.isTrusted` (rar) ar fi diferit. Rocket Relay confirmă că `btn.click()` merge pe Amazon real. |
| **Fast Book button injectat** | Element extra în DOM, dar fără chemări la API care să raporteze "extension detected" |

### 4.3 Comparație cu Rocket Relay (audit reverse-engineering)

Audit-ul din `/var/www/booker-admin/audit/kcnegnbacaohnhpmeckfakhlnahliklf/8.91_0/` confirmă:
- RR folosește același pattern: `document.querySelector('.css-q7ppch')?.click()` pentru refresh
- RR nu emite niciun event suplimentar către Amazon
- RR rulează din 2021 pe mii de useri fără ban-uri publice

**Concluzie:** profilul de risc al extensiei noastre = identic cu Rocket Relay. Singurul "tell" semnificativ ar fi RATE de polling agresiv (peste 2-3 req/sec sustained per account).

### 4.4 Bot signatures pe care le evităm

- ❌ Click cu coordonate `(0, 0)` → folosim `realisticClick()` cu coords reale din `getBoundingClientRect` ± random jitter (pt book buttons)
- ❌ Polling robotic constant → `poll_jitter` configurabil pentru a randomiza intervalul
- ❌ Polluare pe tab inactiv → `poll_only_when_active`
- ❌ Cereri cu headers stripped → preservăm TOATE headers din cached request (cookie, csrf, accept-language, etc)

---

## 5. Configurare end-to-end

### 5.1 Setup extensie

1. **Load unpacked** din `chrome://extensions` → "Load unpacked" → selectează folder `chrome-extension`
2. **`book_unlock.json`** lângă manifest:
   ```json
   {"enabled": true, "token": "ceva-secret"}
   ```
   Restartează extensia (chrome://extensions → 🔄) după modificare.
3. **Popup**: setează `Receiver URL` = `ws://192.168.0.31:9001/ws/stream`
4. Bifează `Mod activ` + `Pull via UI click` (default ON)
5. Save

### 5.2 Setup receiver

```bash
cd python-receiver/app
python3 -m venv venv && source venv/bin/activate
pip install fastapi uvicorn  # + opțional: psutil

# .env (lângă receiver.py)
RECEIVER_PORT=9001
RAW_RING_SIZE=1000
FAIL_AUTO_STOP_THRESHOLD=20
GRACE_PERIOD_S=1.5

# .book_unlock.json
{"enabled": true, "token": ""}

# Start
python3 receiver.py
```

Pe **Windows**:
- Cross-platform RSS lookup via ctypes nu necesită psutil
- Folosește `pythonw.exe receiver.py` pentru a rula în background fără console

### 5.3 Setup booker-admin UI

```sql
INSERT INTO relay_ws_sources (user_id, name, url, active)
VALUES (1, 'LocalReceiver', 'ws://192.168.0.31:9001/ws/stream', 1);
```

Apoi pe `/relay_ws_sources` → click pe stats icon → modal cu:
- 6 charts live (status pie, latency ext→recv, body size, polls/min, fetch ms, recv→UI ms)
- Tab "Last captures" + Tab "Raw JSONs in RAM"
- Buton "Download ZIP" pentru ultimele 50/100/500/1000

---

## 6. Pro / Contra de design

### ✅ Pro

| Aspect | Detaliu |
|---|---|
| **Decuplare PAGE/ISOLATED/SW** | Respectă strict modelul MV3, nu accesează `chrome.*` din PAGE world |
| **Cross-tab orchestration** | Background SW are vedere globală — alegere tab pentru book bazat pe cine are turul în DOM |
| **CSRF auto-capture** | Cached din fetch real, niciodată hardcoded |
| **Multi-host support** | `target_host: auto` acoperă es/de/it/fr/uk/com + mock myvio.eu |
| **Pull via UI click default** | Telemetrie naturală, indistinguibilă de user real |
| **Safety chain 10-gates pentru book** | Defense in depth — book_unlock.json + popup + hardcoded + DOM verify + URL verify + LEVEL 4 fetch/XHR block |
| **Receiver = single source of truth** | Mai mulți dispatcheri pot consuma simultan (UI, agent, etc) |
| **Reports DB** | SQLite local pentru audit retroactiv (book history, snapshots, events) |
| **Raw ring în RAM + ZIP export** | Debug live fără disk overhead, descărcabil oricând |
| **Auto-stop pe fails consecutive** | Circuit breaker când Amazon ne blochează |
| **Memory monitoring multi-platform** | Linux /proc, Windows ctypes, macOS resource — fără dependinte |

### ⚠️ Contra / Compromisuri

| Aspect | De ce e un trade-off | Mitigare |
|---|---|---|
| **MV3 service worker poate adormi** | Chrome oprește SW după 30s idle | `chrome.alarms` keepalive la 20s + `rescheduleActiveFetch()` la fiecare wake |
| **Fetch wrapper detectabil** prin `fetch.toString()` | Anti-bot teoretic poate verifica | În practică, nu se întâmplă (Rocket Relay confirmă) |
| **Niciun fallback la fetch direct** când pull_via_ui_click ON | Dacă click eșuează, refresh nu se face | Fail loud — log în console, user vede imediat. Trade-off: zero risk de bot signature din fetch fără telemetrie |
| **Receiver e single point of failure** | Dacă cade → toți dispatcherii orbi | Queue în background.js (200 mesaje) + reconnect cu backoff |
| **RAW_RING e volatil** | Pierdut la restart | Activează `SAVE_RAW_ENABLED` pentru persistență disc + rotation FIFO |
| **CSRF expiră la restart container mock** | `VALID_SESSIONS` în memorie | F5 pagina pentru sesiune nouă; pentru prod persistență, ar trebui SQLite/Redis |
| **Polling rate fix per receiver** | Toți tabs partajează aceeași rată | Nu suportăm rate per-tab momentan |
| **Mixed content blocking** | Browser HTTPS nu poate fetch HTTP receiver | Proxy PHP server-side (deja implementat în relay_ws_sources_api.php) |
| **WebSocket scaling** | Un receiver = un proces Python | Pentru multi-tenant scaling trebuie sticky session / sharding |
| **No backpressure** | Dacă receiver e overloaded, ext continuă să trimită | Acceptabil pentru 1-5 ext clients; pentru 100+ ar trebui rate limiting |

### 🔍 Comparativ cu alternative

| Strategie | Pro | Contra |
|---|---|---|
| **Direct fetch din extensie** (no UI click) | Mai rapid (~50ms) | Bot signature: no telemetry pe Pendo, lipsa csa.ContentInteraction events |
| **Pull via UI click** (CURRENT) | Telemetrie naturală, indistinguibil de user | ~100-200ms overhead per refresh (extra round-trip prin React lifecycle) |
| **Headless puppeteer extern** | Cross-browser, scriptable | Detectabil prin `navigator.webdriver`, headless flag |
| **MITM proxy (mitmproxy / Burp)** | Acces total request/response | Manual configurat, nu portabil |

---

## 7. Operațional

### 7.1 Deploy

**Extension** (Chrome dev mode):
1. Modifică fișiere local
2. `chrome://extensions` → 🔄 Reload la "Relay Tour Catcher"
3. **F5 pe tab-urile deschise** (content scripts nu re-injectează la reload extensie!)

**Receiver** (Windows 192.168.0.31):
```bash
# Stop curent
# (taskkill /F /IM python.exe sau Ctrl+C în consolă)
# Copy fișiere noi
scp receiver.py user@192.168.0.31:/path/to/app/
# Restart
python receiver.py
```

**Booker-admin UI**:
- Edit `templates/relay_ws_sources/*.html` și `plib/relay/relay_ws_sources_api/*.php` local
- Clear Smarty cache: `rm /var/www/booker-admin/templates_c/*relay_ws_sources*`
- F5 pagină

### 7.2 Debug

| Problemă | Verificare |
|---|---|
| Extensia nu trimite la receiver | Service worker logs (`chrome://extensions` → Relay → "service worker") |
| Polling nu pornește | În popup: `Mod activ` ON? Save apăsat? `pull_via_ui_click` ON? |
| "Receiver process —" | Deploy ultima versiune receiver.py (ctypes Windows fallback) |
| "stale" în health | `LAST_POLL_AT` se update-ează doar la status=200; verifică `consecutive_fails` |
| Mixed content errors | Browser HTTPS nu poate `http://192.168.0.31`. Folosește PHP proxy (`q=stats&id=X`) |
| ZIP corupt | Verifică PHP `ob_end_clean()` + headers `Content-Encoding: identity` |
| 422 pe `/admin/raw-ring/zip` | Ordine rute FastAPI — `/zip` trebuie ÎNAINTE de `/{idx}` |

### 7.3 Monitoring

- **`/admin/stats`** — pool size, ext_clients, uptime, RAM RSS
- **`/admin/insights`** — recent polls, latency avg + time series, consecutive_fails
- **Reports DB** — `/admin/reports/events?level=error` pentru issues istorice
- **UI charts în modal stats** — vizualizare live 1s refresh

---

## 8. Limitări cunoscute

1. **Auto-paginate max 20 pagini** — dacă userul cere `resultSize=50` și sunt 2000 tururi, se opresc la 1000 (20 × 50)
2. **POLL_HISTORY in-memory** — pierdut la restart receiver
3. **RAW_RING in-memory** — idem
4. **Reports DB SQLite** — single-writer; pentru concurrent receivers trebuie PostgreSQL
5. **Book click fallback unic** — DOM click; fetch direct e disabled by design (riscant)
6. **CSRF generat dynamic** la mock — token expiră la restart container

---

## 9. Glosar

- **Tour / WorkOpportunity (WO)** — un job de transport Amazon Relay
- **Pool** — set de tururi active în memoria receiver-ului
- **Grace period** — TTL după care un tur nevăzut e șters din pool
- **Dispatcher** — orice client WS care consumă broadcasts (UI, agent, etc)
- **Capture** — un response `/api/loadboard/search` interceptat
- **Passive** — capture-ul vine de la fetch-ul propriu al paginii Amazon
- **Active** — capture-ul vine de la trigger-ul programat al extensiei
- **Round complete** — răspuns cu `nextItemToken=0` (ultima pagină dintr-o secvență)
- **Fast Book** — buton injectat de extensie pe fiecare card de tur pentru book one-click
- **Simulation mode** — dry-run: highlight + sunet, fără click real pe Reservar

---

## 10. Backup-uri

Toate modificările majore au backup în `/var/www/booker-admin/backups/`:
- `relay_stats_20260525_020909/` — înainte de adăugarea charts + raw ring + ZIP export

Pentru rollback complet: copy din backup în loc.
