@jmp-technologies/analytics 0.1.16 → 0.1.18
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/README.md +3 -1
- package/dist/{chunk-AEQGPTDY.js → chunk-SOHYQ5GT.js} +284 -79
- package/dist/chunk-SOHYQ5GT.js.map +1 -0
- package/dist/{chunk-P4IKTSPX.js → chunk-W7CQ5VG2.js} +2 -2
- package/dist/index.cjs +290 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -14
- package/dist/index.d.ts +70 -14
- package/dist/index.js +15 -3
- package/dist/next.cjs +295 -86
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +13 -6
- package/dist/next.d.ts +13 -6
- package/dist/next.js +27 -13
- package/dist/next.js.map +1 -1
- package/dist/react.cjs +263 -69
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +2 -2
- package/docs/legal-and-privacy.md +1 -0
- package/package.json +1 -1
- package/dist/chunk-AEQGPTDY.js.map +0 -1
- /package/dist/{chunk-P4IKTSPX.js.map → chunk-W7CQ5VG2.js.map} +0 -0
package/dist/react.cjs
CHANGED
|
@@ -33,7 +33,6 @@ var JMP_SESSION_ID_KEY = "jmp_analytics_sid";
|
|
|
33
33
|
var JMP_LANDING_REFERER_KEY = "jmp_analytics_landing_referer";
|
|
34
34
|
var JMP_LANDING_REFERER_COOKIE = "jmp_lr";
|
|
35
35
|
var JMP_UTM_SESSION_KEY = "jmp_analytics_utm";
|
|
36
|
-
var JMP_EARLY_BEACON_PATH_KEY = "jmp_analytics_early_beacon_path";
|
|
37
36
|
var LANDING_REFERER_COOKIE_MAX_AGE_SEC = 30 * 60;
|
|
38
37
|
var JMP_UTM_PARAM_KEYS = [
|
|
39
38
|
"utm_source",
|
|
@@ -119,11 +118,9 @@ function captureLandingAttribution(siteHostname) {
|
|
|
119
118
|
const preservedReferer = storedReferer || cookieReferer;
|
|
120
119
|
const currentReferer = document.referrer?.trim() || null;
|
|
121
120
|
if (currentReferer && isExternalReferrerUrl(currentReferer, siteHostname)) {
|
|
122
|
-
|
|
123
|
-
sessionStorage.setItem(JMP_LANDING_REFERER_KEY, currentReferer);
|
|
124
|
-
}
|
|
121
|
+
sessionStorage.setItem(JMP_LANDING_REFERER_KEY, currentReferer);
|
|
125
122
|
writeLandingRefererCookie(currentReferer, siteHostname);
|
|
126
|
-
landingReferer =
|
|
123
|
+
landingReferer = currentReferer;
|
|
127
124
|
} else if (preservedReferer) {
|
|
128
125
|
landingReferer = preservedReferer;
|
|
129
126
|
if (!storedReferer) {
|
|
@@ -168,75 +165,164 @@ function getOrCreateSessionIdFromStorage() {
|
|
|
168
165
|
return void 0;
|
|
169
166
|
}
|
|
170
167
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
|
|
169
|
+
// src/contact-link-classifier.ts
|
|
170
|
+
var BOOKING_LINK_HOSTS = [
|
|
171
|
+
"calendly.com",
|
|
172
|
+
"cal.com",
|
|
173
|
+
"acuityscheduling.com",
|
|
174
|
+
"calendar.google.com",
|
|
175
|
+
"calendar.app.google",
|
|
176
|
+
"bookings.microsoft.com",
|
|
177
|
+
"outlook.office.com",
|
|
178
|
+
"outlook.office365.com",
|
|
179
|
+
"book.ms",
|
|
180
|
+
"setmore.com",
|
|
181
|
+
"simplybook.me",
|
|
182
|
+
"oncehub.com",
|
|
183
|
+
"scheduleonce.com",
|
|
184
|
+
"youcanbook.me",
|
|
185
|
+
"savvycal.com",
|
|
186
|
+
"tidycal.com",
|
|
187
|
+
"zohobookings.com",
|
|
188
|
+
"square.site",
|
|
189
|
+
"squareup.com",
|
|
190
|
+
"meetings.hubspot.com",
|
|
191
|
+
"jobber.com",
|
|
192
|
+
"housecallpro.com",
|
|
193
|
+
"servicetitan.com",
|
|
194
|
+
"thryv.com",
|
|
195
|
+
"honeybook.com",
|
|
196
|
+
"dubsado.com",
|
|
197
|
+
"fresha.com",
|
|
198
|
+
"booksy.com",
|
|
199
|
+
"vagaro.com",
|
|
200
|
+
"mindbodyonline.com",
|
|
201
|
+
"mindbody.io",
|
|
202
|
+
"janeapp.com",
|
|
203
|
+
"cliniko.com",
|
|
204
|
+
"practicebetter.io"
|
|
205
|
+
];
|
|
206
|
+
var WHATSAPP_HOSTS = ["wa.me", "api.whatsapp.com", "whatsapp.com"];
|
|
207
|
+
function hostnameMatches(host, pattern) {
|
|
208
|
+
const h = host.toLowerCase();
|
|
209
|
+
const p = pattern.toLowerCase();
|
|
210
|
+
return h === p || h.endsWith(`.${p}`);
|
|
211
|
+
}
|
|
212
|
+
function isMapsHost(host, pathname) {
|
|
213
|
+
if (hostnameMatches(host, "maps.apple.com")) {
|
|
214
|
+
return true;
|
|
174
215
|
}
|
|
175
|
-
|
|
176
|
-
return
|
|
177
|
-
} catch {
|
|
178
|
-
return false;
|
|
216
|
+
if (hostnameMatches(host, "maps.app.goo.gl")) {
|
|
217
|
+
return true;
|
|
179
218
|
}
|
|
219
|
+
if (hostnameMatches(host, "google.com") || hostnameMatches(host, "google.co")) {
|
|
220
|
+
return pathname === "/maps" || pathname.startsWith("/maps/") || pathname.startsWith("/local");
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
180
223
|
}
|
|
181
|
-
function
|
|
182
|
-
|
|
183
|
-
|
|
224
|
+
function isWhatsAppHost(host, pathname) {
|
|
225
|
+
for (const pattern of WHATSAPP_HOSTS) {
|
|
226
|
+
if (!hostnameMatches(host, pattern)) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (pattern === "whatsapp.com") {
|
|
230
|
+
return pathname.startsWith("/send");
|
|
231
|
+
}
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
function isBookingHost(host) {
|
|
237
|
+
for (const pattern of BOOKING_LINK_HOSTS) {
|
|
238
|
+
if (hostnameMatches(host, pattern)) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
function classifyContactHref(href) {
|
|
245
|
+
const raw = href.trim();
|
|
246
|
+
if (!raw || raw.startsWith("#") || raw.startsWith("javascript:")) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const lower = raw.toLowerCase();
|
|
250
|
+
if (lower.startsWith("tel:")) {
|
|
251
|
+
return { kind: "call" };
|
|
252
|
+
}
|
|
253
|
+
if (lower.startsWith("mailto:")) {
|
|
254
|
+
return { kind: "email" };
|
|
184
255
|
}
|
|
256
|
+
if (lower.startsWith("sms:") || lower.startsWith("smsto:")) {
|
|
257
|
+
return { kind: "contact_click", channel: "sms", linkHost: null };
|
|
258
|
+
}
|
|
259
|
+
if (lower.startsWith("geo:")) {
|
|
260
|
+
return { kind: "contact_click", channel: "maps", linkHost: null };
|
|
261
|
+
}
|
|
262
|
+
if (raw.startsWith("/") || raw.startsWith("?")) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
let url;
|
|
185
266
|
try {
|
|
186
|
-
|
|
267
|
+
url = new URL(raw);
|
|
187
268
|
} catch {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
272
|
+
return null;
|
|
188
273
|
}
|
|
274
|
+
const host = url.hostname.toLowerCase();
|
|
275
|
+
const pathname = url.pathname.toLowerCase();
|
|
276
|
+
if (isWhatsAppHost(host, pathname)) {
|
|
277
|
+
return { kind: "contact_click", channel: "whatsapp", linkHost: host };
|
|
278
|
+
}
|
|
279
|
+
if (isMapsHost(host, pathname)) {
|
|
280
|
+
return { kind: "contact_click", channel: "maps", linkHost: host };
|
|
281
|
+
}
|
|
282
|
+
if (isBookingHost(host)) {
|
|
283
|
+
return { kind: "contact_click", channel: "booking", linkHost: host };
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
189
286
|
}
|
|
190
|
-
|
|
191
|
-
|
|
287
|
+
|
|
288
|
+
// src/index.ts
|
|
289
|
+
var JMP_TRACKING_CONSENT_STORAGE_KEY = "jmp_analytics_consent_v1";
|
|
290
|
+
var moduleTrackingConsentGranted = true;
|
|
291
|
+
var trackingConsentListeners = /* @__PURE__ */ new Set();
|
|
292
|
+
function readStoredTrackingConsent() {
|
|
293
|
+
if (typeof window === "undefined") {
|
|
192
294
|
return false;
|
|
193
295
|
}
|
|
194
|
-
|
|
195
|
-
|
|
296
|
+
try {
|
|
297
|
+
return localStorage.getItem(JMP_TRACKING_CONSENT_STORAGE_KEY) === "granted";
|
|
298
|
+
} catch {
|
|
196
299
|
return false;
|
|
197
300
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const payload = {
|
|
209
|
-
type: "page_view",
|
|
210
|
-
path,
|
|
211
|
-
trackingKey: args.trackingKey,
|
|
212
|
-
...sessionId ? { sessionId } : {},
|
|
213
|
-
metadata
|
|
214
|
-
};
|
|
215
|
-
const headers = {
|
|
216
|
-
"Content-Type": "application/json",
|
|
217
|
-
"X-JMP-Tracking-Key": args.trackingKey
|
|
218
|
-
};
|
|
219
|
-
if (landingReferer) {
|
|
220
|
-
headers["X-JMP-Visitor-Referer"] = landingReferer;
|
|
221
|
-
}
|
|
222
|
-
markEarlyBeaconSentForPath(path);
|
|
223
|
-
void fetch(`${args.baseUrl.replace(/\/$/, "")}/api/events`, {
|
|
224
|
-
method: "POST",
|
|
225
|
-
headers,
|
|
226
|
-
body: JSON.stringify(payload),
|
|
227
|
-
keepalive: true,
|
|
228
|
-
mode: "cors",
|
|
229
|
-
credentials: "omit"
|
|
230
|
-
}).catch(() => {
|
|
301
|
+
}
|
|
302
|
+
function isJmpTrackingConsentGranted() {
|
|
303
|
+
return moduleTrackingConsentGranted;
|
|
304
|
+
}
|
|
305
|
+
function setJmpTrackingConsent(granted) {
|
|
306
|
+
if (moduleTrackingConsentGranted === granted) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
moduleTrackingConsentGranted = granted;
|
|
310
|
+
if (typeof window !== "undefined") {
|
|
231
311
|
try {
|
|
232
|
-
|
|
312
|
+
localStorage.setItem(
|
|
313
|
+
JMP_TRACKING_CONSENT_STORAGE_KEY,
|
|
314
|
+
granted ? "granted" : "denied"
|
|
315
|
+
);
|
|
233
316
|
} catch {
|
|
234
317
|
}
|
|
235
|
-
|
|
236
|
-
|
|
318
|
+
if (granted) {
|
|
319
|
+
captureLandingAttribution(window.location.hostname);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
for (const listener of trackingConsentListeners) {
|
|
323
|
+
listener();
|
|
324
|
+
}
|
|
237
325
|
}
|
|
238
|
-
|
|
239
|
-
// src/index.ts
|
|
240
326
|
function captureAndGetLandingReferer() {
|
|
241
327
|
if (typeof window === "undefined") {
|
|
242
328
|
return null;
|
|
@@ -286,9 +372,16 @@ function createJmpAnalytics(config) {
|
|
|
286
372
|
);
|
|
287
373
|
}
|
|
288
374
|
const fetchFn = resolvedFetch;
|
|
375
|
+
moduleTrackingConsentGranted = config.requireConsent ? readStoredTrackingConsent() : true;
|
|
376
|
+
function canSend() {
|
|
377
|
+
return moduleTrackingConsentGranted;
|
|
378
|
+
}
|
|
289
379
|
let lastDedupePath = "";
|
|
290
380
|
let lastDedupeAt = 0;
|
|
291
381
|
async function send(body) {
|
|
382
|
+
if (!canSend()) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
292
385
|
const url = `${baseUrl}/api/events`;
|
|
293
386
|
const headers = {
|
|
294
387
|
"Content-Type": "application/json",
|
|
@@ -344,6 +437,9 @@ function createJmpAnalytics(config) {
|
|
|
344
437
|
}
|
|
345
438
|
}
|
|
346
439
|
async function sendLead(body) {
|
|
440
|
+
if (!canSend()) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
347
443
|
const url = `${baseUrl}/api/leads`;
|
|
348
444
|
const headers = {
|
|
349
445
|
"Content-Type": "application/json",
|
|
@@ -406,6 +502,9 @@ function createJmpAnalytics(config) {
|
|
|
406
502
|
return false;
|
|
407
503
|
}
|
|
408
504
|
function trackPageView(options) {
|
|
505
|
+
if (!canSend()) {
|
|
506
|
+
return Promise.resolve();
|
|
507
|
+
}
|
|
409
508
|
const pathFromOptions = options?.path?.trim();
|
|
410
509
|
let pathname = "/";
|
|
411
510
|
let search = "";
|
|
@@ -421,9 +520,6 @@ function createJmpAnalytics(config) {
|
|
|
421
520
|
search = window.location.search.replace(/^\?/, "");
|
|
422
521
|
}
|
|
423
522
|
const path = search ? `${pathname}?${search}` : pathname;
|
|
424
|
-
if (wasEarlyBeaconSentForPath(path)) {
|
|
425
|
-
return Promise.resolve();
|
|
426
|
-
}
|
|
427
523
|
if (shouldDedupePageView(pathname)) {
|
|
428
524
|
return Promise.resolve();
|
|
429
525
|
}
|
|
@@ -474,6 +570,20 @@ function createJmpAnalytics(config) {
|
|
|
474
570
|
metadata: metadata ?? {}
|
|
475
571
|
});
|
|
476
572
|
}
|
|
573
|
+
function trackContactClick(path, options) {
|
|
574
|
+
const metadata = {
|
|
575
|
+
...options.metadata && typeof options.metadata === "object" ? options.metadata : {},
|
|
576
|
+
channel: options.channel
|
|
577
|
+
};
|
|
578
|
+
if (options.linkHost) {
|
|
579
|
+
metadata.linkHost = options.linkHost;
|
|
580
|
+
}
|
|
581
|
+
return send({
|
|
582
|
+
type: "contact_click",
|
|
583
|
+
path: path || "/",
|
|
584
|
+
metadata
|
|
585
|
+
});
|
|
586
|
+
}
|
|
477
587
|
function trackLead(options) {
|
|
478
588
|
const path = options?.path ?? (typeof window !== "undefined" ? window.location.pathname : "/");
|
|
479
589
|
const body = { path: path || "/" };
|
|
@@ -506,7 +616,20 @@ function createJmpAnalytics(config) {
|
|
|
506
616
|
if (type === "call_click") {
|
|
507
617
|
return trackCallClick(payload.path, payload.metadata);
|
|
508
618
|
}
|
|
509
|
-
|
|
619
|
+
if (type === "email_click") {
|
|
620
|
+
return trackEmailClick(payload.path, payload.metadata);
|
|
621
|
+
}
|
|
622
|
+
const meta = payload.metadata ?? {};
|
|
623
|
+
const channel = typeof meta.channel === "string" ? meta.channel : "";
|
|
624
|
+
const linkHost = typeof meta.linkHost === "string" ? meta.linkHost : null;
|
|
625
|
+
if (channel !== "sms" && channel !== "whatsapp" && channel !== "maps" && channel !== "booking") {
|
|
626
|
+
return Promise.resolve();
|
|
627
|
+
}
|
|
628
|
+
return trackContactClick(payload.path, {
|
|
629
|
+
channel,
|
|
630
|
+
linkHost,
|
|
631
|
+
metadata: meta
|
|
632
|
+
});
|
|
510
633
|
}
|
|
511
634
|
function subscribeToSpaNavigation() {
|
|
512
635
|
if (typeof window === "undefined") {
|
|
@@ -540,12 +663,75 @@ function createJmpAnalytics(config) {
|
|
|
540
663
|
history.replaceState = replace;
|
|
541
664
|
};
|
|
542
665
|
}
|
|
543
|
-
|
|
666
|
+
let lastContactClickKey = "";
|
|
667
|
+
let lastContactClickAt = 0;
|
|
668
|
+
function subscribeContactLinkAutoTrack() {
|
|
669
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
670
|
+
return () => {
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
const handler = (event) => {
|
|
674
|
+
if (!canSend()) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const target = event.target;
|
|
678
|
+
if (!(target instanceof Element)) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const anchor = target.closest("a[href]");
|
|
682
|
+
if (!(anchor instanceof HTMLAnchorElement)) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
if (anchor.closest("[data-jmp-no-track]")) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const href = anchor.getAttribute("href") ?? "";
|
|
689
|
+
const classified = classifyContactHref(href);
|
|
690
|
+
if (!classified) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const path = window.location.pathname;
|
|
694
|
+
if (classified.kind === "call") {
|
|
695
|
+
if (dedupeContactClick(`call_click:${path}`)) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
void trackCallClick(path);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (classified.kind === "email") {
|
|
702
|
+
if (dedupeContactClick(`email_click:${path}`)) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
void trackEmailClick(path);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (dedupeContactClick(`contact_click:${classified.channel}:${path}`)) {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
void trackContactClick(path, {
|
|
712
|
+
channel: classified.channel,
|
|
713
|
+
linkHost: classified.linkHost
|
|
714
|
+
});
|
|
715
|
+
};
|
|
716
|
+
document.addEventListener("click", handler, { capture: true });
|
|
717
|
+
return () => {
|
|
718
|
+
document.removeEventListener("click", handler, { capture: true });
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function dedupeContactClick(key) {
|
|
722
|
+
const now = Date.now();
|
|
723
|
+
if (key === lastContactClickKey && now - lastContactClickAt < 1e3) {
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
lastContactClickKey = key;
|
|
727
|
+
lastContactClickAt = now;
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
if (typeof window !== "undefined" && canSend()) {
|
|
544
731
|
captureLandingAttribution(window.location.hostname);
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
});
|
|
732
|
+
}
|
|
733
|
+
if (typeof window !== "undefined" && config.autoTrackContactLinks && canSend()) {
|
|
734
|
+
subscribeContactLinkAutoTrack();
|
|
549
735
|
}
|
|
550
736
|
return {
|
|
551
737
|
track,
|
|
@@ -553,8 +739,12 @@ function createJmpAnalytics(config) {
|
|
|
553
739
|
trackFormSubmit,
|
|
554
740
|
trackCallClick,
|
|
555
741
|
trackEmailClick,
|
|
742
|
+
trackContactClick,
|
|
556
743
|
trackLead,
|
|
557
|
-
subscribeToSpaNavigation
|
|
744
|
+
subscribeToSpaNavigation,
|
|
745
|
+
subscribeContactLinkAutoTrack,
|
|
746
|
+
setTrackingConsent: setJmpTrackingConsent,
|
|
747
|
+
isTrackingConsentGranted: isJmpTrackingConsentGranted
|
|
558
748
|
};
|
|
559
749
|
}
|
|
560
750
|
var DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS = 800;
|
|
@@ -567,11 +757,15 @@ function getJmpBrowserAnalyticsConfigFromEnv(overrides) {
|
|
|
567
757
|
const baseUrl = readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_URL");
|
|
568
758
|
const trackingKey = readProcessEnv("NEXT_PUBLIC_JMP_TRACKING_KEY");
|
|
569
759
|
if (!baseUrl || !trackingKey) return null;
|
|
760
|
+
const requireConsentFromEnv = readProcessEnv("NEXT_PUBLIC_JMP_ANALYTICS_REQUIRE_CONSENT") === "true";
|
|
761
|
+
const autoTrackContactLinksFromEnv = readProcessEnv("NEXT_PUBLIC_JMP_AUTO_CONTACT_LINKS") === "true";
|
|
570
762
|
return {
|
|
571
763
|
baseUrl,
|
|
572
764
|
trackingKey,
|
|
573
765
|
pageViewDedupeMs: DEFAULT_JMP_BROWSER_PAGE_VIEW_DEDUPE_MS,
|
|
574
|
-
...overrides
|
|
766
|
+
...overrides,
|
|
767
|
+
requireConsent: overrides?.requireConsent ?? requireConsentFromEnv,
|
|
768
|
+
autoTrackContactLinks: overrides?.autoTrackContactLinks ?? autoTrackContactLinksFromEnv
|
|
575
769
|
};
|
|
576
770
|
}
|
|
577
771
|
var jmpAnalyticsBrowserSingleton = null;
|