Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/core/lib/CorePlugins.ts
1029 views
1
import { URL } from 'url';
2
import { IBoundLog } from '@secret-agent/interfaces/ILog';
3
import { IPuppetPage } from '@secret-agent/interfaces/IPuppetPage';
4
import { IPuppetWorker } from '@secret-agent/interfaces/IPuppetWorker';
5
import IHttpResourceLoadDetails from '@secret-agent/interfaces/IHttpResourceLoadDetails';
6
import IDnsSettings from '@secret-agent/interfaces/IDnsSettings';
7
import ITcpSettings from '@secret-agent/interfaces/ITcpSettings';
8
import ITlsSettings from '@secret-agent/interfaces/ITlsSettings';
9
import { IInteractionGroups, IInteractionStep } from '@secret-agent/interfaces/IInteractions';
10
import IInteractionsHelper from '@secret-agent/interfaces/IInteractionsHelper';
11
import IPoint from '@secret-agent/interfaces/IPoint';
12
import ICorePlugin, {
13
IBrowserEmulator,
14
IBrowserEmulatorClass,
15
IBrowserEmulatorConfig,
16
ICorePluginClass,
17
IHumanEmulator,
18
IHumanEmulatorClass,
19
IOnClientCommandMeta,
20
ISelectBrowserMeta,
21
} from '@secret-agent/interfaces/ICorePlugin';
22
import ICorePlugins from '@secret-agent/interfaces/ICorePlugins';
23
import ICorePluginCreateOptions from '@secret-agent/interfaces/ICorePluginCreateOptions';
24
import IBrowserEngine from '@secret-agent/interfaces/IBrowserEngine';
25
import { PluginTypes } from '@secret-agent/interfaces/IPluginTypes';
26
import requirePlugins from '@secret-agent/plugin-utils/lib/utils/requirePlugins';
27
import IHttp2ConnectSettings from '@secret-agent/interfaces/IHttp2ConnectSettings';
28
import IDeviceProfile from '@secret-agent/interfaces/IDeviceProfile';
29
import IHttpSocketAgent from '@secret-agent/interfaces/IHttpSocketAgent';
30
import Core from '../index';
31
32
const DefaultBrowserEmulatorId = 'default-browser-emulator';
33
const DefaultHumanEmulatorId = 'default-human-emulator';
34
35
interface IOptionsCreate {
36
userAgentSelector?: string;
37
deviceProfile?: IDeviceProfile;
38
humanEmulatorId?: string;
39
browserEmulatorId?: string;
40
selectBrowserMeta?: ISelectBrowserMeta;
41
dependencyMap?: IDependencyMap;
42
corePluginPaths?: string[];
43
}
44
45
interface IDependencyMap {
46
[clientPluginId: string]: string[];
47
}
48
49
export default class CorePlugins implements ICorePlugins {
50
public static corePluginClassesById: { [id: string]: ICorePluginClass } = {};
51
52
public readonly browserEngine: IBrowserEngine;
53
public readonly browserEmulator: IBrowserEmulator;
54
public readonly humanEmulator: IHumanEmulator;
55
56
private readonly instances: ICorePlugin[] = [];
57
private readonly instanceById: { [id: string]: ICorePlugin } = {};
58
private readonly createOptions: ICorePluginCreateOptions;
59
private readonly logger: IBoundLog;
60
61
constructor(options: IOptionsCreate, logger: IBoundLog) {
62
const {
63
userAgentSelector,
64
dependencyMap,
65
corePluginPaths,
66
browserEmulatorId = DefaultBrowserEmulatorId,
67
humanEmulatorId = DefaultHumanEmulatorId,
68
} = options;
69
70
let BrowserEmulator = Core.pluginMap.browserEmulatorsById[browserEmulatorId];
71
if (!BrowserEmulator) {
72
BrowserEmulator = requirePlugins<IBrowserEmulatorClass>(
73
browserEmulatorId,
74
PluginTypes.BrowserEmulator,
75
)[0];
76
}
77
if (!BrowserEmulator) throw new Error(`Browser emulator ${browserEmulatorId} was not found`);
78
79
let HumanEmulator = Core.pluginMap.humanEmulatorsById[humanEmulatorId];
80
// Backwards compatibility for 1.4.X > 1.5.0
81
if (!HumanEmulator && humanEmulatorId === 'basic') {
82
HumanEmulator = Core.pluginMap.humanEmulatorsById[DefaultHumanEmulatorId];
83
}
84
if (!HumanEmulator) {
85
HumanEmulator = requirePlugins<IHumanEmulatorClass>(
86
humanEmulatorId,
87
PluginTypes.HumanEmulator,
88
)[0];
89
}
90
if (!HumanEmulator) throw new Error(`Human emulator ${humanEmulatorId} was not found`);
91
92
const { browserEngine, userAgentOption } =
93
options.selectBrowserMeta || BrowserEmulator.selectBrowserMeta(userAgentSelector);
94
this.createOptions = {
95
browserEngine,
96
userAgentOption,
97
logger,
98
corePlugins: this,
99
deviceProfile: options.deviceProfile,
100
};
101
this.browserEngine = browserEngine;
102
this.logger = logger;
103
104
this.browserEmulator = new BrowserEmulator(this.createOptions);
105
this.addPluginInstance(this.browserEmulator);
106
107
this.humanEmulator = new HumanEmulator(this.createOptions);
108
this.addPluginInstance(this.humanEmulator);
109
110
Object.values(Core.pluginMap.corePluginsById).forEach(x => this.use(x));
111
112
if (Core.allowDynamicPluginLoading) {
113
if (corePluginPaths) {
114
this.loadCorePluginPaths(corePluginPaths);
115
}
116
if (dependencyMap) {
117
this.loadDependencies(dependencyMap);
118
}
119
}
120
}
121
122
// BROWSER EMULATORS
123
124
public configure(options: IBrowserEmulatorConfig): void {
125
this.instances.filter(p => p.configure).forEach(p => p.configure(options));
126
}
127
128
public onDnsConfiguration(settings: IDnsSettings): void {
129
this.instances.filter(p => p.onDnsConfiguration).forEach(p => p.onDnsConfiguration(settings));
130
}
131
132
public onTcpConfiguration(settings: ITcpSettings): void {
133
this.instances.filter(p => p.onTcpConfiguration).forEach(p => p.onTcpConfiguration(settings));
134
}
135
136
public onTlsConfiguration(settings: ITlsSettings): void {
137
this.instances.filter(p => p.onTlsConfiguration).forEach(p => p.onTlsConfiguration(settings));
138
}
139
140
public async onHttpAgentInitialized(agent: IHttpSocketAgent): Promise<void> {
141
await Promise.all(
142
this.instances
143
.filter(p => p.onHttpAgentInitialized)
144
.map(p => p.onHttpAgentInitialized(agent)),
145
);
146
}
147
148
public async onNewPuppetPage(page: IPuppetPage): Promise<void> {
149
await Promise.all(
150
this.instances.filter(p => p.onNewPuppetPage).map(p => p.onNewPuppetPage(page)),
151
);
152
}
153
154
public async onNewPuppetWorker(worker: IPuppetWorker): Promise<void> {
155
await Promise.all(
156
this.instances.filter(p => p.onNewPuppetWorker).map(p => p.onNewPuppetWorker(worker)),
157
);
158
}
159
160
public async onHttp2SessionConnect(
161
resource: IHttpResourceLoadDetails,
162
settings: IHttp2ConnectSettings,
163
): Promise<void> {
164
await Promise.all(
165
this.instances
166
.filter(p => p.onHttp2SessionConnect)
167
.map(p => p.onHttp2SessionConnect(resource, settings)),
168
);
169
}
170
171
public async beforeHttpRequest(resource: IHttpResourceLoadDetails): Promise<void> {
172
await Promise.all(
173
this.instances.filter(p => p.beforeHttpRequest).map(p => p.beforeHttpRequest(resource)),
174
);
175
}
176
177
public async beforeHttpResponse(resource: IHttpResourceLoadDetails): Promise<any> {
178
await Promise.all(
179
this.instances.filter(p => p.beforeHttpResponse).map(p => p.beforeHttpResponse(resource)),
180
);
181
}
182
183
public websiteHasFirstPartyInteraction(url: URL): void {
184
this.instances
185
.filter(p => p.websiteHasFirstPartyInteraction)
186
.forEach(p => p.websiteHasFirstPartyInteraction(url));
187
}
188
189
// HUMAN EMULATORS
190
191
public async playInteractions(
192
interactionGroups: IInteractionGroups,
193
runFn: (interaction: IInteractionStep) => Promise<void>,
194
helper?: IInteractionsHelper,
195
): Promise<void> {
196
const plugin = this.instances.filter(p => p.playInteractions).pop();
197
if (plugin && plugin.playInteractions) {
198
await plugin.playInteractions(interactionGroups, runFn, helper);
199
} else {
200
for (const interactionGroup of interactionGroups) {
201
for (const interactionStep of interactionGroup) {
202
await runFn(interactionStep);
203
}
204
}
205
}
206
}
207
208
public async getStartingMousePoint(helper?: IInteractionsHelper): Promise<IPoint> {
209
const plugin = this.instances.filter(p => p.getStartingMousePoint).pop();
210
if (plugin && plugin.getStartingMousePoint) {
211
return await plugin.getStartingMousePoint(helper);
212
}
213
}
214
215
// PLUGIN COMMANDS
216
217
public async onPluginCommand(
218
toPluginId: string,
219
commandMeta: IOnClientCommandMeta,
220
args: any[],
221
): Promise<any> {
222
const plugin = this.instanceById[toPluginId];
223
if (plugin && plugin.onClientCommand) {
224
return await plugin.onClientCommand(
225
{
226
puppetPage: commandMeta.puppetPage,
227
puppetFrame: commandMeta.puppetFrame,
228
},
229
...args,
230
);
231
}
232
this.logger.warn(`Plugin (${toPluginId}) could not be found for command`);
233
}
234
235
// ADDING PLUGINS TO THE STACK
236
237
public use(CorePlugin: ICorePluginClass) {
238
if (this.instanceById[CorePlugin.id]) return;
239
this.addPluginInstance(new CorePlugin(this.createOptions));
240
}
241
242
private addPluginInstance(corePlugin: ICorePlugin) {
243
this.instances.push(corePlugin);
244
this.instanceById[corePlugin.id] = corePlugin;
245
}
246
247
private require(corePluginId: string): ICorePluginClass {
248
if (!CorePlugins.corePluginClassesById[corePluginId]) {
249
try {
250
// eslint-disable-next-line global-require,import/no-dynamic-require
251
const CorePlugin = require(corePluginId);
252
if (!CorePlugin) return;
253
CorePlugins.corePluginClassesById[corePluginId] = CorePlugin.default || CorePlugin;
254
} catch (error) {
255
return;
256
}
257
}
258
return CorePlugins.corePluginClassesById[corePluginId];
259
}
260
261
private loadDependencies(dependencyMap: IDependencyMap) {
262
Object.entries(dependencyMap).forEach(([clientPluginId, corePluginIds]) => {
263
corePluginIds.forEach(corePluginId => {
264
if (this.instanceById[corePluginId]) return;
265
if (Core.pluginMap.corePluginsById[corePluginId]) return;
266
this.logger.info(`Dynamically requiring ${corePluginId} requested by ${clientPluginId}`);
267
const Plugin = requirePlugins<ICorePluginClass>(corePluginId, PluginTypes.CorePlugin)[0];
268
if (!Plugin) throw new Error(`Could not find ${corePluginId}`);
269
270
this.use(Plugin);
271
});
272
});
273
}
274
275
private loadCorePluginPaths(corePluginPaths: string[]) {
276
for (const corePluginPath of corePluginPaths) {
277
if (Core.pluginMap.corePluginsById[corePluginPath]) return;
278
const Plugins = requirePlugins<ICorePluginClass>(corePluginPath, PluginTypes.CorePlugin);
279
Plugins.forEach(Plugin => this.use(Plugin));
280
}
281
}
282
}
283
284