@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/CHANGELOG.md +89 -0
- package/README.md +112 -389
- package/dist/index.cjs +973 -2
- package/dist/index.d.mts +91 -16
- package/dist/index.d.ts +91 -16
- package/dist/index.global.js +3 -3
- package/dist/index.js +971 -2
- package/dist/server.d.mts +25 -2
- package/dist/server.d.ts +25 -2
- package/dist/server.js +18 -7
- package/dist/server.mjs +18 -7
- package/dist/vue.cjs +997 -2
- package/dist/vue.global.js +3 -3
- package/dist/vue.js +993 -2
- package/package.json +4 -3
- package/dist/chunk-4IK5PET7.js +0 -2
- package/dist/chunk-DZOEO647.js +0 -2
- package/dist/chunk-XKQYO25W.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,95 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.4.1] — 2026-11-02
|
|
10
|
+
|
|
11
|
+
- Shorter README. No code changes.
|
|
12
|
+
|
|
13
|
+
## [0.4.0] — 2026-11-02
|
|
14
|
+
|
|
15
|
+
Privacy mode is the default: the tracker stores nothing in the visitor's browser, and IndieBob's
|
|
16
|
+
server identifies a visitor for one day from a hash of a daily salt, the project, the IP address and
|
|
17
|
+
the user agent. The 0.3 behaviour is `persistence: 'local'` (needs consent in the EU).
|
|
18
|
+
|
|
19
|
+
Needs IndieBob's `/api/collect` from 2026-11-02 or later; an older server rejects events without
|
|
20
|
+
`anonymousId`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Without `persistence: 'local'`, a visitor who comes back on another day is a new visitor, and a
|
|
24
|
+
signup is not credited to a visit on an earlier day.
|
|
25
|
+
- In privacy mode `identify()` sends `signup` / `login` only when you pass `{ isNew }`.
|
|
26
|
+
- `getAnonymousId()` and `getSessionId()` return `''` in privacy mode.
|
|
27
|
+
- Page paths and URLs keep only the `utm_*` and `ref` query parameters; referrers keep origin and
|
|
28
|
+
path. A navigation that changes only another query parameter is no longer a new page.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- `persistence: 'none' | 'local'`, `data-persistence="local"`, and `bob.setPersistence()` for consent
|
|
32
|
+
banners (`'none'` removes everything the tracker stored).
|
|
33
|
+
- A warning for unknown or misspelled options.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- Nothing is written to storage before Do Not Track, Global Privacy Control and opt-out are checked.
|
|
37
|
+
|
|
38
|
+
### Size
|
|
39
|
+
- Script tag 5,699 bytes gzipped (0.3.0: 5,076).
|
|
40
|
+
|
|
41
|
+
## [0.3.0] — 2026-11-01
|
|
42
|
+
|
|
43
|
+
The npm module bundles (`index.js`/`.cjs`, `vue.js`/`.cjs`) are no longer
|
|
44
|
+
minified — your bundler minifies them. A minified bundle's comma-joined
|
|
45
|
+
declarations slipped past Nuxt's auto-import scanner and broke a Nuxt build.
|
|
46
|
+
The script-tag bundle (`indiebob.min.js`) stays minified, under 5 KB.
|
|
47
|
+
|
|
48
|
+
Needs the matching `/api/collect` (it must accept `type: 'engagement'` and
|
|
49
|
+
the new top-level `id` and `engagement` fields) **before** this version is
|
|
50
|
+
served: the 0.2 server rejects a whole batch that contains an engagement event.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
- **Self-initialising script tag.** `<script src=".../indiebob.min.js"
|
|
54
|
+
data-project="proj_…" defer></script>` initialises itself and tracks the
|
|
55
|
+
page plus every SPA navigation, with no `init()` / `page()` call. Optional
|
|
56
|
+
`data-api` (endpoint), `data-auto-page="false"` (initialise only),
|
|
57
|
+
`data-hash-mode="true"`. A tag without `data-project` behaves exactly as
|
|
58
|
+
before.
|
|
59
|
+
- **Event id.** Every event carries a top-level `id` (21 url-safe
|
|
60
|
+
characters), unchanged across retries and offline replays, so the server
|
|
61
|
+
can store each event once. Server-side events (`/server`) carry one too.
|
|
62
|
+
- **`engagement` event.** Visible-and-focused milliseconds and the deepest
|
|
63
|
+
scroll depth (% of the page) for the page it describes, with that page's
|
|
64
|
+
context. Sent on tab hide, pagehide and right before an SPA navigation,
|
|
65
|
+
only when the scroll grew or ≥ 3 s accumulated since the last one; never a
|
|
66
|
+
pageview.
|
|
67
|
+
- **Automatic file downloads.** A click on a link to a file (pdf, zip, dmg,
|
|
68
|
+
exe, msi, pkg, apk, ipa, deb, rpm, appimage, archives, office files, txt,
|
|
69
|
+
epub, mp3/wav/mp4/mov, iso) fires `file_download` with
|
|
70
|
+
`{ url, path, ext, auto: true }`, on any host; `url` drops the query and
|
|
71
|
+
hash. An outbound download fires only `file_download`. Off with
|
|
72
|
+
`autoDetect: { fileDownloads: false }`.
|
|
73
|
+
- **Owner self-exclude.** `?ib_ignore=1` stops this browser being counted
|
|
74
|
+
(stored as `localStorage.ib_ignore`, with one console line per page load);
|
|
75
|
+
`?ib_ignore=0` undoes it. `bob.isIgnored()` reports it.
|
|
76
|
+
- **`hashMode`** option for `/#/route` routers.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
- A `page()` for the path already counted (same pathname + query; a
|
|
80
|
+
hash-only change unless `hashMode`) is skipped.
|
|
81
|
+
- A back/forward-cache restore (`pageshow` with `persisted`) counts as a
|
|
82
|
+
pageview.
|
|
83
|
+
- A page that is prerendered or opened in a background tab counts its first
|
|
84
|
+
pageview when it is first shown.
|
|
85
|
+
- `navigator.webdriver` browsers send nothing.
|
|
86
|
+
- `init()` with a `projectId` that is already running returns that instance
|
|
87
|
+
(the first config wins) instead of starting a second tracker.
|
|
88
|
+
- Builds: the script-tag bundle targets ES2020 (Chrome 80, Firefox 74,
|
|
89
|
+
Safari 13.1) and the npm ESM/CJS build ES2018 (was ES2015 for both) — the
|
|
90
|
+
price of staying under 5 KB gzipped with the features above. Private
|
|
91
|
+
members are shortened at build time.
|
|
92
|
+
- The plain-http fallback for the session id (no `crypto.randomUUID` outside
|
|
93
|
+
a secure context) now uses the SDK's crypto id generator instead of
|
|
94
|
+
`Math.random`.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
9
98
|
## [0.2.4] — 2026-09-08
|
|
10
99
|
|
|
11
100
|
### Added
|
package/README.md
CHANGED
|
@@ -1,455 +1,178 @@
|
|
|
1
1
|
# @indiebob/tracker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The analytics script for [IndieBob](https://www.indiebob.com): page views, traffic sources, events,
|
|
4
|
+
signups and revenue. About 5.7 KB gzipped, no dependencies, no cookies. By default it stores nothing
|
|
5
|
+
in the visitor's browser.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Install
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# or
|
|
10
|
-
pnpm add @indiebob/tracker
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Or via script tag (CDN):
|
|
9
|
+
**Script tag** — one line before `</head>`. It counts every page, including navigation in a
|
|
10
|
+
single-page app.
|
|
14
11
|
|
|
15
12
|
```html
|
|
16
|
-
<script src="https://assets.indiebob.com/tracker/latest/indiebob.min.js"></script>
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Quick Start
|
|
20
|
-
|
|
21
|
-
Three lines to start tracking:
|
|
22
|
-
|
|
23
|
-
```js
|
|
24
|
-
import { IndieBob } from '@indiebob/tracker'
|
|
25
|
-
|
|
26
|
-
const bob = IndieBob.init({ projectId: 'proj_your16charId' })
|
|
27
|
-
bob.page()
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
That's it. Page views are now tracked. UTM parameters are auto-captured.
|
|
31
|
-
|
|
32
|
-
## Configuration
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
const bob = IndieBob.init({
|
|
36
|
-
projectId: 'proj_your16charId', // Required. From your IndieBob dashboard.
|
|
37
|
-
endpoint: 'https://indiebob.com/api/collect', // Optional. Default shown.
|
|
38
|
-
debug: false, // Optional. Logs events to console when true.
|
|
39
|
-
respectDnt: true, // Optional. Honor browser Do Not Track. Default: true.
|
|
40
|
-
respectGpc: true, // Optional. Honor Global Privacy Control. Default: true.
|
|
41
|
-
autoDetect: true, // Optional. Auto-detect signups, logins, checkouts, etc. Default: true.
|
|
42
|
-
})
|
|
13
|
+
<script src="https://assets.indiebob.com/tracker/latest/indiebob.min.js" data-project="proj_…" defer></script>
|
|
43
14
|
```
|
|
44
15
|
|
|
45
|
-
|
|
|
46
|
-
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `autoDetect` | `boolean \| object` | `true` | Auto-detect user intent from URL patterns, form submissions, outbound clicks, and data attributes. See [Auto-Detection](#auto-detection). |
|
|
53
|
-
|
|
54
|
-
## API Reference
|
|
55
|
-
|
|
56
|
-
### `IndieBob.init(config)`
|
|
16
|
+
| Attribute | |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `data-project` | Your project id (IndieBob › your project › Connect). Required. |
|
|
19
|
+
| `data-persistence="local"` | Full attribution (see below). Only with the visitor's consent. |
|
|
20
|
+
| `data-hash-mode="true"` | Your routes live after a `#` (`/#/pricing`). |
|
|
21
|
+
| `data-auto-page="false"` | Start the tracker but count pages yourself. |
|
|
22
|
+
| `data-api="…"` | Send to another endpoint. |
|
|
57
23
|
|
|
58
|
-
|
|
24
|
+
**npm**
|
|
59
25
|
|
|
60
|
-
```
|
|
61
|
-
|
|
26
|
+
```sh
|
|
27
|
+
npm install @indiebob/tracker
|
|
62
28
|
```
|
|
63
29
|
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
### `bob.page(overrides?)`
|
|
67
|
-
|
|
68
|
-
Track a page view. Call this on every page load and SPA navigation.
|
|
69
|
-
|
|
70
30
|
```ts
|
|
71
|
-
|
|
72
|
-
bob.page({ path: '/pricing' }) // Override path
|
|
73
|
-
bob.page({ title: 'Pricing Page' }) // Override title
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**Auto-detected:** URL path, page title, referrer, UTM parameters, device type.
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
### `bob.track(event, properties?)`
|
|
81
|
-
|
|
82
|
-
Track a custom event.
|
|
31
|
+
import { IndieBob } from '@indiebob/tracker'
|
|
83
32
|
|
|
84
|
-
|
|
85
|
-
bob.
|
|
86
|
-
bob.track('trial_activated', { plan: 'pro' })
|
|
87
|
-
bob.track('feature_used', { feature: 'csv_export', count: 3 })
|
|
88
|
-
bob.track('checkout_completed', { revenue: 29.00, currency: 'USD', plan: 'pro-monthly' })
|
|
33
|
+
const bob = IndieBob.init({ projectId: 'proj_…' })
|
|
34
|
+
bob.page()
|
|
89
35
|
```
|
|
90
36
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
**`properties`** — plain object, optional. Values must be JSON-serializable.
|
|
37
|
+
## Privacy mode and full attribution
|
|
94
38
|
|
|
95
|
-
|
|
39
|
+
| | Privacy mode (default) | Full attribution |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| Stored in the visitor's browser | nothing | a visitor id, a session id, the first and last UTM, the first referrer |
|
|
42
|
+
| A visitor is recognised | for one day | across days |
|
|
43
|
+
| A signup is credited to a visit on an earlier day | no | yes |
|
|
44
|
+
| Needs the visitor's consent in the EU | no | yes |
|
|
96
45
|
|
|
97
|
-
|
|
46
|
+
In privacy mode IndieBob's server identifies a visitor by hashing a random daily salt together with
|
|
47
|
+
the project, the IP address and the user agent. Each day's salt is deleted, and the IP address is
|
|
48
|
+
not stored.
|
|
98
49
|
|
|
99
|
-
|
|
50
|
+
Full attribution stores an id on the visitor's device, which needs consent in the EU (ePrivacy
|
|
51
|
+
Directive, Art. 5(3); localStorage counts the same as a cookie). Switch it on from your consent
|
|
52
|
+
banner:
|
|
100
53
|
|
|
101
54
|
```ts
|
|
102
|
-
bob.
|
|
103
|
-
|
|
104
|
-
name: 'Jane Smith',
|
|
105
|
-
plan: 'pro',
|
|
106
|
-
createdAt: '2026-01-15',
|
|
107
|
-
})
|
|
55
|
+
bob.setPersistence('local') // the visitor accepted
|
|
56
|
+
bob.setPersistence('none') // consent withdrawn: removes everything the tracker stored
|
|
108
57
|
```
|
|
109
58
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
**`traits`** — plain object, optional. Can include `email` for attribution linking.
|
|
113
|
-
|
|
114
|
-
After `identify()`, all subsequent events are linked to this userId until the session ends.
|
|
115
|
-
|
|
116
|
-
---
|
|
59
|
+
If consent is already known when the page loads, pass `persistence: 'local'` to `init()` or put
|
|
60
|
+
`data-persistence="local"` on the tag.
|
|
117
61
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
Track a revenue event. Use alongside Stripe webhooks for complete attribution.
|
|
62
|
+
## Configuration
|
|
121
63
|
|
|
122
64
|
```ts
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
65
|
+
IndieBob.init({
|
|
66
|
+
projectId: 'proj_…', // required
|
|
67
|
+
persistence: 'none', // 'none' (privacy mode) | 'local' (full attribution)
|
|
68
|
+
autoDetect: true, // automatic events, below; false turns them off
|
|
69
|
+
hashMode: false, // routes after a #
|
|
70
|
+
respectDnt: true, // send nothing when Do Not Track is on
|
|
71
|
+
respectGpc: true, // send nothing when Global Privacy Control is on
|
|
72
|
+
endpoint: 'https://www.indiebob.com/api/collect',
|
|
73
|
+
debug: false, // log every event to the console
|
|
128
74
|
})
|
|
129
75
|
```
|
|
130
76
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
### Privacy Controls
|
|
134
|
-
|
|
135
|
-
```ts
|
|
136
|
-
bob.optOut() // Stop all tracking. Clears all stored identifiers.
|
|
137
|
-
bob.optIn() // Re-enable tracking.
|
|
138
|
-
bob.isOptedOut() // Returns boolean.
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
### Debug / Testing
|
|
144
|
-
|
|
145
|
-
```ts
|
|
146
|
-
bob.getAnonymousId() // Returns the current anonymous ID
|
|
147
|
-
bob.getSessionId() // Returns the current session ID
|
|
148
|
-
bob.getConfig() // Returns a read-only copy of the config
|
|
149
|
-
bob.flush() // Force-send all queued events immediately
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
---
|
|
77
|
+
A misspelled or unknown option logs a warning instead of being silently ignored.
|
|
153
78
|
|
|
154
|
-
##
|
|
79
|
+
## API
|
|
155
80
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
### URL Intent Detection
|
|
159
|
-
|
|
160
|
-
When `bob.page()` is called, the SDK checks the URL path against known patterns (multilingual: EN, ES, FR, DE, PT, IT, NL) and fires the corresponding event:
|
|
161
|
-
|
|
162
|
-
| Event | Triggered By |
|
|
81
|
+
| Call | |
|
|
163
82
|
|---|---|
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
Clicks on links pointing to external domains automatically fire `outbound_click` with `{ url, hostname }` properties.
|
|
190
|
-
|
|
191
|
-
### Data Attribute Tracking
|
|
192
|
-
|
|
193
|
-
Add `data-ib-event` to any element for zero-JS custom event tracking:
|
|
194
|
-
|
|
195
|
-
```html
|
|
196
|
-
<button data-ib-event="add_to_cart" data-ib-plan="pro" data-ib-price="29">
|
|
197
|
-
Add to Cart
|
|
198
|
-
</button>
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
This fires `track('add_to_cart', { plan: 'pro', price: '29' })` on click. All `data-ib-*` attributes (except `data-ib-event`) become event properties.
|
|
202
|
-
|
|
203
|
-
### Customizing Auto-Detection
|
|
83
|
+
| `IndieBob.init(config)` | Starts the tracker and returns it. Called again for the same project, it returns the running one. |
|
|
84
|
+
| `bob.page(overrides?)` | Counts a page view. The same path twice in a row counts once. `overrides`: `{ path, title, referrer }`. |
|
|
85
|
+
| `bob.track(name, properties?)` | A named event: `bob.track('csv_exported', { rows: 1240 })`. |
|
|
86
|
+
| `bob.identify(userId, traits?, { isNew? })` | After signup or login. Links the visit to your user id and sends `signup` or `login`. Pass `isNew: true` after a registration and `false` on a login — in privacy mode it is required for the `signup`/`login` event. Use your own id, never an email address, as `userId`. |
|
|
87
|
+
| `bob.revenue({ amount, currency, plan, type })` | A payment, if Stripe is not connected to IndieBob. `type`: `new`, `renewal`, `upgrade`, `downgrade`. |
|
|
88
|
+
| `bob.setPersistence('none' \| 'local')` | Switches mode, above. |
|
|
89
|
+
| `bob.enableAutoPage()` | Counts a page on every history change (the script tag does this itself). |
|
|
90
|
+
| `bob.optOut()`, `bob.optIn()`, `bob.isOptedOut()` | The visitor's own opt-out; `optOut()` removes everything stored. |
|
|
91
|
+
| `bob.isIgnored()` | Whether this browser is excluded with `?ib_ignore=1`. |
|
|
92
|
+
| `bob.flush()` | Sends queued events now. |
|
|
93
|
+
|
|
94
|
+
Events are sent in batches every 5 seconds and when the page is hidden or closed.
|
|
95
|
+
|
|
96
|
+
## Collected automatically
|
|
97
|
+
|
|
98
|
+
| Event | When |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `page` | Every page view, including a page restored with the Back button. |
|
|
101
|
+
| `engagement` | When a page is left or hidden: how long it was visible and focused, and how far it was scrolled. |
|
|
102
|
+
| `outbound_click` | A link to another site is clicked. |
|
|
103
|
+
| `file_download` | A link to a file (pdf, zip, dmg, exe, apk, …) is clicked. |
|
|
104
|
+
| `signup_start`, `login_view`, `pricing_view`, `checkout_view`, `success_view`, `onboarding_view`, `contact_view` | The page's path matches `/signup`, `/login`, `/pricing`, … or their Spanish, French, German, Portuguese, Italian or Dutch words. |
|
|
105
|
+
| `signup_submit`, `login_submit`, `checkout_submit` | A form is submitted on one of those pages. |
|
|
106
|
+
| any name | An element with `data-ib-event="name"` is clicked; its other `data-ib-*` attributes become properties. |
|
|
204
107
|
|
|
205
|
-
|
|
108
|
+
Turn parts off or replace the path patterns:
|
|
206
109
|
|
|
207
110
|
```ts
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
autoDetect: {
|
|
211
|
-
// Custom URL patterns — strings or RegExp (override defaults)
|
|
212
|
-
signupPaths: ['/my-signup-page'], // string: matched via path.includes()
|
|
213
|
-
checkoutPaths: [/\/buy|\/purchase/i], // RegExp: matched via .test()
|
|
214
|
-
|
|
215
|
-
// Disable specific features
|
|
216
|
-
outboundClicks: false,
|
|
217
|
-
formSubmit: false,
|
|
218
|
-
dataAttributes: false,
|
|
219
|
-
},
|
|
220
|
-
})
|
|
111
|
+
autoDetect: { fileDownloads: false, outboundClicks: false, formSubmit: false, dataAttributes: false,
|
|
112
|
+
signupPaths: [/\/start-trial/] }
|
|
221
113
|
```
|
|
222
114
|
|
|
223
|
-
|
|
115
|
+
## What is sent
|
|
224
116
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
| `onboardingPaths` | `(string \| RegExp)[]` | built-in i18n | URL patterns for onboarding pages |
|
|
233
|
-
| `contactPaths` | `(string \| RegExp)[]` | built-in i18n | URL patterns for contact pages |
|
|
234
|
-
| `dataAttributes` | `boolean` | `true` | Track clicks on `data-ib-event` elements |
|
|
235
|
-
| `formSubmit` | `boolean` | `true` | Track form submissions on intent pages |
|
|
236
|
-
| `outboundClicks` | `boolean` | `true` | Track clicks on external links |
|
|
117
|
+
- The page's path and URL, keeping only the `utm_*` and `ref` query parameters. Anything else in a
|
|
118
|
+
query string — sign-in tokens, reset codes, email addresses, ad click ids — is removed before it
|
|
119
|
+
leaves the page, and IndieBob's server removes it again from older versions of the tracker.
|
|
120
|
+
- The page title; the referrer's origin and path; the screen size class; the browser's language and
|
|
121
|
+
time zone; your event's properties.
|
|
122
|
+
- IndieBob adds the country, region and city (looked up from the IP address, which is not stored),
|
|
123
|
+
the browser, the operating system and the device type.
|
|
237
124
|
|
|
238
|
-
|
|
125
|
+
Nothing is sent from a browser with Do Not Track or Global Privacy Control on, after `optOut()`,
|
|
126
|
+
from automated browsers, or from your own browser once you open your site with `?ib_ignore=1`
|
|
127
|
+
(`?ib_ignore=0` undoes it).
|
|
239
128
|
|
|
240
|
-
|
|
129
|
+
IndieBob processes this data for you, in the EU (Frankfurt). A data processing agreement is
|
|
130
|
+
available from info@indiebob.com.
|
|
241
131
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
```ts
|
|
245
|
-
// main.ts or plugins/tracker.client.ts
|
|
246
|
-
import { IndieBobPlugin } from '@indiebob/tracker/vue'
|
|
247
|
-
|
|
248
|
-
app.use(IndieBobPlugin, { projectId: 'proj_abc123def456ghij' })
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Then in any component:
|
|
252
|
-
|
|
253
|
-
```ts
|
|
254
|
-
import { useTracker } from '@indiebob/tracker/vue'
|
|
255
|
-
|
|
256
|
-
const bob = useTracker()
|
|
257
|
-
bob.track('button_clicked', { button: 'cta_hero' })
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
**Nuxt plugin** (`plugins/tracker.client.ts`):
|
|
132
|
+
## Vue and Nuxt
|
|
261
133
|
|
|
262
134
|
```ts
|
|
135
|
+
// plugins/indiebob.client.ts (Nuxt) — or app.use(...) after the router in a Vue app
|
|
263
136
|
import { IndieBobPlugin } from '@indiebob/tracker/vue'
|
|
264
137
|
|
|
265
138
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
266
|
-
nuxtApp.vueApp.use(IndieBobPlugin, {
|
|
267
|
-
projectId: useRuntimeConfig().public.trackerProjectId,
|
|
268
|
-
})
|
|
139
|
+
nuxtApp.vueApp.use(IndieBobPlugin, { projectId: 'proj_…' })
|
|
269
140
|
})
|
|
270
141
|
```
|
|
271
142
|
|
|
272
|
-
Page views
|
|
143
|
+
Page views follow vue-router. In a component: `const bob = useTracker()` from
|
|
144
|
+
`@indiebob/tracker/vue`.
|
|
273
145
|
|
|
274
|
-
|
|
146
|
+
## React
|
|
275
147
|
|
|
276
|
-
|
|
277
|
-
import { IndieBob } from '@indiebob/tracker'
|
|
278
|
-
import { useEffect } from 'react'
|
|
148
|
+
There is no React package; call `page()` when the route changes:
|
|
279
149
|
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
// In a page component or layout
|
|
284
|
-
useEffect(() => {
|
|
285
|
-
bob.page()
|
|
286
|
-
}, [pathname])
|
|
150
|
+
```tsx
|
|
151
|
+
const pathname = usePathname()
|
|
152
|
+
useEffect(() => { bob.page() }, [pathname])
|
|
287
153
|
```
|
|
288
154
|
|
|
289
|
-
|
|
155
|
+
## Server
|
|
290
156
|
|
|
291
|
-
|
|
292
|
-
<script src="https://assets.indiebob.com/tracker/latest/indiebob.min.js"></script>
|
|
293
|
-
<script>
|
|
294
|
-
const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })
|
|
295
|
-
bob.page()
|
|
296
|
-
</script>
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Server-Side (Node.js)
|
|
157
|
+
For events with no browser — a webhook, a job:
|
|
300
158
|
|
|
301
159
|
```ts
|
|
302
160
|
import { IndieBob } from '@indiebob/tracker/server'
|
|
303
161
|
|
|
304
|
-
const bob = IndieBob.initServer({ projectId: '
|
|
305
|
-
bob.track('
|
|
162
|
+
const bob = IndieBob.initServer({ projectId: 'proj_…' })
|
|
163
|
+
await bob.track('trial_started', { plan: 'pro' }, 'user_123')
|
|
164
|
+
await bob.identify('user_123', { plan: 'pro' })
|
|
306
165
|
```
|
|
307
166
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
## How It Works
|
|
311
|
-
|
|
312
|
-
### Anonymous ID
|
|
313
|
-
|
|
314
|
-
A random 21-character ID is generated on first visit and stored in `localStorage`. This persists across sessions so you can track a visitor's journey before they sign up.
|
|
315
|
-
|
|
316
|
-
- Stored as `_ib_anon` in localStorage
|
|
317
|
-
- Falls back to sessionStorage if localStorage is blocked
|
|
318
|
-
- Never uses cookies — no consent banner required
|
|
319
|
-
- Cleared when `optOut()` is called
|
|
320
|
-
|
|
321
|
-
### Session ID
|
|
322
|
-
|
|
323
|
-
A session ID is generated per browser tab/window using `crypto.randomUUID()`. Sessions expire after 30 minutes of inactivity or when the tab closes.
|
|
324
|
-
|
|
325
|
-
### UTM Attribution
|
|
326
|
-
|
|
327
|
-
UTM parameters (`utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`) are captured from the URL automatically on `init()`.
|
|
328
|
-
|
|
329
|
-
- **First-touch** attribution stored in `_ib_first_utm` — never overwritten
|
|
330
|
-
- **Last-touch** attribution in `_ib_last_utm` — updated each time UTMs are present in the URL
|
|
331
|
-
- Enables "which campaign drove this paying customer?" — the core attribution question
|
|
332
|
-
|
|
333
|
-
### Event Batching
|
|
334
|
-
|
|
335
|
-
Events are batched for efficiency:
|
|
336
|
-
- Sent when 10 events accumulate
|
|
337
|
-
- Sent every 5 seconds regardless
|
|
338
|
-
- Sent immediately when the page becomes hidden (tab switch, close)
|
|
339
|
-
- Queued in localStorage when offline; flushed when back online
|
|
340
|
-
|
|
341
|
-
### Privacy by Default
|
|
342
|
-
|
|
343
|
-
The SDK respects:
|
|
344
|
-
- **Do Not Track** (`navigator.doNotTrack === '1'`): no events sent, `respectDnt: true` by default
|
|
345
|
-
- **Global Privacy Control** (`navigator.globalPrivacyControl`): legally binding under CCPA in California, `respectGpc: true` by default
|
|
346
|
-
- Neither cookies nor fingerprinting is used at any point
|
|
347
|
-
- IP addresses are never stored by the server — only the country code derived at ingestion time
|
|
348
|
-
|
|
349
|
-
---
|
|
350
|
-
|
|
351
|
-
## What Is and Isn't Collected
|
|
352
|
-
|
|
353
|
-
### Automatically collected (on `page()`)
|
|
354
|
-
|
|
355
|
-
| Data | What | Stored As |
|
|
356
|
-
|---|---|---|
|
|
357
|
-
| Page path | `/pricing`, `/blog/post-1` | String |
|
|
358
|
-
| Page title | `document.title` | String |
|
|
359
|
-
| Referrer | `document.referrer` | String |
|
|
360
|
-
| UTM params | From URL query string | Object |
|
|
361
|
-
| Screen size | `window.innerWidth/Height` | Numbers |
|
|
362
|
-
| Device type | Derived from screen size | `'desktop'` \| `'mobile'` \| `'tablet'` |
|
|
363
|
-
| Browser locale | `navigator.language` | String |
|
|
364
|
-
| Timezone | `Intl.DateTimeFormat().resolvedOptions().timeZone` | String |
|
|
365
|
-
| Country | Derived server-side from IP | String (e.g. `'US'`) |
|
|
366
|
-
|
|
367
|
-
### Auto-detected events (when `autoDetect` is enabled)
|
|
368
|
-
|
|
369
|
-
| Event | Trigger |
|
|
370
|
-
|---|---|
|
|
371
|
-
| `signup_start` | User visits a signup/register page |
|
|
372
|
-
| `signup_submit` | User submits a form on a signup page |
|
|
373
|
-
| `signup` | First `identify()` call (new user) |
|
|
374
|
-
| `login_view` | User visits a login page |
|
|
375
|
-
| `login_submit` | User submits a form on a login page |
|
|
376
|
-
| `login` | Subsequent `identify()` call (returning user) |
|
|
377
|
-
| `pricing_view` | User visits a pricing page |
|
|
378
|
-
| `checkout_view` | User visits a checkout/cart page |
|
|
379
|
-
| `checkout_submit` | User submits a form on a checkout page |
|
|
380
|
-
| `success_view` | User visits a success/thank-you page |
|
|
381
|
-
| `onboarding_view` | User visits an onboarding page |
|
|
382
|
-
| `contact_view` | User visits a contact page |
|
|
383
|
-
| `outbound_click` | User clicks an external link |
|
|
384
|
-
| Custom via `data-ib-event` | User clicks an element with the attribute |
|
|
385
|
-
|
|
386
|
-
### NOT collected
|
|
387
|
-
|
|
388
|
-
- IP addresses (never stored, used only for country lookup then discarded)
|
|
389
|
-
- Browser fingerprint
|
|
390
|
-
- Cookies
|
|
391
|
-
- Personal data (name, email, etc.) unless you pass it to `identify()`
|
|
392
|
-
- Cross-site tracking data
|
|
393
|
-
|
|
394
|
-
---
|
|
395
|
-
|
|
396
|
-
## Privacy & Compliance
|
|
397
|
-
|
|
398
|
-
### GDPR (EU)
|
|
399
|
-
|
|
400
|
-
The SDK does not use cookies and does not collect personal data by default. The `anonymousId` is a random identifier with no link to personal identity. Under GDPR, anonymous analytics may not require a consent banner — but consult your legal team.
|
|
401
|
-
|
|
402
|
-
When a user calls `optOut()`, all stored keys are cleared: `_ib_anon`, `_ib_session`, `_ib_first_utm`, `_ib_last_utm`, `_ib_referrer`, `_ib_queue`.
|
|
403
|
-
|
|
404
|
-
### CCPA (California)
|
|
405
|
-
|
|
406
|
-
Global Privacy Control (GPC) is automatically honored. If a user has GPC enabled in their browser, the SDK sends no events. This is the legally correct behavior under CCPA as amended by CPRA.
|
|
407
|
-
|
|
408
|
-
### Cookie Law (EU ePrivacy Directive)
|
|
409
|
-
|
|
410
|
-
No cookies are set. The localStorage identifiers are not cookies and fall outside the scope of the Cookie Law.
|
|
411
|
-
|
|
412
|
-
---
|
|
413
|
-
|
|
414
|
-
## Self-Hosting
|
|
415
|
-
|
|
416
|
-
Point the SDK at your own IndieBob instance:
|
|
417
|
-
|
|
418
|
-
```ts
|
|
419
|
-
const bob = IndieBob.init({
|
|
420
|
-
projectId: 'proj_abc123def456ghij',
|
|
421
|
-
endpoint: 'https://your-indiebob.example.com/api/collect',
|
|
422
|
-
})
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
---
|
|
426
|
-
|
|
427
|
-
## Versioning & Backwards Compatibility
|
|
428
|
-
|
|
429
|
-
This SDK follows [Semantic Versioning](https://semver.org/).
|
|
430
|
-
|
|
431
|
-
**1.x guarantee**: All public methods listed in this README are stable. We will never:
|
|
432
|
-
- Remove a method in a 1.x release
|
|
433
|
-
- Change the behavior of an existing method without a deprecation period
|
|
434
|
-
- Break the wire format for events that include `v: 1`
|
|
435
|
-
|
|
436
|
-
**Upgrade path**: `npm update @indiebob/tracker` — no code changes required within 1.x.
|
|
437
|
-
|
|
438
|
-
**Deprecation process**: Methods marked `@deprecated` in JSDoc will log a warning in `debug: true` mode. They remain functional until the next major version.
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
## Bundle Size
|
|
443
|
-
|
|
444
|
-
| Bundle | Size (gzip) |
|
|
445
|
-
|---|---|
|
|
446
|
-
| Browser IIFE (`tracker.js`) | < 5KB |
|
|
447
|
-
| ESM tree-shakeable | < 4KB |
|
|
448
|
-
| Server (Node.js) | No limit |
|
|
167
|
+
## Versions
|
|
449
168
|
|
|
450
|
-
|
|
169
|
+
`/tracker/latest/indiebob.min.js` is always the newest release. To stay on one version, load
|
|
170
|
+
`/tracker/v0.4.1/indiebob.min.js`; a versioned file never changes. The npm package follows
|
|
171
|
+
[semver](https://semver.org/); while it is 0.x, a minor release (0.3 → 0.4) can change behaviour, so
|
|
172
|
+
read [CHANGELOG.md](./CHANGELOG.md) before upgrading.
|
|
451
173
|
|
|
452
|
-
|
|
174
|
+
Browsers: the script tag needs Chrome 80, Firefox 74 or Safari 13.1 or newer; the npm build targets
|
|
175
|
+
ES2018.
|
|
453
176
|
|
|
454
177
|
## License
|
|
455
178
|
|