@jmp-technologies/analytics 0.1.36 → 0.1.38

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 (97) hide show
  1. package/README.md +1 -1
  2. package/dist/astro/client.cjs +83 -42
  3. package/dist/astro/client.cjs.map +1 -1
  4. package/dist/astro/client.d.cts +2 -2
  5. package/dist/astro/client.d.ts +2 -2
  6. package/dist/astro/client.js +2 -2
  7. package/dist/astro/content.cjs +137 -70
  8. package/dist/astro/content.cjs.map +1 -1
  9. package/dist/astro/content.d.cts +5 -4
  10. package/dist/astro/content.d.ts +5 -4
  11. package/dist/astro/content.js +5 -3
  12. package/dist/astro/integration.cjs +104 -66
  13. package/dist/astro/integration.cjs.map +1 -1
  14. package/dist/astro/integration.js +3 -3
  15. package/dist/astro/middleware.cjs +96 -66
  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 +104 -66
  21. package/dist/astro/register.cjs.map +1 -1
  22. package/dist/astro/register.js +3 -3
  23. package/dist/{chunk-AIFJ7DVO.js → chunk-6WQMWPEY.js} +196 -105
  24. package/dist/chunk-6WQMWPEY.js.map +1 -0
  25. package/dist/{chunk-YFG3AQ35.js → chunk-EBU4DKHW.js} +47 -9
  26. package/dist/chunk-EBU4DKHW.js.map +1 -0
  27. package/dist/{chunk-AGGLRTUQ.js → chunk-JVFN7MF2.js} +2 -2
  28. package/dist/{chunk-3ZEMXII6.js → chunk-NCF3J4HV.js} +3 -3
  29. package/dist/{chunk-HFLU7HQE.js → chunk-ONIPLMOY.js} +2 -2
  30. package/dist/{chunk-NAQU6RAE.js → chunk-PQOTEV6O.js} +3 -3
  31. package/dist/{chunk-RNZTKIC6.js → chunk-WHUHCTBQ.js} +16 -3
  32. package/dist/chunk-WHUHCTBQ.js.map +1 -0
  33. package/dist/{create-jmp-analytics-Bz0-K0Af.d.cts → create-jmp-analytics-DMSXmVIH.d.cts} +1 -1
  34. package/dist/{create-jmp-analytics-DRjrlWUn.d.ts → create-jmp-analytics-JZ3pqNBQ.d.ts} +1 -1
  35. package/dist/index.cjs +145 -76
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +10 -6
  38. package/dist/index.d.ts +10 -6
  39. package/dist/index.js +6 -2
  40. package/dist/next/content.cjs +138 -70
  41. package/dist/next/content.cjs.map +1 -1
  42. package/dist/next/content.d.cts +5 -4
  43. package/dist/next/content.d.ts +5 -4
  44. package/dist/next/content.js +6 -3
  45. package/dist/next/content.js.map +1 -1
  46. package/dist/next/instrumentation.cjs +104 -66
  47. package/dist/next/instrumentation.cjs.map +1 -1
  48. package/dist/next/instrumentation.js +2 -2
  49. package/dist/next/landing-pages.cjs +155 -129
  50. package/dist/next/landing-pages.cjs.map +1 -1
  51. package/dist/next/landing-pages.d.cts +2 -2
  52. package/dist/next/landing-pages.d.ts +2 -2
  53. package/dist/next/landing-pages.js +7 -8
  54. package/dist/next/landing-pages.js.map +1 -1
  55. package/dist/next/locations.cjs +114 -88
  56. package/dist/next/locations.cjs.map +1 -1
  57. package/dist/next/locations.d.cts +2 -2
  58. package/dist/next/locations.d.ts +2 -2
  59. package/dist/next/locations.js +7 -8
  60. package/dist/next/locations.js.map +1 -1
  61. package/dist/next/middleware.cjs +96 -66
  62. package/dist/next/middleware.cjs.map +1 -1
  63. package/dist/next/middleware.d.cts +2 -2
  64. package/dist/next/middleware.d.ts +2 -2
  65. package/dist/next/middleware.js +2 -2
  66. package/dist/next/site-root-server.cjs +103 -69
  67. package/dist/next/site-root-server.cjs.map +1 -1
  68. package/dist/next/site-root-server.js +4 -4
  69. package/dist/next.cjs +37 -2
  70. package/dist/next.cjs.map +1 -1
  71. package/dist/next.d.cts +3 -3
  72. package/dist/next.d.ts +3 -3
  73. package/dist/next.js +4 -4
  74. package/dist/react.cjs +88 -53
  75. package/dist/react.cjs.map +1 -1
  76. package/dist/react.d.cts +2 -2
  77. package/dist/react.d.ts +2 -2
  78. package/dist/react.js +3 -3
  79. package/dist/{site-config-Cgq67uQ1.d.cts → site-config-BLnwuRC2.d.ts} +9 -3
  80. package/dist/{site-config-BxEmJxBB.d.ts → site-config-BZJS8Hp_.d.cts} +9 -3
  81. package/dist/{track-page-view-BCX1Dsfk.d.cts → track-page-view-BJQqn2Bg.d.cts} +1 -1
  82. package/dist/{track-page-view-74Su6lQY.d.ts → track-page-view-DGKRUIxm.d.ts} +1 -1
  83. package/dist/{types-yNgoakwk.d.ts → types-CYrgEwUF.d.cts} +16 -2
  84. package/dist/{types-yNgoakwk.d.cts → types-CYrgEwUF.d.ts} +16 -2
  85. package/dist/{utils-BLoWWfSw.d.ts → utils-B1Nt3sBU.d.ts} +1 -1
  86. package/dist/{utils-UPTIdK1D.d.cts → utils-U6TGMIAB.d.cts} +1 -1
  87. package/docs/legal-and-privacy.md +2 -2
  88. package/package.json +1 -1
  89. package/dist/chunk-AIFJ7DVO.js.map +0 -1
  90. package/dist/chunk-RI3KKDPX.js +0 -75
  91. package/dist/chunk-RI3KKDPX.js.map +0 -1
  92. package/dist/chunk-RNZTKIC6.js.map +0 -1
  93. package/dist/chunk-YFG3AQ35.js.map +0 -1
  94. /package/dist/{chunk-AGGLRTUQ.js.map → chunk-JVFN7MF2.js.map} +0 -0
  95. /package/dist/{chunk-3ZEMXII6.js.map → chunk-NCF3J4HV.js.map} +0 -0
  96. /package/dist/{chunk-HFLU7HQE.js.map → chunk-ONIPLMOY.js.map} +0 -0
  97. /package/dist/{chunk-NAQU6RAE.js.map → chunk-PQOTEV6O.js.map} +0 -0
@@ -55,6 +55,100 @@ function pathFromRequestUrl(url) {
55
55
  }
56
56
  }
57
57
 
58
+ // src/env-public.ts
59
+ function firstDefined(...values) {
60
+ for (const v of values) {
61
+ if (v) return v;
62
+ }
63
+ return void 0;
64
+ }
65
+ function readJmpPublicEnvFromProcessEnv() {
66
+ if (typeof process === "undefined" || !process.env) {
67
+ return {};
68
+ }
69
+ const { env } = process;
70
+ const analyticsUrl = firstDefined(
71
+ env.NEXT_PUBLIC_JMP_ANALYTICS_URL,
72
+ env.PUBLIC_JMP_ANALYTICS_URL
73
+ );
74
+ const trackingKey = firstDefined(
75
+ env.NEXT_PUBLIC_JMP_TRACKING_KEY,
76
+ env.PUBLIC_JMP_TRACKING_KEY
77
+ );
78
+ const requireConsentRaw = firstDefined(
79
+ env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,
80
+ env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT
81
+ );
82
+ const autoContactRaw = firstDefined(
83
+ env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,
84
+ env.PUBLIC_JMP_AUTO_CONTACT_LINKS
85
+ );
86
+ return {
87
+ analyticsUrl,
88
+ trackingKey,
89
+ requireConsent: requireConsentRaw === "true",
90
+ autoContactLinks: autoContactRaw === "true"
91
+ };
92
+ }
93
+ function readJmpPublicEnvFromProcess(overrides) {
94
+ const fromEnv = readJmpPublicEnvFromProcessEnv();
95
+ return {
96
+ analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),
97
+ trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),
98
+ requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,
99
+ autoContactLinks: overrides?.autoContactLinks ?? fromEnv.autoContactLinks
100
+ };
101
+ }
102
+
103
+ // src/site-config.ts
104
+ var registeredSite = null;
105
+ function getJmpSiteDefinition() {
106
+ return registeredSite;
107
+ }
108
+ function getJmpContentConfig() {
109
+ const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess();
110
+ if (!analyticsUrl || !trackingKey) {
111
+ return null;
112
+ }
113
+ const site = getJmpSiteDefinition();
114
+ const contentWrappers = site?.contentWrappers ?? void 0;
115
+ const inferredMode = contentWrappers && contentWrappers.length > 0 ? "manual" : void 0;
116
+ return {
117
+ baseUrl: analyticsUrl,
118
+ trackingKey,
119
+ contentWrappers,
120
+ contentManifest: site?.contentManifest ?? void 0,
121
+ contentWrapperMode: site?.contentWrapperMode ?? inferredMode
122
+ };
123
+ }
124
+
125
+ // src/landing-pages/utils.ts
126
+ function resolveJmpSiteUrl(override) {
127
+ const fromArg = override?.trim().replace(/\/$/, "");
128
+ if (fromArg) return fromArg;
129
+ const fromConfig = getJmpSiteDefinition()?.siteUrl?.trim().replace(/\/$/, "");
130
+ return fromConfig || null;
131
+ }
132
+
133
+ // src/request-origin.ts
134
+ function resolveJmpRequestOrigin() {
135
+ const fromSite = resolveJmpSiteUrl();
136
+ if (fromSite) {
137
+ return fromSite;
138
+ }
139
+ if (typeof process === "undefined") {
140
+ return null;
141
+ }
142
+ return process.env.NEXT_PUBLIC_SITE_URL?.trim().replace(/\/$/, "") || null;
143
+ }
144
+ function withJmpOriginHeader(headers) {
145
+ const origin = resolveJmpRequestOrigin();
146
+ if (!origin) {
147
+ return headers;
148
+ }
149
+ return { ...headers, Origin: origin };
150
+ }
151
+
58
152
  // src/server/page-view.ts
59
153
  async function trackJmpServerPageView(config, options = {}) {
60
154
  const baseUrl = normalizeBaseUrl(config.baseUrl);
@@ -65,10 +159,10 @@ async function trackJmpServerPageView(config, options = {}) {
65
159
  const visitorUserAgent = options.userAgent ?? getHeader(options.request?.headers, "user-agent");
66
160
  const visitorReferer = options.referer ?? getHeader(options.request?.headers, "referer");
67
161
  const path = options.path ?? pathFromRequestUrl(options.request?.url);
68
- const headers = {
162
+ const headers = withJmpOriginHeader({
69
163
  "Content-Type": "application/json",
70
164
  "X-JMP-Tracking-Key": config.trackingKey
71
- };
165
+ });
72
166
  if (visitorUserAgent) {
73
167
  headers["X-JMP-Visitor-User-Agent"] = visitorUserAgent;
74
168
  }
@@ -134,70 +228,6 @@ function shouldSkipServerPageViewRequest(request) {
134
228
  return false;
135
229
  }
136
230
 
137
- // src/env-public.ts
138
- function firstDefined(...values) {
139
- for (const v of values) {
140
- if (v) return v;
141
- }
142
- return void 0;
143
- }
144
- function readJmpPublicEnvFromProcessEnv() {
145
- if (typeof process === "undefined" || !process.env) {
146
- return {};
147
- }
148
- const { env } = process;
149
- const analyticsUrl = firstDefined(
150
- env.NEXT_PUBLIC_JMP_ANALYTICS_URL,
151
- env.PUBLIC_JMP_ANALYTICS_URL
152
- );
153
- const trackingKey = firstDefined(
154
- env.NEXT_PUBLIC_JMP_TRACKING_KEY,
155
- env.PUBLIC_JMP_TRACKING_KEY
156
- );
157
- const requireConsentRaw = firstDefined(
158
- env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,
159
- env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT
160
- );
161
- const autoContactRaw = firstDefined(
162
- env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,
163
- env.PUBLIC_JMP_AUTO_CONTACT_LINKS
164
- );
165
- return {
166
- analyticsUrl,
167
- trackingKey,
168
- requireConsent: requireConsentRaw === "true",
169
- autoContactLinks: autoContactRaw === "true"
170
- };
171
- }
172
- function readJmpPublicEnvFromProcess(overrides) {
173
- const fromEnv = readJmpPublicEnvFromProcessEnv();
174
- return {
175
- analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),
176
- trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),
177
- requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,
178
- autoContactLinks: overrides?.autoContactLinks ?? fromEnv.autoContactLinks
179
- };
180
- }
181
-
182
- // src/site-config.ts
183
- var registeredSite = null;
184
- function getJmpSiteDefinition() {
185
- return registeredSite;
186
- }
187
- function getJmpContentConfig() {
188
- const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess();
189
- if (!analyticsUrl || !trackingKey) {
190
- return null;
191
- }
192
- const site = getJmpSiteDefinition();
193
- return {
194
- baseUrl: analyticsUrl,
195
- trackingKey,
196
- contentWrappers: site?.contentWrappers ?? void 0,
197
- contentManifest: site?.contentManifest ?? void 0
198
- };
199
- }
200
-
201
231
  // src/server/track-page-view.ts
202
232
  async function jmpTrackServerPageView(request) {
203
233
  if (shouldSkipServerPageViewRequest(request)) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/astro/middleware.ts","../../src/http.ts","../../src/server/page-view.ts","../../src/server/should-track-server-page-view.ts","../../src/env-public.ts","../../src/site-config.ts","../../src/server/track-page-view.ts"],"sourcesContent":["import { jmpTrackServerPageView } from \"../server/track-page-view\"\n\nexport { jmpTrackServerPageView } from \"../server/track-page-view\"\n\n/**\n * Astro middleware handler — use in `src/middleware.ts`:\n *\n * ```ts\n * import \"./jmp.config\"\n * import { onRequest } from \"@jmp-technologies/analytics/astro/middleware\"\n * export { onRequest }\n * ```\n *\n * Or wrap with `defineMiddleware` from `astro:middleware` if you need custom logic.\n */\nexport async function onRequest(\n context: { request: Request },\n next: () => Promise<Response>,\n): Promise<Response> {\n await jmpTrackServerPageView(context.request)\n return next()\n}\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 {\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","import { getHeader } from \"../http\"\nimport type { JmpServerRequestLike } from \"../types\"\n\n/**\n * Returns true when middleware should not record a server page view.\n * Skips Next.js RSC flights, router prefetch, and non-document fetches so\n * {@link jmpTrackServerPageView} does not inflate counts alongside `JmpSiteRoot`.\n */\nexport function shouldSkipServerPageViewRequest(\n request: JmpServerRequestLike | undefined,\n): boolean {\n if (!request) {\n return false\n }\n\n const url = request.url\n if (url) {\n try {\n const parsed = new URL(url, \"https://jmp.local\")\n if (parsed.searchParams.has(\"_rsc\")) {\n return true\n }\n } catch {\n if (url.includes(\"_rsc=\")) {\n return true\n }\n }\n }\n\n const headers = request.headers\n\n if (\n getHeader(headers, \"next-router-prefetch\") === \"1\" ||\n getHeader(headers, \"purpose\") === \"prefetch\" ||\n getHeader(headers, \"x-middleware-prefetch\") === \"1\"\n ) {\n return true\n }\n\n const rsc = getHeader(headers, \"rsc\")\n if (rsc === \"1\") {\n return true\n }\n\n if (getHeader(headers, \"next-router-state-tree\")) {\n return true\n }\n\n const secDest = getHeader(headers, \"sec-fetch-dest\")\n if (secDest && secDest !== \"document\") {\n return true\n }\n\n return false\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 { JmpContentManifest } from \"./content/manifest\"\nimport type { JmpContentConfig, JmpContentWrapper } from \"./types\"\nimport { readJmpPublicEnvFromProcess } from \"./env-public\"\n\nexport type JmpSiteSocialLinks = {\n linkedin: string | null\n instagram: string | null\n facebook: string | null\n x: string | null\n youtube: string | null\n}\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 instagram?: string | null\n facebook?: string | null\n x?: string | null\n youtube?: string | null\n hours?: string | null\n serviceArea?: string | null\n address?: string | null\n googleMapsUrl?: string | null\n googleMapsEmbedUrl?: 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 * Routes, trust block order, and contact extras for dashboard layout sync.\n * Reported with capabilities when the site loads content from the portal.\n */\n contentManifest?: JmpContentManifest\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 googleMapsEmbedUrl?: 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 contentManifest: site?.contentManifest ?? undefined,\n }\n}\n","import { trackJmpServerPageView } from \"./page-view\"\nimport { shouldSkipServerPageViewRequest } from \"./should-track-server-page-view\"\nimport type { JmpServerRequestLike } from \"../types\"\nimport { getJmpContentConfig } from \"../site-config\"\n\n/**\n * Records a server-side page view when JMP env is configured.\n * Framework-agnostic — pass any `Request`-like object (Next middleware, Astro `context.request`).\n *\n * Skips Next.js RSC/prefetch requests. When using {@link JmpSiteRoot} for browser\n * page views, do not call this from middleware — it duplicates counts and omits `sessionId`.\n */\nexport async function jmpTrackServerPageView(\n request: JmpServerRequestLike,\n): Promise<void> {\n if (shouldSkipServerPageViewRequest(request)) {\n return\n }\n const config = getJmpContentConfig()\n if (!config) {\n return\n }\n await trackJmpServerPageView(config, { request }).catch(() => {})\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/astro/middleware.ts","../../src/http.ts","../../src/env-public.ts","../../src/site-config.ts","../../src/landing-pages/utils.ts","../../src/request-origin.ts","../../src/server/page-view.ts","../../src/server/should-track-server-page-view.ts","../../src/server/track-page-view.ts"],"sourcesContent":["import { jmpTrackServerPageView } from \"../server/track-page-view\"\n\nexport { jmpTrackServerPageView } from \"../server/track-page-view\"\n\n/**\n * Astro middleware handler — use in `src/middleware.ts`:\n *\n * ```ts\n * import \"./jmp.config\"\n * import { onRequest } from \"@jmp-technologies/analytics/astro/middleware\"\n * export { onRequest }\n * ```\n *\n * Or wrap with `defineMiddleware` from `astro:middleware` if you need custom logic.\n */\nexport async function onRequest(\n context: { request: Request },\n next: () => Promise<Response>,\n): Promise<Response> {\n await jmpTrackServerPageView(context.request)\n return next()\n}\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","/**\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 { JmpContentManifest } from \"./content/manifest\"\nimport type { JmpContentConfig, JmpContentWrapper } from \"./types\"\nimport { readJmpPublicEnvFromProcess } from \"./env-public\"\n\nexport type JmpSiteSocialLinks = {\n linkedin: string | null\n instagram: string | null\n facebook: string | null\n x: string | null\n youtube: string | null\n}\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 instagram?: string | null\n facebook?: string | null\n x?: string | null\n youtube?: string | null\n hours?: string | null\n serviceArea?: string | null\n address?: string | null\n googleMapsUrl?: string | null\n googleMapsEmbedUrl?: string | null\n dashboardDemoUrl?: string\n profilePhotoSrc?: string\n privacyUrl?: string\n termsUrl?: string\n}\n\nexport type JmpSiteDefinition = {\n /**\n * Content areas this site actually renders. When set, capabilities report\n * this list (manual mode) so unused dashboard editors stay hidden.\n * List granular Trust slots (`trust.summary`, `trust.reviews`) instead of\n * a bare `trust` wrapper.\n */\n contentWrappers?: JmpContentWrapper[]\n /**\n * Override report mode. Defaults to `manual` when `contentWrappers` is set.\n */\n contentWrapperMode?: JmpContentConfig[\"contentWrapperMode\"]\n /**\n * Routes, trust block order, and contact extras for dashboard layout sync.\n * Reported with capabilities when the site loads content from the portal.\n */\n contentManifest?: JmpContentManifest\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 googleMapsEmbedUrl?: 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 const contentWrappers = site?.contentWrappers ?? undefined\n const inferredMode =\n contentWrappers && contentWrappers.length > 0 ? \"manual\" : undefined\n return {\n baseUrl: analyticsUrl,\n trackingKey,\n contentWrappers,\n contentManifest: site?.contentManifest ?? undefined,\n contentWrapperMode: site?.contentWrapperMode ?? inferredMode,\n }\n}\n","import { getJmpSiteDefinition } from \"../site-config\"\nimport type { JmpLandingPage, JmpLandingPageKind } from \"../types\"\nimport { jmpLandingKindSegment } from \"./hub\"\n\n/** Strip lightweight markdown for meta descriptions and schema text fields. */\nexport function plainTextFromMarkdown(input: string, maxLength = 5000): string {\n return input.replace(/[#*_`[\\]]/g, \"\").trim().slice(0, maxLength)\n}\n\nexport function resolveJmpSiteUrl(override?: string): string | null {\n const fromArg = override?.trim().replace(/\\/$/, \"\")\n if (fromArg) return fromArg\n const fromConfig = getJmpSiteDefinition()?.siteUrl?.trim().replace(/\\/$/, \"\")\n return fromConfig || null\n}\n\nexport function absoluteJmpSiteUrl(path: string, siteUrl?: string): string {\n const base = resolveJmpSiteUrl(siteUrl)\n if (!base) {\n return path.startsWith(\"/\") ? path : `/${path}`\n }\n const normalized = path.startsWith(\"/\") ? path : `/${path}`\n return `${base}${normalized}`\n}\n\nexport function filterJmpLandingPagesForDefaultLocale(\n pages: JmpLandingPage[],\n kind: JmpLandingPageKind\n): JmpLandingPage[] {\n const segment = jmpLandingKindSegment(kind)\n return pages.filter((page) => {\n const parts = page.path.split(\"/\").filter(Boolean)\n return parts[0] === segment\n })\n}\n\n/** Pages whose path is `/{locale}/{areas|services}/{slug}` (three+ segments). */\nexport function filterJmpLandingPagesForLocalizedRoutes(\n pages: JmpLandingPage[],\n kind: JmpLandingPageKind\n): JmpLandingPage[] {\n const segment = jmpLandingKindSegment(kind)\n return pages.filter((page) => {\n const parts = page.path.split(\"/\").filter(Boolean)\n return parts.length >= 3 && parts[1] === segment\n })\n}\n\nexport function jmpLocalizedLandingStaticParams(\n pages: JmpLandingPage[],\n kind: JmpLandingPageKind\n): { locale: string; slug: string }[] {\n return filterJmpLandingPagesForLocalizedRoutes(pages, kind).map((page) => {\n const [locale, , slug] = page.path.split(\"/\").filter(Boolean)\n return { locale: locale!, slug: slug! }\n })\n}\n","import { resolveJmpSiteUrl } from \"./landing-pages/utils\"\n\n/**\n * Site origin for server-to-server JMP API calls (content + ingest).\n * Prefer `defineJmpSite({ siteUrl })`, then `NEXT_PUBLIC_SITE_URL`.\n */\nexport function resolveJmpRequestOrigin(): string | null {\n const fromSite = resolveJmpSiteUrl()\n if (fromSite) {\n return fromSite\n }\n if (typeof process === \"undefined\") {\n return null\n }\n return process.env.NEXT_PUBLIC_SITE_URL?.trim().replace(/\\/$/, \"\") || null\n}\n\n/** Adds `Origin` when a site URL is known (required for production write/ingest checks). */\nexport function withJmpOriginHeader(\n headers: Record<string, string>\n): Record<string, string> {\n const origin = resolveJmpRequestOrigin()\n if (!origin) {\n return headers\n }\n return { ...headers, Origin: origin }\n}\n","import {\n getHeader,\n normalizeBaseUrl,\n pathFromRequestUrl,\n} from \"../http\"\nimport { withJmpOriginHeader } from \"../request-origin\"\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> = withJmpOriginHeader({\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","import { getHeader } from \"../http\"\nimport type { JmpServerRequestLike } from \"../types\"\n\n/**\n * Returns true when middleware should not record a server page view.\n * Skips Next.js RSC flights, router prefetch, and non-document fetches so\n * {@link jmpTrackServerPageView} does not inflate counts alongside `JmpSiteRoot`.\n */\nexport function shouldSkipServerPageViewRequest(\n request: JmpServerRequestLike | undefined,\n): boolean {\n if (!request) {\n return false\n }\n\n const url = request.url\n if (url) {\n try {\n const parsed = new URL(url, \"https://jmp.local\")\n if (parsed.searchParams.has(\"_rsc\")) {\n return true\n }\n } catch {\n if (url.includes(\"_rsc=\")) {\n return true\n }\n }\n }\n\n const headers = request.headers\n\n if (\n getHeader(headers, \"next-router-prefetch\") === \"1\" ||\n getHeader(headers, \"purpose\") === \"prefetch\" ||\n getHeader(headers, \"x-middleware-prefetch\") === \"1\"\n ) {\n return true\n }\n\n const rsc = getHeader(headers, \"rsc\")\n if (rsc === \"1\") {\n return true\n }\n\n if (getHeader(headers, \"next-router-state-tree\")) {\n return true\n }\n\n const secDest = getHeader(headers, \"sec-fetch-dest\")\n if (secDest && secDest !== \"document\") {\n return true\n }\n\n return false\n}\n","import { trackJmpServerPageView } from \"./page-view\"\nimport { shouldSkipServerPageViewRequest } from \"./should-track-server-page-view\"\nimport type { JmpServerRequestLike } from \"../types\"\nimport { getJmpContentConfig } from \"../site-config\"\n\n/**\n * Records a server-side page view when JMP env is configured.\n * Framework-agnostic — pass any `Request`-like object (Next middleware, Astro `context.request`).\n *\n * Skips Next.js RSC/prefetch requests. When using {@link JmpSiteRoot} for browser\n * page views, do not call this from middleware — it duplicates counts and omits `sessionId`.\n */\nexport async function jmpTrackServerPageView(\n request: JmpServerRequestLike,\n): Promise<void> {\n if (shouldSkipServerPageViewRequest(request)) {\n return\n }\n const config = getJmpContentConfig()\n if (!config) {\n return\n }\n await trackJmpServerPageView(config, { request }).catch(() => {})\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
- export { j as jmpTrackServerPageView } from '../track-page-view-BCX1Dsfk.cjs';
2
- import '../types-yNgoakwk.cjs';
1
+ export { j as jmpTrackServerPageView } from '../track-page-view-BJQqn2Bg.cjs';
2
+ import '../types-CYrgEwUF.cjs';
3
3
 
4
4
  /**
5
5
  * Astro middleware handler — use in `src/middleware.ts`:
@@ -1,5 +1,5 @@
1
- export { j as jmpTrackServerPageView } from '../track-page-view-74Su6lQY.js';
2
- import '../types-yNgoakwk.js';
1
+ export { j as jmpTrackServerPageView } from '../track-page-view-DGKRUIxm.js';
2
+ import '../types-CYrgEwUF.js';
3
3
 
4
4
  /**
5
5
  * Astro middleware handler — use in `src/middleware.ts`:
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  jmpTrackServerPageView
3
- } from "../chunk-HFLU7HQE.js";
4
- import "../chunk-AIFJ7DVO.js";
3
+ } from "../chunk-ONIPLMOY.js";
4
+ import "../chunk-6WQMWPEY.js";
5
5
 
6
6
  // src/astro/middleware.ts
7
7
  async function onRequest(context, next) {
@@ -28,7 +28,7 @@ module.exports = __toCommonJS(register_exports);
28
28
  var LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60;
29
29
 
30
30
  // package.json
31
- var version = "0.1.36";
31
+ var version = "0.1.38";
32
32
 
33
33
  // src/constants.ts
34
34
  var JMP_ANALYTICS_SDK_VERSION = version;
@@ -38,6 +38,100 @@ function normalizeBaseUrl(baseUrl) {
38
38
  return baseUrl.replace(/\/$/, "");
39
39
  }
40
40
 
41
+ // src/env-public.ts
42
+ function firstDefined(...values) {
43
+ for (const v of values) {
44
+ if (v) return v;
45
+ }
46
+ return void 0;
47
+ }
48
+ function readJmpPublicEnvFromProcessEnv() {
49
+ if (typeof process === "undefined" || !process.env) {
50
+ return {};
51
+ }
52
+ const { env } = process;
53
+ const analyticsUrl = firstDefined(
54
+ env.NEXT_PUBLIC_JMP_ANALYTICS_URL,
55
+ env.PUBLIC_JMP_ANALYTICS_URL
56
+ );
57
+ const trackingKey = firstDefined(
58
+ env.NEXT_PUBLIC_JMP_TRACKING_KEY,
59
+ env.PUBLIC_JMP_TRACKING_KEY
60
+ );
61
+ const requireConsentRaw = firstDefined(
62
+ env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,
63
+ env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT
64
+ );
65
+ const autoContactRaw = firstDefined(
66
+ env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,
67
+ env.PUBLIC_JMP_AUTO_CONTACT_LINKS
68
+ );
69
+ return {
70
+ analyticsUrl,
71
+ trackingKey,
72
+ requireConsent: requireConsentRaw === "true",
73
+ autoContactLinks: autoContactRaw === "true"
74
+ };
75
+ }
76
+ function readJmpPublicEnvFromProcess(overrides) {
77
+ const fromEnv = readJmpPublicEnvFromProcessEnv();
78
+ return {
79
+ analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),
80
+ trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),
81
+ requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,
82
+ autoContactLinks: overrides?.autoContactLinks ?? fromEnv.autoContactLinks
83
+ };
84
+ }
85
+
86
+ // src/site-config.ts
87
+ var registeredSite = null;
88
+ function getJmpSiteDefinition() {
89
+ return registeredSite;
90
+ }
91
+ function getJmpContentConfig() {
92
+ const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess();
93
+ if (!analyticsUrl || !trackingKey) {
94
+ return null;
95
+ }
96
+ const site = getJmpSiteDefinition();
97
+ const contentWrappers = site?.contentWrappers ?? void 0;
98
+ const inferredMode = contentWrappers && contentWrappers.length > 0 ? "manual" : void 0;
99
+ return {
100
+ baseUrl: analyticsUrl,
101
+ trackingKey,
102
+ contentWrappers,
103
+ contentManifest: site?.contentManifest ?? void 0,
104
+ contentWrapperMode: site?.contentWrapperMode ?? inferredMode
105
+ };
106
+ }
107
+
108
+ // src/landing-pages/utils.ts
109
+ function resolveJmpSiteUrl(override) {
110
+ const fromArg = override?.trim().replace(/\/$/, "");
111
+ if (fromArg) return fromArg;
112
+ const fromConfig = getJmpSiteDefinition()?.siteUrl?.trim().replace(/\/$/, "");
113
+ return fromConfig || null;
114
+ }
115
+
116
+ // src/request-origin.ts
117
+ function resolveJmpRequestOrigin() {
118
+ const fromSite = resolveJmpSiteUrl();
119
+ if (fromSite) {
120
+ return fromSite;
121
+ }
122
+ if (typeof process === "undefined") {
123
+ return null;
124
+ }
125
+ return process.env.NEXT_PUBLIC_SITE_URL?.trim().replace(/\/$/, "") || null;
126
+ }
127
+ function withJmpOriginHeader(headers) {
128
+ const origin = resolveJmpRequestOrigin();
129
+ if (!origin) {
130
+ return headers;
131
+ }
132
+ return { ...headers, Origin: origin };
133
+ }
134
+
41
135
  // src/content/manifest.ts
42
136
  var JMP_CONTENT_MANIFEST_VERSION = 1;
43
137
  var TRUST_SECTIONS = /* @__PURE__ */ new Set([
@@ -241,11 +335,19 @@ function inferWrapperSlotsFromManifest(manifest) {
241
335
  return [...slots];
242
336
  }
243
337
 
338
+ // src/content/fetch.ts
339
+ function jmpContentRequestHeaders(config) {
340
+ return withJmpOriginHeader({
341
+ "X-JMP-Tracking-Key": config.trackingKey
342
+ });
343
+ }
344
+
244
345
  // src/content/wrapper-discovery.ts
245
346
  var WRAPPER_SLOT_ORDER = [
246
347
  "blog",
247
348
  "faq",
248
349
  "contact",
350
+ "legal",
249
351
  "trust",
250
352
  "trust.summary",
251
353
  "trust.testimonials",
@@ -335,7 +437,7 @@ async function registerJmpContentWrappers(config) {
335
437
  method: "POST",
336
438
  headers: {
337
439
  "Content-Type": "application/json",
338
- "X-JMP-Tracking-Key": config.trackingKey
440
+ ...jmpContentRequestHeaders(config)
339
441
  },
340
442
  body: JSON.stringify(body)
341
443
  });
@@ -357,70 +459,6 @@ async function registerJmpContentWrappers(config) {
357
459
  }
358
460
  setContentWrapperSyncHandler(registerJmpContentWrappers);
359
461
 
360
- // src/env-public.ts
361
- function firstDefined(...values) {
362
- for (const v of values) {
363
- if (v) return v;
364
- }
365
- return void 0;
366
- }
367
- function readJmpPublicEnvFromProcessEnv() {
368
- if (typeof process === "undefined" || !process.env) {
369
- return {};
370
- }
371
- const { env } = process;
372
- const analyticsUrl = firstDefined(
373
- env.NEXT_PUBLIC_JMP_ANALYTICS_URL,
374
- env.PUBLIC_JMP_ANALYTICS_URL
375
- );
376
- const trackingKey = firstDefined(
377
- env.NEXT_PUBLIC_JMP_TRACKING_KEY,
378
- env.PUBLIC_JMP_TRACKING_KEY
379
- );
380
- const requireConsentRaw = firstDefined(
381
- env.NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT,
382
- env.PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT
383
- );
384
- const autoContactRaw = firstDefined(
385
- env.NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS,
386
- env.PUBLIC_JMP_AUTO_CONTACT_LINKS
387
- );
388
- return {
389
- analyticsUrl,
390
- trackingKey,
391
- requireConsent: requireConsentRaw === "true",
392
- autoContactLinks: autoContactRaw === "true"
393
- };
394
- }
395
- function readJmpPublicEnvFromProcess(overrides) {
396
- const fromEnv = readJmpPublicEnvFromProcessEnv();
397
- return {
398
- analyticsUrl: firstDefined(overrides?.analyticsUrl, fromEnv.analyticsUrl),
399
- trackingKey: firstDefined(overrides?.trackingKey, fromEnv.trackingKey),
400
- requireConsent: overrides?.requireConsent ?? fromEnv.requireConsent,
401
- autoContactLinks: overrides?.autoContactLinks ?? fromEnv.autoContactLinks
402
- };
403
- }
404
-
405
- // src/site-config.ts
406
- var registeredSite = null;
407
- function getJmpSiteDefinition() {
408
- return registeredSite;
409
- }
410
- function getJmpContentConfig() {
411
- const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess();
412
- if (!analyticsUrl || !trackingKey) {
413
- return null;
414
- }
415
- const site = getJmpSiteDefinition();
416
- return {
417
- baseUrl: analyticsUrl,
418
- trackingKey,
419
- contentWrappers: site?.contentWrappers ?? void 0,
420
- contentManifest: site?.contentManifest ?? void 0
421
- };
422
- }
423
-
424
462
  // src/astro/register.ts
425
463
  async function registerJmpAstroContent() {
426
464
  const config = getJmpContentConfig();