120 lines
3.3 KiB
JavaScript
120 lines
3.3 KiB
JavaScript
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 }); |