Path: blob/main/puppet-chrome/lib/DevtoolsSession.ts
1028 views
/**1* Copyright 2018 Google Inc. All rights reserved.2* Modifications copyright (c) Data Liberation Foundation Inc.3*4* Licensed under the Apache License, Version 2.0 (the "License");5* you may not use this file except in compliance with the License.6* You may obtain a copy of the License at7*8* http://www.apache.org/licenses/LICENSE-2.09*10* Unless required by applicable law or agreed to in writing, software11* distributed under the License is distributed on an "AS IS" BASIS,12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13* See the License for the specific language governing permissions and14* limitations under the License.15*/1617import { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping';18import { Protocol } from 'devtools-protocol';19import { CanceledPromiseError } from '@secret-agent/commons/interfaces/IPendingWaitEvent';20import { TypedEventEmitter } from '@secret-agent/commons/eventUtils';21import IResolvablePromise from '@secret-agent/interfaces/IResolvablePromise';22import { createPromise } from '@secret-agent/commons/utils';23import IDevtoolsSession, {24DevtoolsEvents,25IDevtoolsEventMessage,26IDevtoolsResponseMessage,27} from '@secret-agent/interfaces/IDevtoolsSession';28import ProtocolError from './ProtocolError';29import { Connection } from './Connection';30import RemoteObject = Protocol.Runtime.RemoteObject;3132/**33* The `DevtoolsSession` instances are used to talk raw Chrome Devtools Protocol.34*35* https://chromedevtools.github.io/devtools-protocol/36*/37export class DevtoolsSession extends TypedEventEmitter<DevtoolsEvents> implements IDevtoolsSession {38public connection: Connection;39public messageEvents = new TypedEventEmitter<IMessageEvents>();40public get id() {41return this.sessionId;42}4344private readonly sessionId: string;45private readonly targetType: string;46private readonly pendingMessages: Map<47number,48{ resolvable: IResolvablePromise<any>; method: string }49> = new Map();5051constructor(connection: Connection, targetType: string, sessionId: string) {52super();53this.connection = connection;54this.targetType = targetType;55this.sessionId = sessionId;56}5758async send<T extends keyof ProtocolMapping.Commands>(59method: T,60params: ProtocolMapping.Commands[T]['paramsType'][0] = {},61sendInitiator?: object,62): Promise<ProtocolMapping.Commands[T]['returnType']> {63if (!this.isConnected()) {64throw new CanceledPromiseError(`${method} called after session closed (${this.sessionId})`);65}6667const message = {68sessionId: this.sessionId || undefined,69method,70params,71};72const timestamp = new Date();73const id = this.connection.sendMessage(message);74this.messageEvents.emit(75'send',76{77id,78timestamp,79...message,80},81sendInitiator,82);83const resolvable = createPromise<ProtocolMapping.Commands[T]['returnType']>();8485this.pendingMessages.set(id, { resolvable, method });86return await resolvable.promise;87}8889onMessage(object: IDevtoolsResponseMessage & IDevtoolsEventMessage): void {90this.messageEvents.emit('receive', { ...object });91if (!object.id) {92this.emit(object.method as any, object.params);93return;94}9596const pending = this.pendingMessages.get(object.id);97if (!pending) return;9899const { resolvable, method } = pending;100101this.pendingMessages.delete(object.id);102if (object.error) {103resolvable.reject(new ProtocolError(resolvable.stack, method, object.error));104} else {105resolvable.resolve(object.result);106}107}108109disposeRemoteObject(object: RemoteObject): void {110if (!object.objectId) return;111this.send('Runtime.releaseObject', { objectId: object.objectId }).catch(() => {112// Exceptions might happen in case of a page been navigated or closed.113// Swallow these since they are harmless and we don't leak anything in this case.114});115}116117onClosed(): void {118for (const { resolvable, method } of this.pendingMessages.values()) {119const error = new CanceledPromiseError(`Cancel Pending Promise (${method}): Target closed.`);120error.stack += `\n${'------DEVTOOLS'.padEnd(12150,122'-',123)}\n${`------DEVTOOLS_SESSION_ID=${this.sessionId}`.padEnd(50, '-')}\n${resolvable.stack}`;124resolvable.reject(error);125}126this.pendingMessages.clear();127this.connection = null;128this.emit('disconnected');129}130131public isConnected() {132return this.connection && !this.connection.isClosed;133}134}135136export interface IMessageEvents {137send: { sessionId: string | undefined; id: number; method: string; params: any; timestamp: Date };138receive: IDevtoolsResponseMessage | IDevtoolsEventMessage;139}140141142