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