"""Optional P2P phone pairing plug-in for hub.py.""" import asyncio import os from pathlib import Path from fastapi import Response from fastapi.responses import JSONResponse from p2p_ws_client import HTTPExporter, connect_saved, create_code, load_room, save_room def install(app, *, web_port, token, here): signal = os.environ.get("GA_P2P_SIGNAL", "ws://47.101.182.29:49157/ws") name = os.environ.get("GA_P2P_NAME", "ga-hub-phone") rooms = Path(os.environ.get("GA_P2P_ROOMS", "~/.p2p_ws/rooms.json")).expanduser() state = {"task": None, "invite": None, "status": "idle", "error": None, "started": False} app.state.p2p_pair_open = True async def export(ws): exporter = await HTTPExporter( ws, f"http://127.0.0.1:{web_port}", allow=("/api/",), query={"t": token}, ).start() try: state["status"], state["error"] = "connected", None await exporter.wait_closed() finally: await exporter.close() async def reconnect(): while True: try: state["status"], state["error"] = "reconnecting", None ws = await connect_saved(name, path=rooms, direct_timeout=15) try: await export(ws) finally: await ws.close() # 不关会泄漏信令连接: 旧连接与新连接自配对占满房间 except asyncio.CancelledError: raise except Exception as exc: state["status"], state["error"] = "error", str(exc) await asyncio.sleep(5) async def new_pair(): try: invite = state["invite"] = await create_code( signal, name=name + "-pending", rooms_file=rooms, ) state["status"], state["error"] = "waiting", None ws = await invite.connect(direct_timeout=15) save_room(signal, invite.room, name=name, path=rooms) try: await export(ws) finally: await ws.close() await reconnect() except asyncio.CancelledError: raise except Exception as exc: state["status"], state["error"] = "error", str(exc) @app.get("/pair") async def pair(): task = state["task"] if task is None or task.done(): try: load_room(name, path=rooms) except KeyError: state["task"] = asyncio.create_task(new_pair()) else: state["task"] = asyncio.create_task(reconnect()) return Response(content='''
Enter this code on the phone:
waiting...
starting...
''', media_type="text/html") @app.get("/pair/status") async def pair_status(): invite = state["invite"] return JSONResponse({ "status": state["status"], "code": getattr(invite, "code", None), "expires_at": getattr(invite, "expires_at", None), "error": state["error"], }) async def startup(): if state["started"]: return state["started"] = True try: load_room(name, path=rooms) except KeyError: return state["task"] = asyncio.create_task(reconnect()) app.add_event_handler("startup", startup) return state