@splitwisp/tracker 1.0.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/README.md +196 -0
- package/dist/tracker.d.ts +163 -0
- package/dist/tracker.esm.js +2 -0
- package/dist/tracker.esm.js.map +7 -0
- package/dist/tracker.umd.js +3 -0
- package/dist/tracker.umd.js.map +7 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# tracking-sdk
|
|
2
|
+
|
|
3
|
+
Ultra-lightweight A/B testing SDK. **Zero dependencies. ~3KB gzipped.**
|
|
4
|
+
|
|
5
|
+
Designed for the scale-to-zero Visual A/B Testing MVP architecture.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Tiny** — no heavy dependencies, no plugins, no framework lock-in
|
|
10
|
+
- **Sticky sessions** — auto-generated session ID persisted in localStorage
|
|
11
|
+
- **Offline queue** — failed track calls are queued and retried on next page load
|
|
12
|
+
- **Beacon support** — fire-and-forget tracking on page unload via `navigator.sendBeacon`
|
|
13
|
+
- **SPA-aware** — optional auto-tracking of page views including pushState navigations
|
|
14
|
+
- **GTM-compatible** — UMD build works as a `<script>` tag or GTM Custom HTML tag
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### Script tag (CDN / GTM)
|
|
19
|
+
|
|
20
|
+
The SDK is automatically deployed to CloudFront CDN on every release. Choose the URL that best fits your needs:
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<!-- Recommended: Latest v1.x version (auto-updates with patches/features) -->
|
|
24
|
+
<script src="https://cdn.yourdomain.com/sdk/v1/tracker.umd.js"></script>
|
|
25
|
+
|
|
26
|
+
<!-- Alternative: Pinned version (immutable, never changes) -->
|
|
27
|
+
<script src="https://cdn.yourdomain.com/sdk/v1.2.3/tracker.umd.js"></script>
|
|
28
|
+
<script>
|
|
29
|
+
SplitWisp.init({
|
|
30
|
+
apiKey: 'pk_live_abc123',
|
|
31
|
+
endpoint: 'https://api.yourdomain.com',
|
|
32
|
+
}).then(function (t) {
|
|
33
|
+
// Get variant for an experiment
|
|
34
|
+
var variant = t.getVariant('exp_hero_headline');
|
|
35
|
+
if (variant && variant.variantName === 'short') {
|
|
36
|
+
document.querySelector('h1').textContent = 'Ship faster.';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Track an impression
|
|
40
|
+
t.track({ event: 'impression', experimentId: 'exp_hero_headline' });
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### GTM Custom HTML Tag
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<script>
|
|
49
|
+
(function () {
|
|
50
|
+
var s = document.createElement('script');
|
|
51
|
+
s.src = 'https://cdn.yourdomain.com/sdk/v1/tracker.umd.js';
|
|
52
|
+
s.onload = function () {
|
|
53
|
+
SplitWisp.init({
|
|
54
|
+
apiKey: '{{YOUR_API_KEY}}',
|
|
55
|
+
endpoint: '{{YOUR_API_ENDPOINT}}',
|
|
56
|
+
}).then(function (t) {
|
|
57
|
+
window.__tracker = t;
|
|
58
|
+
t.autoTrackPageViews();
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
document.head.appendChild(s);
|
|
62
|
+
})();
|
|
63
|
+
</script>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### npm / bundler
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install tracking-sdk
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import { Tracker } from 'tracking-sdk';
|
|
74
|
+
|
|
75
|
+
const t = new Tracker({
|
|
76
|
+
apiKey: 'pk_live_abc123',
|
|
77
|
+
endpoint: 'https://api.yourdomain.com',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await t.init();
|
|
81
|
+
|
|
82
|
+
const variant = t.getVariant('exp_pricing_cta');
|
|
83
|
+
t.track({ event: 'impression', experimentId: 'exp_pricing_cta' });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API
|
|
87
|
+
|
|
88
|
+
### `new Tracker(config)`
|
|
89
|
+
|
|
90
|
+
| Option | Type | Required | Default | Description |
|
|
91
|
+
| ----------- | -------- | -------- | ------- | ------------------------------------ |
|
|
92
|
+
| `apiKey` | `string` | ✅ | | Project API key |
|
|
93
|
+
| `endpoint` | `string` | ✅ | | API base URL (no trailing slash) |
|
|
94
|
+
| `sessionId` | `string` | | auto | Override auto-generated session ID |
|
|
95
|
+
| `userId` | `string` | | | Known user ID from your auth system |
|
|
96
|
+
| `timeout` | `number` | | 5000 | API call timeout in ms |
|
|
97
|
+
| `quiet` | `boolean`| | false | Suppress console warnings |
|
|
98
|
+
|
|
99
|
+
### `t.init(): Promise<Assignment[]>`
|
|
100
|
+
|
|
101
|
+
Fetches variant assignments for this session. Call once on page load.
|
|
102
|
+
|
|
103
|
+
### `t.getVariant(experimentId): Assignment | null`
|
|
104
|
+
|
|
105
|
+
Returns the assigned variant for a specific experiment.
|
|
106
|
+
|
|
107
|
+
### `t.track(opts): Promise<void>`
|
|
108
|
+
|
|
109
|
+
Tracks an event. Queues to localStorage on failure.
|
|
110
|
+
|
|
111
|
+
**Options:**
|
|
112
|
+
|
|
113
|
+
| Option | Type | Required | Description |
|
|
114
|
+
| -------------- | -------- | -------- | ---------------------------------------------- |
|
|
115
|
+
| `event` | `string` | ✅ | Event name (e.g. "pageview", "conversion") |
|
|
116
|
+
| `experimentId` | `string` | | Experiment this event relates to (optional) |
|
|
117
|
+
| `value` | `number` | | Numeric value (e.g. revenue in cents) |
|
|
118
|
+
| `properties` | `object` | | Arbitrary metadata |
|
|
119
|
+
|
|
120
|
+
**When to include `experimentId`:**
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
// ✅ Experiment-specific events (impressions, conversions)
|
|
124
|
+
const variant = t.getVariant('exp_hero_headline');
|
|
125
|
+
if (variant) {
|
|
126
|
+
t.track({
|
|
127
|
+
event: 'impression',
|
|
128
|
+
experimentId: 'exp_hero_headline'
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ✅ Conversion tracking
|
|
133
|
+
t.trackConversion('exp_hero_headline', 4999); // $49.99 in cents
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**When to omit `experimentId`:**
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
// ✅ General analytics events (not tied to an experiment)
|
|
140
|
+
t.track({ event: 'pageview' });
|
|
141
|
+
t.track({ event: 'signup' });
|
|
142
|
+
t.track({ event: 'button_click', properties: { button: 'cta' } });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Note:** Events without an `experimentId` are stored with a `_general` experiment ID on the server and won't affect experiment results. Use them for general product analytics.
|
|
146
|
+
|
|
147
|
+
### `t.trackConversion(experimentId, value?, properties?): Promise<void>`
|
|
148
|
+
|
|
149
|
+
Convenience wrapper for conversion events. Always requires an `experimentId`.
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
// Track a conversion with revenue
|
|
153
|
+
t.trackConversion('exp_pricing_cta', 9900); // $99.00 in cents
|
|
154
|
+
|
|
155
|
+
// Track a conversion without revenue
|
|
156
|
+
t.trackConversion('exp_signup_flow');
|
|
157
|
+
|
|
158
|
+
// Track a conversion with metadata
|
|
159
|
+
t.trackConversion('exp_checkout', 14999, {
|
|
160
|
+
plan: 'pro',
|
|
161
|
+
coupon: 'SAVE20'
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `t.trackBeacon(opts): void`
|
|
166
|
+
|
|
167
|
+
Fire-and-forget tracking via `sendBeacon`. Use on page unload.
|
|
168
|
+
|
|
169
|
+
### `t.autoTrackPageViews(): () => void`
|
|
170
|
+
|
|
171
|
+
Auto-tracks page views including SPA navigations. Returns a teardown function.
|
|
172
|
+
|
|
173
|
+
### `t.identify(userId): void`
|
|
174
|
+
|
|
175
|
+
Associate a known user ID with the session.
|
|
176
|
+
|
|
177
|
+
## Build
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npm install
|
|
181
|
+
npm run build
|
|
182
|
+
npm test
|
|
183
|
+
npm run size # check gzip size
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Architecture
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
Browser → tracker.umd.js (~3KB gzip)
|
|
190
|
+
├─ GET /v1/assign?apiKey=...&sessionId=... → variant assignments
|
|
191
|
+
├─ POST /v1/track { event, sessionId, ... } → event ingestion
|
|
192
|
+
└─ localStorage: _sw_sid (session), _sw_q (offline queue)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The SDK talks to API Gateway + Lambda endpoints defined in `tf-mvp/modules/api/`.
|
|
196
|
+
The SDK itself is distributed via S3 + CloudFront defined in `tf-mvp/modules/sdk_cdn/`.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracking SDK — Ultra-lightweight A/B testing tracker.
|
|
3
|
+
*
|
|
4
|
+
* Zero dependencies. ~3KB gzipped. Designed for:
|
|
5
|
+
* - Direct <script> tag inclusion
|
|
6
|
+
* - Google Tag Manager custom HTML tags
|
|
7
|
+
* - ES module import in bundled apps
|
|
8
|
+
*
|
|
9
|
+
* Talks to API Gateway endpoints:
|
|
10
|
+
* GET /v1/assign — sticky variant assignment
|
|
11
|
+
* POST /v1/track — event tracking (impressions, conversions)
|
|
12
|
+
*/
|
|
13
|
+
import type { VisualChange } from '@splitwisp/visual-changes';
|
|
14
|
+
export type { VisualChange } from '@splitwisp/visual-changes';
|
|
15
|
+
export interface TrackerConfig {
|
|
16
|
+
/** Your project's API key (issued from the dashboard). */
|
|
17
|
+
apiKey: string;
|
|
18
|
+
/** Base URL of the API (e.g. https://api.example.com). No trailing slash. */
|
|
19
|
+
endpoint: string;
|
|
20
|
+
/** Override the auto-generated session ID. */
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
/** Known user ID (e.g. from your auth system). */
|
|
23
|
+
userId?: string;
|
|
24
|
+
/** Timeout in ms for API calls. Default 5000. */
|
|
25
|
+
timeout?: number;
|
|
26
|
+
/** If true, suppress all console warnings. Default false. */
|
|
27
|
+
quiet?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* If true, automatically apply visual changes from the assign response.
|
|
30
|
+
* Default true. Set to false to handle visual changes manually.
|
|
31
|
+
*/
|
|
32
|
+
applyVisualChanges?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Max time (ms) to watch for late-loading DOM elements when applying
|
|
35
|
+
* visual changes. Only relevant when applyVisualChanges is true.
|
|
36
|
+
* Default 5000.
|
|
37
|
+
*/
|
|
38
|
+
visualChangeTimeout?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface ConversionGoalConfig {
|
|
41
|
+
/** page_visit: glob pattern for URL matching */
|
|
42
|
+
urlPattern?: string;
|
|
43
|
+
/** element_click: CSS selector for the clickable element */
|
|
44
|
+
selector?: string;
|
|
45
|
+
/** form_submit: CSS selector for the form element */
|
|
46
|
+
formSelector?: string;
|
|
47
|
+
/** scroll_depth: percentage threshold (0–100) */
|
|
48
|
+
threshold?: string;
|
|
49
|
+
/** time_on_page: milliseconds before firing */
|
|
50
|
+
durationMs?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface ConversionGoal {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
type: 'page_visit' | 'element_click' | 'form_submit' | 'scroll_depth' | 'time_on_page';
|
|
56
|
+
config: ConversionGoalConfig;
|
|
57
|
+
}
|
|
58
|
+
export interface Assignment {
|
|
59
|
+
experimentId: string;
|
|
60
|
+
variantId: string;
|
|
61
|
+
variantName: string;
|
|
62
|
+
changes?: VisualChange[];
|
|
63
|
+
conversionGoals?: ConversionGoal[];
|
|
64
|
+
}
|
|
65
|
+
export interface TrackOptions {
|
|
66
|
+
/** Event name, e.g. "conversion", "click", "signup". */
|
|
67
|
+
event: string;
|
|
68
|
+
/** Experiment ID this event relates to (optional — server can infer). */
|
|
69
|
+
experimentId?: string;
|
|
70
|
+
/** Numeric value associated with the event (e.g. revenue in cents). */
|
|
71
|
+
value?: number;
|
|
72
|
+
/** Arbitrary metadata. */
|
|
73
|
+
properties?: Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
export declare class Tracker {
|
|
76
|
+
private cfg;
|
|
77
|
+
private cleanupVisualChanges;
|
|
78
|
+
private cleanupGoalTracking;
|
|
79
|
+
private assignments;
|
|
80
|
+
private initialized;
|
|
81
|
+
private blocked;
|
|
82
|
+
private utm;
|
|
83
|
+
private firedGoals;
|
|
84
|
+
constructor(config: TrackerConfig);
|
|
85
|
+
/** Session ID used for sticky assignment. */
|
|
86
|
+
get sessionId(): string;
|
|
87
|
+
/** Identify a known user (call before or after init). */
|
|
88
|
+
identify(userId: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Fetch variant assignments for all active experiments for this session.
|
|
91
|
+
* Call once on page load. Results are cached in-memory.
|
|
92
|
+
*/
|
|
93
|
+
init(): Promise<Assignment[]>;
|
|
94
|
+
/** Get the variant for a specific experiment. Returns null if not assigned. */
|
|
95
|
+
getVariant(experimentId: string): Assignment | null;
|
|
96
|
+
/** Get all current assignments. */
|
|
97
|
+
getAssignments(): Assignment[];
|
|
98
|
+
/**
|
|
99
|
+
* Track an event (impression, conversion, click, etc.).
|
|
100
|
+
* Uses sendBeacon on page unload, fetch otherwise.
|
|
101
|
+
* Failed calls are queued to localStorage and retried on next init().
|
|
102
|
+
*/
|
|
103
|
+
track(opts: TrackOptions): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Track a conversion event — convenience wrapper around track().
|
|
106
|
+
* @param experimentId - The experiment this conversion is for.
|
|
107
|
+
* @param value - Optional numeric value (e.g. revenue in cents).
|
|
108
|
+
* @param properties - Optional metadata.
|
|
109
|
+
*/
|
|
110
|
+
trackConversion(experimentId: string, value?: number, properties?: Record<string, unknown>): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Fire-and-forget track using sendBeacon. Ideal for pagehide / visibilitychange.
|
|
113
|
+
* Does NOT retry on failure.
|
|
114
|
+
*/
|
|
115
|
+
trackBeacon(opts: TrackOptions): void;
|
|
116
|
+
/**
|
|
117
|
+
* Auto-track page views. Sends a "pageview" event now, and on SPA navigations
|
|
118
|
+
* (popstate + pushState/replaceState monkey-patches).
|
|
119
|
+
* Call once after init().
|
|
120
|
+
*/
|
|
121
|
+
autoTrackPageViews(): () => void;
|
|
122
|
+
/** Whether this API key is currently suppressed. */
|
|
123
|
+
get isSupressed(): boolean;
|
|
124
|
+
private headers;
|
|
125
|
+
private setBlocked;
|
|
126
|
+
private isBlocked;
|
|
127
|
+
/** Tear down goal tracking listeners (called on destroy or re-init). */
|
|
128
|
+
private teardownGoalTracking;
|
|
129
|
+
/**
|
|
130
|
+
* Set up automatic conversion tracking based on goals configured on experiments.
|
|
131
|
+
* Supports 5 goal types: page_visit, element_click, form_submit, scroll_depth, time_on_page.
|
|
132
|
+
* Each goal fires at most once per session per experiment (deduplication via localStorage).
|
|
133
|
+
*/
|
|
134
|
+
private setupGoalTracking;
|
|
135
|
+
private attachGoalListener;
|
|
136
|
+
private loadFiredGoals;
|
|
137
|
+
private saveFiredGoals;
|
|
138
|
+
private captureUtmParams;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create and auto-initialize a Tracker instance.
|
|
142
|
+
*
|
|
143
|
+
* Usage from a <script> tag or GTM Custom HTML:
|
|
144
|
+
*
|
|
145
|
+
* <script src="https://cdn.splitwisp.com/sdk/v1/tracker.umd.js"></script>
|
|
146
|
+
* <script>
|
|
147
|
+
* SplitWisp.init({
|
|
148
|
+
* apiKey: 'pk_live_abc123',
|
|
149
|
+
* endpoint: 'https://api.splitwisp.com',
|
|
150
|
+
* }).then(function(t) {
|
|
151
|
+
* var variant = t.getVariant('exp_hero_headline');
|
|
152
|
+
* if (variant && variant.variantName === 'short') {
|
|
153
|
+
* document.querySelector('h1').textContent = 'Ship faster.';
|
|
154
|
+
* }
|
|
155
|
+
* t.track({ event: 'impression', experimentId: 'exp_hero_headline' });
|
|
156
|
+
* });
|
|
157
|
+
* </script>
|
|
158
|
+
*/
|
|
159
|
+
export declare function init(config: TrackerConfig): Promise<Tracker>;
|
|
160
|
+
export declare const SplitWisp: {
|
|
161
|
+
Tracker: typeof Tracker;
|
|
162
|
+
init: typeof init;
|
|
163
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var V=Object.defineProperty,B=Object.defineProperties;var H=Object.getOwnPropertyDescriptors;var I=Object.getOwnPropertySymbols;var U=Object.prototype.hasOwnProperty,N=Object.prototype.propertyIsEnumerable;var S=(s,e,t)=>e in s?V(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t,T=(s,e)=>{for(var t in e||(e={}))U.call(e,t)&&S(s,t,e[t]);if(I)for(var t of I(e))N.call(e,t)&&S(s,t,e[t]);return s},C=(s,e)=>B(s,H(e));function $(s,e){s.textContent=e}function F(s,e){s.innerHTML=e}function g(s,e,t){s.setAttribute(e,t)}function J(s,e){for(let t of e)t&&s.classList.add(t)}function D(s,e){for(let t of e)t&&s.classList.remove(t)}function f(s,e){g(s,"style",e)}function z(s,e){g(s,"href",e)}function Q(s,e){g(s,"src",e)}function E(s){s.style.display="none",s.style.visibility="hidden"}function x(s){s.style.display="block",s.style.visibility="visible"}function W(s,e){s.outerHTML=e}function Y(s,e){let t=document.head;if(!t)return;let n=document.getElementById("sw-global-css-"+s);n&&n.remove();let i=document.createElement("style");i.innerText=e,i.id="sw-global-css-"+s,t.appendChild(i)}function L(s){return s?s.indexOf(",")===-1?[s.trim()]:s.split(",").map(e=>e.trim()).filter(Boolean):[]}function G(s){if(s.action==="addGlobalCSS")return Y(s.selector,s.value),!0;let e=document.querySelector(s.selector);if(!e||!(e instanceof HTMLElement))return!1;switch(s.action){case"setText":$(e,s.value);break;case"setInnerHTML":F(e,s.value);break;case"setAttribute":if(!s.attribute)return!1;g(e,s.attribute,s.value);break;case"addClasses":J(e,L(s.value));break;case"removeClasses":D(e,L(s.value));break;case"setHref":e instanceof HTMLAnchorElement&&z(e,s.value);break;case"setSrc":e instanceof HTMLImageElement&&Q(e,s.value);break;case"setStyle":f(e,s.value);break;case"hide":E(e);break;case"show":x(e);break;case"setFontSize":f(e,`font-size: ${s.value}`);break;case"setFontColor":f(e,`color: ${s.value}`);break;case"setBackgroundColor":f(e,`background-color: ${s.value}`);break;case"setVisibility":{let t=s.value.toLowerCase();t==="hide"?E(e):t==="show"&&x(e);break}case"setOuterHTML":W(e,s.value);break;default:return!1}return!0}function _(s,e={}){let{timeoutMs:t=5e3}=e,n=new Set(s);for(let a of n)G(a)&&n.delete(a);if(n.size===0)return()=>{};let i=new MutationObserver(()=>{for(let a of n)G(a)&&n.delete(a);n.size===0&&i.disconnect()});i.observe(document.body,{childList:!0,subtree:!0});let r=setTimeout(()=>i.disconnect(),t);return()=>{clearTimeout(r),i.disconnect()}}var j="sw-cloak";function h(){typeof document!="undefined"&&document.documentElement.classList.remove(j)}function O(s=3e3){let e=setTimeout(h,s);return()=>clearTimeout(e)}var P="_sw_sid",p="_sw_q",y="_sw_blocked",X=36e5,R="_sw_utm",q="_sw_goals";function A(){let s=new Uint8Array(16);crypto.getRandomValues(s);let e="";for(let t=0;t<s.length;t++)e+=s[t].toString(36);return e.slice(0,21)}function Z(){try{let s=localStorage.getItem(P);if(s)return s;let e=A();return localStorage.setItem(P,e),e}catch(s){return A()}}function d(s,...e){s||console.warn("[SplitWisp]",...e)}async function w(s,e,t){let n=new AbortController,i=setTimeout(()=>n.abort(),t);try{let r=await fetch(s,C(T({},e),{signal:n.signal}));if(clearTimeout(i),!r.ok)return{data:null,status:r.status};let a=await r.text();return{data:a?JSON.parse(a):null,status:r.status}}catch(r){return clearTimeout(i),{data:null,status:0}}}function ee(s,e){return typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(s,e):!1}function K(s){try{let e=localStorage.getItem(p),t=e?JSON.parse(e):[];t.push(s),t.length>100&&t.shift(),localStorage.setItem(p,JSON.stringify(t))}catch(e){}}function te(s,e){try{let t=localStorage.getItem(p);if(!t)return;let n=JSON.parse(t);if(!n.length)return;localStorage.removeItem(p);for(let i of n)w(i.url,{method:"POST",headers:s,body:i.body},e).catch(()=>{K(i)})}catch(t){}}var v=class{constructor(e){this.cleanupVisualChanges=null;this.cleanupGoalTracking=null;this.assignments=new Map;this.initialized=!1;this.blocked=!1;this.utm={};this.firedGoals=new Set;var t,n,i,r;if(!e.apiKey)throw new Error("[Tracker] apiKey is required");if(!e.endpoint)throw new Error("[Tracker] endpoint is required");this.cfg={apiKey:e.apiKey,endpoint:e.endpoint.replace(/\/+$/,""),sessionId:e.sessionId||Z(),userId:e.userId,timeout:(t=e.timeout)!=null?t:5e3,quiet:(n=e.quiet)!=null?n:!1,applyVisualChanges:(i=e.applyVisualChanges)!=null?i:!0,visualChangeTimeout:(r=e.visualChangeTimeout)!=null?r:5e3},this.captureUtmParams(),this.cfg.applyVisualChanges&&O()}get sessionId(){return this.cfg.sessionId}identify(e){this.cfg.userId=e}async init(){var r;let e=new URLSearchParams({apiKey:this.cfg.apiKey,sessionId:this.cfg.sessionId});if(this.cfg.userId&&e.set("userId",this.cfg.userId),this.isBlocked())return this.blocked=!0,d(this.cfg.quiet,"API key is blocked (cached) \u2014 skipping assign request."),this.initialized=!0,this.cfg.applyVisualChanges&&h(),this.getAssignments();let t=`${this.cfg.endpoint}/v1/assign?${e}`,{data:n,status:i}=await w(t,{method:"GET",headers:this.headers()},this.cfg.timeout);if(i===429)this.blocked=!0,this.setBlocked(),d(this.cfg.quiet,"API returned 429 \u2014 suppressing further requests for this session.");else if(n!=null&&n.assignments)for(let a of n.assignments)this.assignments.set(a.experimentId,a);else d(this.cfg.quiet,"Failed to fetch assignments \u2014 experiments will use defaults.");if(this.initialized=!0,this.cfg.applyVisualChanges){let a=[];for(let u of this.assignments.values())(r=u.changes)!=null&&r.length&&a.push(...u.changes);a.length>0&&(this.cleanupVisualChanges=_(a,{timeoutMs:this.cfg.visualChangeTimeout})),h()}return this.setupGoalTracking(),te(this.headers(),this.cfg.timeout),this.getAssignments()}getVariant(e){var t;return(t=this.assignments.get(e))!=null?t:null}getAssignments(){return Array.from(this.assignments.values())}async track(e){if(!e.event){d(this.cfg.quiet,"track() called without an event name \u2014 ignoring.");return}if(this.blocked)return;let t={apiKey:this.cfg.apiKey,sessionId:this.cfg.sessionId,event:e.event,timestamp:new Date().toISOString(),url:typeof window!="undefined"?window.location.href:void 0};if(this.cfg.userId&&(t.userId=this.cfg.userId),e.experimentId&&(t.experimentId=e.experimentId),e.value!==void 0&&(t.value=e.value),e.properties&&(t.properties=e.properties),this.utm.source&&(t.utmSource=this.utm.source),this.utm.medium&&(t.utmMedium=this.utm.medium),this.utm.campaign&&(t.utmCampaign=this.utm.campaign),e.experimentId){let u=this.assignments.get(e.experimentId);u&&(t.variantId=u.variantId)}let n=`${this.cfg.endpoint}/v1/track`,i=JSON.stringify(t),{data:r,status:a}=await w(n,{method:"POST",headers:this.headers(),body:i},this.cfg.timeout);a===429?(this.blocked=!0,this.setBlocked()):r===null&&a===0&&K({url:n,body:i,ts:Date.now()})}async trackConversion(e,t,n){return this.track({event:"conversion",experimentId:e,value:t,properties:n})}trackBeacon(e){let t={apiKey:this.cfg.apiKey,sessionId:this.cfg.sessionId,event:e.event,timestamp:new Date().toISOString(),url:typeof window!="undefined"?window.location.href:void 0};this.cfg.userId&&(t.userId=this.cfg.userId),e.experimentId&&(t.experimentId=e.experimentId),e.value!==void 0&&(t.value=e.value),e.properties&&(t.properties=e.properties),this.utm.source&&(t.utmSource=this.utm.source),this.utm.medium&&(t.utmMedium=this.utm.medium),this.utm.campaign&&(t.utmCampaign=this.utm.campaign);let n=`${this.cfg.endpoint}/v1/track`;ee(n,JSON.stringify(t))}autoTrackPageViews(){let e=()=>{this.track({event:"pageview"})};e(),window.addEventListener("popstate",e);let t=history.pushState.bind(history),n=history.replaceState.bind(history);history.pushState=function(...r){t(...r),e()},history.replaceState=function(...r){n(...r),e()};let i=()=>{this.trackBeacon({event:"pagehide"})};return document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&i()}),()=>{window.removeEventListener("popstate",e),history.pushState=t,history.replaceState=n}}get isSupressed(){return this.blocked}headers(){return{"Content-Type":"application/json","X-API-Key":this.cfg.apiKey}}setBlocked(){try{sessionStorage.setItem(y,String(Date.now()+X))}catch(e){}}isBlocked(){try{let e=sessionStorage.getItem(y);return e?Date.now()<Number(e)?!0:(sessionStorage.removeItem(y),!1):!1}catch(e){return!1}}teardownGoalTracking(){this.cleanupGoalTracking&&(this.cleanupGoalTracking(),this.cleanupGoalTracking=null)}setupGoalTracking(){var t;this.teardownGoalTracking(),this.loadFiredGoals();let e=[];for(let n of this.assignments.values())if((t=n.conversionGoals)!=null&&t.length)for(let i of n.conversionGoals){let r=`${n.experimentId}:${i.id}`;if(this.firedGoals.has(r))continue;let a=this.attachGoalListener(n.experimentId,i,r);a&&e.push(a)}e.length>0&&(this.cleanupGoalTracking=()=>{for(let n of e)n();e.length=0})}attachGoalListener(e,t,n){var r,a,u,k,b;let i=()=>{this.firedGoals.has(n)||(this.firedGoals.add(n),this.saveFiredGoals(),this.trackConversion(e,void 0,{goalId:t.id,goalName:t.name,goalType:t.type}))};switch(t.type){case"page_visit":{let o=(r=t.config.urlPattern)!=null?r:"";if(!o)return null;if(M(o,window.location.href))return i(),null;let c=()=>{M(o,window.location.href)&&i()};return window.addEventListener("popstate",c),()=>window.removeEventListener("popstate",c)}case"element_click":{let o=(a=t.config.selector)!=null?a:"";if(!o)return null;let c=m=>{let l=m.target;l!=null&&l.closest(o)&&i()};return document.addEventListener("click",c,{capture:!0}),()=>document.removeEventListener("click",c,{capture:!0})}case"form_submit":{let o=(u=t.config.formSelector)!=null?u:"";if(!o)return null;let c=m=>{let l=m.target;l!=null&&l.matches(o)&&i()};return document.addEventListener("submit",c,{capture:!0}),()=>document.removeEventListener("submit",c,{capture:!0})}case"scroll_depth":{let o=parseInt((k=t.config.threshold)!=null?k:"0",10);if(o<=0||o>100)return null;let c=()=>{window.scrollY/(document.documentElement.scrollHeight-window.innerHeight)*100>=o&&i()};return window.addEventListener("scroll",c,{passive:!0}),c(),()=>window.removeEventListener("scroll",c)}case"time_on_page":{let o=parseInt((b=t.config.durationMs)!=null?b:"0",10);if(o<=0)return null;let c=setTimeout(i,o);return()=>clearTimeout(c)}default:return d(this.cfg.quiet,`Unknown goal type: ${t.type}`),null}}loadFiredGoals(){try{let e=sessionStorage.getItem(q);if(e){let t=JSON.parse(e);this.firedGoals=new Set(t)}}catch(e){}}saveFiredGoals(){try{sessionStorage.setItem(q,JSON.stringify([...this.firedGoals]))}catch(e){}}captureUtmParams(){try{let e=sessionStorage.getItem(R);if(e&&(this.utm=JSON.parse(e)),typeof window=="undefined")return;let t=new URLSearchParams(window.location.search),n=t.get("utm_source"),i=t.get("utm_medium"),r=t.get("utm_campaign");(n||i||r)&&(n&&(this.utm.source=n),i&&(this.utm.medium=i),r&&(this.utm.campaign=r),sessionStorage.setItem(R,JSON.stringify(this.utm)))}catch(e){}}};function M(s,e){try{let n=s.startsWith("http://")||s.startsWith("https://")?e:new URL(e).pathname;return new RegExp("^"+s.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".")+"$").test(n)}catch(t){return!1}}async function se(s){let e=new v(s);return await e.init(),e}var ae={Tracker:v,init:se};export{ae as SplitWisp,v as Tracker,se as init};
|
|
2
|
+
//# sourceMappingURL=tracker.esm.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../packages/visual-changes/src/mutators.ts", "../../packages/visual-changes/src/apply.ts", "../../packages/visual-changes/src/observer.ts", "../../packages/visual-changes/src/antiflicker.ts", "../src/tracker.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Individual DOM mutator functions.\n *\n * Pure DOM API calls \u2014 zero dependencies. Each function takes an element\n * and a value, and performs a single mutation. These are the building blocks\n * used by applyVisualChange().\n */\n\nexport function setElementText(element: Element, text: string): void {\n element.textContent = text;\n}\n\nexport function setElementInnerHTML(element: Element, innerHTML: string): void {\n element.innerHTML = innerHTML;\n}\n\nexport function setElementAttribute(\n element: HTMLElement,\n attribute: string,\n value: string,\n): void {\n element.setAttribute(attribute, value);\n}\n\nexport function addClassesToElement(element: HTMLElement, classes: string[]): void {\n for (const className of classes) {\n if (className) element.classList.add(className);\n }\n}\n\nexport function removeClassesFromElement(element: HTMLElement, classes: string[]): void {\n for (const className of classes) {\n if (className) element.classList.remove(className);\n }\n}\n\nexport function setElementStyle(element: HTMLElement, value: string): void {\n setElementAttribute(element, 'style', value);\n}\n\nexport function setElementHref(element: HTMLAnchorElement, href: string): void {\n setElementAttribute(element, 'href', href);\n}\n\nexport function setElementSrc(element: HTMLImageElement, src: string): void {\n setElementAttribute(element, 'src', src);\n}\n\nexport function hideElement(element: HTMLElement): void {\n element.style.display = 'none';\n element.style.visibility = 'hidden';\n}\n\nexport function showElement(element: HTMLElement): void {\n element.style.display = 'block';\n element.style.visibility = 'visible';\n}\n\nexport function setElementOuterHTML(element: HTMLElement, value: string): void {\n element.outerHTML = value;\n}\n\nexport function addGlobalCSS(key: string, value: string): void {\n const head = document.head;\n if (!head) return;\n // Remove existing style with this key to avoid duplicates\n const existing = document.getElementById('sw-global-css-' + key);\n if (existing) existing.remove();\n const style = document.createElement('style');\n style.innerText = value;\n style.id = 'sw-global-css-' + key;\n head.appendChild(style);\n}\n\n/** Parse a comma-separated string into a trimmed array. */\nexport function parseCommaSeparatedList(list: string): string[] {\n if (!list) return [];\n if (list.indexOf(',') === -1) return [list.trim()];\n return list.split(',').map((item) => item.trim()).filter(Boolean);\n}\n", "/**\n * Apply visual changes to the DOM.\n *\n * This is the core engine used by both:\n * - The tracking SDK (production \u2014 applies changes from /v1/assign response)\n * - The visual editor (preview \u2014 applies changes during authoring)\n *\n * Zero dependencies. Pure DOM APIs.\n */\n\nimport type { VisualChange } from './types';\nimport {\n setElementText,\n setElementInnerHTML,\n setElementAttribute,\n addClassesToElement,\n removeClassesFromElement,\n setElementStyle,\n setElementHref,\n setElementSrc,\n hideElement,\n showElement,\n setElementOuterHTML,\n addGlobalCSS,\n parseCommaSeparatedList,\n} from './mutators';\n\n/**\n * Try to apply a single visual change to the DOM.\n * Returns true if the target element was found and the change was applied.\n */\nexport function tryApplyChange(change: VisualChange): boolean {\n // addGlobalCSS doesn't target a specific element\n if (change.action === 'addGlobalCSS') {\n addGlobalCSS(change.selector, change.value);\n return true;\n }\n\n const element = document.querySelector(change.selector);\n if (!element || !(element instanceof HTMLElement)) {\n return false;\n }\n\n switch (change.action) {\n case 'setText':\n setElementText(element, change.value);\n break;\n case 'setInnerHTML':\n setElementInnerHTML(element, change.value);\n break;\n case 'setAttribute':\n if (!change.attribute) return false;\n setElementAttribute(element, change.attribute, change.value);\n break;\n case 'addClasses':\n addClassesToElement(element, parseCommaSeparatedList(change.value));\n break;\n case 'removeClasses':\n removeClassesFromElement(element, parseCommaSeparatedList(change.value));\n break;\n case 'setHref':\n if (element instanceof HTMLAnchorElement) {\n setElementHref(element, change.value);\n }\n break;\n case 'setSrc':\n if (element instanceof HTMLImageElement) {\n setElementSrc(element, change.value);\n }\n break;\n case 'setStyle':\n setElementStyle(element, change.value);\n break;\n case 'hide':\n hideElement(element);\n break;\n case 'show':\n showElement(element);\n break;\n case 'setFontSize':\n setElementStyle(element, `font-size: ${change.value}`);\n break;\n case 'setFontColor':\n setElementStyle(element, `color: ${change.value}`);\n break;\n case 'setBackgroundColor':\n setElementStyle(element, `background-color: ${change.value}`);\n break;\n case 'setVisibility': {\n const val = change.value.toLowerCase();\n if (val === 'hide') hideElement(element);\n else if (val === 'show') showElement(element);\n break;\n }\n case 'setOuterHTML':\n setElementOuterHTML(element, change.value);\n break;\n default:\n return false;\n }\n\n return true;\n}\n\n/**\n * Apply an array of visual changes to the DOM immediately.\n * Changes whose target elements are not found are silently skipped.\n */\nexport function applyVisualChanges(changes: VisualChange[]): void {\n for (const change of changes) {\n tryApplyChange(change);\n }\n}\n", "/**\n * MutationObserver-based retry logic for applying visual changes to\n * late-loading elements (SPAs, lazy-loaded content).\n *\n * Used by the tracking SDK to handle elements that don't exist at init time.\n */\n\nimport type { VisualChange } from './types';\nimport { tryApplyChange } from './apply';\n\nexport interface ApplyWithRetryOptions {\n /** Max time (ms) to watch for new elements. Default 5000. */\n timeoutMs?: number;\n}\n\n/**\n * Apply visual changes immediately where possible, then watch the DOM\n * for late-appearing elements. Automatically disconnects the observer\n * once all changes are applied or the timeout expires.\n *\n * Returns a cleanup function that disconnects the observer early.\n */\nexport function applyWithRetry(\n changes: VisualChange[],\n options: ApplyWithRetryOptions = {},\n): () => void {\n const { timeoutMs = 5000 } = options;\n const pending = new Set(changes);\n\n // Try applying immediately\n for (const change of pending) {\n if (tryApplyChange(change)) pending.delete(change);\n }\n if (pending.size === 0) return () => {};\n\n // Watch for new elements\n const observer = new MutationObserver(() => {\n for (const change of pending) {\n if (tryApplyChange(change)) pending.delete(change);\n }\n if (pending.size === 0) {\n observer.disconnect();\n }\n });\n\n observer.observe(document.body, { childList: true, subtree: true });\n\n // Safety timeout \u2014 don't watch forever\n const timer = setTimeout(() => observer.disconnect(), timeoutMs);\n\n return () => {\n clearTimeout(timer);\n observer.disconnect();\n };\n}\n", "/**\n * Anti-flicker helpers for the tracking SDK.\n *\n * Visual changes applied after page paint cause a flash of original content\n * (FOUC). The standard solution: a tiny inline snippet hides the page until\n * the SDK finishes applying changes, then this module removes the cloak.\n *\n * Usage (inline in <head>, before SDK):\n * <style>.sw-cloak { opacity: 0 !important; }</style>\n * <script>document.documentElement.classList.add('sw-cloak');</script>\n *\n * The SDK calls removeCloak() after applying visual changes.\n */\n\nconst CLOAK_CLASS = 'sw-cloak';\nconst SAFETY_TIMEOUT_MS = 3000;\n\n/**\n * Remove the anti-flicker cloak class from <html>.\n * Safe to call even if the cloak was never added.\n */\nexport function removeCloak(): void {\n if (typeof document !== 'undefined') {\n document.documentElement.classList.remove(CLOAK_CLASS);\n }\n}\n\n/**\n * Schedule automatic cloak removal after a safety timeout.\n * Prevents permanent blank pages if the SDK errors during init.\n * Returns a cancel function.\n */\nexport function scheduleCloakRemoval(timeoutMs: number = SAFETY_TIMEOUT_MS): () => void {\n const timer = setTimeout(removeCloak, timeoutMs);\n return () => clearTimeout(timer);\n}\n", "/**\n * Tracking SDK \u2014 Ultra-lightweight A/B testing tracker.\n *\n * Zero dependencies. ~3KB gzipped. Designed for:\n * - Direct <script> tag inclusion\n * - Google Tag Manager custom HTML tags\n * - ES module import in bundled apps\n *\n * Talks to API Gateway endpoints:\n * GET /v1/assign \u2014 sticky variant assignment\n * POST /v1/track \u2014 event tracking (impressions, conversions)\n */\n\nimport type { VisualChange } from '@splitwisp/visual-changes';\nimport { applyWithRetry, removeCloak, scheduleCloakRemoval } from '@splitwisp/visual-changes';\n\n// Re-export VisualChange so SDK consumers can reference it without a separate import\nexport type { VisualChange } from '@splitwisp/visual-changes';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TrackerConfig {\n /** Your project's API key (issued from the dashboard). */\n apiKey: string;\n /** Base URL of the API (e.g. https://api.example.com). No trailing slash. */\n endpoint: string;\n /** Override the auto-generated session ID. */\n sessionId?: string;\n /** Known user ID (e.g. from your auth system). */\n userId?: string;\n /** Timeout in ms for API calls. Default 5000. */\n timeout?: number;\n /** If true, suppress all console warnings. Default false. */\n quiet?: boolean;\n /**\n * If true, automatically apply visual changes from the assign response.\n * Default true. Set to false to handle visual changes manually.\n */\n applyVisualChanges?: boolean;\n /**\n * Max time (ms) to watch for late-loading DOM elements when applying\n * visual changes. Only relevant when applyVisualChanges is true.\n * Default 5000.\n */\n visualChangeTimeout?: number;\n}\n\nexport interface ConversionGoalConfig {\n /** page_visit: glob pattern for URL matching */\n urlPattern?: string;\n /** element_click: CSS selector for the clickable element */\n selector?: string;\n /** form_submit: CSS selector for the form element */\n formSelector?: string;\n /** scroll_depth: percentage threshold (0\u2013100) */\n threshold?: string;\n /** time_on_page: milliseconds before firing */\n durationMs?: string;\n}\n\nexport interface ConversionGoal {\n id: string;\n name: string;\n type: 'page_visit' | 'element_click' | 'form_submit' | 'scroll_depth' | 'time_on_page';\n config: ConversionGoalConfig;\n}\n\nexport interface Assignment {\n experimentId: string;\n variantId: string;\n variantName: string;\n changes?: VisualChange[];\n conversionGoals?: ConversionGoal[];\n}\n\nexport interface TrackOptions {\n /** Event name, e.g. \"conversion\", \"click\", \"signup\". */\n event: string;\n /** Experiment ID this event relates to (optional \u2014 server can infer). */\n experimentId?: string;\n /** Numeric value associated with the event (e.g. revenue in cents). */\n value?: number;\n /** Arbitrary metadata. */\n properties?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst STORAGE_KEY = '_sw_sid';\nconst QUEUE_KEY = '_sw_q';\nconst BLOCKED_KEY = '_sw_blocked';\nconst BLOCKED_TTL_MS = 3600_000; // 1 hour \u2014 matches server Cache-Control max-age\nconst UTM_KEY = '_sw_utm';\nconst GOAL_FIRED_KEY = '_sw_goals';\n\ninterface UtmParams {\n source?: string;\n medium?: string;\n campaign?: string;\n}\n\nfunction generateId(): string {\n // Compact random ID: 21 URL-safe chars \u2248 126 bits of entropy\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n // Convert to base36 and trim\n let id = '';\n for (let i = 0; i < bytes.length; i++) {\n id += bytes[i].toString(36);\n }\n return id.slice(0, 21);\n}\n\nfunction getOrCreateSessionId(): string {\n try {\n const existing = localStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const id = generateId();\n localStorage.setItem(STORAGE_KEY, id);\n return id;\n } catch {\n // localStorage blocked (e.g. Safari private) \u2014 fall back to in-memory\n return generateId();\n }\n}\n\nfunction warn(quiet: boolean, ...args: unknown[]): void {\n if (!quiet) {\n console.warn('[SplitWisp]', ...args);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Request helper \u2014 thin fetch wrapper with timeout + beacon fallback\n// ---------------------------------------------------------------------------\n\ninterface RequestResult<T> {\n data: T | null;\n status: number;\n}\n\nasync function request<T>(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<RequestResult<T>> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const res = await fetch(url, { ...init, signal: controller.signal });\n clearTimeout(timer);\n if (!res.ok) return { data: null, status: res.status };\n const text = await res.text();\n return { data: text ? (JSON.parse(text) as T) : (null as T), status: res.status };\n } catch {\n clearTimeout(timer);\n return { data: null, status: 0 };\n }\n}\n\nfunction sendBeacon(url: string, body: string): boolean {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n return navigator.sendBeacon(url, body);\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Offline queue \u2014 persist failed track calls and retry later\n// ---------------------------------------------------------------------------\n\ninterface QueuedEvent {\n url: string;\n body: string;\n ts: number;\n}\n\nfunction enqueue(event: QueuedEvent): void {\n try {\n const raw = localStorage.getItem(QUEUE_KEY);\n const queue: QueuedEvent[] = raw ? JSON.parse(raw) : [];\n queue.push(event);\n // Cap at 100 events to avoid storage bloat\n if (queue.length > 100) queue.shift();\n localStorage.setItem(QUEUE_KEY, JSON.stringify(queue));\n } catch {\n // localStorage unavailable \u2014 drop silently\n }\n}\n\nfunction drainQueue(headers: Record<string, string>, timeoutMs: number): void {\n try {\n const raw = localStorage.getItem(QUEUE_KEY);\n if (!raw) return;\n const queue: QueuedEvent[] = JSON.parse(raw);\n if (!queue.length) return;\n localStorage.removeItem(QUEUE_KEY);\n for (const item of queue) {\n request(item.url, {\n method: 'POST',\n headers,\n body: item.body,\n }, timeoutMs).catch(() => {\n // Re-enqueue on failure\n enqueue(item);\n });\n }\n } catch {\n // noop\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main SDK class\n// ---------------------------------------------------------------------------\n\nexport class Tracker {\n private cfg: Required<Pick<TrackerConfig, 'apiKey' | 'endpoint' | 'timeout' | 'quiet' | 'applyVisualChanges' | 'visualChangeTimeout'>> & {\n sessionId: string;\n userId?: string;\n };\n private cleanupVisualChanges: (() => void) | null = null;\n private cleanupGoalTracking: (() => void) | null = null;\n private assignments: Map<string, Assignment> = new Map();\n private initialized = false;\n private blocked = false;\n private utm: UtmParams = {};\n private firedGoals: Set<string> = new Set();\n\n constructor(config: TrackerConfig) {\n if (!config.apiKey) throw new Error('[Tracker] apiKey is required');\n if (!config.endpoint) throw new Error('[Tracker] endpoint is required');\n\n this.cfg = {\n apiKey: config.apiKey,\n endpoint: config.endpoint.replace(/\\/+$/, ''),\n sessionId: config.sessionId || getOrCreateSessionId(),\n userId: config.userId,\n timeout: config.timeout ?? 5000,\n quiet: config.quiet ?? false,\n applyVisualChanges: config.applyVisualChanges ?? true,\n visualChangeTimeout: config.visualChangeTimeout ?? 5000,\n };\n\n // Capture UTM params from URL (persists in sessionStorage for last-touch attribution)\n this.captureUtmParams();\n\n // Schedule safety cloak removal in case init() never completes\n if (this.cfg.applyVisualChanges) {\n scheduleCloakRemoval();\n }\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /** Session ID used for sticky assignment. */\n get sessionId(): string {\n return this.cfg.sessionId;\n }\n\n /** Identify a known user (call before or after init). */\n identify(userId: string): void {\n this.cfg.userId = userId;\n }\n\n /**\n * Fetch variant assignments for all active experiments for this session.\n * Call once on page load. Results are cached in-memory.\n */\n async init(): Promise<Assignment[]> {\n const params = new URLSearchParams({\n apiKey: this.cfg.apiKey,\n sessionId: this.cfg.sessionId,\n });\n if (this.cfg.userId) params.set('userId', this.cfg.userId);\n\n // Check if this API key was recently blocked (zombie defense \u2014 avoids wasting requests)\n if (this.isBlocked()) {\n this.blocked = true;\n warn(this.cfg.quiet, 'API key is blocked (cached) \u2014 skipping assign request.');\n this.initialized = true;\n if (this.cfg.applyVisualChanges) removeCloak();\n return this.getAssignments();\n }\n\n const url = `${this.cfg.endpoint}/v1/assign?${params}`;\n const { data, status } = await request<{ assignments: Assignment[] }>(\n url,\n { method: 'GET', headers: this.headers() },\n this.cfg.timeout,\n );\n\n if (status === 429) {\n // Server says we're blocked (quota exceeded or org suspended)\n // Cache the blocked state so we stop making requests\n this.blocked = true;\n this.setBlocked();\n warn(this.cfg.quiet, 'API returned 429 \u2014 suppressing further requests for this session.');\n } else if (data?.assignments) {\n for (const a of data.assignments) {\n this.assignments.set(a.experimentId, a);\n }\n } else {\n warn(this.cfg.quiet, 'Failed to fetch assignments \u2014 experiments will use defaults.');\n }\n\n this.initialized = true;\n\n // Auto-apply visual changes for all assigned variants\n if (this.cfg.applyVisualChanges) {\n const allChanges: VisualChange[] = [];\n for (const a of this.assignments.values()) {\n if (a.changes?.length) {\n allChanges.push(...a.changes);\n }\n }\n if (allChanges.length > 0) {\n this.cleanupVisualChanges = applyWithRetry(allChanges, {\n timeoutMs: this.cfg.visualChangeTimeout,\n });\n }\n removeCloak();\n }\n\n // Auto-setup conversion goal tracking\n this.setupGoalTracking();\n\n // Drain any events queued from previous page loads\n drainQueue(this.headers(), this.cfg.timeout);\n\n return this.getAssignments();\n }\n\n /** Get the variant for a specific experiment. Returns null if not assigned. */\n getVariant(experimentId: string): Assignment | null {\n return this.assignments.get(experimentId) ?? null;\n }\n\n /** Get all current assignments. */\n getAssignments(): Assignment[] {\n return Array.from(this.assignments.values());\n }\n\n /**\n * Track an event (impression, conversion, click, etc.).\n * Uses sendBeacon on page unload, fetch otherwise.\n * Failed calls are queued to localStorage and retried on next init().\n */\n async track(opts: TrackOptions): Promise<void> {\n if (!opts.event) {\n warn(this.cfg.quiet, 'track() called without an event name \u2014 ignoring.');\n return;\n }\n\n // Zombie defense: don't send track events if we know we're blocked\n if (this.blocked) return;\n\n const body: Record<string, unknown> = {\n apiKey: this.cfg.apiKey,\n sessionId: this.cfg.sessionId,\n event: opts.event,\n timestamp: new Date().toISOString(),\n url: typeof window !== 'undefined' ? window.location.href : undefined,\n };\n\n if (this.cfg.userId) body.userId = this.cfg.userId;\n if (opts.experimentId) body.experimentId = opts.experimentId;\n if (opts.value !== undefined) body.value = opts.value;\n if (opts.properties) body.properties = opts.properties;\n\n // Attach UTM params for source attribution\n if (this.utm.source) body.utmSource = this.utm.source;\n if (this.utm.medium) body.utmMedium = this.utm.medium;\n if (this.utm.campaign) body.utmCampaign = this.utm.campaign;\n\n // Attach current assignment if experimentId provided\n if (opts.experimentId) {\n const a = this.assignments.get(opts.experimentId);\n if (a) body.variantId = a.variantId;\n }\n\n const url = `${this.cfg.endpoint}/v1/track`;\n const payload = JSON.stringify(body);\n\n const { data, status } = await request(\n url,\n { method: 'POST', headers: this.headers(), body: payload },\n this.cfg.timeout,\n );\n\n if (status === 429) {\n // Blocked \u2014 stop sending events for the rest of this session\n this.blocked = true;\n this.setBlocked();\n } else if (data === null && status === 0) {\n // Network error \u2014 queue for retry\n enqueue({ url, body: payload, ts: Date.now() });\n }\n }\n\n /**\n * Track a conversion event \u2014 convenience wrapper around track().\n * @param experimentId - The experiment this conversion is for.\n * @param value - Optional numeric value (e.g. revenue in cents).\n * @param properties - Optional metadata.\n */\n async trackConversion(\n experimentId: string,\n value?: number,\n properties?: Record<string, unknown>,\n ): Promise<void> {\n return this.track({\n event: 'conversion',\n experimentId,\n value,\n properties,\n });\n }\n\n /**\n * Fire-and-forget track using sendBeacon. Ideal for pagehide / visibilitychange.\n * Does NOT retry on failure.\n */\n trackBeacon(opts: TrackOptions): void {\n const body: Record<string, unknown> = {\n apiKey: this.cfg.apiKey,\n sessionId: this.cfg.sessionId,\n event: opts.event,\n timestamp: new Date().toISOString(),\n url: typeof window !== 'undefined' ? window.location.href : undefined,\n };\n if (this.cfg.userId) body.userId = this.cfg.userId;\n if (opts.experimentId) body.experimentId = opts.experimentId;\n if (opts.value !== undefined) body.value = opts.value;\n if (opts.properties) body.properties = opts.properties;\n if (this.utm.source) body.utmSource = this.utm.source;\n if (this.utm.medium) body.utmMedium = this.utm.medium;\n if (this.utm.campaign) body.utmCampaign = this.utm.campaign;\n\n const url = `${this.cfg.endpoint}/v1/track`;\n sendBeacon(url, JSON.stringify(body));\n }\n\n /**\n * Auto-track page views. Sends a \"pageview\" event now, and on SPA navigations\n * (popstate + pushState/replaceState monkey-patches).\n * Call once after init().\n */\n autoTrackPageViews(): () => void {\n const onNav = () => {\n this.track({ event: 'pageview' });\n };\n\n // Track the current page immediately\n onNav();\n\n // SPA navigation: history changes\n window.addEventListener('popstate', onNav);\n\n // Monkey-patch pushState / replaceState for SPA routers\n const origPush = history.pushState.bind(history);\n const origReplace = history.replaceState.bind(history);\n\n history.pushState = function (...args) {\n origPush(...args);\n onNav();\n };\n\n history.replaceState = function (...args) {\n origReplace(...args);\n onNav();\n };\n\n // Track page hides via beacon\n const onHide = () => {\n this.trackBeacon({ event: 'pagehide' });\n };\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') onHide();\n });\n\n // Return teardown function\n return () => {\n window.removeEventListener('popstate', onNav);\n history.pushState = origPush;\n history.replaceState = origReplace;\n };\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n /** Whether this API key is currently suppressed. */\n get isSupressed(): boolean {\n return this.blocked;\n }\n\n private headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n 'X-API-Key': this.cfg.apiKey,\n };\n }\n\n private setBlocked(): void {\n try {\n sessionStorage.setItem(BLOCKED_KEY, String(Date.now() + BLOCKED_TTL_MS));\n } catch { /* noop */ }\n }\n\n private isBlocked(): boolean {\n try {\n const raw = sessionStorage.getItem(BLOCKED_KEY);\n if (!raw) return false;\n if (Date.now() < Number(raw)) return true;\n sessionStorage.removeItem(BLOCKED_KEY);\n return false;\n } catch {\n return false;\n }\n }\n\n /** Tear down goal tracking listeners (called on destroy or re-init). */\n private teardownGoalTracking(): void {\n if (this.cleanupGoalTracking) {\n this.cleanupGoalTracking();\n this.cleanupGoalTracking = null;\n }\n }\n\n /**\n * Set up automatic conversion tracking based on goals configured on experiments.\n * Supports 5 goal types: page_visit, element_click, form_submit, scroll_depth, time_on_page.\n * Each goal fires at most once per session per experiment (deduplication via localStorage).\n */\n private setupGoalTracking(): void {\n this.teardownGoalTracking();\n this.loadFiredGoals();\n\n const cleanups: (() => void)[] = [];\n\n for (const assignment of this.assignments.values()) {\n if (!assignment.conversionGoals?.length) continue;\n\n for (const goal of assignment.conversionGoals) {\n const dedupKey = `${assignment.experimentId}:${goal.id}`;\n if (this.firedGoals.has(dedupKey)) continue;\n\n const cleanup = this.attachGoalListener(\n assignment.experimentId,\n goal,\n dedupKey,\n );\n if (cleanup) cleanups.push(cleanup);\n }\n }\n\n if (cleanups.length > 0) {\n this.cleanupGoalTracking = () => {\n for (const fn of cleanups) fn();\n cleanups.length = 0;\n };\n }\n }\n\n private attachGoalListener(\n experimentId: string,\n goal: ConversionGoal,\n dedupKey: string,\n ): (() => void) | null {\n const fireOnce = () => {\n if (this.firedGoals.has(dedupKey)) return;\n this.firedGoals.add(dedupKey);\n this.saveFiredGoals();\n this.trackConversion(experimentId, undefined, {\n goalId: goal.id,\n goalName: goal.name,\n goalType: goal.type,\n });\n };\n\n switch (goal.type) {\n case 'page_visit': {\n const pattern = goal.config.urlPattern ?? '';\n if (!pattern) return null;\n // Check current URL immediately\n if (matchUrlPattern(pattern, window.location.href)) {\n fireOnce();\n return null;\n }\n // Also listen for SPA navigations\n const check = () => {\n if (matchUrlPattern(pattern, window.location.href)) fireOnce();\n };\n window.addEventListener('popstate', check);\n // Patch pushState/replaceState isn't needed here \u2014 autoTrackPageViews\n // already handles that, and SPA routers trigger popstate.\n return () => window.removeEventListener('popstate', check);\n }\n\n case 'element_click': {\n const selector = goal.config.selector ?? '';\n if (!selector) return null;\n const handler = (e: Event) => {\n const target = e.target as Element | null;\n if (target?.closest(selector)) fireOnce();\n };\n document.addEventListener('click', handler, { capture: true });\n return () => document.removeEventListener('click', handler, { capture: true });\n }\n\n case 'form_submit': {\n const formSelector = goal.config.formSelector ?? '';\n if (!formSelector) return null;\n const handler = (e: Event) => {\n const target = e.target as Element | null;\n if (target?.matches(formSelector)) fireOnce();\n };\n document.addEventListener('submit', handler, { capture: true });\n return () => document.removeEventListener('submit', handler, { capture: true });\n }\n\n case 'scroll_depth': {\n const threshold = parseInt(goal.config.threshold ?? '0', 10);\n if (threshold <= 0 || threshold > 100) return null;\n const handler = () => {\n const scrollPct =\n (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;\n if (scrollPct >= threshold) fireOnce();\n };\n window.addEventListener('scroll', handler, { passive: true });\n // Check immediately in case page is already scrolled\n handler();\n return () => window.removeEventListener('scroll', handler);\n }\n\n case 'time_on_page': {\n const durationMs = parseInt(goal.config.durationMs ?? '0', 10);\n if (durationMs <= 0) return null;\n const timerId = setTimeout(fireOnce, durationMs);\n return () => clearTimeout(timerId);\n }\n\n default:\n warn(this.cfg.quiet, `Unknown goal type: ${(goal as ConversionGoal).type}`);\n return null;\n }\n }\n\n private loadFiredGoals(): void {\n try {\n const raw = sessionStorage.getItem(GOAL_FIRED_KEY);\n if (raw) {\n const arr: string[] = JSON.parse(raw);\n this.firedGoals = new Set(arr);\n }\n } catch { /* noop */ }\n }\n\n private saveFiredGoals(): void {\n try {\n sessionStorage.setItem(GOAL_FIRED_KEY, JSON.stringify([...this.firedGoals]));\n } catch { /* noop */ }\n }\n\n private captureUtmParams(): void {\n try {\n // Restore previously captured UTM from sessionStorage (persists across SPA navigations)\n const stored = sessionStorage.getItem(UTM_KEY);\n if (stored) {\n this.utm = JSON.parse(stored);\n }\n\n // Override with current URL params if present (last-touch attribution)\n if (typeof window === 'undefined') return;\n const params = new URLSearchParams(window.location.search);\n const source = params.get('utm_source');\n const medium = params.get('utm_medium');\n const campaign = params.get('utm_campaign');\n\n if (source || medium || campaign) {\n if (source) this.utm.source = source;\n if (medium) this.utm.medium = medium;\n if (campaign) this.utm.campaign = campaign;\n sessionStorage.setItem(UTM_KEY, JSON.stringify(this.utm));\n }\n } catch { /* SSR or sessionStorage blocked */ }\n }\n}\n\n// ---------------------------------------------------------------------------\n// URL pattern matching for page_visit goals\n// ---------------------------------------------------------------------------\n\nfunction matchUrlPattern(pattern: string, url: string): boolean {\n try {\n // Support simple glob patterns: * matches any characters, ? matches single char\n // Pattern can be a path prefix (\"/checkout/*\") or full URL (\"https://example.com/thanks\")\n const isFullUrl = pattern.startsWith('http://') || pattern.startsWith('https://');\n const subject = isFullUrl ? url : new URL(url).pathname;\n const regex = new RegExp(\n '^' +\n pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex specials (except * and ?)\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.') +\n '$',\n );\n return regex.test(subject);\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Convenience factory (for <script> tag / GTM usage)\n// ---------------------------------------------------------------------------\n\n/**\n * Create and auto-initialize a Tracker instance.\n *\n * Usage from a <script> tag or GTM Custom HTML:\n *\n * <script src=\"https://cdn.splitwisp.com/sdk/v1/tracker.umd.js\"></script>\n * <script>\n * SplitWisp.init({\n * apiKey: 'pk_live_abc123',\n * endpoint: 'https://api.splitwisp.com',\n * }).then(function(t) {\n * var variant = t.getVariant('exp_hero_headline');\n * if (variant && variant.variantName === 'short') {\n * document.querySelector('h1').textContent = 'Ship faster.';\n * }\n * t.track({ event: 'impression', experimentId: 'exp_hero_headline' });\n * });\n * </script>\n */\nexport async function init(config: TrackerConfig): Promise<Tracker> {\n const instance = new Tracker(config);\n await instance.init();\n return instance;\n}\n\n// ---------------------------------------------------------------------------\n// SplitWisp namespace \u2014 mirrors the CDN global for consistent API across\n// script tag and npm/bundler usage.\n// ---------------------------------------------------------------------------\n\nexport const SplitWisp = { Tracker, init };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["setElementText", "element", "text", "setElementInnerHTML", "innerHTML", "setElementAttribute", "attribute", "value", "addClassesToElement", "classes", "className", "removeClassesFromElement", "setElementStyle", "setElementHref", "href", "setElementSrc", "src", "hideElement", "showElement", "setElementOuterHTML", "addGlobalCSS", "key", "head", "existing", "style", "parseCommaSeparatedList", "list", "item", "tryApplyChange", "change", "val", "applyWithRetry", "changes", "options", "timeoutMs", "pending", "change", "tryApplyChange", "observer", "timer", "CLOAK_CLASS", "removeCloak", "scheduleCloakRemoval", "STORAGE_KEY", "QUEUE_KEY", "BLOCKED_KEY", "BLOCKED_TTL_MS", "UTM_KEY", "GOAL_FIRED_KEY", "generateId", "bytes", "id", "i", "getOrCreateSessionId", "existing", "e", "warn", "quiet", "args", "request", "url", "init", "timeoutMs", "controller", "timer", "res", "__spreadProps", "__spreadValues", "text", "sendBeacon", "body", "enqueue", "event", "raw", "queue", "drainQueue", "headers", "item", "Tracker", "config", "_a", "_b", "_c", "_d", "H", "userId", "params", "C", "data", "status", "allChanges", "a", "M", "experimentId", "opts", "payload", "value", "properties", "onNav", "origPush", "origReplace", "onHide", "cleanups", "assignment", "goal", "dedupKey", "cleanup", "fn", "_e", "fireOnce", "pattern", "matchUrlPattern", "check", "selector", "handler", "target", "formSelector", "threshold", "durationMs", "timerId", "arr", "stored", "source", "medium", "campaign", "subject", "instance", "SplitWisp"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";var SplitWisp=(()=>{var g=Object.defineProperty,U=Object.defineProperties,N=Object.getOwnPropertyDescriptor,$=Object.getOwnPropertyDescriptors,F=Object.getOwnPropertyNames,S=Object.getOwnPropertySymbols;var C=Object.prototype.hasOwnProperty,J=Object.prototype.propertyIsEnumerable;var T=(s,e,t)=>e in s?g(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t,E=(s,e)=>{for(var t in e||(e={}))C.call(e,t)&&T(s,t,e[t]);if(S)for(var t of S(e))J.call(e,t)&&T(s,t,e[t]);return s},x=(s,e)=>U(s,$(e));var D=(s,e)=>{for(var t in e)g(s,t,{get:e[t],enumerable:!0})},z=(s,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of F(e))!C.call(s,i)&&i!==t&&g(s,i,{get:()=>e[i],enumerable:!(n=N(e,i))||n.enumerable});return s};var Q=s=>z(g({},"__esModule",{value:!0}),s);var ue={};D(ue,{SplitWisp:()=>ce,Tracker:()=>m,init:()=>H});function W(s,e){s.textContent=e}function Y(s,e){s.innerHTML=e}function p(s,e,t){s.setAttribute(e,t)}function j(s,e){for(let t of e)t&&s.classList.add(t)}function X(s,e){for(let t of e)t&&s.classList.remove(t)}function h(s,e){p(s,"style",e)}function Z(s,e){p(s,"href",e)}function ee(s,e){p(s,"src",e)}function L(s){s.style.display="none",s.style.visibility="hidden"}function G(s){s.style.display="block",s.style.visibility="visible"}function te(s,e){s.outerHTML=e}function se(s,e){let t=document.head;if(!t)return;let n=document.getElementById("sw-global-css-"+s);n&&n.remove();let i=document.createElement("style");i.innerText=e,i.id="sw-global-css-"+s,t.appendChild(i)}function _(s){return s?s.indexOf(",")===-1?[s.trim()]:s.split(",").map(e=>e.trim()).filter(Boolean):[]}function O(s){if(s.action==="addGlobalCSS")return se(s.selector,s.value),!0;let e=document.querySelector(s.selector);if(!e||!(e instanceof HTMLElement))return!1;switch(s.action){case"setText":W(e,s.value);break;case"setInnerHTML":Y(e,s.value);break;case"setAttribute":if(!s.attribute)return!1;p(e,s.attribute,s.value);break;case"addClasses":j(e,_(s.value));break;case"removeClasses":X(e,_(s.value));break;case"setHref":e instanceof HTMLAnchorElement&&Z(e,s.value);break;case"setSrc":e instanceof HTMLImageElement&&ee(e,s.value);break;case"setStyle":h(e,s.value);break;case"hide":L(e);break;case"show":G(e);break;case"setFontSize":h(e,`font-size: ${s.value}`);break;case"setFontColor":h(e,`color: ${s.value}`);break;case"setBackgroundColor":h(e,`background-color: ${s.value}`);break;case"setVisibility":{let t=s.value.toLowerCase();t==="hide"?L(e):t==="show"&&G(e);break}case"setOuterHTML":te(e,s.value);break;default:return!1}return!0}function P(s,e={}){let{timeoutMs:t=5e3}=e,n=new Set(s);for(let a of n)O(a)&&n.delete(a);if(n.size===0)return()=>{};let i=new MutationObserver(()=>{for(let a of n)O(a)&&n.delete(a);n.size===0&&i.disconnect()});i.observe(document.body,{childList:!0,subtree:!0});let r=setTimeout(()=>i.disconnect(),t);return()=>{clearTimeout(r),i.disconnect()}}var ne="sw-cloak";function v(){typeof document!="undefined"&&document.documentElement.classList.remove(ne)}function R(s=3e3){let e=setTimeout(v,s);return()=>clearTimeout(e)}var q="_sw_sid",y="_sw_q",w="_sw_blocked",ie=36e5,A="_sw_utm",M="_sw_goals";function K(){let s=new Uint8Array(16);crypto.getRandomValues(s);let e="";for(let t=0;t<s.length;t++)e+=s[t].toString(36);return e.slice(0,21)}function re(){try{let s=localStorage.getItem(q);if(s)return s;let e=K();return localStorage.setItem(q,e),e}catch(s){return K()}}function d(s,...e){s||console.warn("[SplitWisp]",...e)}async function k(s,e,t){let n=new AbortController,i=setTimeout(()=>n.abort(),t);try{let r=await fetch(s,x(E({},e),{signal:n.signal}));if(clearTimeout(i),!r.ok)return{data:null,status:r.status};let a=await r.text();return{data:a?JSON.parse(a):null,status:r.status}}catch(r){return clearTimeout(i),{data:null,status:0}}}function ae(s,e){return typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(s,e):!1}function B(s){try{let e=localStorage.getItem(y),t=e?JSON.parse(e):[];t.push(s),t.length>100&&t.shift(),localStorage.setItem(y,JSON.stringify(t))}catch(e){}}function oe(s,e){try{let t=localStorage.getItem(y);if(!t)return;let n=JSON.parse(t);if(!n.length)return;localStorage.removeItem(y);for(let i of n)k(i.url,{method:"POST",headers:s,body:i.body},e).catch(()=>{B(i)})}catch(t){}}var m=class{constructor(e){this.cleanupVisualChanges=null;this.cleanupGoalTracking=null;this.assignments=new Map;this.initialized=!1;this.blocked=!1;this.utm={};this.firedGoals=new Set;var t,n,i,r;if(!e.apiKey)throw new Error("[Tracker] apiKey is required");if(!e.endpoint)throw new Error("[Tracker] endpoint is required");this.cfg={apiKey:e.apiKey,endpoint:e.endpoint.replace(/\/+$/,""),sessionId:e.sessionId||re(),userId:e.userId,timeout:(t=e.timeout)!=null?t:5e3,quiet:(n=e.quiet)!=null?n:!1,applyVisualChanges:(i=e.applyVisualChanges)!=null?i:!0,visualChangeTimeout:(r=e.visualChangeTimeout)!=null?r:5e3},this.captureUtmParams(),this.cfg.applyVisualChanges&&R()}get sessionId(){return this.cfg.sessionId}identify(e){this.cfg.userId=e}async init(){var r;let e=new URLSearchParams({apiKey:this.cfg.apiKey,sessionId:this.cfg.sessionId});if(this.cfg.userId&&e.set("userId",this.cfg.userId),this.isBlocked())return this.blocked=!0,d(this.cfg.quiet,"API key is blocked (cached) \u2014 skipping assign request."),this.initialized=!0,this.cfg.applyVisualChanges&&v(),this.getAssignments();let t=`${this.cfg.endpoint}/v1/assign?${e}`,{data:n,status:i}=await k(t,{method:"GET",headers:this.headers()},this.cfg.timeout);if(i===429)this.blocked=!0,this.setBlocked(),d(this.cfg.quiet,"API returned 429 \u2014 suppressing further requests for this session.");else if(n!=null&&n.assignments)for(let a of n.assignments)this.assignments.set(a.experimentId,a);else d(this.cfg.quiet,"Failed to fetch assignments \u2014 experiments will use defaults.");if(this.initialized=!0,this.cfg.applyVisualChanges){let a=[];for(let u of this.assignments.values())(r=u.changes)!=null&&r.length&&a.push(...u.changes);a.length>0&&(this.cleanupVisualChanges=P(a,{timeoutMs:this.cfg.visualChangeTimeout})),v()}return this.setupGoalTracking(),oe(this.headers(),this.cfg.timeout),this.getAssignments()}getVariant(e){var t;return(t=this.assignments.get(e))!=null?t:null}getAssignments(){return Array.from(this.assignments.values())}async track(e){if(!e.event){d(this.cfg.quiet,"track() called without an event name \u2014 ignoring.");return}if(this.blocked)return;let t={apiKey:this.cfg.apiKey,sessionId:this.cfg.sessionId,event:e.event,timestamp:new Date().toISOString(),url:typeof window!="undefined"?window.location.href:void 0};if(this.cfg.userId&&(t.userId=this.cfg.userId),e.experimentId&&(t.experimentId=e.experimentId),e.value!==void 0&&(t.value=e.value),e.properties&&(t.properties=e.properties),this.utm.source&&(t.utmSource=this.utm.source),this.utm.medium&&(t.utmMedium=this.utm.medium),this.utm.campaign&&(t.utmCampaign=this.utm.campaign),e.experimentId){let u=this.assignments.get(e.experimentId);u&&(t.variantId=u.variantId)}let n=`${this.cfg.endpoint}/v1/track`,i=JSON.stringify(t),{data:r,status:a}=await k(n,{method:"POST",headers:this.headers(),body:i},this.cfg.timeout);a===429?(this.blocked=!0,this.setBlocked()):r===null&&a===0&&B({url:n,body:i,ts:Date.now()})}async trackConversion(e,t,n){return this.track({event:"conversion",experimentId:e,value:t,properties:n})}trackBeacon(e){let t={apiKey:this.cfg.apiKey,sessionId:this.cfg.sessionId,event:e.event,timestamp:new Date().toISOString(),url:typeof window!="undefined"?window.location.href:void 0};this.cfg.userId&&(t.userId=this.cfg.userId),e.experimentId&&(t.experimentId=e.experimentId),e.value!==void 0&&(t.value=e.value),e.properties&&(t.properties=e.properties),this.utm.source&&(t.utmSource=this.utm.source),this.utm.medium&&(t.utmMedium=this.utm.medium),this.utm.campaign&&(t.utmCampaign=this.utm.campaign);let n=`${this.cfg.endpoint}/v1/track`;ae(n,JSON.stringify(t))}autoTrackPageViews(){let e=()=>{this.track({event:"pageview"})};e(),window.addEventListener("popstate",e);let t=history.pushState.bind(history),n=history.replaceState.bind(history);history.pushState=function(...r){t(...r),e()},history.replaceState=function(...r){n(...r),e()};let i=()=>{this.trackBeacon({event:"pagehide"})};return document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&i()}),()=>{window.removeEventListener("popstate",e),history.pushState=t,history.replaceState=n}}get isSupressed(){return this.blocked}headers(){return{"Content-Type":"application/json","X-API-Key":this.cfg.apiKey}}setBlocked(){try{sessionStorage.setItem(w,String(Date.now()+ie))}catch(e){}}isBlocked(){try{let e=sessionStorage.getItem(w);return e?Date.now()<Number(e)?!0:(sessionStorage.removeItem(w),!1):!1}catch(e){return!1}}teardownGoalTracking(){this.cleanupGoalTracking&&(this.cleanupGoalTracking(),this.cleanupGoalTracking=null)}setupGoalTracking(){var t;this.teardownGoalTracking(),this.loadFiredGoals();let e=[];for(let n of this.assignments.values())if((t=n.conversionGoals)!=null&&t.length)for(let i of n.conversionGoals){let r=`${n.experimentId}:${i.id}`;if(this.firedGoals.has(r))continue;let a=this.attachGoalListener(n.experimentId,i,r);a&&e.push(a)}e.length>0&&(this.cleanupGoalTracking=()=>{for(let n of e)n();e.length=0})}attachGoalListener(e,t,n){var r,a,u,b,I;let i=()=>{this.firedGoals.has(n)||(this.firedGoals.add(n),this.saveFiredGoals(),this.trackConversion(e,void 0,{goalId:t.id,goalName:t.name,goalType:t.type}))};switch(t.type){case"page_visit":{let o=(r=t.config.urlPattern)!=null?r:"";if(!o)return null;if(V(o,window.location.href))return i(),null;let c=()=>{V(o,window.location.href)&&i()};return window.addEventListener("popstate",c),()=>window.removeEventListener("popstate",c)}case"element_click":{let o=(a=t.config.selector)!=null?a:"";if(!o)return null;let c=f=>{let l=f.target;l!=null&&l.closest(o)&&i()};return document.addEventListener("click",c,{capture:!0}),()=>document.removeEventListener("click",c,{capture:!0})}case"form_submit":{let o=(u=t.config.formSelector)!=null?u:"";if(!o)return null;let c=f=>{let l=f.target;l!=null&&l.matches(o)&&i()};return document.addEventListener("submit",c,{capture:!0}),()=>document.removeEventListener("submit",c,{capture:!0})}case"scroll_depth":{let o=parseInt((b=t.config.threshold)!=null?b:"0",10);if(o<=0||o>100)return null;let c=()=>{window.scrollY/(document.documentElement.scrollHeight-window.innerHeight)*100>=o&&i()};return window.addEventListener("scroll",c,{passive:!0}),c(),()=>window.removeEventListener("scroll",c)}case"time_on_page":{let o=parseInt((I=t.config.durationMs)!=null?I:"0",10);if(o<=0)return null;let c=setTimeout(i,o);return()=>clearTimeout(c)}default:return d(this.cfg.quiet,`Unknown goal type: ${t.type}`),null}}loadFiredGoals(){try{let e=sessionStorage.getItem(M);if(e){let t=JSON.parse(e);this.firedGoals=new Set(t)}}catch(e){}}saveFiredGoals(){try{sessionStorage.setItem(M,JSON.stringify([...this.firedGoals]))}catch(e){}}captureUtmParams(){try{let e=sessionStorage.getItem(A);if(e&&(this.utm=JSON.parse(e)),typeof window=="undefined")return;let t=new URLSearchParams(window.location.search),n=t.get("utm_source"),i=t.get("utm_medium"),r=t.get("utm_campaign");(n||i||r)&&(n&&(this.utm.source=n),i&&(this.utm.medium=i),r&&(this.utm.campaign=r),sessionStorage.setItem(A,JSON.stringify(this.utm)))}catch(e){}}};function V(s,e){try{let n=s.startsWith("http://")||s.startsWith("https://")?e:new URL(e).pathname;return new RegExp("^"+s.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".")+"$").test(n)}catch(t){return!1}}async function H(s){let e=new m(s);return await e.init(),e}var ce={Tracker:m,init:H};return Q(ue);})();
|
|
2
|
+
if(typeof window!=="undefined"){window.SplitWisp=SplitWisp;}
|
|
3
|
+
//# sourceMappingURL=tracker.umd.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/tracker.ts", "../../packages/visual-changes/src/mutators.ts", "../../packages/visual-changes/src/apply.ts", "../../packages/visual-changes/src/observer.ts", "../../packages/visual-changes/src/antiflicker.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Tracking SDK \u2014 Ultra-lightweight A/B testing tracker.\n *\n * Zero dependencies. ~3KB gzipped. Designed for:\n * - Direct <script> tag inclusion\n * - Google Tag Manager custom HTML tags\n * - ES module import in bundled apps\n *\n * Talks to API Gateway endpoints:\n * GET /v1/assign \u2014 sticky variant assignment\n * POST /v1/track \u2014 event tracking (impressions, conversions)\n */\n\nimport type { VisualChange } from '@splitwisp/visual-changes';\nimport { applyWithRetry, removeCloak, scheduleCloakRemoval } from '@splitwisp/visual-changes';\n\n// Re-export VisualChange so SDK consumers can reference it without a separate import\nexport type { VisualChange } from '@splitwisp/visual-changes';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TrackerConfig {\n /** Your project's API key (issued from the dashboard). */\n apiKey: string;\n /** Base URL of the API (e.g. https://api.example.com). No trailing slash. */\n endpoint: string;\n /** Override the auto-generated session ID. */\n sessionId?: string;\n /** Known user ID (e.g. from your auth system). */\n userId?: string;\n /** Timeout in ms for API calls. Default 5000. */\n timeout?: number;\n /** If true, suppress all console warnings. Default false. */\n quiet?: boolean;\n /**\n * If true, automatically apply visual changes from the assign response.\n * Default true. Set to false to handle visual changes manually.\n */\n applyVisualChanges?: boolean;\n /**\n * Max time (ms) to watch for late-loading DOM elements when applying\n * visual changes. Only relevant when applyVisualChanges is true.\n * Default 5000.\n */\n visualChangeTimeout?: number;\n}\n\nexport interface ConversionGoalConfig {\n /** page_visit: glob pattern for URL matching */\n urlPattern?: string;\n /** element_click: CSS selector for the clickable element */\n selector?: string;\n /** form_submit: CSS selector for the form element */\n formSelector?: string;\n /** scroll_depth: percentage threshold (0\u2013100) */\n threshold?: string;\n /** time_on_page: milliseconds before firing */\n durationMs?: string;\n}\n\nexport interface ConversionGoal {\n id: string;\n name: string;\n type: 'page_visit' | 'element_click' | 'form_submit' | 'scroll_depth' | 'time_on_page';\n config: ConversionGoalConfig;\n}\n\nexport interface Assignment {\n experimentId: string;\n variantId: string;\n variantName: string;\n changes?: VisualChange[];\n conversionGoals?: ConversionGoal[];\n}\n\nexport interface TrackOptions {\n /** Event name, e.g. \"conversion\", \"click\", \"signup\". */\n event: string;\n /** Experiment ID this event relates to (optional \u2014 server can infer). */\n experimentId?: string;\n /** Numeric value associated with the event (e.g. revenue in cents). */\n value?: number;\n /** Arbitrary metadata. */\n properties?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst STORAGE_KEY = '_sw_sid';\nconst QUEUE_KEY = '_sw_q';\nconst BLOCKED_KEY = '_sw_blocked';\nconst BLOCKED_TTL_MS = 3600_000; // 1 hour \u2014 matches server Cache-Control max-age\nconst UTM_KEY = '_sw_utm';\nconst GOAL_FIRED_KEY = '_sw_goals';\n\ninterface UtmParams {\n source?: string;\n medium?: string;\n campaign?: string;\n}\n\nfunction generateId(): string {\n // Compact random ID: 21 URL-safe chars \u2248 126 bits of entropy\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n // Convert to base36 and trim\n let id = '';\n for (let i = 0; i < bytes.length; i++) {\n id += bytes[i].toString(36);\n }\n return id.slice(0, 21);\n}\n\nfunction getOrCreateSessionId(): string {\n try {\n const existing = localStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const id = generateId();\n localStorage.setItem(STORAGE_KEY, id);\n return id;\n } catch {\n // localStorage blocked (e.g. Safari private) \u2014 fall back to in-memory\n return generateId();\n }\n}\n\nfunction warn(quiet: boolean, ...args: unknown[]): void {\n if (!quiet) {\n console.warn('[SplitWisp]', ...args);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Request helper \u2014 thin fetch wrapper with timeout + beacon fallback\n// ---------------------------------------------------------------------------\n\ninterface RequestResult<T> {\n data: T | null;\n status: number;\n}\n\nasync function request<T>(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<RequestResult<T>> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const res = await fetch(url, { ...init, signal: controller.signal });\n clearTimeout(timer);\n if (!res.ok) return { data: null, status: res.status };\n const text = await res.text();\n return { data: text ? (JSON.parse(text) as T) : (null as T), status: res.status };\n } catch {\n clearTimeout(timer);\n return { data: null, status: 0 };\n }\n}\n\nfunction sendBeacon(url: string, body: string): boolean {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n return navigator.sendBeacon(url, body);\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Offline queue \u2014 persist failed track calls and retry later\n// ---------------------------------------------------------------------------\n\ninterface QueuedEvent {\n url: string;\n body: string;\n ts: number;\n}\n\nfunction enqueue(event: QueuedEvent): void {\n try {\n const raw = localStorage.getItem(QUEUE_KEY);\n const queue: QueuedEvent[] = raw ? JSON.parse(raw) : [];\n queue.push(event);\n // Cap at 100 events to avoid storage bloat\n if (queue.length > 100) queue.shift();\n localStorage.setItem(QUEUE_KEY, JSON.stringify(queue));\n } catch {\n // localStorage unavailable \u2014 drop silently\n }\n}\n\nfunction drainQueue(headers: Record<string, string>, timeoutMs: number): void {\n try {\n const raw = localStorage.getItem(QUEUE_KEY);\n if (!raw) return;\n const queue: QueuedEvent[] = JSON.parse(raw);\n if (!queue.length) return;\n localStorage.removeItem(QUEUE_KEY);\n for (const item of queue) {\n request(item.url, {\n method: 'POST',\n headers,\n body: item.body,\n }, timeoutMs).catch(() => {\n // Re-enqueue on failure\n enqueue(item);\n });\n }\n } catch {\n // noop\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main SDK class\n// ---------------------------------------------------------------------------\n\nexport class Tracker {\n private cfg: Required<Pick<TrackerConfig, 'apiKey' | 'endpoint' | 'timeout' | 'quiet' | 'applyVisualChanges' | 'visualChangeTimeout'>> & {\n sessionId: string;\n userId?: string;\n };\n private cleanupVisualChanges: (() => void) | null = null;\n private cleanupGoalTracking: (() => void) | null = null;\n private assignments: Map<string, Assignment> = new Map();\n private initialized = false;\n private blocked = false;\n private utm: UtmParams = {};\n private firedGoals: Set<string> = new Set();\n\n constructor(config: TrackerConfig) {\n if (!config.apiKey) throw new Error('[Tracker] apiKey is required');\n if (!config.endpoint) throw new Error('[Tracker] endpoint is required');\n\n this.cfg = {\n apiKey: config.apiKey,\n endpoint: config.endpoint.replace(/\\/+$/, ''),\n sessionId: config.sessionId || getOrCreateSessionId(),\n userId: config.userId,\n timeout: config.timeout ?? 5000,\n quiet: config.quiet ?? false,\n applyVisualChanges: config.applyVisualChanges ?? true,\n visualChangeTimeout: config.visualChangeTimeout ?? 5000,\n };\n\n // Capture UTM params from URL (persists in sessionStorage for last-touch attribution)\n this.captureUtmParams();\n\n // Schedule safety cloak removal in case init() never completes\n if (this.cfg.applyVisualChanges) {\n scheduleCloakRemoval();\n }\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /** Session ID used for sticky assignment. */\n get sessionId(): string {\n return this.cfg.sessionId;\n }\n\n /** Identify a known user (call before or after init). */\n identify(userId: string): void {\n this.cfg.userId = userId;\n }\n\n /**\n * Fetch variant assignments for all active experiments for this session.\n * Call once on page load. Results are cached in-memory.\n */\n async init(): Promise<Assignment[]> {\n const params = new URLSearchParams({\n apiKey: this.cfg.apiKey,\n sessionId: this.cfg.sessionId,\n });\n if (this.cfg.userId) params.set('userId', this.cfg.userId);\n\n // Check if this API key was recently blocked (zombie defense \u2014 avoids wasting requests)\n if (this.isBlocked()) {\n this.blocked = true;\n warn(this.cfg.quiet, 'API key is blocked (cached) \u2014 skipping assign request.');\n this.initialized = true;\n if (this.cfg.applyVisualChanges) removeCloak();\n return this.getAssignments();\n }\n\n const url = `${this.cfg.endpoint}/v1/assign?${params}`;\n const { data, status } = await request<{ assignments: Assignment[] }>(\n url,\n { method: 'GET', headers: this.headers() },\n this.cfg.timeout,\n );\n\n if (status === 429) {\n // Server says we're blocked (quota exceeded or org suspended)\n // Cache the blocked state so we stop making requests\n this.blocked = true;\n this.setBlocked();\n warn(this.cfg.quiet, 'API returned 429 \u2014 suppressing further requests for this session.');\n } else if (data?.assignments) {\n for (const a of data.assignments) {\n this.assignments.set(a.experimentId, a);\n }\n } else {\n warn(this.cfg.quiet, 'Failed to fetch assignments \u2014 experiments will use defaults.');\n }\n\n this.initialized = true;\n\n // Auto-apply visual changes for all assigned variants\n if (this.cfg.applyVisualChanges) {\n const allChanges: VisualChange[] = [];\n for (const a of this.assignments.values()) {\n if (a.changes?.length) {\n allChanges.push(...a.changes);\n }\n }\n if (allChanges.length > 0) {\n this.cleanupVisualChanges = applyWithRetry(allChanges, {\n timeoutMs: this.cfg.visualChangeTimeout,\n });\n }\n removeCloak();\n }\n\n // Auto-setup conversion goal tracking\n this.setupGoalTracking();\n\n // Drain any events queued from previous page loads\n drainQueue(this.headers(), this.cfg.timeout);\n\n return this.getAssignments();\n }\n\n /** Get the variant for a specific experiment. Returns null if not assigned. */\n getVariant(experimentId: string): Assignment | null {\n return this.assignments.get(experimentId) ?? null;\n }\n\n /** Get all current assignments. */\n getAssignments(): Assignment[] {\n return Array.from(this.assignments.values());\n }\n\n /**\n * Track an event (impression, conversion, click, etc.).\n * Uses sendBeacon on page unload, fetch otherwise.\n * Failed calls are queued to localStorage and retried on next init().\n */\n async track(opts: TrackOptions): Promise<void> {\n if (!opts.event) {\n warn(this.cfg.quiet, 'track() called without an event name \u2014 ignoring.');\n return;\n }\n\n // Zombie defense: don't send track events if we know we're blocked\n if (this.blocked) return;\n\n const body: Record<string, unknown> = {\n apiKey: this.cfg.apiKey,\n sessionId: this.cfg.sessionId,\n event: opts.event,\n timestamp: new Date().toISOString(),\n url: typeof window !== 'undefined' ? window.location.href : undefined,\n };\n\n if (this.cfg.userId) body.userId = this.cfg.userId;\n if (opts.experimentId) body.experimentId = opts.experimentId;\n if (opts.value !== undefined) body.value = opts.value;\n if (opts.properties) body.properties = opts.properties;\n\n // Attach UTM params for source attribution\n if (this.utm.source) body.utmSource = this.utm.source;\n if (this.utm.medium) body.utmMedium = this.utm.medium;\n if (this.utm.campaign) body.utmCampaign = this.utm.campaign;\n\n // Attach current assignment if experimentId provided\n if (opts.experimentId) {\n const a = this.assignments.get(opts.experimentId);\n if (a) body.variantId = a.variantId;\n }\n\n const url = `${this.cfg.endpoint}/v1/track`;\n const payload = JSON.stringify(body);\n\n const { data, status } = await request(\n url,\n { method: 'POST', headers: this.headers(), body: payload },\n this.cfg.timeout,\n );\n\n if (status === 429) {\n // Blocked \u2014 stop sending events for the rest of this session\n this.blocked = true;\n this.setBlocked();\n } else if (data === null && status === 0) {\n // Network error \u2014 queue for retry\n enqueue({ url, body: payload, ts: Date.now() });\n }\n }\n\n /**\n * Track a conversion event \u2014 convenience wrapper around track().\n * @param experimentId - The experiment this conversion is for.\n * @param value - Optional numeric value (e.g. revenue in cents).\n * @param properties - Optional metadata.\n */\n async trackConversion(\n experimentId: string,\n value?: number,\n properties?: Record<string, unknown>,\n ): Promise<void> {\n return this.track({\n event: 'conversion',\n experimentId,\n value,\n properties,\n });\n }\n\n /**\n * Fire-and-forget track using sendBeacon. Ideal for pagehide / visibilitychange.\n * Does NOT retry on failure.\n */\n trackBeacon(opts: TrackOptions): void {\n const body: Record<string, unknown> = {\n apiKey: this.cfg.apiKey,\n sessionId: this.cfg.sessionId,\n event: opts.event,\n timestamp: new Date().toISOString(),\n url: typeof window !== 'undefined' ? window.location.href : undefined,\n };\n if (this.cfg.userId) body.userId = this.cfg.userId;\n if (opts.experimentId) body.experimentId = opts.experimentId;\n if (opts.value !== undefined) body.value = opts.value;\n if (opts.properties) body.properties = opts.properties;\n if (this.utm.source) body.utmSource = this.utm.source;\n if (this.utm.medium) body.utmMedium = this.utm.medium;\n if (this.utm.campaign) body.utmCampaign = this.utm.campaign;\n\n const url = `${this.cfg.endpoint}/v1/track`;\n sendBeacon(url, JSON.stringify(body));\n }\n\n /**\n * Auto-track page views. Sends a \"pageview\" event now, and on SPA navigations\n * (popstate + pushState/replaceState monkey-patches).\n * Call once after init().\n */\n autoTrackPageViews(): () => void {\n const onNav = () => {\n this.track({ event: 'pageview' });\n };\n\n // Track the current page immediately\n onNav();\n\n // SPA navigation: history changes\n window.addEventListener('popstate', onNav);\n\n // Monkey-patch pushState / replaceState for SPA routers\n const origPush = history.pushState.bind(history);\n const origReplace = history.replaceState.bind(history);\n\n history.pushState = function (...args) {\n origPush(...args);\n onNav();\n };\n\n history.replaceState = function (...args) {\n origReplace(...args);\n onNav();\n };\n\n // Track page hides via beacon\n const onHide = () => {\n this.trackBeacon({ event: 'pagehide' });\n };\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') onHide();\n });\n\n // Return teardown function\n return () => {\n window.removeEventListener('popstate', onNav);\n history.pushState = origPush;\n history.replaceState = origReplace;\n };\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n /** Whether this API key is currently suppressed. */\n get isSupressed(): boolean {\n return this.blocked;\n }\n\n private headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n 'X-API-Key': this.cfg.apiKey,\n };\n }\n\n private setBlocked(): void {\n try {\n sessionStorage.setItem(BLOCKED_KEY, String(Date.now() + BLOCKED_TTL_MS));\n } catch { /* noop */ }\n }\n\n private isBlocked(): boolean {\n try {\n const raw = sessionStorage.getItem(BLOCKED_KEY);\n if (!raw) return false;\n if (Date.now() < Number(raw)) return true;\n sessionStorage.removeItem(BLOCKED_KEY);\n return false;\n } catch {\n return false;\n }\n }\n\n /** Tear down goal tracking listeners (called on destroy or re-init). */\n private teardownGoalTracking(): void {\n if (this.cleanupGoalTracking) {\n this.cleanupGoalTracking();\n this.cleanupGoalTracking = null;\n }\n }\n\n /**\n * Set up automatic conversion tracking based on goals configured on experiments.\n * Supports 5 goal types: page_visit, element_click, form_submit, scroll_depth, time_on_page.\n * Each goal fires at most once per session per experiment (deduplication via localStorage).\n */\n private setupGoalTracking(): void {\n this.teardownGoalTracking();\n this.loadFiredGoals();\n\n const cleanups: (() => void)[] = [];\n\n for (const assignment of this.assignments.values()) {\n if (!assignment.conversionGoals?.length) continue;\n\n for (const goal of assignment.conversionGoals) {\n const dedupKey = `${assignment.experimentId}:${goal.id}`;\n if (this.firedGoals.has(dedupKey)) continue;\n\n const cleanup = this.attachGoalListener(\n assignment.experimentId,\n goal,\n dedupKey,\n );\n if (cleanup) cleanups.push(cleanup);\n }\n }\n\n if (cleanups.length > 0) {\n this.cleanupGoalTracking = () => {\n for (const fn of cleanups) fn();\n cleanups.length = 0;\n };\n }\n }\n\n private attachGoalListener(\n experimentId: string,\n goal: ConversionGoal,\n dedupKey: string,\n ): (() => void) | null {\n const fireOnce = () => {\n if (this.firedGoals.has(dedupKey)) return;\n this.firedGoals.add(dedupKey);\n this.saveFiredGoals();\n this.trackConversion(experimentId, undefined, {\n goalId: goal.id,\n goalName: goal.name,\n goalType: goal.type,\n });\n };\n\n switch (goal.type) {\n case 'page_visit': {\n const pattern = goal.config.urlPattern ?? '';\n if (!pattern) return null;\n // Check current URL immediately\n if (matchUrlPattern(pattern, window.location.href)) {\n fireOnce();\n return null;\n }\n // Also listen for SPA navigations\n const check = () => {\n if (matchUrlPattern(pattern, window.location.href)) fireOnce();\n };\n window.addEventListener('popstate', check);\n // Patch pushState/replaceState isn't needed here \u2014 autoTrackPageViews\n // already handles that, and SPA routers trigger popstate.\n return () => window.removeEventListener('popstate', check);\n }\n\n case 'element_click': {\n const selector = goal.config.selector ?? '';\n if (!selector) return null;\n const handler = (e: Event) => {\n const target = e.target as Element | null;\n if (target?.closest(selector)) fireOnce();\n };\n document.addEventListener('click', handler, { capture: true });\n return () => document.removeEventListener('click', handler, { capture: true });\n }\n\n case 'form_submit': {\n const formSelector = goal.config.formSelector ?? '';\n if (!formSelector) return null;\n const handler = (e: Event) => {\n const target = e.target as Element | null;\n if (target?.matches(formSelector)) fireOnce();\n };\n document.addEventListener('submit', handler, { capture: true });\n return () => document.removeEventListener('submit', handler, { capture: true });\n }\n\n case 'scroll_depth': {\n const threshold = parseInt(goal.config.threshold ?? '0', 10);\n if (threshold <= 0 || threshold > 100) return null;\n const handler = () => {\n const scrollPct =\n (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;\n if (scrollPct >= threshold) fireOnce();\n };\n window.addEventListener('scroll', handler, { passive: true });\n // Check immediately in case page is already scrolled\n handler();\n return () => window.removeEventListener('scroll', handler);\n }\n\n case 'time_on_page': {\n const durationMs = parseInt(goal.config.durationMs ?? '0', 10);\n if (durationMs <= 0) return null;\n const timerId = setTimeout(fireOnce, durationMs);\n return () => clearTimeout(timerId);\n }\n\n default:\n warn(this.cfg.quiet, `Unknown goal type: ${(goal as ConversionGoal).type}`);\n return null;\n }\n }\n\n private loadFiredGoals(): void {\n try {\n const raw = sessionStorage.getItem(GOAL_FIRED_KEY);\n if (raw) {\n const arr: string[] = JSON.parse(raw);\n this.firedGoals = new Set(arr);\n }\n } catch { /* noop */ }\n }\n\n private saveFiredGoals(): void {\n try {\n sessionStorage.setItem(GOAL_FIRED_KEY, JSON.stringify([...this.firedGoals]));\n } catch { /* noop */ }\n }\n\n private captureUtmParams(): void {\n try {\n // Restore previously captured UTM from sessionStorage (persists across SPA navigations)\n const stored = sessionStorage.getItem(UTM_KEY);\n if (stored) {\n this.utm = JSON.parse(stored);\n }\n\n // Override with current URL params if present (last-touch attribution)\n if (typeof window === 'undefined') return;\n const params = new URLSearchParams(window.location.search);\n const source = params.get('utm_source');\n const medium = params.get('utm_medium');\n const campaign = params.get('utm_campaign');\n\n if (source || medium || campaign) {\n if (source) this.utm.source = source;\n if (medium) this.utm.medium = medium;\n if (campaign) this.utm.campaign = campaign;\n sessionStorage.setItem(UTM_KEY, JSON.stringify(this.utm));\n }\n } catch { /* SSR or sessionStorage blocked */ }\n }\n}\n\n// ---------------------------------------------------------------------------\n// URL pattern matching for page_visit goals\n// ---------------------------------------------------------------------------\n\nfunction matchUrlPattern(pattern: string, url: string): boolean {\n try {\n // Support simple glob patterns: * matches any characters, ? matches single char\n // Pattern can be a path prefix (\"/checkout/*\") or full URL (\"https://example.com/thanks\")\n const isFullUrl = pattern.startsWith('http://') || pattern.startsWith('https://');\n const subject = isFullUrl ? url : new URL(url).pathname;\n const regex = new RegExp(\n '^' +\n pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex specials (except * and ?)\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.') +\n '$',\n );\n return regex.test(subject);\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Convenience factory (for <script> tag / GTM usage)\n// ---------------------------------------------------------------------------\n\n/**\n * Create and auto-initialize a Tracker instance.\n *\n * Usage from a <script> tag or GTM Custom HTML:\n *\n * <script src=\"https://cdn.splitwisp.com/sdk/v1/tracker.umd.js\"></script>\n * <script>\n * SplitWisp.init({\n * apiKey: 'pk_live_abc123',\n * endpoint: 'https://api.splitwisp.com',\n * }).then(function(t) {\n * var variant = t.getVariant('exp_hero_headline');\n * if (variant && variant.variantName === 'short') {\n * document.querySelector('h1').textContent = 'Ship faster.';\n * }\n * t.track({ event: 'impression', experimentId: 'exp_hero_headline' });\n * });\n * </script>\n */\nexport async function init(config: TrackerConfig): Promise<Tracker> {\n const instance = new Tracker(config);\n await instance.init();\n return instance;\n}\n\n// ---------------------------------------------------------------------------\n// SplitWisp namespace \u2014 mirrors the CDN global for consistent API across\n// script tag and npm/bundler usage.\n// ---------------------------------------------------------------------------\n\nexport const SplitWisp = { Tracker, init };\n", "/**\n * Individual DOM mutator functions.\n *\n * Pure DOM API calls \u2014 zero dependencies. Each function takes an element\n * and a value, and performs a single mutation. These are the building blocks\n * used by applyVisualChange().\n */\n\nexport function setElementText(element: Element, text: string): void {\n element.textContent = text;\n}\n\nexport function setElementInnerHTML(element: Element, innerHTML: string): void {\n element.innerHTML = innerHTML;\n}\n\nexport function setElementAttribute(\n element: HTMLElement,\n attribute: string,\n value: string,\n): void {\n element.setAttribute(attribute, value);\n}\n\nexport function addClassesToElement(element: HTMLElement, classes: string[]): void {\n for (const className of classes) {\n if (className) element.classList.add(className);\n }\n}\n\nexport function removeClassesFromElement(element: HTMLElement, classes: string[]): void {\n for (const className of classes) {\n if (className) element.classList.remove(className);\n }\n}\n\nexport function setElementStyle(element: HTMLElement, value: string): void {\n setElementAttribute(element, 'style', value);\n}\n\nexport function setElementHref(element: HTMLAnchorElement, href: string): void {\n setElementAttribute(element, 'href', href);\n}\n\nexport function setElementSrc(element: HTMLImageElement, src: string): void {\n setElementAttribute(element, 'src', src);\n}\n\nexport function hideElement(element: HTMLElement): void {\n element.style.display = 'none';\n element.style.visibility = 'hidden';\n}\n\nexport function showElement(element: HTMLElement): void {\n element.style.display = 'block';\n element.style.visibility = 'visible';\n}\n\nexport function setElementOuterHTML(element: HTMLElement, value: string): void {\n element.outerHTML = value;\n}\n\nexport function addGlobalCSS(key: string, value: string): void {\n const head = document.head;\n if (!head) return;\n // Remove existing style with this key to avoid duplicates\n const existing = document.getElementById('sw-global-css-' + key);\n if (existing) existing.remove();\n const style = document.createElement('style');\n style.innerText = value;\n style.id = 'sw-global-css-' + key;\n head.appendChild(style);\n}\n\n/** Parse a comma-separated string into a trimmed array. */\nexport function parseCommaSeparatedList(list: string): string[] {\n if (!list) return [];\n if (list.indexOf(',') === -1) return [list.trim()];\n return list.split(',').map((item) => item.trim()).filter(Boolean);\n}\n", "/**\n * Apply visual changes to the DOM.\n *\n * This is the core engine used by both:\n * - The tracking SDK (production \u2014 applies changes from /v1/assign response)\n * - The visual editor (preview \u2014 applies changes during authoring)\n *\n * Zero dependencies. Pure DOM APIs.\n */\n\nimport type { VisualChange } from './types';\nimport {\n setElementText,\n setElementInnerHTML,\n setElementAttribute,\n addClassesToElement,\n removeClassesFromElement,\n setElementStyle,\n setElementHref,\n setElementSrc,\n hideElement,\n showElement,\n setElementOuterHTML,\n addGlobalCSS,\n parseCommaSeparatedList,\n} from './mutators';\n\n/**\n * Try to apply a single visual change to the DOM.\n * Returns true if the target element was found and the change was applied.\n */\nexport function tryApplyChange(change: VisualChange): boolean {\n // addGlobalCSS doesn't target a specific element\n if (change.action === 'addGlobalCSS') {\n addGlobalCSS(change.selector, change.value);\n return true;\n }\n\n const element = document.querySelector(change.selector);\n if (!element || !(element instanceof HTMLElement)) {\n return false;\n }\n\n switch (change.action) {\n case 'setText':\n setElementText(element, change.value);\n break;\n case 'setInnerHTML':\n setElementInnerHTML(element, change.value);\n break;\n case 'setAttribute':\n if (!change.attribute) return false;\n setElementAttribute(element, change.attribute, change.value);\n break;\n case 'addClasses':\n addClassesToElement(element, parseCommaSeparatedList(change.value));\n break;\n case 'removeClasses':\n removeClassesFromElement(element, parseCommaSeparatedList(change.value));\n break;\n case 'setHref':\n if (element instanceof HTMLAnchorElement) {\n setElementHref(element, change.value);\n }\n break;\n case 'setSrc':\n if (element instanceof HTMLImageElement) {\n setElementSrc(element, change.value);\n }\n break;\n case 'setStyle':\n setElementStyle(element, change.value);\n break;\n case 'hide':\n hideElement(element);\n break;\n case 'show':\n showElement(element);\n break;\n case 'setFontSize':\n setElementStyle(element, `font-size: ${change.value}`);\n break;\n case 'setFontColor':\n setElementStyle(element, `color: ${change.value}`);\n break;\n case 'setBackgroundColor':\n setElementStyle(element, `background-color: ${change.value}`);\n break;\n case 'setVisibility': {\n const val = change.value.toLowerCase();\n if (val === 'hide') hideElement(element);\n else if (val === 'show') showElement(element);\n break;\n }\n case 'setOuterHTML':\n setElementOuterHTML(element, change.value);\n break;\n default:\n return false;\n }\n\n return true;\n}\n\n/**\n * Apply an array of visual changes to the DOM immediately.\n * Changes whose target elements are not found are silently skipped.\n */\nexport function applyVisualChanges(changes: VisualChange[]): void {\n for (const change of changes) {\n tryApplyChange(change);\n }\n}\n", "/**\n * MutationObserver-based retry logic for applying visual changes to\n * late-loading elements (SPAs, lazy-loaded content).\n *\n * Used by the tracking SDK to handle elements that don't exist at init time.\n */\n\nimport type { VisualChange } from './types';\nimport { tryApplyChange } from './apply';\n\nexport interface ApplyWithRetryOptions {\n /** Max time (ms) to watch for new elements. Default 5000. */\n timeoutMs?: number;\n}\n\n/**\n * Apply visual changes immediately where possible, then watch the DOM\n * for late-appearing elements. Automatically disconnects the observer\n * once all changes are applied or the timeout expires.\n *\n * Returns a cleanup function that disconnects the observer early.\n */\nexport function applyWithRetry(\n changes: VisualChange[],\n options: ApplyWithRetryOptions = {},\n): () => void {\n const { timeoutMs = 5000 } = options;\n const pending = new Set(changes);\n\n // Try applying immediately\n for (const change of pending) {\n if (tryApplyChange(change)) pending.delete(change);\n }\n if (pending.size === 0) return () => {};\n\n // Watch for new elements\n const observer = new MutationObserver(() => {\n for (const change of pending) {\n if (tryApplyChange(change)) pending.delete(change);\n }\n if (pending.size === 0) {\n observer.disconnect();\n }\n });\n\n observer.observe(document.body, { childList: true, subtree: true });\n\n // Safety timeout \u2014 don't watch forever\n const timer = setTimeout(() => observer.disconnect(), timeoutMs);\n\n return () => {\n clearTimeout(timer);\n observer.disconnect();\n };\n}\n", "/**\n * Anti-flicker helpers for the tracking SDK.\n *\n * Visual changes applied after page paint cause a flash of original content\n * (FOUC). The standard solution: a tiny inline snippet hides the page until\n * the SDK finishes applying changes, then this module removes the cloak.\n *\n * Usage (inline in <head>, before SDK):\n * <style>.sw-cloak { opacity: 0 !important; }</style>\n * <script>document.documentElement.classList.add('sw-cloak');</script>\n *\n * The SDK calls removeCloak() after applying visual changes.\n */\n\nconst CLOAK_CLASS = 'sw-cloak';\nconst SAFETY_TIMEOUT_MS = 3000;\n\n/**\n * Remove the anti-flicker cloak class from <html>.\n * Safe to call even if the cloak was never added.\n */\nexport function removeCloak(): void {\n if (typeof document !== 'undefined') {\n document.documentElement.classList.remove(CLOAK_CLASS);\n }\n}\n\n/**\n * Schedule automatic cloak removal after a safety timeout.\n * Prevents permanent blank pages if the SDK errors during init.\n * Returns a cancel function.\n */\nexport function scheduleCloakRemoval(timeoutMs: number = SAFETY_TIMEOUT_MS): () => void {\n const timer = setTimeout(removeCloak, timeoutMs);\n return () => clearTimeout(timer);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["tracker_exports", "__export", "SplitWisp", "Tracker", "init", "setElementText", "element", "text", "setElementInnerHTML", "innerHTML", "setElementAttribute", "attribute", "value", "addClassesToElement", "classes", "className", "removeClassesFromElement", "setElementStyle", "setElementHref", "href", "setElementSrc", "src", "hideElement", "showElement", "setElementOuterHTML", "addGlobalCSS", "key", "head", "existing", "style", "parseCommaSeparatedList", "list", "item", "tryApplyChange", "change", "val", "applyWithRetry", "changes", "options", "timeoutMs", "pending", "change", "tryApplyChange", "observer", "timer", "CLOAK_CLASS", "removeCloak", "scheduleCloakRemoval", "STORAGE_KEY", "QUEUE_KEY", "BLOCKED_KEY", "BLOCKED_TTL_MS", "UTM_KEY", "GOAL_FIRED_KEY", "generateId", "bytes", "id", "i", "getOrCreateSessionId", "existing", "e", "warn", "quiet", "args", "request", "url", "init", "timeoutMs", "controller", "timer", "res", "__spreadProps", "__spreadValues", "text", "sendBeacon", "body", "enqueue", "event", "raw", "queue", "drainQueue", "headers", "item", "Tracker", "config", "_a", "_b", "_c", "_d", "H", "userId", "params", "C", "data", "status", "allChanges", "a", "M", "experimentId", "opts", "payload", "value", "properties", "onNav", "origPush", "origReplace", "onHide", "cleanups", "assignment", "goal", "dedupKey", "cleanup", "fn", "_e", "fireOnce", "pattern", "matchUrlPattern", "check", "selector", "handler", "target", "formSelector", "threshold", "durationMs", "timerId", "arr", "stored", "source", "medium", "campaign", "subject", "instance", "SplitWisp"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@splitwisp/tracker",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Ultra-lightweight A/B testing SDK (~3KB gzipped). Zero dependencies. Script tag, npm, and GTM installation options.",
|
|
5
|
+
"main": "dist/tracker.umd.js",
|
|
6
|
+
"module": "dist/tracker.esm.js",
|
|
7
|
+
"types": "dist/tracker.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "node build.mjs",
|
|
13
|
+
"build:watch": "node build.mjs --watch",
|
|
14
|
+
"test": "node --test src/__tests__/tracker.test.mjs",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"size": "echo 'Checking gzip size...' && gzip -c dist/tracker.umd.js | wc -c"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"keywords": [
|
|
23
|
+
"ab-testing",
|
|
24
|
+
"experimentation",
|
|
25
|
+
"tracking",
|
|
26
|
+
"analytics",
|
|
27
|
+
"lightweight"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@splitwisp/visual-changes": "file:../packages/visual-changes"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@commitlint/cli": "^19.0.0",
|
|
34
|
+
"@commitlint/config-conventional": "^19.0.0",
|
|
35
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
36
|
+
"@semantic-release/git": "^10.0.1",
|
|
37
|
+
"esbuild": "^0.20.0",
|
|
38
|
+
"semantic-release": "^23.0.0",
|
|
39
|
+
"typescript": "^5.3.3"
|
|
40
|
+
}
|
|
41
|
+
}
|