mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-24 05:22:22 +02:00
90 lines
3.3 KiB
Python
90 lines
3.3 KiB
Python
"""Borrow the user's live browser session (cookies + UA) for a domain.
|
|
|
|
Shared by every social MCP shim (reddit/x/tiktok). The shim never stores
|
|
credentials. It asks the backend's browser-session bridge (gated by the same
|
|
per-install token every OpenSwarm shim uses) for the cookies the user's own
|
|
logged-in browser already holds in the persist:openswarm-browser partition, then
|
|
talks to the site as that browser. stdlib-only so the subprocess starts fast.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import time
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
from typing import Dict, Tuple
|
|
|
|
BACKEND_PORT = os.environ.get("OPENSWARM_PORT", "8324")
|
|
AUTH_TOKEN = os.environ.get("OPENSWARM_AUTH_TOKEN", "")
|
|
BRIDGE_URL = f"http://127.0.0.1:{BACKEND_PORT}/api/browser-session/cookies"
|
|
CACHE_TTL_S = 60.0
|
|
|
|
# Fallback only; the bridge returns the real spoofed Chrome UA the webview uses.
|
|
DEFAULT_UA = (
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
|
|
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
|
)
|
|
|
|
p_cache: Dict[str, Tuple[float, str, str]] = {}
|
|
|
|
|
|
class SessionUnavailable(Exception):
|
|
"""No live logged-in session could be borrowed for the domain."""
|
|
|
|
|
|
def get_session(domain: str) -> Tuple[str, str]:
|
|
"""Return (cookie_header, user_agent) for domain from the live browser session.
|
|
|
|
Raises SessionUnavailable with a human-actionable message when the bridge is
|
|
unreachable or the user isn't logged in (no cookies for the domain).
|
|
"""
|
|
now = time.time()
|
|
hit = p_cache.get(domain)
|
|
if hit and now - hit[0] < CACHE_TTL_S:
|
|
return hit[1], hit[2]
|
|
|
|
url = BRIDGE_URL + "?" + urllib.parse.urlencode({"domain": domain})
|
|
headers = {"Accept": "application/json"}
|
|
if AUTH_TOKEN:
|
|
headers["Authorization"] = f"Bearer {AUTH_TOKEN}"
|
|
req = urllib.request.Request(url, headers=headers, method="GET")
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=20.0) as resp:
|
|
data = json.loads(resp.read().decode("utf-8", errors="replace") or "{}")
|
|
except urllib.error.HTTPError as e:
|
|
raise SessionUnavailable(f"Session bridge error (HTTP {e.code}); is the OpenSwarm dashboard open?")
|
|
except urllib.error.URLError as e:
|
|
raise SessionUnavailable(f"Session bridge unreachable: {getattr(e, 'reason', e)}")
|
|
except Exception as e:
|
|
raise SessionUnavailable(f"Session bridge request failed: {e!r}")
|
|
|
|
if data.get("error"):
|
|
raise SessionUnavailable(str(data["error"]))
|
|
cookies = data.get("cookies") or []
|
|
if not cookies:
|
|
raise SessionUnavailable(
|
|
f"Not logged in to {domain}. Open {domain} in the OpenSwarm browser, sign in, then retry."
|
|
)
|
|
cookie_header = "; ".join(
|
|
f"{c['name']}={c['value']}" for c in cookies if c.get("name")
|
|
)
|
|
user_agent = data.get("userAgent") or DEFAULT_UA
|
|
p_cache[domain] = (now, cookie_header, user_agent)
|
|
return cookie_header, user_agent
|
|
|
|
|
|
def cookie_value(domain: str, name: str) -> str:
|
|
"""Pull a single cookie's value from the borrowed session (e.g. x's ct0 CSRF token)."""
|
|
cookie_header, _ = get_session(domain)
|
|
for pair in cookie_header.split(";"):
|
|
k, _, v = pair.strip().partition("=")
|
|
if k == name:
|
|
return v
|
|
return ""
|
|
|
|
|
|
def invalidate(domain: str) -> None:
|
|
"""Drop the cached session so the next call re-borrows fresh cookies."""
|
|
p_cache.pop(domain, None)
|