pagespeed-quest
Version:
A framework for efficient web front-end speed improvement
189 lines • 18.4 kB
JavaScript
import Fs from 'fs';
import Fsp from 'fs/promises';
import Path from 'path';
import { compress, decompress } from './encoding.js';
import { convertEditableText, isText } from './formatting.js';
import { parseContentTypeHeader, requestContentFilePath, stringifyContentTypeHeader } from './http.js';
const InventoryDir = 'inventory';
const IndexFile = 'index.json';
const InventoryFile = 'inventory.json'; // Rust proxy uses this name
export class InventoryRepository {
dirPath;
dependency;
constructor(dirPath, dependency) {
this.dirPath = dirPath || Path.join(process.cwd(), InventoryDir);
this.dependency = dependency || {};
}
async saveInventory(inventory) {
const inventoryJson = JSON.stringify(inventory, null, 2);
await Fsp.mkdir(this.dirPath, { recursive: true });
// Save as both index.json (legacy) and inventory.json (Rust proxy)
await Fsp.writeFile(Path.join(this.dirPath, IndexFile), inventoryJson);
await Fsp.writeFile(Path.join(this.dirPath, InventoryFile), inventoryJson);
}
async loadInventory() {
// Try inventory.json first (Rust proxy), fallback to index.json (legacy)
let inventoryPath = Path.join(this.dirPath, InventoryFile);
try {
const inventoryJson = await Fsp.readFile(inventoryPath, 'utf8');
const inventory = JSON.parse(inventoryJson);
return inventory;
}
catch {
// Fallback to index.json
inventoryPath = Path.join(this.dirPath, IndexFile);
const inventoryJson = await Fsp.readFile(inventoryPath, 'utf8');
const inventory = JSON.parse(inventoryJson);
return inventory;
}
}
async saveTransactions(transactions) {
// To keep transactions order in Promise.all,
// store transactions and resources in a map.
const map = new Map();
await Fsp.mkdir(this.dirPath, { recursive: true });
const saveTransaction = async (transaction) => {
const resource = {
method: transaction.method,
url: transaction.url,
ttfbMs: transaction.ttfbMs,
statusCode: transaction.statusCode,
errorMessage: transaction.errorMessage,
rawHeaders: transaction.rawHeaders,
};
// Headers
if (transaction.rawHeaders) {
if (transaction.rawHeaders['content-type']) {
const { mime, charset } = parseContentTypeHeader(transaction.rawHeaders['content-type']);
if (mime)
resource.contentTypeMime = mime;
if (charset)
resource.contentTypeCharset = charset;
}
if (transaction.rawHeaders['content-encoding']) {
const contentEncoding = transaction.rawHeaders['content-encoding'];
if (contentEncoding)
resource.contentEncoding = contentEncoding;
}
}
// Mbps
if (transaction.durationMs && transaction.content) {
const contentBits = transaction.content.length * 8;
const seconds = transaction.durationMs / 1000;
const mega = 1024 * 1024;
resource.mbps = contentBits / seconds / mega;
}
// Content
if (transaction.content) {
const steps = {};
const contentFilePath = requestContentFilePath(resource.method, resource.url);
const fullPath = Path.join(this.dirPath, contentFilePath);
// Content-Encoding
steps.decoded = resource.contentEncoding
? await decompress(resource.contentEncoding, transaction.content)
: transaction.content;
// Try to make editable (utf8, beautify)
steps.editable = steps.decoded;
if (isText(resource.contentTypeMime)) {
try {
steps.editable = await convertEditableText(steps.decoded, resource.contentTypeMime, resource.contentTypeCharset);
resource.contentTypeCharset = 'utf-8';
}
catch (err) {
this.dependency.logger?.error({ err, resource }, `Formatting failed ${transaction.url}: ${err.message}`);
}
}
await Fsp.mkdir(Path.dirname(fullPath), { recursive: true });
await Fsp.writeFile(fullPath, steps.editable);
resource.contentFilePath = contentFilePath;
}
map.set(transaction, resource);
};
const tryToSaveTransaction = async (transaction) => {
try {
await saveTransaction(transaction);
}
catch (err) {
this.dependency.logger?.error({ err, method: transaction.method, url: transaction.url }, `Failed to save transaction ${transaction.url}: ${err.message}`);
}
};
await Promise.all(transactions.map(tryToSaveTransaction));
// Restore transactions order after Promise.all
const resources = transactions.reduce((resources, transaction) => {
const resource = map.get(transaction);
if (resource)
resources.push(resource);
return resources;
}, []);
return resources;
}
async loadTransactions(resources) {
const map = new Map();
const loadTransaction = async (resource) => {
const transaction = {
method: resource.method,
url: resource.url,
ttfbMs: resource.ttfbMs,
statusCode: resource.statusCode,
errorMessage: resource.errorMessage,
rawHeaders: { ...(resource.rawHeaders || {}) },
};
// content
let content;
if (resource.contentUtf8 !== undefined) {
content = Buffer.from(resource.contentUtf8);
}
else if (resource.contentBase64 !== undefined) {
content = Buffer.from(resource.contentBase64, 'base64');
}
else if (resource.contentFilePath) {
const fullPath = Path.join(this.dirPath, resource.contentFilePath);
if (Fs.existsSync(fullPath)) {
content = await Fsp.readFile(fullPath);
}
}
if (content) {
// encoding
if (resource.contentEncoding) {
transaction.content = await compress(resource.contentEncoding, content);
transaction.rawHeaders['content-encoding'] = resource.contentEncoding;
}
else {
transaction.content = content;
delete transaction.rawHeaders['content-encoding'];
}
// length
transaction.rawHeaders['content-length'] = `${transaction.content.length}`;
// duration
const bytesPerMs = resource.mbps ? (resource.mbps * 1024 * 1024) / 8 / 1000 : 0;
transaction.durationMs = bytesPerMs ? content.length / bytesPerMs : 0;
}
else {
transaction.rawHeaders['content-length'] = '0';
transaction.durationMs = 0;
}
// Content-Type
if (resource.contentTypeMime) {
transaction.rawHeaders['content-type'] = stringifyContentTypeHeader(resource.contentTypeMime, resource.contentTypeCharset);
}
map.set(resource, transaction);
};
const tryToLoadTransaction = async (resource) => {
try {
await loadTransaction(resource);
}
catch (err) {
this.dependency.logger?.error({ err, resource }, `Loading transaction failed ${resource.url}: ${err.message}`);
}
};
await Promise.all(resources.map(tryToLoadTransaction));
const transactions = resources.reduce((transactions, resource) => {
const transaction = map.get(resource);
if (transaction)
transactions.push(transaction);
return transactions;
}, []);
return transactions;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW52ZW50b3J5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ludmVudG9yeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxJQUFJLENBQUE7QUFDbkIsT0FBTyxHQUFHLE1BQU0sYUFBYSxDQUFBO0FBQzdCLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQTtBQUV2QixPQUFPLEVBQUUsUUFBUSxFQUF1QixVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUE7QUFDekUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFBO0FBQzdELE9BQU8sRUFBZSxzQkFBc0IsRUFBRSxzQkFBc0IsRUFBRSwwQkFBMEIsRUFBRSxNQUFNLFdBQVcsQ0FBQTtBQUduSCxNQUFNLFlBQVksR0FBRyxXQUFXLENBQUE7QUFDaEMsTUFBTSxTQUFTLEdBQUcsWUFBWSxDQUFBO0FBQzlCLE1BQU0sYUFBYSxHQUFHLGdCQUFnQixDQUFBLENBQUMsNEJBQTRCO0FBc0NuRSxNQUFNLE9BQU8sbUJBQW1CO0lBQzlCLE9BQU8sQ0FBUztJQUNoQixVQUFVLENBQStCO0lBRXpDLFlBQVksT0FBZ0IsRUFBRSxVQUEwQztRQUN0RSxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxZQUFZLENBQUMsQ0FBQTtRQUNoRSxJQUFJLENBQUMsVUFBVSxHQUFHLFVBQVUsSUFBSSxFQUFFLENBQUE7SUFDcEMsQ0FBQztJQUVELEtBQUssQ0FBQyxhQUFhLENBQUMsU0FBb0I7UUFDdEMsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFBO1FBQ3hELE1BQU0sR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUE7UUFDbEQsbUVBQW1FO1FBQ25FLE1BQU0sR0FBRyxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsU0FBUyxDQUFDLEVBQUUsYUFBYSxDQUFDLENBQUE7UUFDdEUsTUFBTSxHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxhQUFhLENBQUMsRUFBRSxhQUFhLENBQUMsQ0FBQTtJQUM1RSxDQUFDO0lBRUQsS0FBSyxDQUFDLGFBQWE7UUFDakIseUVBQXlFO1FBQ3pFLElBQUksYUFBYSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxhQUFhLENBQUMsQ0FBQTtRQUMxRCxJQUFJLENBQUM7WUFDSCxNQUFNLGFBQWEsR0FBRyxNQUFNLEdBQUcsQ0FBQyxRQUFRLENBQUMsYUFBYSxFQUFFLE1BQU0sQ0FBQyxDQUFBO1lBQy9ELE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsYUFBYSxDQUFDLENBQUE7WUFDM0MsT0FBTyxTQUFTLENBQUE7UUFDbEIsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLHlCQUF5QjtZQUN6QixhQUFhLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLFNBQVMsQ0FBQyxDQUFBO1lBQ2xELE1BQU0sYUFBYSxHQUFHLE1BQU0sR0FBRyxDQUFDLFFBQVEsQ0FBQyxhQUFhLEVBQUUsTUFBTSxDQUFDLENBQUE7WUFDL0QsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxhQUFhLENBQUMsQ0FBQTtZQUMzQyxPQUFPLFNBQVMsQ0FBQTtRQUNsQixDQUFDO0lBQ0gsQ0FBQztJQUVELEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxZQUEyQjtRQUNoRCw2Q0FBNkM7UUFDN0MsNkNBQTZDO1FBQzdDLE1BQU0sR0FBRyxHQUFHLElBQUksR0FBRyxFQUF5QixDQUFBO1FBRTVDLE1BQU0sR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUE7UUFFbEQsTUFBTSxlQUFlLEdBQUcsS0FBSyxFQUFFLFdBQXdCLEVBQUUsRUFBRTtZQUN6RCxNQUFNLFFBQVEsR0FBYTtnQkFDekIsTUFBTSxFQUFFLFdBQVcsQ0FBQyxNQUFNO2dCQUMxQixHQUFHLEVBQUUsV0FBVyxDQUFDLEdBQUc7Z0JBQ3BCLE1BQU0sRUFBRSxXQUFXLENBQUMsTUFBTTtnQkFDMUIsVUFBVSxFQUFFLFdBQVcsQ0FBQyxVQUFVO2dCQUNsQyxZQUFZLEVBQUUsV0FBVyxDQUFDLFlBQVk7Z0JBQ3RDLFVBQVUsRUFBRSxXQUFXLENBQUMsVUFBVTthQUNuQyxDQUFBO1lBRUQsVUFBVTtZQUNWLElBQUksV0FBVyxDQUFDLFVBQVUsRUFBRSxDQUFDO2dCQUMzQixJQUFJLFdBQVcsQ0FBQyxVQUFVLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQztvQkFDM0MsTUFBTSxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsR0FBRyxzQkFBc0IsQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUE7b0JBQ3hGLElBQUksSUFBSTt3QkFBRSxRQUFRLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQTtvQkFDekMsSUFBSSxPQUFPO3dCQUFFLFFBQVEsQ0FBQyxrQkFBa0IsR0FBRyxPQUFPLENBQUE7Z0JBQ3BELENBQUM7Z0JBRUQsSUFBSSxXQUFXLENBQUMsVUFBVSxDQUFDLGtCQUFrQixDQUFDLEVBQUUsQ0FBQztvQkFDL0MsTUFBTSxlQUFlLEdBQUcsV0FBVyxDQUFDLFVBQVUsQ0FBQyxrQkFBa0IsQ0FBd0IsQ0FBQTtvQkFDekYsSUFBSSxlQUFlO3dCQUFFLFFBQVEsQ0FBQyxlQUFlLEdBQUcsZUFBZSxDQUFBO2dCQUNqRSxDQUFDO1lBQ0gsQ0FBQztZQUVELE9BQU87WUFDUCxJQUFJLFdBQVcsQ0FBQyxVQUFVLElBQUksV0FBVyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUNsRCxNQUFNLFdBQVcsR0FBRyxXQUFXLENBQUMsT0FBTyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUE7Z0JBQ2xELE1BQU0sT0FBTyxHQUFHLFdBQVcsQ0FBQyxVQUFVLEdBQUcsSUFBSSxDQUFBO2dCQUM3QyxNQUFNLElBQUksR0FBRyxJQUFJLEdBQUcsSUFBSSxDQUFBO2dCQUN4QixRQUFRLENBQUMsSUFBSSxHQUFHLFdBQVcsR0FBRyxPQUFPLEdBQUcsSUFBSSxDQUFBO1lBQzlDLENBQUM7WUFFRCxVQUFVO1lBQ1YsSUFBSSxXQUFXLENBQUMsT0FBTyxFQUFFLENBQUM7Z0JBQ3hCLE1BQU0sS0FBSyxHQUdQLEVBQUUsQ0FBQTtnQkFFTixNQUFNLGVBQWUsR0FBRyxzQkFBc0IsQ0FBQyxRQUFRLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQTtnQkFDN0UsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLGVBQWUsQ0FBQyxDQUFBO2dCQUV6RCxtQkFBbUI7Z0JBQ25CLEtBQUssQ0FBQyxPQUFPLEdBQUcsUUFBUSxDQUFDLGVBQWU7b0JBQ3RDLENBQUMsQ0FBQyxNQUFNLFVBQVUsQ0FBQyxRQUFRLENBQUMsZUFBZSxFQUFFLFdBQVcsQ0FBQyxPQUFPLENBQUM7b0JBQ2pFLENBQUMsQ0FBQyxXQUFXLENBQUMsT0FBTyxDQUFBO2dCQUV2Qix3Q0FBd0M7Z0JBQ3hDLEtBQUssQ0FBQyxRQUFRLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQTtnQkFDOUIsSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDLGVBQWUsQ0FBQyxFQUFFLENBQUM7b0JBQ3JDLElBQUksQ0FBQzt3QkFDSCxLQUFLLENBQUMsUUFBUSxHQUFHLE1BQU0sbUJBQW1CLENBQ3hDLEtBQUssQ0FBQyxPQUFPLEVBQ2IsUUFBUSxDQUFDLGVBQWUsRUFDeEIsUUFBUSxDQUFDLGtCQUFrQixDQUM1QixDQUFBO3dCQUNELFFBQVEsQ0FBQyxrQkFBa0IsR0FBRyxPQUFPLENBQUE7b0JBQ3ZDLENBQUM7b0JBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQzt3QkFDYixJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEVBQUUscUJBQXFCLFdBQVcsQ0FBQyxHQUFHLEtBQUssR0FBRyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUE7b0JBQzFHLENBQUM7Z0JBQ0gsQ0FBQztnQkFFRCxNQUFNLEdBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFBO2dCQUM1RCxNQUFNLEdBQUcsQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQTtnQkFFN0MsUUFBUSxDQUFDLGVBQWUsR0FBRyxlQUFlLENBQUE7WUFDNUMsQ0FBQztZQUVELEdBQUcsQ0FBQyxHQUFHLENBQUMsV0FBVyxFQUFFLFFBQVEsQ0FBQyxDQUFBO1FBQ2hDLENBQUMsQ0FBQTtRQUVELE1BQU0sb0JBQW9CLEdBQUcsS0FBSyxFQUFFLFdBQXdCLEVBQUUsRUFBRTtZQUM5RCxJQUFJLENBQUM7Z0JBQ0gsTUFBTSxlQUFlLENBQUMsV0FBVyxDQUFDLENBQUE7WUFDcEMsQ0FBQztZQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7Z0JBQ2IsSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUMzQixFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsV0FBVyxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsV0FBVyxDQUFDLEdBQUcsRUFBRSxFQUN6RCw4QkFBOEIsV0FBVyxDQUFDLEdBQUcsS0FBSyxHQUFHLENBQUMsT0FBTyxFQUFFLENBQ2hFLENBQUE7WUFDSCxDQUFDO1FBQ0gsQ0FBQyxDQUFBO1FBRUQsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsb0JBQW9CLENBQUMsQ0FBQyxDQUFBO1FBRXpELCtDQUErQztRQUMvQyxNQUFNLFNBQVMsR0FBZSxZQUFZLENBQUMsTUFBTSxDQUFhLENBQUMsU0FBUyxFQUFFLFdBQVcsRUFBRSxFQUFFO1lBQ3ZGLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUE7WUFDckMsSUFBSSxRQUFRO2dCQUFFLFNBQVMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUE7WUFDdEMsT0FBTyxTQUFTLENBQUE7UUFDbEIsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBRU4sT0FBTyxTQUFTLENBQUE7SUFDbEIsQ0FBQztJQUVELEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFxQjtRQUMxQyxNQUFNLEdBQUcsR0FBRyxJQUFJLEdBQUcsRUFBeUIsQ0FBQTtRQUU1QyxNQUFNLGVBQWUsR0FBRyxLQUFLLEVBQUUsUUFBa0IsRUFBRSxFQUFFO1lBQ25ELE1BQU0sV0FBVyxHQUFnQjtnQkFDL0IsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNO2dCQUN2QixHQUFHLEVBQUUsUUFBUSxDQUFDLEdBQUc7Z0JBQ2pCLE1BQU0sRUFBRSxRQUFRLENBQUMsTUFBTTtnQkFDdkIsVUFBVSxFQUFFLFFBQVEsQ0FBQyxVQUFVO2dCQUMvQixZQUFZLEVBQUUsUUFBUSxDQUFDLFlBQVk7Z0JBQ25DLFVBQVUsRUFBRSxFQUFFLEdBQUcsQ0FBQyxRQUFRLENBQUMsVUFBVSxJQUFJLEVBQUUsQ0FBQyxFQUFFO2FBQy9DLENBQUE7WUFFRCxVQUFVO1lBQ1YsSUFBSSxPQUEyQixDQUFBO1lBRS9CLElBQUksUUFBUSxDQUFDLFdBQVcsS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDdkMsT0FBTyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBQzdDLENBQUM7aUJBQU0sSUFBSSxRQUFRLENBQUMsYUFBYSxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUNoRCxPQUFPLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsYUFBYSxFQUFFLFFBQVEsQ0FBQyxDQUFBO1lBQ3pELENBQUM7aUJBQU0sSUFBSSxRQUFRLENBQUMsZUFBZSxFQUFFLENBQUM7Z0JBQ3BDLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxRQUFRLENBQUMsZUFBZSxDQUFDLENBQUE7Z0JBQ2xFLElBQUksRUFBRSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO29CQUM1QixPQUFPLEdBQUcsTUFBTSxHQUFHLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFBO2dCQUN4QyxDQUFDO1lBQ0gsQ0FBQztZQUVELElBQUksT0FBTyxFQUFFLENBQUM7Z0JBQ1osV0FBVztnQkFDWCxJQUFJLFFBQVEsQ0FBQyxlQUFlLEVBQUUsQ0FBQztvQkFDN0IsV0FBVyxDQUFDLE9BQU8sR0FBRyxNQUFNLFFBQVEsQ0FBQyxRQUFRLENBQUMsZUFBZSxFQUFFLE9BQU8sQ0FBQyxDQUFBO29CQUN2RSxXQUFXLENBQUMsVUFBVSxDQUFDLGtCQUFrQixDQUFDLEdBQUcsUUFBUSxDQUFDLGVBQWUsQ0FBQTtnQkFDdkUsQ0FBQztxQkFBTSxDQUFDO29CQUNOLFdBQVcsQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFBO29CQUM3QixPQUFPLFdBQVcsQ0FBQyxVQUFVLENBQUMsa0JBQWtCLENBQUMsQ0FBQTtnQkFDbkQsQ0FBQztnQkFFRCxTQUFTO2dCQUNULFdBQVcsQ0FBQyxVQUFVLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxHQUFHLFdBQVcsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLENBQUE7Z0JBRTFFLFdBQVc7Z0JBQ1gsTUFBTSxVQUFVLEdBQUcsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsSUFBSSxHQUFHLElBQUksR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7Z0JBQy9FLFdBQVcsQ0FBQyxVQUFVLEdBQUcsVUFBVSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTSxHQUFHLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFBO1lBQ3ZFLENBQUM7aUJBQU0sQ0FBQztnQkFDTixXQUFXLENBQUMsVUFBVSxDQUFDLGdCQUFnQixDQUFDLEdBQUcsR0FBRyxDQUFBO2dCQUM5QyxXQUFXLENBQUMsVUFBVSxHQUFHLENBQUMsQ0FBQTtZQUM1QixDQUFDO1lBRUQsZUFBZTtZQUNmLElBQUksUUFBUSxDQUFDLGVBQWUsRUFBRSxDQUFDO2dCQUM3QixXQUFXLENBQUMsVUFBVSxDQUFDLGNBQWMsQ0FBQyxHQUFHLDBCQUEwQixDQUNqRSxRQUFRLENBQUMsZUFBZSxFQUN4QixRQUFRLENBQUMsa0JBQWtCLENBQzVCLENBQUE7WUFDSCxDQUFDO1lBRUQsR0FBRyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsV0FBVyxDQUFDLENBQUE7UUFDaEMsQ0FBQyxDQUFBO1FBRUQsTUFBTSxvQkFBb0IsR0FBRyxLQUFLLEVBQUUsUUFBa0IsRUFBRSxFQUFFO1lBQ3hELElBQUksQ0FBQztnQkFDSCxNQUFNLGVBQWUsQ0FBQyxRQUFRLENBQUMsQ0FBQTtZQUNqQyxDQUFDO1lBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztnQkFDYixJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEVBQUUsOEJBQThCLFFBQVEsQ0FBQyxHQUFHLEtBQUssR0FBRyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUE7WUFDaEgsQ0FBQztRQUNILENBQUMsQ0FBQTtRQUVELE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLG9CQUFvQixDQUFDLENBQUMsQ0FBQTtRQUV0RCxNQUFNLFlBQVksR0FBa0IsU0FBUyxDQUFDLE1BQU0sQ0FBZ0IsQ0FBQyxZQUFZLEVBQUUsUUFBUSxFQUFFLEVBQUU7WUFDN0YsTUFBTSxXQUFXLEdBQUcsR0FBRyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQTtZQUNyQyxJQUFJLFdBQVc7Z0JBQUUsWUFBWSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQTtZQUMvQyxPQUFPLFlBQVksQ0FBQTtRQUNyQixDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUE7UUFFTixPQUFPLFlBQVksQ0FBQTtJQUNyQixDQUFDO0NBQ0YifQ==