From 6f352357fb432b954930d6aeae890b5e0fd2e6aa Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 5 Sep 2022 13:37:08 -0700 Subject: [PATCH] Ensure path can be specified for clearPreviewData (#40238) As updated in https://github.com/vercel/next.js/pull/38313 this ensures the `path` option can also be passed to `clearPreviewData` to properly clear the preview cookies. ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` Fixes: https://github.com/vercel/next.js/issues/39853 --- packages/next/server/api-utils/index.ts | 12 ++++++- packages/next/server/api-utils/node.ts | 3 +- packages/next/shared/lib/utils.ts | 6 +++- .../prerender-preview/pages/api/reset.js | 8 ++++- .../prerender-preview/test/index.test.js | 32 +++++++++++++++++++ 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/packages/next/server/api-utils/index.ts b/packages/next/server/api-utils/index.ts index 54927a434ff4ae7..e9588e9b41e3e44 100644 --- a/packages/next/server/api-utils/index.ts +++ b/packages/next/server/api-utils/index.ts @@ -2,6 +2,7 @@ import type { IncomingMessage } from 'http' import type { BaseNextRequest } from '../base-http' import { NextApiRequest, NextApiResponse } from '../../shared/lib/utils' +import type { CookieSerializeOptions } from 'next/dist/compiled/cookie' export type NextApiRequestCookies = Partial<{ [key: string]: string }> export type NextApiRequestQuery = Partial<{ [key: string]: string | string[] }> @@ -98,7 +99,10 @@ export const SYMBOL_PREVIEW_DATA = Symbol(COOKIE_NAME_PRERENDER_DATA) export const SYMBOL_CLEARED_COOKIES = Symbol(COOKIE_NAME_PRERENDER_BYPASS) export function clearPreviewData( - res: NextApiResponse + res: NextApiResponse, + options: { + path?: string + } = {} ): NextApiResponse { if (SYMBOL_CLEARED_COOKIES in res) { return res @@ -122,6 +126,9 @@ export function clearPreviewData( sameSite: process.env.NODE_ENV !== 'development' ? 'none' : 'lax', secure: process.env.NODE_ENV !== 'development', path: '/', + ...(options.path !== undefined + ? ({ path: options.path } as CookieSerializeOptions) + : undefined), }), serialize(COOKIE_NAME_PRERENDER_DATA, '', { // To delete a cookie, set `expires` to a date in the past: @@ -132,6 +139,9 @@ export function clearPreviewData( sameSite: process.env.NODE_ENV !== 'development' ? 'none' : 'lax', secure: process.env.NODE_ENV !== 'development', path: '/', + ...(options.path !== undefined + ? ({ path: options.path } as CookieSerializeOptions) + : undefined), }), ]) diff --git a/packages/next/server/api-utils/node.ts b/packages/next/server/api-utils/node.ts index 0c061c7e0cc7a18..a4fcdb5430843d8 100644 --- a/packages/next/server/api-utils/node.ts +++ b/packages/next/server/api-utils/node.ts @@ -494,7 +494,8 @@ export async function apiResolver( redirect(apiRes, statusOrUrl, url) apiRes.setPreviewData = (data, options = {}) => setPreviewData(apiRes, data, Object.assign({}, apiContext, options)) - apiRes.clearPreviewData = () => clearPreviewData(apiRes) + apiRes.clearPreviewData = (options = {}) => + clearPreviewData(apiRes, options) apiRes.revalidate = ( urlPath: string, opts?: { diff --git a/packages/next/shared/lib/utils.ts b/packages/next/shared/lib/utils.ts index 1e6bb46df7bc0dd..ec5197e0c3b9a80 100644 --- a/packages/next/shared/lib/utils.ts +++ b/packages/next/shared/lib/utils.ts @@ -263,7 +263,11 @@ export type NextApiResponse = ServerResponse & { path?: string } ) => NextApiResponse - clearPreviewData: () => NextApiResponse + + /** + * Clear preview data for Next.js' prerender mode + */ + clearPreviewData: (options?: { path?: string }) => NextApiResponse /** * @deprecated `unstable_revalidate` has been renamed to `revalidate` diff --git a/test/integration/prerender-preview/pages/api/reset.js b/test/integration/prerender-preview/pages/api/reset.js index 0434bf5b64f4771..99cb46fb766bba0 100644 --- a/test/integration/prerender-preview/pages/api/reset.js +++ b/test/integration/prerender-preview/pages/api/reset.js @@ -1,4 +1,10 @@ export default (req, res) => { - res.clearPreviewData() + res.clearPreviewData( + req.query.cookiePath + ? { + path: req.query.cookiePath, + } + : undefined + ) res.status(200).end() } diff --git a/test/integration/prerender-preview/test/index.test.js b/test/integration/prerender-preview/test/index.test.js index 885cdb19d149122..c27b0c59fff1151 100644 --- a/test/integration/prerender-preview/test/index.test.js +++ b/test/integration/prerender-preview/test/index.test.js @@ -211,6 +211,38 @@ function runTests(startServer = nextStart) { expect(cookies[1]).not.toHaveProperty('Max-Age') }) + it('should return cookies to be expired on reset request with path specified', async () => { + const res = await fetchViaHTTP( + appPort, + '/api/reset', + { cookiePath: '/blog' }, + { headers: { Cookie: previewCookieString } } + ) + expect(res.status).toBe(200) + + const cookies = res.headers + .get('set-cookie') + .replace(/(=(?!Lax)\w{3}),/g, '$1') + .split(',') + .map(cookie.parse) + + expect(cookies.length).toBe(2) + expect(cookies[0]).toMatchObject({ + Path: '/blog', + SameSite: 'None', + Expires: 'Thu 01 Jan 1970 00:00:00 GMT', + }) + expect(cookies[0]).toHaveProperty('__prerender_bypass') + expect(cookies[0]).not.toHaveProperty('Max-Age') + expect(cookies[1]).toMatchObject({ + Path: '/blog', + SameSite: 'None', + Expires: 'Thu 01 Jan 1970 00:00:00 GMT', + }) + expect(cookies[1]).toHaveProperty('__next_preview_data') + expect(cookies[1]).not.toHaveProperty('Max-Age') + }) + it('should pass undefined to API routes when not in preview', async () => { const res = await fetchViaHTTP(appPort, `/api/read`) const json = await res.json()