mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-05 19:04:43 +03:00
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 () => {
|
||||
const deadline = params.timeout ? monotonicTime() + params.timeout : 0;
|
||||
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 connection = new Connection();
|
||||
connection.markAsRemote();
|
||||
@ -168,8 +173,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||
throw new Error('Malformed endpoint. Did you use launchServer method?');
|
||||
}
|
||||
playwright._setSelectors(this._playwright.selectors);
|
||||
if ((params as any).__testHookPortForwarding)
|
||||
playwright._enablePortForwarding((params as any).__testHookPortForwarding.redirectPortForTest);
|
||||
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
||||
browser._logger = logger;
|
||||
browser._shouldCloseConnectionOnClose = true;
|
||||
|
@ -55,6 +55,9 @@ class Root extends ChannelOwner<channels.RootChannel> {
|
||||
}
|
||||
}
|
||||
|
||||
class DummyChannelOwner<T> extends ChannelOwner<T> {
|
||||
}
|
||||
|
||||
export class Connection extends EventEmitter {
|
||||
readonly _objects = new Map<string, ChannelOwner>();
|
||||
onmessage = (message: object): void => {};
|
||||
@ -254,6 +257,9 @@ export class Connection extends EventEmitter {
|
||||
case 'Selectors':
|
||||
result = new SelectorsOwner(parent, type, guid, initializer);
|
||||
break;
|
||||
case 'SocksSupport':
|
||||
result = new DummyChannelOwner(parent, type, guid, initializer);
|
||||
break;
|
||||
case 'Tracing':
|
||||
result = new Tracing(parent, type, guid, initializer);
|
||||
break;
|
||||
|
@ -14,12 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import dns from 'dns';
|
||||
import net from 'net';
|
||||
import util from 'util';
|
||||
import * as channels from '../protocol/channels';
|
||||
import { TimeoutError } from '../utils/errors';
|
||||
import { createSocket } from '../utils/netUtils';
|
||||
import * as socks from '../utils/socksProxy';
|
||||
import { Android } from './android';
|
||||
import { BrowserType } from './browserType';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
@ -28,7 +25,6 @@ import { APIRequest } from './fetch';
|
||||
import { LocalUtils } from './localUtils';
|
||||
import { Selectors, SelectorsOwner } from './selectors';
|
||||
import { Size } from './types';
|
||||
const dnsLookupAsync = util.promisify(dns.lookup);
|
||||
|
||||
type DeviceDescriptor = {
|
||||
userAgent: string,
|
||||
@ -51,8 +47,7 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
|
||||
readonly request: APIRequest;
|
||||
readonly errors: { TimeoutError: typeof TimeoutError };
|
||||
_utils: LocalUtils;
|
||||
private _sockets = new Map<string, net.Socket>();
|
||||
private _redirectPortForTest: number | undefined;
|
||||
private _socksProxyHandler: socks.SocksProxyHandler | undefined;
|
||||
|
||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.PlaywrightInitializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
@ -76,8 +71,7 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
|
||||
this.selectors._addChannel(selectorsOwner);
|
||||
this._connection.on('close', () => {
|
||||
this.selectors._removeChannel(selectorsOwner);
|
||||
for (const uid of this._sockets.keys())
|
||||
this._onSocksClosed(uid);
|
||||
this._socksProxyHandler?.cleanup();
|
||||
});
|
||||
(global as any)._playwrightInstance = this;
|
||||
}
|
||||
@ -93,49 +87,24 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
|
||||
this.selectors._addChannel(selectorsOwner);
|
||||
}
|
||||
|
||||
// TODO: remove this methods together with PlaywrightClient.
|
||||
_enablePortForwarding(redirectPortForTest?: number) {
|
||||
this._redirectPortForTest = redirectPortForTest;
|
||||
this._channel.on('socksRequested', ({ uid, host, port }) => this._onSocksRequested(uid, host, port));
|
||||
this._channel.on('socksData', ({ uid, data }) => this._onSocksData(uid, Buffer.from(data, 'base64')));
|
||||
this._channel.on('socksClosed', ({ uid }) => this._onSocksClosed(uid));
|
||||
}
|
||||
|
||||
private async _onSocksRequested(uid: string, host: string, port: number): 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 => 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);
|
||||
const socksSupport = this._initializer.socksSupport;
|
||||
if (!socksSupport)
|
||||
return;
|
||||
const handler = new socks.SocksProxyHandler(redirectPortForTest);
|
||||
this._socksProxyHandler = handler;
|
||||
handler.on(socks.SocksProxyHandler.Events.SocksConnected, (payload: socks.SocksSocketConnectedPayload) => socksSupport.socksConnected(payload).catch(() => {}));
|
||||
handler.on(socks.SocksProxyHandler.Events.SocksData, (payload: socks.SocksSocketDataPayload) => socksSupport.socksData({ uid: payload.uid, data: payload.data.toString('base64') }).catch(() => {}));
|
||||
handler.on(socks.SocksProxyHandler.Events.SocksError, (payload: socks.SocksSocketErrorPayload) => socksSupport.socksError(payload).catch(() => {}));
|
||||
handler.on(socks.SocksProxyHandler.Events.SocksFailed, (payload: socks.SocksSocketFailedPayload) => socksSupport.socksFailed(payload).catch(() => {}));
|
||||
handler.on(socks.SocksProxyHandler.Events.SocksEnd, (payload: socks.SocksSocketEndPayload) => socksSupport.socksEnd(payload).catch(() => {}));
|
||||
socksSupport.on('socksRequested', payload => handler.socketRequested(payload));
|
||||
socksSupport.on('socksClosed', payload => handler.socketClosed(payload));
|
||||
socksSupport.on('socksData', payload => handler.sendSocketData({ uid: payload.uid, data: Buffer.from(payload.data, 'base64') }));
|
||||
}
|
||||
|
||||
static from(channel: channels.PlaywrightChannel): Playwright {
|
||||
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 { getUserAgent, makeWaitForNextTask } from '../utils/utils';
|
||||
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 {
|
||||
_type_BrowserType = true;
|
||||
@ -65,11 +67,16 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.Brow
|
||||
headers: paramsHeaders,
|
||||
followRedirects: true,
|
||||
});
|
||||
let socksInterceptor: SocksInterceptor | undefined;
|
||||
const pipe = new JsonPipeDispatcher(this._scope);
|
||||
const openPromise = new ManualPromise<{ pipe: JsonPipeDispatcher }>();
|
||||
ws.on('open', () => openPromise.resolve({ pipe }));
|
||||
ws.on('close', () => pipe.wasClosed());
|
||||
ws.on('close', () => {
|
||||
socksInterceptor?.cleanup();
|
||||
pipe.wasClosed();
|
||||
});
|
||||
ws.on('error', error => {
|
||||
socksInterceptor?.cleanup();
|
||||
if (openPromise.isDone()) {
|
||||
pipe.wasClosed(error);
|
||||
} else {
|
||||
@ -77,12 +84,19 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.Brow
|
||||
openPromise.reject(error);
|
||||
}
|
||||
});
|
||||
pipe.on('close', () => ws.close());
|
||||
pipe.on('close', () => {
|
||||
socksInterceptor?.cleanup();
|
||||
ws.close();
|
||||
});
|
||||
pipe.on('message', message => ws.send(JSON.stringify(message)));
|
||||
ws.addEventListener('message', event => {
|
||||
waitForNextTask(() => {
|
||||
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) {
|
||||
ws.close();
|
||||
}
|
||||
@ -91,3 +105,55 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.Brow
|
||||
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 { GlobalAPIRequestContext } from '../server/fetch';
|
||||
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 { AndroidDispatcher } from './androidDispatcher';
|
||||
import { BrowserTypeDispatcher } from './browserTypeDispatcher';
|
||||
@ -28,11 +28,11 @@ import { LocalUtilsDispatcher } from './localUtilsDispatcher';
|
||||
import { APIRequestContextDispatcher } from './networkDispatchers';
|
||||
import { SelectorsDispatcher } from './selectorsDispatcher';
|
||||
import { ConnectedBrowserDispatcher } from './browserDispatcher';
|
||||
import { createGuid } from '../utils/utils';
|
||||
|
||||
export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.PlaywrightChannel> implements channels.PlaywrightChannel {
|
||||
_type_Playwright;
|
||||
private _browserDispatcher: ConnectedBrowserDispatcher | undefined;
|
||||
private _socksProxy: SocksProxy | undefined;
|
||||
|
||||
constructor(scope: DispatcherScope, playwright: Playwright, socksProxy?: SocksProxy, preLaunchedBrowser?: Browser) {
|
||||
const descriptors = require('../server/deviceDescriptors') as types.Devices;
|
||||
@ -49,35 +49,10 @@ export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.Playwr
|
||||
deviceDescriptors,
|
||||
selectors: new SelectorsDispatcher(scope, browserDispatcher?.selectors || playwright.selectors),
|
||||
preLaunchedBrowser: browserDispatcher,
|
||||
socksSupport: socksProxy ? new SocksSupportDispatcher(scope, socksProxy) : undefined,
|
||||
}, false);
|
||||
this._type_Playwright = true;
|
||||
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> {
|
||||
@ -94,3 +69,37 @@ export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.Playwr
|
||||
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 { createPlaywright } from '../server/playwright';
|
||||
import { gracefullyCloseAll } from '../utils/processLauncher';
|
||||
import { SocksProxy } from '../server/socksProxy';
|
||||
import { SocksProxy } from '../utils/socksProxy';
|
||||
|
||||
function launchGridWorker(gridURL: string, agentId: string, workerId: string) {
|
||||
const log = debug(`pw:grid:worker${workerId}`);
|
||||
|
@ -50,6 +50,7 @@ export type InitializerTraits<T> =
|
||||
T extends BrowserChannel ? BrowserInitializer :
|
||||
T extends BrowserTypeChannel ? BrowserTypeInitializer :
|
||||
T extends SelectorsChannel ? SelectorsInitializer :
|
||||
T extends SocksSupportChannel ? SocksSupportInitializer :
|
||||
T extends PlaywrightChannel ? PlaywrightInitializer :
|
||||
T extends RootChannel ? RootInitializer :
|
||||
T extends LocalUtilsChannel ? LocalUtilsInitializer :
|
||||
@ -85,6 +86,7 @@ export type EventsTraits<T> =
|
||||
T extends BrowserChannel ? BrowserEvents :
|
||||
T extends BrowserTypeChannel ? BrowserTypeEvents :
|
||||
T extends SelectorsChannel ? SelectorsEvents :
|
||||
T extends SocksSupportChannel ? SocksSupportEvents :
|
||||
T extends PlaywrightChannel ? PlaywrightEvents :
|
||||
T extends RootChannel ? RootEvents :
|
||||
T extends LocalUtilsChannel ? LocalUtilsEvents :
|
||||
@ -120,6 +122,7 @@ export type EventTargetTraits<T> =
|
||||
T extends BrowserChannel ? BrowserEventTarget :
|
||||
T extends BrowserTypeChannel ? BrowserTypeEventTarget :
|
||||
T extends SelectorsChannel ? SelectorsEventTarget :
|
||||
T extends SocksSupportChannel ? SocksSupportEventTarget :
|
||||
T extends PlaywrightChannel ? PlaywrightEventTarget :
|
||||
T extends RootChannel ? RootEventTarget :
|
||||
T extends LocalUtilsChannel ? LocalUtilsEventTarget :
|
||||
@ -423,74 +426,15 @@ export type PlaywrightInitializer = {
|
||||
}[],
|
||||
selectors: SelectorsChannel,
|
||||
preLaunchedBrowser?: BrowserChannel,
|
||||
socksSupport?: SocksSupportChannel,
|
||||
};
|
||||
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 {
|
||||
_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>;
|
||||
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 = {
|
||||
baseURL?: string,
|
||||
userAgent?: string,
|
||||
@ -543,9 +487,80 @@ export type PlaywrightHideHighlightOptions = {};
|
||||
export type PlaywrightHideHighlightResult = void;
|
||||
|
||||
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 -----------
|
||||
@ -588,11 +603,15 @@ export type BrowserTypeConnectParams = {
|
||||
headers?: any,
|
||||
slowMo?: number,
|
||||
timeout?: number,
|
||||
enableSocksProxy?: boolean,
|
||||
socksProxyRedirectPortForTest?: number,
|
||||
};
|
||||
export type BrowserTypeConnectOptions = {
|
||||
headers?: any,
|
||||
slowMo?: number,
|
||||
timeout?: number,
|
||||
enableSocksProxy?: boolean,
|
||||
socksProxyRedirectPortForTest?: number,
|
||||
};
|
||||
export type BrowserTypeConnectResult = {
|
||||
pipe: JsonPipeChannel,
|
||||
|
@ -483,34 +483,10 @@ Playwright:
|
||||
selectors: Selectors
|
||||
# Only present when connecting remotely via BrowserType.connect() method.
|
||||
preLaunchedBrowser: Browser?
|
||||
# Only present when socks proxy is supported.
|
||||
socksSupport: SocksSupport?
|
||||
|
||||
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:
|
||||
parameters:
|
||||
baseURL: string?
|
||||
@ -548,6 +524,35 @@ Playwright:
|
||||
|
||||
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:
|
||||
socksRequested:
|
||||
parameters:
|
||||
@ -564,7 +569,6 @@ Playwright:
|
||||
parameters:
|
||||
uid: string
|
||||
|
||||
|
||||
Selectors:
|
||||
type: interface
|
||||
|
||||
@ -592,6 +596,8 @@ BrowserType:
|
||||
headers: json?
|
||||
slowMo: number?
|
||||
timeout: number?
|
||||
enableSocksProxy: boolean?
|
||||
socksProxyRedirectPortForTest: number?
|
||||
returns:
|
||||
pipe: JsonPipe
|
||||
|
||||
|
@ -196,26 +196,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
scheme.RootInitializeParams = tObject({
|
||||
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({
|
||||
baseURL: tOptional(tString),
|
||||
userAgent: tOptional(tString),
|
||||
@ -239,6 +219,26 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
tracesDir: tOptional(tString),
|
||||
});
|
||||
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({
|
||||
name: tString,
|
||||
source: tString,
|
||||
@ -249,6 +249,8 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
headers: tOptional(tAny),
|
||||
slowMo: tOptional(tNumber),
|
||||
timeout: tOptional(tNumber),
|
||||
enableSocksProxy: tOptional(tBoolean),
|
||||
socksProxyRedirectPortForTest: tOptional(tNumber),
|
||||
});
|
||||
scheme.BrowserTypeLaunchParams = tObject({
|
||||
channel: tOptional(tString),
|
||||
|
@ -24,7 +24,7 @@ import { Browser } from '../server/browser';
|
||||
import { gracefullyCloseAll } from '../utils/processLauncher';
|
||||
import { registry } from '../utils/registry';
|
||||
import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher';
|
||||
import { SocksProxy } from '../server/socksProxy';
|
||||
import { SocksProxy } from '../utils/socksProxy';
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import net from 'net';
|
||||
import { assert } from './utils';
|
||||
import dns from 'dns';
|
||||
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
|
||||
|
||||
@ -50,13 +57,21 @@ enum SocksReply {
|
||||
AddressTypeNotSupported = 0x08
|
||||
}
|
||||
|
||||
export interface SocksConnectionClient {
|
||||
onSocketRequested(uid: string, host: string, port: number): void;
|
||||
onSocketData(uid: string, data: Buffer): void;
|
||||
onSocketClosed(uid: string): void;
|
||||
export type SocksSocketRequestedPayload = { uid: string, host: string, port: number };
|
||||
export type SocksSocketConnectedPayload = { uid: string, host: string, port: number };
|
||||
export type SocksSocketDataPayload = { uid: string, data: Buffer };
|
||||
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 _offset = 0;
|
||||
private _fence = 0;
|
||||
@ -94,7 +109,7 @@ export class SocksConnection {
|
||||
}
|
||||
|
||||
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> {
|
||||
@ -199,7 +214,7 @@ export class SocksConnection {
|
||||
}
|
||||
|
||||
private _onClose() {
|
||||
this._client.onSocketClosed(this._uid);
|
||||
this._client.onSocketClosed({ uid: this._uid });
|
||||
}
|
||||
|
||||
private _onData(buffer: Buffer) {
|
||||
@ -220,7 +235,7 @@ export class SocksConnection {
|
||||
...parseIP(host), // Address
|
||||
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) {
|
||||
@ -268,3 +283,134 @@ function parseIP(address: string): number[] {
|
||||
throw new Error('IPv6 is not supported');
|
||||
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 }) => {
|
||||
const browser = await browserType.launch();
|
||||
const GOLDEN_PRECONDITION = {
|
||||
|
Loading…
Reference in New Issue
Block a user