Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/core/index.ts
1028 views
1
import ICoreConfigureOptions from '@secret-agent/interfaces/ICoreConfigureOptions';
2
import { LocationTrigger } from '@secret-agent/interfaces/Location';
3
import Log, { hasBeenLoggedSymbol } from '@secret-agent/commons/Logger';
4
import Resolvable from '@secret-agent/commons/Resolvable';
5
import {
6
IBrowserEmulatorClass,
7
ICorePluginClass,
8
IHumanEmulatorClass,
9
} from '@secret-agent/interfaces/ICorePlugin';
10
import { PluginTypes } from '@secret-agent/interfaces/IPluginTypes';
11
import DefaultBrowserEmulator from '@secret-agent/default-browser-emulator';
12
import DefaultHumanEmulator from '@secret-agent/default-human-emulator';
13
import extractPlugins from '@secret-agent/plugin-utils/lib/utils/extractPlugins';
14
import requirePlugins from '@secret-agent/plugin-utils/lib/utils/requirePlugins';
15
import { IPluginClass } from '@secret-agent/interfaces/IPlugin';
16
import ShutdownHandler from '@secret-agent/commons/ShutdownHandler';
17
import ConnectionToClient from './server/ConnectionToClient';
18
import CoreServer from './server';
19
import CoreProcess from './lib/CoreProcess';
20
import Session from './lib/Session';
21
import Tab from './lib/Tab';
22
import GlobalPool from './lib/GlobalPool';
23
24
const { log } = Log(module);
25
26
export { GlobalPool, Tab, Session, LocationTrigger, CoreProcess };
27
28
export default class Core {
29
public static server = new CoreServer();
30
public static readonly connections: ConnectionToClient[] = [];
31
public static pluginMap: {
32
humanEmulatorsById: { [id: string]: IHumanEmulatorClass };
33
browserEmulatorsById: { [id: string]: IBrowserEmulatorClass };
34
corePluginsById: { [id: string]: ICorePluginClass };
35
} = {
36
humanEmulatorsById: {
37
[DefaultHumanEmulator.id]: DefaultHumanEmulator,
38
},
39
browserEmulatorsById: {
40
[DefaultBrowserEmulator.id]: DefaultBrowserEmulator,
41
},
42
corePluginsById: {},
43
};
44
45
public static onShutdown: () => void;
46
47
public static isClosing: Promise<void>;
48
public static allowDynamicPluginLoading = true;
49
private static wasManuallyStarted = false;
50
private static isStarting = false;
51
52
public static addConnection(): ConnectionToClient {
53
const connection = new ConnectionToClient();
54
connection.on('close', () => {
55
const idx = this.connections.indexOf(connection);
56
if (idx >= 0) this.connections.splice(idx, 1);
57
this.checkForAutoShutdown();
58
});
59
this.connections.push(connection);
60
return connection;
61
}
62
63
public static use(PluginObject: string | ICorePluginClass | { [name: string]: IPluginClass }) {
64
let Plugins: IPluginClass[];
65
if (typeof PluginObject === 'string') {
66
Plugins = requirePlugins(PluginObject as string);
67
} else {
68
Plugins = extractPlugins(PluginObject as any);
69
}
70
71
for (const Plugin of Plugins) {
72
if (Plugin.type === PluginTypes.HumanEmulator) {
73
this.pluginMap.humanEmulatorsById[Plugin.id] = Plugin as IHumanEmulatorClass;
74
} else if (Plugin.type === PluginTypes.BrowserEmulator) {
75
this.pluginMap.browserEmulatorsById[Plugin.id] = Plugin as IBrowserEmulatorClass;
76
} else if (Plugin.type === PluginTypes.CorePlugin) {
77
this.pluginMap.corePluginsById[Plugin.id] = Plugin;
78
}
79
}
80
}
81
82
public static async start(
83
options: ICoreConfigureOptions = {},
84
isExplicitlyStarted = true,
85
): Promise<void> {
86
if (this.isStarting) return;
87
const startLogId = log.info('Core.start', {
88
options,
89
isExplicitlyStarted,
90
sessionId: null,
91
});
92
this.isClosing = null;
93
this.isStarting = true;
94
if (isExplicitlyStarted) this.wasManuallyStarted = true;
95
96
this.registerSignals();
97
const { localProxyPortStart, sessionsDir, maxConcurrentAgentsCount } = options;
98
99
if (maxConcurrentAgentsCount !== undefined)
100
GlobalPool.maxConcurrentAgentsCount = maxConcurrentAgentsCount;
101
102
if (localProxyPortStart !== undefined)
103
GlobalPool.localProxyPortStart = options.localProxyPortStart;
104
105
if (sessionsDir !== undefined) {
106
GlobalPool.sessionsDir = options.sessionsDir;
107
}
108
109
await GlobalPool.start();
110
111
await this.server.listen({ port: options.coreServerPort });
112
113
const host = await this.server.address;
114
115
log.info('Core started', {
116
coreHost: await Core.server.address,
117
sessionId: null,
118
parentLogId: startLogId,
119
sessionsDir: GlobalPool.sessionsDir,
120
});
121
// if started as a subprocess, send back the host
122
if (process.send && process.connected) {
123
ShutdownHandler.exitOnSignal = true;
124
process.send(host);
125
}
126
}
127
128
public static async shutdown(force = false): Promise<void> {
129
if (this.isClosing) return this.isClosing;
130
131
const isClosing = new Resolvable<void>();
132
this.isClosing = isClosing.promise;
133
134
this.isStarting = false;
135
const logid = log.info('Core.shutdown');
136
const shutDownErrors: Error[] = [];
137
try {
138
await Promise.all(this.connections.map(x => x.disconnect())).catch(error =>
139
shutDownErrors.push(error),
140
);
141
await GlobalPool.close().catch(error => shutDownErrors.push(error));
142
await this.server.close(!force).catch(error => shutDownErrors.push(error));
143
144
this.wasManuallyStarted = false;
145
if (this.onShutdown) this.onShutdown();
146
isClosing.resolve();
147
} catch (error) {
148
isClosing.reject(error);
149
} finally {
150
log.info('Core.shutdownComplete', {
151
parentLogId: logid,
152
sessionId: null,
153
errors: shutDownErrors.length ? shutDownErrors : undefined,
154
});
155
}
156
return isClosing.promise;
157
}
158
159
public static logUnhandledError(clientError: Error, fatalError = false): void {
160
if (!clientError || clientError[hasBeenLoggedSymbol]) return;
161
if (fatalError) {
162
log.error('UnhandledError(fatal)', { clientError, sessionId: null });
163
} else if (!clientError[hasBeenLoggedSymbol]) {
164
log.error('UnhandledErrorOrRejection', { clientError, sessionId: null });
165
}
166
}
167
168
private static checkForAutoShutdown(): void {
169
if (Core.wasManuallyStarted || this.connections.some(x => x.isActive())) return;
170
171
Core.shutdown().catch(error => {
172
log.error('Core.autoShutdown', {
173
error,
174
sessionId: null,
175
});
176
});
177
}
178
179
private static registerSignals() {
180
ShutdownHandler.register(() => Core.shutdown());
181
182
if (process.env.NODE_ENV !== 'test') {
183
process.on('uncaughtExceptionMonitor', async (error: Error) => {
184
await Core.logUnhandledError(error, true);
185
await Core.shutdown();
186
});
187
process.on('unhandledRejection', async (error: Error) => {
188
await Core.logUnhandledError(error, false);
189
});
190
}
191
}
192
}
193
194