UNPKG

seitu

Version:
116 lines (115 loc) • 3.99 kB
import { createReadableSubscription, createSubscription } from "./core/subscription/index.mjs"; import { n as validateSchema } from "./schema-store-Dfbr678J.mjs"; import { parseCookie, serializeCookie } from "./utils/cookie/index.mjs"; import { tryParseJson } from "./utils/json/index.mjs"; import { repairValueObjectWithDefault } from "./utils/validation/index.mjs"; //#region src/internal/cookie-event.ts const CHANNEL_NAME = "seitu:cookie"; const hasCookieStore = () => typeof cookieStore !== "undefined"; /** Tells other tabs about a write when the browser has no `cookieStore`. */ function broadcastCookieChange(key) { if (hasCookieStore() || typeof BroadcastChannel === "undefined") return; const channel = new BroadcastChannel(CHANNEL_NAME); channel.postMessage({ key }); channel.close(); } /** * Notifies on cookie changes from other tabs: `cookieStore` when available, * otherwise a `BroadcastChannel` fed by `broadcastCookieChange`. * Returns undefined on the server. */ function listenCookie(key, notify) { if (typeof window === "undefined") return; if (hasCookieStore()) { const listener = (event) => { if ([...event.changed, ...event.deleted].some((item) => item.name === key)) notify(); }; cookieStore.addEventListener("change", listener); return () => cookieStore.removeEventListener("change", listener); } if (typeof BroadcastChannel === "undefined") return; const channel = new BroadcastChannel(CHANNEL_NAME); channel.onmessage = (event) => { if (event.data.key === key) notify(); }; return () => channel.close(); } //#endregion //#region src/web/cookie-value/index.ts const MAX_COOKIE_BYTES = 4096; const decode = (raw) => { try { return decodeURIComponent(raw); } catch { return raw; } }; function createCookieValue(options) { const { schema, key, attributes } = options; const defaultValue = options.defaultValue ?? null; const isDefaultValueObject = typeof defaultValue === "object" && defaultValue !== null; const label = `createCookieValue:${key}`; const readRaw = () => { const header = typeof window === "undefined" ? options.getServerCookies?.() : document.cookie; return header ? parseCookie(header, key) : null; }; let cachedRaw; let cachedValue; const { subscribe, notify } = createSubscription({ onFirstSubscribe: () => listenCookie(key, () => { if (readRaw() !== cachedRaw) notify(); }) }); const get = () => { const raw = readRaw(); if (cachedRaw !== void 0 && raw === cachedRaw) return cachedValue; cachedRaw = raw; if (raw === null) { cachedValue = defaultValue; return cachedValue; } const parsed = tryParseJson(decode(raw)); try { cachedValue = validateSchema(schema, parsed, { defaultValue, label, key, onError: (props) => { const toReturn = options.onValidationError?.(props); return toReturn === void 0 && isDefaultValueObject ? repairValueObjectWithDefault(props) : toReturn; } }); } catch (error) { console.warn(`[${label}] Validation failed, returned default value instead`, error); cachedValue = defaultValue; } return cachedValue; }; const readable = createReadableSubscription(get, subscribe, notify, get); const write = (newRaw) => { if (typeof window === "undefined") return; const oldRaw = readRaw(); if (newRaw === null) document.cookie = serializeCookie(key, "", { ...attributes, maxAge: 0, expires: /* @__PURE__ */ new Date(0) }); else { if (new TextEncoder().encode(`${key}=${newRaw}`).length > MAX_COOKIE_BYTES) { console.warn(`[${label}] Cookie is over 4096 bytes, write skipped`); return; } document.cookie = serializeCookie(key, newRaw, attributes); } cachedRaw = void 0; if (oldRaw !== newRaw) { notify(); broadcastCookieChange(key); } }; return { ...readable, set: (value) => write(encodeURIComponent(JSON.stringify(typeof value === "function" ? value(get()) : value))), clear: () => write(null) }; } //#endregion export { createCookieValue as t };