Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/puppet/index.ts
1028 views
1
import PuppetChrome from '@secret-agent/puppet-chrome';
2
import { IBoundLog } from '@secret-agent/interfaces/ILog';
3
import Log from '@secret-agent/commons/Logger';
4
import IPuppetLauncher from '@secret-agent/interfaces/IPuppetLauncher';
5
import IPuppetBrowser from '@secret-agent/interfaces/IPuppetBrowser';
6
import IBrowserEngine from '@secret-agent/interfaces/IBrowserEngine';
7
import ICorePlugins from '@secret-agent/interfaces/ICorePlugins';
8
import IProxyConnectionOptions from '@secret-agent/interfaces/IProxyConnectionOptions';
9
import IPuppetLaunchArgs from '@secret-agent/interfaces/IPuppetLaunchArgs';
10
import IPuppetContext from '@secret-agent/interfaces/IPuppetContext';
11
import { TypedEventEmitter } from '@secret-agent/commons/eventUtils';
12
import IDevtoolsSession from '@secret-agent/interfaces/IDevtoolsSession';
13
import Resolvable from '@secret-agent/commons/Resolvable';
14
import PuppetLaunchError from './lib/PuppetLaunchError';
15
import BrowserProcess from './lib/BrowserProcess';
16
17
const { log } = Log(module);
18
19
let puppBrowserCounter = 1;
20
export default class Puppet extends TypedEventEmitter<{ close: void }> {
21
public get browserId(): string {
22
return this.browser?.id;
23
}
24
25
public readonly id: number;
26
public readonly browserEngine: IBrowserEngine;
27
public supportsBrowserContextProxy: boolean;
28
public isReady = new Resolvable<void | Error>();
29
public isStarted = false;
30
private readonly launcher: IPuppetLauncher;
31
private isShuttingDown: Promise<Error | void>;
32
private browser: IPuppetBrowser;
33
34
constructor(browserEngine: IBrowserEngine, args: IPuppetLaunchArgs = {}) {
35
super();
36
this.browserEngine = browserEngine;
37
this.id = puppBrowserCounter;
38
if (browserEngine.name === 'chrome') {
39
if (browserEngine.isHeaded) args.showBrowser = true;
40
PuppetChrome.getLaunchArgs(args, browserEngine);
41
this.launcher = PuppetChrome;
42
} else {
43
throw new Error(`No Puppet launcher available for ${this.browserEngine.name}`);
44
}
45
puppBrowserCounter += 1;
46
}
47
48
public async start(
49
attachToDevtools?: (session: IDevtoolsSession) => Promise<any>,
50
): Promise<Puppet> {
51
const parentLogId = log.info('Puppet.Starting', {
52
sessionId: null,
53
name: this.browserEngine.name,
54
fullVersion: this.browserEngine.fullVersion,
55
});
56
if (this.isStarted) {
57
await this.isReady.promise;
58
return this;
59
}
60
try {
61
this.isStarted = true;
62
63
if (this.browserEngine.verifyLaunchable) {
64
await this.browserEngine.verifyLaunchable();
65
}
66
67
const launchedProcess = new BrowserProcess(this.browserEngine);
68
const hasError = await launchedProcess.hasLaunchError;
69
if (hasError) throw hasError;
70
launchedProcess.once('close', () => this.emit('close'));
71
72
this.browser = await this.launcher.createPuppet(launchedProcess, this.browserEngine);
73
this.browser.onDevtoolsPanelAttached = attachToDevtools;
74
75
this.supportsBrowserContextProxy = this.browser.majorVersion >= 85;
76
77
this.isReady.resolve();
78
log.stats('Puppet.Started', {
79
sessionId: null,
80
parentLogId,
81
});
82
return this;
83
} catch (err) {
84
const launchError = this.launcher.translateLaunchError(err);
85
const puppetLaunchError = new PuppetLaunchError(
86
launchError.message,
87
launchError.stack,
88
launchError.isSandboxError,
89
);
90
this.isReady.reject(puppetLaunchError);
91
log.stats('Puppet.LaunchError', {
92
puppetLaunchError,
93
sessionId: null,
94
parentLogId,
95
});
96
await this.isReady.promise;
97
}
98
}
99
100
public async newContext(
101
plugins: ICorePlugins,
102
logger: IBoundLog,
103
proxy?: IProxyConnectionOptions,
104
): Promise<IPuppetContext> {
105
await this.isReady.promise;
106
if (!this.browser) {
107
throw new Error('This Puppet instance has not had start() called on it');
108
}
109
if (this.isShuttingDown) throw new Error('Shutting down');
110
return this.browser.newContext(plugins, logger, proxy);
111
}
112
113
public async close(): Promise<void | Error> {
114
if (!this.isStarted) return;
115
if (this.isShuttingDown) return this.isShuttingDown;
116
117
const parentLogId = log.stats('Puppet.Closing');
118
119
try {
120
// if we started to get ready, clear out now
121
this.isStarted = false;
122
if (this.isReady) {
123
const err = await this.isReady.catch(startError => startError);
124
this.isReady = null;
125
if (err) return;
126
}
127
128
this.isShuttingDown = this.browser?.close();
129
await this.isShuttingDown;
130
} catch (error) {
131
log.error('Puppet.Closing:Error', { parentLogId, sessionId: null, error });
132
} finally {
133
this.emit('close');
134
log.stats('Puppet.Closed', { parentLogId, sessionId: null });
135
}
136
}
137
}
138
139