"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkManager = void 0;
const exception_js_1 = require("../exception.js");
const cdp_util_js_1 = require("./cdp-util.js");
const IGNORE_REQUEST_LOG_TYPES = ['Image', 'Font', 'Stylesheet', 'Media'];
class NetworkManager {
    constructor(page) {
        this.page = page;
        this.pendingRequestIds = new Set();
        this.collectedResources = [];
        this.lastNetworkEventAt = 0;
        page.target.on('Network.requestWillBeSent', ev => this.onRequestWillBeSent(ev));
        page.target.on('Network.responseReceived', ev => this.onResponseReceived(ev));
        page.target.on('Network.loadingFinished', ev => this.onLoadingFinished(ev));
        page.target.on('Network.loadingFailed', ev => this.onLoadingFailed(ev));
    }
    get logger() {
        return this.page.logger;
    }
    isSilent() {
        return this.pendingRequestIds.size === 0;
    }
    isSilentFor(duration) {
        return this.isSilent() && this.lastNetworkEventAt + duration < Date.now();
    }
    invalidate() {
        this.collectedResources = [];
        this.pendingRequestIds.clear();
    }
    getCollectedResources() {
        return this.collectedResources;
    }
    getPendingRequests() {
        const results = [];
        for (const requestId of this.pendingRequestIds) {
            const res = this.getResourceById(requestId);
            if (res) {
                results.push(res);
            }
        }
        return results;
    }
    getResourceById(requestId) {
        return this.collectedResources.find(_ => _.requestId === requestId) || null;
    }
    async getResponseBody(requestId) {
        const { body, base64Encoded } = await this.page.send('Network.getResponseBody', { requestId });
        return Buffer.from(body, base64Encoded ? 'base64' : 'utf-8');
    }
    async waitForFinish(requestId, timeout = 30000) {
        return await new Promise((resolve, reject) => {
            const timer = setTimeout(onTimeout, timeout);
            const { target } = this.page;
            target.addListener('Network.loadingFinished', onFinished);
            target.addListener('Network.loadingFailed', onFailed);
            const rs = this.collectedResources.find(rs => rs.requestId === requestId);
            if (rs && rs.status === 'loadingFinished') {
                return onFinished(rs);
            }
            if (rs && rs.status === 'loadingFailed') {
                return onFailed(rs);
            }
            function cleanup() {
                clearTimeout(timer);
                target.removeListener('Network.loadingFinished', onFinished);
                target.removeListener('Network.loadingFailed', onFailed);
            }
            function onFinished(rs) {
                if (rs.requestId === requestId) {
                    cleanup();
                    resolve();
                }
            }
            function onFailed(rs) {
                if (rs.requestId === requestId) {
                    cleanup();
                    reject(new exception_js_1.Exception({
                        name: 'NetworkRequestFailed',
                        message: 'Resource loading has failed',
                        retry: false,
                    }));
                }
            }
            function onTimeout() {
                cleanup();
                reject(new exception_js_1.Exception({
                    name: 'NetworkRequestTimeout',
                    message: 'Timeout while waiting for resource to load',
                    retry: false,
                }));
            }
        });
    }
    onRequestWillBeSent(ev) {
        const { requestId, frameId, type, request } = ev;
        const topFrame = this.page.mainFrame();
        if (frameId === topFrame.frameId && type === 'Document') {
            this.invalidate();
        }
        this.lastNetworkEventAt = Date.now();
        this.pendingRequestIds.add(requestId);
        this.collectedResources.push({
            requestId,
            frameId,
            type,
            status: 'requestWillBeSent',
            localSendTimestamp: Date.now(),
            request,
        });
    }
    onResponseReceived(ev) {
        this.lastNetworkEventAt = Date.now();
        const rs = this.getResourceById(ev.requestId);
        if (rs) {
            rs.response = ev.response;
            rs.status = 'responseReceived';
            if (ev.response.status >= 400) {
                this.logHttpError(rs);
            }
        }
    }
    onLoadingFinished(ev) {
        this.lastNetworkEventAt = Date.now();
        this.pendingRequestIds.delete(ev.requestId);
        const rs = this.getResourceById(ev.requestId);
        if (rs) {
            rs.status = 'loadingFinished';
        }
    }
    onLoadingFailed(ev) {
        this.lastNetworkEventAt = Date.now();
        this.pendingRequestIds.delete(ev.requestId);
        const rs = this.getResourceById(ev.requestId);
        if (rs) {
            rs.status = 'loadingFailed';
            rs.errorText = ev.errorText;
            this.logFailedRequest(rs);
        }
    }
    logFailedRequest(rs) {
        const looksLikeAborted = !rs.errorText || rs.errorText === 'net::ERR_ABORTED';
        if (looksLikeAborted && rs.type !== 'Document') {
            return;
        }
        if (IGNORE_REQUEST_LOG_TYPES.includes(rs.type)) {
            return;
        }
        this.logger.debug('Request failed to load', {
            details: rs,
        });
    }
    logHttpError(rs) {
        if (IGNORE_REQUEST_LOG_TYPES.includes(rs.type)) {
            return;
        }
        this.logger.debug('Request failed with HTTP error', {
            details: rs,
        });
    }
    static headersObjectToEntries(cdpHeaders) {
        return (0, cdp_util_js_1.convertHeadersToEntries)(cdpHeaders);
    }
    static headerEntriesToObject(cdpHeaderEntries) {
        return (0, cdp_util_js_1.convertHeadersToObject)(cdpHeaderEntries);
    }
}
exports.NetworkManager = NetworkManager;
//# sourceMappingURL=network-manager.js.map