Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/client/lib/CoreEventHeap.ts
1028 views
1
import { IJsPath } from 'awaited-dom/base/AwaitedPath';
2
import ISessionMeta from '@secret-agent/interfaces/ISessionMeta';
3
import Log from '@secret-agent/commons/Logger';
4
import ConnectionToCore from '../connections/ConnectionToCore';
5
6
const { log } = Log(module);
7
8
type IListenerFn = (...args: any[]) => void;
9
type IInterceptorFn = (...args: any[]) => any;
10
11
export default class CoreEventHeap {
12
private readonly connection: ConnectionToCore;
13
private readonly listenerFnById: Map<string, IListenerFn> = new Map();
14
private readonly listenerIdByHandle: Map<string, string> = new Map();
15
private readonly eventInterceptors: Map<string, IInterceptorFn[]> = new Map();
16
private readonly meta: ISessionMeta;
17
private pendingRegistrations: Promise<any> = Promise.resolve();
18
19
constructor(meta: ISessionMeta | null, connection: ConnectionToCore) {
20
this.meta = meta;
21
this.connection = connection;
22
}
23
24
public hasEventInterceptors(type: string): boolean {
25
return this.eventInterceptors.has(type);
26
}
27
28
public registerEventInterceptors(interceptors: { [type: string]: IInterceptorFn }): void {
29
for (const [type, interceptor] of Object.entries(interceptors)) {
30
const events = this.eventInterceptors.get(type) ?? [];
31
events.push(interceptor);
32
this.eventInterceptors.set(type, events);
33
}
34
}
35
36
public async addListener(
37
jsPath: IJsPath | null,
38
type: string,
39
listenerFn: (...args: any[]) => void,
40
options?,
41
): Promise<void> {
42
const handle = this.generateListenerHandle(jsPath, type, listenerFn);
43
if (this.listenerIdByHandle.has(handle)) return;
44
45
const subscriptionPromise = this.connection.sendRequest({
46
meta: this.meta,
47
startDate: new Date(),
48
command: 'Session.addEventListener',
49
args: [jsPath, type, options],
50
});
51
52
this.pendingRegistrations = this.pendingRegistrations.then(() => subscriptionPromise);
53
54
const response = await subscriptionPromise;
55
const { listenerId } = response.data;
56
let wrapped = listenerFn;
57
if (this.eventInterceptors.has(type)) {
58
const interceptorFns = this.eventInterceptors.get(type);
59
wrapped = (...args: any[]) => {
60
let processedArgs = args;
61
for (const fn of interceptorFns) {
62
let result = fn(...processedArgs);
63
if (!Array.isArray(result)) result = [result];
64
processedArgs = result;
65
}
66
listenerFn(...processedArgs);
67
};
68
}
69
70
this.listenerFnById.set(listenerId, wrapped);
71
this.listenerIdByHandle.set(handle, listenerId);
72
}
73
74
public removeListener(
75
jsPath: IJsPath | null,
76
type: string,
77
listenerFn: (...args: any[]) => void,
78
): void {
79
const handle = this.generateListenerHandle(jsPath, type, listenerFn);
80
const listenerId = this.listenerIdByHandle.get(handle);
81
if (!listenerId) return;
82
83
this.connection
84
.sendRequest({
85
meta: this.meta,
86
startDate: new Date(),
87
command: 'Session.removeEventListener',
88
args: [listenerId],
89
})
90
.catch(error => {
91
log.error('removeEventListener Error: ', { error, sessionId: this.meta?.sessionId });
92
});
93
this.listenerFnById.delete(listenerId);
94
this.listenerIdByHandle.delete(handle);
95
}
96
97
public incomingEvent(meta: ISessionMeta, listenerId: string, eventArgs: any[]): void {
98
this.pendingRegistrations
99
.then(() => {
100
const listenerFn = this.listenerFnById.get(listenerId);
101
if (listenerFn) listenerFn(...eventArgs);
102
return null;
103
})
104
.catch(error => {
105
log.error('incomingEvent Error: ', { error, sessionId: this.meta?.sessionId });
106
});
107
}
108
109
private generateListenerHandle(
110
jsPath: IJsPath | null,
111
type: string,
112
listenerFn: (...args: any[]) => void,
113
): string {
114
const parts = [jsPath ? JSON.stringify(jsPath) : 'BASE'];
115
parts.push(type);
116
parts.push(listenerFn.toString());
117
return parts.join(':');
118
}
119
}
120
121