@arthurreira/analytics 0.17.0 → 0.18.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/dist/af-analytics.umd.js +110 -10
- package/dist/client.js +123 -3
- package/package.json +1 -1
package/dist/af-analytics.umd.js
CHANGED
|
@@ -42,6 +42,9 @@ var AfAnalytics = (() => {
|
|
|
42
42
|
});
|
|
43
43
|
var _currentScript = document.currentScript;
|
|
44
44
|
var SESSION_EXPIRY_MS = 30 * 60 * 1e3;
|
|
45
|
+
var IDLE_TOUCH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
46
|
+
var BC_CHANNEL = "af_analytics_session";
|
|
47
|
+
var BC_ADOPT_TIMEOUT_MS = 50;
|
|
45
48
|
function readConfig() {
|
|
46
49
|
var _a, _b, _c, _d;
|
|
47
50
|
const script = _currentScript != null ? _currentScript : document.querySelector("script[data-api-key]");
|
|
@@ -110,6 +113,49 @@ var AfAnalytics = (() => {
|
|
|
110
113
|
page_url: `${window.location.origin}${window.location.pathname}`
|
|
111
114
|
};
|
|
112
115
|
}
|
|
116
|
+
function requestSessionFromPeer() {
|
|
117
|
+
if (typeof BroadcastChannel === "undefined") return Promise.resolve(null);
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
let channel;
|
|
120
|
+
try {
|
|
121
|
+
channel = new BroadcastChannel(BC_CHANNEL);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
resolve(null);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const timer = setTimeout(() => {
|
|
127
|
+
channel.close();
|
|
128
|
+
resolve(null);
|
|
129
|
+
}, BC_ADOPT_TIMEOUT_MS);
|
|
130
|
+
channel.onmessage = (e) => {
|
|
131
|
+
var _a;
|
|
132
|
+
if (((_a = e.data) == null ? void 0 : _a.type) === "session_adopt" && typeof e.data.session_id === "string") {
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
channel.close();
|
|
135
|
+
resolve(e.data.session_id);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
channel.postMessage({ type: "session_claim" });
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function openSessionResponder(sessionId) {
|
|
142
|
+
if (typeof BroadcastChannel === "undefined") return () => {
|
|
143
|
+
};
|
|
144
|
+
let channel;
|
|
145
|
+
try {
|
|
146
|
+
channel = new BroadcastChannel(BC_CHANNEL);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
return () => {
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
channel.onmessage = (e) => {
|
|
152
|
+
var _a;
|
|
153
|
+
if (((_a = e.data) == null ? void 0 : _a.type) === "session_claim") {
|
|
154
|
+
channel.postMessage({ type: "session_adopt", session_id: sessionId });
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
return () => channel.close();
|
|
158
|
+
}
|
|
113
159
|
async function createRemoteSession(apiUrl, apiKey, visitorId) {
|
|
114
160
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
115
161
|
const nav = navigator;
|
|
@@ -151,9 +197,17 @@ var AfAnalytics = (() => {
|
|
|
151
197
|
touchSession();
|
|
152
198
|
return stored;
|
|
153
199
|
}
|
|
200
|
+
const adopted = await requestSessionFromPeer();
|
|
201
|
+
if (adopted) {
|
|
202
|
+
localStorage.setItem("af_session_id", adopted);
|
|
203
|
+
localStorage.setItem("af_session_last_activity", String(Date.now()));
|
|
204
|
+
return adopted;
|
|
205
|
+
}
|
|
154
206
|
const id = await createRemoteSession(apiUrl, apiKey, getVisitorId());
|
|
155
|
-
|
|
156
|
-
|
|
207
|
+
if (id) {
|
|
208
|
+
localStorage.setItem("af_session_id", id);
|
|
209
|
+
localStorage.setItem("af_session_last_activity", String(Date.now()));
|
|
210
|
+
}
|
|
157
211
|
return id;
|
|
158
212
|
}
|
|
159
213
|
function trackPageview(apiUrl, apiKey, sessionId) {
|
|
@@ -188,6 +242,9 @@ var AfAnalytics = (() => {
|
|
|
188
242
|
const pending = [];
|
|
189
243
|
let sessionEnded = false;
|
|
190
244
|
let lastScrollDepth = 0;
|
|
245
|
+
let closeResponder = () => {
|
|
246
|
+
};
|
|
247
|
+
let idleTimer = null;
|
|
191
248
|
function enqueue(fn) {
|
|
192
249
|
if (sessionId) {
|
|
193
250
|
touchSession();
|
|
@@ -196,9 +253,43 @@ var AfAnalytics = (() => {
|
|
|
196
253
|
pending.push(fn);
|
|
197
254
|
}
|
|
198
255
|
}
|
|
199
|
-
|
|
256
|
+
function startSession(id) {
|
|
200
257
|
sessionId = id;
|
|
258
|
+
closeResponder = openSessionResponder(id);
|
|
259
|
+
idleTimer = setInterval(touchSession, IDLE_TOUCH_INTERVAL_MS);
|
|
201
260
|
pending.splice(0).forEach((fn) => fn(id));
|
|
261
|
+
}
|
|
262
|
+
function endSession() {
|
|
263
|
+
if (!sessionId || sessionEnded) return;
|
|
264
|
+
sessionEnded = true;
|
|
265
|
+
if (idleTimer !== null) {
|
|
266
|
+
clearInterval(idleTimer);
|
|
267
|
+
idleTimer = null;
|
|
268
|
+
}
|
|
269
|
+
closeResponder();
|
|
270
|
+
navigator.sendBeacon(`${apiUrl}/sessions/${sessionId}/end`);
|
|
271
|
+
clearSession();
|
|
272
|
+
}
|
|
273
|
+
function restartSession() {
|
|
274
|
+
sessionEnded = false;
|
|
275
|
+
sessionId = null;
|
|
276
|
+
if (idleTimer !== null) {
|
|
277
|
+
clearInterval(idleTimer);
|
|
278
|
+
idleTimer = null;
|
|
279
|
+
}
|
|
280
|
+
closeResponder();
|
|
281
|
+
closeResponder = () => {
|
|
282
|
+
};
|
|
283
|
+
clearSession();
|
|
284
|
+
getOrCreateSession(apiUrl, apiKey).then((id) => {
|
|
285
|
+
if (!id) return;
|
|
286
|
+
startSession(id);
|
|
287
|
+
trackPageview(apiUrl, apiKey, id);
|
|
288
|
+
}).catch(() => {
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
getOrCreateSession(apiUrl, apiKey).then((id) => {
|
|
292
|
+
if (id) startSession(id);
|
|
202
293
|
}).catch(() => {
|
|
203
294
|
});
|
|
204
295
|
enqueue((id) => trackPageview(apiUrl, apiKey, id));
|
|
@@ -263,12 +354,11 @@ var AfAnalytics = (() => {
|
|
|
263
354
|
});
|
|
264
355
|
});
|
|
265
356
|
});
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
};
|
|
357
|
+
window.addEventListener("storage", (e) => {
|
|
358
|
+
if (e.key === "af_session_id" && e.newValue === null && e.oldValue !== null) {
|
|
359
|
+
restartSession();
|
|
360
|
+
}
|
|
361
|
+
});
|
|
272
362
|
let visHideTimer = null;
|
|
273
363
|
document.addEventListener("visibilitychange", () => {
|
|
274
364
|
if (document.visibilityState === "hidden") {
|
|
@@ -280,7 +370,17 @@ var AfAnalytics = (() => {
|
|
|
280
370
|
}
|
|
281
371
|
}
|
|
282
372
|
});
|
|
283
|
-
window.addEventListener("pagehide",
|
|
373
|
+
window.addEventListener("pagehide", (e) => {
|
|
374
|
+
if (!e.persisted) endSession();
|
|
375
|
+
});
|
|
376
|
+
window.addEventListener("pageshow", (e) => {
|
|
377
|
+
if (!e.persisted) return;
|
|
378
|
+
if (sessionEnded || !sessionId) {
|
|
379
|
+
restartSession();
|
|
380
|
+
} else {
|
|
381
|
+
trackPageview(apiUrl, apiKey, sessionId);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
284
384
|
window.addEventListener("beforeunload", endSession);
|
|
285
385
|
}
|
|
286
386
|
if (document.readyState === "loading") {
|
package/dist/client.js
CHANGED
|
@@ -216,7 +216,51 @@ function connectPresence(apiKey, sessionId, wsUrl = DEFAULT_WS_URL) {
|
|
|
216
216
|
|
|
217
217
|
// src/hooks/useAnalytics.ts
|
|
218
218
|
var SESSION_EXPIRY_MINUTES = 30;
|
|
219
|
+
var IDLE_TOUCH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
220
|
+
var BC_CHANNEL = "af_analytics_session";
|
|
221
|
+
var BC_ADOPT_TIMEOUT_MS = 50;
|
|
219
222
|
var _sessionFlight = null;
|
|
223
|
+
function requestSessionFromPeer() {
|
|
224
|
+
if (typeof BroadcastChannel === "undefined") return Promise.resolve(null);
|
|
225
|
+
return new Promise((resolve) => {
|
|
226
|
+
let channel;
|
|
227
|
+
try {
|
|
228
|
+
channel = new BroadcastChannel(BC_CHANNEL);
|
|
229
|
+
} catch {
|
|
230
|
+
resolve(null);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const timer = setTimeout(() => {
|
|
234
|
+
channel.close();
|
|
235
|
+
resolve(null);
|
|
236
|
+
}, BC_ADOPT_TIMEOUT_MS);
|
|
237
|
+
channel.onmessage = (e) => {
|
|
238
|
+
if (e.data?.type === "session_adopt" && typeof e.data.session_id === "string") {
|
|
239
|
+
clearTimeout(timer);
|
|
240
|
+
channel.close();
|
|
241
|
+
resolve(e.data.session_id);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
channel.postMessage({ type: "session_claim" });
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function openSessionResponder(sessionId) {
|
|
248
|
+
if (typeof BroadcastChannel === "undefined") return () => {
|
|
249
|
+
};
|
|
250
|
+
let channel;
|
|
251
|
+
try {
|
|
252
|
+
channel = new BroadcastChannel(BC_CHANNEL);
|
|
253
|
+
} catch {
|
|
254
|
+
return () => {
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
channel.onmessage = (e) => {
|
|
258
|
+
if (e.data?.type === "session_claim") {
|
|
259
|
+
channel.postMessage({ type: "session_adopt", session_id: sessionId });
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
return () => channel.close();
|
|
263
|
+
}
|
|
220
264
|
async function getOrCreateSession(apiUrl, apiKey, visitorId) {
|
|
221
265
|
const storedSessionId = localStorage.getItem("af_session_id");
|
|
222
266
|
const lastActivity = localStorage.getItem("af_session_last_activity");
|
|
@@ -231,11 +275,20 @@ async function getOrCreateSession(apiUrl, apiKey, visitorId) {
|
|
|
231
275
|
localStorage.removeItem("af_session_last_activity");
|
|
232
276
|
}
|
|
233
277
|
if (_sessionFlight) return _sessionFlight;
|
|
234
|
-
_sessionFlight =
|
|
278
|
+
_sessionFlight = (async () => {
|
|
279
|
+
const adopted = await requestSessionFromPeer();
|
|
280
|
+
if (adopted) {
|
|
281
|
+
localStorage.setItem("af_session_id", adopted);
|
|
282
|
+
localStorage.setItem("af_session_last_activity", String(Date.now()));
|
|
283
|
+
return adopted;
|
|
284
|
+
}
|
|
285
|
+
const id = await createSession(apiUrl, apiKey, visitorId);
|
|
235
286
|
if (id) {
|
|
236
287
|
localStorage.setItem("af_session_id", id);
|
|
237
288
|
localStorage.setItem("af_session_last_activity", String(Date.now()));
|
|
238
289
|
}
|
|
290
|
+
return id;
|
|
291
|
+
})().then((id) => {
|
|
239
292
|
_sessionFlight = null;
|
|
240
293
|
return id;
|
|
241
294
|
}).catch((err) => {
|
|
@@ -249,6 +302,13 @@ function useAnalytics(apiUrl, apiKey, currentPath, wsUrl) {
|
|
|
249
302
|
const pendingEvents = useRef([]);
|
|
250
303
|
const hasSentEnd = useRef(false);
|
|
251
304
|
const presenceRef = useRef(null);
|
|
305
|
+
const closeResponderRef = useRef(() => {
|
|
306
|
+
});
|
|
307
|
+
const idleTimerRef = useRef(null);
|
|
308
|
+
const currentPathRef = useRef(currentPath);
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
currentPathRef.current = currentPath;
|
|
311
|
+
}, [currentPath]);
|
|
252
312
|
useEffect(() => {
|
|
253
313
|
if (typeof window === "undefined") return;
|
|
254
314
|
const visitor_id = localStorage.getItem("af_analytics_visitor_id") || crypto.randomUUID();
|
|
@@ -257,6 +317,12 @@ function useAnalytics(apiUrl, apiKey, currentPath, wsUrl) {
|
|
|
257
317
|
getOrCreateSession(apiUrl, apiKey, visitor_id).then((id) => {
|
|
258
318
|
if (cancelled || !id) return;
|
|
259
319
|
sessionId.current = id;
|
|
320
|
+
closeResponderRef.current = openSessionResponder(id);
|
|
321
|
+
idleTimerRef.current = setInterval(() => {
|
|
322
|
+
if (!hasSentEnd.current) {
|
|
323
|
+
localStorage.setItem("af_session_last_activity", String(Date.now()));
|
|
324
|
+
}
|
|
325
|
+
}, IDLE_TOUCH_INTERVAL_MS);
|
|
260
326
|
if (wsUrl) {
|
|
261
327
|
presenceRef.current = connectPresence(apiKey, id, wsUrl);
|
|
262
328
|
}
|
|
@@ -272,11 +338,61 @@ function useAnalytics(apiUrl, apiKey, currentPath, wsUrl) {
|
|
|
272
338
|
const sendEnd = () => {
|
|
273
339
|
if (!sessionId.current || hasSentEnd.current) return;
|
|
274
340
|
hasSentEnd.current = true;
|
|
341
|
+
if (idleTimerRef.current !== null) {
|
|
342
|
+
clearInterval(idleTimerRef.current);
|
|
343
|
+
idleTimerRef.current = null;
|
|
344
|
+
}
|
|
345
|
+
closeResponderRef.current();
|
|
275
346
|
presenceRef.current?.disconnect();
|
|
347
|
+
presenceRef.current = null;
|
|
276
348
|
navigator.sendBeacon(`${apiUrl}/sessions/${sessionId.current}/end`);
|
|
277
349
|
localStorage.removeItem("af_session_id");
|
|
278
350
|
localStorage.removeItem("af_session_last_activity");
|
|
279
351
|
};
|
|
352
|
+
const restartSession = () => {
|
|
353
|
+
sessionId.current = null;
|
|
354
|
+
hasSentEnd.current = false;
|
|
355
|
+
if (idleTimerRef.current !== null) {
|
|
356
|
+
clearInterval(idleTimerRef.current);
|
|
357
|
+
idleTimerRef.current = null;
|
|
358
|
+
}
|
|
359
|
+
closeResponderRef.current();
|
|
360
|
+
closeResponderRef.current = () => {
|
|
361
|
+
};
|
|
362
|
+
presenceRef.current?.disconnect();
|
|
363
|
+
presenceRef.current = null;
|
|
364
|
+
const visitor_id = localStorage.getItem("af_analytics_visitor_id") || crypto.randomUUID();
|
|
365
|
+
getOrCreateSession(apiUrl, apiKey, visitor_id).then((id) => {
|
|
366
|
+
if (!id) return;
|
|
367
|
+
sessionId.current = id;
|
|
368
|
+
closeResponderRef.current = openSessionResponder(id);
|
|
369
|
+
idleTimerRef.current = setInterval(() => {
|
|
370
|
+
if (!hasSentEnd.current) {
|
|
371
|
+
localStorage.setItem("af_session_last_activity", String(Date.now()));
|
|
372
|
+
}
|
|
373
|
+
}, IDLE_TOUCH_INTERVAL_MS);
|
|
374
|
+
if (wsUrl) {
|
|
375
|
+
presenceRef.current = connectPresence(apiKey, id, wsUrl);
|
|
376
|
+
}
|
|
377
|
+
trackPageview(apiUrl, apiKey, id, currentPathRef.current);
|
|
378
|
+
});
|
|
379
|
+
};
|
|
380
|
+
const handleStorage = (e) => {
|
|
381
|
+
if (e.key === "af_session_id" && e.newValue === null && e.oldValue !== null) {
|
|
382
|
+
restartSession();
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
const handlePageHide = (e) => {
|
|
386
|
+
if (!e.persisted) sendEnd();
|
|
387
|
+
};
|
|
388
|
+
const handlePageShow = (e) => {
|
|
389
|
+
if (!e.persisted) return;
|
|
390
|
+
if (hasSentEnd.current || !sessionId.current) {
|
|
391
|
+
restartSession();
|
|
392
|
+
} else {
|
|
393
|
+
trackPageview(apiUrl, apiKey, sessionId.current, currentPathRef.current);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
280
396
|
let visHideTimer = null;
|
|
281
397
|
const handleVisibility = () => {
|
|
282
398
|
if (document.visibilityState === "hidden") {
|
|
@@ -288,13 +404,17 @@ function useAnalytics(apiUrl, apiKey, currentPath, wsUrl) {
|
|
|
288
404
|
}
|
|
289
405
|
}
|
|
290
406
|
};
|
|
407
|
+
window.addEventListener("storage", handleStorage);
|
|
291
408
|
document.addEventListener("visibilitychange", handleVisibility);
|
|
292
|
-
window.addEventListener("pagehide",
|
|
409
|
+
window.addEventListener("pagehide", handlePageHide);
|
|
410
|
+
window.addEventListener("pageshow", handlePageShow);
|
|
293
411
|
window.addEventListener("beforeunload", sendEnd);
|
|
294
412
|
return () => {
|
|
295
413
|
if (visHideTimer !== null) clearTimeout(visHideTimer);
|
|
414
|
+
window.removeEventListener("storage", handleStorage);
|
|
296
415
|
document.removeEventListener("visibilitychange", handleVisibility);
|
|
297
|
-
window.removeEventListener("pagehide",
|
|
416
|
+
window.removeEventListener("pagehide", handlePageHide);
|
|
417
|
+
window.removeEventListener("pageshow", handlePageShow);
|
|
298
418
|
window.removeEventListener("beforeunload", sendEnd);
|
|
299
419
|
};
|
|
300
420
|
}, [apiUrl]);
|