No Description
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.

proxy.js 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.errorRequestHandler = exports.proxyRequestHandler = void 0;
  4. const lodash_1 = require("lodash");
  5. const node_fetch_1 = require("node-fetch");
  6. const stream_1 = require("stream");
  7. const url_1 = require("url");
  8. const apiv2_1 = require("../apiv2");
  9. const error_1 = require("../error");
  10. const logger_1 = require("../logger");
  11. const REQUIRED_VARY_VALUES = ["Accept-Encoding", "Authorization", "Cookie"];
  12. function makeVary(vary = "") {
  13. if (!vary) {
  14. return "Accept-Encoding, Authorization, Cookie";
  15. }
  16. const varies = vary.split(/, ?/).map((v) => {
  17. return v
  18. .split("-")
  19. .map((part) => (0, lodash_1.capitalize)(part))
  20. .join("-");
  21. });
  22. REQUIRED_VARY_VALUES.forEach((requiredVary) => {
  23. if (!(0, lodash_1.includes)(varies, requiredVary)) {
  24. varies.push(requiredVary);
  25. }
  26. });
  27. return varies.join(", ");
  28. }
  29. function proxyRequestHandler(url, rewriteIdentifier, options = {}) {
  30. return async (req, res, next) => {
  31. var _a;
  32. logger_1.logger.info(`[hosting] Rewriting ${req.url} to ${url} for ${rewriteIdentifier}`);
  33. const cookie = req.headers.cookie || "";
  34. const sessionCookie = cookie.split(/; ?/).find((c) => {
  35. return c.trim().startsWith("__session=");
  36. });
  37. const u = new url_1.URL(url + req.url);
  38. const c = new apiv2_1.Client({ urlPrefix: u.origin, auth: false });
  39. let passThrough;
  40. if (req.method && !["GET", "HEAD"].includes(req.method)) {
  41. passThrough = new stream_1.PassThrough();
  42. req.pipe(passThrough);
  43. }
  44. const headers = new node_fetch_1.Headers({
  45. "X-Forwarded-Host": req.headers.host || "",
  46. "X-Original-Url": req.url || "",
  47. Pragma: "no-cache",
  48. "Cache-Control": "no-cache, no-store",
  49. Cookie: sessionCookie || "",
  50. });
  51. const headersToSkip = new Set(["host"]);
  52. for (const key of Object.keys(req.headers)) {
  53. if (headersToSkip.has(key)) {
  54. continue;
  55. }
  56. const value = req.headers[key];
  57. if (value === undefined) {
  58. headers.delete(key);
  59. }
  60. else if (Array.isArray(value)) {
  61. headers.delete(key);
  62. for (const v of value) {
  63. headers.append(key, v);
  64. }
  65. }
  66. else {
  67. headers.set(key, value);
  68. }
  69. }
  70. let proxyRes;
  71. try {
  72. proxyRes = await c.request({
  73. method: (req.method || "GET"),
  74. path: u.pathname,
  75. queryParams: u.searchParams,
  76. headers,
  77. resolveOnHTTPError: true,
  78. responseType: "stream",
  79. redirect: "manual",
  80. body: passThrough,
  81. timeout: 60000,
  82. compress: false,
  83. });
  84. }
  85. catch (err) {
  86. const isAbortError = err instanceof error_1.FirebaseError && ((_a = err.original) === null || _a === void 0 ? void 0 : _a.name.includes("AbortError"));
  87. const isTimeoutError = err instanceof error_1.FirebaseError &&
  88. err.original instanceof node_fetch_1.FetchError &&
  89. err.original.code === "ETIMEDOUT";
  90. const isSocketTimeoutError = err instanceof error_1.FirebaseError &&
  91. err.original instanceof node_fetch_1.FetchError &&
  92. err.original.code === "ESOCKETTIMEDOUT";
  93. if (isAbortError || isTimeoutError || isSocketTimeoutError) {
  94. res.statusCode = 504;
  95. return res.end("Timed out waiting for function to respond.\n");
  96. }
  97. res.statusCode = 500;
  98. return res.end(`An internal error occurred while proxying for ${rewriteIdentifier}\n`);
  99. }
  100. if (proxyRes.status === 404) {
  101. const cascade = proxyRes.response.headers.get("x-cascade");
  102. if (options.forceCascade || (cascade && cascade.toUpperCase() === "PASS")) {
  103. return next();
  104. }
  105. }
  106. if (!proxyRes.response.headers.get("cache-control")) {
  107. proxyRes.response.headers.set("cache-control", "private");
  108. }
  109. const cc = proxyRes.response.headers.get("cache-control");
  110. if (cc && !cc.includes("private")) {
  111. proxyRes.response.headers.delete("set-cookie");
  112. }
  113. proxyRes.response.headers.set("vary", makeVary(proxyRes.response.headers.get("vary")));
  114. const location = proxyRes.response.headers.get("location");
  115. if (location) {
  116. try {
  117. const locationURL = new url_1.URL(location);
  118. if (locationURL.origin === u.origin) {
  119. const unborkedLocation = location.replace(locationURL.origin, "");
  120. proxyRes.response.headers.set("location", unborkedLocation);
  121. }
  122. }
  123. catch (e) {
  124. logger_1.logger.debug(`[hosting] had trouble parsing location header, but this may be okay: "${location}"`);
  125. }
  126. }
  127. for (const [key, value] of Object.entries(proxyRes.response.headers.raw())) {
  128. res.setHeader(key, value);
  129. }
  130. res.statusCode = proxyRes.status;
  131. proxyRes.response.body.pipe(res);
  132. };
  133. }
  134. exports.proxyRequestHandler = proxyRequestHandler;
  135. function errorRequestHandler(error) {
  136. return (req, res) => {
  137. res.statusCode = 500;
  138. const out = `A problem occurred while trying to handle a proxied rewrite: ${error}`;
  139. logger_1.logger.error(out);
  140. res.end(out);
  141. };
  142. }
  143. exports.errorRequestHandler = errorRequestHandler;