@jmp-technologies/analytics 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/astro/client.cjs +93 -108
- package/dist/astro/client.cjs.map +1 -1
- package/dist/astro/client.d.cts +2 -1
- package/dist/astro/client.d.ts +2 -1
- package/dist/astro/client.js +4 -2
- package/dist/astro/client.js.map +1 -1
- package/dist/astro/content.cjs +96 -92
- package/dist/astro/content.cjs.map +1 -1
- package/dist/astro/content.d.cts +2 -2
- package/dist/astro/content.d.ts +2 -2
- package/dist/astro/content.js +3 -2
- package/dist/astro/integration.cjs +64 -60
- package/dist/astro/integration.cjs.map +1 -1
- package/dist/astro/integration.js +3 -2
- package/dist/astro/integration.js.map +1 -1
- package/dist/astro/middleware.cjs +62 -63
- package/dist/astro/middleware.cjs.map +1 -1
- package/dist/astro/middleware.d.cts +2 -2
- package/dist/astro/middleware.d.ts +2 -2
- package/dist/astro/middleware.js +2 -2
- package/dist/astro/register.cjs +64 -60
- package/dist/astro/register.cjs.map +1 -1
- package/dist/astro/register.js +3 -2
- package/dist/{chunk-QIWGUF7V.js → chunk-6TDPOZMJ.js} +6 -4
- package/dist/{chunk-QIWGUF7V.js.map → chunk-6TDPOZMJ.js.map} +1 -1
- package/dist/{chunk-ALTSW5DB.js → chunk-DXHJGKIL.js} +2 -2
- package/dist/chunk-GLEOJIKL.js +211 -0
- package/dist/chunk-GLEOJIKL.js.map +1 -0
- package/dist/{chunk-GFCBZN6S.js → chunk-PDOQ2XBW.js} +71 -271
- package/dist/chunk-PDOQ2XBW.js.map +1 -0
- package/dist/{chunk-NZS4M3V3.js → chunk-TK4VRDMO.js} +2 -2
- package/dist/chunk-TK4VRDMO.js.map +1 -0
- package/dist/{chunk-UDNKWFBM.js → chunk-XA55GBSU.js} +5 -3
- package/dist/{chunk-UDNKWFBM.js.map → chunk-XA55GBSU.js.map} +1 -1
- package/dist/create-jmp-analytics-BdGOgPxl.d.ts +70 -0
- package/dist/create-jmp-analytics-DxhHOmtR.d.cts +70 -0
- package/dist/index.cjs +189 -194
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -340
- package/dist/index.d.ts +15 -340
- package/dist/index.js +7 -5
- package/dist/landing-capture-C8pyZWuF.d.cts +4 -0
- package/dist/landing-capture-C8pyZWuF.d.ts +4 -0
- package/dist/next/content.cjs +96 -92
- package/dist/next/content.cjs.map +1 -1
- package/dist/next/content.d.cts +2 -1
- package/dist/next/content.d.ts +2 -1
- package/dist/next/content.js +3 -2
- package/dist/next/content.js.map +1 -1
- package/dist/next/instrumentation.cjs +64 -60
- package/dist/next/instrumentation.cjs.map +1 -1
- package/dist/next/instrumentation.js +4 -2
- package/dist/next/instrumentation.js.map +1 -1
- package/dist/next/middleware.cjs +62 -63
- package/dist/next/middleware.cjs.map +1 -1
- package/dist/next/middleware.d.cts +2 -2
- package/dist/next/middleware.d.ts +2 -2
- package/dist/next/middleware.js +2 -2
- package/dist/next.cjs +96 -109
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +3 -1
- package/dist/next.d.ts +3 -1
- package/dist/next.js +3 -2
- package/dist/next.js.map +1 -1
- package/dist/react.cjs +96 -109
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -1
- package/dist/react.d.ts +2 -1
- package/dist/react.js +3 -2
- package/dist/site-config-BRuZWGSW.d.cts +46 -0
- package/dist/site-config-hkGMSeT7.d.ts +46 -0
- package/dist/{track-page-view-CoYo96n0.d.cts → track-page-view-BLS0YfL-.d.ts} +1 -1
- package/dist/{track-page-view-DAy0I0Vb.d.ts → track-page-view-DfCyWHqe.d.cts} +1 -1
- package/dist/types-BtiK_Acj.d.cts +224 -0
- package/dist/types-BtiK_Acj.d.ts +224 -0
- package/package.json +2 -4
- package/dist/chunk-GFCBZN6S.js.map +0 -1
- package/dist/chunk-NZS4M3V3.js.map +0 -1
- /package/dist/{chunk-ALTSW5DB.js.map → chunk-DXHJGKIL.js.map} +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getJmpContentConfig,
|
|
3
2
|
registerJmpContentWrappers
|
|
4
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PDOQ2XBW.js";
|
|
4
|
+
import {
|
|
5
|
+
getJmpContentConfig
|
|
6
|
+
} from "./chunk-GLEOJIKL.js";
|
|
5
7
|
|
|
6
8
|
// src/astro/register.ts
|
|
7
9
|
async function registerJmpAstroContent() {
|
|
@@ -16,4 +18,4 @@ async function registerJmpAstroContent() {
|
|
|
16
18
|
export {
|
|
17
19
|
registerJmpAstroContent
|
|
18
20
|
};
|
|
19
|
-
//# sourceMappingURL=chunk-
|
|
21
|
+
//# sourceMappingURL=chunk-XA55GBSU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/astro/register.ts"],"sourcesContent":["import { registerJmpContentWrappers } from \"../index\"\nimport { getJmpContentConfig } from \"../site-config\"\n\n/**\n * Register content wrappers with the dashboard (call once per build).\n * Import `./jmp.config` in `astro.config.mjs` before calling this, or use {@link jmpAnalytics} integration.\n */\nexport async function registerJmpAstroContent(): Promise<void> {\n const config = getJmpContentConfig()\n if (!config) {\n return\n }\n await registerJmpContentWrappers(config).catch(() => {})\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/astro/register.ts"],"sourcesContent":["import { registerJmpContentWrappers } from \"../index\"\nimport { getJmpContentConfig } from \"../site-config\"\n\n/**\n * Register content wrappers with the dashboard (call once per build).\n * Import `./jmp.config` in `astro.config.mjs` before calling this, or use {@link jmpAnalytics} integration.\n */\nexport async function registerJmpAstroContent(): Promise<void> {\n const config = getJmpContentConfig()\n if (!config) {\n return\n }\n await registerJmpContentWrappers(config).catch(() => {})\n}\n"],"mappings":";;;;;;;;AAOA,eAAsB,0BAAyC;AAC7D,QAAM,SAAS,oBAAoB;AACnC,MAAI,CAAC,QAAQ;AACX;AAAA,EACF;AACA,QAAM,2BAA2B,MAAM,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACzD;","names":[]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { h as JmpAnalyticsConfig, i as JmpEventType, T as TrackPageViewOptions, j as TrackLeadOptions } from './types-BtiK_Acj.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Classify outbound contact links for auto-tracking.
|
|
5
|
+
*
|
|
6
|
+
* Keep channel set in sync with `lib/ingest/contact-click.ts` on the dashboard side.
|
|
7
|
+
*
|
|
8
|
+
* Channels detected from `href` alone (no widget / iframe detection):
|
|
9
|
+
*
|
|
10
|
+
* - `call` — `tel:` (delegates to existing `trackCallClick`)
|
|
11
|
+
* - `email` — `mailto:` (delegates to existing `trackEmailClick`)
|
|
12
|
+
* - `sms` — `sms:` / `smsto:`
|
|
13
|
+
* - `whatsapp` — `wa.me`, `api.whatsapp.com`, `whatsapp.com/send`
|
|
14
|
+
* - `maps` — Google Maps, Apple Maps, `maps.app.goo.gl`, `geo:`
|
|
15
|
+
* - `booking` — fixed allowlist of common scheduling hosts (see {@link BOOKING_LINK_HOSTS})
|
|
16
|
+
*
|
|
17
|
+
* `linkHost` returned in `contact_click` results is the hostname only — never the full URL
|
|
18
|
+
* (avoids leaking phone numbers in `tel:`/`sms:` URLs and PII-laden booking query strings).
|
|
19
|
+
*/
|
|
20
|
+
type ContactLinkChannel = "sms" | "whatsapp" | "maps" | "booking";
|
|
21
|
+
type ClassifiedContactLink = {
|
|
22
|
+
kind: "call";
|
|
23
|
+
} | {
|
|
24
|
+
kind: "email";
|
|
25
|
+
} | {
|
|
26
|
+
kind: "contact_click";
|
|
27
|
+
channel: ContactLinkChannel;
|
|
28
|
+
linkHost: string | null;
|
|
29
|
+
};
|
|
30
|
+
/** v1 booking hosts (~80%+ SMB coverage). Add hosts here when a client uses something rare. */
|
|
31
|
+
declare const BOOKING_LINK_HOSTS: readonly ["calendly.com", "cal.com", "acuityscheduling.com", "calendar.google.com", "calendar.app.google", "bookings.microsoft.com", "outlook.office.com", "outlook.office365.com", "book.ms", "setmore.com", "simplybook.me", "oncehub.com", "scheduleonce.com", "youcanbook.me", "savvycal.com", "tidycal.com", "zohobookings.com", "square.site", "squareup.com", "meetings.hubspot.com", "jobber.com", "housecallpro.com", "servicetitan.com", "thryv.com", "honeybook.com", "dubsado.com", "fresha.com", "booksy.com", "vagaro.com", "mindbodyonline.com", "mindbody.io", "janeapp.com", "cliniko.com", "practicebetter.io"];
|
|
32
|
+
/** Returns `null` when the link should not be tracked as a contact link. */
|
|
33
|
+
declare function classifyContactHref(href: string): ClassifiedContactLink | null;
|
|
34
|
+
|
|
35
|
+
/** Whether analytics events may be sent (after CMP / cookie banner). */
|
|
36
|
+
declare function isJmpTrackingConsentGranted(): boolean;
|
|
37
|
+
/** Subscribe to consent changes (for React `useSyncExternalStore`). */
|
|
38
|
+
declare function subscribeJmpTrackingConsent(listener: () => void): () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Grant or deny analytics. Persists in `localStorage` when `requireConsent` is used.
|
|
41
|
+
* Call from your cookie banner before tracking runs.
|
|
42
|
+
*/
|
|
43
|
+
declare function setJmpTrackingConsent(granted: boolean): void;
|
|
44
|
+
|
|
45
|
+
declare function createJmpAnalytics(config: JmpAnalyticsConfig): {
|
|
46
|
+
track: (type: JmpEventType, payload: {
|
|
47
|
+
path: string;
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
source?: string;
|
|
50
|
+
device?: string;
|
|
51
|
+
country?: string;
|
|
52
|
+
}) => Promise<void>;
|
|
53
|
+
trackPageView: (options?: TrackPageViewOptions) => Promise<void>;
|
|
54
|
+
trackFormSubmit: (path: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
55
|
+
trackCallClick: (path: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
56
|
+
trackEmailClick: (path: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
57
|
+
trackContactClick: (path: string, options: {
|
|
58
|
+
channel: ContactLinkChannel;
|
|
59
|
+
linkHost?: string | null;
|
|
60
|
+
metadata?: Record<string, unknown>;
|
|
61
|
+
}) => Promise<void>;
|
|
62
|
+
trackLead: (options?: TrackLeadOptions) => Promise<void>;
|
|
63
|
+
subscribeToSpaNavigation: () => () => void;
|
|
64
|
+
subscribeContactLinkAutoTrack: () => () => void;
|
|
65
|
+
setTrackingConsent: typeof setJmpTrackingConsent;
|
|
66
|
+
isTrackingConsentGranted: typeof isJmpTrackingConsentGranted;
|
|
67
|
+
};
|
|
68
|
+
type JmpAnalytics = ReturnType<typeof createJmpAnalytics>;
|
|
69
|
+
|
|
70
|
+
export { BOOKING_LINK_HOSTS as B, type ClassifiedContactLink as C, type JmpAnalytics as J, subscribeJmpTrackingConsent as a, type ContactLinkChannel as b, classifyContactHref as c, createJmpAnalytics as d, isJmpTrackingConsentGranted as i, setJmpTrackingConsent as s };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { h as JmpAnalyticsConfig, i as JmpEventType, T as TrackPageViewOptions, j as TrackLeadOptions } from './types-BtiK_Acj.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Classify outbound contact links for auto-tracking.
|
|
5
|
+
*
|
|
6
|
+
* Keep channel set in sync with `lib/ingest/contact-click.ts` on the dashboard side.
|
|
7
|
+
*
|
|
8
|
+
* Channels detected from `href` alone (no widget / iframe detection):
|
|
9
|
+
*
|
|
10
|
+
* - `call` — `tel:` (delegates to existing `trackCallClick`)
|
|
11
|
+
* - `email` — `mailto:` (delegates to existing `trackEmailClick`)
|
|
12
|
+
* - `sms` — `sms:` / `smsto:`
|
|
13
|
+
* - `whatsapp` — `wa.me`, `api.whatsapp.com`, `whatsapp.com/send`
|
|
14
|
+
* - `maps` — Google Maps, Apple Maps, `maps.app.goo.gl`, `geo:`
|
|
15
|
+
* - `booking` — fixed allowlist of common scheduling hosts (see {@link BOOKING_LINK_HOSTS})
|
|
16
|
+
*
|
|
17
|
+
* `linkHost` returned in `contact_click` results is the hostname only — never the full URL
|
|
18
|
+
* (avoids leaking phone numbers in `tel:`/`sms:` URLs and PII-laden booking query strings).
|
|
19
|
+
*/
|
|
20
|
+
type ContactLinkChannel = "sms" | "whatsapp" | "maps" | "booking";
|
|
21
|
+
type ClassifiedContactLink = {
|
|
22
|
+
kind: "call";
|
|
23
|
+
} | {
|
|
24
|
+
kind: "email";
|
|
25
|
+
} | {
|
|
26
|
+
kind: "contact_click";
|
|
27
|
+
channel: ContactLinkChannel;
|
|
28
|
+
linkHost: string | null;
|
|
29
|
+
};
|
|
30
|
+
/** v1 booking hosts (~80%+ SMB coverage). Add hosts here when a client uses something rare. */
|
|
31
|
+
declare const BOOKING_LINK_HOSTS: readonly ["calendly.com", "cal.com", "acuityscheduling.com", "calendar.google.com", "calendar.app.google", "bookings.microsoft.com", "outlook.office.com", "outlook.office365.com", "book.ms", "setmore.com", "simplybook.me", "oncehub.com", "scheduleonce.com", "youcanbook.me", "savvycal.com", "tidycal.com", "zohobookings.com", "square.site", "squareup.com", "meetings.hubspot.com", "jobber.com", "housecallpro.com", "servicetitan.com", "thryv.com", "honeybook.com", "dubsado.com", "fresha.com", "booksy.com", "vagaro.com", "mindbodyonline.com", "mindbody.io", "janeapp.com", "cliniko.com", "practicebetter.io"];
|
|
32
|
+
/** Returns `null` when the link should not be tracked as a contact link. */
|
|
33
|
+
declare function classifyContactHref(href: string): ClassifiedContactLink | null;
|
|
34
|
+
|
|
35
|
+
/** Whether analytics events may be sent (after CMP / cookie banner). */
|
|
36
|
+
declare function isJmpTrackingConsentGranted(): boolean;
|
|
37
|
+
/** Subscribe to consent changes (for React `useSyncExternalStore`). */
|
|
38
|
+
declare function subscribeJmpTrackingConsent(listener: () => void): () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Grant or deny analytics. Persists in `localStorage` when `requireConsent` is used.
|
|
41
|
+
* Call from your cookie banner before tracking runs.
|
|
42
|
+
*/
|
|
43
|
+
declare function setJmpTrackingConsent(granted: boolean): void;
|
|
44
|
+
|
|
45
|
+
declare function createJmpAnalytics(config: JmpAnalyticsConfig): {
|
|
46
|
+
track: (type: JmpEventType, payload: {
|
|
47
|
+
path: string;
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
source?: string;
|
|
50
|
+
device?: string;
|
|
51
|
+
country?: string;
|
|
52
|
+
}) => Promise<void>;
|
|
53
|
+
trackPageView: (options?: TrackPageViewOptions) => Promise<void>;
|
|
54
|
+
trackFormSubmit: (path: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
55
|
+
trackCallClick: (path: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
56
|
+
trackEmailClick: (path: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
57
|
+
trackContactClick: (path: string, options: {
|
|
58
|
+
channel: ContactLinkChannel;
|
|
59
|
+
linkHost?: string | null;
|
|
60
|
+
metadata?: Record<string, unknown>;
|
|
61
|
+
}) => Promise<void>;
|
|
62
|
+
trackLead: (options?: TrackLeadOptions) => Promise<void>;
|
|
63
|
+
subscribeToSpaNavigation: () => () => void;
|
|
64
|
+
subscribeContactLinkAutoTrack: () => () => void;
|
|
65
|
+
setTrackingConsent: typeof setJmpTrackingConsent;
|
|
66
|
+
isTrackingConsentGranted: typeof isJmpTrackingConsentGranted;
|
|
67
|
+
};
|
|
68
|
+
type JmpAnalytics = ReturnType<typeof createJmpAnalytics>;
|
|
69
|
+
|
|
70
|
+
export { BOOKING_LINK_HOSTS as B, type ClassifiedContactLink as C, type JmpAnalytics as J, subscribeJmpTrackingConsent as a, type ContactLinkChannel as b, classifyContactHref as c, createJmpAnalytics as d, isJmpTrackingConsentGranted as i, setJmpTrackingConsent as s };
|
package/dist/index.cjs
CHANGED
|
@@ -312,72 +312,12 @@ function classifyContactHref(href) {
|
|
|
312
312
|
return null;
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
// src/
|
|
316
|
-
|
|
317
|
-
if (typeof process === "undefined" || !process.env) return void 0;
|
|
318
|
-
const v = process.env[name];
|
|
319
|
-
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
320
|
-
}
|
|
321
|
-
function firstDefined(...values) {
|
|
322
|
-
for (const v of values) {
|
|
323
|
-
if (v) return v;
|
|
324
|
-
}
|
|
325
|
-
return void 0;
|
|
326
|
-
}
|
|
327
|
-
function readJmpPublicEnvFromProcess(overrides) {
|
|
328
|
-
const analyticsUrl = firstDefined(
|
|
329
|
-
overrides?.analyticsUrl,
|
|
330
|
-
readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL"),
|
|
331
|
-
readProcessEnv("PUBLIC_JMP_ANALYTICS_URL")
|
|
332
|
-
);
|
|
333
|
-
const trackingKey = firstDefined(
|
|
334
|
-
overrides?.trackingKey,
|
|
335
|
-
readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY"),
|
|
336
|
-
readProcessEnv("PUBLIC_JMP_TRACKING_KEY")
|
|
337
|
-
);
|
|
338
|
-
const requireConsentRaw = firstDefined(
|
|
339
|
-
overrides?.requireConsent !== void 0 ? String(overrides.requireConsent) : void 0,
|
|
340
|
-
readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT"),
|
|
341
|
-
readProcessEnv("PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT")
|
|
342
|
-
);
|
|
343
|
-
const autoContactRaw = firstDefined(
|
|
344
|
-
overrides?.autoContactLinks !== void 0 ? String(overrides.autoContactLinks) : void 0,
|
|
345
|
-
readProcessEnv("NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS"),
|
|
346
|
-
readProcessEnv("PUBLIC_JMP_AUTO_CONTACT_LINKS")
|
|
347
|
-
);
|
|
348
|
-
return {
|
|
349
|
-
analyticsUrl,
|
|
350
|
-
trackingKey,
|
|
351
|
-
requireConsent: requireConsentRaw === "true",
|
|
352
|
-
autoContactLinks: autoContactRaw === "true"
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// src/site-config.ts
|
|
357
|
-
var registeredSite = null;
|
|
358
|
-
function defineJmpSite(definition) {
|
|
359
|
-
registeredSite = definition;
|
|
360
|
-
return definition;
|
|
361
|
-
}
|
|
362
|
-
function getJmpSiteDefinition() {
|
|
363
|
-
return registeredSite;
|
|
364
|
-
}
|
|
365
|
-
function getJmpContentConfig() {
|
|
366
|
-
const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess();
|
|
367
|
-
if (!analyticsUrl || !trackingKey) {
|
|
368
|
-
return null;
|
|
369
|
-
}
|
|
370
|
-
const site = getJmpSiteDefinition();
|
|
371
|
-
return {
|
|
372
|
-
baseUrl: analyticsUrl,
|
|
373
|
-
trackingKey,
|
|
374
|
-
contentWrappers: site?.contentWrappers ?? void 0
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// src/index.ts
|
|
379
|
-
var JMP_ANALYTICS_SDK_VERSION = "0.1.20";
|
|
315
|
+
// src/constants.ts
|
|
316
|
+
var JMP_ANALYTICS_SDK_VERSION = "0.1.21";
|
|
380
317
|
var JMP_TRACKING_CONSENT_STORAGE_KEY = "jmp_analytics_consent_v1";
|
|
318
|
+
var DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 800;
|
|
319
|
+
|
|
320
|
+
// src/consent.ts
|
|
381
321
|
var moduleTrackingConsentGranted = true;
|
|
382
322
|
var trackingConsentListeners = /* @__PURE__ */ new Set();
|
|
383
323
|
function readStoredTrackingConsent() {
|
|
@@ -390,6 +330,9 @@ function readStoredTrackingConsent() {
|
|
|
390
330
|
return false;
|
|
391
331
|
}
|
|
392
332
|
}
|
|
333
|
+
function initJmpTrackingConsent(requireConsent) {
|
|
334
|
+
moduleTrackingConsentGranted = requireConsent ? readStoredTrackingConsent() : true;
|
|
335
|
+
}
|
|
393
336
|
function isJmpTrackingConsentGranted() {
|
|
394
337
|
return moduleTrackingConsentGranted;
|
|
395
338
|
}
|
|
@@ -420,33 +363,8 @@ function setJmpTrackingConsent(granted) {
|
|
|
420
363
|
listener();
|
|
421
364
|
}
|
|
422
365
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
return null;
|
|
426
|
-
}
|
|
427
|
-
return captureLandingAttribution(window.location.hostname).landingReferer;
|
|
428
|
-
}
|
|
429
|
-
function mergeSessionUtms(metadata) {
|
|
430
|
-
if (typeof window === "undefined") {
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
const { utm } = captureLandingAttribution(window.location.hostname);
|
|
434
|
-
for (const key of JMP_UTM_PARAM_KEYS) {
|
|
435
|
-
if (metadata[key] === void 0 && utm[key]) {
|
|
436
|
-
metadata[key] = utm[key];
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
function splitPathAndSearch(pathInput) {
|
|
441
|
-
const qIndex = pathInput.indexOf("?");
|
|
442
|
-
if (qIndex === -1) {
|
|
443
|
-
return { pathname: pathInput || "/", search: "" };
|
|
444
|
-
}
|
|
445
|
-
return {
|
|
446
|
-
pathname: pathInput.slice(0, qIndex) || "/",
|
|
447
|
-
search: pathInput.slice(qIndex + 1)
|
|
448
|
-
};
|
|
449
|
-
}
|
|
366
|
+
|
|
367
|
+
// src/http.ts
|
|
450
368
|
function responseLooksLikeHtml(body) {
|
|
451
369
|
const s = body.trimStart();
|
|
452
370
|
return s.startsWith("<!") || s.toLowerCase().startsWith("<html") || s.startsWith("<") && /<html[\s>]/i.test(s.slice(0, 500));
|
|
@@ -479,24 +397,75 @@ function pathFromRequestUrl(url) {
|
|
|
479
397
|
return url.startsWith("/") ? url : "/";
|
|
480
398
|
}
|
|
481
399
|
}
|
|
400
|
+
function resolveFetch(fetchImpl, context) {
|
|
401
|
+
const resolved = fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
402
|
+
if (!resolved) {
|
|
403
|
+
throw new Error(`${context}: fetch is not available; pass fetchImpl in config`);
|
|
404
|
+
}
|
|
405
|
+
return resolved;
|
|
406
|
+
}
|
|
407
|
+
function formatApiErrorMessage(status, text, prefix) {
|
|
408
|
+
if (responseLooksLikeHtml(text)) {
|
|
409
|
+
return `${prefix} HTTP ${status} \u2014 response was HTML (not the JSON API). Check baseUrl points at the JMP Analytics dashboard.`;
|
|
410
|
+
}
|
|
411
|
+
let parsed = {};
|
|
412
|
+
try {
|
|
413
|
+
parsed = JSON.parse(text);
|
|
414
|
+
} catch {
|
|
415
|
+
parsed = { error: text };
|
|
416
|
+
}
|
|
417
|
+
const detail = parsed.error ?? text;
|
|
418
|
+
return [
|
|
419
|
+
`${prefix} HTTP ${status}`,
|
|
420
|
+
parsed.code ? `[${parsed.code}]` : null,
|
|
421
|
+
detail,
|
|
422
|
+
parsed.hint ? `Hint: ${parsed.hint}` : null,
|
|
423
|
+
parsed.expectedDomain ? `Domain in JMP admin: ${parsed.expectedDomain}` : null
|
|
424
|
+
].filter(Boolean).join(" \u2014 ");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/tracker/helpers.ts
|
|
428
|
+
function captureAndGetLandingReferer() {
|
|
429
|
+
if (typeof window === "undefined") {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
return captureLandingAttribution(window.location.hostname).landingReferer;
|
|
433
|
+
}
|
|
434
|
+
function mergeSessionUtms(metadata) {
|
|
435
|
+
if (typeof window === "undefined") {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const { utm } = captureLandingAttribution(window.location.hostname);
|
|
439
|
+
for (const key of JMP_UTM_PARAM_KEYS) {
|
|
440
|
+
if (metadata[key] === void 0 && utm[key]) {
|
|
441
|
+
metadata[key] = utm[key];
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function splitPathAndSearch(pathInput) {
|
|
446
|
+
const qIndex = pathInput.indexOf("?");
|
|
447
|
+
if (qIndex === -1) {
|
|
448
|
+
return { pathname: pathInput || "/", search: "" };
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
pathname: pathInput.slice(0, qIndex) || "/",
|
|
452
|
+
search: pathInput.slice(qIndex + 1)
|
|
453
|
+
};
|
|
454
|
+
}
|
|
482
455
|
function inferDevice() {
|
|
483
456
|
if (typeof window === "undefined") {
|
|
484
457
|
return "unknown";
|
|
485
458
|
}
|
|
486
459
|
return window.innerWidth < 768 ? "mobile" : "desktop";
|
|
487
460
|
}
|
|
461
|
+
|
|
462
|
+
// src/tracker/create-jmp-analytics.ts
|
|
488
463
|
function createJmpAnalytics(config) {
|
|
489
464
|
const baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
throw new Error(
|
|
493
|
-
"createJmpAnalytics: fetch is not available; pass fetchImpl in config"
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
const fetchFn = resolvedFetch;
|
|
497
|
-
moduleTrackingConsentGranted = config.requireConsent ? readStoredTrackingConsent() : true;
|
|
465
|
+
const fetchFn = resolveFetch(config.fetchImpl, "createJmpAnalytics");
|
|
466
|
+
initJmpTrackingConsent(config.requireConsent);
|
|
498
467
|
function canSend() {
|
|
499
|
-
return
|
|
468
|
+
return isJmpTrackingConsentGranted();
|
|
500
469
|
}
|
|
501
470
|
let lastDedupePath = "";
|
|
502
471
|
let lastDedupeAt = 0;
|
|
@@ -528,32 +497,9 @@ function createJmpAnalytics(config) {
|
|
|
528
497
|
});
|
|
529
498
|
if (!res.ok) {
|
|
530
499
|
const text = await res.text();
|
|
531
|
-
|
|
532
|
-
throw new Error(
|
|
533
|
-
`JMP Analytics HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl / NEXT_PUBLIC_JMP_ANALYTICS_URL to your JMP Analytics dashboard origin (the deployment that includes POST /api/events), not a marketing or client site unless that codebase ships the same API route.`
|
|
534
|
-
);
|
|
535
|
-
}
|
|
536
|
-
let parsed = {};
|
|
537
|
-
try {
|
|
538
|
-
parsed = JSON.parse(text);
|
|
539
|
-
} catch {
|
|
540
|
-
parsed = { error: text };
|
|
541
|
-
}
|
|
542
|
-
const detail = parsed.error ?? text;
|
|
543
|
-
const segments = [
|
|
544
|
-
`JMP Analytics HTTP ${res.status}`,
|
|
545
|
-
parsed.code ? `[${parsed.code}]` : null,
|
|
546
|
-
detail,
|
|
547
|
-
parsed.hint ? `Hint: ${parsed.hint}` : null,
|
|
548
|
-
parsed.expectedDomain ? `Domain in JMP admin: ${parsed.expectedDomain}` : null
|
|
549
|
-
].filter(Boolean);
|
|
550
|
-
const message = segments.join(" \u2014 ");
|
|
500
|
+
const message = formatApiErrorMessage(res.status, text, "JMP Analytics");
|
|
551
501
|
if (config.debug) {
|
|
552
|
-
console.debug("[@jmp-technologies/analytics]", "error response", {
|
|
553
|
-
status: res.status,
|
|
554
|
-
...parsed,
|
|
555
|
-
raw: text
|
|
556
|
-
});
|
|
502
|
+
console.debug("[@jmp-technologies/analytics]", "error response", { status: res.status, raw: text });
|
|
557
503
|
}
|
|
558
504
|
throw new Error(message);
|
|
559
505
|
}
|
|
@@ -580,32 +526,9 @@ function createJmpAnalytics(config) {
|
|
|
580
526
|
});
|
|
581
527
|
if (!res.ok) {
|
|
582
528
|
const text = await res.text();
|
|
583
|
-
|
|
584
|
-
throw new Error(
|
|
585
|
-
`JMP Analytics HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl / NEXT_PUBLIC_JMP_ANALYTICS_URL to your JMP Analytics dashboard origin (the deployment that includes POST /api/leads).`
|
|
586
|
-
);
|
|
587
|
-
}
|
|
588
|
-
let parsed = {};
|
|
589
|
-
try {
|
|
590
|
-
parsed = JSON.parse(text);
|
|
591
|
-
} catch {
|
|
592
|
-
parsed = { error: text };
|
|
593
|
-
}
|
|
594
|
-
const detail = parsed.error ?? text;
|
|
595
|
-
const segments = [
|
|
596
|
-
`JMP Analytics HTTP ${res.status}`,
|
|
597
|
-
parsed.code ? `[${parsed.code}]` : null,
|
|
598
|
-
detail,
|
|
599
|
-
parsed.hint ? `Hint: ${parsed.hint}` : null,
|
|
600
|
-
parsed.expectedDomain ? `Domain in JMP admin: ${parsed.expectedDomain}` : null
|
|
601
|
-
].filter(Boolean);
|
|
602
|
-
const message = segments.join(" \u2014 ");
|
|
529
|
+
const message = formatApiErrorMessage(res.status, text, "JMP Analytics");
|
|
603
530
|
if (config.debug) {
|
|
604
|
-
console.debug("[@jmp-technologies/analytics]", "error response", {
|
|
605
|
-
status: res.status,
|
|
606
|
-
...parsed,
|
|
607
|
-
raw: text
|
|
608
|
-
});
|
|
531
|
+
console.debug("[@jmp-technologies/analytics]", "error response", { status: res.status, raw: text });
|
|
609
532
|
}
|
|
610
533
|
throw new Error(message);
|
|
611
534
|
}
|
|
@@ -869,6 +792,8 @@ function createJmpAnalytics(config) {
|
|
|
869
792
|
isTrackingConsentGranted: isJmpTrackingConsentGranted
|
|
870
793
|
};
|
|
871
794
|
}
|
|
795
|
+
|
|
796
|
+
// src/server/page-view.ts
|
|
872
797
|
async function trackJmpServerPageView(config, options = {}) {
|
|
873
798
|
const baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
874
799
|
const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
@@ -910,6 +835,55 @@ async function trackJmpServerPageView(config, options = {}) {
|
|
|
910
835
|
throw new Error(`JMP server analytics HTTP ${res.status} \u2014 ${text}`);
|
|
911
836
|
}
|
|
912
837
|
}
|
|
838
|
+
|
|
839
|
+
// src/content/fetch.ts
|
|
840
|
+
async function getContent(config, path) {
|
|
841
|
+
const text = await getContentText(config, path);
|
|
842
|
+
try {
|
|
843
|
+
return JSON.parse(text);
|
|
844
|
+
} catch {
|
|
845
|
+
const baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
846
|
+
const url = `${baseUrl}${path}`;
|
|
847
|
+
throw new Error(`JMP content HTTP 200 \u2014 ${url} returned invalid JSON.`);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
async function getContentText(config, path) {
|
|
851
|
+
const baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
852
|
+
const resolvedFetch = config.fetchImpl ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
853
|
+
if (!resolvedFetch) {
|
|
854
|
+
throw new Error("JMP content: fetch is not available; pass fetchImpl in config");
|
|
855
|
+
}
|
|
856
|
+
const url = `${baseUrl}${path}`;
|
|
857
|
+
if (config.debug) {
|
|
858
|
+
console.debug("[@jmp-technologies/analytics]", "GET", url);
|
|
859
|
+
}
|
|
860
|
+
const res = await resolvedFetch(url, {
|
|
861
|
+
method: "GET",
|
|
862
|
+
headers: {
|
|
863
|
+
"X-JMP-Tracking-Key": config.trackingKey
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
const text = await res.text();
|
|
867
|
+
if (!res.ok) {
|
|
868
|
+
if (responseLooksLikeHtml(text)) {
|
|
869
|
+
throw new Error(
|
|
870
|
+
`JMP content HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl to your JMP Analytics dashboard origin.`
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
let parsed = {};
|
|
874
|
+
try {
|
|
875
|
+
parsed = JSON.parse(text);
|
|
876
|
+
} catch {
|
|
877
|
+
parsed = { error: text };
|
|
878
|
+
}
|
|
879
|
+
throw new Error(
|
|
880
|
+
[`JMP content HTTP ${res.status}`, parsed.error, parsed.hint].filter(Boolean).join(" \u2014 ")
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
return text;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// src/content/api.ts
|
|
913
887
|
async function getJmpBlogPosts(config) {
|
|
914
888
|
const data = await getContent(
|
|
915
889
|
config,
|
|
@@ -1005,6 +979,8 @@ async function registerJmpContentWrappers(config) {
|
|
|
1005
979
|
const data = JSON.parse(text);
|
|
1006
980
|
return { ok: true, wrappers: data.wrappers ?? wrappers };
|
|
1007
981
|
}
|
|
982
|
+
|
|
983
|
+
// src/content/client.ts
|
|
1008
984
|
function createJmpContentClient(config) {
|
|
1009
985
|
return {
|
|
1010
986
|
getBlogPosts: () => getJmpBlogPosts(config),
|
|
@@ -1016,52 +992,49 @@ function createJmpContentClient(config) {
|
|
|
1016
992
|
registerContentWrappers: () => registerJmpContentWrappers(config)
|
|
1017
993
|
};
|
|
1018
994
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
const url = `${baseUrl}${path}`;
|
|
1026
|
-
throw new Error(`JMP content HTTP 200 \u2014 ${url} returned invalid JSON.`);
|
|
1027
|
-
}
|
|
995
|
+
|
|
996
|
+
// src/env-public.ts
|
|
997
|
+
function readProcessEnv(name) {
|
|
998
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
999
|
+
const v = process.env[name];
|
|
1000
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1028
1001
|
}
|
|
1029
|
-
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
if (!resolvedFetch) {
|
|
1033
|
-
throw new Error("JMP content: fetch is not available; pass fetchImpl in config");
|
|
1034
|
-
}
|
|
1035
|
-
const url = `${baseUrl}${path}`;
|
|
1036
|
-
if (config.debug) {
|
|
1037
|
-
console.debug("[@jmp-technologies/analytics]", "GET", url);
|
|
1038
|
-
}
|
|
1039
|
-
const res = await resolvedFetch(url, {
|
|
1040
|
-
method: "GET",
|
|
1041
|
-
headers: {
|
|
1042
|
-
"X-JMP-Tracking-Key": config.trackingKey
|
|
1043
|
-
}
|
|
1044
|
-
});
|
|
1045
|
-
const text = await res.text();
|
|
1046
|
-
if (!res.ok) {
|
|
1047
|
-
if (responseLooksLikeHtml(text)) {
|
|
1048
|
-
throw new Error(
|
|
1049
|
-
`JMP content HTTP ${res.status} \u2014 ${url} returned HTML (not the JSON API). Set baseUrl to your JMP Analytics dashboard origin.`
|
|
1050
|
-
);
|
|
1051
|
-
}
|
|
1052
|
-
let parsed = {};
|
|
1053
|
-
try {
|
|
1054
|
-
parsed = JSON.parse(text);
|
|
1055
|
-
} catch {
|
|
1056
|
-
parsed = { error: text };
|
|
1057
|
-
}
|
|
1058
|
-
throw new Error(
|
|
1059
|
-
[`JMP content HTTP ${res.status}`, parsed.error, parsed.hint].filter(Boolean).join(" \u2014 ")
|
|
1060
|
-
);
|
|
1002
|
+
function firstDefined(...values) {
|
|
1003
|
+
for (const v of values) {
|
|
1004
|
+
if (v) return v;
|
|
1061
1005
|
}
|
|
1062
|
-
return
|
|
1006
|
+
return void 0;
|
|
1063
1007
|
}
|
|
1064
|
-
|
|
1008
|
+
function readJmpPublicEnvFromProcess(overrides) {
|
|
1009
|
+
const analyticsUrl = firstDefined(
|
|
1010
|
+
overrides?.analyticsUrl,
|
|
1011
|
+
readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL"),
|
|
1012
|
+
readProcessEnv("PUBLIC_JMP_ANALYTICS_URL")
|
|
1013
|
+
);
|
|
1014
|
+
const trackingKey = firstDefined(
|
|
1015
|
+
overrides?.trackingKey,
|
|
1016
|
+
readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY"),
|
|
1017
|
+
readProcessEnv("PUBLIC_JMP_TRACKING_KEY")
|
|
1018
|
+
);
|
|
1019
|
+
const requireConsentRaw = firstDefined(
|
|
1020
|
+
overrides?.requireConsent !== void 0 ? String(overrides.requireConsent) : void 0,
|
|
1021
|
+
readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT"),
|
|
1022
|
+
readProcessEnv("PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT")
|
|
1023
|
+
);
|
|
1024
|
+
const autoContactRaw = firstDefined(
|
|
1025
|
+
overrides?.autoContactLinks !== void 0 ? String(overrides.autoContactLinks) : void 0,
|
|
1026
|
+
readProcessEnv("NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS"),
|
|
1027
|
+
readProcessEnv("PUBLIC_JMP_AUTO_CONTACT_LINKS")
|
|
1028
|
+
);
|
|
1029
|
+
return {
|
|
1030
|
+
analyticsUrl,
|
|
1031
|
+
trackingKey,
|
|
1032
|
+
requireConsent: requireConsentRaw === "true",
|
|
1033
|
+
autoContactLinks: autoContactRaw === "true"
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// src/env/browser.ts
|
|
1065
1038
|
function getJmpBrowserAnalyticsConfigFromEnv(overrides) {
|
|
1066
1039
|
const pub = readJmpPublicEnvFromProcess();
|
|
1067
1040
|
if (!pub.analyticsUrl || !pub.trackingKey) return null;
|
|
@@ -1100,6 +1073,28 @@ function createJmpContentClientFromServerEnv(overrides) {
|
|
|
1100
1073
|
if (!config) return null;
|
|
1101
1074
|
return createJmpContentClient(config);
|
|
1102
1075
|
}
|
|
1076
|
+
|
|
1077
|
+
// src/site-config.ts
|
|
1078
|
+
var registeredSite = null;
|
|
1079
|
+
function defineJmpSite(definition) {
|
|
1080
|
+
registeredSite = definition;
|
|
1081
|
+
return definition;
|
|
1082
|
+
}
|
|
1083
|
+
function getJmpSiteDefinition() {
|
|
1084
|
+
return registeredSite;
|
|
1085
|
+
}
|
|
1086
|
+
function getJmpContentConfig() {
|
|
1087
|
+
const { analyticsUrl, trackingKey } = readJmpPublicEnvFromProcess();
|
|
1088
|
+
if (!analyticsUrl || !trackingKey) {
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
const site = getJmpSiteDefinition();
|
|
1092
|
+
return {
|
|
1093
|
+
baseUrl: analyticsUrl,
|
|
1094
|
+
trackingKey,
|
|
1095
|
+
contentWrappers: site?.contentWrappers ?? void 0
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1103
1098
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1104
1099
|
0 && (module.exports = {
|
|
1105
1100
|
BOOKING_LINK_HOSTS,
|