feat(port-forwarding): add playwrightclient support (#6786)

This commit is contained in:
Max Schmitt 2021-06-02 14:35:17 -07:00 committed by GitHub
parent 33c2f6c31e
commit e91e49e533
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 122 additions and 87 deletions

View file

@ -34,9 +34,6 @@ import { BrowserContext } from './server/browserContext';
import { CRBrowser } from './server/chromium/crBrowser'; import { CRBrowser } from './server/chromium/crBrowser';
import { CDPSessionDispatcher } from './dispatchers/cdpSessionDispatcher'; import { CDPSessionDispatcher } from './dispatchers/cdpSessionDispatcher';
import { PageDispatcher } from './dispatchers/pageDispatcher'; import { PageDispatcher } from './dispatchers/pageDispatcher';
import { BrowserServerPortForwardingServer } from './server/socksSocket';
import { SocksSocketDispatcher } from './dispatchers/socksSocketDispatcher';
import { SocksInterceptedSocketHandler } from './server/socksServer';
export class BrowserServerLauncherImpl implements BrowserServerLauncher { export class BrowserServerLauncherImpl implements BrowserServerLauncher {
private _browserName: 'chromium' | 'firefox' | 'webkit'; private _browserName: 'chromium' | 'firefox' | 'webkit';
@ -47,14 +44,14 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> { async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> {
const playwright = createPlaywright(); const playwright = createPlaywright();
const portForwardingServer = new BrowserServerPortForwardingServer(playwright, !!options._acceptForwardedPorts); if (options._acceptForwardedPorts)
await playwright._enablePortForwarding();
// 1. Pre-launch the browser // 1. Pre-launch the browser
const browser = await playwright[this._browserName].launch(internalCallMetadata(), { const browser = await playwright[this._browserName].launch(internalCallMetadata(), {
...options, ...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined, ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs), ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? envObjectToArray(options.env) : undefined, env: options.env ? envObjectToArray(options.env) : undefined,
...portForwardingServer.browserLaunchOptions(),
}, toProtocolLogger(options.logger)); }, toProtocolLogger(options.logger));
// 2. Start the server // 2. Start the server
@ -62,9 +59,9 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
path: '/' + createGuid(), path: '/' + createGuid(),
allowMultipleClients: options._acceptForwardedPorts ? false : true, allowMultipleClients: options._acceptForwardedPorts ? false : true,
onClose: () => { onClose: () => {
portForwardingServer.stop(); playwright._disablePortForwarding();
}, },
onConnect: this._onConnect.bind(this, playwright, browser, portForwardingServer), onConnect: this._onConnect.bind(this, playwright, browser),
}; };
const server = new PlaywrightServer(delegate); const server = new PlaywrightServer(delegate);
const wsEndpoint = await server.listen(options.port); const wsEndpoint = await server.listen(options.port);
@ -83,7 +80,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
return browserServer; return browserServer;
} }
private _onConnect(playwright: Playwright, browser: Browser, portForwardingServer: BrowserServerPortForwardingServer, scope: DispatcherScope, forceDisconnect: () => void) { private async _onConnect(playwright: Playwright, browser: Browser, scope: DispatcherScope, forceDisconnect: () => void) {
const selectors = new Selectors(); const selectors = new Selectors();
const selectorsDispatcher = new SelectorsDispatcher(scope, selectors); const selectorsDispatcher = new SelectorsDispatcher(scope, selectors);
const browserDispatcher = new ConnectedBrowserDispatcher(scope, browser, selectors); const browserDispatcher = new ConnectedBrowserDispatcher(scope, browser, selectors);
@ -91,16 +88,8 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
// Underlying browser did close for some reason - force disconnect the client. // Underlying browser did close for some reason - force disconnect the client.
forceDisconnect(); forceDisconnect();
}); });
const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, selectorsDispatcher, browserDispatcher, (ports: number[]) => { new PlaywrightDispatcher(scope, playwright, selectorsDispatcher, browserDispatcher);
portForwardingServer.enablePortForwarding(ports);
});
const incomingSocksSocketHandler = (socket: SocksInterceptedSocketHandler) => {
playwrightDispatcher._dispatchEvent('incomingSocksSocket', { socket: new SocksSocketDispatcher(playwrightDispatcher, socket) });
};
portForwardingServer.on('incomingSocksSocket', incomingSocksSocketHandler);
return () => { return () => {
portForwardingServer.off('incomingSocksSocket', incomingSocksSocketHandler);
// Cleanup contexts upon disconnect. // Cleanup contexts upon disconnect.
browserDispatcher.cleanupContexts().catch(e => {}); browserDispatcher.cleanupContexts().catch(e => {});
}; };

View file

@ -54,7 +54,7 @@ export function runDriver() {
} }
export async function runServer(port: number | undefined) { export async function runServer(port: number | undefined) {
const wsEndpoint = await PlaywrightServer.startDefault(port); const wsEndpoint = await PlaywrightServer.startDefault({port});
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
} }

View file

@ -192,10 +192,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
}); });
if (params._forwardPorts) { if (params._forwardPorts) {
try { try {
await playwright._channel.enablePortForwarding({ await playwright._enablePortForwarding(params._forwardPorts);
ports: params._forwardPorts,
});
playwright._forwardPorts = params._forwardPorts;
} catch (err) { } catch (err) {
reject(err); reject(err);
return; return;

View file

@ -65,6 +65,11 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel, channel
this._channel.on('incomingSocksSocket', ({socket}) => SocksSocket.from(socket)); this._channel.on('incomingSocksSocket', ({socket}) => SocksSocket.from(socket));
} }
async _enablePortForwarding(ports: number[]) {
this._forwardPorts = ports;
await this._channel.setForwardedPorts({ports});
}
_cleanup() { _cleanup() {
this.selectors._removeChannel(this._selectorsOwner); this.selectors._removeChannel(this._selectorsOwner);
} }

View file

@ -22,11 +22,11 @@ import { Dispatcher, DispatcherScope } from './dispatcher';
import { ElectronDispatcher } from './electronDispatcher'; import { ElectronDispatcher } from './electronDispatcher';
import { SelectorsDispatcher } from './selectorsDispatcher'; import { SelectorsDispatcher } from './selectorsDispatcher';
import * as types from '../server/types'; import * as types from '../server/types';
import { assert } from '../utils/utils'; import { SocksSocketDispatcher } from './socksSocketDispatcher';
import { SocksInterceptedSocketHandler } from '../server/socksServer';
export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.PlaywrightInitializer> implements channels.PlaywrightChannel { export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.PlaywrightInitializer> implements channels.PlaywrightChannel {
private _portForwardingCallback: ((ports: number[]) => void) | undefined; constructor(scope: DispatcherScope, playwright: Playwright, customSelectors?: channels.SelectorsChannel, preLaunchedBrowser?: channels.BrowserChannel) {
constructor(scope: DispatcherScope, playwright: Playwright, customSelectors?: channels.SelectorsChannel, preLaunchedBrowser?: channels.BrowserChannel, portForwardingCallback?: (ports: number[]) => void) {
const descriptors = require('../server/deviceDescriptors') as types.Devices; const descriptors = require('../server/deviceDescriptors') as types.Devices;
const deviceDescriptors = Object.entries(descriptors) const deviceDescriptors = Object.entries(descriptors)
.map(([name, descriptor]) => ({ name, descriptor })); .map(([name, descriptor]) => ({ name, descriptor }));
@ -40,11 +40,12 @@ export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.Playwr
selectors: customSelectors || new SelectorsDispatcher(scope, playwright.selectors), selectors: customSelectors || new SelectorsDispatcher(scope, playwright.selectors),
preLaunchedBrowser, preLaunchedBrowser,
}, false); }, false);
this._portForwardingCallback = portForwardingCallback; this._object.on('incomingSocksSocket', (socket: SocksInterceptedSocketHandler) => {
this._dispatchEvent('incomingSocksSocket', { socket: new SocksSocketDispatcher(this, socket) });
});
} }
async enablePortForwarding(params: channels.PlaywrightEnablePortForwardingParams): Promise<void> { async setForwardedPorts(params: channels.PlaywrightSetForwardedPortsParams): Promise<void> {
assert(this._portForwardingCallback, 'Port forwarding is only supported when using connect()'); this._object._setForwardedPorts(params.ports);
this._portForwardingCallback(params.ports);
} }
} }

View file

@ -180,18 +180,18 @@ export type PlaywrightInitializer = {
}; };
export interface PlaywrightChannel extends Channel { export interface PlaywrightChannel extends Channel {
on(event: 'incomingSocksSocket', callback: (params: PlaywrightIncomingSocksSocketEvent) => void): this; on(event: 'incomingSocksSocket', callback: (params: PlaywrightIncomingSocksSocketEvent) => void): this;
enablePortForwarding(params: PlaywrightEnablePortForwardingParams, metadata?: Metadata): Promise<PlaywrightEnablePortForwardingResult>; setForwardedPorts(params: PlaywrightSetForwardedPortsParams, metadata?: Metadata): Promise<PlaywrightSetForwardedPortsResult>;
} }
export type PlaywrightIncomingSocksSocketEvent = { export type PlaywrightIncomingSocksSocketEvent = {
socket: SocksSocketChannel, socket: SocksSocketChannel,
}; };
export type PlaywrightEnablePortForwardingParams = { export type PlaywrightSetForwardedPortsParams = {
ports: number[], ports: number[],
}; };
export type PlaywrightEnablePortForwardingOptions = { export type PlaywrightSetForwardedPortsOptions = {
}; };
export type PlaywrightEnablePortForwardingResult = void; export type PlaywrightSetForwardedPortsResult = void;
// ----------- Selectors ----------- // ----------- Selectors -----------
export type SelectorsInitializer = {}; export type SelectorsInitializer = {};

View file

@ -368,7 +368,7 @@ Playwright:
commands: commands:
enablePortForwarding: setForwardedPorts:
parameters: parameters:
ports: ports:
type: array type: array

View file

@ -147,7 +147,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
})), })),
value: tOptional(tType('SerializedValue')), value: tOptional(tType('SerializedValue')),
}); });
scheme.PlaywrightEnablePortForwardingParams = tObject({ scheme.PlaywrightSetForwardedPortsParams = tObject({
ports: tArray(tNumber), ports: tArray(tNumber),
}); });
scheme.SelectorsRegisterParams = tObject({ scheme.SelectorsRegisterParams = tObject({

View file

@ -18,24 +18,43 @@ import WebSocket from 'ws';
import { Connection } from '../client/connection'; import { Connection } from '../client/connection';
import { Playwright } from '../client/playwright'; import { Playwright } from '../client/playwright';
export type PlaywrightClientConnectOptions = {
wsEndpoint: string;
forwardPorts?: number[];
timeout?: number
};
export class PlaywrightClient { export class PlaywrightClient {
private _playwright: Playwright; private _playwright: Playwright;
private _ws: WebSocket; private _ws: WebSocket;
private _closePromise: Promise<void>; private _closePromise: Promise<void>;
static async connect(wsEndpoint: string): Promise<PlaywrightClient> { static async connect(options: PlaywrightClientConnectOptions): Promise<PlaywrightClient> {
const {wsEndpoint, forwardPorts, timeout = 30000} = options;
const connection = new Connection(); const connection = new Connection();
const ws = new WebSocket(wsEndpoint); const ws = new WebSocket(wsEndpoint);
connection.onmessage = message => ws.send(JSON.stringify(message)); connection.onmessage = message => ws.send(JSON.stringify(message));
ws.on('message', message => connection.dispatch(JSON.parse(message.toString()))); ws.on('message', message => connection.dispatch(JSON.parse(message.toString())));
const errorPromise = new Promise((_, reject) => ws.on('error', error => reject(error))); const errorPromise = new Promise((_, reject) => ws.on('error', error => reject(error)));
const closePromise = new Promise((_, reject) => ws.on('close', () => reject(new Error('Connection closed')))); const closePromise = new Promise((_, reject) => ws.on('close', () => reject(new Error('Connection closed'))));
const playwright = await Promise.race([ const playwrightClientPromise = new Promise<PlaywrightClient>(async (resolve, reject) => {
connection.waitForObjectWithKnownName('Playwright'), const playwright = await connection.waitForObjectWithKnownName('Playwright') as Playwright;
errorPromise, if (forwardPorts)
closePromise await playwright._enablePortForwarding(forwardPorts).catch(reject);
]); resolve(new PlaywrightClient(playwright, ws));
return new PlaywrightClient(playwright as Playwright, ws); });
let timer: NodeJS.Timeout;
try {
await Promise.race([
playwrightClientPromise,
errorPromise,
closePromise,
new Promise((_, reject) => timer = setTimeout(reject, timeout))
]);
return await playwrightClientPromise;
} finally {
clearTimeout(timer!);
}
} }
constructor(playwright: Playwright, ws: WebSocket) { constructor(playwright: Playwright, ws: WebSocket) {

View file

@ -28,16 +28,21 @@ const debugLog = debug('pw:server');
export interface PlaywrightServerDelegate { export interface PlaywrightServerDelegate {
path: string; path: string;
allowMultipleClients: boolean; allowMultipleClients: boolean;
onConnect(rootScope: DispatcherScope, forceDisconnect: () => void): () => any; onConnect(rootScope: DispatcherScope, forceDisconnect: () => void): Promise<() => any>;
onClose: () => any; onClose: () => any;
} }
export type PlaywrightServerOptions = {
port?: number;
acceptForwardedPorts?: boolean
};
export class PlaywrightServer { export class PlaywrightServer {
private _wsServer: ws.Server | undefined; private _wsServer: ws.Server | undefined;
private _clientsCount = 0; private _clientsCount = 0;
private _delegate: PlaywrightServerDelegate; private _delegate: PlaywrightServerDelegate;
static async startDefault(port: number = 0): Promise<string> { static async startDefault({port = 0, acceptForwardedPorts }: PlaywrightServerOptions): Promise<string> {
const cleanup = async () => { const cleanup = async () => {
await gracefullyCloseAll().catch(e => {}); await gracefullyCloseAll().catch(e => {});
serverSelectors.unregisterAll(); serverSelectors.unregisterAll();
@ -46,9 +51,15 @@ export class PlaywrightServer {
path: '/ws', path: '/ws',
allowMultipleClients: false, allowMultipleClients: false,
onClose: cleanup, onClose: cleanup,
onConnect: (rootScope: DispatcherScope) => { onConnect: async (rootScope: DispatcherScope) => {
new PlaywrightDispatcher(rootScope, createPlaywright()); const playwright = createPlaywright();
return cleanup; if (acceptForwardedPorts)
await playwright._enablePortForwarding();
new PlaywrightDispatcher(rootScope, playwright);
return () => {
cleanup();
playwright._disablePortForwarding();
};
}, },
}; };
const server = new PlaywrightServer(delegate); const server = new PlaywrightServer(delegate);
@ -66,12 +77,12 @@ export class PlaywrightServer {
server.on('error', error => debugLog(error)); server.on('error', error => debugLog(error));
const path = this._delegate.path; const path = this._delegate.path;
const wsEndpoint = await new Promise<string>(resolve => { const wsEndpoint = await new Promise<string>((resolve, reject) => {
server.listen(port, () => { server.listen(port, () => {
const address = server.address(); const address = server.address();
const wsEndpoint = typeof address === 'string' ? `${address}${path}` : `ws://127.0.0.1:${address.port}${path}`; const wsEndpoint = typeof address === 'string' ? `${address}${path}` : `ws://127.0.0.1:${address.port}${path}`;
resolve(wsEndpoint); resolve(wsEndpoint);
}); }).on('error', reject);
}); });
debugLog('Listening at ' + wsEndpoint); debugLog('Listening at ' + wsEndpoint);
@ -96,7 +107,7 @@ export class PlaywrightServer {
const forceDisconnect = () => socket.close(); const forceDisconnect = () => socket.close();
const scope = connection.rootDispatcher(); const scope = connection.rootDispatcher();
const onDisconnect = this._delegate.onConnect(scope, forceDisconnect); let onDisconnect = () => {};
const disconnected = () => { const disconnected = () => {
this._clientsCount--; this._clientsCount--;
// Avoid sending any more messages over closed socket. // Avoid sending any more messages over closed socket.
@ -111,6 +122,7 @@ export class PlaywrightServer {
debugLog('Client error ' + error); debugLog('Client error ' + error);
disconnected(); disconnected();
}); });
onDisconnect = await this._delegate.onConnect(scope, forceDisconnect);
}); });
return wsEndpoint; return wsEndpoint;

View file

@ -35,6 +35,7 @@ export interface BrowserProcess {
export type PlaywrightOptions = { export type PlaywrightOptions = {
registry: registry.Registry, registry: registry.Registry,
rootSdkObject: SdkObject, rootSdkObject: SdkObject,
loopbackProxyOverride?: () => string,
}; };
export type BrowserOptions = PlaywrightOptions & { export type BrowserOptions = PlaywrightOptions & {

View file

@ -60,7 +60,7 @@ export abstract class BrowserType extends SdkObject {
} }
async launch(metadata: CallMetadata, options: types.LaunchOptions, protocolLogger?: types.ProtocolLogger): Promise<Browser> { async launch(metadata: CallMetadata, options: types.LaunchOptions, protocolLogger?: types.ProtocolLogger): Promise<Browser> {
options = validateLaunchOptions(options); options = validateLaunchOptions(options, this._playwrightOptions.loopbackProxyOverride?.());
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
controller.setLogName('browser'); controller.setLogName('browser');
const browser = await controller.run(progress => { const browser = await controller.run(progress => {
@ -70,7 +70,7 @@ export abstract class BrowserType extends SdkObject {
} }
async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: types.LaunchPersistentOptions): Promise<BrowserContext> { async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: types.LaunchPersistentOptions): Promise<BrowserContext> {
options = validateLaunchOptions(options); options = validateLaunchOptions(options, this._playwrightOptions.loopbackProxyOverride?.());
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
const persistent: types.BrowserContextOptions = options; const persistent: types.BrowserContextOptions = options;
controller.setLogName('browser'); controller.setLogName('browser');
@ -273,12 +273,14 @@ function copyTestHooks(from: object, to: object) {
} }
} }
function validateLaunchOptions<Options extends types.LaunchOptions>(options: Options): Options { function validateLaunchOptions<Options extends types.LaunchOptions>(options: Options, proxyOverride?: string): Options {
const { devtools = false } = options; const { devtools = false } = options;
let { headless = !devtools, downloadsPath } = options; let { headless = !devtools, downloadsPath, proxy } = options;
if (debugMode()) if (debugMode())
headless = false; headless = false;
if (downloadsPath && !path.isAbsolute(downloadsPath)) if (downloadsPath && !path.isAbsolute(downloadsPath))
downloadsPath = path.join(process.cwd(), downloadsPath); downloadsPath = path.join(process.cwd(), downloadsPath);
return { ...options, devtools, headless, downloadsPath }; if (proxyOverride)
proxy = { server: proxyOverride };
return { ...options, devtools, headless, downloadsPath, proxy };
} }

View file

@ -34,10 +34,6 @@ import { CallMetadata } from '../instrumentation';
import { findChromiumChannel } from './findChromiumChannel'; import { findChromiumChannel } from './findChromiumChannel';
import http from 'http'; import http from 'http';
type LaunchServerOptions = {
_acceptForwardedPorts?: boolean,
};
export class Chromium extends BrowserType { export class Chromium extends BrowserType {
private _devtools: CRDevTools | undefined; private _devtools: CRDevTools | undefined;
@ -123,7 +119,7 @@ export class Chromium extends BrowserType {
transport.send(message); transport.send(message);
} }
_defaultArgs(options: types.LaunchOptions & LaunchServerOptions, isPersistent: boolean, userDataDir: string): string[] { _defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] {
const { args = [], proxy } = options; const { args = [], proxy } = options;
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir')); const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
if (userDataDirArg) if (userDataDirArg)
@ -161,7 +157,7 @@ export class Chromium extends BrowserType {
chromeArguments.push(`--proxy-server=${proxy.server}`); chromeArguments.push(`--proxy-server=${proxy.server}`);
const proxyBypassRules = []; const proxyBypassRules = [];
// https://source.chromium.org/chromium/chromium/src/+/master:net/docs/proxy.md;l=548;drc=71698e610121078e0d1a811054dcf9fd89b49578 // https://source.chromium.org/chromium/chromium/src/+/master:net/docs/proxy.md;l=548;drc=71698e610121078e0d1a811054dcf9fd89b49578
if (options._acceptForwardedPorts) if (this._playwrightOptions.loopbackProxyOverride)
proxyBypassRules.push('<-loopback>'); proxyBypassRules.push('<-loopback>');
if (proxy.bypass) if (proxy.bypass)
proxyBypassRules.push(...proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t)); proxyBypassRules.push(...proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t));

View file

@ -26,6 +26,9 @@ import { WebKit } from './webkit/webkit';
import { Registry } from '../utils/registry'; import { Registry } from '../utils/registry';
import { CallMetadata, createInstrumentation, SdkObject } from './instrumentation'; import { CallMetadata, createInstrumentation, SdkObject } from './instrumentation';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { PortForwardingServer } from './socksSocket';
import { SocksInterceptedSocketHandler } from './socksServer';
import { assert } from '../utils/utils';
export class Playwright extends SdkObject { export class Playwright extends SdkObject {
readonly selectors: Selectors; readonly selectors: Selectors;
@ -35,6 +38,7 @@ export class Playwright extends SdkObject {
readonly firefox: Firefox; readonly firefox: Firefox;
readonly webkit: WebKit; readonly webkit: WebKit;
readonly options: PlaywrightOptions; readonly options: PlaywrightOptions;
private _portForwardingServer: PortForwardingServer | undefined;
constructor(isInternal: boolean) { constructor(isInternal: boolean) {
super({ attribution: { isInternal }, instrumentation: createInstrumentation() } as any, undefined, 'Playwright'); super({ attribution: { isInternal }, instrumentation: createInstrumentation() } as any, undefined, 'Playwright');
@ -54,6 +58,27 @@ export class Playwright extends SdkObject {
this.android = new Android(new AdbBackend(), this.options); this.android = new Android(new AdbBackend(), this.options);
this.selectors = serverSelectors; this.selectors = serverSelectors;
} }
async _enablePortForwarding() {
assert(!this._portForwardingServer);
this._portForwardingServer = await PortForwardingServer.create(this);
this.options.loopbackProxyOverride = () => this._portForwardingServer!.proxyServer();
this._portForwardingServer.on('incomingSocksSocket', (socket: SocksInterceptedSocketHandler) => {
this.emit('incomingSocksSocket', socket);
});
}
_disablePortForwarding() {
if (!this._portForwardingServer)
return;
this._portForwardingServer.stop();
}
_setForwardedPorts(ports: number[]) {
if (!this._portForwardingServer)
throw new Error(`Port forwarding needs to be enabled when launching the server via BrowserType.launchServer.`);
this._portForwardingServer.setForwardedPorts(ports);
}
} }
export function createPlaywright(isInternal = false) { export function createPlaywright(isInternal = false) {

View file

@ -301,8 +301,8 @@ export class SocksProxyServer {
this.server = net.createServer(this._handleConnection.bind(this, incomingMessageHandler)); this.server = net.createServer(this._handleConnection.bind(this, incomingMessageHandler));
} }
public listen(port: number, host?: string) { public async listen(port: number, host?: string) {
this.server.listen(port, host); await new Promise(resolve => this.server.listen(port, host, resolve));
} }
async _handleConnection(incomingMessageHandler: IncomingProxyRequestHandler, socket: net.Socket) { async _handleConnection(incomingMessageHandler: IncomingProxyRequestHandler, socket: net.Socket) {

View file

@ -21,39 +21,31 @@ import { SdkObject } from './instrumentation';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { isLocalIpAddress } from '../utils/utils'; import { isLocalIpAddress } from '../utils/utils';
import { SocksProxyServer, SocksConnectionInfo, SocksInterceptedSocketHandler } from './socksServer'; import { SocksProxyServer, SocksConnectionInfo, SocksInterceptedSocketHandler } from './socksServer';
import { LaunchOptions } from './types';
export class BrowserServerPortForwardingServer extends EventEmitter { export class PortForwardingServer extends EventEmitter {
enabled: boolean;
private _forwardPorts: number[] = []; private _forwardPorts: number[] = [];
private _parent: SdkObject; private _parent: SdkObject;
private _server: SocksProxyServer; private _server: SocksProxyServer;
constructor(parent: SdkObject, enabled: boolean) { constructor(parent: SdkObject) {
super(); super();
this.setMaxListeners(0); this.setMaxListeners(0);
this.enabled = enabled;
this._parent = parent; this._parent = parent;
this._server = new SocksProxyServer(this._handler.bind(this)); this._server = new SocksProxyServer(this._handler.bind(this));
if (enabled) { }
this._server.listen(0);
debugLogger.log('proxy', `initialized server on port ${this._port()})`); static async create(parent: SdkObject) {
} const server = new PortForwardingServer(parent);
await server._server.listen(0);
debugLogger.log('proxy', `starting server on port ${server._port()})`);
return server;
} }
private _port(): number { private _port(): number {
if (!this.enabled)
return 0;
return (this._server.server.address() as net.AddressInfo).port; return (this._server.server.address() as net.AddressInfo).port;
} }
public browserLaunchOptions(): LaunchOptions | undefined { public proxyServer() {
if (!this.enabled) return `socks5://127.0.0.1:${this._port()}`;
return;
return {
proxy: {
server: `socks5://127.0.0.1:${this._port()}`
}
};
} }
private _handler(info: SocksConnectionInfo, forward: () => void, intercept: (parent: SdkObject) => SocksInterceptedSocketHandler): void { private _handler(info: SocksConnectionInfo, forward: () => void, intercept: (parent: SdkObject) => SocksInterceptedSocketHandler): void {
@ -67,16 +59,12 @@ export class BrowserServerPortForwardingServer extends EventEmitter {
this.emit('incomingSocksSocket', socket); this.emit('incomingSocksSocket', socket);
} }
public enablePortForwarding(ports: number[]): void { public setForwardedPorts(ports: number[]): void {
if (!this.enabled)
throw new Error(`Port forwarding needs to be enabled when launching the server via BrowserType.launchServer.`);
debugLogger.log('proxy', `enable port forwarding on ports: ${ports}`); debugLogger.log('proxy', `enable port forwarding on ports: ${ports}`);
this._forwardPorts = ports; this._forwardPorts = ports;
} }
public stop(): void { public stop(): void {
if (!this.enabled)
return;
debugLogger.log('proxy', 'stopping server'); debugLogger.log('proxy', 'stopping server');
this._server.close(); this._server.close();
} }