firebase-tools
Version:
Command-Line Interface for Firebase
164 lines (163 loc) • 6.35 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.proxyRequestHandler = proxyRequestHandler;
exports.errorRequestHandler = errorRequestHandler;
const lodash_1 = require("lodash");
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) => {
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 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) {
logger_1.logger.error("[PROXY ERROR]", err);
const isAbortError = err instanceof error_1.FirebaseError && err.original?.name.includes("AbortError");
const isTimeoutError = err instanceof error_1.FirebaseError &&
(err.original?.code === "ETIMEDOUT" ||
err.original?.cause?.code === "ETIMEDOUT");
const isSocketTimeoutError = err instanceof error_1.FirebaseError &&
(err.original?.code === "ESOCKETTIMEDOUT" ||
err.original?.cause?.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`);
}
finally {
if (passThrough) {
passThrough.resume();
}
}
const resHeaders = {};
for (const [key, value] of proxyRes.response.headers.entries()) {
resHeaders[key.toLowerCase()] = value;
}
if (proxyRes.status === 404) {
const cascade = resHeaders["x-cascade"];
if (options.forceCascade ||
(typeof cascade === "string" && cascade.toUpperCase() === "PASS")) {
return next();
}
}
if (!resHeaders["cache-control"]) {
resHeaders["cache-control"] = "private";
}
const cc = resHeaders["cache-control"];
if (typeof cc === "string" && !cc.includes("private")) {
delete resHeaders["set-cookie"];
}
const vary = resHeaders["vary"];
resHeaders["vary"] = makeVary(typeof vary === "string" ? vary : null);
const location = resHeaders["location"];
if (typeof location === "string" && location) {
try {
const locationURL = new url_1.URL(location);
if (locationURL.origin === u.origin) {
const unborkedLocation = location.replace(locationURL.origin, "");
resHeaders["location"] = unborkedLocation;
}
}
catch (e) {
logger_1.logger.debug(`[hosting] had trouble parsing location header, but this may be okay: "${location}"`);
}
}
for (const key of Object.keys(resHeaders)) {
const value = resHeaders[key];
if (key === "set-cookie") {
if (typeof proxyRes.response.headers.getSetCookie === "function") {
res.setHeader(key, proxyRes.response.headers.getSetCookie());
}
else {
res.setHeader(key, value);
}
}
else {
res.setHeader(key, value);
}
}
res.statusCode = proxyRes.status;
proxyRes.body.pipe(res);
};
}
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);
};
}