@jmp-technologies/analytics 0.1.27 → 0.1.32

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 (100) hide show
  1. package/dist/astro/client.cjs +256 -11
  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 +308 -13
  7. package/dist/astro/content.cjs.map +1 -1
  8. package/dist/astro/content.d.cts +12 -3
  9. package/dist/astro/content.d.ts +12 -3
  10. package/dist/astro/content.js +9 -3
  11. package/dist/astro/integration.cjs +228 -10
  12. package/dist/astro/integration.cjs.map +1 -1
  13. package/dist/astro/integration.js +3 -3
  14. package/dist/astro/middleware.cjs +2 -1
  15. package/dist/astro/middleware.cjs.map +1 -1
  16. package/dist/astro/middleware.d.cts +2 -2
  17. package/dist/astro/middleware.d.ts +2 -2
  18. package/dist/astro/middleware.js +2 -2
  19. package/dist/astro/register.cjs +228 -10
  20. package/dist/astro/register.cjs.map +1 -1
  21. package/dist/astro/register.js +3 -3
  22. package/dist/{chunk-KZ4WVDGL.js → chunk-7623G3XL.js} +3 -3
  23. package/dist/{chunk-A4RRTY5D.js → chunk-AIFJ7DVO.js} +3 -2
  24. package/dist/{chunk-A4RRTY5D.js.map → chunk-AIFJ7DVO.js.map} +1 -1
  25. package/dist/{chunk-YCVHRMM6.js → chunk-G5BMWPQ6.js} +2 -3
  26. package/dist/{chunk-YCVHRMM6.js.map → chunk-G5BMWPQ6.js.map} +1 -1
  27. package/dist/{chunk-HODG4NJQ.js → chunk-HFLU7HQE.js} +2 -2
  28. package/dist/{chunk-PG3IOI5O.js → chunk-HKMDWTA2.js} +53 -6
  29. package/dist/chunk-HKMDWTA2.js.map +1 -0
  30. package/dist/{chunk-DNYJF7BG.js → chunk-KPTMZQCY.js} +3 -3
  31. package/dist/{chunk-FNWE2DT5.js → chunk-NDSPSAWQ.js} +18 -8
  32. package/dist/chunk-NDSPSAWQ.js.map +1 -0
  33. package/dist/{chunk-UVYF33XE.js → chunk-NI7R6QZQ.js} +293 -13
  34. package/dist/chunk-NI7R6QZQ.js.map +1 -0
  35. package/dist/chunk-RI3KKDPX.js +75 -0
  36. package/dist/chunk-RI3KKDPX.js.map +1 -0
  37. package/dist/{create-jmp-analytics-D4Qw_d6o.d.cts → create-jmp-analytics-D1dUsqCQ.d.cts} +1 -2
  38. package/dist/{create-jmp-analytics-DpG5Hdl5.d.ts → create-jmp-analytics-htx2oOEf.d.ts} +1 -2
  39. package/dist/index.cjs +295 -12
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +8 -6
  42. package/dist/index.d.ts +8 -6
  43. package/dist/index.js +10 -2
  44. package/dist/next/content.cjs +310 -13
  45. package/dist/next/content.cjs.map +1 -1
  46. package/dist/next/content.d.cts +7 -5
  47. package/dist/next/content.d.ts +7 -5
  48. package/dist/next/content.js +11 -3
  49. package/dist/next/content.js.map +1 -1
  50. package/dist/next/instrumentation.cjs +228 -10
  51. package/dist/next/instrumentation.cjs.map +1 -1
  52. package/dist/next/instrumentation.js +2 -2
  53. package/dist/next/landing-capture-script-server.cjs +1 -2
  54. package/dist/next/landing-capture-script-server.cjs.map +1 -1
  55. package/dist/next/landing-capture-script-server.js +1 -1
  56. package/dist/next/landing-pages.cjs +228 -12
  57. package/dist/next/landing-pages.cjs.map +1 -1
  58. package/dist/next/landing-pages.d.cts +4 -16
  59. package/dist/next/landing-pages.d.ts +4 -16
  60. package/dist/next/landing-pages.js +13 -67
  61. package/dist/next/landing-pages.js.map +1 -1
  62. package/dist/next/locations.cjs +737 -0
  63. package/dist/next/locations.cjs.map +1 -0
  64. package/dist/next/locations.d.cts +55 -0
  65. package/dist/next/locations.d.ts +55 -0
  66. package/dist/next/locations.js +158 -0
  67. package/dist/next/locations.js.map +1 -0
  68. package/dist/next/middleware.cjs +2 -1
  69. package/dist/next/middleware.cjs.map +1 -1
  70. package/dist/next/middleware.d.cts +2 -2
  71. package/dist/next/middleware.d.ts +2 -2
  72. package/dist/next/middleware.js +2 -2
  73. package/dist/next/site-root-server.cjs +259 -14
  74. package/dist/next/site-root-server.cjs.map +1 -1
  75. package/dist/next/site-root-server.js +5 -5
  76. package/dist/next.cjs +270 -17
  77. package/dist/next.cjs.map +1 -1
  78. package/dist/next.d.cts +3 -3
  79. package/dist/next.d.ts +3 -3
  80. package/dist/next.js +4 -4
  81. package/dist/react.cjs +389 -136
  82. package/dist/react.cjs.map +1 -1
  83. package/dist/react.d.cts +2 -2
  84. package/dist/react.d.ts +2 -2
  85. package/dist/react.js +3 -3
  86. package/dist/{site-config-UTzDG-UG.d.ts → site-config-B7USGGzu.d.cts} +22 -2
  87. package/dist/{site-config-CJqYnZm7.d.cts → site-config-C4bcUoLI.d.ts} +22 -2
  88. package/dist/{track-page-view-tX0Magkd.d.cts → track-page-view-C4TbbULE.d.cts} +1 -1
  89. package/dist/{track-page-view-CMAqyhD_.d.ts → track-page-view-RiB-xgIx.d.ts} +1 -1
  90. package/dist/{types-B-111Wht.d.ts → types-DRljY1Yy.d.cts} +92 -2
  91. package/dist/{types-B-111Wht.d.cts → types-DRljY1Yy.d.ts} +92 -2
  92. package/dist/utils-BajG58pj.d.cts +15 -0
  93. package/dist/utils-CHT3SQFv.d.ts +15 -0
  94. package/package.json +7 -2
  95. package/dist/chunk-FNWE2DT5.js.map +0 -1
  96. package/dist/chunk-PG3IOI5O.js.map +0 -1
  97. package/dist/chunk-UVYF33XE.js.map +0 -1
  98. /package/dist/{chunk-KZ4WVDGL.js.map → chunk-7623G3XL.js.map} +0 -0
  99. /package/dist/{chunk-HODG4NJQ.js.map → chunk-HFLU7HQE.js.map} +0 -0
  100. /package/dist/{chunk-DNYJF7BG.js.map → chunk-KPTMZQCY.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/next/landing-capture-script-server.tsx"],"sourcesContent":["import { buildLandingCaptureInlineScript } from \"../landing-capture\"\n\ntype JmpLandingCaptureScriptServerProps = {\n trackingKey?: string\n}\n\n/**\n * SSR-only landing referrer / UTM capture. Render in root layout `<head>`.\n *\n * Do not use `next/script` or a client component — React 19 will warn and\n * deferred client mounts miss the first referrer anyway.\n */\nexport function JmpLandingCaptureScriptServer({\n trackingKey,\n}: JmpLandingCaptureScriptServerProps = {}) {\n if (!trackingKey?.trim()) {\n return null\n }\n\n return (\n <script\n dangerouslySetInnerHTML={{\n __html: buildLandingCaptureInlineScript(),\n }}\n id=\"jmp-landing-capture\"\n suppressHydrationWarning\n />\n )\n}\n"],"mappings":";;;;;AAoBI;AARG,SAAS,8BAA8B;AAAA,EAC5C;AACF,IAAwC,CAAC,GAAG;AAC1C,MAAI,CAAC,aAAa,KAAK,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,yBAAyB;AAAA,QACvB,QAAQ,gCAAgC;AAAA,MAC1C;AAAA,MACA,IAAG;AAAA,MACH,0BAAwB;AAAA;AAAA,EAC1B;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/next/landing-capture-script-server.tsx"],"sourcesContent":["import { buildLandingCaptureInlineScript } from \"../landing-capture\"\n\ntype JmpLandingCaptureScriptServerProps = {\n trackingKey?: string\n}\n\n/**\n * SSR-only landing referrer / UTM capture. Render in root layout `<head>`.\n *\n * Do not use `next/script` or a client component — React 19 will warn and\n * deferred client mounts miss the first referrer anyway.\n */\nexport function JmpLandingCaptureScriptServer({\n trackingKey,\n}: JmpLandingCaptureScriptServerProps = {}) {\n if (!trackingKey?.trim()) {\n return null\n }\n\n return (\n <script\n dangerouslySetInnerHTML={{\n __html: buildLandingCaptureInlineScript(),\n }}\n id=\"jmp-landing-capture\"\n />\n )\n}\n"],"mappings":";;;;;AAoBI;AARG,SAAS,8BAA8B;AAAA,EAC5C;AACF,IAAwC,CAAC,GAAG;AAC1C,MAAI,CAAC,aAAa,KAAK,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,yBAAyB;AAAA,QACvB,QAAQ,gCAAgC;AAAA,MAC1C;AAAA,MACA,IAAG;AAAA;AAAA,EACL;AAEJ;","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  getHeader,
3
3
  getJmpContentConfig,
4
4
  trackJmpServerPageView
5
- } from "./chunk-A4RRTY5D.js";
5
+ } from "./chunk-AIFJ7DVO.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-HODG4NJQ.js.map
59
+ //# sourceMappingURL=chunk-HFLU7HQE.js.map
@@ -5,13 +5,15 @@ import {
5
5
  getJmpLandingPageDetail,
6
6
  getJmpLandingPageSitemap,
7
7
  getJmpLandingPages,
8
+ getJmpLocationDetail,
9
+ getJmpLocations,
8
10
  getJmpSiteProfile,
9
11
  getJmpTrustBundle
10
- } from "./chunk-UVYF33XE.js";
12
+ } from "./chunk-NI7R6QZQ.js";
11
13
  import {
12
14
  getJmpContentConfig,
13
15
  getJmpSiteDefinition
14
- } from "./chunk-A4RRTY5D.js";
16
+ } from "./chunk-AIFJ7DVO.js";
15
17
 
16
18
  // src/content/site-content.ts
17
19
  var DEFAULT_EXTRA_KEYS = {
@@ -19,7 +21,8 @@ var DEFAULT_EXTRA_KEYS = {
19
21
  dashboardDemoUrl: ["dashboard-demo-url", "sample-dashboard-url"],
20
22
  profilePhotoSrc: ["profile-photo-url", "profile_photo_url"],
21
23
  hours: ["hours", "business-hours"],
22
- serviceArea: ["service-area", "service_area", "service-area-text"]
24
+ serviceArea: ["service-area", "service_area", "service-area-text"],
25
+ googleMapsEmbedUrl: ["google-maps-embed-url", "google_maps_embed_url"]
23
26
  };
24
27
  function sortBlogPosts(posts) {
25
28
  return [...posts].sort((a, b) => {
@@ -70,7 +73,13 @@ function resolveSiteContact(profile) {
70
73
  ...site?.contactExtraKeys
71
74
  };
72
75
  const email = profile?.email?.trim() || fallback.email || "";
73
- const linkedin = profile?.social?.linkedin?.trim() || fallback.linkedin || null;
76
+ const social = {
77
+ linkedin: profile?.social?.linkedin?.trim() || fallback.linkedin || null,
78
+ instagram: profile?.social?.instagram?.trim() || fallback.instagram || null,
79
+ facebook: profile?.social?.facebook?.trim() || fallback.facebook || null,
80
+ x: profile?.social?.x?.trim() || fallback.x || null,
81
+ youtube: profile?.social?.youtube?.trim() || fallback.youtube || null
82
+ };
74
83
  const phone = profile?.phone?.trim() || fallback.phone || null;
75
84
  return {
76
85
  displayName: profile?.display_name?.trim() || profile?.name?.trim() || fallback.displayName || "",
@@ -78,8 +87,12 @@ function resolveSiteContact(profile) {
78
87
  dashboardDemoUrl: readProfileExtra(profile, extraKeys.dashboardDemoUrl ?? []) || fallback.dashboardDemoUrl || "",
79
88
  profilePhotoSrc: readProfileExtra(profile, extraKeys.profilePhotoSrc ?? []) || fallback.profilePhotoSrc || "",
80
89
  email,
81
- linkedin,
90
+ linkedin: social.linkedin,
91
+ social,
82
92
  phone,
93
+ address: profile?.address?.trim() || fallback.address || null,
94
+ googleMapsUrl: profile?.google_maps_url?.trim() || fallback.googleMapsUrl || null,
95
+ googleMapsEmbedUrl: readProfileExtra(profile, extraKeys.googleMapsEmbedUrl ?? []) || fallback.googleMapsEmbedUrl || null,
83
96
  hours: readProfileExtra(profile, extraKeys.hours ?? []) || fallback.hours || null,
84
97
  serviceArea: readProfileExtra(profile, extraKeys.serviceArea ?? []) || fallback.serviceArea || null,
85
98
  privacyUrl: profile?.legal?.privacy_policy_url?.trim() || fallback.privacyUrl || "/privacy",
@@ -94,6 +107,15 @@ async function getResolvedSiteContact() {
94
107
  const profile = await loadSiteProfile();
95
108
  return resolveSiteContact(profile);
96
109
  }
110
+ function listResolvedSocialUrls(social) {
111
+ return [
112
+ social.linkedin,
113
+ social.instagram,
114
+ social.facebook,
115
+ social.x,
116
+ social.youtube
117
+ ].filter((url) => Boolean(url));
118
+ }
97
119
  async function loadBlogPosts() {
98
120
  const c = getJmpContentConfig();
99
121
  if (!c) {
@@ -170,6 +192,28 @@ async function loadLandingPageSitemap() {
170
192
  return [];
171
193
  }
172
194
  }
195
+ async function loadLocations() {
196
+ const c = getJmpContentConfig();
197
+ if (!c) {
198
+ return [];
199
+ }
200
+ try {
201
+ return await getJmpLocations(c);
202
+ } catch {
203
+ return [];
204
+ }
205
+ }
206
+ async function loadLocationDetail(slug) {
207
+ const c = getJmpContentConfig();
208
+ if (!c) {
209
+ return null;
210
+ }
211
+ try {
212
+ return await getJmpLocationDetail(c, slug);
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
173
217
  async function loadTrustBundle() {
174
218
  const c = getJmpContentConfig();
175
219
  if (!c) {
@@ -199,6 +243,7 @@ export {
199
243
  loadSiteProfile,
200
244
  resolveSiteContact,
201
245
  getResolvedSiteContact,
246
+ listResolvedSocialUrls,
202
247
  loadBlogPosts,
203
248
  loadBlogPost,
204
249
  loadFaqs,
@@ -206,7 +251,9 @@ export {
206
251
  loadLandingPages,
207
252
  loadLandingPageDetail,
208
253
  loadLandingPageSitemap,
254
+ loadLocations,
255
+ loadLocationDetail,
209
256
  loadTrustBundle,
210
257
  filterFeaturedTrustQuotes
211
258
  };
212
- //# sourceMappingURL=chunk-PG3IOI5O.js.map
259
+ //# sourceMappingURL=chunk-HKMDWTA2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/content/site-content.ts"],"sourcesContent":["import {\n getJmpBlogPost,\n getJmpBlogPosts,\n getJmpFaqs,\n getJmpLandingPageDetail,\n getJmpLandingPageSitemap,\n getJmpLandingPages,\n getJmpLocations,\n getJmpLocationDetail,\n getJmpSiteProfile,\n getJmpTrustBundle,\n type JmpBlogPost,\n type JmpFaqItem,\n type JmpLandingPage,\n type JmpLandingPageDetail,\n type JmpLandingPageKind,\n type JmpLandingPageSitemapEntry,\n type JmpWebsiteLocation,\n type JmpWebsiteLocationDetail,\n type JmpSiteProfile,\n type JmpTestimonial,\n type JmpTrustBundle,\n} from \"../index\"\nimport {\n getJmpContentConfig,\n getJmpSiteDefinition,\n type JmpSiteSocialLinks,\n} from \"../site-config\"\n\nexport type {\n JmpBlogPost,\n JmpFaqItem,\n JmpLandingPage,\n JmpLandingPageDetail,\n JmpLandingPageKind,\n JmpLandingPageSitemapEntry,\n JmpWebsiteLocation,\n JmpWebsiteLocationDetail,\n JmpSiteProfile,\n JmpTestimonial,\n JmpTrustBundle,\n}\n\nexport { getJmpContentConfig } from \"../site-config\"\n\nexport type { JmpSiteSocialLinks } from \"../site-config\"\n\nexport type ResolvedSiteContact = {\n displayName: string\n email: string\n founderName: string\n dashboardDemoUrl: string\n profilePhotoSrc: string\n linkedin: string | null\n social: JmpSiteSocialLinks\n phone: string | null\n address: string | null\n googleMapsUrl: string | null\n googleMapsEmbedUrl: string | null\n hours: string | null\n serviceArea: string | null\n privacyUrl: string\n termsUrl: string\n cta: { label: string | null; url: string | null }\n}\n\nconst DEFAULT_EXTRA_KEYS = {\n founderName: [\"founder-name\", \"founder_name\"],\n dashboardDemoUrl: [\"dashboard-demo-url\", \"sample-dashboard-url\"],\n profilePhotoSrc: [\"profile-photo-url\", \"profile_photo_url\"],\n hours: [\"hours\", \"business-hours\"],\n serviceArea: [\"service-area\", \"service_area\", \"service-area-text\"],\n googleMapsEmbedUrl: [\"google-maps-embed-url\", \"google_maps_embed_url\"],\n} as const\n\nfunction sortBlogPosts(posts: JmpBlogPost[]): JmpBlogPost[] {\n return [...posts].sort((a, b) => {\n const da = a.published_at ?? a.updated_at\n const db = b.published_at ?? b.updated_at\n return db.localeCompare(da)\n })\n}\n\nfunction sortFaqs(items: JmpFaqItem[]): JmpFaqItem[] {\n return [...items].sort((a, b) => a.sort_order - b.sort_order)\n}\n\nfunction readProfileExtra(\n profile: JmpSiteProfile | null,\n keys: readonly string[],\n): string | null {\n for (const key of keys) {\n const value = profile?.extras?.[key]?.trim()\n if (value) {\n return value\n }\n }\n return null\n}\n\n/** Resolve a path or URL against {@link defineJmpSite}'s `siteUrl`. */\nexport function absoluteSiteAssetUrl(src: string): string {\n const trimmed = src.trim()\n if (trimmed.startsWith(\"http://\") || trimmed.startsWith(\"https://\")) {\n return trimmed\n }\n const siteUrl = getJmpSiteDefinition()?.siteUrl?.replace(/\\/$/, \"\")\n if (!siteUrl) {\n return trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`\n }\n return `${siteUrl}${trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`}`\n}\n\nexport async function loadSiteProfile(): Promise<JmpSiteProfile | null> {\n const config = getJmpContentConfig()\n if (!config) {\n return null\n }\n try {\n return await getJmpSiteProfile(config)\n } catch {\n return null\n }\n}\n\nexport function resolveSiteContact(profile: JmpSiteProfile | null): ResolvedSiteContact {\n const site = getJmpSiteDefinition()\n const fallback = site?.contactFallback ?? {}\n const extraKeys = {\n ...DEFAULT_EXTRA_KEYS,\n ...site?.contactExtraKeys,\n }\n\n const email = profile?.email?.trim() || fallback.email || \"\"\n const social: JmpSiteSocialLinks = {\n linkedin:\n profile?.social?.linkedin?.trim() || fallback.linkedin || null,\n instagram:\n profile?.social?.instagram?.trim() || fallback.instagram || null,\n facebook:\n profile?.social?.facebook?.trim() || fallback.facebook || null,\n x: profile?.social?.x?.trim() || fallback.x || null,\n youtube:\n profile?.social?.youtube?.trim() || fallback.youtube || null,\n }\n const phone = profile?.phone?.trim() || fallback.phone || null\n\n return {\n displayName:\n profile?.display_name?.trim() ||\n profile?.name?.trim() ||\n fallback.displayName ||\n \"\",\n founderName:\n readProfileExtra(profile, extraKeys.founderName ?? []) ||\n fallback.founderName ||\n \"\",\n dashboardDemoUrl:\n readProfileExtra(profile, extraKeys.dashboardDemoUrl ?? []) ||\n fallback.dashboardDemoUrl ||\n \"\",\n profilePhotoSrc:\n readProfileExtra(profile, extraKeys.profilePhotoSrc ?? []) ||\n fallback.profilePhotoSrc ||\n \"\",\n email,\n linkedin: social.linkedin,\n social,\n phone,\n address: profile?.address?.trim() || fallback.address || null,\n googleMapsUrl:\n profile?.google_maps_url?.trim() || fallback.googleMapsUrl || null,\n googleMapsEmbedUrl:\n readProfileExtra(profile, extraKeys.googleMapsEmbedUrl ?? []) ||\n fallback.googleMapsEmbedUrl ||\n null,\n hours:\n readProfileExtra(profile, extraKeys.hours ?? []) || fallback.hours || null,\n serviceArea:\n readProfileExtra(profile, extraKeys.serviceArea ?? []) ||\n fallback.serviceArea ||\n null,\n privacyUrl: profile?.legal?.privacy_policy_url?.trim() || fallback.privacyUrl || \"/privacy\",\n termsUrl: profile?.legal?.terms_url?.trim() || fallback.termsUrl || \"/terms\",\n cta: {\n label: profile?.cta?.label?.trim() || null,\n url: profile?.cta?.url?.trim() || null,\n },\n }\n}\n\nexport async function getResolvedSiteContact(): Promise<ResolvedSiteContact> {\n const profile = await loadSiteProfile()\n return resolveSiteContact(profile)\n}\n\n/** Non-null social profile URLs in a stable order for JSON-LD `sameAs`. */\nexport function listResolvedSocialUrls(social: JmpSiteSocialLinks): string[] {\n return [\n social.linkedin,\n social.instagram,\n social.facebook,\n social.x,\n social.youtube,\n ].filter((url): url is string => Boolean(url))\n}\n\nexport async function loadBlogPosts(): Promise<JmpBlogPost[]> {\n const c = getJmpContentConfig()\n if (!c) {\n return []\n }\n try {\n const posts = await getJmpBlogPosts(c)\n return sortBlogPosts(posts)\n } catch {\n return []\n }\n}\n\nexport async function loadBlogPost(slug: string): Promise<JmpBlogPost | null> {\n const c = getJmpContentConfig()\n if (!c) {\n return null\n }\n try {\n return await getJmpBlogPost(c, slug)\n } catch {\n return null\n }\n}\n\nexport async function loadFaqs(): Promise<JmpFaqItem[]> {\n const c = getJmpContentConfig()\n if (!c) {\n return []\n }\n try {\n const faqs = await getJmpFaqs(c)\n return sortFaqs(faqs)\n } catch {\n return []\n }\n}\n\nexport async function loadFaqBySlug(slug: string): Promise<JmpFaqItem | null> {\n const normalized = slug.trim()\n if (!normalized) {\n return null\n }\n const items = await loadFaqs()\n return (\n items.find((item) => item.slug === normalized || item.id === normalized) ?? null\n )\n}\n\nexport async function loadLandingPages(filters?: {\n kind?: JmpLandingPageKind\n locale?: string\n}): Promise<JmpLandingPage[]> {\n const c = getJmpContentConfig()\n if (!c) {\n return []\n }\n try {\n return await getJmpLandingPages(c, filters)\n } catch {\n return []\n }\n}\n\nexport async function loadLandingPageDetail(\n kind: JmpLandingPageKind,\n slug: string,\n locale?: string\n): Promise<JmpLandingPageDetail | null> {\n const c = getJmpContentConfig()\n if (!c) {\n return null\n }\n try {\n return await getJmpLandingPageDetail(c, kind, slug, locale)\n } catch {\n return null\n }\n}\n\nexport async function loadLandingPageSitemap(): Promise<JmpLandingPageSitemapEntry[]> {\n const c = getJmpContentConfig()\n if (!c) {\n return []\n }\n try {\n return await getJmpLandingPageSitemap(c)\n } catch {\n return []\n }\n}\n\nexport async function loadLocations(): Promise<JmpWebsiteLocation[]> {\n const c = getJmpContentConfig()\n if (!c) {\n return []\n }\n try {\n return await getJmpLocations(c)\n } catch {\n return []\n }\n}\n\nexport async function loadLocationDetail(\n slug: string\n): Promise<JmpWebsiteLocationDetail | null> {\n const c = getJmpContentConfig()\n if (!c) {\n return null\n }\n try {\n return await getJmpLocationDetail(c, slug)\n } catch {\n return null\n }\n}\n\nexport async function loadTrustBundle(): Promise<JmpTrustBundle | null> {\n const c = getJmpContentConfig()\n if (!c) {\n return null\n }\n try {\n return await getJmpTrustBundle(c)\n } catch {\n return null\n }\n}\n\nexport function filterFeaturedTrustQuotes(\n testimonials: JmpTestimonial[],\n placement: \"testimonials\" | \"reviews\",\n limit = 2,\n): JmpTestimonial[] {\n return testimonials\n .filter((item) => {\n if (!item.featured) {\n return false\n }\n const bucket = item.placement?.trim().toLowerCase()\n if (placement === \"reviews\") {\n return bucket === \"reviews\"\n }\n return !bucket || bucket === \"testimonials\"\n })\n .sort((a, b) => a.sort_order - b.sort_order)\n .slice(0, limit)\n}\n"],"mappings":"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","names":[]}
@@ -2,10 +2,10 @@ import {
2
2
  getOrCreateJmpAnalyticsFromBrowserEnv,
3
3
  isJmpTrackingConsentGranted,
4
4
  subscribeJmpTrackingConsent
5
- } from "./chunk-UVYF33XE.js";
5
+ } from "./chunk-NI7R6QZQ.js";
6
6
  import {
7
7
  readJmpPublicEnvFromProcess
8
- } from "./chunk-A4RRTY5D.js";
8
+ } from "./chunk-AIFJ7DVO.js";
9
9
 
10
10
  // src/next.tsx
11
11
  import {
@@ -130,4 +130,4 @@ export {
130
130
  JmpNextAnalytics,
131
131
  JmpSiteRoot
132
132
  };
133
- //# sourceMappingURL=chunk-DNYJF7BG.js.map
133
+ //# sourceMappingURL=chunk-KPTMZQCY.js.map
@@ -1,6 +1,8 @@
1
1
  import {
2
+ extractMailtoTarget,
3
+ extractTelTarget,
2
4
  getOrCreateJmpAnalyticsFromBrowserEnv
3
- } from "./chunk-UVYF33XE.js";
5
+ } from "./chunk-NI7R6QZQ.js";
4
6
 
5
7
  // src/react.tsx
6
8
  import { useMemo } from "react";
@@ -8,13 +10,21 @@ import { jsx } from "react/jsx-runtime";
8
10
  function useJmpAnalytics() {
9
11
  return useMemo(() => getOrCreateJmpAnalyticsFromBrowserEnv(), []);
10
12
  }
11
- function trackCallClickFromCurrentPath() {
13
+ function trackCallClickFromHref(href) {
12
14
  const path = typeof window !== "undefined" ? window.location.pathname : "/";
13
- void getOrCreateJmpAnalyticsFromBrowserEnv()?.trackCallClick(path);
15
+ const contactTarget = extractTelTarget(href);
16
+ void getOrCreateJmpAnalyticsFromBrowserEnv()?.trackCallClick(
17
+ path,
18
+ contactTarget ? { contactTarget } : void 0
19
+ );
14
20
  }
15
- function trackEmailClickFromCurrentPath() {
21
+ function trackEmailClickFromHref(href) {
16
22
  const path = typeof window !== "undefined" ? window.location.pathname : "/";
17
- void getOrCreateJmpAnalyticsFromBrowserEnv()?.trackEmailClick(path);
23
+ const contactTarget = extractMailtoTarget(href);
24
+ void getOrCreateJmpAnalyticsFromBrowserEnv()?.trackEmailClick(
25
+ path,
26
+ contactTarget ? { contactTarget } : void 0
27
+ );
18
28
  }
19
29
  function JmpTrackedTelLink({
20
30
  href,
@@ -28,7 +38,7 @@ function JmpTrackedTelLink({
28
38
  "data-jmp-tracked": "true",
29
39
  ...rest,
30
40
  onClick: (e) => {
31
- trackCallClickFromCurrentPath();
41
+ trackCallClickFromHref(href);
32
42
  onClick?.(e);
33
43
  }
34
44
  }
@@ -46,7 +56,7 @@ function JmpTrackedMailtoLink({
46
56
  "data-jmp-tracked": "true",
47
57
  ...rest,
48
58
  onClick: (e) => {
49
- trackEmailClickFromCurrentPath();
59
+ trackEmailClickFromHref(href);
50
60
  onClick?.(e);
51
61
  }
52
62
  }
@@ -58,4 +68,4 @@ export {
58
68
  JmpTrackedTelLink,
59
69
  JmpTrackedMailtoLink
60
70
  };
61
- //# sourceMappingURL=chunk-FNWE2DT5.js.map
71
+ //# sourceMappingURL=chunk-NDSPSAWQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react.tsx"],"sourcesContent":["\"use client\"\n\nimport { useMemo, type ComponentPropsWithoutRef } from \"react\"\nimport type { JmpAnalytics } from \"./index\"\nimport { extractMailtoTarget, extractTelTarget } from \"./contact-link-classifier\"\nimport { getOrCreateJmpAnalyticsFromBrowserEnv } from \"./index\"\n\nexport function useJmpAnalytics(): JmpAnalytics | null {\n return useMemo(() => getOrCreateJmpAnalyticsFromBrowserEnv(), [])\n}\n\nfunction trackCallClickFromHref(href: string): void {\n const path =\n typeof window !== \"undefined\" ? window.location.pathname : \"/\"\n const contactTarget = extractTelTarget(href)\n void getOrCreateJmpAnalyticsFromBrowserEnv()?.trackCallClick(\n path,\n contactTarget ? { contactTarget } : undefined\n )\n}\n\nfunction trackEmailClickFromHref(href: string): void {\n const path =\n typeof window !== \"undefined\" ? window.location.pathname : \"/\"\n const contactTarget = extractMailtoTarget(href)\n void getOrCreateJmpAnalyticsFromBrowserEnv()?.trackEmailClick(\n path,\n contactTarget ? { contactTarget } : undefined\n )\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 trackCallClickFromHref(href)\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 trackEmailClickFromHref(href)\n onClick?.(e)\n }}\n />\n )\n}\n"],"mappings":";;;;;;;AAEA,SAAS,eAA8C;AA0CnD;AArCG,SAAS,kBAAuC;AACrD,SAAO,QAAQ,MAAM,sCAAsC,GAAG,CAAC,CAAC;AAClE;AAEA,SAAS,uBAAuB,MAAoB;AAClD,QAAM,OACJ,OAAO,WAAW,cAAc,OAAO,SAAS,WAAW;AAC7D,QAAM,gBAAgB,iBAAiB,IAAI;AAC3C,OAAK,sCAAsC,GAAG;AAAA,IAC5C;AAAA,IACA,gBAAgB,EAAE,cAAc,IAAI;AAAA,EACtC;AACF;AAEA,SAAS,wBAAwB,MAAoB;AACnD,QAAM,OACJ,OAAO,WAAW,cAAc,OAAO,SAAS,WAAW;AAC7D,QAAM,gBAAgB,oBAAoB,IAAI;AAC9C,OAAK,sCAAsC,GAAG;AAAA,IAC5C;AAAA,IACA,gBAAgB,EAAE,cAAc,IAAI;AAAA,EACtC;AACF;AASO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA2B;AACzB,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,oBAAiB;AAAA,MAChB,GAAG;AAAA,MACJ,SAAS,CAAC,MAAM;AACd,+BAAuB,IAAI;AAC3B,kBAAU,CAAC;AAAA,MACb;AAAA;AAAA,EACF;AAEJ;AASO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA8B;AAC5B,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,oBAAiB;AAAA,MAChB,GAAG;AAAA,MACJ,SAAS,CAAC,MAAM;AACd,gCAAwB,IAAI;AAC5B,kBAAU,CAAC;AAAA,MACb;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -10,7 +10,7 @@ import {
10
10
  readJmpPublicEnvFromProcess,
11
11
  resolveFetch,
12
12
  responseLooksLikeHtml
13
- } from "./chunk-A4RRTY5D.js";
13
+ } from "./chunk-AIFJ7DVO.js";
14
14
 
15
15
  // src/contact-link-classifier.ts
16
16
  var BOOKING_LINK_HOSTS = [
@@ -130,6 +130,26 @@ function classifyContactHref(href) {
130
130
  }
131
131
  return null;
132
132
  }
133
+ function extractTelTarget(href) {
134
+ const raw = href.trim();
135
+ if (!raw.toLowerCase().startsWith("tel:")) {
136
+ return null;
137
+ }
138
+ const target = decodeURIComponent(
139
+ raw.slice(4).split("?")[0]?.split(";")[0] ?? ""
140
+ ).trim();
141
+ return target || null;
142
+ }
143
+ function extractMailtoTarget(href) {
144
+ const raw = href.trim();
145
+ if (!raw.toLowerCase().startsWith("mailto:")) {
146
+ return null;
147
+ }
148
+ const target = decodeURIComponent(
149
+ raw.slice(7).split("?")[0]?.split(",")[0] ?? ""
150
+ ).trim();
151
+ return target || null;
152
+ }
133
153
 
134
154
  // src/constants.ts
135
155
  var JMP_ANALYTICS_SDK_VERSION = "0.1.26";
@@ -517,14 +537,22 @@ function createJmpAnalytics(config) {
517
537
  if (dedupeContactClick(`call_click:${path}`)) {
518
538
  return;
519
539
  }
520
- void trackCallClick(path);
540
+ const contactTarget = extractTelTarget(href);
541
+ void trackCallClick(
542
+ path,
543
+ contactTarget ? { contactTarget } : void 0
544
+ );
521
545
  return;
522
546
  }
523
547
  if (classified.kind === "email") {
524
548
  if (dedupeContactClick(`email_click:${path}`)) {
525
549
  return;
526
550
  }
527
- void trackEmailClick(path);
551
+ const contactTarget = extractMailtoTarget(href);
552
+ void trackEmailClick(
553
+ path,
554
+ contactTarget ? { contactTarget } : void 0
555
+ );
528
556
  return;
529
557
  }
530
558
  if (dedupeContactClick(`contact_click:${classified.channel}:${path}`)) {
@@ -570,6 +598,209 @@ function createJmpAnalytics(config) {
570
598
  };
571
599
  }
572
600
 
601
+ // src/content/manifest.ts
602
+ var JMP_CONTENT_MANIFEST_VERSION = 1;
603
+ var TRUST_SECTIONS = /* @__PURE__ */ new Set([
604
+ "summary",
605
+ "testimonials",
606
+ "reviews",
607
+ "stats",
608
+ "logos",
609
+ "credentials"
610
+ ]);
611
+ function normalizeKey(value) {
612
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
613
+ }
614
+ function isValidExtraKey(key) {
615
+ return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(key);
616
+ }
617
+ var MAX_EXTERNAL_ADMIN_LINKS = 10;
618
+ var MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH = 80;
619
+ var EXTERNAL_ADMIN_LINK_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
620
+ function tryNormalizeHttpUrl(input) {
621
+ if (typeof input !== "string" || !input.trim()) {
622
+ return null;
623
+ }
624
+ try {
625
+ const url = new URL(input.trim());
626
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
627
+ return null;
628
+ }
629
+ return url.toString().replace(/\/$/, "");
630
+ } catch {
631
+ return null;
632
+ }
633
+ }
634
+ function normalizeShopifyExternalSource(shopify) {
635
+ if (!shopify || typeof shopify !== "object") {
636
+ return null;
637
+ }
638
+ const adminUrl = tryNormalizeHttpUrl(shopify.adminUrl);
639
+ const storeUrl = tryNormalizeHttpUrl(shopify.storeUrl);
640
+ if (!adminUrl && !storeUrl) {
641
+ return null;
642
+ }
643
+ return {
644
+ ...adminUrl ? { adminUrl } : {},
645
+ ...storeUrl ? { storeUrl } : {}
646
+ };
647
+ }
648
+ function normalizeExternalAdminLinkEntries(links) {
649
+ if (!Array.isArray(links)) {
650
+ return [];
651
+ }
652
+ const seenIds = /* @__PURE__ */ new Set();
653
+ const out = [];
654
+ for (const raw of links) {
655
+ if (!raw || typeof raw !== "object" || out.length >= MAX_EXTERNAL_ADMIN_LINKS) {
656
+ continue;
657
+ }
658
+ const id = typeof raw.id === "string" ? raw.id.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") : "";
659
+ const label = typeof raw.label === "string" ? raw.label.trim().slice(0, MAX_EXTERNAL_ADMIN_LINK_LABEL_LENGTH) : "";
660
+ const url = tryNormalizeHttpUrl(raw.url);
661
+ if (!id || !label || !url || !EXTERNAL_ADMIN_LINK_ID_PATTERN.test(id) || seenIds.has(id)) {
662
+ continue;
663
+ }
664
+ seenIds.add(id);
665
+ out.push({ id, label, url });
666
+ }
667
+ return out;
668
+ }
669
+ function normalizeExternalSources(externalSources) {
670
+ if (!externalSources || typeof externalSources !== "object") {
671
+ return null;
672
+ }
673
+ const studioUrl = tryNormalizeHttpUrl(externalSources.sanity?.studioUrl);
674
+ const shopify = normalizeShopifyExternalSource(externalSources.shopify);
675
+ const links = normalizeExternalAdminLinkEntries(externalSources.links);
676
+ if (!studioUrl && !shopify && links.length === 0) {
677
+ return null;
678
+ }
679
+ return {
680
+ ...studioUrl ? { sanity: { studioUrl } } : {},
681
+ ...shopify ? { shopify } : {},
682
+ ...links.length > 0 ? { links } : {}
683
+ };
684
+ }
685
+ function normalizeJmpContentManifest(input) {
686
+ if (!input || typeof input !== "object") {
687
+ return null;
688
+ }
689
+ const routes = normalizeRoutes(input.routes);
690
+ const externalSources = normalizeExternalSources(input.externalSources);
691
+ const trust = normalizeTrust(input.trust);
692
+ const contact = normalizeContact(input.contact);
693
+ if (routes.length === 0 && !externalSources && !trust && !contact) {
694
+ return null;
695
+ }
696
+ return {
697
+ version: JMP_CONTENT_MANIFEST_VERSION,
698
+ ...routes.length > 0 ? { routes } : {},
699
+ ...externalSources ? { externalSources } : {},
700
+ ...trust ? { trust } : {},
701
+ ...contact ? { contact } : {}
702
+ };
703
+ }
704
+ function normalizeRoutes(routes) {
705
+ if (!Array.isArray(routes)) {
706
+ return [];
707
+ }
708
+ const seen = /* @__PURE__ */ new Set();
709
+ const out = [];
710
+ for (const raw of routes) {
711
+ if (!raw || typeof raw !== "object") continue;
712
+ const wrapper = typeof raw.wrapper === "string" ? raw.wrapper.trim().toLowerCase() : "";
713
+ const pattern = typeof raw.pattern === "string" ? raw.pattern.trim() : "";
714
+ if (!wrapper || !pattern) continue;
715
+ const dedupe = `${wrapper}:${pattern}`;
716
+ if (seen.has(dedupe)) continue;
717
+ seen.add(dedupe);
718
+ out.push({
719
+ wrapper,
720
+ pattern,
721
+ ...typeof raw.index === "string" && raw.index.trim() ? { index: raw.index.trim() } : {},
722
+ ...typeof raw.label === "string" && raw.label.trim() ? { label: raw.label.trim() } : {}
723
+ });
724
+ }
725
+ return out;
726
+ }
727
+ function normalizeTrust(trust) {
728
+ if (!trust || typeof trust !== "object") return null;
729
+ const blocks = normalizeTrustBlocks(trust.blocks);
730
+ const labels = normalizeTrustLabels(trust.labels);
731
+ const mergeTestimonialsReviews = trust.mergeTestimonialsReviews === true;
732
+ if (blocks.length === 0 && !labels && !mergeTestimonialsReviews) return null;
733
+ return {
734
+ ...blocks.length > 0 ? { blocks } : {},
735
+ ...labels ? { labels } : {},
736
+ ...mergeTestimonialsReviews ? { mergeTestimonialsReviews: true } : {}
737
+ };
738
+ }
739
+ function normalizeTrustBlocks(blocks) {
740
+ if (!Array.isArray(blocks)) return [];
741
+ const seen = /* @__PURE__ */ new Set();
742
+ const out = [];
743
+ for (const raw of blocks) {
744
+ if (!raw || typeof raw !== "object") continue;
745
+ const section = typeof raw.section === "string" ? raw.section.trim().toLowerCase() : "";
746
+ if (!TRUST_SECTIONS.has(section) || seen.has(section)) continue;
747
+ seen.add(section);
748
+ out.push({
749
+ section,
750
+ ...typeof raw.label === "string" && raw.label.trim() ? { label: raw.label.trim() } : {}
751
+ });
752
+ }
753
+ return out;
754
+ }
755
+ function normalizeTrustLabels(labels) {
756
+ if (!labels || typeof labels !== "object") return null;
757
+ const out = {};
758
+ for (const [key, value] of Object.entries(labels)) {
759
+ if (TRUST_SECTIONS.has(key) && typeof value === "string" && value.trim()) {
760
+ out[key] = value.trim();
761
+ }
762
+ }
763
+ return Object.keys(out).length > 0 ? out : null;
764
+ }
765
+ function normalizeContact(contact) {
766
+ if (!contact || typeof contact !== "object") return null;
767
+ const extras = normalizeContactExtras(contact.extras);
768
+ return extras.length > 0 ? { extras } : null;
769
+ }
770
+ function normalizeContactExtras(extras) {
771
+ if (!Array.isArray(extras)) return [];
772
+ const seen = /* @__PURE__ */ new Set();
773
+ const out = [];
774
+ for (const raw of extras) {
775
+ if (!raw || typeof raw !== "object") continue;
776
+ const label = typeof raw.label === "string" ? raw.label.trim() : "";
777
+ const key = normalizeKey(
778
+ typeof raw.key === "string" && raw.key.trim() ? raw.key : label
779
+ );
780
+ if (!key || !label || !isValidExtraKey(key) || seen.has(key)) continue;
781
+ seen.add(key);
782
+ out.push({ key, label });
783
+ }
784
+ return out;
785
+ }
786
+ function stableJmpManifestFingerprint(manifest) {
787
+ return manifest ? JSON.stringify(manifest) : "";
788
+ }
789
+ function inferWrapperSlotsFromManifest(manifest) {
790
+ const slots = /* @__PURE__ */ new Set();
791
+ for (const route of manifest?.routes ?? []) {
792
+ slots.add(route.wrapper);
793
+ }
794
+ for (const block of manifest?.trust?.blocks ?? []) {
795
+ slots.add(`trust.${block.section}`);
796
+ slots.add("trust");
797
+ }
798
+ if (manifest?.contact?.extras?.length) {
799
+ slots.add("contact");
800
+ }
801
+ return [...slots];
802
+ }
803
+
573
804
  // src/content/fetch.ts
574
805
  async function getContent(config, path) {
575
806
  const text = await getContentText(config, path);
@@ -684,16 +915,22 @@ function inferTrustWrapperSlots(bundle) {
684
915
  function getDiscoveredContentWrappers() {
685
916
  return sortWrapperSlots([...discovered]);
686
917
  }
918
+ function resolveContentManifestForReport(config) {
919
+ return normalizeJmpContentManifest(config.contentManifest);
920
+ }
687
921
  function resolveContentWrappersForReport(config) {
688
922
  const explicit = config.contentWrappers ?? [];
689
923
  const auto = getDiscoveredContentWrappers();
924
+ const fromManifest = inferWrapperSlotsFromManifest(
925
+ resolveContentManifestForReport(config)
926
+ );
690
927
  if (config.contentWrapperMode === "manual") {
691
- return sortWrapperSlots([...explicit]);
928
+ return sortWrapperSlots([...explicit, ...fromManifest]);
692
929
  }
693
930
  if (config.contentWrapperMode === "explicit-only") {
694
- return sortWrapperSlots([...explicit]);
931
+ return sortWrapperSlots([...explicit, ...fromManifest]);
695
932
  }
696
- const merged = /* @__PURE__ */ new Set([...explicit, ...auto]);
933
+ const merged = /* @__PURE__ */ new Set([...explicit, ...auto, ...fromManifest]);
697
934
  return sortWrapperSlots([...merged]);
698
935
  }
699
936
  function hasReportedContentWrappers(dedupeKey) {
@@ -831,6 +1068,35 @@ async function getJmpLandingPageSitemap(config) {
831
1068
  afterContentWrapperUse(config);
832
1069
  return data.entries;
833
1070
  }
1071
+ async function getJmpLocations(config) {
1072
+ const data = await getContent(
1073
+ config,
1074
+ "/api/content/locations"
1075
+ );
1076
+ markContentWrapper("locations");
1077
+ afterContentWrapperUse(config);
1078
+ return data.locations;
1079
+ }
1080
+ async function getJmpLocationDetail(config, slug) {
1081
+ const normalized = slug.trim();
1082
+ if (!normalized) {
1083
+ return null;
1084
+ }
1085
+ try {
1086
+ const detail = await getContent(
1087
+ config,
1088
+ `/api/content/locations/${encodeURIComponent(normalized)}`
1089
+ );
1090
+ markContentWrapper("locations");
1091
+ afterContentWrapperUse(config);
1092
+ return detail;
1093
+ } catch (error) {
1094
+ if (error instanceof Error && error.message.includes("HTTP 404")) {
1095
+ return null;
1096
+ }
1097
+ throw error;
1098
+ }
1099
+ }
834
1100
  async function getJmpTrustBundle(config) {
835
1101
  const bundle = await getContent(config, "/api/content/trust");
836
1102
  markTrustBundleWrappers(bundle);
@@ -854,7 +1120,8 @@ async function registerJmpContentWrappers(config) {
854
1120
  if (!wrappers.length) {
855
1121
  return { ok: false, skipped: true };
856
1122
  }
857
- const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(",")}`;
1123
+ const manifest = resolveContentManifestForReport(config);
1124
+ const dedupeKey = `${config.trackingKey}:${[...wrappers].sort().join(",")}:${stableJmpManifestFingerprint(manifest)}`;
858
1125
  if (hasReportedContentWrappers(dedupeKey)) {
859
1126
  return { ok: true, wrappers };
860
1127
  }
@@ -867,7 +1134,17 @@ async function registerJmpContentWrappers(config) {
867
1134
  }
868
1135
  const url = `${baseUrl}/api/content/capabilities`;
869
1136
  if (config.debug) {
870
- console.debug("[@jmp-technologies/analytics]", "POST", url, { wrappers });
1137
+ console.debug("[@jmp-technologies/analytics]", "POST", url, {
1138
+ wrappers,
1139
+ manifest
1140
+ });
1141
+ }
1142
+ const body = {
1143
+ wrappers,
1144
+ sdkVersion: JMP_ANALYTICS_SDK_VERSION
1145
+ };
1146
+ if (manifest) {
1147
+ body.manifest = manifest;
871
1148
  }
872
1149
  const res = await resolvedFetch(url, {
873
1150
  method: "POST",
@@ -875,10 +1152,7 @@ async function registerJmpContentWrappers(config) {
875
1152
  "Content-Type": "application/json",
876
1153
  "X-JMP-Tracking-Key": config.trackingKey
877
1154
  },
878
- body: JSON.stringify({
879
- wrappers,
880
- sdkVersion: JMP_ANALYTICS_SDK_VERSION
881
- })
1155
+ body: JSON.stringify(body)
882
1156
  });
883
1157
  const text = await res.text();
884
1158
  if (!res.ok) {
@@ -962,6 +1236,8 @@ function createJmpContentClientFromServerEnv(overrides) {
962
1236
  export {
963
1237
  BOOKING_LINK_HOSTS,
964
1238
  classifyContactHref,
1239
+ extractTelTarget,
1240
+ extractMailtoTarget,
965
1241
  JMP_ANALYTICS_SDK_VERSION,
966
1242
  JMP_TRACKING_CONSENT_STORAGE_KEY,
967
1243
  DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
@@ -969,6 +1245,8 @@ export {
969
1245
  subscribeJmpTrackingConsent,
970
1246
  setJmpTrackingConsent,
971
1247
  createJmpAnalytics,
1248
+ normalizeJmpContentManifest,
1249
+ stableJmpManifestFingerprint,
972
1250
  markContentWrapper,
973
1251
  inferTrustWrapperSlots,
974
1252
  getDiscoveredContentWrappers,
@@ -979,6 +1257,8 @@ export {
979
1257
  getJmpLandingPages,
980
1258
  getJmpLandingPageDetail,
981
1259
  getJmpLandingPageSitemap,
1260
+ getJmpLocations,
1261
+ getJmpLocationDetail,
982
1262
  getJmpTrustBundle,
983
1263
  getJmpSiteProfile,
984
1264
  getJmpRobotsTxt,
@@ -991,4 +1271,4 @@ export {
991
1271
  getOrCreateJmpAnalyticsFromBrowserEnv,
992
1272
  createJmpContentClientFromServerEnv
993
1273
  };
994
- //# sourceMappingURL=chunk-UVYF33XE.js.map
1274
+ //# sourceMappingURL=chunk-NI7R6QZQ.js.map