@shware/analytics 4.1.0 → 5.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 +1 -1
- package/dist/click-id/index.cjs.map +1 -1
- package/dist/click-id/index.mjs +1 -1
- package/dist/click-id/index.mjs.map +1 -1
- package/dist/hooks/use-track-impression.cjs +5 -4
- package/dist/hooks/use-track-impression.cjs.map +1 -1
- package/dist/hooks/use-track-impression.d.cts +2 -1
- package/dist/hooks/use-track-impression.d.cts.map +1 -1
- package/dist/hooks/use-track-impression.d.mts +2 -1
- package/dist/hooks/use-track-impression.d.mts.map +1 -1
- package/dist/hooks/use-track-impression.mjs +6 -5
- package/dist/hooks/use-track-impression.mjs.map +1 -1
- package/dist/link/index.cjs +1 -1
- package/dist/link/index.cjs.map +1 -1
- package/dist/link/index.mjs +1 -1
- package/dist/link/index.mjs.map +1 -1
- package/dist/native/fingerprint.cjs +1 -1
- package/dist/native/fingerprint.mjs +1 -1
- package/dist/native/firebase.cjs +2 -2
- package/dist/native/firebase.cjs.map +1 -1
- package/dist/native/firebase.mjs +2 -2
- package/dist/native/firebase.mjs.map +1 -1
- package/dist/native/index.cjs +1 -1
- package/dist/native/index.mjs +1 -1
- package/dist/native/setup.cjs +1 -1
- package/dist/native/setup.mjs +1 -1
- package/dist/react-router/index.cjs +1 -1
- package/dist/react-router/index.mjs +1 -1
- package/dist/tanstack/analytics.cjs +1 -1
- package/dist/tanstack/analytics.mjs +1 -1
- package/package.json +10 -10
package/dist/click-id/index.cjs
CHANGED
|
@@ -1 +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"}
|
|
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 !== -1 ? 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,MAAM,KAAK,IAAI,MAAM,IAAI,CAAC,IAAI,EAAE;CAC7D;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"}
|
package/dist/click-id/index.mjs
CHANGED
|
@@ -1 +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"}
|
|
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 !== -1 ? 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,MAAM,KAAK,IAAI,MAAM,IAAI,CAAC,IAAI,EAAE;CAC7D;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,22 +4,23 @@ let react = require("react");
|
|
|
4
4
|
//#region src/hooks/use-track-impression.ts
|
|
5
5
|
function useTrackImpression(name, properties) {
|
|
6
6
|
const fired = (0, react.useRef)(false);
|
|
7
|
-
const
|
|
7
|
+
const [node, setNode] = (0, react.useState)(null);
|
|
8
8
|
const onTrack = (0, react.useEffectEvent)(() => {
|
|
9
9
|
if (fired.current) return;
|
|
10
10
|
require_track_index.track(name, properties);
|
|
11
11
|
fired.current = true;
|
|
12
12
|
});
|
|
13
13
|
(0, react.useEffect)(() => {
|
|
14
|
+
if (!node) return;
|
|
14
15
|
const observer = new IntersectionObserver(([entry]) => {
|
|
15
16
|
if (!entry.isIntersecting) return;
|
|
16
17
|
onTrack();
|
|
17
18
|
observer.disconnect();
|
|
18
19
|
}, { threshold: .5 });
|
|
19
|
-
|
|
20
|
+
observer.observe(node);
|
|
20
21
|
return () => observer.disconnect();
|
|
21
|
-
}, [
|
|
22
|
-
return
|
|
22
|
+
}, [node]);
|
|
23
|
+
return setNode;
|
|
23
24
|
}
|
|
24
25
|
//#endregion
|
|
25
26
|
exports.useTrackImpression = useTrackImpression;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-track-impression.cjs","names":[],"sources":["../../src/hooks/use-track-impression.ts"],"sourcesContent":["import { useEffect, useEffectEvent, useRef } from 'react';\nimport { track } from '../track/index';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\n\nexport function useTrackImpression
|
|
1
|
+
{"version":3,"file":"use-track-impression.cjs","names":[],"sources":["../../src/hooks/use-track-impression.ts"],"sourcesContent":["import { type RefCallback, useEffect, useEffectEvent, useRef, useState } from 'react';\nimport { track } from '../track/index';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\n\nexport function useTrackImpression<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n): RefCallback<Element> {\n const fired = useRef(false);\n const [node, setNode] = useState<Element | null>(null);\n\n const onTrack = useEffectEvent(() => {\n if (fired.current) return;\n track(name, properties);\n fired.current = true;\n });\n\n useEffect(() => {\n if (!node) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry.isIntersecting) return;\n onTrack();\n observer.disconnect();\n },\n { threshold: 0.5 }\n );\n\n observer.observe(node);\n return () => observer.disconnect();\n }, [node]);\n\n // A callback ref (contravariant in the element type) instead of a RefObject:\n // it attaches to any element without an R type parameter, so `name` stays the\n // only inference site and explicit type arguments are never needed. It also\n // observes elements that mount late, which the previous [ref.current] effect\n // dependency missed.\n return setNode;\n}\n"],"mappings":";;;;AAIA,SAAgB,mBACd,MACA,YACsB;CACtB,MAAM,SAAA,GAAA,MAAA,OAAA,CAAe,KAAK;CAC1B,MAAM,CAAC,MAAM,YAAA,GAAA,MAAA,SAAA,CAAoC,IAAI;CAErD,MAAM,WAAA,GAAA,MAAA,eAAA,OAA+B;EACnC,IAAI,MAAM,SAAS;EACnB,oBAAA,MAAM,MAAM,UAAU;EACtB,MAAM,UAAU;CAClB,CAAC;CAED,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,IAAI,CAAC,MAAM;EAEX,MAAM,WAAW,IAAI,sBAClB,CAAC,WAAW;GACX,IAAI,CAAC,MAAM,gBAAgB;GAC3B,QAAQ;GACR,SAAS,WAAW;EACtB,GACA,EAAE,WAAW,GAAI,CACnB;EAEA,SAAS,QAAQ,IAAI;EACrB,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,IAAI,CAAC;CAOT,OAAO;AACT"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventName, TrackName, TrackProperties } from "../track/types.cjs";
|
|
2
|
+
import { RefCallback } from "react";
|
|
2
3
|
//#region src/hooks/use-track-impression.d.ts
|
|
3
|
-
declare function useTrackImpression<
|
|
4
|
+
declare function useTrackImpression<T extends EventName = EventName>(name: TrackName<T>, properties?: TrackProperties<T>): RefCallback<Element>;
|
|
4
5
|
//#endregion
|
|
5
6
|
export { useTrackImpression };
|
|
6
7
|
//# sourceMappingURL=use-track-impression.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-track-impression.d.cts","names":[],"sources":["../../src/hooks/use-track-impression.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-track-impression.d.cts","names":[],"sources":["../../src/hooks/use-track-impression.ts"],"mappings":";;;iBAIgB,mBAAmB,UAAU,YAAY,WACvD,MAAM,UAAU,IAChB,aAAa,gBAAgB,KAC5B,YAAY"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventName, TrackName, TrackProperties } from "../track/types.mjs";
|
|
2
|
+
import { RefCallback } from "react";
|
|
2
3
|
//#region src/hooks/use-track-impression.d.ts
|
|
3
|
-
declare function useTrackImpression<
|
|
4
|
+
declare function useTrackImpression<T extends EventName = EventName>(name: TrackName<T>, properties?: TrackProperties<T>): RefCallback<Element>;
|
|
4
5
|
//#endregion
|
|
5
6
|
export { useTrackImpression };
|
|
6
7
|
//# sourceMappingURL=use-track-impression.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-track-impression.d.mts","names":[],"sources":["../../src/hooks/use-track-impression.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-track-impression.d.mts","names":[],"sources":["../../src/hooks/use-track-impression.ts"],"mappings":";;;iBAIgB,mBAAmB,UAAU,YAAY,WACvD,MAAM,UAAU,IAChB,aAAa,gBAAgB,KAC5B,YAAY"}
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import { track } from "../track/index.mjs";
|
|
2
|
-
import { useEffect, useEffectEvent, useRef } from "react";
|
|
2
|
+
import { useEffect, useEffectEvent, useRef, useState } from "react";
|
|
3
3
|
//#region src/hooks/use-track-impression.ts
|
|
4
4
|
function useTrackImpression(name, properties) {
|
|
5
5
|
const fired = useRef(false);
|
|
6
|
-
const
|
|
6
|
+
const [node, setNode] = useState(null);
|
|
7
7
|
const onTrack = useEffectEvent(() => {
|
|
8
8
|
if (fired.current) return;
|
|
9
9
|
track(name, properties);
|
|
10
10
|
fired.current = true;
|
|
11
11
|
});
|
|
12
12
|
useEffect(() => {
|
|
13
|
+
if (!node) return;
|
|
13
14
|
const observer = new IntersectionObserver(([entry]) => {
|
|
14
15
|
if (!entry.isIntersecting) return;
|
|
15
16
|
onTrack();
|
|
16
17
|
observer.disconnect();
|
|
17
18
|
}, { threshold: .5 });
|
|
18
|
-
|
|
19
|
+
observer.observe(node);
|
|
19
20
|
return () => observer.disconnect();
|
|
20
|
-
}, [
|
|
21
|
-
return
|
|
21
|
+
}, [node]);
|
|
22
|
+
return setNode;
|
|
22
23
|
}
|
|
23
24
|
//#endregion
|
|
24
25
|
export { useTrackImpression };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-track-impression.mjs","names":[],"sources":["../../src/hooks/use-track-impression.ts"],"sourcesContent":["import { useEffect, useEffectEvent, useRef } from 'react';\nimport { track } from '../track/index';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\n\nexport function useTrackImpression
|
|
1
|
+
{"version":3,"file":"use-track-impression.mjs","names":[],"sources":["../../src/hooks/use-track-impression.ts"],"sourcesContent":["import { type RefCallback, useEffect, useEffectEvent, useRef, useState } from 'react';\nimport { track } from '../track/index';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\n\nexport function useTrackImpression<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n): RefCallback<Element> {\n const fired = useRef(false);\n const [node, setNode] = useState<Element | null>(null);\n\n const onTrack = useEffectEvent(() => {\n if (fired.current) return;\n track(name, properties);\n fired.current = true;\n });\n\n useEffect(() => {\n if (!node) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry.isIntersecting) return;\n onTrack();\n observer.disconnect();\n },\n { threshold: 0.5 }\n );\n\n observer.observe(node);\n return () => observer.disconnect();\n }, [node]);\n\n // A callback ref (contravariant in the element type) instead of a RefObject:\n // it attaches to any element without an R type parameter, so `name` stays the\n // only inference site and explicit type arguments are never needed. It also\n // observes elements that mount late, which the previous [ref.current] effect\n // dependency missed.\n return setNode;\n}\n"],"mappings":";;;AAIA,SAAgB,mBACd,MACA,YACsB;CACtB,MAAM,QAAQ,OAAO,KAAK;CAC1B,MAAM,CAAC,MAAM,WAAW,SAAyB,IAAI;CAErD,MAAM,UAAU,qBAAqB;EACnC,IAAI,MAAM,SAAS;EACnB,MAAM,MAAM,UAAU;EACtB,MAAM,UAAU;CAClB,CAAC;CAED,gBAAgB;EACd,IAAI,CAAC,MAAM;EAEX,MAAM,WAAW,IAAI,sBAClB,CAAC,WAAW;GACX,IAAI,CAAC,MAAM,gBAAgB;GAC3B,QAAQ;GACR,SAAS,WAAW;EACtB,GACA,EAAE,WAAW,GAAI,CACnB;EAEA,SAAS,QAAQ,IAAI;EACrB,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,IAAI,CAAC;CAOT,OAAO;AACT"}
|
package/dist/link/index.cjs
CHANGED
|
@@ -23,7 +23,7 @@ async function getLink(id) {
|
|
|
23
23
|
console.error(`Failed to get link(${id}): ${response.status} ${await response.text()}`);
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
-
return response.json();
|
|
26
|
+
return await response.json();
|
|
27
27
|
} catch {
|
|
28
28
|
console.error(`Failed to get link(${id}): network error`);
|
|
29
29
|
return null;
|
package/dist/link/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["config"],"sources":["../../src/link/index.ts"],"sourcesContent":["import { fetch } from '@shware/utils';\nimport type { CreateLinkDTO } from '../schema/index';\nimport { config } from '../setup/index';\n\nexport interface Link extends CreateLinkDTO {\n id: string;\n created_at: string;\n}\n\nexport async function createLink(dto: CreateLinkDTO) {\n const response = await fetch(`${config.endpoint}/links`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to create link: ${response.status} ${await response.text()}`);\n }\n return response.json() as Promise<Link>;\n}\n\nexport async function getLink(id: string): Promise<Link | null> {\n try {\n const response = await fetch(`${config.endpoint}/links/${id}`, {\n method: 'GET',\n credentials: 'include',\n headers: await config.getHeaders(),\n });\n\n if (!response.ok) {\n console.error(`Failed to get link(${id}): ${response.status} ${await response.text()}`);\n return null;\n }\n return response.json() as
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["config"],"sources":["../../src/link/index.ts"],"sourcesContent":["import { fetch } from '@shware/utils';\nimport type { CreateLinkDTO } from '../schema/index';\nimport { config } from '../setup/index';\n\nexport interface Link extends CreateLinkDTO {\n id: string;\n created_at: string;\n}\n\nexport async function createLink(dto: CreateLinkDTO) {\n const response = await fetch(`${config.endpoint}/links`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to create link: ${response.status} ${await response.text()}`);\n }\n return response.json() as Promise<Link>;\n}\n\nexport async function getLink(id: string): Promise<Link | null> {\n try {\n const response = await fetch(`${config.endpoint}/links/${id}`, {\n method: 'GET',\n credentials: 'include',\n headers: await config.getHeaders(),\n });\n\n if (!response.ok) {\n console.error(`Failed to get link(${id}): ${response.status} ${await response.text()}`);\n return null;\n }\n return (await response.json()) as Link;\n } catch {\n console.error(`Failed to get link(${id}): network error`);\n return null;\n }\n}\n"],"mappings":";;;;AASA,eAAsB,WAAW,KAAoB;CACnD,MAAM,WAAW,OAAA,GAAA,cAAA,MAAA,CAAY,GAAGA,oBAAAA,OAAO,SAAS,SAAS;EACvD,QAAQ;EACR,aAAa;EACb,SAAS,MAAMA,oBAAAA,OAAO,WAAW;EACjC,MAAM,KAAK,UAAU,GAAG;CAC1B,CAAC;CAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,0BAA0B,SAAS,OAAO,GAAG,MAAM,SAAS,KAAK,GAAG;CAEtF,OAAO,SAAS,KAAK;AACvB;AAEA,eAAsB,QAAQ,IAAkC;CAC9D,IAAI;EACF,MAAM,WAAW,OAAA,GAAA,cAAA,MAAA,CAAY,GAAGA,oBAAAA,OAAO,SAAS,SAAS,MAAM;GAC7D,QAAQ;GACR,aAAa;GACb,SAAS,MAAMA,oBAAAA,OAAO,WAAW;EACnC,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GAChB,QAAQ,MAAM,sBAAsB,GAAG,KAAK,SAAS,OAAO,GAAG,MAAM,SAAS,KAAK,GAAG;GACtF,OAAO;EACT;EACA,OAAQ,MAAM,SAAS,KAAK;CAC9B,QAAQ;EACN,QAAQ,MAAM,sBAAsB,GAAG,iBAAiB;EACxD,OAAO;CACT;AACF"}
|
package/dist/link/index.mjs
CHANGED
|
@@ -22,7 +22,7 @@ async function getLink(id) {
|
|
|
22
22
|
console.error(`Failed to get link(${id}): ${response.status} ${await response.text()}`);
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
|
-
return response.json();
|
|
25
|
+
return await response.json();
|
|
26
26
|
} catch {
|
|
27
27
|
console.error(`Failed to get link(${id}): network error`);
|
|
28
28
|
return null;
|
package/dist/link/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/link/index.ts"],"sourcesContent":["import { fetch } from '@shware/utils';\nimport type { CreateLinkDTO } from '../schema/index';\nimport { config } from '../setup/index';\n\nexport interface Link extends CreateLinkDTO {\n id: string;\n created_at: string;\n}\n\nexport async function createLink(dto: CreateLinkDTO) {\n const response = await fetch(`${config.endpoint}/links`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to create link: ${response.status} ${await response.text()}`);\n }\n return response.json() as Promise<Link>;\n}\n\nexport async function getLink(id: string): Promise<Link | null> {\n try {\n const response = await fetch(`${config.endpoint}/links/${id}`, {\n method: 'GET',\n credentials: 'include',\n headers: await config.getHeaders(),\n });\n\n if (!response.ok) {\n console.error(`Failed to get link(${id}): ${response.status} ${await response.text()}`);\n return null;\n }\n return response.json() as
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/link/index.ts"],"sourcesContent":["import { fetch } from '@shware/utils';\nimport type { CreateLinkDTO } from '../schema/index';\nimport { config } from '../setup/index';\n\nexport interface Link extends CreateLinkDTO {\n id: string;\n created_at: string;\n}\n\nexport async function createLink(dto: CreateLinkDTO) {\n const response = await fetch(`${config.endpoint}/links`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to create link: ${response.status} ${await response.text()}`);\n }\n return response.json() as Promise<Link>;\n}\n\nexport async function getLink(id: string): Promise<Link | null> {\n try {\n const response = await fetch(`${config.endpoint}/links/${id}`, {\n method: 'GET',\n credentials: 'include',\n headers: await config.getHeaders(),\n });\n\n if (!response.ok) {\n console.error(`Failed to get link(${id}): ${response.status} ${await response.text()}`);\n return null;\n }\n return (await response.json()) as Link;\n } catch {\n console.error(`Failed to get link(${id}): network error`);\n return null;\n }\n}\n"],"mappings":";;;AASA,eAAsB,WAAW,KAAoB;CACnD,MAAM,WAAW,MAAM,MAAM,GAAG,OAAO,SAAS,SAAS;EACvD,QAAQ;EACR,aAAa;EACb,SAAS,MAAM,OAAO,WAAW;EACjC,MAAM,KAAK,UAAU,GAAG;CAC1B,CAAC;CAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,0BAA0B,SAAS,OAAO,GAAG,MAAM,SAAS,KAAK,GAAG;CAEtF,OAAO,SAAS,KAAK;AACvB;AAEA,eAAsB,QAAQ,IAAkC;CAC9D,IAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,OAAO,SAAS,SAAS,MAAM;GAC7D,QAAQ;GACR,aAAa;GACb,SAAS,MAAM,OAAO,WAAW;EACnC,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GAChB,QAAQ,MAAM,sBAAsB,GAAG,KAAK,SAAS,OAAO,GAAG,MAAM,SAAS,KAAK,GAAG;GACtF,OAAO;EACT;EACA,OAAQ,MAAM,SAAS,KAAK;CAC9B,QAAQ;EACN,QAAQ,MAAM,sBAAsB,GAAG,iBAAiB;EACxD,OAAO;CACT;AACF"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let react_native = require("react-native");
|
|
2
3
|
let expo_application = require("expo-application");
|
|
3
4
|
let expo_clipboard = require("expo-clipboard");
|
|
4
5
|
let expo_device = require("expo-device");
|
|
5
6
|
let expo_localization = require("expo-localization");
|
|
6
|
-
let react_native = require("react-native");
|
|
7
7
|
//#region src/native/fingerprint.ts
|
|
8
8
|
/**
|
|
9
9
|
* reference: https://docs.swmansion.com/detour/docs/Architecture/matching
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { Dimensions, PixelRatio, Platform } from "react-native";
|
|
1
2
|
import { getInstallReferrerAsync } from "expo-application";
|
|
2
3
|
import { getStringAsync } from "expo-clipboard";
|
|
3
4
|
import { manufacturer, modelId, modelName, osName, osVersion, supportedCpuArchitectures } from "expo-device";
|
|
4
5
|
import { getCalendars, getLocales } from "expo-localization";
|
|
5
|
-
import { Dimensions, PixelRatio, Platform } from "react-native";
|
|
6
6
|
//#region src/native/fingerprint.ts
|
|
7
7
|
/**
|
|
8
8
|
* reference: https://docs.swmansion.com/detour/docs/Architecture/matching
|
package/dist/native/firebase.cjs
CHANGED
|
@@ -3,11 +3,11 @@ let _react_native_firebase_analytics = require("@react-native-firebase/analytics
|
|
|
3
3
|
//#region src/native/firebase.ts
|
|
4
4
|
const analytics = (0, _react_native_firebase_analytics.getAnalytics)();
|
|
5
5
|
async function sendFirebaseEvent(name, properties) {
|
|
6
|
-
if (name === "screen_view")
|
|
6
|
+
if (name === "screen_view") (0, _react_native_firebase_analytics.logEvent)(analytics, "screen_view", {
|
|
7
7
|
firebase_screen: properties?.screen_name,
|
|
8
8
|
firebase_screen_class: properties?.screen_class
|
|
9
9
|
});
|
|
10
|
-
else
|
|
10
|
+
else (0, _react_native_firebase_analytics.logEvent)(analytics, name, properties);
|
|
11
11
|
}
|
|
12
12
|
//#endregion
|
|
13
13
|
exports.sendFirebaseEvent = sendFirebaseEvent;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firebase.cjs","names":[],"sources":["../../src/native/firebase.ts"],"sourcesContent":["import { getAnalytics, logEvent } from '@react-native-firebase/analytics';\nimport type { StandardEvents } from '../track/gtag';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\n\nconst analytics = getAnalytics();\n\nexport async function sendFirebaseEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n if (name === 'screen_view') {\n
|
|
1
|
+
{"version":3,"file":"firebase.cjs","names":[],"sources":["../../src/native/firebase.ts"],"sourcesContent":["import { getAnalytics, logEvent } from '@react-native-firebase/analytics';\nimport type { StandardEvents } from '../track/gtag';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\n\nconst analytics = getAnalytics();\n\nexport async function sendFirebaseEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n // @react-native-firebase/analytics 26 made the modular logEvent fire-and-forget (void).\n // sendFirebaseEvent stays async so callers that await it keep compiling.\n if (name === 'screen_view') {\n logEvent(analytics, 'screen_view', {\n firebase_screen: (properties as StandardEvents['screen_view'] | undefined)?.screen_name,\n firebase_screen_class: (properties as StandardEvents['screen_view'] | undefined)\n ?.screen_class,\n });\n } else {\n logEvent(analytics, name, properties);\n }\n}\n"],"mappings":";;;AAIA,MAAM,aAAA,GAAA,iCAAA,aAAA,CAAyB;AAE/B,eAAsB,kBACpB,MACA,YACA;CAGA,IAAI,SAAS,eACX,CAAA,GAAA,iCAAA,SAAA,CAAS,WAAW,eAAe;EACjC,iBAAkB,YAA0D;EAC5E,uBAAwB,YACpB;CACN,CAAC;MAED,CAAA,GAAA,iCAAA,SAAA,CAAS,WAAW,MAAM,UAAU;AAExC"}
|
package/dist/native/firebase.mjs
CHANGED
|
@@ -2,11 +2,11 @@ import { getAnalytics, logEvent } from "@react-native-firebase/analytics";
|
|
|
2
2
|
//#region src/native/firebase.ts
|
|
3
3
|
const analytics = getAnalytics();
|
|
4
4
|
async function sendFirebaseEvent(name, properties) {
|
|
5
|
-
if (name === "screen_view")
|
|
5
|
+
if (name === "screen_view") logEvent(analytics, "screen_view", {
|
|
6
6
|
firebase_screen: properties?.screen_name,
|
|
7
7
|
firebase_screen_class: properties?.screen_class
|
|
8
8
|
});
|
|
9
|
-
else
|
|
9
|
+
else logEvent(analytics, name, properties);
|
|
10
10
|
}
|
|
11
11
|
//#endregion
|
|
12
12
|
export { sendFirebaseEvent };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firebase.mjs","names":[],"sources":["../../src/native/firebase.ts"],"sourcesContent":["import { getAnalytics, logEvent } from '@react-native-firebase/analytics';\nimport type { StandardEvents } from '../track/gtag';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\n\nconst analytics = getAnalytics();\n\nexport async function sendFirebaseEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n if (name === 'screen_view') {\n
|
|
1
|
+
{"version":3,"file":"firebase.mjs","names":[],"sources":["../../src/native/firebase.ts"],"sourcesContent":["import { getAnalytics, logEvent } from '@react-native-firebase/analytics';\nimport type { StandardEvents } from '../track/gtag';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\n\nconst analytics = getAnalytics();\n\nexport async function sendFirebaseEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n // @react-native-firebase/analytics 26 made the modular logEvent fire-and-forget (void).\n // sendFirebaseEvent stays async so callers that await it keep compiling.\n if (name === 'screen_view') {\n logEvent(analytics, 'screen_view', {\n firebase_screen: (properties as StandardEvents['screen_view'] | undefined)?.screen_name,\n firebase_screen_class: (properties as StandardEvents['screen_view'] | undefined)\n ?.screen_class,\n });\n } else {\n logEvent(analytics, name, properties);\n }\n}\n"],"mappings":";;AAIA,MAAM,YAAY,aAAa;AAE/B,eAAsB,kBACpB,MACA,YACA;CAGA,IAAI,SAAS,eACX,SAAS,WAAW,eAAe;EACjC,iBAAkB,YAA0D;EAC5E,uBAAwB,YACpB;CACN,CAAC;MAED,SAAS,WAAW,MAAM,UAAU;AAExC"}
|
package/dist/native/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_hooks_use_app_analytics = require("../hooks/use-app-analytics.cjs");
|
|
2
3
|
const require_native_fingerprint = require("./fingerprint.cjs");
|
|
3
4
|
const require_native_setup = require("./setup.cjs");
|
|
4
|
-
const require_hooks_use_app_analytics = require("../hooks/use-app-analytics.cjs");
|
|
5
5
|
exports.getDeterministicFingerprint = require_native_fingerprint.getDeterministicFingerprint;
|
|
6
6
|
exports.getDeviceId = require_native_setup.getDeviceId;
|
|
7
7
|
exports.getDeviceType = require_native_setup.getDeviceType;
|
package/dist/native/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
+
import { useAppAnalytics } from "../hooks/use-app-analytics.mjs";
|
|
1
2
|
import { getDeterministicFingerprint, getProbabilisticFingerprint } from "./fingerprint.mjs";
|
|
2
3
|
import { getDeviceId, getDeviceType, getTags, storage } from "./setup.mjs";
|
|
3
|
-
import { useAppAnalytics } from "../hooks/use-app-analytics.mjs";
|
|
4
4
|
export { getDeterministicFingerprint, getDeviceId, getDeviceType, getProbabilisticFingerprint, getTags, storage, useAppAnalytics };
|
package/dist/native/setup.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_setup_index = require("../setup/index.cjs");
|
|
3
|
+
let react_native = require("react-native");
|
|
3
4
|
let expo_application = require("expo-application");
|
|
4
5
|
let expo_device = require("expo-device");
|
|
5
6
|
let expo_localization = require("expo-localization");
|
|
6
|
-
let react_native = require("react-native");
|
|
7
7
|
require("expo-sqlite/localStorage/install");
|
|
8
8
|
let expo_crypto = require("expo-crypto");
|
|
9
9
|
let expo_tracking_transparency = require("expo-tracking-transparency");
|
package/dist/native/setup.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { cache, config } from "../setup/index.mjs";
|
|
2
|
+
import { Dimensions, PixelRatio, Platform } from "react-native";
|
|
2
3
|
import { getAndroidId, getInstallReferrerAsync, getIosIdForVendorAsync } from "expo-application";
|
|
3
4
|
import { DeviceType, deviceType, manufacturer, modelId, modelName, osName, osVersion } from "expo-device";
|
|
4
5
|
import { getCalendars, getLocales } from "expo-localization";
|
|
5
|
-
import { Dimensions, PixelRatio, Platform } from "react-native";
|
|
6
6
|
import "expo-sqlite/localStorage/install";
|
|
7
7
|
import { randomUUID } from "expo-crypto";
|
|
8
8
|
import { getAdvertisingId } from "expo-tracking-transparency";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_track_index = require("../track/index.cjs");
|
|
3
3
|
const require_hooks_use_outbound_click_analytics = require("../hooks/use-outbound-click-analytics.cjs");
|
|
4
|
-
const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs");
|
|
5
4
|
const require_hooks_use_report_web_vitals = require("../hooks/use-report-web-vitals.cjs");
|
|
5
|
+
const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs");
|
|
6
6
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
7
7
|
let react_router = require("react-router");
|
|
8
8
|
//#region src/react-router/index.tsx
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { track } from "../track/index.mjs";
|
|
2
2
|
import { useOutboundClickAnalytics } from "../hooks/use-outbound-click-analytics.mjs";
|
|
3
|
-
import { useWebAnalytics } from "../hooks/use-web-analytics.mjs";
|
|
4
3
|
import { useReportWebVitals } from "../hooks/use-report-web-vitals.mjs";
|
|
4
|
+
import { useWebAnalytics } from "../hooks/use-web-analytics.mjs";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { useLocation } from "react-router";
|
|
7
7
|
//#region src/react-router/index.tsx
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_track_index = require("../track/index.cjs");
|
|
3
3
|
const require_hooks_use_outbound_click_analytics = require("../hooks/use-outbound-click-analytics.cjs");
|
|
4
|
-
const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs");
|
|
5
4
|
const require_hooks_use_report_web_vitals = require("../hooks/use-report-web-vitals.cjs");
|
|
5
|
+
const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs");
|
|
6
6
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
7
7
|
let _tanstack_react_router = require("@tanstack/react-router");
|
|
8
8
|
//#region src/tanstack/analytics.tsx
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { track } from "../track/index.mjs";
|
|
2
2
|
import { useOutboundClickAnalytics } from "../hooks/use-outbound-click-analytics.mjs";
|
|
3
|
-
import { useWebAnalytics } from "../hooks/use-web-analytics.mjs";
|
|
4
3
|
import { useReportWebVitals } from "../hooks/use-report-web-vitals.mjs";
|
|
4
|
+
import { useWebAnalytics } from "../hooks/use-web-analytics.mjs";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { useLocation } from "@tanstack/react-router";
|
|
7
7
|
//#region src/tanstack/analytics.tsx
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shware/analytics",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -128,23 +128,23 @@
|
|
|
128
128
|
"dependencies": {
|
|
129
129
|
"cookie": "^2.0.1",
|
|
130
130
|
"uuid": "^14.0.1",
|
|
131
|
-
"web-vitals": "^
|
|
131
|
+
"web-vitals": "^6.1.0",
|
|
132
132
|
"zod": "^4.4.3",
|
|
133
133
|
"@shware/utils": "^1.5.2"
|
|
134
134
|
},
|
|
135
135
|
"devDependencies": {
|
|
136
136
|
"@types/facebook-nodejs-business-sdk": "^24.0.0",
|
|
137
|
-
"@types/node": "^26.1.
|
|
138
|
-
"@types/react": "^19.2.
|
|
137
|
+
"@types/node": "^26.1.2",
|
|
138
|
+
"@types/react": "^19.2.18",
|
|
139
139
|
"bowser": "^2.14.1",
|
|
140
140
|
"typescript": "^7.0.2",
|
|
141
141
|
"vitest": "^4.1.10",
|
|
142
142
|
"@repo/typescript-config": "0.0.0"
|
|
143
143
|
},
|
|
144
144
|
"peerDependencies": {
|
|
145
|
-
"@react-native-firebase/analytics": "^
|
|
145
|
+
"@react-native-firebase/analytics": "^26.0.0",
|
|
146
146
|
"@tanstack/react-router": "^1.170.18",
|
|
147
|
-
"@tanstack/react-start": "^1.168.
|
|
147
|
+
"@tanstack/react-start": "^1.168.34",
|
|
148
148
|
"expo-application": "^57.0.2",
|
|
149
149
|
"expo-clipboard": "^57.0.1",
|
|
150
150
|
"expo-crypto": "^57.0.1",
|
|
@@ -153,13 +153,13 @@
|
|
|
153
153
|
"expo-sqlite": "^57.0.1",
|
|
154
154
|
"expo-tracking-transparency": "^57.0.1",
|
|
155
155
|
"facebook-nodejs-business-sdk": "^24.0.1",
|
|
156
|
-
"next": "^16.2.
|
|
157
|
-
"posthog-js": "^1.
|
|
156
|
+
"next": "^16.2.12",
|
|
157
|
+
"posthog-js": "^1.409.5",
|
|
158
158
|
"react": "^19.2.8",
|
|
159
|
-
"react-native": "^0.86.
|
|
159
|
+
"react-native": "^0.86.2",
|
|
160
160
|
"react-native-fbsdk-next": "^13.4.3",
|
|
161
161
|
"react-native-url-polyfill": "^4.0.0",
|
|
162
|
-
"react-router": "^8.
|
|
162
|
+
"react-router": "^8.3.0"
|
|
163
163
|
},
|
|
164
164
|
"peerDependenciesMeta": {
|
|
165
165
|
"@react-native-firebase/analytics": {
|