Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/core/models/ResourcesTable.ts
1028 views
1
import decodeBuffer from '@secret-agent/commons/decodeBuffer';
2
import IResourceMeta from '@secret-agent/interfaces/IResourceMeta';
3
import { Database as SqliteDatabase } from 'better-sqlite3';
4
import ResourceType from '@secret-agent/interfaces/ResourceType';
5
import IResourceHeaders from '@secret-agent/interfaces/IResourceHeaders';
6
import SqliteTable from '@secret-agent/commons/SqliteTable';
7
8
export default class ResourcesTable extends SqliteTable<IResourcesRecord> {
9
constructor(readonly db: SqliteDatabase) {
10
super(
11
db,
12
'Resources',
13
[
14
['id', 'INTEGER', 'NOT NULL PRIMARY KEY'],
15
['devtoolsRequestId', 'TEXT'],
16
['tabId', 'INTEGER'],
17
['socketId', 'INTEGER'],
18
['protocol', 'TEXT'],
19
['type', 'TEXT'],
20
['receivedAtCommandId', 'INTEGER'],
21
['seenAtCommandId', 'INTEGER'],
22
['requestMethod', 'TEXT'],
23
['requestUrl', 'TEXT'],
24
['requestHeaders', 'TEXT'],
25
['requestTrailers', 'TEXT'],
26
['requestTimestamp', 'INTEGER'],
27
['requestPostData', 'TEXT'],
28
['redirectedToUrl', 'TEXT'],
29
['statusCode', 'INTEGER'],
30
['statusMessage', 'TEXT'],
31
['responseUrl', 'TEXT'],
32
['responseHeaders', 'TEXT'],
33
['responseTrailers', 'TEXT'],
34
['responseTimestamp', 'INTEGER'],
35
['responseEncoding', 'TEXT'],
36
['responseData', 'BLOB'],
37
['dnsResolvedIp', 'TEXT'],
38
['isHttp2Push', 'INTEGER'],
39
['usedArtificialCache', 'INTEGER'],
40
['didUserScriptBlockResource', 'INTEGER'],
41
['requestOriginalHeaders', 'TEXT'],
42
['responseOriginalHeaders', 'TEXT'],
43
['httpError', 'TEXT'],
44
['browserServedFromCache', 'TEXT'],
45
['browserLoadFailure', 'TEXT'],
46
['browserBlockedReason', 'TEXT'],
47
['browserCanceled', 'INTEGER'],
48
['documentUrl', 'TEXT'],
49
],
50
true,
51
);
52
}
53
54
public updateResource(id: number, data: { tabId: number; browserRequestId: string }): void {
55
const pendingInserts = this.findPendingInserts(x => x[0] === id);
56
if (pendingInserts.length) {
57
const pending = pendingInserts.pop();
58
pending[1] = data.browserRequestId;
59
pending[2] = data.tabId;
60
return;
61
}
62
this.db
63
.prepare(`update ${this.tableName} set tabId=?, devtoolsRequestId=? where id=?`)
64
.run(data.tabId, data.browserRequestId, id);
65
}
66
67
public get(id: number): IResourcesRecord {
68
const pending = this.findPendingRecords(x => x[0] === id);
69
if (pending.length) return pending.pop();
70
71
return this.db.prepare(`select * from ${this.tableName} where id=?`).get(id);
72
}
73
74
public save(record: IResourcesRecord): void {
75
return this.queuePendingInsert([
76
record.id,
77
record.devtoolsRequestId,
78
record.tabId,
79
record.socketId,
80
record.protocol,
81
record.type,
82
record.receivedAtCommandId,
83
record.seenAtCommandId,
84
record.requestMethod,
85
record.requestUrl,
86
record.requestHeaders,
87
record.requestTrailers,
88
record.requestTimestamp,
89
record.requestPostData,
90
record.redirectedToUrl,
91
record.statusCode,
92
record.statusMessage,
93
record.responseUrl,
94
record.responseHeaders,
95
record.responseTrailers,
96
record.responseTimestamp,
97
record.responseEncoding,
98
record.responseData,
99
record.dnsResolvedIp,
100
record.isHttp2Push ? 1 : 0,
101
record.usedArtificialCache ? 1 : 0,
102
record.didUserScriptBlockResource ? 1 : 0,
103
record.requestOriginalHeaders,
104
record.responseOriginalHeaders,
105
record.httpError,
106
record.browserServedFromCache,
107
record.browserLoadFailure,
108
record.browserBlockedReason,
109
record.browserCanceled ? 1 : 0,
110
record.documentUrl,
111
]);
112
}
113
114
public insert(
115
tabId: number,
116
meta: IResourceMeta,
117
body: Buffer,
118
extras: {
119
socketId: number;
120
redirectedToUrl?: string;
121
originalHeaders: IResourceHeaders;
122
responseOriginalHeaders?: IResourceHeaders;
123
protocol: string;
124
dnsResolvedIp?: string;
125
wasCached?: boolean;
126
didBlockResource: boolean;
127
browserRequestId?: string;
128
isHttp2Push: boolean;
129
browserBlockedReason?: string;
130
browserCanceled?: boolean;
131
},
132
error?: Error,
133
) {
134
const errorString = ResourcesTable.getErrorString(error);
135
136
let contentEncoding: string;
137
if (meta.response && meta.response.headers) {
138
contentEncoding = <string>(
139
(meta.response.headers['Content-Encoding'] ?? meta.response.headers['content-encoding'])
140
);
141
}
142
return this.queuePendingInsert([
143
meta.id,
144
extras.browserRequestId,
145
tabId,
146
extras.socketId,
147
extras.protocol,
148
meta.type,
149
meta.receivedAtCommandId,
150
null,
151
meta.request.method,
152
meta.request.url,
153
JSON.stringify(meta.request.headers ?? {}),
154
JSON.stringify(meta.request.trailers ?? {}),
155
meta.request.timestamp,
156
meta.request.postData,
157
extras.redirectedToUrl,
158
meta.response?.statusCode,
159
meta.response?.statusMessage,
160
meta.response?.url,
161
meta.response ? JSON.stringify(meta.response.headers ?? {}) : undefined,
162
meta.response ? JSON.stringify(meta.response.trailers ?? {}) : undefined,
163
meta.response?.timestamp,
164
contentEncoding,
165
meta.response ? body : undefined,
166
extras.dnsResolvedIp,
167
extras.isHttp2Push ? 1 : 0,
168
extras.wasCached ? 1 : 0,
169
extras.didBlockResource ? 1 : 0,
170
JSON.stringify(extras.originalHeaders ?? {}),
171
JSON.stringify(extras.responseOriginalHeaders ?? {}),
172
errorString,
173
meta.response?.browserServedFromCache,
174
meta.response?.browserLoadFailure,
175
extras.browserBlockedReason,
176
extras.browserCanceled ? 1 : 0,
177
meta.documentUrl,
178
]);
179
}
180
181
public getResponse(
182
resourceId: number,
183
): Pick<
184
IResourcesRecord,
185
'responseEncoding' | 'responseHeaders' | 'statusCode' | 'responseData'
186
> {
187
const record = this.db
188
.prepare(
189
`select responseEncoding, responseHeaders, statusCode, responseData from ${this.tableName} where id=? limit 1`,
190
)
191
.get(resourceId);
192
if (!record) return null;
193
return record;
194
}
195
196
public async getResourceBodyById(resourceId: number, decompress = true): Promise<Buffer> {
197
const pendingRecords = this.findPendingRecords(x => x[0] === resourceId);
198
199
let record = pendingRecords.find(x => !!x.responseData);
200
201
if (!record) {
202
record = this.db
203
.prepare(`select responseData, responseEncoding from ${this.tableName} where id=? limit 1`)
204
.get(resourceId);
205
}
206
if (!record) return null;
207
208
const { responseData, responseEncoding } = record;
209
if (!decompress) return responseData;
210
return await decodeBuffer(responseData, responseEncoding);
211
}
212
213
public static getErrorString(error: Error | string): string {
214
if (error) {
215
if (typeof error === 'string') return error;
216
return JSON.stringify({
217
name: error.name,
218
stack: error.stack,
219
message: error.message,
220
...error,
221
});
222
}
223
}
224
}
225
226
export interface IResourcesRecord {
227
id: number;
228
devtoolsRequestId: string;
229
tabId: number;
230
socketId: number;
231
protocol: string;
232
type: ResourceType;
233
receivedAtCommandId: number;
234
seenAtCommandId: number;
235
requestMethod: string;
236
requestUrl: string;
237
requestHeaders: string;
238
requestTrailers?: string;
239
requestTimestamp: number;
240
requestPostData?: string;
241
redirectedToUrl?: string;
242
statusCode: number;
243
statusMessage: string;
244
responseUrl: string;
245
responseHeaders: string;
246
responseTrailers?: string;
247
responseTimestamp: number;
248
responseEncoding: string;
249
responseData?: Buffer;
250
dnsResolvedIp?: string;
251
usedArtificialCache: boolean;
252
didUserScriptBlockResource: boolean;
253
isHttp2Push: boolean;
254
requestOriginalHeaders: string;
255
responseOriginalHeaders: string;
256
httpError: string;
257
258
browserServedFromCache?: 'service-worker' | 'disk' | 'prefetch' | 'memory';
259
browserLoadFailure?: string;
260
browserBlockedReason?: string;
261
browserCanceled?: boolean;
262
documentUrl: string;
263
}
264
265