@shware/analytics 3.8.2 → 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.
- package/dist/click-id/index.cjs +125 -0
- package/dist/click-id/index.cjs.map +1 -0
- package/dist/click-id/index.d.cts +76 -0
- package/dist/click-id/index.d.cts.map +1 -0
- package/dist/click-id/index.d.mts +76 -0
- package/dist/click-id/index.d.mts.map +1 -0
- package/dist/click-id/index.mjs +119 -0
- package/dist/click-id/index.mjs.map +1 -0
- package/dist/constants/storage.cjs +1 -4
- package/dist/constants/storage.cjs.map +1 -1
- package/dist/constants/storage.d.cts +0 -3
- package/dist/constants/storage.d.cts.map +1 -1
- package/dist/constants/storage.d.mts +0 -3
- package/dist/constants/storage.d.mts.map +1 -1
- package/dist/constants/storage.mjs +1 -4
- package/dist/constants/storage.mjs.map +1 -1
- package/dist/hooks/use-report-web-vitals.cjs +17 -0
- package/dist/hooks/use-report-web-vitals.cjs.map +1 -0
- package/dist/hooks/use-report-web-vitals.d.cts +6 -0
- package/dist/hooks/use-report-web-vitals.d.cts.map +1 -0
- package/dist/hooks/use-report-web-vitals.d.mts +6 -0
- package/dist/hooks/use-report-web-vitals.d.mts.map +1 -0
- package/dist/hooks/use-report-web-vitals.mjs +16 -0
- package/dist/hooks/use-report-web-vitals.mjs.map +1 -0
- package/dist/next/index.cjs +1 -3
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts.map +1 -1
- package/dist/next/index.d.mts.map +1 -1
- package/dist/next/index.mjs +1 -3
- package/dist/next/index.mjs.map +1 -1
- package/dist/react-router/index.cjs +3 -15
- package/dist/react-router/index.cjs.map +1 -1
- package/dist/react-router/index.d.cts.map +1 -1
- package/dist/react-router/index.d.mts.map +1 -1
- package/dist/react-router/index.mjs +2 -14
- package/dist/react-router/index.mjs.map +1 -1
- package/dist/server/index.cjs +7 -0
- package/dist/server/index.d.cts +2 -1
- package/dist/server/index.d.mts +2 -1
- package/dist/server/index.mjs +2 -1
- package/dist/tanstack/analytics.cjs +154 -0
- package/dist/tanstack/analytics.cjs.map +1 -0
- package/dist/tanstack/analytics.d.cts +22 -0
- package/dist/tanstack/analytics.d.cts.map +1 -0
- package/dist/tanstack/analytics.d.mts +22 -0
- package/dist/tanstack/analytics.d.mts.map +1 -0
- package/dist/tanstack/analytics.mjs +153 -0
- package/dist/tanstack/analytics.mjs.map +1 -0
- package/dist/tanstack/index.cjs +5 -165
- package/dist/tanstack/index.d.cts +3 -22
- package/dist/tanstack/index.d.mts +3 -22
- package/dist/tanstack/index.mjs +3 -165
- package/dist/tanstack/middleware.cjs +52 -0
- package/dist/tanstack/middleware.cjs.map +1 -0
- package/dist/tanstack/middleware.d.cts +54 -0
- package/dist/tanstack/middleware.d.cts.map +1 -0
- package/dist/tanstack/middleware.d.mts +54 -0
- package/dist/tanstack/middleware.d.mts.map +1 -0
- package/dist/tanstack/middleware.mjs +50 -0
- package/dist/tanstack/middleware.mjs.map +1 -0
- package/dist/web/index.cjs +3 -4
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts.map +1 -1
- package/dist/web/index.d.mts.map +1 -1
- package/dist/web/index.mjs +3 -4
- package/dist/web/index.mjs.map +1 -1
- package/package.json +5 -1
- package/dist/hooks/use-click-id-persistence.cjs +0 -52
- package/dist/hooks/use-click-id-persistence.cjs.map +0 -1
- package/dist/hooks/use-click-id-persistence.d.cts +0 -5
- package/dist/hooks/use-click-id-persistence.d.cts.map +0 -1
- package/dist/hooks/use-click-id-persistence.d.mts +0 -5
- package/dist/hooks/use-click-id-persistence.d.mts.map +0 -1
- package/dist/hooks/use-click-id-persistence.mjs +0 -51
- package/dist/hooks/use-click-id-persistence.mjs.map +0 -1
- package/dist/tanstack/index.cjs.map +0 -1
- package/dist/tanstack/index.d.cts.map +0 -1
- package/dist/tanstack/index.d.mts.map +0 -1
- package/dist/tanstack/index.mjs.map +0 -1
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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"}
|
package/dist/next/index.cjs
CHANGED
|
@@ -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) => {
|