pagespeed-quest 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,175 @@
1
+ import Fs from 'fs';
2
+ import Fsp from 'fs/promises';
3
+ import Path from 'path';
4
+ import { compress, decompress } from './encoding.js';
5
+ import { convertEditableText, isText } from './formatting.js';
6
+ import { parseContentTypeHeader, requestContentFilePath, stringifyContentTypeHeader } from './http.js';
7
+ const InventoryDir = 'inventory';
8
+ const IndexFile = 'index.json';
9
+ export class InventoryRepository {
10
+ dirPath;
11
+ dependency;
12
+ constructor(dirPath, dependency) {
13
+ this.dirPath = dirPath || Path.join(process.cwd(), InventoryDir);
14
+ this.dependency = dependency || {};
15
+ }
16
+ async saveInventory(inventory) {
17
+ const inventoryJson = JSON.stringify(inventory, null, 2);
18
+ await Fsp.mkdir(this.dirPath, { recursive: true });
19
+ await Fsp.writeFile(Path.join(this.dirPath, IndexFile), inventoryJson);
20
+ }
21
+ async loadInventory() {
22
+ const inventoryJson = await Fsp.readFile(Path.join(this.dirPath, IndexFile), 'utf8');
23
+ const inventory = JSON.parse(inventoryJson);
24
+ return inventory;
25
+ }
26
+ async saveTransactions(transactions) {
27
+ // To keep transactions order in Promise.all,
28
+ // store transactions and resources in a map.
29
+ const map = new Map();
30
+ await Fsp.mkdir(this.dirPath, { recursive: true });
31
+ const saveTransaction = async (transaction) => {
32
+ const resource = {
33
+ method: transaction.method,
34
+ url: transaction.url,
35
+ ttfbMs: transaction.ttfbMs,
36
+ statusCode: transaction.statusCode,
37
+ errorMessage: transaction.errorMessage,
38
+ rawHeaders: transaction.rawHeaders,
39
+ };
40
+ // Headers
41
+ if (transaction.rawHeaders) {
42
+ if (transaction.rawHeaders['content-type']) {
43
+ const { mime, charset } = parseContentTypeHeader(transaction.rawHeaders['content-type']);
44
+ if (mime)
45
+ resource.contentTypeMime = mime;
46
+ if (charset)
47
+ resource.contentTypeCharset = charset;
48
+ }
49
+ if (transaction.rawHeaders['content-encoding']) {
50
+ const contentEncoding = transaction.rawHeaders['content-encoding'];
51
+ if (contentEncoding)
52
+ resource.contentEncoding = contentEncoding;
53
+ }
54
+ }
55
+ // Mbps
56
+ if (transaction.durationMs && transaction.content) {
57
+ const contentBits = transaction.content.length * 8;
58
+ const seconds = transaction.durationMs / 1000;
59
+ const mega = 1024 * 1024;
60
+ resource.mbps = contentBits / seconds / mega;
61
+ }
62
+ // Content
63
+ if (transaction.content) {
64
+ const steps = {};
65
+ const contentFilePath = requestContentFilePath(resource.method, resource.url);
66
+ const fullPath = Path.join(this.dirPath, contentFilePath);
67
+ // Content-Encoding
68
+ steps.decoded = resource.contentEncoding
69
+ ? await decompress(resource.contentEncoding, transaction.content)
70
+ : transaction.content;
71
+ // Try to make editable (utf8, beautify)
72
+ steps.editable = steps.decoded;
73
+ if (isText(resource.contentTypeMime)) {
74
+ try {
75
+ steps.editable = await convertEditableText(steps.decoded, resource.contentTypeMime, resource.contentTypeCharset);
76
+ resource.contentTypeCharset = 'utf-8';
77
+ }
78
+ catch (err) {
79
+ this.dependency.logger?.error({ err, resource }, `Formatting failed ${transaction.url}: ${err.message}`);
80
+ }
81
+ }
82
+ await Fsp.mkdir(Path.dirname(fullPath), { recursive: true });
83
+ await Fsp.writeFile(fullPath, steps.editable);
84
+ resource.contentFilePath = contentFilePath;
85
+ }
86
+ map.set(transaction, resource);
87
+ };
88
+ const tryToSaveTransaction = async (transaction) => {
89
+ try {
90
+ await saveTransaction(transaction);
91
+ }
92
+ catch (err) {
93
+ this.dependency.logger?.error({ err, method: transaction.method, url: transaction.url }, `Failed to save transaction ${transaction.url}: ${err.message}`);
94
+ }
95
+ };
96
+ await Promise.all(transactions.map(tryToSaveTransaction));
97
+ // Restore transactions order after Promise.all
98
+ const resources = transactions.reduce((resources, transaction) => {
99
+ const resource = map.get(transaction);
100
+ if (resource)
101
+ resources.push(resource);
102
+ return resources;
103
+ }, []);
104
+ return resources;
105
+ }
106
+ async loadTransactions(resources) {
107
+ const map = new Map();
108
+ const loadTransaction = async (resource) => {
109
+ const transaction = {
110
+ method: resource.method,
111
+ url: resource.url,
112
+ ttfbMs: resource.ttfbMs,
113
+ statusCode: resource.statusCode,
114
+ errorMessage: resource.errorMessage,
115
+ rawHeaders: { ...(resource.rawHeaders || {}) },
116
+ };
117
+ // content
118
+ let content;
119
+ if (resource.contentUtf8) {
120
+ content = Buffer.from(resource.contentUtf8);
121
+ }
122
+ else if (resource.contentBase64) {
123
+ content = Buffer.from(resource.contentBase64, 'base64');
124
+ }
125
+ else if (resource.contentFilePath) {
126
+ const fullPath = Path.join(this.dirPath, resource.contentFilePath);
127
+ if (Fs.existsSync(fullPath)) {
128
+ content = await Fsp.readFile(fullPath);
129
+ }
130
+ }
131
+ if (content) {
132
+ // encoding
133
+ if (resource.contentEncoding) {
134
+ transaction.content = await compress(resource.contentEncoding, content);
135
+ transaction.rawHeaders['content-encoding'] = resource.contentEncoding;
136
+ }
137
+ else {
138
+ transaction.content = content;
139
+ delete transaction.rawHeaders['content-encoding'];
140
+ }
141
+ // length
142
+ transaction.rawHeaders['content-length'] = `${transaction.content.length}`;
143
+ // duration
144
+ const bytesPerMs = resource.mbps ? (resource.mbps * 1024 * 1024) / 8 / 1000 : 0;
145
+ transaction.durationMs = bytesPerMs ? content.length / bytesPerMs : 0;
146
+ }
147
+ else {
148
+ transaction.rawHeaders['content-length'] = '0';
149
+ transaction.durationMs = 0;
150
+ }
151
+ // Content-Type
152
+ if (resource.contentTypeMime) {
153
+ transaction.rawHeaders['content-type'] = stringifyContentTypeHeader(resource.contentTypeMime, resource.contentTypeCharset);
154
+ }
155
+ map.set(resource, transaction);
156
+ };
157
+ const tryToLoadTransaction = async (resource) => {
158
+ try {
159
+ await loadTransaction(resource);
160
+ }
161
+ catch (err) {
162
+ this.dependency.logger?.error({ err, resource }, `Loading transaction failed ${resource.url}: ${err.message}`);
163
+ }
164
+ };
165
+ await Promise.all(resources.map(tryToLoadTransaction));
166
+ const transactions = resources.reduce((transactions, resource) => {
167
+ const transaction = map.get(resource);
168
+ if (transaction)
169
+ transactions.push(transaction);
170
+ return transactions;
171
+ }, []);
172
+ return transactions;
173
+ }
174
+ }
175
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW52ZW50b3J5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ludmVudG9yeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxJQUFJLENBQUE7QUFDbkIsT0FBTyxHQUFHLE1BQU0sYUFBYSxDQUFBO0FBQzdCLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQTtBQUV2QixPQUFPLEVBQUUsUUFBUSxFQUF1QixVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUE7QUFDekUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFBO0FBQzdELE9BQU8sRUFBZSxzQkFBc0IsRUFBRSxzQkFBc0IsRUFBRSwwQkFBMEIsRUFBRSxNQUFNLFdBQVcsQ0FBQTtBQUduSCxNQUFNLFlBQVksR0FBRyxXQUFXLENBQUE7QUFDaEMsTUFBTSxTQUFTLEdBQUcsWUFBWSxDQUFBO0FBc0M5QixNQUFNLE9BQU8sbUJBQW1CO0lBQzlCLE9BQU8sQ0FBUztJQUNoQixVQUFVLENBQStCO0lBRXpDLFlBQVksT0FBZ0IsRUFBRSxVQUEwQztRQUN0RSxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxZQUFZLENBQUMsQ0FBQTtRQUNoRSxJQUFJLENBQUMsVUFBVSxHQUFHLFVBQVUsSUFBSSxFQUFFLENBQUE7SUFDcEMsQ0FBQztJQUVELEtBQUssQ0FBQyxhQUFhLENBQUMsU0FBb0I7UUFDdEMsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFBO1FBQ3hELE1BQU0sR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUE7UUFDbEQsTUFBTSxHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxTQUFTLENBQUMsRUFBRSxhQUFhLENBQUMsQ0FBQTtJQUN4RSxDQUFDO0lBRUQsS0FBSyxDQUFDLGFBQWE7UUFDakIsTUFBTSxhQUFhLEdBQUcsTUFBTSxHQUFHLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxTQUFTLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQTtRQUNwRixNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLGFBQWEsQ0FBQyxDQUFBO1FBQzNDLE9BQU8sU0FBUyxDQUFBO0lBQ2xCLENBQUM7SUFFRCxLQUFLLENBQUMsZ0JBQWdCLENBQUMsWUFBMkI7UUFDaEQsNkNBQTZDO1FBQzdDLDZDQUE2QztRQUM3QyxNQUFNLEdBQUcsR0FBRyxJQUFJLEdBQUcsRUFBeUIsQ0FBQTtRQUU1QyxNQUFNLEdBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFBO1FBRWxELE1BQU0sZUFBZSxHQUFHLEtBQUssRUFBRSxXQUF3QixFQUFFLEVBQUU7WUFDekQsTUFBTSxRQUFRLEdBQWE7Z0JBQ3pCLE1BQU0sRUFBRSxXQUFXLENBQUMsTUFBTTtnQkFDMUIsR0FBRyxFQUFFLFdBQVcsQ0FBQyxHQUFHO2dCQUNwQixNQUFNLEVBQUUsV0FBVyxDQUFDLE1BQU07Z0JBQzFCLFVBQVUsRUFBRSxXQUFXLENBQUMsVUFBVTtnQkFDbEMsWUFBWSxFQUFFLFdBQVcsQ0FBQyxZQUFZO2dCQUN0QyxVQUFVLEVBQUUsV0FBVyxDQUFDLFVBQVU7YUFDbkMsQ0FBQTtZQUVELFVBQVU7WUFDVixJQUFJLFdBQVcsQ0FBQyxVQUFVLEVBQUU7Z0JBQzFCLElBQUksV0FBVyxDQUFDLFVBQVUsQ0FBQyxjQUFjLENBQUMsRUFBRTtvQkFDMUMsTUFBTSxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsR0FBRyxzQkFBc0IsQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUE7b0JBQ3hGLElBQUksSUFBSTt3QkFBRSxRQUFRLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQTtvQkFDekMsSUFBSSxPQUFPO3dCQUFFLFFBQVEsQ0FBQyxrQkFBa0IsR0FBRyxPQUFPLENBQUE7aUJBQ25EO2dCQUVELElBQUksV0FBVyxDQUFDLFVBQVUsQ0FBQyxrQkFBa0IsQ0FBQyxFQUFFO29CQUM5QyxNQUFNLGVBQWUsR0FBRyxXQUFXLENBQUMsVUFBVSxDQUFDLGtCQUFrQixDQUF3QixDQUFBO29CQUN6RixJQUFJLGVBQWU7d0JBQUUsUUFBUSxDQUFDLGVBQWUsR0FBRyxlQUFlLENBQUE7aUJBQ2hFO2FBQ0Y7WUFFRCxPQUFPO1lBQ1AsSUFBSSxXQUFXLENBQUMsVUFBVSxJQUFJLFdBQVcsQ0FBQyxPQUFPLEVBQUU7Z0JBQ2pELE1BQU0sV0FBVyxHQUFHLFdBQVcsQ0FBQyxPQUFPLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQTtnQkFDbEQsTUFBTSxPQUFPLEdBQUcsV0FBVyxDQUFDLFVBQVUsR0FBRyxJQUFJLENBQUE7Z0JBQzdDLE1BQU0sSUFBSSxHQUFHLElBQUksR0FBRyxJQUFJLENBQUE7Z0JBQ3hCLFFBQVEsQ0FBQyxJQUFJLEdBQUcsV0FBVyxHQUFHLE9BQU8sR0FBRyxJQUFJLENBQUE7YUFDN0M7WUFFRCxVQUFVO1lBQ1YsSUFBSSxXQUFXLENBQUMsT0FBTyxFQUFFO2dCQUN2QixNQUFNLEtBQUssR0FHUCxFQUFFLENBQUE7Z0JBRU4sTUFBTSxlQUFlLEdBQUcsc0JBQXNCLENBQUMsUUFBUSxDQUFDLE1BQU0sRUFBRSxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUE7Z0JBQzdFLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxlQUFlLENBQUMsQ0FBQTtnQkFFekQsbUJBQW1CO2dCQUNuQixLQUFLLENBQUMsT0FBTyxHQUFHLFFBQVEsQ0FBQyxlQUFlO29CQUN0QyxDQUFDLENBQUMsTUFBTSxVQUFVLENBQUMsUUFBUSxDQUFDLGVBQWUsRUFBRSxXQUFXLENBQUMsT0FBTyxDQUFDO29CQUNqRSxDQUFDLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQTtnQkFFdkIsd0NBQXdDO2dCQUN4QyxLQUFLLENBQUMsUUFBUSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUE7Z0JBQzlCLElBQUksTUFBTSxDQUFDLFFBQVEsQ0FBQyxlQUFlLENBQUMsRUFBRTtvQkFDcEMsSUFBSTt3QkFDRixLQUFLLENBQUMsUUFBUSxHQUFHLE1BQU0sbUJBQW1CLENBQ3hDLEtBQUssQ0FBQyxPQUFPLEVBQ2IsUUFBUSxDQUFDLGVBQWUsRUFDeEIsUUFBUSxDQUFDLGtCQUFrQixDQUM1QixDQUFBO3dCQUNELFFBQVEsQ0FBQyxrQkFBa0IsR0FBRyxPQUFPLENBQUE7cUJBQ3RDO29CQUFDLE9BQU8sR0FBRyxFQUFFO3dCQUNaLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxFQUFFLEdBQUcsRUFBRSxRQUFRLEVBQUUsRUFBRSxxQkFBcUIsV0FBVyxDQUFDLEdBQUcsS0FBSyxHQUFHLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQTtxQkFDekc7aUJBQ0Y7Z0JBRUQsTUFBTSxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQTtnQkFDNUQsTUFBTSxHQUFHLENBQUMsU0FBUyxDQUFDLFFBQVEsRUFBRSxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUE7Z0JBRTdDLFFBQVEsQ0FBQyxlQUFlLEdBQUcsZUFBZSxDQUFBO2FBQzNDO1lBRUQsR0FBRyxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsUUFBUSxDQUFDLENBQUE7UUFDaEMsQ0FBQyxDQUFBO1FBRUQsTUFBTSxvQkFBb0IsR0FBRyxLQUFLLEVBQUUsV0FBd0IsRUFBRSxFQUFFO1lBQzlELElBQUk7Z0JBQ0YsTUFBTSxlQUFlLENBQUMsV0FBVyxDQUFDLENBQUE7YUFDbkM7WUFBQyxPQUFPLEdBQUcsRUFBRTtnQkFDWixJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQzNCLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxXQUFXLENBQUMsTUFBTSxFQUFFLEdBQUcsRUFBRSxXQUFXLENBQUMsR0FBRyxFQUFFLEVBQ3pELDhCQUE4QixXQUFXLENBQUMsR0FBRyxLQUFLLEdBQUcsQ0FBQyxPQUFPLEVBQUUsQ0FDaEUsQ0FBQTthQUNGO1FBQ0gsQ0FBQyxDQUFBO1FBRUQsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsb0JBQW9CLENBQUMsQ0FBQyxDQUFBO1FBRXpELCtDQUErQztRQUMvQyxNQUFNLFNBQVMsR0FBZSxZQUFZLENBQUMsTUFBTSxDQUFhLENBQUMsU0FBUyxFQUFFLFdBQVcsRUFBRSxFQUFFO1lBQ3ZGLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUE7WUFDckMsSUFBSSxRQUFRO2dCQUFFLFNBQVMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUE7WUFDdEMsT0FBTyxTQUFTLENBQUE7UUFDbEIsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBRU4sT0FBTyxTQUFTLENBQUE7SUFDbEIsQ0FBQztJQUVELEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFxQjtRQUMxQyxNQUFNLEdBQUcsR0FBRyxJQUFJLEdBQUcsRUFBeUIsQ0FBQTtRQUU1QyxNQUFNLGVBQWUsR0FBRyxLQUFLLEVBQUUsUUFBa0IsRUFBRSxFQUFFO1lBQ25ELE1BQU0sV0FBVyxHQUFnQjtnQkFDL0IsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNO2dCQUN2QixHQUFHLEVBQUUsUUFBUSxDQUFDLEdBQUc7Z0JBQ2pCLE1BQU0sRUFBRSxRQUFRLENBQUMsTUFBTTtnQkFDdkIsVUFBVSxFQUFFLFFBQVEsQ0FBQyxVQUFVO2dCQUMvQixZQUFZLEVBQUUsUUFBUSxDQUFDLFlBQVk7Z0JBQ25DLFVBQVUsRUFBRSxFQUFFLEdBQUcsQ0FBQyxRQUFRLENBQUMsVUFBVSxJQUFJLEVBQUUsQ0FBQyxFQUFFO2FBQy9DLENBQUE7WUFFRCxVQUFVO1lBQ1YsSUFBSSxPQUEyQixDQUFBO1lBRS9CLElBQUksUUFBUSxDQUFDLFdBQVcsRUFBRTtnQkFDeEIsT0FBTyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxDQUFBO2FBQzVDO2lCQUFNLElBQUksUUFBUSxDQUFDLGFBQWEsRUFBRTtnQkFDakMsT0FBTyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLGFBQWEsRUFBRSxRQUFRLENBQUMsQ0FBQTthQUN4RDtpQkFBTSxJQUFJLFFBQVEsQ0FBQyxlQUFlLEVBQUU7Z0JBQ25DLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxRQUFRLENBQUMsZUFBZSxDQUFDLENBQUE7Z0JBQ2xFLElBQUksRUFBRSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsRUFBRTtvQkFDM0IsT0FBTyxHQUFHLE1BQU0sR0FBRyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQTtpQkFDdkM7YUFDRjtZQUVELElBQUksT0FBTyxFQUFFO2dCQUNYLFdBQVc7Z0JBQ1gsSUFBSSxRQUFRLENBQUMsZUFBZSxFQUFFO29CQUM1QixXQUFXLENBQUMsT0FBTyxHQUFHLE1BQU0sUUFBUSxDQUFDLFFBQVEsQ0FBQyxlQUFlLEVBQUUsT0FBTyxDQUFDLENBQUE7b0JBQ3ZFLFdBQVcsQ0FBQyxVQUFVLENBQUMsa0JBQWtCLENBQUMsR0FBRyxRQUFRLENBQUMsZUFBZSxDQUFBO2lCQUN0RTtxQkFBTTtvQkFDTCxXQUFXLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQTtvQkFDN0IsT0FBTyxXQUFXLENBQUMsVUFBVSxDQUFDLGtCQUFrQixDQUFDLENBQUE7aUJBQ2xEO2dCQUVELFNBQVM7Z0JBQ1QsV0FBVyxDQUFDLFVBQVUsQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLEdBQUcsV0FBVyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQTtnQkFFMUUsV0FBVztnQkFDWCxNQUFNLFVBQVUsR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxJQUFJLEdBQUcsSUFBSSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtnQkFDL0UsV0FBVyxDQUFDLFVBQVUsR0FBRyxVQUFVLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEdBQUcsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7YUFDdEU7aUJBQU07Z0JBQ0wsV0FBVyxDQUFDLFVBQVUsQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLEdBQUcsQ0FBQTtnQkFDOUMsV0FBVyxDQUFDLFVBQVUsR0FBRyxDQUFDLENBQUE7YUFDM0I7WUFFRCxlQUFlO1lBQ2YsSUFBSSxRQUFRLENBQUMsZUFBZSxFQUFFO2dCQUM1QixXQUFXLENBQUMsVUFBVSxDQUFDLGNBQWMsQ0FBQyxHQUFHLDBCQUEwQixDQUNqRSxRQUFRLENBQUMsZUFBZSxFQUN4QixRQUFRLENBQUMsa0JBQWtCLENBQzVCLENBQUE7YUFDRjtZQUVELEdBQUcsQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLFdBQVcsQ0FBQyxDQUFBO1FBQ2hDLENBQUMsQ0FBQTtRQUVELE1BQU0sb0JBQW9CLEdBQUcsS0FBSyxFQUFFLFFBQWtCLEVBQUUsRUFBRTtZQUN4RCxJQUFJO2dCQUNGLE1BQU0sZUFBZSxDQUFDLFFBQVEsQ0FBQyxDQUFBO2FBQ2hDO1lBQUMsT0FBTyxHQUFHLEVBQUU7Z0JBQ1osSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxFQUFFLDhCQUE4QixRQUFRLENBQUMsR0FBRyxLQUFLLEdBQUcsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFBO2FBQy9HO1FBQ0gsQ0FBQyxDQUFBO1FBRUQsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsb0JBQW9CLENBQUMsQ0FBQyxDQUFBO1FBRXRELE1BQU0sWUFBWSxHQUFrQixTQUFTLENBQUMsTUFBTSxDQUFnQixDQUFDLFlBQVksRUFBRSxRQUFRLEVBQUUsRUFBRTtZQUM3RixNQUFNLFdBQVcsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFBO1lBQ3JDLElBQUksV0FBVztnQkFBRSxZQUFZLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBQy9DLE9BQU8sWUFBWSxDQUFBO1FBQ3JCLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQTtRQUVOLE9BQU8sWUFBWSxDQUFBO0lBQ3JCLENBQUM7Q0FDRiJ9
@@ -0,0 +1,12 @@
1
+ import { DependencyInterface, DeviceType } from './types.js';
2
+ export interface ExecLighthouseInput {
3
+ url: string;
4
+ proxyPort: number;
5
+ deviceType?: DeviceType;
6
+ cpuMultiplier?: string;
7
+ noThrottling?: boolean;
8
+ view?: boolean;
9
+ artifactsDir?: string;
10
+ headless: boolean;
11
+ }
12
+ export declare function execLighthouse(opts: ExecLighthouseInput, dependency: Pick<DependencyInterface, 'mkdirp' | 'executeLighthouse'>): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import Path from 'path';
2
+ export async function execLighthouse(opts, dependency) {
3
+ const artifactsDir = opts.artifactsDir || './artifacts';
4
+ await dependency.mkdirp(artifactsDir);
5
+ const deviceType = opts.deviceType || 'mobile';
6
+ const outputPath = Path.join(artifactsDir, 'lighthouse');
7
+ const args = [
8
+ opts.url,
9
+ '--save-assets',
10
+ '--output=html,json',
11
+ `--output-path=${outputPath}`,
12
+ '--only-categories=performance',
13
+ `--form-factor=${deviceType}`,
14
+ ];
15
+ if (opts.noThrottling) {
16
+ args.push('--throttling.rttMs=0', '--throttling.throughputKbps=0', '--throttling.downloadThroughputKbps=0', '--throttling.uploadThroughputKbps=0', '--throttling.cpuSlowdownMultiplier=1');
17
+ }
18
+ else if (opts.cpuMultiplier)
19
+ args.push(`--throttling.cpuSlowdownMultiplier=${opts.cpuMultiplier}`);
20
+ const chromeFlags = ['--ignore-certificate-errors', `--proxy-server=http://localhost:${opts.proxyPort}`];
21
+ if (opts.headless)
22
+ chromeFlags.push('--headless');
23
+ args.push(`--chrome-flags="${chromeFlags.join(' ')}"`);
24
+ if (opts.view)
25
+ args.push('--view');
26
+ await dependency.executeLighthouse(args);
27
+ }
28
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGlnaHRob3VzZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9saWdodGhvdXNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQTtBQWV2QixNQUFNLENBQUMsS0FBSyxVQUFVLGNBQWMsQ0FDbEMsSUFBeUIsRUFDekIsVUFBcUU7SUFFckUsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLFlBQVksSUFBSSxhQUFhLENBQUE7SUFDdkQsTUFBTSxVQUFVLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFBO0lBRXJDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxVQUFVLElBQUksUUFBUSxDQUFBO0lBQzlDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFLFlBQVksQ0FBQyxDQUFBO0lBQ3hELE1BQU0sSUFBSSxHQUFhO1FBQ3JCLElBQUksQ0FBQyxHQUFHO1FBQ1IsZUFBZTtRQUNmLG9CQUFvQjtRQUNwQixpQkFBaUIsVUFBVSxFQUFFO1FBQzdCLCtCQUErQjtRQUMvQixpQkFBaUIsVUFBVSxFQUFFO0tBQzlCLENBQUE7SUFFRCxJQUFJLElBQUksQ0FBQyxZQUFZLEVBQUU7UUFDckIsSUFBSSxDQUFDLElBQUksQ0FDUCxzQkFBc0IsRUFDdEIsK0JBQStCLEVBQy9CLHVDQUF1QyxFQUN2QyxxQ0FBcUMsRUFDckMsc0NBQXNDLENBQ3ZDLENBQUE7S0FDRjtTQUFNLElBQUksSUFBSSxDQUFDLGFBQWE7UUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLHNDQUFzQyxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUMsQ0FBQTtJQUVwRyxNQUFNLFdBQVcsR0FBYSxDQUFDLDZCQUE2QixFQUFFLG1DQUFtQyxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUMsQ0FBQTtJQUNsSCxJQUFJLElBQUksQ0FBQyxRQUFRO1FBQUUsV0FBVyxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQTtJQUNqRCxJQUFJLENBQUMsSUFBSSxDQUFDLG1CQUFtQixXQUFXLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUV0RCxJQUFJLElBQUksQ0FBQyxJQUFJO1FBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQTtJQUVsQyxNQUFNLFVBQVUsQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsQ0FBQTtBQUMxQyxDQUFDIn0=
@@ -0,0 +1,19 @@
1
+ import { DependencyInterface, DeviceType } from './types.js';
2
+ export interface ExecLoadshowInput {
3
+ url: string;
4
+ proxyPort: number;
5
+ deviceType?: DeviceType;
6
+ noThrottling?: boolean;
7
+ syncLighthouseSpec?: boolean;
8
+ artifactsDir?: string;
9
+ }
10
+ export interface ExecLoadshowSpec {
11
+ viewportWidth?: number;
12
+ columns?: number;
13
+ cpuThrottling?: number;
14
+ networkLatencyMs?: number;
15
+ networkThroughputMbps?: number;
16
+ userAgent?: string;
17
+ proxyPort?: number;
18
+ }
19
+ export declare function execLoadshow(input: ExecLoadshowInput, dependency: Pick<DependencyInterface, 'mkdirp' | 'executeLoadshow'>): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import Path from 'path';
2
+ import { defaultConfig, desktopConfig } from 'lighthouse';
3
+ function execSpecToCommandArgs(spec) {
4
+ const args = [];
5
+ // layout
6
+ if (spec.columns !== undefined)
7
+ args.push('-u', `layout.columns=${spec.columns}`);
8
+ // recording
9
+ if (spec.viewportWidth !== undefined)
10
+ args.push('-u', `recording.viewportWidth=${spec.viewportWidth}`);
11
+ if (spec.cpuThrottling !== undefined)
12
+ args.push('-u', `recording.cpuThrottling=${spec.cpuThrottling}`);
13
+ if (spec.networkLatencyMs !== undefined)
14
+ args.push('-u', `recording.network.latencyMs=${spec.networkLatencyMs}`);
15
+ if (spec.networkThroughputMbps !== undefined) {
16
+ args.push('-u', `recording.network.uploadThroughputMbps=${spec.networkThroughputMbps}`);
17
+ args.push('-u', `recording.network.downloadThroughputMbps=${spec.networkThroughputMbps}`);
18
+ }
19
+ if (spec.userAgent !== undefined)
20
+ args.push('-u', `recording.headers.User-Agent=${spec.userAgent}`);
21
+ // recording.puppeteer
22
+ const chromeArgs = ['--ignore-certificate-errors'];
23
+ if (spec.proxyPort !== undefined) {
24
+ chromeArgs.push(`--proxy-server=http://localhost:${spec.proxyPort}`);
25
+ }
26
+ args.push('-u', 'recording.puppeteer.args=' + chromeArgs.join(','));
27
+ return args;
28
+ }
29
+ export async function execLoadshow(input, dependency) {
30
+ const artifactsDir = input.artifactsDir || './artifacts';
31
+ const loadshowDir = Path.join(artifactsDir, 'loadshow');
32
+ const outputPath = Path.join(artifactsDir, 'loadshow.mp4');
33
+ await dependency.mkdirp(loadshowDir);
34
+ // By form factor
35
+ const lighthouseByDevice = input.deviceType === 'desktop' ? desktopConfig : defaultConfig;
36
+ const customByDevice = input.deviceType === 'desktop' ? { columns: 2 } : { columns: 3 };
37
+ // Basic spec
38
+ const userAgent = lighthouseByDevice.settings?.emulatedUserAgent;
39
+ const spec = {
40
+ proxyPort: input.proxyPort,
41
+ columns: customByDevice.columns,
42
+ viewportWidth: lighthouseByDevice.settings?.screenEmulation?.width,
43
+ cpuThrottling: lighthouseByDevice.settings?.throttling?.cpuSlowdownMultiplier,
44
+ userAgent: typeof userAgent === 'string' ? userAgent : undefined,
45
+ };
46
+ // Sync network conditions with Lighthouse
47
+ if (input.syncLighthouseSpec) {
48
+ if (lighthouseByDevice.settings?.throttling?.rttMs)
49
+ spec.networkLatencyMs = lighthouseByDevice.settings?.throttling?.rttMs;
50
+ if (lighthouseByDevice.settings?.throttling?.throughputKbps)
51
+ spec.networkThroughputMbps = lighthouseByDevice.settings?.throttling?.throughputKbps / 1024;
52
+ }
53
+ // No throttling
54
+ if (input.noThrottling) {
55
+ spec.networkLatencyMs = 0;
56
+ spec.networkThroughputMbps = 999999;
57
+ }
58
+ const args = [];
59
+ args.push('record');
60
+ args.push('-a', loadshowDir);
61
+ args.push(...execSpecToCommandArgs(spec));
62
+ args.push(input.url, outputPath);
63
+ await dependency.executeLoadshow(args);
64
+ }
65
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9hZHNob3cuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvbG9hZHNob3cudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxJQUFJLE1BQU0sTUFBTSxDQUFBO0FBRXZCLE9BQU8sRUFBRSxhQUFhLEVBQUUsYUFBYSxFQUFFLE1BQU0sWUFBWSxDQUFBO0FBdUJ6RCxTQUFTLHFCQUFxQixDQUFDLElBQXNCO0lBQ25ELE1BQU0sSUFBSSxHQUFhLEVBQUUsQ0FBQTtJQUV6QixTQUFTO0lBQ1QsSUFBSSxJQUFJLENBQUMsT0FBTyxLQUFLLFNBQVM7UUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxrQkFBa0IsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUE7SUFFakYsWUFBWTtJQUNaLElBQUksSUFBSSxDQUFDLGFBQWEsS0FBSyxTQUFTO1FBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsMkJBQTJCLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQyxDQUFBO0lBQ3RHLElBQUksSUFBSSxDQUFDLGFBQWEsS0FBSyxTQUFTO1FBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsMkJBQTJCLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQyxDQUFBO0lBQ3RHLElBQUksSUFBSSxDQUFDLGdCQUFnQixLQUFLLFNBQVM7UUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSwrQkFBK0IsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUMsQ0FBQTtJQUNoSCxJQUFJLElBQUksQ0FBQyxxQkFBcUIsS0FBSyxTQUFTLEVBQUU7UUFDNUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsMENBQTBDLElBQUksQ0FBQyxxQkFBcUIsRUFBRSxDQUFDLENBQUE7UUFDdkYsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsNENBQTRDLElBQUksQ0FBQyxxQkFBcUIsRUFBRSxDQUFDLENBQUE7S0FD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
@@ -0,0 +1,22 @@
1
+ /// <reference types="node" />
2
+ import { HttpHeaders } from './http.js';
3
+ import { Inventory } from './inventory.js';
4
+ import { Proxy, ProxyDependency, ProxyOptions } from './proxy.js';
5
+ export interface PlaybackTransaction {
6
+ method: string;
7
+ url: string;
8
+ ttfbMs: number;
9
+ statusCode?: number;
10
+ err?: Error;
11
+ rawHeaders?: HttpHeaders;
12
+ contentChunks: Buffer[];
13
+ contentLength: number;
14
+ durationMs: number;
15
+ }
16
+ export declare class PlaybackProxy extends Proxy {
17
+ transactionsMap: Map<string, Map<string, PlaybackTransaction>>;
18
+ loadTransactions(inventory: Inventory): Promise<void>;
19
+ setup(): Promise<void>;
20
+ shutdown(): Promise<void>;
21
+ }
22
+ export declare function withPlaybackProxy(options: ProxyOptions, dependency: ProxyDependency, cb: (proxy: PlaybackProxy) => Promise<void>): Promise<void>;
@@ -0,0 +1,104 @@
1
+ import { Proxy } from './proxy.js';
2
+ const ChunkSize = 1024 * 16;
3
+ export class PlaybackProxy extends Proxy {
4
+ transactionsMap = new Map();
5
+ async loadTransactions(inventory) {
6
+ const transactions = await this.inventoryRepository.loadTransactions(inventory.resources);
7
+ for (const transaction of transactions) {
8
+ const playbackTransaction = {
9
+ method: transaction.method,
10
+ url: transaction.url,
11
+ ttfbMs: transaction.ttfbMs,
12
+ statusCode: transaction.statusCode,
13
+ err: transaction.errorMessage ? new Error(transaction.errorMessage) : undefined,
14
+ rawHeaders: transaction.rawHeaders || {},
15
+ contentChunks: [],
16
+ contentLength: 0,
17
+ durationMs: transaction.durationMs || 0,
18
+ };
19
+ if (transaction.content) {
20
+ const maxChunks = 10;
21
+ const minInterval = 10;
22
+ const chunks = Math.min(maxChunks, Math.floor(playbackTransaction.durationMs / minInterval));
23
+ playbackTransaction.contentChunks = [];
24
+ const chunkSize = Math.max(ChunkSize, Math.ceil(transaction.content.length / chunks));
25
+ for (let i = 0; i <= transaction.content.length; i += chunkSize) {
26
+ playbackTransaction.contentChunks.push(transaction.content.subarray(i, i + chunkSize));
27
+ }
28
+ }
29
+ if (!this.transactionsMap.has(transaction.method)) {
30
+ this.transactionsMap.set(transaction.method, new Map());
31
+ }
32
+ this.transactionsMap.get(transaction.method).set(transaction.url, playbackTransaction);
33
+ }
34
+ }
35
+ async setup() {
36
+ const inventory = await this.inventoryRepository.loadInventory();
37
+ await this.loadTransactions(inventory);
38
+ if (inventory.entryUrl)
39
+ this.entryUrl = inventory.entryUrl;
40
+ let requestNumber = 1;
41
+ this.proxy.onRequest((ctx, onRequestComplete) => {
42
+ const number = requestNumber++;
43
+ const identifier = Proxy.contextRequest(ctx);
44
+ const transaction = this.transactionsMap.get(identifier.method)?.get(identifier.url);
45
+ if (!transaction) {
46
+ this.dependency.logger?.warn({ number, identifier }, `Request #${number} ${identifier.url} (${identifier.method}) not found in inventory`);
47
+ return;
48
+ }
49
+ const contentStream = this.createThrottlingTransform() || ctx.proxyToClientResponse;
50
+ if (contentStream !== ctx.proxyToClientResponse) {
51
+ contentStream.pipe(ctx.proxyToClientResponse);
52
+ }
53
+ this.dependency.logger?.debug({ number, identifier }, `Request #${number} ${transaction.url} started`);
54
+ ctx.onError((_, err) => {
55
+ this.dependency.logger?.warn({ number, identifier, err }, `Request #${number} ${transaction.url} failed: ${err.message}`);
56
+ });
57
+ setTimeout(() => {
58
+ // Error
59
+ if (transaction.err) {
60
+ return onRequestComplete(transaction.err);
61
+ }
62
+ // Status code
63
+ ctx.proxyToClientResponse.statusCode = transaction.statusCode || 500;
64
+ // Headers
65
+ if (transaction.rawHeaders) {
66
+ for (const [key, value] of Object.entries(transaction.rawHeaders)) {
67
+ if (ctx.proxyToClientResponse.headersSent)
68
+ break;
69
+ ctx.proxyToClientResponse.setHeader(key, value);
70
+ }
71
+ }
72
+ // Empty content body
73
+ if (!transaction.contentChunks || transaction.contentChunks.length === 0) {
74
+ contentStream.end();
75
+ return;
76
+ }
77
+ // Content body
78
+ const chunks = [...transaction.contentChunks];
79
+ const intervalMs = transaction.durationMs / transaction.contentChunks.length;
80
+ const interval = setInterval(() => {
81
+ const chunk = chunks.shift();
82
+ if (chunk) {
83
+ contentStream.write(chunk);
84
+ }
85
+ if (chunks.length === 0) {
86
+ clearInterval(interval);
87
+ contentStream.end();
88
+ this.dependency.logger?.debug({ number, identifier }, `Request #${number} ${transaction.url} completed`);
89
+ }
90
+ }, intervalMs);
91
+ }, transaction.ttfbMs);
92
+ });
93
+ }
94
+ async shutdown() {
95
+ // nothing to do
96
+ }
97
+ }
98
+ export async function withPlaybackProxy(options, dependency, cb) {
99
+ const proxy = new PlaybackProxy(options, dependency);
100
+ await proxy.start();
101
+ await cb(proxy);
102
+ await proxy.stop();
103
+ }
104
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGxheWJhY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvcGxheWJhY2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUFFLEtBQUssRUFBaUMsTUFBTSxZQUFZLENBQUE7QUFFakUsTUFBTSxTQUFTLEdBQUcsSUFBSSxHQUFHLEVBQUUsQ0FBQTtBQWMzQixNQUFNLE9BQU8sYUFBYyxTQUFRLEtBQUs7SUFDdEMsZUFBZSxHQUFrRCxJQUFJLEdBQUcsRUFBRSxDQUFBO0lBRTFFLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFvQjtRQUN6QyxNQUFNLFlBQVksR0FBRyxNQUFNLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLENBQUE7UUFFekYsS0FBSyxNQUFNLFdBQVcsSUFBSSxZQUFZLEVBQUU7WUFDdEMsTUFBTSxtQkFBbUIsR0FBd0I7Z0JBQy9DLE1BQU0sRUFBRSxXQUFXLENBQUMsTUFBTTtnQkFDMUIsR0FBRyxFQUFFLFdBQVcsQ0FBQyxHQUFHO2dCQUNwQixNQUFNLEVBQUUsV0FBVyxDQUFDLE1BQU07Z0JBQzFCLFVBQVUsRUFBRSxXQUFXLENBQUMsVUFBVTtnQkFDbEMsR0FBRyxFQUFFLFdBQVcsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxDQUFDLFdBQVcsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUztnQkFDL0UsVUFBVSxFQUFFLFdBQVcsQ0FBQyxVQUFVLElBQUksRUFBRTtnQkFDeEMsYUFBYSxFQUFFLEVBQUU7Z0JBQ2pCLGFBQWEsRUFBRSxDQUFDO2dCQUNoQixVQUFVLEVBQUUsV0FBVyxDQUFDLFVBQVUsSUFBSSxDQUFDO2FBQ3hDLENBQUE7WUFFRCxJQUFJLFdBQVcsQ0FBQyxPQUFPLEVBQUU7Z0JBQ3ZCLE1BQU0sU0FBUyxHQUFHLEVBQUUsQ0FBQTtnQkFDcEIsTUFBTSxXQUFXLEdBQUcsRUFBRSxDQUFBO2dC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
@@ -0,0 +1,35 @@
1
+ import HttpMitmProxy from 'http-mitm-proxy';
2
+ import { InventoryRepository } from './inventory.js';
3
+ import { Throttle, ThrottlingTransform } from './throttling.js';
4
+ import { DependencyInterface, DeviceType } from './types.js';
5
+ export interface ProxyOptions extends HttpMitmProxy.IProxyOptions {
6
+ inventoryRepository?: InventoryRepository;
7
+ throttle?: Throttle;
8
+ throttlingRetryIntervalMs?: number;
9
+ entryUrl?: string;
10
+ deviceType?: DeviceType;
11
+ }
12
+ export type ProxyDependency = Pick<DependencyInterface, 'logger'>;
13
+ export declare abstract class Proxy {
14
+ proxyOptions: HttpMitmProxy.IProxyOptions;
15
+ proxy: HttpMitmProxy.IProxy;
16
+ inventoryRepository: InventoryRepository;
17
+ throttle?: Throttle;
18
+ throttlingRetryIntervalMs: number;
19
+ entryUrl?: string;
20
+ deviceType?: DeviceType;
21
+ dependency: ProxyDependency;
22
+ constructor(options?: ProxyOptions, dependency?: ProxyDependency);
23
+ static contextRequest(ctx: HttpMitmProxy.IContext): {
24
+ method: string;
25
+ url: string;
26
+ };
27
+ createThrottlingTransform(): ThrottlingTransform | void;
28
+ abstract setup(): Promise<void>;
29
+ abstract shutdown(): Promise<void>;
30
+ start(): Promise<void>;
31
+ get port(): number;
32
+ get inventoryDirPath(): string;
33
+ stop(): Promise<void>;
34
+ }
35
+ export declare function withProxy<ProxyType extends Proxy>(proxy: ProxyType, fn: (proxy: ProxyType) => Promise<void>): Promise<void>;
package/build/proxy.js ADDED
@@ -0,0 +1,96 @@
1
+ import Crypto from 'crypto';
2
+ import Fsp from 'fs/promises';
3
+ import Http from 'http';
4
+ import Https from 'https';
5
+ import Os from 'os';
6
+ import Path from 'path';
7
+ import GetPort from 'get-port';
8
+ import HttpMitmProxy from 'http-mitm-proxy';
9
+ import { InventoryRepository } from './inventory.js';
10
+ import { ThrottlingTransform } from './throttling.js';
11
+ export class Proxy {
12
+ proxyOptions;
13
+ proxy;
14
+ inventoryRepository;
15
+ throttle;
16
+ throttlingRetryIntervalMs;
17
+ entryUrl;
18
+ deviceType;
19
+ dependency;
20
+ constructor(options, dependency) {
21
+ options ||= {};
22
+ this.dependency = dependency || {};
23
+ // Proxy
24
+ this.proxyOptions = options;
25
+ this.proxy = HttpMitmProxy();
26
+ // Inventory repository
27
+ this.inventoryRepository = options.inventoryRepository ?? new InventoryRepository(undefined, this.dependency);
28
+ // Throttle
29
+ if (options.throttle)
30
+ this.throttle = options.throttle;
31
+ this.throttlingRetryIntervalMs = options.throttlingRetryIntervalMs || 10;
32
+ // Entry URL
33
+ this.entryUrl = options.entryUrl;
34
+ // Device type
35
+ this.deviceType = options.deviceType;
36
+ }
37
+ static contextRequest(ctx) {
38
+ if (!ctx.clientToProxyRequest.headers.host)
39
+ throw new Error('ctx.clientToProxyRequest.headers.host is empty');
40
+ const url = [
41
+ ctx.isSSL ? 'https://' : 'http://',
42
+ ctx.clientToProxyRequest.headers.host,
43
+ ctx.clientToProxyRequest.url,
44
+ ].join('');
45
+ const method = (ctx.clientToProxyRequest.method || 'get').toLowerCase();
46
+ return {
47
+ method,
48
+ url,
49
+ };
50
+ }
51
+ createThrottlingTransform() {
52
+ if (this.throttle) {
53
+ return new ThrottlingTransform(this.throttle, this.throttlingRetryIntervalMs);
54
+ }
55
+ }
56
+ async start() {
57
+ if (this.throttle)
58
+ this.throttle.start();
59
+ const sslCaDir = this.proxyOptions.sslCaDir || process.env.SSL_CA_DIR || Path.join(Os.homedir(), '.pagespeed-quest/ca');
60
+ const port = Number(this.proxyOptions.port || process.env.PORT || (await GetPort()));
61
+ const options = {
62
+ port,
63
+ sslCaDir,
64
+ httpAgent: new Http.Agent({
65
+ keepAlive: true,
66
+ }),
67
+ httpsAgent: new Https.Agent({
68
+ keepAlive: true,
69
+ secureOptions: Crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
70
+ }),
71
+ };
72
+ await this.setup();
73
+ await Fsp.mkdir(options.sslCaDir, { recursive: true });
74
+ await new Promise((resolve, reject) => this.proxy.listen(options, (error) => (error ? reject(error) : resolve())));
75
+ this.dependency.logger?.info(`Proxy started to listening on port ${this.port}`);
76
+ }
77
+ get port() {
78
+ return this.proxy.httpPort;
79
+ }
80
+ get inventoryDirPath() {
81
+ return this.inventoryRepository.dirPath;
82
+ }
83
+ async stop() {
84
+ this.proxy.close();
85
+ await this.shutdown();
86
+ if (this.throttle)
87
+ this.throttle.stop();
88
+ this.dependency.logger?.info(`Proxy stopped`);
89
+ }
90
+ }
91
+ export async function withProxy(proxy, fn) {
92
+ await proxy.start();
93
+ await fn(proxy);
94
+ await proxy.stop();
95
+ }
96
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvcHJveHkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxNQUFNLE1BQU0sUUFBUSxDQUFBO0FBQzNCLE9BQU8sR0FBRyxNQUFNLGFBQWEsQ0FBQTtBQUM3QixPQUFPLElBQUksTUFBTSxNQUFNLENBQUE7QUFDdkIsT0FBTyxLQUFLLE1BQU0sT0FBTyxDQUFBO0FBQ3pCLE9BQU8sRUFBRSxNQUFNLElBQUksQ0FBQTtBQUNuQixPQUFPLElBQUksTUFBTSxNQUFNLENBQUE7QUFFdkIsT0FBTyxPQUFPLE1BQU0sVUFBVSxDQUFBO0FBQzlCLE9BQU8sYUFBYSxNQUFNLGlCQUFpQixDQUFBO0FBRTNDLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGdCQUFnQixDQUFBO0FBQ3BELE9BQU8sRUFBWSxtQkFBbUIsRUFBRSxNQUFNLGlCQUFpQixDQUFBO0FBYS9ELE1BQU0sT0FBZ0IsS0FBSztJQUN6QixZQUFZLENBQThCO0lBQzFDLEtBQUssQ0FBdUI7SUFDNUIsbUJBQW1CLENBQXNCO0lBQ3pDLFFBQVEsQ0FBVztJQUNuQix5QkFBeUIsQ0FBUztJQUNsQyxRQUFRLENBQVM7SUFDakIsVUFBVSxDQUFhO0lBQ3ZCLFVBQVUsQ0FBaUI7SUFFM0IsWUFBWSxPQUFzQixFQUFFLFVBQTRCO1FBQzlELE9BQU8sS0FBSyxFQUFFLENBQUE7UUFDZCxJQUFJLENBQUMsVUFBVSxHQUFHLFVBQVUsSUFBSSxFQUFFLENBQUE7UUFFbEMsUUFBUTtRQUNSLElBQUksQ0FBQyxZQUFZLEdBQUcsT0FBTyxDQUFBO1FBQzNCLElBQUksQ0FBQyxLQUFLLEdBQUcsYUFBYSxFQUFFLENBQUE7UUFFNUIsdUJBQXVCO1FBQ3ZCLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxPQUFPLENBQUMsbUJBQW1CLElBQUksSUFBSSxtQkFBbUIsQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFBO1FB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
@@ -0,0 +1,28 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { IncomingHttpHeaders } from 'http';
4
+ import { Proxy, ProxyDependency, ProxyOptions } from './proxy.js';
5
+ export interface RecordingTransaction {
6
+ startedAt?: Date;
7
+ responseStartedAt?: Date;
8
+ responseEndedAt?: Date;
9
+ method: string;
10
+ url: string;
11
+ statusCode?: number;
12
+ incomingHttpHeaders?: IncomingHttpHeaders;
13
+ contentChunks: Buffer[];
14
+ err?: Error;
15
+ errKind?: string;
16
+ }
17
+ export interface RecordingSession {
18
+ startedAt?: Date;
19
+ transactions: RecordingTransaction[];
20
+ }
21
+ export declare class RecordingProxy extends Proxy {
22
+ startedAt?: Date;
23
+ transactions: RecordingTransaction[];
24
+ setup(): Promise<void>;
25
+ saveInventory(): Promise<void>;
26
+ shutdown(): Promise<void>;
27
+ }
28
+ export declare function withRecordingProxy(options: ProxyOptions, dependency: ProxyDependency, cb: (proxy: RecordingProxy) => Promise<void>): Promise<void>;