@faststats/web 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -0
- package/dist/chunks/{api-urls-Dp2XNjRF.js → api-urls-DWk43fu6.js} +12 -5
- package/dist/chunks/extensions-DW7tJY0T.d.ts +51 -0
- package/dist/chunks/{identifiers-BvRNbiwA.js → identifiers-KgQDX74Q.js} +3 -3
- package/dist/chunks/{send-data-DR1JUXka.js → send-data-DFZnsaL2.js} +63 -45
- package/dist/error.d.ts +7 -2
- package/dist/error.js +36 -14
- package/dist/index.d.ts +54 -72
- package/dist/index.js +341 -363
- package/dist/outbound-links.d.ts +10 -0
- package/dist/outbound-links.js +62 -0
- package/dist/replay.d.ts +130 -2
- package/dist/replay.js +81 -32
- package/dist/web-vitals.d.ts +7 -1
- package/dist/web-vitals.js +39 -12
- package/package.json +11 -12
- package/dist/chunks/replay-BeC2XA4N.d.ts +0 -124
- package/dist/feature-flags.d.ts +0 -18
- package/dist/feature-flags.js +0 -32
package/dist/index.js
CHANGED
|
@@ -1,92 +1,108 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
3
|
-
import { n as resetAnonymousId, t as getAnonymousId } from "./chunks/identifiers-
|
|
4
|
-
//#region src/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import { a as sanitizeUrl, i as resolveBaseUrl, n as URLS, r as getPageContext, t as ANALYTICS_BASE } from "./chunks/api-urls-DWk43fu6.js";
|
|
2
|
+
import { a as resetSession, o as touchActivity, r as getSessionContext, t as sendData } from "./chunks/send-data-DFZnsaL2.js";
|
|
3
|
+
import { n as resetAnonymousId, t as getAnonymousId } from "./chunks/identifiers-KgQDX74Q.js";
|
|
4
|
+
//#region src/extensions.ts
|
|
5
|
+
var ExtensionHost = class {
|
|
6
|
+
runtime;
|
|
7
|
+
handlers = /* @__PURE__ */ new Map();
|
|
8
|
+
cleanups = [];
|
|
9
|
+
stopped = false;
|
|
10
|
+
constructor(extensions, runtime) {
|
|
11
|
+
this.runtime = runtime;
|
|
12
|
+
for (const extension of extensions) this.setup(extension);
|
|
13
|
+
}
|
|
14
|
+
emit(hook, event) {
|
|
15
|
+
if (this.stopped) return;
|
|
16
|
+
for (const handler of this.handlers.get(hook) ?? []) try {
|
|
17
|
+
handler(event)?.catch((error) => this.runtime.log(`${hook} failed`, error));
|
|
18
|
+
} catch (error) {
|
|
19
|
+
this.runtime.log(`${hook} failed`, error);
|
|
20
|
+
}
|
|
13
21
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
this.
|
|
24
|
-
|
|
22
|
+
stop(discard = false) {
|
|
23
|
+
if (this.stopped) return;
|
|
24
|
+
this.stopped = true;
|
|
25
|
+
for (const cleanup of this.cleanups.reverse()) try {
|
|
26
|
+
cleanup({ discard });
|
|
27
|
+
} catch (error) {
|
|
28
|
+
this.runtime.log("cleanup failed", error);
|
|
29
|
+
}
|
|
30
|
+
this.cleanups.length = 0;
|
|
31
|
+
this.handlers.clear();
|
|
32
|
+
}
|
|
33
|
+
async setup(extension) {
|
|
34
|
+
if (this.stopped) return;
|
|
35
|
+
const subscriptions = [];
|
|
36
|
+
try {
|
|
37
|
+
const cleanup = await extension.setup({
|
|
38
|
+
...this.runtime,
|
|
39
|
+
on: (hook, handler) => {
|
|
40
|
+
if (this.stopped) return () => {};
|
|
41
|
+
let handlers = this.handlers.get(hook);
|
|
42
|
+
if (!handlers) {
|
|
43
|
+
handlers = /* @__PURE__ */ new Set();
|
|
44
|
+
this.handlers.set(hook, handlers);
|
|
45
|
+
}
|
|
46
|
+
handlers.add(handler);
|
|
47
|
+
const off = () => handlers.delete(handler);
|
|
48
|
+
subscriptions.push(off);
|
|
49
|
+
return off;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
if (this.stopped) cleanup?.({ discard: true });
|
|
53
|
+
else if (cleanup) this.cleanups.push(cleanup);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
for (const off of subscriptions) off();
|
|
56
|
+
this.runtime.log(`${extension.name} setup failed`, error);
|
|
57
|
+
}
|
|
25
58
|
}
|
|
26
59
|
};
|
|
27
|
-
function isEligibleActivation(event) {
|
|
28
|
-
if (event.defaultPrevented) return false;
|
|
29
|
-
if (event.type === "click") return event.button === 0;
|
|
30
|
-
if (event.type === "auxclick") return event.button === 1;
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
function findOutboundAnchor(event) {
|
|
34
|
-
for (const node of event.composedPath()) if (isOutboundAnchor(node)) return node;
|
|
35
|
-
let node = event.target;
|
|
36
|
-
while (node) {
|
|
37
|
-
if (isOutboundAnchor(node)) return node;
|
|
38
|
-
node = node.parentNode;
|
|
39
|
-
}
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
function isOutboundAnchor(node) {
|
|
43
|
-
return node instanceof HTMLAnchorElement && (node.protocol === "http:" || node.protocol === "https:") && node.host !== location.host;
|
|
44
|
-
}
|
|
45
60
|
//#endregion
|
|
46
61
|
//#region src/analytics.ts
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
function
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (instance) instance.setConsentMode(mode);
|
|
67
|
-
else pendingConsentMode = mode;
|
|
68
|
-
}
|
|
69
|
-
function optIn() {
|
|
70
|
-
setConsentMode("granted");
|
|
71
|
-
}
|
|
72
|
-
function optOut() {
|
|
73
|
-
setConsentMode("denied");
|
|
74
|
-
}
|
|
75
|
-
function reportError(error) {
|
|
76
|
-
activeInstance()?.reportError(error);
|
|
62
|
+
const navigationListeners = /* @__PURE__ */ new Set();
|
|
63
|
+
const historyPatches = {};
|
|
64
|
+
function patchHistory(method) {
|
|
65
|
+
if (historyPatches[method]?.owner === history) return;
|
|
66
|
+
const original = history[method];
|
|
67
|
+
const wrapped = function(data, unused, url) {
|
|
68
|
+
Reflect.apply(original, this, [
|
|
69
|
+
data,
|
|
70
|
+
unused,
|
|
71
|
+
url
|
|
72
|
+
]);
|
|
73
|
+
for (const listener of navigationListeners) listener();
|
|
74
|
+
};
|
|
75
|
+
history[method] = wrapped;
|
|
76
|
+
historyPatches[method] = {
|
|
77
|
+
owner: history,
|
|
78
|
+
original,
|
|
79
|
+
wrapped
|
|
80
|
+
};
|
|
77
81
|
}
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
function observeHistory(listener) {
|
|
83
|
+
navigationListeners.add(listener);
|
|
84
|
+
patchHistory("pushState");
|
|
85
|
+
patchHistory("replaceState");
|
|
86
|
+
return () => {
|
|
87
|
+
navigationListeners.delete(listener);
|
|
88
|
+
if (navigationListeners.size) return;
|
|
89
|
+
for (const method of ["pushState", "replaceState"]) {
|
|
90
|
+
const patch = historyPatches[method];
|
|
91
|
+
if (patch && history[method] === patch.wrapped) {
|
|
92
|
+
history[method] = patch.original;
|
|
93
|
+
delete historyPatches[method];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
81
97
|
}
|
|
82
98
|
var WebAnalytics = class {
|
|
83
99
|
options;
|
|
84
100
|
baseUrl;
|
|
85
101
|
debug;
|
|
102
|
+
cleanup = [];
|
|
86
103
|
started = false;
|
|
87
|
-
destroyed = false;
|
|
88
|
-
epoch = 0;
|
|
89
104
|
heartbeatTimer = null;
|
|
105
|
+
extensions = null;
|
|
90
106
|
pageKey = "";
|
|
91
107
|
entry = {
|
|
92
108
|
path: "",
|
|
@@ -98,365 +114,327 @@ var WebAnalytics = class {
|
|
|
98
114
|
visitStartedAt = null;
|
|
99
115
|
pendingPageviewTrigger = null;
|
|
100
116
|
consentMode;
|
|
101
|
-
pendingBehavior;
|
|
102
|
-
cleanup = [];
|
|
103
|
-
childTrackers = [];
|
|
104
|
-
outboundLinks = new OutboundLinkTracker();
|
|
105
117
|
constructor(options) {
|
|
106
118
|
this.options = options;
|
|
119
|
+
if (!options.siteKey.trim()) throw new Error("siteKey is required");
|
|
107
120
|
this.baseUrl = resolveBaseUrl(options.baseUrl, ANALYTICS_BASE);
|
|
108
121
|
this.debug = options.debug ?? false;
|
|
109
|
-
this.consentMode =
|
|
110
|
-
pendingConsentMode = void 0;
|
|
111
|
-
this.pendingBehavior = options.consent?.pendingBehavior ?? "anonymous";
|
|
112
|
-
if (options.autoTrack ?? true) {
|
|
113
|
-
if (typeof window === "undefined") return;
|
|
114
|
-
if (isTrackingDisabled()) {
|
|
115
|
-
this.log("disabled");
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
setTimeout(() => void this.start(), 0);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
log(msg) {
|
|
122
|
-
if (this.debug) console.log(`[Analytics] ${msg}`);
|
|
123
|
-
}
|
|
124
|
-
isTrackingBlocked() {
|
|
125
|
-
return typeof window === "undefined" || this.destroyed || isTrackingDisabled();
|
|
126
|
-
}
|
|
127
|
-
ensureActive() {
|
|
128
|
-
if (this.isTrackingBlocked()) return false;
|
|
129
|
-
if (!this.started) this.start();
|
|
130
|
-
return this.started && !this.destroyed && instance === this;
|
|
131
|
-
}
|
|
132
|
-
canTrack() {
|
|
133
|
-
return this.consentMode === "granted" || this.consentMode === "pending" && this.pendingBehavior === "anonymous";
|
|
134
|
-
}
|
|
135
|
-
canSendEvents() {
|
|
136
|
-
return !this.isTrackingBlocked() && this.canTrack();
|
|
137
|
-
}
|
|
138
|
-
cookieless() {
|
|
139
|
-
return !!this.options.cookieless || this.consentMode === "pending" && this.pendingBehavior === "anonymous";
|
|
140
|
-
}
|
|
141
|
-
touch() {
|
|
142
|
-
if (this.canTrack()) touchActivity(this.options.siteKey, this.cookieless());
|
|
143
|
-
}
|
|
144
|
-
bind(target, type, listener, options) {
|
|
145
|
-
target.addEventListener(type, listener, options);
|
|
146
|
-
this.cleanup.push(() => target.removeEventListener(type, listener, options));
|
|
147
|
-
}
|
|
148
|
-
stopHeartbeat() {
|
|
149
|
-
if (!this.heartbeatTimer) return;
|
|
150
|
-
clearInterval(this.heartbeatTimer);
|
|
151
|
-
this.heartbeatTimer = null;
|
|
152
|
-
}
|
|
153
|
-
loadChild(name, create) {
|
|
154
|
-
const epoch = this.epoch;
|
|
155
|
-
create().then(async (tracker) => {
|
|
156
|
-
if (this.epoch !== epoch || !this.started) return tracker.stop?.(true);
|
|
157
|
-
await tracker.start();
|
|
158
|
-
if (this.epoch !== epoch || !this.started) return tracker.stop?.(true);
|
|
159
|
-
this.childTrackers.push(tracker);
|
|
160
|
-
this.log(`${name} loaded`);
|
|
161
|
-
}).catch((error) => this.log(`failed to initialize ${name} tracker: ${String(error)}`));
|
|
162
|
-
}
|
|
163
|
-
loadOptionalTrackers() {
|
|
164
|
-
const o = this.options;
|
|
165
|
-
const base = {
|
|
166
|
-
siteKey: o.siteKey,
|
|
167
|
-
baseUrl: this.baseUrl,
|
|
168
|
-
debug: this.debug,
|
|
169
|
-
trackHash: o.trackHash ?? false
|
|
170
|
-
};
|
|
171
|
-
if (o.sessionReplays?.enabled) this.loadChild("replay", async () => {
|
|
172
|
-
const { default: ReplayTracker } = await import("./replay.js");
|
|
173
|
-
return new ReplayTracker({
|
|
174
|
-
...base,
|
|
175
|
-
...o.replayOptions
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
if (o.errorTracking?.enabled) this.loadChild("error", async () => {
|
|
179
|
-
const { default: ErrorTracker } = await import("./error.js");
|
|
180
|
-
return new ErrorTracker({
|
|
181
|
-
...base,
|
|
182
|
-
sdkName: o.sdkName,
|
|
183
|
-
sdkVersion: o.sdkVersion
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
if (o.webVitals?.enabled) this.loadChild("web-vitals", async () => {
|
|
187
|
-
const { default: WebVitalsTracker } = await import("./web-vitals.js");
|
|
188
|
-
return new WebVitalsTracker({
|
|
189
|
-
...base,
|
|
190
|
-
attribution: o.webVitals?.attribution ?? false
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
enterPage() {
|
|
195
|
-
const includeHash = this.options.trackHash ?? false;
|
|
196
|
-
const hash = includeHash ? location.hash : "";
|
|
197
|
-
this.entry = {
|
|
198
|
-
path: `${location.pathname}${hash}`,
|
|
199
|
-
url: sanitizeUrl(location.href, includeHash),
|
|
200
|
-
search: location.search
|
|
201
|
-
};
|
|
202
|
-
this.left = false;
|
|
203
|
-
this.startVisit();
|
|
204
|
-
}
|
|
205
|
-
beginTracking(trigger) {
|
|
206
|
-
this.touch();
|
|
207
|
-
this.loadOptionalTrackers();
|
|
208
|
-
this.enterPage();
|
|
209
|
-
this.pageKey = "";
|
|
210
|
-
if (document.visibilityState === "visible") {
|
|
211
|
-
this.pageview({ trigger });
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
this.pendingPageviewTrigger = trigger;
|
|
122
|
+
this.consentMode = options.consent ?? "granted";
|
|
215
123
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (href) this.send("outbound_link", { outbound_link: sanitizeUrl(href) });
|
|
219
|
-
};
|
|
220
|
-
onVisibilityChange = () => {
|
|
221
|
-
this.touch();
|
|
222
|
-
if (document.visibilityState === "hidden") {
|
|
223
|
-
this.pauseVisit();
|
|
224
|
-
for (const tracker of this.childTrackers) tracker.onPageHidden?.(false, false);
|
|
225
|
-
this.stopHeartbeat();
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
this.startHeartbeat();
|
|
229
|
-
this.resumeVisit();
|
|
230
|
-
const trigger = this.pendingPageviewTrigger;
|
|
231
|
-
this.pendingPageviewTrigger = null;
|
|
232
|
-
if (trigger) this.pageview({ trigger });
|
|
233
|
-
};
|
|
234
|
-
onPageHide = (event) => {
|
|
235
|
-
const persisted = event.persisted;
|
|
236
|
-
this.pauseVisit();
|
|
237
|
-
for (const tracker of this.childTrackers) tracker.onPageHidden?.(persisted, !persisted);
|
|
238
|
-
if (!persisted) this.leavePage();
|
|
239
|
-
};
|
|
240
|
-
onPageShow = (event) => {
|
|
241
|
-
if (document.visibilityState === "visible") this.resumeVisit();
|
|
242
|
-
for (const tracker of this.childTrackers) tracker.onPageShow?.(event.persisted);
|
|
243
|
-
};
|
|
244
|
-
async start() {
|
|
245
|
-
if (this.started) return;
|
|
246
|
-
if (this.isTrackingBlocked()) {
|
|
247
|
-
if (isTrackingDisabled()) this.log("disabled");
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
if (instance && instance !== this) {
|
|
251
|
-
this.log("already started by another instance");
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
124
|
+
start() {
|
|
125
|
+
if (this.started !== false || typeof window === "undefined") return;
|
|
254
126
|
this.started = true;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
this.bind(document, "click", this.onLinkClick, true);
|
|
258
|
-
this.bind(document, "auxclick", this.onLinkClick, true);
|
|
259
|
-
this.bind(document, "visibilitychange", this.onVisibilityChange);
|
|
260
|
-
this.bind(window, "pagehide", this.onPageHide);
|
|
261
|
-
this.bind(window, "pageshow", this.onPageShow);
|
|
262
|
-
this.bind(window, "popstate", () => this.navigate());
|
|
263
|
-
if (this.options.trackHash ?? false) this.bind(window, "hashchange", () => this.navigate());
|
|
264
|
-
for (const method of ["pushState", "replaceState"]) {
|
|
265
|
-
const original = history[method];
|
|
266
|
-
const wrapped = (data, unused, url) => {
|
|
267
|
-
Reflect.apply(original, history, [
|
|
268
|
-
data,
|
|
269
|
-
unused,
|
|
270
|
-
url
|
|
271
|
-
]);
|
|
272
|
-
this.navigate();
|
|
273
|
-
};
|
|
274
|
-
history[method] = wrapped;
|
|
275
|
-
this.cleanup.push(() => {
|
|
276
|
-
if (history[method] === wrapped) history[method] = original;
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
this.startHeartbeat();
|
|
280
|
-
if (this.canTrack()) this.beginTracking("load");
|
|
127
|
+
this.installBrowserLifecycle();
|
|
128
|
+
if (this.canTrack()) this.activate("load");
|
|
281
129
|
}
|
|
282
130
|
destroy() {
|
|
283
|
-
if (this.
|
|
284
|
-
this.
|
|
285
|
-
|
|
131
|
+
if (this.started === null) return;
|
|
132
|
+
if (this.started && typeof window !== "undefined" && this.canTrack()) this.leavePage();
|
|
133
|
+
this.started = null;
|
|
286
134
|
this.pendingPageviewTrigger = null;
|
|
287
135
|
this.stopHeartbeat();
|
|
288
|
-
for (const
|
|
289
|
-
|
|
290
|
-
if (instance === this) instance = null;
|
|
291
|
-
this.started = false;
|
|
292
|
-
this.destroyed = true;
|
|
136
|
+
for (const cleanup of this.cleanup.splice(0).reverse()) cleanup();
|
|
137
|
+
this.stopExtensions(false);
|
|
293
138
|
}
|
|
294
|
-
pageview(
|
|
139
|
+
pageview(properties = {}) {
|
|
295
140
|
if (!this.ensureActive()) return;
|
|
296
|
-
const
|
|
297
|
-
const key = `${location.pathname}|${location.search}|${
|
|
141
|
+
const includeHash = this.options.trackHash ?? false;
|
|
142
|
+
const key = `${location.pathname}|${location.search}|${includeHash ? location.hash : ""}`;
|
|
298
143
|
if (key === this.pageKey) return;
|
|
299
144
|
this.pageKey = key;
|
|
300
|
-
this.send("pageview",
|
|
145
|
+
this.send("pageview", properties);
|
|
301
146
|
}
|
|
302
|
-
track(name,
|
|
147
|
+
track(name, properties = {}) {
|
|
303
148
|
if (!this.ensureActive()) return;
|
|
304
149
|
const eventName = name.trim();
|
|
305
150
|
if (!eventName || eventName === "pageview" || eventName === "page_leave" || eventName === "error" || eventName === "outbound_link") return;
|
|
306
|
-
this.send(eventName,
|
|
307
|
-
}
|
|
308
|
-
async identify(
|
|
309
|
-
if (!this.ensureActive() || !this.canTrack() || this.
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
151
|
+
this.send(eventName, properties);
|
|
152
|
+
}
|
|
153
|
+
async identify(userOrExternalId, email, options = {}) {
|
|
154
|
+
if (!this.ensureActive() || !this.canTrack() || this.isCookieless()) return false;
|
|
155
|
+
const user = typeof userOrExternalId === "string" ? {
|
|
156
|
+
id: userOrExternalId,
|
|
157
|
+
email,
|
|
158
|
+
...options
|
|
159
|
+
} : userOrExternalId;
|
|
160
|
+
const externalId = user.id.trim();
|
|
161
|
+
if (!externalId) return false;
|
|
162
|
+
const clearFields = [
|
|
163
|
+
"email",
|
|
164
|
+
"name",
|
|
165
|
+
"phone",
|
|
166
|
+
"avatarUrl"
|
|
167
|
+
].filter((field) => user[field] === null);
|
|
168
|
+
return await sendData({
|
|
314
169
|
url: `${this.baseUrl}${URLS.identify}`,
|
|
315
170
|
data: JSON.stringify({
|
|
316
171
|
token: this.options.siteKey,
|
|
317
172
|
identifier: this.getAnonymousId(),
|
|
318
|
-
externalId
|
|
319
|
-
email:
|
|
320
|
-
name:
|
|
321
|
-
phone:
|
|
322
|
-
avatarUrl:
|
|
323
|
-
traits:
|
|
173
|
+
externalId,
|
|
174
|
+
email: user.email?.trim() || void 0,
|
|
175
|
+
name: user.name?.trim() || void 0,
|
|
176
|
+
phone: user.phone?.trim() || void 0,
|
|
177
|
+
avatarUrl: user.avatarUrl?.trim() || void 0,
|
|
178
|
+
traits: user.traits,
|
|
179
|
+
replaceTraits: user.traitMode === "replace",
|
|
180
|
+
unsetTraits: user.unsetTraits,
|
|
181
|
+
aliases: user.aliases,
|
|
182
|
+
clearFields
|
|
324
183
|
}),
|
|
325
184
|
contentType: "text/plain",
|
|
326
185
|
debug: this.debug,
|
|
327
|
-
debugPrefix: "[
|
|
186
|
+
debugPrefix: "[FastStats] identify",
|
|
328
187
|
useBeacon: false
|
|
329
188
|
});
|
|
330
189
|
}
|
|
331
190
|
logout(resetAnonymousIdentity = true) {
|
|
332
191
|
if (!this.ensureActive()) return;
|
|
333
|
-
if (resetAnonymousIdentity) resetAnonymousId(this.options.siteKey, this.
|
|
334
|
-
resetSession(this.options.siteKey);
|
|
192
|
+
if (resetAnonymousIdentity) resetAnonymousId(this.options.siteKey, this.isCookieless());
|
|
193
|
+
resetSession(this.options.siteKey, this.isCookieless());
|
|
335
194
|
}
|
|
336
195
|
setConsentMode(mode) {
|
|
337
196
|
if (mode === this.consentMode) return;
|
|
338
197
|
const wasTracking = this.canTrack();
|
|
198
|
+
const wasCookieless = this.isCookieless();
|
|
199
|
+
const willTrack = mode !== "denied";
|
|
200
|
+
const willBeCookieless = this.options.cookieless === true || mode === "anonymous";
|
|
201
|
+
if (wasTracking && willTrack && wasCookieless !== willBeCookieless) this.leavePage();
|
|
339
202
|
this.consentMode = mode;
|
|
340
|
-
|
|
341
|
-
const cookieless = this.cookieless();
|
|
342
|
-
setCookielessMode(cookieless);
|
|
343
|
-
if (!tracking) {
|
|
344
|
-
this.epoch++;
|
|
203
|
+
if (!willTrack) {
|
|
345
204
|
this.pauseVisit();
|
|
346
205
|
this.stopHeartbeat();
|
|
347
206
|
this.pendingPageviewTrigger = null;
|
|
348
|
-
|
|
207
|
+
this.stopExtensions(true);
|
|
349
208
|
return;
|
|
350
209
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
210
|
+
if (wasTracking) {
|
|
211
|
+
this.extensions?.emit("consentChange", { cookieless: willBeCookieless });
|
|
212
|
+
if (wasCookieless !== willBeCookieless) {
|
|
213
|
+
this.enterPage();
|
|
214
|
+
this.pageKey = "";
|
|
215
|
+
this.pageview({ trigger: "consent" });
|
|
216
|
+
}
|
|
217
|
+
} else if (this.started) this.activate("consent");
|
|
359
218
|
}
|
|
360
219
|
getAnonymousId() {
|
|
361
|
-
|
|
362
|
-
return getAnonymousId(this.options.siteKey, this.cookieless());
|
|
220
|
+
return this.canTrack() ? getAnonymousId(this.options.siteKey, this.isCookieless()) : "";
|
|
363
221
|
}
|
|
364
|
-
|
|
365
|
-
if (!this.canTrack()) return
|
|
366
|
-
|
|
222
|
+
reportError(error) {
|
|
223
|
+
if (!this.ensureActive() || !this.canTrack()) return;
|
|
224
|
+
this.extensions?.emit("error", { error });
|
|
367
225
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return getSessionContext(this.options.siteKey, this.cookieless()).windowId;
|
|
226
|
+
canTrack() {
|
|
227
|
+
return this.consentMode !== "denied";
|
|
371
228
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const externalId = opts?.externalId?.trim();
|
|
375
|
-
const identifier = this.getAnonymousId();
|
|
376
|
-
if (!externalId && !identifier) return { value: "false" };
|
|
377
|
-
const { fetchFeatureFlagEvaluation } = await import("./feature-flags.js");
|
|
378
|
-
return fetchFeatureFlagEvaluation(key, {
|
|
379
|
-
baseUrl: resolveBaseUrl(this.options.featureFlagsBaseUrl, FEATURE_FLAGS_BASE),
|
|
380
|
-
projectToken: this.options.siteKey,
|
|
381
|
-
...identifier ? { identifier } : {},
|
|
382
|
-
...externalId ? { externalId } : {},
|
|
383
|
-
sessionId: this.getSessionId(),
|
|
384
|
-
attributes,
|
|
385
|
-
signal: opts?.signal
|
|
386
|
-
});
|
|
229
|
+
isCookieless() {
|
|
230
|
+
return this.options.cookieless === true || this.consentMode === "anonymous";
|
|
387
231
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
this.childTrackers.find((tracker) => tracker.captureError)?.captureError?.(error);
|
|
232
|
+
ensureActive() {
|
|
233
|
+
return this.started === true && typeof window !== "undefined";
|
|
391
234
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
this.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
...getPageContext(this.options.trackHash ?? false),
|
|
407
|
-
...dims,
|
|
408
|
-
properties
|
|
409
|
-
}),
|
|
410
|
-
contentType: "text/plain",
|
|
235
|
+
activate(trigger) {
|
|
236
|
+
this.touch();
|
|
237
|
+
this.startExtensions();
|
|
238
|
+
this.enterPage();
|
|
239
|
+
this.pageKey = "";
|
|
240
|
+
this.startHeartbeat();
|
|
241
|
+
if (document.visibilityState === "visible") this.pageview({ trigger });
|
|
242
|
+
else this.pendingPageviewTrigger = trigger;
|
|
243
|
+
}
|
|
244
|
+
startExtensions() {
|
|
245
|
+
if (this.extensions || !this.options.extensions?.length) return;
|
|
246
|
+
const host = new ExtensionHost(this.options.extensions, {
|
|
247
|
+
siteKey: this.options.siteKey,
|
|
248
|
+
baseUrl: this.baseUrl,
|
|
411
249
|
debug: this.debug,
|
|
412
|
-
|
|
250
|
+
trackHash: this.options.trackHash ?? false,
|
|
251
|
+
sdkName: this.options.sdkName,
|
|
252
|
+
sdkVersion: this.options.sdkVersion,
|
|
253
|
+
isCookieless: () => this.isCookieless(),
|
|
254
|
+
getAnonymousId: () => this.getAnonymousId(),
|
|
255
|
+
getSession: () => getSessionContext(this.options.siteKey, this.isCookieless()),
|
|
256
|
+
capture: (event, properties) => this.send(event, properties),
|
|
257
|
+
log: (...details) => {
|
|
258
|
+
if (this.debug) console.log(...details);
|
|
259
|
+
}
|
|
413
260
|
});
|
|
261
|
+
this.extensions = host;
|
|
262
|
+
}
|
|
263
|
+
stopExtensions(discard) {
|
|
264
|
+
this.extensions?.stop(discard);
|
|
265
|
+
this.extensions = null;
|
|
266
|
+
}
|
|
267
|
+
installBrowserLifecycle() {
|
|
268
|
+
this.bind(document, "visibilitychange", this.onVisibilityChange);
|
|
269
|
+
this.bind(window, "pagehide", this.onPageHide);
|
|
270
|
+
this.bind(window, "pageshow", this.onPageShow);
|
|
271
|
+
this.bind(window, "popstate", this.onNavigate);
|
|
272
|
+
if (this.options.trackHash) this.bind(window, "hashchange", this.onNavigate);
|
|
273
|
+
this.cleanup.push(observeHistory(() => this.navigate()));
|
|
274
|
+
}
|
|
275
|
+
bind(target, type, listener, options) {
|
|
276
|
+
target.addEventListener(type, listener, options);
|
|
277
|
+
this.cleanup.push(() => target.removeEventListener(type, listener, options));
|
|
278
|
+
}
|
|
279
|
+
onNavigate = () => this.navigate();
|
|
280
|
+
onVisibilityChange = () => {
|
|
281
|
+
if (!this.canTrack()) return;
|
|
282
|
+
this.touch();
|
|
283
|
+
if (document.visibilityState === "hidden") {
|
|
284
|
+
this.pauseVisit();
|
|
285
|
+
this.extensions?.emit("pageHide", {
|
|
286
|
+
persisted: false,
|
|
287
|
+
terminal: false
|
|
288
|
+
});
|
|
289
|
+
this.stopHeartbeat();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.startHeartbeat();
|
|
293
|
+
this.resumeVisit();
|
|
294
|
+
const trigger = this.pendingPageviewTrigger;
|
|
295
|
+
this.pendingPageviewTrigger = null;
|
|
296
|
+
if (trigger) this.pageview({ trigger });
|
|
297
|
+
};
|
|
298
|
+
onPageHide = (event) => {
|
|
299
|
+
if (!this.canTrack()) return;
|
|
300
|
+
const persisted = event.persisted;
|
|
301
|
+
this.pauseVisit();
|
|
302
|
+
this.extensions?.emit("pageHide", {
|
|
303
|
+
persisted,
|
|
304
|
+
terminal: !persisted
|
|
305
|
+
});
|
|
306
|
+
if (!persisted) this.leavePage();
|
|
307
|
+
};
|
|
308
|
+
onPageShow = (event) => {
|
|
309
|
+
if (!this.canTrack()) return;
|
|
310
|
+
if (document.visibilityState === "visible") this.resumeVisit();
|
|
311
|
+
this.extensions?.emit("pageShow", { persisted: event.persisted });
|
|
312
|
+
};
|
|
313
|
+
navigate() {
|
|
314
|
+
if (this.started !== true || !this.canTrack()) return;
|
|
315
|
+
const includeHash = this.options.trackHash ?? false;
|
|
316
|
+
if (`${location.pathname}${includeHash ? location.hash : ""}` === this.entry.path && location.search === this.entry.search) return;
|
|
317
|
+
const url = sanitizeUrl(location.href, includeHash);
|
|
318
|
+
this.extensions?.emit("pageChange", { url });
|
|
319
|
+
this.leavePage();
|
|
320
|
+
this.enterPage();
|
|
321
|
+
this.pageview({ trigger: "navigation" });
|
|
322
|
+
}
|
|
323
|
+
enterPage() {
|
|
324
|
+
const includeHash = this.options.trackHash ?? false;
|
|
325
|
+
const hash = includeHash ? location.hash : "";
|
|
326
|
+
this.entry = {
|
|
327
|
+
path: `${location.pathname}${hash}`,
|
|
328
|
+
url: sanitizeUrl(location.href, includeHash),
|
|
329
|
+
search: location.search
|
|
330
|
+
};
|
|
331
|
+
this.left = false;
|
|
332
|
+
this.visitDuration = 0;
|
|
333
|
+
this.visitStartedAt = document.visibilityState === "visible" ? Date.now() : null;
|
|
414
334
|
}
|
|
415
335
|
leavePage() {
|
|
416
|
-
if (this.
|
|
336
|
+
if (this.started !== true || this.left || !this.canTrack()) return;
|
|
417
337
|
this.pauseVisit();
|
|
418
338
|
this.left = true;
|
|
419
|
-
const
|
|
339
|
+
const session = getSessionContext(this.options.siteKey, this.isCookieless());
|
|
420
340
|
this.send("page_leave", {
|
|
421
341
|
time_on_page: this.visitDuration,
|
|
422
|
-
session_duration: Date.now() -
|
|
342
|
+
session_duration: Date.now() - session.sessionStart
|
|
423
343
|
}, {
|
|
424
344
|
page: this.entry.path,
|
|
425
345
|
url: this.entry.url
|
|
426
346
|
});
|
|
427
347
|
}
|
|
428
|
-
startVisit() {
|
|
429
|
-
this.visitDuration = 0;
|
|
430
|
-
this.visitStartedAt = document.visibilityState === "visible" ? Date.now() : null;
|
|
431
|
-
}
|
|
432
348
|
pauseVisit() {
|
|
433
349
|
if (this.visitStartedAt === null) return;
|
|
434
350
|
this.visitDuration += Math.max(0, Date.now() - this.visitStartedAt);
|
|
435
351
|
this.visitStartedAt = null;
|
|
436
352
|
}
|
|
437
353
|
resumeVisit() {
|
|
438
|
-
if (this.left
|
|
439
|
-
|
|
354
|
+
if (!this.left && this.visitStartedAt === null) this.visitStartedAt = Date.now();
|
|
355
|
+
}
|
|
356
|
+
touch() {
|
|
357
|
+
if (this.canTrack()) touchActivity(this.options.siteKey, this.isCookieless());
|
|
440
358
|
}
|
|
441
359
|
startHeartbeat() {
|
|
442
360
|
this.stopHeartbeat();
|
|
443
361
|
this.heartbeatTimer = setInterval(() => {
|
|
444
|
-
if (document.visibilityState === "hidden")
|
|
445
|
-
this.stopHeartbeat();
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
362
|
+
if (document.visibilityState === "hidden") return this.stopHeartbeat();
|
|
448
363
|
this.touch();
|
|
449
|
-
},
|
|
364
|
+
}, 3e5);
|
|
450
365
|
}
|
|
451
|
-
|
|
452
|
-
if (!this.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
this.
|
|
458
|
-
this.
|
|
366
|
+
stopHeartbeat() {
|
|
367
|
+
if (!this.heartbeatTimer) return;
|
|
368
|
+
clearInterval(this.heartbeatTimer);
|
|
369
|
+
this.heartbeatTimer = null;
|
|
370
|
+
}
|
|
371
|
+
send(event, properties = {}, dimensions = {}) {
|
|
372
|
+
if (this.started !== true || !this.canTrack()) return;
|
|
373
|
+
const cookieless = this.isCookieless();
|
|
374
|
+
const userId = getAnonymousId(this.options.siteKey, cookieless);
|
|
375
|
+
const session = getSessionContext(this.options.siteKey, cookieless);
|
|
376
|
+
let data;
|
|
377
|
+
try {
|
|
378
|
+
data = JSON.stringify({
|
|
379
|
+
token: this.options.siteKey,
|
|
380
|
+
...userId ? { userId } : {},
|
|
381
|
+
sessionId: session.sessionId,
|
|
382
|
+
windowId: session.windowId,
|
|
383
|
+
event,
|
|
384
|
+
...getPageContext(this.options.trackHash ?? false),
|
|
385
|
+
...dimensions,
|
|
386
|
+
properties
|
|
387
|
+
});
|
|
388
|
+
} catch (error) {
|
|
389
|
+
if (this.debug) console.log("serialization failed", event, error);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
sendData({
|
|
393
|
+
url: `${this.baseUrl}${URLS.events}`,
|
|
394
|
+
data,
|
|
395
|
+
contentType: "text/plain",
|
|
396
|
+
debug: this.debug,
|
|
397
|
+
debugPrefix: "[FastStats]"
|
|
398
|
+
});
|
|
459
399
|
}
|
|
460
400
|
};
|
|
461
401
|
//#endregion
|
|
462
|
-
|
|
402
|
+
//#region src/client.ts
|
|
403
|
+
let client;
|
|
404
|
+
/** Creates an independent client. Call `start()` when it should begin tracking. */
|
|
405
|
+
function createClient(options) {
|
|
406
|
+
return new WebAnalytics(options);
|
|
407
|
+
}
|
|
408
|
+
/** Initializes and starts the shared client used by the convenience functions. */
|
|
409
|
+
function init(options) {
|
|
410
|
+
if (client) return client;
|
|
411
|
+
client = createClient(options);
|
|
412
|
+
client.start();
|
|
413
|
+
return client;
|
|
414
|
+
}
|
|
415
|
+
/** Stops and forgets the shared client. Independent clients are unaffected. */
|
|
416
|
+
function shutdown() {
|
|
417
|
+
client?.destroy();
|
|
418
|
+
client = void 0;
|
|
419
|
+
}
|
|
420
|
+
function pageview(properties) {
|
|
421
|
+
client?.pageview(properties);
|
|
422
|
+
}
|
|
423
|
+
function track(name, properties) {
|
|
424
|
+
client?.track(name, properties);
|
|
425
|
+
}
|
|
426
|
+
function identify(userOrExternalId, email, options) {
|
|
427
|
+
if (!client) return Promise.resolve(false);
|
|
428
|
+
return typeof userOrExternalId === "string" ? client.identify(userOrExternalId, email, options) : client.identify(userOrExternalId);
|
|
429
|
+
}
|
|
430
|
+
function logout(resetAnonymousIdentity) {
|
|
431
|
+
client?.logout(resetAnonymousIdentity);
|
|
432
|
+
}
|
|
433
|
+
function setConsentMode(mode) {
|
|
434
|
+
client?.setConsentMode(mode);
|
|
435
|
+
}
|
|
436
|
+
function reportError(error) {
|
|
437
|
+
client?.reportError(error);
|
|
438
|
+
}
|
|
439
|
+
//#endregion
|
|
440
|
+
export { WebAnalytics, createClient, identify, init, logout, pageview, reportError, setConsentMode, shutdown, track };
|