""" IoT Gateway — HTTP reverse proxy for ESP32 web servers running in QEMU. When an ESP32 sketch starts a WebServer on port 80, QEMU's slirp networking with hostfwd exposes it on a dynamic host port. This endpoint proxies HTTP requests from the browser to that host port, enabling users to interact with their simulated ESP32 HTTP server. URL pattern: /api/gateway/{client_id}/{path} → http://127.0.0.1:{hostfwd_port}/{path} """ import json import logging import httpx from fastapi import APIRouter, Request, Response from app.core.hooks import iot_gateway_gate from app.services.esp32_lib_manager import esp_lib_manager router = APIRouter() logger = logging.getLogger(__name__) @router.api_route( '/{client_id}/{path:path}', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'], ) async def gateway_proxy(client_id: str, path: str, request: Request) -> Response: """Reverse-proxy an HTTP request to the ESP32's web server.""" # Plan gate (overlay-supplied). OSS image has no gate → allow everyone. # When the velxio-prod overlay is loaded, the gateway is a Maker+ feature; # free / anonymous callers get a 402 with an upgrade pointer. block_detail = await iot_gateway_gate(request) if block_detail is not None: # The frontend opens the gateway via window.open(_blank), so a raw # JSON 402 would dump in a new tab. Content-negotiate: serve a tiny # HTML upgrade page to browser navigations, JSON to programmatic # (fetch/XHR) callers. accepts_html = 'text/html' in (request.headers.get('accept') or '') upgrade_url = block_detail.get('upgrade_url', '/pricing') msg = block_detail.get('message', 'This is a paid feature.') if accepts_html: html = ( '
' '{msg} Upgrade to access live ESP32 web servers running in your ' 'simulated circuit.
' f'See plans' '