chrome-devtools-mcp-for-extension 0.23.4 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,15 +5,18 @@
5
5
  */
6
6
  /**
7
7
  * Login status enum for state machine
8
+ * Based on ChatGPT/Gemini recommendations
8
9
  */
9
10
  export var LoginStatus;
10
11
  (function (LoginStatus) {
11
12
  LoginStatus["LOGGED_IN"] = "LOGGED_IN";
12
13
  LoginStatus["NEEDS_LOGIN"] = "NEEDS_LOGIN";
13
14
  LoginStatus["IN_PROGRESS"] = "IN_PROGRESS";
15
+ LoginStatus["BLOCKED"] = "BLOCKED";
14
16
  })(LoginStatus || (LoginStatus = {}));
15
17
  /**
16
18
  * Multi-language ARIA label patterns for Gemini profile button detection
19
+ * Extended based on Gemini's recommendation to add PT/RU/AR
17
20
  */
18
21
  const GEMINI_PROFILE_PATTERNS = [
19
22
  'Google Account', // English
@@ -25,30 +28,71 @@ const GEMINI_PROFILE_PATTERNS = [
25
28
  '구글 계정', // Korean
26
29
  '谷歌帐户', // Chinese Simplified
27
30
  'Google 帳戶', // Chinese Traditional
31
+ 'Conta do Google', // Portuguese (Gemini recommendation)
32
+ 'Аккаунт Google', // Russian (Gemini recommendation)
33
+ 'حساب Google', // Arabic (Gemini recommendation)
28
34
  ];
29
35
  /**
30
36
  * Probe ChatGPT session via API endpoint (most reliable method)
31
37
  * Based on ChatGPT's recommendation to use /api/auth/session
38
+ * Improved with stricter validation and HTTP status handling
32
39
  */
33
40
  export async function probeChatGPTSession(page) {
34
41
  try {
35
42
  const result = await page.evaluate(async () => {
36
43
  try {
37
- const r = await fetch('/api/auth/session', { credentials: 'include' });
44
+ // Use AbortController for timeout (ChatGPT recommendation)
45
+ const controller = new AbortController();
46
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
47
+ const r = await fetch('/api/auth/session', {
48
+ credentials: 'include',
49
+ cache: 'no-store',
50
+ signal: controller.signal,
51
+ });
52
+ clearTimeout(timeoutId);
53
+ const contentType = r.headers.get('content-type') || '';
54
+ const isJson = contentType.includes('application/json');
55
+ // Handle non-JSON responses (WAF/interstitial pages)
56
+ if (!isJson) {
57
+ return { ok: false, status: r.status, isHtml: true, error: 'Non-JSON response' };
58
+ }
38
59
  const j = await r.json().catch(() => ({}));
39
- return { ok: true, status: r.status, json: j };
60
+ return { ok: r.ok, status: r.status, json: j };
40
61
  }
41
62
  catch (e) {
42
- return { ok: false, error: String(e) };
63
+ const isAbort = e instanceof Error && e.name === 'AbortError';
64
+ return { ok: false, error: String(e), isTimeout: isAbort };
43
65
  }
44
66
  });
67
+ // Handle timeout
68
+ if (result?.isTimeout) {
69
+ console.error('[login-helper] Session probe timed out');
70
+ return LoginStatus.IN_PROGRESS;
71
+ }
72
+ // Handle HTML response (WAF/blocked)
73
+ if (result?.isHtml) {
74
+ console.error('[login-helper] Session probe returned HTML (possibly blocked)');
75
+ return LoginStatus.BLOCKED;
76
+ }
77
+ // Handle HTTP error statuses (ChatGPT recommendation)
78
+ if (result?.status === 429) {
79
+ console.error('[login-helper] Rate limited (429)');
80
+ return LoginStatus.BLOCKED;
81
+ }
82
+ if (result?.status === 401 || result?.status === 403) {
83
+ console.error(`[login-helper] Auth error (${result.status})`);
84
+ return LoginStatus.NEEDS_LOGIN;
85
+ }
45
86
  if (!result?.ok) {
46
87
  console.error('[login-helper] Session probe failed:', result);
47
88
  return LoginStatus.IN_PROGRESS;
48
89
  }
90
+ // Stricter validation: check for specific fields (ChatGPT recommendation)
49
91
  const j = result.json ?? {};
50
- const loggedIn = j && typeof j === 'object' && Object.keys(j).length > 0;
51
- console.error(`[login-helper] ChatGPT session probe: ${loggedIn ? 'LOGGED_IN' : 'NEEDS_LOGIN'}`);
92
+ const hasUser = j?.user?.id || j?.user?.email;
93
+ const hasToken = j?.accessToken;
94
+ const loggedIn = !!(hasUser || hasToken);
95
+ console.error(`[login-helper] ChatGPT session probe: ${loggedIn ? 'LOGGED_IN' : 'NEEDS_LOGIN'} (user: ${!!hasUser}, token: ${!!hasToken})`);
52
96
  return loggedIn ? LoginStatus.LOGGED_IN : LoginStatus.NEEDS_LOGIN;
53
97
  }
54
98
  catch (error) {
@@ -59,6 +103,7 @@ export async function probeChatGPTSession(page) {
59
103
  /**
60
104
  * Get Gemini login status using ARIA labels (multi-language support)
61
105
  * Based on Gemini's recommendation to use aria-label patterns
106
+ * Added fail-fast Sign In detection (Gemini recommendation)
62
107
  */
63
108
  export async function getGeminiStatus(page) {
64
109
  const url = page.url();
@@ -68,20 +113,51 @@ export async function getGeminiStatus(page) {
68
113
  return LoginStatus.NEEDS_LOGIN;
69
114
  }
70
115
  try {
71
- const profileFound = await page.evaluate((patterns) => {
116
+ const result = await page.evaluate((patterns) => {
117
+ // FAIL-FAST: Check for Sign In link (Gemini recommendation)
118
+ // This is language-agnostic and very reliable
119
+ const signInLink = document.querySelector('a[href*="accounts.google.com/ServiceLogin"]');
120
+ if (signInLink) {
121
+ return { signInPresent: true, profileFound: false };
122
+ }
123
+ // Check for Terms of Service / dialog blocking the UI (Gemini recommendation)
124
+ const hasBlockingDialog = document.querySelector('[role="dialog"]') !== null;
125
+ // Method 1: Check aria-label attribute
72
126
  for (const pattern of patterns) {
73
127
  if (document.querySelector(`button[aria-label*="${pattern}"]`)) {
74
- return true;
128
+ return { signInPresent: false, profileFound: true, hasDialog: hasBlockingDialog };
129
+ }
130
+ }
131
+ // Method 2: Check button text content (Gemini may not use aria-label)
132
+ const buttons = Array.from(document.querySelectorAll('button'));
133
+ for (const pattern of patterns) {
134
+ const found = buttons.some(btn => btn.textContent?.includes(pattern));
135
+ if (found) {
136
+ return { signInPresent: false, profileFound: true, hasDialog: hasBlockingDialog };
75
137
  }
76
138
  }
77
139
  // Fallback: check for nav with chat history (logged-in indicator)
78
140
  if (document.querySelector('nav[aria-label*="Recent"]')) {
79
- return true;
141
+ return { signInPresent: false, profileFound: true, hasDialog: hasBlockingDialog };
80
142
  }
81
- return false;
143
+ // Fallback: check for textbox (user is on main chat page)
144
+ if (document.querySelector('[role="textbox"]')) {
145
+ return { signInPresent: false, profileFound: true, hasDialog: hasBlockingDialog };
146
+ }
147
+ return { signInPresent: false, profileFound: false, hasDialog: hasBlockingDialog };
82
148
  }, GEMINI_PROFILE_PATTERNS);
83
- console.error(`[login-helper] Gemini ARIA check: ${profileFound ? 'LOGGED_IN' : 'NEEDS_LOGIN'}`);
84
- return profileFound ? LoginStatus.LOGGED_IN : LoginStatus.NEEDS_LOGIN;
149
+ // Fail-fast: Sign In link present means not logged in
150
+ if (result.signInPresent) {
151
+ console.error('[login-helper] Gemini: Sign In link detected (fail-fast)');
152
+ return LoginStatus.NEEDS_LOGIN;
153
+ }
154
+ // Logged in but dialog blocking
155
+ if (result.profileFound && result.hasDialog) {
156
+ console.error('[login-helper] Gemini: Logged in but dialog present (Terms of Service?)');
157
+ return LoginStatus.IN_PROGRESS;
158
+ }
159
+ console.error(`[login-helper] Gemini check: ${result.profileFound ? 'LOGGED_IN' : 'NEEDS_LOGIN'}`);
160
+ return result.profileFound ? LoginStatus.LOGGED_IN : LoginStatus.NEEDS_LOGIN;
85
161
  }
86
162
  catch (error) {
87
163
  console.error(`[login-helper] Error checking Gemini status: ${error}`);
@@ -126,7 +202,9 @@ export async function waitForLoginStatus(page, provider, timeoutMs = 120000, onS
126
202
  log(`⏳ まだ待機中... (${elapsed}秒経過)`);
127
203
  }
128
204
  await new Promise((r) => setTimeout(r, delay));
129
- delay = Math.min(3000, Math.floor(delay * 1.5)); // backoff
205
+ // Backoff with jitter (ChatGPT recommendation: ±10-20% randomization)
206
+ const jitter = 0.9 + Math.random() * 0.2; // 0.9 to 1.1
207
+ delay = Math.min(3000, Math.floor(delay * 1.5 * jitter));
130
208
  }
131
209
  log('❌ タイムアウト: 再度お試しください');
132
210
  return LoginStatus.NEEDS_LOGIN;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp-for-extension",
3
- "version": "0.23.4",
3
+ "version": "0.24.0",
4
4
  "description": "MCP server for Chrome extension development with Web Store automation. Fork of chrome-devtools-mcp with extension-specific tools.",
5
5
  "type": "module",
6
6
  "bin": "./scripts/cli.mjs",