2020-06-25 16:05:36 -07:00
|
|
|
/**
|
|
|
|
* 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 { EventEmitter } from 'ws';
|
|
|
|
import { Browser } from './client/browser';
|
|
|
|
import { BrowserContext } from './client/browserContext';
|
|
|
|
import { BrowserType } from './client/browserType';
|
|
|
|
import { ChannelOwner } from './client/channelOwner';
|
|
|
|
import { ElementHandle } from './client/elementHandle';
|
|
|
|
import { Frame } from './client/frame';
|
|
|
|
import { JSHandle } from './client/jsHandle';
|
2020-06-26 11:51:47 -07:00
|
|
|
import { Request, Response, Route } from './client/network';
|
|
|
|
import { Page, BindingCall } from './client/page';
|
2020-06-25 16:05:36 -07:00
|
|
|
import debug = require('debug');
|
|
|
|
import { Channel } from './channels';
|
2020-06-26 12:28:27 -07:00
|
|
|
import { ConsoleMessage } from './client/consoleMessage';
|
2020-06-25 16:05:36 -07:00
|
|
|
|
|
|
|
export class Connection {
|
|
|
|
private _channels = new Map<string, Channel>();
|
2020-06-26 12:28:27 -07:00
|
|
|
private _waitingForObject = new Map<string, any>();
|
2020-06-25 16:05:36 -07:00
|
|
|
sendMessageToServerTransport = (message: any): Promise<any> => Promise.resolve();
|
|
|
|
|
|
|
|
constructor() {}
|
|
|
|
|
2020-06-26 12:28:27 -07:00
|
|
|
private _createRemoteObject(type: string, guid: string, initializer: any): any {
|
2020-06-25 16:05:36 -07:00
|
|
|
const channel = this._createChannel(guid) as any;
|
|
|
|
this._channels.set(guid, channel);
|
2020-06-26 12:28:27 -07:00
|
|
|
let result: ChannelOwner<any, any>;
|
|
|
|
initializer = this._replaceGuidsWithChannels(initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
switch (type) {
|
|
|
|
case 'browserType':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new BrowserType(this, channel, initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
break;
|
|
|
|
case 'browser':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new Browser(this, channel, initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
break;
|
|
|
|
case 'context':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new BrowserContext(this, channel, initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
break;
|
|
|
|
case 'page':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new Page(this, channel, initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
break;
|
|
|
|
case 'frame':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new Frame(this, channel, initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
break;
|
|
|
|
case 'request':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new Request(this, channel, initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
break;
|
|
|
|
case 'response':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new Response(this, channel, initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
break;
|
2020-06-26 11:51:47 -07:00
|
|
|
case 'route':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new Route(this, channel, initializer);
|
2020-06-26 11:51:47 -07:00
|
|
|
break;
|
|
|
|
case 'bindingCall':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new BindingCall(this, channel, initializer);
|
2020-06-26 11:51:47 -07:00
|
|
|
break;
|
2020-06-25 16:05:36 -07:00
|
|
|
case 'jsHandle':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new JSHandle(this, channel, initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
break;
|
|
|
|
case 'elementHandle':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new ElementHandle(this, channel, initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
break;
|
2020-06-25 18:01:18 -07:00
|
|
|
case 'consoleMessage':
|
2020-06-26 12:28:27 -07:00
|
|
|
result = new ConsoleMessage(this, channel, initializer);
|
2020-06-25 18:01:18 -07:00
|
|
|
break;
|
2020-06-25 16:05:36 -07:00
|
|
|
default:
|
|
|
|
throw new Error('Missing type ' + type);
|
|
|
|
}
|
|
|
|
channel._object = result;
|
2020-06-26 12:28:27 -07:00
|
|
|
const callback = this._waitingForObject.get(guid);
|
|
|
|
if (callback) {
|
|
|
|
callback(result);
|
|
|
|
this._waitingForObject.delete(guid);
|
|
|
|
}
|
2020-06-25 16:05:36 -07:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-06-26 12:28:27 -07:00
|
|
|
waitForObjectWithKnownName(guid: string): Promise<any> {
|
|
|
|
if (this._channels.has(guid))
|
|
|
|
return this._channels.get(guid)!._object;
|
|
|
|
return new Promise(f => this._waitingForObject.set(guid, f));
|
|
|
|
}
|
|
|
|
|
2020-06-25 16:05:36 -07:00
|
|
|
async sendMessageToServer(message: { guid: string, method: string, params: any }) {
|
|
|
|
const converted = {...message, params: this._replaceChannelsWithGuids(message.params)};
|
|
|
|
debug('pw:channel:command')(converted);
|
|
|
|
const response = await this.sendMessageToServerTransport(converted);
|
|
|
|
debug('pw:channel:response')(response);
|
|
|
|
return this._replaceGuidsWithChannels(response);
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatchMessageFromServer(message: { guid: string, method: string, params: any }) {
|
|
|
|
debug('pw:channel:event')(message);
|
|
|
|
const { guid, method, params } = message;
|
|
|
|
|
|
|
|
if (method === '__create__') {
|
2020-06-26 12:28:27 -07:00
|
|
|
this._createRemoteObject(params.type, guid, params.initializer);
|
2020-06-25 16:05:36 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const channel = this._channels.get(guid)!;
|
|
|
|
channel.emit(method, this._replaceGuidsWithChannels(params));
|
|
|
|
}
|
|
|
|
|
|
|
|
private _createChannel(guid: string): Channel {
|
|
|
|
const base = new EventEmitter();
|
|
|
|
(base as any)._guid = guid;
|
|
|
|
return new Proxy(base, {
|
|
|
|
get: (obj: any, prop) => {
|
|
|
|
if (String(prop).startsWith('_'))
|
|
|
|
return obj[prop];
|
|
|
|
if (prop === 'then')
|
|
|
|
return obj.then;
|
|
|
|
if (prop === 'emit')
|
|
|
|
return obj.emit;
|
|
|
|
if (prop === 'on')
|
|
|
|
return obj.on;
|
2020-06-25 18:01:18 -07:00
|
|
|
if (prop === 'once')
|
|
|
|
return obj.once;
|
2020-06-25 16:05:36 -07:00
|
|
|
if (prop === 'addEventListener')
|
|
|
|
return obj.addListener;
|
|
|
|
if (prop === 'removeEventListener')
|
|
|
|
return obj.removeListener;
|
|
|
|
return (params: any) => this.sendMessageToServer({ guid, method: String(prop), params });
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private _replaceChannelsWithGuids(payload: any): any {
|
|
|
|
if (!payload)
|
|
|
|
return payload;
|
|
|
|
if (Array.isArray(payload))
|
|
|
|
return payload.map(p => this._replaceChannelsWithGuids(p));
|
|
|
|
if (payload._guid)
|
|
|
|
return { guid: payload._guid };
|
2020-06-26 11:51:47 -07:00
|
|
|
// TODO: send base64
|
|
|
|
if (payload instanceof Buffer)
|
|
|
|
return payload;
|
2020-06-25 16:05:36 -07:00
|
|
|
if (typeof payload === 'object')
|
|
|
|
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceChannelsWithGuids(v)]));
|
|
|
|
return payload;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _replaceGuidsWithChannels(payload: any): any {
|
|
|
|
if (!payload)
|
|
|
|
return payload;
|
|
|
|
if (Array.isArray(payload))
|
|
|
|
return payload.map(p => this._replaceGuidsWithChannels(p));
|
|
|
|
if (payload.guid && this._channels.has(payload.guid))
|
|
|
|
return this._channels.get(payload.guid);
|
2020-06-26 11:51:47 -07:00
|
|
|
// TODO: send base64
|
|
|
|
if (payload instanceof Buffer)
|
|
|
|
return payload;
|
2020-06-25 16:05:36 -07:00
|
|
|
if (typeof payload === 'object')
|
|
|
|
return Object.fromEntries([...Object.entries(payload)].map(([n,v]) => [n, this._replaceGuidsWithChannels(v)]));
|
|
|
|
return payload;
|
|
|
|
}
|
|
|
|
}
|