Skip to content

Instantly share code, notes, and snippets.

@NachoToast
Created October 4, 2022 03:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NachoToast/37e3263c3b999b4d9a44779cbb333152 to your computer and use it in GitHub Desktop.
Save NachoToast/37e3263c3b999b4d9a44779cbb333152 to your computer and use it in GitHub Desktop.
Cookie Manager
/** Flags we put at the start of each cookie value so we know how to parse them. */
enum CookieTypeMap {
Boolean = `b`,
Number = `n`,
Object = `o`,
String = `s`,
}
/**
* Options when setting this cookie.
*
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#write_a_new_cookie API Reference}
*/
interface CookieOptions {
/**
* How long until this cookie expires, in seconds.
*
* This get's applied as the `max-age` property, and defaults to the end of
* the session if omitted.
*
* @example 60 * 60 * 24 * 365 (1 year)
*/
expiresIn?: number;
/**
* Whether this cookie should only be transmitted over HTTPS.
*
* @default false
*/
secure?: boolean;
/**
* Whether to prevent the browser from sending this cookie along cross-site requests.
*
* - `lax` - Send in same-site and and top-level navigation `GET` requests.
* - `strict` - Send in same-site only.
* - `none` - Send in same-site and cross-site.
*
* @default `none` (In modern browsers)
*/
sameSite?: `lax` | `strict` | `none`;
}
/**
* Manages geting and setting typed cookie values.
*
* Works with boolean, number, string, and JSON objects.
*
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie API Reference}
*/
export default class CookieManager<T = Record<string, unknown>> {
public set<K extends keyof T>(key: K, value: T[K], options?: CookieOptions): void {
let finalVal: string;
let finalType: CookieTypeMap;
switch (typeof value) {
case `boolean`:
finalVal = String(value);
finalType = CookieTypeMap.Boolean;
break;
case `number`:
finalVal = value.toString();
finalType = CookieTypeMap.Number;
break;
case `object`:
finalVal = JSON.stringify(value);
finalType = CookieTypeMap.Object;
break;
case `string`:
finalVal = value;
finalType = CookieTypeMap.String;
break;
default:
throw new Error(`Unsupported value type for ${String(key)}: ${typeof value}`);
}
const optionArray = new Array<string>();
if (options?.expiresIn !== undefined) {
optionArray.push(`;max-age=${options.expiresIn}`);
}
if (options?.secure === true) {
optionArray.push(`;secure`);
}
if (options?.sameSite !== undefined) {
if (options.sameSite === `none` && options.secure !== true) {
throw new Error(
`Cannot set 'sameSite=None' without also setting 'Secure'. See https://web.dev/samesite-cookies-explained/#samesite=none-must-be-secure for more info`,
);
}
optionArray.push(`;samesite=${options.sameSite}`);
}
finalVal = encodeURIComponent(finalType + finalVal);
document.cookie = `${String(key)}=${finalVal}${optionArray.join(``)}`;
}
public get<K extends keyof T>(key: K): T[K] | undefined {
let row = document.cookie
.split(`; `)
.find((row) => row.startsWith(`${String(key)}=`))
?.slice(`${String(key)}=`.length);
if (row === undefined) return undefined;
let output: boolean | string | number;
row = decodeURIComponent(row);
switch (row[0] as CookieTypeMap) {
case CookieTypeMap.Boolean:
if (row.slice(1) === `true`) output = true;
else if (row.slice(1) === `false`) output = false;
else
throw new Error(
`Unrecognized value for ${String(key)} (expected 'true' or 'false'): ${row.slice(1)}`,
);
break;
case CookieTypeMap.Number:
output = Number(row.slice(1));
if (Number.isNaN(output)) {
throw new Error(`Unrecognized value for ${String(key)} (expected valid number): ${row.slice(1)}`);
}
break;
case CookieTypeMap.Object:
try {
output = JSON.parse(row.slice(1));
} catch (error) {
throw new Error(`Unrecognized value for ${String(key)} (expected JSON object): ${row.slice(1)}`);
}
break;
case CookieTypeMap.String:
output = row.slice(1);
break;
default:
throw new Error(`Unrecognized value type for ${String(key)}: ${row[0]}`);
}
return output as T[K];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment