@indiebob/tracker 0.3.0 → 0.4.1
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/CHANGELOG.md +32 -0
- package/README.md +112 -508
- package/dist/index.cjs +187 -94
- package/dist/index.d.mts +36 -7
- package/dist/index.d.ts +36 -7
- package/dist/index.global.js +2 -2
- package/dist/index.js +187 -94
- package/dist/server.d.mts +17 -2
- package/dist/server.d.ts +17 -2
- package/dist/server.js +15 -2
- package/dist/server.mjs +15 -2
- package/dist/vue.cjs +187 -94
- package/dist/vue.global.js +2 -2
- package/dist/vue.js +187 -94
- package/package.json +2 -2
- package/dist/chunk-4IK5PET7.js +0 -2
- package/dist/chunk-DZOEO647.js +0 -2
- package/dist/chunk-XKQYO25W.js +0 -2
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
// src/core/config.ts
|
|
4
4
|
var DEFAULT_ENDPOINT = "https://www.indiebob.com/api/collect";
|
|
5
|
+
var OPTIONS = "projectId endpoint debug respectDnt respectGpc autoDetect hashMode persistence";
|
|
6
|
+
var AUTO_OPTIONS = "signupPaths loginPaths pricingPaths checkoutPaths successPaths onboardingPaths contactPaths dataAttributes formSubmit outboundClicks fileDownloads";
|
|
7
|
+
function warnUnknown(obj, known, where) {
|
|
8
|
+
for (const k of Object.keys(obj)) {
|
|
9
|
+
if (!known.split(" ").includes(k)) console.warn(`[IndieBob] Unknown option ${where}"${k}" ignored`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
5
12
|
function resolveConfig(input) {
|
|
6
13
|
var _a, _b, _c, _d, _e, _f;
|
|
7
14
|
if (!input.projectId || !input.projectId.startsWith("proj_")) {
|
|
@@ -9,6 +16,11 @@ function resolveConfig(input) {
|
|
|
9
16
|
`[IndieBob] Invalid projectId "${input.projectId}". Must start with "proj_".`
|
|
10
17
|
);
|
|
11
18
|
}
|
|
19
|
+
warnUnknown(input, OPTIONS, "");
|
|
20
|
+
if (typeof input.autoDetect === "object") warnUnknown(input.autoDetect, AUTO_OPTIONS, "autoDetect.");
|
|
21
|
+
if (input.persistence !== void 0 && input.persistence !== "local" && input.persistence !== "none") {
|
|
22
|
+
console.warn(`[IndieBob] persistence "${String(input.persistence)}" is not 'none' or 'local'; using 'none'`);
|
|
23
|
+
}
|
|
12
24
|
return {
|
|
13
25
|
projectId: input.projectId,
|
|
14
26
|
endpoint: (_a = input.endpoint) != null ? _a : DEFAULT_ENDPOINT,
|
|
@@ -16,7 +28,8 @@ function resolveConfig(input) {
|
|
|
16
28
|
respectDnt: (_c = input.respectDnt) != null ? _c : true,
|
|
17
29
|
respectGpc: (_d = input.respectGpc) != null ? _d : true,
|
|
18
30
|
autoDetect: (_e = input.autoDetect) != null ? _e : true,
|
|
19
|
-
hashMode: (_f = input.hashMode) != null ? _f : false
|
|
31
|
+
hashMode: (_f = input.hashMode) != null ? _f : false,
|
|
32
|
+
persistence: input.persistence === "local" ? "local" : "none"
|
|
20
33
|
};
|
|
21
34
|
}
|
|
22
35
|
|
|
@@ -279,7 +292,6 @@ async function sendBatch(payload, config, isUnloading = false) {
|
|
|
279
292
|
var OFFLINE_KEY = "_ib_queue";
|
|
280
293
|
var MAX_BATCH = 10;
|
|
281
294
|
var FLUSH_INTERVAL_MS = 5e3;
|
|
282
|
-
var MAX_OFFLINE = 50;
|
|
283
295
|
var EventQueue = class {
|
|
284
296
|
/**
|
|
285
297
|
* @param beforeHide runs right before the hide flush, so an event that
|
|
@@ -287,7 +299,9 @@ var EventQueue = class {
|
|
|
287
299
|
*/
|
|
288
300
|
constructor(config, beforeHide) {
|
|
289
301
|
this.b = [];
|
|
290
|
-
|
|
302
|
+
/** Failed events awaiting a retry, when nothing may be stored ('none'). */
|
|
303
|
+
this.j = [];
|
|
304
|
+
this.k = false;
|
|
291
305
|
this.a = config;
|
|
292
306
|
setInterval(() => {
|
|
293
307
|
if (this.b.length > 0) void this.flush();
|
|
@@ -302,10 +316,10 @@ var EventQueue = class {
|
|
|
302
316
|
});
|
|
303
317
|
window.addEventListener("pagehide", hide);
|
|
304
318
|
window.addEventListener("online", () => {
|
|
305
|
-
void this.
|
|
319
|
+
void this.o();
|
|
306
320
|
});
|
|
307
321
|
if (navigator.onLine) {
|
|
308
|
-
void this.
|
|
322
|
+
void this.o();
|
|
309
323
|
}
|
|
310
324
|
}
|
|
311
325
|
push(event) {
|
|
@@ -315,32 +329,49 @@ var EventQueue = class {
|
|
|
315
329
|
}
|
|
316
330
|
}
|
|
317
331
|
async flush(isUnloading = false) {
|
|
318
|
-
if (this.
|
|
332
|
+
if (this.k && !isUnloading) return;
|
|
319
333
|
if (this.b.length === 0) return;
|
|
320
|
-
this.
|
|
334
|
+
this.k = true;
|
|
321
335
|
const batch = this.b.splice(0);
|
|
322
336
|
const payload = { batch };
|
|
323
337
|
const ok = await sendBatch(payload, this.a, isUnloading);
|
|
324
338
|
if (!ok) {
|
|
325
|
-
|
|
326
|
-
const trimmed = [...this.n(), ...batch].slice(-MAX_OFFLINE);
|
|
327
|
-
localStorage.setItem(OFFLINE_KEY, JSON.stringify(trimmed));
|
|
328
|
-
} catch (e) {
|
|
329
|
-
}
|
|
339
|
+
this.t([...this.p(), ...batch].slice(-50));
|
|
330
340
|
}
|
|
331
|
-
this.
|
|
341
|
+
this.k = false;
|
|
332
342
|
}
|
|
333
|
-
async
|
|
334
|
-
const stored = this.
|
|
343
|
+
async o() {
|
|
344
|
+
const stored = this.p();
|
|
335
345
|
if (stored.length === 0) return;
|
|
346
|
+
this.clearStored();
|
|
347
|
+
const payload = { batch: stored };
|
|
348
|
+
await sendBatch(payload, this.a);
|
|
349
|
+
}
|
|
350
|
+
/** Give events still waiting the ids they lacked (storage was just allowed). */
|
|
351
|
+
stamp(anonymousId, sessionId) {
|
|
352
|
+
for (const e of this.b) if (!e.anonymousId) Object.assign(e, { anonymousId, sessionId });
|
|
353
|
+
}
|
|
354
|
+
/** Drop the retry queue, wherever it is kept. */
|
|
355
|
+
clearStored() {
|
|
356
|
+
this.j = [];
|
|
336
357
|
try {
|
|
337
358
|
localStorage.removeItem(OFFLINE_KEY);
|
|
338
359
|
} catch (e) {
|
|
339
360
|
}
|
|
340
|
-
const payload = { batch: stored };
|
|
341
|
-
await sendBatch(payload, this.a);
|
|
342
361
|
}
|
|
343
|
-
|
|
362
|
+
/** In localStorage only with persistence 'local'; in memory otherwise. */
|
|
363
|
+
t(events) {
|
|
364
|
+
if (this.a.persistence !== "local") {
|
|
365
|
+
this.j = events;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
localStorage.setItem(OFFLINE_KEY, JSON.stringify(events));
|
|
370
|
+
} catch (e) {
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
p() {
|
|
374
|
+
if (this.a.persistence !== "local") return this.j;
|
|
344
375
|
try {
|
|
345
376
|
const raw = localStorage.getItem(OFFLINE_KEY);
|
|
346
377
|
if (!raw) return [];
|
|
@@ -351,9 +382,40 @@ var EventQueue = class {
|
|
|
351
382
|
}
|
|
352
383
|
};
|
|
353
384
|
|
|
385
|
+
// src/core/url.ts
|
|
386
|
+
var KEEP = /^(utm_\w+|ref)$/;
|
|
387
|
+
function cleanSearch(search) {
|
|
388
|
+
const out = new URLSearchParams();
|
|
389
|
+
new URLSearchParams(search).forEach((v, k) => {
|
|
390
|
+
if (KEEP.test(k)) out.append(k, v);
|
|
391
|
+
});
|
|
392
|
+
const s = out.toString();
|
|
393
|
+
return s ? "?" + s : "";
|
|
394
|
+
}
|
|
395
|
+
function cleanHash(hash) {
|
|
396
|
+
const route = hash.split("?")[0];
|
|
397
|
+
return route.includes("=") ? "" : route;
|
|
398
|
+
}
|
|
399
|
+
function cleanUrl(href, hashMode) {
|
|
400
|
+
try {
|
|
401
|
+
const u = new URL(href);
|
|
402
|
+
return u.origin + u.pathname + cleanSearch(u.search) + (hashMode ? cleanHash(u.hash) : "");
|
|
403
|
+
} catch (e) {
|
|
404
|
+
return "";
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function cleanReferrer(ref) {
|
|
408
|
+
try {
|
|
409
|
+
const u = new URL(ref);
|
|
410
|
+
return u.origin + u.pathname;
|
|
411
|
+
} catch (e) {
|
|
412
|
+
return "";
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
354
416
|
// src/collectors/page.ts
|
|
355
417
|
function pagePath(loc, hashMode) {
|
|
356
|
-
return loc.pathname + loc.search + (hashMode ? loc.hash : "");
|
|
418
|
+
return loc.pathname + cleanSearch(loc.search) + (hashMode ? cleanHash(loc.hash) : "");
|
|
357
419
|
}
|
|
358
420
|
function patchHistory(onNavigate, hashMode) {
|
|
359
421
|
if (typeof window === "undefined") return () => {
|
|
@@ -379,9 +441,9 @@ function patchHistory(onNavigate, hashMode) {
|
|
|
379
441
|
|
|
380
442
|
// src/collectors/context.ts
|
|
381
443
|
var PAYMENT_HOST = /^https?:\/\/[^/]*(stripe\.com|paypal\.com|paddle\.com|lemonsqueezy\.com|gumroad\.com)(\/|$)/i;
|
|
382
|
-
function buildContext(overrides, hashMode) {
|
|
383
|
-
var _a, _b, _c, _d, _e, _f, _g, _h
|
|
384
|
-
const sdkVersion = "0.
|
|
444
|
+
function buildContext(overrides, hashMode, local) {
|
|
445
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
446
|
+
const sdkVersion = "0.4.1" ;
|
|
385
447
|
if (typeof window === "undefined") {
|
|
386
448
|
return {
|
|
387
449
|
page: { path: (_a = overrides == null ? void 0 : overrides.path) != null ? _a : "", url: "", title: (_b = overrides == null ? void 0 : overrides.title) != null ? _b : "", referrer: (_c = overrides == null ? void 0 : overrides.referrer) != null ? _c : "" },
|
|
@@ -396,18 +458,19 @@ function buildContext(overrides, hashMode) {
|
|
|
396
458
|
return {
|
|
397
459
|
page: {
|
|
398
460
|
path: (_e = overrides == null ? void 0 : overrides.path) != null ? _e : pagePath(location, hashMode),
|
|
399
|
-
url: location.href,
|
|
461
|
+
url: cleanUrl(location.href, hashMode),
|
|
400
462
|
title: (_f = overrides == null ? void 0 : overrides.title) != null ? _f : document.title,
|
|
401
463
|
// First touch survives the round trip: a /thanks page reached from Stripe
|
|
402
|
-
// reports the referrer
|
|
403
|
-
// — otherwise every sale files itself under the
|
|
404
|
-
|
|
464
|
+
// reports the referrer the visit STARTED with (stored with 'local'), and
|
|
465
|
+
// never checkout.stripe.com — otherwise every sale files itself under the
|
|
466
|
+
// payment host. Without storage the server's visit already holds it.
|
|
467
|
+
referrer: cleanReferrer(liveReferrer === "" || PAYMENT_HOST.test(liveReferrer) ? local && getStoredReferrer() || "" : liveReferrer)
|
|
405
468
|
},
|
|
406
469
|
// Device class from the viewport width; still required by /api/collect
|
|
407
470
|
device: { width, height: innerHeight, type: width <= 768 ? "mobile" : width <= 1024 ? "tablet" : "desktop" },
|
|
408
|
-
utm: (
|
|
471
|
+
utm: (_g = parseUtm(location.search)) != null ? _g : local ? getStoredFirstUtm() : void 0,
|
|
409
472
|
locale: navigator.language,
|
|
410
|
-
timezone: (
|
|
473
|
+
timezone: (_h = Intl.DateTimeFormat().resolvedOptions().timeZone) != null ? _h : "UTC",
|
|
411
474
|
sdkVersion
|
|
412
475
|
};
|
|
413
476
|
}
|
|
@@ -578,9 +641,9 @@ function trackEngagement() {
|
|
|
578
641
|
window.addEventListener("scroll", sample);
|
|
579
642
|
reset();
|
|
580
643
|
return {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
644
|
+
u: sample,
|
|
645
|
+
w: reset,
|
|
646
|
+
x(measure) {
|
|
584
647
|
if (measure) sample();
|
|
585
648
|
tick();
|
|
586
649
|
if (!shouldSendEngagement(ms, scroll, sent)) return null;
|
|
@@ -599,17 +662,17 @@ function unseen() {
|
|
|
599
662
|
}
|
|
600
663
|
var IndieBob = class _IndieBob {
|
|
601
664
|
constructor(config) {
|
|
602
|
-
this.f = null;
|
|
603
|
-
this.k = null;
|
|
604
665
|
this.g = null;
|
|
666
|
+
this.m = null;
|
|
667
|
+
this.h = null;
|
|
605
668
|
/** Engagement meter for the current page (browser only). */
|
|
606
|
-
this.
|
|
669
|
+
this.e = null;
|
|
607
670
|
/** Context of the page last counted — what an engagement event describes. */
|
|
608
|
-
this.
|
|
671
|
+
this.n = null;
|
|
609
672
|
/** A first pageview waiting for the tab to become visible. */
|
|
610
|
-
this.
|
|
673
|
+
this.i = false;
|
|
611
674
|
this.a = config;
|
|
612
|
-
this.b = new EventQueue(config, () => this.
|
|
675
|
+
this.b = new EventQueue(config, () => this.s(true));
|
|
613
676
|
if (typeof window !== "undefined") {
|
|
614
677
|
try {
|
|
615
678
|
if (applyIgnore(window.location.search, localStorage)) {
|
|
@@ -617,17 +680,17 @@ var IndieBob = class _IndieBob {
|
|
|
617
680
|
}
|
|
618
681
|
} catch (e) {
|
|
619
682
|
}
|
|
620
|
-
initAttribution(window.location.search, document.referrer);
|
|
621
|
-
this.
|
|
683
|
+
if (this.c && isTrackingAllowed(config)) initAttribution(window.location.search, document.referrer);
|
|
684
|
+
this.e = trackEngagement();
|
|
622
685
|
window.addEventListener("pageshow", (e) => {
|
|
623
|
-
if (e.persisted && this.
|
|
624
|
-
this.
|
|
625
|
-
this.page(this.
|
|
686
|
+
if (e.persisted && this.f !== void 0) {
|
|
687
|
+
this.f = void 0;
|
|
688
|
+
this.page(this.q);
|
|
626
689
|
}
|
|
627
690
|
});
|
|
628
691
|
if (config.autoDetect !== false) {
|
|
629
692
|
const opts = typeof config.autoDetect === "object" ? config.autoDetect : {};
|
|
630
|
-
this.
|
|
693
|
+
this.m = initClickTracking(
|
|
631
694
|
(event, properties) => this.track(event, properties),
|
|
632
695
|
opts.dataAttributes !== false,
|
|
633
696
|
opts.fileDownloads !== false,
|
|
@@ -662,14 +725,14 @@ var IndieBob = class _IndieBob {
|
|
|
662
725
|
page(overrides) {
|
|
663
726
|
var _a;
|
|
664
727
|
if (!isTrackingAllowed(this.a)) return;
|
|
665
|
-
if (this.
|
|
666
|
-
this.
|
|
667
|
-
if (!this.
|
|
668
|
-
this.
|
|
728
|
+
if (this.f === void 0 && unseen()) {
|
|
729
|
+
this.r = overrides;
|
|
730
|
+
if (!this.i) {
|
|
731
|
+
this.i = true;
|
|
669
732
|
const go = () => {
|
|
670
|
-
if (this.
|
|
671
|
-
this.
|
|
672
|
-
this.page(this.
|
|
733
|
+
if (this.i && !unseen()) {
|
|
734
|
+
this.i = false;
|
|
735
|
+
this.page(this.r);
|
|
673
736
|
}
|
|
674
737
|
};
|
|
675
738
|
document.addEventListener("visibilitychange", go);
|
|
@@ -677,34 +740,34 @@ var IndieBob = class _IndieBob {
|
|
|
677
740
|
}
|
|
678
741
|
return;
|
|
679
742
|
}
|
|
680
|
-
const ctx = buildContext(overrides, this.a.hashMode);
|
|
681
|
-
if (ctx.page.path === this.
|
|
682
|
-
this.
|
|
683
|
-
this.
|
|
684
|
-
this.
|
|
685
|
-
this.
|
|
686
|
-
if (this.
|
|
687
|
-
this.
|
|
688
|
-
setTimeout(this.
|
|
743
|
+
const ctx = buildContext(overrides, this.a.hashMode, this.c);
|
|
744
|
+
if (ctx.page.path === this.f) return;
|
|
745
|
+
this.s(false);
|
|
746
|
+
this.f = ctx.page.path;
|
|
747
|
+
this.q = overrides;
|
|
748
|
+
this.n = ctx;
|
|
749
|
+
if (this.e) {
|
|
750
|
+
this.e.w();
|
|
751
|
+
setTimeout(this.e.u, 1e3);
|
|
689
752
|
}
|
|
690
|
-
this.b.push(this.
|
|
753
|
+
this.b.push(this.d("page", { context: ctx }));
|
|
691
754
|
if (this.a.autoDetect !== false) {
|
|
692
755
|
const path = ctx.page.path;
|
|
693
756
|
const opts = typeof this.a.autoDetect === "object" ? this.a.autoDetect : {};
|
|
694
757
|
const intent = detectIntent(path, opts);
|
|
695
758
|
if (intent) {
|
|
696
|
-
this.b.push(this.
|
|
759
|
+
this.b.push(this.d("track", {
|
|
697
760
|
event: intent,
|
|
698
761
|
properties: { auto: true },
|
|
699
762
|
context: ctx
|
|
700
763
|
}));
|
|
701
764
|
log(this.a, `auto-detected: ${intent} on ${path}`);
|
|
702
765
|
if (opts.formSubmit !== false) {
|
|
703
|
-
(_a = this.
|
|
766
|
+
(_a = this.h) == null ? void 0 : _a.call(this);
|
|
704
767
|
const submitEvent = intentToSubmitEvent(intent);
|
|
705
768
|
if (submitEvent) {
|
|
706
|
-
this.
|
|
707
|
-
this.b.push(this.
|
|
769
|
+
this.h = observeFormSubmits(submitEvent, (eventName) => {
|
|
770
|
+
this.b.push(this.d("track", {
|
|
708
771
|
event: eventName,
|
|
709
772
|
properties: { auto: true }
|
|
710
773
|
}));
|
|
@@ -724,7 +787,7 @@ var IndieBob = class _IndieBob {
|
|
|
724
787
|
*/
|
|
725
788
|
track(event, properties) {
|
|
726
789
|
if (!isTrackingAllowed(this.a)) return;
|
|
727
|
-
this.b.push(this.
|
|
790
|
+
this.b.push(this.d("track", { event, properties }));
|
|
728
791
|
}
|
|
729
792
|
/**
|
|
730
793
|
* Associate the current anonymous visitor with a known user ID.
|
|
@@ -749,18 +812,19 @@ var IndieBob = class _IndieBob {
|
|
|
749
812
|
*/
|
|
750
813
|
identify(userId, traits, options) {
|
|
751
814
|
if (!isTrackingAllowed(this.a)) return;
|
|
752
|
-
this.
|
|
753
|
-
this.b.push(this.
|
|
754
|
-
|
|
755
|
-
|
|
815
|
+
this.l = userId;
|
|
816
|
+
this.b.push(this.d("identify", { traits, userId }));
|
|
817
|
+
const known = typeof (options == null ? void 0 : options.isNew) === "boolean";
|
|
818
|
+
if (this.a.autoDetect !== false && (known || this.c)) {
|
|
819
|
+
const first = known ? options.isNew : isFirstIdentify();
|
|
756
820
|
const eventName = first ? "signup" : "login";
|
|
757
|
-
this.b.push(this.
|
|
821
|
+
this.b.push(this.d("track", {
|
|
758
822
|
event: eventName,
|
|
759
823
|
// `auto` says where the verdict came from, so a funnel that looks
|
|
760
824
|
// wrong later can be traced to a guess rather than to the server.
|
|
761
|
-
properties: { auto:
|
|
825
|
+
properties: { auto: !known }
|
|
762
826
|
}));
|
|
763
|
-
markIdentified();
|
|
827
|
+
if (this.c) markIdentified();
|
|
764
828
|
log(this.a, `${eventName} for ${userId}`);
|
|
765
829
|
}
|
|
766
830
|
}
|
|
@@ -773,7 +837,7 @@ var IndieBob = class _IndieBob {
|
|
|
773
837
|
revenue(data) {
|
|
774
838
|
var _a;
|
|
775
839
|
if (!isTrackingAllowed(this.a)) return;
|
|
776
|
-
this.b.push(this.
|
|
840
|
+
this.b.push(this.d("revenue", {
|
|
777
841
|
revenue: {
|
|
778
842
|
amount: data.amount,
|
|
779
843
|
currency: (_a = data.currency) != null ? _a : "USD",
|
|
@@ -791,11 +855,11 @@ var IndieBob = class _IndieBob {
|
|
|
791
855
|
void this.b.flush();
|
|
792
856
|
optOut();
|
|
793
857
|
clearIdentified();
|
|
794
|
-
this.
|
|
795
|
-
(_a = this.
|
|
796
|
-
(_b = this.
|
|
797
|
-
this.
|
|
798
|
-
this.
|
|
858
|
+
this.l = void 0;
|
|
859
|
+
(_a = this.m) == null ? void 0 : _a.call(this);
|
|
860
|
+
(_b = this.h) == null ? void 0 : _b.call(this);
|
|
861
|
+
this.m = null;
|
|
862
|
+
this.h = null;
|
|
799
863
|
log(this.a, "opted out");
|
|
800
864
|
}
|
|
801
865
|
/** Re-enable tracking after optOut(). */
|
|
@@ -818,13 +882,42 @@ var IndieBob = class _IndieBob {
|
|
|
818
882
|
async flush() {
|
|
819
883
|
await this.b.flush();
|
|
820
884
|
}
|
|
821
|
-
/**
|
|
885
|
+
/** The stored anonymous ID with `persistence: 'local'`; '' without storage. */
|
|
822
886
|
getAnonymousId() {
|
|
823
|
-
return getAnonymousId();
|
|
887
|
+
return this.c ? getAnonymousId() : "";
|
|
824
888
|
}
|
|
825
|
-
/**
|
|
889
|
+
/** The stored session ID with `persistence: 'local'`; '' without storage. */
|
|
826
890
|
getSessionId() {
|
|
827
|
-
return getSessionId();
|
|
891
|
+
return this.c ? getSessionId() : "";
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Switch storage on or off — call it from your consent banner.
|
|
895
|
+
* `'local'` starts storing the visitor id and first-touch attribution from
|
|
896
|
+
* this page on; `'none'` removes everything the tracker stored.
|
|
897
|
+
*
|
|
898
|
+
* @example
|
|
899
|
+
* onConsent(() => bob.setPersistence('local'))
|
|
900
|
+
* onConsentWithdrawn(() => bob.setPersistence('none'))
|
|
901
|
+
*/
|
|
902
|
+
setPersistence(mode) {
|
|
903
|
+
if (mode !== "local" && mode !== "none") return;
|
|
904
|
+
this.a.persistence = mode;
|
|
905
|
+
if (mode === "local") {
|
|
906
|
+
if (typeof window !== "undefined" && isTrackingAllowed(this.a)) {
|
|
907
|
+
initAttribution(location.search, document.referrer);
|
|
908
|
+
this.b.stamp(getAnonymousId(), getSessionId());
|
|
909
|
+
}
|
|
910
|
+
} else {
|
|
911
|
+
clearAnonymousId();
|
|
912
|
+
clearSession();
|
|
913
|
+
clearAttribution();
|
|
914
|
+
clearIdentified();
|
|
915
|
+
this.b.clearStored();
|
|
916
|
+
}
|
|
917
|
+
log(this.a, `persistence: ${mode}`);
|
|
918
|
+
}
|
|
919
|
+
get c() {
|
|
920
|
+
return this.a.persistence === "local";
|
|
828
921
|
}
|
|
829
922
|
/** Get the resolved configuration (read-only). */
|
|
830
923
|
getConfig() {
|
|
@@ -840,12 +933,12 @@ var IndieBob = class _IndieBob {
|
|
|
840
933
|
*/
|
|
841
934
|
enableAutoPage() {
|
|
842
935
|
var _a;
|
|
843
|
-
(_a = this.
|
|
844
|
-
this.
|
|
936
|
+
(_a = this.g) == null ? void 0 : _a.call(this);
|
|
937
|
+
this.g = patchHistory(() => this.page(), this.a.hashMode);
|
|
845
938
|
return () => {
|
|
846
939
|
var _a2;
|
|
847
|
-
(_a2 = this.
|
|
848
|
-
this.
|
|
940
|
+
(_a2 = this.g) == null ? void 0 : _a2.call(this);
|
|
941
|
+
this.g = null;
|
|
849
942
|
};
|
|
850
943
|
}
|
|
851
944
|
/**
|
|
@@ -853,14 +946,14 @@ var IndieBob = class _IndieBob {
|
|
|
853
946
|
* worth sending. `measure`: sample the scroll first (false on an SPA
|
|
854
947
|
* change, when the DOM already shows the next page).
|
|
855
948
|
*/
|
|
856
|
-
|
|
949
|
+
s(measure) {
|
|
857
950
|
var _a;
|
|
858
|
-
const reading = this.
|
|
951
|
+
const reading = this.n && ((_a = this.e) == null ? void 0 : _a.x(measure));
|
|
859
952
|
if (reading && isTrackingAllowed(this.a)) {
|
|
860
|
-
this.b.push(this.
|
|
953
|
+
this.b.push(this.d("engagement", { context: this.n, engagement: reading }));
|
|
861
954
|
}
|
|
862
955
|
}
|
|
863
|
-
|
|
956
|
+
d(type, extras) {
|
|
864
957
|
var _a, _b;
|
|
865
958
|
return {
|
|
866
959
|
...extras,
|
|
@@ -868,11 +961,11 @@ var IndieBob = class _IndieBob {
|
|
|
868
961
|
id: generateId(),
|
|
869
962
|
projectId: this.a.projectId,
|
|
870
963
|
type,
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
964
|
+
// Without storage the server derives both from its daily hash
|
|
965
|
+
...this.c ? { anonymousId: getAnonymousId(), sessionId: getSessionId() } : {},
|
|
966
|
+
userId: (_a = this.l) != null ? _a : extras.userId,
|
|
874
967
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
875
|
-
context: (_b = extras.context) != null ? _b : buildContext(void 0, this.a.hashMode)
|
|
968
|
+
context: (_b = extras.context) != null ? _b : buildContext(void 0, this.a.hashMode, this.c)
|
|
876
969
|
};
|
|
877
970
|
}
|
|
878
971
|
};
|
package/dist/index.d.mts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
interface TrackerConfig {
|
|
5
5
|
/** Your SDK project ID from the IndieBob dashboard. Format: 'proj_...' */
|
|
6
6
|
projectId: string;
|
|
7
|
-
/** Ingestion endpoint URL. Default: 'https://indiebob.com/api/collect' */
|
|
7
|
+
/** Ingestion endpoint URL. Default: 'https://www.indiebob.com/api/collect' */
|
|
8
8
|
endpoint?: string;
|
|
9
9
|
/** Log all events to the browser console. Default: false */
|
|
10
10
|
debug?: boolean;
|
|
11
11
|
/** Honor the browser Do Not Track header. Default: true */
|
|
12
12
|
respectDnt?: boolean;
|
|
13
|
-
/** Honor Global Privacy Control signal (legally binding in
|
|
13
|
+
/** Honor Global Privacy Control signal (legally binding under CCPA in California). Default: true */
|
|
14
14
|
respectGpc?: boolean;
|
|
15
15
|
/**
|
|
16
16
|
* Auto-detect user intent from URL patterns and identify() calls.
|
|
@@ -51,7 +51,22 @@ interface TrackerConfig {
|
|
|
51
51
|
* path. Default: false (a hash-only change is not a new page).
|
|
52
52
|
*/
|
|
53
53
|
hashMode?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* What the tracker keeps in the visitor's browser.
|
|
56
|
+
*
|
|
57
|
+
* - `'none'` (default): nothing. No id, no stored UTM or referrer, no offline
|
|
58
|
+
* queue. IndieBob counts the visitor on its server from a hash of a daily
|
|
59
|
+
* salt, the site, the IP address and the user agent; the salt is deleted
|
|
60
|
+
* after a day and the IP is never stored. No consent needed.
|
|
61
|
+
* - `'local'`: a random visitor id, first-touch UTM and referrer in
|
|
62
|
+
* localStorage, so a visitor is recognised across days. Storing an id on
|
|
63
|
+
* the visitor's device needs their consent in the EU (ePrivacy Art. 5(3)):
|
|
64
|
+
* switch to it from your consent banner with `bob.setPersistence('local')`.
|
|
65
|
+
*/
|
|
66
|
+
persistence?: Persistence;
|
|
54
67
|
}
|
|
68
|
+
/** Where the tracker may store anything: nowhere, or localStorage. */
|
|
69
|
+
type Persistence = 'none' | 'local';
|
|
55
70
|
/** Resolved config with all defaults applied. */
|
|
56
71
|
interface ResolvedConfig {
|
|
57
72
|
projectId: string;
|
|
@@ -73,6 +88,7 @@ interface ResolvedConfig {
|
|
|
73
88
|
fileDownloads?: boolean;
|
|
74
89
|
};
|
|
75
90
|
hashMode: boolean;
|
|
91
|
+
persistence: Persistence;
|
|
76
92
|
}
|
|
77
93
|
/** The tracking event type. */
|
|
78
94
|
type EventType = 'page' | 'track' | 'identify' | 'revenue' | 'engagement';
|
|
@@ -117,9 +133,11 @@ interface TrackerEvent {
|
|
|
117
133
|
id: string;
|
|
118
134
|
projectId: string;
|
|
119
135
|
type: EventType;
|
|
120
|
-
|
|
136
|
+
/** Absent with `persistence: 'none'`: the server derives the visitor from its daily hash. */
|
|
137
|
+
anonymousId?: string;
|
|
121
138
|
userId?: string;
|
|
122
|
-
|
|
139
|
+
/** Absent with `persistence: 'none'`: the server groups visits on its own. */
|
|
140
|
+
sessionId?: string;
|
|
123
141
|
timestamp: string;
|
|
124
142
|
context: EventContext;
|
|
125
143
|
event?: string;
|
|
@@ -253,10 +271,21 @@ declare class IndieBob {
|
|
|
253
271
|
isIgnored(): boolean;
|
|
254
272
|
/** Force-send all queued events immediately. Useful in tests. */
|
|
255
273
|
flush(): Promise<void>;
|
|
256
|
-
/**
|
|
274
|
+
/** The stored anonymous ID with `persistence: 'local'`; '' without storage. */
|
|
257
275
|
getAnonymousId(): string;
|
|
258
|
-
/**
|
|
276
|
+
/** The stored session ID with `persistence: 'local'`; '' without storage. */
|
|
259
277
|
getSessionId(): string;
|
|
278
|
+
/**
|
|
279
|
+
* Switch storage on or off — call it from your consent banner.
|
|
280
|
+
* `'local'` starts storing the visitor id and first-touch attribution from
|
|
281
|
+
* this page on; `'none'` removes everything the tracker stored.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* onConsent(() => bob.setPersistence('local'))
|
|
285
|
+
* onConsentWithdrawn(() => bob.setPersistence('none'))
|
|
286
|
+
*/
|
|
287
|
+
setPersistence(mode: Persistence): void;
|
|
288
|
+
private get _local();
|
|
260
289
|
/** Get the resolved configuration (read-only). */
|
|
261
290
|
getConfig(): Readonly<ResolvedConfig>;
|
|
262
291
|
/**
|
|
@@ -277,4 +306,4 @@ declare class IndieBob {
|
|
|
277
306
|
private _buildEvent;
|
|
278
307
|
}
|
|
279
308
|
|
|
280
|
-
export { type DeviceType, type EventContext, type EventType, IndieBob, type PageOverrides, type RevenueData, type RevenueType, type TrackerConfig, type TrackerEvent, type UtmData };
|
|
309
|
+
export { type DeviceType, type EventContext, type EventType, IndieBob, type PageOverrides, type Persistence, type RevenueData, type RevenueType, type TrackerConfig, type TrackerEvent, type UtmData };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
interface TrackerConfig {
|
|
5
5
|
/** Your SDK project ID from the IndieBob dashboard. Format: 'proj_...' */
|
|
6
6
|
projectId: string;
|
|
7
|
-
/** Ingestion endpoint URL. Default: 'https://indiebob.com/api/collect' */
|
|
7
|
+
/** Ingestion endpoint URL. Default: 'https://www.indiebob.com/api/collect' */
|
|
8
8
|
endpoint?: string;
|
|
9
9
|
/** Log all events to the browser console. Default: false */
|
|
10
10
|
debug?: boolean;
|
|
11
11
|
/** Honor the browser Do Not Track header. Default: true */
|
|
12
12
|
respectDnt?: boolean;
|
|
13
|
-
/** Honor Global Privacy Control signal (legally binding in
|
|
13
|
+
/** Honor Global Privacy Control signal (legally binding under CCPA in California). Default: true */
|
|
14
14
|
respectGpc?: boolean;
|
|
15
15
|
/**
|
|
16
16
|
* Auto-detect user intent from URL patterns and identify() calls.
|
|
@@ -51,7 +51,22 @@ interface TrackerConfig {
|
|
|
51
51
|
* path. Default: false (a hash-only change is not a new page).
|
|
52
52
|
*/
|
|
53
53
|
hashMode?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* What the tracker keeps in the visitor's browser.
|
|
56
|
+
*
|
|
57
|
+
* - `'none'` (default): nothing. No id, no stored UTM or referrer, no offline
|
|
58
|
+
* queue. IndieBob counts the visitor on its server from a hash of a daily
|
|
59
|
+
* salt, the site, the IP address and the user agent; the salt is deleted
|
|
60
|
+
* after a day and the IP is never stored. No consent needed.
|
|
61
|
+
* - `'local'`: a random visitor id, first-touch UTM and referrer in
|
|
62
|
+
* localStorage, so a visitor is recognised across days. Storing an id on
|
|
63
|
+
* the visitor's device needs their consent in the EU (ePrivacy Art. 5(3)):
|
|
64
|
+
* switch to it from your consent banner with `bob.setPersistence('local')`.
|
|
65
|
+
*/
|
|
66
|
+
persistence?: Persistence;
|
|
54
67
|
}
|
|
68
|
+
/** Where the tracker may store anything: nowhere, or localStorage. */
|
|
69
|
+
type Persistence = 'none' | 'local';
|
|
55
70
|
/** Resolved config with all defaults applied. */
|
|
56
71
|
interface ResolvedConfig {
|
|
57
72
|
projectId: string;
|
|
@@ -73,6 +88,7 @@ interface ResolvedConfig {
|
|
|
73
88
|
fileDownloads?: boolean;
|
|
74
89
|
};
|
|
75
90
|
hashMode: boolean;
|
|
91
|
+
persistence: Persistence;
|
|
76
92
|
}
|
|
77
93
|
/** The tracking event type. */
|
|
78
94
|
type EventType = 'page' | 'track' | 'identify' | 'revenue' | 'engagement';
|
|
@@ -117,9 +133,11 @@ interface TrackerEvent {
|
|
|
117
133
|
id: string;
|
|
118
134
|
projectId: string;
|
|
119
135
|
type: EventType;
|
|
120
|
-
|
|
136
|
+
/** Absent with `persistence: 'none'`: the server derives the visitor from its daily hash. */
|
|
137
|
+
anonymousId?: string;
|
|
121
138
|
userId?: string;
|
|
122
|
-
|
|
139
|
+
/** Absent with `persistence: 'none'`: the server groups visits on its own. */
|
|
140
|
+
sessionId?: string;
|
|
123
141
|
timestamp: string;
|
|
124
142
|
context: EventContext;
|
|
125
143
|
event?: string;
|
|
@@ -253,10 +271,21 @@ declare class IndieBob {
|
|
|
253
271
|
isIgnored(): boolean;
|
|
254
272
|
/** Force-send all queued events immediately. Useful in tests. */
|
|
255
273
|
flush(): Promise<void>;
|
|
256
|
-
/**
|
|
274
|
+
/** The stored anonymous ID with `persistence: 'local'`; '' without storage. */
|
|
257
275
|
getAnonymousId(): string;
|
|
258
|
-
/**
|
|
276
|
+
/** The stored session ID with `persistence: 'local'`; '' without storage. */
|
|
259
277
|
getSessionId(): string;
|
|
278
|
+
/**
|
|
279
|
+
* Switch storage on or off — call it from your consent banner.
|
|
280
|
+
* `'local'` starts storing the visitor id and first-touch attribution from
|
|
281
|
+
* this page on; `'none'` removes everything the tracker stored.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* onConsent(() => bob.setPersistence('local'))
|
|
285
|
+
* onConsentWithdrawn(() => bob.setPersistence('none'))
|
|
286
|
+
*/
|
|
287
|
+
setPersistence(mode: Persistence): void;
|
|
288
|
+
private get _local();
|
|
260
289
|
/** Get the resolved configuration (read-only). */
|
|
261
290
|
getConfig(): Readonly<ResolvedConfig>;
|
|
262
291
|
/**
|
|
@@ -277,4 +306,4 @@ declare class IndieBob {
|
|
|
277
306
|
private _buildEvent;
|
|
278
307
|
}
|
|
279
308
|
|
|
280
|
-
export { type DeviceType, type EventContext, type EventType, IndieBob, type PageOverrides, type RevenueData, type RevenueType, type TrackerConfig, type TrackerEvent, type UtmData };
|
|
309
|
+
export { type DeviceType, type EventContext, type EventType, IndieBob, type PageOverrides, type Persistence, type RevenueData, type RevenueType, type TrackerConfig, type TrackerEvent, type UtmData };
|