Sfoglia il codice sorgente

fix: use origin headers

Alexandru Cambose 3 mesi fa
parent
commit
498dbd0316

+ 6 - 13
apps/insights/src/config/server.ts

@@ -8,7 +8,7 @@ import "server-only";
 /**
  * Throw if the env var `key` is not set (at either runtime or build time).
  */
-const demand = (key: string): string => {
+export const demand = (key: string): string => {
   const value = process.env[key];
   if (value === undefined || value === "") {
     throw new MissingEnvironmentError(key);
@@ -70,18 +70,11 @@ export function getRedis(): Redis {
   return redisClient;
 }
 
-export const PUBLIC_URL = (() => {
-  if (IS_PRODUCTION_SERVER) {
-    const productionUrl = demand("VERCEL_PROJECT_PRODUCTION_URL");
-    return `https://${productionUrl}`;
-  } else if (IS_PREVIEW_SERVER) {
-    const previewUrl = demand("VERCEL_URL");
-    return `https://${previewUrl}`;
-  } else {
-    return `http://localhost:3003`;
-  }
-})();
-
 export const VERCEL_AUTOMATION_BYPASS_SECRET = demand(
   "VERCEL_AUTOMATION_BYPASS_SECRET",
 );
+
+export const VERCEL_REQUEST_HEADERS = {
+  // this is a way to bypass vercel protection for the internal api route
+  "x-vercel-protection-bypass": VERCEL_AUTOMATION_BYPASS_SECRET,
+};

+ 42 - 46
apps/insights/src/server/pyth.ts

@@ -1,26 +1,26 @@
 import { parse } from "superjson";
 import { z } from "zod";
 
-import { PUBLIC_URL, VERCEL_AUTOMATION_BYPASS_SECRET } from "../config/server";
+import { VERCEL_REQUEST_HEADERS } from "../config/server";
 import { Cluster, ClusterToName, priceFeedsSchema } from "../services/pyth";
+import { absoluteUrl } from "../utils/absolute-url";
 import { DEFAULT_CACHE_TTL } from "../utils/cache";
 
 export async function getPublishersForFeedRequest(
   cluster: Cluster,
   symbol: string,
 ) {
-  const data = await fetch(
-    `${PUBLIC_URL}/api/pyth/get-publishers/${encodeURIComponent(symbol)}?cluster=${ClusterToName[cluster]}`,
-    {
-      next: {
-        revalidate: DEFAULT_CACHE_TTL,
-      },
-      headers: {
-        // this is a way to bypass vercel protection for the internal api route
-        "x-vercel-protection-bypass": VERCEL_AUTOMATION_BYPASS_SECRET,
-      },
-    },
+  const url = await absoluteUrl(
+    `/api/pyth/get-publishers/${encodeURIComponent(symbol)}`,
   );
+  url.searchParams.set("cluster", ClusterToName[cluster]);
+
+  const data = await fetch(url, {
+    next: {
+      revalidate: DEFAULT_CACHE_TTL,
+    },
+    headers: VERCEL_REQUEST_HEADERS,
+  });
   const parsedData: unknown = await data.json();
   return z.array(z.string()).parse(parsedData);
 }
@@ -29,36 +29,33 @@ export async function getFeedsForPublisherRequest(
   cluster: Cluster,
   publisher: string,
 ) {
-  const data = await fetch(
-    `${PUBLIC_URL}/api/pyth/get-feeds-for-publisher/${encodeURIComponent(publisher)}?cluster=${ClusterToName[cluster]}`,
-    {
-      next: {
-        revalidate: DEFAULT_CACHE_TTL,
-      },
-      headers: {
-        // this is a way to bypass vercel protection for the internal api route
-        "x-vercel-protection-bypass": VERCEL_AUTOMATION_BYPASS_SECRET,
-      },
-    },
+  const url = await absoluteUrl(
+    `/api/pyth/get-feeds-for-publisher/${encodeURIComponent(publisher)}`,
   );
+  url.searchParams.set("cluster", ClusterToName[cluster]);
+
+  const data = await fetch(url, {
+    next: {
+      revalidate: DEFAULT_CACHE_TTL,
+    },
+    headers: VERCEL_REQUEST_HEADERS,
+  });
   const rawData = await data.text();
   const parsedData = parse(rawData);
   return priceFeedsSchema.parse(parsedData);
 }
 
 export const getFeedsRequest = async (cluster: Cluster) => {
-  const data = await fetch(
-    `${PUBLIC_URL}/api/pyth/get-feeds?cluster=${ClusterToName[cluster]}&excludePriceComponents=true`,
-    {
-      next: {
-        revalidate: DEFAULT_CACHE_TTL,
-      },
-      headers: {
-        // this is a way to bypass vercel protection for the internal api route
-        "x-vercel-protection-bypass": VERCEL_AUTOMATION_BYPASS_SECRET,
-      },
+  const url = await absoluteUrl(`/api/pyth/get-feeds`);
+  url.searchParams.set("cluster", ClusterToName[cluster]);
+  url.searchParams.set("excludePriceComponents", "true");
+
+  const data = await fetch(url, {
+    next: {
+      revalidate: DEFAULT_CACHE_TTL,
     },
-  );
+    headers: VERCEL_REQUEST_HEADERS,
+  });
   const rawData = await data.text();
   const parsedData = parse(rawData);
 
@@ -74,19 +71,18 @@ export const getFeedForSymbolRequest = async ({
 }: {
   symbol: string;
   cluster?: Cluster;
-}): Promise<z.infer<typeof priceFeedsSchema>[0] | undefined> => {
-  const data = await fetch(
-    `${PUBLIC_URL}/api/pyth/get-feeds/${encodeURIComponent(symbol)}?cluster=${ClusterToName[cluster]}`,
-    {
-      next: {
-        revalidate: DEFAULT_CACHE_TTL,
-      },
-      headers: {
-        // this is a way to bypass vercel protection for the internal api route
-        "x-vercel-protection-bypass": VERCEL_AUTOMATION_BYPASS_SECRET,
-      },
-    },
+}): Promise<z.infer<typeof priceFeedsSchema.element> | undefined> => {
+  const url = await absoluteUrl(
+    `/api/pyth/get-feeds/${encodeURIComponent(symbol)}`,
   );
+  url.searchParams.set("cluster", ClusterToName[cluster]);
+
+  const data = await fetch(url, {
+    next: {
+      revalidate: DEFAULT_CACHE_TTL,
+    },
+    headers: VERCEL_REQUEST_HEADERS,
+  });
 
   if (!data.ok) {
     return undefined;

+ 51 - 0
apps/insights/src/utils/absolute-url.ts

@@ -0,0 +1,51 @@
+import { headers } from "next/headers";
+
+import {
+  demand,
+  IS_PREVIEW_SERVER,
+  IS_PRODUCTION_SERVER,
+} from "../config/server";
+
+/**
+ * Returns an absolute URL for the given pathname.
+ *
+ * @param pathname - The pathname to make absolute.
+ * @returns A URL object with the absolute URL.
+ */
+export async function absoluteUrl(pathname: string) {
+  let origin: string | undefined;
+
+  try {
+    // note that using headers() makes the context dynamic (disables full static optimization)
+    const nextHeaders = await headers();
+    // this can be comma-separated, so we take the first one
+    const xfHost = nextHeaders.get("x-forwarded-host")?.split(",")[0]?.trim();
+    const host = xfHost ?? nextHeaders.get("host") ?? undefined;
+
+    // this can be comma-separated, so we take the first one
+    const proto =
+      nextHeaders.get("x-forwarded-proto")?.split(",")[0]?.trim() ??
+      (host?.startsWith("localhost") ? "http" : "https");
+
+    // if we have a host and a proto, we can construct the origin
+    if (host && proto) origin = `${proto}://${host}`;
+  } catch {
+    // headers() is unavailable
+  }
+
+  // Fallbacks for requests where headers() is not available
+  if (!origin) {
+    if (IS_PRODUCTION_SERVER) {
+      const productionUrl = demand("VERCEL_PROJECT_PRODUCTION_URL");
+      origin = `https://${productionUrl}`;
+    } else if (IS_PREVIEW_SERVER) {
+      const previewUrl = demand("VERCEL_URL");
+      origin = `https://${previewUrl}`;
+    } else {
+      origin = "http://localhost:3003";
+    }
+  }
+
+  const path = pathname.startsWith("/") ? pathname : `/${pathname}`;
+  return new URL(origin + path);
+}