Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | 222x 4x 10x 10x 10x 7x 166x 68x 68x 72x 72x 72x 72x 72x 67x 67x 67x 1x 1x 159x 158x 18x 18x 18x 2x 2x 18x 2x 18x 17x 18x 1x 18x 2x 18x 4x 18x 6x 6x 6x 6x 6x 1x 6x | /**
* Pure, framework-agnostic helpers for reading and writing `document.cookie`.
* These are unit-tested in isolation and used by {@link useCookie}.
*/
/**
* The `SameSite` cookie attribute values.
*
* - `"strict"` — cookie is sent only for same-site requests.
* - `"lax"` — cookie is sent on same-site requests and top-level navigations.
* - `"none"` — cookie is sent on all requests; **requires `secure: true`**.
*/
export type SameSite = "strict" | "lax" | "none";
/**
* Cookie write attributes, mirroring the standard `Set-Cookie` directives.
* These control how the cookie is persisted by the browser.
*/
export interface CookieAttributes {
/**
* Expiry of the cookie. Either an absolute `Date`, or a number of **days**
* from now (e.g. `7` = expires in a week). Omit for a session cookie.
*/
expires?: Date | number;
/**
* `Max-Age` in **seconds** from now. Takes precedence over `expires` in
* browsers that support it. Use `0` or a negative number to expire immediately.
*/
maxAge?: number;
/**
* The URL path the cookie is scoped to.
* @default "/"
*/
path?: string;
/** The domain the cookie is scoped to (e.g. `.example.com`). */
domain?: string;
/** When `true`, the cookie is only sent over HTTPS. */
secure?: boolean;
/** The `SameSite` policy. `"none"` requires `secure: true`. */
sameSite?: SameSite;
}
/** `true` when running in an environment that exposes `document`. */
export function isDocumentAvailable(): boolean {
return typeof document !== "undefined";
}
/** Capitalize the first letter (e.g. `"lax"` → `"Lax"`) for the `SameSite` directive. */
function capitalizeSameSite(value: SameSite): string {
return value.charAt(0).toUpperCase() + value.slice(1);
}
/**
* Default serializer — JSON-encodes any value.
* @default JSON.stringify
*/
export function defaultSerializer<T>(value: T): string {
return JSON.stringify(value);
}
/**
* Default deserializer — tries `JSON.parse`, and **falls back to the raw
* string** if the value is not valid JSON. Cookies are very often plain,
* non-JSON strings (set by a server or another library), so this never throws
* on a legacy/plain value: `"bar"` stays `"bar"`, while `'{"a":1}'` becomes
* `{ a: 1 }`.
*/
export function defaultDeserializer<T>(value: string): T {
try {
return JSON.parse(value) as T;
} catch {
return value as unknown as T;
}
}
/**
* Parse a raw `document.cookie` string and return the **decoded** value for
* `key`, or `undefined` when the key is absent. Pure — does not touch
* `document`, so it is trivially testable.
*
* @param cookieString - The raw `document.cookie` value (e.g. `"a=1; b=2"`).
* @param key - The (un-encoded) cookie name to look up.
*/
export function parseCookie(
cookieString: string,
key: string
): string | undefined {
if (!cookieString) return undefined;
const pairs = cookieString.split("; ");
for (const pair of pairs) {
const eqIndex = pair.indexOf("=");
const rawName = eqIndex > -1 ? pair.slice(0, eqIndex) : pair;
let name: string;
try {
name = decodeURIComponent(rawName);
} catch {
name = rawName;
}
if (name === key) {
const rawValue = eqIndex > -1 ? pair.slice(eqIndex + 1) : "";
try {
return decodeURIComponent(rawValue);
} catch {
return rawValue;
}
}
}
return undefined;
}
/**
* Read the decoded value of a cookie from `document.cookie`, guarding for SSR.
* Returns `undefined` on the server or when the cookie is absent.
*/
export function readRawCookie(key: string): string | undefined {
if (!isDocumentAvailable()) return undefined;
return parseCookie(document.cookie, key);
}
/**
* Build a `document.cookie` assignment string for a cookie, encoding the name
* and value and appending the standard attributes. Pure and testable.
*
* @example
* buildCookieString("theme", "dark", { path: "/", maxAge: 3600 })
* // => "theme=dark; Path=/; Max-Age=3600"
*/
export function buildCookieString(
name: string,
value: string,
attributes: CookieAttributes = {}
): string {
const { expires, maxAge, path = "/", domain, secure, sameSite } = attributes;
let str = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
if (expires !== undefined) {
const date =
typeof expires === "number"
? new Date(Date.now() + expires * 864e5)
: expires;
str += `; Expires=${date.toUTCString()}`;
}
if (maxAge !== undefined) {
str += `; Max-Age=${maxAge}`;
}
if (path) {
str += `; Path=${path}`;
}
if (domain) {
str += `; Domain=${domain}`;
}
if (secure) {
str += "; Secure";
}
if (sameSite) {
str += `; SameSite=${capitalizeSameSite(sameSite)}`;
}
return str;
}
/**
* Build the `document.cookie` assignment string that **deletes** a cookie. The
* value is emptied and the cookie is expired in the past (`Max-Age=-1` plus an
* epoch `Expires`). The same `path`/`domain` used to write it must be provided,
* otherwise the browser treats it as a different cookie and deletion is a no-op.
*/
export function buildRemovalCookieString(
name: string,
attributes: Pick<CookieAttributes, "path" | "domain"> = {}
): string {
const { path = "/", domain } = attributes;
let str = `${encodeURIComponent(name)}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=-1`;
Eif (path) {
str += `; Path=${path}`;
}
if (domain) {
str += `; Domain=${domain}`;
}
return str;
}
|