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.
- package/build/adhoc.d.ts +2 -0
- package/build/adhoc.js +31 -0
- package/build/command.d.ts +2 -0
- package/build/command.js +121 -0
- package/build/dependency.d.ts +9 -0
- package/build/dependency.js +31 -0
- package/build/encoding.d.ts +11 -0
- package/build/encoding.js +43 -0
- package/build/formatting.d.ts +8 -0
- package/build/formatting.js +55 -0
- package/build/http.d.ts +26 -0
- package/build/http.js +96 -0
- package/build/index.d.ts +8 -0
- package/build/index.js +9 -0
- package/build/inventory.d.ts +46 -0
- package/build/inventory.js +175 -0
- package/build/lighthouse.d.ts +12 -0
- package/build/lighthouse.js +28 -0
- package/build/loadshow.d.ts +19 -0
- package/build/loadshow.js +65 -0
- package/build/playback.d.ts +22 -0
- package/build/playback.js +104 -0
- package/build/proxy.d.ts +35 -0
- package/build/proxy.js +96 -0
- package/build/recording.d.ts +28 -0
- package/build/recording.js +94 -0
- package/build/throttling.d.ts +33 -0
- package/build/throttling.js +89 -0
- package/build/types.d.ts +8 -0
- package/build/types.js +2 -0
- package/package.json +3 -2
|
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW52ZW50b3J5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ludmVudG9yeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxJQUFJLENBQUE7QUFDbkIsT0FBTyxHQUFHLE1BQU0sYUFBYSxDQUFBO0FBQzdCLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQTtBQUV2QixPQUFPLEVBQUUsUUFBUSxFQUF1QixVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUE7QUFDekUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFBO0FBQzdELE9BQU8sRUFBZSxzQkFBc0IsRUFBRSxzQkFBc0IsRUFBRSwwQkFBMEIsRUFBRSxNQUFNLFdBQVcsQ0FBQTtBQUduSCxNQUFNLFlBQVksR0FBRyxXQUFXLENBQUE7QUFDaEMsTUFBTSxTQUFTLEdBQUcsWUFBWSxDQUFBO0FBc0M5QixNQUFNLE9BQU8sbUJBQW1CO0lBQzlCLE9BQU8sQ0FBUztJQUNoQixVQUFVLENBQStCO0lBRXpDLFlBQVksT0FBZ0IsRUFBRSxVQUEwQztRQUN0RSxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxZQUFZLENBQUMsQ0FBQTtRQUNoRSxJQUFJLENBQUMsVUFBVSxHQUFHLFVBQVUsSUFBSSxFQUFFLENBQUE7SUFDcEMsQ0FBQztJQUVELEtBQUssQ0FBQyxhQUFhLENBQUMsU0FBb0I7UUFDdEMsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFBO1FBQ3hELE1BQU0sR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUE7UUFDbEQsTUFBTSxHQUFHLENBQUMsU0FBUyxDQUFDLElB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
|
|
@@ -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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
|
package/build/proxy.d.ts
ADDED
|
@@ -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>;
|