2019-11-18 18:18:28 -08:00
|
|
|
/**
|
|
|
|
* Copyright 2018 Google Inc. All rights reserved.
|
|
|
|
* Modifications 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.
|
|
|
|
*/
|
|
|
|
|
2020-04-01 14:42:47 -07:00
|
|
|
import * as WebSocket from 'ws';
|
2020-05-29 14:39:34 -07:00
|
|
|
import { Progress } from './progress';
|
2020-08-24 06:51:51 -07:00
|
|
|
import { makeWaitForNextTask } from '../utils/utils';
|
2020-04-01 14:42:47 -07:00
|
|
|
|
2020-03-27 15:18:34 -07:00
|
|
|
export type ProtocolRequest = {
|
|
|
|
id: number;
|
2020-03-26 23:30:55 -07:00
|
|
|
method: string;
|
2020-03-27 15:18:34 -07:00
|
|
|
params: any;
|
|
|
|
sessionId?: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type ProtocolResponse = {
|
|
|
|
id?: number;
|
|
|
|
method?: string;
|
2020-03-26 23:30:55 -07:00
|
|
|
sessionId?: string;
|
|
|
|
error?: { message: string; data: any; };
|
2020-03-27 15:18:34 -07:00
|
|
|
params?: any;
|
2020-03-26 23:30:55 -07:00
|
|
|
result?: any;
|
|
|
|
pageProxyId?: string;
|
2020-06-10 13:36:45 -07:00
|
|
|
browserContextId?: string;
|
2020-03-26 23:30:55 -07:00
|
|
|
};
|
|
|
|
|
2019-12-12 21:30:49 -08:00
|
|
|
export interface ConnectionTransport {
|
2020-03-27 15:18:34 -07:00
|
|
|
send(s: ProtocolRequest): void;
|
2020-01-22 17:42:10 -08:00
|
|
|
close(): void; // Note: calling close is expected to issue onclose at some point.
|
2020-03-27 15:18:34 -07:00
|
|
|
onmessage?: (message: ProtocolResponse) => void,
|
2019-12-12 21:30:49 -08:00
|
|
|
onclose?: () => void,
|
|
|
|
}
|
|
|
|
|
2020-04-01 14:42:47 -07:00
|
|
|
export class WebSocketTransport implements ConnectionTransport {
|
2020-05-21 09:43:10 -07:00
|
|
|
private _ws: WebSocket;
|
2020-05-29 14:39:34 -07:00
|
|
|
private _progress: Progress;
|
2020-04-01 14:42:47 -07:00
|
|
|
|
|
|
|
onmessage?: (message: ProtocolResponse) => void;
|
|
|
|
onclose?: () => void;
|
|
|
|
|
2020-06-04 16:43:48 -07:00
|
|
|
static async connect(progress: Progress, url: string): Promise<WebSocketTransport> {
|
2020-08-17 14:12:31 -07:00
|
|
|
progress.log(`<ws connecting> ${url}`);
|
2020-05-29 14:39:34 -07:00
|
|
|
const transport = new WebSocketTransport(progress, url);
|
2020-06-04 16:43:48 -07:00
|
|
|
let success = false;
|
2021-02-08 17:33:01 -08:00
|
|
|
progress.cleanupWhenAborted(async () => {
|
2020-06-04 16:43:48 -07:00
|
|
|
if (!success)
|
2021-02-08 17:33:01 -08:00
|
|
|
await transport.closeAndWait().catch(e => null);
|
2020-06-04 16:43:48 -07:00
|
|
|
});
|
|
|
|
await new Promise<WebSocketTransport>((fulfill, reject) => {
|
2020-05-11 13:49:57 -07:00
|
|
|
transport._ws.addEventListener('open', async () => {
|
2020-08-17 14:12:31 -07:00
|
|
|
progress.log(`<ws connected> ${url}`);
|
2020-05-21 09:43:10 -07:00
|
|
|
fulfill(transport);
|
2020-05-11 13:49:57 -07:00
|
|
|
});
|
2020-05-18 19:00:38 -07:00
|
|
|
transport._ws.addEventListener('error', event => {
|
2020-08-17 14:12:31 -07:00
|
|
|
progress.log(`<ws connect error> ${url} ${event.message}`);
|
2020-05-18 19:00:38 -07:00
|
|
|
reject(new Error('WebSocket error: ' + event.message));
|
2020-05-21 09:43:10 -07:00
|
|
|
transport._ws.close();
|
2020-05-18 19:00:38 -07:00
|
|
|
});
|
2020-04-01 14:42:47 -07:00
|
|
|
});
|
2020-06-04 16:43:48 -07:00
|
|
|
success = true;
|
|
|
|
return transport;
|
2020-04-01 14:42:47 -07:00
|
|
|
}
|
|
|
|
|
2020-05-29 14:39:34 -07:00
|
|
|
constructor(progress: Progress, url: string) {
|
2020-04-01 14:42:47 -07:00
|
|
|
this._ws = new WebSocket(url, [], {
|
|
|
|
perMessageDeflate: false,
|
2020-05-21 09:43:10 -07:00
|
|
|
maxPayload: 256 * 1024 * 1024, // 256Mb,
|
2020-06-05 15:53:30 -07:00
|
|
|
handshakeTimeout: progress.timeUntilDeadline(),
|
2020-04-01 14:42:47 -07:00
|
|
|
});
|
2020-05-29 14:39:34 -07:00
|
|
|
this._progress = progress;
|
2020-04-01 14:42:47 -07:00
|
|
|
// The 'ws' module in node sometimes sends us multiple messages in a single task.
|
|
|
|
// In Web, all IO callbacks (e.g. WebSocket callbacks)
|
|
|
|
// are dispatched into separate tasks, so there's no need
|
|
|
|
// to do anything extra.
|
2020-08-22 07:07:13 -07:00
|
|
|
const messageWrap: (cb: () => void) => void = makeWaitForNextTask();
|
2020-04-01 14:42:47 -07:00
|
|
|
|
|
|
|
this._ws.addEventListener('message', event => {
|
|
|
|
messageWrap(() => {
|
|
|
|
if (this.onmessage)
|
|
|
|
this.onmessage.call(null, JSON.parse(event.data));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
this._ws.addEventListener('close', event => {
|
2020-08-17 14:12:31 -07:00
|
|
|
this._progress && this._progress.log(`<ws disconnected> ${url}`);
|
2020-04-01 14:42:47 -07:00
|
|
|
if (this.onclose)
|
|
|
|
this.onclose.call(null);
|
|
|
|
});
|
2020-10-27 11:09:41 -07:00
|
|
|
// Prevent Error: read ECONNRESET.
|
2020-04-01 14:42:47 -07:00
|
|
|
this._ws.addEventListener('error', () => {});
|
|
|
|
}
|
|
|
|
|
|
|
|
send(message: ProtocolRequest) {
|
|
|
|
this._ws.send(JSON.stringify(message));
|
|
|
|
}
|
|
|
|
|
|
|
|
close() {
|
2020-08-17 14:12:31 -07:00
|
|
|
this._progress && this._progress.log(`<ws disconnecting> ${this._ws.url}`);
|
2020-04-01 14:42:47 -07:00
|
|
|
this._ws.close();
|
|
|
|
}
|
2020-05-29 14:39:34 -07:00
|
|
|
|
|
|
|
async closeAndWait() {
|
|
|
|
const promise = new Promise(f => this.onclose = f);
|
|
|
|
this.close();
|
|
|
|
return promise; // Make sure to await the actual disconnect.
|
|
|
|
}
|
2020-04-01 14:42:47 -07:00
|
|
|
}
|