@indiebob/tracker 0.1.0
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 +28 -0
- package/README.md +349 -0
- package/dist/chunk-XKQYO25W.js +2 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.mts +171 -0
- package/dist/index.d.ts +171 -0
- package/dist/index.global.js +2 -0
- package/dist/index.js +1 -0
- package/dist/server.d.mts +39 -0
- package/dist/server.d.ts +39 -0
- package/dist/server.js +93 -0
- package/dist/server.mjs +90 -0
- package/dist/vue.cjs +2 -0
- package/dist/vue.d.mts +29 -0
- package/dist/vue.d.ts +29 -0
- package/dist/vue.global.js +2 -0
- package/dist/vue.js +1 -0
- package/package.json +82 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@indiebob/tracker` will be documented here.
|
|
4
|
+
|
|
5
|
+
This project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## [1.0.0] — 2026-02-24
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `IndieBob.init()` factory — creates a tracker instance with config validation
|
|
13
|
+
- `bob.page()` — track page views with auto-detection of URL, title, referrer
|
|
14
|
+
- `bob.track()` — custom event tracking with arbitrary properties
|
|
15
|
+
- `bob.identify()` — link anonymous visitors to known user IDs
|
|
16
|
+
- `bob.revenue()` — track revenue events for attribution
|
|
17
|
+
- `bob.optOut()` / `bob.optIn()` / `bob.isOptedOut()` — privacy controls
|
|
18
|
+
- `bob.flush()` — force-send all queued events
|
|
19
|
+
- `bob.getAnonymousId()`, `bob.getSessionId()`, `bob.getConfig()` — introspection
|
|
20
|
+
- Event batching: auto-flush at 10 events or 5-second interval
|
|
21
|
+
- `visibilitychange` flush for reliable mobile delivery
|
|
22
|
+
- Offline queue via localStorage with auto-flush on reconnect
|
|
23
|
+
- UTM first-touch and last-touch attribution
|
|
24
|
+
- DNT (Do Not Track) and GPC (Global Privacy Control) support
|
|
25
|
+
- `@indiebob/tracker/vue` — Vue plugin + `useTracker()` composable
|
|
26
|
+
- `@indiebob/tracker/server` — Node.js server-side tracking
|
|
27
|
+
- Browser IIFE bundle: < 5KB gzipped, zero runtime dependencies
|
|
28
|
+
- Event schema v1 with `v: 1` version field for backwards compatibility
|
package/README.md
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# @indiebob/tracker
|
|
2
|
+
|
|
3
|
+
Privacy-first analytics SDK for web applications. Under 5KB gzipped. No cookies. GDPR/CCPA/GPC compliant.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @indiebob/tracker
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @indiebob/tracker
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or via script tag (CDN):
|
|
14
|
+
|
|
15
|
+
```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
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Option | Type | Default | Description |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| `projectId` | `string` | **required** | Your SDK project ID from the dashboard |
|
|
47
|
+
| `endpoint` | `string` | `'https://indiebob.com/api/collect'` | Override for self-hosted instances |
|
|
48
|
+
| `debug` | `boolean` | `false` | Log all events to browser console |
|
|
49
|
+
| `respectDnt` | `boolean` | `true` | Honor the browser's Do Not Track header |
|
|
50
|
+
| `respectGpc` | `boolean` | `true` | Honor Global Privacy Control signal (legally binding in CA/GDPR) |
|
|
51
|
+
|
|
52
|
+
## API Reference
|
|
53
|
+
|
|
54
|
+
### `IndieBob.init(config)`
|
|
55
|
+
|
|
56
|
+
Creates and returns a tracker instance. Call once at app startup.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### `bob.page(overrides?)`
|
|
65
|
+
|
|
66
|
+
Track a page view. Call this on every page load and SPA navigation.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
bob.page() // Auto-detects path, title, referrer
|
|
70
|
+
bob.page({ path: '/pricing' }) // Override path
|
|
71
|
+
bob.page({ title: 'Pricing Page' }) // Override title
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Auto-detected:** URL path, page title, referrer, UTM parameters, device type.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### `bob.track(event, properties?)`
|
|
79
|
+
|
|
80
|
+
Track a custom event.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
bob.track('signup_started')
|
|
84
|
+
bob.track('trial_activated', { plan: 'pro' })
|
|
85
|
+
bob.track('feature_used', { feature: 'csv_export', count: 3 })
|
|
86
|
+
bob.track('checkout_completed', { revenue: 29.00, currency: 'USD', plan: 'pro-monthly' })
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**`event`** — string, required. Max 256 characters. Use `snake_case` by convention.
|
|
90
|
+
|
|
91
|
+
**`properties`** — plain object, optional. Values must be JSON-serializable.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### `bob.identify(userId, traits?)`
|
|
96
|
+
|
|
97
|
+
Associate the current anonymous visitor with a known user ID. Call this after login or signup.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
bob.identify('user_123', {
|
|
101
|
+
email: 'jane@example.com',
|
|
102
|
+
name: 'Jane Smith',
|
|
103
|
+
plan: 'pro',
|
|
104
|
+
createdAt: '2026-01-15',
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**`userId`** — string, required. Your internal user ID. Never pass PII as the userId directly.
|
|
109
|
+
|
|
110
|
+
**`traits`** — plain object, optional. Can include `email` for attribution linking.
|
|
111
|
+
|
|
112
|
+
After `identify()`, all subsequent events are linked to this userId until the session ends.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### `bob.revenue(data)`
|
|
117
|
+
|
|
118
|
+
Track a revenue event. Use alongside Stripe webhooks for complete attribution.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
bob.revenue({
|
|
122
|
+
amount: 29.00, // Required. Positive number.
|
|
123
|
+
currency: 'USD', // Optional. Default: 'USD'. ISO 4217 code.
|
|
124
|
+
plan: 'pro-monthly', // Optional. Your plan name.
|
|
125
|
+
type: 'new', // Optional. 'new' | 'renewal' | 'upgrade' | 'downgrade'
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### Privacy Controls
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
bob.optOut() // Stop all tracking. Clears all stored identifiers.
|
|
135
|
+
bob.optIn() // Re-enable tracking.
|
|
136
|
+
bob.isOptedOut() // Returns boolean.
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### Debug / Testing
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
bob.getAnonymousId() // Returns the current anonymous ID
|
|
145
|
+
bob.getSessionId() // Returns the current session ID
|
|
146
|
+
bob.getConfig() // Returns a read-only copy of the config
|
|
147
|
+
bob.flush() // Force-send all queued events immediately
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Framework Integrations
|
|
153
|
+
|
|
154
|
+
### Vue / Nuxt
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
// main.ts or plugins/tracker.client.ts
|
|
158
|
+
import { IndieBobPlugin } from '@indiebob/tracker/vue'
|
|
159
|
+
|
|
160
|
+
app.use(IndieBobPlugin, { projectId: 'proj_abc123def456ghij' })
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Then in any component:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { useTracker } from '@indiebob/tracker/vue'
|
|
167
|
+
|
|
168
|
+
const bob = useTracker()
|
|
169
|
+
bob.track('button_clicked', { button: 'cta_hero' })
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Nuxt plugin** (`plugins/tracker.client.ts`):
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { IndieBobPlugin } from '@indiebob/tracker/vue'
|
|
176
|
+
|
|
177
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
178
|
+
nuxtApp.vueApp.use(IndieBobPlugin, {
|
|
179
|
+
projectId: useRuntimeConfig().public.trackerProjectId,
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Page views auto-track on vue-router navigation.
|
|
185
|
+
|
|
186
|
+
### React
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
import { IndieBob } from '@indiebob/tracker'
|
|
190
|
+
import { useEffect } from 'react'
|
|
191
|
+
|
|
192
|
+
// Initialize once at app root
|
|
193
|
+
const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })
|
|
194
|
+
|
|
195
|
+
// In a page component or layout
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
bob.page()
|
|
198
|
+
}, [pathname])
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Vanilla JS (Script Tag)
|
|
202
|
+
|
|
203
|
+
```html
|
|
204
|
+
<script src="https://assets.indiebob.com/tracker/latest/indiebob.min.js"></script>
|
|
205
|
+
<script>
|
|
206
|
+
const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })
|
|
207
|
+
bob.page()
|
|
208
|
+
</script>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Server-Side (Node.js)
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { IndieBob } from '@indiebob/tracker/server'
|
|
215
|
+
|
|
216
|
+
const bob = IndieBob.initServer({ projectId: 'proj_abc123def456ghij' })
|
|
217
|
+
bob.track('server_event', { source: 'cron_job' })
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## How It Works
|
|
223
|
+
|
|
224
|
+
### Anonymous ID
|
|
225
|
+
|
|
226
|
+
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.
|
|
227
|
+
|
|
228
|
+
- Stored as `_ib_anon` in localStorage
|
|
229
|
+
- Falls back to sessionStorage if localStorage is blocked
|
|
230
|
+
- Never uses cookies — no consent banner required
|
|
231
|
+
- Cleared when `optOut()` is called
|
|
232
|
+
|
|
233
|
+
### Session ID
|
|
234
|
+
|
|
235
|
+
A session ID is generated per browser tab/window using `crypto.randomUUID()`. Sessions expire after 30 minutes of inactivity or when the tab closes.
|
|
236
|
+
|
|
237
|
+
### UTM Attribution
|
|
238
|
+
|
|
239
|
+
UTM parameters (`utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`) are captured from the URL automatically on `init()`.
|
|
240
|
+
|
|
241
|
+
- **First-touch** attribution stored in `_ib_first_utm` — never overwritten
|
|
242
|
+
- **Last-touch** attribution in `_ib_last_utm` — updated each time UTMs are present in the URL
|
|
243
|
+
- Enables "which campaign drove this paying customer?" — the core attribution question
|
|
244
|
+
|
|
245
|
+
### Event Batching
|
|
246
|
+
|
|
247
|
+
Events are batched for efficiency:
|
|
248
|
+
- Sent when 10 events accumulate
|
|
249
|
+
- Sent every 5 seconds regardless
|
|
250
|
+
- Sent immediately when the page becomes hidden (tab switch, close)
|
|
251
|
+
- Queued in localStorage when offline; flushed when back online
|
|
252
|
+
|
|
253
|
+
### Privacy by Default
|
|
254
|
+
|
|
255
|
+
The SDK respects:
|
|
256
|
+
- **Do Not Track** (`navigator.doNotTrack === '1'`): no events sent, `respectDnt: true` by default
|
|
257
|
+
- **Global Privacy Control** (`navigator.globalPrivacyControl`): legally binding under CCPA in California, `respectGpc: true` by default
|
|
258
|
+
- Neither cookies nor fingerprinting is used at any point
|
|
259
|
+
- IP addresses are never stored by the server — only the country code derived at ingestion time
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## What Is and Isn't Collected
|
|
264
|
+
|
|
265
|
+
### Automatically collected (on `page()`)
|
|
266
|
+
|
|
267
|
+
| Data | What | Stored As |
|
|
268
|
+
|---|---|---|
|
|
269
|
+
| Page path | `/pricing`, `/blog/post-1` | String |
|
|
270
|
+
| Page title | `document.title` | String |
|
|
271
|
+
| Referrer | `document.referrer` | String |
|
|
272
|
+
| UTM params | From URL query string | Object |
|
|
273
|
+
| Screen size | `window.innerWidth/Height` | Numbers |
|
|
274
|
+
| Device type | Derived from screen size | `'desktop'` \| `'mobile'` \| `'tablet'` |
|
|
275
|
+
| Browser locale | `navigator.language` | String |
|
|
276
|
+
| Timezone | `Intl.DateTimeFormat().resolvedOptions().timeZone` | String |
|
|
277
|
+
| Country | Derived server-side from IP | String (e.g. `'US'`) |
|
|
278
|
+
|
|
279
|
+
### NOT collected
|
|
280
|
+
|
|
281
|
+
- IP addresses (never stored, used only for country lookup then discarded)
|
|
282
|
+
- Browser fingerprint
|
|
283
|
+
- Cookies
|
|
284
|
+
- Personal data (name, email, etc.) unless you pass it to `identify()`
|
|
285
|
+
- Cross-site tracking data
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Privacy & Compliance
|
|
290
|
+
|
|
291
|
+
### GDPR (EU)
|
|
292
|
+
|
|
293
|
+
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.
|
|
294
|
+
|
|
295
|
+
When a user calls `optOut()`, all stored keys are cleared: `_ib_anon`, `_ib_session`, `_ib_first_utm`, `_ib_last_utm`, `_ib_referrer`, `_ib_queue`.
|
|
296
|
+
|
|
297
|
+
### CCPA (California)
|
|
298
|
+
|
|
299
|
+
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.
|
|
300
|
+
|
|
301
|
+
### Cookie Law (EU ePrivacy Directive)
|
|
302
|
+
|
|
303
|
+
No cookies are set. The localStorage identifiers are not cookies and fall outside the scope of the Cookie Law.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Self-Hosting
|
|
308
|
+
|
|
309
|
+
Point the SDK at your own IndieBob instance:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
const bob = IndieBob.init({
|
|
313
|
+
projectId: 'proj_abc123def456ghij',
|
|
314
|
+
endpoint: 'https://your-indiebob.example.com/api/collect',
|
|
315
|
+
})
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Versioning & Backwards Compatibility
|
|
321
|
+
|
|
322
|
+
This SDK follows [Semantic Versioning](https://semver.org/).
|
|
323
|
+
|
|
324
|
+
**1.x guarantee**: All public methods listed in this README are stable. We will never:
|
|
325
|
+
- Remove a method in a 1.x release
|
|
326
|
+
- Change the behavior of an existing method without a deprecation period
|
|
327
|
+
- Break the wire format for events that include `v: 1`
|
|
328
|
+
|
|
329
|
+
**Upgrade path**: `npm update @indiebob/tracker` — no code changes required within 1.x.
|
|
330
|
+
|
|
331
|
+
**Deprecation process**: Methods marked `@deprecated` in JSDoc will log a warning in `debug: true` mode. They remain functional until the next major version.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Bundle Size
|
|
336
|
+
|
|
337
|
+
| Bundle | Size (gzip) |
|
|
338
|
+
|---|---|
|
|
339
|
+
| Browser IIFE (`tracker.js`) | < 5KB |
|
|
340
|
+
| ESM tree-shakeable | < 4KB |
|
|
341
|
+
| Server (Node.js) | No limit |
|
|
342
|
+
|
|
343
|
+
Zero runtime dependencies.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## License
|
|
348
|
+
|
|
349
|
+
MIT
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var J=Object.defineProperty,z=Object.defineProperties;var G=Object.getOwnPropertyDescriptors;var k=Object.getOwnPropertySymbols;var W=Object.prototype.hasOwnProperty,Z=Object.prototype.propertyIsEnumerable;var A=(e,t,o)=>t in e?J(e,t,{enumerable:true,configurable:true,writable:true,value:o}):e[t]=o,d=(e,t)=>{for(var o in t||(t={}))W.call(t,o)&&A(e,o,t[o]);if(k)for(var o of k(t))Z.call(t,o)&&A(e,o,t[o]);return e},x=(e,t)=>z(e,G(t));var a=(e,t,o)=>new Promise((n,i)=>{var r=s=>{try{l(o.next(s));}catch(c){i(c);}},g=s=>{try{l(o.throw(s));}catch(c){i(c);}},l=s=>s.done?n(s.value):Promise.resolve(s.value).then(r,g);l((o=o.apply(e,t)).next());});var $="https://indiebob.com/api/collect";function D(e){var t,o,n,i;if(!e.projectId||!e.projectId.startsWith("proj_"))throw new Error(`[IndieBob] Invalid projectId "${e.projectId}". Must start with "proj_".`);return {projectId:e.projectId,endpoint:(t=e.endpoint)!=null?t:$,debug:(o=e.debug)!=null?o:false,respectDnt:(n=e.respectDnt)!=null?n:true,respectGpc:(i=e.respectGpc)!=null?i:true}}var u="_ib_anon";function h(){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-",t="";if(typeof crypto!="undefined"&&crypto.getRandomValues){let o=new Uint8Array(21);crypto.getRandomValues(o);for(let n of o)t+=e[n%e.length];}else for(let o=0;o<21;o++)t+=e[Math.floor(Math.random()*e.length)];return t}function v(){try{let e=localStorage.getItem(u);return e||(e=h(),localStorage.setItem(u,e)),e}catch(e){try{let t=sessionStorage.getItem(u);return t||(t=h(),sessionStorage.setItem(u,t)),t}catch(t){return h()}}}function P(){try{localStorage.removeItem(u);}catch(e){}try{sessionStorage.removeItem(u);}catch(e){}}var y="_ib_session",I="_ib_session_last";function N(){try{return crypto.randomUUID()}catch(e){return Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)}}function S(){var e;try{let t=Date.now(),o=parseInt((e=sessionStorage.getItem(I))!=null?e:"0",10),n=sessionStorage.getItem(y),i=o>0&&t-o>18e5;return (!n||i)&&(n=N(),sessionStorage.setItem(y,n)),sessionStorage.setItem(I,String(t)),n}catch(t){return N()}}function U(){try{sessionStorage.removeItem(y);}catch(e){}try{sessionStorage.removeItem(I);}catch(e){}}var b="_ib_first_utm",M="_ib_last_utm",_="_ib_referrer";function q(e){let t=new URLSearchParams(e),o={},n=false;return t.has("utm_source")&&(o.source=t.get("utm_source"),n=true),t.has("utm_medium")&&(o.medium=t.get("utm_medium"),n=true),t.has("utm_campaign")&&(o.campaign=t.get("utm_campaign"),n=true),t.has("utm_term")&&(o.term=t.get("utm_term"),n=true),t.has("utm_content")&&(o.content=t.get("utm_content"),n=true),n?o:null}function B(e,t){let o=q(e);try{o&&!localStorage.getItem(b)&&localStorage.setItem(b,JSON.stringify(o)),o&&localStorage.setItem(M,JSON.stringify(o)),t&&!localStorage.getItem(_)&&localStorage.setItem(_,t);}catch(n){}}function F(e){var t;return (t=q(e))!=null?t:void 0}function L(){try{localStorage.removeItem(b);}catch(e){}try{localStorage.removeItem(M);}catch(e){}try{localStorage.removeItem(_);}catch(e){}}var p="_ib_optout",tt="_ib_queue";function f(e){try{if(localStorage.getItem(p))return !1}catch(t){}return !(e.respectDnt&&typeof navigator!="undefined"&&(navigator.doNotTrack==="1"||navigator.msDoNotTrack==="1")||e.respectGpc&&typeof navigator!="undefined"&&navigator.globalPrivacyControl===true)}function j(){try{localStorage.setItem(p,"1");}catch(e){}P(),U(),L();try{localStorage.removeItem(tt);}catch(e){}}function Y(){try{localStorage.removeItem(p);}catch(e){}}function K(){try{return !!localStorage.getItem(p)}catch(e){return false}}var et=[1e3,2e3,4e3];function ot(e){return a(this,null,function*(){return new Promise(t=>setTimeout(t,e))})}function w(e,t,o=false){return a(this,null,function*(){let n=JSON.stringify(e);if(o&&typeof navigator!="undefined"&&navigator.sendBeacon){let i=new Blob([n],{type:"application/json"}),r=navigator.sendBeacon(t.endpoint,i);return t.debug&&console.log("[IndieBob] sendBeacon:",r,e.batch.length,"events"),r}for(let i=0;i<3;i++){try{let r=yield fetch(t.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:n,keepalive:!0});if(r.ok)return t.debug&&console.log("[IndieBob] sent:",e.batch.length,"events"),!0;if(r.status>=400&&r.status<500)return t.debug&&console.warn("[IndieBob] 4xx error, not retrying:",r.status),!1}catch(r){t.debug&&console.warn("[IndieBob] fetch error (attempt",i+1,"):",r);}i<2&&(yield ot(et[i]));}return t.debug&&console.warn("[IndieBob] failed to send after",3,"attempts"),false})}var E="_ib_queue",nt=10,it=5e3,rt=50,m=class{constructor(t){this.queue=[];this.timer=null;this.flushing=false;this.config=t,this.startTimer(),this.setupVisibilityFlush(),this.setupOnlineFlush();}push(t){this.queue.push(t),this.queue.length>=nt&&this.flush();}flush(t=false){return a(this,null,function*(){if(this.flushing&&!t||this.queue.length===0)return;this.flushing=true;let o=this.queue.splice(0);(yield w({batch:o},this.config,t))||this.pushToOfflineQueue(o),this.flushing=false;})}flushOfflineQueue(){return a(this,null,function*(){let t=this.getOfflineQueue();if(t.length===0)return;this.clearOfflineQueue(),yield w({batch:t},this.config);})}destroy(){this.timer&&clearInterval(this.timer);}startTimer(){this.timer=setInterval(()=>{this.queue.length>0&&this.flush();},it);}setupVisibilityFlush(){typeof document!="undefined"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush(true);},{passive:true});}setupOnlineFlush(){typeof window!="undefined"&&(window.addEventListener("online",()=>{this.flushOfflineQueue();}),navigator.onLine&&this.flushOfflineQueue());}pushToOfflineQueue(t){try{let i=[...this.getOfflineQueue(),...t].slice(-rt);localStorage.setItem(E,JSON.stringify(i));}catch(o){}}getOfflineQueue(){try{let t=localStorage.getItem(E);return t?JSON.parse(t):[]}catch(t){return []}}clearOfflineQueue(){try{localStorage.removeItem(E);}catch(t){}}};function Q(){if(typeof window=="undefined")return "desktop";let e=window.innerWidth;return e<=768?"mobile":e<=1024?"tablet":"desktop"}function V(){return typeof window=="undefined"?{width:0,height:0}:{width:window.innerWidth,height:window.innerHeight}}function T(e){var c,O,R,C;let t=typeof window=="undefined",o=(c=e==null?void 0:e.path)!=null?c:t?"":window.location.pathname+window.location.search,n=t?"":window.location.href,i=(O=e==null?void 0:e.title)!=null?O:t?"":document.title,r=(R=e==null?void 0:e.referrer)!=null?R:t?"":document.referrer,g=V(),l=Q(),s=t?void 0:F(window.location.search);return {page:{path:o,url:n,title:i,referrer:r},device:x(d({},g),{type:l}),utm:s,locale:t?"en":navigator.language,timezone:t?"UTC":(C=Intl.DateTimeFormat().resolvedOptions().timeZone)!=null?C:"UTC",sdkVersion:"0.1.0"}}function H(e){if(typeof window=="undefined")return ()=>{};let t=history.pushState.bind(history),o=history.replaceState.bind(history);return history.pushState=function(...n){t(...n),e();},history.replaceState=function(...n){o(...n);},window.addEventListener("popstate",e),()=>{history.pushState=t,history.replaceState=o,window.removeEventListener("popstate",e);}}var X=class e{constructor(t){this.cleanupHistory=null;this.config=t,this.queue=new m(t),typeof window!="undefined"&&B(window.location.search,document.referrer);}static init(t){let o=D(t);return new e(o)}page(t){f(this.config)&&this.queue.push(this.buildEvent("page",{context:T(t)}));}track(t,o){f(this.config)&&this.queue.push(this.buildEvent("track",{event:t,properties:o}));}identify(t,o){f(this.config)&&(this.userId=t,this.queue.push(this.buildEvent("identify",{traits:o,userId:t})));}revenue(t){var o;f(this.config)&&this.queue.push(this.buildEvent("revenue",{revenue:{amount:t.amount,currency:(o=t.currency)!=null?o:"USD",plan:t.plan,type:t.type}}));}optOut(){this.queue.flush(),j(),this.userId=void 0,this.config.debug&&console.log("[IndieBob] opted out");}optIn(){Y(),this.config.debug&&console.log("[IndieBob] opted in");}isOptedOut(){return K()}flush(){return a(this,null,function*(){yield this.queue.flush();})}getAnonymousId(){return v()}getSessionId(){return S()}getConfig(){return Object.freeze(d({},this.config))}enableAutoPage(){var t;return (t=this.cleanupHistory)==null||t.call(this),this.cleanupHistory=H(()=>this.page()),()=>{var o;(o=this.cleanupHistory)==null||o.call(this),this.cleanupHistory=null;}}buildEvent(t,o){var n,i;return {v:1,projectId:this.config.projectId,type:t,anonymousId:v(),userId:(n=this.userId)!=null?n:o.userId,sessionId:S(),timestamp:new Date().toISOString(),context:(i=o.context)!=null?i:T(),event:o.event,properties:o.properties,traits:o.traits,revenue:o.revenue}}};
|
|
2
|
+
export{X as a};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var J=Object.defineProperty,z=Object.defineProperties;var G=Object.getOwnPropertyDescriptors;var k=Object.getOwnPropertySymbols;var W=Object.prototype.hasOwnProperty,Z=Object.prototype.propertyIsEnumerable;var A=(e,t,o)=>t in e?J(e,t,{enumerable:true,configurable:true,writable:true,value:o}):e[t]=o,d=(e,t)=>{for(var o in t||(t={}))W.call(t,o)&&A(e,o,t[o]);if(k)for(var o of k(t))Z.call(t,o)&&A(e,o,t[o]);return e},x=(e,t)=>z(e,G(t));var a=(e,t,o)=>new Promise((n,i)=>{var r=s=>{try{l(o.next(s));}catch(c){i(c);}},g=s=>{try{l(o.throw(s));}catch(c){i(c);}},l=s=>s.done?n(s.value):Promise.resolve(s.value).then(r,g);l((o=o.apply(e,t)).next());});var $="https://indiebob.com/api/collect";function D(e){var t,o,n,i;if(!e.projectId||!e.projectId.startsWith("proj_"))throw new Error(`[IndieBob] Invalid projectId "${e.projectId}". Must start with "proj_".`);return {projectId:e.projectId,endpoint:(t=e.endpoint)!=null?t:$,debug:(o=e.debug)!=null?o:false,respectDnt:(n=e.respectDnt)!=null?n:true,respectGpc:(i=e.respectGpc)!=null?i:true}}var u="_ib_anon";function h(){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-",t="";if(typeof crypto!="undefined"&&crypto.getRandomValues){let o=new Uint8Array(21);crypto.getRandomValues(o);for(let n of o)t+=e[n%e.length];}else for(let o=0;o<21;o++)t+=e[Math.floor(Math.random()*e.length)];return t}function v(){try{let e=localStorage.getItem(u);return e||(e=h(),localStorage.setItem(u,e)),e}catch(e){try{let t=sessionStorage.getItem(u);return t||(t=h(),sessionStorage.setItem(u,t)),t}catch(t){return h()}}}function P(){try{localStorage.removeItem(u);}catch(e){}try{sessionStorage.removeItem(u);}catch(e){}}var y="_ib_session",I="_ib_session_last";function N(){try{return crypto.randomUUID()}catch(e){return Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)}}function S(){var e;try{let t=Date.now(),o=parseInt((e=sessionStorage.getItem(I))!=null?e:"0",10),n=sessionStorage.getItem(y),i=o>0&&t-o>18e5;return (!n||i)&&(n=N(),sessionStorage.setItem(y,n)),sessionStorage.setItem(I,String(t)),n}catch(t){return N()}}function U(){try{sessionStorage.removeItem(y);}catch(e){}try{sessionStorage.removeItem(I);}catch(e){}}var b="_ib_first_utm",M="_ib_last_utm",_="_ib_referrer";function q(e){let t=new URLSearchParams(e),o={},n=false;return t.has("utm_source")&&(o.source=t.get("utm_source"),n=true),t.has("utm_medium")&&(o.medium=t.get("utm_medium"),n=true),t.has("utm_campaign")&&(o.campaign=t.get("utm_campaign"),n=true),t.has("utm_term")&&(o.term=t.get("utm_term"),n=true),t.has("utm_content")&&(o.content=t.get("utm_content"),n=true),n?o:null}function B(e,t){let o=q(e);try{o&&!localStorage.getItem(b)&&localStorage.setItem(b,JSON.stringify(o)),o&&localStorage.setItem(M,JSON.stringify(o)),t&&!localStorage.getItem(_)&&localStorage.setItem(_,t);}catch(n){}}function F(e){var t;return (t=q(e))!=null?t:void 0}function L(){try{localStorage.removeItem(b);}catch(e){}try{localStorage.removeItem(M);}catch(e){}try{localStorage.removeItem(_);}catch(e){}}var p="_ib_optout",tt="_ib_queue";function f(e){try{if(localStorage.getItem(p))return !1}catch(t){}return !(e.respectDnt&&typeof navigator!="undefined"&&(navigator.doNotTrack==="1"||navigator.msDoNotTrack==="1")||e.respectGpc&&typeof navigator!="undefined"&&navigator.globalPrivacyControl===true)}function j(){try{localStorage.setItem(p,"1");}catch(e){}P(),U(),L();try{localStorage.removeItem(tt);}catch(e){}}function Y(){try{localStorage.removeItem(p);}catch(e){}}function K(){try{return !!localStorage.getItem(p)}catch(e){return false}}var et=[1e3,2e3,4e3];function ot(e){return a(this,null,function*(){return new Promise(t=>setTimeout(t,e))})}function w(e,t,o=false){return a(this,null,function*(){let n=JSON.stringify(e);if(o&&typeof navigator!="undefined"&&navigator.sendBeacon){let i=new Blob([n],{type:"application/json"}),r=navigator.sendBeacon(t.endpoint,i);return t.debug&&console.log("[IndieBob] sendBeacon:",r,e.batch.length,"events"),r}for(let i=0;i<3;i++){try{let r=yield fetch(t.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:n,keepalive:!0});if(r.ok)return t.debug&&console.log("[IndieBob] sent:",e.batch.length,"events"),!0;if(r.status>=400&&r.status<500)return t.debug&&console.warn("[IndieBob] 4xx error, not retrying:",r.status),!1}catch(r){t.debug&&console.warn("[IndieBob] fetch error (attempt",i+1,"):",r);}i<2&&(yield ot(et[i]));}return t.debug&&console.warn("[IndieBob] failed to send after",3,"attempts"),false})}var E="_ib_queue",nt=10,it=5e3,rt=50,m=class{constructor(t){this.queue=[];this.timer=null;this.flushing=false;this.config=t,this.startTimer(),this.setupVisibilityFlush(),this.setupOnlineFlush();}push(t){this.queue.push(t),this.queue.length>=nt&&this.flush();}flush(t=false){return a(this,null,function*(){if(this.flushing&&!t||this.queue.length===0)return;this.flushing=true;let o=this.queue.splice(0);(yield w({batch:o},this.config,t))||this.pushToOfflineQueue(o),this.flushing=false;})}flushOfflineQueue(){return a(this,null,function*(){let t=this.getOfflineQueue();if(t.length===0)return;this.clearOfflineQueue(),yield w({batch:t},this.config);})}destroy(){this.timer&&clearInterval(this.timer);}startTimer(){this.timer=setInterval(()=>{this.queue.length>0&&this.flush();},it);}setupVisibilityFlush(){typeof document!="undefined"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush(true);},{passive:true});}setupOnlineFlush(){typeof window!="undefined"&&(window.addEventListener("online",()=>{this.flushOfflineQueue();}),navigator.onLine&&this.flushOfflineQueue());}pushToOfflineQueue(t){try{let i=[...this.getOfflineQueue(),...t].slice(-rt);localStorage.setItem(E,JSON.stringify(i));}catch(o){}}getOfflineQueue(){try{let t=localStorage.getItem(E);return t?JSON.parse(t):[]}catch(t){return []}}clearOfflineQueue(){try{localStorage.removeItem(E);}catch(t){}}};function Q(){if(typeof window=="undefined")return "desktop";let e=window.innerWidth;return e<=768?"mobile":e<=1024?"tablet":"desktop"}function V(){return typeof window=="undefined"?{width:0,height:0}:{width:window.innerWidth,height:window.innerHeight}}function T(e){var c,O,R,C;let t=typeof window=="undefined",o=(c=e==null?void 0:e.path)!=null?c:t?"":window.location.pathname+window.location.search,n=t?"":window.location.href,i=(O=e==null?void 0:e.title)!=null?O:t?"":document.title,r=(R=e==null?void 0:e.referrer)!=null?R:t?"":document.referrer,g=V(),l=Q(),s=t?void 0:F(window.location.search);return {page:{path:o,url:n,title:i,referrer:r},device:x(d({},g),{type:l}),utm:s,locale:t?"en":navigator.language,timezone:t?"UTC":(C=Intl.DateTimeFormat().resolvedOptions().timeZone)!=null?C:"UTC",sdkVersion:"0.1.0"}}function H(e){if(typeof window=="undefined")return ()=>{};let t=history.pushState.bind(history),o=history.replaceState.bind(history);return history.pushState=function(...n){t(...n),e();},history.replaceState=function(...n){o(...n);},window.addEventListener("popstate",e),()=>{history.pushState=t,history.replaceState=o,window.removeEventListener("popstate",e);}}var X=class e{constructor(t){this.cleanupHistory=null;this.config=t,this.queue=new m(t),typeof window!="undefined"&&B(window.location.search,document.referrer);}static init(t){let o=D(t);return new e(o)}page(t){f(this.config)&&this.queue.push(this.buildEvent("page",{context:T(t)}));}track(t,o){f(this.config)&&this.queue.push(this.buildEvent("track",{event:t,properties:o}));}identify(t,o){f(this.config)&&(this.userId=t,this.queue.push(this.buildEvent("identify",{traits:o,userId:t})));}revenue(t){var o;f(this.config)&&this.queue.push(this.buildEvent("revenue",{revenue:{amount:t.amount,currency:(o=t.currency)!=null?o:"USD",plan:t.plan,type:t.type}}));}optOut(){this.queue.flush(),j(),this.userId=void 0,this.config.debug&&console.log("[IndieBob] opted out");}optIn(){Y(),this.config.debug&&console.log("[IndieBob] opted in");}isOptedOut(){return K()}flush(){return a(this,null,function*(){yield this.queue.flush();})}getAnonymousId(){return v()}getSessionId(){return S()}getConfig(){return Object.freeze(d({},this.config))}enableAutoPage(){var t;return (t=this.cleanupHistory)==null||t.call(this),this.cleanupHistory=H(()=>this.page()),()=>{var o;(o=this.cleanupHistory)==null||o.call(this),this.cleanupHistory=null;}}buildEvent(t,o){var n,i;return {v:1,projectId:this.config.projectId,type:t,anonymousId:v(),userId:(n=this.userId)!=null?n:o.userId,sessionId:S(),timestamp:new Date().toISOString(),context:(i=o.context)!=null?i:T(),event:o.event,properties:o.properties,traits:o.traits,revenue:o.revenue}}};
|
|
2
|
+
exports.IndieBob=X;
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the IndieBob tracker.
|
|
3
|
+
*/
|
|
4
|
+
interface TrackerConfig {
|
|
5
|
+
/** Your SDK project ID from the IndieBob dashboard. Format: 'proj_...' */
|
|
6
|
+
projectId: string;
|
|
7
|
+
/** Ingestion endpoint URL. Default: 'https://indiebob.com/api/collect' */
|
|
8
|
+
endpoint?: string;
|
|
9
|
+
/** Log all events to the browser console. Default: false */
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
/** Honor the browser Do Not Track header. Default: true */
|
|
12
|
+
respectDnt?: boolean;
|
|
13
|
+
/** Honor Global Privacy Control signal (legally binding in CA/GDPR). Default: true */
|
|
14
|
+
respectGpc?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Resolved config with all defaults applied. */
|
|
17
|
+
interface ResolvedConfig {
|
|
18
|
+
projectId: string;
|
|
19
|
+
endpoint: string;
|
|
20
|
+
debug: boolean;
|
|
21
|
+
respectDnt: boolean;
|
|
22
|
+
respectGpc: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** The tracking event type. */
|
|
25
|
+
type EventType = 'page' | 'track' | 'identify' | 'revenue';
|
|
26
|
+
/** Device category. */
|
|
27
|
+
type DeviceType = 'desktop' | 'mobile' | 'tablet';
|
|
28
|
+
/** Revenue event type. */
|
|
29
|
+
type RevenueType = 'new' | 'renewal' | 'upgrade' | 'downgrade';
|
|
30
|
+
/** UTM attribution data. */
|
|
31
|
+
interface UtmData {
|
|
32
|
+
source?: string;
|
|
33
|
+
medium?: string;
|
|
34
|
+
campaign?: string;
|
|
35
|
+
term?: string;
|
|
36
|
+
content?: string;
|
|
37
|
+
}
|
|
38
|
+
/** Context attached to every event. */
|
|
39
|
+
interface EventContext {
|
|
40
|
+
page: {
|
|
41
|
+
path: string;
|
|
42
|
+
url: string;
|
|
43
|
+
title: string;
|
|
44
|
+
referrer: string;
|
|
45
|
+
};
|
|
46
|
+
device: {
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
type: DeviceType;
|
|
50
|
+
};
|
|
51
|
+
utm?: UtmData;
|
|
52
|
+
locale: string;
|
|
53
|
+
timezone: string;
|
|
54
|
+
sdkVersion: string;
|
|
55
|
+
}
|
|
56
|
+
/** A single tracking event in wire format (v1 schema). */
|
|
57
|
+
interface TrackerEvent {
|
|
58
|
+
/** Schema version. Always 1 for this SDK. Enables server-side backwards compat. */
|
|
59
|
+
v: 1;
|
|
60
|
+
projectId: string;
|
|
61
|
+
type: EventType;
|
|
62
|
+
anonymousId: string;
|
|
63
|
+
userId?: string;
|
|
64
|
+
sessionId: string;
|
|
65
|
+
timestamp: string;
|
|
66
|
+
context: EventContext;
|
|
67
|
+
event?: string;
|
|
68
|
+
properties?: Record<string, unknown>;
|
|
69
|
+
traits?: Record<string, unknown>;
|
|
70
|
+
revenue?: {
|
|
71
|
+
amount: number;
|
|
72
|
+
currency: string;
|
|
73
|
+
plan?: string;
|
|
74
|
+
type?: RevenueType;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Overrides for manual page() calls. */
|
|
78
|
+
interface PageOverrides {
|
|
79
|
+
path?: string;
|
|
80
|
+
title?: string;
|
|
81
|
+
referrer?: string;
|
|
82
|
+
}
|
|
83
|
+
/** Revenue data for bob.revenue(). */
|
|
84
|
+
interface RevenueData {
|
|
85
|
+
amount: number;
|
|
86
|
+
currency?: string;
|
|
87
|
+
plan?: string;
|
|
88
|
+
type?: RevenueType;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* IndieBob Analytics Tracker.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })
|
|
96
|
+
* bob.page()
|
|
97
|
+
*/
|
|
98
|
+
declare class IndieBob {
|
|
99
|
+
private config;
|
|
100
|
+
private queue;
|
|
101
|
+
private userId;
|
|
102
|
+
private cleanupHistory;
|
|
103
|
+
private constructor();
|
|
104
|
+
/**
|
|
105
|
+
* Create a tracker instance. Call once at app startup.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* const bob = IndieBob.init({ projectId: 'proj_your16charId' })
|
|
109
|
+
*/
|
|
110
|
+
static init(config: TrackerConfig): IndieBob;
|
|
111
|
+
/**
|
|
112
|
+
* Track a page view. Call on every page load and SPA navigation.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* bob.page()
|
|
116
|
+
* bob.page({ path: '/pricing' })
|
|
117
|
+
*/
|
|
118
|
+
page(overrides?: PageOverrides): void;
|
|
119
|
+
/**
|
|
120
|
+
* Track a custom event.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* bob.track('signup_started')
|
|
124
|
+
* bob.track('feature_used', { feature: 'csv_export' })
|
|
125
|
+
*/
|
|
126
|
+
track(event: string, properties?: Record<string, unknown>): void;
|
|
127
|
+
/**
|
|
128
|
+
* Associate the current anonymous visitor with a known user ID.
|
|
129
|
+
* Call after login or signup.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* bob.identify('user_123', { email: 'jane@example.com', plan: 'pro' })
|
|
133
|
+
*/
|
|
134
|
+
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
135
|
+
/**
|
|
136
|
+
* Track a revenue event.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* bob.revenue({ amount: 29.00, currency: 'USD', plan: 'pro-monthly', type: 'new' })
|
|
140
|
+
*/
|
|
141
|
+
revenue(data: RevenueData): void;
|
|
142
|
+
/**
|
|
143
|
+
* Stop all tracking and clear all stored identifiers.
|
|
144
|
+
* Respects user's privacy preferences.
|
|
145
|
+
*/
|
|
146
|
+
optOut(): void;
|
|
147
|
+
/** Re-enable tracking after optOut(). */
|
|
148
|
+
optIn(): void;
|
|
149
|
+
/** Returns true if the user has explicitly opted out. */
|
|
150
|
+
isOptedOut(): boolean;
|
|
151
|
+
/** Force-send all queued events immediately. Useful in tests. */
|
|
152
|
+
flush(): Promise<void>;
|
|
153
|
+
/** Get the current anonymous ID. */
|
|
154
|
+
getAnonymousId(): string;
|
|
155
|
+
/** Get the current session ID. */
|
|
156
|
+
getSessionId(): string;
|
|
157
|
+
/** Get the resolved configuration (read-only). */
|
|
158
|
+
getConfig(): Readonly<ResolvedConfig>;
|
|
159
|
+
/**
|
|
160
|
+
* Enable auto-tracking of SPA navigation.
|
|
161
|
+
* Returns a cleanup function to remove the listeners.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* const cleanup = bob.enableAutoPage()
|
|
165
|
+
* // Later: cleanup()
|
|
166
|
+
*/
|
|
167
|
+
enableAutoPage(): () => void;
|
|
168
|
+
private buildEvent;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export { type DeviceType, type EventContext, type EventType, IndieBob, type PageOverrides, type RevenueData, type RevenueType, type TrackerConfig, type TrackerEvent, type UtmData };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the IndieBob tracker.
|
|
3
|
+
*/
|
|
4
|
+
interface TrackerConfig {
|
|
5
|
+
/** Your SDK project ID from the IndieBob dashboard. Format: 'proj_...' */
|
|
6
|
+
projectId: string;
|
|
7
|
+
/** Ingestion endpoint URL. Default: 'https://indiebob.com/api/collect' */
|
|
8
|
+
endpoint?: string;
|
|
9
|
+
/** Log all events to the browser console. Default: false */
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
/** Honor the browser Do Not Track header. Default: true */
|
|
12
|
+
respectDnt?: boolean;
|
|
13
|
+
/** Honor Global Privacy Control signal (legally binding in CA/GDPR). Default: true */
|
|
14
|
+
respectGpc?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Resolved config with all defaults applied. */
|
|
17
|
+
interface ResolvedConfig {
|
|
18
|
+
projectId: string;
|
|
19
|
+
endpoint: string;
|
|
20
|
+
debug: boolean;
|
|
21
|
+
respectDnt: boolean;
|
|
22
|
+
respectGpc: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** The tracking event type. */
|
|
25
|
+
type EventType = 'page' | 'track' | 'identify' | 'revenue';
|
|
26
|
+
/** Device category. */
|
|
27
|
+
type DeviceType = 'desktop' | 'mobile' | 'tablet';
|
|
28
|
+
/** Revenue event type. */
|
|
29
|
+
type RevenueType = 'new' | 'renewal' | 'upgrade' | 'downgrade';
|
|
30
|
+
/** UTM attribution data. */
|
|
31
|
+
interface UtmData {
|
|
32
|
+
source?: string;
|
|
33
|
+
medium?: string;
|
|
34
|
+
campaign?: string;
|
|
35
|
+
term?: string;
|
|
36
|
+
content?: string;
|
|
37
|
+
}
|
|
38
|
+
/** Context attached to every event. */
|
|
39
|
+
interface EventContext {
|
|
40
|
+
page: {
|
|
41
|
+
path: string;
|
|
42
|
+
url: string;
|
|
43
|
+
title: string;
|
|
44
|
+
referrer: string;
|
|
45
|
+
};
|
|
46
|
+
device: {
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
type: DeviceType;
|
|
50
|
+
};
|
|
51
|
+
utm?: UtmData;
|
|
52
|
+
locale: string;
|
|
53
|
+
timezone: string;
|
|
54
|
+
sdkVersion: string;
|
|
55
|
+
}
|
|
56
|
+
/** A single tracking event in wire format (v1 schema). */
|
|
57
|
+
interface TrackerEvent {
|
|
58
|
+
/** Schema version. Always 1 for this SDK. Enables server-side backwards compat. */
|
|
59
|
+
v: 1;
|
|
60
|
+
projectId: string;
|
|
61
|
+
type: EventType;
|
|
62
|
+
anonymousId: string;
|
|
63
|
+
userId?: string;
|
|
64
|
+
sessionId: string;
|
|
65
|
+
timestamp: string;
|
|
66
|
+
context: EventContext;
|
|
67
|
+
event?: string;
|
|
68
|
+
properties?: Record<string, unknown>;
|
|
69
|
+
traits?: Record<string, unknown>;
|
|
70
|
+
revenue?: {
|
|
71
|
+
amount: number;
|
|
72
|
+
currency: string;
|
|
73
|
+
plan?: string;
|
|
74
|
+
type?: RevenueType;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Overrides for manual page() calls. */
|
|
78
|
+
interface PageOverrides {
|
|
79
|
+
path?: string;
|
|
80
|
+
title?: string;
|
|
81
|
+
referrer?: string;
|
|
82
|
+
}
|
|
83
|
+
/** Revenue data for bob.revenue(). */
|
|
84
|
+
interface RevenueData {
|
|
85
|
+
amount: number;
|
|
86
|
+
currency?: string;
|
|
87
|
+
plan?: string;
|
|
88
|
+
type?: RevenueType;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* IndieBob Analytics Tracker.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })
|
|
96
|
+
* bob.page()
|
|
97
|
+
*/
|
|
98
|
+
declare class IndieBob {
|
|
99
|
+
private config;
|
|
100
|
+
private queue;
|
|
101
|
+
private userId;
|
|
102
|
+
private cleanupHistory;
|
|
103
|
+
private constructor();
|
|
104
|
+
/**
|
|
105
|
+
* Create a tracker instance. Call once at app startup.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* const bob = IndieBob.init({ projectId: 'proj_your16charId' })
|
|
109
|
+
*/
|
|
110
|
+
static init(config: TrackerConfig): IndieBob;
|
|
111
|
+
/**
|
|
112
|
+
* Track a page view. Call on every page load and SPA navigation.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* bob.page()
|
|
116
|
+
* bob.page({ path: '/pricing' })
|
|
117
|
+
*/
|
|
118
|
+
page(overrides?: PageOverrides): void;
|
|
119
|
+
/**
|
|
120
|
+
* Track a custom event.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* bob.track('signup_started')
|
|
124
|
+
* bob.track('feature_used', { feature: 'csv_export' })
|
|
125
|
+
*/
|
|
126
|
+
track(event: string, properties?: Record<string, unknown>): void;
|
|
127
|
+
/**
|
|
128
|
+
* Associate the current anonymous visitor with a known user ID.
|
|
129
|
+
* Call after login or signup.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* bob.identify('user_123', { email: 'jane@example.com', plan: 'pro' })
|
|
133
|
+
*/
|
|
134
|
+
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
135
|
+
/**
|
|
136
|
+
* Track a revenue event.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* bob.revenue({ amount: 29.00, currency: 'USD', plan: 'pro-monthly', type: 'new' })
|
|
140
|
+
*/
|
|
141
|
+
revenue(data: RevenueData): void;
|
|
142
|
+
/**
|
|
143
|
+
* Stop all tracking and clear all stored identifiers.
|
|
144
|
+
* Respects user's privacy preferences.
|
|
145
|
+
*/
|
|
146
|
+
optOut(): void;
|
|
147
|
+
/** Re-enable tracking after optOut(). */
|
|
148
|
+
optIn(): void;
|
|
149
|
+
/** Returns true if the user has explicitly opted out. */
|
|
150
|
+
isOptedOut(): boolean;
|
|
151
|
+
/** Force-send all queued events immediately. Useful in tests. */
|
|
152
|
+
flush(): Promise<void>;
|
|
153
|
+
/** Get the current anonymous ID. */
|
|
154
|
+
getAnonymousId(): string;
|
|
155
|
+
/** Get the current session ID. */
|
|
156
|
+
getSessionId(): string;
|
|
157
|
+
/** Get the resolved configuration (read-only). */
|
|
158
|
+
getConfig(): Readonly<ResolvedConfig>;
|
|
159
|
+
/**
|
|
160
|
+
* Enable auto-tracking of SPA navigation.
|
|
161
|
+
* Returns a cleanup function to remove the listeners.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* const cleanup = bob.enableAutoPage()
|
|
165
|
+
* // Later: cleanup()
|
|
166
|
+
*/
|
|
167
|
+
enableAutoPage(): () => void;
|
|
168
|
+
private buildEvent;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export { type DeviceType, type EventContext, type EventType, IndieBob, type PageOverrides, type RevenueData, type RevenueType, type TrackerConfig, type TrackerEvent, type UtmData };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var IndieBob=(function(exports){'use strict';var J=Object.defineProperty,z=Object.defineProperties;var G=Object.getOwnPropertyDescriptors;var k=Object.getOwnPropertySymbols;var W=Object.prototype.hasOwnProperty,Z=Object.prototype.propertyIsEnumerable;var A=(e,t,o)=>t in e?J(e,t,{enumerable:true,configurable:true,writable:true,value:o}):e[t]=o,d=(e,t)=>{for(var o in t||(t={}))W.call(t,o)&&A(e,o,t[o]);if(k)for(var o of k(t))Z.call(t,o)&&A(e,o,t[o]);return e},x=(e,t)=>z(e,G(t));var a=(e,t,o)=>new Promise((n,i)=>{var r=s=>{try{l(o.next(s));}catch(c){i(c);}},g=s=>{try{l(o.throw(s));}catch(c){i(c);}},l=s=>s.done?n(s.value):Promise.resolve(s.value).then(r,g);l((o=o.apply(e,t)).next());});var $="https://indiebob.com/api/collect";function D(e){var t,o,n,i;if(!e.projectId||!e.projectId.startsWith("proj_"))throw new Error(`[IndieBob] Invalid projectId "${e.projectId}". Must start with "proj_".`);return {projectId:e.projectId,endpoint:(t=e.endpoint)!=null?t:$,debug:(o=e.debug)!=null?o:false,respectDnt:(n=e.respectDnt)!=null?n:true,respectGpc:(i=e.respectGpc)!=null?i:true}}var u="_ib_anon";function h(){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-",t="";if(typeof crypto!="undefined"&&crypto.getRandomValues){let o=new Uint8Array(21);crypto.getRandomValues(o);for(let n of o)t+=e[n%e.length];}else for(let o=0;o<21;o++)t+=e[Math.floor(Math.random()*e.length)];return t}function v(){try{let e=localStorage.getItem(u);return e||(e=h(),localStorage.setItem(u,e)),e}catch(e){try{let t=sessionStorage.getItem(u);return t||(t=h(),sessionStorage.setItem(u,t)),t}catch(t){return h()}}}function P(){try{localStorage.removeItem(u);}catch(e){}try{sessionStorage.removeItem(u);}catch(e){}}var y="_ib_session",I="_ib_session_last";function N(){try{return crypto.randomUUID()}catch(e){return Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)}}function S(){var e;try{let t=Date.now(),o=parseInt((e=sessionStorage.getItem(I))!=null?e:"0",10),n=sessionStorage.getItem(y),i=o>0&&t-o>18e5;return (!n||i)&&(n=N(),sessionStorage.setItem(y,n)),sessionStorage.setItem(I,String(t)),n}catch(t){return N()}}function U(){try{sessionStorage.removeItem(y);}catch(e){}try{sessionStorage.removeItem(I);}catch(e){}}var b="_ib_first_utm",M="_ib_last_utm",_="_ib_referrer";function q(e){let t=new URLSearchParams(e),o={},n=false;return t.has("utm_source")&&(o.source=t.get("utm_source"),n=true),t.has("utm_medium")&&(o.medium=t.get("utm_medium"),n=true),t.has("utm_campaign")&&(o.campaign=t.get("utm_campaign"),n=true),t.has("utm_term")&&(o.term=t.get("utm_term"),n=true),t.has("utm_content")&&(o.content=t.get("utm_content"),n=true),n?o:null}function B(e,t){let o=q(e);try{o&&!localStorage.getItem(b)&&localStorage.setItem(b,JSON.stringify(o)),o&&localStorage.setItem(M,JSON.stringify(o)),t&&!localStorage.getItem(_)&&localStorage.setItem(_,t);}catch(n){}}function F(e){var t;return (t=q(e))!=null?t:void 0}function L(){try{localStorage.removeItem(b);}catch(e){}try{localStorage.removeItem(M);}catch(e){}try{localStorage.removeItem(_);}catch(e){}}var p="_ib_optout",tt="_ib_queue";function f(e){try{if(localStorage.getItem(p))return !1}catch(t){}return !(e.respectDnt&&typeof navigator!="undefined"&&(navigator.doNotTrack==="1"||navigator.msDoNotTrack==="1")||e.respectGpc&&typeof navigator!="undefined"&&navigator.globalPrivacyControl===true)}function j(){try{localStorage.setItem(p,"1");}catch(e){}P(),U(),L();try{localStorage.removeItem(tt);}catch(e){}}function Y(){try{localStorage.removeItem(p);}catch(e){}}function K(){try{return !!localStorage.getItem(p)}catch(e){return false}}var et=[1e3,2e3,4e3];function ot(e){return a(this,null,function*(){return new Promise(t=>setTimeout(t,e))})}function w(e,t,o=false){return a(this,null,function*(){let n=JSON.stringify(e);if(o&&typeof navigator!="undefined"&&navigator.sendBeacon){let i=new Blob([n],{type:"application/json"}),r=navigator.sendBeacon(t.endpoint,i);return t.debug&&console.log("[IndieBob] sendBeacon:",r,e.batch.length,"events"),r}for(let i=0;i<3;i++){try{let r=yield fetch(t.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:n,keepalive:!0});if(r.ok)return t.debug&&console.log("[IndieBob] sent:",e.batch.length,"events"),!0;if(r.status>=400&&r.status<500)return t.debug&&console.warn("[IndieBob] 4xx error, not retrying:",r.status),!1}catch(r){t.debug&&console.warn("[IndieBob] fetch error (attempt",i+1,"):",r);}i<2&&(yield ot(et[i]));}return t.debug&&console.warn("[IndieBob] failed to send after",3,"attempts"),false})}var E="_ib_queue",nt=10,it=5e3,rt=50,m=class{constructor(t){this.queue=[];this.timer=null;this.flushing=false;this.config=t,this.startTimer(),this.setupVisibilityFlush(),this.setupOnlineFlush();}push(t){this.queue.push(t),this.queue.length>=nt&&this.flush();}flush(t=false){return a(this,null,function*(){if(this.flushing&&!t||this.queue.length===0)return;this.flushing=true;let o=this.queue.splice(0);(yield w({batch:o},this.config,t))||this.pushToOfflineQueue(o),this.flushing=false;})}flushOfflineQueue(){return a(this,null,function*(){let t=this.getOfflineQueue();if(t.length===0)return;this.clearOfflineQueue(),yield w({batch:t},this.config);})}destroy(){this.timer&&clearInterval(this.timer);}startTimer(){this.timer=setInterval(()=>{this.queue.length>0&&this.flush();},it);}setupVisibilityFlush(){typeof document!="undefined"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush(true);},{passive:true});}setupOnlineFlush(){typeof window!="undefined"&&(window.addEventListener("online",()=>{this.flushOfflineQueue();}),navigator.onLine&&this.flushOfflineQueue());}pushToOfflineQueue(t){try{let i=[...this.getOfflineQueue(),...t].slice(-rt);localStorage.setItem(E,JSON.stringify(i));}catch(o){}}getOfflineQueue(){try{let t=localStorage.getItem(E);return t?JSON.parse(t):[]}catch(t){return []}}clearOfflineQueue(){try{localStorage.removeItem(E);}catch(t){}}};function Q(){if(typeof window=="undefined")return "desktop";let e=window.innerWidth;return e<=768?"mobile":e<=1024?"tablet":"desktop"}function V(){return typeof window=="undefined"?{width:0,height:0}:{width:window.innerWidth,height:window.innerHeight}}function T(e){var c,O,R,C;let t=typeof window=="undefined",o=(c=e==null?void 0:e.path)!=null?c:t?"":window.location.pathname+window.location.search,n=t?"":window.location.href,i=(O=e==null?void 0:e.title)!=null?O:t?"":document.title,r=(R=e==null?void 0:e.referrer)!=null?R:t?"":document.referrer,g=V(),l=Q(),s=t?void 0:F(window.location.search);return {page:{path:o,url:n,title:i,referrer:r},device:x(d({},g),{type:l}),utm:s,locale:t?"en":navigator.language,timezone:t?"UTC":(C=Intl.DateTimeFormat().resolvedOptions().timeZone)!=null?C:"UTC",sdkVersion:"0.1.0"}}function H(e){if(typeof window=="undefined")return ()=>{};let t=history.pushState.bind(history),o=history.replaceState.bind(history);return history.pushState=function(...n){t(...n),e();},history.replaceState=function(...n){o(...n);},window.addEventListener("popstate",e),()=>{history.pushState=t,history.replaceState=o,window.removeEventListener("popstate",e);}}var X=class e{constructor(t){this.cleanupHistory=null;this.config=t,this.queue=new m(t),typeof window!="undefined"&&B(window.location.search,document.referrer);}static init(t){let o=D(t);return new e(o)}page(t){f(this.config)&&this.queue.push(this.buildEvent("page",{context:T(t)}));}track(t,o){f(this.config)&&this.queue.push(this.buildEvent("track",{event:t,properties:o}));}identify(t,o){f(this.config)&&(this.userId=t,this.queue.push(this.buildEvent("identify",{traits:o,userId:t})));}revenue(t){var o;f(this.config)&&this.queue.push(this.buildEvent("revenue",{revenue:{amount:t.amount,currency:(o=t.currency)!=null?o:"USD",plan:t.plan,type:t.type}}));}optOut(){this.queue.flush(),j(),this.userId=void 0,this.config.debug&&console.log("[IndieBob] opted out");}optIn(){Y(),this.config.debug&&console.log("[IndieBob] opted in");}isOptedOut(){return K()}flush(){return a(this,null,function*(){yield this.queue.flush();})}getAnonymousId(){return v()}getSessionId(){return S()}getConfig(){return Object.freeze(d({},this.config))}enableAutoPage(){var t;return (t=this.cleanupHistory)==null||t.call(this),this.cleanupHistory=H(()=>this.page()),()=>{var o;(o=this.cleanupHistory)==null||o.call(this),this.cleanupHistory=null;}}buildEvent(t,o){var n,i;return {v:1,projectId:this.config.projectId,type:t,anonymousId:v(),userId:(n=this.userId)!=null?n:o.userId,sessionId:S(),timestamp:new Date().toISOString(),context:(i=o.context)!=null?i:T(),event:o.event,properties:o.properties,traits:o.traits,revenue:o.revenue}}};
|
|
2
|
+
exports.IndieBob=X;return exports;})({});
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{a as IndieBob}from'./chunk-XKQYO25W.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the IndieBob tracker.
|
|
3
|
+
*/
|
|
4
|
+
interface TrackerConfig {
|
|
5
|
+
/** Your SDK project ID from the IndieBob dashboard. Format: 'proj_...' */
|
|
6
|
+
projectId: string;
|
|
7
|
+
/** Ingestion endpoint URL. Default: 'https://indiebob.com/api/collect' */
|
|
8
|
+
endpoint?: string;
|
|
9
|
+
/** Log all events to the browser console. Default: false */
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
/** Honor the browser Do Not Track header. Default: true */
|
|
12
|
+
respectDnt?: boolean;
|
|
13
|
+
/** Honor Global Privacy Control signal (legally binding in CA/GDPR). Default: true */
|
|
14
|
+
respectGpc?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Server-side tracker for Node.js environments.
|
|
19
|
+
* No browser APIs are used here.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Server-side IndieBob tracker. Use this in Node.js/Nuxt server routes.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const bob = IndieBob.initServer({ projectId: 'proj_abc123def456ghij' })
|
|
27
|
+
* bob.track('server_event', { source: 'webhook' })
|
|
28
|
+
*/
|
|
29
|
+
declare class IndieBobServer {
|
|
30
|
+
private config;
|
|
31
|
+
private constructor();
|
|
32
|
+
static initServer(config: TrackerConfig): IndieBobServer;
|
|
33
|
+
track(event: string, properties?: Record<string, unknown>, userId?: string): Promise<boolean>;
|
|
34
|
+
identify(userId: string, traits?: Record<string, unknown>): Promise<boolean>;
|
|
35
|
+
private buildEvent;
|
|
36
|
+
}
|
|
37
|
+
declare const IndieBob: typeof IndieBobServer;
|
|
38
|
+
|
|
39
|
+
export { IndieBob, IndieBobServer };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the IndieBob tracker.
|
|
3
|
+
*/
|
|
4
|
+
interface TrackerConfig {
|
|
5
|
+
/** Your SDK project ID from the IndieBob dashboard. Format: 'proj_...' */
|
|
6
|
+
projectId: string;
|
|
7
|
+
/** Ingestion endpoint URL. Default: 'https://indiebob.com/api/collect' */
|
|
8
|
+
endpoint?: string;
|
|
9
|
+
/** Log all events to the browser console. Default: false */
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
/** Honor the browser Do Not Track header. Default: true */
|
|
12
|
+
respectDnt?: boolean;
|
|
13
|
+
/** Honor Global Privacy Control signal (legally binding in CA/GDPR). Default: true */
|
|
14
|
+
respectGpc?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Server-side tracker for Node.js environments.
|
|
19
|
+
* No browser APIs are used here.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Server-side IndieBob tracker. Use this in Node.js/Nuxt server routes.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const bob = IndieBob.initServer({ projectId: 'proj_abc123def456ghij' })
|
|
27
|
+
* bob.track('server_event', { source: 'webhook' })
|
|
28
|
+
*/
|
|
29
|
+
declare class IndieBobServer {
|
|
30
|
+
private config;
|
|
31
|
+
private constructor();
|
|
32
|
+
static initServer(config: TrackerConfig): IndieBobServer;
|
|
33
|
+
track(event: string, properties?: Record<string, unknown>, userId?: string): Promise<boolean>;
|
|
34
|
+
identify(userId: string, traits?: Record<string, unknown>): Promise<boolean>;
|
|
35
|
+
private buildEvent;
|
|
36
|
+
}
|
|
37
|
+
declare const IndieBob: typeof IndieBobServer;
|
|
38
|
+
|
|
39
|
+
export { IndieBob, IndieBobServer };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/core/config.ts
|
|
4
|
+
var DEFAULT_ENDPOINT = "https://indiebob.com/api/collect";
|
|
5
|
+
function resolveConfig(input) {
|
|
6
|
+
if (!input.projectId || !input.projectId.startsWith("proj_")) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
`[IndieBob] Invalid projectId "${input.projectId}". Must start with "proj_".`
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
projectId: input.projectId,
|
|
13
|
+
endpoint: input.endpoint ?? DEFAULT_ENDPOINT,
|
|
14
|
+
debug: input.debug ?? false,
|
|
15
|
+
respectDnt: input.respectDnt ?? true,
|
|
16
|
+
respectGpc: input.respectGpc ?? true
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/core/transport.ts
|
|
21
|
+
var MAX_RETRIES = 3;
|
|
22
|
+
var RETRY_DELAYS = [1e3, 2e3, 4e3];
|
|
23
|
+
async function sleep(ms) {
|
|
24
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
25
|
+
}
|
|
26
|
+
async function sendBatchServer(payload, config) {
|
|
27
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(config.endpoint, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
body: JSON.stringify(payload)
|
|
33
|
+
});
|
|
34
|
+
if (res.ok) return true;
|
|
35
|
+
if (res.status >= 400 && res.status < 500) return false;
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
if (attempt < MAX_RETRIES - 1) await sleep(RETRY_DELAYS[attempt]);
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/integrations/server.ts
|
|
44
|
+
function generateId() {
|
|
45
|
+
try {
|
|
46
|
+
return crypto.randomUUID();
|
|
47
|
+
} catch {
|
|
48
|
+
return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var IndieBobServer = class _IndieBobServer {
|
|
52
|
+
constructor(config) {
|
|
53
|
+
this.config = config;
|
|
54
|
+
}
|
|
55
|
+
static initServer(config) {
|
|
56
|
+
return new _IndieBobServer(resolveConfig(config));
|
|
57
|
+
}
|
|
58
|
+
track(event, properties, userId) {
|
|
59
|
+
return sendBatchServer({
|
|
60
|
+
batch: [this.buildEvent("track", { event, properties, userId })]
|
|
61
|
+
}, this.config);
|
|
62
|
+
}
|
|
63
|
+
identify(userId, traits) {
|
|
64
|
+
return sendBatchServer({
|
|
65
|
+
batch: [this.buildEvent("identify", { traits, userId })]
|
|
66
|
+
}, this.config);
|
|
67
|
+
}
|
|
68
|
+
buildEvent(type, extras) {
|
|
69
|
+
return {
|
|
70
|
+
v: 1,
|
|
71
|
+
projectId: this.config.projectId,
|
|
72
|
+
type,
|
|
73
|
+
anonymousId: `server_${generateId()}`,
|
|
74
|
+
userId: extras.userId,
|
|
75
|
+
sessionId: `server_${generateId()}`,
|
|
76
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
77
|
+
context: {
|
|
78
|
+
page: { path: "", url: "", title: "", referrer: "" },
|
|
79
|
+
device: { width: 0, height: 0, type: "desktop" },
|
|
80
|
+
locale: "en",
|
|
81
|
+
timezone: "UTC",
|
|
82
|
+
sdkVersion: "0.1.0"
|
|
83
|
+
},
|
|
84
|
+
event: extras.event,
|
|
85
|
+
properties: extras.properties,
|
|
86
|
+
traits: extras.traits
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var IndieBob = IndieBobServer;
|
|
91
|
+
|
|
92
|
+
exports.IndieBob = IndieBob;
|
|
93
|
+
exports.IndieBobServer = IndieBobServer;
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/core/config.ts
|
|
2
|
+
var DEFAULT_ENDPOINT = "https://indiebob.com/api/collect";
|
|
3
|
+
function resolveConfig(input) {
|
|
4
|
+
if (!input.projectId || !input.projectId.startsWith("proj_")) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
`[IndieBob] Invalid projectId "${input.projectId}". Must start with "proj_".`
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
projectId: input.projectId,
|
|
11
|
+
endpoint: input.endpoint ?? DEFAULT_ENDPOINT,
|
|
12
|
+
debug: input.debug ?? false,
|
|
13
|
+
respectDnt: input.respectDnt ?? true,
|
|
14
|
+
respectGpc: input.respectGpc ?? true
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/core/transport.ts
|
|
19
|
+
var MAX_RETRIES = 3;
|
|
20
|
+
var RETRY_DELAYS = [1e3, 2e3, 4e3];
|
|
21
|
+
async function sleep(ms) {
|
|
22
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
|
+
}
|
|
24
|
+
async function sendBatchServer(payload, config) {
|
|
25
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(config.endpoint, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { "Content-Type": "application/json" },
|
|
30
|
+
body: JSON.stringify(payload)
|
|
31
|
+
});
|
|
32
|
+
if (res.ok) return true;
|
|
33
|
+
if (res.status >= 400 && res.status < 500) return false;
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
if (attempt < MAX_RETRIES - 1) await sleep(RETRY_DELAYS[attempt]);
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/integrations/server.ts
|
|
42
|
+
function generateId() {
|
|
43
|
+
try {
|
|
44
|
+
return crypto.randomUUID();
|
|
45
|
+
} catch {
|
|
46
|
+
return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
var IndieBobServer = class _IndieBobServer {
|
|
50
|
+
constructor(config) {
|
|
51
|
+
this.config = config;
|
|
52
|
+
}
|
|
53
|
+
static initServer(config) {
|
|
54
|
+
return new _IndieBobServer(resolveConfig(config));
|
|
55
|
+
}
|
|
56
|
+
track(event, properties, userId) {
|
|
57
|
+
return sendBatchServer({
|
|
58
|
+
batch: [this.buildEvent("track", { event, properties, userId })]
|
|
59
|
+
}, this.config);
|
|
60
|
+
}
|
|
61
|
+
identify(userId, traits) {
|
|
62
|
+
return sendBatchServer({
|
|
63
|
+
batch: [this.buildEvent("identify", { traits, userId })]
|
|
64
|
+
}, this.config);
|
|
65
|
+
}
|
|
66
|
+
buildEvent(type, extras) {
|
|
67
|
+
return {
|
|
68
|
+
v: 1,
|
|
69
|
+
projectId: this.config.projectId,
|
|
70
|
+
type,
|
|
71
|
+
anonymousId: `server_${generateId()}`,
|
|
72
|
+
userId: extras.userId,
|
|
73
|
+
sessionId: `server_${generateId()}`,
|
|
74
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
75
|
+
context: {
|
|
76
|
+
page: { path: "", url: "", title: "", referrer: "" },
|
|
77
|
+
device: { width: 0, height: 0, type: "desktop" },
|
|
78
|
+
locale: "en",
|
|
79
|
+
timezone: "UTC",
|
|
80
|
+
sdkVersion: "0.1.0"
|
|
81
|
+
},
|
|
82
|
+
event: extras.event,
|
|
83
|
+
properties: extras.properties,
|
|
84
|
+
traits: extras.traits
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var IndieBob = IndieBobServer;
|
|
89
|
+
|
|
90
|
+
export { IndieBob, IndieBobServer };
|
package/dist/vue.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var z=Object.defineProperty,G=Object.defineProperties;var W=Object.getOwnPropertyDescriptors;var x=Object.getOwnPropertySymbols;var $=Object.prototype.hasOwnProperty,Z=Object.prototype.propertyIsEnumerable;var P=(t,e,o)=>e in t?z(t,e,{enumerable:true,configurable:true,writable:true,value:o}):t[e]=o,d=(t,e)=>{for(var o in e||(e={}))$.call(e,o)&&P(t,o,e[o]);if(x)for(var o of x(e))Z.call(e,o)&&P(t,o,e[o]);return t},D=(t,e)=>G(t,W(e));var a=(t,e,o)=>new Promise((n,r)=>{var i=s=>{try{l(o.next(s));}catch(u){r(u);}},h=s=>{try{l(o.throw(s));}catch(u){r(u);}},l=s=>s.done?n(s.value):Promise.resolve(s.value).then(i,h);l((o=o.apply(t,e)).next());});var ee="https://indiebob.com/api/collect";function N(t){var e,o,n,r;if(!t.projectId||!t.projectId.startsWith("proj_"))throw new Error(`[IndieBob] Invalid projectId "${t.projectId}". Must start with "proj_".`);return {projectId:t.projectId,endpoint:(e=t.endpoint)!=null?e:ee,debug:(o=t.debug)!=null?o:false,respectDnt:(n=t.respectDnt)!=null?n:true,respectGpc:(r=t.respectGpc)!=null?r:true}}var c="_ib_anon";function y(){let t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-",e="";if(typeof crypto!="undefined"&&crypto.getRandomValues){let o=new Uint8Array(21);crypto.getRandomValues(o);for(let n of o)e+=t[n%t.length];}else for(let o=0;o<21;o++)e+=t[Math.floor(Math.random()*t.length)];return e}function v(){try{let t=localStorage.getItem(c);return t||(t=y(),localStorage.setItem(c,t)),t}catch(t){try{let e=sessionStorage.getItem(c);return e||(e=y(),sessionStorage.setItem(c,e)),e}catch(e){return y()}}}function B(){try{localStorage.removeItem(c);}catch(t){}try{sessionStorage.removeItem(c);}catch(t){}}var I="_ib_session",S="_ib_session_last";function U(){try{return crypto.randomUUID()}catch(t){return Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)}}function b(){var t;try{let e=Date.now(),o=parseInt((t=sessionStorage.getItem(S))!=null?t:"0",10),n=sessionStorage.getItem(I),r=o>0&&e-o>18e5;return (!n||r)&&(n=U(),sessionStorage.setItem(I,n)),sessionStorage.setItem(S,String(e)),n}catch(e){return U()}}function M(){try{sessionStorage.removeItem(I);}catch(t){}try{sessionStorage.removeItem(S);}catch(t){}}var _="_ib_first_utm",q="_ib_last_utm",T="_ib_referrer";function F(t){let e=new URLSearchParams(t),o={},n=false;return e.has("utm_source")&&(o.source=e.get("utm_source"),n=true),e.has("utm_medium")&&(o.medium=e.get("utm_medium"),n=true),e.has("utm_campaign")&&(o.campaign=e.get("utm_campaign"),n=true),e.has("utm_term")&&(o.term=e.get("utm_term"),n=true),e.has("utm_content")&&(o.content=e.get("utm_content"),n=true),n?o:null}function L(t,e){let o=F(t);try{o&&!localStorage.getItem(_)&&localStorage.setItem(_,JSON.stringify(o)),o&&localStorage.setItem(q,JSON.stringify(o)),e&&!localStorage.getItem(T)&&localStorage.setItem(T,e);}catch(n){}}function j(t){var e;return (e=F(t))!=null?e:void 0}function Y(){try{localStorage.removeItem(_);}catch(t){}try{localStorage.removeItem(q);}catch(t){}try{localStorage.removeItem(T);}catch(t){}}var p="_ib_optout",te="_ib_queue";function f(t){try{if(localStorage.getItem(p))return !1}catch(e){}return !(t.respectDnt&&typeof navigator!="undefined"&&(navigator.doNotTrack==="1"||navigator.msDoNotTrack==="1")||t.respectGpc&&typeof navigator!="undefined"&&navigator.globalPrivacyControl===true)}function K(){try{localStorage.setItem(p,"1");}catch(t){}B(),M(),Y();try{localStorage.removeItem(te);}catch(t){}}function Q(){try{localStorage.removeItem(p);}catch(t){}}function V(){try{return !!localStorage.getItem(p)}catch(t){return false}}var oe=[1e3,2e3,4e3];function ne(t){return a(this,null,function*(){return new Promise(e=>setTimeout(e,t))})}function E(t,e,o=false){return a(this,null,function*(){let n=JSON.stringify(t);if(o&&typeof navigator!="undefined"&&navigator.sendBeacon){let r=new Blob([n],{type:"application/json"}),i=navigator.sendBeacon(e.endpoint,r);return e.debug&&console.log("[IndieBob] sendBeacon:",i,t.batch.length,"events"),i}for(let r=0;r<3;r++){try{let i=yield fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:n,keepalive:!0});if(i.ok)return e.debug&&console.log("[IndieBob] sent:",t.batch.length,"events"),!0;if(i.status>=400&&i.status<500)return e.debug&&console.warn("[IndieBob] 4xx error, not retrying:",i.status),!1}catch(i){e.debug&&console.warn("[IndieBob] fetch error (attempt",r+1,"):",i);}r<2&&(yield ne(oe[r]));}return e.debug&&console.warn("[IndieBob] failed to send after",3,"attempts"),false})}var w="_ib_queue",re=10,ie=5e3,se=50,m=class{constructor(e){this.queue=[];this.timer=null;this.flushing=false;this.config=e,this.startTimer(),this.setupVisibilityFlush(),this.setupOnlineFlush();}push(e){this.queue.push(e),this.queue.length>=re&&this.flush();}flush(e=false){return a(this,null,function*(){if(this.flushing&&!e||this.queue.length===0)return;this.flushing=true;let o=this.queue.splice(0);(yield E({batch:o},this.config,e))||this.pushToOfflineQueue(o),this.flushing=false;})}flushOfflineQueue(){return a(this,null,function*(){let e=this.getOfflineQueue();if(e.length===0)return;this.clearOfflineQueue(),yield E({batch:e},this.config);})}destroy(){this.timer&&clearInterval(this.timer);}startTimer(){this.timer=setInterval(()=>{this.queue.length>0&&this.flush();},ie);}setupVisibilityFlush(){typeof document!="undefined"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush(true);},{passive:true});}setupOnlineFlush(){typeof window!="undefined"&&(window.addEventListener("online",()=>{this.flushOfflineQueue();}),navigator.onLine&&this.flushOfflineQueue());}pushToOfflineQueue(e){try{let r=[...this.getOfflineQueue(),...e].slice(-se);localStorage.setItem(w,JSON.stringify(r));}catch(o){}}getOfflineQueue(){try{let e=localStorage.getItem(w);return e?JSON.parse(e):[]}catch(e){return []}}clearOfflineQueue(){try{localStorage.removeItem(w);}catch(e){}}};function H(){if(typeof window=="undefined")return "desktop";let t=window.innerWidth;return t<=768?"mobile":t<=1024?"tablet":"desktop"}function X(){return typeof window=="undefined"?{width:0,height:0}:{width:window.innerWidth,height:window.innerHeight}}function R(t){var u,C,k,A;let e=typeof window=="undefined",o=(u=t==null?void 0:t.path)!=null?u:e?"":window.location.pathname+window.location.search,n=e?"":window.location.href,r=(C=t==null?void 0:t.title)!=null?C:e?"":document.title,i=(k=t==null?void 0:t.referrer)!=null?k:e?"":document.referrer,h=X(),l=H(),s=e?void 0:j(window.location.search);return {page:{path:o,url:n,title:r,referrer:i},device:D(d({},h),{type:l}),utm:s,locale:e?"en":navigator.language,timezone:e?"UTC":(A=Intl.DateTimeFormat().resolvedOptions().timeZone)!=null?A:"UTC",sdkVersion:"0.1.0"}}function J(t){if(typeof window=="undefined")return ()=>{};let e=history.pushState.bind(history),o=history.replaceState.bind(history);return history.pushState=function(...n){e(...n),t();},history.replaceState=function(...n){o(...n);},window.addEventListener("popstate",t),()=>{history.pushState=e,history.replaceState=o,window.removeEventListener("popstate",t);}}var g=class t{constructor(e){this.cleanupHistory=null;this.config=e,this.queue=new m(e),typeof window!="undefined"&&L(window.location.search,document.referrer);}static init(e){let o=N(e);return new t(o)}page(e){f(this.config)&&this.queue.push(this.buildEvent("page",{context:R(e)}));}track(e,o){f(this.config)&&this.queue.push(this.buildEvent("track",{event:e,properties:o}));}identify(e,o){f(this.config)&&(this.userId=e,this.queue.push(this.buildEvent("identify",{traits:o,userId:e})));}revenue(e){var o;f(this.config)&&this.queue.push(this.buildEvent("revenue",{revenue:{amount:e.amount,currency:(o=e.currency)!=null?o:"USD",plan:e.plan,type:e.type}}));}optOut(){this.queue.flush(),K(),this.userId=void 0,this.config.debug&&console.log("[IndieBob] opted out");}optIn(){Q(),this.config.debug&&console.log("[IndieBob] opted in");}isOptedOut(){return V()}flush(){return a(this,null,function*(){yield this.queue.flush();})}getAnonymousId(){return v()}getSessionId(){return b()}getConfig(){return Object.freeze(d({},this.config))}enableAutoPage(){var e;return (e=this.cleanupHistory)==null||e.call(this),this.cleanupHistory=J(()=>this.page()),()=>{var o;(o=this.cleanupHistory)==null||o.call(this),this.cleanupHistory=null;}}buildEvent(e,o){var n,r;return {v:1,projectId:this.config.projectId,type:e,anonymousId:v(),userId:(n=this.userId)!=null?n:o.userId,sessionId:b(),timestamp:new Date().toISOString(),context:(r=o.context)!=null?r:R(),event:o.event,properties:o.properties,traits:o.traits,revenue:o.revenue}}};var ae=Symbol("indiebob"),O=null,Fe={install(t,e){let o=g.init(e);O=o,t.provide(ae,o),t.config.globalProperties.$bob=o;let n=t.config.globalProperties.$router;n&&n.afterEach(()=>{o.page();});}};function Le(){if(O)return O;throw new Error("[IndieBob] useTracker() called before IndieBobPlugin was installed.")}
|
|
2
|
+
exports.IndieBob=g;exports.IndieBobPlugin=Fe;exports.useTracker=Le;
|
package/dist/vue.d.mts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Plugin } from 'vue';
|
|
2
|
+
import { IndieBob } from './index.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vue plugin for @indiebob/tracker.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // main.ts
|
|
9
|
+
* app.use(IndieBobPlugin, { projectId: 'proj_abc123def456ghij' })
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // Nuxt plugin (plugins/tracker.client.ts)
|
|
13
|
+
* export default defineNuxtPlugin((nuxtApp) => {
|
|
14
|
+
* nuxtApp.vueApp.use(IndieBobPlugin, {
|
|
15
|
+
* projectId: useRuntimeConfig().public.trackerProjectId,
|
|
16
|
+
* })
|
|
17
|
+
* })
|
|
18
|
+
*/
|
|
19
|
+
declare const IndieBobPlugin: Plugin;
|
|
20
|
+
/**
|
|
21
|
+
* Get the IndieBob tracker instance inside a Vue component or composable.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const bob = useTracker()
|
|
25
|
+
* bob.track('button_clicked', { button: 'cta_hero' })
|
|
26
|
+
*/
|
|
27
|
+
declare function useTracker(): IndieBob;
|
|
28
|
+
|
|
29
|
+
export { IndieBob, IndieBobPlugin, useTracker };
|
package/dist/vue.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Plugin } from 'vue';
|
|
2
|
+
import { IndieBob } from './index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vue plugin for @indiebob/tracker.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // main.ts
|
|
9
|
+
* app.use(IndieBobPlugin, { projectId: 'proj_abc123def456ghij' })
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // Nuxt plugin (plugins/tracker.client.ts)
|
|
13
|
+
* export default defineNuxtPlugin((nuxtApp) => {
|
|
14
|
+
* nuxtApp.vueApp.use(IndieBobPlugin, {
|
|
15
|
+
* projectId: useRuntimeConfig().public.trackerProjectId,
|
|
16
|
+
* })
|
|
17
|
+
* })
|
|
18
|
+
*/
|
|
19
|
+
declare const IndieBobPlugin: Plugin;
|
|
20
|
+
/**
|
|
21
|
+
* Get the IndieBob tracker instance inside a Vue component or composable.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const bob = useTracker()
|
|
25
|
+
* bob.track('button_clicked', { button: 'cta_hero' })
|
|
26
|
+
*/
|
|
27
|
+
declare function useTracker(): IndieBob;
|
|
28
|
+
|
|
29
|
+
export { IndieBob, IndieBobPlugin, useTracker };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var IndieBob=(function(exports){'use strict';var z=Object.defineProperty,G=Object.defineProperties;var W=Object.getOwnPropertyDescriptors;var x=Object.getOwnPropertySymbols;var $=Object.prototype.hasOwnProperty,Z=Object.prototype.propertyIsEnumerable;var P=(t,e,o)=>e in t?z(t,e,{enumerable:true,configurable:true,writable:true,value:o}):t[e]=o,d=(t,e)=>{for(var o in e||(e={}))$.call(e,o)&&P(t,o,e[o]);if(x)for(var o of x(e))Z.call(e,o)&&P(t,o,e[o]);return t},D=(t,e)=>G(t,W(e));var a=(t,e,o)=>new Promise((n,r)=>{var i=s=>{try{l(o.next(s));}catch(u){r(u);}},h=s=>{try{l(o.throw(s));}catch(u){r(u);}},l=s=>s.done?n(s.value):Promise.resolve(s.value).then(i,h);l((o=o.apply(t,e)).next());});var ee="https://indiebob.com/api/collect";function N(t){var e,o,n,r;if(!t.projectId||!t.projectId.startsWith("proj_"))throw new Error(`[IndieBob] Invalid projectId "${t.projectId}". Must start with "proj_".`);return {projectId:t.projectId,endpoint:(e=t.endpoint)!=null?e:ee,debug:(o=t.debug)!=null?o:false,respectDnt:(n=t.respectDnt)!=null?n:true,respectGpc:(r=t.respectGpc)!=null?r:true}}var c="_ib_anon";function y(){let t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-",e="";if(typeof crypto!="undefined"&&crypto.getRandomValues){let o=new Uint8Array(21);crypto.getRandomValues(o);for(let n of o)e+=t[n%t.length];}else for(let o=0;o<21;o++)e+=t[Math.floor(Math.random()*t.length)];return e}function v(){try{let t=localStorage.getItem(c);return t||(t=y(),localStorage.setItem(c,t)),t}catch(t){try{let e=sessionStorage.getItem(c);return e||(e=y(),sessionStorage.setItem(c,e)),e}catch(e){return y()}}}function B(){try{localStorage.removeItem(c);}catch(t){}try{sessionStorage.removeItem(c);}catch(t){}}var I="_ib_session",S="_ib_session_last";function U(){try{return crypto.randomUUID()}catch(t){return Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)}}function b(){var t;try{let e=Date.now(),o=parseInt((t=sessionStorage.getItem(S))!=null?t:"0",10),n=sessionStorage.getItem(I),r=o>0&&e-o>18e5;return (!n||r)&&(n=U(),sessionStorage.setItem(I,n)),sessionStorage.setItem(S,String(e)),n}catch(e){return U()}}function M(){try{sessionStorage.removeItem(I);}catch(t){}try{sessionStorage.removeItem(S);}catch(t){}}var _="_ib_first_utm",q="_ib_last_utm",T="_ib_referrer";function F(t){let e=new URLSearchParams(t),o={},n=false;return e.has("utm_source")&&(o.source=e.get("utm_source"),n=true),e.has("utm_medium")&&(o.medium=e.get("utm_medium"),n=true),e.has("utm_campaign")&&(o.campaign=e.get("utm_campaign"),n=true),e.has("utm_term")&&(o.term=e.get("utm_term"),n=true),e.has("utm_content")&&(o.content=e.get("utm_content"),n=true),n?o:null}function L(t,e){let o=F(t);try{o&&!localStorage.getItem(_)&&localStorage.setItem(_,JSON.stringify(o)),o&&localStorage.setItem(q,JSON.stringify(o)),e&&!localStorage.getItem(T)&&localStorage.setItem(T,e);}catch(n){}}function j(t){var e;return (e=F(t))!=null?e:void 0}function Y(){try{localStorage.removeItem(_);}catch(t){}try{localStorage.removeItem(q);}catch(t){}try{localStorage.removeItem(T);}catch(t){}}var p="_ib_optout",te="_ib_queue";function f(t){try{if(localStorage.getItem(p))return !1}catch(e){}return !(t.respectDnt&&typeof navigator!="undefined"&&(navigator.doNotTrack==="1"||navigator.msDoNotTrack==="1")||t.respectGpc&&typeof navigator!="undefined"&&navigator.globalPrivacyControl===true)}function K(){try{localStorage.setItem(p,"1");}catch(t){}B(),M(),Y();try{localStorage.removeItem(te);}catch(t){}}function Q(){try{localStorage.removeItem(p);}catch(t){}}function V(){try{return !!localStorage.getItem(p)}catch(t){return false}}var oe=[1e3,2e3,4e3];function ne(t){return a(this,null,function*(){return new Promise(e=>setTimeout(e,t))})}function E(t,e,o=false){return a(this,null,function*(){let n=JSON.stringify(t);if(o&&typeof navigator!="undefined"&&navigator.sendBeacon){let r=new Blob([n],{type:"application/json"}),i=navigator.sendBeacon(e.endpoint,r);return e.debug&&console.log("[IndieBob] sendBeacon:",i,t.batch.length,"events"),i}for(let r=0;r<3;r++){try{let i=yield fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:n,keepalive:!0});if(i.ok)return e.debug&&console.log("[IndieBob] sent:",t.batch.length,"events"),!0;if(i.status>=400&&i.status<500)return e.debug&&console.warn("[IndieBob] 4xx error, not retrying:",i.status),!1}catch(i){e.debug&&console.warn("[IndieBob] fetch error (attempt",r+1,"):",i);}r<2&&(yield ne(oe[r]));}return e.debug&&console.warn("[IndieBob] failed to send after",3,"attempts"),false})}var w="_ib_queue",re=10,ie=5e3,se=50,m=class{constructor(e){this.queue=[];this.timer=null;this.flushing=false;this.config=e,this.startTimer(),this.setupVisibilityFlush(),this.setupOnlineFlush();}push(e){this.queue.push(e),this.queue.length>=re&&this.flush();}flush(e=false){return a(this,null,function*(){if(this.flushing&&!e||this.queue.length===0)return;this.flushing=true;let o=this.queue.splice(0);(yield E({batch:o},this.config,e))||this.pushToOfflineQueue(o),this.flushing=false;})}flushOfflineQueue(){return a(this,null,function*(){let e=this.getOfflineQueue();if(e.length===0)return;this.clearOfflineQueue(),yield E({batch:e},this.config);})}destroy(){this.timer&&clearInterval(this.timer);}startTimer(){this.timer=setInterval(()=>{this.queue.length>0&&this.flush();},ie);}setupVisibilityFlush(){typeof document!="undefined"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush(true);},{passive:true});}setupOnlineFlush(){typeof window!="undefined"&&(window.addEventListener("online",()=>{this.flushOfflineQueue();}),navigator.onLine&&this.flushOfflineQueue());}pushToOfflineQueue(e){try{let r=[...this.getOfflineQueue(),...e].slice(-se);localStorage.setItem(w,JSON.stringify(r));}catch(o){}}getOfflineQueue(){try{let e=localStorage.getItem(w);return e?JSON.parse(e):[]}catch(e){return []}}clearOfflineQueue(){try{localStorage.removeItem(w);}catch(e){}}};function H(){if(typeof window=="undefined")return "desktop";let t=window.innerWidth;return t<=768?"mobile":t<=1024?"tablet":"desktop"}function X(){return typeof window=="undefined"?{width:0,height:0}:{width:window.innerWidth,height:window.innerHeight}}function R(t){var u,C,k,A;let e=typeof window=="undefined",o=(u=t==null?void 0:t.path)!=null?u:e?"":window.location.pathname+window.location.search,n=e?"":window.location.href,r=(C=t==null?void 0:t.title)!=null?C:e?"":document.title,i=(k=t==null?void 0:t.referrer)!=null?k:e?"":document.referrer,h=X(),l=H(),s=e?void 0:j(window.location.search);return {page:{path:o,url:n,title:r,referrer:i},device:D(d({},h),{type:l}),utm:s,locale:e?"en":navigator.language,timezone:e?"UTC":(A=Intl.DateTimeFormat().resolvedOptions().timeZone)!=null?A:"UTC",sdkVersion:"0.1.0"}}function J(t){if(typeof window=="undefined")return ()=>{};let e=history.pushState.bind(history),o=history.replaceState.bind(history);return history.pushState=function(...n){e(...n),t();},history.replaceState=function(...n){o(...n);},window.addEventListener("popstate",t),()=>{history.pushState=e,history.replaceState=o,window.removeEventListener("popstate",t);}}var g=class t{constructor(e){this.cleanupHistory=null;this.config=e,this.queue=new m(e),typeof window!="undefined"&&L(window.location.search,document.referrer);}static init(e){let o=N(e);return new t(o)}page(e){f(this.config)&&this.queue.push(this.buildEvent("page",{context:R(e)}));}track(e,o){f(this.config)&&this.queue.push(this.buildEvent("track",{event:e,properties:o}));}identify(e,o){f(this.config)&&(this.userId=e,this.queue.push(this.buildEvent("identify",{traits:o,userId:e})));}revenue(e){var o;f(this.config)&&this.queue.push(this.buildEvent("revenue",{revenue:{amount:e.amount,currency:(o=e.currency)!=null?o:"USD",plan:e.plan,type:e.type}}));}optOut(){this.queue.flush(),K(),this.userId=void 0,this.config.debug&&console.log("[IndieBob] opted out");}optIn(){Q(),this.config.debug&&console.log("[IndieBob] opted in");}isOptedOut(){return V()}flush(){return a(this,null,function*(){yield this.queue.flush();})}getAnonymousId(){return v()}getSessionId(){return b()}getConfig(){return Object.freeze(d({},this.config))}enableAutoPage(){var e;return (e=this.cleanupHistory)==null||e.call(this),this.cleanupHistory=J(()=>this.page()),()=>{var o;(o=this.cleanupHistory)==null||o.call(this),this.cleanupHistory=null;}}buildEvent(e,o){var n,r;return {v:1,projectId:this.config.projectId,type:e,anonymousId:v(),userId:(n=this.userId)!=null?n:o.userId,sessionId:b(),timestamp:new Date().toISOString(),context:(r=o.context)!=null?r:R(),event:o.event,properties:o.properties,traits:o.traits,revenue:o.revenue}}};var ae=Symbol("indiebob"),O=null,Fe={install(t,e){let o=g.init(e);O=o,t.provide(ae,o),t.config.globalProperties.$bob=o;let n=t.config.globalProperties.$router;n&&n.afterEach(()=>{o.page();});}};function Le(){if(O)return O;throw new Error("[IndieBob] useTracker() called before IndieBobPlugin was installed.")}
|
|
2
|
+
exports.IndieBob=g;exports.IndieBobPlugin=Fe;exports.useTracker=Le;return exports;})({});
|
package/dist/vue.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {a}from'./chunk-XKQYO25W.js';export{a as IndieBob}from'./chunk-XKQYO25W.js';var l=Symbol("indiebob"),r=null,b={install(o,n){let e=a.init(n);r=e,o.provide(l,e),o.config.globalProperties.$bob=e;let t=o.config.globalProperties.$router;t&&t.afterEach(()=>{e.page();});}};function p(){if(r)return r;throw new Error("[IndieBob] useTracker() called before IndieBobPlugin was installed.")}export{b as IndieBobPlugin,p as useTracker};
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@indiebob/tracker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Privacy-first analytics SDK for web applications. <5KB gzipped. No cookies.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "IndieBob <hello@indiebob.com>",
|
|
8
|
+
"homepage": "https://indiebob.com/docs/sdk",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://gitlab.com/indiebob/indiebob-monorepo.git",
|
|
12
|
+
"directory": "packages/tracker"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"analytics",
|
|
16
|
+
"tracking",
|
|
17
|
+
"privacy",
|
|
18
|
+
"sdk",
|
|
19
|
+
"web-analytics",
|
|
20
|
+
"event-tracking",
|
|
21
|
+
"utm",
|
|
22
|
+
"attribution",
|
|
23
|
+
"vue",
|
|
24
|
+
"nuxt"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"main": "./dist/index.cjs",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"browser": "./dist/index.global.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js",
|
|
35
|
+
"require": "./dist/index.cjs"
|
|
36
|
+
},
|
|
37
|
+
"./vue": {
|
|
38
|
+
"types": "./dist/vue.d.ts",
|
|
39
|
+
"import": "./dist/vue.js",
|
|
40
|
+
"require": "./dist/vue.cjs"
|
|
41
|
+
},
|
|
42
|
+
"./server": {
|
|
43
|
+
"types": "./dist/server.d.ts",
|
|
44
|
+
"import": "./dist/server.js",
|
|
45
|
+
"require": "./dist/server.cjs"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"README.md",
|
|
51
|
+
"CHANGELOG.md"
|
|
52
|
+
],
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"type-check": "tsc --noEmit",
|
|
57
|
+
"cdn:publish": "tsx scripts/publish-cdn.mts",
|
|
58
|
+
"prepublishOnly": "pnpm run build"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@aws-sdk/client-s3": "^3.1000.0",
|
|
62
|
+
"@types/node": "^22.0.0",
|
|
63
|
+
"dotenv": "^16.5.0",
|
|
64
|
+
"tsup": "^8.5.1",
|
|
65
|
+
"tsx": "^4.19.0",
|
|
66
|
+
"typescript": "^5.7.0",
|
|
67
|
+
"vue": "^3.5.0",
|
|
68
|
+
"vue-router": "^4.5.0"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"vue": ">=3.3.0",
|
|
72
|
+
"vue-router": ">=4.0.0"
|
|
73
|
+
},
|
|
74
|
+
"peerDependenciesMeta": {
|
|
75
|
+
"vue": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"vue-router": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|