You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
5.7 KiB
143 lines
5.7 KiB
"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;
|