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.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { MaskingOption, SessionRecorderConfig } from "./types";
|
|
2
|
-
export type { SessionRecorderConfig, MaskingOption, };
|
|
1
|
+
import { MaskingOption, RecorderMode, EventBatch, OnEventsCallback, SessionRecorderConfig, AutoModeConfig, ManualModeConfig } from "./types";
|
|
2
|
+
export type { SessionRecorderConfig, AutoModeConfig, ManualModeConfig, RecorderMode, EventBatch, OnEventsCallback, MaskingOption, };
|
|
3
3
|
export default class SessionRecorder {
|
|
4
4
|
#private;
|
|
5
|
+
private mode;
|
|
5
6
|
private userId?;
|
|
7
|
+
private onEvents?;
|
|
6
8
|
private TIMEOUT;
|
|
7
9
|
private BUFFER_SIZE;
|
|
8
10
|
private maskingOptions;
|
|
9
|
-
private
|
|
11
|
+
private sessionManager;
|
|
10
12
|
private sessionEvents;
|
|
11
13
|
private bufferBytes;
|
|
12
14
|
private rrwebStop;
|
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,23 +20891,41 @@ 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
|
-
|
|
20715
|
-
|
|
20716
|
-
|
|
20717
|
-
|
|
20718
|
-
|
|
20719
|
-
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
|
|
20723
|
-
|
|
20899
|
+
const chunk_start_ts = new Date(start_ts_ms).toISOString();
|
|
20900
|
+
const chunk_end_ts = new Date(end_ts_ms).toISOString();
|
|
20901
|
+
if (this.mode === "manual") {
|
|
20902
|
+
try {
|
|
20903
|
+
void this.onEvents({
|
|
20904
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
20905
|
+
window_id: this.sessionManager.windowId,
|
|
20906
|
+
chunk_seq,
|
|
20907
|
+
chunk_start_ts,
|
|
20908
|
+
chunk_end_ts,
|
|
20909
|
+
events,
|
|
20910
|
+
keepalive: true,
|
|
20911
|
+
});
|
|
20912
|
+
}
|
|
20913
|
+
catch {
|
|
20914
|
+
// ignore
|
|
20915
|
+
}
|
|
20916
|
+
}
|
|
20917
|
+
else {
|
|
20918
|
+
uploadSessionEvents({
|
|
20919
|
+
user_id: this.userId,
|
|
20920
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
20921
|
+
window_id: this.sessionManager.windowId,
|
|
20922
|
+
chunk_seq,
|
|
20923
|
+
chunk_start_ts,
|
|
20924
|
+
chunk_end_ts,
|
|
20925
|
+
events,
|
|
20926
|
+
keepalive: true,
|
|
20927
|
+
}).catch(() => { });
|
|
20928
|
+
}
|
|
20724
20929
|
this.sessionEvents = [];
|
|
20725
20930
|
this.bufferBytes = 0;
|
|
20726
20931
|
}
|
|
@@ -20728,6 +20933,19 @@ class SessionRecorder {
|
|
|
20728
20933
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Page hide handling failed", err);
|
|
20729
20934
|
}
|
|
20730
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
|
+
});
|
|
20731
20949
|
try {
|
|
20732
20950
|
if (typeof window === "undefined")
|
|
20733
20951
|
return;
|
|
@@ -20749,11 +20967,22 @@ class SessionRecorder {
|
|
|
20749
20967
|
sessionStorage.setItem(testKey, "1");
|
|
20750
20968
|
sessionStorage.removeItem(testKey);
|
|
20751
20969
|
this.debug = (_a = config.debug) !== null && _a !== void 0 ? _a : false;
|
|
20752
|
-
if (
|
|
20753
|
-
|
|
20970
|
+
if (config.mode === "manual") {
|
|
20971
|
+
this.mode = "manual";
|
|
20972
|
+
if (!config.onEvents || typeof config.onEvents !== "function") {
|
|
20973
|
+
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "onEvents callback is required in manual mode");
|
|
20974
|
+
return;
|
|
20975
|
+
}
|
|
20976
|
+
this.onEvents = config.onEvents;
|
|
20977
|
+
}
|
|
20978
|
+
else {
|
|
20979
|
+
this.mode = "auto";
|
|
20980
|
+
if (!((_b = config.WRITE_CODE) === null || _b === void 0 ? void 0 : _b.trim()) || !((_c = config.userId) === null || _c === void 0 ? void 0 : _c.trim())) {
|
|
20981
|
+
return;
|
|
20982
|
+
}
|
|
20983
|
+
saveWriteCode(config.WRITE_CODE);
|
|
20984
|
+
this.userId = config.userId;
|
|
20754
20985
|
}
|
|
20755
|
-
saveWriteCode(config.WRITE_CODE);
|
|
20756
|
-
this.userId = config.userId;
|
|
20757
20986
|
const { recordingOptions = {} } = config;
|
|
20758
20987
|
const { TIMEOUT = 30 * 60 * 1000, BUFFER_SIZE = 30, maskingOptions = ["passwords"], recordCrossOriginIframes = false, } = recordingOptions;
|
|
20759
20988
|
this.TIMEOUT = TIMEOUT;
|
|
@@ -20761,6 +20990,7 @@ class SessionRecorder {
|
|
|
20761
20990
|
this.maskingOptions = maskingOptions;
|
|
20762
20991
|
this.recordCrossOriginIframes = recordCrossOriginIframes;
|
|
20763
20992
|
this.sessionEvents = [];
|
|
20993
|
+
this.sessionManager = new SessionManager(this.TIMEOUT);
|
|
20764
20994
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_initRecorder).call(this);
|
|
20765
20995
|
}
|
|
20766
20996
|
catch (err) {
|
|
@@ -20769,7 +20999,8 @@ class SessionRecorder {
|
|
|
20769
20999
|
}
|
|
20770
21000
|
}
|
|
20771
21001
|
getSessionId() {
|
|
20772
|
-
|
|
21002
|
+
var _a;
|
|
21003
|
+
return (_a = this.sessionManager) === null || _a === void 0 ? void 0 : _a.sessionUuid;
|
|
20773
21004
|
}
|
|
20774
21005
|
stop() {
|
|
20775
21006
|
try {
|
|
@@ -20779,15 +21010,16 @@ class SessionRecorder {
|
|
|
20779
21010
|
this.rrwebStop();
|
|
20780
21011
|
this.rrwebStop = null;
|
|
20781
21012
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
|
|
20782
|
-
|
|
21013
|
+
this.sessionManager.dispose();
|
|
20783
21014
|
window.removeEventListener("pagehide", __classPrivateFieldGet(this, _SessionRecorder_handlePageHide, "f"));
|
|
21015
|
+
document.removeEventListener("visibilitychange", __classPrivateFieldGet(this, _SessionRecorder_handleVisibilityChange, "f"));
|
|
20784
21016
|
}
|
|
20785
21017
|
catch (err) {
|
|
20786
21018
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Stop failed", err);
|
|
20787
21019
|
}
|
|
20788
21020
|
}
|
|
20789
21021
|
}
|
|
20790
|
-
_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) {
|
|
20791
21023
|
if (!this.debug)
|
|
20792
21024
|
return;
|
|
20793
21025
|
if (error) {
|
|
@@ -20799,7 +21031,7 @@ _SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = ne
|
|
|
20799
21031
|
}, _SessionRecorder_initRecorder = function _SessionRecorder_initRecorder() {
|
|
20800
21032
|
if (this.rrwebStop)
|
|
20801
21033
|
return;
|
|
20802
|
-
|
|
21034
|
+
this.sessionManager.initialize();
|
|
20803
21035
|
this.rrwebStop = record({
|
|
20804
21036
|
emit: (event, isCheckout) => {
|
|
20805
21037
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_handleEvent).call(this, event, isCheckout);
|
|
@@ -20823,16 +21055,16 @@ _SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = ne
|
|
|
20823
21055
|
}, _SessionRecorder_handleEvent = function _SessionRecorder_handleEvent(event, _isCheckout) {
|
|
20824
21056
|
try {
|
|
20825
21057
|
const now = Date.now();
|
|
20826
|
-
const
|
|
20827
|
-
if (
|
|
20828
|
-
__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") {
|
|
20829
21064
|
takeFullSnapshot(true);
|
|
20830
21065
|
}
|
|
20831
21066
|
if (__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_isUserInteraction).call(this, event)) {
|
|
20832
|
-
|
|
20833
|
-
if (latest) {
|
|
20834
|
-
writeSessionState({ ...latest, last_active: now });
|
|
20835
|
-
}
|
|
21067
|
+
this.sessionManager.bumpActivity(now);
|
|
20836
21068
|
}
|
|
20837
21069
|
this.sessionEvents.push(event);
|
|
20838
21070
|
this.bufferBytes += estimateEventSize(event);
|
|
@@ -20858,30 +21090,13 @@ _SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = ne
|
|
|
20858
21090
|
__classPrivateFieldSet(this, _SessionRecorder_bufferTimer, null, "f");
|
|
20859
21091
|
}
|
|
20860
21092
|
void __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_trackEvents).call(this);
|
|
20861
|
-
}, _SessionRecorder_resetSession = function _SessionRecorder_resetSession() {
|
|
20862
|
-
clearSessionState();
|
|
20863
|
-
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
|
|
20864
|
-
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_createSession).call(this);
|
|
20865
|
-
}, _SessionRecorder_createSession = function _SessionRecorder_createSession() {
|
|
20866
|
-
const now = Date.now();
|
|
20867
|
-
const state = readSessionState();
|
|
20868
|
-
const isExpired = !state || now - state.last_active > this.TIMEOUT;
|
|
20869
|
-
if (!state || isExpired) {
|
|
20870
|
-
this.sessionUuid = generateUuid();
|
|
20871
|
-
const initial_url = typeof window !== "undefined" ? window.location.href : "";
|
|
20872
|
-
writeSessionState({
|
|
20873
|
-
session_uuid: this.sessionUuid,
|
|
20874
|
-
last_active: now,
|
|
20875
|
-
chunk_seq: 0,
|
|
20876
|
-
initial_url,
|
|
20877
|
-
});
|
|
20878
|
-
}
|
|
20879
|
-
else {
|
|
20880
|
-
this.sessionUuid = state.session_uuid;
|
|
20881
|
-
writeSessionState({ ...state, last_active: now });
|
|
20882
|
-
}
|
|
20883
21093
|
}, _SessionRecorder_initListeners = function _SessionRecorder_initListeners() {
|
|
20884
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);
|
|
20885
21100
|
}, _SessionRecorder_trackEvents = async function _SessionRecorder_trackEvents() {
|
|
20886
21101
|
if (__classPrivateFieldGet(this, _SessionRecorder_uploading, "f"))
|
|
20887
21102
|
return;
|
|
@@ -20894,30 +21109,38 @@ _SessionRecorder_uploading = new WeakMap(), _SessionRecorder_uploadingMaxTs = ne
|
|
|
20894
21109
|
__classPrivateFieldSet(this, _SessionRecorder_uploadingMaxTs, events[events.length - 1].timestamp, "f");
|
|
20895
21110
|
const start_ts_ms = events[0].timestamp;
|
|
20896
21111
|
const end_ts_ms = events[events.length - 1].timestamp;
|
|
20897
|
-
const
|
|
20898
|
-
if (
|
|
20899
|
-
__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");
|
|
20900
21115
|
return;
|
|
20901
21116
|
}
|
|
20902
|
-
const
|
|
20903
|
-
|
|
20904
|
-
|
|
20905
|
-
|
|
20906
|
-
|
|
20907
|
-
|
|
20908
|
-
|
|
20909
|
-
|
|
20910
|
-
|
|
20911
|
-
|
|
21117
|
+
const chunk_start_ts = new Date(start_ts_ms).toISOString();
|
|
21118
|
+
const chunk_end_ts = new Date(end_ts_ms).toISOString();
|
|
21119
|
+
if (this.mode === "manual") {
|
|
21120
|
+
await this.onEvents({
|
|
21121
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
21122
|
+
window_id: this.sessionManager.windowId,
|
|
21123
|
+
chunk_seq,
|
|
21124
|
+
chunk_start_ts,
|
|
21125
|
+
chunk_end_ts,
|
|
21126
|
+
events,
|
|
21127
|
+
});
|
|
21128
|
+
}
|
|
21129
|
+
else {
|
|
21130
|
+
await uploadSessionEvents({
|
|
21131
|
+
user_id: this.userId,
|
|
21132
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
21133
|
+
window_id: this.sessionManager.windowId,
|
|
21134
|
+
chunk_seq,
|
|
21135
|
+
chunk_start_ts,
|
|
21136
|
+
chunk_end_ts,
|
|
21137
|
+
events,
|
|
21138
|
+
});
|
|
21139
|
+
}
|
|
20912
21140
|
const removedBytes = events.reduce((sum, e) => sum + estimateEventSize(e), 0);
|
|
20913
21141
|
this.sessionEvents = this.sessionEvents.slice(snapshot_count);
|
|
20914
21142
|
this.bufferBytes = Math.max(0, this.bufferBytes - removedBytes);
|
|
20915
|
-
|
|
20916
|
-
if (after &&
|
|
20917
|
-
after.session_uuid === state.session_uuid &&
|
|
20918
|
-
after.chunk_seq === chunk_seq) {
|
|
20919
|
-
writeSessionState({ ...after, chunk_seq: chunk_seq + 1 });
|
|
20920
|
-
}
|
|
21143
|
+
this.sessionManager.commitChunkSeq(chunk_seq);
|
|
20921
21144
|
}
|
|
20922
21145
|
catch (err) {
|
|
20923
21146
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Event tracking failed", err);
|