@indiebob/tracker 0.2.4 → 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/dist/server.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 CA/GDPR). Default: true */
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.
@@ -42,8 +42,31 @@ interface TrackerConfig {
42
42
  formSubmit?: boolean;
43
43
  /** Track clicks on outbound links. Default: true */
44
44
  outboundClicks?: boolean;
45
+ /** Track clicks on links to files (pdf, zip, dmg, …) as `file_download`. Default: true */
46
+ fileDownloads?: boolean;
45
47
  };
48
+ /**
49
+ * Treat the URL hash as part of the page path (for `/#/route` routers):
50
+ * a hash-only change becomes a pageview and the hash is reported in the
51
+ * path. Default: false (a hash-only change is not a new page).
52
+ */
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;
46
67
  }
68
+ /** Where the tracker may store anything: nowhere, or localStorage. */
69
+ type Persistence = 'none' | 'local';
47
70
 
48
71
  /**
49
72
  * Server-side tracker for Node.js environments.
package/dist/server.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 CA/GDPR). Default: true */
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.
@@ -42,8 +42,31 @@ interface TrackerConfig {
42
42
  formSubmit?: boolean;
43
43
  /** Track clicks on outbound links. Default: true */
44
44
  outboundClicks?: boolean;
45
+ /** Track clicks on links to files (pdf, zip, dmg, …) as `file_download`. Default: true */
46
+ fileDownloads?: boolean;
45
47
  };
48
+ /**
49
+ * Treat the URL hash as part of the page path (for `/#/route` routers):
50
+ * a hash-only change becomes a pageview and the hash is reported in the
51
+ * path. Default: false (a hash-only change is not a new page).
52
+ */
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;
46
67
  }
68
+ /** Where the tracker may store anything: nowhere, or localStorage. */
69
+ type Persistence = 'none' | 'local';
47
70
 
48
71
  /**
49
72
  * Server-side tracker for Node.js environments.
package/dist/server.js CHANGED
@@ -2,28 +2,38 @@
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
  if (!input.projectId || !input.projectId.startsWith("proj_")) {
7
14
  throw new Error(
8
15
  `[IndieBob] Invalid projectId "${input.projectId}". Must start with "proj_".`
9
16
  );
10
17
  }
18
+ warnUnknown(input, OPTIONS, "");
19
+ if (typeof input.autoDetect === "object") warnUnknown(input.autoDetect, AUTO_OPTIONS, "autoDetect.");
20
+ if (input.persistence !== void 0 && input.persistence !== "local" && input.persistence !== "none") {
21
+ console.warn(`[IndieBob] persistence "${String(input.persistence)}" is not 'none' or 'local'; using 'none'`);
22
+ }
11
23
  return {
12
24
  projectId: input.projectId,
13
25
  endpoint: input.endpoint ?? DEFAULT_ENDPOINT,
14
26
  debug: input.debug ?? false,
15
27
  respectDnt: input.respectDnt ?? true,
16
28
  respectGpc: input.respectGpc ?? true,
17
- autoDetect: input.autoDetect ?? true
29
+ autoDetect: input.autoDetect ?? true,
30
+ hashMode: input.hashMode ?? false,
31
+ persistence: input.persistence === "local" ? "local" : "none"
18
32
  };
19
33
  }
20
34
 
21
35
  // src/core/transport.ts
22
36
  var MAX_RETRIES = 3;
23
- var RETRY_DELAYS = [1e3, 2e3, 4e3];
24
- async function sleep(ms) {
25
- return new Promise((resolve) => setTimeout(resolve, ms));
26
- }
27
37
  async function sendBatchServer(payload, config) {
28
38
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
29
39
  try {
@@ -36,7 +46,7 @@ async function sendBatchServer(payload, config) {
36
46
  if (res.status >= 400 && res.status < 500) return false;
37
47
  } catch {
38
48
  }
39
- if (attempt < MAX_RETRIES - 1) await sleep(RETRY_DELAYS[attempt]);
49
+ if (attempt < MAX_RETRIES - 1) await new Promise((resolve) => setTimeout(resolve, 1e3 << attempt));
40
50
  }
41
51
  return false;
42
52
  }
@@ -69,6 +79,7 @@ var IndieBobServer = class _IndieBobServer {
69
79
  buildEvent(type, extras) {
70
80
  return {
71
81
  v: 1,
82
+ id: generateId(),
72
83
  projectId: this.config.projectId,
73
84
  type,
74
85
  anonymousId: `server_${generateId()}`,
@@ -80,7 +91,7 @@ var IndieBobServer = class _IndieBobServer {
80
91
  device: { width: 0, height: 0, type: "desktop" },
81
92
  locale: "en",
82
93
  timezone: "UTC",
83
- sdkVersion: "0.2.4"
94
+ sdkVersion: "0.4.1"
84
95
  },
85
96
  event: extras.event,
86
97
  properties: extras.properties,
package/dist/server.mjs CHANGED
@@ -1,27 +1,37 @@
1
1
  // src/core/config.ts
2
2
  var DEFAULT_ENDPOINT = "https://www.indiebob.com/api/collect";
3
+ var OPTIONS = "projectId endpoint debug respectDnt respectGpc autoDetect hashMode persistence";
4
+ var AUTO_OPTIONS = "signupPaths loginPaths pricingPaths checkoutPaths successPaths onboardingPaths contactPaths dataAttributes formSubmit outboundClicks fileDownloads";
5
+ function warnUnknown(obj, known, where) {
6
+ for (const k of Object.keys(obj)) {
7
+ if (!known.split(" ").includes(k)) console.warn(`[IndieBob] Unknown option ${where}"${k}" ignored`);
8
+ }
9
+ }
3
10
  function resolveConfig(input) {
4
11
  if (!input.projectId || !input.projectId.startsWith("proj_")) {
5
12
  throw new Error(
6
13
  `[IndieBob] Invalid projectId "${input.projectId}". Must start with "proj_".`
7
14
  );
8
15
  }
16
+ warnUnknown(input, OPTIONS, "");
17
+ if (typeof input.autoDetect === "object") warnUnknown(input.autoDetect, AUTO_OPTIONS, "autoDetect.");
18
+ if (input.persistence !== void 0 && input.persistence !== "local" && input.persistence !== "none") {
19
+ console.warn(`[IndieBob] persistence "${String(input.persistence)}" is not 'none' or 'local'; using 'none'`);
20
+ }
9
21
  return {
10
22
  projectId: input.projectId,
11
23
  endpoint: input.endpoint ?? DEFAULT_ENDPOINT,
12
24
  debug: input.debug ?? false,
13
25
  respectDnt: input.respectDnt ?? true,
14
26
  respectGpc: input.respectGpc ?? true,
15
- autoDetect: input.autoDetect ?? true
27
+ autoDetect: input.autoDetect ?? true,
28
+ hashMode: input.hashMode ?? false,
29
+ persistence: input.persistence === "local" ? "local" : "none"
16
30
  };
17
31
  }
18
32
 
19
33
  // src/core/transport.ts
20
34
  var MAX_RETRIES = 3;
21
- var RETRY_DELAYS = [1e3, 2e3, 4e3];
22
- async function sleep(ms) {
23
- return new Promise((resolve) => setTimeout(resolve, ms));
24
- }
25
35
  async function sendBatchServer(payload, config) {
26
36
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
27
37
  try {
@@ -34,7 +44,7 @@ async function sendBatchServer(payload, config) {
34
44
  if (res.status >= 400 && res.status < 500) return false;
35
45
  } catch {
36
46
  }
37
- if (attempt < MAX_RETRIES - 1) await sleep(RETRY_DELAYS[attempt]);
47
+ if (attempt < MAX_RETRIES - 1) await new Promise((resolve) => setTimeout(resolve, 1e3 << attempt));
38
48
  }
39
49
  return false;
40
50
  }
@@ -67,6 +77,7 @@ var IndieBobServer = class _IndieBobServer {
67
77
  buildEvent(type, extras) {
68
78
  return {
69
79
  v: 1,
80
+ id: generateId(),
70
81
  projectId: this.config.projectId,
71
82
  type,
72
83
  anonymousId: `server_${generateId()}`,
@@ -78,7 +89,7 @@ var IndieBobServer = class _IndieBobServer {
78
89
  device: { width: 0, height: 0, type: "desktop" },
79
90
  locale: "en",
80
91
  timezone: "UTC",
81
- sdkVersion: "0.2.4"
92
+ sdkVersion: "0.4.1"
82
93
  },
83
94
  event: extras.event,
84
95
  properties: extras.properties,