UNPKG

@percy/agent

Version:

An agent process for integrating with Percy.

80 lines (79 loc) 3.55 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const crypto = require("crypto"); const fs = require("fs"); const os = require("os"); const path = require("path"); const url_1 = require("url"); const logger_1 = require("../utils/logger"); const constants_1 = require("./constants"); const percy_client_service_1 = require("./percy-client-service"); const resource_service_1 = require("./resource-service"); class ResponseService extends percy_client_service_1.default { constructor(buildId) { super(); this.ALLOWED_RESPONSE_STATUSES = [200, 201, 304]; this.responsesProcessed = new Map(); this.resourceService = new resource_service_1.default(buildId); } async processResponse(rootResourceUrl, response, width) { logger_1.default.debug(`processing response: ${response.url()} for width: ${width}`); const url = this.parseRequestPath(response.url()); // skip responses already processed const processResponse = this.responsesProcessed.get(url); if (processResponse) { return processResponse; } const request = response.request(); const parsedRootResourceUrl = new url_1.URL(rootResourceUrl); const rootUrl = `${parsedRootResourceUrl.protocol}//${parsedRootResourceUrl.host}`; if (request.url() === rootResourceUrl) { // Always skip the root resource logger_1.default.debug(`Skipping [is_root_resource]: ${request.url()}`); return; } if (!this.ALLOWED_RESPONSE_STATUSES.includes(response.status())) { // Only allow 2XX responses: logger_1.default.debug(`Skipping [disallowed_response_status_${response.status()}] [${width} px]: ${response.url()}`); return; } if (!request.url().startsWith(rootUrl)) { // Disallow remote resource requests. logger_1.default.debug(`Skipping [is_remote_resource] [${width} px]: ${request.url()}`); return; } if (!response.url().startsWith(rootUrl)) { // Disallow remote redirects. logger_1.default.debug(`Skipping [is_remote_redirect] [${width} px]: ${response.url()}`); return; } const localCopy = await this.makeLocalCopy(response); const responseBodySize = fs.statSync(localCopy).size; if (responseBodySize > constants_1.default.MAX_FILE_SIZE_BYTES) { // Skip large resources logger_1.default.debug(`Skipping [max_file_size_exceeded_${responseBodySize}] [${width} px]: ${response.url()}`); return; } const contentType = response.headers()['content-type']; const resource = this.resourceService.createResourceFromFile(url, localCopy, contentType); this.responsesProcessed.set(url, resource); return resource; } async makeLocalCopy(response) { logger_1.default.debug(`Making local copy of response: ${response.url()}`); const buffer = await response.buffer(); const sha = crypto.createHash('sha256').update(buffer).digest('hex'); const filename = path.join(this.tmpDir(), sha); if (!fs.existsSync(filename)) { fs.writeFileSync(filename, buffer); } else { logger_1.default.debug(`Skipping file copy [already_copied]: ${response.url()}`); } return filename; } tmpDir() { return os.tmpdir(); } } exports.default = ResponseService;