"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkStorage = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
const uuid_js_1 = require("../../../utils/uuid.js");
const NetworkRequest_js_1 = require("./NetworkRequest.js");
const NetworkUtils_js_1 = require("./NetworkUtils.js");
/** Stores network and intercept maps. */
class NetworkStorage {
    #eventManager;
    #targets = new Set();
    /**
     * A map from network request ID to Network Request objects.
     * Needed as long as information about requests comes from different events.
     */
    #requests = new Map();
    /** A map from intercept ID to track active network intercepts. */
    #intercepts = new Map();
    #interceptionStages = {
        request: false,
        response: false,
        auth: false,
    };
    constructor(eventManager, browserClient) {
        this.#eventManager = eventManager;
        browserClient.on('Target.detachedFromTarget', ({ sessionId }) => {
            this.disposeRequestMap(sessionId);
        });
    }
    /**
     * Gets the network request with the given ID, if any.
     * Otherwise, creates a new network request with the given ID and cdp target.
     */
    #getOrCreateNetworkRequest(id, cdpTarget, redirectCount) {
        let request = this.getRequestById(id);
        if (request) {
            return request;
        }
        request = new NetworkRequest_js_1.NetworkRequest(id, this.#eventManager, this, cdpTarget, redirectCount);
        this.addRequest(request);
        return request;
    }
    onCdpTargetCreated(cdpTarget) {
        this.#targets.add(cdpTarget);
        const cdpClient = cdpTarget.cdpClient;
        // TODO: Wrap into object
        const listeners = [
            [
                'Network.requestWillBeSent',
                (params) => {
                    const request = this.getRequestById(params.requestId);
                    if (request && request.isRedirecting()) {
                        request.handleRedirect(params);
                        this.deleteRequest(params.requestId);
                        this.#getOrCreateNetworkRequest(params.requestId, cdpTarget, request.redirectCount + 1).onRequestWillBeSentEvent(params);
                    }
                    else if (request) {
                        request.onRequestWillBeSentEvent(params);
                    }
                    else {
                        this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onRequestWillBeSentEvent(params);
                    }
                },
            ],
            [
                'Network.requestWillBeSentExtraInfo',
                (params) => {
                    this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onRequestWillBeSentExtraInfoEvent(params);
                },
            ],
            [
                'Network.responseReceived',
                (params) => {
                    this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onResponseReceivedEvent(params);
                },
            ],
            [
                'Network.responseReceivedExtraInfo',
                (params) => {
                    this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onResponseReceivedExtraInfoEvent(params);
                },
            ],
            [
                'Network.requestServedFromCache',
                (params) => {
                    this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onServedFromCache();
                },
            ],
            [
                'Network.loadingFailed',
                (params) => {
                    this.#getOrCreateNetworkRequest(params.requestId, cdpTarget).onLoadingFailedEvent(params);
                },
            ],
            [
                'Fetch.requestPaused',
                (event) => {
                    this.#handleNetworkInterception(event, cdpTarget);
                },
            ],
            [
                'Fetch.authRequired',
                (event) => {
                    this.#handleAuthInterception(event, cdpTarget);
                },
            ],
        ];
        for (const [event, listener] of listeners) {
            cdpClient.on(event, listener);
        }
    }
    async toggleInterception() {
        if (this.#intercepts.size) {
            const stages = {
                request: false,
                response: false,
                auth: false,
            };
            for (const intercept of this.#intercepts.values()) {
                stages.request ||= intercept.phases.includes("beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */);
                stages.response ||= intercept.phases.includes("responseStarted" /* Network.InterceptPhase.ResponseStarted */);
                stages.auth ||= intercept.phases.includes("authRequired" /* Network.InterceptPhase.AuthRequired */);
            }
            const patterns = [];
            if (this.#interceptionStages.request === stages.request &&
                this.#interceptionStages.response === stages.response &&
                this.#interceptionStages.auth === stages.auth) {
                return;
            }
            this.#interceptionStages = stages;
            // CDP quirk we need request interception when we intercept auth
            if (stages.request || stages.auth) {
                patterns.push({
                    urlPattern: '*',
                    requestStage: 'Request',
                });
            }
            if (stages.response) {
                patterns.push({
                    urlPattern: '*',
                    requestStage: 'Response',
                });
            }
            // TODO: Don't enable on start as we will have
            // no network interceptions at this time.
            // Needed to enable fetch events.
            await Promise.all([...this.#targets.values()].map(async (cdpTarget) => {
                return await cdpTarget.enableFetchIfNeeded({
                    patterns,
                    handleAuthRequests: stages.auth,
                });
            }));
        }
        else {
            this.#interceptionStages = {
                request: false,
                response: false,
                auth: false,
            };
            await Promise.all([...this.#targets.values()].map((target) => {
                return target.disableFetchIfNeeded();
            }));
        }
    }
    requestBlockedBy(request, phase) {
        if (request.url === undefined || phase === undefined) {
            return new Set();
        }
        const intercepts = new Set();
        for (const [interceptId, intercept] of this.#intercepts.entries()) {
            if (!intercept.phases.includes(phase)) {
                continue;
            }
            if (intercept.urlPatterns.length === 0) {
                intercepts.add(interceptId);
                continue;
            }
            for (const pattern of intercept.urlPatterns) {
                if ((0, NetworkUtils_js_1.matchUrlPattern)(pattern, request.url)) {
                    intercepts.add(interceptId);
                    break;
                }
            }
        }
        return intercepts;
    }
    disposeRequestMap(sessionId) {
        const requests = [...this.#requests.values()].filter((request) => {
            return request.cdpClient.sessionId === sessionId;
        });
        for (const request of requests) {
            request.dispose();
            this.#requests.delete(request.id);
        }
    }
    #handleNetworkInterception(event, cdpTarget) {
        // CDP quirk if the Network domain is not present this is undefined
        this.#getOrCreateNetworkRequest(event.networkId ?? '', cdpTarget).onRequestPaused(event);
    }
    #handleAuthInterception(event, cdpTarget) {
        // CDP quirk if the Network domain is not present this is undefined
        const request = this.getRequestByFetchId(event.requestId ?? '');
        if (!request) {
            // CDP quirk even both request/response may be continued
            // with this command
            void cdpTarget.cdpClient
                .sendCommand('Fetch.continueWithAuth', {
                requestId: event.requestId,
                authChallengeResponse: {
                    response: 'Default',
                },
            })
                .catch(() => {
                // TODO: add logging
            });
            return;
        }
        request.onAuthRequired(event);
    }
    /**
     * Adds the given entry to the intercept map.
     * URL patterns are assumed to be parsed.
     *
     * @return The intercept ID.
     */
    async addIntercept(value) {
        const interceptId = (0, uuid_js_1.uuidv4)();
        this.#intercepts.set(interceptId, value);
        await this.toggleInterception();
        return interceptId;
    }
    /**
     * Removes the given intercept from the intercept map.
     * Throws NoSuchInterceptException if the intercept does not exist.
     */
    async removeIntercept(intercept) {
        if (!this.#intercepts.has(intercept)) {
            throw new protocol_js_1.NoSuchInterceptException(`Intercept '${intercept}' does not exist.`);
        }
        this.#intercepts.delete(intercept);
        await this.toggleInterception();
    }
    getRequestById(id) {
        return this.#requests.get(id);
    }
    getRequestByFetchId(fetchId) {
        for (const request of this.#requests.values()) {
            if (request.fetchId === fetchId) {
                return request;
            }
        }
        return;
    }
    addRequest(request) {
        this.#requests.set(request.id, request);
    }
    deleteRequest(id) {
        const request = this.#requests.get(id);
        if (request) {
            request.dispose();
            this.#requests.delete(id);
        }
    }
}
exports.NetworkStorage = NetworkStorage;
//# sourceMappingURL=NetworkStorage.js.map