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