import * as cookie from "cookie"; import setCookieParser from "set-cookie-parser"; const isBrowser = () => typeof window !== "undefined"; const createCookie = (name, value, options) => { let sameSite = options.sameSite; if (sameSite === true) { sameSite = "strict"; } if (sameSite === undefined || sameSite === false) { sameSite = "lax"; } const cookieToSet = { ...options, sameSite }; delete cookieToSet.encode; return { name, value, ...cookieToSet }; }; const hasSameProperties = (firstCookie, secondCookie) => { const firstProperties = Object.getOwnPropertyNames(firstCookie); const secondProperties = Object.getOwnPropertyNames(secondCookie); if (firstProperties.length !== secondProperties.length) { return false; } return firstProperties.every( (propertyName) => firstCookie[propertyName] === secondCookie[propertyName] ); }; const areCookiesEqual = (firstCookie, secondCookie) => { let sameSiteMatches = firstCookie.sameSite === secondCookie.sameSite; if ( typeof firstCookie.sameSite === "string" && typeof secondCookie.sameSite === "string" ) { sameSiteMatches = firstCookie.sameSite.toLowerCase() === secondCookie.sameSite.toLowerCase(); } return ( hasSameProperties( { ...firstCookie, sameSite: undefined }, { ...secondCookie, sameSite: undefined } ) && sameSiteMatches ); }; export const parseCookies = (ctx, options) => { if (ctx?.req?.headers?.cookie) { return cookie.parse(ctx.req.headers.cookie, options); } if (isBrowser()) { return cookie.parse(document.cookie, options); } return {}; }; export const setCookie = (ctx, name, value, options = {}) => { if (ctx?.res?.getHeader && ctx.res.setHeader) { if (ctx?.res?.finished) { console.warn(`Not setting "${name}" cookie. Response has finished.`); console.warn("You should set cookie before res.send()"); return {}; } let cookies = ctx.res.getHeader("Set-Cookie") || []; if (typeof cookies === "string") { cookies = [cookies]; } if (typeof cookies === "number") { cookies = []; } const parsedCookies = setCookieParser.parse(cookies, { decodeValues: false }); const newCookie = createCookie(name, value, options); const cookiesToSet = []; parsedCookies.forEach((parsedCookie) => { if (!areCookiesEqual(parsedCookie, newCookie)) { cookiesToSet.push( cookie.serialize(parsedCookie.name, parsedCookie.value, { encode: (cookieValue) => cookieValue, ...parsedCookie }) ); } }); cookiesToSet.push(cookie.serialize(name, value, options)); ctx.res.setHeader("Set-Cookie", cookiesToSet); } if (isBrowser()) { if (options?.httpOnly) { throw new Error("Can not set a httpOnly cookie in the browser."); } document.cookie = cookie.serialize(name, value, options); } return {}; }; export const destroyCookie = (ctx, name, options) => setCookie(ctx, name, "", { ...(options || {}), maxAge: -1 });