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