Quellcode durchsuchen

Merge pull request #2961 from pyth-network/fix/headers-invariant

fix: added headers invariant
Alexandru Cambose vor 3 Monaten
Ursprung
Commit
e78fec7a6f

+ 1 - 1
apps/insights/src/utils/cache.ts → apps/insights/src/cache.ts

@@ -2,7 +2,7 @@ import type { Cache as ACDCache } from "async-cache-dedupe";
 import { createCache } from "async-cache-dedupe";
 import { stringify, parse } from "superjson";
 
-import { getRedis } from "../config/server";
+import { getRedis } from "./config/server";
 
 const transformer = {
   serialize: stringify,

+ 30 - 0
apps/insights/src/get-host.ts

@@ -0,0 +1,30 @@
+import { headers } from "next/headers";
+
+/**
+ * Returns the host of the current request.
+ *
+ * @returns The host of the current request.
+ */
+export const getHost = async () => {
+  const nextHeaders = await headers();
+  const xfHost = nextHeaders.get("x-forwarded-host")?.split(",")[0]?.trim();
+  const host = xfHost ?? nextHeaders.get("host") ?? undefined;
+  if (host === undefined) {
+    throw new NoHostError();
+  } else {
+    const proto =
+      nextHeaders.get("x-forwarded-proto")?.split(",")[0]?.trim() ??
+      (host.startsWith("localhost") ? "http" : "https");
+
+    return `${proto}://${host}`;
+  }
+};
+
+class NoHostError extends Error {
+  constructor() {
+    super(
+      "Request had neither an `x-forwarded-host` header nor a `host` header",
+    );
+    this.name = "NoHostError";
+  }
+}

+ 9 - 6
apps/insights/src/server/pyth.ts

@@ -1,17 +1,18 @@
 import { parse } from "superjson";
 import { z } from "zod";
 
+import { DEFAULT_CACHE_TTL } from "../cache";
 import { VERCEL_REQUEST_HEADERS } from "../config/server";
+import { getHost } from "../get-host";
 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 url = await absoluteUrl(
+  const url = new URL(
     `/api/pyth/get-publishers/${encodeURIComponent(symbol)}`,
+    await getHost(),
   );
   url.searchParams.set("cluster", ClusterToName[cluster]);
 
@@ -29,8 +30,9 @@ export async function getFeedsForPublisherRequest(
   cluster: Cluster,
   publisher: string,
 ) {
-  const url = await absoluteUrl(
+  const url = new URL(
     `/api/pyth/get-feeds-for-publisher/${encodeURIComponent(publisher)}`,
+    await getHost(),
   );
   url.searchParams.set("cluster", ClusterToName[cluster]);
 
@@ -46,7 +48,7 @@ export async function getFeedsForPublisherRequest(
 }
 
 export const getFeedsRequest = async (cluster: Cluster) => {
-  const url = await absoluteUrl(`/api/pyth/get-feeds`);
+  const url = new URL(`/api/pyth/get-feeds`, await getHost());
   url.searchParams.set("cluster", ClusterToName[cluster]);
   url.searchParams.set("excludePriceComponents", "true");
 
@@ -72,8 +74,9 @@ export const getFeedForSymbolRequest = async ({
   symbol: string;
   cluster?: Cluster;
 }): Promise<z.infer<typeof priceFeedsSchema.element> | undefined> => {
-  const url = await absoluteUrl(
+  const url = new URL(
     `/api/pyth/get-feeds/${encodeURIComponent(symbol)}`,
+    await getHost(),
   );
   url.searchParams.set("cluster", ClusterToName[cluster]);
 

+ 1 - 1
apps/insights/src/services/clickhouse.ts

@@ -5,8 +5,8 @@ import type { ZodSchema, ZodTypeDef } from "zod";
 import { z } from "zod";
 
 import { Cluster, ClusterToName } from "./pyth";
+import { redisCache } from "../cache";
 import { CLICKHOUSE } from "../config/server";
-import { redisCache } from "../utils/cache";
 
 const client = createClient(CLICKHOUSE);
 

+ 1 - 1
apps/insights/src/services/pyth/get-feeds.ts

@@ -1,6 +1,6 @@
 import { Cluster, priceFeedsSchema } from ".";
 import { getPythMetadataCached } from "./get-metadata";
-import { redisCache } from "../../utils/cache";
+import { redisCache } from "../../cache";
 
 const _getFeeds = async (cluster: Cluster) => {
   const unfilteredData = await getPythMetadataCached(cluster);

+ 1 - 1
apps/insights/src/services/pyth/get-metadata.ts

@@ -1,5 +1,5 @@
 import { clients, Cluster } from ".";
-import { memoryOnlyCache } from "../../utils/cache";
+import { memoryOnlyCache } from "../../cache";
 
 const getPythMetadata = async (cluster: Cluster) => {
   return clients[cluster].getData();

+ 1 - 1
apps/insights/src/services/pyth/get-publishers-for-cluster.ts

@@ -1,6 +1,6 @@
 import { Cluster } from ".";
 import { getPythMetadataCached } from "./get-metadata";
-import { redisCache } from "../../utils/cache";
+import { redisCache } from "../../cache";
 
 const _getPublishersForCluster = async (cluster: Cluster) => {
   const data = await getPythMetadataCached(cluster);

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

@@ -1,51 +0,0 @@
-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);
-}