userlens-session-recorder 1.2.2 → 2.0.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/api/index.d.ts +2 -2
- package/dist/index.cjs.js +134 -98
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.esm.js +134 -98
- package/dist/index.esm.js.map +1 -1
- package/dist/types/index.d.ts +2 -21
- package/dist/userlens-session-recorder.umd.js +134 -98
- package/dist/userlens-session-recorder.umd.js.map +1 -1
- package/dist/utils.d.ts +4 -0
- package/package.json +1 -1
- package/dist/index.js +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { MaskingOption, SessionRecorderConfig
|
|
2
|
-
export type { SessionRecorderConfig,
|
|
1
|
+
import { MaskingOption, SessionRecorderConfig } from "./types";
|
|
2
|
+
export type { SessionRecorderConfig, MaskingOption, };
|
|
3
3
|
export default class SessionRecorder {
|
|
4
4
|
#private;
|
|
5
|
-
private mode;
|
|
6
5
|
private userId?;
|
|
7
|
-
private onEvents?;
|
|
8
6
|
private TIMEOUT;
|
|
9
7
|
private BUFFER_SIZE;
|
|
10
8
|
private maskingOptions;
|
package/dist/index.esm.js
CHANGED
|
@@ -20605,48 +20605,71 @@ const getWriteCode = () => {
|
|
|
20605
20605
|
return null;
|
|
20606
20606
|
}
|
|
20607
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
|
+
}
|
|
20608
20639
|
|
|
20609
|
-
const
|
|
20610
|
-
|
|
20611
|
-
|
|
20612
|
-
const jsonStr = JSON.stringify(data);
|
|
20613
|
-
const stream = new Blob([new TextEncoder().encode(jsonStr)])
|
|
20614
|
-
.stream()
|
|
20615
|
-
.pipeThrough(new CompressionStream("gzip"));
|
|
20616
|
-
const buffer = await new Response(stream).arrayBuffer();
|
|
20617
|
-
const bytes = new Uint8Array(buffer);
|
|
20618
|
-
let binary = "";
|
|
20619
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
20620
|
-
binary += String.fromCharCode(bytes[i]);
|
|
20621
|
-
}
|
|
20622
|
-
return btoa(binary);
|
|
20623
|
-
}
|
|
20624
|
-
const uploadSessionEvents = async (userId, sessionUuid, events, chunkTimestamp) => {
|
|
20640
|
+
const INGEST_BASE_URL = "https://ul-ingest.userlens.io";
|
|
20641
|
+
const uploadSessionEvents = async (args) => {
|
|
20642
|
+
var _a;
|
|
20625
20643
|
const writeCode = getWriteCode();
|
|
20626
20644
|
if (!writeCode) {
|
|
20627
20645
|
return;
|
|
20628
20646
|
}
|
|
20629
|
-
|
|
20630
|
-
|
|
20631
|
-
|
|
20632
|
-
|
|
20633
|
-
|
|
20647
|
+
const body = {
|
|
20648
|
+
session_uuid: args.session_uuid,
|
|
20649
|
+
chunk_seq: args.chunk_seq,
|
|
20650
|
+
chunk_start_ts: args.chunk_start_ts,
|
|
20651
|
+
chunk_end_ts: args.chunk_end_ts,
|
|
20652
|
+
user_id: args.user_id,
|
|
20653
|
+
events: args.events,
|
|
20634
20654
|
};
|
|
20635
|
-
|
|
20636
|
-
|
|
20655
|
+
if (args.initial_url !== undefined) {
|
|
20656
|
+
body.initial_url = args.initial_url;
|
|
20657
|
+
}
|
|
20658
|
+
const res = await fetch(`${INGEST_BASE_URL}/session-recording`, {
|
|
20637
20659
|
method: "POST",
|
|
20638
20660
|
headers: {
|
|
20639
|
-
"Content-Type": "
|
|
20661
|
+
"Content-Type": "application/json",
|
|
20640
20662
|
Authorization: `Basic ${writeCode}`,
|
|
20641
20663
|
},
|
|
20642
|
-
body:
|
|
20664
|
+
body: JSON.stringify(body),
|
|
20665
|
+
keepalive: (_a = args.keepalive) !== null && _a !== void 0 ? _a : false,
|
|
20643
20666
|
});
|
|
20644
20667
|
if (!res.ok)
|
|
20645
20668
|
throw new Error("Userlens HTTP error: failed to track");
|
|
20646
20669
|
return "ok";
|
|
20647
20670
|
};
|
|
20648
20671
|
|
|
20649
|
-
var _SessionRecorder_instances, _SessionRecorder_trackEventsThrottled, _SessionRecorder_log, _SessionRecorder_initRecorder, _SessionRecorder_isUserInteraction, _SessionRecorder_handleEvent, _SessionRecorder_resetSession, _SessionRecorder_createSession,
|
|
20672
|
+
var _SessionRecorder_instances, _SessionRecorder_trackEventsThrottled, _SessionRecorder_uploading, _SessionRecorder_log, _SessionRecorder_initRecorder, _SessionRecorder_isUserInteraction, _SessionRecorder_handleEvent, _SessionRecorder_resetSession, _SessionRecorder_createSession, _SessionRecorder_handlePageHide, _SessionRecorder_initListeners, _SessionRecorder_throttle, _SessionRecorder_trackEvents, _SessionRecorder_clearEvents;
|
|
20650
20673
|
class SessionRecorder {
|
|
20651
20674
|
constructor(config) {
|
|
20652
20675
|
var _a, _b, _c;
|
|
@@ -20655,65 +20678,61 @@ class SessionRecorder {
|
|
|
20655
20678
|
this.rrwebStop = null;
|
|
20656
20679
|
this.debug = false;
|
|
20657
20680
|
_SessionRecorder_trackEventsThrottled.set(this, void 0);
|
|
20658
|
-
|
|
20681
|
+
_SessionRecorder_uploading.set(this, false);
|
|
20682
|
+
_SessionRecorder_handlePageHide.set(this, () => {
|
|
20659
20683
|
try {
|
|
20660
|
-
if (
|
|
20661
|
-
|
|
20662
|
-
|
|
20663
|
-
|
|
20664
|
-
|
|
20665
|
-
|
|
20666
|
-
|
|
20667
|
-
|
|
20668
|
-
|
|
20669
|
-
|
|
20684
|
+
if (this.sessionEvents.length === 0)
|
|
20685
|
+
return;
|
|
20686
|
+
const events = [...this.sessionEvents];
|
|
20687
|
+
const state = readSessionState();
|
|
20688
|
+
if (!state)
|
|
20689
|
+
return;
|
|
20690
|
+
const chunk_seq = __classPrivateFieldGet(this, _SessionRecorder_uploading, "f") ? state.chunk_seq + 1 : state.chunk_seq;
|
|
20691
|
+
writeSessionState({ ...state, chunk_seq: chunk_seq + 1 });
|
|
20692
|
+
const start_ts_ms = events[0].timestamp;
|
|
20693
|
+
const end_ts_ms = events[events.length - 1].timestamp;
|
|
20694
|
+
uploadSessionEvents({
|
|
20695
|
+
user_id: this.userId,
|
|
20696
|
+
session_uuid: this.sessionUuid,
|
|
20697
|
+
chunk_seq,
|
|
20698
|
+
chunk_start_ts: new Date(start_ts_ms).toISOString(),
|
|
20699
|
+
chunk_end_ts: new Date(end_ts_ms).toISOString(),
|
|
20700
|
+
initial_url: chunk_seq === 0 ? state.initial_url : undefined,
|
|
20701
|
+
events,
|
|
20702
|
+
keepalive: true,
|
|
20703
|
+
}).catch(() => { });
|
|
20704
|
+
this.sessionEvents = [];
|
|
20670
20705
|
}
|
|
20671
20706
|
catch (err) {
|
|
20672
|
-
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "
|
|
20707
|
+
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Page hide handling failed", err);
|
|
20673
20708
|
}
|
|
20674
20709
|
});
|
|
20675
20710
|
try {
|
|
20676
|
-
// Check for browser environment
|
|
20677
20711
|
if (typeof window === "undefined")
|
|
20678
20712
|
return;
|
|
20679
20713
|
if (typeof document === "undefined")
|
|
20680
20714
|
return;
|
|
20681
20715
|
if (typeof localStorage === "undefined")
|
|
20682
20716
|
return;
|
|
20683
|
-
|
|
20684
|
-
if (typeof CompressionStream === "undefined")
|
|
20717
|
+
if (typeof sessionStorage === "undefined")
|
|
20685
20718
|
return;
|
|
20686
20719
|
if (typeof MutationObserver === "undefined")
|
|
20687
20720
|
return;
|
|
20688
|
-
if (typeof TextEncoder === "undefined")
|
|
20689
|
-
return;
|
|
20690
20721
|
if (typeof fetch === "undefined")
|
|
20691
20722
|
return;
|
|
20692
|
-
if (typeof Blob === "undefined")
|
|
20693
|
-
return;
|
|
20694
20723
|
if (typeof crypto === "undefined" || !crypto.getRandomValues)
|
|
20695
20724
|
return;
|
|
20696
|
-
// Check localStorage actually works (can be blocked even if defined)
|
|
20697
20725
|
const testKey = "__userlens_test__";
|
|
20698
20726
|
localStorage.setItem(testKey, "1");
|
|
20699
20727
|
localStorage.removeItem(testKey);
|
|
20700
|
-
|
|
20728
|
+
sessionStorage.setItem(testKey, "1");
|
|
20729
|
+
sessionStorage.removeItem(testKey);
|
|
20701
20730
|
this.debug = (_a = config.debug) !== null && _a !== void 0 ? _a : false;
|
|
20702
|
-
if (config.
|
|
20703
|
-
|
|
20704
|
-
if (!config.onEvents || typeof config.onEvents !== "function") {
|
|
20705
|
-
return;
|
|
20706
|
-
}
|
|
20707
|
-
this.onEvents = config.onEvents;
|
|
20708
|
-
}
|
|
20709
|
-
else {
|
|
20710
|
-
this.mode = "auto";
|
|
20711
|
-
if (!((_b = config.WRITE_CODE) === null || _b === void 0 ? void 0 : _b.trim()) || !((_c = config.userId) === null || _c === void 0 ? void 0 : _c.trim())) {
|
|
20712
|
-
return;
|
|
20713
|
-
}
|
|
20714
|
-
saveWriteCode(config.WRITE_CODE);
|
|
20715
|
-
this.userId = config.userId;
|
|
20731
|
+
if (!((_b = config.WRITE_CODE) === null || _b === void 0 ? void 0 : _b.trim()) || !((_c = config.userId) === null || _c === void 0 ? void 0 : _c.trim())) {
|
|
20732
|
+
return;
|
|
20716
20733
|
}
|
|
20734
|
+
saveWriteCode(config.WRITE_CODE);
|
|
20735
|
+
this.userId = config.userId;
|
|
20717
20736
|
const { recordingOptions = {} } = config;
|
|
20718
20737
|
const { TIMEOUT = 30 * 60 * 1000, BUFFER_SIZE = 10, maskingOptions = ["passwords"], recordCrossOriginIframes = false, } = recordingOptions;
|
|
20719
20738
|
this.TIMEOUT = TIMEOUT;
|
|
@@ -20742,15 +20761,15 @@ class SessionRecorder {
|
|
|
20742
20761
|
this.rrwebStop();
|
|
20743
20762
|
this.rrwebStop = null;
|
|
20744
20763
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
|
|
20745
|
-
|
|
20746
|
-
window.removeEventListener("
|
|
20764
|
+
clearSessionState();
|
|
20765
|
+
window.removeEventListener("pagehide", __classPrivateFieldGet(this, _SessionRecorder_handlePageHide, "f"));
|
|
20747
20766
|
}
|
|
20748
20767
|
catch (err) {
|
|
20749
20768
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Stop failed", err);
|
|
20750
20769
|
}
|
|
20751
20770
|
}
|
|
20752
20771
|
}
|
|
20753
|
-
_SessionRecorder_trackEventsThrottled = new WeakMap(),
|
|
20772
|
+
_SessionRecorder_trackEventsThrottled = new WeakMap(), _SessionRecorder_uploading = new WeakMap(), _SessionRecorder_handlePageHide = new WeakMap(), _SessionRecorder_instances = new WeakSet(), _SessionRecorder_log = function _SessionRecorder_log(message, error) {
|
|
20754
20773
|
if (!this.debug)
|
|
20755
20774
|
return;
|
|
20756
20775
|
if (error) {
|
|
@@ -20775,7 +20794,7 @@ _SessionRecorder_trackEventsThrottled = new WeakMap(), _SessionRecorder_handleVi
|
|
|
20775
20794
|
plugins: [getRecordConsolePlugin()],
|
|
20776
20795
|
checkoutEveryNth: 100,
|
|
20777
20796
|
});
|
|
20778
|
-
__classPrivateFieldGet(this, _SessionRecorder_instances, "m",
|
|
20797
|
+
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_initListeners).call(this);
|
|
20779
20798
|
}, _SessionRecorder_isUserInteraction = function _SessionRecorder_isUserInteraction(event) {
|
|
20780
20799
|
var _a;
|
|
20781
20800
|
if (event.type === 3) {
|
|
@@ -20787,15 +20806,16 @@ _SessionRecorder_trackEventsThrottled = new WeakMap(), _SessionRecorder_handleVi
|
|
|
20787
20806
|
var _a;
|
|
20788
20807
|
try {
|
|
20789
20808
|
const now = Date.now();
|
|
20790
|
-
const
|
|
20791
|
-
|
|
20792
|
-
if (lastActive && now - lastActive > this.TIMEOUT) {
|
|
20809
|
+
const state = readSessionState();
|
|
20810
|
+
if (state && now - state.last_active > this.TIMEOUT) {
|
|
20793
20811
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_resetSession).call(this);
|
|
20794
20812
|
takeFullSnapshot(true);
|
|
20795
20813
|
}
|
|
20796
|
-
// only update lastActive on actual user interactions, not DOM mutations
|
|
20797
20814
|
if (__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_isUserInteraction).call(this, event)) {
|
|
20798
|
-
|
|
20815
|
+
const latest = readSessionState();
|
|
20816
|
+
if (latest) {
|
|
20817
|
+
writeSessionState({ ...latest, last_active: now });
|
|
20818
|
+
}
|
|
20799
20819
|
}
|
|
20800
20820
|
this.sessionEvents.push(event);
|
|
20801
20821
|
if (this.sessionEvents.length >= this.BUFFER_SIZE) {
|
|
@@ -20806,24 +20826,29 @@ _SessionRecorder_trackEventsThrottled = new WeakMap(), _SessionRecorder_handleVi
|
|
|
20806
20826
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Event handling failed", err);
|
|
20807
20827
|
}
|
|
20808
20828
|
}, _SessionRecorder_resetSession = function _SessionRecorder_resetSession() {
|
|
20809
|
-
|
|
20829
|
+
clearSessionState();
|
|
20810
20830
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
|
|
20811
20831
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_createSession).call(this);
|
|
20812
20832
|
}, _SessionRecorder_createSession = function _SessionRecorder_createSession() {
|
|
20813
20833
|
const now = Date.now();
|
|
20814
|
-
const
|
|
20815
|
-
const
|
|
20816
|
-
|
|
20817
|
-
if (!storedUuid || isExpired) {
|
|
20834
|
+
const state = readSessionState();
|
|
20835
|
+
const isExpired = !state || now - state.last_active > this.TIMEOUT;
|
|
20836
|
+
if (!state || isExpired) {
|
|
20818
20837
|
this.sessionUuid = generateUuid();
|
|
20819
|
-
|
|
20838
|
+
const initial_url = typeof window !== "undefined" ? window.location.href : "";
|
|
20839
|
+
writeSessionState({
|
|
20840
|
+
session_uuid: this.sessionUuid,
|
|
20841
|
+
last_active: now,
|
|
20842
|
+
chunk_seq: 0,
|
|
20843
|
+
initial_url,
|
|
20844
|
+
});
|
|
20820
20845
|
}
|
|
20821
20846
|
else {
|
|
20822
|
-
this.sessionUuid =
|
|
20847
|
+
this.sessionUuid = state.session_uuid;
|
|
20848
|
+
writeSessionState({ ...state, last_active: now });
|
|
20823
20849
|
}
|
|
20824
|
-
|
|
20825
|
-
|
|
20826
|
-
window.addEventListener("visibilitychange", __classPrivateFieldGet(this, _SessionRecorder_handleVisibilityChange, "f"));
|
|
20850
|
+
}, _SessionRecorder_initListeners = function _SessionRecorder_initListeners() {
|
|
20851
|
+
window.addEventListener("pagehide", __classPrivateFieldGet(this, _SessionRecorder_handlePageHide, "f"));
|
|
20827
20852
|
}, _SessionRecorder_throttle = function _SessionRecorder_throttle(func, delay) {
|
|
20828
20853
|
let lastCall = 0;
|
|
20829
20854
|
return (...args) => {
|
|
@@ -20834,34 +20859,45 @@ _SessionRecorder_trackEventsThrottled = new WeakMap(), _SessionRecorder_handleVi
|
|
|
20834
20859
|
}
|
|
20835
20860
|
};
|
|
20836
20861
|
}, _SessionRecorder_trackEvents = async function _SessionRecorder_trackEvents() {
|
|
20862
|
+
if (__classPrivateFieldGet(this, _SessionRecorder_uploading, "f"))
|
|
20863
|
+
return;
|
|
20864
|
+
if (this.sessionEvents.length === 0)
|
|
20865
|
+
return;
|
|
20866
|
+
__classPrivateFieldSet(this, _SessionRecorder_uploading, true, "f");
|
|
20837
20867
|
try {
|
|
20838
|
-
if (this.sessionEvents.length === 0) {
|
|
20839
|
-
return;
|
|
20840
|
-
}
|
|
20841
|
-
const chunkTimestamp = this.sessionEvents[this.sessionEvents.length - 1].timestamp;
|
|
20842
20868
|
const events = [...this.sessionEvents];
|
|
20843
|
-
|
|
20844
|
-
|
|
20845
|
-
|
|
20846
|
-
|
|
20847
|
-
|
|
20848
|
-
|
|
20849
|
-
|
|
20850
|
-
});
|
|
20851
|
-
}
|
|
20869
|
+
const snapshot_count = events.length;
|
|
20870
|
+
const start_ts_ms = events[0].timestamp;
|
|
20871
|
+
const end_ts_ms = events[events.length - 1].timestamp;
|
|
20872
|
+
const state = readSessionState();
|
|
20873
|
+
if (!state) {
|
|
20874
|
+
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "No session state during upload — skipping chunk");
|
|
20875
|
+
return;
|
|
20852
20876
|
}
|
|
20853
|
-
|
|
20854
|
-
|
|
20877
|
+
const chunk_seq = state.chunk_seq;
|
|
20878
|
+
await uploadSessionEvents({
|
|
20879
|
+
user_id: this.userId,
|
|
20880
|
+
session_uuid: this.sessionUuid,
|
|
20881
|
+
chunk_seq,
|
|
20882
|
+
chunk_start_ts: new Date(start_ts_ms).toISOString(),
|
|
20883
|
+
chunk_end_ts: new Date(end_ts_ms).toISOString(),
|
|
20884
|
+
initial_url: chunk_seq === 0 ? state.initial_url : undefined,
|
|
20885
|
+
events,
|
|
20886
|
+
});
|
|
20887
|
+
this.sessionEvents = this.sessionEvents.slice(snapshot_count);
|
|
20888
|
+
const after = readSessionState();
|
|
20889
|
+
if (after && after.session_uuid === state.session_uuid) {
|
|
20890
|
+
writeSessionState({ ...after, chunk_seq: after.chunk_seq + 1 });
|
|
20855
20891
|
}
|
|
20856
20892
|
}
|
|
20857
20893
|
catch (err) {
|
|
20858
20894
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Event tracking failed", err);
|
|
20859
20895
|
}
|
|
20896
|
+
finally {
|
|
20897
|
+
__classPrivateFieldSet(this, _SessionRecorder_uploading, false, "f");
|
|
20898
|
+
}
|
|
20860
20899
|
}, _SessionRecorder_clearEvents = function _SessionRecorder_clearEvents() {
|
|
20861
20900
|
this.sessionEvents = [];
|
|
20862
|
-
}, _SessionRecorder_removeLocalSessionData = function _SessionRecorder_removeLocalSessionData() {
|
|
20863
|
-
localStorage.removeItem("userlensSessionUuid");
|
|
20864
|
-
localStorage.removeItem("userlensSessionLastActive");
|
|
20865
20901
|
};
|
|
20866
20902
|
|
|
20867
20903
|
export { SessionRecorder as default };
|