File size: 3,755 Bytes
4f95823
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
"""
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
        ]