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