pagespeed-quest
Version:
A framework for efficient web front-end speed improvement
181 lines • 15.2 kB
JavaScript
import { spawn } from 'child_process';
import { createRequire } from 'module';
import Net from 'net';
import Path from 'path';
import { Proxy as RustProxy, startRecording } from 'rust-http-playback-proxy';
import { InventoryRepository } from './inventory.js';
async function getAvailablePort() {
return new Promise((resolve, reject) => {
const server = Net.createServer();
server.listen(0, '127.0.0.1', () => {
const address = server.address();
if (address && typeof address === 'object') {
const port = address.port;
server.close(() => resolve(port));
}
else {
server.close(() => reject(new Error('Failed to get port from server address')));
}
});
server.on('error', reject);
});
}
async function waitForPort(port, timeoutMs = 60000) {
const startTime = Date.now();
let delay = 50;
while (Date.now() - startTime < timeoutMs) {
try {
await new Promise((resolve, reject) => {
const socket = Net.createConnection({ port, host: '127.0.0.1' });
socket.on('connect', () => {
socket.destroy();
resolve();
});
socket.on('error', reject);
socket.setTimeout(1000, () => {
socket.destroy();
reject(new Error('Connection timeout'));
});
});
return;
}
catch {
await new Promise((resolve) => setTimeout(resolve, delay));
delay = Math.min(delay * 1.5, 500);
}
}
throw new Error(`Timeout waiting for port ${port} to become available`);
}
export class RecordingProxy {
rustProxy;
inventoryRepository;
entryUrl;
deviceType;
requestedPort;
excludePatterns;
dependency;
constructor(options, dependency) {
options ||= {};
this.dependency = dependency || {};
// Inventory repository
this.inventoryRepository = options.inventoryRepository ?? new InventoryRepository(undefined, this.dependency);
// Entry URL
this.entryUrl = options.entryUrl;
// Device type
this.deviceType = options.deviceType;
// Port
this.requestedPort = options.port;
// Exclude patterns
this.excludePatterns = options.excludePatterns;
}
async start() {
this.dependency.logger?.info(`Starting recording proxy...`);
const proxyPort = this.requestedPort || 0;
if (this.excludePatterns && this.excludePatterns.length > 0) {
// Use direct binary spawn to support -x exclude patterns
this.rustProxy = await startRecordingWithExclude({
entryUrl: this.entryUrl,
deviceType: this.deviceType,
inventoryDir: this.inventoryRepository.dirPath,
port: proxyPort,
excludePatterns: this.excludePatterns,
});
}
else {
this.rustProxy = await startRecording({
entryUrl: this.entryUrl,
deviceType: this.deviceType,
inventoryDir: this.inventoryRepository.dirPath,
port: proxyPort,
});
}
this.dependency.logger?.info(`Recording proxy started on port ${this.rustProxy.port}`);
}
get port() {
if (!this.rustProxy)
throw new Error('Proxy not started');
return this.rustProxy.port;
}
get inventoryDirPath() {
return this.inventoryRepository.dirPath;
}
async stop() {
if (this.rustProxy) {
this.dependency.logger?.info(`Stopping recording proxy...`);
// Stop the proxy (this triggers graceful shutdown via SIGTERM signal)
// The Rust proxy will save index.json automatically during graceful shutdown
await this.rustProxy.stop();
// Wait for the Rust proxy to finish writing files to disk
// The Rust proxy needs time to process resources and save index.json
await new Promise((resolve) => setTimeout(resolve, 2000));
this.dependency.logger?.info(`Recording proxy stopped`);
}
}
}
function getProxyBinaryPath() {
const require = createRequire(import.meta.url);
// Resolve the main entry point (which is allowed by exports) and derive package root
const mainPath = require.resolve('rust-http-playback-proxy');
// mainPath is like .../node_modules/rust-http-playback-proxy/dist/index.js
const pkgDir = Path.resolve(Path.dirname(mainPath), '..');
const platform = process.platform === 'win32' ? 'windows' : process.platform;
const arch = process.arch;
const ext = process.platform === 'win32' ? '.exe' : '';
return Path.join(pkgDir, 'bin', `${platform}-${arch}`, `http-playback-proxy${ext}`);
}
async function startRecordingWithExclude(options) {
const binaryPath = getProxyBinaryPath();
let port;
if (options.port === undefined || options.port === 0) {
port = await getAvailablePort();
}
else {
port = options.port;
}
const deviceType = options.deviceType || 'mobile';
const inventoryDir = options.inventoryDir || './inventory';
const args = ['recording'];
if (options.entryUrl) {
args.push(options.entryUrl);
}
args.push('--port', port.toString());
args.push('--device', deviceType);
args.push('--inventory', inventoryDir);
for (const pattern of options.excludePatterns) {
args.push('--exclude', pattern);
}
const proc = spawn(binaryPath, args, {
stdio: ['ignore', 'inherit', 'inherit'],
detached: false,
});
const proxy = new RustProxy('recording', port, inventoryDir, options.entryUrl, deviceType);
proxy.setProcess(proc);
let exited = false;
proc.on('exit', (code) => {
exited = true;
if (code !== 0 && code !== null) {
console.error(`Proxy process exited early with code ${code}`);
}
});
try {
await waitForPort(port, 15000);
}
catch (err) {
if (exited) {
throw new Error('Proxy process exited before port became available');
}
throw err;
}
return proxy;
}
export async function withRecordingProxy(options, dependency, cb) {
const proxy = new RecordingProxy(options, dependency);
await proxy.start();
try {
await cb(proxy);
}
finally {
await proxy.stop();
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVjb3JkaW5nLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3JlY29yZGluZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sZUFBZSxDQUFBO0FBQ3JDLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxRQUFRLENBQUE7QUFDdEMsT0FBTyxHQUFHLE1BQU0sS0FBSyxDQUFBO0FBQ3JCLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQTtBQUV2QixPQUFPLEVBQUUsS0FBSyxJQUFJLFNBQVMsRUFBRSxjQUFjLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQTtBQUU3RSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQTtBQWFwRCxLQUFLLFVBQVUsZ0JBQWdCO0lBQzdCLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDckMsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLFlBQVksRUFBRSxDQUFBO1FBQ2pDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLFdBQVcsRUFBRSxHQUFHLEVBQUU7WUFDakMsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFBO1lBQ2hDLElBQUksT0FBTyxJQUFJLE9BQU8sT0FBTyxLQUFLLFFBQVEsRUFBRSxDQUFDO2dCQUMzQyxNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFBO2dCQUN6QixNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFBO1lBQ25DLENBQUM7aUJBQU0sQ0FBQztnQkFDTixNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQyx3Q0FBd0MsQ0FBQyxDQUFDLENBQUMsQ0FBQTtZQUNqRixDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUE7UUFD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