Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/core/lib/InjectedScripts.ts
1029 views
1
import * as fs from 'fs';
2
import { IPuppetPage } from '@secret-agent/interfaces/IPuppetPage';
3
import { stringifiedTypeSerializerClass } from '@secret-agent/commons/TypeSerializer';
4
import injectedSourceUrl from '@secret-agent/interfaces/injectedSourceUrl';
5
import { IIndexedDB } from '@secret-agent/interfaces/IIndexedDB';
6
import { IFrontendDomChangeEvent } from '../models/DomChangesTable';
7
8
const pageScripts = {
9
domStorage: fs.readFileSync(`${__dirname}/../injected-scripts/domStorage.js`, 'utf8'),
10
indexedDbRestore: fs.readFileSync(`${__dirname}/../injected-scripts/indexedDbRestore.js`, 'utf8'),
11
domReplayer: fs.readFileSync(`${__dirname}/../injected-scripts/domReplayer.js`, 'utf8'),
12
interactReplayer: fs.readFileSync(`${__dirname}/../injected-scripts/interactReplayer.js`, 'utf8'),
13
NodeTracker: fs.readFileSync(`${__dirname}/../injected-scripts/NodeTracker.js`, 'utf8'),
14
jsPath: fs.readFileSync(`${__dirname}/../injected-scripts/jsPath.js`, 'utf8'),
15
Fetcher: fs.readFileSync(`${__dirname}/../injected-scripts/Fetcher.js`, 'utf8'),
16
MouseEvents: fs.readFileSync(`${__dirname}/../injected-scripts/MouseEvents.js`, 'utf8'),
17
pageEventsRecorder: fs.readFileSync(
18
`${__dirname}/../injected-scripts/pageEventsRecorder.js`,
19
'utf8',
20
),
21
};
22
const pageEventsCallbackName = '__saPageListenerCallback';
23
24
const injectedScript = `(function installInjectedScripts() {
25
const exports = {}; // workaround for ts adding an exports variable
26
${stringifiedTypeSerializerClass};
27
28
${pageScripts.NodeTracker};
29
${pageScripts.jsPath};
30
${pageScripts.Fetcher};
31
${pageScripts.MouseEvents};
32
33
(function installDomRecorder(runtimeFunction) {
34
${pageScripts.pageEventsRecorder}
35
})('${pageEventsCallbackName}');
36
37
window.SA = {
38
JsPath,
39
MouseEvents,
40
Fetcher,
41
};
42
43
${pageScripts.domStorage}
44
})();`;
45
46
const detachedInjectedScript = `(function installInjectedScripts() {
47
const exports = {}; // workaround for ts adding an exports variable
48
${stringifiedTypeSerializerClass};
49
50
const TSON = TypeSerializer;
51
52
${pageScripts.NodeTracker};
53
${pageScripts.jsPath};
54
${pageScripts.Fetcher};
55
56
window.SA = {
57
JsPath,
58
Fetcher,
59
};
60
})();`;
61
62
const replayDomAndInteractionScript = `
63
if (typeof exports === 'undefined') {
64
var exports = {}; // workaround for ts adding an exports variable
65
}
66
${pageScripts.NodeTracker};
67
${pageScripts.domReplayer};
68
69
${pageScripts.interactReplayer};
70
`;
71
72
const installedSymbol = Symbol('InjectedScripts.Installed');
73
const replayInstalledSymbol = Symbol('InjectedScripts.replayInstalled');
74
75
export default class InjectedScripts {
76
public static JsPath = `SA.JsPath`;
77
public static Fetcher = `SA.Fetcher`;
78
public static PageEventsCallbackName = pageEventsCallbackName;
79
80
public static install(puppetPage: IPuppetPage): Promise<any> {
81
if (puppetPage[installedSymbol]) return;
82
puppetPage[installedSymbol] = true;
83
84
return Promise.all([
85
puppetPage.addPageCallback(pageEventsCallbackName),
86
puppetPage.addNewDocumentScript(injectedScript, true),
87
puppetPage.addNewDocumentScript(`delete window.${pageEventsCallbackName}`, false),
88
]);
89
}
90
91
public static getReplayScript(): string {
92
return replayDomAndInteractionScript;
93
}
94
95
public static async installDetachedScripts(puppetPage: IPuppetPage): Promise<void> {
96
if (puppetPage[installedSymbol]) return;
97
puppetPage[installedSymbol] = true;
98
99
await puppetPage.addNewDocumentScript(detachedInjectedScript, true);
100
}
101
102
public static async restoreDom(
103
puppetPage: IPuppetPage,
104
domChanges: IFrontendDomChangeEvent[],
105
): Promise<void> {
106
const columns = [
107
'action',
108
'nodeId',
109
'nodeType',
110
'textContent',
111
'tagName',
112
'namespaceUri',
113
'parentNodeId',
114
'previousSiblingId',
115
'attributeNamespaces',
116
'attributes',
117
'properties',
118
];
119
const records = domChanges.map(x => columns.map(col => x[col]));
120
if (!puppetPage[installedSymbol]) {
121
await this.installDetachedScripts(puppetPage);
122
}
123
// NOTE: NodeTracker is installed by detachedScripts
124
const domScript = puppetPage[replayInstalledSymbol] ? '' : pageScripts.domReplayer;
125
puppetPage[replayInstalledSymbol] = true;
126
await puppetPage.mainFrame.evaluate(
127
`(function replayEvents(){
128
const exports = {};
129
window.isMainFrame = true;
130
131
(() => {
132
${domScript};
133
})();
134
135
const records = ${JSON.stringify(records).replace(/,null/g, ',')};
136
const events = [];
137
for (const [${columns.join(',')}] of records) {
138
const event = {${columns.join(',')}};
139
events.push(event);
140
}
141
142
window.replayDomChanges(events);
143
})()
144
//# sourceURL=${injectedSourceUrl}`,
145
true,
146
);
147
}
148
149
public static getIndexedDbStorageRestoreScript(restoreDBs: IIndexedDB[]): string {
150
return `(function restoreIndexedDB(dbs) {
151
const exports = {}; // workaround for ts adding an exports variable
152
${stringifiedTypeSerializerClass};
153
154
${pageScripts.indexedDbRestore};
155
restoreUserStorage(dbs);
156
})(${JSON.stringify(restoreDBs)});`;
157
}
158
}
159
160