bump pymd3

This commit is contained in:
2026-04-04 22:30:27 -04:00
parent 94cb2441e2
commit 5dbbeb3394
4 changed files with 137 additions and 112 deletions

View File

@@ -11,16 +11,17 @@ import random
import math
import socketio
import httpx
from contextlib import asynccontextmanager, suppress
from contextlib import suppress
from typing import Optional, Dict
from dotenv import load_dotenv
with warnings.catch_warnings():
# Ignore: "Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater."
warnings.simplefilter("ignore", category=UserWarning)
warnings.simplefilter("ignore", category=UserWarning)
import fastapi
import uvicorn
from fastapi import FastAPI, APIRouter, Request
from fastapi import FastAPI, APIRouter, Request, Response
from fastapi.encoders import jsonable_encoder
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
@@ -140,16 +141,6 @@ class TunneldRunnerSio:
usbmux_monitor: bool = True,
mobdev2_monitor: bool = True,
) -> None:
# instance = cls(
# host,
# port,
# protocol=protocol,
# usb_monitor=usb_monitor,
# wifi_monitor=wifi_monitor,
# usbmux_monitor=usbmux_monitor,
# mobdev2_monitor=mobdev2_monitor,
# context=context,)
# asyncio.run(instance._run_app())
cls(
host,
port,
@@ -158,7 +149,8 @@ class TunneldRunnerSio:
wifi_monitor=wifi_monitor,
usbmux_monitor=usbmux_monitor,
mobdev2_monitor=mobdev2_monitor,
context=context)._run_app()
context=context,
)._run_app()
def __init__(
self,
@@ -171,11 +163,12 @@ class TunneldRunnerSio:
usbmux_monitor: bool = True,
mobdev2_monitor: bool = True,
):
@asynccontextmanager
async def lifespan(app: FastAPI):
async def app_startup() -> None:
logger.info("Application startup: starting tunneld core and watcher")
self._tunneld_core.start()
await start_tunnel_watcher()
yield
async def app_shutdown() -> None:
logger.info("Closing tunneld tasks...")
await end_tunnel_watcher()
await end_icloud_monitor()
@@ -195,10 +188,14 @@ class TunneldRunnerSio:
self._vue_dist = os.getenv("VUE_DIST")
self._vue_app = FastAPI(
title="iOS Device Management API",
lifespan=lifespan,
cors_allowed_origins="*",
)
self._asgi_app = socketio.ASGIApp(self.context.sio, self._vue_app)
self._asgi_app = socketio.ASGIApp(
self.context.sio,
self._vue_app,
on_startup=app_startup,
on_shutdown=app_shutdown,
)
self.context.icloud_monitor.sio = self.context.sio
self.context.icloud_monitor.get_client_sids = lambda: list(
self.context.connected_clients
@@ -347,60 +344,84 @@ class TunneldRunnerSio:
async def tunnel_watcher_loop() -> None:
previous = collect_active_tunnels()
while True:
await asyncio.sleep(1)
current = collect_active_tunnels()
added_keys = set(current.keys()) - set(previous.keys())
removed_keys = set(previous.keys()) - set(current.keys())
added = [current[k] for k in sorted(added_keys)]
removed = [previous[k] for k in sorted(removed_keys)]
for item in added:
logger.info(
"Tunnel discovered interface=%s udid=%s address=%s port=%s transport=%s",
item.get("interface"),
item.get("udid"),
item.get("address"),
item.get("port"),
item.get("transport"),
)
await safe_sio_emit("tunnel_device_connected", item)
logger.info(f"Current udid: %s, new udid: %s",
self.context.udid, item.get("udid"))
if self.context.udid is None:
self.context.udid = item.get("udid")
device_name = await get_device_name()
if device_name != self.context.device_name:
self._tunneld_core.cancel(udid=item.get("udid"))
logger.warning(
"Tunnel established to wrong device. Dropping tunnel. wrong_udid=%s for device: %s",
self.context.udid, self.context.device_name)
self.context.udid = None
await safe_sio_emit(
"appError",
{
"type": "tunnel_wrong_device",
"message": "Tunnel established to wrong device. Dropping tunnel.",
"wrong_udid": self.context.udid,
"device_name": device_name,
},
)
await get_tun(item.get("udid"), max_retries=10, retry_delay=1.0)
try:
await asyncio.sleep(1)
current = collect_active_tunnels()
added_keys = set(current.keys()) - set(previous.keys())
removed_keys = set(previous.keys()) - set(current.keys())
added = [current[k] for k in sorted(added_keys)]
removed = [previous[k] for k in sorted(removed_keys)]
for item in added:
try:
logger.info(
"Tunnel discovered interface=%s udid=%s address=%s port=%s transport=%s",
item.get("interface"),
item.get("udid"),
item.get("address"),
item.get("port"),
item.get("transport"),
)
await safe_sio_emit("tunnel_device_connected", item)
logger.info(f"Current udid: %s, new udid: %s",
self.context.udid, item.get("udid"))
if self.context.udid is None:
self.context.udid = item.get("udid")
device_name = await get_device_name()
selected_device_name = self.context.device_name
if selected_device_name and device_name != selected_device_name:
wrong_udid = self.context.udid
self._tunneld_core.cancel(udid=item.get("udid"))
logger.warning(
"Tunnel established to wrong device. Dropping tunnel. wrong_udid=%s for device: %s",
wrong_udid, selected_device_name)
self.context.udid = None
await safe_sio_emit(
"appError",
{
"type": "tunnel_wrong_device",
"message": "Tunnel established to wrong device. Dropping tunnel.",
"wrong_udid": wrong_udid,
"device_name": device_name,
},
)
await get_tun(item.get("udid"), max_retries=10, retry_delay=1.0)
except Exception:
logger.exception(
"Tunnel watcher failed while handling added tunnel for udid=%s",
item.get("udid"),
)
if removed:
for item in removed:
logger.warning(
"Tunnel disconnected interface=%s udid=%s address=%s port=%s",
item.get("interface"),
item.get("udid"),
item.get("address"),
item.get("port"),
)
await safe_sio_emit("tunnel_device_disconnected", item)
await handle_tunnel_drop(removed)
if removed:
for item in removed:
try:
logger.warning(
"Tunnel disconnected interface=%s udid=%s address=%s port=%s",
item.get("interface"),
item.get("udid"),
item.get("address"),
item.get("port"),
)
await safe_sio_emit("tunnel_device_disconnected", item)
except Exception:
logger.exception(
"Tunnel watcher failed while emitting disconnection for udid=%s",
item.get("udid"),
)
await handle_tunnel_drop(removed)
previous = current
previous = current
except asyncio.CancelledError:
raise
except Exception:
logger.exception("Tunnel watcher loop crashed; continuing")
async def start_tunnel_watcher() -> None:
if self.context.tunnel_watcher_task is None or self.context.tunnel_watcher_task.done():
if self.context.tunnel_watcher_task is not None and self.context.tunnel_watcher_task.done():
with suppress(Exception):
exc = self.context.tunnel_watcher_task.exception()
if exc is not None:
logger.error("Previous tunnel watcher task exited with error: %s", exc)
self.context.tunnel_watcher_task = asyncio.create_task(
tunnel_watcher_loop(),
name="tunnel-task-watcher",
@@ -1303,17 +1324,49 @@ class TunneldRunnerSio:
logger.info("Reverse Geocoded %s to %s", coords, rev_geocode)
return generate_http_response(rev_geocode)
@self._app.post("/proxy/ors/{full_path:path}")
@self._app.get("/ors/status")
async def app_ors_status(request: Request) -> fastapi.Response:
logger.info("request: %s", request)
target_url = "https://ors.intrepidnet.org/ors/v2/status"
async with httpx.AsyncClient() as client:
# Forward the request to the external service
response = await client.get(target_url, params=request.query_params)
# Return the response content and status code back to the original client
return Response(
content=response.content,
status_code=response.status_code,
headers=dict(response.headers)
)
@self._app.get("/ors/health")
async def app_ors_status(request: Request) -> fastapi.Response:
logger.info("request: %s", request)
target_url = "https://ors.intrepidnet.org/ors/v2/health"
async with httpx.AsyncClient() as client:
# Forward the request to the external service
response = await client.get(target_url, params=request.query_params)
# Return the response content and status code back to the original client
return Response(
content=response.content,
status_code=response.status_code,
headers=dict(response.headers)
)
@self._app.post("/ors/proxy/{full_path:path}")
async def app_proxy_ors(full_path: str, request: Request):
logger.info("request: %s", request)
body = await request.body()
body = await request.json()
headers = dict(request.headers)
method = request.method
url = "https://ors.intrepidnet.org/" + full_path
logger.info("body: %s", body)
logger.info("headers: %s", headers)
logger.info("method: %s", method)
logger.info("url: %s", url)
async with httpx.AsyncClient() as client:
response = await client.request(method, url, headers=headers, content=body)
return response.content
response = await client.request(method, url, json=body)
return response.json()
""" Socket.IO Functions"""