fix(accounts): keep first login launch in one window

This commit is contained in:
chengma
2026-07-14 17:22:52 +08:00
parent a4ac3f8ee5
commit 699625ea0e
10 changed files with 608 additions and 60 deletions
+159 -22
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
import os
import time
from datetime import datetime
from typing import Optional
from urllib.parse import urlparse
@@ -12,6 +13,15 @@ from . import config as account_config
DEFAULT_REGION_HOST = editor.DEFAULT_REGION_HOST
INITIAL_LOGIN_TAB_WAIT_SECONDS = 2.0
INITIAL_LOGIN_TAB_POLL_SECONDS = 0.1
SAFE_STARTUP_PAGE_PREFIXES = (
"about:blank",
"chrome://newtab",
"chrome://new-tab-page",
"chrome://welcome",
"chrome://intro",
)
class AccountError(RuntimeError):
@@ -222,13 +232,19 @@ def _seller_portal_url(account) -> str:
return f"https://{normalize_region_host(_account_value(account, 'region_host'))}/portal/"
def _find_login_tab(account, host):
def _page_tabs(host):
return [
tab
for tab in cdp.http_get("/json", host=host)
if tab.get("type") == "page"
]
def _find_login_tab_in_tabs(account, tabs):
region_host = normalize_region_host(_account_value(account, 'region_host')).lower()
seller_tabs = []
login_tabs = []
for tab in cdp.http_get("/json", host=host):
if tab.get("type") != "page":
continue
for tab in tabs:
url = str(tab.get("url") or "")
lower_url = url.lower()
if region_host in lower_url:
@@ -238,38 +254,157 @@ def _find_login_tab(account, host):
return (seller_tabs or login_tabs or [None])[0]
def _open_or_activate_login_tab(account):
host = _debug_host(account)
portal_url = _seller_portal_url(account)
tab = _find_login_tab(account, host)
if tab:
target_id = tab.get("id")
if target_id:
cdp.activate_tab(target_id, host=host)
return {
"target_id": target_id,
"url": tab.get("url") or portal_url,
"created_tab": False,
}
def _find_login_tab(account, host):
return _find_login_tab_in_tabs(account, _page_tabs(host))
tab = cdp.create_tab_info(portal_url, host=host)
def _wait_for_initial_login_tab(account, host, timeout=INITIAL_LOGIN_TAB_WAIT_SECONDS):
timeout = max(0.0, float(timeout))
deadline = time.monotonic() + timeout
last_tabs = []
probe_error = None
first_probe = True
while first_probe or time.monotonic() < deadline:
first_probe = False
try:
last_tabs = _page_tabs(host)
probe_error = None
except Exception as exc:
probe_error = exc.__class__.__name__
tab = _find_login_tab_in_tabs(account, last_tabs)
if tab:
return tab, last_tabs, probe_error
remaining = deadline - time.monotonic()
if remaining <= 0:
break
time.sleep(min(INITIAL_LOGIN_TAB_POLL_SECONDS, remaining))
return None, last_tabs, probe_error
def _safe_startup_page(tabs):
if len(tabs) != 1:
return None
tab = tabs[0]
url = str(tab.get("url") or "").strip().lower()
if not url or any(url.startswith(prefix) for prefix in SAFE_STARTUP_PAGE_PREFIXES):
return tab
return None
def _navigate_page_target(tab, url):
websocket_url = tab.get("webSocketDebuggerUrl")
if not websocket_url:
raise AccountError("Chrome 初始页面缺少 CDP 地址,无法复用")
client = cdp.CDP(websocket_url)
try:
result = client.send("Page.navigate", {"url": url})
finally:
client.close()
if result.get("errorText"):
raise AccountError("Chrome 初始页面导航到蝦皮卖家中心失败")
def _activate_login_tab_result(
tab,
host,
portal_url,
*,
created_tab=False,
startup_target_reused=False,
startup_page_navigated=False,
page_target_count=0,
fallback_reason=None,
target_probe_error=None,
):
target_id = tab.get("id")
if target_id:
cdp.activate_tab(target_id, host=host)
return {
"target_id": target_id,
"url": tab.get("url") or portal_url,
"created_tab": True,
"created_tab": bool(created_tab),
"startup_target_reused": bool(startup_target_reused),
"startup_page_navigated": bool(startup_page_navigated),
"page_target_count": int(page_target_count),
"fallback_reason": fallback_reason,
"target_probe_error": target_probe_error,
}
def _open_or_activate_login_tab(
account,
*,
newly_launched=False,
startup_wait_timeout=INITIAL_LOGIN_TAB_WAIT_SECONDS,
):
host = _debug_host(account)
portal_url = _seller_portal_url(account)
if newly_launched:
tab, tabs, probe_error = _wait_for_initial_login_tab(
account,
host,
timeout=startup_wait_timeout,
)
else:
tabs = _page_tabs(host)
tab = _find_login_tab_in_tabs(account, tabs)
probe_error = None
if tab:
return _activate_login_tab_result(
tab,
host,
portal_url,
startup_target_reused=newly_launched,
page_target_count=len(tabs),
target_probe_error=probe_error,
)
fallback_reason = "existing_chrome_no_login_tab"
if newly_launched:
startup_tab = _safe_startup_page(tabs)
if startup_tab:
try:
_navigate_page_target(startup_tab, portal_url)
except Exception as exc:
fallback_reason = "startup_navigation_failed"
probe_error = probe_error or exc.__class__.__name__
else:
navigated_tab = dict(startup_tab)
navigated_tab["url"] = portal_url
return _activate_login_tab_result(
navigated_tab,
host,
portal_url,
startup_target_reused=True,
startup_page_navigated=True,
page_target_count=len(tabs),
target_probe_error=probe_error,
)
elif tabs:
fallback_reason = "startup_page_not_safe"
else:
fallback_reason = "startup_page_missing"
tab = cdp.create_tab_info(portal_url, host=host)
return _activate_login_tab_result(
tab,
host,
portal_url,
created_tab=True,
page_target_count=len(tabs) + 1,
fallback_reason=fallback_reason,
target_probe_error=probe_error,
)
def launch_for_login(account_or_alias, path=None, config=None):
account = resolve_account(account_or_alias, path=path, config=config)
port = normalize_debug_port(_account_value(account, "debug_port"))
alias = _account_value(account, "alias")
portal_url = _seller_portal_url(account)
if chrome.is_running(port):
tab = _open_or_activate_login_tab(account)
tab = _open_or_activate_login_tab(account, newly_launched=False)
return {
"ok": True,
"action": "reused",
@@ -278,14 +413,15 @@ def launch_for_login(account_or_alias, path=None, config=None):
"alias": alias,
"debug_port": port,
"pid": None,
"initial_url": portal_url,
**tab,
}
process = chrome.launch_chrome(account, config=config)
process = chrome.launch_chrome(account, config=config, initial_url=portal_url)
timeout = appconfig.cdp_ready_timeout(config)
if not chrome.wait_debug_ready(port, timeout=timeout):
raise AccountError(f"Chrome 已启动,但 CDP 端口 {port} 未在 {timeout} 秒内就绪")
tab = _open_or_activate_login_tab(account)
tab = _open_or_activate_login_tab(account, newly_launched=True)
return {
"ok": True,
"action": "launched",
@@ -294,6 +430,7 @@ def launch_for_login(account_or_alias, path=None, config=None):
"alias": alias,
"debug_port": port,
"pid": getattr(process, "pid", None),
"initial_url": portal_url,
**tab,
}