103 lines
3.5 KiB
Python
Executable File
103 lines
3.5 KiB
Python
Executable File
"""
|
|
Minimal browser manager for DDMA - only handles persistent profile and keeping browser alive.
|
|
Does NOT modify any login/OTP logic.
|
|
"""
|
|
import os
|
|
import threading
|
|
from selenium import webdriver
|
|
from selenium.webdriver.chrome.service import Service
|
|
from webdriver_manager.chrome import ChromeDriverManager
|
|
|
|
|
|
class DDMABrowserManager:
|
|
"""
|
|
Singleton that manages a persistent Chrome browser instance.
|
|
- Uses --user-data-dir for persistent profile (device trust tokens, cookies)
|
|
- Keeps browser alive between patient runs
|
|
"""
|
|
_instance = None
|
|
_lock = threading.Lock()
|
|
|
|
def __new__(cls):
|
|
with cls._lock:
|
|
if cls._instance is None:
|
|
cls._instance = super().__new__(cls)
|
|
cls._instance._driver = None
|
|
cls._instance.profile_dir = os.path.abspath("chrome_profile_ddma")
|
|
cls._instance.download_dir = os.path.abspath("seleniumDownloads")
|
|
os.makedirs(cls._instance.profile_dir, exist_ok=True)
|
|
os.makedirs(cls._instance.download_dir, exist_ok=True)
|
|
return cls._instance
|
|
|
|
def get_driver(self, headless=False):
|
|
"""Get or create the persistent browser instance."""
|
|
with self._lock:
|
|
if self._driver is None:
|
|
print("[BrowserManager] Driver is None, creating new driver")
|
|
self._create_driver(headless)
|
|
elif not self._is_alive():
|
|
print("[BrowserManager] Driver not alive, recreating")
|
|
self._create_driver(headless)
|
|
else:
|
|
print("[BrowserManager] Reusing existing driver")
|
|
return self._driver
|
|
|
|
def _is_alive(self):
|
|
"""Check if browser is still responsive."""
|
|
try:
|
|
url = self._driver.current_url
|
|
print(f"[BrowserManager] Driver alive, current URL: {url[:50]}...")
|
|
return True
|
|
except Exception as e:
|
|
print(f"[BrowserManager] Driver not alive: {e}")
|
|
return False
|
|
|
|
def _create_driver(self, headless=False):
|
|
"""Create browser with persistent profile."""
|
|
if self._driver:
|
|
try:
|
|
self._driver.quit()
|
|
except:
|
|
pass
|
|
|
|
options = webdriver.ChromeOptions()
|
|
if headless:
|
|
options.add_argument("--headless")
|
|
|
|
# Persistent profile - THIS IS THE KEY for device trust
|
|
options.add_argument(f"--user-data-dir={self.profile_dir}")
|
|
options.add_argument("--no-sandbox")
|
|
options.add_argument("--disable-dev-shm-usage")
|
|
|
|
prefs = {
|
|
"download.default_directory": self.download_dir,
|
|
"plugins.always_open_pdf_externally": True,
|
|
"download.prompt_for_download": False,
|
|
"download.directory_upgrade": True
|
|
}
|
|
options.add_experimental_option("prefs", prefs)
|
|
|
|
service = Service(ChromeDriverManager().install())
|
|
self._driver = webdriver.Chrome(service=service, options=options)
|
|
self._driver.maximize_window()
|
|
|
|
def quit_driver(self):
|
|
"""Quit browser (only call on shutdown)."""
|
|
with self._lock:
|
|
if self._driver:
|
|
try:
|
|
self._driver.quit()
|
|
except:
|
|
pass
|
|
self._driver = None
|
|
|
|
|
|
# Singleton accessor
|
|
_manager = None
|
|
|
|
def get_browser_manager():
|
|
global _manager
|
|
if _manager is None:
|
|
_manager = DDMABrowserManager()
|
|
return _manager
|