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/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,65 +20682,61 @@ 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
20741
|
const { TIMEOUT = 30 * 60 * 1000, BUFFER_SIZE = 10, maskingOptions = ["passwords"], recordCrossOriginIframes = false, } = recordingOptions;
|
|
20723
20742
|
this.TIMEOUT = TIMEOUT;
|
|
@@ -20746,15 +20765,15 @@ class SessionRecorder {
|
|
|
20746
20765
|
this.rrwebStop();
|
|
20747
20766
|
this.rrwebStop = null;
|
|
20748
20767
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
|
|
20749
|
-
|
|
20750
|
-
window.removeEventListener("
|
|
20768
|
+
clearSessionState();
|
|
20769
|
+
window.removeEventListener("pagehide", __classPrivateFieldGet(this, _SessionRecorder_handlePageHide, "f"));
|
|
20751
20770
|
}
|
|
20752
20771
|
catch (err) {
|
|
20753
20772
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Stop failed", err);
|
|
20754
20773
|
}
|
|
20755
20774
|
}
|
|
20756
20775
|
}
|
|
20757
|
-
_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) {
|
|
20758
20777
|
if (!this.debug)
|
|
20759
20778
|
return;
|
|
20760
20779
|
if (error) {
|
|
@@ -20779,7 +20798,7 @@ _SessionRecorder_trackEventsThrottled = new WeakMap(), _SessionRecorder_handleVi
|
|
|
20779
20798
|
plugins: [getRecordConsolePlugin()],
|
|
20780
20799
|
checkoutEveryNth: 100,
|
|
20781
20800
|
});
|
|
20782
|
-
__classPrivateFieldGet(this, _SessionRecorder_instances, "m",
|
|
20801
|
+
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_initListeners).call(this);
|
|
20783
20802
|
}, _SessionRecorder_isUserInteraction = function _SessionRecorder_isUserInteraction(event) {
|
|
20784
20803
|
var _a;
|
|
20785
20804
|
if (event.type === 3) {
|
|
@@ -20791,15 +20810,16 @@ _SessionRecorder_trackEventsThrottled = new WeakMap(), _SessionRecorder_handleVi
|
|
|
20791
20810
|
var _a;
|
|
20792
20811
|
try {
|
|
20793
20812
|
const now = Date.now();
|
|
20794
|
-
const
|
|
20795
|
-
|
|
20796
|
-
if (lastActive && now - lastActive > this.TIMEOUT) {
|
|
20813
|
+
const state = readSessionState();
|
|
20814
|
+
if (state && now - state.last_active > this.TIMEOUT) {
|
|
20797
20815
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_resetSession).call(this);
|
|
20798
20816
|
takeFullSnapshot(true);
|
|
20799
20817
|
}
|
|
20800
|
-
// only update lastActive on actual user interactions, not DOM mutations
|
|
20801
20818
|
if (__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_isUserInteraction).call(this, event)) {
|
|
20802
|
-
|
|
20819
|
+
const latest = readSessionState();
|
|
20820
|
+
if (latest) {
|
|
20821
|
+
writeSessionState({ ...latest, last_active: now });
|
|
20822
|
+
}
|
|
20803
20823
|
}
|
|
20804
20824
|
this.sessionEvents.push(event);
|
|
20805
20825
|
if (this.sessionEvents.length >= this.BUFFER_SIZE) {
|
|
@@ -20810,24 +20830,29 @@ _SessionRecorder_trackEventsThrottled = new WeakMap(), _SessionRecorder_handleVi
|
|
|
20810
20830
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Event handling failed", err);
|
|
20811
20831
|
}
|
|
20812
20832
|
}, _SessionRecorder_resetSession = function _SessionRecorder_resetSession() {
|
|
20813
|
-
|
|
20833
|
+
clearSessionState();
|
|
20814
20834
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_clearEvents).call(this);
|
|
20815
20835
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_createSession).call(this);
|
|
20816
20836
|
}, _SessionRecorder_createSession = function _SessionRecorder_createSession() {
|
|
20817
20837
|
const now = Date.now();
|
|
20818
|
-
const
|
|
20819
|
-
const
|
|
20820
|
-
|
|
20821
|
-
if (!storedUuid || isExpired) {
|
|
20838
|
+
const state = readSessionState();
|
|
20839
|
+
const isExpired = !state || now - state.last_active > this.TIMEOUT;
|
|
20840
|
+
if (!state || isExpired) {
|
|
20822
20841
|
this.sessionUuid = generateUuid();
|
|
20823
|
-
|
|
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
|
+
});
|
|
20824
20849
|
}
|
|
20825
20850
|
else {
|
|
20826
|
-
this.sessionUuid =
|
|
20851
|
+
this.sessionUuid = state.session_uuid;
|
|
20852
|
+
writeSessionState({ ...state, last_active: now });
|
|
20827
20853
|
}
|
|
20828
|
-
|
|
20829
|
-
|
|
20830
|
-
window.addEventListener("visibilitychange", __classPrivateFieldGet(this, _SessionRecorder_handleVisibilityChange, "f"));
|
|
20854
|
+
}, _SessionRecorder_initListeners = function _SessionRecorder_initListeners() {
|
|
20855
|
+
window.addEventListener("pagehide", __classPrivateFieldGet(this, _SessionRecorder_handlePageHide, "f"));
|
|
20831
20856
|
}, _SessionRecorder_throttle = function _SessionRecorder_throttle(func, delay) {
|
|
20832
20857
|
let lastCall = 0;
|
|
20833
20858
|
return (...args) => {
|
|
@@ -20838,34 +20863,45 @@ _SessionRecorder_trackEventsThrottled = new WeakMap(), _SessionRecorder_handleVi
|
|
|
20838
20863
|
}
|
|
20839
20864
|
};
|
|
20840
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");
|
|
20841
20871
|
try {
|
|
20842
|
-
if (this.sessionEvents.length === 0) {
|
|
20843
|
-
return;
|
|
20844
|
-
}
|
|
20845
|
-
const chunkTimestamp = this.sessionEvents[this.sessionEvents.length - 1].timestamp;
|
|
20846
20872
|
const events = [...this.sessionEvents];
|
|
20847
|
-
|
|
20848
|
-
|
|
20849
|
-
|
|
20850
|
-
|
|
20851
|
-
|
|
20852
|
-
|
|
20853
|
-
|
|
20854
|
-
});
|
|
20855
|
-
}
|
|
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;
|
|
20856
20880
|
}
|
|
20857
|
-
|
|
20858
|
-
|
|
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 });
|
|
20859
20895
|
}
|
|
20860
20896
|
}
|
|
20861
20897
|
catch (err) {
|
|
20862
20898
|
__classPrivateFieldGet(this, _SessionRecorder_instances, "m", _SessionRecorder_log).call(this, "Event tracking failed", err);
|
|
20863
20899
|
}
|
|
20900
|
+
finally {
|
|
20901
|
+
__classPrivateFieldSet(this, _SessionRecorder_uploading, false, "f");
|
|
20902
|
+
}
|
|
20864
20903
|
}, _SessionRecorder_clearEvents = function _SessionRecorder_clearEvents() {
|
|
20865
20904
|
this.sessionEvents = [];
|
|
20866
|
-
}, _SessionRecorder_removeLocalSessionData = function _SessionRecorder_removeLocalSessionData() {
|
|
20867
|
-
localStorage.removeItem("userlensSessionUuid");
|
|
20868
|
-
localStorage.removeItem("userlensSessionLastActive");
|
|
20869
20905
|
};
|
|
20870
20906
|
|
|
20871
20907
|
exports.default = SessionRecorder;
|