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.
- package/build/src/login-helper.js +90 -12
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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:
|
|
60
|
+
return { ok: r.ok, status: r.status, json: j };
|
|
40
61
|
}
|
|
41
62
|
catch (e) {
|
|
42
|
-
|
|
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
|
|
51
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|