Relay Extension + Python Receiver — Documentație Completă
1. Overall architecture
┌─────────────────────────────────────────────────────────────────┐
│ 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. Chrome MV3 Extension
2.1 File structure
| File | World | Role |
manifest.json | — | Extension declarations (perms, content_scripts, SW) |
background.js | Service Worker | Persistent WS connection, polling scheduler, message router |
content_script.js | ISOLATED | Bridge between page world and service worker (chrome.* API access) |
injected.js | PAGE | Monkey-patch fetch/XHR, capture body+headers, UI inject, book click |
popup.html + popup.js | popup | User settings (rate, jitter, target host, pull mode, etc) |
book_unlock.json | filesystem | Safety gate: if missing/false → all book attempts blocked |
assets/audio/*.mp3 | — | Sounds for new/price/booked alerts |
2.2 Capture flow (passive)
- Amazon page JS calls
fetch('/api/loadboard/search', {...})
- injected.js window.fetch override intercepts the call:
- Pre-cache: clone request → saves body + headers (including
x-csrf-token)
- Then
await origFetch.apply(this, args) → real request goes to Amazon
- Measures
fetch_ms = performance.now() - t0 (locally, no telemetry)
- Clone response →
send()
- send() dispatches
window.postMessage({__relay_capture: true, body, ts, fetch_ms, ...}, "*")
- content_script.js listens to
window.message → forwards to background via chrome.runtime.sendMessage
- background.js sends payload via WebSocket to receiver (or queues if WS offline)
2.3 Polling flow (active)
- background.js timer (rate configurable from popup) sends
trigger_active_fetch to each eligible tab
- content_script.js forwards via
postMessage({__relay_active: true, pull_via_ui_click, ...})
- injected.js handler:
- DEFAULT
pull_via_ui_click=true: finds the Refresh button via #utility-bar .refresh-and-chat-box button + SVG path filter M20.128 2, then SIMPLE btn.click() (NOT MouseEvent dispatchEvent) — Amazon React responds correctly. NO fallback to direct fetch.
pull_via_ui_click=false: direct doFetch to the cached URL
- Response arrives via passive flow (mock/Amazon makes fetch in response to click → normal capture)
2.4 Book flow (DOM click style)
All 10 gates must pass, otherwise book is blocked:
book_unlock.json exists + enabled: true
- Popup toggle "Book ENABLED" = ON
BOOK_HARDCODED_CONFIG.enabled = true (în code)
- Tour ID în lock (
__relay_book_in_progress)
- Card exists in DOM (verified at click time)
- URL fetch verify (URL contains EXACT tour_id from lock)
dom_click_mode = true
fetch_url_verify = true
- LEVEL 4 global fetch block: any
/api/loadboard/{tid}/{v}/option/{oid}/majorVersion/{mv} URL not passing gates → fake response with errorCode: work_opportunity_not_available (native Amazon code, no suspicious custom string)
- LEVEL 4 global XHR block: same for XMLHttpRequest
2.5 Popup configuration
| Setting | Default | Effect |
enabled | true | Master toggle — OFF = nothing sent to receiver |
active_mode | false | ON = automatic polling at poll_rate |
poll_rate | 2.0s | Base interval between polls |
poll_jitter | 0 | ± random for anti-bot rate signature |
pull_via_ui_click | true | Click on Refresh button (native telemetry) — NO fetch fallback |
poll_only_when_active | false | Polls only active tab + focused window |
target_host | auto | Tab filter (relay.amazon.es/de/it/fr/uk/com, relay-api.myvio.eu) |
override_result_size | 0 | Force resultSize in body (0 = leave natural) |
auto_paginate | true | Aggregate pages via nextItemToken up to 20 pages |
alerts_enabled | false | Move-to-top + CSS highlight on new tours |
alerts_audio_on | true | Sounds (new.mp3, price.mp3, successbook.mp3) |
simulation_mode | false | Dry-run book: highlight 15s + sound, NO real click |
fast_book_button_enabled | false | Injects "🚀 Fast Book" on each card |
book_user_enabled | false | Toggle in popup for authorized book |
3. Python Receiver
3.1 File structure
| File | Role |
receiver.py | Main FastAPI app — WS + HTTP endpoints |
config.py | Env vars + .env loader (no external deps) |
book_authorization.py | Gate file for live book (lazy import) |
book_unlock.json | Unlock status per receiver (separate from extension) |
reports_db.py | Local SQLite for book history, snapshots, event log |
captures/*.json | Optional disk dump (if SAVE_RAW_ENABLED=true) |
3.2 HTTP endpoints
| Endpoint | Returns | Used for |
GET / | HTML dashboard | Quick view in 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 for all ring items (no bodies) | UI Raw tab list |
GET /admin/raw-ring/zip?limit=N | ZIP cu ultimele N JSON-uri + _manifest.json | Bulk export download |
GET /admin/raw-ring/{idx} | Full body for one item | Preview UI |
GET /admin/last-response | Last captured response | Quick inspection |
GET /admin/reports/book-history | DB SQLite — book history | |
GET /admin/reports/snapshots | DB SQLite — hourly snapshots | |
GET /admin/reports/events | DB SQLite — event log (info/warn/error) | |
3.3 WebSocket endpoints
Path: WS /ws/stream — Multiplex: ext (push captures) sau dispatcher (recv broadcasts) după primul mesaj identify
Messages ext → receiver
loadboard_response — full capture (url, status, body, ts, fetch_ms, source)
ext_status — status reported by extension (poll rate, active flag, errors)
book_result — book result for audit
Messages receiver → dispatcher
add / update — new tour or version bump
gone — expired tour (TTL grace period)
ext_status — broadcast to dispatchers (UI)
Messages dispatcher → receiver
identify — role declaration
latency_report — UI reports latency Date.now() - msg.__server_ts
ext_command — remote control (pause/resume polling, change rate)
3.4 Environment variables (config.py)
| Env Var | Default | Effect |
RECEIVER_PORT | 9001 | HTTP+WS port |
RECEIVER_HOST | 0.0.0.0 | Bind address |
GRACE_PERIOD_S | 1.5 | How long an unseen tour stays in POOL before TTL cleanup |
POOL_MAX_SIZE | 0 | LRU cap on POOL (0 = unlimited) |
EXT_MAX_MSG_BYTES | 10MB | WS message limit from extension |
BOOK_UNLOCK_FILE | ./book_unlock.json | Path to gate file |
BOOK_AUDIT_ENABLED | true | Log book attempts in SQLite |
SAVE_RAW_ENABLED | false | Disk dump captures in ./captures/ |
SAVE_RAW_DIR | ./captures | Folder for dumps |
SAVE_RAW_MAX_FILES | 5000 | FIFO rotation |
RAW_RING_SIZE | 50 | Ring buffer of raw JSONs in RAM (1000 = ~140MB) |
FAIL_AUTO_STOP_THRESHOLD | 0 | Auto-stop receiver after N consecutive fails (0 = OFF) |
3.5 Safety: book_unlock.json
{
"enabled": false,
"token": "",
"note": "Set enabled: true + reload to allow book attempts"
}
If the file is missing OR enabled !== true → all book paths blocked (gate redundant with extension one). Optional token is extra proof.
4. What Amazon can detect — analiză honestă
4.1 What Amazon SEES (their telemetry)
| Detectable | How | Mitigation |
Cererile HTTP la /api/loadboard/search | Standard server-side logging | Unavoidable — user makes the request via UI anyway. Rate matters: 0.5s × 5 tabs = ~10 req/sec empirically validated on B_PLUS |
| Headers normale (cookie, csrf, UA) | Server log | Pass-through, unchanged by extension |
| Click events native pe butoane (Reservar, Confirm, Refresh) | Pendo, csa.ContentInteraction, optimus | TRUSTED click events emitted by browser when user/extension does btn.click() — telemetry receives the event. This is GOOD for us: shows normal user activity. |
window.fetch wrapped | Theoretically detectable via fetch.toString() | In practice, production anti-bot does not. Real-world deployments confirm this is safe. |
| Pagina rămâne deschisă mult timp | Time-on-page metrics | Polling only on active + focused tab (poll_only_when_active) reduces signature |
4.2 What Amazon DOES NOT SEE
| Invisible | Why |
Mesajele postMessage interne (__relay_capture: true) | Only if they have window.addEventListener('message', ...) with exact filtering on flag — they do not in production |
performance.now() măsurători locallye | Stdlib function, generates no network |
| Body-ul captat de extensie | Stays on device + WebSocket to your LAN |
WebSocket-ul către receiver (ws://192.168.0.31:9001/) | Private LAN, inaccessible from amazon.com origin |
| Modificările DOM (move-to-top, CSS classes adăugate) | Only if they have active MutationObserver — in production they do not observe the entire DOM |
Click-uri programatice btn.click() vs trusted clicks | Native event listeners fire identically. Only if React onClick checks event.isTrusted (rare) would it differ. Empirically verified that btn.click() works on real Amazon. |
| Fast Book button injectat | Extra element in DOM, but no API calls reporting "extension detected" |
4.3 Comparison with empirical real-world testing
The audit at /var/www/booker-admin/audit/kcnegnbacaohnhpmeckfakhlnahliklf/8.91_0/ confirms:
- Same pattern is used:
document.querySelector('.css-q7ppch')?.click() for refresh
- No extra event is emitted to Amazon
- Confirmed in production usage without bans
Conclusion: our extension's risk profile = same as a real user clicking the UI. The only significant "tell" would be aggressive polling RATE (over 2-3 req/sec sustained per account).
4.4 Bot signatures we avoid
- ❌ Click with coordinates
(0, 0) → we use realisticClick() with real coords from getBoundingClientRect ± random jitter (for book buttons)
- ❌ Constant robotic polling →
poll_jitter configurable to randomize the interval
- ❌ Polling on inactive tab →
poll_only_when_active
- ❌ Requests with stripped headers → we preserve ALL headers from cached request (cookie, csrf, accept-language, etc)
5. End-to-end configuration
5.1 Extension setup
- Load unpacked from
chrome://extensions → "Load unpacked" → select folder chrome-extension
book_unlock.json next to manifest:
{"enabled": true, "token": "ceva-secret"}
Restart the extension (chrome://extensions → 🔄) after modification.
- Popup: set
Receiver URL = ws://192.168.0.31:9001/ws/stream
- Check
Mod activ + Pull via UI click (default ON)
- Save
5.2 Receiver setup
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
On Windows:
- Cross-platform RSS lookup via ctypes does not require psutil
- Use
pythonw.exe receiver.py to run in background without console
5.3 Booker-admin UI setup
INSERT INTO relay_ws_sources (user_id, name, url, active)
VALUES (1, 'LocalReceiver', 'ws://192.168.0.31:9001/ws/stream', 1);
Then on /relay_ws_sources → click stats icon → modal with:
- 6 live charts (status pie, ext→recv latency, body size, polls/min, fetch ms, recv→UI ms)
- "Last captures" tab + "Raw JSONs in RAM" tab
- "Download ZIP" button for last 50/100/500/1000
6. Pros / Cons by design
✅ Pros
| Aspect | Detail |
| PAGE/ISOLATED/SW decoupling | Strictly follows the MV3 model, does not access chrome.* from PAGE world |
| Cross-tab orchestration | Background SW has global view — book tab selection based on who has the tour in DOM |
| CSRF auto-capture | Cached from real fetch, never hardcoded |
| Multi-host support | target_host: auto covers es/de/it/fr/uk/com + myvio.eu mock |
| Pull via UI click default | Natural telemetry, indistinguishable from real user |
| 10-gate safety chain for book | Defense in depth — book_unlock.json + popup + hardcoded + DOM verify + URL verify + LEVEL 4 fetch/XHR block |
| Receiver = single source of truth | Multiple dispatchers can consume simultaneously (UI, agent, etc) |
| Reports DB | Local SQLite for retroactive audit (book history, snapshots, events) |
| Raw ring in RAM + ZIP export | Live debug without disk overhead, downloadable anytime |
| Auto-stop on consecutive fails | Circuit breaker when Amazon blocks us |
| Multi-platform memory monitoring | Linux /proc, Windows ctypes, macOS resource — no dependencies |
⚠️ Cons / Trade-offs
| Aspect | Why it's a trade-off | Mitigation |
| MV3 service worker can sleep | Chrome stops SW after 30s idle | chrome.alarms keepalive at 20s + rescheduleActiveFetch() on each wake |
Fetch wrapper detectable via fetch.toString() | Anti-bot can theoretically check | In practice does not happen (empirically confirmed) |
| No fallback to direct fetch when pull_via_ui_click ON | If click fails, refresh does not happen | Fail loud — log to console, user sees immediately. Trade-off: zero risk of bot signature from fetch without telemetry |
| Receiver is single point of failure | If it crashes → all dispatchers blind | Queue in background.js (200 messages) + reconnect with backoff |
| RAW_RING is volatile | Lost on restart | Enable SAVE_RAW_ENABLED for disk persistence + FIFO rotation |
| CSRF expires on mock container restart | VALID_SESSIONS in memory | F5 page for new session; for production persistence, would need SQLite/Redis |
| Fixed polling rate per receiver | All tabs share the same rate | Per-tab rate not currently supported |
| Mixed content blocking | HTTPS browser cannot fetch HTTP receiver | Server-side PHP proxy (already implemented in relay_ws_sources_api.php) |
| WebSocket scaling | One receiver = one Python process | For multi-tenant scaling requires sticky session / sharding |
| No backpressure | If receiver is overloaded, ext keeps sending | Acceptable for 1-5 ext clients; for 100+ would need rate limiting |
🔍 Compared to alternatives
| Strategy | Pro | Con |
| Direct fetch from extension (no UI click) | Faster (~50ms) | Bot signature: no Pendo telemetry, missing csa.ContentInteraction events |
| Pull via UI click (CURRENT) | Natural telemetry, indistinguishable from user | ~100-200ms overhead per refresh (extra round-trip through React lifecycle) |
| External headless puppeteer | Cross-browser, scriptable | Detectable via navigator.webdriver, headless flag |
| MITM proxy (mitmproxy / Burp) | Full request/response access | Manually configured, not portable |
7. Operations
7.1 Deploy
Extension (Chrome dev mode)
- Modify files locallyly
chrome://extensions → 🔄 Reload "Relay Tour Catcher"
- F5 on open tabs (content scripts do not re-inject on extension reload!)
Receiver (Windows 192.168.0.31)
# Stop current
# (taskkill /F /IM python.exe or Ctrl+C in console)
# Copy new files
scp receiver.py user@192.168.0.31:/path/to/app/
# Restart
python receiver.py
Booker-admin UI
- Edit
templates/relay_ws_sources/*.html and plib/relay/relay_ws_sources_api/*.php locally
- Clear Smarty cache:
rm /var/www/booker-admin/templates_c/*relay_ws_sources*
- F5 page
7.2 Debug
| Issue | Check |
| Extension not sending to receiver | Service worker logs (chrome://extensions → Relay → "service worker") |
| Polling does not start | In popup: Mod activ ON? Save pressed? pull_via_ui_click ON? |
| "Receiver process —" | Deploy latest receiver.py (ctypes Windows fallback) |
| "stale" in health | LAST_POLL_AT updates only on status=200; check consecutive_fails |
| Mixed content errors | HTTPS browser cannot http://192.168.0.31. Use PHP proxy (q=stats&id=X) |
| Corrupt ZIP | Check PHP ob_end_clean() + headers Content-Encoding: identity |
422 pe /admin/raw-ring/zip | FastAPI route order — /zip must be BEFORE /{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 for historical issues
- UI charts in stats modal — live 1s refresh visualization
8. Known limitations
- Auto-paginate max 20 pages — if user requests
resultSize=50 and sunt 2000 tururi, se opresc la 1000 (20 × 50)
- POLL_HISTORY in-memory — lost on receiver restart
- RAW_RING in-memory — same
- SQLite Reports DB — single-writer; for concurrent receivers requires PostgreSQL
- Single book click fallback — DOM click; direct fetch is disabled by design (risky)
- CSRF generated dynamically at mock — token expires on container restart
9. Glossary
- Tour / WorkOpportunity (WO) — an Amazon Relay transport job
- Pool — set of active tours in receiver memory
- Grace period — TTL after which an unseen tour is removed from the pool
- Dispatcher — any WS client consuming broadcasts (UI, agent, etc)
- Capture — a response
/api/loadboard/search intercepted
- Passive — capture comes from the page's own Amazon fetch
- Active — capture comes from the extension's scheduled trigger
- Round complete — response with
nextItemToken=0 (last page of a sequence)
- Fast Book — buton injectat de extensie on each card de tur pentru book one-click
- Simulation mode — dry-run: highlight + sound, no real click on Reservar
10. Backups
All major changes have backups in /var/www/booker-admin/backups/:
relay_stats_20260525_020909/ — before adding charts + raw ring + ZIP export
For full rollback: copy from backup in place.