diff options
Diffstat (limited to 'packages/core/src')
| -rw-r--r-- | packages/core/src/code_assist/oauth2.test.ts | 7 | ||||
| -rw-r--r-- | packages/core/src/code_assist/oauth2.ts | 11 | ||||
| -rw-r--r-- | packages/core/src/index.ts | 1 | ||||
| -rw-r--r-- | packages/core/src/utils/browser.ts | 53 |
4 files changed, 66 insertions, 6 deletions
diff --git a/packages/core/src/code_assist/oauth2.test.ts b/packages/core/src/code_assist/oauth2.test.ts index 9d8fb892..8a1af056 100644 --- a/packages/core/src/code_assist/oauth2.test.ts +++ b/packages/core/src/code_assist/oauth2.test.ts @@ -31,6 +31,9 @@ vi.mock('http'); vi.mock('open'); vi.mock('crypto'); vi.mock('node:readline'); +vi.mock('../utils/browser.js', () => ({ + shouldAttemptBrowserLaunch: () => true, +})); const mockConfig = { getNoBrowser: () => false, @@ -83,7 +86,7 @@ describe('oauth2', () => { ); vi.spyOn(crypto, 'randomBytes').mockReturnValue(mockState as never); - (open as Mock).mockImplementation(async () => ({}) as never); + (open as Mock).mockImplementation(async () => ({ on: vi.fn() }) as never); // Mock the UserInfo API response (global.fetch as Mock).mockResolvedValue({ @@ -236,7 +239,7 @@ describe('oauth2', () => { expect(mockGetToken).toHaveBeenCalledWith({ code: mockCode, codeVerifier: mockCodeVerifier.codeVerifier, - redirect_uri: 'https://sdk.cloud.google.com/authcode_cloudcode.html', + redirect_uri: 'https://codeassist.google.com/authcode', }); expect(mockSetCredentials).toHaveBeenCalledWith(mockTokens); diff --git a/packages/core/src/code_assist/oauth2.ts b/packages/core/src/code_assist/oauth2.ts index ac45177c..51227086 100644 --- a/packages/core/src/code_assist/oauth2.ts +++ b/packages/core/src/code_assist/oauth2.ts @@ -26,6 +26,7 @@ import { clearCachedGoogleAccount, } from '../utils/user_account.js'; import { AuthType } from '../core/contentGenerator.js'; +import { shouldAttemptBrowserLaunch } from '../utils/browser.js'; import readline from 'node:readline'; // OAuth Client ID used to initiate OAuth2Client class. @@ -121,7 +122,7 @@ export async function getOauthClient( } } - if (config.getNoBrowser()) { + if (config.getNoBrowser() || !shouldAttemptBrowserLaunch()) { let success = false; const maxRetries = 2; for (let i = 0; !success && i < maxRetries; i++) { @@ -156,15 +157,17 @@ export async function getOauthClient( // causing the entire Node.js process to crash. childProcess.on('error', (_) => { console.error( - 'Failed to open browser automatically. Please open the URL manually:', + 'Failed to open browser automatically. Please try running again with NO_BROWSER=true set.', ); - console.error(webLogin.authUrl); + process.exit(1); }); } catch (err) { console.error( 'An unexpected error occurred while trying to open the browser:', err, + '\nPlease try running again with NO_BROWSER=true set.', ); + process.exit(1); } console.log('Waiting for authentication...'); @@ -175,7 +178,7 @@ export async function getOauthClient( } async function authWithUserCode(client: OAuth2Client): Promise<boolean> { - const redirectUri = 'https://sdk.cloud.google.com/authcode_cloudcode.html'; + const redirectUri = 'https://codeassist.google.com/authcode'; const codeVerifier = await client.generateCodeVerifierAsync(); const state = crypto.randomBytes(32).toString('hex'); const authUrl: string = client.generateAuthUrl({ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2e85deff..a3d77ddc 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -80,3 +80,4 @@ export { OAuthUtils } from './mcp/oauth-utils.js'; // Export telemetry functions export * from './telemetry/index.js'; export { sessionId } from './utils/session.js'; +export * from './utils/browser.js'; diff --git a/packages/core/src/utils/browser.ts b/packages/core/src/utils/browser.ts new file mode 100644 index 00000000..a9b2b013 --- /dev/null +++ b/packages/core/src/utils/browser.ts @@ -0,0 +1,53 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Determines if we should attempt to launch a browser for authentication + * based on the user's environment. + * + * This is an adaptation of the logic from the Google Cloud SDK. + * @returns True if the tool should attempt to launch a browser. + */ +export function shouldAttemptBrowserLaunch(): boolean { + // A list of browser names that indicate we should not attempt to open a + // web browser for the user. + const browserBlocklist = ['www-browser']; + const browserEnv = process.env.BROWSER; + if (browserEnv && browserBlocklist.includes(browserEnv)) { + return false; + } + // Common environment variables used in CI/CD or other non-interactive shells. + if (process.env.CI || process.env.DEBIAN_FRONTEND === 'noninteractive') { + return false; + } + + // The presence of SSH_CONNECTION indicates a remote session. + // We should not attempt to launch a browser unless a display is explicitly available + // (checked below for Linux). + const isSSH = !!process.env.SSH_CONNECTION; + + // On Linux, the presence of a display server is a strong indicator of a GUI. + if (process.platform === 'linux') { + // These are environment variables that can indicate a running compositor on + // Linux. + const displayVariables = ['DISPLAY', 'WAYLAND_DISPLAY', 'MIR_SOCKET']; + const hasDisplay = displayVariables.some((v) => !!process.env[v]); + if (!hasDisplay) { + return false; + } + } + + // If in an SSH session on a non-Linux OS (e.g., macOS), don't launch browser. + // The Linux case is handled above (it's allowed if DISPLAY is set). + if (isSSH && process.platform !== 'linux') { + return false; + } + + // For non-Linux OSes, we generally assume a GUI is available + // unless other signals (like SSH) suggest otherwise. + // The `open` command's error handling will catch final edge cases. + return true; +} |
