File size: 7,043 Bytes
a5784e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# --- browser_utils/cookie_refresh.py ---
"""
Cookie Refresh Module

Provides functionality to automatically refresh and persist browser cookies
back to the auth profile files, keeping them up-to-date during runtime.

Features:
- Periodic background refresh
- On-demand refresh after successful API requests
- Graceful shutdown save
"""

import asyncio
import json
import logging
import os
import time
from typing import Optional

from config.settings import (
    COOKIE_REFRESH_ENABLED,
    COOKIE_REFRESH_INTERVAL_SECONDS,
    COOKIE_REFRESH_ON_REQUEST_ENABLED,
    COOKIE_REFRESH_ON_SHUTDOWN,
    COOKIE_REFRESH_REQUEST_INTERVAL,
)

logger = logging.getLogger("CookieRefresh")

# Module-level state
_last_refresh_time: float = 0
_request_count_since_refresh: int = 0
_refresh_lock = asyncio.Lock()
_periodic_task: Optional[asyncio.Task] = None


async def save_current_cookies_to_profile() -> bool:
    """
    Save the current browser cookies back to the active auth profile file.

    Returns:
        True if cookies were saved successfully, False otherwise.
    """
    global _last_refresh_time

    if not COOKIE_REFRESH_ENABLED:
        logger.debug("Cookie refresh is disabled, skipping save")
        return False

    try:
        from api_utils.server_state import state

        # Check if we have a valid page instance
        if not state.page_instance or state.page_instance.is_closed():
            logger.debug("No active page instance, skipping cookie save")
            return False

        # Get the current auth profile path
        profile_path = state.current_auth_profile_path
        if not profile_path:
            profile_path = os.environ.get("ACTIVE_AUTH_JSON_PATH")

        if not profile_path or not os.path.exists(profile_path):
            logger.debug(f"No valid auth profile path found: {profile_path}")
            return False

        # Use lock to prevent concurrent saves
        async with _refresh_lock:
            context = state.page_instance.context

            # Get current storage state (cookies + origins/localStorage)
            storage_state = await context.storage_state()

            # Read existing profile to preserve any custom data
            try:
                with open(profile_path, "r", encoding="utf-8") as f:
                    existing_data = json.load(f)
            except (json.JSONDecodeError, OSError):
                existing_data = {}

            # Update cookies and origins
            existing_data["cookies"] = storage_state.get("cookies", [])
            existing_data["origins"] = storage_state.get("origins", [])

            # Write back to file
            with open(profile_path, "w", encoding="utf-8") as f:
                json.dump(existing_data, f, indent=2, ensure_ascii=False)

            _last_refresh_time = time.time()
            cookie_count = len(storage_state.get("cookies", []))
            logger.info(
                f"🍪 Cookies saved to '{os.path.basename(profile_path)}' "
                f"({cookie_count} cookies)"
            )
            return True

    except asyncio.CancelledError:
        raise
    except Exception as e:
        logger.error(f"Failed to save cookies: {e}")
        return False


async def maybe_refresh_on_request() -> bool:
    """
    Called after successful API requests. Saves cookies if enough requests
    have been processed since the last save.

    Returns:
        True if cookies were saved, False otherwise.
    """
    global _request_count_since_refresh

    if not COOKIE_REFRESH_ON_REQUEST_ENABLED:
        return False

    _request_count_since_refresh += 1

    if _request_count_since_refresh >= COOKIE_REFRESH_REQUEST_INTERVAL:
        _request_count_since_refresh = 0
        logger.debug(
            f"Request-based cookie refresh triggered "
            f"(every {COOKIE_REFRESH_REQUEST_INTERVAL} requests)"
        )
        return await save_current_cookies_to_profile()

    return False


async def save_cookies_on_shutdown() -> bool:
    """
    Save cookies during graceful shutdown.

    Returns:
        True if cookies were saved successfully, False otherwise.
    """
    if not COOKIE_REFRESH_ON_SHUTDOWN:
        logger.debug("Cookie save on shutdown is disabled")
        return False

    logger.info("💾 Saving cookies before shutdown...")
    return await save_current_cookies_to_profile()


async def _periodic_refresh_loop():
    """
    Background task that periodically saves cookies.
    """
    global _last_refresh_time

    logger.info(
        f"🔄 Periodic cookie refresh started "
        f"(interval: {COOKIE_REFRESH_INTERVAL_SECONDS}s)"
    )

    # Initial delay before first save
    await asyncio.sleep(COOKIE_REFRESH_INTERVAL_SECONDS)

    while True:
        try:
            elapsed = time.time() - _last_refresh_time
            if elapsed >= COOKIE_REFRESH_INTERVAL_SECONDS:
                logger.debug("Periodic cookie refresh triggered")
                await save_current_cookies_to_profile()

            # Sleep until next check
            await asyncio.sleep(min(60, COOKIE_REFRESH_INTERVAL_SECONDS // 2))

        except asyncio.CancelledError:
            logger.info("🔄 Periodic cookie refresh task cancelled")
            raise
        except Exception as e:
            logger.error(f"Error in periodic cookie refresh: {e}")
            # Continue running despite errors
            await asyncio.sleep(60)


def start_periodic_refresh() -> Optional[asyncio.Task]:
    """
    Start the periodic cookie refresh background task.

    Returns:
        The created asyncio task, or None if refresh is disabled.
    """
    global _periodic_task

    if not COOKIE_REFRESH_ENABLED:
        logger.info("Cookie refresh is disabled, not starting periodic task")
        return None

    if _periodic_task is not None and not _periodic_task.done():
        logger.warning("Periodic refresh task already running")
        return _periodic_task

    _periodic_task = asyncio.create_task(_periodic_refresh_loop())
    return _periodic_task


async def stop_periodic_refresh():
    """
    Stop the periodic cookie refresh background task.
    """
    global _periodic_task

    if _periodic_task is not None and not _periodic_task.done():
        _periodic_task.cancel()
        try:
            await _periodic_task
        except asyncio.CancelledError:
            pass
        _periodic_task = None
        logger.info("🔄 Periodic cookie refresh stopped")


def get_refresh_stats() -> dict:
    """
    Get statistics about cookie refresh operations.

    Returns:
        Dict with refresh statistics.
    """
    return {
        "enabled": COOKIE_REFRESH_ENABLED,
        "last_refresh_time": _last_refresh_time,
        "requests_since_refresh": _request_count_since_refresh,
        "refresh_interval_seconds": COOKIE_REFRESH_INTERVAL_SECONDS,
        "request_interval": COOKIE_REFRESH_REQUEST_INTERVAL,
        "periodic_task_running": _periodic_task is not None
        and not _periodic_task.done(),
    }