@outlit/browser 0.2.0 → 0.2.2
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/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +498 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +492 -12
- package/dist/index.mjs.map +1 -1
- package/dist/outlit.global.js +1 -1
- package/dist/outlit.global.js.map +1 -1
- package/dist/react/index.d.mts +12 -12
- package/dist/react/index.d.ts +12 -12
- package/dist/react/index.js +512 -33
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +507 -27
- package/dist/react/index.mjs.map +1 -1
- package/dist/{tracker-CocH64L9.d.mts → tracker-DFcTv3EM.d.mts} +39 -9
- package/dist/{tracker-CocH64L9.d.ts → tracker-DFcTv3EM.d.ts} +39 -9
- package/package.json +3 -3
package/dist/react/index.js
CHANGED
|
@@ -32,7 +32,7 @@ module.exports = __toCommonJS(react_exports);
|
|
|
32
32
|
var import_react = require("react");
|
|
33
33
|
|
|
34
34
|
// src/tracker.ts
|
|
35
|
-
var
|
|
35
|
+
var import_core3 = require("@outlit/core");
|
|
36
36
|
|
|
37
37
|
// src/autocapture.ts
|
|
38
38
|
var import_core = require("@outlit/core");
|
|
@@ -52,19 +52,22 @@ function capturePageview() {
|
|
|
52
52
|
lastUrl = url;
|
|
53
53
|
pageviewCallback(url, referrer, title);
|
|
54
54
|
}
|
|
55
|
+
function capturePageviewDelayed() {
|
|
56
|
+
setTimeout(capturePageview, 10);
|
|
57
|
+
}
|
|
55
58
|
function setupSpaListeners() {
|
|
56
59
|
window.addEventListener("popstate", () => {
|
|
57
|
-
|
|
60
|
+
capturePageviewDelayed();
|
|
58
61
|
});
|
|
59
62
|
const originalPushState = history.pushState;
|
|
60
63
|
const originalReplaceState = history.replaceState;
|
|
61
64
|
history.pushState = function(...args) {
|
|
62
65
|
originalPushState.apply(this, args);
|
|
63
|
-
|
|
66
|
+
capturePageviewDelayed();
|
|
64
67
|
};
|
|
65
68
|
history.replaceState = function(...args) {
|
|
66
69
|
originalReplaceState.apply(this, args);
|
|
67
|
-
|
|
70
|
+
capturePageviewDelayed();
|
|
68
71
|
};
|
|
69
72
|
}
|
|
70
73
|
var formCallback = null;
|
|
@@ -115,6 +118,431 @@ function stopAutocapture() {
|
|
|
115
118
|
document.removeEventListener("submit", handleFormSubmit, true);
|
|
116
119
|
}
|
|
117
120
|
|
|
121
|
+
// src/embed-integrations.ts
|
|
122
|
+
var callbacks = null;
|
|
123
|
+
var isListening = false;
|
|
124
|
+
var calSetupAttempts = 0;
|
|
125
|
+
var calCallbackRegistered = false;
|
|
126
|
+
var lastBookingUid = null;
|
|
127
|
+
var CAL_MAX_RETRY_ATTEMPTS = 10;
|
|
128
|
+
var CAL_INITIAL_DELAY_MS = 200;
|
|
129
|
+
var CAL_MAX_DELAY_MS = 2e3;
|
|
130
|
+
function parseCalComBooking(data) {
|
|
131
|
+
const event = {
|
|
132
|
+
provider: "cal.com"
|
|
133
|
+
};
|
|
134
|
+
if (data.title) {
|
|
135
|
+
event.eventType = data.title;
|
|
136
|
+
const nameMatch = data.title.match(/between .+ and (.+)$/i);
|
|
137
|
+
if (nameMatch?.[1]) {
|
|
138
|
+
event.inviteeName = nameMatch[1].trim();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (data.startTime) event.startTime = data.startTime;
|
|
142
|
+
if (data.endTime) event.endTime = data.endTime;
|
|
143
|
+
if (data.startTime && data.endTime) {
|
|
144
|
+
const start = new Date(data.startTime);
|
|
145
|
+
const end = new Date(data.endTime);
|
|
146
|
+
event.duration = Math.round((end.getTime() - start.getTime()) / 6e4);
|
|
147
|
+
}
|
|
148
|
+
if (data.isRecurring !== void 0) {
|
|
149
|
+
event.isRecurring = data.isRecurring;
|
|
150
|
+
}
|
|
151
|
+
return event;
|
|
152
|
+
}
|
|
153
|
+
function setupCalComListener() {
|
|
154
|
+
if (typeof window === "undefined") return;
|
|
155
|
+
if (calCallbackRegistered) return;
|
|
156
|
+
calSetupAttempts++;
|
|
157
|
+
if ("Cal" in window) {
|
|
158
|
+
const Cal = window.Cal;
|
|
159
|
+
if (typeof Cal === "function") {
|
|
160
|
+
try {
|
|
161
|
+
Cal("on", {
|
|
162
|
+
action: "bookingSuccessfulV2",
|
|
163
|
+
callback: handleCalComBooking
|
|
164
|
+
});
|
|
165
|
+
calCallbackRegistered = true;
|
|
166
|
+
return;
|
|
167
|
+
} catch (_e) {
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (calSetupAttempts < CAL_MAX_RETRY_ATTEMPTS) {
|
|
172
|
+
const delay = Math.min(CAL_INITIAL_DELAY_MS * calSetupAttempts, CAL_MAX_DELAY_MS);
|
|
173
|
+
setTimeout(setupCalComListener, delay);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function handleCalComBooking(e) {
|
|
177
|
+
if (!callbacks) return;
|
|
178
|
+
const data = e.detail?.data;
|
|
179
|
+
if (!data) return;
|
|
180
|
+
if (data.uid && data.uid === lastBookingUid) return;
|
|
181
|
+
lastBookingUid = data.uid || null;
|
|
182
|
+
const bookingEvent = parseCalComBooking(data);
|
|
183
|
+
callbacks.onCalendarBooked(bookingEvent);
|
|
184
|
+
}
|
|
185
|
+
function handlePostMessage(event) {
|
|
186
|
+
if (!callbacks) return;
|
|
187
|
+
if (isCalendlyEvent(event)) {
|
|
188
|
+
if (event.data.event === "calendly.event_scheduled") {
|
|
189
|
+
const bookingEvent = parseCalendlyBooking(event.data.payload);
|
|
190
|
+
callbacks.onCalendarBooked(bookingEvent);
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (isCalComRawMessage(event)) {
|
|
195
|
+
const bookingData = extractCalComBookingFromMessage(event.data);
|
|
196
|
+
if (bookingData) {
|
|
197
|
+
if (bookingData.uid && bookingData.uid === lastBookingUid) return;
|
|
198
|
+
lastBookingUid = bookingData.uid || null;
|
|
199
|
+
const bookingEvent = parseCalComBooking(bookingData);
|
|
200
|
+
callbacks.onCalendarBooked(bookingEvent);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function isCalComRawMessage(event) {
|
|
205
|
+
if (!event.origin.includes("cal.com")) return false;
|
|
206
|
+
const data = event.data;
|
|
207
|
+
if (!data || typeof data !== "object") return false;
|
|
208
|
+
const messageType = data.type || data.action;
|
|
209
|
+
return messageType === "bookingSuccessfulV2" || messageType === "bookingSuccessful" || messageType === "booking_successful";
|
|
210
|
+
}
|
|
211
|
+
function extractCalComBookingFromMessage(data) {
|
|
212
|
+
if (!data || typeof data !== "object") return null;
|
|
213
|
+
const messageData = data;
|
|
214
|
+
if (messageData.data && typeof messageData.data === "object") {
|
|
215
|
+
return messageData.data;
|
|
216
|
+
}
|
|
217
|
+
if (messageData.booking && typeof messageData.booking === "object") {
|
|
218
|
+
return messageData.booking;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
function isCalendlyEvent(e) {
|
|
223
|
+
return e.origin === "https://calendly.com" && e.data && typeof e.data.event === "string" && e.data.event.startsWith("calendly.");
|
|
224
|
+
}
|
|
225
|
+
function parseCalendlyBooking(_payload) {
|
|
226
|
+
return {
|
|
227
|
+
provider: "calendly"
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function initCalendarTracking(cbs) {
|
|
231
|
+
if (isListening) return;
|
|
232
|
+
callbacks = cbs;
|
|
233
|
+
isListening = true;
|
|
234
|
+
calSetupAttempts = 0;
|
|
235
|
+
window.addEventListener("message", handlePostMessage);
|
|
236
|
+
setupCalComListener();
|
|
237
|
+
}
|
|
238
|
+
function stopCalendarTracking() {
|
|
239
|
+
if (!isListening) return;
|
|
240
|
+
window.removeEventListener("message", handlePostMessage);
|
|
241
|
+
callbacks = null;
|
|
242
|
+
isListening = false;
|
|
243
|
+
calCallbackRegistered = false;
|
|
244
|
+
calSetupAttempts = 0;
|
|
245
|
+
lastBookingUid = null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/session-tracker.ts
|
|
249
|
+
var import_core2 = require("@outlit/core");
|
|
250
|
+
var DEFAULT_IDLE_TIMEOUT = 3e4;
|
|
251
|
+
var SESSION_TIMEOUT = 30 * 60 * 1e3;
|
|
252
|
+
var TIME_UPDATE_INTERVAL = 1e3;
|
|
253
|
+
var MIN_SPURIOUS_THRESHOLD = 50;
|
|
254
|
+
var SESSION_ID_KEY = "outlit_session_id";
|
|
255
|
+
var SESSION_LAST_ACTIVITY_KEY = "outlit_session_last_activity";
|
|
256
|
+
var SessionTracker = class {
|
|
257
|
+
state;
|
|
258
|
+
options;
|
|
259
|
+
idleTimeout;
|
|
260
|
+
timeUpdateInterval = null;
|
|
261
|
+
boundHandleActivity;
|
|
262
|
+
boundHandleVisibilityChange;
|
|
263
|
+
constructor(options) {
|
|
264
|
+
this.options = options;
|
|
265
|
+
this.idleTimeout = options.idleTimeout ?? DEFAULT_IDLE_TIMEOUT;
|
|
266
|
+
this.state = this.createInitialState();
|
|
267
|
+
this.boundHandleActivity = this.handleActivity.bind(this);
|
|
268
|
+
this.boundHandleVisibilityChange = this.handleVisibilityChange.bind(this);
|
|
269
|
+
this.setupEventListeners();
|
|
270
|
+
this.startTimeUpdateInterval();
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get the current session ID.
|
|
274
|
+
*/
|
|
275
|
+
getSessionId() {
|
|
276
|
+
return this.state.sessionId;
|
|
277
|
+
}
|
|
278
|
+
// ============================================
|
|
279
|
+
// PUBLIC METHODS
|
|
280
|
+
// ============================================
|
|
281
|
+
/**
|
|
282
|
+
* Emit an engagement event for the current page session.
|
|
283
|
+
* Called by Tracker on exit events and SPA navigation.
|
|
284
|
+
*
|
|
285
|
+
* This method:
|
|
286
|
+
* 1. Finalizes any pending active time
|
|
287
|
+
* 2. Creates and emits the engagement event (if meaningful)
|
|
288
|
+
* 3. Resets state for the next session
|
|
289
|
+
*/
|
|
290
|
+
emitEngagement() {
|
|
291
|
+
if (this.state.hasEmittedEngagement) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
this.state.hasEmittedEngagement = true;
|
|
295
|
+
this.updateActiveTime();
|
|
296
|
+
const totalTimeMs = Date.now() - this.state.pageEntryTime;
|
|
297
|
+
const isSpuriousEvent = this.state.activeTimeMs < MIN_SPURIOUS_THRESHOLD && totalTimeMs < MIN_SPURIOUS_THRESHOLD;
|
|
298
|
+
if (!isSpuriousEvent) {
|
|
299
|
+
const event = (0, import_core2.buildEngagementEvent)({
|
|
300
|
+
url: this.state.currentUrl,
|
|
301
|
+
referrer: document.referrer,
|
|
302
|
+
activeTimeMs: this.state.activeTimeMs,
|
|
303
|
+
totalTimeMs,
|
|
304
|
+
sessionId: this.state.sessionId
|
|
305
|
+
});
|
|
306
|
+
this.options.onEngagement(event);
|
|
307
|
+
}
|
|
308
|
+
this.resetState();
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Handle SPA navigation.
|
|
312
|
+
* Called by Tracker when a new pageview is detected.
|
|
313
|
+
*
|
|
314
|
+
* This method:
|
|
315
|
+
* 1. Emits engagement for the OLD page (using stored state)
|
|
316
|
+
* 2. Updates state for the NEW page
|
|
317
|
+
*/
|
|
318
|
+
onNavigation(newUrl) {
|
|
319
|
+
this.emitEngagement();
|
|
320
|
+
this.state.currentUrl = newUrl;
|
|
321
|
+
this.state.currentPath = this.extractPath(newUrl);
|
|
322
|
+
this.state.pageEntryTime = Date.now();
|
|
323
|
+
this.state.activeTimeMs = 0;
|
|
324
|
+
this.state.lastActiveTime = Date.now();
|
|
325
|
+
this.state.isUserActive = true;
|
|
326
|
+
this.state.hasEmittedEngagement = false;
|
|
327
|
+
this.resetIdleTimer();
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Stop session tracking and clean up.
|
|
331
|
+
*/
|
|
332
|
+
stop() {
|
|
333
|
+
this.removeEventListeners();
|
|
334
|
+
if (this.timeUpdateInterval) {
|
|
335
|
+
clearInterval(this.timeUpdateInterval);
|
|
336
|
+
this.timeUpdateInterval = null;
|
|
337
|
+
}
|
|
338
|
+
if (this.state.idleTimeoutId) {
|
|
339
|
+
clearTimeout(this.state.idleTimeoutId);
|
|
340
|
+
this.state.idleTimeoutId = null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// ============================================
|
|
344
|
+
// PRIVATE METHODS
|
|
345
|
+
// ============================================
|
|
346
|
+
createInitialState() {
|
|
347
|
+
const now = Date.now();
|
|
348
|
+
return {
|
|
349
|
+
currentUrl: typeof window !== "undefined" ? window.location.href : "",
|
|
350
|
+
currentPath: typeof window !== "undefined" ? window.location.pathname : "/",
|
|
351
|
+
pageEntryTime: now,
|
|
352
|
+
lastActiveTime: now,
|
|
353
|
+
activeTimeMs: 0,
|
|
354
|
+
isPageVisible: typeof document !== "undefined" ? document.visibilityState === "visible" : true,
|
|
355
|
+
isUserActive: true,
|
|
356
|
+
// Assume active on page load
|
|
357
|
+
idleTimeoutId: null,
|
|
358
|
+
sessionId: this.getOrCreateSessionId(),
|
|
359
|
+
hasEmittedEngagement: false
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
resetState() {
|
|
363
|
+
const now = Date.now();
|
|
364
|
+
this.state.pageEntryTime = now;
|
|
365
|
+
this.state.lastActiveTime = now;
|
|
366
|
+
this.state.activeTimeMs = 0;
|
|
367
|
+
this.state.isUserActive = true;
|
|
368
|
+
this.resetIdleTimer();
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Get existing session ID from storage or create a new one.
|
|
372
|
+
* Session ID is reset if:
|
|
373
|
+
* - No existing session ID in storage
|
|
374
|
+
* - Last activity was more than 30 minutes ago
|
|
375
|
+
*/
|
|
376
|
+
getOrCreateSessionId() {
|
|
377
|
+
if (typeof sessionStorage === "undefined") {
|
|
378
|
+
return this.generateSessionId();
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
const existingSessionId = sessionStorage.getItem(SESSION_ID_KEY);
|
|
382
|
+
const lastActivityStr = sessionStorage.getItem(SESSION_LAST_ACTIVITY_KEY);
|
|
383
|
+
const lastActivity = lastActivityStr ? Number.parseInt(lastActivityStr, 10) : 0;
|
|
384
|
+
const now = Date.now();
|
|
385
|
+
if (existingSessionId && lastActivity && now - lastActivity < SESSION_TIMEOUT) {
|
|
386
|
+
this.updateSessionActivity();
|
|
387
|
+
return existingSessionId;
|
|
388
|
+
}
|
|
389
|
+
const newSessionId = this.generateSessionId();
|
|
390
|
+
sessionStorage.setItem(SESSION_ID_KEY, newSessionId);
|
|
391
|
+
sessionStorage.setItem(SESSION_LAST_ACTIVITY_KEY, now.toString());
|
|
392
|
+
return newSessionId;
|
|
393
|
+
} catch {
|
|
394
|
+
return this.generateSessionId();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Generate a new session ID (UUID v4).
|
|
399
|
+
*/
|
|
400
|
+
generateSessionId() {
|
|
401
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
402
|
+
return crypto.randomUUID();
|
|
403
|
+
}
|
|
404
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
405
|
+
const r = Math.random() * 16 | 0;
|
|
406
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
407
|
+
return v.toString(16);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Update the session's last activity timestamp.
|
|
412
|
+
*/
|
|
413
|
+
updateSessionActivity() {
|
|
414
|
+
if (typeof sessionStorage === "undefined") return;
|
|
415
|
+
try {
|
|
416
|
+
sessionStorage.setItem(SESSION_LAST_ACTIVITY_KEY, Date.now().toString());
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Check if the current session has expired and create a new one if needed.
|
|
422
|
+
* Called when user returns to the page after being away.
|
|
423
|
+
*/
|
|
424
|
+
checkSessionExpiry() {
|
|
425
|
+
if (typeof sessionStorage === "undefined") return;
|
|
426
|
+
try {
|
|
427
|
+
const lastActivityStr = sessionStorage.getItem(SESSION_LAST_ACTIVITY_KEY);
|
|
428
|
+
const lastActivity = lastActivityStr ? Number.parseInt(lastActivityStr, 10) : 0;
|
|
429
|
+
const now = Date.now();
|
|
430
|
+
if (now - lastActivity >= SESSION_TIMEOUT) {
|
|
431
|
+
const newSessionId = this.generateSessionId();
|
|
432
|
+
sessionStorage.setItem(SESSION_ID_KEY, newSessionId);
|
|
433
|
+
this.state.sessionId = newSessionId;
|
|
434
|
+
}
|
|
435
|
+
sessionStorage.setItem(SESSION_LAST_ACTIVITY_KEY, now.toString());
|
|
436
|
+
} catch {
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
setupEventListeners() {
|
|
440
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
441
|
+
const activityEvents = ["mousemove", "keydown", "click", "scroll", "touchstart"];
|
|
442
|
+
for (const event of activityEvents) {
|
|
443
|
+
document.addEventListener(event, this.boundHandleActivity, { passive: true });
|
|
444
|
+
}
|
|
445
|
+
document.addEventListener("visibilitychange", this.boundHandleVisibilityChange);
|
|
446
|
+
this.resetIdleTimer();
|
|
447
|
+
}
|
|
448
|
+
removeEventListeners() {
|
|
449
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
450
|
+
const activityEvents = ["mousemove", "keydown", "click", "scroll", "touchstart"];
|
|
451
|
+
for (const event of activityEvents) {
|
|
452
|
+
document.removeEventListener(event, this.boundHandleActivity);
|
|
453
|
+
}
|
|
454
|
+
document.removeEventListener("visibilitychange", this.boundHandleVisibilityChange);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Handle user activity events.
|
|
458
|
+
* Marks user as active and resets idle timer.
|
|
459
|
+
*/
|
|
460
|
+
handleActivity() {
|
|
461
|
+
if (!this.state.isUserActive) {
|
|
462
|
+
this.checkSessionExpiry();
|
|
463
|
+
this.state.lastActiveTime = Date.now();
|
|
464
|
+
}
|
|
465
|
+
this.state.isUserActive = true;
|
|
466
|
+
this.resetIdleTimer();
|
|
467
|
+
this.updateSessionActivity();
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Handle visibility change events.
|
|
471
|
+
* Pauses time accumulation when tab is hidden.
|
|
472
|
+
*/
|
|
473
|
+
handleVisibilityChange() {
|
|
474
|
+
const wasVisible = this.state.isPageVisible;
|
|
475
|
+
const isNowVisible = document.visibilityState === "visible";
|
|
476
|
+
if (wasVisible && !isNowVisible) {
|
|
477
|
+
this.updateActiveTime();
|
|
478
|
+
}
|
|
479
|
+
this.state.isPageVisible = isNowVisible;
|
|
480
|
+
if (!wasVisible && isNowVisible) {
|
|
481
|
+
this.checkSessionExpiry();
|
|
482
|
+
this.state.lastActiveTime = Date.now();
|
|
483
|
+
this.state.hasEmittedEngagement = false;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Reset the idle timer.
|
|
488
|
+
* Called on activity and initialization.
|
|
489
|
+
*/
|
|
490
|
+
resetIdleTimer() {
|
|
491
|
+
if (this.state.idleTimeoutId) {
|
|
492
|
+
clearTimeout(this.state.idleTimeoutId);
|
|
493
|
+
}
|
|
494
|
+
this.state.idleTimeoutId = setTimeout(() => {
|
|
495
|
+
this.updateActiveTime();
|
|
496
|
+
this.state.isUserActive = false;
|
|
497
|
+
}, this.idleTimeout);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Start the interval for updating active time.
|
|
501
|
+
*/
|
|
502
|
+
startTimeUpdateInterval() {
|
|
503
|
+
if (this.timeUpdateInterval) return;
|
|
504
|
+
this.timeUpdateInterval = setInterval(() => {
|
|
505
|
+
this.updateActiveTime();
|
|
506
|
+
}, TIME_UPDATE_INTERVAL);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Update accumulated active time.
|
|
510
|
+
* Only accumulates when page is visible AND user is active.
|
|
511
|
+
*/
|
|
512
|
+
updateActiveTime() {
|
|
513
|
+
if (this.state.isPageVisible && this.state.isUserActive) {
|
|
514
|
+
const now = Date.now();
|
|
515
|
+
this.state.activeTimeMs += now - this.state.lastActiveTime;
|
|
516
|
+
this.state.lastActiveTime = now;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Extract path from URL.
|
|
521
|
+
*/
|
|
522
|
+
extractPath(url) {
|
|
523
|
+
try {
|
|
524
|
+
return new URL(url).pathname;
|
|
525
|
+
} catch {
|
|
526
|
+
return "/";
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
var sessionTrackerInstance = null;
|
|
531
|
+
function initSessionTracking(options) {
|
|
532
|
+
if (sessionTrackerInstance) {
|
|
533
|
+
console.warn("[Outlit] Session tracking already initialized");
|
|
534
|
+
return sessionTrackerInstance;
|
|
535
|
+
}
|
|
536
|
+
sessionTrackerInstance = new SessionTracker(options);
|
|
537
|
+
return sessionTrackerInstance;
|
|
538
|
+
}
|
|
539
|
+
function stopSessionTracking() {
|
|
540
|
+
if (sessionTrackerInstance) {
|
|
541
|
+
sessionTrackerInstance.stop();
|
|
542
|
+
sessionTrackerInstance = null;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
118
546
|
// src/storage.ts
|
|
119
547
|
var VISITOR_ID_KEY = "outlit_visitor_id";
|
|
120
548
|
function generateVisitorId() {
|
|
@@ -196,7 +624,7 @@ function setCookie(name, value, days) {
|
|
|
196
624
|
}
|
|
197
625
|
|
|
198
626
|
// src/tracker.ts
|
|
199
|
-
var
|
|
627
|
+
var Outlit = class {
|
|
200
628
|
publicKey;
|
|
201
629
|
apiHost;
|
|
202
630
|
visitorId = null;
|
|
@@ -206,15 +634,29 @@ var Tracker = class {
|
|
|
206
634
|
isInitialized = false;
|
|
207
635
|
isTrackingEnabled = false;
|
|
208
636
|
options;
|
|
637
|
+
hasHandledExit = false;
|
|
638
|
+
sessionTracker = null;
|
|
209
639
|
constructor(options) {
|
|
210
640
|
this.publicKey = options.publicKey;
|
|
211
|
-
this.apiHost = options.apiHost ??
|
|
641
|
+
this.apiHost = options.apiHost ?? import_core3.DEFAULT_API_HOST;
|
|
212
642
|
this.flushInterval = options.flushInterval ?? 5e3;
|
|
213
643
|
this.options = options;
|
|
214
644
|
if (typeof window !== "undefined") {
|
|
215
|
-
|
|
645
|
+
const handleExit = () => {
|
|
646
|
+
if (this.hasHandledExit) return;
|
|
647
|
+
this.hasHandledExit = true;
|
|
648
|
+
this.sessionTracker?.emitEngagement();
|
|
216
649
|
this.flush();
|
|
650
|
+
};
|
|
651
|
+
document.addEventListener("visibilitychange", () => {
|
|
652
|
+
if (document.visibilityState === "hidden") {
|
|
653
|
+
handleExit();
|
|
654
|
+
} else {
|
|
655
|
+
this.hasHandledExit = false;
|
|
656
|
+
}
|
|
217
657
|
});
|
|
658
|
+
window.addEventListener("pagehide", handleExit);
|
|
659
|
+
window.addEventListener("beforeunload", handleExit);
|
|
218
660
|
}
|
|
219
661
|
this.isInitialized = true;
|
|
220
662
|
if (options.autoTrack !== false) {
|
|
@@ -239,12 +681,18 @@ var Tracker = class {
|
|
|
239
681
|
}
|
|
240
682
|
this.visitorId = getOrCreateVisitorId();
|
|
241
683
|
this.startFlushTimer();
|
|
684
|
+
if (this.options.trackEngagement !== false) {
|
|
685
|
+
this.initSessionTracking();
|
|
686
|
+
}
|
|
242
687
|
if (this.options.trackPageviews !== false) {
|
|
243
688
|
this.initPageviewTracking();
|
|
244
689
|
}
|
|
245
690
|
if (this.options.trackForms !== false) {
|
|
246
691
|
this.initFormTracking(this.options.formFieldDenylist);
|
|
247
692
|
}
|
|
693
|
+
if (this.options.trackCalendarEmbeds !== false) {
|
|
694
|
+
this.initCalendarTracking();
|
|
695
|
+
}
|
|
248
696
|
this.isTrackingEnabled = true;
|
|
249
697
|
}
|
|
250
698
|
/**
|
|
@@ -261,7 +709,7 @@ var Tracker = class {
|
|
|
261
709
|
console.warn("[Outlit] Tracking not enabled. Call enableTracking() first.");
|
|
262
710
|
return;
|
|
263
711
|
}
|
|
264
|
-
const event = (0,
|
|
712
|
+
const event = (0, import_core3.buildCustomEvent)({
|
|
265
713
|
url: window.location.href,
|
|
266
714
|
referrer: document.referrer,
|
|
267
715
|
eventName,
|
|
@@ -278,7 +726,7 @@ var Tracker = class {
|
|
|
278
726
|
console.warn("[Outlit] Tracking not enabled. Call enableTracking() first.");
|
|
279
727
|
return;
|
|
280
728
|
}
|
|
281
|
-
const event = (0,
|
|
729
|
+
const event = (0, import_core3.buildIdentifyEvent)({
|
|
282
730
|
url: window.location.href,
|
|
283
731
|
referrer: document.referrer,
|
|
284
732
|
email: options.email,
|
|
@@ -304,7 +752,7 @@ var Tracker = class {
|
|
|
304
752
|
await this.sendEvents(events);
|
|
305
753
|
}
|
|
306
754
|
/**
|
|
307
|
-
* Shutdown the
|
|
755
|
+
* Shutdown the client.
|
|
308
756
|
*/
|
|
309
757
|
async shutdown() {
|
|
310
758
|
if (this.flushTimer) {
|
|
@@ -312,14 +760,26 @@ var Tracker = class {
|
|
|
312
760
|
this.flushTimer = null;
|
|
313
761
|
}
|
|
314
762
|
stopAutocapture();
|
|
763
|
+
stopCalendarTracking();
|
|
764
|
+
stopSessionTracking();
|
|
765
|
+
this.sessionTracker = null;
|
|
315
766
|
await this.flush();
|
|
316
767
|
}
|
|
317
768
|
// ============================================
|
|
318
769
|
// INTERNAL METHODS
|
|
319
770
|
// ============================================
|
|
771
|
+
initSessionTracking() {
|
|
772
|
+
this.sessionTracker = initSessionTracking({
|
|
773
|
+
onEngagement: (event) => {
|
|
774
|
+
this.enqueue(event);
|
|
775
|
+
},
|
|
776
|
+
idleTimeout: this.options.idleTimeout
|
|
777
|
+
});
|
|
778
|
+
}
|
|
320
779
|
initPageviewTracking() {
|
|
321
780
|
initPageviewTracking((url, referrer, title) => {
|
|
322
|
-
|
|
781
|
+
this.sessionTracker?.onNavigation(url);
|
|
782
|
+
const event = (0, import_core3.buildPageviewEvent)({ url, referrer, title });
|
|
323
783
|
this.enqueue(event);
|
|
324
784
|
});
|
|
325
785
|
}
|
|
@@ -336,7 +796,7 @@ var Tracker = class {
|
|
|
336
796
|
} : void 0;
|
|
337
797
|
initFormTracking(
|
|
338
798
|
(url, formId, fields) => {
|
|
339
|
-
const event = (0,
|
|
799
|
+
const event = (0, import_core3.buildFormEvent)({
|
|
340
800
|
url,
|
|
341
801
|
referrer: document.referrer,
|
|
342
802
|
formId,
|
|
@@ -348,6 +808,25 @@ var Tracker = class {
|
|
|
348
808
|
identityCallback2
|
|
349
809
|
);
|
|
350
810
|
}
|
|
811
|
+
initCalendarTracking() {
|
|
812
|
+
initCalendarTracking({
|
|
813
|
+
onCalendarBooked: (bookingEvent) => {
|
|
814
|
+
const event = (0, import_core3.buildCalendarEvent)({
|
|
815
|
+
url: window.location.href,
|
|
816
|
+
referrer: document.referrer,
|
|
817
|
+
provider: bookingEvent.provider,
|
|
818
|
+
eventType: bookingEvent.eventType,
|
|
819
|
+
startTime: bookingEvent.startTime,
|
|
820
|
+
endTime: bookingEvent.endTime,
|
|
821
|
+
duration: bookingEvent.duration,
|
|
822
|
+
isRecurring: bookingEvent.isRecurring,
|
|
823
|
+
inviteeEmail: bookingEvent.inviteeEmail,
|
|
824
|
+
inviteeName: bookingEvent.inviteeName
|
|
825
|
+
});
|
|
826
|
+
this.enqueue(event);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
351
830
|
enqueue(event) {
|
|
352
831
|
this.eventQueue.push(event);
|
|
353
832
|
if (this.eventQueue.length >= 10) {
|
|
@@ -363,7 +842,7 @@ var Tracker = class {
|
|
|
363
842
|
async sendEvents(events) {
|
|
364
843
|
if (events.length === 0) return;
|
|
365
844
|
if (!this.visitorId) return;
|
|
366
|
-
const payload = (0,
|
|
845
|
+
const payload = (0, import_core3.buildIngestPayload)(this.visitorId, "client", events);
|
|
367
846
|
const url = `${this.apiHost}/api/i/v1/${this.publicKey}/events`;
|
|
368
847
|
try {
|
|
369
848
|
if (typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
@@ -388,7 +867,7 @@ var Tracker = class {
|
|
|
388
867
|
// src/react/provider.tsx
|
|
389
868
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
390
869
|
var OutlitContext = (0, import_react.createContext)({
|
|
391
|
-
|
|
870
|
+
outlit: null,
|
|
392
871
|
isInitialized: false,
|
|
393
872
|
isTrackingEnabled: false,
|
|
394
873
|
enableTracking: () => {
|
|
@@ -405,12 +884,12 @@ function OutlitProvider({
|
|
|
405
884
|
autoTrack = true,
|
|
406
885
|
autoIdentify = true
|
|
407
886
|
}) {
|
|
408
|
-
const
|
|
887
|
+
const outlitRef = (0, import_react.useRef)(null);
|
|
409
888
|
const initializedRef = (0, import_react.useRef)(false);
|
|
410
889
|
const [isTrackingEnabled, setIsTrackingEnabled] = (0, import_react.useState)(false);
|
|
411
890
|
(0, import_react.useEffect)(() => {
|
|
412
891
|
if (initializedRef.current) return;
|
|
413
|
-
|
|
892
|
+
outlitRef.current = new Outlit({
|
|
414
893
|
publicKey,
|
|
415
894
|
apiHost,
|
|
416
895
|
trackPageviews,
|
|
@@ -421,9 +900,9 @@ function OutlitProvider({
|
|
|
421
900
|
autoIdentify
|
|
422
901
|
});
|
|
423
902
|
initializedRef.current = true;
|
|
424
|
-
setIsTrackingEnabled(
|
|
903
|
+
setIsTrackingEnabled(outlitRef.current.isEnabled());
|
|
425
904
|
return () => {
|
|
426
|
-
|
|
905
|
+
outlitRef.current?.shutdown();
|
|
427
906
|
};
|
|
428
907
|
}, [
|
|
429
908
|
publicKey,
|
|
@@ -436,8 +915,8 @@ function OutlitProvider({
|
|
|
436
915
|
autoIdentify
|
|
437
916
|
]);
|
|
438
917
|
const enableTracking = (0, import_react.useCallback)(() => {
|
|
439
|
-
if (
|
|
440
|
-
|
|
918
|
+
if (outlitRef.current) {
|
|
919
|
+
outlitRef.current.enableTracking();
|
|
441
920
|
setIsTrackingEnabled(true);
|
|
442
921
|
}
|
|
443
922
|
}, []);
|
|
@@ -445,7 +924,7 @@ function OutlitProvider({
|
|
|
445
924
|
OutlitContext.Provider,
|
|
446
925
|
{
|
|
447
926
|
value: {
|
|
448
|
-
|
|
927
|
+
outlit: outlitRef.current,
|
|
449
928
|
isInitialized: initializedRef.current,
|
|
450
929
|
isTrackingEnabled,
|
|
451
930
|
enableTracking
|
|
@@ -458,31 +937,31 @@ function OutlitProvider({
|
|
|
458
937
|
// src/react/hooks.ts
|
|
459
938
|
var import_react2 = require("react");
|
|
460
939
|
function useOutlit() {
|
|
461
|
-
const {
|
|
940
|
+
const { outlit, isInitialized, isTrackingEnabled, enableTracking } = (0, import_react2.useContext)(OutlitContext);
|
|
462
941
|
const track = (0, import_react2.useCallback)(
|
|
463
942
|
(eventName, properties) => {
|
|
464
|
-
if (!
|
|
465
|
-
console.warn("[Outlit]
|
|
943
|
+
if (!outlit) {
|
|
944
|
+
console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
|
|
466
945
|
return;
|
|
467
946
|
}
|
|
468
|
-
|
|
947
|
+
outlit.track(eventName, properties);
|
|
469
948
|
},
|
|
470
|
-
[
|
|
949
|
+
[outlit]
|
|
471
950
|
);
|
|
472
951
|
const identify = (0, import_react2.useCallback)(
|
|
473
952
|
(options) => {
|
|
474
|
-
if (!
|
|
475
|
-
console.warn("[Outlit]
|
|
953
|
+
if (!outlit) {
|
|
954
|
+
console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
|
|
476
955
|
return;
|
|
477
956
|
}
|
|
478
|
-
|
|
957
|
+
outlit.identify(options);
|
|
479
958
|
},
|
|
480
|
-
[
|
|
959
|
+
[outlit]
|
|
481
960
|
);
|
|
482
961
|
const getVisitorId = (0, import_react2.useCallback)(() => {
|
|
483
|
-
if (!
|
|
484
|
-
return
|
|
485
|
-
}, [
|
|
962
|
+
if (!outlit) return null;
|
|
963
|
+
return outlit.getVisitorId();
|
|
964
|
+
}, [outlit]);
|
|
486
965
|
return {
|
|
487
966
|
track,
|
|
488
967
|
identify,
|