userlens-session-recorder 2.0.2 → 2.2.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/index.cjs.js +329 -106
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.esm.js +329 -106
- package/dist/index.esm.js.map +1 -1
- package/dist/sessionManager.d.ts +61 -0
- package/dist/storage.d.ts +10 -0
- package/dist/types/index.d.ts +26 -3
- package/dist/userlens-session-recorder.umd.js +329 -106
- package/dist/userlens-session-recorder.umd.js.map +1 -1
- package/dist/utils.d.ts +1 -5
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -12188,6 +12188,8 @@ var n$1;
|
|
|
12188
12188
|
!function(t2) {
|
|
12189
12189
|
t2[t2.NotStarted = 0] = "NotStarted", t2[t2.Running = 1] = "Running", t2[t2.Stopped = 2] = "Stopped";
|
|
12190
12190
|
}(n$1 || (n$1 = {}));
|
|
12191
|
+
const { addCustomEvent } = record;
|
|
12192
|
+
const { freezePage } = record;
|
|
12191
12193
|
const { takeFullSnapshot } = record;
|
|
12192
12194
|
|
|
12193
12195
|
var __defProp = Object.defineProperty;
|
|
@@ -20592,7 +20594,7 @@ function generateUuid() {
|
|
|
20592
20594
|
function saveWriteCode(writeCode) {
|
|
20593
20595
|
window.localStorage.setItem("$ul_WRITE_CODE", btoa(`${writeCode}:`));
|
|
20594
20596
|
}
|
|
20595
|
-
|
|
20597
|
+
function getWriteCode() {
|
|
20596
20598
|
try {
|
|
20597
20599
|
const raw = window.localStorage.getItem("$ul_WRITE_CODE");
|
|
20598
20600
|
if (raw == null)
|
|
@@ -20608,37 +20610,6 @@ const getWriteCode = () => {
|
|
|
20608
20610
|
catch {
|
|
20609
20611
|
return null;
|
|
20610
20612
|
}
|
|
20611
|
-
};
|
|
20612
|
-
const STATE_KEY = "userlensSessionRec";
|
|
20613
|
-
function readSessionState() {
|
|
20614
|
-
try {
|
|
20615
|
-
const raw = window.sessionStorage.getItem(STATE_KEY);
|
|
20616
|
-
if (!raw)
|
|
20617
|
-
return null;
|
|
20618
|
-
const parsed = JSON.parse(raw);
|
|
20619
|
-
if (typeof parsed !== "object" || parsed === null ||
|
|
20620
|
-
typeof parsed.session_uuid !== "string" ||
|
|
20621
|
-
typeof parsed.last_active !== "number" ||
|
|
20622
|
-
typeof parsed.chunk_seq !== "number" ||
|
|
20623
|
-
typeof parsed.initial_url !== "string")
|
|
20624
|
-
return null;
|
|
20625
|
-
return parsed;
|
|
20626
|
-
}
|
|
20627
|
-
catch {
|
|
20628
|
-
return null;
|
|
20629
|
-
}
|
|
20630
|
-
}
|
|
20631
|
-
function writeSessionState(state) {
|
|
20632
|
-
try {
|
|
20633
|
-
window.sessionStorage.setItem(STATE_KEY, JSON.stringify(state));
|
|
20634
|
-
}
|
|
20635
|
-
catch { }
|
|
20636
|
-
}
|
|
20637
|
-
function clearSessionState() {
|
|
20638
|
-
try {
|
|
20639
|
-
window.sessionStorage.removeItem(STATE_KEY);
|
|
20640
|
-
}
|
|
20641
|
-
catch { }
|
|
20642
20613
|
}
|
|
20643
20614
|
|
|
20644
20615
|
const INGEST_BASE_URL = "https://ul-ingest.userlens.io";
|
|
@@ -20650,15 +20621,13 @@ const uploadSessionEvents = async (args) => {
|
|
|
20650
20621
|
}
|
|
20651
20622
|
const body = {
|
|
20652
20623
|
session_uuid: args.session_uuid,
|
|
20624
|
+
window_id: args.window_id,
|
|
20653
20625
|
chunk_seq: args.chunk_seq,
|
|
20654
20626
|
chunk_start_ts: args.chunk_start_ts,
|
|
20655
20627
|
chunk_end_ts: args.chunk_end_ts,
|
|
20656
20628
|
user_id: args.user_id,
|
|
20657
20629
|
events: args.events,
|
|
20658
20630
|
};
|
|
20659
|
-
if (args.initial_url !== undefined) {
|
|
20660
|
-
body.initial_url = args.initial_url;
|
|
20661
|
-
}
|
|
20662
20631
|
const res = await fetch(`${INGEST_BASE_URL}/session-recording`, {
|
|
20663
20632
|
method: "POST",
|
|
20664
20633
|
headers: {
|
|
@@ -20673,7 +20642,224 @@ const uploadSessionEvents = async (args) => {
|
|
|
20673
20642
|
return "ok";
|
|
20674
20643
|
};
|
|
20675
20644
|
|
|
20676
|
-
|
|
20645
|
+
const SHARED_KEY = "userlensSession";
|
|
20646
|
+
const WINDOW_KEY = "userlensWindow";
|
|
20647
|
+
// Set in sessionStorage on construct, cleared on pagehide. If we read it on
|
|
20648
|
+
// init and it's already set, sessionStorage was cloned (Cmd+click, duplicate,
|
|
20649
|
+
// window.open) — discard inherited window_id and mint a fresh one.
|
|
20650
|
+
const PRIMARY_WINDOW_KEY = "userlensPrimaryWindow";
|
|
20651
|
+
function readSharedSessionState() {
|
|
20652
|
+
try {
|
|
20653
|
+
const raw = window.localStorage.getItem(SHARED_KEY);
|
|
20654
|
+
if (!raw)
|
|
20655
|
+
return null;
|
|
20656
|
+
const parsed = JSON.parse(raw);
|
|
20657
|
+
if (typeof parsed !== "object" || parsed === null ||
|
|
20658
|
+
typeof parsed.session_uuid !== "string" ||
|
|
20659
|
+
typeof parsed.last_active !== "number")
|
|
20660
|
+
return null;
|
|
20661
|
+
return parsed;
|
|
20662
|
+
}
|
|
20663
|
+
catch {
|
|
20664
|
+
return null;
|
|
20665
|
+
}
|
|
20666
|
+
}
|
|
20667
|
+
function writeSharedSessionState(state) {
|
|
20668
|
+
try {
|
|
20669
|
+
window.localStorage.setItem(SHARED_KEY, JSON.stringify(state));
|
|
20670
|
+
}
|
|
20671
|
+
catch { }
|
|
20672
|
+
}
|
|
20673
|
+
function clearSharedSessionState() {
|
|
20674
|
+
try {
|
|
20675
|
+
window.localStorage.removeItem(SHARED_KEY);
|
|
20676
|
+
}
|
|
20677
|
+
catch { }
|
|
20678
|
+
}
|
|
20679
|
+
function readWindowState() {
|
|
20680
|
+
try {
|
|
20681
|
+
const raw = window.sessionStorage.getItem(WINDOW_KEY);
|
|
20682
|
+
if (!raw)
|
|
20683
|
+
return null;
|
|
20684
|
+
const parsed = JSON.parse(raw);
|
|
20685
|
+
if (typeof parsed !== "object" || parsed === null ||
|
|
20686
|
+
typeof parsed.window_id !== "string" ||
|
|
20687
|
+
typeof parsed.chunk_seq !== "number")
|
|
20688
|
+
return null;
|
|
20689
|
+
return parsed;
|
|
20690
|
+
}
|
|
20691
|
+
catch {
|
|
20692
|
+
return null;
|
|
20693
|
+
}
|
|
20694
|
+
}
|
|
20695
|
+
function writeWindowState(state) {
|
|
20696
|
+
try {
|
|
20697
|
+
window.sessionStorage.setItem(WINDOW_KEY, JSON.stringify(state));
|
|
20698
|
+
}
|
|
20699
|
+
catch { }
|
|
20700
|
+
}
|
|
20701
|
+
function clearWindowState() {
|
|
20702
|
+
try {
|
|
20703
|
+
window.sessionStorage.removeItem(WINDOW_KEY);
|
|
20704
|
+
}
|
|
20705
|
+
catch { }
|
|
20706
|
+
}
|
|
20707
|
+
function wasPrimaryWindowFlagAlreadySet() {
|
|
20708
|
+
try {
|
|
20709
|
+
return window.sessionStorage.getItem(PRIMARY_WINDOW_KEY) === "1";
|
|
20710
|
+
}
|
|
20711
|
+
catch {
|
|
20712
|
+
return false;
|
|
20713
|
+
}
|
|
20714
|
+
}
|
|
20715
|
+
function markPrimaryWindow() {
|
|
20716
|
+
try {
|
|
20717
|
+
window.sessionStorage.setItem(PRIMARY_WINDOW_KEY, "1");
|
|
20718
|
+
}
|
|
20719
|
+
catch { }
|
|
20720
|
+
}
|
|
20721
|
+
function clearPrimaryWindowFlag() {
|
|
20722
|
+
try {
|
|
20723
|
+
window.sessionStorage.removeItem(PRIMARY_WINDOW_KEY);
|
|
20724
|
+
}
|
|
20725
|
+
catch { }
|
|
20726
|
+
}
|
|
20727
|
+
|
|
20728
|
+
var _SessionManager_instances, _SessionManager_initWindow, _SessionManager_joinOrStartSession, _SessionManager_startFresh;
|
|
20729
|
+
/**
|
|
20730
|
+
* Owns the (session_uuid, window_id, chunk_seq, last_active) lifecycle.
|
|
20731
|
+
*
|
|
20732
|
+
* Two storage scopes:
|
|
20733
|
+
* - shared (localStorage): session_uuid + last_active — visible to every tab
|
|
20734
|
+
* of the same origin so tabs can adopt one logical session and a single
|
|
20735
|
+
* idle timeout governs the whole user.
|
|
20736
|
+
* - per-tab (sessionStorage): window_id + chunk_seq — independent per tab so
|
|
20737
|
+
* concurrent tabs can upload chunks without colliding on chunk_seq.
|
|
20738
|
+
*
|
|
20739
|
+
* The recorder talks to this class instead of touching storage directly.
|
|
20740
|
+
*/
|
|
20741
|
+
class SessionManager {
|
|
20742
|
+
constructor(timeoutMs) {
|
|
20743
|
+
_SessionManager_instances.add(this);
|
|
20744
|
+
this.timeoutMs = timeoutMs;
|
|
20745
|
+
}
|
|
20746
|
+
initialize() {
|
|
20747
|
+
__classPrivateFieldGet(this, _SessionManager_instances, "m", _SessionManager_initWindow).call(this);
|
|
20748
|
+
__classPrivateFieldGet(this, _SessionManager_instances, "m", _SessionManager_joinOrStartSession).call(this);
|
|
20749
|
+
}
|
|
20750
|
+
get sessionUuid() {
|
|
20751
|
+
return this._sessionUuid;
|
|
20752
|
+
}
|
|
20753
|
+
get windowId() {
|
|
20754
|
+
return this._windowId;
|
|
20755
|
+
}
|
|
20756
|
+
/**
|
|
20757
|
+
* Reconciles in-memory session_uuid with whatever shared storage says.
|
|
20758
|
+
* Called on every event so cross-tab session changes are picked up quickly.
|
|
20759
|
+
*
|
|
20760
|
+
* Returns:
|
|
20761
|
+
* "expired" — shared session has been idle past timeout; we rotated.
|
|
20762
|
+
* "rotated" — another tab rotated the session_uuid; we adopted it.
|
|
20763
|
+
* "none" — no change, business as usual.
|
|
20764
|
+
*
|
|
20765
|
+
* Caller takes a fresh full snapshot on "expired" or "rotated" so this
|
|
20766
|
+
* window's first chunk in the new session is replayable on its own.
|
|
20767
|
+
*/
|
|
20768
|
+
syncWithSharedState(now) {
|
|
20769
|
+
const shared = readSharedSessionState();
|
|
20770
|
+
if (shared && now - shared.last_active > this.timeoutMs) {
|
|
20771
|
+
__classPrivateFieldGet(this, _SessionManager_instances, "m", _SessionManager_startFresh).call(this, now);
|
|
20772
|
+
return "expired";
|
|
20773
|
+
}
|
|
20774
|
+
if (shared && shared.session_uuid !== this._sessionUuid) {
|
|
20775
|
+
this._sessionUuid = shared.session_uuid;
|
|
20776
|
+
writeWindowState({ window_id: this._windowId, chunk_seq: 0 });
|
|
20777
|
+
return "rotated";
|
|
20778
|
+
}
|
|
20779
|
+
return "none";
|
|
20780
|
+
}
|
|
20781
|
+
/** Bump shared last_active. Call only on real user interactions. */
|
|
20782
|
+
bumpActivity(now) {
|
|
20783
|
+
const latest = readSharedSessionState();
|
|
20784
|
+
if (latest) {
|
|
20785
|
+
writeSharedSessionState({ ...latest, last_active: now });
|
|
20786
|
+
}
|
|
20787
|
+
}
|
|
20788
|
+
/** Read this window's current chunk_seq for a pending upload. */
|
|
20789
|
+
currentChunkSeq() {
|
|
20790
|
+
var _a, _b;
|
|
20791
|
+
return (_b = (_a = readWindowState()) === null || _a === void 0 ? void 0 : _a.chunk_seq) !== null && _b !== void 0 ? _b : null;
|
|
20792
|
+
}
|
|
20793
|
+
/**
|
|
20794
|
+
* Mark a chunk_seq as committed after a successful upload. No-ops if the
|
|
20795
|
+
* stored seq has moved on (e.g. a session rotation reset it to 0 mid-flight).
|
|
20796
|
+
*/
|
|
20797
|
+
commitChunkSeq(uploadedSeq) {
|
|
20798
|
+
const after = readWindowState();
|
|
20799
|
+
if (after && after.window_id === this._windowId && after.chunk_seq === uploadedSeq) {
|
|
20800
|
+
writeWindowState({ window_id: this._windowId, chunk_seq: uploadedSeq + 1 });
|
|
20801
|
+
}
|
|
20802
|
+
}
|
|
20803
|
+
/**
|
|
20804
|
+
* Reserve and return the current chunk_seq for a pagehide flush, advancing
|
|
20805
|
+
* storage so the next normal upload uses seq+1. Returns null if window state
|
|
20806
|
+
* is missing.
|
|
20807
|
+
*/
|
|
20808
|
+
reserveChunkSeq() {
|
|
20809
|
+
const win = readWindowState();
|
|
20810
|
+
if (!win)
|
|
20811
|
+
return null;
|
|
20812
|
+
writeWindowState({ window_id: this._windowId, chunk_seq: win.chunk_seq + 1 });
|
|
20813
|
+
return win.chunk_seq;
|
|
20814
|
+
}
|
|
20815
|
+
/** Hard reset: wipe shared session and start a fresh one. */
|
|
20816
|
+
reset() {
|
|
20817
|
+
clearSharedSessionState();
|
|
20818
|
+
__classPrivateFieldGet(this, _SessionManager_instances, "m", _SessionManager_joinOrStartSession).call(this);
|
|
20819
|
+
}
|
|
20820
|
+
/**
|
|
20821
|
+
* Called from pagehide. Lets a future reload of the same tab reuse the
|
|
20822
|
+
* window_id (because the flag is gone), while a cloned tab still sees the
|
|
20823
|
+
* flag set in its inherited sessionStorage and treats itself as a duplicate.
|
|
20824
|
+
*/
|
|
20825
|
+
notifyPageHide() {
|
|
20826
|
+
clearPrimaryWindowFlag();
|
|
20827
|
+
}
|
|
20828
|
+
/** Full teardown — call on stop(). */
|
|
20829
|
+
dispose() {
|
|
20830
|
+
clearSharedSessionState();
|
|
20831
|
+
clearWindowState();
|
|
20832
|
+
clearPrimaryWindowFlag();
|
|
20833
|
+
}
|
|
20834
|
+
}
|
|
20835
|
+
_SessionManager_instances = new WeakSet(), _SessionManager_initWindow = function _SessionManager_initWindow() {
|
|
20836
|
+
const cloned = wasPrimaryWindowFlagAlreadySet();
|
|
20837
|
+
const existing = readWindowState();
|
|
20838
|
+
if (existing && !cloned) {
|
|
20839
|
+
this._windowId = existing.window_id;
|
|
20840
|
+
}
|
|
20841
|
+
else {
|
|
20842
|
+
this._windowId = generateUuid();
|
|
20843
|
+
writeWindowState({ window_id: this._windowId, chunk_seq: 0 });
|
|
20844
|
+
}
|
|
20845
|
+
markPrimaryWindow();
|
|
20846
|
+
}, _SessionManager_joinOrStartSession = function _SessionManager_joinOrStartSession() {
|
|
20847
|
+
const now = Date.now();
|
|
20848
|
+
const shared = readSharedSessionState();
|
|
20849
|
+
if (!shared || now - shared.last_active > this.timeoutMs) {
|
|
20850
|
+
__classPrivateFieldGet(this, _SessionManager_instances, "m", _SessionManager_startFresh).call(this, now);
|
|
20851
|
+
}
|
|
20852
|
+
else {
|
|
20853
|
+
this._sessionUuid = shared.session_uuid;
|
|
20854
|
+
writeSharedSessionState({ ...shared, last_active: now });
|
|
20855
|
+
}
|
|
20856
|
+
}, _SessionManager_startFresh = function _SessionManager_startFresh(now) {
|
|
20857
|
+
this._sessionUuid = generateUuid();
|
|
20858
|
+
writeSharedSessionState({ session_uuid: this._sessionUuid, last_active: now });
|
|
20859
|
+
writeWindowState({ window_id: this._windowId, chunk_seq: 0 });
|
|
20860
|
+
};
|
|
20861
|
+
|
|
20862
|
+
var _SessionRecorder_instances, _SessionRecorder_uploading, _SessionRecorder_uploadingMaxTs, _SessionRecorder_bufferTimer, _SessionRecorder_log, _SessionRecorder_initRecorder, _SessionRecorder_isUserInteraction, _SessionRecorder_handleEvent, _SessionRecorder_armBufferTimer, _SessionRecorder_flushBuffer, _SessionRecorder_handlePageHide, _SessionRecorder_handleVisibilityChange, _SessionRecorder_initListeners, _SessionRecorder_trackEvents, _SessionRecorder_clearEvents;
|
|
20677
20863
|
const BUFFER_TIMEOUT_MS = 2000;
|
|
20678
20864
|
const MAX_BUFFER_BYTES = 900 * 1024;
|
|
20679
20865
|
function estimateEventSize(event) {
|
|
@@ -20697,6 +20883,7 @@ class SessionRecorder {
|
|
|
20697
20883
|
_SessionRecorder_bufferTimer.set(this, null);
|
|
20698
20884
|
_SessionRecorder_handlePageHide.set(this, () => {
|
|
20699
20885
|
try {
|
|
20886
|
+
this.sessionManager.notifyPageHide();
|
|
20700
20887
|
if (this.sessionEvents.length === 0)
|
|
20701
20888
|
return;
|
|
20702
20889
|
let events;
|
|
@@ -20708,23 +20895,41 @@ class SessionRecorder {
|
|
|
20708
20895
|
}
|
|
20709
20896
|
if (events.length === 0)
|
|
20710
20897
|
return;
|
|
20711
|
-
const
|
|
20712
|
-
if (
|
|
20898
|
+
const chunk_seq = this.sessionManager.reserveChunkSeq();
|
|
20899
|
+
if (chunk_seq === null)
|
|
20713
20900
|
return;
|
|
20714
|
-
const chunk_seq = __classPrivateFieldGet(this, _SessionRecorder_uploading, "f") ? state.chunk_seq + 1 : state.chunk_seq;
|
|
20715
|
-
writeSessionState({ ...state, chunk_seq: chunk_seq + 1 });
|
|
20716
20901
|
const start_ts_ms = events[0].timestamp;
|
|
20717
20902
|
const end_ts_ms = events[events.length - 1].timestamp;
|
|
20718
|
-
|
|
20719
|
-
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
|
|
20723
|
-
|
|
20724
|
-
|
|
20725
|
-
|
|
20726
|
-
|
|
20727
|
-
|
|
20903
|
+
const chunk_start_ts = new Date(start_ts_ms).toISOString();
|
|
20904
|
+
const chunk_end_ts = new Date(end_ts_ms).toISOString();
|
|
20905
|
+
if (this.mode === "manual") {
|
|
20906
|
+
try {
|
|
20907
|
+
void this.onEvents({
|
|
20908
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
20909
|
+
window_id: this.sessionManager.windowId,
|
|
20910
|
+
chunk_seq,
|
|
20911
|
+
chunk_start_ts,
|
|
20912
|
+
chunk_end_ts,
|
|
20913
|
+
events,
|
|
20914
|
+
keepalive: true,
|
|
20915
|
+
});
|
|
20916
|
+
}
|
|
20917
|
+
catch {
|
|
20918
|
+
// ignore
|
|
20919
|
+
}
|
|
20920
|
+
}
|
|
20921
|
+
else {
|
|
20922
|
+
uploadSessionEvents({
|
|
20923
|
+
user_id: this.userId,
|
|
20924
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
20925
|
+
window_id: this.sessionManager.windowId,
|
|
20926
|
+
chunk_seq,
|
|
20927
|
+
chunk_start_ts,
|
|
20928
|
+
chunk_end_ts,
|
|
20929
|
+
events,
|
|
20930
|
+
keepalive: true,
|
|
20931
|
+
}).catch(() => { });
|
|
20932
|
+
}
|
|
20728
20933
|
this.sessionEvents = [];
|
|
20729
20934
|
this.bufferBytes = 0;
|
|
20730
20935
|
}
|
|
@@ -20732,6 +20937,19 @@ class SessionRecorder {
|
|
|
20732
20937
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Page hide handling failed", err);
|
|
20733
20938
|
}
|
|
20734
20939
|
});
|
|
20940
|
+
// Player groups multi-tab recordings into segments by which tab was visible
|
|
20941
|
+
// when. Without these markers it has to guess from chunk timestamps and
|
|
20942
|
+
// active-source heuristics, which is unreliable when two tabs overlap.
|
|
20943
|
+
_SessionRecorder_handleVisibilityChange.set(this, () => {
|
|
20944
|
+
try {
|
|
20945
|
+
if (typeof document === "undefined" || !document.visibilityState)
|
|
20946
|
+
return;
|
|
20947
|
+
record.addCustomEvent("window " + document.visibilityState, {});
|
|
20948
|
+
}
|
|
20949
|
+
catch (err) {
|
|
20950
|
+
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Visibility change handling failed", err);
|
|
20951
|
+
}
|
|
20952
|
+
});
|
|
20735
20953
|
try {
|
|
20736
20954
|
if (typeof window === "undefined")
|
|
20737
20955
|
return;
|
|
@@ -20753,11 +20971,22 @@ class SessionRecorder {
|
|
|
20753
20971
|
sessionStorage.setItem(testKey, "1");
|
|
20754
20972
|
sessionStorage.removeItem(testKey);
|
|
20755
20973
|
this.debug = (_a = config.debug) !== null && _a !== void 0 ? _a : false;
|
|
20756
|
-
if (
|
|
20757
|
-
|
|
20974
|
+
if (config.mode === "manual") {
|
|
20975
|
+
this.mode = "manual";
|
|
20976
|
+
if (!config.onEvents || typeof config.onEvents !== "function") {
|
|
20977
|
+
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "onEvents callback is required in manual mode");
|
|
20978
|
+
return;
|
|
20979
|
+
}
|
|
20980
|
+
this.onEvents = config.onEvents;
|
|
20981
|
+
}
|
|
20982
|
+
else {
|
|
20983
|
+
this.mode = "auto";
|
|
20984
|
+
if (!((_b = config.WRITE_CODE) === null || _b === void 0 ? void 0 : _b.trim()) || !((_c = config.userId) === null || _c === void 0 ? void 0 : _c.trim())) {
|
|
20985
|
+
return;
|
|
20986
|
+
}
|
|
20987
|
+
saveWriteCode(config.WRITE_CODE);
|
|
20988
|
+
this.userId = config.userId;
|
|
20758
20989
|
}
|
|
20759
|
-
saveWriteCode(config.WRITE_CODE);
|
|
20760
|
-
this.userId = config.userId;
|
|
20761
20990
|
const { recordingOptions = {} } = config;
|
|
20762
20991
|
const { TIMEOUT = 30 * 60 * 1000, BUFFER_SIZE = 30, maskingOptions = ["passwords"], recordCrossOriginIframes = false, } = recordingOptions;
|
|
20763
20992
|
this.TIMEOUT = TIMEOUT;
|
|
@@ -20765,6 +20994,7 @@ class SessionRecorder {
|
|
|
20765
20994
|
this.maskingOptions = maskingOptions;
|
|
20766
20995
|
this.recordCrossOriginIframes = recordCrossOriginIframes;
|
|
20767
20996
|
this.sessionEvents = [];
|
|
20997
|
+
this.sessionManager = new SessionManager(this.TIMEOUT);
|
|
20768
20998
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_initRecorder).call(this);
|
|
20769
20999
|
}
|
|
20770
21000
|
catch (err) {
|
|
@@ -20773,7 +21003,8 @@ class SessionRecorder {
|
|
|
20773
21003
|
}
|
|
20774
21004
|
}
|
|
20775
21005
|
getSessionId() {
|
|
20776
|
-
|
|
21006
|
+
var _a;
|
|
21007
|
+
return (_a = this.sessionManager) === null || _a === void 0 ? void 0 : _a.sessionUuid;
|
|
20777
21008
|
}
|
|
20778
21009
|
stop() {
|
|
20779
21010
|
try {
|
|
@@ -20783,15 +21014,16 @@ class SessionRecorder {
|
|
|
20783
21014
|
this.rrwebStop();
|
|
20784
21015
|
this.rrwebStop = null;
|
|
20785
21016
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
|
|
20786
|
-
|
|
21017
|
+
this.sessionManager.dispose();
|
|
20787
21018
|
window.removeEventListener("pagehide", __classPrivateFieldGet(this, _SessionRecorder_handlePageHide, "f"));
|
|
21019
|
+
document.removeEventListener("visibilitychange", __classPrivateFieldGet(this, _SessionRecorder_handleVisibilityChange, "f"));
|
|
20788
21020
|
}
|
|
20789
21021
|
catch (err) {
|
|
20790
21022
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Stop failed", err);
|
|
20791
21023
|
}
|
|
20792
21024
|
}
|
|
20793
21025
|
}
|
|
20794
|
-
_SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = new WeakMap(), _SessionRecorder_bufferTimer = new WeakMap(), _SessionRecorder_handlePageHide = new WeakMap(), _SessionRecorder_instances = new WeakSet(), _SessionRecorder_log = function _SessionRecorder_log(message, error) {
|
|
21026
|
+
_SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = new WeakMap(), _SessionRecorder_bufferTimer = new WeakMap(), _SessionRecorder_handlePageHide = new WeakMap(), _SessionRecorder_handleVisibilityChange = new WeakMap(), _SessionRecorder_instances = new WeakSet(), _SessionRecorder_log = function _SessionRecorder_log(message, error) {
|
|
20795
21027
|
if (!this.debug)
|
|
20796
21028
|
return;
|
|
20797
21029
|
if (error) {
|
|
@@ -20803,7 +21035,7 @@ _SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = ne
|
|
|
20803
21035
|
}, _SessionRecorder_initRecorder = function _SessionRecorder_initRecorder() {
|
|
20804
21036
|
if (this.rrwebStop)
|
|
20805
21037
|
return;
|
|
20806
|
-
|
|
21038
|
+
this.sessionManager.initialize();
|
|
20807
21039
|
this.rrwebStop = record({
|
|
20808
21040
|
emit: (event, isCheckout) => {
|
|
20809
21041
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_handleEvent).call(this, event, isCheckout);
|
|
@@ -20827,16 +21059,16 @@ _SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = ne
|
|
|
20827
21059
|
}, _SessionRecorder_handleEvent = function _SessionRecorder_handleEvent(event, _isCheckout) {
|
|
20828
21060
|
try {
|
|
20829
21061
|
const now = Date.now();
|
|
20830
|
-
const
|
|
20831
|
-
if (
|
|
20832
|
-
__classPrivateFieldGet(this, _SessionRecorder_instances, "m",
|
|
21062
|
+
const change = this.sessionManager.syncWithSharedState(now);
|
|
21063
|
+
if (change === "expired") {
|
|
21064
|
+
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
|
|
21065
|
+
takeFullSnapshot(true);
|
|
21066
|
+
}
|
|
21067
|
+
else if (change === "rotated") {
|
|
20833
21068
|
takeFullSnapshot(true);
|
|
20834
21069
|
}
|
|
20835
21070
|
if (__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_isUserInteraction).call(this, event)) {
|
|
20836
|
-
|
|
20837
|
-
if (latest) {
|
|
20838
|
-
writeSessionState({ ...latest, last_active: now });
|
|
20839
|
-
}
|
|
21071
|
+
this.sessionManager.bumpActivity(now);
|
|
20840
21072
|
}
|
|
20841
21073
|
this.sessionEvents.push(event);
|
|
20842
21074
|
this.bufferBytes += estimateEventSize(event);
|
|
@@ -20862,30 +21094,13 @@ _SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = ne
|
|
|
20862
21094
|
__classPrivateFieldSet(this, _SessionRecorder_bufferTimer, null, "f");
|
|
20863
21095
|
}
|
|
20864
21096
|
void __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_trackEvents).call(this);
|
|
20865
|
-
}, _SessionRecorder_resetSession = function _SessionRecorder_resetSession() {
|
|
20866
|
-
clearSessionState();
|
|
20867
|
-
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
|
|
20868
|
-
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_createSession).call(this);
|
|
20869
|
-
}, _SessionRecorder_createSession = function _SessionRecorder_createSession() {
|
|
20870
|
-
const now = Date.now();
|
|
20871
|
-
const state = readSessionState();
|
|
20872
|
-
const isExpired = !state || now - state.last_active > this.TIMEOUT;
|
|
20873
|
-
if (!state || isExpired) {
|
|
20874
|
-
this.sessionUuid = generateUuid();
|
|
20875
|
-
const initial_url = typeof window !== "undefined" ? window.location.href : "";
|
|
20876
|
-
writeSessionState({
|
|
20877
|
-
session_uuid: this.sessionUuid,
|
|
20878
|
-
last_active: now,
|
|
20879
|
-
chunk_seq: 0,
|
|
20880
|
-
initial_url,
|
|
20881
|
-
});
|
|
20882
|
-
}
|
|
20883
|
-
else {
|
|
20884
|
-
this.sessionUuid = state.session_uuid;
|
|
20885
|
-
writeSessionState({ ...state, last_active: now });
|
|
20886
|
-
}
|
|
20887
21097
|
}, _SessionRecorder_initListeners = function _SessionRecorder_initListeners() {
|
|
20888
21098
|
window.addEventListener("pagehide", __classPrivateFieldGet(this, _SessionRecorder_handlePageHide, "f"));
|
|
21099
|
+
document.addEventListener("visibilitychange", __classPrivateFieldGet(this, _SessionRecorder_handleVisibilityChange, "f"));
|
|
21100
|
+
// Emit the initial state so the player knows whether this tab started
|
|
21101
|
+
// visible or hidden (a tab opened in the background never fires
|
|
21102
|
+
// visibilitychange until the user focuses it).
|
|
21103
|
+
__classPrivateFieldGet(this, _SessionRecorder_handleVisibilityChange, "f").call(this);
|
|
20889
21104
|
}, _SessionRecorder_trackEvents = async function _SessionRecorder_trackEvents() {
|
|
20890
21105
|
if (__classPrivateFieldGet(this, _SessionRecorder_uploading, "f"))
|
|
20891
21106
|
return;
|
|
@@ -20898,30 +21113,38 @@ _SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = ne
|
|
|
20898
21113
|
__classPrivateFieldSet(this, _SessionRecorder_uploadingMaxTs, events[events.length - 1].timestamp, "f");
|
|
20899
21114
|
const start_ts_ms = events[0].timestamp;
|
|
20900
21115
|
const end_ts_ms = events[events.length - 1].timestamp;
|
|
20901
|
-
const
|
|
20902
|
-
if (
|
|
20903
|
-
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "No
|
|
21116
|
+
const chunk_seq = this.sessionManager.currentChunkSeq();
|
|
21117
|
+
if (chunk_seq === null) {
|
|
21118
|
+
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "No window state during upload — skipping chunk");
|
|
20904
21119
|
return;
|
|
20905
21120
|
}
|
|
20906
|
-
const
|
|
20907
|
-
|
|
20908
|
-
|
|
20909
|
-
|
|
20910
|
-
|
|
20911
|
-
|
|
20912
|
-
|
|
20913
|
-
|
|
20914
|
-
|
|
20915
|
-
|
|
21121
|
+
const chunk_start_ts = new Date(start_ts_ms).toISOString();
|
|
21122
|
+
const chunk_end_ts = new Date(end_ts_ms).toISOString();
|
|
21123
|
+
if (this.mode === "manual") {
|
|
21124
|
+
await this.onEvents({
|
|
21125
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
21126
|
+
window_id: this.sessionManager.windowId,
|
|
21127
|
+
chunk_seq,
|
|
21128
|
+
chunk_start_ts,
|
|
21129
|
+
chunk_end_ts,
|
|
21130
|
+
events,
|
|
21131
|
+
});
|
|
21132
|
+
}
|
|
21133
|
+
else {
|
|
21134
|
+
await uploadSessionEvents({
|
|
21135
|
+
user_id: this.userId,
|
|
21136
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
21137
|
+
window_id: this.sessionManager.windowId,
|
|
21138
|
+
chunk_seq,
|
|
21139
|
+
chunk_start_ts,
|
|
21140
|
+
chunk_end_ts,
|
|
21141
|
+
events,
|
|
21142
|
+
});
|
|
21143
|
+
}
|
|
20916
21144
|
const removedBytes = events.reduce((sum, e) => sum + estimateEventSize(e), 0);
|
|
20917
21145
|
this.sessionEvents = this.sessionEvents.slice(snapshot_count);
|
|
20918
21146
|
this.bufferBytes = Math.max(0, this.bufferBytes - removedBytes);
|
|
20919
|
-
|
|
20920
|
-
if (after &&
|
|
20921
|
-
after.session_uuid === state.session_uuid &&
|
|
20922
|
-
after.chunk_seq === chunk_seq) {
|
|
20923
|
-
writeSessionState({ ...after, chunk_seq: chunk_seq + 1 });
|
|
20924
|
-
}
|
|
21147
|
+
this.sessionManager.commitChunkSeq(chunk_seq);
|
|
20925
21148
|
}
|
|
20926
21149
|
catch (err) {
|
|
20927
21150
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Event tracking failed", err);
|