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

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.
@@ -3,6 +3,134 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
+ /**
7
+ * Login status enum for state machine
8
+ */
9
+ export var LoginStatus;
10
+ (function (LoginStatus) {
11
+ LoginStatus["LOGGED_IN"] = "LOGGED_IN";
12
+ LoginStatus["NEEDS_LOGIN"] = "NEEDS_LOGIN";
13
+ LoginStatus["IN_PROGRESS"] = "IN_PROGRESS";
14
+ })(LoginStatus || (LoginStatus = {}));
15
+ /**
16
+ * Multi-language ARIA label patterns for Gemini profile button detection
17
+ */
18
+ const GEMINI_PROFILE_PATTERNS = [
19
+ 'Google Account', // English
20
+ 'Google アカウント', // Japanese
21
+ 'Compte Google', // French
22
+ 'Google-Konto', // German
23
+ 'Cuenta de Google', // Spanish
24
+ 'Account Google', // Italian
25
+ '구글 계정', // Korean
26
+ '谷歌帐户', // Chinese Simplified
27
+ 'Google 帳戶', // Chinese Traditional
28
+ ];
29
+ /**
30
+ * Probe ChatGPT session via API endpoint (most reliable method)
31
+ * Based on ChatGPT's recommendation to use /api/auth/session
32
+ */
33
+ export async function probeChatGPTSession(page) {
34
+ try {
35
+ const result = await page.evaluate(async () => {
36
+ try {
37
+ const r = await fetch('/api/auth/session', { credentials: 'include' });
38
+ const j = await r.json().catch(() => ({}));
39
+ return { ok: true, status: r.status, json: j };
40
+ }
41
+ catch (e) {
42
+ return { ok: false, error: String(e) };
43
+ }
44
+ });
45
+ if (!result?.ok) {
46
+ console.error('[login-helper] Session probe failed:', result);
47
+ return LoginStatus.IN_PROGRESS;
48
+ }
49
+ 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'}`);
52
+ return loggedIn ? LoginStatus.LOGGED_IN : LoginStatus.NEEDS_LOGIN;
53
+ }
54
+ catch (error) {
55
+ console.error(`[login-helper] Error probing ChatGPT session: ${error}`);
56
+ return LoginStatus.IN_PROGRESS;
57
+ }
58
+ }
59
+ /**
60
+ * Get Gemini login status using ARIA labels (multi-language support)
61
+ * Based on Gemini's recommendation to use aria-label patterns
62
+ */
63
+ export async function getGeminiStatus(page) {
64
+ const url = page.url();
65
+ // URL-based check (fastest)
66
+ if (url.includes('accounts.google.com')) {
67
+ console.error('[login-helper] Gemini: On Google login page');
68
+ return LoginStatus.NEEDS_LOGIN;
69
+ }
70
+ try {
71
+ const profileFound = await page.evaluate((patterns) => {
72
+ for (const pattern of patterns) {
73
+ if (document.querySelector(`button[aria-label*="${pattern}"]`)) {
74
+ return true;
75
+ }
76
+ }
77
+ // Fallback: check for nav with chat history (logged-in indicator)
78
+ if (document.querySelector('nav[aria-label*="Recent"]')) {
79
+ return true;
80
+ }
81
+ return false;
82
+ }, 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;
85
+ }
86
+ catch (error) {
87
+ console.error(`[login-helper] Error checking Gemini status: ${error}`);
88
+ return LoginStatus.IN_PROGRESS;
89
+ }
90
+ }
91
+ /**
92
+ * Get login status for a specific provider
93
+ */
94
+ export async function getLoginStatus(page, provider) {
95
+ if (provider === 'chatgpt') {
96
+ // Try session probe first (most reliable)
97
+ const sessionStatus = await probeChatGPTSession(page);
98
+ if (sessionStatus !== LoginStatus.IN_PROGRESS) {
99
+ return sessionStatus;
100
+ }
101
+ // Fallback to DOM-based detection
102
+ const needsLogin = await isLoginRequired(page);
103
+ return needsLogin ? LoginStatus.NEEDS_LOGIN : LoginStatus.LOGGED_IN;
104
+ }
105
+ else {
106
+ return await getGeminiStatus(page);
107
+ }
108
+ }
109
+ /**
110
+ * Wait for login with auto-polling and backoff
111
+ * Returns when user logs in or timeout occurs
112
+ */
113
+ export async function waitForLoginStatus(page, provider, timeoutMs = 120000, onStatusUpdate) {
114
+ const log = onStatusUpdate || console.error;
115
+ const start = Date.now();
116
+ let delay = 500;
117
+ log(`⏳ ログイン待機中(最大${Math.floor(timeoutMs / 1000)}秒)...`);
118
+ while (Date.now() - start < timeoutMs) {
119
+ const status = await getLoginStatus(page, provider);
120
+ if (status === LoginStatus.LOGGED_IN) {
121
+ log('✅ ログイン検出!続行します...');
122
+ return status;
123
+ }
124
+ const elapsed = Math.floor((Date.now() - start) / 1000);
125
+ if (elapsed % 10 === 0 && elapsed > 0) {
126
+ log(`⏳ まだ待機中... (${elapsed}秒経過)`);
127
+ }
128
+ await new Promise((r) => setTimeout(r, delay));
129
+ delay = Math.min(3000, Math.floor(delay * 1.5)); // backoff
130
+ }
131
+ log('❌ タイムアウト: 再度お試しください');
132
+ return LoginStatus.NEEDS_LOGIN;
133
+ }
6
134
  /**
7
135
  * Check if the page requires login
8
136
  * More robust than just checking URL
@@ -10,7 +10,7 @@ import { ToolCategories } from './categories.js';
10
10
  import { defineTool } from './ToolDefinition.js';
11
11
  import { loadSelectors, getSelector } from '../selectors/loader.js';
12
12
  import { CHATGPT_CONFIG } from '../config.js';
13
- import { isLoginRequired } from '../login-helper.js';
13
+ import { getLoginStatus, waitForLoginStatus, LoginStatus } from '../login-helper.js';
14
14
  /**
15
15
  * Navigate with retry logic for handling ERR_ABORTED and other network errors
16
16
  */
@@ -243,17 +243,30 @@ export const askChatGPTWeb = defineTool({
243
243
  else {
244
244
  response.appendResponseLine('✅ 既存のChatGPTタブを再利用');
245
245
  }
246
- // Step 2: Check if login is required (don't wait - stop immediately)
247
- const needsLogin = await isLoginRequired(page);
248
- if (needsLogin) {
246
+ // Step 2: Check login status using session probe (most reliable)
247
+ const loginStatus = await getLoginStatus(page, 'chatgpt');
248
+ if (loginStatus === LoginStatus.NEEDS_LOGIN) {
249
249
  response.appendResponseLine('\n❌ ChatGPTへのログインが必要です');
250
250
  response.appendResponseLine('');
251
251
  response.appendResponseLine('📱 ブラウザウィンドウでChatGPTにログインしてください:');
252
252
  response.appendResponseLine(' 1. ブラウザウィンドウの「ログイン」ボタンをクリック');
253
253
  response.appendResponseLine(' 2. メールアドレスまたはGoogleアカウントでログイン');
254
- response.appendResponseLine(' 3. ログイン完了後、このツールを再実行してください');
255
254
  response.appendResponseLine('');
256
- return;
255
+ // Auto-poll for login completion (max 2 minutes)
256
+ const finalStatus = await waitForLoginStatus(page, 'chatgpt', 120000, (msg) => response.appendResponseLine(msg));
257
+ if (finalStatus !== LoginStatus.LOGGED_IN) {
258
+ response.appendResponseLine('❌ ログインがタイムアウトしました。再度お試しください。');
259
+ return;
260
+ }
261
+ }
262
+ else if (loginStatus === LoginStatus.IN_PROGRESS) {
263
+ // Wait a bit and retry
264
+ await new Promise(r => setTimeout(r, 2000));
265
+ const retryStatus = await getLoginStatus(page, 'chatgpt');
266
+ if (retryStatus !== LoginStatus.LOGGED_IN) {
267
+ response.appendResponseLine('⚠️ ログイン状態を確認できませんでした。再試行してください。');
268
+ return;
269
+ }
257
270
  }
258
271
  response.appendResponseLine('✅ ログイン確認完了');
259
272
  // Step 2: Load existing session or create new chat
@@ -9,7 +9,7 @@ import z from 'zod';
9
9
  import { ToolCategories } from './categories.js';
10
10
  import { defineTool } from './ToolDefinition.js';
11
11
  import { GEMINI_CONFIG } from '../config.js';
12
- import { isLoginRequired } from '../login-helper.js';
12
+ import { getLoginStatus, waitForLoginStatus, LoginStatus } from '../login-helper.js';
13
13
  /**
14
14
  * Navigate with retry logic for handling ERR_ABORTED and other network errors
15
15
  */
@@ -210,18 +210,43 @@ export const askGeminiWeb = defineTool({
210
210
  // Navigate directly to target URL (skip intermediate navigation)
211
211
  response.appendResponseLine('Geminiに接続中...');
212
212
  await navigateWithRetry(page, targetUrl, { waitUntil: 'networkidle2' });
213
- // Wait for Gemini SPA to fully render (networkidle2 is not enough for SPAs)
214
- await new Promise(resolve => setTimeout(resolve, 1000));
215
- // Check login only once after navigation
216
- const needsLogin = await isLoginRequired(page);
217
- if (needsLogin) {
213
+ // Wait for Gemini SPA to fully render using selector-based detection
214
+ // Instead of fixed 1000ms wait, wait for either profile button (logged in) or login link
215
+ try {
216
+ await Promise.race([
217
+ page.waitForSelector('button[aria-label*="Account"], button[aria-label*="アカウント"]', { timeout: 10000 }),
218
+ page.waitForSelector('a[href*="accounts.google.com"]', { timeout: 10000 }),
219
+ page.waitForSelector('[role="textbox"]', { timeout: 10000 }),
220
+ ]);
221
+ }
222
+ catch {
223
+ // Timeout is acceptable - continue with login check
224
+ response.appendResponseLine('⚠️ UI安定化待機タイムアウト(続行)');
225
+ }
226
+ // Check login using ARIA-based detection (multi-language support)
227
+ const loginStatus = await getLoginStatus(page, 'gemini');
228
+ if (loginStatus === LoginStatus.NEEDS_LOGIN) {
218
229
  response.appendResponseLine('\n❌ Geminiへのログインが必要です');
219
230
  response.appendResponseLine('');
220
231
  response.appendResponseLine('📱 ブラウザウィンドウでGeminiにログインしてください:');
221
232
  response.appendResponseLine(' 1. ブラウザウィンドウでGoogleアカウントを選択');
222
233
  response.appendResponseLine(' 2. パスワードを入力してログイン');
223
- response.appendResponseLine(' 3. ログイン完了後、このツールを再実行してください');
224
- return;
234
+ response.appendResponseLine('');
235
+ // Auto-poll for login completion (max 2 minutes)
236
+ const finalStatus = await waitForLoginStatus(page, 'gemini', 120000, (msg) => response.appendResponseLine(msg));
237
+ if (finalStatus !== LoginStatus.LOGGED_IN) {
238
+ response.appendResponseLine('❌ ログインがタイムアウトしました。再度お試しください。');
239
+ return;
240
+ }
241
+ }
242
+ else if (loginStatus === LoginStatus.IN_PROGRESS) {
243
+ // Wait a bit and retry
244
+ await new Promise(r => setTimeout(r, 2000));
245
+ const retryStatus = await getLoginStatus(page, 'gemini');
246
+ if (retryStatus !== LoginStatus.LOGGED_IN) {
247
+ response.appendResponseLine('⚠️ ログイン状態を確認できませんでした。再試行してください。');
248
+ return;
249
+ }
225
250
  }
226
251
  response.appendResponseLine('✅ ログイン確認完了');
227
252
  response.appendResponseLine('質問を送信中...');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp-for-extension",
3
- "version": "0.23.3",
3
+ "version": "0.23.4",
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",