Skip to content

Commit 498dbd0

Browse files
committed
fix: use origin headers
1 parent 9966636 commit 498dbd0

File tree

3 files changed

+99
-59
lines changed

3 files changed

+99
-59
lines changed

apps/insights/src/config/server.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import "server-only";
88
/**
99
* Throw if the env var `key` is not set (at either runtime or build time).
1010
*/
11-
const demand = (key: string): string => {
11+
export const demand = (key: string): string => {
1212
const value = process.env[key];
1313
if (value === undefined || value === "") {
1414
throw new MissingEnvironmentError(key);
@@ -70,18 +70,11 @@ export function getRedis(): Redis {
7070
return redisClient;
7171
}
7272

73-
export const PUBLIC_URL = (() => {
74-
if (IS_PRODUCTION_SERVER) {
75-
const productionUrl = demand("VERCEL_PROJECT_PRODUCTION_URL");
76-
return `https://${productionUrl}`;
77-
} else if (IS_PREVIEW_SERVER) {
78-
const previewUrl = demand("VERCEL_URL");
79-
return `https://${previewUrl}`;
80-
} else {
81-
return `http://localhost:3003`;
82-
}
83-
})();
84-
8573
export const VERCEL_AUTOMATION_BYPASS_SECRET = demand(
8674
"VERCEL_AUTOMATION_BYPASS_SECRET",
8775
);
76+
77+
export const VERCEL_REQUEST_HEADERS = {
78+
// this is a way to bypass vercel protection for the internal api route
79+
"x-vercel-protection-bypass": VERCEL_AUTOMATION_BYPASS_SECRET,
80+
};

apps/insights/src/server/pyth.ts

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
import { parse } from "superjson";
22
import { z } from "zod";
33

4-
import { PUBLIC_URL, VERCEL_AUTOMATION_BYPASS_SECRET } from "../config/server";
4+
import { VERCEL_REQUEST_HEADERS } from "../config/server";
55
import { Cluster, ClusterToName, priceFeedsSchema } from "../services/pyth";
6+
import { absoluteUrl } from "../utils/absolute-url";
67
import { DEFAULT_CACHE_TTL } from "../utils/cache";
78

89
export async function getPublishersForFeedRequest(
910
cluster: Cluster,
1011
symbol: string,
1112
) {
12-
const data = await fetch(
13-
`${PUBLIC_URL}/api/pyth/get-publishers/${encodeURIComponent(symbol)}?cluster=${ClusterToName[cluster]}`,
14-
{
15-
next: {
16-
revalidate: DEFAULT_CACHE_TTL,
17-
},
18-
headers: {
19-
// this is a way to bypass vercel protection for the internal api route
20-
"x-vercel-protection-bypass": VERCEL_AUTOMATION_BYPASS_SECRET,
21-
},
22-
},
13+
const url = await absoluteUrl(
14+
`/api/pyth/get-publishers/${encodeURIComponent(symbol)}`,
2315
);
16+
url.searchParams.set("cluster", ClusterToName[cluster]);
17+
18+
const data = await fetch(url, {
19+
next: {
20+
revalidate: DEFAULT_CACHE_TTL,
21+
},
22+
headers: VERCEL_REQUEST_HEADERS,
23+
});
2424
const parsedData: unknown = await data.json();
2525
return z.array(z.string()).parse(parsedData);
2626
}
@@ -29,36 +29,33 @@ export async function getFeedsForPublisherRequest(
2929
cluster: Cluster,
3030
publisher: string,
3131
) {
32-
const data = await fetch(
33-
`${PUBLIC_URL}/api/pyth/get-feeds-for-publisher/${encodeURIComponent(publisher)}?cluster=${ClusterToName[cluster]}`,
34-
{
35-
next: {
36-
revalidate: DEFAULT_CACHE_TTL,
37-
},
38-
headers: {
39-
// this is a way to bypass vercel protection for the internal api route
40-
"x-vercel-protection-bypass": VERCEL_AUTOMATION_BYPASS_SECRET,
41-
},
42-
},
32+
const url = await absoluteUrl(
33+
`/api/pyth/get-feeds-for-publisher/${encodeURIComponent(publisher)}`,
4334
);
35+
url.searchParams.set("cluster", ClusterToName[cluster]);
36+
37+
const data = await fetch(url, {
38+
next: {
39+
revalidate: DEFAULT_CACHE_TTL,
40+
},
41+
headers: VERCEL_REQUEST_HEADERS,
42+
});
4443
const rawData = await data.text();
4544
const parsedData = parse(rawData);
4645
return priceFeedsSchema.parse(parsedData);
4746
}
4847

4948
export const getFeedsRequest = async (cluster: Cluster) => {
50-
const data = await fetch(
51-
`${PUBLIC_URL}/api/pyth/get-feeds?cluster=${ClusterToName[cluster]}&excludePriceComponents=true`,
52-
{
53-
next: {
54-
revalidate: DEFAULT_CACHE_TTL,
55-
},
56-
headers: {
57-
// this is a way to bypass vercel protection for the internal api route
58-
"x-vercel-protection-bypass": VERCEL_AUTOMATION_BYPASS_SECRET,
59-
},
49+
const url = await absoluteUrl(`/api/pyth/get-feeds`);
50+
url.searchParams.set("cluster", ClusterToName[cluster]);
51+
url.searchParams.set("excludePriceComponents", "true");
52+
53+
const data = await fetch(url, {
54+
next: {
55+
revalidate: DEFAULT_CACHE_TTL,
6056
},
61-
);
57+
headers: VERCEL_REQUEST_HEADERS,
58+
});
6259
const rawData = await data.text();
6360
const parsedData = parse(rawData);
6461

@@ -74,19 +71,18 @@ export const getFeedForSymbolRequest = async ({
7471
}: {
7572
symbol: string;
7673
cluster?: Cluster;
77-
}): Promise<z.infer<typeof priceFeedsSchema>[0] | undefined> => {
78-
const data = await fetch(
79-
`${PUBLIC_URL}/api/pyth/get-feeds/${encodeURIComponent(symbol)}?cluster=${ClusterToName[cluster]}`,
80-
{
81-
next: {
82-
revalidate: DEFAULT_CACHE_TTL,
83-
},
84-
headers: {
85-
// this is a way to bypass vercel protection for the internal api route
86-
"x-vercel-protection-bypass": VERCEL_AUTOMATION_BYPASS_SECRET,
87-
},
88-
},
74+
}): Promise<z.infer<typeof priceFeedsSchema.element> | undefined> => {
75+
const url = await absoluteUrl(
76+
`/api/pyth/get-feeds/${encodeURIComponent(symbol)}`,
8977
);
78+
url.searchParams.set("cluster", ClusterToName[cluster]);
79+
80+
const data = await fetch(url, {
81+
next: {
82+
revalidate: DEFAULT_CACHE_TTL,
83+
},
84+
headers: VERCEL_REQUEST_HEADERS,
85+
});
9086

9187
if (!data.ok) {
9288
return undefined;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { headers } from "next/headers";
2+
3+
import {
4+
demand,
5+
IS_PREVIEW_SERVER,
6+
IS_PRODUCTION_SERVER,
7+
} from "../config/server";
8+
9+
/**
10+
* Returns an absolute URL for the given pathname.
11+
*
12+
* @param pathname - The pathname to make absolute.
13+
* @returns A URL object with the absolute URL.
14+
*/
15+
export async function absoluteUrl(pathname: string) {
16+
let origin: string | undefined;
17+
18+
try {
19+
// note that using headers() makes the context dynamic (disables full static optimization)
20+
const nextHeaders = await headers();
21+
// this can be comma-separated, so we take the first one
22+
const xfHost = nextHeaders.get("x-forwarded-host")?.split(",")[0]?.trim();
23+
const host = xfHost ?? nextHeaders.get("host") ?? undefined;
24+
25+
// this can be comma-separated, so we take the first one
26+
const proto =
27+
nextHeaders.get("x-forwarded-proto")?.split(",")[0]?.trim() ??
28+
(host?.startsWith("localhost") ? "http" : "https");
29+
30+
// if we have a host and a proto, we can construct the origin
31+
if (host && proto) origin = `${proto}://${host}`;
32+
} catch {
33+
// headers() is unavailable
34+
}
35+
36+
// Fallbacks for requests where headers() is not available
37+
if (!origin) {
38+
if (IS_PRODUCTION_SERVER) {
39+
const productionUrl = demand("VERCEL_PROJECT_PRODUCTION_URL");
40+
origin = `https://${productionUrl}`;
41+
} else if (IS_PREVIEW_SERVER) {
42+
const previewUrl = demand("VERCEL_URL");
43+
origin = `https://${previewUrl}`;
44+
} else {
45+
origin = "http://localhost:3003";
46+
}
47+
}
48+
49+
const path = pathname.startsWith("/") ? pathname : `/${pathname}`;
50+
return new URL(origin + path);
51+
}

0 commit comments

Comments
 (0)