-
Notifications
You must be signed in to change notification settings - Fork 100
/
Copy pathpages-router.tsx
140 lines (123 loc) · 4.02 KB
/
pages-router.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import type { Metadata, NextApiRequest, NextApiResponse } from "next";
import { createFrames as coreCreateFrames } from "../core";
import {
createReadableStreamFromReadable,
writeReadableStreamToWritable,
} from "../lib/stream-pump";
export { Button, type types } from "../core";
import React from "react";
export { fetchMetadata } from "./fetchMetadata";
export const createFrames: typeof coreCreateFrames =
function createFramesForNextJSPagesRouter(options: any) {
const frames = coreCreateFrames(options);
// @ts-expect-error
return function createHandler(handler, handlerOptions) {
const requestHandler = frames(handler, handlerOptions);
return async function handleNextJSApiRequest(
req: NextApiRequest,
res: NextApiResponse
) {
const response = await requestHandler(createRequest(req, res));
await sendResponse(res, response);
};
};
} as unknown as typeof coreCreateFrames;
/**
* Converts metadata returned from fetchMetadata() call to Next.js <Head /> compatible components.
*
* @example
* import { fetchMetadata, metadataToMetaTags } from "frames.js/next/pages-router";
*
* export const getServerSideProps = async function getServerSideProps() {
* return {
* props: {
* metadata: await fetchMetadata(
* new URL("/api", process.env.VERCEL_URL || "http://localhost:3000")
* ),
* },
* };
*
* export default function Page({
* metadata,
* }: InferGetServerSidePropsType<typeof getServerSideProps>) {
* return (
* <>
* <Head>
* <title>Frames.js app</title>
* {metadataToMetaTags(metadata)}
* </Head>
* </>
* );
* }
*/
export function metadataToMetaTags(metadata: NonNullable<Metadata["other"]>) {
return (
<>
{Object.entries(metadata).map(([key, value]) => {
if (typeof value === "string") {
return <meta name={key} key={key} content={value} />;
}
return null;
})}
</>
);
}
function createRequest(req: NextApiRequest, res: NextApiResponse): Request {
// req.hostname doesn't include port information so grab that from
// `X-Forwarded-Host` or `Host`
const xForwardedHost = req.headers["x-forwarded-host"];
const normalizedXForwardedHost = Array.isArray(xForwardedHost)
? xForwardedHost[0]
: xForwardedHost;
let [, hostnamePort] = normalizedXForwardedHost?.split(":") ?? [];
let [, hostPort] = req.headers["host"]?.split(":") ?? [];
let port = hostnamePort || hostPort;
// Use req.hostname here as it respects the "trust proxy" setting
let resolvedHost = `${req.headers["host"]}${!hostPort ? `:${port}` : ""}`;
// Use `req.url` so NextJS is aware of the full path
let url = new URL(
`${"encrypted" in req.socket && req.socket.encrypted ? "https" : "http"}://${resolvedHost}${req.url}`
);
// Abort action/loaders once we can no longer write a response
let controller = new AbortController();
res.on("close", () => controller.abort());
let init: RequestInit = {
method: req.method,
headers: createRequestHeaders(req.headers),
signal: controller.signal,
};
if (req.method !== "GET" && req.method !== "HEAD") {
init.body = createReadableStreamFromReadable(req);
(init as { duplex: "half" }).duplex = "half";
}
return new Request(url.href, init);
}
export function createRequestHeaders(
requestHeaders: NextApiRequest["headers"]
): Headers {
let headers = new Headers();
for (let [key, values] of Object.entries(requestHeaders)) {
if (values) {
if (Array.isArray(values)) {
for (let value of values) {
headers.append(key, value);
}
} else {
headers.set(key, values);
}
}
}
return headers;
}
async function sendResponse(res: NextApiResponse, response: Response) {
res.statusMessage = response.statusText;
res.status(response.status);
for (let [key, value] of response.headers.entries()) {
res.setHeader(key, value);
}
if (response.body) {
await writeReadableStreamToWritable(response.body, res);
} else {
res.end();
}
}