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 {
|
|
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
|
|
247
|
-
const
|
|
248
|
-
if (
|
|
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
|
-
|
|
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 {
|
|
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
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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('
|
|
224
|
-
|
|
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
|
+
"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",
|