曹旭辉 发表于 2024-9-16 04:36:17

使用rrweb进行前端页面可回溯录制

配景

公司需要对用户的页面交互进行可回溯记录,固使用rrweb进行需求实现;
先容

rrweb 全称 'record and replay the web',是当下很流行的一个录制屏幕的开源库。与我们传统认知的录屏方式(如 WebRTC)不同的是,rrweb 录制的不是真正的视频流,而是一个记录页面 DOM 变革的 JSON 数组,因此不能录制整个显示器的屏幕,只能录制欣赏器的一个页签。
   rrweb的github地址:rrweb/guide.zh_CN.md at master · rrweb-io/rrweb · GitHub
功能实现



[*] 首先安装rrweb和rrweb-player
npm install --save rrweb
npm install --save rrweb-player

[*] 创建screen-record.js文件对上报方法进行封装
rrweb 提供了一个基于 fflate 的简单压缩函数,在提交录制中可以作为 packFn 传入使用。
可以将录制流程分为不同的节点(stepIndex),与服务端商量好进行时间轴排序存储
import { record, pack, unpack } from 'rrweb'
import { saveTCDataByXhr, saveTCDataByBeacon, getTcKey, saveTCDataByFetch } from '@/api/record'

/**
* 开始屏幕录制
*/
export function startRecord() {
if (typeof window.stopRecordFn === 'function') {
    window.stopRecordFn()
}
window.screenRecords = []
console.log('=========== Record Start ==========')
const stopFn = record({
    emit(event) {
      window.screenRecords.push(event)
    },
    packFn: pack, // rrweb 内包含了基于 fflate 的简单压缩 rrweb.pack,在录制时可以作为 packFn 传入。
    inlineStylesheet: false,
    sampling: {
      scroll: 150, // 每 150ms 最多触发一次
      // set the interval of media interaction event
      media: 800,
      // input: 'last' // 连续输入时,只录制最终值
    },
})
window.stopRecordFn = stopFn
}

[*]  提交录制数据
由于一般提交页面录制数据的时机为页面离开的时机,当欣赏器中的某个页面发生停止时,不能包管进程中的HTTP请求会成功(请参阅有关“停止”和页面生命周期的其他状态的更多信息)。以是我们有如下几个解决方法:

[*]使用async/await阻塞异步请求,直至请求发送完毕再进行路由跳转,但似乎这种方法牺牲了用户体验;
[*]使用fetch请求的keepalive标识进行数据提交,但是会有欣赏器兼容性题目,其兼容性如下图:https://i-blog.csdnimg.cn/direct/55a5b6895f7d440a82f42b1c68718f1a.png
[*]使用Navigator.sendBeacon(),该函数专门用于发送单向请求(信标)。但是此 API 不答应您发送自定义标头。因此,为了让我们以“application/json”的情势发送数据,我们需要做一些小调整并使用Blob: /**
* beacon上传可回溯录制数据
*/
export function saveTCDataByBeacon(data) {
const url = `${RECORD_BASE_API}/tc/groOrder/saveTcData`
// let data = new FormData();
// for (let key in params) {
//   data.append(key, params);
// }
const headers = {
    type: 'application/json'
}
const blob = new Blob(, headers)
return navigator.sendBeacon(url, blob)
}
但是注意:beacon只能提交少量数据,chrome限定最高64KB
基于上诉思路我们封装提交录制方法:
/**
*
* @param stepIndex 录制节点 立即投保: 100, 暂存: 200, 提交审核: 300, 支付: 400, 支付成功: 500
* @param goodsCode
* @param submitType 提交数据的方式 默认xhr,页面返回beacon
*/
export async function submitRecord(stepIndex, linkNo, submitType = 'xhr') {
if (!window.screenRecords || !window.screenRecords.length) return
const startEvent = unpack(window.screenRecords)
const endEvent = unpack(window.screenRecords)
const { data: tcKey } = await getTcKey({ linkNo })
const dataString = JSON.stringify(window.screenRecords)
window.screenRecords.length = 0

const params = {
    linkNo,
    tcKey,
    stepIndex,
    startTime: startEvent.timestamp,
    endTime: endEvent.timestamp,
    data: dataString,
}
// if (stepIndex >= 60) {
//   params.linkNo = linkNo
//   delete params.tcKey
// }

return new Promise((reslove, reject) => {
    // @ts-ignore
    if (navigator && navigator.sendBeacon && submitType === 'beacon') {
      // beacon只能提交少量数据,chrome限制最高64KB
      const result = saveTCDataByBeacon(params)
      if (result) {
      console.log('回溯数据请求成功排队 等待执行')
      reslove()
      } else {
      console.log('回溯数据提交失败')
      reject('回溯数据提交失败')
      }
    } else if (submitType === 'fetch') {
      saveTCDataByFetch(params).then(() => {
      reslove()
      }).catch(e => reject(e))
    } else {
      saveTCDataByXhr(params).then(() => {
      reslove()
      }).catch(e => reject(e))
    }
})
}
/**
* xhr上传可回溯录制数据
*/
export function saveTCDataByXhr(data) {
return request({
    url: `${RECORD_BASE_API}/tc/groOrder/saveTcData`,
    method: 'post',
    data,
})
}

/**
* Fetch上传可回溯录制数据 将 keepalive 设置为 true 就可确保浏览器关闭或回退,调用接口的链接不会被关闭,调用成功
*/
export function saveTCDataByFetch(data) {
const url = `${RECORD_BASE_API}/tc/groOrder/saveTcData`
const headers = {
    'Content-Type': 'application/json;charset=UTF-8',
    : getToken()
}
return new Promise((resolve, reject) => {
    fetch(url, {
      method: 'POST',
      headers,
      body: JSON.stringify(data),
      // keepalive: true,
    })
      .then(res => {
      resolve(res.json())
      })
      .catch(error => {
      reject(error)
      })
})
}
页面使用

开始录制:
startRecord() 提交录制数据:
submitRecord(300, this.baseInfo.grpLinkNo) 页面回放:
</template>   
<!-- 回放容器 -->
<div ref="replayContainer" class="replay-container flex justify-center" />
</template>

<script setup>
import { ref, onMounted} from 'vue';
import RrwebPlayer from 'rrweb-player';
import { unpack } from 'rrweb';

const replayContainer = ref();
const events = ref([]) // 回放数据通过服务端获取

onMounted(() => {
   new RrwebPlayer({
         target: replayContainer.value,
         unpackFn: unpack,
         props: {
            events: events, // 包含回放所需的数据
            skipInactive: true, // 是否快速跳过无用户操作的阶段
            autoPlay: false, // 是否自动播放
            UNSAFE_replayCanvas: true, // 回放时是否回放 canvas 内容,开启后将会关闭沙盒策略,导致一定风险
            mouseTail: false, // 是否在回放时增加鼠标轨迹
          },
      })
   })
})
</script>

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 使用rrweb进行前端页面可回溯录制