Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/core/models/DevtoolsMessagesTable.ts
1028 views
1
// eslint-disable-next-line max-classes-per-file
2
import { Database as SqliteDatabase } from 'better-sqlite3';
3
import type { IPuppetContextEvents } from '@secret-agent/interfaces/IPuppetContext';
4
import SqliteTable from '@secret-agent/commons/SqliteTable';
5
6
export default class DevtoolsMessagesTable extends SqliteTable<IDevtoolsMessageRecord> {
7
private fetchRequestIdToNetworkId = new Map<string, string>();
8
private pageIds = new IdAssigner();
9
private workerIds = new IdAssigner();
10
private frameIds = new IdAssigner();
11
private requestIds = new IdAssigner();
12
13
private sentMessageIds: {
14
id: number;
15
sessionId: string;
16
frameId?: string;
17
requestId?: string;
18
}[] = [];
19
20
constructor(readonly db: SqliteDatabase) {
21
super(db, 'DevtoolsMessages', [
22
['send', 'INTEGER'],
23
['pageNumber', 'INTEGER'],
24
['workerNumber', 'INTEGER'],
25
['frameNumber', 'INTEGER'],
26
['requestNumber', 'INTEGER'],
27
['isBrowserSession', 'INTEGER'],
28
['method', 'TEXT'],
29
['id', 'INTEGER'],
30
['params', 'TEXT'],
31
['error', 'TEXT'],
32
['result', 'TEXT'],
33
['timestamp', 'INTEGER'],
34
]);
35
}
36
37
public insert(event: IPuppetContextEvents['devtools-message']) {
38
if (filteredEventMethods.has(event.method)) return;
39
const params = event.params;
40
let frameId = event.frameId;
41
let requestId: string;
42
let pageId = event.pageTargetId;
43
if (params) {
44
frameId = frameId ?? params.frame?.id ?? params.frameId ?? params.context?.auxData?.frameId;
45
46
// translate Fetch.requestPaused networkId (which is what we use in other parts of the app
47
requestId =
48
this.fetchRequestIdToNetworkId.get(params.requestId) ??
49
params.networkId ??
50
params.requestId;
51
if (params.networkId) this.fetchRequestIdToNetworkId.set(params.requestId, params.networkId);
52
53
if (!pageId && params.targetInfo && params.targetInfo?.type === 'page') {
54
pageId = params.targetInfo.targetId;
55
}
56
}
57
58
if ((requestId || frameId) && event.direction === 'send') {
59
this.sentMessageIds.push({
60
id: event.id,
61
sessionId: event.sessionId,
62
frameId,
63
});
64
}
65
66
if ((!requestId || !frameId) && event.direction === 'receive' && event.id) {
67
const match = this.sentMessageIds.find(
68
x => x.id === event.id && x.sessionId === event.sessionId,
69
);
70
if (match) {
71
this.sentMessageIds.splice(this.sentMessageIds.indexOf(match), 1);
72
if (!frameId) frameId = match.frameId;
73
if (!requestId) requestId = match.requestId;
74
}
75
}
76
77
function paramsStringifyFilter(key: string, value: any) {
78
if (
79
key === 'payload' &&
80
event.method === 'Runtime.bindingCalled' &&
81
params.name === '__saPageListenerCallback' &&
82
value?.length > 250
83
) {
84
return `${value.substr(0, 250)}... [truncated ${value.length - 250} chars]`;
85
}
86
87
if (
88
key === 'source' &&
89
event.method === 'Page.addScriptToEvaluateOnNewDocument' &&
90
value?.length > 50
91
) {
92
return `${value.substr(0, 50)}... [truncated ${value.length - 50} chars]`;
93
}
94
95
if ((key === 'headers' || key === 'postData') && params.request) {
96
// clean out post data (we have these in resources table)
97
return 'SA_REMOVED_FOR_DB';
98
}
99
return value;
100
}
101
102
const workerId = event.workerTargetId;
103
const record = [
104
event.direction === 'send' ? 1 : undefined,
105
this.pageIds.get(pageId),
106
this.workerIds.get(workerId),
107
this.frameIds.get(frameId),
108
this.requestIds.get(requestId),
109
event.sessionType === 'browser' ? 1 : undefined,
110
event.method,
111
event.id,
112
params ? JSON.stringify(params, paramsStringifyFilter) : undefined,
113
event.error ? JSON.stringify(event.error) : undefined,
114
event.result ? JSON.stringify(event.result) : undefined,
115
event.timestamp.getTime(),
116
];
117
this.queuePendingInsert(record);
118
}
119
}
120
121
class IdAssigner {
122
private counter = 0;
123
private devtoolIdToNumeric = new Map<string, number>();
124
get(id: string): number {
125
if (!id) return undefined;
126
if (!this.devtoolIdToNumeric.has(id)) {
127
this.devtoolIdToNumeric.set(id, (this.counter += 1));
128
}
129
return this.devtoolIdToNumeric.get(id);
130
}
131
}
132
133
const filteredEventMethods = new Set([
134
'Network.dataReceived', // Not useful to SA since we use Mitm
135
'Page.domContentEventFired', // duplicated by Page.lifecycleEvent
136
'Page.loadEventFired', // duplicated by Page.lifecycleEvent
137
]);
138
139
export interface IDevtoolsMessageRecord {
140
send: boolean;
141
pageNumber?: number;
142
workerNumber?: number;
143
frameNumber?: number;
144
requestNumber?: string;
145
isBrowserSession: boolean;
146
method?: string;
147
id?: number;
148
params?: string;
149
error?: string;
150
result?: string;
151
timestamp: number;
152
}
153
154