Skip to content

Commit 9defdf7

Browse files
committed
fix: setting cookies in draftmode
1 parent 18569cf commit 9defdf7

File tree

3 files changed

+50
-17
lines changed

3 files changed

+50
-17
lines changed

packages/next/src/server/async-storage/draft-mode-provider.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { IncomingMessage } from 'http'
2-
import type { ReadonlyRequestCookies } from '../web/spec-extension/adapters/request-cookies'
2+
import {
3+
setMutableCookieUnchecked,
4+
type ReadonlyRequestCookies,
5+
} from '../web/spec-extension/adapters/request-cookies'
36
import type { ResponseCookies } from '../web/spec-extension/cookies'
47
import type { BaseNextRequest } from '../base-http'
58
import type { NextRequest } from '../web/spec-extension/request'
@@ -58,7 +61,7 @@ export class DraftModeProvider {
5861
)
5962
}
6063

61-
this._mutableCookies.set({
64+
setMutableCookieUnchecked(this._mutableCookies, {
6265
name: COOKIE_NAME_PRERENDER_BYPASS,
6366
value: this._previewModeId,
6467
httpOnly: true,
@@ -72,7 +75,7 @@ export class DraftModeProvider {
7275
// To delete a cookie, set `expires` to a date in the past:
7376
// https://tools.ietf.org/html/rfc6265#section-4.1.1
7477
// `Max-Age: 0` is not valid, thus ignored, and the cookie is persisted.
75-
this._mutableCookies.set({
78+
setMutableCookieUnchecked(this._mutableCookies, {
7679
name: COOKIE_NAME_PRERENDER_BYPASS,
7780
value: '',
7881
httpOnly: true,

packages/next/src/server/async-storage/with-request-store.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
MutableRequestCookiesAdapter,
2020
RequestCookiesAdapter,
2121
responseCookiesToRequestCookies,
22+
setMutableCookieUnchecked,
2223
type ReadonlyRequestCookies,
2324
} from '../web/spec-extension/adapters/request-cookies'
2425
import { ResponseCookies, RequestCookies } from '../web/spec-extension/cookies'
@@ -99,7 +100,11 @@ function mergeMiddlewareCookies(
99100

100101
// Transfer cookies from ResponseCookies to RequestCookies
101102
for (const cookie of responseCookies.getAll()) {
102-
existingCookies.set(cookie)
103+
if (existingCookies instanceof ResponseCookies) {
104+
setMutableCookieUnchecked(existingCookies, cookie)
105+
} else {
106+
existingCookies.set(cookie)
107+
}
103108
}
104109
}
105110
}

packages/next/src/server/web/spec-extension/adapters/request-cookies.ts

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export class RequestCookiesAdapter {
5353
}
5454

5555
const SYMBOL_MODIFY_COOKIE_VALUES = Symbol.for('next.mutated.cookies')
56+
const SYMBOL_COOKIE_SET_UNCHECKED = Symbol.for('next.cookies.setUnchecked')
5657

5758
export function getModifiedCookieValues(
5859
cookies: ResponseCookies
@@ -67,6 +68,25 @@ export function getModifiedCookieValues(
6768
return modified
6869
}
6970

71+
type SetCookieArgs =
72+
| [key: string, value: string, cookie?: Partial<ResponseCookie>]
73+
| [options: ResponseCookie]
74+
75+
/** Allows setting mutable cookies during render. */
76+
export function setMutableCookieUnchecked(
77+
mutableCookies: ResponseCookies,
78+
...args: SetCookieArgs
79+
) {
80+
const setUnchecked: ResponseCookies['set'] =
81+
// @ts-expect-error
82+
mutableCookies[SYMBOL_COOKIE_SET_UNCHECKED]
83+
if (setUnchecked) {
84+
return setUnchecked(...args)
85+
} else {
86+
return mutableCookies.set(...args)
87+
}
88+
}
89+
7090
export function appendMutableCookies(
7191
headers: Headers,
7292
mutableCookies: ResponseCookies
@@ -132,12 +152,27 @@ export class MutableRequestCookiesAdapter {
132152
}
133153
}
134154

155+
const setUnchecked = (target: ResponseCookies, ...args: SetCookieArgs) => {
156+
modifiedCookies.add(typeof args[0] === 'string' ? args[0] : args[0].name)
157+
try {
158+
return target.set(...args)
159+
} finally {
160+
updateResponseCookies()
161+
}
162+
}
163+
135164
return new Proxy(responseCookies, {
136165
get(target, prop, receiver) {
137166
switch (prop) {
138167
// A special symbol to get the modified cookie values
139168
case SYMBOL_MODIFY_COOKIE_VALUES:
140169
return modifiedValues
170+
// A special symbol to get an unchecked version of .set()
171+
// that allows setting cookies during render
172+
case SYMBOL_COOKIE_SET_UNCHECKED:
173+
return function (...args: SetCookieArgs) {
174+
setUnchecked(target, ...args)
175+
}
141176

142177
// TODO: Throw error if trying to set a cookie after the response
143178
// headers have been set.
@@ -154,21 +189,11 @@ export class MutableRequestCookiesAdapter {
154189
}
155190
}
156191
case 'set':
157-
return function (
158-
...args:
159-
| [key: string, value: string, cookie?: Partial<ResponseCookie>]
160-
| [options: ResponseCookie]
161-
) {
192+
return function (...args: SetCookieArgs) {
162193
ensureCookiesAreStillMutable('cookies().set')
163-
modifiedCookies.add(
164-
typeof args[0] === 'string' ? args[0] : args[0].name
165-
)
166-
try {
167-
return target.set(...args)
168-
} finally {
169-
updateResponseCookies()
170-
}
194+
setUnchecked(target, ...args)
171195
}
196+
172197
default:
173198
return ReflectAdapter.get(target, prop, receiver)
174199
}

0 commit comments

Comments
 (0)