chore: intercept socks proxy in the driver (#12021)
This commit is contained in:
parent
a0072af2f3
commit
fb00991a78
|
|
@ -128,7 +128,12 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
return await this._wrapApiCall(async () => {
|
return await this._wrapApiCall(async () => {
|
||||||
const deadline = params.timeout ? monotonicTime() + params.timeout : 0;
|
const deadline = params.timeout ? monotonicTime() + params.timeout : 0;
|
||||||
let browser: Browser;
|
let browser: Browser;
|
||||||
const { pipe } = await this._channel.connect({ wsEndpoint, headers: params.headers, slowMo: params.slowMo, timeout: params.timeout });
|
const connectParams: channels.BrowserTypeConnectParams = { wsEndpoint, headers: params.headers, slowMo: params.slowMo, timeout: params.timeout };
|
||||||
|
if ((params as any).__testHookPortForwarding) {
|
||||||
|
connectParams.enableSocksProxy = true;
|
||||||
|
connectParams.socksProxyRedirectPortForTest = (params as any).__testHookPortForwarding.redirectPortForTest;
|
||||||
|
}
|
||||||
|
const { pipe } = await this._channel.connect(connectParams);
|
||||||
const closePipe = () => pipe.close().catch(() => {});
|
const closePipe = () => pipe.close().catch(() => {});
|
||||||
const connection = new Connection();
|
const connection = new Connection();
|
||||||
connection.markAsRemote();
|
connection.markAsRemote();
|
||||||
|
|
@ -168,8 +173,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
throw new Error('Malformed endpoint. Did you use launchServer method?');
|
throw new Error('Malformed endpoint. Did you use launchServer method?');
|
||||||
}
|
}
|
||||||
playwright._setSelectors(this._playwright.selectors);
|
playwright._setSelectors(this._playwright.selectors);
|
||||||
if ((params as any).__testHookPortForwarding)
|
|
||||||
playwright._enablePortForwarding((params as any).__testHookPortForwarding.redirectPortForTest);
|
|
||||||
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
||||||
browser._logger = logger;
|
browser._logger = logger;
|
||||||
browser._shouldCloseConnectionOnClose = true;
|
browser._shouldCloseConnectionOnClose = true;
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,9 @@ class Root extends ChannelOwner<channels.RootChannel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DummyChannelOwner<T> extends ChannelOwner<T> {
|
||||||
|
}
|
||||||
|
|
||||||
export class Connection extends EventEmitter {
|
export class Connection extends EventEmitter {
|
||||||
readonly _objects = new Map<string, ChannelOwner>();
|
readonly _objects = new Map<string, ChannelOwner>();
|
||||||
onmessage = (message: object): void => {};
|
onmessage = (message: object): void => {};
|
||||||
|
|
@ -254,6 +257,9 @@ export class Connection extends EventEmitter {
|
||||||
case 'Selectors':
|
case 'Selectors':
|
||||||
result = new SelectorsOwner(parent, type, guid, initializer);
|
result = new SelectorsOwner(parent, type, guid, initializer);
|
||||||
break;
|
break;
|
||||||
|
case 'SocksSupport':
|
||||||
|
result = new DummyChannelOwner(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
case 'Tracing':
|
case 'Tracing':
|
||||||
result = new Tracing(parent, type, guid, initializer);
|
result = new Tracing(parent, type, guid, initializer);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import dns from 'dns';
|
|
||||||
import net from 'net';
|
|
||||||
import util from 'util';
|
|
||||||
import * as channels from '../protocol/channels';
|
import * as channels from '../protocol/channels';
|
||||||
import { TimeoutError } from '../utils/errors';
|
import { TimeoutError } from '../utils/errors';
|
||||||
import { createSocket } from '../utils/netUtils';
|
import * as socks from '../utils/socksProxy';
|
||||||
import { Android } from './android';
|
import { Android } from './android';
|
||||||
import { BrowserType } from './browserType';
|
import { BrowserType } from './browserType';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
|
|
@ -28,7 +25,6 @@ import { APIRequest } from './fetch';
|
||||||
import { LocalUtils } from './localUtils';
|
import { LocalUtils } from './localUtils';
|
||||||
import { Selectors, SelectorsOwner } from './selectors';
|
import { Selectors, SelectorsOwner } from './selectors';
|
||||||
import { Size } from './types';
|
import { Size } from './types';
|
||||||
const dnsLookupAsync = util.promisify(dns.lookup);
|
|
||||||
|
|
||||||
type DeviceDescriptor = {
|
type DeviceDescriptor = {
|
||||||
userAgent: string,
|
userAgent: string,
|
||||||
|
|
@ -51,8 +47,7 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
|
||||||
readonly request: APIRequest;
|
readonly request: APIRequest;
|
||||||
readonly errors: { TimeoutError: typeof TimeoutError };
|
readonly errors: { TimeoutError: typeof TimeoutError };
|
||||||
_utils: LocalUtils;
|
_utils: LocalUtils;
|
||||||
private _sockets = new Map<string, net.Socket>();
|
private _socksProxyHandler: socks.SocksProxyHandler | undefined;
|
||||||
private _redirectPortForTest: number | undefined;
|
|
||||||
|
|
||||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.PlaywrightInitializer) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.PlaywrightInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
|
|
@ -76,8 +71,7 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
|
||||||
this.selectors._addChannel(selectorsOwner);
|
this.selectors._addChannel(selectorsOwner);
|
||||||
this._connection.on('close', () => {
|
this._connection.on('close', () => {
|
||||||
this.selectors._removeChannel(selectorsOwner);
|
this.selectors._removeChannel(selectorsOwner);
|
||||||
for (const uid of this._sockets.keys())
|
this._socksProxyHandler?.cleanup();
|
||||||
this._onSocksClosed(uid);
|
|
||||||
});
|
});
|
||||||
(global as any)._playwrightInstance = this;
|
(global as any)._playwrightInstance = this;
|
||||||
}
|
}
|
||||||
|
|
@ -93,49 +87,24 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
|
||||||
this.selectors._addChannel(selectorsOwner);
|
this.selectors._addChannel(selectorsOwner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove this methods together with PlaywrightClient.
|
||||||
_enablePortForwarding(redirectPortForTest?: number) {
|
_enablePortForwarding(redirectPortForTest?: number) {
|
||||||
this._redirectPortForTest = redirectPortForTest;
|
const socksSupport = this._initializer.socksSupport;
|
||||||
this._channel.on('socksRequested', ({ uid, host, port }) => this._onSocksRequested(uid, host, port));
|
if (!socksSupport)
|
||||||
this._channel.on('socksData', ({ uid, data }) => this._onSocksData(uid, Buffer.from(data, 'base64')));
|
return;
|
||||||
this._channel.on('socksClosed', ({ uid }) => this._onSocksClosed(uid));
|
const handler = new socks.SocksProxyHandler(redirectPortForTest);
|
||||||
}
|
this._socksProxyHandler = handler;
|
||||||
|
handler.on(socks.SocksProxyHandler.Events.SocksConnected, (payload: socks.SocksSocketConnectedPayload) => socksSupport.socksConnected(payload).catch(() => {}));
|
||||||
private async _onSocksRequested(uid: string, host: string, port: number): Promise<void> {
|
handler.on(socks.SocksProxyHandler.Events.SocksData, (payload: socks.SocksSocketDataPayload) => socksSupport.socksData({ uid: payload.uid, data: payload.data.toString('base64') }).catch(() => {}));
|
||||||
if (host === 'local.playwright')
|
handler.on(socks.SocksProxyHandler.Events.SocksError, (payload: socks.SocksSocketErrorPayload) => socksSupport.socksError(payload).catch(() => {}));
|
||||||
host = 'localhost';
|
handler.on(socks.SocksProxyHandler.Events.SocksFailed, (payload: socks.SocksSocketFailedPayload) => socksSupport.socksFailed(payload).catch(() => {}));
|
||||||
try {
|
handler.on(socks.SocksProxyHandler.Events.SocksEnd, (payload: socks.SocksSocketEndPayload) => socksSupport.socksEnd(payload).catch(() => {}));
|
||||||
if (this._redirectPortForTest)
|
socksSupport.on('socksRequested', payload => handler.socketRequested(payload));
|
||||||
port = this._redirectPortForTest;
|
socksSupport.on('socksClosed', payload => handler.socketClosed(payload));
|
||||||
const { address } = await dnsLookupAsync(host);
|
socksSupport.on('socksData', payload => handler.sendSocketData({ uid: payload.uid, data: Buffer.from(payload.data, 'base64') }));
|
||||||
const socket = await createSocket(address, port);
|
|
||||||
socket.on('data', data => this._channel.socksData({ uid, data: data.toString('base64') }).catch(() => {}));
|
|
||||||
socket.on('error', error => {
|
|
||||||
this._channel.socksError({ uid, error: error.message }).catch(() => { });
|
|
||||||
this._sockets.delete(uid);
|
|
||||||
});
|
|
||||||
socket.on('end', () => {
|
|
||||||
this._channel.socksEnd({ uid }).catch(() => {});
|
|
||||||
this._sockets.delete(uid);
|
|
||||||
});
|
|
||||||
const localAddress = socket.localAddress;
|
|
||||||
const localPort = socket.localPort;
|
|
||||||
this._sockets.set(uid, socket);
|
|
||||||
this._channel.socksConnected({ uid, host: localAddress, port: localPort }).catch(() => {});
|
|
||||||
} catch (error) {
|
|
||||||
this._channel.socksFailed({ uid, errorCode: error.code }).catch(() => {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onSocksData(uid: string, data: Buffer): void {
|
|
||||||
this._sockets.get(uid)?.write(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static from(channel: channels.PlaywrightChannel): Playwright {
|
static from(channel: channels.PlaywrightChannel): Playwright {
|
||||||
return (channel as any)._object;
|
return (channel as any)._object;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onSocksClosed(uid: string): void {
|
|
||||||
this._sockets.get(uid)?.destroy();
|
|
||||||
this._sockets.delete(uid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ import WebSocket from 'ws';
|
||||||
import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher';
|
import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher';
|
||||||
import { getUserAgent, makeWaitForNextTask } from '../utils/utils';
|
import { getUserAgent, makeWaitForNextTask } from '../utils/utils';
|
||||||
import { ManualPromise } from '../utils/async';
|
import { ManualPromise } from '../utils/async';
|
||||||
|
import * as socks from '../utils/socksProxy';
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.BrowserTypeChannel> implements channels.BrowserTypeChannel {
|
export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.BrowserTypeChannel> implements channels.BrowserTypeChannel {
|
||||||
_type_BrowserType = true;
|
_type_BrowserType = true;
|
||||||
|
|
@ -65,11 +67,16 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.Brow
|
||||||
headers: paramsHeaders,
|
headers: paramsHeaders,
|
||||||
followRedirects: true,
|
followRedirects: true,
|
||||||
});
|
});
|
||||||
|
let socksInterceptor: SocksInterceptor | undefined;
|
||||||
const pipe = new JsonPipeDispatcher(this._scope);
|
const pipe = new JsonPipeDispatcher(this._scope);
|
||||||
const openPromise = new ManualPromise<{ pipe: JsonPipeDispatcher }>();
|
const openPromise = new ManualPromise<{ pipe: JsonPipeDispatcher }>();
|
||||||
ws.on('open', () => openPromise.resolve({ pipe }));
|
ws.on('open', () => openPromise.resolve({ pipe }));
|
||||||
ws.on('close', () => pipe.wasClosed());
|
ws.on('close', () => {
|
||||||
|
socksInterceptor?.cleanup();
|
||||||
|
pipe.wasClosed();
|
||||||
|
});
|
||||||
ws.on('error', error => {
|
ws.on('error', error => {
|
||||||
|
socksInterceptor?.cleanup();
|
||||||
if (openPromise.isDone()) {
|
if (openPromise.isDone()) {
|
||||||
pipe.wasClosed(error);
|
pipe.wasClosed(error);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -77,12 +84,19 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.Brow
|
||||||
openPromise.reject(error);
|
openPromise.reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
pipe.on('close', () => ws.close());
|
pipe.on('close', () => {
|
||||||
|
socksInterceptor?.cleanup();
|
||||||
|
ws.close();
|
||||||
|
});
|
||||||
pipe.on('message', message => ws.send(JSON.stringify(message)));
|
pipe.on('message', message => ws.send(JSON.stringify(message)));
|
||||||
ws.addEventListener('message', event => {
|
ws.addEventListener('message', event => {
|
||||||
waitForNextTask(() => {
|
waitForNextTask(() => {
|
||||||
try {
|
try {
|
||||||
pipe.dispatch(JSON.parse(event.data as string));
|
const json = JSON.parse(event.data as string);
|
||||||
|
if (params.enableSocksProxy && json.method === '__create__' && json.params.type === 'SocksSupport')
|
||||||
|
socksInterceptor = new SocksInterceptor(ws, params.socksProxyRedirectPortForTest, json.params.guid);
|
||||||
|
if (!socksInterceptor?.interceptMessage(json))
|
||||||
|
pipe.dispatch(json);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ws.close();
|
ws.close();
|
||||||
}
|
}
|
||||||
|
|
@ -91,3 +105,55 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.Brow
|
||||||
return openPromise;
|
return openPromise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SocksInterceptor {
|
||||||
|
private _handler: socks.SocksProxyHandler;
|
||||||
|
private _channel: channels.SocksSupportChannel & EventEmitter;
|
||||||
|
private _socksSupportObjectGuid: string;
|
||||||
|
private _ids = new Set<number>();
|
||||||
|
|
||||||
|
constructor(ws: WebSocket, redirectPortForTest: number | undefined, socksSupportObjectGuid: string) {
|
||||||
|
this._handler = new socks.SocksProxyHandler(redirectPortForTest);
|
||||||
|
this._socksSupportObjectGuid = socksSupportObjectGuid;
|
||||||
|
|
||||||
|
let lastId = -1;
|
||||||
|
this._channel = new Proxy(new EventEmitter(), {
|
||||||
|
get: (obj: any, prop) => {
|
||||||
|
if ((prop in obj) || obj[prop] !== undefined || typeof prop !== 'string')
|
||||||
|
return obj[prop];
|
||||||
|
return (params: any) => {
|
||||||
|
try {
|
||||||
|
const id = --lastId;
|
||||||
|
this._ids.add(id);
|
||||||
|
ws.send(JSON.stringify({ id, guid: socksSupportObjectGuid, method: prop, params, metadata: { stack: [], apiName: '', internal: true } }));
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}) as channels.SocksSupportChannel & EventEmitter;
|
||||||
|
this._handler.on(socks.SocksProxyHandler.Events.SocksConnected, (payload: socks.SocksSocketConnectedPayload) => this._channel.socksConnected(payload));
|
||||||
|
this._handler.on(socks.SocksProxyHandler.Events.SocksData, (payload: socks.SocksSocketDataPayload) => this._channel.socksData({ uid: payload.uid, data: payload.data.toString('base64') }));
|
||||||
|
this._handler.on(socks.SocksProxyHandler.Events.SocksError, (payload: socks.SocksSocketErrorPayload) => this._channel.socksError(payload));
|
||||||
|
this._handler.on(socks.SocksProxyHandler.Events.SocksFailed, (payload: socks.SocksSocketFailedPayload) => this._channel.socksFailed(payload));
|
||||||
|
this._handler.on(socks.SocksProxyHandler.Events.SocksEnd, (payload: socks.SocksSocketEndPayload) => this._channel.socksEnd(payload));
|
||||||
|
this._channel.on('socksRequested', payload => this._handler.socketRequested(payload));
|
||||||
|
this._channel.on('socksClosed', payload => this._handler.socketClosed(payload));
|
||||||
|
this._channel.on('socksData', payload => this._handler.sendSocketData({ uid: payload.uid, data: Buffer.from(payload.data, 'base64') }));
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
this._handler.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
interceptMessage(message: any): boolean {
|
||||||
|
if (this._ids.has(message.id)) {
|
||||||
|
this._ids.delete(message.id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (message.guid === this._socksSupportObjectGuid) {
|
||||||
|
this._channel.emit(message.method, message.params);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import * as channels from '../protocol/channels';
|
||||||
import { Browser } from '../server/browser';
|
import { Browser } from '../server/browser';
|
||||||
import { GlobalAPIRequestContext } from '../server/fetch';
|
import { GlobalAPIRequestContext } from '../server/fetch';
|
||||||
import { Playwright } from '../server/playwright';
|
import { Playwright } from '../server/playwright';
|
||||||
import { SocksProxy } from '../server/socksProxy';
|
import { SocksProxy, SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../utils/socksProxy';
|
||||||
import * as types from '../server/types';
|
import * as types from '../server/types';
|
||||||
import { AndroidDispatcher } from './androidDispatcher';
|
import { AndroidDispatcher } from './androidDispatcher';
|
||||||
import { BrowserTypeDispatcher } from './browserTypeDispatcher';
|
import { BrowserTypeDispatcher } from './browserTypeDispatcher';
|
||||||
|
|
@ -28,11 +28,11 @@ import { LocalUtilsDispatcher } from './localUtilsDispatcher';
|
||||||
import { APIRequestContextDispatcher } from './networkDispatchers';
|
import { APIRequestContextDispatcher } from './networkDispatchers';
|
||||||
import { SelectorsDispatcher } from './selectorsDispatcher';
|
import { SelectorsDispatcher } from './selectorsDispatcher';
|
||||||
import { ConnectedBrowserDispatcher } from './browserDispatcher';
|
import { ConnectedBrowserDispatcher } from './browserDispatcher';
|
||||||
|
import { createGuid } from '../utils/utils';
|
||||||
|
|
||||||
export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.PlaywrightChannel> implements channels.PlaywrightChannel {
|
export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.PlaywrightChannel> implements channels.PlaywrightChannel {
|
||||||
_type_Playwright;
|
_type_Playwright;
|
||||||
private _browserDispatcher: ConnectedBrowserDispatcher | undefined;
|
private _browserDispatcher: ConnectedBrowserDispatcher | undefined;
|
||||||
private _socksProxy: SocksProxy | undefined;
|
|
||||||
|
|
||||||
constructor(scope: DispatcherScope, playwright: Playwright, socksProxy?: SocksProxy, preLaunchedBrowser?: Browser) {
|
constructor(scope: DispatcherScope, playwright: Playwright, socksProxy?: SocksProxy, preLaunchedBrowser?: Browser) {
|
||||||
const descriptors = require('../server/deviceDescriptors') as types.Devices;
|
const descriptors = require('../server/deviceDescriptors') as types.Devices;
|
||||||
|
|
@ -49,35 +49,10 @@ export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.Playwr
|
||||||
deviceDescriptors,
|
deviceDescriptors,
|
||||||
selectors: new SelectorsDispatcher(scope, browserDispatcher?.selectors || playwright.selectors),
|
selectors: new SelectorsDispatcher(scope, browserDispatcher?.selectors || playwright.selectors),
|
||||||
preLaunchedBrowser: browserDispatcher,
|
preLaunchedBrowser: browserDispatcher,
|
||||||
|
socksSupport: socksProxy ? new SocksSupportDispatcher(scope, socksProxy) : undefined,
|
||||||
}, false);
|
}, false);
|
||||||
this._type_Playwright = true;
|
this._type_Playwright = true;
|
||||||
this._browserDispatcher = browserDispatcher;
|
this._browserDispatcher = browserDispatcher;
|
||||||
if (socksProxy) {
|
|
||||||
this._socksProxy = socksProxy;
|
|
||||||
socksProxy.on(SocksProxy.Events.SocksRequested, data => this._dispatchEvent('socksRequested', data));
|
|
||||||
socksProxy.on(SocksProxy.Events.SocksData, data => this._dispatchEvent('socksData', data));
|
|
||||||
socksProxy.on(SocksProxy.Events.SocksClosed, data => this._dispatchEvent('socksClosed', data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async socksConnected(params: channels.PlaywrightSocksConnectedParams): Promise<void> {
|
|
||||||
this._socksProxy?.socketConnected(params.uid, params.host, params.port);
|
|
||||||
}
|
|
||||||
|
|
||||||
async socksFailed(params: channels.PlaywrightSocksFailedParams): Promise<void> {
|
|
||||||
this._socksProxy?.socketFailed(params.uid, params.errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
async socksData(params: channels.PlaywrightSocksDataParams): Promise<void> {
|
|
||||||
this._socksProxy?.sendSocketData(params.uid, Buffer.from(params.data, 'base64'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async socksError(params: channels.PlaywrightSocksErrorParams): Promise<void> {
|
|
||||||
this._socksProxy?.sendSocketError(params.uid, params.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
async socksEnd(params: channels.PlaywrightSocksEndParams): Promise<void> {
|
|
||||||
this._socksProxy?.sendSocketEnd(params.uid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async newRequest(params: channels.PlaywrightNewRequestParams, metadata?: channels.Metadata): Promise<channels.PlaywrightNewRequestResult> {
|
async newRequest(params: channels.PlaywrightNewRequestParams, metadata?: channels.Metadata): Promise<channels.PlaywrightNewRequestResult> {
|
||||||
|
|
@ -94,3 +69,37 @@ export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.Playwr
|
||||||
await this._browserDispatcher?.cleanupContexts();
|
await this._browserDispatcher?.cleanupContexts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SocksSupportDispatcher extends Dispatcher<{ guid: string }, channels.SocksSupportChannel> implements channels.SocksSupportChannel {
|
||||||
|
_type_SocksSupport: boolean;
|
||||||
|
private _socksProxy: SocksProxy;
|
||||||
|
|
||||||
|
constructor(scope: DispatcherScope, socksProxy: SocksProxy) {
|
||||||
|
super(scope, { guid: 'socksSupport@' + createGuid() }, 'SocksSupport', {});
|
||||||
|
this._type_SocksSupport = true;
|
||||||
|
this._socksProxy = socksProxy;
|
||||||
|
socksProxy.on(SocksProxy.Events.SocksRequested, (payload: SocksSocketRequestedPayload) => this._dispatchEvent('socksRequested', payload));
|
||||||
|
socksProxy.on(SocksProxy.Events.SocksData, (payload: SocksSocketDataPayload) => this._dispatchEvent('socksData', { uid: payload.uid, data: payload.data.toString('base64') }));
|
||||||
|
socksProxy.on(SocksProxy.Events.SocksClosed, (payload: SocksSocketClosedPayload) => this._dispatchEvent('socksClosed', payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
async socksConnected(params: channels.SocksSupportSocksConnectedParams): Promise<void> {
|
||||||
|
this._socksProxy?.socketConnected(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async socksFailed(params: channels.SocksSupportSocksFailedParams): Promise<void> {
|
||||||
|
this._socksProxy?.socketFailed(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async socksData(params: channels.SocksSupportSocksDataParams): Promise<void> {
|
||||||
|
this._socksProxy?.sendSocketData({ uid: params.uid, data: Buffer.from(params.data, 'base64') });
|
||||||
|
}
|
||||||
|
|
||||||
|
async socksError(params: channels.SocksSupportSocksErrorParams): Promise<void> {
|
||||||
|
this._socksProxy?.sendSocketError(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async socksEnd(params: channels.SocksSupportSocksEndParams): Promise<void> {
|
||||||
|
this._socksProxy?.sendSocketEnd(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { DispatcherConnection, Root } from '../dispatchers/dispatcher';
|
||||||
import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher';
|
import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher';
|
||||||
import { createPlaywright } from '../server/playwright';
|
import { createPlaywright } from '../server/playwright';
|
||||||
import { gracefullyCloseAll } from '../utils/processLauncher';
|
import { gracefullyCloseAll } from '../utils/processLauncher';
|
||||||
import { SocksProxy } from '../server/socksProxy';
|
import { SocksProxy } from '../utils/socksProxy';
|
||||||
|
|
||||||
function launchGridWorker(gridURL: string, agentId: string, workerId: string) {
|
function launchGridWorker(gridURL: string, agentId: string, workerId: string) {
|
||||||
const log = debug(`pw:grid:worker${workerId}`);
|
const log = debug(`pw:grid:worker${workerId}`);
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ export type InitializerTraits<T> =
|
||||||
T extends BrowserChannel ? BrowserInitializer :
|
T extends BrowserChannel ? BrowserInitializer :
|
||||||
T extends BrowserTypeChannel ? BrowserTypeInitializer :
|
T extends BrowserTypeChannel ? BrowserTypeInitializer :
|
||||||
T extends SelectorsChannel ? SelectorsInitializer :
|
T extends SelectorsChannel ? SelectorsInitializer :
|
||||||
|
T extends SocksSupportChannel ? SocksSupportInitializer :
|
||||||
T extends PlaywrightChannel ? PlaywrightInitializer :
|
T extends PlaywrightChannel ? PlaywrightInitializer :
|
||||||
T extends RootChannel ? RootInitializer :
|
T extends RootChannel ? RootInitializer :
|
||||||
T extends LocalUtilsChannel ? LocalUtilsInitializer :
|
T extends LocalUtilsChannel ? LocalUtilsInitializer :
|
||||||
|
|
@ -85,6 +86,7 @@ export type EventsTraits<T> =
|
||||||
T extends BrowserChannel ? BrowserEvents :
|
T extends BrowserChannel ? BrowserEvents :
|
||||||
T extends BrowserTypeChannel ? BrowserTypeEvents :
|
T extends BrowserTypeChannel ? BrowserTypeEvents :
|
||||||
T extends SelectorsChannel ? SelectorsEvents :
|
T extends SelectorsChannel ? SelectorsEvents :
|
||||||
|
T extends SocksSupportChannel ? SocksSupportEvents :
|
||||||
T extends PlaywrightChannel ? PlaywrightEvents :
|
T extends PlaywrightChannel ? PlaywrightEvents :
|
||||||
T extends RootChannel ? RootEvents :
|
T extends RootChannel ? RootEvents :
|
||||||
T extends LocalUtilsChannel ? LocalUtilsEvents :
|
T extends LocalUtilsChannel ? LocalUtilsEvents :
|
||||||
|
|
@ -120,6 +122,7 @@ export type EventTargetTraits<T> =
|
||||||
T extends BrowserChannel ? BrowserEventTarget :
|
T extends BrowserChannel ? BrowserEventTarget :
|
||||||
T extends BrowserTypeChannel ? BrowserTypeEventTarget :
|
T extends BrowserTypeChannel ? BrowserTypeEventTarget :
|
||||||
T extends SelectorsChannel ? SelectorsEventTarget :
|
T extends SelectorsChannel ? SelectorsEventTarget :
|
||||||
|
T extends SocksSupportChannel ? SocksSupportEventTarget :
|
||||||
T extends PlaywrightChannel ? PlaywrightEventTarget :
|
T extends PlaywrightChannel ? PlaywrightEventTarget :
|
||||||
T extends RootChannel ? RootEventTarget :
|
T extends RootChannel ? RootEventTarget :
|
||||||
T extends LocalUtilsChannel ? LocalUtilsEventTarget :
|
T extends LocalUtilsChannel ? LocalUtilsEventTarget :
|
||||||
|
|
@ -423,74 +426,15 @@ export type PlaywrightInitializer = {
|
||||||
}[],
|
}[],
|
||||||
selectors: SelectorsChannel,
|
selectors: SelectorsChannel,
|
||||||
preLaunchedBrowser?: BrowserChannel,
|
preLaunchedBrowser?: BrowserChannel,
|
||||||
|
socksSupport?: SocksSupportChannel,
|
||||||
};
|
};
|
||||||
export interface PlaywrightEventTarget {
|
export interface PlaywrightEventTarget {
|
||||||
on(event: 'socksRequested', callback: (params: PlaywrightSocksRequestedEvent) => void): this;
|
|
||||||
on(event: 'socksData', callback: (params: PlaywrightSocksDataEvent) => void): this;
|
|
||||||
on(event: 'socksClosed', callback: (params: PlaywrightSocksClosedEvent) => void): this;
|
|
||||||
}
|
}
|
||||||
export interface PlaywrightChannel extends PlaywrightEventTarget, Channel {
|
export interface PlaywrightChannel extends PlaywrightEventTarget, Channel {
|
||||||
_type_Playwright: boolean;
|
_type_Playwright: boolean;
|
||||||
socksConnected(params: PlaywrightSocksConnectedParams, metadata?: Metadata): Promise<PlaywrightSocksConnectedResult>;
|
|
||||||
socksFailed(params: PlaywrightSocksFailedParams, metadata?: Metadata): Promise<PlaywrightSocksFailedResult>;
|
|
||||||
socksData(params: PlaywrightSocksDataParams, metadata?: Metadata): Promise<PlaywrightSocksDataResult>;
|
|
||||||
socksError(params: PlaywrightSocksErrorParams, metadata?: Metadata): Promise<PlaywrightSocksErrorResult>;
|
|
||||||
socksEnd(params: PlaywrightSocksEndParams, metadata?: Metadata): Promise<PlaywrightSocksEndResult>;
|
|
||||||
newRequest(params: PlaywrightNewRequestParams, metadata?: Metadata): Promise<PlaywrightNewRequestResult>;
|
newRequest(params: PlaywrightNewRequestParams, metadata?: Metadata): Promise<PlaywrightNewRequestResult>;
|
||||||
hideHighlight(params?: PlaywrightHideHighlightParams, metadata?: Metadata): Promise<PlaywrightHideHighlightResult>;
|
hideHighlight(params?: PlaywrightHideHighlightParams, metadata?: Metadata): Promise<PlaywrightHideHighlightResult>;
|
||||||
}
|
}
|
||||||
export type PlaywrightSocksRequestedEvent = {
|
|
||||||
uid: string,
|
|
||||||
host: string,
|
|
||||||
port: number,
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksDataEvent = {
|
|
||||||
uid: string,
|
|
||||||
data: Binary,
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksClosedEvent = {
|
|
||||||
uid: string,
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksConnectedParams = {
|
|
||||||
uid: string,
|
|
||||||
host: string,
|
|
||||||
port: number,
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksConnectedOptions = {
|
|
||||||
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksConnectedResult = void;
|
|
||||||
export type PlaywrightSocksFailedParams = {
|
|
||||||
uid: string,
|
|
||||||
errorCode: string,
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksFailedOptions = {
|
|
||||||
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksFailedResult = void;
|
|
||||||
export type PlaywrightSocksDataParams = {
|
|
||||||
uid: string,
|
|
||||||
data: Binary,
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksDataOptions = {
|
|
||||||
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksDataResult = void;
|
|
||||||
export type PlaywrightSocksErrorParams = {
|
|
||||||
uid: string,
|
|
||||||
error: string,
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksErrorOptions = {
|
|
||||||
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksErrorResult = void;
|
|
||||||
export type PlaywrightSocksEndParams = {
|
|
||||||
uid: string,
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksEndOptions = {
|
|
||||||
|
|
||||||
};
|
|
||||||
export type PlaywrightSocksEndResult = void;
|
|
||||||
export type PlaywrightNewRequestParams = {
|
export type PlaywrightNewRequestParams = {
|
||||||
baseURL?: string,
|
baseURL?: string,
|
||||||
userAgent?: string,
|
userAgent?: string,
|
||||||
|
|
@ -543,9 +487,80 @@ export type PlaywrightHideHighlightOptions = {};
|
||||||
export type PlaywrightHideHighlightResult = void;
|
export type PlaywrightHideHighlightResult = void;
|
||||||
|
|
||||||
export interface PlaywrightEvents {
|
export interface PlaywrightEvents {
|
||||||
'socksRequested': PlaywrightSocksRequestedEvent;
|
}
|
||||||
'socksData': PlaywrightSocksDataEvent;
|
|
||||||
'socksClosed': PlaywrightSocksClosedEvent;
|
// ----------- SocksSupport -----------
|
||||||
|
export type SocksSupportInitializer = {};
|
||||||
|
export interface SocksSupportEventTarget {
|
||||||
|
on(event: 'socksRequested', callback: (params: SocksSupportSocksRequestedEvent) => void): this;
|
||||||
|
on(event: 'socksData', callback: (params: SocksSupportSocksDataEvent) => void): this;
|
||||||
|
on(event: 'socksClosed', callback: (params: SocksSupportSocksClosedEvent) => void): this;
|
||||||
|
}
|
||||||
|
export interface SocksSupportChannel extends SocksSupportEventTarget, Channel {
|
||||||
|
_type_SocksSupport: boolean;
|
||||||
|
socksConnected(params: SocksSupportSocksConnectedParams, metadata?: Metadata): Promise<SocksSupportSocksConnectedResult>;
|
||||||
|
socksFailed(params: SocksSupportSocksFailedParams, metadata?: Metadata): Promise<SocksSupportSocksFailedResult>;
|
||||||
|
socksData(params: SocksSupportSocksDataParams, metadata?: Metadata): Promise<SocksSupportSocksDataResult>;
|
||||||
|
socksError(params: SocksSupportSocksErrorParams, metadata?: Metadata): Promise<SocksSupportSocksErrorResult>;
|
||||||
|
socksEnd(params: SocksSupportSocksEndParams, metadata?: Metadata): Promise<SocksSupportSocksEndResult>;
|
||||||
|
}
|
||||||
|
export type SocksSupportSocksRequestedEvent = {
|
||||||
|
uid: string,
|
||||||
|
host: string,
|
||||||
|
port: number,
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksDataEvent = {
|
||||||
|
uid: string,
|
||||||
|
data: Binary,
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksClosedEvent = {
|
||||||
|
uid: string,
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksConnectedParams = {
|
||||||
|
uid: string,
|
||||||
|
host: string,
|
||||||
|
port: number,
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksConnectedOptions = {
|
||||||
|
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksConnectedResult = void;
|
||||||
|
export type SocksSupportSocksFailedParams = {
|
||||||
|
uid: string,
|
||||||
|
errorCode: string,
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksFailedOptions = {
|
||||||
|
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksFailedResult = void;
|
||||||
|
export type SocksSupportSocksDataParams = {
|
||||||
|
uid: string,
|
||||||
|
data: Binary,
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksDataOptions = {
|
||||||
|
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksDataResult = void;
|
||||||
|
export type SocksSupportSocksErrorParams = {
|
||||||
|
uid: string,
|
||||||
|
error: string,
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksErrorOptions = {
|
||||||
|
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksErrorResult = void;
|
||||||
|
export type SocksSupportSocksEndParams = {
|
||||||
|
uid: string,
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksEndOptions = {
|
||||||
|
|
||||||
|
};
|
||||||
|
export type SocksSupportSocksEndResult = void;
|
||||||
|
|
||||||
|
export interface SocksSupportEvents {
|
||||||
|
'socksRequested': SocksSupportSocksRequestedEvent;
|
||||||
|
'socksData': SocksSupportSocksDataEvent;
|
||||||
|
'socksClosed': SocksSupportSocksClosedEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------- Selectors -----------
|
// ----------- Selectors -----------
|
||||||
|
|
@ -588,11 +603,15 @@ export type BrowserTypeConnectParams = {
|
||||||
headers?: any,
|
headers?: any,
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
|
enableSocksProxy?: boolean,
|
||||||
|
socksProxyRedirectPortForTest?: number,
|
||||||
};
|
};
|
||||||
export type BrowserTypeConnectOptions = {
|
export type BrowserTypeConnectOptions = {
|
||||||
headers?: any,
|
headers?: any,
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
|
enableSocksProxy?: boolean,
|
||||||
|
socksProxyRedirectPortForTest?: number,
|
||||||
};
|
};
|
||||||
export type BrowserTypeConnectResult = {
|
export type BrowserTypeConnectResult = {
|
||||||
pipe: JsonPipeChannel,
|
pipe: JsonPipeChannel,
|
||||||
|
|
|
||||||
|
|
@ -483,34 +483,10 @@ Playwright:
|
||||||
selectors: Selectors
|
selectors: Selectors
|
||||||
# Only present when connecting remotely via BrowserType.connect() method.
|
# Only present when connecting remotely via BrowserType.connect() method.
|
||||||
preLaunchedBrowser: Browser?
|
preLaunchedBrowser: Browser?
|
||||||
|
# Only present when socks proxy is supported.
|
||||||
|
socksSupport: SocksSupport?
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
|
|
||||||
socksConnected:
|
|
||||||
parameters:
|
|
||||||
uid: string
|
|
||||||
host: string
|
|
||||||
port: number
|
|
||||||
|
|
||||||
socksFailed:
|
|
||||||
parameters:
|
|
||||||
uid: string
|
|
||||||
errorCode: string
|
|
||||||
|
|
||||||
socksData:
|
|
||||||
parameters:
|
|
||||||
uid: string
|
|
||||||
data: binary
|
|
||||||
|
|
||||||
socksError:
|
|
||||||
parameters:
|
|
||||||
uid: string
|
|
||||||
error: string
|
|
||||||
|
|
||||||
socksEnd:
|
|
||||||
parameters:
|
|
||||||
uid: string
|
|
||||||
|
|
||||||
newRequest:
|
newRequest:
|
||||||
parameters:
|
parameters:
|
||||||
baseURL: string?
|
baseURL: string?
|
||||||
|
|
@ -548,6 +524,35 @@ Playwright:
|
||||||
|
|
||||||
hideHighlight:
|
hideHighlight:
|
||||||
|
|
||||||
|
SocksSupport:
|
||||||
|
type: interface
|
||||||
|
|
||||||
|
commands:
|
||||||
|
socksConnected:
|
||||||
|
parameters:
|
||||||
|
uid: string
|
||||||
|
host: string
|
||||||
|
port: number
|
||||||
|
|
||||||
|
socksFailed:
|
||||||
|
parameters:
|
||||||
|
uid: string
|
||||||
|
errorCode: string
|
||||||
|
|
||||||
|
socksData:
|
||||||
|
parameters:
|
||||||
|
uid: string
|
||||||
|
data: binary
|
||||||
|
|
||||||
|
socksError:
|
||||||
|
parameters:
|
||||||
|
uid: string
|
||||||
|
error: string
|
||||||
|
|
||||||
|
socksEnd:
|
||||||
|
parameters:
|
||||||
|
uid: string
|
||||||
|
|
||||||
events:
|
events:
|
||||||
socksRequested:
|
socksRequested:
|
||||||
parameters:
|
parameters:
|
||||||
|
|
@ -564,7 +569,6 @@ Playwright:
|
||||||
parameters:
|
parameters:
|
||||||
uid: string
|
uid: string
|
||||||
|
|
||||||
|
|
||||||
Selectors:
|
Selectors:
|
||||||
type: interface
|
type: interface
|
||||||
|
|
||||||
|
|
@ -592,6 +596,8 @@ BrowserType:
|
||||||
headers: json?
|
headers: json?
|
||||||
slowMo: number?
|
slowMo: number?
|
||||||
timeout: number?
|
timeout: number?
|
||||||
|
enableSocksProxy: boolean?
|
||||||
|
socksProxyRedirectPortForTest: number?
|
||||||
returns:
|
returns:
|
||||||
pipe: JsonPipe
|
pipe: JsonPipe
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -196,26 +196,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
scheme.RootInitializeParams = tObject({
|
scheme.RootInitializeParams = tObject({
|
||||||
sdkLanguage: tString,
|
sdkLanguage: tString,
|
||||||
});
|
});
|
||||||
scheme.PlaywrightSocksConnectedParams = tObject({
|
|
||||||
uid: tString,
|
|
||||||
host: tString,
|
|
||||||
port: tNumber,
|
|
||||||
});
|
|
||||||
scheme.PlaywrightSocksFailedParams = tObject({
|
|
||||||
uid: tString,
|
|
||||||
errorCode: tString,
|
|
||||||
});
|
|
||||||
scheme.PlaywrightSocksDataParams = tObject({
|
|
||||||
uid: tString,
|
|
||||||
data: tBinary,
|
|
||||||
});
|
|
||||||
scheme.PlaywrightSocksErrorParams = tObject({
|
|
||||||
uid: tString,
|
|
||||||
error: tString,
|
|
||||||
});
|
|
||||||
scheme.PlaywrightSocksEndParams = tObject({
|
|
||||||
uid: tString,
|
|
||||||
});
|
|
||||||
scheme.PlaywrightNewRequestParams = tObject({
|
scheme.PlaywrightNewRequestParams = tObject({
|
||||||
baseURL: tOptional(tString),
|
baseURL: tOptional(tString),
|
||||||
userAgent: tOptional(tString),
|
userAgent: tOptional(tString),
|
||||||
|
|
@ -239,6 +219,26 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
tracesDir: tOptional(tString),
|
tracesDir: tOptional(tString),
|
||||||
});
|
});
|
||||||
scheme.PlaywrightHideHighlightParams = tOptional(tObject({}));
|
scheme.PlaywrightHideHighlightParams = tOptional(tObject({}));
|
||||||
|
scheme.SocksSupportSocksConnectedParams = tObject({
|
||||||
|
uid: tString,
|
||||||
|
host: tString,
|
||||||
|
port: tNumber,
|
||||||
|
});
|
||||||
|
scheme.SocksSupportSocksFailedParams = tObject({
|
||||||
|
uid: tString,
|
||||||
|
errorCode: tString,
|
||||||
|
});
|
||||||
|
scheme.SocksSupportSocksDataParams = tObject({
|
||||||
|
uid: tString,
|
||||||
|
data: tBinary,
|
||||||
|
});
|
||||||
|
scheme.SocksSupportSocksErrorParams = tObject({
|
||||||
|
uid: tString,
|
||||||
|
error: tString,
|
||||||
|
});
|
||||||
|
scheme.SocksSupportSocksEndParams = tObject({
|
||||||
|
uid: tString,
|
||||||
|
});
|
||||||
scheme.SelectorsRegisterParams = tObject({
|
scheme.SelectorsRegisterParams = tObject({
|
||||||
name: tString,
|
name: tString,
|
||||||
source: tString,
|
source: tString,
|
||||||
|
|
@ -249,6 +249,8 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
headers: tOptional(tAny),
|
headers: tOptional(tAny),
|
||||||
slowMo: tOptional(tNumber),
|
slowMo: tOptional(tNumber),
|
||||||
timeout: tOptional(tNumber),
|
timeout: tOptional(tNumber),
|
||||||
|
enableSocksProxy: tOptional(tBoolean),
|
||||||
|
socksProxyRedirectPortForTest: tOptional(tNumber),
|
||||||
});
|
});
|
||||||
scheme.BrowserTypeLaunchParams = tObject({
|
scheme.BrowserTypeLaunchParams = tObject({
|
||||||
channel: tOptional(tString),
|
channel: tOptional(tString),
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import { Browser } from '../server/browser';
|
||||||
import { gracefullyCloseAll } from '../utils/processLauncher';
|
import { gracefullyCloseAll } from '../utils/processLauncher';
|
||||||
import { registry } from '../utils/registry';
|
import { registry } from '../utils/registry';
|
||||||
import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher';
|
import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher';
|
||||||
import { SocksProxy } from '../server/socksProxy';
|
import { SocksProxy } from '../utils/socksProxy';
|
||||||
|
|
||||||
const debugLog = debug('pw:server');
|
const debugLog = debug('pw:server');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) Microsoft Corporation.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import net, { AddressInfo } from 'net';
|
|
||||||
import { debugLogger } from '../utils/debugLogger';
|
|
||||||
import { SocksConnection, SocksConnectionClient } from '../utils/socksProxy';
|
|
||||||
import { createGuid } from '../utils/utils';
|
|
||||||
import EventEmitter from 'events';
|
|
||||||
|
|
||||||
export class SocksProxy extends EventEmitter implements SocksConnectionClient {
|
|
||||||
static Events = {
|
|
||||||
SocksRequested: 'socksRequested',
|
|
||||||
SocksData: 'socksData',
|
|
||||||
SocksClosed: 'socksClosed',
|
|
||||||
};
|
|
||||||
|
|
||||||
private _server: net.Server;
|
|
||||||
private _connections = new Map<string, SocksConnection>();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this._server = new net.Server((socket: net.Socket) => {
|
|
||||||
const uid = createGuid();
|
|
||||||
const connection = new SocksConnection(uid, socket, this);
|
|
||||||
this._connections.set(uid, connection);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async listen(port: number): Promise<number> {
|
|
||||||
return new Promise(f => {
|
|
||||||
this._server.listen(port, () => {
|
|
||||||
const port = (this._server.address() as AddressInfo).port;
|
|
||||||
debugLogger.log('proxy', `Starting socks proxy server on port ${port}`);
|
|
||||||
f(port);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async close() {
|
|
||||||
await new Promise(f => this._server.close(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
onSocketRequested(uid: string, host: string, port: number): void {
|
|
||||||
this.emit(SocksProxy.Events.SocksRequested, { uid, host, port });
|
|
||||||
}
|
|
||||||
|
|
||||||
onSocketData(uid: string, data: Buffer): void {
|
|
||||||
this.emit(SocksProxy.Events.SocksData, { uid, data: data.toString('base64') });
|
|
||||||
}
|
|
||||||
|
|
||||||
onSocketClosed(uid: string): void {
|
|
||||||
this.emit(SocksProxy.Events.SocksClosed, { uid });
|
|
||||||
}
|
|
||||||
|
|
||||||
socketConnected(uid: string, host: string, port: number) {
|
|
||||||
this._connections.get(uid)?.socketConnected(host, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
socketFailed(uid: string, errorCode: string) {
|
|
||||||
this._connections.get(uid)?.socketFailed(errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendSocketData(uid: string, buffer: Buffer) {
|
|
||||||
this._connections.get(uid)?.sendData(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendSocketEnd(uid: string) {
|
|
||||||
this._connections.get(uid)?.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendSocketError(uid: string, error: string) {
|
|
||||||
this._connections.get(uid)?.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -14,8 +14,15 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import net from 'net';
|
import dns from 'dns';
|
||||||
import { assert } from './utils';
|
import EventEmitter from 'events';
|
||||||
|
import net, { AddressInfo } from 'net';
|
||||||
|
import util from 'util';
|
||||||
|
import { debugLogger } from './debugLogger';
|
||||||
|
import { createSocket } from './netUtils';
|
||||||
|
import { assert, createGuid } from './utils';
|
||||||
|
|
||||||
|
const dnsLookupAsync = util.promisify(dns.lookup);
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc1928
|
// https://tools.ietf.org/html/rfc1928
|
||||||
|
|
||||||
|
|
@ -50,13 +57,21 @@ enum SocksReply {
|
||||||
AddressTypeNotSupported = 0x08
|
AddressTypeNotSupported = 0x08
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SocksConnectionClient {
|
export type SocksSocketRequestedPayload = { uid: string, host: string, port: number };
|
||||||
onSocketRequested(uid: string, host: string, port: number): void;
|
export type SocksSocketConnectedPayload = { uid: string, host: string, port: number };
|
||||||
onSocketData(uid: string, data: Buffer): void;
|
export type SocksSocketDataPayload = { uid: string, data: Buffer };
|
||||||
onSocketClosed(uid: string): void;
|
export type SocksSocketErrorPayload = { uid: string, error: string };
|
||||||
|
export type SocksSocketFailedPayload = { uid: string, errorCode: string };
|
||||||
|
export type SocksSocketClosedPayload = { uid: string };
|
||||||
|
export type SocksSocketEndPayload = { uid: string };
|
||||||
|
|
||||||
|
interface SocksConnectionClient {
|
||||||
|
onSocketRequested(payload: SocksSocketRequestedPayload): void;
|
||||||
|
onSocketData(payload: SocksSocketDataPayload): void;
|
||||||
|
onSocketClosed(payload: SocksSocketClosedPayload): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SocksConnection {
|
class SocksConnection {
|
||||||
private _buffer = Buffer.from([]);
|
private _buffer = Buffer.from([]);
|
||||||
private _offset = 0;
|
private _offset = 0;
|
||||||
private _fence = 0;
|
private _fence = 0;
|
||||||
|
|
@ -94,7 +109,7 @@ export class SocksConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
this._socket.off('data', this._boundOnData);
|
this._socket.off('data', this._boundOnData);
|
||||||
this._client.onSocketRequested(this._uid, host, port);
|
this._client.onSocketRequested({ uid: this._uid, host, port });
|
||||||
}
|
}
|
||||||
|
|
||||||
async _authenticate(): Promise<boolean> {
|
async _authenticate(): Promise<boolean> {
|
||||||
|
|
@ -199,7 +214,7 @@ export class SocksConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onClose() {
|
private _onClose() {
|
||||||
this._client.onSocketClosed(this._uid);
|
this._client.onSocketClosed({ uid: this._uid });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onData(buffer: Buffer) {
|
private _onData(buffer: Buffer) {
|
||||||
|
|
@ -220,7 +235,7 @@ export class SocksConnection {
|
||||||
...parseIP(host), // Address
|
...parseIP(host), // Address
|
||||||
port << 8, port & 0xFF // Port
|
port << 8, port & 0xFF // Port
|
||||||
]));
|
]));
|
||||||
this._socket.on('data', data => this._client.onSocketData(this._uid, data));
|
this._socket.on('data', data => this._client.onSocketData({ uid: this._uid, data }));
|
||||||
}
|
}
|
||||||
|
|
||||||
socketFailed(errorCode: string) {
|
socketFailed(errorCode: string) {
|
||||||
|
|
@ -268,3 +283,134 @@ function parseIP(address: string): number[] {
|
||||||
throw new Error('IPv6 is not supported');
|
throw new Error('IPv6 is not supported');
|
||||||
return address.split('.', 4).map(t => +t);
|
return address.split('.', 4).map(t => +t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SocksProxy extends EventEmitter implements SocksConnectionClient {
|
||||||
|
static Events = {
|
||||||
|
SocksRequested: 'socksRequested',
|
||||||
|
SocksData: 'socksData',
|
||||||
|
SocksClosed: 'socksClosed',
|
||||||
|
};
|
||||||
|
|
||||||
|
private _server: net.Server;
|
||||||
|
private _connections = new Map<string, SocksConnection>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._server = new net.Server((socket: net.Socket) => {
|
||||||
|
const uid = createGuid();
|
||||||
|
const connection = new SocksConnection(uid, socket, this);
|
||||||
|
this._connections.set(uid, connection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async listen(port: number): Promise<number> {
|
||||||
|
return new Promise(f => {
|
||||||
|
this._server.listen(port, () => {
|
||||||
|
const port = (this._server.address() as AddressInfo).port;
|
||||||
|
debugLogger.log('proxy', `Starting socks proxy server on port ${port}`);
|
||||||
|
f(port);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
await new Promise(f => this._server.close(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
onSocketRequested(payload: SocksSocketRequestedPayload) {
|
||||||
|
this.emit(SocksProxy.Events.SocksRequested, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSocketData(payload: SocksSocketDataPayload): void {
|
||||||
|
this.emit(SocksProxy.Events.SocksData, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSocketClosed(payload: SocksSocketClosedPayload): void {
|
||||||
|
this.emit(SocksProxy.Events.SocksClosed, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
socketConnected({ uid, host, port }: SocksSocketConnectedPayload) {
|
||||||
|
this._connections.get(uid)?.socketConnected(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
socketFailed({ uid, errorCode }: SocksSocketFailedPayload) {
|
||||||
|
this._connections.get(uid)?.socketFailed(errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSocketData({ uid, data }: SocksSocketDataPayload) {
|
||||||
|
this._connections.get(uid)?.sendData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSocketEnd({ uid }: SocksSocketEndPayload) {
|
||||||
|
this._connections.get(uid)?.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSocketError({ uid, error }: SocksSocketErrorPayload) {
|
||||||
|
this._connections.get(uid)?.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SocksProxyHandler extends EventEmitter {
|
||||||
|
static Events = {
|
||||||
|
SocksConnected: 'socksConnected',
|
||||||
|
SocksData: 'socksData',
|
||||||
|
SocksError: 'socksError',
|
||||||
|
SocksFailed: 'socksFailed',
|
||||||
|
SocksEnd: 'socksEnd',
|
||||||
|
};
|
||||||
|
|
||||||
|
private _sockets = new Map<string, net.Socket>();
|
||||||
|
private _redirectPortForTest: number | undefined;
|
||||||
|
|
||||||
|
constructor(redirectPortForTest?: number) {
|
||||||
|
super();
|
||||||
|
this._redirectPortForTest = redirectPortForTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
for (const uid of this._sockets.keys())
|
||||||
|
this.socketClosed({ uid });
|
||||||
|
}
|
||||||
|
|
||||||
|
async socketRequested({ uid, host, port }: SocksSocketRequestedPayload): Promise<void> {
|
||||||
|
if (host === 'local.playwright')
|
||||||
|
host = 'localhost';
|
||||||
|
try {
|
||||||
|
if (this._redirectPortForTest)
|
||||||
|
port = this._redirectPortForTest;
|
||||||
|
const { address } = await dnsLookupAsync(host);
|
||||||
|
const socket = await createSocket(address, port);
|
||||||
|
socket.on('data', data => {
|
||||||
|
const payload: SocksSocketDataPayload = { uid, data };
|
||||||
|
this.emit(SocksProxyHandler.Events.SocksData, payload);
|
||||||
|
});
|
||||||
|
socket.on('error', error => {
|
||||||
|
const payload: SocksSocketErrorPayload = { uid, error: error.message };
|
||||||
|
this.emit(SocksProxyHandler.Events.SocksError, payload);
|
||||||
|
this._sockets.delete(uid);
|
||||||
|
});
|
||||||
|
socket.on('end', () => {
|
||||||
|
const payload: SocksSocketEndPayload = { uid };
|
||||||
|
this.emit(SocksProxyHandler.Events.SocksEnd, payload);
|
||||||
|
this._sockets.delete(uid);
|
||||||
|
});
|
||||||
|
const localAddress = socket.localAddress;
|
||||||
|
const localPort = socket.localPort;
|
||||||
|
this._sockets.set(uid, socket);
|
||||||
|
const payload: SocksSocketConnectedPayload = { uid, host: localAddress, port: localPort };
|
||||||
|
this.emit(SocksProxyHandler.Events.SocksConnected, payload);
|
||||||
|
} catch (error) {
|
||||||
|
const payload: SocksSocketFailedPayload = { uid, errorCode: error.code };
|
||||||
|
this.emit(SocksProxyHandler.Events.SocksFailed, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSocketData({ uid, data }: SocksSocketDataPayload): void {
|
||||||
|
this._sockets.get(uid)?.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
socketClosed({ uid }: SocksSocketClosedPayload): void {
|
||||||
|
this._sockets.get(uid)?.destroy();
|
||||||
|
this._sockets.delete(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ it.use({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.skip(({ mode }) => mode === 'service');
|
||||||
|
|
||||||
it('should scope context handles', async ({ browserType, server }) => {
|
it('should scope context handles', async ({ browserType, server }) => {
|
||||||
const browser = await browserType.launch();
|
const browser = await browserType.launch();
|
||||||
const GOLDEN_PRECONDITION = {
|
const GOLDEN_PRECONDITION = {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue