"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.errorRequestHandler = exports.proxyRequestHandler = void 0;
const lodash_1 = require("lodash");
const node_fetch_1 = require("node-fetch");
const stream_1 = require("stream");
const url_1 = require("url");
const apiv2_1 = require("../apiv2");
const error_1 = require("../error");
const logger_1 = require("../logger");
const REQUIRED_VARY_VALUES = ["Accept-Encoding", "Authorization", "Cookie"];
function makeVary(vary = "") {
    if (!vary) {
        return "Accept-Encoding, Authorization, Cookie";
    }
    const varies = vary.split(/, ?/).map((v) => {
        return v
            .split("-")
            .map((part) => (0, lodash_1.capitalize)(part))
            .join("-");
    });
    REQUIRED_VARY_VALUES.forEach((requiredVary) => {
        if (!(0, lodash_1.includes)(varies, requiredVary)) {
            varies.push(requiredVary);
        }
    });
    return varies.join(", ");
}
function proxyRequestHandler(url, rewriteIdentifier, options = {}) {
    return async (req, res, next) => {
        var _a;
        logger_1.logger.info(`[hosting] Rewriting ${req.url} to ${url} for ${rewriteIdentifier}`);
        const cookie = req.headers.cookie || "";
        const sessionCookie = cookie.split(/; ?/).find((c) => {
            return c.trim().startsWith("__session=");
        });
        const u = new url_1.URL(url + req.url);
        const c = new apiv2_1.Client({ urlPrefix: u.origin, auth: false });
        let passThrough;
        if (req.method && !["GET", "HEAD"].includes(req.method)) {
            passThrough = new stream_1.PassThrough();
            req.pipe(passThrough);
        }
        const headers = new node_fetch_1.Headers({
            "X-Forwarded-Host": req.headers.host || "",
            "X-Original-Url": req.url || "",
            Pragma: "no-cache",
            "Cache-Control": "no-cache, no-store",
            Cookie: sessionCookie || "",
        });
        const headersToSkip = new Set(["host"]);
        for (const key of Object.keys(req.headers)) {
            if (headersToSkip.has(key)) {
                continue;
            }
            const value = req.headers[key];
            if (value === undefined) {
                headers.delete(key);
            }
            else if (Array.isArray(value)) {
                headers.delete(key);
                for (const v of value) {
                    headers.append(key, v);
                }
            }
            else {
                headers.set(key, value);
            }
        }
        let proxyRes;
        try {
            proxyRes = await c.request({
                method: (req.method || "GET"),
                path: u.pathname,
                queryParams: u.searchParams,
                headers,
                resolveOnHTTPError: true,
                responseType: "stream",
                redirect: "manual",
                body: passThrough,
                timeout: 60000,
                compress: false,
            });
        }
        catch (err) {
            const isAbortError = err instanceof error_1.FirebaseError && ((_a = err.original) === null || _a === void 0 ? void 0 : _a.name.includes("AbortError"));
            const isTimeoutError = err instanceof error_1.FirebaseError &&
                err.original instanceof node_fetch_1.FetchError &&
                err.original.code === "ETIMEDOUT";
            const isSocketTimeoutError = err instanceof error_1.FirebaseError &&
                err.original instanceof node_fetch_1.FetchError &&
                err.original.code === "ESOCKETTIMEDOUT";
            if (isAbortError || isTimeoutError || isSocketTimeoutError) {
                res.statusCode = 504;
                return res.end("Timed out waiting for function to respond.\n");
            }
            res.statusCode = 500;
            return res.end(`An internal error occurred while proxying for ${rewriteIdentifier}\n`);
        }
        if (proxyRes.status === 404) {
            const cascade = proxyRes.response.headers.get("x-cascade");
            if (options.forceCascade || (cascade && cascade.toUpperCase() === "PASS")) {
                return next();
            }
        }
        if (!proxyRes.response.headers.get("cache-control")) {
            proxyRes.response.headers.set("cache-control", "private");
        }
        const cc = proxyRes.response.headers.get("cache-control");
        if (cc && !cc.includes("private")) {
            proxyRes.response.headers.delete("set-cookie");
        }
        proxyRes.response.headers.set("vary", makeVary(proxyRes.response.headers.get("vary")));
        const location = proxyRes.response.headers.get("location");
        if (location) {
            try {
                const locationURL = new url_1.URL(location);
                if (locationURL.origin === u.origin) {
                    const unborkedLocation = location.replace(locationURL.origin, "");
                    proxyRes.response.headers.set("location", unborkedLocation);
                }
            }
            catch (e) {
                logger_1.logger.debug(`[hosting] had trouble parsing location header, but this may be okay: "${location}"`);
            }
        }
        for (const [key, value] of Object.entries(proxyRes.response.headers.raw())) {
            res.setHeader(key, value);
        }
        res.statusCode = proxyRes.status;
        proxyRes.response.body.pipe(res);
    };
}
exports.proxyRequestHandler = proxyRequestHandler;
function errorRequestHandler(error) {
    return (req, res) => {
        res.statusCode = 500;
        const out = `A problem occurred while trying to handle a proxied rewrite: ${error}`;
        logger_1.logger.error(out);
        res.end(out);
    };
}
exports.errorRequestHandler = errorRequestHandler;
