UNPKG

next

Version:

The React Framework

169 lines (168 loc) • 6.76 kB
import { RequestCookies } from '../cookies'; import { ResponseCookies } from '../cookies'; import { ReflectAdapter } from './reflect'; import { workAsyncStorage } from '../../../app-render/work-async-storage.external'; import { getExpectedRequestStore } from '../../../app-render/work-unit-async-storage.external'; /** * @internal */ export class ReadonlyRequestCookiesError extends Error { constructor(){ super('Cookies can only be modified in a Server Action or Route Handler. Read more: https://nextjs.org/docs/app/api-reference/functions/cookies#options'); } static callable() { throw new ReadonlyRequestCookiesError(); } } export class RequestCookiesAdapter { static seal(cookies) { return new Proxy(cookies, { get (target, prop, receiver) { switch(prop){ case 'clear': case 'delete': case 'set': return ReadonlyRequestCookiesError.callable; default: return ReflectAdapter.get(target, prop, receiver); } } }); } } const SYMBOL_MODIFY_COOKIE_VALUES = Symbol.for('next.mutated.cookies'); export function getModifiedCookieValues(cookies) { const modified = cookies[SYMBOL_MODIFY_COOKIE_VALUES]; if (!modified || !Array.isArray(modified) || modified.length === 0) { return []; } return modified; } export function appendMutableCookies(headers, mutableCookies) { const modifiedCookieValues = getModifiedCookieValues(mutableCookies); if (modifiedCookieValues.length === 0) { return false; } // Return a new response that extends the response with // the modified cookies as fallbacks. `res` cookies // will still take precedence. const resCookies = new ResponseCookies(headers); const returnedCookies = resCookies.getAll(); // Set the modified cookies as fallbacks. for (const cookie of modifiedCookieValues){ resCookies.set(cookie); } // Set the original cookies as the final values. for (const cookie of returnedCookies){ resCookies.set(cookie); } return true; } export class MutableRequestCookiesAdapter { static wrap(cookies, onUpdateCookies) { const responseCookies = new ResponseCookies(new Headers()); for (const cookie of cookies.getAll()){ responseCookies.set(cookie); } let modifiedValues = []; const modifiedCookies = new Set(); const updateResponseCookies = ()=>{ // TODO-APP: change method of getting workStore const workStore = workAsyncStorage.getStore(); if (workStore) { workStore.pathWasRevalidated = true; } const allCookies = responseCookies.getAll(); modifiedValues = allCookies.filter((c)=>modifiedCookies.has(c.name)); if (onUpdateCookies) { const serializedCookies = []; for (const cookie of modifiedValues){ const tempCookies = new ResponseCookies(new Headers()); tempCookies.set(cookie); serializedCookies.push(tempCookies.toString()); } onUpdateCookies(serializedCookies); } }; const wrappedCookies = new Proxy(responseCookies, { get (target, prop, receiver) { switch(prop){ // A special symbol to get the modified cookie values case SYMBOL_MODIFY_COOKIE_VALUES: return modifiedValues; // TODO: Throw error if trying to set a cookie after the response // headers have been set. case 'delete': return function(...args) { modifiedCookies.add(typeof args[0] === 'string' ? args[0] : args[0].name); try { target.delete(...args); return wrappedCookies; } finally{ updateResponseCookies(); } }; case 'set': return function(...args) { modifiedCookies.add(typeof args[0] === 'string' ? args[0] : args[0].name); try { target.set(...args); return wrappedCookies; } finally{ updateResponseCookies(); } }; default: return ReflectAdapter.get(target, prop, receiver); } } }); return wrappedCookies; } } export function wrapWithMutableAccessCheck(responseCookies) { const wrappedCookies = new Proxy(responseCookies, { get (target, prop, receiver) { switch(prop){ case 'delete': return function(...args) { ensureCookiesAreStillMutable('cookies().delete'); target.delete(...args); return wrappedCookies; }; case 'set': return function(...args) { ensureCookiesAreStillMutable('cookies().set'); target.set(...args); return wrappedCookies; }; default: return ReflectAdapter.get(target, prop, receiver); } } }); return wrappedCookies; } export function areCookiesMutableInCurrentPhase(requestStore) { return requestStore.phase === 'action'; } /** Ensure that cookies() starts throwing on mutation * if we changed phases and can no longer mutate. * * This can happen when going: * 'render' -> 'after' * 'action' -> 'render' * */ function ensureCookiesAreStillMutable(callingExpression) { const requestStore = getExpectedRequestStore(callingExpression); if (!areCookiesMutableInCurrentPhase(requestStore)) { // TODO: maybe we can give a more precise error message based on callingExpression? throw new ReadonlyRequestCookiesError(); } } export function responseCookiesToRequestCookies(responseCookies) { const requestCookies = new RequestCookies(new Headers()); for (const cookie of responseCookies.getAll()){ requestCookies.set(cookie); } return requestCookies; } //# sourceMappingURL=request-cookies.js.map