@behindthescenes/analytics 0.0.5 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +480 -162
- package/dist/browser/browser.js +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/manifest.json +2 -2
- package/docs/advanced-setup.md +499 -0
- package/docs/api-reference.md +498 -0
- package/docs/frameworks.md +936 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,63 +1,21 @@
|
|
|
1
1
|
# `@behindthescenes/analytics`
|
|
2
2
|
|
|
3
|
-
Browser SDK for BTS external-site analytics.
|
|
3
|
+
Browser SDK for BTS external-site analytics. Track visitor behavior, capture conversion events, and maintain attribution across the customer journey.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Auto-tracks pageviews, SPA history changes, outbound clicks, button clicks, form submissions, safe GET search forms, and opt-in content views by default
|
|
9
|
-
- Supports custom client-side events with `track(...)`
|
|
10
|
-
- Preserves journey tokens, UTM params, and click IDs for downstream attribution and CAPI delivery
|
|
7
|
+
The BTS Analytics SDK provides:
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
- **Automatic event tracking** - Page views, clicks, form submissions, and content views captured without code changes
|
|
10
|
+
- **Conversion tracking** - Standard e-commerce and lead events (purchase, checkout, sign_up, lead)
|
|
11
|
+
- **Cross-site attribution** - Journey tokens preserve visitor context across domains
|
|
12
|
+
- **Attribution preservation** - UTM parameters and click IDs captured and persisted
|
|
13
|
+
- **GoHighLevel integration** - Contact form submissions directly to GHL
|
|
14
|
+
- **Google Analytics compatibility** - Detects existing GA installations or loads tags in proxy mode
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
## Installation
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
- `outbound_click`: links to external origins
|
|
18
|
-
- `button_click`: clicks on `button`, `[role="button"]`, or `[data-bts-track-click]`
|
|
19
|
-
- `form_submit`: form submissions
|
|
20
|
-
- `search`: GET search forms with common query keys (`q`, `query`, `search`)
|
|
21
|
-
- `view_content`: elements that opt in with `data-bts-view-content`
|
|
22
|
-
|
|
23
|
-
Opt into automatic content-view tracking by marking elements that represent content cards, offers, posts, videos, or products:
|
|
24
|
-
|
|
25
|
-
```html
|
|
26
|
-
<article
|
|
27
|
-
data-bts-view-content="offer_123"
|
|
28
|
-
data-bts-content-type="offer"
|
|
29
|
-
data-bts-content-title="Creator Accelerator"
|
|
30
|
-
>
|
|
31
|
-
Creator Accelerator
|
|
32
|
-
</article>
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Identity, purchases, signups, payment steps, and arbitrary custom events are not auto-detected because they need product context. Call those explicitly after your app knows what happened.
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
// analytics.identify("user_123", { email: "fan@example.com" });
|
|
39
|
-
// analytics.track("watch_preview_clicked", { placement: "hero" });
|
|
40
|
-
// analytics.trackStandard("lead", { formId: "newsletter" });
|
|
41
|
-
// analytics.trackStandard("sign_up", { method: "email" });
|
|
42
|
-
// analytics.trackStandard("begin_checkout", { checkoutId: "offer_123" });
|
|
43
|
-
// analytics.trackStandard("add_payment_info", { checkoutId: "offer_123" });
|
|
44
|
-
// analytics.trackStandard("purchase", { orderId: "order_123", monetaryValue: 149 });
|
|
45
|
-
// const { journeyToken } = await analytics.startFunnel("/landing");
|
|
46
|
-
// const checkoutUrl = analytics.decorateUrl("https://behindthescenes.com/checkout", journeyToken);
|
|
47
|
-
// await analytics.flushNow();
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Auto pageviews and SPA navigation
|
|
51
|
-
|
|
52
|
-
- `**autoPageviews: false**` turns off the initial `page_view` **and** disables SPA history hooks (`pushState` / `replaceState` / `popstate`), so route changes do not emit automatic `page_view` events.
|
|
53
|
-
- To track SPA navigations without the first-load pageview, pass an `autoTrack` object, for example `{ pageviews: false, history: true, outboundLinks: true, buttonClicks: true, formSubmissions: true, search: true, viewContent: true }`.
|
|
54
|
-
|
|
55
|
-
## Flush reliability
|
|
56
|
-
|
|
57
|
-
- If a batch flush fails (network error or non-2xx response), events are **put back on the queue** and a later flush is scheduled (including the keepalive path used when `requestHeaders` is set).
|
|
58
|
-
- The default `sendBeacon` unload path cannot attach custom headers or observe success; use `requestHeaders` if you need the same retry semantics on `pagehide`.
|
|
59
|
-
|
|
60
|
-
## Install from npm
|
|
18
|
+
### npm/yarn/pnpm/bun
|
|
61
19
|
|
|
62
20
|
```bash
|
|
63
21
|
npm install @behindthescenes/analytics
|
|
@@ -75,174 +33,458 @@ yarn add @behindthescenes/analytics
|
|
|
75
33
|
bun add @behindthescenes/analytics
|
|
76
34
|
```
|
|
77
35
|
|
|
78
|
-
|
|
79
|
-
import { createBTSAnalytics } from "@behindthescenes/analytics";
|
|
36
|
+
### Browser Bundle (Hosted)
|
|
80
37
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
38
|
+
```html
|
|
39
|
+
<script type="module">
|
|
40
|
+
import { createBTSAnalytics } from "https://behindthescenes.com/sdk/analytics/latest/browser/browser.js";
|
|
84
41
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// analytics.trackStandard("begin_checkout", { checkoutId: "offer_123" });
|
|
90
|
-
// const { journeyToken } = await analytics.startFunnel("/landing");
|
|
91
|
-
// const checkoutUrl = analytics.decorateUrl("https://behindthescenes.com/checkout", journeyToken);
|
|
92
|
-
// await analytics.flushNow();
|
|
42
|
+
window.btsAnalytics = createBTSAnalytics({
|
|
43
|
+
siteKey: "your-public-site-key"
|
|
44
|
+
});
|
|
45
|
+
</script>
|
|
93
46
|
```
|
|
94
47
|
|
|
95
|
-
##
|
|
48
|
+
## Quick Start
|
|
96
49
|
|
|
97
|
-
|
|
50
|
+
```typescript
|
|
51
|
+
import { createBTSAnalytics } from "@behindthescenes/analytics";
|
|
98
52
|
|
|
99
|
-
```ts
|
|
100
53
|
const analytics = createBTSAnalytics({
|
|
101
54
|
siteKey: "your-public-site-key",
|
|
102
|
-
endpoint: "https://staging-api.bts.dev/v2/website/analytics",
|
|
103
55
|
});
|
|
104
56
|
```
|
|
105
57
|
|
|
106
|
-
##
|
|
58
|
+
## Setup in BTS
|
|
107
59
|
|
|
108
|
-
|
|
60
|
+
Before using the SDK, configure your site in the BTS platform:
|
|
109
61
|
|
|
110
|
-
|
|
111
|
-
await analytics.submitContactForm({
|
|
112
|
-
locationId: "your-ghl-location-id",
|
|
113
|
-
email: "fan@example.com",
|
|
114
|
-
subject: "Partnership enquiry",
|
|
115
|
-
body: "Tell us more about working together.",
|
|
116
|
-
});
|
|
117
|
-
```
|
|
62
|
+
### 1. Get Your Site Key
|
|
118
63
|
|
|
119
|
-
|
|
64
|
+
- In BTS, navigate to your creator dashboard
|
|
65
|
+
- Go to **Settings > Website Integration**
|
|
66
|
+
- Copy your **Public Site Key**
|
|
120
67
|
|
|
121
|
-
|
|
122
|
-
- `email`: the visitor's email address
|
|
123
|
-
- `subject`: short enquiry subject
|
|
124
|
-
- `body`: the message or "tell us more" text
|
|
68
|
+
### 2. Configure GoHighLevel (Optional)
|
|
125
69
|
|
|
126
|
-
|
|
70
|
+
For contact form submissions:
|
|
127
71
|
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
- `customFields`: GHL custom fields to pass through directly
|
|
132
|
-
- `metadata`: extra BTS metadata; values are forwarded as `bts_meta_<key>` contact fields
|
|
72
|
+
- Go to **Settings > Integrations > GoHighLevel**
|
|
73
|
+
- Connect your GHL account
|
|
74
|
+
- Note your **Location ID** for use with `submitContactForm()`
|
|
133
75
|
|
|
134
|
-
|
|
76
|
+
### 3. Enable External Site Tracking
|
|
135
77
|
|
|
136
|
-
|
|
78
|
+
Ensure your domain is added to the allowed domains list in your BTS space settings.
|
|
137
79
|
|
|
138
|
-
|
|
139
|
-
|
|
80
|
+
## Framework Setup
|
|
81
|
+
|
|
82
|
+
### React
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
// BTSAnalyticsProvider.tsx
|
|
86
|
+
import { createBTSAnalytics, BTSAnalytics } from "@behindthescenes/analytics";
|
|
87
|
+
import { createContext, PropsWithChildren, useContext, useMemo } from "react";
|
|
88
|
+
|
|
89
|
+
const BTSAnalyticsContext = createContext<BTSAnalytics | null>(null);
|
|
90
|
+
|
|
91
|
+
export function BTSAnalyticsProvider({ children }: PropsWithChildren) {
|
|
92
|
+
const analytics = useMemo(() =>
|
|
93
|
+
createBTSAnalytics({
|
|
94
|
+
siteKey: "your-public-site-key",
|
|
95
|
+
autoPageviews: true,
|
|
96
|
+
}),
|
|
97
|
+
[]
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<BTSAnalyticsContext.Provider value={analytics}>
|
|
102
|
+
{children}
|
|
103
|
+
</BTSAnalyticsContext.Provider>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function useBTSAnalytics() {
|
|
108
|
+
const analytics = useContext(BTSAnalyticsContext);
|
|
109
|
+
if (!analytics) throw new Error("useBTSAnalytics must be used inside BTSAnalyticsProvider");
|
|
110
|
+
return analytics;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Usage:
|
|
115
|
+
```tsx
|
|
116
|
+
const analytics = useBTSAnalytics();
|
|
117
|
+
analytics.track("cta_clicked", { placement: "hero" });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Next.js
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// app/providers.tsx
|
|
124
|
+
"use client";
|
|
125
|
+
|
|
126
|
+
import { createBTSAnalytics, BTSAnalytics } from "@behindthescenes/analytics";
|
|
127
|
+
import { createContext, PropsWithChildren, useContext, useMemo } from "react";
|
|
128
|
+
|
|
129
|
+
const BTSAnalyticsContext = createContext<BTSAnalytics | null>(null);
|
|
130
|
+
|
|
131
|
+
export function Providers({ children }: PropsWithChildren) {
|
|
132
|
+
const analytics = useMemo(() =>
|
|
133
|
+
createBTSAnalytics({
|
|
134
|
+
siteKey: "your-public-site-key",
|
|
135
|
+
autoPageviews: true,
|
|
136
|
+
}),
|
|
137
|
+
[]
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<BTSAnalyticsContext.Provider value={analytics}>
|
|
142
|
+
{children}
|
|
143
|
+
</BTSAnalyticsContext.Provider>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function useBTSAnalytics() {
|
|
148
|
+
const analytics = useContext(BTSAnalyticsContext);
|
|
149
|
+
if (!analytics) throw new Error("useBTSAnalytics must be used inside Providers");
|
|
150
|
+
return analytics;
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Remix
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
// app/root.tsx
|
|
158
|
+
import { createBTSAnalytics } from "@behindthescenes/analytics";
|
|
159
|
+
import { useEffect, useMemo } from "react";
|
|
160
|
+
import { Outlet, useLocation } from "react-router";
|
|
161
|
+
|
|
162
|
+
export const analytics = createBTSAnalytics({
|
|
140
163
|
siteKey: "your-public-site-key",
|
|
164
|
+
autoPageviews: false,
|
|
141
165
|
});
|
|
142
|
-
```
|
|
143
166
|
|
|
144
|
-
|
|
167
|
+
export function Layout() {
|
|
168
|
+
const location = useLocation();
|
|
169
|
+
const path = useMemo(() => location.pathname + location.search, [location]);
|
|
145
170
|
|
|
146
|
-
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
analytics.page(path);
|
|
173
|
+
}, [path]);
|
|
147
174
|
|
|
148
|
-
|
|
149
|
-
|
|
175
|
+
return <Outlet />;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
150
178
|
|
|
151
|
-
|
|
152
|
-
event.preventDefault();
|
|
179
|
+
### Vue
|
|
153
180
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
page: window.location.pathname,
|
|
163
|
-
},
|
|
164
|
-
});
|
|
181
|
+
```typescript
|
|
182
|
+
// src/plugins/bts-analytics.ts
|
|
183
|
+
import { createBTSAnalytics } from "@behindthescenes/analytics";
|
|
184
|
+
import type { App } from "vue";
|
|
185
|
+
|
|
186
|
+
export const analytics = createBTSAnalytics({
|
|
187
|
+
siteKey: "your-public-site-key",
|
|
188
|
+
autoPageviews: true,
|
|
165
189
|
});
|
|
190
|
+
|
|
191
|
+
export function installBTSAnalytics(app: App) {
|
|
192
|
+
app.provide("btsAnalytics", analytics);
|
|
193
|
+
}
|
|
166
194
|
```
|
|
167
195
|
|
|
168
|
-
|
|
196
|
+
```vue
|
|
197
|
+
<script setup lang="ts">
|
|
198
|
+
import { inject } from "vue";
|
|
199
|
+
import type { BTSAnalytics } from "@behindthescenes/analytics";
|
|
169
200
|
|
|
170
|
-
|
|
171
|
-
<script type="module">
|
|
172
|
-
import { createBTSAnalytics } from "https://app.behindthescenes.com/sdk/analytics/latest/browser/browser.js";
|
|
201
|
+
const analytics = inject<BTSAnalytics>("btsAnalytics")!;
|
|
173
202
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
203
|
+
function trackEvent() {
|
|
204
|
+
analytics.track("button_clicked", { buttonId: "cta" });
|
|
205
|
+
}
|
|
177
206
|
</script>
|
|
178
207
|
```
|
|
179
208
|
|
|
180
|
-
|
|
209
|
+
### Plain JavaScript
|
|
210
|
+
|
|
211
|
+
```html
|
|
212
|
+
<script async src="https://api.bts.it.com/v2/website/analytics/sdk/analytics/latest/browser/browser.js"></script>
|
|
213
|
+
<script>
|
|
214
|
+
window.btsDataLayer = window.btsDataLayer || [];
|
|
215
|
+
function bts(){window.btsDataLayer.push(arguments);}
|
|
216
|
+
bts("js", new Date());
|
|
217
|
+
bts("config", "your-public-site-key", {
|
|
218
|
+
autoPageviews: true
|
|
219
|
+
});
|
|
220
|
+
</script>
|
|
221
|
+
```
|
|
181
222
|
|
|
182
|
-
|
|
223
|
+
**See the [docs folder](./docs) for complete framework guides:**
|
|
224
|
+
- [Frameworks Guide](./docs/frameworks.md) - React, Next.js, Remix, Vue, Svelte, Angular, SolidJS
|
|
225
|
+
- [Advanced Setup](./docs/advanced-setup.md) - Custom endpoints, request signing, GA integration, SPA config
|
|
226
|
+
- [API Reference](./docs/api-reference.md) - Complete API documentation
|
|
183
227
|
|
|
184
|
-
##
|
|
228
|
+
## Auto-Tracked Events
|
|
185
229
|
|
|
186
|
-
|
|
230
|
+
The SDK automatically captures these events without additional code:
|
|
187
231
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
232
|
+
| Event | Trigger | Description |
|
|
233
|
+
|-------|---------|-------------|
|
|
234
|
+
| `page_view` | Page load, SPA navigation | Initial load and history changes |
|
|
235
|
+
| `outbound_click` | External link clicks | Links to different origins |
|
|
236
|
+
| `button_click` | Button interactions | `button`, `[role="button"]`, or `[data-bts-track-click]` |
|
|
237
|
+
| `form_submit` | Form submissions | Any form submit event |
|
|
238
|
+
| `search` | GET search forms | Forms with query params `q`, `query`, or `search` |
|
|
239
|
+
| `view_content` | Element visibility | Elements with `data-bts-view-content` |
|
|
191
240
|
|
|
192
|
-
|
|
241
|
+
### Content View Tracking
|
|
193
242
|
|
|
194
|
-
|
|
243
|
+
Opt-in to automatic content-view tracking by marking elements:
|
|
195
244
|
|
|
196
|
-
```
|
|
197
|
-
|
|
245
|
+
```html
|
|
246
|
+
<article
|
|
247
|
+
data-bts-view-content="offer_123"
|
|
248
|
+
data-bts-content-type="offer"
|
|
249
|
+
data-bts-content-title="Creator Accelerator"
|
|
250
|
+
>
|
|
251
|
+
Creator Accelerator
|
|
252
|
+
</article>
|
|
198
253
|
```
|
|
199
254
|
|
|
200
|
-
## Standard
|
|
255
|
+
## Standard Events
|
|
201
256
|
|
|
202
|
-
|
|
203
|
-
analytics.trackStandard("view_content", {
|
|
204
|
-
contentId: "offer-42",
|
|
205
|
-
});
|
|
257
|
+
Use `trackStandard()` for built-in conversion and engagement events:
|
|
206
258
|
|
|
259
|
+
```typescript
|
|
260
|
+
// Engagement
|
|
261
|
+
analytics.trackStandard("page_view");
|
|
262
|
+
analytics.trackStandard("view_content", { contentId: "offer-42" });
|
|
263
|
+
analytics.trackStandard("search", { query: "pricing" });
|
|
264
|
+
|
|
265
|
+
// Lead & Account
|
|
266
|
+
analytics.trackStandard("lead", { formId: "newsletter" });
|
|
267
|
+
analytics.trackStandard("sign_up", { method: "email" });
|
|
268
|
+
|
|
269
|
+
// E-commerce
|
|
270
|
+
analytics.trackStandard("begin_checkout", { checkoutId: "offer_123" });
|
|
271
|
+
analytics.trackStandard("add_payment_info", { checkoutId: "offer_123" });
|
|
207
272
|
analytics.trackStandard("purchase", {
|
|
208
|
-
currency: "USD",
|
|
209
|
-
monetaryValue: 149,
|
|
210
273
|
orderId: "order_123",
|
|
274
|
+
monetaryValue: 149,
|
|
275
|
+
currency: "USD"
|
|
211
276
|
});
|
|
277
|
+
|
|
278
|
+
// Engagement
|
|
279
|
+
analytics.trackStandard("outbound_click", { url: "https://partner.com" });
|
|
280
|
+
analytics.trackStandard("button_click", { buttonId: "cta-primary" });
|
|
281
|
+
analytics.trackStandard("form_submit", { formId: "contact" });
|
|
212
282
|
```
|
|
213
283
|
|
|
214
|
-
|
|
284
|
+
### Legacy Event Aliases
|
|
215
285
|
|
|
216
|
-
|
|
217
|
-
- `view_content`
|
|
218
|
-
- `search`
|
|
219
|
-
- `lead`
|
|
220
|
-
- `sign_up`
|
|
221
|
-
- `begin_checkout`
|
|
222
|
-
- `add_payment_info`
|
|
223
|
-
- `purchase`
|
|
224
|
-
- `outbound_click`
|
|
225
|
-
- `button_click`
|
|
226
|
-
- `form_submit`
|
|
286
|
+
These aliases are automatically normalized to standard events:
|
|
227
287
|
|
|
228
|
-
Legacy
|
|
288
|
+
| Legacy Name | Standard Event |
|
|
289
|
+
|-------------|----------------|
|
|
290
|
+
| `$pageview` | `page_view` |
|
|
291
|
+
| `checkout_started` | `begin_checkout` |
|
|
292
|
+
| `lead_capture` | `lead` |
|
|
293
|
+
| `purchase_completed` | `purchase` |
|
|
294
|
+
| `registration_complete` | `sign_up` |
|
|
229
295
|
|
|
230
|
-
## Custom
|
|
296
|
+
## Custom Events
|
|
297
|
+
|
|
298
|
+
Track arbitrary events with custom properties:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
analytics.track("video_played", {
|
|
302
|
+
videoId: "intro-01",
|
|
303
|
+
duration: 120,
|
|
304
|
+
autoplay: false
|
|
305
|
+
});
|
|
231
306
|
|
|
232
|
-
```ts
|
|
233
307
|
analytics.track("cta_clicked", {
|
|
234
308
|
ctaId: "hero-primary",
|
|
235
309
|
placement: "hero",
|
|
310
|
+
variant: "blue"
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## User Identification
|
|
315
|
+
|
|
316
|
+
Associate events with a known user:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
analytics.identify("user_123", {
|
|
320
|
+
email: "fan@example.com",
|
|
321
|
+
name: "John Doe",
|
|
322
|
+
plan: "pro"
|
|
236
323
|
});
|
|
237
324
|
```
|
|
238
325
|
|
|
239
|
-
##
|
|
326
|
+
## Contact Form Submission
|
|
327
|
+
|
|
328
|
+
Submit contact enquiries directly to GoHighLevel:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
await analytics.submitContactForm({
|
|
332
|
+
locationId: "your-ghl-location-id",
|
|
333
|
+
email: "fan@example.com",
|
|
334
|
+
subject: "Partnership enquiry",
|
|
335
|
+
body: "Tell us more about working together.",
|
|
336
|
+
name: "Jane Smith",
|
|
337
|
+
source: "website_contact",
|
|
338
|
+
tags: ["partnership", "high-value"],
|
|
339
|
+
customFields: {
|
|
340
|
+
company: "Acme Inc"
|
|
341
|
+
},
|
|
342
|
+
metadata: {
|
|
343
|
+
page: window.location.pathname,
|
|
344
|
+
referrer: document.referrer
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Required Fields
|
|
350
|
+
|
|
351
|
+
- `locationId`: Your GoHighLevel location ID
|
|
352
|
+
- At least one of `email` or `phone`
|
|
353
|
+
|
|
354
|
+
### Optional Fields
|
|
355
|
+
|
|
356
|
+
- `subject`, `body`: Message content (stored as `bts_meta_subject` and `bts_meta_body`)
|
|
357
|
+
- `name`, `firstName`, `lastName`: Contact identity
|
|
358
|
+
- `source`: Defaults to `behind_the_scenes`
|
|
359
|
+
- `tags`: Array of GHL tags to attach
|
|
360
|
+
- `customFields`: Direct GHL custom field mappings
|
|
361
|
+
- `metadata`: Additional context forwarded as `bts_meta_<key>` fields
|
|
240
362
|
|
|
241
|
-
|
|
363
|
+
## Journey Tracking (Cross-Site Attribution)
|
|
242
364
|
|
|
243
|
-
|
|
365
|
+
Track visitors across external sites and BTS properties:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// Start a funnel journey
|
|
369
|
+
const { journeyToken, journeyId, expiresAt } = await analytics.startFunnel("/landing");
|
|
370
|
+
|
|
371
|
+
// Decorate BTS URLs with journey context
|
|
372
|
+
const checkoutUrl = analytics.decorateUrl(
|
|
373
|
+
"https://behindthescenes.com/checkout",
|
|
374
|
+
journeyToken
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
// Get the persisted token for later use
|
|
378
|
+
const token = analytics.getPersistedJourneyToken();
|
|
379
|
+
|
|
380
|
+
// Notify handoff after visitor reaches BTS
|
|
381
|
+
await analytics.notifyHandoff(journeyToken, {
|
|
382
|
+
source: "external_landing_page",
|
|
383
|
+
campaign: "summer_2024"
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Configuration Options
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
244
390
|
const analytics = createBTSAnalytics({
|
|
245
|
-
siteKey: "
|
|
391
|
+
siteKey: "required-site-key",
|
|
392
|
+
|
|
393
|
+
// Auto-tracking configuration
|
|
394
|
+
autoTrack: {
|
|
395
|
+
pageviews: true, // Initial page_view + SPA navigation
|
|
396
|
+
history: true, // pushState/replaceState hooks
|
|
397
|
+
outboundLinks: true, // External link clicks
|
|
398
|
+
buttonClicks: true, // Button interactions
|
|
399
|
+
formSubmissions: true, // Form submit events
|
|
400
|
+
search: true, // GET search forms
|
|
401
|
+
viewContent: true // IntersectionObserver content views
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
// Or disable all auto-tracking
|
|
405
|
+
autoTrack: false,
|
|
406
|
+
|
|
407
|
+
// Legacy option: disable pageviews (also disables history hooks)
|
|
408
|
+
autoPageviews: false,
|
|
409
|
+
|
|
410
|
+
// Debug mode (logs to console)
|
|
411
|
+
debug: false,
|
|
412
|
+
|
|
413
|
+
// Flush interval in milliseconds (default: 300ms)
|
|
414
|
+
flushIntervalMs: 300,
|
|
415
|
+
|
|
416
|
+
// Google Analytics integration
|
|
417
|
+
googleAnalytics: {
|
|
418
|
+
loadTag: true,
|
|
419
|
+
measurementId: "G-XXXXXXXXXX"
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
// Custom request signing
|
|
423
|
+
requestHeaders: async ({ bodyText, path, headers }) => {
|
|
424
|
+
return {
|
|
425
|
+
...headers,
|
|
426
|
+
"X-Signature": await signPayload(bodyText)
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Auto-Tracking Behavior
|
|
433
|
+
|
|
434
|
+
- `autoTrack: false` - Disables all automatic tracking
|
|
435
|
+
- `autoPageviews: false` - Disables initial pageview and SPA navigation
|
|
436
|
+
- Setting `pageviews: false` in `autoTrack` object also disables `history` hooks
|
|
437
|
+
- Disabling pageviews prevents duplicate tracking when implementing custom pageview logic
|
|
438
|
+
|
|
439
|
+
## Google Analytics Integration
|
|
440
|
+
|
|
441
|
+
### Detection Mode (Default)
|
|
442
|
+
|
|
443
|
+
The SDK automatically detects existing GA installations and includes context:
|
|
444
|
+
|
|
445
|
+
- `ga_client_id`: From `_ga` cookie
|
|
446
|
+
- `ga_tag_installed`: Boolean if gtag detected
|
|
447
|
+
- `ga_measurement_id`: From script tag
|
|
448
|
+
|
|
449
|
+
### Proxy Mode
|
|
450
|
+
|
|
451
|
+
Load GA tag for scanner visibility while forwarding events server-side:
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
const analytics = createBTSAnalytics({
|
|
455
|
+
siteKey: "your-site-key",
|
|
456
|
+
googleAnalytics: {
|
|
457
|
+
loadTag: true,
|
|
458
|
+
measurementId: "G-XXXXXXXXXX"
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
This loads the Google tag with `send_page_view: false` so BTS can forward events while GA scanners detect an installed tag.
|
|
464
|
+
|
|
465
|
+
## Attribution & UTM Parameters
|
|
466
|
+
|
|
467
|
+
The SDK automatically captures and persists:
|
|
468
|
+
|
|
469
|
+
**UTM Parameters:**
|
|
470
|
+
- `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`
|
|
471
|
+
|
|
472
|
+
**Click IDs:**
|
|
473
|
+
- `fbclid`, `gclid`, `gbraid`, `wbraid`
|
|
474
|
+
- `li_fat_id`, `msclkid`, `ttclid`
|
|
475
|
+
|
|
476
|
+
**Cookies:**
|
|
477
|
+
- `_fbp`, `_fbc` (Facebook Pixel)
|
|
478
|
+
|
|
479
|
+
Attribution data is stored in localStorage and attached to every event, preserving the original landing URL and referrer across the session.
|
|
480
|
+
|
|
481
|
+
## Request Signing
|
|
482
|
+
|
|
483
|
+
Add custom headers for request validation:
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
const analytics = createBTSAnalytics({
|
|
487
|
+
siteKey: "your-site-key",
|
|
246
488
|
requestHeaders: async ({ bodyText, headers, path }) => {
|
|
247
489
|
const timestamp = new Date().toISOString();
|
|
248
490
|
const signature = await signAnalyticsPayload(`${timestamp}:${path}:${bodyText}`);
|
|
@@ -256,12 +498,88 @@ const analytics = createBTSAnalytics({
|
|
|
256
498
|
});
|
|
257
499
|
```
|
|
258
500
|
|
|
259
|
-
When `requestHeaders` is configured,
|
|
501
|
+
When `requestHeaders` is configured, the SDK uses `fetch(..., { keepalive: true })` instead of `sendBeacon` for page-unload events, enabling the same retry semantics for all requests.
|
|
502
|
+
|
|
503
|
+
## Flush Reliability
|
|
504
|
+
|
|
505
|
+
The SDK queues events and flushes them in batches:
|
|
260
506
|
|
|
261
|
-
|
|
507
|
+
- Automatic flush every 300ms (configurable)
|
|
508
|
+
- Immediate flush when queue reaches 50 events
|
|
509
|
+
- Failed batches are requeued and retried
|
|
510
|
+
- Page unload uses `sendBeacon` (or `fetch` with `keepalive` when custom headers are set)
|
|
262
511
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
512
|
+
### Manual Flush
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
// Force immediate flush
|
|
516
|
+
await analytics.flushNow();
|
|
266
517
|
```
|
|
267
518
|
|
|
519
|
+
## Cleanup
|
|
520
|
+
|
|
521
|
+
Remove event listeners and flush pending events:
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
analytics.destroy();
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
This:
|
|
528
|
+
- Removes click, submit, and navigation listeners
|
|
529
|
+
- Disconnects IntersectionObserver and MutationObserver
|
|
530
|
+
- Flushes any pending events
|
|
531
|
+
- Prevents further event tracking
|
|
532
|
+
|
|
533
|
+
## API Reference
|
|
534
|
+
|
|
535
|
+
### Methods
|
|
536
|
+
|
|
537
|
+
| Method | Description |
|
|
538
|
+
|--------|-------------|
|
|
539
|
+
| `track(eventName, properties?)` | Track a custom event |
|
|
540
|
+
| `trackStandard(eventName, properties?)` | Track a standard event |
|
|
541
|
+
| `identify(userId, traits?)` | Identify the current user |
|
|
542
|
+
| `page(path?)` | Manually trigger a page view |
|
|
543
|
+
| `startFunnel(entryPath?)` | Start a cross-site journey |
|
|
544
|
+
| `decorateUrl(url, journeyToken?)` | Append site key and journey to URL |
|
|
545
|
+
| `getPersistedJourneyToken()` | Get stored journey token |
|
|
546
|
+
| `notifyHandoff(journeyToken, context?)` | Notify BTS of journey handoff |
|
|
547
|
+
| `submitContactForm(input)` | Submit to GoHighLevel |
|
|
548
|
+
| `flushNow()` | Immediately flush event queue |
|
|
549
|
+
| `listStandardEvents()` | Get list of standard event names |
|
|
550
|
+
| `destroy()` | Cleanup and remove listeners |
|
|
551
|
+
|
|
552
|
+
### Types
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
type BTSAnalyticsStandardEventName =
|
|
556
|
+
| "page_view"
|
|
557
|
+
| "view_content"
|
|
558
|
+
| "search"
|
|
559
|
+
| "lead"
|
|
560
|
+
| "sign_up"
|
|
561
|
+
| "begin_checkout"
|
|
562
|
+
| "add_payment_info"
|
|
563
|
+
| "purchase"
|
|
564
|
+
| "outbound_click"
|
|
565
|
+
| "button_click"
|
|
566
|
+
| "form_submit";
|
|
567
|
+
|
|
568
|
+
type BTSAnalyticsEventType = "page_view" | "custom" | "identify" | "conversion";
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
## Releases
|
|
572
|
+
|
|
573
|
+
Publishing the npm package bumps the version with standard semver release types:
|
|
574
|
+
|
|
575
|
+
- `patch`: Bug fixes and backwards-compatible corrections (e.g., `1.2.3` to `1.2.4`)
|
|
576
|
+
- `minor`: Backwards-compatible features (e.g., `1.2.3` to `1.3.0`)
|
|
577
|
+
- `major`: Breaking changes (e.g., `1.2.3` to `2.0.0`)
|
|
578
|
+
|
|
579
|
+
Run the GitHub Actions release workflow manually and choose the release type to publish the next package version. The workflow reads the latest published npm version, increments it, validates the package, publishes it, and commits the updated `package.json` version.
|
|
580
|
+
|
|
581
|
+
For local publishing, set the release type before running the publish script:
|
|
582
|
+
|
|
583
|
+
```sh
|
|
584
|
+
RELEASE_TYPE=minor bun run release:publish
|
|
585
|
+
```
|