Skip to content
SlashClone

Cookie consent banner

@slashclone/cookie-consent-banner

Add an honest cookie banner to a web app: analytics, ad pixels, and video embeds load only after a yes, and saying no is one click. One registry of every cookie and service drives the banner, the loaders, the CSP, and the privacy page's cookie table. Includes a registry checker and React templates.

SkillDevOpsv0.1.0 (latest)Tested
Activity
New: be the first to try it
  1. 1/**
  2. 2 * Cookie consent core: the stored choice and the rules for applying it. Framework free.
  3. 3 * Pair it with a registry like examples/services.json (ids, categories, what each stores).
  4. 4 */
  5. 5
  6. 6export const CONSENT_COOKIE = "consent";
  7. 7/** About six months, then ask again. */
  8. 8export const CONSENT_MAX_AGE_SECONDS = 182 * 24 * 60 * 60;
  9. 9
  10. 10export const CATEGORIES = ["analytics", "advertising", "media"] as const;
  11. 11export type Category = (typeof CATEGORIES)[number];
  12. 13export interface ConsentChoice {
  13. 14 v: 1;
  14. 15 analytics: boolean;
  15. 16 advertising: boolean;
  16. 17 media: boolean;
  17. 18 /** Service ids the visitor saw when choosing; a new one means ask again. */
  18. 19 s: string[];
  19. 20 at: number;
  20. 23export function parseConsent(raw: string | undefined | null): ConsentChoice | null {
  21. 24 if (!raw) return null;
  22. 25 try {
  23. 26 const c = JSON.parse(decodeURIComponent(raw)) as Partial<ConsentChoice>;
  24. 27 const ok =
  25. 28 c.v === 1 &&
  26. 29 typeof c.analytics === "boolean" &&
  27. 30 typeof c.advertising === "boolean" &&
  28. 31 typeof c.media === "boolean" &&
  29. 32 Array.isArray(c.s) &&
  30. 33 c.s.every((id) => typeof id === "string") &&
  31. 34 typeof c.at === "number";
  32. 35 return ok ? (c as ConsentChoice) : null;
  33. 36 } catch {
  34. 37 return null;
  35. 38 }
  36. 41export function serializeConsent(choice: ConsentChoice): string {
  37. 42 return encodeURIComponent(JSON.stringify(choice));
  38. 45/**
  39. 46 * The choice to apply, or null to ask. Global Privacy Control keeps advertising off
  40. 47 * whatever was stored; a service added since the choice asks again.
  41. 48 */
  42. 49export function effectiveConsent(
  43. 50 stored: ConsentChoice | null,
  44. 51 currentServiceIds: string[],
  45. 52 gpc: boolean,
  46. 53): ConsentChoice | null {
  47. 54 if (!stored) return null;
  48. 55 if (currentServiceIds.some((id) => !stored.s.includes(id))) return null;
  49. 56 return gpc ? { ...stored, advertising: false } : stored;
  50. 59/** Page URLs for trackers: origin and path only. Query strings can hold search text or tokens. */
  51. 60export function trackedUrl(href: string): string {
  52. 61 try {
  53. 62 const url = new URL(href);
  54. 63 return `${url.origin}${url.pathname}`;
  55. 64 } catch {
  56. 65 return "";
  57. 66 }
  58. 69/** Ad pixels read the full URL themselves, so keep them off pages holding what someone typed. */
  59. 70export function pixelsMaySend(
  60. 71 href: string,
  61. 72 typedParams: string[] = ["q", "query", "search"],
  62. 73): boolean {
  63. 74 try {
  64. 75 const params = new URL(href).searchParams;
  65. 76 return !typedParams.some((p) => params.has(p));
  66. 77 } catch {
  67. 78 return false;
  68. 79 }
  69. 82/** Browser only: deletes cookies and storage keys matching a revoked category's pattern. */
  70. 83export function clearMatching(pattern: RegExp): void {
  71. 84 const host = window.location.hostname;
  72. 85 const domains = ["", host, `.${host.split(".").slice(-2).join(".")}`];
  73. 86 for (const c of document.cookie.split("; ")) {
  74. 87 const name = c.split("=")[0] ?? "";
  75. 88 if (!pattern.test(name)) continue;
  76. 89 for (const d of domains)
  77. 90 document.cookie = `${name}=; Path=/; Max-Age=0${d ? `; Domain=${d}` : ""}`;
  78. 91 }
  79. 92 try {
  80. 93 for (const key of Object.keys(localStorage))
  81. 94 if (pattern.test(key)) localStorage.removeItem(key);
  82. 95 } catch {
  83. 96 // Storage can be blocked; then there is nothing to clear.
  84. 97 }
  85. 100export function gpcOn(): boolean {
  86. 101 return (
  87. 102 (navigator as Navigator & { globalPrivacyControl?: boolean }).globalPrivacyControl === true
  88. 103 );

Cookies that keep you signed in and remember these choices are always on. Everything else is off until you turn it on.