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