@jmp-technologies/analytics 0.1.25 → 0.1.26

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 (91) hide show
  1. package/dist/astro/client.cjs +103 -0
  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 +5 -3
  6. package/dist/astro/client.js.map +1 -1
  7. package/dist/astro/content.cjs +185 -1
  8. package/dist/astro/content.cjs.map +1 -1
  9. package/dist/astro/content.d.cts +2 -2
  10. package/dist/astro/content.d.ts +2 -2
  11. package/dist/astro/content.js +4 -3
  12. package/dist/astro/integration.cjs +55 -9
  13. package/dist/astro/integration.cjs.map +1 -1
  14. package/dist/astro/integration.js +4 -3
  15. package/dist/astro/integration.js.map +1 -1
  16. package/dist/astro/middleware.cjs.map +1 -1
  17. package/dist/astro/middleware.d.cts +2 -2
  18. package/dist/astro/middleware.d.ts +2 -2
  19. package/dist/astro/middleware.js +2 -2
  20. package/dist/astro/register.cjs +55 -9
  21. package/dist/astro/register.cjs.map +1 -1
  22. package/dist/astro/register.js +4 -3
  23. package/dist/{chunk-MOX7RJUU.js → chunk-A4RRTY5D.js} +1 -1
  24. package/dist/{chunk-MOX7RJUU.js.map → chunk-A4RRTY5D.js.map} +1 -1
  25. package/dist/{chunk-7MLPSL6T.js → chunk-HODG4NJQ.js} +2 -2
  26. package/dist/{chunk-L67PVZYL.js → chunk-PKGUBYZR.js} +3 -24
  27. package/dist/chunk-PKGUBYZR.js.map +1 -0
  28. package/dist/{chunk-VRDPVKAN.js → chunk-RCOHQAGL.js} +159 -156
  29. package/dist/chunk-RCOHQAGL.js.map +1 -0
  30. package/dist/chunk-TCZ6TC2L.js +149 -0
  31. package/dist/chunk-TCZ6TC2L.js.map +1 -0
  32. package/dist/{chunk-AJ33NQVZ.js → chunk-TFHOTBOL.js} +3 -3
  33. package/dist/{chunk-5GORAM7J.js → chunk-WFTTIRDJ.js} +2 -2
  34. package/dist/{chunk-OLWCYWVB.js → chunk-XQZOGWF2.js} +3 -3
  35. package/dist/chunk-YCVHRMM6.js +28 -0
  36. package/dist/chunk-YCVHRMM6.js.map +1 -0
  37. package/dist/{create-jmp-analytics-COxo3-yg.d.ts → create-jmp-analytics-JKAsJAWb.d.ts} +1 -1
  38. package/dist/{create-jmp-analytics-ClLOMb9p.d.cts → create-jmp-analytics-Mau-oxji.d.cts} +1 -1
  39. package/dist/index.cjs +156 -11
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +12 -6
  42. package/dist/index.d.ts +12 -6
  43. package/dist/index.js +13 -3
  44. package/dist/next/content.cjs +185 -1
  45. package/dist/next/content.cjs.map +1 -1
  46. package/dist/next/content.d.cts +2 -2
  47. package/dist/next/content.d.ts +2 -2
  48. package/dist/next/content.js +4 -3
  49. package/dist/next/content.js.map +1 -1
  50. package/dist/next/instrumentation.cjs +55 -9
  51. package/dist/next/instrumentation.cjs.map +1 -1
  52. package/dist/next/instrumentation.js +3 -2
  53. package/dist/next/instrumentation.js.map +1 -1
  54. package/dist/next/landing-capture-script-server.cjs +61 -0
  55. package/dist/next/landing-capture-script-server.cjs.map +1 -0
  56. package/dist/next/landing-capture-script-server.d.cts +14 -0
  57. package/dist/next/landing-capture-script-server.d.ts +14 -0
  58. package/dist/next/landing-capture-script-server.js +8 -0
  59. package/dist/next/landing-capture-script-server.js.map +1 -0
  60. package/dist/next/middleware.cjs.map +1 -1
  61. package/dist/next/middleware.d.cts +2 -2
  62. package/dist/next/middleware.d.ts +2 -2
  63. package/dist/next/middleware.js +2 -2
  64. package/dist/next/site-root-server.cjs +159 -23
  65. package/dist/next/site-root-server.cjs.map +1 -1
  66. package/dist/next/site-root-server.js +9 -4
  67. package/dist/next/site-root-server.js.map +1 -1
  68. package/dist/next.cjs +152 -70
  69. package/dist/next.cjs.map +1 -1
  70. package/dist/next.d.cts +7 -15
  71. package/dist/next.d.ts +7 -15
  72. package/dist/next.js +7 -7
  73. package/dist/react.cjs +103 -0
  74. package/dist/react.cjs.map +1 -1
  75. package/dist/react.d.cts +2 -2
  76. package/dist/react.d.ts +2 -2
  77. package/dist/react.js +4 -3
  78. package/dist/{site-config-BSMVyUjk.d.ts → site-config-CXUdGLqw.d.cts} +6 -3
  79. package/dist/{site-config-8TTHIW6w.d.cts → site-config-w_Db9YSk.d.ts} +6 -3
  80. package/dist/{track-page-view-BweavmXz.d.cts → track-page-view-CkuSYuco.d.cts} +1 -1
  81. package/dist/{track-page-view-DtxbdM5q.d.ts → track-page-view-QRSk8vap.d.ts} +1 -1
  82. package/dist/{types-Bk0ST1aH.d.ts → types-C1eq8E5u.d.cts} +12 -4
  83. package/dist/{types-Bk0ST1aH.d.cts → types-C1eq8E5u.d.ts} +12 -4
  84. package/docs/astro-integration.md +1 -1
  85. package/package.json +7 -2
  86. package/dist/chunk-L67PVZYL.js.map +0 -1
  87. package/dist/chunk-VRDPVKAN.js.map +0 -1
  88. /package/dist/{chunk-7MLPSL6T.js.map → chunk-HODG4NJQ.js.map} +0 -0
  89. /package/dist/{chunk-AJ33NQVZ.js.map → chunk-TFHOTBOL.js.map} +0 -0
  90. /package/dist/{chunk-5GORAM7J.js.map → chunk-WFTTIRDJ.js.map} +0 -0
  91. /package/dist/{chunk-OLWCYWVB.js.map → chunk-XQZOGWF2.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/http.ts","../src/server/page-view.ts","../src/env-public.ts","../src/site-config.ts"],"sourcesContent":["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 {\n getHeader,\n normalizeBaseUrl,\n pathFromRequestUrl,\n resolveFetch,\n} from \"../http\"\nimport type { JmpContentConfig, TrackServerPageViewOptions } from \"../types\"\n\nexport async function trackJmpServerPageView(\n config: JmpContentConfig,\n options: TrackServerPageViewOptions = {}\n): Promise<void> {\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 server analytics: fetch is not available; pass fetchImpl in config\")\n }\n\n const visitorUserAgent =\n options.userAgent ?? getHeader(options.request?.headers, \"user-agent\")\n const visitorReferer =\n options.referer ?? getHeader(options.request?.headers, \"referer\")\n const path = options.path ?? pathFromRequestUrl(options.request?.url)\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n }\n if (visitorUserAgent) {\n headers[\"X-JMP-Visitor-User-Agent\"] = visitorUserAgent\n }\n if (visitorReferer) {\n headers[\"X-JMP-Visitor-Referer\"] = visitorReferer\n }\n\n const payload: Record<string, unknown> = {\n type: \"page_view\",\n path: path || \"/\",\n metadata:\n options.metadata && typeof options.metadata === \"object\"\n ? options.metadata\n : {},\n }\n if (options.sessionId) {\n payload.sessionId = options.sessionId\n }\n\n const url = `${baseUrl}/api/events`\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, payload)\n }\n\n const res = await resolvedFetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n })\n\n if (!res.ok) {\n const text = await res.text()\n throw new Error(`JMP server analytics HTTP ${res.status} — ${text}`)\n }\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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/http.ts","../src/server/page-view.ts","../src/env-public.ts","../src/site-config.ts"],"sourcesContent":["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 {\n getHeader,\n normalizeBaseUrl,\n pathFromRequestUrl,\n resolveFetch,\n} from \"../http\"\nimport type { JmpContentConfig, TrackServerPageViewOptions } from \"../types\"\n\nexport async function trackJmpServerPageView(\n config: JmpContentConfig,\n options: TrackServerPageViewOptions = {}\n): Promise<void> {\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 server analytics: fetch is not available; pass fetchImpl in config\")\n }\n\n const visitorUserAgent =\n options.userAgent ?? getHeader(options.request?.headers, \"user-agent\")\n const visitorReferer =\n options.referer ?? getHeader(options.request?.headers, \"referer\")\n const path = options.path ?? pathFromRequestUrl(options.request?.url)\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-JMP-Tracking-Key\": config.trackingKey,\n }\n if (visitorUserAgent) {\n headers[\"X-JMP-Visitor-User-Agent\"] = visitorUserAgent\n }\n if (visitorReferer) {\n headers[\"X-JMP-Visitor-Referer\"] = visitorReferer\n }\n\n const payload: Record<string, unknown> = {\n type: \"page_view\",\n path: path || \"/\",\n metadata:\n options.metadata && typeof options.metadata === \"object\"\n ? options.metadata\n : {},\n }\n if (options.sessionId) {\n payload.sessionId = options.sessionId\n }\n\n const url = `${baseUrl}/api/events`\n if (config.debug) {\n console.debug(\"[@jmp-technologies/analytics]\", \"POST\", url, payload)\n }\n\n const res = await resolvedFetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n })\n\n if (!res.ok) {\n const text = await res.text()\n throw new Error(`JMP server analytics HTTP ${res.status} — ${text}`)\n }\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 /**\n * Optional override. Wrappers are auto-discovered when content loaders run\n * (`loadBlogPosts`, `loadFaqs`, etc.) and reported to the dashboard.\n */\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"],"mappings":"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","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  getHeader,
3
3
  getJmpContentConfig,
4
4
  trackJmpServerPageView
5
- } from "./chunk-MOX7RJUU.js";
5
+ } from "./chunk-A4RRTY5D.js";
6
6
 
7
7
  // src/server/should-track-server-page-view.ts
8
8
  function shouldSkipServerPageViewRequest(request) {
@@ -56,4 +56,4 @@ async function jmpTrackServerPageView(request) {
56
56
  export {
57
57
  jmpTrackServerPageView
58
58
  };
59
- //# sourceMappingURL=chunk-7MLPSL6T.js.map
59
+ //# sourceMappingURL=chunk-HODG4NJQ.js.map
@@ -1,15 +1,13 @@
1
1
  import {
2
- buildLandingCaptureInlineScript,
3
2
  getOrCreateJmpAnalyticsFromBrowserEnv,
4
3
  isJmpTrackingConsentGranted,
5
4
  subscribeJmpTrackingConsent
6
- } from "./chunk-VRDPVKAN.js";
5
+ } from "./chunk-RCOHQAGL.js";
7
6
  import {
8
7
  readJmpPublicEnvFromProcess
9
- } from "./chunk-MOX7RJUU.js";
8
+ } from "./chunk-A4RRTY5D.js";
10
9
 
11
10
  // src/next.tsx
12
- import Script from "next/script";
13
11
  import {
14
12
  Suspense,
15
13
  useLayoutEffect,
@@ -44,23 +42,6 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
44
42
  function useJmpAnalytics() {
45
43
  return useMemo(() => getOrCreateJmpAnalyticsForNextApp(), []);
46
44
  }
47
- function JmpLandingCaptureScript({
48
- trackingKey = readJmpPublicEnvFromProcess().trackingKey
49
- } = {}) {
50
- if (!trackingKey?.trim()) {
51
- return null;
52
- }
53
- return /* @__PURE__ */ jsx(
54
- Script,
55
- {
56
- id: "jmp-landing-capture",
57
- strategy: "beforeInteractive",
58
- dangerouslySetInnerHTML: {
59
- __html: buildLandingCaptureInlineScript()
60
- }
61
- }
62
- );
63
- }
64
45
  function JmpNextPageViewTrackerInner({
65
46
  configOverrides
66
47
  }) {
@@ -120,7 +101,6 @@ function JmpNextAnalytics({
120
101
  }
121
102
  return /* @__PURE__ */ jsxs(Fragment, { children: [
122
103
  /* @__PURE__ */ jsx(JmpNextAnalyticsBootstrap, { configOverrides }),
123
- /* @__PURE__ */ jsx(JmpLandingCaptureScript, { trackingKey }),
124
104
  /* @__PURE__ */ jsx(JmpNextPageViewTracker, { configOverrides })
125
105
  ] });
126
106
  }
@@ -146,9 +126,8 @@ function JmpSiteRoot({
146
126
  export {
147
127
  getOrCreateJmpAnalyticsForNextApp,
148
128
  useJmpAnalytics,
149
- JmpLandingCaptureScript,
150
129
  JmpNextPageViewTracker,
151
130
  JmpNextAnalytics,
152
131
  JmpSiteRoot
153
132
  };
154
- //# sourceMappingURL=chunk-L67PVZYL.js.map
133
+ //# sourceMappingURL=chunk-PKGUBYZR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/next.tsx","../src/env/browser-next.ts"],"sourcesContent":["\"use client\"\n\nimport {\n Suspense,\n useLayoutEffect,\n useMemo,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\"\nimport { usePathname, useSearchParams } from \"next/navigation\"\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\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/** Client route tracking + analytics bootstrap — landing capture is SSR-only via {@link JmpLandingCaptureScriptServer}. */\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 <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: App Router page views + children.\n * Put {@link JmpLandingCaptureScriptServer} in layout `<head>` (or use {@link JmpSiteRootServer}).\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","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"],"mappings":"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","names":[]}
@@ -1,3 +1,8 @@
1
+ import {
2
+ JMP_UTM_PARAM_KEYS,
3
+ captureLandingAttribution,
4
+ getOrCreateSessionIdFromStorage
5
+ } from "./chunk-TCZ6TC2L.js";
1
6
  import {
2
7
  formatApiErrorMessage,
3
8
  getJmpSiteDefinition,
@@ -5,149 +10,7 @@ import {
5
10
  readJmpPublicEnvFromProcess,
6
11
  resolveFetch,
7
12
  responseLooksLikeHtml
8
- } from "./chunk-MOX7RJUU.js";
9
-
10
- // src/landing-capture.ts
11
- var JMP_SESSION_ID_KEY = "jmp_analytics_sid";
12
- var JMP_LANDING_REFERER_KEY = "jmp_analytics_landing_referer";
13
- var JMP_LANDING_REFERER_COOKIE = "jmp_lr";
14
- var JMP_UTM_SESSION_KEY = "jmp_analytics_utm";
15
- var LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60;
16
- var JMP_UTM_PARAM_KEYS = [
17
- "utm_source",
18
- "utm_medium",
19
- "utm_campaign",
20
- "utm_content",
21
- "utm_term"
22
- ];
23
- function refererHostFromUrl(referer) {
24
- const trimmed = referer.trim();
25
- try {
26
- return new URL(trimmed).hostname.replace(/^www\./i, "").toLowerCase();
27
- } catch {
28
- try {
29
- return new URL(`https://${trimmed}`).hostname.replace(/^www\./i, "").toLowerCase();
30
- } catch {
31
- return null;
32
- }
33
- }
34
- }
35
- function isExternalReferrerUrl(referer, siteHostname) {
36
- const refHost = refererHostFromUrl(referer);
37
- if (!refHost) {
38
- return false;
39
- }
40
- const site = siteHostname.replace(/^www\./i, "").toLowerCase();
41
- return refHost !== site && !refHost.endsWith(`.${site}`);
42
- }
43
- function registrableSiteDomain(siteHostname) {
44
- const site = siteHostname.replace(/^www\./i, "").toLowerCase();
45
- if (!site || site === "localhost" || site.endsWith(".local")) {
46
- return null;
47
- }
48
- if (!site.includes(".")) {
49
- return null;
50
- }
51
- return site;
52
- }
53
- function readLandingRefererCookie(siteHostname) {
54
- if (typeof document === "undefined") {
55
- return null;
56
- }
57
- const apex = registrableSiteDomain(siteHostname);
58
- if (!apex) {
59
- return null;
60
- }
61
- const prefix = `${JMP_LANDING_REFERER_COOKIE}=`;
62
- for (const part of document.cookie.split(";")) {
63
- const trimmed = part.trim();
64
- if (!trimmed.startsWith(prefix)) {
65
- continue;
66
- }
67
- try {
68
- const value = decodeURIComponent(trimmed.slice(prefix.length)).trim();
69
- return value.length > 0 ? value : null;
70
- } catch {
71
- return null;
72
- }
73
- }
74
- return null;
75
- }
76
- function writeLandingRefererCookie(referer, siteHostname) {
77
- if (typeof document === "undefined") {
78
- return;
79
- }
80
- const apex = registrableSiteDomain(siteHostname);
81
- if (!apex) {
82
- return;
83
- }
84
- const value = encodeURIComponent(referer.trim().slice(0, 2048));
85
- const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
86
- document.cookie = `${JMP_LANDING_REFERER_COOKIE}=${value}; Domain=.${apex}; Path=/; Max-Age=${LANDING_REFERER_COOKIE_MAX_AGE_SEC}; SameSite=Lax${secure}`;
87
- }
88
- function captureLandingAttribution(siteHostname) {
89
- if (typeof document === "undefined" || typeof window === "undefined") {
90
- return { landingReferer: null, utm: {} };
91
- }
92
- let landingReferer = null;
93
- let utm = {};
94
- try {
95
- const storedReferer = sessionStorage.getItem(JMP_LANDING_REFERER_KEY)?.trim() || null;
96
- const cookieReferer = readLandingRefererCookie(siteHostname);
97
- const preservedReferer = storedReferer || cookieReferer;
98
- const currentReferer = document.referrer?.trim() || null;
99
- if (currentReferer && isExternalReferrerUrl(currentReferer, siteHostname)) {
100
- sessionStorage.setItem(JMP_LANDING_REFERER_KEY, currentReferer);
101
- writeLandingRefererCookie(currentReferer, siteHostname);
102
- landingReferer = currentReferer;
103
- } else if (preservedReferer) {
104
- landingReferer = preservedReferer;
105
- if (!storedReferer) {
106
- sessionStorage.setItem(JMP_LANDING_REFERER_KEY, preservedReferer);
107
- }
108
- }
109
- const params = new URLSearchParams(window.location.search);
110
- const rawUtm = sessionStorage.getItem(JMP_UTM_SESSION_KEY);
111
- if (rawUtm) {
112
- const parsed = JSON.parse(rawUtm);
113
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
114
- utm = parsed;
115
- }
116
- }
117
- let updated = false;
118
- for (const key of JMP_UTM_PARAM_KEYS) {
119
- const value = params.get(key)?.trim();
120
- if (value) {
121
- utm[key] = value;
122
- updated = true;
123
- }
124
- }
125
- if (updated) {
126
- sessionStorage.setItem(JMP_UTM_SESSION_KEY, JSON.stringify(utm));
127
- }
128
- } catch {
129
- }
130
- return { landingReferer, utm };
131
- }
132
- function getOrCreateSessionIdFromStorage() {
133
- if (typeof window === "undefined" || !globalThis.crypto?.randomUUID) {
134
- return void 0;
135
- }
136
- try {
137
- let id = sessionStorage.getItem(JMP_SESSION_ID_KEY);
138
- if (!id) {
139
- id = crypto.randomUUID();
140
- sessionStorage.setItem(JMP_SESSION_ID_KEY, id);
141
- }
142
- return id;
143
- } catch {
144
- return void 0;
145
- }
146
- }
147
- function buildLandingCaptureInlineScript() {
148
- const cookieMaxAge = String(LANDING_REFERER_COOKIE_MAX_AGE_SEC);
149
- 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){}})();`;
150
- }
13
+ } from "./chunk-A4RRTY5D.js";
151
14
 
152
15
  // src/contact-link-classifier.ts
153
16
  var BOOKING_LINK_HOSTS = [
@@ -754,12 +617,146 @@ async function getContentText(config, path) {
754
617
  return text;
755
618
  }
756
619
 
620
+ // src/content/wrapper-discovery.ts
621
+ var WRAPPER_SLOT_ORDER = [
622
+ "blog",
623
+ "faq",
624
+ "contact",
625
+ "trust",
626
+ "trust.summary",
627
+ "trust.testimonials",
628
+ "trust.reviews",
629
+ "trust.stats",
630
+ "trust.logos",
631
+ "trust.credentials"
632
+ ];
633
+ var discovered = /* @__PURE__ */ new Set();
634
+ var syncTimer = null;
635
+ var pendingSyncConfig = null;
636
+ var reportedDedupeKeys = /* @__PURE__ */ new Set();
637
+ var syncHandler = null;
638
+ function markContentWrapper(slot) {
639
+ const normalized = slot.trim().toLowerCase();
640
+ if (!normalized) {
641
+ return;
642
+ }
643
+ discovered.add(normalized);
644
+ }
645
+ function markTrustBundleWrappers(bundle) {
646
+ for (const slot of inferTrustWrapperSlots(bundle)) {
647
+ discovered.add(slot);
648
+ }
649
+ }
650
+ function inferTrustWrapperSlots(bundle) {
651
+ const slots = /* @__PURE__ */ new Set(["trust"]);
652
+ const { summary, testimonials, stats, logos, credentials } = bundle;
653
+ const hasSummary = summary.google_rating != null || summary.google_review_count != null || Boolean(summary.google_reviews_url?.trim()) || Boolean(summary.yelp_url?.trim()) || Boolean(summary.trustpilot_url?.trim()) || Boolean(summary.disclaimer?.trim());
654
+ if (hasSummary) {
655
+ slots.add("trust.summary");
656
+ }
657
+ let hasTestimonials = false;
658
+ let hasReviews = false;
659
+ for (const item of testimonials) {
660
+ const bucket = item.placement?.trim().toLowerCase();
661
+ if (bucket === "reviews") {
662
+ hasReviews = true;
663
+ } else {
664
+ hasTestimonials = true;
665
+ }
666
+ }
667
+ if (hasTestimonials) {
668
+ slots.add("trust.testimonials");
669
+ }
670
+ if (hasReviews) {
671
+ slots.add("trust.reviews");
672
+ }
673
+ if (stats.length > 0) {
674
+ slots.add("trust.stats");
675
+ }
676
+ if (logos.length > 0) {
677
+ slots.add("trust.logos");
678
+ }
679
+ if (credentials.length > 0) {
680
+ slots.add("trust.credentials");
681
+ }
682
+ return sortWrapperSlots([...slots]);
683
+ }
684
+ function getDiscoveredContentWrappers() {
685
+ return sortWrapperSlots([...discovered]);
686
+ }
687
+ function resolveContentWrappersForReport(config) {
688
+ const explicit = config.contentWrappers ?? [];
689
+ const auto = getDiscoveredContentWrappers();
690
+ if (config.contentWrapperMode === "manual") {
691
+ return sortWrapperSlots([...explicit]);
692
+ }
693
+ if (config.contentWrapperMode === "explicit-only") {
694
+ return sortWrapperSlots([...explicit]);
695
+ }
696
+ const merged = /* @__PURE__ */ new Set([...explicit, ...auto]);
697
+ return sortWrapperSlots([...merged]);
698
+ }
699
+ function hasReportedContentWrappers(dedupeKey) {
700
+ return reportedDedupeKeys.has(dedupeKey);
701
+ }
702
+ function rememberReportedContentWrappers(dedupeKey) {
703
+ reportedDedupeKeys.add(dedupeKey);
704
+ }
705
+ function setContentWrapperSyncHandler(handler) {
706
+ syncHandler = handler;
707
+ }
708
+ function scheduleContentWrapperSync(config) {
709
+ if (!syncHandler) {
710
+ return;
711
+ }
712
+ pendingSyncConfig = config;
713
+ if (syncTimer) {
714
+ clearTimeout(syncTimer);
715
+ }
716
+ syncTimer = setTimeout(() => {
717
+ syncTimer = null;
718
+ const cfg = pendingSyncConfig;
719
+ pendingSyncConfig = null;
720
+ if (!cfg || !syncHandler) {
721
+ return;
722
+ }
723
+ void syncHandler(cfg).catch(() => {
724
+ });
725
+ }, 750);
726
+ }
727
+ function resetContentWrapperDiscoveryForTests() {
728
+ discovered.clear();
729
+ reportedDedupeKeys.clear();
730
+ syncHandler = null;
731
+ if (syncTimer) {
732
+ clearTimeout(syncTimer);
733
+ syncTimer = null;
734
+ }
735
+ pendingSyncConfig = null;
736
+ }
737
+ function sortWrapperSlots(slots) {
738
+ const order = new Map(WRAPPER_SLOT_ORDER.map((slot, index) => [slot, index]));
739
+ return [...new Set(slots)].sort((a, b) => {
740
+ const ai = order.get(a) ?? 999;
741
+ const bi = order.get(b) ?? 999;
742
+ if (ai !== bi) {
743
+ return ai - bi;
744
+ }
745
+ return a.localeCompare(b);
746
+ });
747
+ }
748
+
757
749
  // src/content/api.ts
750
+ function afterContentWrapperUse(config) {
751
+ scheduleContentWrapperSync(config);
752
+ }
758
753
  async function getJmpBlogPosts(config) {
759
754
  const data = await getContent(
760
755
  config,
761
756
  "/api/content/blog"
762
757
  );
758
+ markContentWrapper("blog");
759
+ afterContentWrapperUse(config);
763
760
  return data.posts;
764
761
  }
765
762
  async function getJmpBlogPost(config, slug) {
@@ -768,6 +765,8 @@ async function getJmpBlogPost(config, slug) {
768
765
  config,
769
766
  `/api/content/blog/${encodeURIComponent(slug)}`
770
767
  );
768
+ markContentWrapper("blog");
769
+ afterContentWrapperUse(config);
771
770
  return data.post;
772
771
  } catch (error) {
773
772
  if (error instanceof Error && error.message.includes("HTTP 404")) {
@@ -781,35 +780,35 @@ async function getJmpFaqs(config) {
781
780
  config,
782
781
  "/api/content/faqs"
783
782
  );
783
+ markContentWrapper("faq");
784
+ afterContentWrapperUse(config);
784
785
  return data.faqs;
785
786
  }
786
787
  async function getJmpTrustBundle(config) {
787
- return getContent(config, "/api/content/trust");
788
+ const bundle = await getContent(config, "/api/content/trust");
789
+ markTrustBundleWrappers(bundle);
790
+ afterContentWrapperUse(config);
791
+ return bundle;
788
792
  }
789
793
  async function getJmpSiteProfile(config) {
790
794
  const data = await getContent(
791
795
  config,
792
796
  "/api/content/site"
793
797
  );
798
+ markContentWrapper("contact");
799
+ afterContentWrapperUse(config);
794
800
  return data.site;
795
801
  }
796
802
  async function getJmpRobotsTxt(config) {
797
803
  return getContentText(config, "/api/content/robots");
798
804
  }
799
- var registeredContentWrappers = /* @__PURE__ */ new Set();
800
- function resolveContentWrappersFromConfig(config) {
801
- if (config.contentWrappers?.length) {
802
- return [...config.contentWrappers];
803
- }
804
- return [];
805
- }
806
805
  async function registerJmpContentWrappers(config) {
807
- const wrappers = resolveContentWrappersFromConfig(config);
806
+ const wrappers = resolveContentWrappersForReport(config);
808
807
  if (!wrappers.length) {
809
808
  return { ok: false, skipped: true };
810
809
  }
811
810
  const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(",")}`;
812
- if (registeredContentWrappers.has(dedupeKey)) {
811
+ if (hasReportedContentWrappers(dedupeKey)) {
813
812
  return { ok: true, wrappers };
814
813
  }
815
814
  const baseUrl = normalizeBaseUrl(config.baseUrl);
@@ -846,10 +845,11 @@ async function registerJmpContentWrappers(config) {
846
845
  [`JMP content wrappers HTTP ${res.status}`, parsed.error, parsed.hint].filter(Boolean).join(" \u2014 ")
847
846
  );
848
847
  }
849
- registeredContentWrappers.add(dedupeKey);
848
+ rememberReportedContentWrappers(dedupeKey);
850
849
  const data = JSON.parse(text);
851
850
  return { ok: true, wrappers: data.wrappers ?? wrappers };
852
851
  }
852
+ setContentWrapperSyncHandler(registerJmpContentWrappers);
853
853
 
854
854
  // src/content/client.ts
855
855
  function createJmpContentClient(config) {
@@ -910,7 +910,6 @@ function createJmpContentClientFromServerEnv(overrides) {
910
910
  }
911
911
 
912
912
  export {
913
- buildLandingCaptureInlineScript,
914
913
  BOOKING_LINK_HOSTS,
915
914
  classifyContactHref,
916
915
  JMP_ANALYTICS_SDK_VERSION,
@@ -920,6 +919,10 @@ export {
920
919
  subscribeJmpTrackingConsent,
921
920
  setJmpTrackingConsent,
922
921
  createJmpAnalytics,
922
+ markContentWrapper,
923
+ inferTrustWrapperSlots,
924
+ getDiscoveredContentWrappers,
925
+ resetContentWrapperDiscoveryForTests,
923
926
  getJmpBlogPosts,
924
927
  getJmpBlogPost,
925
928
  getJmpFaqs,
@@ -935,4 +938,4 @@ export {
935
938
  getOrCreateJmpAnalyticsFromBrowserEnv,
936
939
  createJmpContentClientFromServerEnv
937
940
  };
938
- //# sourceMappingURL=chunk-VRDPVKAN.js.map
941
+ //# sourceMappingURL=chunk-RCOHQAGL.js.map