|
@@ -1,3 +1,6 @@
|
|
|
+import { record } from "rrweb";
|
|
|
+import Replayer from "rrweb-player";
|
|
|
+import "rrweb-player/dist/style.css"; // 必须导入样式
|
|
|
|
|
|
// 通用 JSBridge
|
|
|
window.JSBridge = {
|
|
@@ -157,6 +160,10 @@ class Tracker {
|
|
|
beforeDestroy: () => {}, // 销毁前的回调函数
|
|
|
crashTime: 3, // 闪退阈值(几秒内关闭)
|
|
|
idleTimeout: 300000, // 默认 5 分钟无操作视为离开
|
|
|
+ events: [], // 录制事件列表
|
|
|
+ stopFn: () => {}, // 停止录制函数
|
|
|
+ replayDom: '', // 回放dom
|
|
|
+ enableAutoRecording: true, // 是否开启自动录制
|
|
|
}
|
|
|
hasInitialized = false; // 是否已初始化
|
|
|
initialize(options) {
|
|
@@ -178,6 +185,10 @@ class Tracker {
|
|
|
this.idleTimeout = options.idleTimeout || this.initCofig.idleTimeout; // 默认 5 分钟无操作视为离开
|
|
|
this.sendDataDebounceTimers = {}; // 【2024-06-09】存储每种事件类型的定时器,用于事件级防抖
|
|
|
this.debounceTime = options.debounceTime || this.debounceTime || 300; // 【2024-06-09】防抖间隔支持初始化参数传入
|
|
|
+ this.events = options.events || this.initCofig.events; // 录制事件列表
|
|
|
+ this.stopFn = options.stopFn || this.initCofig.stopFn; // 停止录制函数
|
|
|
+ this.replayDom = options.replayDom || document.getElementById('replay'); // 回放dom
|
|
|
+ this.enableAutoRecording = options.enableAutoRecording || this.initCofig.enableAutoRecording; // 是否开启自动录制
|
|
|
|
|
|
window.JSBridge.getUserId((id) => {
|
|
|
this.userId = id;
|
|
@@ -209,6 +220,10 @@ class Tracker {
|
|
|
this.startHeartbeat();
|
|
|
console.log(`埋点初始化成功,用户ID: ${this.userId}`);
|
|
|
this.hasInitialized = true;
|
|
|
+ if(this.enableAutoRecording) {
|
|
|
+ console.log(`开启自动录制`);
|
|
|
+ this.startRecording(); // 开启自动录制
|
|
|
+ }
|
|
|
}
|
|
|
if (isBlocked) {
|
|
|
// restoreBlock();
|
|
@@ -288,6 +303,43 @@ class Tracker {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ startRecording() {
|
|
|
+ this.events = []; // 清空之前的记录
|
|
|
+ this.stopFn = record({
|
|
|
+ emit: (event) => { // 使用箭头函数确保 this 正确
|
|
|
+ this.events.push(event);
|
|
|
+ },
|
|
|
+ });
|
|
|
+ console.log("开始录制用户会话");
|
|
|
+ }
|
|
|
+
|
|
|
+ stopRecording() {
|
|
|
+ this.stopFn?.(); // 停止录制
|
|
|
+ const sessionData = JSON.stringify(this.events);
|
|
|
+ localStorage.setItem("rrweb-session", sessionData);
|
|
|
+ console.log("已停止录制,共记录 " + this.events.length + " 个事件");
|
|
|
+ console.log(localStorage.getItem("rrweb-session"));
|
|
|
+ }
|
|
|
+
|
|
|
+ replay() {
|
|
|
+ if (!this.events.length) return;
|
|
|
+ if (!this.replayDom) return;
|
|
|
+
|
|
|
+ this.replayDom.innerHTML = ""; // 清空容器
|
|
|
+
|
|
|
+ // 创建回放器
|
|
|
+ const replayer = new Replayer({
|
|
|
+ target: this.replayDom,
|
|
|
+ props: {
|
|
|
+ events: this.events,
|
|
|
+ width: 800,
|
|
|
+ height: 500,
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ replayer.play();
|
|
|
+ }
|
|
|
+
|
|
|
handleClickEvent = (event) => {
|
|
|
const target = event.target;
|
|
|
if (target.matches('[data-track]')) {
|
|
@@ -300,6 +352,12 @@ class Tracker {
|
|
|
};
|
|
|
this.sendData(trackInfo);
|
|
|
}
|
|
|
+ if (target.matches('[data-stopRecording]')) {
|
|
|
+ this.stopRecording();
|
|
|
+ }
|
|
|
+ if (target.matches('[data-replay]')) {
|
|
|
+ this.replay();
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
handleVisibilityChange = () => {
|
|
@@ -359,7 +417,6 @@ class Tracker {
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
destroy() {
|
|
|
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
|
|
window.removeEventListener('click', this.handleClickEvent);
|
|
@@ -384,4 +441,4 @@ class Tracker {
|
|
|
// tracker.init();
|
|
|
|
|
|
// 暴露给全局或者作为模块导出
|
|
|
-window.Tracker = Tracker;
|
|
|
+window.Tracker = Tracker;
|