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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type SessionChange = "none" | "expired" | "rotated";
|
|
2
|
+
/**
|
|
3
|
+
* Owns the (session_uuid, window_id, chunk_seq, last_active) lifecycle.
|
|
4
|
+
*
|
|
5
|
+
* Two storage scopes:
|
|
6
|
+
* - shared (localStorage): session_uuid + last_active — visible to every tab
|
|
7
|
+
* of the same origin so tabs can adopt one logical session and a single
|
|
8
|
+
* idle timeout governs the whole user.
|
|
9
|
+
* - per-tab (sessionStorage): window_id + chunk_seq — independent per tab so
|
|
10
|
+
* concurrent tabs can upload chunks without colliding on chunk_seq.
|
|
11
|
+
*
|
|
12
|
+
* The recorder talks to this class instead of touching storage directly.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SessionManager {
|
|
15
|
+
#private;
|
|
16
|
+
private readonly timeoutMs;
|
|
17
|
+
private _sessionUuid;
|
|
18
|
+
private _windowId;
|
|
19
|
+
constructor(timeoutMs: number);
|
|
20
|
+
initialize(): void;
|
|
21
|
+
get sessionUuid(): string;
|
|
22
|
+
get windowId(): string;
|
|
23
|
+
/**
|
|
24
|
+
* Reconciles in-memory session_uuid with whatever shared storage says.
|
|
25
|
+
* Called on every event so cross-tab session changes are picked up quickly.
|
|
26
|
+
*
|
|
27
|
+
* Returns:
|
|
28
|
+
* "expired" — shared session has been idle past timeout; we rotated.
|
|
29
|
+
* "rotated" — another tab rotated the session_uuid; we adopted it.
|
|
30
|
+
* "none" — no change, business as usual.
|
|
31
|
+
*
|
|
32
|
+
* Caller takes a fresh full snapshot on "expired" or "rotated" so this
|
|
33
|
+
* window's first chunk in the new session is replayable on its own.
|
|
34
|
+
*/
|
|
35
|
+
syncWithSharedState(now: number): SessionChange;
|
|
36
|
+
/** Bump shared last_active. Call only on real user interactions. */
|
|
37
|
+
bumpActivity(now: number): void;
|
|
38
|
+
/** Read this window's current chunk_seq for a pending upload. */
|
|
39
|
+
currentChunkSeq(): number | null;
|
|
40
|
+
/**
|
|
41
|
+
* Mark a chunk_seq as committed after a successful upload. No-ops if the
|
|
42
|
+
* stored seq has moved on (e.g. a session rotation reset it to 0 mid-flight).
|
|
43
|
+
*/
|
|
44
|
+
commitChunkSeq(uploadedSeq: number): void;
|
|
45
|
+
/**
|
|
46
|
+
* Reserve and return the current chunk_seq for a pagehide flush, advancing
|
|
47
|
+
* storage so the next normal upload uses seq+1. Returns null if window state
|
|
48
|
+
* is missing.
|
|
49
|
+
*/
|
|
50
|
+
reserveChunkSeq(): number | null;
|
|
51
|
+
/** Hard reset: wipe shared session and start a fresh one. */
|
|
52
|
+
reset(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Called from pagehide. Lets a future reload of the same tab reuse the
|
|
55
|
+
* window_id (because the flag is gone), while a cloned tab still sees the
|
|
56
|
+
* flag set in its inherited sessionStorage and treats itself as a duplicate.
|
|
57
|
+
*/
|
|
58
|
+
notifyPageHide(): void;
|
|
59
|
+
/** Full teardown — call on stop(). */
|
|
60
|
+
dispose(): void;
|
|
61
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SharedSessionState, WindowState } from "./types";
|
|
2
|
+
export declare function readSharedSessionState(): SharedSessionState | null;
|
|
3
|
+
export declare function writeSharedSessionState(state: SharedSessionState): void;
|
|
4
|
+
export declare function clearSharedSessionState(): void;
|
|
5
|
+
export declare function readWindowState(): WindowState | null;
|
|
6
|
+
export declare function writeWindowState(state: WindowState): void;
|
|
7
|
+
export declare function clearWindowState(): void;
|
|
8
|
+
export declare function wasPrimaryWindowFlagAlreadySet(): boolean;
|
|
9
|
+
export declare function markPrimaryWindow(): void;
|
|
10
|
+
export declare function clearPrimaryWindowFlag(): void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,10 +10,10 @@ interface SessionRecordingOptions {
|
|
|
10
10
|
}
|
|
11
11
|
interface EventBatch {
|
|
12
12
|
session_uuid: string;
|
|
13
|
+
window_id: string;
|
|
13
14
|
chunk_seq: number;
|
|
14
15
|
chunk_start_ts: string;
|
|
15
16
|
chunk_end_ts: string;
|
|
16
|
-
initial_url?: string;
|
|
17
17
|
events: eventWithTime[];
|
|
18
18
|
keepalive?: boolean;
|
|
19
19
|
}
|
|
@@ -41,7 +41,7 @@ declare class SessionRecorder {
|
|
|
41
41
|
private TIMEOUT;
|
|
42
42
|
private BUFFER_SIZE;
|
|
43
43
|
private maskingOptions;
|
|
44
|
-
private
|
|
44
|
+
private sessionManager;
|
|
45
45
|
private sessionEvents;
|
|
46
46
|
private bufferBytes;
|
|
47
47
|
private rrwebStop;
|
|
@@ -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,24 +20897,21 @@
|
|
|
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
|
-
const initial_url = chunk_seq === 0 ? state.initial_url : undefined;
|
|
20721
20905
|
const chunk_start_ts = new Date(start_ts_ms).toISOString();
|
|
20722
20906
|
const chunk_end_ts = new Date(end_ts_ms).toISOString();
|
|
20723
20907
|
if (this.mode === "manual") {
|
|
20724
20908
|
try {
|
|
20725
20909
|
void this.onEvents({
|
|
20726
|
-
session_uuid: this.sessionUuid,
|
|
20910
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
20911
|
+
window_id: this.sessionManager.windowId,
|
|
20727
20912
|
chunk_seq,
|
|
20728
20913
|
chunk_start_ts,
|
|
20729
20914
|
chunk_end_ts,
|
|
20730
|
-
initial_url,
|
|
20731
20915
|
events,
|
|
20732
20916
|
keepalive: true,
|
|
20733
20917
|
});
|
|
@@ -20739,11 +20923,11 @@
|
|
|
20739
20923
|
else {
|
|
20740
20924
|
uploadSessionEvents({
|
|
20741
20925
|
user_id: this.userId,
|
|
20742
|
-
session_uuid: this.sessionUuid,
|
|
20926
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
20927
|
+
window_id: this.sessionManager.windowId,
|
|
20743
20928
|
chunk_seq,
|
|
20744
20929
|
chunk_start_ts,
|
|
20745
20930
|
chunk_end_ts,
|
|
20746
|
-
initial_url,
|
|
20747
20931
|
events,
|
|
20748
20932
|
keepalive: true,
|
|
20749
20933
|
}).catch(() => { });
|
|
@@ -20755,6 +20939,19 @@
|
|
|
20755
20939
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Page hide handling failed", err);
|
|
20756
20940
|
}
|
|
20757
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
|
+
});
|
|
20758
20955
|
try {
|
|
20759
20956
|
if (typeof window === "undefined")
|
|
20760
20957
|
return;
|
|
@@ -20799,6 +20996,7 @@
|
|
|
20799
20996
|
this.maskingOptions = maskingOptions;
|
|
20800
20997
|
this.recordCrossOriginIframes = recordCrossOriginIframes;
|
|
20801
20998
|
this.sessionEvents = [];
|
|
20999
|
+
this.sessionManager = new SessionManager(this.TIMEOUT);
|
|
20802
21000
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_initRecorder).call(this);
|
|
20803
21001
|
}
|
|
20804
21002
|
catch (err) {
|
|
@@ -20807,7 +21005,8 @@
|
|
|
20807
21005
|
}
|
|
20808
21006
|
}
|
|
20809
21007
|
getSessionId() {
|
|
20810
|
-
|
|
21008
|
+
var _a;
|
|
21009
|
+
return (_a = this.sessionManager) === null || _a === void 0 ? void 0 : _a.sessionUuid;
|
|
20811
21010
|
}
|
|
20812
21011
|
stop() {
|
|
20813
21012
|
try {
|
|
@@ -20817,15 +21016,16 @@
|
|
|
20817
21016
|
this.rrwebStop();
|
|
20818
21017
|
this.rrwebStop = null;
|
|
20819
21018
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
|
|
20820
|
-
|
|
21019
|
+
this.sessionManager.dispose();
|
|
20821
21020
|
window.removeEventListener("pagehide", __classPrivateFieldGet(this, _SessionRecorder_handlePageHide, "f"));
|
|
21021
|
+
document.removeEventListener("visibilitychange", __classPrivateFieldGet(this, _SessionRecorder_handleVisibilityChange, "f"));
|
|
20822
21022
|
}
|
|
20823
21023
|
catch (err) {
|
|
20824
21024
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Stop failed", err);
|
|
20825
21025
|
}
|
|
20826
21026
|
}
|
|
20827
21027
|
}
|
|
20828
|
-
_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) {
|
|
20829
21029
|
if (!this.debug)
|
|
20830
21030
|
return;
|
|
20831
21031
|
if (error) {
|
|
@@ -20837,7 +21037,7 @@
|
|
|
20837
21037
|
}, _SessionRecorder_initRecorder = function _SessionRecorder_initRecorder() {
|
|
20838
21038
|
if (this.rrwebStop)
|
|
20839
21039
|
return;
|
|
20840
|
-
|
|
21040
|
+
this.sessionManager.initialize();
|
|
20841
21041
|
this.rrwebStop = record({
|
|
20842
21042
|
emit: (event, isCheckout) => {
|
|
20843
21043
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_handleEvent).call(this, event, isCheckout);
|
|
@@ -20861,16 +21061,16 @@
|
|
|
20861
21061
|
}, _SessionRecorder_handleEvent = function _SessionRecorder_handleEvent(event, _isCheckout) {
|
|
20862
21062
|
try {
|
|
20863
21063
|
const now = Date.now();
|
|
20864
|
-
const
|
|
20865
|
-
if (
|
|
20866
|
-
__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") {
|
|
20867
21070
|
takeFullSnapshot(true);
|
|
20868
21071
|
}
|
|
20869
21072
|
if (__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_isUserInteraction).call(this, event)) {
|
|
20870
|
-
|
|
20871
|
-
if (latest) {
|
|
20872
|
-
writeSessionState({ ...latest, last_active: now });
|
|
20873
|
-
}
|
|
21073
|
+
this.sessionManager.bumpActivity(now);
|
|
20874
21074
|
}
|
|
20875
21075
|
this.sessionEvents.push(event);
|
|
20876
21076
|
this.bufferBytes += estimateEventSize(event);
|
|
@@ -20896,30 +21096,13 @@
|
|
|
20896
21096
|
__classPrivateFieldSet(this, _SessionRecorder_bufferTimer, null, "f");
|
|
20897
21097
|
}
|
|
20898
21098
|
void __classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_trackEvents).call(this);
|
|
20899
|
-
}, _SessionRecorder_resetSession = function _SessionRecorder_resetSession() {
|
|
20900
|
-
clearSessionState();
|
|
20901
|
-
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
|
|
20902
|
-
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_createSession).call(this);
|
|
20903
|
-
}, _SessionRecorder_createSession = function _SessionRecorder_createSession() {
|
|
20904
|
-
const now = Date.now();
|
|
20905
|
-
const state = readSessionState();
|
|
20906
|
-
const isExpired = !state || now - state.last_active > this.TIMEOUT;
|
|
20907
|
-
if (!state || isExpired) {
|
|
20908
|
-
this.sessionUuid = generateUuid();
|
|
20909
|
-
const initial_url = typeof window !== "undefined" ? window.location.href : "";
|
|
20910
|
-
writeSessionState({
|
|
20911
|
-
session_uuid: this.sessionUuid,
|
|
20912
|
-
last_active: now,
|
|
20913
|
-
chunk_seq: 0,
|
|
20914
|
-
initial_url,
|
|
20915
|
-
});
|
|
20916
|
-
}
|
|
20917
|
-
else {
|
|
20918
|
-
this.sessionUuid = state.session_uuid;
|
|
20919
|
-
writeSessionState({ ...state, last_active: now });
|
|
20920
|
-
}
|
|
20921
21099
|
}, _SessionRecorder_initListeners = function _SessionRecorder_initListeners() {
|
|
20922
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);
|
|
20923
21106
|
}, _SessionRecorder_trackEvents = async function _SessionRecorder_trackEvents() {
|
|
20924
21107
|
if (__classPrivateFieldGet(this, _SessionRecorder_uploading, "f"))
|
|
20925
21108
|
return;
|
|
@@ -20932,45 +21115,38 @@
|
|
|
20932
21115
|
__classPrivateFieldSet(this, _SessionRecorder_uploadingMaxTs, events[events.length - 1].timestamp, "f");
|
|
20933
21116
|
const start_ts_ms = events[0].timestamp;
|
|
20934
21117
|
const end_ts_ms = events[events.length - 1].timestamp;
|
|
20935
|
-
const
|
|
20936
|
-
if (
|
|
20937
|
-
__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");
|
|
20938
21121
|
return;
|
|
20939
21122
|
}
|
|
20940
|
-
const chunk_seq = state.chunk_seq;
|
|
20941
|
-
const initial_url = chunk_seq === 0 ? state.initial_url : undefined;
|
|
20942
21123
|
const chunk_start_ts = new Date(start_ts_ms).toISOString();
|
|
20943
21124
|
const chunk_end_ts = new Date(end_ts_ms).toISOString();
|
|
20944
21125
|
if (this.mode === "manual") {
|
|
20945
21126
|
await this.onEvents({
|
|
20946
|
-
session_uuid: this.sessionUuid,
|
|
21127
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
21128
|
+
window_id: this.sessionManager.windowId,
|
|
20947
21129
|
chunk_seq,
|
|
20948
21130
|
chunk_start_ts,
|
|
20949
21131
|
chunk_end_ts,
|
|
20950
|
-
initial_url,
|
|
20951
21132
|
events,
|
|
20952
21133
|
});
|
|
20953
21134
|
}
|
|
20954
21135
|
else {
|
|
20955
21136
|
await uploadSessionEvents({
|
|
20956
21137
|
user_id: this.userId,
|
|
20957
|
-
session_uuid: this.sessionUuid,
|
|
21138
|
+
session_uuid: this.sessionManager.sessionUuid,
|
|
21139
|
+
window_id: this.sessionManager.windowId,
|
|
20958
21140
|
chunk_seq,
|
|
20959
21141
|
chunk_start_ts,
|
|
20960
21142
|
chunk_end_ts,
|
|
20961
|
-
initial_url,
|
|
20962
21143
|
events,
|
|
20963
21144
|
});
|
|
20964
21145
|
}
|
|
20965
21146
|
const removedBytes = events.reduce((sum, e) => sum + estimateEventSize(e), 0);
|
|
20966
21147
|
this.sessionEvents = this.sessionEvents.slice(snapshot_count);
|
|
20967
21148
|
this.bufferBytes = Math.max(0, this.bufferBytes - removedBytes);
|
|
20968
|
-
|
|
20969
|
-
if (after &&
|
|
20970
|
-
after.session_uuid === state.session_uuid &&
|
|
20971
|
-
after.chunk_seq === chunk_seq) {
|
|
20972
|
-
writeSessionState({ ...after, chunk_seq: chunk_seq + 1 });
|
|
20973
|
-
}
|
|
21149
|
+
this.sessionManager.commitChunkSeq(chunk_seq);
|
|
20974
21150
|
}
|
|
20975
21151
|
catch (err) {
|
|
20976
21152
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Event tracking failed", err);
|