@jmp-technologies/analytics 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +22 -1
  2. package/dist/astro/client.cjs.map +1 -1
  3. package/dist/astro/client.d.cts +2 -2
  4. package/dist/astro/client.d.ts +2 -2
  5. package/dist/astro/client.js +2 -2
  6. package/dist/astro/content.cjs.map +1 -1
  7. package/dist/astro/content.d.cts +2 -2
  8. package/dist/astro/content.d.ts +2 -2
  9. package/dist/astro/content.js +3 -3
  10. package/dist/astro/integration.cjs +1 -1
  11. package/dist/astro/integration.cjs.map +1 -1
  12. package/dist/astro/integration.js +3 -3
  13. package/dist/astro/middleware.cjs.map +1 -1
  14. package/dist/astro/middleware.d.cts +2 -2
  15. package/dist/astro/middleware.d.ts +2 -2
  16. package/dist/astro/middleware.js +2 -2
  17. package/dist/astro/register.cjs +1 -1
  18. package/dist/astro/register.cjs.map +1 -1
  19. package/dist/astro/register.js +3 -3
  20. package/dist/chunk-2KAGOVRZ.js +45 -0
  21. package/dist/chunk-2KAGOVRZ.js.map +1 -0
  22. package/dist/{chunk-G3SNRG7J.js → chunk-5GORAM7J.js} +2 -2
  23. package/dist/{chunk-776LXUWH.js → chunk-7MLPSL6T.js} +2 -2
  24. package/dist/{chunk-HIJ6OV4S.js → chunk-AJ33NQVZ.js} +3 -3
  25. package/dist/chunk-L67PVZYL.js +154 -0
  26. package/dist/chunk-L67PVZYL.js.map +1 -0
  27. package/dist/{chunk-B3FYJIXY.js → chunk-MOX7RJUU.js} +1 -1
  28. package/dist/{chunk-B3FYJIXY.js.map → chunk-MOX7RJUU.js.map} +1 -1
  29. package/dist/{chunk-QUNCGUEE.js → chunk-OLWCYWVB.js} +3 -3
  30. package/dist/{chunk-W6VYMD2P.js → chunk-VRDPVKAN.js} +6 -4
  31. package/dist/{chunk-W6VYMD2P.js.map → chunk-VRDPVKAN.js.map} +1 -1
  32. package/dist/{create-jmp-analytics-BdGOgPxl.d.ts → create-jmp-analytics-COxo3-yg.d.ts} +1 -1
  33. package/dist/{create-jmp-analytics-DxhHOmtR.d.cts → create-jmp-analytics-ClLOMb9p.d.cts} +1 -1
  34. package/dist/index.cjs +25 -24
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +6 -6
  37. package/dist/index.d.ts +6 -6
  38. package/dist/index.js +2 -2
  39. package/dist/next/content.cjs.map +1 -1
  40. package/dist/next/content.d.cts +2 -2
  41. package/dist/next/content.d.ts +2 -2
  42. package/dist/next/content.js +3 -3
  43. package/dist/next/google-tag.cjs +80 -0
  44. package/dist/next/google-tag.cjs.map +1 -0
  45. package/dist/next/google-tag.d.cts +17 -0
  46. package/dist/next/google-tag.d.ts +17 -0
  47. package/dist/next/google-tag.js +8 -0
  48. package/dist/next/google-tag.js.map +1 -0
  49. package/dist/next/instrumentation.cjs +1 -1
  50. package/dist/next/instrumentation.cjs.map +1 -1
  51. package/dist/next/instrumentation.js +2 -2
  52. package/dist/next/middleware.cjs.map +1 -1
  53. package/dist/next/middleware.d.cts +2 -2
  54. package/dist/next/middleware.d.ts +2 -2
  55. package/dist/next/middleware.js +2 -2
  56. package/dist/next/site-root-server.cjs +1134 -0
  57. package/dist/next/site-root-server.cjs.map +1 -0
  58. package/dist/next/site-root-server.d.cts +24 -0
  59. package/dist/next/site-root-server.d.ts +24 -0
  60. package/dist/next/site-root-server.js +62 -0
  61. package/dist/next/site-root-server.js.map +1 -0
  62. package/dist/next.cjs +110 -29
  63. package/dist/next.cjs.map +1 -1
  64. package/dist/next.d.cts +16 -7
  65. package/dist/next.d.ts +16 -7
  66. package/dist/next.js +15 -108
  67. package/dist/next.js.map +1 -1
  68. package/dist/react.cjs +8 -1
  69. package/dist/react.cjs.map +1 -1
  70. package/dist/react.d.cts +2 -2
  71. package/dist/react.d.ts +2 -2
  72. package/dist/react.js +3 -3
  73. package/dist/{site-config-BRuZWGSW.d.cts → site-config-8TTHIW6w.d.cts} +6 -1
  74. package/dist/{site-config-hkGMSeT7.d.ts → site-config-BSMVyUjk.d.ts} +6 -1
  75. package/dist/{track-page-view-Cr8I4Unm.d.cts → track-page-view-BweavmXz.d.cts} +1 -1
  76. package/dist/{track-page-view-CcPlzQNB.d.ts → track-page-view-DtxbdM5q.d.ts} +1 -1
  77. package/dist/{types-BtiK_Acj.d.ts → types-Bk0ST1aH.d.cts} +2 -0
  78. package/dist/{types-BtiK_Acj.d.cts → types-Bk0ST1aH.d.ts} +2 -0
  79. package/package.json +7 -2
  80. /package/dist/{chunk-G3SNRG7J.js.map → chunk-5GORAM7J.js.map} +0 -0
  81. /package/dist/{chunk-776LXUWH.js.map → chunk-7MLPSL6T.js.map} +0 -0
  82. /package/dist/{chunk-HIJ6OV4S.js.map → chunk-AJ33NQVZ.js.map} +0 -0
  83. /package/dist/{chunk-QUNCGUEE.js.map → chunk-OLWCYWVB.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/next/site-root-server.tsx","../../src/constants.ts","../../src/http.ts","../../src/content/fetch.ts","../../src/content/api.ts","../../src/env-public.ts","../../src/site-config.ts","../../src/next.tsx","../../src/landing-capture.ts","../../src/tracker/page-view-dedupe.ts","../../src/contact-link-classifier.ts","../../src/consent.ts","../../src/tracker/helpers.ts","../../src/tracker/create-jmp-analytics.ts","../../src/env/browser.ts","../../src/env/browser-next.ts","../../src/react.tsx","../../src/next/google-tag.tsx"],"sourcesContent":["import type { ReactNode } from \"react\"\n\nimport { getJmpSiteProfile } from \"../content/api\"\nimport { readJmpPublicEnvFromProcess } from \"../env-public\"\nimport { getJmpContentConfig, getJmpSiteDefinition } from \"../site-config\"\nimport { JmpSiteRoot } from \"../next\"\nimport { JmpGoogleTag } from \"./google-tag\"\n\nexport type JmpSiteRootServerProps = {\n children: ReactNode\n deferUntilConsent?: boolean\n /**\n * When true (default), loads GA4 from the dashboard site profile when\n * `NEXT_PUBLIC_GA_MEASUREMENT_ID` is unset. Set false to use env-only GA.\n */\n gaFromPortal?: boolean\n}\n\nasync function resolveGaMeasurementId(\n gaFromPortal: boolean\n): Promise<string | null> {\n const fromEnv = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID?.trim()\n if (fromEnv) {\n return fromEnv\n }\n if (!gaFromPortal) {\n return null\n }\n const config = getJmpContentConfig()\n if (!config) {\n return null\n }\n try {\n const profile = await getJmpSiteProfile(config)\n return profile.ga_measurement_id?.trim() || null\n } catch {\n return null\n }\n}\n\n/**\n * Server Component: reads `process.env` at request time and passes config into the\n * client tracker. Use this in `app/layout.tsx` instead of importing `JmpSiteRoot`\n * directly — fixes missing `POST /api/events` when env vars are not inlined into\n * client chunks (common on Vercel if vars were added after build or mis-scoped).\n *\n * Optional GA4: set `ga_measurement_id` in jmp-dashboard (Content → Contact, admin)\n * or `NEXT_PUBLIC_GA_MEASUREMENT_ID` on the client site (env wins).\n */\nexport async function JmpSiteRootServer({\n children,\n deferUntilConsent,\n gaFromPortal = true,\n}: JmpSiteRootServerProps) {\n const pub = readJmpPublicEnvFromProcess()\n const site = getJmpSiteDefinition()\n const gaId = await resolveGaMeasurementId(gaFromPortal)\n\n return (\n <>\n {gaId ? <JmpGoogleTag gaId={gaId} /> : null}\n <JmpSiteRoot\n analyticsUrl={pub.analyticsUrl ?? site?.analyticsPortalUrl}\n trackingKey={pub.trackingKey}\n deferUntilConsent={deferUntilConsent}\n >\n {children}\n </JmpSiteRoot>\n </>\n )\n}\n","/** Published package version — sent with capabilities for admin readiness. */\nexport const JMP_ANALYTICS_SDK_VERSION = \"0.1.25\"\n\n/** `localStorage` key when {@link JmpAnalyticsConfig.requireConsent} is enabled. */\nexport const JMP_TRACKING_CONSENT_STORAGE_KEY = \"jmp_analytics_consent_v1\"\n\n/** Default `pageViewDedupeMs` when resolving browser config from env (Strict Mode / SPA). */\nexport const DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 2500\n\n/** Marks links tracked explicitly — skips delegated `autoTrackContactLinks` handler. */\nexport const JMP_TRACKED_LINK_ATTR = \"data-jmp-tracked\"\n","import type { JmpServerRequestLike } from \"./types\"\n\n/** CDN / Next.js often return HTML error pages; API errors are JSON */\nexport function responseLooksLikeHtml(body: string): boolean {\n const s = body.trimStart()\n return (\n s.startsWith(\"<!\") ||\n s.toLowerCase().startsWith(\"<html\") ||\n (s.startsWith(\"<\") && /<html[\\s>]/i.test(s.slice(0, 500)))\n )\n}\n\nexport function normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/$/, \"\")\n}\n\nexport function getHeader(\n headers: JmpServerRequestLike[\"headers\"] | undefined,\n key: string\n): string | null {\n if (!headers) {\n return null\n }\n if (typeof (headers as Pick<Headers, \"get\">).get === \"function\") {\n return (headers as Pick<Headers, \"get\">).get(key)\n }\n const record = headers as Record<string, string | string[] | undefined | null>\n const direct = record[key] ?? record[key.toLowerCase()]\n if (Array.isArray(direct)) {\n return direct[0] ?? null\n }\n return direct ?? null\n}\n\nexport function pathFromRequestUrl(url: string | undefined): string {\n if (!url) {\n return \"/\"\n }\n try {\n const u = new URL(url)\n return `${u.pathname}${u.search}`\n } catch {\n return url.startsWith(\"/\") ? url : \"/\"\n }\n}\n\nexport function resolveFetch(\n fetchImpl: typeof fetch | undefined,\n context: string\n): typeof fetch {\n const resolved =\n fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n if (!resolved) {\n throw new Error(`${context}: fetch is not available; pass fetchImpl in config`)\n }\n return resolved\n}\n\ntype ApiErrorJson = {\n error?: string\n code?: string\n hint?: string\n expectedDomain?: string\n}\n\nexport function formatApiErrorMessage(\n status: number,\n text: string,\n prefix: string\n): string {\n if (responseLooksLikeHtml(text)) {\n return `${prefix} HTTP ${status} — response was HTML (not the JSON API). Check baseUrl points at the JMP Analytics dashboard.`\n }\n let parsed: ApiErrorJson = {}\n try {\n parsed = JSON.parse(text) as ApiErrorJson\n } catch {\n parsed = { error: text }\n }\n const detail = parsed.error ?? text\n return [\n `${prefix} HTTP ${status}`,\n parsed.code ? `[${parsed.code}]` : null,\n detail,\n parsed.hint ? `Hint: ${parsed.hint}` : null,\n parsed.expectedDomain\n ? `Domain in JMP admin: ${parsed.expectedDomain}`\n : null,\n ]\n .filter(Boolean)\n .join(\" — \")\n}\n","import { normalizeBaseUrl, resolveFetch, responseLooksLikeHtml } from \"../http\"\nimport type { JmpContentConfig } from \"../types\"\n\nexport async function getContent<T>(\n config: JmpContentConfig,\n path: string\n): Promise<T> {\n const text = await getContentText(config, path)\n\n try {\n return JSON.parse(text) as T\n } catch {\n const baseUrl = normalizeBaseUrl(config.baseUrl)\n const url = `${baseUrl}${path}`\n throw new Error(`JMP content HTTP 200 — ${url} returned invalid JSON.`)\n }\n}\n\nexport async function getContentText(\n config: JmpContentConfig,\n path: string\n): Promise<string> {\n const baseUrl = normalizeBaseUrl(config.baseUrl)\n const resolvedFetch =\n config.fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n\n if (!resolvedFetch) {\n throw new Error(\"JMP content: fetch is not available; pass fetchImpl in config\")\n }\n\n const url = `${baseUrl}${path}`\n\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"GET\", url)\n }\n\n const res = await resolvedFetch(url, {\n method: \"GET\",\n headers: {\n \"X-JMP-Tracking-Key\": config.trackingKey,\n },\n })\n\n const text = await res.text()\n\n if (!res.ok) {\n if (responseLooksLikeHtml(text)) {\n throw new Error(\n `JMP content HTTP ${res.status} — ${url} returned HTML (not the JSON API). ` +\n `Set baseUrl to your JMP Analytics dashboard origin.`\n )\n }\n\n let parsed: { error?: string; hint?: string } = {}\n try {\n parsed = JSON.parse(text) as typeof parsed\n } catch {\n parsed = { error: text }\n }\n\n throw new Error(\n [`JMP content HTTP ${res.status}`, parsed.error, parsed.hint]\n .filter(Boolean)\n .join(\" — \")\n )\n }\n\n return text\n}\n","import { JMP_ANALYTICS_SDK_VERSION } from \"../constants\"\nimport { normalizeBaseUrl, resolveFetch } from \"../http\"\nimport type {\n JmpBlogPost,\n JmpContentConfig,\n JmpFaqItem,\n JmpSiteProfile,\n JmpTrustBundle,\n} from \"../types\"\nimport { getContent, getContentText } from \"./fetch\"\n\nexport async function getJmpBlogPosts(\n config: JmpContentConfig\n): Promise<JmpBlogPost[]> {\n const data = await getContent<{ posts: JmpBlogPost[] }>(\n config,\n \"/api/content/blog\"\n )\n return data.posts\n}\n\nexport async function getJmpBlogPost(\n config: JmpContentConfig,\n slug: string\n): Promise<JmpBlogPost | null> {\n try {\n const data = await getContent<{ post: JmpBlogPost }>(\n config,\n `/api/content/blog/${encodeURIComponent(slug)}`\n )\n return data.post\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"HTTP 404\")) {\n return null\n }\n throw error\n }\n}\n\nexport async function getJmpFaqs(\n config: JmpContentConfig\n): Promise<JmpFaqItem[]> {\n const data = await getContent<{ faqs: JmpFaqItem[] }>(\n config,\n \"/api/content/faqs\"\n )\n return data.faqs\n}\n\nexport async function getJmpTrustBundle(\n config: JmpContentConfig\n): Promise<JmpTrustBundle> {\n return getContent<JmpTrustBundle>(config, \"/api/content/trust\")\n}\n\nexport async function getJmpSiteProfile(\n config: JmpContentConfig\n): Promise<JmpSiteProfile> {\n const data = await getContent<{ site: JmpSiteProfile }>(\n config,\n \"/api/content/site\"\n )\n return data.site\n}\n\nexport async function getJmpRobotsTxt(config: JmpContentConfig): Promise<string> {\n return getContentText(config, \"/api/content/robots\")\n}\n\nconst registeredContentWrappers = new Set<string>()\n\nfunction resolveContentWrappersFromConfig(\n config: JmpContentConfig\n): string[] {\n if (config.contentWrappers?.length) {\n return [...config.contentWrappers]\n }\n return []\n}\n\n/**\n * Tell the JMP dashboard which content wrappers this site wires (blog, faq, trust.*, contact).\n * Safe to call on every cold start; dedupes by tracking key + wrapper list.\n */\nexport async function registerJmpContentWrappers(\n config: JmpContentConfig\n): Promise<\n { ok: true; wrappers: string[] } | { ok: false; skipped: true }\n> {\n const wrappers = resolveContentWrappersFromConfig(config)\n if (!wrappers.length) {\n return { ok: false, skipped: true }\n }\n\n const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(\",\")}`\n if (registeredContentWrappers.has(dedupeKey)) {\n return { ok: true, wrappers }\n }\n\n const baseUrl = normalizeBaseUrl(config.baseUrl)\n const resolvedFetch =\n config.fetchImpl ??\n (typeof globalThis !== \"undefined\" && globalThis.fetch\n ? globalThis.fetch.bind(globalThis)\n : undefined)\n\n if (!resolvedFetch) {\n throw new Error(\n \"JMP content wrappers: fetch is not available; pass fetchImpl in config\"\n )\n }\n\n const url = `${baseUrl}/api/content/capabilities`\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, { wrappers })\n }\n\n const res = await resolvedFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n },\n body: JSON.stringify({\n wrappers,\n sdkVersion: JMP_ANALYTICS_SDK_VERSION,\n }),\n })\n\n const text = await res.text()\n if (!res.ok) {\n let parsed: { error?: string; hint?: string } = {}\n try {\n parsed = JSON.parse(text) as typeof parsed\n } catch {\n parsed = { error: text }\n }\n throw new Error(\n [`JMP content wrappers HTTP ${res.status}`, parsed.error, parsed.hint]\n .filter(Boolean)\n .join(\" — \")\n )\n }\n\n registeredContentWrappers.add(dedupeKey)\n const data = JSON.parse(text) as { wrappers?: string[] }\n return { ok: true, wrappers: data.wrappers ?? wrappers }\n}\n","/**\n * Public env vars for JMP — supports Next.js (`NEXT_PUBLIC_*`) and Astro (`PUBLIC_*`).\n * Server/build: `process.env`. Astro client bundles: pass overrides from `import.meta.env`.\n */\n\nexport type JmpPublicEnv = {\n analyticsUrl?: string\n trackingKey?: string\n requireConsent?: boolean\n autoContactLinks?: boolean\n}\n\nfunction firstDefined(...values: (string | undefined)[]): string | undefined {\n for (const v of values) {\n if (v) return v\n }\n return undefined\n}\n\n/** Static reads so Next.js / Vite inline `NEXT_PUBLIC_*` into client bundles. */\nfunction readJmpPublicEnvFromProcessEnv(): Omit<JmpPublicEnv, never> {\n if (typeof process === \"undefined\" || !process.env) {\n return {}\n }\n const { env } = process\n const analyticsUrl = firstDefined(\n env.NEXT_PUBLIC_JMP_ANALYTICS_URL,\n env.PUBLIC_JMP_ANALYTICS_URL,\n )\n const trackingKey = firstDefined(\n env.NEXT_PUBLIC_JMP_TRACKING_KEY,\n env.PUBLIC_JMP_TRACKING_KEY,\n )\n const requireConsentRaw = firstDefined(\n env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,\n env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,\n )\n const autoContactRaw = firstDefined(\n env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,\n env.PUBLIC_JMP_AUTO_CONTACT_LINKS,\n )\n\n return {\n analyticsUrl,\n trackingKey,\n requireConsent: requireConsentRaw === \"true\",\n autoContactLinks: autoContactRaw === \"true\",\n }\n}\n\n/** Read dashboard URL + tracking key from Node / build-time env. */\nexport function readJmpPublicEnvFromProcess(\n overrides?: Partial<JmpPublicEnv>,\n): JmpPublicEnv {\n const fromEnv = readJmpPublicEnvFromProcessEnv()\n\n return {\n analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),\n trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),\n requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,\n autoContactLinks:\n overrides?.autoContactLinks ?? fromEnv.autoContactLinks,\n }\n}\n\n/** Read from Astro/Vite `import.meta.env` in client scripts. */\nexport function readJmpPublicEnvFromImportMeta(\n meta: Record<string, unknown> | undefined,\n overrides?: Partial<JmpPublicEnv>,\n): JmpPublicEnv {\n const get = (key: string): string | undefined => {\n const v = meta?.[key]\n return typeof v === \"string\" && v.length > 0 ? v : undefined\n }\n\n const analyticsUrl = firstDefined(\n overrides?.analyticsUrl,\n get(\"PUBLIC_JMP_ANALYTICS_URL\"),\n get(\"NEXT_PUBLIC_JMP_ANALYTICS_URL\"),\n )\n const trackingKey = firstDefined(\n overrides?.trackingKey,\n get(\"PUBLIC_JMP_TRACKING_KEY\"),\n get(\"NEXT_PUBLIC_JMP_TRACKING_KEY\"),\n )\n const requireConsentRaw = firstDefined(\n overrides?.requireConsent !== undefined\n ? String(overrides.requireConsent)\n : undefined,\n get(\"PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT\"),\n get(\"NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT\"),\n )\n const autoContactRaw = firstDefined(\n overrides?.autoContactLinks !== undefined\n ? String(overrides.autoContactLinks)\n : undefined,\n get(\"PUBLIC_JMP_AUTO_CONTACT_LINKS\"),\n get(\"NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS\"),\n )\n\n return {\n analyticsUrl,\n trackingKey,\n requireConsent: requireConsentRaw === \"true\",\n autoContactLinks: autoContactRaw === \"true\",\n }\n}\n","import type { JmpContentConfig, JmpContentWrapper } from \"./types\"\nimport { readJmpPublicEnvFromProcess } from \"./env-public\"\n\n/** Per-site values used when the dashboard profile is missing or fields are empty. */\nexport type JmpSiteContactFallback = {\n displayName?: string\n founderName?: string\n email?: string\n phone?: string | null\n linkedin?: string | null\n hours?: string | null\n serviceArea?: string | null\n dashboardDemoUrl?: string\n profilePhotoSrc?: string\n privacyUrl?: string\n termsUrl?: string\n}\n\nexport type JmpSiteDefinition = {\n /** Content tabs this site uses — reported via instrumentation capabilities. */\n contentWrappers: JmpContentWrapper[]\n /**\n * Public site origin (no trailing slash), e.g. `https://example.com`.\n * Used to resolve relative profile image paths in structured data.\n */\n siteUrl?: string\n /**\n * JMP dashboard origin (no trailing slash), e.g. `https://portal.example.com`.\n * Fallback when `NEXT_PUBLIC_JMP_ANALYTICS_URL` is missing from the client bundle.\n */\n analyticsPortalUrl?: string\n contactFallback?: JmpSiteContactFallback\n /**\n * `extras` keys to read from the dashboard site profile (hyphenated keys from admin).\n */\n contactExtraKeys?: {\n founderName?: readonly string[]\n dashboardDemoUrl?: readonly string[]\n profilePhotoSrc?: readonly string[]\n hours?: readonly string[]\n serviceArea?: readonly string[]\n }\n}\n\nlet registeredSite: JmpSiteDefinition | null = null\n\n/**\n * Register site-specific JMP integration options once (typically from `jmp.config.ts`).\n * Call at module load time before server content or instrumentation runs.\n */\nexport function defineJmpSite(definition: JmpSiteDefinition): JmpSiteDefinition {\n registeredSite = definition\n return definition\n}\n\nexport function getJmpSiteDefinition(): JmpSiteDefinition | null {\n return registeredSite\n}\n\n/** Server content + lead config: env vars + wrappers from {@link defineJmpSite}. */\nexport function getJmpContentConfig(): JmpContentConfig | null {\n const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess()\n if (!analyticsUrl || !trackingKey) {\n return null\n }\n const site = getJmpSiteDefinition()\n return {\n baseUrl: analyticsUrl,\n trackingKey,\n contentWrappers: site?.contentWrappers ?? undefined,\n }\n}\n","\"use client\"\n\nimport Script from \"next/script\"\nimport {\n Suspense,\n useLayoutEffect,\n useMemo,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\"\nimport { usePathname, useSearchParams } from \"next/navigation\"\nimport { buildLandingCaptureInlineScript } from \"./landing-capture\"\nimport { readJmpPublicEnvFromProcess } from \"./env-public\"\nimport type { JmpAnalyticsConfig } from \"./types\"\nimport { getOrCreateJmpAnalyticsForNextApp } from \"./env/browser-next\"\nimport {\n isJmpTrackingConsentGranted,\n subscribeJmpTrackingConsent,\n} from \"./index\"\n\nexport {\n setJmpTrackingConsent,\n isJmpTrackingConsentGranted,\n subscribeJmpTrackingConsent,\n} from \"./index\"\nimport { JmpTrackedMailtoLink, JmpTrackedTelLink } from \"./react\"\nimport type { JmpAnalytics } from \"./index\"\n\nexport { JmpTrackedMailtoLink, JmpTrackedTelLink }\nexport { JmpGoogleTag } from \"./next/google-tag\"\nexport { getOrCreateJmpAnalyticsForNextApp } from \"./env/browser-next\"\nexport { readJmpPublicEnvFromProcess } from \"./env-public\"\n\nexport function useJmpAnalytics(): JmpAnalytics | null {\n return useMemo(() => getOrCreateJmpAnalyticsForNextApp(), [])\n}\nexport { buildLandingCaptureInlineScript } from \"./landing-capture\"\n\ntype JmpLandingCaptureScriptProps = {\n baseUrl?: string\n trackingKey?: string\n}\n\n/**\n * Earliest capture: stores landing referrer/UTMs before React hydrates.\n */\nexport function JmpLandingCaptureScript({\n trackingKey = readJmpPublicEnvFromProcess().trackingKey,\n}: JmpLandingCaptureScriptProps = {}) {\n if (!trackingKey?.trim()) {\n return null\n }\n\n return (\n <Script\n id=\"jmp-landing-capture\"\n strategy=\"beforeInteractive\"\n dangerouslySetInnerHTML={{\n __html: buildLandingCaptureInlineScript(),\n }}\n />\n )\n}\n\nfunction JmpNextPageViewTrackerInner({\n configOverrides,\n}: {\n configOverrides?: Partial<JmpAnalyticsConfig>\n}) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const query = searchParams?.toString() ?? \"\"\n const path = query ? `${pathname}?${query}` : pathname\n\n useLayoutEffect(() => {\n const client = getOrCreateJmpAnalyticsForNextApp(configOverrides)\n void client?.trackPageView({ path })\n }, [path, configOverrides])\n\n return null\n}\n\n/**\n * App Router: `page_view` on first paint and client navigations.\n */\nexport function JmpNextPageViewTracker({\n configOverrides,\n}: {\n configOverrides?: Partial<JmpAnalyticsConfig>\n}) {\n return (\n <Suspense fallback={null}>\n <JmpNextPageViewTrackerInner configOverrides={configOverrides} />\n </Suspense>\n )\n}\n\nfunction useJmpTrackingActive(deferUntilConsent: boolean): boolean {\n const consentGranted = useSyncExternalStore(\n subscribeJmpTrackingConsent,\n () => isJmpTrackingConsentGranted(),\n () => false\n )\n return !deferUntilConsent || consentGranted\n}\n\ntype JmpNextAnalyticsProps = {\n /**\n * When true (or `NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT=true`), scripts stay\n * off until {@link setJmpTrackingConsent}(true) from your cookie banner.\n */\n deferUntilConsent?: boolean\n /** From {@link JmpSiteRootServer} — ensures the client bundle can reach the portal. */\n analyticsUrl?: string\n trackingKey?: string\n}\n\nfunction jmpConfigOverridesFromProps(\n props: Pick<JmpNextAnalyticsProps, \"analyticsUrl\" | \"trackingKey\">,\n): Partial<JmpAnalyticsConfig> | undefined {\n const { analyticsUrl, trackingKey } = props\n if (!analyticsUrl?.trim() && !trackingKey?.trim()) {\n return undefined\n }\n return {\n ...(analyticsUrl?.trim() ? { baseUrl: analyticsUrl.trim() } : {}),\n ...(trackingKey?.trim() ? { trackingKey: trackingKey.trim() } : {}),\n }\n}\n\n/** Initializes the browser client (contact auto-track defaults) before page views. */\nfunction JmpNextAnalyticsBootstrap({\n configOverrides,\n}: {\n configOverrides?: Partial<JmpAnalyticsConfig>\n}) {\n useLayoutEffect(() => {\n getOrCreateJmpAnalyticsForNextApp(configOverrides)\n }, [configOverrides])\n return null\n}\n\n/** Landing capture script + route tracking — add once in root layout. */\nexport function JmpNextAnalytics({\n deferUntilConsent = readJmpPublicEnvFromProcess().requireConsent === true,\n analyticsUrl,\n trackingKey,\n}: JmpNextAnalyticsProps = {}) {\n const active = useJmpTrackingActive(deferUntilConsent)\n const configOverrides = useMemo(\n () => jmpConfigOverridesFromProps({ analyticsUrl, trackingKey }),\n [analyticsUrl, trackingKey],\n )\n\n if (!active) {\n return null\n }\n\n return (\n <>\n <JmpNextAnalyticsBootstrap configOverrides={configOverrides} />\n <JmpLandingCaptureScript trackingKey={trackingKey} />\n <JmpNextPageViewTracker configOverrides={configOverrides} />\n </>\n )\n}\n\ntype JmpSiteRootProps = {\n children: ReactNode\n deferUntilConsent?: boolean\n /** Prefer {@link JmpSiteRootServer} in layout — pass-through from server env. */\n analyticsUrl?: string\n trackingKey?: string\n}\n\n/**\n * Root layout wrapper: landing capture + App Router page views + children.\n * Prefer {@link JmpSiteRootServer} in `app/layout.tsx` so env reaches the browser.\n */\nexport function JmpSiteRoot({\n children,\n deferUntilConsent,\n analyticsUrl,\n trackingKey,\n}: JmpSiteRootProps) {\n return (\n <>\n <JmpNextAnalytics\n deferUntilConsent={deferUntilConsent}\n analyticsUrl={analyticsUrl}\n trackingKey={trackingKey}\n />\n {children}\n </>\n )\n}\n\n","/** First-party landing attribution captured before React / useEffect (sessionStorage). */\n\nexport const JMP_SESSION_ID_KEY = \"jmp_analytics_sid\"\nexport const JMP_LANDING_REFERER_KEY = \"jmp_analytics_landing_referer\"\n/** Cross-subdomain landing referrer (www ↔ apex share sessionStorage origins). */\nexport const JMP_LANDING_REFERER_COOKIE = \"jmp_lr\"\nexport const JMP_UTM_SESSION_KEY = \"jmp_analytics_utm\"\n\nconst LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60\n\nexport const JMP_UTM_PARAM_KEYS = [\n \"utm_source\",\n \"utm_medium\",\n \"utm_campaign\",\n \"utm_content\",\n \"utm_term\",\n] as const\n\nfunction refererHostFromUrl(referer: string): string | null {\n const trimmed = referer.trim()\n try {\n return new URL(trimmed).hostname.replace(/^www\\./i, \"\").toLowerCase()\n } catch {\n try {\n return new URL(`https://${trimmed}`).hostname\n .replace(/^www\\./i, \"\")\n .toLowerCase()\n } catch {\n return null\n }\n }\n}\n\nexport function isExternalReferrerUrl(\n referer: string,\n siteHostname: string\n): boolean {\n const refHost = refererHostFromUrl(referer)\n if (!refHost) {\n return false\n }\n const site = siteHostname.replace(/^www\\./i, \"\").toLowerCase()\n return refHost !== site && !refHost.endsWith(`.${site}`)\n}\n\n/** Apex hostname used for `Domain=.example.com` cookies (www + apex). */\nexport function registrableSiteDomain(siteHostname: string): string | null {\n const site = siteHostname.replace(/^www\\./i, \"\").toLowerCase()\n if (!site || site === \"localhost\" || site.endsWith(\".local\")) {\n return null\n }\n if (!site.includes(\".\")) {\n return null\n }\n return site\n}\n\nexport function readLandingRefererCookie(siteHostname: string): string | null {\n if (typeof document === \"undefined\") {\n return null\n }\n\n const apex = registrableSiteDomain(siteHostname)\n if (!apex) {\n return null\n }\n\n const prefix = `${JMP_LANDING_REFERER_COOKIE}=`\n for (const part of document.cookie.split(\";\")) {\n const trimmed = part.trim()\n if (!trimmed.startsWith(prefix)) {\n continue\n }\n try {\n const value = decodeURIComponent(trimmed.slice(prefix.length)).trim()\n return value.length > 0 ? value : null\n } catch {\n return null\n }\n }\n\n return null\n}\n\nexport function writeLandingRefererCookie(\n referer: string,\n siteHostname: string\n): void {\n if (typeof document === \"undefined\") {\n return\n }\n\n const apex = registrableSiteDomain(siteHostname)\n if (!apex) {\n return\n }\n\n const value = encodeURIComponent(referer.trim().slice(0, 2048))\n const secure =\n typeof window !== \"undefined\" && window.location.protocol === \"https:\"\n ? \"; Secure\"\n : \"\"\n\n document.cookie = `${JMP_LANDING_REFERER_COOKIE}=${value}; Domain=.${apex}; Path=/; Max-Age=${LANDING_REFERER_COOKIE_MAX_AGE_SEC}; SameSite=Lax${secure}`\n}\n\nexport function captureLandingAttribution(siteHostname: string): {\n landingReferer: string | null\n utm: Record<string, string>\n} {\n if (typeof document === \"undefined\" || typeof window === \"undefined\") {\n return { landingReferer: null, utm: {} }\n }\n\n let landingReferer: string | null = null\n let utm: Record<string, string> = {}\n\n try {\n const storedReferer =\n sessionStorage.getItem(JMP_LANDING_REFERER_KEY)?.trim() || null\n const cookieReferer = readLandingRefererCookie(siteHostname)\n const preservedReferer = storedReferer || cookieReferer\n const currentReferer = document.referrer?.trim() || null\n\n if (\n currentReferer &&\n isExternalReferrerUrl(currentReferer, siteHostname)\n ) {\n sessionStorage.setItem(JMP_LANDING_REFERER_KEY, currentReferer)\n writeLandingRefererCookie(currentReferer, siteHostname)\n landingReferer = currentReferer\n } else if (preservedReferer) {\n landingReferer = preservedReferer\n if (!storedReferer) {\n sessionStorage.setItem(JMP_LANDING_REFERER_KEY, preservedReferer)\n }\n }\n\n const params = new URLSearchParams(window.location.search)\n const rawUtm = sessionStorage.getItem(JMP_UTM_SESSION_KEY)\n if (rawUtm) {\n const parsed = JSON.parse(rawUtm) as unknown\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n utm = parsed as Record<string, string>\n }\n }\n\n let updated = false\n for (const key of JMP_UTM_PARAM_KEYS) {\n const value = params.get(key)?.trim()\n if (value) {\n utm[key] = value\n updated = true\n }\n }\n\n if (updated) {\n sessionStorage.setItem(JMP_UTM_SESSION_KEY, JSON.stringify(utm))\n }\n } catch {\n }\n\n return { landingReferer, utm }\n}\n\nexport function getOrCreateSessionIdFromStorage(): string | undefined {\n if (typeof window === \"undefined\" || !globalThis.crypto?.randomUUID) {\n return undefined\n }\n try {\n let id = sessionStorage.getItem(JMP_SESSION_ID_KEY)\n if (!id) {\n id = crypto.randomUUID()\n sessionStorage.setItem(JMP_SESSION_ID_KEY, id)\n }\n return id\n } catch {\n return undefined\n }\n}\n\n/** Inline script for Next.js `beforeInteractive` — capture referrer/UTM only (no network). */\nexport function buildLandingCaptureInlineScript(): string {\n const cookieMaxAge = String(LANDING_REFERER_COOKIE_MAX_AGE_SEC)\n\n return `(function(){try{var h=location.hostname;var ref=document.referrer||'';function rh(u){try{return new URL(u).hostname.replace(/^www\\\\./i,'').toLowerCase()}catch(e){try{return new URL('https://'+u).hostname.replace(/^www\\\\./i,'').toLowerCase()}catch(e2){return null}}}function ext(r){var x=rh(r);if(!x)return false;var s=h.replace(/^www\\\\./i,'').toLowerCase();return x!==s&&x.slice(-('.'+s).length)!==('.'+s)}function apexDom(hn){var s=hn.replace(/^www\\\\./i,'').toLowerCase();if(!s||s==='localhost'||/\\\\.local$/i.test(s)||s.indexOf('.')<0)return null;return s}function readCk(hn){var apex=apexDom(hn);if(!apex)return '';var pre='${JMP_LANDING_REFERER_COOKIE}=';var parts=document.cookie.split(';');for(var i=0;i<parts.length;i++){var t=parts[i].trim();if(t.indexOf(pre)===0){try{return decodeURIComponent(t.slice(pre.length)).trim()}catch(e){return ''}}}return ''}function writeCk(r,hn){var apex=apexDom(hn);if(!apex)return;var sec=location.protocol==='https:'?'; Secure':'';document.cookie='${JMP_LANDING_REFERER_COOKIE}='+encodeURIComponent(r.slice(0,2048))+'; Domain=.'+apex+'; Path=/; Max-Age=${cookieMaxAge}; SameSite=Lax'+sec}var stored=sessionStorage.getItem('${JMP_LANDING_REFERER_KEY}')||'';var ck=readCk(h);var preserved=stored||ck;var lr='';if(ref&&ext(ref)){sessionStorage.setItem('${JMP_LANDING_REFERER_KEY}',ref);writeCk(ref,h);lr=ref}else{lr=preserved||'';if(lr&&!stored)sessionStorage.setItem('${JMP_LANDING_REFERER_KEY}',lr)}var sid=sessionStorage.getItem('${JMP_SESSION_ID_KEY}');if(!sid&&crypto&&crypto.randomUUID){sid=crypto.randomUUID();sessionStorage.setItem('${JMP_SESSION_ID_KEY}',sid)}try{var sp=new URLSearchParams(location.search);var utm={};var updated=false;['utm_source','utm_medium','utm_campaign','utm_content','utm_term'].forEach(function(n){var v=sp.get(n);if(v){utm[n]=v;updated=true}});if(updated)sessionStorage.setItem('${JMP_UTM_SESSION_KEY}',JSON.stringify(utm))}catch(e){}}catch(e){}})();`\n}\n","export const JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY = \"jmp_analytics_last_pv\"\n\nlet moduleLastPath = \"\"\nlet moduleLastAt = 0\n\n/**\n * Suppress duplicate browser `page_view` within `dedupeMs` (Strict Mode, App Router\n * searchParams resolution, multiple bundles). Uses pathname-only as the dedupe key.\n */\nexport function shouldDedupeBrowserPageView(\n pathname: string,\n dedupeMs: number,\n storage: Pick<Storage, \"getItem\" | \"setItem\"> | null = null,\n): boolean {\n if (dedupeMs <= 0) {\n return false\n }\n\n const now = Date.now()\n if (pathname === moduleLastPath && now - moduleLastAt < dedupeMs) {\n return true\n }\n\n try {\n const raw = storage?.getItem(JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY)\n if (raw) {\n const prev = JSON.parse(raw) as { path: string; at: number }\n if (prev.path === pathname && now - prev.at < dedupeMs) {\n return true\n }\n }\n storage?.setItem(\n JMP_PAGE_VIEW_DEDUPE_STORAGE_KEY,\n JSON.stringify({ path: pathname, at: now }),\n )\n } catch {\n // private mode / disabled storage\n }\n\n moduleLastPath = pathname\n moduleLastAt = now\n return false\n}\n\n/** @internal test helper */\nexport function resetBrowserPageViewDedupeForTests(): void {\n moduleLastPath = \"\"\n moduleLastAt = 0\n}\n","/**\n * Classify outbound contact links for auto-tracking.\n *\n * Keep channel set in sync with `lib/ingest/contact-click.ts` on the dashboard side.\n *\n * Channels detected from `href` alone (no widget / iframe detection):\n *\n * - `call` — `tel:` (delegates to existing `trackCallClick`)\n * - `email` — `mailto:` (delegates to existing `trackEmailClick`)\n * - `sms` — `sms:` / `smsto:`\n * - `whatsapp` — `wa.me`, `api.whatsapp.com`, `whatsapp.com/send`\n * - `maps` — Google Maps, Apple Maps, `maps.app.goo.gl`, `geo:`\n * - `booking` — fixed allowlist of common scheduling hosts (see {@link BOOKING_LINK_HOSTS})\n *\n * `linkHost` returned in `contact_click` results is the hostname only — never the full URL\n * (avoids leaking phone numbers in `tel:`/`sms:` URLs and PII-laden booking query strings).\n */\n\nexport type ContactLinkChannel = \"sms\" | \"whatsapp\" | \"maps\" | \"booking\"\n\nexport type ClassifiedContactLink =\n | { kind: \"call\" }\n | { kind: \"email\" }\n | { kind: \"contact_click\"; channel: ContactLinkChannel; linkHost: string | null }\n\n/** v1 booking hosts (~80%+ SMB coverage). Add hosts here when a client uses something rare. */\nexport const BOOKING_LINK_HOSTS = [\n \"calendly.com\",\n \"cal.com\",\n \"acuityscheduling.com\",\n \"calendar.google.com\",\n \"calendar.app.google\",\n \"bookings.microsoft.com\",\n \"outlook.office.com\",\n \"outlook.office365.com\",\n \"book.ms\",\n \"setmore.com\",\n \"simplybook.me\",\n \"oncehub.com\",\n \"scheduleonce.com\",\n \"youcanbook.me\",\n \"savvycal.com\",\n \"tidycal.com\",\n \"zohobookings.com\",\n \"square.site\",\n \"squareup.com\",\n \"meetings.hubspot.com\",\n \"jobber.com\",\n \"housecallpro.com\",\n \"servicetitan.com\",\n \"thryv.com\",\n \"honeybook.com\",\n \"dubsado.com\",\n \"fresha.com\",\n \"booksy.com\",\n \"vagaro.com\",\n \"mindbodyonline.com\",\n \"mindbody.io\",\n \"janeapp.com\",\n \"cliniko.com\",\n \"practicebetter.io\",\n] as const\n\nconst WHATSAPP_HOSTS = [\"wa.me\", \"api.whatsapp.com\", \"whatsapp.com\"] as const\n\nexport function hostnameMatches(host: string, pattern: string): boolean {\n const h = host.toLowerCase()\n const p = pattern.toLowerCase()\n return h === p || h.endsWith(`.${p}`)\n}\n\nfunction isMapsHost(host: string, pathname: string): boolean {\n if (hostnameMatches(host, \"maps.apple.com\")) {\n return true\n }\n if (hostnameMatches(host, \"maps.app.goo.gl\")) {\n return true\n }\n if (hostnameMatches(host, \"google.com\") || hostnameMatches(host, \"google.co\")) {\n return pathname === \"/maps\" || pathname.startsWith(\"/maps/\") || pathname.startsWith(\"/local\")\n }\n return false\n}\n\nfunction isWhatsAppHost(host: string, pathname: string): boolean {\n for (const pattern of WHATSAPP_HOSTS) {\n if (!hostnameMatches(host, pattern)) {\n continue\n }\n if (pattern === \"whatsapp.com\") {\n return pathname.startsWith(\"/send\")\n }\n return true\n }\n return false\n}\n\nfunction isBookingHost(host: string): boolean {\n for (const pattern of BOOKING_LINK_HOSTS) {\n if (hostnameMatches(host, pattern)) {\n return true\n }\n }\n return false\n}\n\n/** Returns `null` when the link should not be tracked as a contact link. */\nexport function classifyContactHref(href: string): ClassifiedContactLink | null {\n const raw = href.trim()\n if (!raw || raw.startsWith(\"#\") || raw.startsWith(\"javascript:\")) {\n return null\n }\n\n const lower = raw.toLowerCase()\n\n if (lower.startsWith(\"tel:\")) {\n return { kind: \"call\" }\n }\n if (lower.startsWith(\"mailto:\")) {\n return { kind: \"email\" }\n }\n if (lower.startsWith(\"sms:\") || lower.startsWith(\"smsto:\")) {\n return { kind: \"contact_click\", channel: \"sms\", linkHost: null }\n }\n if (lower.startsWith(\"geo:\")) {\n return { kind: \"contact_click\", channel: \"maps\", linkHost: null }\n }\n\n // Skip anchors/relative paths that aren't a known contact scheme.\n if (raw.startsWith(\"/\") || raw.startsWith(\"?\")) {\n return null\n }\n\n let url: URL\n try {\n url = new URL(raw)\n } catch {\n return null\n }\n\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n return null\n }\n\n const host = url.hostname.toLowerCase()\n const pathname = url.pathname.toLowerCase()\n\n if (isWhatsAppHost(host, pathname)) {\n return { kind: \"contact_click\", channel: \"whatsapp\", linkHost: host }\n }\n if (isMapsHost(host, pathname)) {\n return { kind: \"contact_click\", channel: \"maps\", linkHost: host }\n }\n if (isBookingHost(host)) {\n return { kind: \"contact_click\", channel: \"booking\", linkHost: host }\n }\n\n return null\n}\n","import { captureLandingAttribution } from \"./landing-capture\"\nimport { JMP_TRACKING_CONSENT_STORAGE_KEY } from \"./constants\"\n\nlet moduleTrackingConsentGranted = true\nconst trackingConsentListeners = new Set<() => void>()\n\nfunction readStoredTrackingConsent(): boolean {\n if (typeof window === \"undefined\") {\n return false\n }\n try {\n return (\n localStorage.getItem(JMP_TRACKING_CONSENT_STORAGE_KEY) === \"granted\"\n )\n } catch {\n return false\n }\n}\n\n/** Initialize consent gate when creating a tracker (browser only). */\nexport function initJmpTrackingConsent(requireConsent: boolean | undefined): void {\n moduleTrackingConsentGranted = requireConsent\n ? readStoredTrackingConsent()\n : true\n}\n\n/** Whether analytics events may be sent (after CMP / cookie banner). */\nexport function isJmpTrackingConsentGranted(): boolean {\n return moduleTrackingConsentGranted\n}\n\n/** Subscribe to consent changes (for React `useSyncExternalStore`). */\nexport function subscribeJmpTrackingConsent(listener: () => void): () => void {\n trackingConsentListeners.add(listener)\n return () => {\n trackingConsentListeners.delete(listener)\n }\n}\n\n/**\n * Grant or deny analytics. Persists in `localStorage` when `requireConsent` is used.\n * Call from your cookie banner before tracking runs.\n */\nexport function setJmpTrackingConsent(granted: boolean): void {\n if (moduleTrackingConsentGranted === granted) {\n return\n }\n moduleTrackingConsentGranted = granted\n if (typeof window !== \"undefined\") {\n try {\n localStorage.setItem(\n JMP_TRACKING_CONSENT_STORAGE_KEY,\n granted ? \"granted\" : \"denied\"\n )\n } catch {\n // private mode\n }\n if (granted) {\n captureLandingAttribution(window.location.hostname)\n }\n }\n for (const listener of trackingConsentListeners) {\n listener()\n }\n}\n","import {\n captureLandingAttribution,\n JMP_UTM_PARAM_KEYS,\n} from \"../landing-capture\"\n\nexport function captureAndGetLandingReferer(): string | null {\n if (typeof window === \"undefined\") {\n return null\n }\n return captureLandingAttribution(window.location.hostname).landingReferer\n}\n\nexport function mergeSessionUtms(metadata: Record<string, unknown>) {\n if (typeof window === \"undefined\") {\n return\n }\n const { utm } = captureLandingAttribution(window.location.hostname)\n for (const key of JMP_UTM_PARAM_KEYS) {\n if (metadata[key] === undefined && utm[key]) {\n metadata[key] = utm[key]\n }\n }\n}\n\nexport function splitPathAndSearch(pathInput: string): {\n pathname: string\n search: string\n} {\n const qIndex = pathInput.indexOf(\"?\")\n if (qIndex === -1) {\n return { pathname: pathInput || \"/\", search: \"\" }\n }\n return {\n pathname: pathInput.slice(0, qIndex) || \"/\",\n search: pathInput.slice(qIndex + 1),\n }\n}\n\nexport function inferDevice(): string {\n if (typeof window === \"undefined\") {\n return \"unknown\"\n }\n return window.innerWidth < 768 ? \"mobile\" : \"desktop\"\n}\n","import { DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS } from \"../constants\"\nimport {\n captureLandingAttribution,\n getOrCreateSessionIdFromStorage,\n JMP_UTM_PARAM_KEYS,\n} from \"../landing-capture\"\nimport { shouldDedupeBrowserPageView } from \"./page-view-dedupe\"\nimport { classifyContactHref } from \"../contact-link-classifier\"\nimport type { ContactLinkChannel } from \"../contact-link-classifier\"\nimport {\n initJmpTrackingConsent,\n isJmpTrackingConsentGranted,\n setJmpTrackingConsent,\n} from \"../consent\"\nimport {\n formatApiErrorMessage,\n normalizeBaseUrl,\n resolveFetch,\n} from \"../http\"\nimport type {\n JmpAnalyticsConfig,\n JmpEventType,\n TrackLeadOptions,\n TrackPageViewOptions,\n} from \"../types\"\nimport {\n captureAndGetLandingReferer,\n inferDevice,\n mergeSessionUtms,\n splitPathAndSearch,\n} from \"./helpers\"\n\nexport function createJmpAnalytics(config: JmpAnalyticsConfig) {\n const baseUrl = normalizeBaseUrl(config.baseUrl)\n const fetchFn = resolveFetch(config.fetchImpl, \"createJmpAnalytics\")\n\n initJmpTrackingConsent(config.requireConsent)\n\n function canSend(): boolean {\n return isJmpTrackingConsentGranted()\n }\n\n async function send(body: Record<string, unknown>) {\n if (!canSend()) {\n return\n }\n\n const url = `${baseUrl}/api/events`\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n }\n\n if (body.type === \"page_view\" && typeof window !== \"undefined\") {\n const landingReferer = captureAndGetLandingReferer()\n if (landingReferer) {\n headers[\"X-JMP-Visitor-Referer\"] = landingReferer\n }\n }\n\n const sid = getOrCreateSessionIdFromStorage()\n const payload = { ...body, ...(sid ? { sessionId: sid } : {}) }\n\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, payload)\n }\n\n const res = await fetchFn(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n keepalive: true,\n })\n\n if (!res.ok) {\n const text = await res.text()\n const message = formatApiErrorMessage(res.status, text, \"JMP Analytics\")\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"error response\", { status: res.status, raw: text })\n }\n throw new Error(message)\n }\n }\n\n async function sendLead(body: Record<string, unknown>) {\n if (!canSend()) {\n return\n }\n\n const url = `${baseUrl}/api/leads`\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n }\n\n const sid = getOrCreateSessionIdFromStorage()\n const payload = { ...body, ...(sid ? { sessionId: sid } : {}) }\n\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, payload)\n }\n\n const res = await fetchFn(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n keepalive: true,\n })\n\n if (!res.ok) {\n const text = await res.text()\n const message = formatApiErrorMessage(res.status, text, \"JMP Analytics\")\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"error response\", { status: res.status, raw: text })\n }\n throw new Error(message)\n }\n }\n\n function trackPageView(options?: TrackPageViewOptions) {\n if (!canSend()) {\n return Promise.resolve()\n }\n\n const pathFromOptions = options?.path?.trim()\n let pathname = \"/\"\n let search = \"\"\n\n if (pathFromOptions) {\n const split = splitPathAndSearch(pathFromOptions)\n pathname = split.pathname\n search = split.search\n } else if (typeof window !== \"undefined\") {\n pathname = window.location.pathname\n search = window.location.search.replace(/^\\?/, \"\")\n }\n\n if (!search && typeof window !== \"undefined\") {\n search = window.location.search.replace(/^\\?/, \"\")\n }\n\n const path = search ? `${pathname}?${search}` : pathname\n\n const dedupeMs =\n config.pageViewDedupeMs ?? DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS\n const storage =\n typeof window !== \"undefined\" ? window.sessionStorage : null\n if (shouldDedupeBrowserPageView(pathname, dedupeMs, storage)) {\n return Promise.resolve()\n }\n\n const metadata: Record<string, unknown> =\n options?.metadata && typeof options.metadata === \"object\"\n ? { ...options.metadata }\n : {}\n\n if (typeof window !== \"undefined\") {\n metadata.pageHostname = window.location.hostname\n const params = new URLSearchParams(\n search ? `?${search}` : window.location.search\n )\n for (const key of JMP_UTM_PARAM_KEYS) {\n const v = params.get(key)\n if (v && metadata[key] === undefined) {\n metadata[key] = v\n }\n }\n mergeSessionUtms(metadata)\n const landingReferer = captureAndGetLandingReferer()\n if (landingReferer && metadata.referer === undefined) {\n metadata.referer = landingReferer\n }\n }\n\n return send({\n type: \"page_view\",\n path: path || \"/\",\n device: options?.device ?? inferDevice(),\n country: options?.country,\n metadata,\n })\n }\n\n function trackFormSubmit(\n path: string,\n metadata?: Record<string, unknown>\n ) {\n return send({\n type: \"form_submit\",\n path: path || \"/\",\n metadata: metadata ?? {},\n })\n }\n\n function trackCallClick(\n path: string,\n metadata?: Record<string, unknown>\n ) {\n return send({\n type: \"call_click\",\n path: path || \"/\",\n metadata: metadata ?? {},\n })\n }\n\n function trackEmailClick(\n path: string,\n metadata?: Record<string, unknown>\n ) {\n return send({\n type: \"email_click\",\n path: path || \"/\",\n metadata: metadata ?? {},\n })\n }\n\n function trackContactClick(\n path: string,\n options: {\n channel: ContactLinkChannel\n linkHost?: string | null\n metadata?: Record<string, unknown>\n }\n ) {\n const metadata: Record<string, unknown> = {\n ...(options.metadata && typeof options.metadata === \"object\"\n ? options.metadata\n : {}),\n channel: options.channel,\n }\n if (options.linkHost) {\n metadata.linkHost = options.linkHost\n }\n return send({\n type: \"contact_click\",\n path: path || \"/\",\n metadata,\n })\n }\n\n function trackLead(options?: TrackLeadOptions) {\n const path =\n options?.path ??\n (typeof window !== \"undefined\" ? window.location.pathname : \"/\")\n\n const body: Record<string, unknown> = { path: path || \"/\" }\n if (options?.name) body.name = options.name\n if (options?.email) body.email = options.email\n if (options?.phone) body.phone = options.phone\n if (options?.company) body.company = options.company\n if (options?.website) body.website = options.website\n if (options?.subject) body.subject = options.subject\n if (options?.message) body.message = options.message\n if (options?.metadata && typeof options.metadata === \"object\") {\n body.metadata = options.metadata\n }\n if (options?.sessionId) body.sessionId = options.sessionId\n\n return sendLead(body)\n }\n\n function track(\n type: JmpEventType,\n payload: {\n path: string\n metadata?: Record<string, unknown>\n source?: string\n device?: string\n country?: string\n }\n ) {\n if (type === \"page_view\") {\n return trackPageView({\n path: payload.path,\n source: payload.source,\n device: payload.device,\n country: payload.country,\n metadata: payload.metadata,\n })\n }\n if (type === \"form_submit\") {\n return trackFormSubmit(payload.path, payload.metadata)\n }\n if (type === \"call_click\") {\n return trackCallClick(payload.path, payload.metadata)\n }\n if (type === \"email_click\") {\n return trackEmailClick(payload.path, payload.metadata)\n }\n const meta = payload.metadata ?? {}\n const channel = typeof meta.channel === \"string\" ? meta.channel : \"\"\n const linkHost =\n typeof meta.linkHost === \"string\" ? (meta.linkHost as string) : null\n if (\n channel !== \"sms\" &&\n channel !== \"whatsapp\" &&\n channel !== \"maps\" &&\n channel !== \"booking\"\n ) {\n return Promise.resolve()\n }\n return trackContactClick(payload.path, {\n channel: channel as ContactLinkChannel,\n linkHost,\n metadata: meta,\n })\n }\n\n /**\n * Fire `page_view` on browser back/forward and when `history.pushState` /\n * `replaceState` are used (many SPAs). Call once after creating the tracker.\n * Returns an unsubscribe function.\n */\n function subscribeToSpaNavigation(): () => void {\n if (typeof window === \"undefined\") {\n return () => {}\n }\n\n let lastPath = window.location.pathname\n\n const run = () => {\n const path = window.location.pathname\n if (path !== lastPath) {\n lastPath = path\n void trackPageView({ path })\n }\n }\n\n window.addEventListener(\"popstate\", run)\n\n const push = history.pushState.bind(history)\n const replace = history.replaceState.bind(history)\n\n history.pushState = (...args: Parameters<History[\"pushState\"]>) => {\n const r = push(...args)\n queueMicrotask(run)\n return r\n }\n history.replaceState = (...args: Parameters<History[\"replaceState\"]>) => {\n const r = replace(...args)\n queueMicrotask(run)\n return r\n }\n\n return () => {\n window.removeEventListener(\"popstate\", run)\n history.pushState = push\n history.replaceState = replace\n }\n }\n\n let lastContactClickKey = \"\"\n let lastContactClickAt = 0\n\n /**\n * Capture-phase `click` listener that classifies outgoing links and records\n * the corresponding contact event. Returns an unsubscribe function. Safe to\n * call only in the browser; no-op on the server.\n */\n function subscribeContactLinkAutoTrack(): () => void {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return () => {}\n }\n\n const handler = (event: MouseEvent) => {\n if (!canSend()) {\n return\n }\n const target = event.target\n if (!(target instanceof Element)) {\n return\n }\n const anchor = target.closest(\"a[href]\")\n if (!(anchor instanceof HTMLAnchorElement)) {\n return\n }\n if (\n anchor.closest(\"[data-jmp-no-track]\") ||\n anchor.closest(\"[data-jmp-tracked]\")\n ) {\n return\n }\n\n const href = anchor.getAttribute(\"href\") ?? \"\"\n const classified = classifyContactHref(href)\n if (!classified) {\n return\n }\n\n const path = window.location.pathname\n\n if (classified.kind === \"call\") {\n if (dedupeContactClick(`call_click:${path}`)) {\n return\n }\n void trackCallClick(path)\n return\n }\n if (classified.kind === \"email\") {\n if (dedupeContactClick(`email_click:${path}`)) {\n return\n }\n void trackEmailClick(path)\n return\n }\n if (dedupeContactClick(`contact_click:${classified.channel}:${path}`)) {\n return\n }\n void trackContactClick(path, {\n channel: classified.channel,\n linkHost: classified.linkHost,\n })\n }\n\n document.addEventListener(\"click\", handler, { capture: true })\n return () => {\n document.removeEventListener(\"click\", handler, { capture: true } as EventListenerOptions)\n }\n }\n\n function dedupeContactClick(key: string): boolean {\n const now = Date.now()\n if (key === lastContactClickKey && now - lastContactClickAt < 1000) {\n return true\n }\n lastContactClickKey = key\n lastContactClickAt = now\n return false\n }\n\n if (typeof window !== \"undefined\" && canSend()) {\n captureLandingAttribution(window.location.hostname)\n }\n\n if (\n typeof window !== \"undefined\" &&\n config.autoTrackContactLinks &&\n canSend()\n ) {\n subscribeContactLinkAutoTrack()\n }\n\n return {\n track,\n trackPageView,\n trackFormSubmit,\n trackCallClick,\n trackEmailClick,\n trackContactClick,\n trackLead,\n subscribeToSpaNavigation,\n subscribeContactLinkAutoTrack,\n setTrackingConsent: setJmpTrackingConsent,\n isTrackingConsentGranted: isJmpTrackingConsentGranted,\n }\n}\n\nexport type JmpAnalytics = ReturnType<typeof createJmpAnalytics>\n","import {\n createJmpAnalytics,\n type JmpAnalytics,\n} from \"../tracker/create-jmp-analytics\"\nimport { DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS } from \"../constants\"\nimport { readJmpPublicEnvFromProcess } from \"../env-public\"\nimport { getJmpSiteDefinition } from \"../site-config\"\nimport type { JmpAnalyticsConfig, JmpContentConfig } from \"../types\"\nimport { createJmpContentClient, type JmpContentClient } from \"../content/client\"\n\n/**\n * Browser / client bundle: reads `NEXT_PUBLIC_*` or Astro `PUBLIC_*` vars.\n * Use for `createJmpAnalytics` on the client. Returns `null` if incomplete.\n */\nexport function getJmpBrowserAnalyticsConfigFromEnv(\n overrides?: Partial<JmpAnalyticsConfig>\n): JmpAnalyticsConfig | null {\n const pub = readJmpPublicEnvFromProcess()\n const site = getJmpSiteDefinition()\n const baseUrl =\n overrides?.baseUrl ?? pub.analyticsUrl ?? site?.analyticsPortalUrl\n const trackingKey = overrides?.trackingKey ?? pub.trackingKey\n if (!baseUrl || !trackingKey) return null\n return {\n baseUrl,\n trackingKey,\n pageViewDedupeMs: DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,\n ...overrides,\n requireConsent: overrides?.requireConsent ?? pub.requireConsent,\n autoTrackContactLinks:\n overrides?.autoTrackContactLinks ?? pub.autoContactLinks,\n }\n}\n\n/**\n * Server / Node (SSR, Route Handlers, `getStaticProps`, etc.):\n * Same **`NEXT_PUBLIC_*`** vars as the browser (`NEXT_PUBLIC_JMP_ANALYTICS_URL` +\n * `NEXT_PUBLIC_JMP_TRACKING_KEY`). Next.js injects these on the server for RSC/fetches.\n * Returns `null` if incomplete.\n */\nexport function getJmpServerContentConfigFromEnv(\n overrides?: Partial<JmpContentConfig>\n): JmpContentConfig | null {\n const pub = readJmpPublicEnvFromProcess()\n const baseUrl = overrides?.baseUrl ?? pub.analyticsUrl\n const trackingKey = overrides?.trackingKey ?? pub.trackingKey\n if (!baseUrl || !trackingKey) return null\n return { baseUrl, trackingKey, ...overrides }\n}\n\nexport function isJmpBrowserAnalyticsEnvConfigured(): boolean {\n const pub = readJmpPublicEnvFromProcess()\n return Boolean(pub.analyticsUrl && pub.trackingKey)\n}\n\nexport function isJmpServerContentEnvConfigured(): boolean {\n return isJmpBrowserAnalyticsEnvConfigured()\n}\n\nlet jmpAnalyticsBrowserSingleton: JmpAnalytics | null = null\n\n/**\n * One `JmpAnalytics` instance per browser tab (module singleton). Safe for page views and events.\n * Returns `null` on the server or when public env vars are missing.\n */\nexport function getOrCreateJmpAnalyticsFromBrowserEnv(\n overrides?: Partial<JmpAnalyticsConfig>\n): JmpAnalytics | null {\n if (typeof window === \"undefined\") return null\n if (jmpAnalyticsBrowserSingleton) return jmpAnalyticsBrowserSingleton\n const config = getJmpBrowserAnalyticsConfigFromEnv(overrides)\n if (!config) return null\n jmpAnalyticsBrowserSingleton = createJmpAnalytics(config)\n return jmpAnalyticsBrowserSingleton\n}\n\n/**\n * `createJmpContentClient` using server env resolution. Returns `null` if env is incomplete.\n */\nexport function createJmpContentClientFromServerEnv(\n overrides?: Partial<JmpContentConfig>\n): JmpContentClient | null {\n const config = getJmpServerContentConfigFromEnv(overrides)\n if (!config) return null\n return createJmpContentClient(config)\n}\n","import type { JmpAnalytics } from \"../tracker/create-jmp-analytics\"\nimport type { JmpAnalyticsConfig } from \"../types\"\nimport { getOrCreateJmpAnalyticsFromBrowserEnv } from \"./browser\"\n\n/**\n * Next.js App Router defaults: auto-track `tel:` / `mailto:` (and other contact\n * links) unless `NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS=false`.\n */\nfunction readNextAutoContactLinksEnv(): string | undefined {\n if (typeof process === \"undefined\" || !process.env) {\n return undefined\n }\n const { env } = process\n const raw = env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS ?? env.PUBLIC_JMP_AUTO_CONTACT_LINKS\n return typeof raw === \"string\" && raw.length > 0 ? raw : undefined\n}\n\nexport function getJmpNextAnalyticsConfigOverrides(): Partial<JmpAnalyticsConfig> {\n const autoEnv = readNextAutoContactLinksEnv()\n const autoContactLinks =\n autoEnv === \"false\" ? false : autoEnv === \"true\" ? true : true\n return { autoTrackContactLinks: autoContactLinks }\n}\n\n/**\n * Browser singleton for Next.js sites — ensures contact auto-track defaults apply\n * on the first `getOrCreate` call (page views, tracked links, delegated clicks).\n */\nexport function getOrCreateJmpAnalyticsForNextApp(\n overrides?: Partial<JmpAnalyticsConfig>,\n): JmpAnalytics | null {\n return getOrCreateJmpAnalyticsFromBrowserEnv({\n ...getJmpNextAnalyticsConfigOverrides(),\n ...overrides,\n })\n}\n","\"use client\"\n\nimport { useMemo, type ComponentPropsWithoutRef } from \"react\"\nimport type { JmpAnalytics } from \"./index\"\nimport { getOrCreateJmpAnalyticsFromBrowserEnv } from \"./index\"\n\nexport function useJmpAnalytics(): JmpAnalytics | null {\n return useMemo(() => getOrCreateJmpAnalyticsFromBrowserEnv(), [])\n}\n\nfunction trackCallClickFromCurrentPath(): void {\n const path =\n typeof window !== \"undefined\" ? window.location.pathname : \"/\"\n void getOrCreateJmpAnalyticsFromBrowserEnv()?.trackCallClick(path)\n}\n\nfunction trackEmailClickFromCurrentPath(): void {\n const path =\n typeof window !== \"undefined\" ? window.location.pathname : \"/\"\n void getOrCreateJmpAnalyticsFromBrowserEnv()?.trackEmailClick(path)\n}\n\nexport type JmpTrackedTelLinkProps = ComponentPropsWithoutRef<\"a\"> & {\n href: string\n}\n\n/**\n * `tel:` (or other) link that records a `call_click` with the current path.\n */\nexport function JmpTrackedTelLink({\n href,\n onClick,\n ...rest\n}: JmpTrackedTelLinkProps) {\n return (\n <a\n href={href}\n data-jmp-tracked=\"true\"\n {...rest}\n onClick={(e) => {\n trackCallClickFromCurrentPath()\n onClick?.(e)\n }}\n />\n )\n}\n\nexport type JmpTrackedMailtoLinkProps = ComponentPropsWithoutRef<\"a\"> & {\n href: string\n}\n\n/**\n * `mailto:` link that records an `email_click` with the current path.\n */\nexport function JmpTrackedMailtoLink({\n href,\n onClick,\n ...rest\n}: JmpTrackedMailtoLinkProps) {\n return (\n <a\n href={href}\n data-jmp-tracked=\"true\"\n {...rest}\n onClick={(e) => {\n trackEmailClickFromCurrentPath()\n onClick?.(e)\n }}\n />\n )\n}\n","\"use client\"\n\nimport Script from \"next/script\"\nimport { usePathname } from \"next/navigation\"\nimport { useEffect, useRef } from \"react\"\n\ndeclare global {\n interface Window {\n gtag?: (...args: unknown[]) => void\n dataLayer?: unknown[]\n }\n}\n\n/**\n * GA4 via gtag.js + page_path updates on client-side navigations (App Router).\n * Loaded after idle to avoid blocking LCP.\n */\nexport function JmpGoogleTag({ gaId }: { gaId: string }) {\n return (\n <>\n <Script\n src={`https://www.googletagmanager.com/gtag/js?id=${gaId}`}\n strategy=\"lazyOnload\"\n />\n <Script id=\"jmp-ga4-init\" strategy=\"lazyOnload\">\n {`\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);}\n gtag('js', new Date());\n gtag('config', '${gaId}', { send_page_view: true });\n `}\n </Script>\n <JmpGoogleTagPagePath gaId={gaId} />\n </>\n )\n}\n\nfunction JmpGoogleTagPagePath({ gaId }: { gaId: string }) {\n const pathname = usePathname()\n const isFirstNavigation = useRef(true)\n\n useEffect(() => {\n if (isFirstNavigation.current) {\n isFirstNavigation.current = false\n return\n }\n const report = () => {\n window.gtag?.(\"config\", gaId, { page_path: pathname || \"/\" })\n }\n report()\n const timer = window.setTimeout(report, 100)\n return () => window.clearTimeout(timer)\n }, [gaId, pathname])\n\n return null\n}\n"],"mappings":"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","names":["import_script","import_react","import_navigation","import_react","import_jsx_runtime","Script","import_jsx_runtime","Script","import_jsx_runtime"]}
@@ -0,0 +1,24 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type JmpSiteRootServerProps = {
5
+ children: ReactNode;
6
+ deferUntilConsent?: boolean;
7
+ /**
8
+ * When true (default), loads GA4 from the dashboard site profile when
9
+ * `NEXT_PUBLIC_GA_MEASUREMENT_ID` is unset. Set false to use env-only GA.
10
+ */
11
+ gaFromPortal?: boolean;
12
+ };
13
+ /**
14
+ * Server Component: reads `process.env` at request time and passes config into the
15
+ * client tracker. Use this in `app/layout.tsx` instead of importing `JmpSiteRoot`
16
+ * directly — fixes missing `POST /api/events` when env vars are not inlined into
17
+ * client chunks (common on Vercel if vars were added after build or mis-scoped).
18
+ *
19
+ * Optional GA4: set `ga_measurement_id` in jmp-dashboard (Content → Contact, admin)
20
+ * or `NEXT_PUBLIC_GA_MEASUREMENT_ID` on the client site (env wins).
21
+ */
22
+ declare function JmpSiteRootServer({ children, deferUntilConsent, gaFromPortal, }: JmpSiteRootServerProps): Promise<react_jsx_runtime.JSX.Element>;
23
+
24
+ export { JmpSiteRootServer, type JmpSiteRootServerProps };
@@ -0,0 +1,24 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type JmpSiteRootServerProps = {
5
+ children: ReactNode;
6
+ deferUntilConsent?: boolean;
7
+ /**
8
+ * When true (default), loads GA4 from the dashboard site profile when
9
+ * `NEXT_PUBLIC_GA_MEASUREMENT_ID` is unset. Set false to use env-only GA.
10
+ */
11
+ gaFromPortal?: boolean;
12
+ };
13
+ /**
14
+ * Server Component: reads `process.env` at request time and passes config into the
15
+ * client tracker. Use this in `app/layout.tsx` instead of importing `JmpSiteRoot`
16
+ * directly — fixes missing `POST /api/events` when env vars are not inlined into
17
+ * client chunks (common on Vercel if vars were added after build or mis-scoped).
18
+ *
19
+ * Optional GA4: set `ga_measurement_id` in jmp-dashboard (Content → Contact, admin)
20
+ * or `NEXT_PUBLIC_GA_MEASUREMENT_ID` on the client site (env wins).
21
+ */
22
+ declare function JmpSiteRootServer({ children, deferUntilConsent, gaFromPortal, }: JmpSiteRootServerProps): Promise<react_jsx_runtime.JSX.Element>;
23
+
24
+ export { JmpSiteRootServer, type JmpSiteRootServerProps };
@@ -0,0 +1,62 @@
1
+ import {
2
+ JmpSiteRoot
3
+ } from "../chunk-L67PVZYL.js";
4
+ import {
5
+ JmpGoogleTag
6
+ } from "../chunk-2KAGOVRZ.js";
7
+ import "../chunk-5GORAM7J.js";
8
+ import {
9
+ getJmpSiteProfile
10
+ } from "../chunk-VRDPVKAN.js";
11
+ import {
12
+ getJmpContentConfig,
13
+ getJmpSiteDefinition,
14
+ readJmpPublicEnvFromProcess
15
+ } from "../chunk-MOX7RJUU.js";
16
+
17
+ // src/next/site-root-server.tsx
18
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
19
+ async function resolveGaMeasurementId(gaFromPortal) {
20
+ const fromEnv = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID?.trim();
21
+ if (fromEnv) {
22
+ return fromEnv;
23
+ }
24
+ if (!gaFromPortal) {
25
+ return null;
26
+ }
27
+ const config = getJmpContentConfig();
28
+ if (!config) {
29
+ return null;
30
+ }
31
+ try {
32
+ const profile = await getJmpSiteProfile(config);
33
+ return profile.ga_measurement_id?.trim() || null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+ async function JmpSiteRootServer({
39
+ children,
40
+ deferUntilConsent,
41
+ gaFromPortal = true
42
+ }) {
43
+ const pub = readJmpPublicEnvFromProcess();
44
+ const site = getJmpSiteDefinition();
45
+ const gaId = await resolveGaMeasurementId(gaFromPortal);
46
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
47
+ gaId ? /* @__PURE__ */ jsx(JmpGoogleTag, { gaId }) : null,
48
+ /* @__PURE__ */ jsx(
49
+ JmpSiteRoot,
50
+ {
51
+ analyticsUrl: pub.analyticsUrl ?? site?.analyticsPortalUrl,
52
+ trackingKey: pub.trackingKey,
53
+ deferUntilConsent,
54
+ children
55
+ }
56
+ )
57
+ ] });
58
+ }
59
+ export {
60
+ JmpSiteRootServer
61
+ };
62
+ //# sourceMappingURL=site-root-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/next/site-root-server.tsx"],"sourcesContent":["import type { ReactNode } from \"react\"\n\nimport { getJmpSiteProfile } from \"../content/api\"\nimport { readJmpPublicEnvFromProcess } from \"../env-public\"\nimport { getJmpContentConfig, getJmpSiteDefinition } from \"../site-config\"\nimport { JmpSiteRoot } from \"../next\"\nimport { JmpGoogleTag } from \"./google-tag\"\n\nexport type JmpSiteRootServerProps = {\n children: ReactNode\n deferUntilConsent?: boolean\n /**\n * When true (default), loads GA4 from the dashboard site profile when\n * `NEXT_PUBLIC_GA_MEASUREMENT_ID` is unset. Set false to use env-only GA.\n */\n gaFromPortal?: boolean\n}\n\nasync function resolveGaMeasurementId(\n gaFromPortal: boolean\n): Promise<string | null> {\n const fromEnv = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID?.trim()\n if (fromEnv) {\n return fromEnv\n }\n if (!gaFromPortal) {\n return null\n }\n const config = getJmpContentConfig()\n if (!config) {\n return null\n }\n try {\n const profile = await getJmpSiteProfile(config)\n return profile.ga_measurement_id?.trim() || null\n } catch {\n return null\n }\n}\n\n/**\n * Server Component: reads `process.env` at request time and passes config into the\n * client tracker. Use this in `app/layout.tsx` instead of importing `JmpSiteRoot`\n * directly — fixes missing `POST /api/events` when env vars are not inlined into\n * client chunks (common on Vercel if vars were added after build or mis-scoped).\n *\n * Optional GA4: set `ga_measurement_id` in jmp-dashboard (Content → Contact, admin)\n * or `NEXT_PUBLIC_GA_MEASUREMENT_ID` on the client site (env wins).\n */\nexport async function JmpSiteRootServer({\n children,\n deferUntilConsent,\n gaFromPortal = true,\n}: JmpSiteRootServerProps) {\n const pub = readJmpPublicEnvFromProcess()\n const site = getJmpSiteDefinition()\n const gaId = await resolveGaMeasurementId(gaFromPortal)\n\n return (\n <>\n {gaId ? <JmpGoogleTag gaId={gaId} /> : null}\n <JmpSiteRoot\n analyticsUrl={pub.analyticsUrl ?? site?.analyticsPortalUrl}\n trackingKey={pub.trackingKey}\n deferUntilConsent={deferUntilConsent}\n >\n {children}\n </JmpSiteRoot>\n </>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA2DI,mBACU,KADV;AAzCJ,eAAe,uBACb,cACwB;AACxB,QAAM,UAAU,QAAQ,IAAI,+BAA+B,KAAK;AAChE,MAAI,SAAS;AACX,WAAO;AAAA,EACT;AACA,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,EACT;AACA,QAAM,SAAS,oBAAoB;AACnC,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,UAAU,MAAM,kBAAkB,MAAM;AAC9C,WAAO,QAAQ,mBAAmB,KAAK,KAAK;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAWA,eAAsB,kBAAkB;AAAA,EACtC;AAAA,EACA;AAAA,EACA,eAAe;AACjB,GAA2B;AACzB,QAAM,MAAM,4BAA4B;AACxC,QAAM,OAAO,qBAAqB;AAClC,QAAM,OAAO,MAAM,uBAAuB,YAAY;AAEtD,SACE,iCACG;AAAA,WAAO,oBAAC,gBAAa,MAAY,IAAK;AAAA,IACvC;AAAA,MAAC;AAAA;AAAA,QACC,cAAc,IAAI,gBAAgB,MAAM;AAAA,QACxC,aAAa,IAAI;AAAA,QACjB;AAAA,QAEC;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;","names":[]}
package/dist/next.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/next.tsx
32
32
  var next_exports = {};
33
33
  __export(next_exports, {
34
+ JmpGoogleTag: () => JmpGoogleTag,
34
35
  JmpLandingCaptureScript: () => JmpLandingCaptureScript,
35
36
  JmpNextAnalytics: () => JmpNextAnalytics,
36
37
  JmpNextPageViewTracker: () => JmpNextPageViewTracker,
@@ -46,9 +47,9 @@ __export(next_exports, {
46
47
  useJmpAnalytics: () => useJmpAnalytics
47
48
  });
48
49
  module.exports = __toCommonJS(next_exports);
49
- var import_script = __toESM(require("next/script"), 1);
50
- var import_react2 = require("react");
51
- var import_navigation = require("next/navigation");
50
+ var import_script2 = __toESM(require("next/script"), 1);
51
+ var import_react3 = require("react");
52
+ var import_navigation2 = require("next/navigation");
52
53
 
53
54
  // src/landing-capture.ts
54
55
  var JMP_SESSION_ID_KEY = "jmp_analytics_sid";
@@ -829,10 +830,17 @@ function createJmpAnalytics(config) {
829
830
  };
830
831
  }
831
832
 
833
+ // src/site-config.ts
834
+ var registeredSite = null;
835
+ function getJmpSiteDefinition() {
836
+ return registeredSite;
837
+ }
838
+
832
839
  // src/env/browser.ts
833
840
  function getJmpBrowserAnalyticsConfigFromEnv(overrides) {
834
841
  const pub = readJmpPublicEnvFromProcess();
835
- const baseUrl = overrides?.baseUrl ?? pub.analyticsUrl;
842
+ const site = getJmpSiteDefinition();
843
+ const baseUrl = overrides?.baseUrl ?? pub.analyticsUrl ?? site?.analyticsPortalUrl;
836
844
  const trackingKey = overrides?.trackingKey ?? pub.trackingKey;
837
845
  if (!baseUrl || !trackingKey) return null;
838
846
  return {
@@ -923,10 +931,51 @@ function JmpTrackedMailtoLink({
923
931
  );
924
932
  }
925
933
 
926
- // src/next.tsx
934
+ // src/next/google-tag.tsx
935
+ var import_script = __toESM(require("next/script"), 1);
936
+ var import_navigation = require("next/navigation");
937
+ var import_react2 = require("react");
927
938
  var import_jsx_runtime2 = require("react/jsx-runtime");
939
+ function JmpGoogleTag({ gaId }) {
940
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
941
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
942
+ import_script.default,
943
+ {
944
+ src: `https://www.googletagmanager.com/gtag/js?id=${gaId}`,
945
+ strategy: "lazyOnload"
946
+ }
947
+ ),
948
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_script.default, { id: "jmp-ga4-init", strategy: "lazyOnload", children: `
949
+ window.dataLayer = window.dataLayer || [];
950
+ function gtag(){dataLayer.push(arguments);}
951
+ gtag('js', new Date());
952
+ gtag('config', '${gaId}', { send_page_view: true });
953
+ ` }),
954
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpGoogleTagPagePath, { gaId })
955
+ ] });
956
+ }
957
+ function JmpGoogleTagPagePath({ gaId }) {
958
+ const pathname = (0, import_navigation.usePathname)();
959
+ const isFirstNavigation = (0, import_react2.useRef)(true);
960
+ (0, import_react2.useEffect)(() => {
961
+ if (isFirstNavigation.current) {
962
+ isFirstNavigation.current = false;
963
+ return;
964
+ }
965
+ const report = () => {
966
+ window.gtag?.("config", gaId, { page_path: pathname || "/" });
967
+ };
968
+ report();
969
+ const timer = window.setTimeout(report, 100);
970
+ return () => window.clearTimeout(timer);
971
+ }, [gaId, pathname]);
972
+ return null;
973
+ }
974
+
975
+ // src/next.tsx
976
+ var import_jsx_runtime3 = require("react/jsx-runtime");
928
977
  function useJmpAnalytics() {
929
- return (0, import_react2.useMemo)(() => getOrCreateJmpAnalyticsForNextApp(), []);
978
+ return (0, import_react3.useMemo)(() => getOrCreateJmpAnalyticsForNextApp(), []);
930
979
  }
931
980
  function JmpLandingCaptureScript({
932
981
  trackingKey = readJmpPublicEnvFromProcess().trackingKey
@@ -934,8 +983,8 @@ function JmpLandingCaptureScript({
934
983
  if (!trackingKey?.trim()) {
935
984
  return null;
936
985
  }
937
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
938
- import_script.default,
986
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
987
+ import_script2.default,
939
988
  {
940
989
  id: "jmp-landing-capture",
941
990
  strategy: "beforeInteractive",
@@ -945,58 +994,90 @@ function JmpLandingCaptureScript({
945
994
  }
946
995
  );
947
996
  }
948
- function JmpNextPageViewTrackerInner() {
949
- const pathname = (0, import_navigation.usePathname)();
950
- const searchParams = (0, import_navigation.useSearchParams)();
997
+ function JmpNextPageViewTrackerInner({
998
+ configOverrides
999
+ }) {
1000
+ const pathname = (0, import_navigation2.usePathname)();
1001
+ const searchParams = (0, import_navigation2.useSearchParams)();
951
1002
  const query = searchParams?.toString() ?? "";
952
1003
  const path = query ? `${pathname}?${query}` : pathname;
953
- (0, import_react2.useLayoutEffect)(() => {
954
- const client = getOrCreateJmpAnalyticsForNextApp();
1004
+ (0, import_react3.useLayoutEffect)(() => {
1005
+ const client = getOrCreateJmpAnalyticsForNextApp(configOverrides);
955
1006
  void client?.trackPageView({ path });
956
- }, [path]);
1007
+ }, [path, configOverrides]);
957
1008
  return null;
958
1009
  }
959
- function JmpNextPageViewTracker() {
960
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextPageViewTrackerInner, {}) });
1010
+ function JmpNextPageViewTracker({
1011
+ configOverrides
1012
+ }) {
1013
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react3.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JmpNextPageViewTrackerInner, { configOverrides }) });
961
1014
  }
962
1015
  function useJmpTrackingActive(deferUntilConsent) {
963
- const consentGranted = (0, import_react2.useSyncExternalStore)(
1016
+ const consentGranted = (0, import_react3.useSyncExternalStore)(
964
1017
  subscribeJmpTrackingConsent,
965
1018
  () => isJmpTrackingConsentGranted(),
966
1019
  () => false
967
1020
  );
968
1021
  return !deferUntilConsent || consentGranted;
969
1022
  }
970
- function JmpNextAnalyticsBootstrap() {
971
- (0, import_react2.useLayoutEffect)(() => {
972
- getOrCreateJmpAnalyticsForNextApp();
973
- }, []);
1023
+ function jmpConfigOverridesFromProps(props) {
1024
+ const { analyticsUrl, trackingKey } = props;
1025
+ if (!analyticsUrl?.trim() && !trackingKey?.trim()) {
1026
+ return void 0;
1027
+ }
1028
+ return {
1029
+ ...analyticsUrl?.trim() ? { baseUrl: analyticsUrl.trim() } : {},
1030
+ ...trackingKey?.trim() ? { trackingKey: trackingKey.trim() } : {}
1031
+ };
1032
+ }
1033
+ function JmpNextAnalyticsBootstrap({
1034
+ configOverrides
1035
+ }) {
1036
+ (0, import_react3.useLayoutEffect)(() => {
1037
+ getOrCreateJmpAnalyticsForNextApp(configOverrides);
1038
+ }, [configOverrides]);
974
1039
  return null;
975
1040
  }
976
1041
  function JmpNextAnalytics({
977
- deferUntilConsent = readJmpPublicEnvFromProcess().requireConsent === true
1042
+ deferUntilConsent = readJmpPublicEnvFromProcess().requireConsent === true,
1043
+ analyticsUrl,
1044
+ trackingKey
978
1045
  } = {}) {
979
1046
  const active = useJmpTrackingActive(deferUntilConsent);
1047
+ const configOverrides = (0, import_react3.useMemo)(
1048
+ () => jmpConfigOverridesFromProps({ analyticsUrl, trackingKey }),
1049
+ [analyticsUrl, trackingKey]
1050
+ );
980
1051
  if (!active) {
981
1052
  return null;
982
1053
  }
983
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
984
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextAnalyticsBootstrap, {}),
985
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpLandingCaptureScript, {}),
986
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextPageViewTracker, {})
1054
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1055
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JmpNextAnalyticsBootstrap, { configOverrides }),
1056
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JmpLandingCaptureScript, { trackingKey }),
1057
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JmpNextPageViewTracker, { configOverrides })
987
1058
  ] });
988
1059
  }
989
1060
  function JmpSiteRoot({
990
1061
  children,
991
- deferUntilConsent
1062
+ deferUntilConsent,
1063
+ analyticsUrl,
1064
+ trackingKey
992
1065
  }) {
993
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
994
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JmpNextAnalytics, { deferUntilConsent }),
1066
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1067
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1068
+ JmpNextAnalytics,
1069
+ {
1070
+ deferUntilConsent,
1071
+ analyticsUrl,
1072
+ trackingKey
1073
+ }
1074
+ ),
995
1075
  children
996
1076
  ] });
997
1077
  }
998
1078
  // Annotate the CommonJS export names for ESM import in node:
999
1079
  0 && (module.exports = {
1080
+ JmpGoogleTag,
1000
1081
  JmpLandingCaptureScript,
1001
1082
  JmpNextAnalytics,
1002
1083
  JmpNextPageViewTracker,