mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-21 04:10:47 +02:00
feat: require explicit browser provider consent
This commit is contained in:
@@ -44,6 +44,30 @@ export function isCustomChromium(): boolean {
|
||||
return p.includes('GBrowser') || p.includes('gbrowser');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the explicitly selected Chromium executable for both headless and
|
||||
* headed launches. Keeping this opt-in preserves the managed browser fallback
|
||||
* while allowing the lightweight playwright-core adapter to reuse a system or
|
||||
* host-managed Chrome without downloading Playwright's browser package.
|
||||
*/
|
||||
export function configuredChromiumExecutable(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): string | undefined {
|
||||
const value = env.GSTACK_CHROMIUM_PATH?.trim();
|
||||
return value || undefined;
|
||||
}
|
||||
|
||||
/** Installed-system Chromium is supported only for headless automation. */
|
||||
export function assertHeadedBrowserProvider(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): void {
|
||||
if (env.GSTACK_BROWSER_PROVIDER === 'installed') {
|
||||
throw new Error(
|
||||
'Visible GStack Browser requires managed Chromium; installed Chrome-family browsers are headless-only',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide whether Playwright should request Chromium's sandbox.
|
||||
*
|
||||
@@ -361,6 +385,7 @@ export class BrowserManager {
|
||||
const { STEALTH_LAUNCH_ARGS, buildGStackLaunchArgs } = await import('./stealth');
|
||||
const launchArgs: string[] = [...STEALTH_LAUNCH_ARGS, ...buildGStackLaunchArgs()];
|
||||
let useHeadless = true;
|
||||
const executablePath = configuredChromiumExecutable();
|
||||
|
||||
// Docker/CI/root: Chromium sandbox requires unprivileged user namespaces which
|
||||
// are typically disabled in containers and are never available for the root
|
||||
@@ -387,7 +412,11 @@ export class BrowserManager {
|
||||
|
||||
this.browser = await chromium.launch({
|
||||
headless: useHeadless,
|
||||
...(useHeadless && managedHeadlessChannel() ? { channel: 'chromium' as const } : {}),
|
||||
...(executablePath
|
||||
? { executablePath }
|
||||
: useHeadless && managedHeadlessChannel()
|
||||
? { channel: 'chromium' as const }
|
||||
: {}),
|
||||
// On Windows, Chromium's sandbox fails when the server is spawned through
|
||||
// the Bun→Node process chain (GitHub #276). Disable it — local daemon
|
||||
// browsing user-specified URLs has marginal sandbox benefit. Also disabled
|
||||
@@ -447,6 +476,7 @@ export class BrowserManager {
|
||||
* every action Claude takes in real time.
|
||||
*/
|
||||
async launchHeaded(authToken?: string): Promise<void> {
|
||||
assertHeadedBrowserProvider();
|
||||
// Clear old state before repopulating
|
||||
this.pages.clear();
|
||||
this.tabSessions.clear();
|
||||
@@ -515,7 +545,7 @@ export class BrowserManager {
|
||||
|
||||
// Support custom Chromium binary via GSTACK_CHROMIUM_PATH env var.
|
||||
// Used by GStack Browser.app to point at the bundled Chromium.
|
||||
const executablePath = process.env.GSTACK_CHROMIUM_PATH || undefined;
|
||||
const executablePath = configuredChromiumExecutable();
|
||||
|
||||
// Rebrand Chromium → GStack Browser in macOS menu bar / Dock / Cmd+Tab.
|
||||
// Patch the Chromium .app's Info.plist so macOS shows our name.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import { startTestServer } from './test-server';
|
||||
import { BrowserManager } from '../src/browser-manager';
|
||||
import { BrowserManager, assertHeadedBrowserProvider, configuredChromiumExecutable } from '../src/browser-manager';
|
||||
import { resolveServerScript } from '../src/cli';
|
||||
import { handleReadCommand as _handleReadCommand, parseOutArgs, hasOutArg, resultToString } from '../src/read-commands';
|
||||
import { handleWriteCommand as _handleWriteCommand } from '../src/write-commands';
|
||||
@@ -23,6 +23,25 @@ const handleReadCommand = (cmd: string, args: string[], b: BrowserManager) =>
|
||||
const handleWriteCommand = (cmd: string, args: string[], b: BrowserManager) =>
|
||||
_handleWriteCommand(cmd, args, b.getActiveSession(), b);
|
||||
|
||||
describe('configuredChromiumExecutable', () => {
|
||||
test('returns and trims an explicitly selected system browser', () => {
|
||||
expect(configuredChromiumExecutable({
|
||||
GSTACK_CHROMIUM_PATH: ' /Applications/Google Chrome.app/Contents/MacOS/Google Chrome ',
|
||||
})).toBe('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome');
|
||||
});
|
||||
|
||||
test('keeps the managed-browser path when no override is selected', () => {
|
||||
expect(configuredChromiumExecutable({})).toBeUndefined();
|
||||
expect(configuredChromiumExecutable({ GSTACK_CHROMIUM_PATH: ' ' })).toBeUndefined();
|
||||
});
|
||||
|
||||
test('rejects headed launch when setup selected an installed system browser', () => {
|
||||
expect(() => assertHeadedBrowserProvider({ GSTACK_BROWSER_PROVIDER: 'installed' }))
|
||||
.toThrow('Visible GStack Browser requires managed Chromium');
|
||||
expect(() => assertHeadedBrowserProvider({ GSTACK_BROWSER_PROVIDER: 'managed' })).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Pure arg-parser + result-conversion unit tests (no browser) ───
|
||||
describe('parseOutArgs / hasOutArg', () => {
|
||||
test('--out <path> splits the flag from the positional', () => {
|
||||
|
||||
Reference in New Issue
Block a user