@fleek-platform/next-on-fleek
Version:
`@fleek-platform/next-on-fleek` is a CLI tool that you can use to build and develop [Next.js](https://nextjs.org/) applications so that they can run on [Fleek Functions](https://fleek.xyz/docs/platform/fleek-functions/).
137 lines (126 loc) • 4.7 kB
text/typescript
import type { MatchPCREResult } from './pcre';
import { applyPCREMatches } from './pcre';
/**
* Applies a set of headers to a response.
*
* If the header key is `set-cookie`, the value will be appended. Otherwise, the value will be set
* and overridden to prevent duplicates which sometimes happens with headers like `x-matched-path`.
*
* @param target Headers object to apply to.
* @param source Headers to apply.
* @param pcreMatch PCRE match result to apply to header values.
*/
export function applyHeaders(
target: Headers,
source: Record<string, string> | Headers,
pcreMatch?: MatchPCREResult,
): void {
const entries =
source instanceof Headers ? source.entries() : Object.entries(source);
for (const [key, value] of entries) {
const lowerKey = key.toLowerCase();
const newValue = pcreMatch?.match
? applyPCREMatches(value, pcreMatch.match, pcreMatch.captureGroupKeys)
: value;
if (lowerKey === 'set-cookie') {
target.append(lowerKey, newValue);
} else {
target.set(lowerKey, newValue);
}
}
}
/**
* Checks if a string is an URL.
*
* @param url String to check.
* @returns Whether the string is an URL.
*/
export function isUrl(url: string): boolean {
return /^https?:\/\//.test(url);
}
/**
* Merges search params from one URLSearchParams object to another.
*
* Only appends the parameter if the target does not contain it, or if the value is different and not undefined.
*
* For params prefixed with `nxtP`, it also sets the param without the prefix if it does not exist.
* The `nxtP` prefix indicates that it is for Next.js dynamic route parameters. In some cases,
* Next.js fails to derive the correct route parameters and so we need to set them manually.
* https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/constants.ts#L3
*
* For params prefixed with `nxtI`, this is a route intercept. It sets the param without the prefix,
* and removes any intercepts from the param's value. This is so that the route intercept is able
* to have the correct route parameters for the page.
* https://github.com/vercel/next.js/blob/cdf2b79ea/packages/next/src/shared/lib/router/utils/route-regex.ts#L6
*
* @param target Target that search params will be applied to.
* @param source Source search params to apply to the target.
*/
export function applySearchParams(
target: URLSearchParams,
source: URLSearchParams,
) {
for (const [key, value] of source.entries()) {
const nxtParamMatch = /^nxtP(.+)$/.exec(key);
const nxtInterceptMatch = /^nxtI(.+)$/.exec(key);
if (nxtParamMatch?.[1]) {
target.set(key, value);
target.set(nxtParamMatch[1], value);
} else if (nxtInterceptMatch?.[1]) {
target.set(nxtInterceptMatch[1], value.replace(/(\(\.+\))+/, ''));
} else if (
!target.has(key) ||
(!!value && !target.getAll(key).includes(value))
) {
target.append(key, value);
}
}
}
/**
* Creates a new Request object with the same body, headers, and search params as the original.
*
* Replaces the URL with the given path, stripping the `.html` extension and `/index.html` for
* asset matching.
* https://developers.cloudflare.com/pages/platform/serving-pages/#route-matching
*
* @param req Request object to re-create.
* @param path URL to use for the new Request object.
* @returns A new Request object with the same body and headers as the original.
*/
export function createRouteRequest(req: Request, path: string) {
const newUrl = new URL(path, req.url);
applySearchParams(newUrl.searchParams, new URL(req.url).searchParams);
newUrl.pathname = newUrl.pathname
.replace(/\/index.html$/, '/')
.replace(/\.html$/, '');
return new Request(newUrl, req);
}
/**
* Creates a new Response object with the same body and headers as the original.
*
* Useful when the response object may be immutable.
*
* @param resp Response object to re-create.
* @returns A new Response object with the same body and headers.
*/
export function createMutableResponse(resp: Response) {
return new Response(resp.body, resp);
}
/**
* Parses the Accept-Language header value and returns an array of locales sorted by quality.
*
* @param headerValue Accept-Language header value.
* @returns Array of locales sorted by quality.
*/
export function parseAcceptLanguage(headerValue: string): string[] {
return headerValue
.split(',')
.map(val => {
const [lang, qual] = val.split(';') as [string, string | undefined];
const quality = parseFloat((qual ?? 'q=1').replace(/q *= */gi, ''));
return [lang.trim(), isNaN(quality) ? 1 : quality] as [string, number];
})
.sort((a, b) => b[1] - a[1])
.map(([locale]) => (locale === '*' || locale === '' ? [] : locale))
.flat();
}