Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/puppet-chrome/lib/Worker.ts
1028 views
1
import EventSubscriber from '@secret-agent/commons/EventSubscriber';
2
import { TypedEventEmitter } from '@secret-agent/commons/eventUtils';
3
import Protocol from 'devtools-protocol';
4
import { IPuppetWorker, IPuppetWorkerEvents } from '@secret-agent/interfaces/IPuppetWorker';
5
import { createPromise } from '@secret-agent/commons/utils';
6
import { IBoundLog } from '@secret-agent/interfaces/ILog';
7
import { CanceledPromiseError } from '@secret-agent/commons/interfaces/IPendingWaitEvent';
8
import ICorePlugins from '@secret-agent/interfaces/ICorePlugins';
9
import { BrowserContext } from './BrowserContext';
10
import { DevtoolsSession } from './DevtoolsSession';
11
import { NetworkManager } from './NetworkManager';
12
import ConsoleMessage from './ConsoleMessage';
13
import ConsoleAPICalledEvent = Protocol.Runtime.ConsoleAPICalledEvent;
14
import TargetInfo = Protocol.Target.TargetInfo;
15
import ExceptionThrownEvent = Protocol.Runtime.ExceptionThrownEvent;
16
import ExecutionContextCreatedEvent = Protocol.Runtime.ExecutionContextCreatedEvent;
17
18
export class Worker extends TypedEventEmitter<IPuppetWorkerEvents> implements IPuppetWorker {
19
public readonly browserContext: BrowserContext;
20
public isReady: Promise<Error | null>;
21
public get isInitializationSent(): Promise<void> {
22
return this.initializationSent.promise;
23
}
24
25
public hasLoadedResponse = false;
26
public readonly devtoolsSession: DevtoolsSession;
27
28
protected readonly logger: IBoundLog;
29
30
private readonly initializationSent = createPromise<void>();
31
private readonly networkManager: NetworkManager;
32
private readonly targetInfo: TargetInfo;
33
34
private readonly eventSubscriber = new EventSubscriber();
35
private readonly executionContextId = createPromise<number>();
36
37
public get id(): string {
38
return this.targetInfo.targetId;
39
}
40
41
public get url(): string {
42
return this.targetInfo.url;
43
}
44
45
public get type(): IPuppetWorker['type'] {
46
return this.targetInfo.type as IPuppetWorker['type'];
47
}
48
49
constructor(
50
browserContext: BrowserContext,
51
parentNetworkManager: NetworkManager,
52
devtoolsSession: DevtoolsSession,
53
logger: IBoundLog,
54
targetInfo: TargetInfo,
55
) {
56
super();
57
this.targetInfo = targetInfo;
58
this.devtoolsSession = devtoolsSession;
59
this.browserContext = browserContext;
60
this.logger = logger.createChild(module, {
61
workerTargetId: this.id,
62
workerType: this.type,
63
});
64
this.networkManager = new NetworkManager(devtoolsSession, this.logger, browserContext.proxy);
65
const events = this.eventSubscriber;
66
events.on(devtoolsSession, 'Runtime.consoleAPICalled', this.onRuntimeConsole.bind(this));
67
events.on(devtoolsSession, 'Runtime.exceptionThrown', this.onRuntimeException.bind(this));
68
events.on(devtoolsSession, 'Runtime.executionContextCreated', this.onContextCreated.bind(this));
69
events.on(devtoolsSession, 'disconnected', this.emit.bind(this, 'close'));
70
71
this.isReady = this.initialize(parentNetworkManager).catch(err => err);
72
}
73
74
initialize(pageNetworkManager: NetworkManager): Promise<void> {
75
const { plugins } = this.browserContext;
76
const result = Promise.all([
77
this.networkManager.initializeFromParent(pageNetworkManager).catch(err => {
78
// web workers can use parent network
79
if (err.message.includes(`'Fetch.enable' wasn't found`)) return;
80
throw err;
81
}),
82
this.devtoolsSession.send('Runtime.enable'),
83
this.initializeEmulation(plugins),
84
this.devtoolsSession.send('Runtime.runIfWaitingForDebugger'),
85
]);
86
87
setImmediate(() => this.initializationSent.resolve());
88
return result.then(() => null);
89
}
90
91
async evaluate<T>(expression: string, isInitializationScript = false): Promise<T> {
92
const result = await this.devtoolsSession.send('Runtime.evaluate', {
93
expression,
94
awaitPromise: !isInitializationScript,
95
// contextId,
96
returnByValue: true,
97
});
98
99
if (result.exceptionDetails) {
100
throw ConsoleMessage.exceptionToError(result.exceptionDetails);
101
}
102
103
const remote = result.result;
104
if (remote.objectId) this.devtoolsSession.disposeRemoteObject(remote);
105
return remote.value as T;
106
}
107
108
close(): void {
109
this.networkManager.close();
110
this.cancelPendingEvents('Worker closing', ['close']);
111
this.eventSubscriber.close();
112
}
113
114
toJSON() {
115
return {
116
id: this.id,
117
url: this.url,
118
type: this.type,
119
};
120
}
121
122
private initializeEmulation(plugins: ICorePlugins): Promise<any> {
123
if (!plugins.onNewPuppetWorker) return;
124
125
return Promise.all([
126
plugins.onNewPuppetWorker(this),
127
this.devtoolsSession.send('Debugger.enable'),
128
this.devtoolsSession.send('Debugger.setBreakpointByUrl', {
129
lineNumber: 0,
130
url: this.targetInfo.url,
131
}),
132
])
133
.then(this.resumeAfterEmulation.bind(this))
134
.catch(error => {
135
if (error instanceof CanceledPromiseError) return;
136
this.logger.error('Emulator.onNewPuppetWorkerError', {
137
error,
138
});
139
throw error;
140
});
141
}
142
143
private resumeAfterEmulation(): Promise<any> {
144
return Promise.all([
145
this.devtoolsSession.send('Runtime.runIfWaitingForDebugger'),
146
this.devtoolsSession.send('Debugger.disable'),
147
]);
148
}
149
150
private onContextCreated(event: ExecutionContextCreatedEvent): void {
151
this.executionContextId.resolve(event.context.id);
152
}
153
154
private onRuntimeException(msg: ExceptionThrownEvent): void {
155
const error = ConsoleMessage.exceptionToError(msg.exceptionDetails);
156
157
this.emit('page-error', {
158
error,
159
});
160
}
161
162
private onRuntimeConsole(event: ConsoleAPICalledEvent): void {
163
const message = ConsoleMessage.create(this.devtoolsSession, event);
164
const frameId = `${this.type}:${this.url}`; // TBD
165
166
this.emit('console', {
167
frameId,
168
...message,
169
});
170
}
171
}
172
173