@shware/analytics 3.8.3 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +0 -2
  2. package/dist/click-id/index.cjs +125 -0
  3. package/dist/click-id/index.cjs.map +1 -0
  4. package/dist/click-id/index.d.cts +76 -0
  5. package/dist/click-id/index.d.cts.map +1 -0
  6. package/dist/click-id/index.d.mts +76 -0
  7. package/dist/click-id/index.d.mts.map +1 -0
  8. package/dist/click-id/index.mjs +119 -0
  9. package/dist/click-id/index.mjs.map +1 -0
  10. package/dist/constants/storage.cjs +1 -4
  11. package/dist/constants/storage.cjs.map +1 -1
  12. package/dist/constants/storage.d.cts +0 -3
  13. package/dist/constants/storage.d.cts.map +1 -1
  14. package/dist/constants/storage.d.mts +0 -3
  15. package/dist/constants/storage.d.mts.map +1 -1
  16. package/dist/constants/storage.mjs +1 -4
  17. package/dist/constants/storage.mjs.map +1 -1
  18. package/dist/hooks/use-report-web-vitals.cjs +17 -0
  19. package/dist/hooks/use-report-web-vitals.cjs.map +1 -0
  20. package/dist/hooks/use-report-web-vitals.d.cts +6 -0
  21. package/dist/hooks/use-report-web-vitals.d.cts.map +1 -0
  22. package/dist/hooks/use-report-web-vitals.d.mts +6 -0
  23. package/dist/hooks/use-report-web-vitals.d.mts.map +1 -0
  24. package/dist/hooks/use-report-web-vitals.mjs +16 -0
  25. package/dist/hooks/use-report-web-vitals.mjs.map +1 -0
  26. package/dist/next/index.cjs +1 -3
  27. package/dist/next/index.cjs.map +1 -1
  28. package/dist/next/index.d.cts.map +1 -1
  29. package/dist/next/index.d.mts.map +1 -1
  30. package/dist/next/index.mjs +1 -3
  31. package/dist/next/index.mjs.map +1 -1
  32. package/dist/react-router/index.cjs +3 -15
  33. package/dist/react-router/index.cjs.map +1 -1
  34. package/dist/react-router/index.d.cts.map +1 -1
  35. package/dist/react-router/index.d.mts.map +1 -1
  36. package/dist/react-router/index.mjs +2 -14
  37. package/dist/react-router/index.mjs.map +1 -1
  38. package/dist/server/index.cjs +7 -0
  39. package/dist/server/index.d.cts +2 -1
  40. package/dist/server/index.d.mts +2 -1
  41. package/dist/server/index.mjs +2 -1
  42. package/dist/tanstack/analytics.cjs +154 -0
  43. package/dist/tanstack/analytics.cjs.map +1 -0
  44. package/dist/tanstack/analytics.d.cts +22 -0
  45. package/dist/tanstack/analytics.d.cts.map +1 -0
  46. package/dist/tanstack/analytics.d.mts +22 -0
  47. package/dist/tanstack/analytics.d.mts.map +1 -0
  48. package/dist/tanstack/analytics.mjs +153 -0
  49. package/dist/tanstack/analytics.mjs.map +1 -0
  50. package/dist/tanstack/index.cjs +5 -165
  51. package/dist/tanstack/index.d.cts +3 -22
  52. package/dist/tanstack/index.d.mts +3 -22
  53. package/dist/tanstack/index.mjs +3 -165
  54. package/dist/tanstack/middleware.cjs +52 -0
  55. package/dist/tanstack/middleware.cjs.map +1 -0
  56. package/dist/tanstack/middleware.d.cts +54 -0
  57. package/dist/tanstack/middleware.d.cts.map +1 -0
  58. package/dist/tanstack/middleware.d.mts +54 -0
  59. package/dist/tanstack/middleware.d.mts.map +1 -0
  60. package/dist/tanstack/middleware.mjs +50 -0
  61. package/dist/tanstack/middleware.mjs.map +1 -0
  62. package/dist/web/index.cjs +6 -7
  63. package/dist/web/index.cjs.map +1 -1
  64. package/dist/web/index.d.cts.map +1 -1
  65. package/dist/web/index.d.mts.map +1 -1
  66. package/dist/web/index.mjs +5 -8
  67. package/dist/web/index.mjs.map +1 -1
  68. package/package.json +6 -2
  69. package/dist/_virtual/_rolldown/runtime.mjs +0 -24
  70. package/dist/hooks/use-click-id-persistence.cjs +0 -52
  71. package/dist/hooks/use-click-id-persistence.cjs.map +0 -1
  72. package/dist/hooks/use-click-id-persistence.d.cts +0 -5
  73. package/dist/hooks/use-click-id-persistence.d.cts.map +0 -1
  74. package/dist/hooks/use-click-id-persistence.d.mts +0 -5
  75. package/dist/hooks/use-click-id-persistence.d.mts.map +0 -1
  76. package/dist/hooks/use-click-id-persistence.mjs +0 -51
  77. package/dist/hooks/use-click-id-persistence.mjs.map +0 -1
  78. package/dist/node_modules/.pnpm/bowser@2.14.1/node_modules/bowser/es5.cjs +0 -1725
  79. package/dist/node_modules/.pnpm/bowser@2.14.1/node_modules/bowser/es5.cjs.map +0 -1
  80. package/dist/node_modules/.pnpm/bowser@2.14.1/node_modules/bowser/es5.mjs +0 -1722
  81. package/dist/node_modules/.pnpm/bowser@2.14.1/node_modules/bowser/es5.mjs.map +0 -1
  82. package/dist/tanstack/index.cjs.map +0 -1
  83. package/dist/tanstack/index.d.cts.map +0 -1
  84. package/dist/tanstack/index.d.mts.map +0 -1
  85. package/dist/tanstack/index.mjs.map +0 -1
@@ -5,7 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
9
8
  var __copyProps = (to, from, except, desc) => {
10
9
  if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
10
  key = keys[i];
@@ -21,5 +20,4 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
20
  enumerable: true
22
21
  }) : target, mod));
23
22
  //#endregion
24
- exports.__commonJSMin = __commonJSMin;
25
23
  exports.__toESM = __toESM;
@@ -0,0 +1,125 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let cookie = require("cookie");
3
+ //#region src/click-id/index.ts
4
+ /**
5
+ * Server-side resolution of ad-click-id cookies (`_fbc`, `_rdt_cid`) from the incoming request.
6
+ *
7
+ * This is the framework-agnostic core meant to run on the *document* response (e.g. TanStack Start
8
+ * server middleware, Next middleware). Setting `_fbc` via an HTTP `Set-Cookie` header on the top
9
+ * document — rather than `document.cookie` on the client — is what Meta officially recommends and is
10
+ * the only reliable way to keep the cookie alive for 90 days in Safari: ITP caps JavaScript-set
11
+ * cookies on a fbclid-decorated landing page to 24 hours, and the document response is never
12
+ * classified as CNAME/IP cloaking (it is the reference the browser measures cloaking against).
13
+ *
14
+ * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc
15
+ */
16
+ const FBC_TTL_MS = 2160 * 60 * 60 * 1e3;
17
+ const RDT_CID_TTL_MS = 2160 * 60 * 60 * 1e3;
18
+ const CLOCK_SKEW_MS = 1440 * 60 * 1e3;
19
+ const FBC_COOKIE = "_fbc";
20
+ const RDT_CID_COOKIE = "_rdt_cid";
21
+ /**
22
+ * Parse `fb.<subdomainIndex>.<creationTime>.<fbclid>`.
23
+ * Returns undefined for anything malformed — a fbclid never contains a dot, but joining the tail
24
+ * back together keeps us forward-compatible if that ever changes.
25
+ */
26
+ function parseFbc(raw, now = Date.now()) {
27
+ if (!raw) return void 0;
28
+ const parts = raw.split(".");
29
+ if (parts.length < 4 || parts[0] !== "fb") return void 0;
30
+ const creationTime = Number(parts[2]);
31
+ const fbclid = parts.slice(3).join(".");
32
+ if (!fbclid) return void 0;
33
+ if (!Number.isFinite(creationTime)) return void 0;
34
+ if (creationTime < 0xe8d4a51000 || creationTime > now + CLOCK_SKEW_MS) return void 0;
35
+ return {
36
+ raw,
37
+ creationTime,
38
+ fbclid
39
+ };
40
+ }
41
+ /**
42
+ * Build a fresh `_fbc` value. subdomainIndex is the cookie's domain level (com=0, example.com=1,
43
+ * www.example.com=2); 1 is correct for an apex-hosted cookie.
44
+ */
45
+ function formatFbc(fbclid, now, subdomainIndex = 1) {
46
+ return `fb.${subdomainIndex}.${now}.${fbclid}`;
47
+ }
48
+ function searchParams(url) {
49
+ try {
50
+ return new URL(url).searchParams;
51
+ } catch {
52
+ const q = url.indexOf("?");
53
+ return new URLSearchParams(q >= 0 ? url.slice(q + 1) : "");
54
+ }
55
+ }
56
+ /**
57
+ * Resolve the click-id cookies to set on the current document response.
58
+ *
59
+ * `_fbc` follows Meta's documented conditional-write rule value-wise: a new fbclid (or an absent
60
+ * cookie) opens a fresh 90-day window; a same-fbclid cookie keeps its original value and
61
+ * `creationTime`. Expired (>90d) or malformed values are cleared instead of forwarded. By default,
62
+ * ({@link ResolveClickIdCookiesInput.refresh}) a still-valid cookie is re-issued unchanged at its
63
+ * *remaining* lifetime — never `now + 90d` — so a returning visitor's window cannot slide forward
64
+ * (which is what makes Meta flag an expired fbclid).
65
+ */
66
+ function resolveClickIdCookies(input) {
67
+ const { url, cookieHeader, domain, secure = true, subdomainIndex = 1, refresh = true } = input;
68
+ const now = input.now ?? Date.now();
69
+ const params = searchParams(url);
70
+ const jar = (0, cookie.parseCookie)(cookieHeader ?? "");
71
+ const cookies = [];
72
+ const result = { cookies };
73
+ const base = {
74
+ path: "/",
75
+ secure,
76
+ sameSite: "lax",
77
+ domain
78
+ };
79
+ const set = (name, value, ttlMs) => cookies.push({
80
+ name,
81
+ value,
82
+ maxAge: Math.floor(ttlMs / 1e3),
83
+ ...base
84
+ });
85
+ const del = (name) => cookies.push({
86
+ name,
87
+ value: "",
88
+ maxAge: 0,
89
+ ...base
90
+ });
91
+ const urlFbclid = params.get("fbclid") || void 0;
92
+ const existingFbc = parseFbc(jar[FBC_COOKIE], now);
93
+ if (urlFbclid && urlFbclid !== existingFbc?.fbclid) {
94
+ const raw = formatFbc(urlFbclid, now, subdomainIndex);
95
+ set(FBC_COOKIE, raw, FBC_TTL_MS);
96
+ result.fbc = raw;
97
+ } else if (existingFbc) {
98
+ const remainingMs = existingFbc.creationTime + FBC_TTL_MS - now;
99
+ if (remainingMs <= 0) del(FBC_COOKIE);
100
+ else {
101
+ result.fbc = existingFbc.raw;
102
+ if (refresh) set(FBC_COOKIE, existingFbc.raw, remainingMs);
103
+ }
104
+ } else if (jar["_fbc"]) del(FBC_COOKIE);
105
+ const urlRdtCid = params.get("rdt_cid") || void 0;
106
+ const existingRdtCid = jar["_rdt_cid"] || void 0;
107
+ if (urlRdtCid && urlRdtCid !== existingRdtCid) {
108
+ set(RDT_CID_COOKIE, urlRdtCid, RDT_CID_TTL_MS);
109
+ result.rdt_cid = urlRdtCid;
110
+ } else if (existingRdtCid) result.rdt_cid = existingRdtCid;
111
+ return result;
112
+ }
113
+ /** Serialize the resolved cookies into `Set-Cookie` header values. */
114
+ function toSetCookieHeaders(cookies) {
115
+ return cookies.map((cookie$1) => (0, cookie.stringifySetCookie)(cookie$1));
116
+ }
117
+ //#endregion
118
+ exports.FBC_COOKIE = FBC_COOKIE;
119
+ exports.RDT_CID_COOKIE = RDT_CID_COOKIE;
120
+ exports.formatFbc = formatFbc;
121
+ exports.parseFbc = parseFbc;
122
+ exports.resolveClickIdCookies = resolveClickIdCookies;
123
+ exports.toSetCookieHeaders = toSetCookieHeaders;
124
+
125
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["cookie"],"sources":["../../src/click-id/index.ts"],"sourcesContent":["import { type SetCookie, parseCookie, stringifySetCookie } from 'cookie';\n\n/**\n * Server-side resolution of ad-click-id cookies (`_fbc`, `_rdt_cid`) from the incoming request.\n *\n * This is the framework-agnostic core meant to run on the *document* response (e.g. TanStack Start\n * server middleware, Next middleware). Setting `_fbc` via an HTTP `Set-Cookie` header on the top\n * document — rather than `document.cookie` on the client — is what Meta officially recommends and is\n * the only reliable way to keep the cookie alive for 90 days in Safari: ITP caps JavaScript-set\n * cookies on a fbclid-decorated landing page to 24 hours, and the document response is never\n * classified as CNAME/IP cloaking (it is the reference the browser measures cloaking against).\n *\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc\n */\n\n// Meta's recommended _fbc cookie expiry. Events Manager warns (\"Server sending expired fbclid\")\n// when the embedded creationTime is older than 90 days and match quality/attribution may degrade;\n// no hard drop is documented.\nconst FBC_TTL_MS = 90 * 24 * 60 * 60 * 1000;\nconst RDT_CID_TTL_MS = 90 * 24 * 60 * 60 * 1000;\n// Tolerate a little clock skew when validating a creationTime against \"now\".\nconst CLOCK_SKEW_MS = 24 * 60 * 60 * 1000;\n\nexport const FBC_COOKIE = '_fbc';\nexport const RDT_CID_COOKIE = '_rdt_cid';\n\nexport type ParsedFbc = { raw: string; creationTime: number; fbclid: string };\n\n/**\n * Parse `fb.<subdomainIndex>.<creationTime>.<fbclid>`.\n * Returns undefined for anything malformed — a fbclid never contains a dot, but joining the tail\n * back together keeps us forward-compatible if that ever changes.\n */\nexport function parseFbc(\n raw: string | undefined | null,\n now: number = Date.now()\n): ParsedFbc | undefined {\n if (!raw) return undefined;\n const parts = raw.split('.');\n if (parts.length < 4 || parts[0] !== 'fb') return undefined;\n\n const creationTime = Number(parts[2]);\n const fbclid = parts.slice(3).join('.');\n if (!fbclid) return undefined;\n // creationTime is UNIX ms; reject seconds-precision or future-dated values as malformed.\n if (!Number.isFinite(creationTime)) return undefined;\n if (creationTime < 1e12 || creationTime > now + CLOCK_SKEW_MS) return undefined;\n\n return { raw, creationTime, fbclid };\n}\n\n/**\n * Build a fresh `_fbc` value. subdomainIndex is the cookie's domain level (com=0, example.com=1,\n * www.example.com=2); 1 is correct for an apex-hosted cookie.\n */\nexport function formatFbc(fbclid: string, now: number, subdomainIndex = 1): string {\n return `fb.${subdomainIndex}.${now}.${fbclid}`;\n}\n\nexport type ResolveClickIdCookiesInput = {\n /** The absolute request URL (must include the query string). */\n url: string;\n /** The raw `Cookie` request header, if any. */\n cookieHeader?: string | null;\n /** Overridable clock, primarily for tests. Defaults to `Date.now()`. */\n now?: number;\n /** `Domain` attribute for the emitted cookies, e.g. `.edensign.io`. Omit for a host-only cookie. */\n domain?: string;\n /** `Secure` attribute, default true. Set false only for local http testing. */\n secure?: boolean;\n /** subdomainIndex for a freshly built `_fbc` (see {@link formatFbc}). Default 1. */\n subdomainIndex?: number;\n /**\n * Re-issue a still-valid `_fbc` at its *remaining* lifetime on every call. On by default, as an\n * ITP self-heal: cookies follow last-writer-wins, so if the Meta Pixel overwrote `_fbc` via\n * `document.cookie` (Safari caps JS-written cookies on a fbclid-decorated landing to 24h),\n * re-issuing the long-lived HTTP cookie on the next navigation restores it — the same\n * continuous-re-issue mitigation the sGTM ecosystem uses (stape Cookie Keeper, Cookie Monster).\n * The value (and its creationTime) is byte-for-byte identical and the window never slides, so it\n * cannot trigger Meta's expired/modified-fbclid warnings.\n *\n * Set false to strictly follow Meta's documented conditional-write rule (\"only set the cookie if\n * it doesn't exist or the fbclid changed\") — e.g. to keep pages CDN-cacheable, since the\n * re-issue attaches a per-user `Set-Cookie` (forcing `no-store`) to every response carrying an\n * `_fbc`.\n */\n refresh?: boolean;\n};\n\nexport type ResolveClickIdCookiesResult = {\n /**\n * Cookies to emit on the response, ready for `Set-Cookie`. Empty when nothing needs to change.\n * A `value: ''`, `maxAge: 0` entry is a deletion (an expired or malformed leftover).\n */\n cookies: SetCookie[];\n /** The resolved `_fbc` value, for immediate server-side use (e.g. Conversions API). */\n fbc?: string;\n /** The resolved `_rdt_cid` value. */\n rdt_cid?: string;\n};\n\nfunction searchParams(url: string): URLSearchParams {\n try {\n return new URL(url).searchParams;\n } catch {\n // Fall back to a manual split so a relative or slightly malformed URL still yields the query.\n const q = url.indexOf('?');\n return new URLSearchParams(q >= 0 ? url.slice(q + 1) : '');\n }\n}\n\n/**\n * Resolve the click-id cookies to set on the current document response.\n *\n * `_fbc` follows Meta's documented conditional-write rule value-wise: a new fbclid (or an absent\n * cookie) opens a fresh 90-day window; a same-fbclid cookie keeps its original value and\n * `creationTime`. Expired (>90d) or malformed values are cleared instead of forwarded. By default,\n * ({@link ResolveClickIdCookiesInput.refresh}) a still-valid cookie is re-issued unchanged at its\n * *remaining* lifetime — never `now + 90d` — so a returning visitor's window cannot slide forward\n * (which is what makes Meta flag an expired fbclid).\n */\nexport function resolveClickIdCookies(\n input: ResolveClickIdCookiesInput\n): ResolveClickIdCookiesResult {\n const { url, cookieHeader, domain, secure = true, subdomainIndex = 1, refresh = true } = input;\n const now = input.now ?? Date.now();\n\n const params = searchParams(url);\n const jar = parseCookie(cookieHeader ?? '');\n const cookies: SetCookie[] = [];\n const result: ResolveClickIdCookiesResult = { cookies };\n\n const base = { path: '/', secure, sameSite: 'lax', domain } as const;\n const set = (name: string, value: string, ttlMs: number) =>\n cookies.push({ name, value, maxAge: Math.floor(ttlMs / 1000), ...base });\n const del = (name: string) => cookies.push({ name, value: '', maxAge: 0, ...base });\n\n // --- Meta _fbc ---\n const urlFbclid = params.get('fbclid') || undefined;\n const existingFbc = parseFbc(jar[FBC_COOKIE], now);\n\n if (urlFbclid && urlFbclid !== existingFbc?.fbclid) {\n // A new click always wins and opens a fresh 90-day window.\n const raw = formatFbc(urlFbclid, now, subdomainIndex);\n set(FBC_COOKIE, raw, FBC_TTL_MS);\n result.fbc = raw;\n } else if (existingFbc) {\n const remainingMs = existingFbc.creationTime + FBC_TTL_MS - now;\n if (remainingMs <= 0) {\n del(FBC_COOKIE);\n } else {\n // Same fbclid: the value and creationTime are preserved (Meta's rule); refresh (default)\n // re-issues it unchanged at its remaining lifetime as an ITP self-heal.\n result.fbc = existingFbc.raw;\n if (refresh) set(FBC_COOKIE, existingFbc.raw, remainingMs);\n }\n } else if (jar[FBC_COOKIE]) {\n // Malformed leftover — clear it rather than forwarding it to Meta.\n del(FBC_COOKIE);\n }\n\n // --- Reddit _rdt_cid ---\n // No embedded timestamp, so it can only be anchored at first capture; set it once from the URL and\n // otherwise leave the existing cookie untouched (re-issuing would slide its window).\n const urlRdtCid = params.get('rdt_cid') || undefined;\n const existingRdtCid = jar[RDT_CID_COOKIE] || undefined;\n if (urlRdtCid && urlRdtCid !== existingRdtCid) {\n set(RDT_CID_COOKIE, urlRdtCid, RDT_CID_TTL_MS);\n result.rdt_cid = urlRdtCid;\n } else if (existingRdtCid) {\n result.rdt_cid = existingRdtCid;\n }\n\n return result;\n}\n\n/** Serialize the resolved cookies into `Set-Cookie` header values. */\nexport function toSetCookieHeaders(cookies: SetCookie[]): string[] {\n return cookies.map((cookie) => stringifySetCookie(cookie));\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,MAAM,aAAa,OAAU,KAAK,KAAK;AACvC,MAAM,iBAAiB,OAAU,KAAK,KAAK;AAE3C,MAAM,gBAAgB,OAAU,KAAK;AAErC,MAAa,aAAa;AAC1B,MAAa,iBAAiB;;;;;;AAS9B,SAAgB,SACd,KACA,MAAc,KAAK,IAAI,GACA;CACvB,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,MAAM,QAAQ,IAAI,MAAM,GAAG;CAC3B,IAAI,MAAM,SAAS,KAAK,MAAM,OAAO,MAAM,OAAO,KAAA;CAElD,MAAM,eAAe,OAAO,MAAM,EAAE;CACpC,MAAM,SAAS,MAAM,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;CACtC,IAAI,CAAC,QAAQ,OAAO,KAAA;CAEpB,IAAI,CAAC,OAAO,SAAS,YAAY,GAAG,OAAO,KAAA;CAC3C,IAAI,eAAe,gBAAQ,eAAe,MAAM,eAAe,OAAO,KAAA;CAEtE,OAAO;EAAE;EAAK;EAAc;CAAO;AACrC;;;;;AAMA,SAAgB,UAAU,QAAgB,KAAa,iBAAiB,GAAW;CACjF,OAAO,MAAM,eAAe,GAAG,IAAI,GAAG;AACxC;AA4CA,SAAS,aAAa,KAA8B;CAClD,IAAI;EACF,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC;CACtB,QAAQ;EAEN,MAAM,IAAI,IAAI,QAAQ,GAAG;EACzB,OAAO,IAAI,gBAAgB,KAAK,IAAI,IAAI,MAAM,IAAI,CAAC,IAAI,EAAE;CAC3D;AACF;;;;;;;;;;;AAYA,SAAgB,sBACd,OAC6B;CAC7B,MAAM,EAAE,KAAK,cAAc,QAAQ,SAAS,MAAM,iBAAiB,GAAG,UAAU,SAAS;CACzF,MAAM,MAAM,MAAM,OAAO,KAAK,IAAI;CAElC,MAAM,SAAS,aAAa,GAAG;CAC/B,MAAM,OAAA,GAAA,OAAA,YAAA,CAAkB,gBAAgB,EAAE;CAC1C,MAAM,UAAuB,CAAC;CAC9B,MAAM,SAAsC,EAAE,QAAQ;CAEtD,MAAM,OAAO;EAAE,MAAM;EAAK;EAAQ,UAAU;EAAO;CAAO;CAC1D,MAAM,OAAO,MAAc,OAAe,UACxC,QAAQ,KAAK;EAAE;EAAM;EAAO,QAAQ,KAAK,MAAM,QAAQ,GAAI;EAAG,GAAG;CAAK,CAAC;CACzE,MAAM,OAAO,SAAiB,QAAQ,KAAK;EAAE;EAAM,OAAO;EAAI,QAAQ;EAAG,GAAG;CAAK,CAAC;CAGlF,MAAM,YAAY,OAAO,IAAI,QAAQ,KAAK,KAAA;CAC1C,MAAM,cAAc,SAAS,IAAI,aAAa,GAAG;CAEjD,IAAI,aAAa,cAAc,aAAa,QAAQ;EAElD,MAAM,MAAM,UAAU,WAAW,KAAK,cAAc;EACpD,IAAI,YAAY,KAAK,UAAU;EAC/B,OAAO,MAAM;CACf,OAAO,IAAI,aAAa;EACtB,MAAM,cAAc,YAAY,eAAe,aAAa;EAC5D,IAAI,eAAe,GACjB,IAAI,UAAU;OACT;GAGL,OAAO,MAAM,YAAY;GACzB,IAAI,SAAS,IAAI,YAAY,YAAY,KAAK,WAAW;EAC3D;CACF,OAAO,IAAI,IAAA,SAET,IAAI,UAAU;CAMhB,MAAM,YAAY,OAAO,IAAI,SAAS,KAAK,KAAA;CAC3C,MAAM,iBAAiB,IAAA,eAAuB,KAAA;CAC9C,IAAI,aAAa,cAAc,gBAAgB;EAC7C,IAAI,gBAAgB,WAAW,cAAc;EAC7C,OAAO,UAAU;CACnB,OAAO,IAAI,gBACT,OAAO,UAAU;CAGnB,OAAO;AACT;;AAGA,SAAgB,mBAAmB,SAAgC;CACjE,OAAO,QAAQ,KAAK,cAAA,GAAA,OAAA,mBAAA,CAA8BA,QAAM,CAAC;AAC3D"}
@@ -0,0 +1,76 @@
1
+ import { SetCookie } from "cookie";
2
+ //#region src/click-id/index.d.ts
3
+ declare const FBC_COOKIE = "_fbc";
4
+ declare const RDT_CID_COOKIE = "_rdt_cid";
5
+ type ParsedFbc = {
6
+ raw: string;
7
+ creationTime: number;
8
+ fbclid: string;
9
+ };
10
+ /**
11
+ * Parse `fb.<subdomainIndex>.<creationTime>.<fbclid>`.
12
+ * Returns undefined for anything malformed — a fbclid never contains a dot, but joining the tail
13
+ * back together keeps us forward-compatible if that ever changes.
14
+ */
15
+ declare function parseFbc(raw: string | undefined | null, now?: number): ParsedFbc | undefined;
16
+ /**
17
+ * Build a fresh `_fbc` value. subdomainIndex is the cookie's domain level (com=0, example.com=1,
18
+ * www.example.com=2); 1 is correct for an apex-hosted cookie.
19
+ */
20
+ declare function formatFbc(fbclid: string, now: number, subdomainIndex?: number): string;
21
+ type ResolveClickIdCookiesInput = {
22
+ /** The absolute request URL (must include the query string). */
23
+ url: string;
24
+ /** The raw `Cookie` request header, if any. */
25
+ cookieHeader?: string | null;
26
+ /** Overridable clock, primarily for tests. Defaults to `Date.now()`. */
27
+ now?: number;
28
+ /** `Domain` attribute for the emitted cookies, e.g. `.edensign.io`. Omit for a host-only cookie. */
29
+ domain?: string;
30
+ /** `Secure` attribute, default true. Set false only for local http testing. */
31
+ secure?: boolean;
32
+ /** subdomainIndex for a freshly built `_fbc` (see {@link formatFbc}). Default 1. */
33
+ subdomainIndex?: number;
34
+ /**
35
+ * Re-issue a still-valid `_fbc` at its *remaining* lifetime on every call. On by default, as an
36
+ * ITP self-heal: cookies follow last-writer-wins, so if the Meta Pixel overwrote `_fbc` via
37
+ * `document.cookie` (Safari caps JS-written cookies on a fbclid-decorated landing to 24h),
38
+ * re-issuing the long-lived HTTP cookie on the next navigation restores it — the same
39
+ * continuous-re-issue mitigation the sGTM ecosystem uses (stape Cookie Keeper, Cookie Monster).
40
+ * The value (and its creationTime) is byte-for-byte identical and the window never slides, so it
41
+ * cannot trigger Meta's expired/modified-fbclid warnings.
42
+ *
43
+ * Set false to strictly follow Meta's documented conditional-write rule ("only set the cookie if
44
+ * it doesn't exist or the fbclid changed") — e.g. to keep pages CDN-cacheable, since the
45
+ * re-issue attaches a per-user `Set-Cookie` (forcing `no-store`) to every response carrying an
46
+ * `_fbc`.
47
+ */
48
+ refresh?: boolean;
49
+ };
50
+ type ResolveClickIdCookiesResult = {
51
+ /**
52
+ * Cookies to emit on the response, ready for `Set-Cookie`. Empty when nothing needs to change.
53
+ * A `value: ''`, `maxAge: 0` entry is a deletion (an expired or malformed leftover).
54
+ */
55
+ cookies: SetCookie[];
56
+ /** The resolved `_fbc` value, for immediate server-side use (e.g. Conversions API). */
57
+ fbc?: string;
58
+ /** The resolved `_rdt_cid` value. */
59
+ rdt_cid?: string;
60
+ };
61
+ /**
62
+ * Resolve the click-id cookies to set on the current document response.
63
+ *
64
+ * `_fbc` follows Meta's documented conditional-write rule value-wise: a new fbclid (or an absent
65
+ * cookie) opens a fresh 90-day window; a same-fbclid cookie keeps its original value and
66
+ * `creationTime`. Expired (>90d) or malformed values are cleared instead of forwarded. By default,
67
+ * ({@link ResolveClickIdCookiesInput.refresh}) a still-valid cookie is re-issued unchanged at its
68
+ * *remaining* lifetime — never `now + 90d` — so a returning visitor's window cannot slide forward
69
+ * (which is what makes Meta flag an expired fbclid).
70
+ */
71
+ declare function resolveClickIdCookies(input: ResolveClickIdCookiesInput): ResolveClickIdCookiesResult;
72
+ /** Serialize the resolved cookies into `Set-Cookie` header values. */
73
+ declare function toSetCookieHeaders(cookies: SetCookie[]): string[];
74
+ //#endregion
75
+ export { FBC_COOKIE, ParsedFbc, RDT_CID_COOKIE, ResolveClickIdCookiesInput, ResolveClickIdCookiesResult, formatFbc, parseFbc, resolveClickIdCookies, toSetCookieHeaders };
76
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/click-id/index.ts"],"mappings":";;cAuBa;cACA;KAED;EAAc;EAAa;EAAsB;;;;;;;iBAO7C,SACd,gCACA,eACC;;;;;iBAmBa,UAAU,gBAAgB,aAAa;KAI3C;;EAEV;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;;;;;;;;EAeA;;KAGU;;;;;EAKV,SAAS;;EAET;;EAEA;;;;;;;;;;;;iBAuBc,sBACd,OAAO,6BACN;;iBAsDa,mBAAmB,SAAS"}
@@ -0,0 +1,76 @@
1
+ import { SetCookie } from "cookie";
2
+ //#region src/click-id/index.d.ts
3
+ declare const FBC_COOKIE = "_fbc";
4
+ declare const RDT_CID_COOKIE = "_rdt_cid";
5
+ type ParsedFbc = {
6
+ raw: string;
7
+ creationTime: number;
8
+ fbclid: string;
9
+ };
10
+ /**
11
+ * Parse `fb.<subdomainIndex>.<creationTime>.<fbclid>`.
12
+ * Returns undefined for anything malformed — a fbclid never contains a dot, but joining the tail
13
+ * back together keeps us forward-compatible if that ever changes.
14
+ */
15
+ declare function parseFbc(raw: string | undefined | null, now?: number): ParsedFbc | undefined;
16
+ /**
17
+ * Build a fresh `_fbc` value. subdomainIndex is the cookie's domain level (com=0, example.com=1,
18
+ * www.example.com=2); 1 is correct for an apex-hosted cookie.
19
+ */
20
+ declare function formatFbc(fbclid: string, now: number, subdomainIndex?: number): string;
21
+ type ResolveClickIdCookiesInput = {
22
+ /** The absolute request URL (must include the query string). */
23
+ url: string;
24
+ /** The raw `Cookie` request header, if any. */
25
+ cookieHeader?: string | null;
26
+ /** Overridable clock, primarily for tests. Defaults to `Date.now()`. */
27
+ now?: number;
28
+ /** `Domain` attribute for the emitted cookies, e.g. `.edensign.io`. Omit for a host-only cookie. */
29
+ domain?: string;
30
+ /** `Secure` attribute, default true. Set false only for local http testing. */
31
+ secure?: boolean;
32
+ /** subdomainIndex for a freshly built `_fbc` (see {@link formatFbc}). Default 1. */
33
+ subdomainIndex?: number;
34
+ /**
35
+ * Re-issue a still-valid `_fbc` at its *remaining* lifetime on every call. On by default, as an
36
+ * ITP self-heal: cookies follow last-writer-wins, so if the Meta Pixel overwrote `_fbc` via
37
+ * `document.cookie` (Safari caps JS-written cookies on a fbclid-decorated landing to 24h),
38
+ * re-issuing the long-lived HTTP cookie on the next navigation restores it — the same
39
+ * continuous-re-issue mitigation the sGTM ecosystem uses (stape Cookie Keeper, Cookie Monster).
40
+ * The value (and its creationTime) is byte-for-byte identical and the window never slides, so it
41
+ * cannot trigger Meta's expired/modified-fbclid warnings.
42
+ *
43
+ * Set false to strictly follow Meta's documented conditional-write rule ("only set the cookie if
44
+ * it doesn't exist or the fbclid changed") — e.g. to keep pages CDN-cacheable, since the
45
+ * re-issue attaches a per-user `Set-Cookie` (forcing `no-store`) to every response carrying an
46
+ * `_fbc`.
47
+ */
48
+ refresh?: boolean;
49
+ };
50
+ type ResolveClickIdCookiesResult = {
51
+ /**
52
+ * Cookies to emit on the response, ready for `Set-Cookie`. Empty when nothing needs to change.
53
+ * A `value: ''`, `maxAge: 0` entry is a deletion (an expired or malformed leftover).
54
+ */
55
+ cookies: SetCookie[];
56
+ /** The resolved `_fbc` value, for immediate server-side use (e.g. Conversions API). */
57
+ fbc?: string;
58
+ /** The resolved `_rdt_cid` value. */
59
+ rdt_cid?: string;
60
+ };
61
+ /**
62
+ * Resolve the click-id cookies to set on the current document response.
63
+ *
64
+ * `_fbc` follows Meta's documented conditional-write rule value-wise: a new fbclid (or an absent
65
+ * cookie) opens a fresh 90-day window; a same-fbclid cookie keeps its original value and
66
+ * `creationTime`. Expired (>90d) or malformed values are cleared instead of forwarded. By default,
67
+ * ({@link ResolveClickIdCookiesInput.refresh}) a still-valid cookie is re-issued unchanged at its
68
+ * *remaining* lifetime — never `now + 90d` — so a returning visitor's window cannot slide forward
69
+ * (which is what makes Meta flag an expired fbclid).
70
+ */
71
+ declare function resolveClickIdCookies(input: ResolveClickIdCookiesInput): ResolveClickIdCookiesResult;
72
+ /** Serialize the resolved cookies into `Set-Cookie` header values. */
73
+ declare function toSetCookieHeaders(cookies: SetCookie[]): string[];
74
+ //#endregion
75
+ export { FBC_COOKIE, ParsedFbc, RDT_CID_COOKIE, ResolveClickIdCookiesInput, ResolveClickIdCookiesResult, formatFbc, parseFbc, resolveClickIdCookies, toSetCookieHeaders };
76
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/click-id/index.ts"],"mappings":";;cAuBa;cACA;KAED;EAAc;EAAa;EAAsB;;;;;;;iBAO7C,SACd,gCACA,eACC;;;;;iBAmBa,UAAU,gBAAgB,aAAa;KAI3C;;EAEV;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;;;;;;;;EAeA;;KAGU;;;;;EAKV,SAAS;;EAET;;EAEA;;;;;;;;;;;;iBAuBc,sBACd,OAAO,6BACN;;iBAsDa,mBAAmB,SAAS"}
@@ -0,0 +1,119 @@
1
+ import { parseCookie, stringifySetCookie } from "cookie";
2
+ //#region src/click-id/index.ts
3
+ /**
4
+ * Server-side resolution of ad-click-id cookies (`_fbc`, `_rdt_cid`) from the incoming request.
5
+ *
6
+ * This is the framework-agnostic core meant to run on the *document* response (e.g. TanStack Start
7
+ * server middleware, Next middleware). Setting `_fbc` via an HTTP `Set-Cookie` header on the top
8
+ * document — rather than `document.cookie` on the client — is what Meta officially recommends and is
9
+ * the only reliable way to keep the cookie alive for 90 days in Safari: ITP caps JavaScript-set
10
+ * cookies on a fbclid-decorated landing page to 24 hours, and the document response is never
11
+ * classified as CNAME/IP cloaking (it is the reference the browser measures cloaking against).
12
+ *
13
+ * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc
14
+ */
15
+ const FBC_TTL_MS = 2160 * 60 * 60 * 1e3;
16
+ const RDT_CID_TTL_MS = 2160 * 60 * 60 * 1e3;
17
+ const CLOCK_SKEW_MS = 1440 * 60 * 1e3;
18
+ const FBC_COOKIE = "_fbc";
19
+ const RDT_CID_COOKIE = "_rdt_cid";
20
+ /**
21
+ * Parse `fb.<subdomainIndex>.<creationTime>.<fbclid>`.
22
+ * Returns undefined for anything malformed — a fbclid never contains a dot, but joining the tail
23
+ * back together keeps us forward-compatible if that ever changes.
24
+ */
25
+ function parseFbc(raw, now = Date.now()) {
26
+ if (!raw) return void 0;
27
+ const parts = raw.split(".");
28
+ if (parts.length < 4 || parts[0] !== "fb") return void 0;
29
+ const creationTime = Number(parts[2]);
30
+ const fbclid = parts.slice(3).join(".");
31
+ if (!fbclid) return void 0;
32
+ if (!Number.isFinite(creationTime)) return void 0;
33
+ if (creationTime < 0xe8d4a51000 || creationTime > now + CLOCK_SKEW_MS) return void 0;
34
+ return {
35
+ raw,
36
+ creationTime,
37
+ fbclid
38
+ };
39
+ }
40
+ /**
41
+ * Build a fresh `_fbc` value. subdomainIndex is the cookie's domain level (com=0, example.com=1,
42
+ * www.example.com=2); 1 is correct for an apex-hosted cookie.
43
+ */
44
+ function formatFbc(fbclid, now, subdomainIndex = 1) {
45
+ return `fb.${subdomainIndex}.${now}.${fbclid}`;
46
+ }
47
+ function searchParams(url) {
48
+ try {
49
+ return new URL(url).searchParams;
50
+ } catch {
51
+ const q = url.indexOf("?");
52
+ return new URLSearchParams(q >= 0 ? url.slice(q + 1) : "");
53
+ }
54
+ }
55
+ /**
56
+ * Resolve the click-id cookies to set on the current document response.
57
+ *
58
+ * `_fbc` follows Meta's documented conditional-write rule value-wise: a new fbclid (or an absent
59
+ * cookie) opens a fresh 90-day window; a same-fbclid cookie keeps its original value and
60
+ * `creationTime`. Expired (>90d) or malformed values are cleared instead of forwarded. By default,
61
+ * ({@link ResolveClickIdCookiesInput.refresh}) a still-valid cookie is re-issued unchanged at its
62
+ * *remaining* lifetime — never `now + 90d` — so a returning visitor's window cannot slide forward
63
+ * (which is what makes Meta flag an expired fbclid).
64
+ */
65
+ function resolveClickIdCookies(input) {
66
+ const { url, cookieHeader, domain, secure = true, subdomainIndex = 1, refresh = true } = input;
67
+ const now = input.now ?? Date.now();
68
+ const params = searchParams(url);
69
+ const jar = parseCookie(cookieHeader ?? "");
70
+ const cookies = [];
71
+ const result = { cookies };
72
+ const base = {
73
+ path: "/",
74
+ secure,
75
+ sameSite: "lax",
76
+ domain
77
+ };
78
+ const set = (name, value, ttlMs) => cookies.push({
79
+ name,
80
+ value,
81
+ maxAge: Math.floor(ttlMs / 1e3),
82
+ ...base
83
+ });
84
+ const del = (name) => cookies.push({
85
+ name,
86
+ value: "",
87
+ maxAge: 0,
88
+ ...base
89
+ });
90
+ const urlFbclid = params.get("fbclid") || void 0;
91
+ const existingFbc = parseFbc(jar[FBC_COOKIE], now);
92
+ if (urlFbclid && urlFbclid !== existingFbc?.fbclid) {
93
+ const raw = formatFbc(urlFbclid, now, subdomainIndex);
94
+ set(FBC_COOKIE, raw, FBC_TTL_MS);
95
+ result.fbc = raw;
96
+ } else if (existingFbc) {
97
+ const remainingMs = existingFbc.creationTime + FBC_TTL_MS - now;
98
+ if (remainingMs <= 0) del(FBC_COOKIE);
99
+ else {
100
+ result.fbc = existingFbc.raw;
101
+ if (refresh) set(FBC_COOKIE, existingFbc.raw, remainingMs);
102
+ }
103
+ } else if (jar["_fbc"]) del(FBC_COOKIE);
104
+ const urlRdtCid = params.get("rdt_cid") || void 0;
105
+ const existingRdtCid = jar["_rdt_cid"] || void 0;
106
+ if (urlRdtCid && urlRdtCid !== existingRdtCid) {
107
+ set(RDT_CID_COOKIE, urlRdtCid, RDT_CID_TTL_MS);
108
+ result.rdt_cid = urlRdtCid;
109
+ } else if (existingRdtCid) result.rdt_cid = existingRdtCid;
110
+ return result;
111
+ }
112
+ /** Serialize the resolved cookies into `Set-Cookie` header values. */
113
+ function toSetCookieHeaders(cookies) {
114
+ return cookies.map((cookie) => stringifySetCookie(cookie));
115
+ }
116
+ //#endregion
117
+ export { FBC_COOKIE, RDT_CID_COOKIE, formatFbc, parseFbc, resolveClickIdCookies, toSetCookieHeaders };
118
+
119
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/click-id/index.ts"],"sourcesContent":["import { type SetCookie, parseCookie, stringifySetCookie } from 'cookie';\n\n/**\n * Server-side resolution of ad-click-id cookies (`_fbc`, `_rdt_cid`) from the incoming request.\n *\n * This is the framework-agnostic core meant to run on the *document* response (e.g. TanStack Start\n * server middleware, Next middleware). Setting `_fbc` via an HTTP `Set-Cookie` header on the top\n * document — rather than `document.cookie` on the client — is what Meta officially recommends and is\n * the only reliable way to keep the cookie alive for 90 days in Safari: ITP caps JavaScript-set\n * cookies on a fbclid-decorated landing page to 24 hours, and the document response is never\n * classified as CNAME/IP cloaking (it is the reference the browser measures cloaking against).\n *\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc\n */\n\n// Meta's recommended _fbc cookie expiry. Events Manager warns (\"Server sending expired fbclid\")\n// when the embedded creationTime is older than 90 days and match quality/attribution may degrade;\n// no hard drop is documented.\nconst FBC_TTL_MS = 90 * 24 * 60 * 60 * 1000;\nconst RDT_CID_TTL_MS = 90 * 24 * 60 * 60 * 1000;\n// Tolerate a little clock skew when validating a creationTime against \"now\".\nconst CLOCK_SKEW_MS = 24 * 60 * 60 * 1000;\n\nexport const FBC_COOKIE = '_fbc';\nexport const RDT_CID_COOKIE = '_rdt_cid';\n\nexport type ParsedFbc = { raw: string; creationTime: number; fbclid: string };\n\n/**\n * Parse `fb.<subdomainIndex>.<creationTime>.<fbclid>`.\n * Returns undefined for anything malformed — a fbclid never contains a dot, but joining the tail\n * back together keeps us forward-compatible if that ever changes.\n */\nexport function parseFbc(\n raw: string | undefined | null,\n now: number = Date.now()\n): ParsedFbc | undefined {\n if (!raw) return undefined;\n const parts = raw.split('.');\n if (parts.length < 4 || parts[0] !== 'fb') return undefined;\n\n const creationTime = Number(parts[2]);\n const fbclid = parts.slice(3).join('.');\n if (!fbclid) return undefined;\n // creationTime is UNIX ms; reject seconds-precision or future-dated values as malformed.\n if (!Number.isFinite(creationTime)) return undefined;\n if (creationTime < 1e12 || creationTime > now + CLOCK_SKEW_MS) return undefined;\n\n return { raw, creationTime, fbclid };\n}\n\n/**\n * Build a fresh `_fbc` value. subdomainIndex is the cookie's domain level (com=0, example.com=1,\n * www.example.com=2); 1 is correct for an apex-hosted cookie.\n */\nexport function formatFbc(fbclid: string, now: number, subdomainIndex = 1): string {\n return `fb.${subdomainIndex}.${now}.${fbclid}`;\n}\n\nexport type ResolveClickIdCookiesInput = {\n /** The absolute request URL (must include the query string). */\n url: string;\n /** The raw `Cookie` request header, if any. */\n cookieHeader?: string | null;\n /** Overridable clock, primarily for tests. Defaults to `Date.now()`. */\n now?: number;\n /** `Domain` attribute for the emitted cookies, e.g. `.edensign.io`. Omit for a host-only cookie. */\n domain?: string;\n /** `Secure` attribute, default true. Set false only for local http testing. */\n secure?: boolean;\n /** subdomainIndex for a freshly built `_fbc` (see {@link formatFbc}). Default 1. */\n subdomainIndex?: number;\n /**\n * Re-issue a still-valid `_fbc` at its *remaining* lifetime on every call. On by default, as an\n * ITP self-heal: cookies follow last-writer-wins, so if the Meta Pixel overwrote `_fbc` via\n * `document.cookie` (Safari caps JS-written cookies on a fbclid-decorated landing to 24h),\n * re-issuing the long-lived HTTP cookie on the next navigation restores it — the same\n * continuous-re-issue mitigation the sGTM ecosystem uses (stape Cookie Keeper, Cookie Monster).\n * The value (and its creationTime) is byte-for-byte identical and the window never slides, so it\n * cannot trigger Meta's expired/modified-fbclid warnings.\n *\n * Set false to strictly follow Meta's documented conditional-write rule (\"only set the cookie if\n * it doesn't exist or the fbclid changed\") — e.g. to keep pages CDN-cacheable, since the\n * re-issue attaches a per-user `Set-Cookie` (forcing `no-store`) to every response carrying an\n * `_fbc`.\n */\n refresh?: boolean;\n};\n\nexport type ResolveClickIdCookiesResult = {\n /**\n * Cookies to emit on the response, ready for `Set-Cookie`. Empty when nothing needs to change.\n * A `value: ''`, `maxAge: 0` entry is a deletion (an expired or malformed leftover).\n */\n cookies: SetCookie[];\n /** The resolved `_fbc` value, for immediate server-side use (e.g. Conversions API). */\n fbc?: string;\n /** The resolved `_rdt_cid` value. */\n rdt_cid?: string;\n};\n\nfunction searchParams(url: string): URLSearchParams {\n try {\n return new URL(url).searchParams;\n } catch {\n // Fall back to a manual split so a relative or slightly malformed URL still yields the query.\n const q = url.indexOf('?');\n return new URLSearchParams(q >= 0 ? url.slice(q + 1) : '');\n }\n}\n\n/**\n * Resolve the click-id cookies to set on the current document response.\n *\n * `_fbc` follows Meta's documented conditional-write rule value-wise: a new fbclid (or an absent\n * cookie) opens a fresh 90-day window; a same-fbclid cookie keeps its original value and\n * `creationTime`. Expired (>90d) or malformed values are cleared instead of forwarded. By default,\n * ({@link ResolveClickIdCookiesInput.refresh}) a still-valid cookie is re-issued unchanged at its\n * *remaining* lifetime — never `now + 90d` — so a returning visitor's window cannot slide forward\n * (which is what makes Meta flag an expired fbclid).\n */\nexport function resolveClickIdCookies(\n input: ResolveClickIdCookiesInput\n): ResolveClickIdCookiesResult {\n const { url, cookieHeader, domain, secure = true, subdomainIndex = 1, refresh = true } = input;\n const now = input.now ?? Date.now();\n\n const params = searchParams(url);\n const jar = parseCookie(cookieHeader ?? '');\n const cookies: SetCookie[] = [];\n const result: ResolveClickIdCookiesResult = { cookies };\n\n const base = { path: '/', secure, sameSite: 'lax', domain } as const;\n const set = (name: string, value: string, ttlMs: number) =>\n cookies.push({ name, value, maxAge: Math.floor(ttlMs / 1000), ...base });\n const del = (name: string) => cookies.push({ name, value: '', maxAge: 0, ...base });\n\n // --- Meta _fbc ---\n const urlFbclid = params.get('fbclid') || undefined;\n const existingFbc = parseFbc(jar[FBC_COOKIE], now);\n\n if (urlFbclid && urlFbclid !== existingFbc?.fbclid) {\n // A new click always wins and opens a fresh 90-day window.\n const raw = formatFbc(urlFbclid, now, subdomainIndex);\n set(FBC_COOKIE, raw, FBC_TTL_MS);\n result.fbc = raw;\n } else if (existingFbc) {\n const remainingMs = existingFbc.creationTime + FBC_TTL_MS - now;\n if (remainingMs <= 0) {\n del(FBC_COOKIE);\n } else {\n // Same fbclid: the value and creationTime are preserved (Meta's rule); refresh (default)\n // re-issues it unchanged at its remaining lifetime as an ITP self-heal.\n result.fbc = existingFbc.raw;\n if (refresh) set(FBC_COOKIE, existingFbc.raw, remainingMs);\n }\n } else if (jar[FBC_COOKIE]) {\n // Malformed leftover — clear it rather than forwarding it to Meta.\n del(FBC_COOKIE);\n }\n\n // --- Reddit _rdt_cid ---\n // No embedded timestamp, so it can only be anchored at first capture; set it once from the URL and\n // otherwise leave the existing cookie untouched (re-issuing would slide its window).\n const urlRdtCid = params.get('rdt_cid') || undefined;\n const existingRdtCid = jar[RDT_CID_COOKIE] || undefined;\n if (urlRdtCid && urlRdtCid !== existingRdtCid) {\n set(RDT_CID_COOKIE, urlRdtCid, RDT_CID_TTL_MS);\n result.rdt_cid = urlRdtCid;\n } else if (existingRdtCid) {\n result.rdt_cid = existingRdtCid;\n }\n\n return result;\n}\n\n/** Serialize the resolved cookies into `Set-Cookie` header values. */\nexport function toSetCookieHeaders(cookies: SetCookie[]): string[] {\n return cookies.map((cookie) => stringifySetCookie(cookie));\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,MAAM,aAAa,OAAU,KAAK,KAAK;AACvC,MAAM,iBAAiB,OAAU,KAAK,KAAK;AAE3C,MAAM,gBAAgB,OAAU,KAAK;AAErC,MAAa,aAAa;AAC1B,MAAa,iBAAiB;;;;;;AAS9B,SAAgB,SACd,KACA,MAAc,KAAK,IAAI,GACA;CACvB,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,MAAM,QAAQ,IAAI,MAAM,GAAG;CAC3B,IAAI,MAAM,SAAS,KAAK,MAAM,OAAO,MAAM,OAAO,KAAA;CAElD,MAAM,eAAe,OAAO,MAAM,EAAE;CACpC,MAAM,SAAS,MAAM,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;CACtC,IAAI,CAAC,QAAQ,OAAO,KAAA;CAEpB,IAAI,CAAC,OAAO,SAAS,YAAY,GAAG,OAAO,KAAA;CAC3C,IAAI,eAAe,gBAAQ,eAAe,MAAM,eAAe,OAAO,KAAA;CAEtE,OAAO;EAAE;EAAK;EAAc;CAAO;AACrC;;;;;AAMA,SAAgB,UAAU,QAAgB,KAAa,iBAAiB,GAAW;CACjF,OAAO,MAAM,eAAe,GAAG,IAAI,GAAG;AACxC;AA4CA,SAAS,aAAa,KAA8B;CAClD,IAAI;EACF,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC;CACtB,QAAQ;EAEN,MAAM,IAAI,IAAI,QAAQ,GAAG;EACzB,OAAO,IAAI,gBAAgB,KAAK,IAAI,IAAI,MAAM,IAAI,CAAC,IAAI,EAAE;CAC3D;AACF;;;;;;;;;;;AAYA,SAAgB,sBACd,OAC6B;CAC7B,MAAM,EAAE,KAAK,cAAc,QAAQ,SAAS,MAAM,iBAAiB,GAAG,UAAU,SAAS;CACzF,MAAM,MAAM,MAAM,OAAO,KAAK,IAAI;CAElC,MAAM,SAAS,aAAa,GAAG;CAC/B,MAAM,MAAM,YAAY,gBAAgB,EAAE;CAC1C,MAAM,UAAuB,CAAC;CAC9B,MAAM,SAAsC,EAAE,QAAQ;CAEtD,MAAM,OAAO;EAAE,MAAM;EAAK;EAAQ,UAAU;EAAO;CAAO;CAC1D,MAAM,OAAO,MAAc,OAAe,UACxC,QAAQ,KAAK;EAAE;EAAM;EAAO,QAAQ,KAAK,MAAM,QAAQ,GAAI;EAAG,GAAG;CAAK,CAAC;CACzE,MAAM,OAAO,SAAiB,QAAQ,KAAK;EAAE;EAAM,OAAO;EAAI,QAAQ;EAAG,GAAG;CAAK,CAAC;CAGlF,MAAM,YAAY,OAAO,IAAI,QAAQ,KAAK,KAAA;CAC1C,MAAM,cAAc,SAAS,IAAI,aAAa,GAAG;CAEjD,IAAI,aAAa,cAAc,aAAa,QAAQ;EAElD,MAAM,MAAM,UAAU,WAAW,KAAK,cAAc;EACpD,IAAI,YAAY,KAAK,UAAU;EAC/B,OAAO,MAAM;CACf,OAAO,IAAI,aAAa;EACtB,MAAM,cAAc,YAAY,eAAe,aAAa;EAC5D,IAAI,eAAe,GACjB,IAAI,UAAU;OACT;GAGL,OAAO,MAAM,YAAY;GACzB,IAAI,SAAS,IAAI,YAAY,YAAY,KAAK,WAAW;EAC3D;CACF,OAAO,IAAI,IAAA,SAET,IAAI,UAAU;CAMhB,MAAM,YAAY,OAAO,IAAI,SAAS,KAAK,KAAA;CAC3C,MAAM,iBAAiB,IAAA,eAAuB,KAAA;CAC9C,IAAI,aAAa,cAAc,gBAAgB;EAC7C,IAAI,gBAAgB,WAAW,cAAc;EAC7C,OAAO,UAAU;CACnB,OAAO,IAAI,gBACT,OAAO,UAAU;CAGnB,OAAO;AACT;;AAGA,SAAgB,mBAAmB,SAAgC;CACjE,OAAO,QAAQ,KAAK,WAAW,mBAAmB,MAAM,CAAC;AAC3D"}
@@ -4,10 +4,7 @@ const keys = {
4
4
  device_id: "device_id",
5
5
  visitor_id: "visitor_id",
6
6
  first_open_time: "first_open_time",
7
- first_visit_time: "first_visit_time",
8
- fbc: "fbc",
9
- fbclid: "fbclid",
10
- rdt_cid: "rdt_cid"
7
+ first_visit_time: "first_visit_time"
11
8
  };
12
9
  //#endregion
13
10
  exports.keys = keys;
@@ -1 +1 @@
1
- {"version":3,"file":"storage.cjs","names":[],"sources":["../../src/constants/storage.ts"],"sourcesContent":["export const keys = {\n device_id: 'device_id',\n visitor_id: 'visitor_id',\n first_open_time: 'first_open_time',\n first_visit_time: 'first_visit_time',\n // click ids\n fbc: 'fbc',\n fbclid: 'fbclid',\n rdt_cid: 'rdt_cid',\n} as const;\n"],"mappings":";;AAAA,MAAa,OAAO;CAClB,WAAW;CACX,YAAY;CACZ,iBAAiB;CACjB,kBAAkB;CAElB,KAAK;CACL,QAAQ;CACR,SAAS;AACX"}
1
+ {"version":3,"file":"storage.cjs","names":[],"sources":["../../src/constants/storage.ts"],"sourcesContent":["export const keys = {\n device_id: 'device_id',\n visitor_id: 'visitor_id',\n first_open_time: 'first_open_time',\n first_visit_time: 'first_visit_time',\n} as const;\n"],"mappings":";;AAAA,MAAa,OAAO;CAClB,WAAW;CACX,YAAY;CACZ,iBAAiB;CACjB,kBAAkB;AACpB"}
@@ -4,9 +4,6 @@ declare const keys: {
4
4
  readonly visitor_id: 'visitor_id';
5
5
  readonly first_open_time: 'first_open_time';
6
6
  readonly first_visit_time: 'first_visit_time';
7
- readonly fbc: 'fbc';
8
- readonly fbclid: 'fbclid';
9
- readonly rdt_cid: 'rdt_cid';
10
7
  };
11
8
  //#endregion
12
9
  export { keys };
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.cts","names":[],"sources":["../../src/constants/storage.ts"],"mappings":";cAAa;WACX;WACA;WACA;WACA;WAEA;WACA;WACA"}
1
+ {"version":3,"file":"storage.d.cts","names":[],"sources":["../../src/constants/storage.ts"],"mappings":";cAAa;WACX;WACA;WACA;WACA"}
@@ -4,9 +4,6 @@ declare const keys: {
4
4
  readonly visitor_id: 'visitor_id';
5
5
  readonly first_open_time: 'first_open_time';
6
6
  readonly first_visit_time: 'first_visit_time';
7
- readonly fbc: 'fbc';
8
- readonly fbclid: 'fbclid';
9
- readonly rdt_cid: 'rdt_cid';
10
7
  };
11
8
  //#endregion
12
9
  export { keys };
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.mts","names":[],"sources":["../../src/constants/storage.ts"],"mappings":";cAAa;WACX;WACA;WACA;WACA;WAEA;WACA;WACA"}
1
+ {"version":3,"file":"storage.d.mts","names":[],"sources":["../../src/constants/storage.ts"],"mappings":";cAAa;WACX;WACA;WACA;WACA"}
@@ -3,10 +3,7 @@ const keys = {
3
3
  device_id: "device_id",
4
4
  visitor_id: "visitor_id",
5
5
  first_open_time: "first_open_time",
6
- first_visit_time: "first_visit_time",
7
- fbc: "fbc",
8
- fbclid: "fbclid",
9
- rdt_cid: "rdt_cid"
6
+ first_visit_time: "first_visit_time"
10
7
  };
11
8
  //#endregion
12
9
  export { keys };
@@ -1 +1 @@
1
- {"version":3,"file":"storage.mjs","names":[],"sources":["../../src/constants/storage.ts"],"sourcesContent":["export const keys = {\n device_id: 'device_id',\n visitor_id: 'visitor_id',\n first_open_time: 'first_open_time',\n first_visit_time: 'first_visit_time',\n // click ids\n fbc: 'fbc',\n fbclid: 'fbclid',\n rdt_cid: 'rdt_cid',\n} as const;\n"],"mappings":";AAAA,MAAa,OAAO;CAClB,WAAW;CACX,YAAY;CACZ,iBAAiB;CACjB,kBAAkB;CAElB,KAAK;CACL,QAAQ;CACR,SAAS;AACX"}
1
+ {"version":3,"file":"storage.mjs","names":[],"sources":["../../src/constants/storage.ts"],"sourcesContent":["export const keys = {\n device_id: 'device_id',\n visitor_id: 'visitor_id',\n first_open_time: 'first_open_time',\n first_visit_time: 'first_visit_time',\n} as const;\n"],"mappings":";AAAA,MAAa,OAAO;CAClB,WAAW;CACX,YAAY;CACZ,iBAAiB;CACjB,kBAAkB;AACpB"}
@@ -0,0 +1,17 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let react = require("react");
3
+ let web_vitals = require("web-vitals");
4
+ //#region src/hooks/use-report-web-vitals.ts
5
+ function useReportWebVitals(reportWebVitalsFn) {
6
+ (0, react.useEffect)(() => {
7
+ (0, web_vitals.onCLS)(reportWebVitalsFn);
8
+ (0, web_vitals.onLCP)(reportWebVitalsFn);
9
+ (0, web_vitals.onINP)(reportWebVitalsFn);
10
+ (0, web_vitals.onFCP)(reportWebVitalsFn);
11
+ (0, web_vitals.onTTFB)(reportWebVitalsFn);
12
+ }, [reportWebVitalsFn]);
13
+ }
14
+ //#endregion
15
+ exports.useReportWebVitals = useReportWebVitals;
16
+
17
+ //# sourceMappingURL=use-report-web-vitals.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-report-web-vitals.cjs","names":[],"sources":["../../src/hooks/use-report-web-vitals.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { type Metric, onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals';\n\nexport function useReportWebVitals(reportWebVitalsFn: (metric: Metric) => void) {\n useEffect(() => {\n onCLS(reportWebVitalsFn);\n onLCP(reportWebVitalsFn);\n onINP(reportWebVitalsFn);\n onFCP(reportWebVitalsFn);\n onTTFB(reportWebVitalsFn);\n }, [reportWebVitalsFn]);\n}\n"],"mappings":";;;;AAGA,SAAgB,mBAAmB,mBAA6C;CAC9E,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,CAAA,GAAA,WAAA,MAAA,CAAM,iBAAiB;EACvB,CAAA,GAAA,WAAA,MAAA,CAAM,iBAAiB;EACvB,CAAA,GAAA,WAAA,MAAA,CAAM,iBAAiB;EACvB,CAAA,GAAA,WAAA,MAAA,CAAM,iBAAiB;EACvB,CAAA,GAAA,WAAA,OAAA,CAAO,iBAAiB;CAC1B,GAAG,CAAC,iBAAiB,CAAC;AACxB"}
@@ -0,0 +1,6 @@
1
+ import { Metric } from "web-vitals";
2
+ //#region src/hooks/use-report-web-vitals.d.ts
3
+ declare function useReportWebVitals(reportWebVitalsFn: (metric: Metric) => void): void;
4
+ //#endregion
5
+ export { useReportWebVitals };
6
+ //# sourceMappingURL=use-report-web-vitals.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-report-web-vitals.d.cts","names":[],"sources":["../../src/hooks/use-report-web-vitals.ts"],"mappings":";;iBAGgB,mBAAmB,oBAAoB,QAAQ"}
@@ -0,0 +1,6 @@
1
+ import { Metric } from "web-vitals";
2
+ //#region src/hooks/use-report-web-vitals.d.ts
3
+ declare function useReportWebVitals(reportWebVitalsFn: (metric: Metric) => void): void;
4
+ //#endregion
5
+ export { useReportWebVitals };
6
+ //# sourceMappingURL=use-report-web-vitals.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-report-web-vitals.d.mts","names":[],"sources":["../../src/hooks/use-report-web-vitals.ts"],"mappings":";;iBAGgB,mBAAmB,oBAAoB,QAAQ"}
@@ -0,0 +1,16 @@
1
+ import { useEffect } from "react";
2
+ import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals";
3
+ //#region src/hooks/use-report-web-vitals.ts
4
+ function useReportWebVitals(reportWebVitalsFn) {
5
+ useEffect(() => {
6
+ onCLS(reportWebVitalsFn);
7
+ onLCP(reportWebVitalsFn);
8
+ onINP(reportWebVitalsFn);
9
+ onFCP(reportWebVitalsFn);
10
+ onTTFB(reportWebVitalsFn);
11
+ }, [reportWebVitalsFn]);
12
+ }
13
+ //#endregion
14
+ export { useReportWebVitals };
15
+
16
+ //# sourceMappingURL=use-report-web-vitals.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-report-web-vitals.mjs","names":[],"sources":["../../src/hooks/use-report-web-vitals.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { type Metric, onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals';\n\nexport function useReportWebVitals(reportWebVitalsFn: (metric: Metric) => void) {\n useEffect(() => {\n onCLS(reportWebVitalsFn);\n onLCP(reportWebVitalsFn);\n onINP(reportWebVitalsFn);\n onFCP(reportWebVitalsFn);\n onTTFB(reportWebVitalsFn);\n }, [reportWebVitalsFn]);\n}\n"],"mappings":";;;AAGA,SAAgB,mBAAmB,mBAA6C;CAC9E,gBAAgB;EACd,MAAM,iBAAiB;EACvB,MAAM,iBAAiB;EACvB,MAAM,iBAAiB;EACvB,MAAM,iBAAiB;EACvB,OAAO,iBAAiB;CAC1B,GAAG,CAAC,iBAAiB,CAAC;AACxB"}
@@ -2,17 +2,15 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
4
4
  const require_track_index = require("../track/index.cjs");
5
- const require_hooks_use_click_id_persistence = require("../hooks/use-click-id-persistence.cjs");
6
5
  const require_hooks_use_outbound_click_analytics = require("../hooks/use-outbound-click-analytics.cjs");
7
6
  const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs");
7
+ let react_jsx_runtime = require("react/jsx-runtime");
8
8
  let next_navigation_js = require("next/navigation.js");
9
9
  let next_script_js = require("next/script.js");
10
10
  next_script_js = require_runtime.__toESM(next_script_js, 1);
11
11
  let next_web_vitals_js = require("next/web-vitals.js");
12
- let react_jsx_runtime = require("react/jsx-runtime");
13
12
  //#region src/next/index.tsx
14
13
  function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, hotjarId, redditPixelId, linkedInPartnerId, facebookAppId, reportWebVitals = true }) {
15
- require_hooks_use_click_id_persistence.useClickIdPersistence();
16
14
  require_hooks_use_web_analytics.useWebAnalytics((0, next_navigation_js.usePathname)());
17
15
  require_hooks_use_outbound_click_analytics.useOutboundClickAnalytics();
18
16
  (0, next_web_vitals_js.useReportWebVitals)((metric) => {