Browser-back / keepalive.py
Nrighton233j
Add keep-alive session pinging (scoped, capped)
4f95823
Raw
History Blame Contribute Delete
3.76 kB
"""
keepalive.py β€” server-side "Keep Awake" for B24 Browser.
SCOPE (read this before wiring it up):
This pings a URL periodically from the backend, so a tracked tab stays
"active" even while the user's phone is asleep or the app is backgrounded
or fully closed β€” as long as this backend itself keeps running (note: a
free HF Space can also idle-sleep, so this isn't infinite either).
What this does NOT do: it does not simulate mouse movement, keystrokes,
or run the page's real JavaScript. It's a plain periodic HTTP request.
That's enough for many simple session-timeout mechanisms (dashboards,
admin panels, some web tools) but will NOT fool sites that check real
interaction via WebSocket heartbeats or JS-side activity checks β€” those
need a full headless browser per session, which isn't safe to run at
scale on limited RAM, and many such platforms (e.g. hosted notebook /
compute services) explicitly prohibit keep-alive workarounds in their
ToS. Keep this scoped to "don't lose my session on a page I'm using
while I multitask", not "trick any site indefinitely" β€” and maybe warn
users in the UI if a URL looks like a known compute/notebook host.
"""
import time
import threading
import requests
MAX_SESSIONS_PER_USER = 3
MAX_DURATION_SECONDS = 6 * 60 * 60 # 6h safety cap, then auto-stop
MIN_INTERVAL_SECONDS = 30
_sessions = {} # session_id -> dict(user_id, url, interval, started_at, stop_event, thread)
_lock = threading.Lock()
def _run(session_id, url, interval, headers, stop_event, started_at):
while not stop_event.is_set():
if time.time() - started_at > MAX_DURATION_SECONDS:
break
try:
requests.get(url, headers=headers or {}, timeout=10)
except requests.RequestException:
pass # transient failure β€” just try again next tick
stop_event.wait(interval)
with _lock:
_sessions.pop(session_id, None)
def start(user_id: str, url: str, interval: int = 60, headers: dict = None):
"""Begin pinging `url` every `interval` seconds on behalf of user_id."""
interval = max(interval, MIN_INTERVAL_SECONDS)
with _lock:
active_for_user = [s for s in _sessions.values() if s["user_id"] == user_id]
if len(active_for_user) >= MAX_SESSIONS_PER_USER:
return {"error": f"max {MAX_SESSIONS_PER_USER} keep-alive sessions per user"}
session_id = f"{user_id}:{int(time.time() * 1000)}"
stop_event = threading.Event()
thread = threading.Thread(
target=_run,
args=(session_id, url, interval, headers, stop_event, time.time()),
daemon=True,
)
_sessions[session_id] = {
"user_id": user_id,
"url": url,
"interval": interval,
"started_at": time.time(),
"stop_event": stop_event,
"thread": thread,
}
thread.start()
return {"session_id": session_id, "interval": interval, "max_duration": MAX_DURATION_SECONDS}
def stop(user_id: str, session_id: str):
with _lock:
session = _sessions.get(session_id)
if not session or session["user_id"] != user_id:
return {"error": "session not found"}
session["stop_event"].set()
return {"stopped": True}
def status(user_id: str):
with _lock:
return [
{
"session_id": sid,
"url": s["url"],
"interval": s["interval"],
"elapsed_seconds": int(time.time() - s["started_at"]),
"remaining_seconds": max(0, MAX_DURATION_SECONDS - int(time.time() - s["started_at"])),
}
for sid, s in _sessions.items()
if s["user_id"] == user_id
]