chrome-ai-bridge 1.0.10 → 1.0.12

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.
@@ -209,31 +209,54 @@ export async function getLoginStatus(page, provider) {
209
209
  return await getGeminiStatus(page);
210
210
  }
211
211
  }
212
+ /**
213
+ * Default login timeout: 5 minutes (for 2FA, SMS auth, Authenticator apps)
214
+ */
215
+ const LOGIN_TIMEOUT_MS = 300000;
212
216
  /**
213
217
  * Wait for login with auto-polling and backoff
214
218
  * Returns when user logs in or timeout occurs
219
+ *
220
+ * Improvements (v0.x.x):
221
+ * - Extended timeout: 5 minutes (2FA/SMS/Authenticator support)
222
+ * - Browser brought to front for visibility
223
+ * - Progress updates with remaining time (every 15 seconds)
224
+ * - Clear success feedback on login detection
215
225
  */
216
- export async function waitForLoginStatus(page, provider, timeoutMs = 120000, onStatusUpdate) {
226
+ export async function waitForLoginStatus(page, provider, timeoutMs = LOGIN_TIMEOUT_MS, onStatusUpdate) {
217
227
  const log = onStatusUpdate || console.error;
218
228
  const start = Date.now();
219
229
  let delay = 500;
220
- log(`⏳ ログイン待機中(最大${Math.floor(timeoutMs / 1000)}秒)...`);
230
+ let lastProgressReport = 0;
231
+ // Bring browser to front so user can see login page
232
+ try {
233
+ await page.bringToFront();
234
+ }
235
+ catch {
236
+ // Ignore errors (page might be closed)
237
+ }
221
238
  while (Date.now() - start < timeoutMs) {
222
239
  const status = await getLoginStatus(page, provider);
223
240
  if (status === LoginStatus.LOGGED_IN) {
224
- log('✅ ログイン検出!続行します...');
241
+ log('✅ ログイン検出!処理を続行します');
225
242
  return status;
226
243
  }
227
- const elapsed = Math.floor((Date.now() - start) / 1000);
228
- if (elapsed % 10 === 0 && elapsed > 0) {
229
- log(`⏳ まだ待機中... (${elapsed}秒経過)`);
244
+ // Progress update every 15 seconds with remaining time (minutes:seconds format)
245
+ const elapsed = Date.now() - start;
246
+ if (elapsed - lastProgressReport >= 15000) {
247
+ const remainingMs = timeoutMs - elapsed;
248
+ const mins = Math.floor(remainingMs / 60000);
249
+ const secs = Math.ceil((remainingMs % 60000) / 1000);
250
+ const timeStr = mins > 0 ? `${mins}分${secs}秒` : `${secs}秒`;
251
+ log(`⏳ ログイン待機中... 残り ${timeStr}`);
252
+ lastProgressReport = elapsed;
230
253
  }
231
254
  await new Promise(r => setTimeout(r, delay));
232
255
  // Backoff with jitter (ChatGPT recommendation: ±10-20% randomization)
233
256
  const jitter = 0.9 + Math.random() * 0.2; // 0.9 to 1.1
234
257
  delay = Math.min(3000, Math.floor(delay * 1.5 * jitter));
235
258
  }
236
- log('❌ タイムアウト: 再度お試しください');
259
+ log('❌ ログインタイムアウト(5分)');
237
260
  return LoginStatus.NEEDS_LOGIN;
238
261
  }
239
262
  /**
@@ -281,14 +281,13 @@ export const askChatGPTWeb = defineTool({
281
281
  // Step 3: Check login status
282
282
  const loginStatus = await getLoginStatus(page, 'chatgpt');
283
283
  if (loginStatus === LoginStatus.NEEDS_LOGIN) {
284
- response.appendResponseLine('\n ChatGPTへのログインが必要です');
284
+ response.appendResponseLine('\n🔐 ログインが必要です');
285
+ response.appendResponseLine('📱 ブラウザウィンドウを開きました。ログインしてください');
286
+ response.appendResponseLine('⏳ ログイン完了を自動検出します(最大5分待機)');
287
+ response.appendResponseLine('💡 二段階認証もゆっくり対応できます');
285
288
  response.appendResponseLine('');
286
- response.appendResponseLine('📱 ブラウザウィンドウでChatGPTにログインしてください:');
287
- response.appendResponseLine(' 1. ブラウザウィンドウの「ログイン」ボタンをクリック');
288
- response.appendResponseLine(' 2. メールアドレスまたはGoogleアカウントでログイン');
289
- response.appendResponseLine('');
290
- // Auto-poll for login completion (max 2 minutes)
291
- const finalStatus = await waitForLoginStatus(page, 'chatgpt', 120000, msg => response.appendResponseLine(msg));
289
+ // Auto-poll for login completion (max 5 minutes for 2FA support)
290
+ const finalStatus = await waitForLoginStatus(page, 'chatgpt', 300000, msg => response.appendResponseLine(msg));
292
291
  if (finalStatus !== LoginStatus.LOGGED_IN) {
293
292
  response.appendResponseLine('❌ ログインがタイムアウトしました。再度お試しください。');
294
293
  return;
@@ -136,11 +136,14 @@ export const askGeminiImage = defineTool({
136
136
  // Check login
137
137
  const loginStatus = await getLoginStatus(page, 'gemini');
138
138
  if (loginStatus === LoginStatus.NEEDS_LOGIN) {
139
- response.appendResponseLine('\n Geminiへのログインが必要です');
140
- response.appendResponseLine('📱 ブラウザでGoogleアカウントにログインしてください');
141
- const finalStatus = await waitForLoginStatus(page, 'gemini', 120000, msg => response.appendResponseLine(msg));
139
+ response.appendResponseLine('\n🔐 ログインが必要です');
140
+ response.appendResponseLine('📱 ブラウザウィンドウを開きました。Googleアカウントでログインしてください');
141
+ response.appendResponseLine('⏳ ログイン完了を自動検出します(最大5分待機)');
142
+ response.appendResponseLine('💡 二段階認証もゆっくり対応できます');
143
+ // Auto-poll for login completion (max 5 minutes for 2FA support)
144
+ const finalStatus = await waitForLoginStatus(page, 'gemini', 300000, msg => response.appendResponseLine(msg));
142
145
  if (finalStatus !== LoginStatus.LOGGED_IN) {
143
- response.appendResponseLine('❌ ログインタイムアウト');
146
+ response.appendResponseLine('❌ ログインタイムアウト(5分)');
144
147
  return;
145
148
  }
146
149
  }
@@ -282,6 +285,9 @@ export const askGeminiImage = defineTool({
282
285
  downloadManager.on('started', (filename) => {
283
286
  response.appendResponseLine(`📥 ダウンロード開始: ${filename}`);
284
287
  });
288
+ // Get existing Gemini images BEFORE clicking download button
289
+ const existingFiles = await fs.promises.readdir(userDownloadsDir);
290
+ const existingGeminiImages = new Set(existingFiles.filter(f => f.startsWith('Gemini_Generated_Image_') && f.endsWith('.png')));
285
291
  // Click download button - Gemini uses "フルサイズの画像をダウンロード" button
286
292
  // Improved selector: prioritize aria-describedby for more reliable detection
287
293
  const downloadClicked = await page.evaluate(() => {
@@ -325,25 +331,41 @@ export const askGeminiImage = defineTool({
325
331
  response.appendResponseLine('ヒント: ブラウザで画像を右クリックして保存してください');
326
332
  return;
327
333
  }
328
- // Wait for download to complete using CDP events (more reliable than file polling)
329
- response.appendResponseLine('⏳ CDP経由でダウンロード完了を待機中...');
330
- let downloadedPath;
331
- try {
332
- downloadedPath = await downloadManager.waitForDownload(60000); // 60 seconds
333
- response.appendResponseLine(`✅ ダウンロード完了: ${path.basename(downloadedPath)}`);
334
- }
335
- catch (downloadError) {
336
- const errMsg = downloadError instanceof Error ? downloadError.message : String(downloadError);
337
- if (errMsg.includes('timeout')) {
338
- response.appendResponseLine('❌ ダウンロードタイムアウト (60秒)');
339
- response.appendResponseLine('💡 ヒント: ブラウザで画像を右クリックして「画像を保存」してください');
334
+ // Wait for download to complete using hybrid approach:
335
+ // 1. Try CDP events first (reliable for standard downloads)
336
+ // 2. Fall back to filesystem monitoring (for blob/JS downloads like Gemini)
337
+ response.appendResponseLine('⏳ ダウンロード完了を待機中...');
338
+ let downloadedPath = null;
339
+ const downloadStartTime = Date.now();
340
+ const downloadTimeout = 60000; // 60 seconds
341
+ // Try CDP-based detection with filesystem fallback
342
+ while (Date.now() - downloadStartTime < downloadTimeout) {
343
+ // Check for new Gemini image files (filesystem fallback)
344
+ const currentFiles = await fs.promises.readdir(userDownloadsDir);
345
+ const newGeminiImages = currentFiles.filter(f => f.startsWith('Gemini_Generated_Image_') &&
346
+ f.endsWith('.png') &&
347
+ !existingGeminiImages.has(f));
348
+ if (newGeminiImages.length > 0) {
349
+ // Found new image file
350
+ const newestImage = newGeminiImages.sort().pop();
351
+ downloadedPath = path.join(userDownloadsDir, newestImage);
352
+ // Wait a bit for file to be fully written
353
+ await new Promise(resolve => setTimeout(resolve, 500));
354
+ response.appendResponseLine(`✅ ダウンロード完了: ${newestImage}`);
355
+ break;
340
356
  }
341
- else if (errMsg.includes('canceled')) {
342
- response.appendResponseLine('❌ ダウンロードがキャンセルされました');
343
- }
344
- else {
345
- response.appendResponseLine(`❌ ダウンロードエラー: ${errMsg}`);
357
+ // Also check CDP events (for standard downloads)
358
+ const completedDownloads = Array.from(downloadManager.getPendingDownloads()).filter((d) => d.state === 'completed');
359
+ if (completedDownloads.length > 0) {
360
+ // CDP detected completion - but Gemini uses blob downloads so this rarely fires
361
+ break;
346
362
  }
363
+ // Short wait before next check
364
+ await new Promise(resolve => setTimeout(resolve, 500));
365
+ }
366
+ if (!downloadedPath) {
367
+ response.appendResponseLine('❌ ダウンロードタイムアウト (60秒)');
368
+ response.appendResponseLine('💡 ヒント: ブラウザで画像を右クリックして「画像を保存」してください');
347
369
  return;
348
370
  }
349
371
  // Ensure output directory exists
@@ -244,14 +244,13 @@ export const askGeminiWeb = defineTool({
244
244
  // Check login using ARIA-based detection (multi-language support)
245
245
  const loginStatus = await getLoginStatus(page, 'gemini');
246
246
  if (loginStatus === LoginStatus.NEEDS_LOGIN) {
247
- response.appendResponseLine('\n Geminiへのログインが必要です');
247
+ response.appendResponseLine('\n🔐 ログインが必要です');
248
+ response.appendResponseLine('📱 ブラウザウィンドウを開きました。Googleアカウントでログインしてください');
249
+ response.appendResponseLine('⏳ ログイン完了を自動検出します(最大5分待機)');
250
+ response.appendResponseLine('💡 二段階認証もゆっくり対応できます');
248
251
  response.appendResponseLine('');
249
- response.appendResponseLine('📱 ブラウザウィンドウでGeminiにログインしてください:');
250
- response.appendResponseLine(' 1. ブラウザウィンドウでGoogleアカウントを選択');
251
- response.appendResponseLine(' 2. パスワードを入力してログイン');
252
- response.appendResponseLine('');
253
- // Auto-poll for login completion (max 2 minutes)
254
- const finalStatus = await waitForLoginStatus(page, 'gemini', 120000, msg => response.appendResponseLine(msg));
252
+ // Auto-poll for login completion (max 5 minutes for 2FA support)
253
+ const finalStatus = await waitForLoginStatus(page, 'gemini', 300000, msg => response.appendResponseLine(msg));
255
254
  if (finalStatus !== LoginStatus.LOGGED_IN) {
256
255
  response.appendResponseLine('❌ ログインがタイムアウトしました。再度お試しください。');
257
256
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-ai-bridge",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "MCP server bridging Chrome browser and AI assistants (ChatGPT, Gemini). Browser automation + AI consultation.",
5
5
  "type": "module",
6
6
  "bin": "./scripts/cli.mjs",