-
Notifications
You must be signed in to change notification settings - Fork 26.3k
/
redirect.ts
161 lines (145 loc) · 5.35 KB
/
redirect.ts
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import { requestAsyncStorage } from './request-async-storage.external'
import type { ResponseCookies } from '../../server/web/spec-extension/cookies'
import { actionAsyncStorage } from './action-async-storage.external'
import { RedirectStatusCode } from './redirect-status-code'
const REDIRECT_ERROR_CODE = 'NEXT_REDIRECT'
export enum RedirectType {
push = 'push',
replace = 'replace',
}
export type RedirectError<U extends string> = Error & {
digest: `${typeof REDIRECT_ERROR_CODE};${RedirectType};${U};${RedirectStatusCode};`
mutableCookies: ResponseCookies
}
export function getRedirectError(
url: string,
type: RedirectType,
statusCode: RedirectStatusCode = RedirectStatusCode.TemporaryRedirect
): RedirectError<typeof url> {
const error = new Error(REDIRECT_ERROR_CODE) as RedirectError<typeof url>
error.digest = `${REDIRECT_ERROR_CODE};${type};${url};${statusCode};`
const requestStore = requestAsyncStorage.getStore()
if (requestStore) {
error.mutableCookies = requestStore.mutableCookies
}
return error
}
/**
* This function allows you to redirect the user to another URL. It can be used in
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
*
* - In a Server Component, this will insert a meta tag to redirect the user to the target page.
* - In a Route Handler or Server Action, it will serve a 307/303 to the caller.
*
* Read more: [Next.js Docs: `redirect`](https://nextjs.org/docs/app/api-reference/functions/redirect)
*/
export function redirect(
/** The URL to redirect to */
url: string,
type: RedirectType = RedirectType.replace
): never {
const actionStore = actionAsyncStorage.getStore()
throw getRedirectError(
url,
type,
// If we're in an action, we want to use a 303 redirect
// as we don't want the POST request to follow the redirect,
// as it could result in erroneous re-submissions.
actionStore?.isAction
? RedirectStatusCode.SeeOther
: RedirectStatusCode.TemporaryRedirect
)
}
/**
* This function allows you to redirect the user to another URL. It can be used in
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
*
* - In a Server Component, this will insert a meta tag to redirect the user to the target page.
* - In a Route Handler or Server Action, it will serve a 308/303 to the caller.
*
* Read more: [Next.js Docs: `redirect`](https://nextjs.org/docs/app/api-reference/functions/redirect)
*/
export function permanentRedirect(
/** The URL to redirect to */
url: string,
type: RedirectType = RedirectType.replace
): never {
const actionStore = actionAsyncStorage.getStore()
throw getRedirectError(
url,
type,
// If we're in an action, we want to use a 303 redirect
// as we don't want the POST request to follow the redirect,
// as it could result in erroneous re-submissions.
actionStore?.isAction
? RedirectStatusCode.SeeOther
: RedirectStatusCode.PermanentRedirect
)
}
/**
* Checks an error to determine if it's an error generated by the
* `redirect(url)` helper.
*
* @param error the error that may reference a redirect error
* @returns true if the error is a redirect error
*/
export function isRedirectError<U extends string>(
error: unknown
): error is RedirectError<U> {
if (
typeof error !== 'object' ||
error === null ||
!('digest' in error) ||
typeof error.digest !== 'string'
) {
return false
}
const digest = error.digest.split(';')
const [errorCode, type] = digest
const destination = digest.slice(2, -2).join(';')
const status = digest.at(-2)
const statusCode = Number(status)
return (
errorCode === REDIRECT_ERROR_CODE &&
(type === 'replace' || type === 'push') &&
typeof destination === 'string' &&
!isNaN(statusCode) &&
statusCode in RedirectStatusCode
)
}
/**
* Returns the encoded URL from the error if it's a RedirectError, null
* otherwise. Note that this does not validate the URL returned.
*
* @param error the error that may be a redirect error
* @return the url if the error was a redirect error
*/
export function getURLFromRedirectError<U extends string>(
error: RedirectError<U>
): U
export function getURLFromRedirectError(error: unknown): string | null {
if (!isRedirectError(error)) return null
// Slices off the beginning of the digest that contains the code and the
// separating ';'.
return error.digest.split(';').slice(2, -2).join(';')
}
export function getRedirectTypeFromError<U extends string>(
error: RedirectError<U>
): RedirectType {
if (!isRedirectError(error)) {
throw new Error('Not a redirect error')
}
return error.digest.split(';', 2)[1] as RedirectType
}
export function getRedirectStatusCodeFromError<U extends string>(
error: RedirectError<U>
): number {
if (!isRedirectError(error)) {
throw new Error('Not a redirect error')
}
return Number(error.digest.split(';').at(-2))
}