Skip to content

构建回放界面

请将应用的授权边界置于 rrweb Cloud 之前。私有 API 密钥(secret_key_rr_*)拥有完整的读写权限。它必须保留在 受信服务器或密钥管理器中。绝不要在浏览器代码中暴露私有 API 密钥。 公开写入密钥可以摄取事件,但无法获取录制。

下面的模式分为两部分:你的服务器先对查看者进行身份验证,并向 Cloud 请求一个受限的事件 URL,然后浏览器下载该事件流并 将其交给 rrwebPlayer

从受信服务器获取回放访问权限

将私有 API 密钥存储为服务器密钥,应用你自己的授权检查,然后暴露一个向 rrweb Cloud 请求限定范围的已签名事件 URL、并只返回回放界面所需元数据字段的服务器路由。

RRWEB_CLOUD_PRIVATE_API_KEY 存储为服务器密钥。其值以 secret_key_rr_ 开头;不要硬编码。在应用你自己的授权检查之后,暴露一个 服务器路由,例如 /api/replay-access/:recordingId,运行以下函数:

javascript
const rrwebCloudApi = 'https://api.rrweb.com';

export async function getReplayAccess(recordingId) {
  const detailUrl = new URL(`/recordings/${recordingId}`, rrwebCloudApi);
  detailUrl.searchParams.set('includeSignedUrls', 'true');

  const response = await fetch(detailUrl, {
    method: 'GET',
    headers: {
      Authorization: `Bearer ${process.env.RRWEB_CLOUD_PRIVATE_API_KEY}`,
      Accept: 'application/json',
    },
  });

  if (!response.ok) {
    throw new Error(`rrweb Cloud returned ${response.status}`);
  }

  const recording = await response.json();
  if (!recording.links.eventsSigned) {
    throw new Error('A signed event URL was not available');
  }

  return {
    recordingId: recording.recordingId,
    metadata: browserSafeMetadata(recording.metadata),
    eventsUrl: new URL(recording.links.eventsSigned, rrwebCloudApi).toString(),
  };
}

// Metadata can carry server-only fields such as support case ids or account
// segments. Return only the keys the replay UI needs.
const browserSafeMetadataKeys = ['environment', 'app_version', 'feature_flag'];

function browserSafeMetadata(metadata = {}) {
  return Object.fromEntries(
    Object.entries(metadata).filter(([key]) =>
      browserSafeMetadataKeys.includes(key),
    ),
  );
}

这使用了获取录制。其正常 响应为 { recordingId, metadata, links };设置 includeSignedUrls=true 会在配置签名后添加 eventsSigned。仅在验证已登录的应用用户有权查看该录制之后 才返回结果,并对返回的元数据键做白名单:应用元数据 可能包含不得到达浏览器的仅限服务器使用的分类信息。

你也可以在服务器上请求获取录制 事件,并代理返回的顶层事件 数组。受限 URL 可以避免让事件正文经过你的服务器,而代理则能让你的服务器 对响应有更严格的控制。

在浏览器中初始化播放器

安装播放器及其样式表:

bash
npm install rrweb-player

在模块运行之前,向页面添加挂载元素:

html
<div id="replay"></div>

该示例渲染一个 1024×576 像素的播放器。请确保周围布局能够 容纳该尺寸,或者选择适合你回放视图的尺寸。

然后向你自己的已认证路由请求回放访问权限,获取受限事件 数组,并初始化播放器。此示例使用有效的录制 UUID, 且不会使用写入凭据读取数据:

javascript
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';

const replayTarget = document.querySelector('#replay');
if (!(replayTarget instanceof HTMLElement)) {
  throw new Error('Replay mount element #replay was not found');
}

const recordingId = '550e8400-e29b-41d4-a716-446655440000';
const accessResponse = await fetch(`/api/replay-access/${recordingId}`, {
  credentials: 'same-origin',
});

if (!accessResponse.ok) {
  throw new Error(`Replay access failed: ${accessResponse.status}`);
}

const { eventsUrl } = await accessResponse.json();
const eventsResponse = await fetch(eventsUrl);

if (!eventsResponse.ok) {
  throw new Error(`Event retrieval failed: ${eventsResponse.status}`);
}

const events = await eventsResponse.json();
if (!Array.isArray(events) || events.length === 0) {
  throw new Error('The recording has no replayable events');
}

const player = new rrwebPlayer({
  target: replayTarget,
  props: {
    events,
    width: 1024,
    height: 576,
    autoPlay: false,
    showController: true,
    skipInactive: true,
    mouseTail: true,
  },
});

当其容器被移除时,使用 player.$destroy() 销毁组件。 有关控件、插件、大型事件流和故障诊断,请继续阅读 高级回放与故障排查