# Technical Documentation: rrweb-io/rrweb

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/rrweb-io/rrweb)

### Overview (`README.md`)

<p align="center">
  <img width="100px" height="100px" src="https://www.rrweb.io/favicon.png">
</p>
<p align="center">
  <a href="https://www.rrweb.io/" style="font-weight: bold">Try rrweb</a>
</p>

# rrweb

**[(new!) rrweb cloud - hosted rrweb backend is now available](https://rrweb.com/)**

[](https://join.slack.com/t/rrweb/shared_invite/zt-41pxl6epj-~N2YyGQxvI5r3V6d59it8w)
[](https://twitter.com/rrweb_io)
[](https://www.reddit.com/r/rrweb)


[](https://cdn.rrweb.com/catalog.json)

[中文文档](./README.zh_CN.md)

rrweb refers to 'record and replay the web', which is a tool for recording and replaying users' interactions on the web.

## Guide

[**📚 Read the rrweb guide here. 📚**](https://rrweb.com/docs/guide)

[**🍳 Recipes 🍳**](https://rrweb.com/docs/recipes/)

[**📺 Presentation:** Hacking the browser to digital twin your users 📺](https://youtu.be/cWxpp9HwLYw)

## Project Structure

rrweb is composed of the following principal parts:

- **[rrweb-snapshot](packages/rrweb-snapshot/)**, including both snapshot and rebuilding features. The snapshot is used to convert the DOM and its state into a serializable data structure with a unique identifier; the rebuilding feature is to rebuild the snapshot into corresponding DOM. This package can be used on it's own to provide a static HTML based 'screenshot' of the current web page state, with all Javascript deactivated.
- **[record](packages/record)**, builds on an initial snapshot to record all HTML state changes (mutations) and user interactions as the user browses the web page. Multiple page loads can be chained together into a single recording.
- **[replay](packages/replay)**, rebuilds the inital snapshot and replays the list or stream of subsequent events to show a 'session replay'
- **[rrweb-player](packages/rrweb-player/)**, a player UI on top of the replay package to provide GUI-based functions like pause, fast-forward, drag and drop to play at any time.

## Roadmap

- ✅ [storage engine: do deduplication on a large number of rrweb sessions](https://rrweb.com)
- Token efficient AI session replay format (in progress)
- provide plugins via the new plugin API, including:
  - XHR plugin
  - fetch plugin
  - GraphQL plugin
  - ...

## Internal Design

- [serialization](./docs/serialization.md)
- [incremental snapshot](./docs/observer.md)
- [replay](./docs/replay.md)
- [sandbox](./docs/sandbox.md)

## Contribute Guide

Since we want the record and replay sides to share a strongly typed data structure, rrweb is developed with typescript which provides stronger type support.

[Typescript handbook](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html)

For the latest setup, testing, and pull request workflow, see [Contributing to rrweb](./CONTRIBUTING.md).

In addition to adding integration tests and unit tests, rrweb also provides a REPL testing tool.

[Using the REPL tool](./guide.md#REPL-tool)

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt="Justin Halsall"
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`.changeset/README.md`)

# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

---

### Overview (`packages/README.md`)

# rrweb Packages

rrweb is a monorepo containing the following packages:

- [rrweb](rrweb/) — Original package including both record and replay; deprecated in favour of `@rrweb/record` + `@rrweb/replay` (or `@rrweb/all`)
- [@rrweb/record](record/) — The record related code in rrweb and is designed to be published in a frontend app/webpage
- [@rrweb/replay](replay/) — Rebuild and replay recorded events in an iframe
- [rrweb-player (ui)](rrweb-player/) — Builds on @rrweb/replay to provide a feature-rich playback UI out of the box
- [rrweb-snapshot](rrweb-snapshot/) — Snapshot the DOM into a stateful and serializable data structure &mdash; basis of the FullSnapshot event in an rrweb recording
- [@rrweb/types](types/) — Shared types used across rrweb packages
- [@rrweb/utils](utils/) — Shared utility functions used across rrweb packages
- [rrdom](rrdom/) — a virtual dom library used to fast forward DOM mutation during replay
- [rrdom-nodejs](rrdom-nodejs/) — A Node.js implementation of the `rrdom` library for use in server side processing of rrweb data
- [rrvideo](rrvideo/) — A tool for transforming the session recorded by rrweb into a video
- [web-extension](web-extension/) — Provides a browser extension for recording and replaying web pages
- [@rrweb/packer](packer/) — Basic per-event compression before network transmission
- [@rrweb/all](all/) — Convenience package that includes record, replay + the packer, but no longer includes any plugins

See also [plugin packages](../docs/recipes/plugin-api.md).

---

### Overview (`packages/all/README.md`)

# @rrweb/all

## Overview

Convenience package that includes a bundle of rrweb packages.

In most production setups, the recorder and replayer are deployed to different pages/apps. Use [@rrweb/record](packages/record) on recorded pages and [@rrweb/replay](packages/replay) (or [rrweb-player](packages/rrweb-player) to include UI) on replay pages.

| Use case                                            | Package choice                    |
| --------------------------------------------------- | --------------------------------- |
| Most new apps (explicit record/replay dependencies) | `@rrweb/record` + `@rrweb/replay` |
| Single import for record, replay + packer           | `@rrweb/all`                      |

Use `@rrweb/all` when you intentionally want one package for convenience (for example demos, tooling, or simplified setups); the deprecated `rrweb` package also served a similar all-in-one purpose.

Includes the following packages:

- [rrweb](../rrweb)
- [@rrweb/record](../record)
- [@rrweb/replay](../replay)
- [@rrweb/packer](../packer)

## Installation

### 1) Bundler / npm

```bash
npm install @rrweb/all
```

```js
import { record, Replayer, pack, unpack } from '@rrweb/all';
import '@rrweb/all/dist/style.css';
```

For API details and examples, see the [guide](../../guide.md).

### 2) Browser Without Bundler (ESM)

```html
<link rel="stylesheet" href="https://cdn.rrweb.com/all/current/style.css" />
<script type="module">
  import {
    record,
    Replayer,
    pack,
    unpack,
  } from 'https://cdn.rrweb.com/all/current/all.js';
</script>
```

Use `current` for the latest stable release, or pin an exact version such as
`https://cdn.rrweb.com/all/2.0.1/all.js` and
`https://cdn.rrweb.com/all/2.0.1/style.css` for immutable production URLs. See the
[CDN assets docs](https://rrweb.com/docs/cloud/cdn-assets) for the full URL
shape and catalog.

### 3) Legacy Direct `<script>` Include (UMD fallback)

Use this only for compatibility with non-module environments.

```html
<link rel="stylesheet" href="https://cdn.rrweb.com/all/current/style.css" />
<script src="https://cdn.rrweb.com/all/current/all.umd.cjs"></script>
```

The legacy UMD global is `rrweb`, so you will need to prefix the example APIs, e.g. `rrweb.record`, `new rrweb.Replayer(...)`, `rrweb.pack`, and `rrweb.unpack`, rather than using these functions directly.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/browser-client/README.md`)

# @rrweb/browser-client

## Overview

Browser client for recording rrweb sessions to an rrweb Cloud-compatible API. It wraps rrweb recording, streams events over WebSocket, and falls back to HTTP POST for buffered events.

This README covers the npm/ESM package. For the hosted script snippet and broader rrweb Cloud setup, see the [Browser Client guide](https://rrweb.com/docs/cloud/browser-client).

## Installation

```bash
npm install @rrweb/browser-client
```

## Quick Start

```js
import rrwebBrowserClient, {
  start,
  stop,
  addMeta,
  addAnnotation,
  getRecordingId,
} from '@rrweb/browser-client';

start({
  publicApiKey: 'public_key_rr_...',
  includePii: false,
  meta: {
    userId: 'user-123',
    environment: 'production',
  },
});

addMeta({ plan: 'pro' });
addAnnotation({ type: 'timelineMarker', text: 'Checkout started' });

console.log('recording id', getRecordingId());
stop(false);
```

The default export exposes the same methods:

```js
rrwebBrowserClient.start({
  publicApiKey: 'public_key_rr_...',
});
```

## Options

- `serverUrl`: optional events endpoint. Defaults to `https://api.rrweb.com/recordings/{recordingId}/events/ws`. Include `{recordingId}` in custom URLs, or the client will add it as a query parameter. `http` and `https` URLs are converted to `ws` and `wss` for the WebSocket connection, and the HTTP fallback posts to the same endpoint without a trailing `/ws`. See [Recording endpoint proxying](https://rrweb.com/docs/cloud/ingest-proxying) when routing events through your own domain.
- `publicApiKey`: public write-only API key sent with WebSocket and HTTP fallback requests. rrweb Cloud public keys use the `public_key_rr_...` format. See [API Keys](https://rrweb.com/docs/cloud/api-keys).
- `includePii`: default `false`. When enabled, the client includes additional visitor metadata such as language, timezone, screen size, title, and referrer details. See [Pre-baked metadata](https://rrweb.com/docs/cloud/prebaked-meta).
- `meta`: custom recording metadata sent before recorded events. Built-in diagnostics such as `recordVersion`, `recordCommitHash`, `jsSource`, and `jsEntrypoint` are added automatically after custom metadata. See [Application Metadata](https://rrweb.com/docs/cloud/application-meta).
- `jsSource`: optional source identifier for programmatic loaders. URL values are recorded without query strings or hashes.
- `jsEntrypoint`: optional entrypoint label. Defaults to `programmatic` for direct `start()` calls and `script-tag` for script-tag autostart.
- rrweb record options: other options are passed through to `record()` from rrweb, such as masking, blocking, sampling, and DOM capture options. See the [rrweb recording docs](https://rrweb.com/docs/packages/record/readme).

### Stylesheet Capture

`inlineStylesheet` is currently used for stylesheet capture compatibility. Once the `captureAssets` recording API lands from the assets branch, `captureAssets.stylesheets` should replace that compatibility path.

## Recording Helpers

- `getRecordingId()`: returns the current recording id, creating it before `start()` if needed. Recording ids are stored in `sessionStorage`, so separate tabs get separate recording contexts.
- `addMeta(payload)`: adds or updates recording metadata after recording has started.
- `addPageviewMeta(payload)`: adds metadata for the current page view.
- `addCustomEvent(tag, payload)`: records a custom rrweb event.
- `addAnnotation(annotation)`: records a caption or timeline marker annotation.
- `stop(resetRecordingId)`: stops rrweb recording and closes the WebSocket. Pass `true` to clear the stored recording id before a future `start()`.

Both `addCustomEvent()` and `addAnnotation()` queue events until the recorder is ready,
including calls made before `start()` or while the document is still loading.
When recording starts, queued events pass through the configured `emit` callback
and are sent to the server with their original call-time timestamps. Events added
during active recording use the same callback and delivery path. Calling `stop()`
discards events still waiting for the recorder to start.

## Further Reading

- [Browser Client guide](https://rrweb.com/docs/cloud/browser-client)
- [WebSocket event streaming](https://rrweb.com/docs/cloud/websocket-ingest)
- [Application Metadata](https://rrweb.com/docs/cloud/application-meta)
- [Replaying Guide](https://rrweb.com/docs/cloud/replaying-guide)

## Local Dev/Test Env Vars

Copy `.env.example` to `.env` in this package when running local integration tests.

```bash
VITE_RRWEB_BROWSER_CLIENT_SERVER_URL=http://localhost:8787/recordings/{recordingId}/events/ws
VITE_RRWEB_BROWSER_CLIENT_API_BASE_URL=http://localhost:8787
VITE_TEST_API_KEY=public_key_rr_XXXX
```

---

### Overview (`packages/packer/README.md`)

# @rrweb/packer

## Overview

`@rrweb/packer` compresses rrweb events on the frontend before network transmission. It uses [fflate](https://github.com/101arrowz/fflate) for zlib compression and compresses each rrweb event separately as it is emitted. The encoder is needed on the recording side and adds roughly 17KB minified to your bundle (less after gzip). Decompression at replay time is handled separately via the `@rrweb/packer/unpack` entry point.

See the [storage recipe](../../docs/recipes/optimize-storage.md#compression) for more info on how it works.
And the [guide](../../guide.md) for more info on rrweb.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/record/README.md`)

# @rrweb/record

## Overview

This package contains all the record related code in rrweb and is designed to be published in a frontend app/webpage. See [Getting Started](../../guide.md#getting-started) in the rrweb guide for more information including the [record options](../../guide.md#record-options).

For websocket based recording against the rrweb cloud API use the [browser-client](https://rrweb.com/docs/cloud/browser-client) which builds on this package and accepts the same record options.

## Installation

### 1) Bundler / npm (Recommended)

```bash
npm install @rrweb/record
```

```js
import { record } from '@rrweb/record';
```

### 2) Browser Without Bundler (ESM)

Use the CDN-hosted `@rrweb/record` browser asset when loading directly in the
browser:

```html
<script type="module">
  import { record } from 'https://cdn.rrweb.com/record/current/dist/record.js';
</script>
```

Use `current` for the latest stable release, or pin an exact version such as
`https://cdn.rrweb.com/record/2.0.0/dist/record.js` for immutable
production URLs.

### 3) Legacy Direct `<script>` Include (UMD fallback)

Use this only for compatibility with non-module environments.

```html
<script src="https://cdn.rrweb.com/record/current/dist/record.umd.cjs"></script>
```

The legacy UMD global is `rrwebRecord`.

## Usage

```js
import { record } from '@rrweb/record';

record({
  emit(event) {
    // send event to server
  },
});
```

## Notes

Currently this package is really just a wrapper around the `record` function in the main `rrweb` package.
All `record` related code will get moved here in the future.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/replay/README.md`)

# @rrweb/replay

## Overview

This package contains all the necessary code to replay recorded events.
See the [replay](../../guide.md#replay) section of the guide for more info.

This package handles basic replay, but leaves UI and controls up to you.
For a pre-built UI check out the [rrweb-player](../../packages/rrweb-player/README.md) package.

## Installation

### 1) Bundler / npm (Recommended)

```bash
npm install @rrweb/replay
```

```js
import { Replayer } from '@rrweb/replay';
import '@rrweb/replay/dist/style.css';
```

### 2) Browser Without Bundler (ESM)

Use the CDN-hosted `@rrweb/replay` browser asset when loading directly in the
browser:

```html
<link
  rel="stylesheet"
  href="https://cdn.rrweb.com/replay/current/dist/style.css"
/>
<script type="module">
  import { Replayer } from 'https://cdn.rrweb.com/replay/current/dist/replay.js';
</script>
```

Use `current` for the latest stable release, or pin an exact version such as
`https://cdn.rrweb.com/replay/2.0.0/dist/replay.js` for immutable
production URLs.

### 3) Legacy Direct `<script>` Include (UMD fallback)

Use this only for compatibility with non-module environments.

```html
<link
  rel="stylesheet"
  href="https://cdn.rrweb.com/replay/current/dist/style.css"
/>
<script src="https://cdn.rrweb.com/replay/current/dist/replay.umd.cjs"></script>
```

The legacy UMD global is `rrwebReplay`.

## Usage

```js
import { Replayer } from '@rrweb/replay';
import '@rrweb/replay/dist/style.css';

const replayer = new Replayer(events, {
  // options
});
replayer.play();
```

## Notes

Currently this package is really just a wrapper around the `Replayer` class in the main `rrweb` package.
All `Replayer` related code will get moved here in the future.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/rrdom-nodejs/README.md`)

# rrdom-nodejs

## Overview

`rrdom-nodejs` is a Node.js implementation of the [`rrdom`](../rrdom/) library for use in server side processing of rrweb data. It allows you to replay and inspect recorded user interactions with `rrweb` in a Node.js environment.

See the [guide](../../guide.md) for more info on rrweb.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/rrdom/README.md`)

# rrdom

## Overview

`rrdom` is a virtual dom library that is used by `rrweb` to replay DOM mutations. It is a standalone library that can be used to create a virtual dom tree and apply patches to the real dom. It's used in `rrweb` to optimize replay performance especially when seeking.

See the [guide](../../guide.md) for more info on rrweb.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/rrvideo/README.md`)

# rrvideo

## Overview

[中文文档](./README.zh_CN.md)

rrvideo is a tool for transforming the session recorded by [rrweb](https://github.com/rrweb-io/rrweb) into a video.



## Install rrvideo

1. Install [Node.JS](https://nodejs.org/en/download/)。
2. Run `npm i rrvideo` to install the rrvideo CLI.

## Use rrvideo

### Transform a rrweb session(in JSON format) into a video.

```shell
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE
```

Running this command will output a `rrvideo-output.webm` file in the current working directory.

### Config the output path

```shell
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_FILE --output OUTPUT_PATH
```

### Config the replay

You can prepare a rrvideo config file and pass it to CLI.

```shell
rrvideo --input PATH_TO_YOUR_RRWEB_EVENTS_JSON_FILE --config PATH_TO_YOUR_RRVIDEO_CONFIG_FILE
```

You can find an example of the rrvideo config file [here](./rrvideo.config.example.json).

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/rrweb-player/README.md`)

# rrweb-player

## Overview

Use this package to get a feature-rich playback UI out of the box; if you want to build your own UI use the [@rrweb/replay](../../packages/replay/README.md) package.

This is the same replayer used in the [cloud dashboard](https://app.rrweb.com/), if you want to try out a replay of your website, you can sign up and make a quick recording using the 'Test record any webpage' bookmarklet.

## How is this different from `new Replayer()`?

`new Replayer()` is from the more foundational [@rrweb/replay](../../packages/replay/README.md) package, which renders and rebuilds an rrweb event stream within an iframe. This package builds on that to provide UI in the form of play/pause controls and a timeline using the [Svelte](https://svelte.dev/) UI framework. The similar naming is unfortunate, and we aim to fix that in a future major version.

## Installation

### 1) Bundler / npm (Recommended)

```shell
npm install rrweb-player
```

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

### 2) Browser Without Bundler (ESM)

```html
<link
  rel="stylesheet"
  href="https://cdn.rrweb.com/rrweb-player/current/style.css"
/>
<script type="module">
  import rrwebPlayer from 'https://cdn.rrweb.com/rrweb-player/current/rrweb-player.js';
</script>
```

Use `current` for the latest stable release, or pin an exact version such as
`https://cdn.rrweb.com/rrweb-player/2.0.0/rrweb-player.js` for immutable
production URLs. See the [CDN assets docs](https://rrweb.com/docs/cloud/cdn-assets)
for the full URL shape and catalog.

### 3) Legacy Direct `<script>` Include (UMD fallback)

Use this only for compatibility with non-module environments.

```html
<link
  rel="stylesheet"
  href="https://cdn.rrweb.com/rrweb-player/current/style.css"
/>
<script src="https://cdn.rrweb.com/rrweb-player/current/rrweb-player.umd.cjs"></script>
```

## Usage

```js
new rrwebPlayer({
  target: document.body, // customizable root element
  props: {
    events,
  },
});
```

## Options

| key                 | default          | description                                                                                                       |
| ------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------- |
| events              | []               | the events for replaying                                                                                          |
| width               | 1024             | the width of the replayer                                                                                         |
| height              | 576              | the height of the replayer                                                                                        |
| maxScale            | 1                | the maximum scale of the replayer (1 = 100%), set to 0 for unlimited                                              |
| autoPlay            | true             | whether to autoplay                                                                                               |
| speed               | 1                | The default speed to play at                                                                                      |
| speedOption         | [1, 2, 4, 8]     | speed options in UI                                                                                               |
| showCaptions        | true             | display timed captions from annotation events; viewers can toggle them with CC                                    |
| showController      | true             | whether to show the controller UI                                                                                 |
| timelineMarkerColor | rgb(73, 80, 246) | color of timeline marker annotations                                                                              |
| tags                | {}               | customize the custom events style with a key-value map                                                            |
| inactiveColor       | #D4D4D4          | Customize the color of inactive periods indicator in the progress bar with a valid CSS color string.              |
| ...                 | -                | all the [rrweb Replayer options](https://github.com/rrweb-io/rrweb/blob/main/guide.md#options-1) will be bypassed |

## Captions and timeline markers

Use `record.addAnnotation()` to add captions and timeline markers. Captions are
shown by default; set `showCaptions: false` to hide them initially. Viewers can
toggle them with CC.

```js
record.addAnnotation({ type: 'caption', text: 'Click Save.' });
record.addAnnotation({
  type: 'timelineMarker',
  text: 'Saving also creates a default workspace.',
});
record.addAnnotation({ type: 'caption', text: '' });
```

Captions persist until replaced or cleared with `''` or `false`.
`timelineMarkerColor` controls annotation marker colors independently of custom
event `tags`. Text is plain text; HTML is not interpreted.

Tab to the playback position slider to seek with the arrow keys in five-second
steps, or use Home/End to jump to the start/end.

See the [annotation recipe](../../docs/recipes/annotations.md)
for replay callbacks, keyboard interactions, seeking, and TypeScript usage.

## Methods on the rrwebPlayer component

```ts
addEventListener(event: string, handler: (params: any) => unknown): void;
```

```ts
addEvent(event: eventWithTime): void;
```

```ts
getMetaData() => {
    startTime: number;
    endTime: number;
    totalTime: number;
}
```

```ts
getReplayer() => Replayer;
```

```ts
getMirror() => Mirror;
```

Toggles between play/pause

```ts
toggle();
```

Sets speed of player

```ts
setSpeed(speed: number)
```

Turns on/off skip inactive

```ts
toggleSkipInactive();
```

Triggers resize, do this whenever you change width/height

```ts
triggerResize();
```

Plays replay

```ts
play();
```

Pauses replay

```ts
pause();
```

Go to a point in time and pause or play from then

```ts
goto(timeOffset: number, play?: boolean)
```

Plays from a time to a time and (optionally) loop

```ts
playRange(
    timeOffset: number,
    endTimeOffset: number,
    startLooping: boolean = false,
    afterHook: undefined | (() => void) = undefined,
  )
```

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/rrweb-snapshot/README.md`)

# rrweb-snapshot

## Overview

Snapshot the DOM into a stateful and serializable data structure.
Also, provide the ability to rebuild the DOM via snapshot.

## API

This module export following methods:

### snapshot

`snapshot` will traverse the DOM and return a stateful and serializable data structure which can represent the current DOM **view**.

There are several things will be done during snapshot:

1. Inline some DOM states into HTML attributes, e.g, HTMLInputElement's value.
2. Turn script tags into `noscript` tags to avoid scripts being executed.
3. Try to inline stylesheets to make sure local stylesheets can be used.
4. Make relative paths in href, src, CSS to be absolute paths.
5. Give an id to each Node, and return the id node map when snapshot finished.

#### rebuild

`rebuild` will build the DOM according to the taken snapshot.

In browser environments, `rebuild()` is a low-level API and requires a document created by `rebuildIntoSandboxedIframe()`, unless `UNSAFE_allowUnprotectedRebuild: true` is passed. Untrusted replay data must not be rebuilt directly into the top-level `document` or a caller-created iframe document.

For browser usage, prefer `rebuildIntoSandboxedIframe`:

`rebuildIntoSandboxedIframe` requires an explicit `root` element.

```ts
const { iframe, node } = rebuildIntoSandboxedIframe(snapshot, {
  root: document.body,
  cache,
  mirror,
});
```

If you need to manage the iframe separately, create it with `createSandboxedIframe()` before calling `rebuild()`.

If you intentionally accept the script-execution risk, pass `UNSAFE_allowUnprotectedRebuild: true` to `rebuild`.

There are several things will be done during rebuild:

1. Add data-rrid attribute if the Node is an Element.
2. Create some extra DOM node like text node to place inline CSS and some states.
3. Add data-extra-child-index attribute if Node has some extra child DOM.

#### serializeNodeWithId

`serializeNodeWithId` can serialize a node into snapshot format with id.

#### buildNodeWithSN

`buildNodeWithSN` will build DOM from serialized node and store serialized information in the `mirror.getMeta(node)`.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/rrweb/README.md`)

# rrweb

## Overview

> **Deprecated.** `rrweb` is kept for backward compatibility only. New projects should depend on [@rrweb/record](packages/record/) and [@rrweb/replay](packages/replay/) directly, or use [@rrweb/all](packages/all/) for a single convenience import. Moving to these entrypoints lets us slim down and eventually remove this package.

rrweb refers to 'record and replay the web', a tool for recording and replaying users' interactions on the web.

In most production setups, the recorder and replayer are deployed to different pages/apps. Use [@rrweb/record](packages/record/) on recorded pages and [@rrweb/replay](packages/replay/) (or [rrweb-player](packages/rrweb-player/) to include UI) on replay pages.

| Use case                                        | Package choice                    |
| ----------------------------------------------- | --------------------------------- |
| Most apps (explicit record/replay dependencies) | `@rrweb/record` + `@rrweb/replay` |
| Single import for record, replay + packer       | `@rrweb/all`                      |

### Dev Note

As this was the original rrweb package, typescript code for both @rrweb/record and @rrweb/replay still lives in src/record and src/replay in this package. These will be refactored into their respective packages in due course, but for now this package is the principal one for both record and replay related PRs. See [Contributing to rrweb](../../CONTRIBUTING.md) for more info.

## Installation

### 1) Bundler / npm (Recommended)

For new projects:

```shell
npm install @rrweb/record @rrweb/replay
```

```js
import { record } from '@rrweb/record';
import { Replayer } from '@rrweb/replay';
import '@rrweb/replay/dist/style.css';
```

Convenience single-package option:

```shell
npm install @rrweb/all
```

```js
import { record, Replayer, pack, unpack } from '@rrweb/all';
import '@rrweb/all/dist/style.css';
```

Legacy compatibility package:

```shell
npm install rrweb
```

```js
import { record, Replayer } from 'rrweb';
import 'rrweb/dist/style.css';
```

### 2) Browser Without Bundler (ESM)

```html
<link
  rel="stylesheet"
  href="https://cdn.rrweb.com/replay/current/dist/style.css"
/>
<script type="module">
  import { record } from 'https://cdn.rrweb.com/record/current/dist/record.js';
  import { Replayer } from 'https://cdn.rrweb.com/replay/current/dist/replay.js';
</script>
```

Use `current` for the latest stable release, or pin exact versions such as
`https://cdn.rrweb.com/record/2.0.0/dist/record.js` and
`https://cdn.rrweb.com/replay/2.0.0/dist/replay.js` for immutable
production URLs.

### 3) Legacy Direct `<script>` Include (UMD fallback)

Use this only for compatibility with non-module environments; modern browsers
support the ESM method above.

```html
<link
  rel="stylesheet"
  href="https://cdn.rrweb.com/replay/current/dist/style.css"
/>
<script src="https://cdn.rrweb.com/record/current/dist/record.umd.cjs"></script>
<script src="https://cdn.rrweb.com/replay/current/dist/replay.umd.cjs"></script>
```

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/types/README.md`)

# @rrweb/types

## Overview

This package contains the shared types used across rrweb packages. rrweb makes extensive use of typescript types to enforce correctness and define the data API between packages. Check out the [Events](../../docs/events.md) doc for an introduction to the principal `Event` type. [View the source code](https://github.com/rrweb-io/rrweb/blob/main/packages/types/src/index.ts) to get a flavour the types available, or check out the [Contributor Guide](../../CONTRIBUTING.md) for more info.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/utils/README.md`)

# @rrweb/utils

## Overview

This package contains the shared utility functions used across rrweb packages.
See the [guide](../../guide.md) for more info on rrweb.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://www.rrweb.io/logos/smartx.png">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://www.rrweb.io/logos/posthog.png">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
    <tr>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://cdn.analyzee.io/assets/analyzee-logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/web-extension/README.md`)

<p align="center">
  <img width="100px" height="100px" src="https://www.rrweb.io/favicon.png">
</p>

# rrweb extension

## Overview

The package web-extension provides a browser extension for recording and replaying web pages.

## Installation

```
yarn install
```

## Build

```bash
# build for chrome
yarn build:chrome

# build for firefox
yarn build:firefox
```

## Development

```bash
# start a development chrome browser
yarn dev:chrome
# start a development firefox browser
yarn dev:firefox
```

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/plugins/rrweb-plugin-canvas-webrtc-record/README.md`)

# rrweb canvas webrtc plugin

Plugin that live streams contents of canvas elements via webrtc

## Example of live streaming via `yarn live-stream`

https://user-images.githubusercontent.com/4106/186701616-fd71a107-5d53-423c-ba09-0395a3a0252f.mov

## Instructions

### Record side

```js
// Record side

import { record } from '@rrweb/record';
import { RRWebPluginCanvasWebRTCRecord } from '@rrweb/rrweb-plugin-canvas-webrtc-record';

const webRTCRecordPlugin = new RRWebPluginCanvasWebRTCRecord({
  signalSendCallback: (msg) => {
    // provides webrtc sdp offer signal & connect message
    // make sure you send this to the replayer's `webRTCReplayPlugin.signalReceive(signal)`
    sendSignalToReplayer(msg); // example of function that sends the signal to the replayer
  },
});

record({
  emit: (event) => {
    // send these events to the `replayer.addEvent(event)`, how you do that is up to you
    // you can send them to a server for example which can then send them to the replayer
    sendEventToReplayer(event); // example of function that sends the event to the replayer
  },
  plugins: [
    // add the plugin to the list of plugins, and initialize it via `.initPlugin()`
    webRTCRecordPlugin.initPlugin(),
  ],
  recordCanvas: false, // we don't want canvas recording turned on, we're going to do that via the plugin
});
```

### Cross-origin recording

The plugin rejects cross-origin `postMessage` commands by default. This setting
is separate from rrweb's `recordCrossOriginIframes` option. Enabling that option
in `record()` does not enable cross-origin canvas streaming in this plugin.

For cross-origin canvas streaming, set `recordCrossOriginIframes: true` in the
plugin constructor in both the recording root page and each participating iframe.
Also enable rrweb's `recordCrossOriginIframes` option for cross-origin event recording.

```js
const webRTCRecordPlugin = new RRWebPluginCanvasWebRTCRecord({
  signalSendCallback: sendSignalToReplayer,
  recordCrossOriginIframes: true,
});
```

Enabling this option accepts commands from any origin. It does not authenticate
the embedding page or restrict signaling to an allowlist. A page with this option
enabled must restrict embedding to trusted origins, for example with a
`Content-Security-Policy: frame-ancestors` response header. Leave the option
disabled if the page may be embedded by untrusted sites.

Same-origin commands continue to work without opt-in. Messages from opaque origins,
such as sandboxed frames without `allow-same-origin`, require explicit opt-in.

### Replay Side

```js
// Replay side
import { Replayer } from '@rrweb/replay';
import { RRWebPluginCanvasWebRTCReplay } from '@rrweb/rrweb-plugin-canvas-webrtc-replay';

const webRTCReplayPlugin = new RRWebPluginCanvasWebRTCReplay({
  canvasFoundCallback(canvas, context) {
    console.log('canvas', canvas);
    // send the canvas id to `webRTCRecordPlugin.setupStream(id)`, how you do that is up to you
    // you can send them to a server for example which can then send them to the replayer
    sendCanvasIdToRecordScript(context.id); // example of function that sends the id to the record script
  },
  signalSendCallback(signal) {
    // provides webrtc sdp offer signal & connect message
    // make sure you send this to the record script's `webRTCRecordPlugin.signalReceive(signal)`
    sendSignalToRecordScript(signal); // example of function that sends the signal to the record script
  },
});

const replayer = new Replayer([], {
  UNSAFE_replayCanvas: true, // turn canvas replay on!
  liveMode: true, // live mode is needed to stream events to the replayer
  plugins: [webRTCReplayPlugin.initPlugin()],
});
replayer.startLive(); // start the replayer in live mode

replayer.addEvent(event); // call this whenever an event is received from the record script
```

**Enabling canvas replay adds `allow-scripts` to the replay iframe and opts out of rrweb's sandbox script-execution protection. Only use `UNSAFE_replayCanvas` for replay data whose risk you accept.**

## More info

https://github.com/rrweb-io/rrweb/pull/976

---

### Overview (`packages/plugins/rrweb-plugin-canvas-webrtc-replay/README.md`)

# rrweb canvas webrtc plugin

Plugin that live streams contents of canvas elements via webrtc

## Example of live streaming via `yarn live-stream`

https://user-images.githubusercontent.com/4106/186701616-fd71a107-5d53-423c-ba09-0395a3a0252f.mov

## Instructions

### Record side

```js
// Record side

import { record } from '@rrweb/record';
import { RRWebPluginCanvasWebRTCRecord } from '@rrweb/rrweb-plugin-canvas-webrtc-record';

const webRTCRecordPlugin = new RRWebPluginCanvasWebRTCRecord({
  signalSendCallback: (msg) => {
    // provides webrtc sdp offer signal & connect message
    // make sure you send this to the replayer's `webRTCReplayPlugin.signalReceive(signal)`
    sendSignalToReplayer(msg); // example of function that sends the signal to the replayer
  },
});

record({
  emit: (event) => {
    // send these events to the `replayer.addEvent(event)`, how you do that is up to you
    // you can send them to a server for example which can then send them to the replayer
    sendEventToReplayer(event); // example of function that sends the event to the replayer
  },
  plugins: [
    // add the plugin to the list of plugins, and initialize it via `.initPlugin()`
    webRTCRecordPlugin.initPlugin(),
  ],
  recordCanvas: false, // we don't want canvas recording turned on, we're going to do that via the plugin
});
```

### Replay Side

```js
// Replay side
import { Replayer } from '@rrweb/replay';
import { RRWebPluginCanvasWebRTCReplay } from '@rrweb/rrweb-plugin-canvas-webrtc-replay';

const webRTCReplayPlugin = new RRWebPluginCanvasWebRTCReplay({
  canvasFoundCallback(canvas, context) {
    console.log('canvas', canvas);
    // send the canvas id to `webRTCRecordPlugin.setupStream(id)`, how you do that is up to you
    // you can send them to a server for example which can then send them to the replayer
    sendCanvasIdToRecordScript(context.id); // example of function that sends the id to the record script
  },
  signalSendCallback(signal) {
    // provides webrtc sdp offer signal & connect message
    // make sure you send this to the record script's `webRTCRecordPlugin.signalReceive(signal)`
    sendSignalToRecordScript(signal); // example of function that sends the signal to the record script
  },
});

const replayer = new Replayer([], {
  UNSAFE_replayCanvas: true, // turn canvas replay on!
  liveMode: true, // live mode is needed to stream events to the replayer
  plugins: [webRTCReplayPlugin.initPlugin()],
});
replayer.startLive(); // start the replayer in live mode

replayer.addEvent(event); // call this whenever an event is received from the record script
```

**Enabling canvas replay adds `allow-scripts` to the replay iframe and opts out of rrweb's sandbox script-execution protection. Only use `UNSAFE_replayCanvas` for replay data whose risk you accept.**

## More info

https://github.com/rrweb-io/rrweb/pull/976

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://www.rrweb.io/logos/smartx.png">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://www.rrweb.io/logos/posthog.png">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
    <tr>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://cdn.analyzee.io/assets/analyzee-logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/plugins/rrweb-plugin-console-record/README.md`)

# @rrweb/rrweb-plugin-console-record

Please refer to the [console recipe](../../../docs/recipes/console.md) on how to use this plugin.
See the [guide](../../../guide.md) for more info on rrweb.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/plugins/rrweb-plugin-console-replay/README.md`)

# @rrweb/rrweb-plugin-console-replay

Please refer to the [console recipe](../../../docs/recipes/console.md) on how to use this plugin.
See the [guide](../../../guide.md) for more info on rrweb.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/plugins/rrweb-plugin-network-record/README.md`)

# @rrweb/rrweb-plugin-network-record

Please refer to the [network recipe](../../../docs/recipes/network.md) on how to use this plugin.
See the [guide](../../../guide.md) for more info on rrweb.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on GitHub with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/plugins/rrweb-plugin-network-replay/README.md`)

# @rrweb/rrweb-plugin-network-replay

Please refer to the [network recipe](../../../docs/recipes/network.md) on how to use this plugin.
See the [guide](../../../guide.md) for more info on rrweb.

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on GitHub with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/plugins/rrweb-plugin-sequential-id-record/README.md`)

# @rrweb/rrweb-plugin-sequential-id-record

Use this plugin in combination with the [@rrweb/rrweb-plugin-sequential-id-replay](../rrweb-plugin-sequential-id-replay) plugin to record and replay events with a sequential id.
See the [guide](../../../guide.md) for more info on rrweb.

## Installation

```bash
npm install @rrweb/rrweb-plugin-sequential-id-record
```

## Usage

```js
import { record } from '@rrweb/record';
import { getRecordSequentialIdPlugin } from '@rrweb/rrweb-plugin-sequential-id-record';

record({
  emit: function emit(event) {
    // send events to server
  },
  plugins: [
    getRecordSequentialIdPlugin({
      key: '_sid', // default value
    }),
  ],
});
```

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Overview (`packages/plugins/rrweb-plugin-sequential-id-replay/README.md`)

# @rrweb/rrweb-plugin-sequential-id-replay

Use this plugin in combination with the [@rrweb/rrweb-plugin-sequential-id-record](../rrweb-plugin-sequential-id-record) plugin to record and replay events with a sequential id.
See the [guide](../../../guide.md) for more info on rrweb.

## Installation

```bash
npm install @rrweb/rrweb-plugin-sequential-id-replay
```

## Usage

```js
import { Replayer } from '@rrweb/replay';
import { getReplaySequentialIdPlugin } from '@rrweb/rrweb-plugin-sequential-id-replay';

const replayer = new Replayer(events, {
  plugins: [
    getReplaySequentialIdPlugin({
      // make sure this is the same as the record side
      key: '_id', // default value
      warnOnMissingId: true, // default value
    }),
  ],
});
replayer.play(); // ERROR: [sequential-id-plugin]: expect to get an id with value "42", but got "666"`
```

## Sponsors

[Become a sponsor](https://opencollective.com/rrweb#sponsor) and get your logo on our README on Github with a link to your site.

### Gold Sponsors 🥇

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/0/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/1/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/2/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/3/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/4/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/5/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/gold-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/gold-sponsor/6/avatar.svg?requireActive=false&avatarHeight=225" alt="sponsor"></a>

</div>

### Silver Sponsors 🥈

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/0/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/1/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/2/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/3/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/4/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/5/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/silver-sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/silver-sponsor/6/avatar.svg?requireActive=false&avatarHeight=158" alt="sponsor"></a>

</div>

### Bronze Sponsors 🥉

<div dir="auto">

<a href="https://opencollective.com/rrweb/tiers/sponsors/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/0/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/1/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/2/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/3/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/4/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/5/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/6/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/7/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>
<a href="https://opencollective.com/rrweb/tiers/sponsors/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/rrweb/tiers/sponsors/8/avatar.svg?requireActive=false&avatarHeight=70" alt="sponsor"></a>

</div>

### Backers

<a href="https://opencollective.com/rrweb#sponsor" rel="nofollow"><img src="https://opencollective.com/rrweb/tiers/backers.svg?avatarHeight=36"></a>

## Core Team Members

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/Yuyz0112">
        <img
          src="https://avatars.githubusercontent.com/u/13651389?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yuyz0112</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/YunFeng0817">
        <img
          src="https://avatars.githubusercontent.com/u/27533910?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Yun Feng</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/eoghanmurray">
        <img
          src="https://avatars.githubusercontent.com/u/156780?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>eoghanmurray</b></sub>
        <br /><br />
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/Juice10">
        <img
          src="https://avatars.githubusercontent.com/u/4106?s=100"
          width="100px;"
          alt=""
        />
        <br /><sub><b>Juice10</b></sub>
        <br /><sub>open for rrweb consulting</sub>
      </a>
    </td>
  </tr>
</table>

## Who's using rrweb?

<table>
  <tr>
    <td align="center">
      <a href="http://www.smartx.com/" target="_blank">
        <img width="195px" src="https://raw.githubusercontent.com/rrweb-io/web/HEAD/static/logos/smartx.png" alt="SmartX">
      </a>
    </td>
    <td align="center">
      <a href="https://posthog.com?utm_source=rrweb&utm_medium=sponsorship&utm_campaign=open-source-sponsorship" target="_blank">
        <img width="195px" src="https://rrweb.com/posthog.png" alt="PostHog">
      </a>
    </td>
    <td align="center">
      <a href="https://statcounter.com/session-replay/" target="_blank">
        <img width="195px" src="https://statcounter.com/images/logo-statcounter-arc-blue.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://recordonce.com/" target="_blank">
        <img width="195px" alt="Smart screen recording for SaaS" src="https://uploads-ssl.webflow.com/5f3d133183156245630d4446/5f3d1940abe8db8612c23521_Record-Once-logo-554x80px.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://sentry.io" target="_blank">
        <img width="195px" src="https://rrweb.com/sentry.png" alt="Sentry">
      </a>
    </td>
    <td align="center">
      <a href="https://www.pendo.io" target="_blank">
        <img width="195px" src="https://rrweb.com/pendo.png" alt="Pendo">
      </a>
    </td>
    <td align="center">
      <a href="https://mixpanel.com" target="_blank">
        <img width="195px" src="https://rrweb.com/mixpanel.png" alt="Mixpanel">
      </a>
    </td>
    <td align="center">
      <a href="https://www.datadoghq.com" target="_blank">
        <img width="195px" src="https://rrweb.com/datadog.png" alt="Datadog">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://amplitude.com" target="_blank">
        <img width="195px" src="https://rrweb.com/amplitude.png" alt="Amplitude">
      </a>
    </td>
    <td align="center">
      <a href="https://newrelic.com" target="_blank">
        <img width="195px" src="https://rrweb.com/new%20relic.png" alt="New Relic">
      </a>
    </td>
    <td align="center">
      <a href="https://cux.io" target="_blank">
        <img style="padding: 8px" alt="The first ever UX automation tool" width="195px" src="https://cux.io/cux-logo.svg">
      </a>
    </td>
    <td align="center">
      <a href="https://remsupp.com" target="_blank">
        <img style="padding: 8px" alt="Remote Access & Co-Browsing" width="195px" src="https://remsupp.com/images/logo.png">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://highlight.io" target="_blank">
        <img style="padding: 8px" alt="The open source, fullstack Monitoring Platform." width="195px" src="https://github.com/highlight/highlight/raw/main/highlight.io/public/images/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://analyzee.io" target="_blank">
        <img style="padding: 8px" alt="Comprehensive data analytics platform that empowers businesses to gain valuable insights and make data-driven decisions." width="195px" src="https://analyzee.io/img/analyzee-main-logo.webp">
      </a>
    </td>
    <td align="center">
      <a href="https://requestly.io" target="_blank">
        <img style="padding: 8px" alt="Intercept, Modify, Record & Replay HTTP Requests." width="195px" src="https://github.com/requestly/requestly/assets/16779465/652552db-c867-44cb-9bb5-94a2026e04ca">
      </a>
    </td>
    <td align="center">
      <a href="https://gleap.io" target="_blank">
        <img style="padding: 8px" alt="In-app bug reporting & customer feedback platform." width="195px" src="https://assets-global.website-files.com/6506f3f29c68b1724807619d/6506f56010237164c6306591_GleapLogo.svg">
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://uxwizz.com" target="_blank">
        <img style="padding: 8px" alt="Self-hosted website analytics with heatmaps and session recordings." width="195px" src="https://github.com/UXWizz/public-files/raw/main/assets/logo.png">
      </a>
    </td>
    <td align="center">
      <a href="https://www.howdygo.com" target="_blank">
        <img style="padding: 8px" alt="Interactive product demos for small marketing teams" width="195px" src="https://assets-global.website-files.com/650afb446f1dd5bd410f00cc/650b2cec6188ff54dd9b01e1_Logo.svg">
      </a>
    </td>
  </tr>
</table>

---

### Design Index (`docs/design/index.md`)

# Internal Design

Technical design documents explaining how rrweb was originally designed and how it works under the hood.

- [Serialization](../serialization.md) — how rrweb captures a full DOM snapshot, including cross-origin iframes, canvas, and media elements.
- [Incremental Snapshots](../observer.md) — how mutations, input events, scroll, and other incremental changes are recorded after the initial snapshot.
- [Replay](../replay.md) — how recorded events are replayed with high-precision timing and minimal impact on the host page.
- [Sandbox](../sandbox.md) — how the replayer uses iframe sandboxing to safely replay pages without executing their scripts.

---

### Recipes Index (`docs/recipes/index.md`)

# Recipes

You may also want to read the [guide](../../guide.md) to understand the APIs, or read the [Internal Design](../design/index.md) docs to know more technical details of rrweb.

## Scenarios

### Record And Replay

Record and Replay is the most common use case, which is suitable for any scenario that needs to collect user behaviors and replay them.

[link](./record-and-replay.md)

### Load Events Asynchronous

When the size of the recorded events increased, load them in one request is not performant. You can paginate the events and load them as you need.

[link](./pagination.md)

### Real-time Replay (Live Mode）

If you want to replay the events in a real-time way, you can use the live mode API. This API is also useful for some real-time collaboration usage.

[link](./live-mode.md)

### Custom Event

You may need to record some custom events along with the rrweb events, and let them be played as other events. The custom event API was designed for this.

[link](./custom-event.md)

### Annotations

Add captions and timeline markers to a recording, display them with rrweb-player,
or render your own annotation UI using replay callbacks.

[link](./annotations.md)

### Interact With UI During Replay

By default, the UI could not interact during replay. But you can use API to enable/disable this programmatically.

[link](./interaction.md)

### Customize The Replayer

When `Replayer` and the rrweb-player UI do not fit your need, you can customize your own replayer UI.

[link](./customize-replayer.md)

### Convert To Video

The event data recorded by rrweb is a performant, easy to compress, text-based format. And the replay is also pixel perfect.

But if you really need to convert it into a video format, there are some tools that can do this work.

[link](./export-to-video.md)

### Optimize The Storage Size

In some Apps, rrweb may record an unexpected amount of data. This part will help to find a suitable way to optimize the storage.

[link](./optimize-storage.md)

### Canvas

Canvas is a special HTML element, which will not be recorded by rrweb by default. There are some options for recording and replaying Canvas.

[link](./canvas.md)

### Console Recorder and Replayer

Starting from v1.0.0, we add the plugin to record and play back console output.
This feature aims to provide developers with more information about the bug scene. There are some options for recording and replaying console output.

[link](./console.md)

### Plugin API

The plugin API is designed to enable extending the functionality of rrweb without impacting regular use of the core packages.

[link](./plugin-api.md)

---

### Events (`docs/events.md`)

# Events

The events recorded by rrweb are a set of strictly-typed JSON data. You may discover some flexible ways to use them when you are familiar with the details.

## Data Types

Every event has a `timestamp` attribute to record the time it was emitted.

There is also a `type` attribute indicates the event's type, the semantic of event's type is:

```
type -> EventType.DomContentLoaded
event -> domContentLoadedEvent

type = EventType.Load
event -> loadedEvent

type -> EventType.FullSnapshot
event -> fullSnapshotEvent

type -> EventType.IncrementalSnapshot
event -> incrementalSnapshotEvent

type -> EventType.Meta
event -> metaEvent

type -> EventType.Custom
event -> customEvent
```

The EventType is Typescript's numeric enum, which is a self-increased number from 0 in runtime. You can find its definition in this [list](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L10).

In these kinds of events, the incrementalSnapshotEvent is the event that contains incremental data. You can use `event.data.source` to find which kind of incremental data it belongs to:

```
source -> IncrementalSource.Mutation
data -> mutationData

source -> IncrementalSource.MouseMove
data -> mousemoveData

source -> IncrementalSource.MouseInteraction
data -> mouseInteractionData

source -> IncrementalSource.Scroll
data -> scrollData

source -> IncrementalSource.ViewportResize
data -> viewportResizeData

source -> IncrementalSource.Input
data -> inputData

source -> IncrementalSource.TouchMove
data -> mouseInteractionData

source -> IncrementalSource.MediaInteraction
data -> mediaInteractionData

source -> IncrementalSource.StyleSheetRule
data -> styleSheetRuleData

source -> IncrementalSource.CanvasMutation
data -> canvasMutationData

source -> IncrementalSource.Font
data -> fontData
```

enum IncrementalSource's definition can be found in this [list](https://github.com/rrweb-io/rrweb/blob/98e71cd0d23628cd1fbdbe47664a65748084c4a4/packages/types/src/index.ts#L69).

## Recording

For a recording to be replayable, it usually should begins with a `Meta` event which contains the URL of the page and screen dimensions, followed by a `FullSnapshot` event which is used to rebuild the HTML state. Both user actions and page state changes come in through subsequent `IncrementalSnapshot` events, with `source: IncrementalSource.Mutation` for the state changes. These type of events are known as 'mutation events', and one builds upon the next, meaning they should be replayed in the correct order; if one of them is dropped subsequent mutations may fail (for example one mutation adds a nav element, and the next mutation has an attribute change representing the hover state for a nav item in the menu). See the [Sequential ID plugin](packages/plugins/rrweb-plugin-sequential-id-record/) for how to add debug event ordering problems.

When a user navigates to another page on a website, this process begins again with another pair of Meta + FullSnapshot events. Each subsequent mutation event builds incrementally on the first FullSnapshot of a page load. Event streams can be linearly combined into a single 'Session Replay' so long as mutations are associated with the correct full snapshot. Note that rrweb Cloud has extra facilities for recreating multi-tab browsing sessions correctly; see the cloud [Replaying Guide](https://rrweb.com/docs/cloud/replaying-guide) for an overview or the [Get replay](https://rrweb.com/docs/operations/get-replays-%7BgroupingProp%7D-%7BreplayValue%7D) endpoint for how to compose or combine multiple recordings.

---

### Events.Zh CN (`docs/events.zh_CN.md`)

# 事件

录制数据是一组类型严格的 JSON 数据，通过熟悉其格式，可以更灵活的使用录制数据。

## 数据类型

每个 event 都拥有 `timestamp` 属性用于标记时间戳。

除此之外，也都拥有 `type` 属性标记 event 类型，其对应关系如下：

```
type -> EventType.DomContentLoaded
event -> domContentLoadedEvent

type = EventType.Load
event -> loadedEvent

type -> EventType.FullSnapshot
event -> fullSnapshotEvent

type -> EventType.IncrementalSnapshot
event -> incrementalSnapshotEvent

type -> EventType.Meta
event -> metaEvent

type -> EventType.Custom
event -> customEvent
```

其中 EventType 是 Typescipt 的 numeric enum，在运行时是从 0 开始的数字，其类型定义详见[列表](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L10)。

其中 incrementalSnapshotEvent 代表增量数据，其具体增量类型可以通过 `event.data.source` 字段进行判断：

```
source -> IncrementalSource.Mutation
data -> mutationData

source -> IncrementalSource.MouseMove
data -> mousemoveData

source -> IncrementalSource.MouseInteraction
data -> mouseInteractionData

source -> IncrementalSource.Scroll
data -> scrollData

source -> IncrementalSource.ViewportResize
data -> viewportResizeData

source -> IncrementalSource.Input
data -> inputData

source -> IncrementalSource.TouchMove
data -> mouseInteractionData

source -> IncrementalSource.MediaInteraction
data -> mediaInteractionData

source -> IncrementalSource.StyleSheetRule
data -> styleSheetRuleData

source -> IncrementalSource.CanvasMutation
data -> canvasMutationData

source -> IncrementalSource.Font
data -> fontData
```

enum IncrementalSource 的定义详见[列表](https://github.com/rrweb-io/rrweb/blob/98e71cd0d23628cd1fbdbe47664a65748084c4a4/packages/types/src/index.ts#L69)。

## 录制

要让录制数据可被回放，通常应以一个 `Meta` 事件开始，其中包含页面的 URL 和屏幕尺寸，随后是一个用于重建 HTML 状态的 `FullSnapshot` 事件。用户操作和页面状态变化都会通过后续的 `IncrementalSnapshot` 事件传入，其中状态变化对应 `source: IncrementalSource.Mutation`。这类事件被称为「mutation 事件」（变更事件），它们彼此层层叠加，因此必须按正确的顺序回放；如果其中某个事件丢失，后续的变更可能会失败（例如某个变更添加了一个导航元素，而下一个变更包含了表示菜单中某个导航项 hover 状态的属性变化）。关于如何调试事件顺序问题，参见 [Sequential ID 插件](packages/plugins/rrweb-plugin-sequential-id-record/)。

当用户导航到网站的另一个页面时，该过程会以另一对 Meta + FullSnapshot 事件重新开始。每个后续的 mutation 事件都是在某次页面加载的首个 FullSnapshot 基础上增量构建的。只要变更与正确的全量快照相关联，多段事件流就可以线性地组合成单个「会话回放」（Session Replay）。请注意，rrweb Cloud 提供了额外的能力来正确地重建多标签页浏览会话；概览可参见 cloud 的[回放指南](https://rrweb.com/docs/cloud/replaying-guide)，或参阅 [Get replay](https://rrweb.com/docs/operations/get-replays-%7BgroupingProp%7D-%7BreplayValue%7D) 接口了解如何组合或合并多段录制数据。

---

### Observer (`docs/observer.md`)

# Incremental snapshots

After completing a full snapshot, we need to record events that change the state.

Right now, rrweb records the following events (we will expand upon this):

- DOM changes
  - Node creation, deletion
  - Node attribute changes
  - Text changes
- Mouse movement
- Mouse interaction
  - mouse up, mouse down
  - click, double click, context menu
  - focus, blur
  - touch start, touch move, touch end
- Page or element scrolling
- Window size changes
- Input

## Mutation Observer

Since we don't execute any JavaScript during replay, we instead need to record all changes scripts make to the document.

Consider this example:

> User clicks a button. A dropdown menu appears. User selects the first item. The dropdown menu disappears.

During replay, the dropdown menu does not automatically appear after the "click button" is executed, because the original JavaScript is not part of the recording. Thus, we need to record the creation of the dropdown menu DOM nodes, the selection of the first item, and subsequent deletion of the dropdown menu DOM nodes. This is the most difficult part.

Fortunately, modern browsers have provided us with a very powerful API which can do exactly this: [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).

This documentation does not explain the basic usages of MutationObserver, but only focuses on aspects in particular relevant to rrweb.

The first thing to understand is that MutationObserver uses a **Bulk Asynchronous** callback. Specifically, there will be a single callback after a series of DOM changes occur, and it is passed an array of multiple mutation records.

This mechanism is not problematic for normal use, because we do not only have the mutation record, but we can also directly access the DOM object of the mutated node as well as any parent, child and sibling nodes.

However in rrweb, since we have a serialization process, we need more sophisticated solution to be able to deal with various scenarios.

### Add node

For example, the following two operations generate the same DOM structure, but produce a different set of mutation records:

```
body
  n1
    n2
```

1. Create node n1 and append it as child of body, then create node n2 and append it as child of n1.
2. Create nodes n1 and n2, then append n2 as child to of n1, then append n1 as child of body.

In the first case, two mutation records will be generated, namely adding node n1 and adding node n2; in the second case, only one mutation record will be generated, that is, node n1 (including children) is added.

**Note**: In the first case, although n1 has no child node when it is added, due to the above-mentioned batch asynchronous callback mechanism, when we receive the mutation record and process the n1 node the it already has the child node n2 in the DOM.

Due to the second case, when processing new nodes we must traverse all its descendants to ensure that all new nodes are recorded, however this strategy will cause n2 to be (incorrectly) recorded during the first record. Then, when processing the second record, adding a the node for a second time will result in a DOM structure that is inconsistent with the original page during replay.

Therefore, when dealing with multiple mutation records in a callback, we need to "lazily" process the newly-added nodes, that is, first collect all raw, unprocessed nodes when we go through each mutation record, and then after we've been through all the mutation records we determine the order in which the nodes were added to the DOM. When these new nodes are added, we perform deduplication to ensure that each node is only recorded once and we check no nodes were missed.

We already introduced in the [serialization design document](./serialization.md) that we need to maintain a mapping of `id -> Node`, so when new nodes appear, we need to serialize the new nodes and add them to the map. But since we want to perform deduplication, and thus only serialize after all the mutation records have been processed, some problems may arise, as demonstrated in the following example:

1. mutation record 1, add node n1. We will not serialize it yet, since we are waiting for the final deduplication.
2. mutation record 2, n1 added attribute a1. We tried to record it as an incremental snapshot, but we found that we couldn't find the id for n1 from the map because it was not serialized yet.

As you can see, since we have delayed serialization of the newly added nodes, all mutation records also need to be processed first, and only then the new nodes can be de-duplicated without causing trouble.

### Remove node

When processing mutation records, we may encounter a removed node that has not yet been serialized. That indicates that it is a newly added node, and the "add node" mutation record is also somewhere in the mutation records we received. We label these nodes as "dropped nodes".

There are two cases we need to handle here:

1. Since the node was removed already, there is no need to replay it, and thus we remove it from the newly added node pool.
2. This also applies to descendants of the dropped node, thus when processing newly added nodes we need to check if it has a dropped node as an ancestor.

### Attribute change

Although MutationObserver is an asynchronous batch callback, we can still assume that the time interval between mutations occurring in a callback is extremely short, so we can optimize the size of the incremental snapshot by overwriting some data when recording the DOM property changes.

For example, resizing a `<textarea>` will trigger a large number of mutation records with varying width and height properties. While a full record will make replay more realistic, it can also result in a large increase in the number of incremental snapshots. After making a trade-off, we think that only the final value of an attribute of the same node needs to be recorded in a single mutation callback, that is, each subsequent mutation record will overwrite the attribute change part of the mutation record that existing before the write.

## Mouse movement

By recording the mouse movement position, we can simulate the mouse movement trajectory during replay.

Try to ensure that the mouse moves smoothly during replay and also minimize the number of corresponding incremental snapshots, so we need to perform two layers of throttling while listening to mousemove. The first layer records the mouse coordinates at most once every 20 ms, the second layer transmits the mouse coordinate set at most once every 500 ms to ensure a single snapshot doesn't accumulate a lot of mouse position data and becomes too large.

### Time reversal

We record a timestamp when each incremental snapshot is generated so that during replay it can be applied at the correct time. However, due to the effect of throttling, the timestamps of the mouse movement corresponding to the incremental snapshot will be later than the actual recording time, so we need to record a negative time difference for correction and time calibration during replay.

## Input

We need to observe the input of the three elements `<input>`, `<textarea>`, `<select>`, including human input and programmatic changes.

### Human input

For human input, we mainly rely on listening to the input and change events. It is necessary to deduplicate different events triggered for the same the human input action. In addition, `<input type="radio" />` is also a special kind of control. If the multiple radio elements have the same name attribute, then when one is selected, the others will be reversed, but no event will be triggered on those others, so this needs to be handled separately.

### Programmatic changes

Setting the properties of these elements directly through the code will not trigger the MutationObserver. We can still achieve monitoring by hijacking the setter of the corresponding property. The sample code is as follows:

```typescript
function hookSetter<T>(
  target: T,
  key: string | number | symbol,
  d: PropertyDescriptor,
): hookResetter {
  const original = Object.getOwnPropertyDescriptor(target, key);
  Object.defineProperty(target, key, {
    set(value) {
      // put hooked setter into event loop to avoid of set latency
      setTimeout(() => {
        d.set!.call(this, value);
      }, 0);
      if (original && original.set) {
        original.set.call(this, value);
      }
    },
  });
  return () => hookSetter(target, key, original || {});
}
```

Note that in order to prevent our logic in the setter from blocking the normal interaction of the recorded page, we should put the logic into the event loop and execute it asynchronously.

---

### Observer.Zh CN (`docs/observer.zh_CN.md`)

# 增量快照

在完成一次全量快照之后，我们就需要基于当前视图状态观察所有可能对视图造成改动的事件，在 rrweb 中我们已经观察了以下事件（将不断增加）：

- DOM 变动
  - 节点创建、销毁
  - 节点属性变化
  - 文本变化
- 鼠标移动
- 鼠标交互
  - mouse up、mouse down
  - click、double click、context menu
  - focus、blur
  - touch start、touch move、touch end
- 页面或元素滚动
- 视窗大小改变
- 输入

## Mutation Observer

由于我们在回放时不会执行所有的 JavaScript 脚本，所以例如这样的场景我们需要完整记录才能够实现回放：

> 点击 button，出现 dropdown menu，选择第一项，dropdown menu 消失

回放时，在“点击 button”执行之后 dropdown menu 不会自动出现，因为已经没有 JavaScript 脚本为我们执行这件事，所以我们需要记录 dropdown menu 相关的 DOM 节点的创建以及后续的销毁，这也是录制中的最大难点。

好在现代浏览器已经给我们提供了非常强大的 API —— [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) 用来完成这一功能。

此处我们不具体讲解 MutationObserver 的基本使用方式，只专注于在 rrweb 中我们需要做哪些特殊处理。

首先要了解 MutationObserver 的触发方式为**批量异步**回调，具体来说就是会在一系列 DOM 变化发生之后将这些变化一次性回调，传出的是一个 mutation 记录数组。

这一机制在常规使用时不会有问题，因为从 mutation 记录中我们可以获取到变更节点的 JS 对象，可以做很多等值比较以及访问父子、兄弟节点等操作来保证我们可以精准回放一条 mutation 记录。

但是在 rrweb 中由于我们有序列化的过程，我们就需要更多精细的判断来应对各种场景。

### 新增节点

例如以下两种操作会生成相同的 DOM 结构，但是产生不同的 mutation 记录：

```
body
  n1
    n2
```

1. 创建节点 n1 并 append 在 body 中，再创建节点 n2 并 append 在 n1 中。
2. 创建节点 n1、n2，将 n2 append 在 n1 中，再将 n1 append 在 body 中。

第 1 种情况将产生两条 mutation 记录，分别为增加节点 n1 和增加节点 n2；第 2 种情况则只会产生一条 mutation 记录，即增加节点 n1。

**注意**，在第一种情况下虽然 n1 append 时还没有子节点，但是由于上述的批量异步回调机制，当我们处理 mutation 记录时获取到的 n1 是已经有子节点 n2 的状态。

受第二种情况的限制，我们在处理新增节点时必须遍历其所有子孙节点，才能保证所有新增节点都被记录，但是这一策略应用在第一种情况中就会导致 n2 被作为新增节点记录两次，回放时就会产生与原页面不一致的 DOM 结构。

因此，在处理一次回调中的多个 mutation 记录时我们需要“惰性”处理新增节点，即在遍历每条 mutation 记录遇到新增节点时先收集，再在全部 mutation 遍历完毕之后对收集的新增节点进行去重操作，保证不遗漏节点的同时每个节点只被记录一次。

在[序列化设计](./serialization.zh_CN.md)中已经介绍了我们需要维护一个 `id -> Node` 的映射，因此当出现新增节点时，我们需要将新节点序列化并加入映射中。但由于我们为了去重新增节点，选择在所有 mutation 记录遍历完毕之后才进行序列化，在以下示例中就会出现问题：

1. mutation 记录 1，新增节点 n1。我们暂不处理，等待最终去重后序列化。
2. mutation 记录 2，n1 新增属性 a1。我们试图将它记录成一次增量快照，但会发现无法从映射中找到 n1 对应的 id，因为此时它还未被序列化。

由此可见，由于我们对新增节点进行了延迟序列化的处理，所有 mutation 记录也都需要先收集，再新增节点去重并序列化之后再做处理。

### 移除节点

在处理移除节点时，我们需要做以下处理：

1. 移除的节点还未被序列化，则说明是在本次 callback 中新增的节点，无需记录，并且从新增节点池中将其移除。
2. 上步中在一次 callback 中被新增又移除的节点我们将其称为 dropped node，用于最终处理新增节点时判断节点的父节点是否已经 drop。

### 属性变化覆盖写

尽管 MutationObserver 是异步批量回调，但是我们仍然可以认为在一次回调中发生的 mutations 之间时间间隔极短，因此在记录 DOM 属性变化时我们可以通过覆盖写的方式优化增量快照的体积。

例如对一个 `<textarea>` 进行 resize 操作，会触发大量的 width 和 height 属性变化的 mutation 记录。虽然完整记录会让回放更加真实，但是也可能导致增量快照数量大大增加。进行取舍之后，我认为在同一次 mutation callback 中只记录同一个节点某一属性的最终值即可，也就是后续的 mutation 记录会覆盖写之前已有的 mutation 记录中的属性变化部分。

## 鼠标移动

通过记录鼠标移动位置，我们可以在回放时模拟鼠标移动轨迹。

尽量保证回放时鼠标移动流畅的同时也要尽量减少对应增量快照的数量，所以我们需要在监听 mousemove 的同时进行两层节流处理。第一层是每 20 ms 最多记录一次鼠标坐标，第二层是每 500 ms 最多发送一次鼠标坐标集合，第二层的主要目的是避免一次请求内容过多而做的分段。

### 时间逆推

我们在每个增量快照生成时会记录一个时间戳，用于在回放时按正确的时间差回放。但是由于节流处理的影响，鼠标移动对应增量快照的时间戳会比实际记录时间要更晚，因此我们需要记录一个用于校正的负时间差，在回放时将时间校准。

## 输入

我们需要观察 `<input>`, `<textarea>`, `<select>` 三种元素的输入，包含人为交互和程序设置两种途径的输入。

### 人为交互

对于人为交互的操作我们主要靠监听 input 和 change 两个事件观察，需要注意的是对不同事件但值相同的情况进行去重。此外 `<input type="radio" />` 也是一类特殊的控件，如果多个 radio 元素的组件 name 属性相同，那么当一个被选择时其他都会被反选，但是不会触发任何事件，因此我们需要单独处理。

### 程序设置

通过代码直接设置这些元素的属性也不会触发事件，我们可以通过劫持对应属性的 setter 来达到监听的目的，示例代码如下：

```typescript
function hookSetter<T>(
  target: T,
  key: string | number | symbol,
  d: PropertyDescriptor,
): hookResetter {
  const original = Object.getOwnPropertyDescriptor(target, key);
  Object.defineProperty(target, key, {
    set(value) {
      // put hooked setter into event loop to avoid of set latency
      setTimeout(() => {
        d.set!.call(this, value);
      }, 0);
      if (original && original.set) {
        original.set.call(this, value);
      }
    },
  });
  return () => hookSetter(target, key, original || {});
}
```

注意为了避免我们在 setter 中的逻辑阻塞被录制页面的正常交互，我们应该把逻辑放入 event loop 中异步执行。

---

### Replay (`docs/replay.md`)

# Replay

A design principle of rrweb is to process as little as possible on the recording side, minimizing the impact on the recorded page. This means we need to do some special processing on the replay side.

## High precision timer

During replay, we will get the complete snapshot chain at one time. If all the snapshots are executed in sequence, we can directly get the last state of the recorded page, but what we need is to synchronously initialize the first full snapshot, and then apply the remaining incremental snapshots asynchronously. Using a time interval we replay each incremental snapshot one after the other, which requires a high-precision timer.

The reason why **high precision** is emphasized is because the native `setTimeout` does not guarantee accurate execution after the set delay time, for example, when the main thread is blocked.

For our replay function, this imprecise delay is unacceptable and can lead to various weird phenomena, so we implement a constantly calibrated timer with `requestAnimationFrame` to ensure that in most cases incremental snapshots have a replay delay of no more than one frame.

At the same time, the custom timer is also the basis for our "fast forward" function.

## Completing missing nodes

The delay serialization strategy when rrweb uses MutationObserver is mentioned in the [incremental snapshot design](./observer.md), which may result in the following scenarios where we cannot record a full incremental snapshot:

```
parent
    node bar
    node foo
```

1. Node `foo` is added as a child of the parent
2. Node `bar` is added before existing child `foo`

According to the actual execution order, `foo` will be serialized by rrweb first, but when serializing new nodes, we need to record adjacent nodes in addition to the parent node, to ensure that the newly added nodes can be placed in the correct position during replay. At this point `bar` already exists but has not been serialized, so we will record it as `id: -1` (or, if there are no neighbors `null` as the id to indicate it doesn't exist).

During replay, when we process the incremental snapshot of the new `foo`, we know that its neighbor hasn't been inserted yet because it has an id of -1, and then temporarily put it into the "missing node pool". It is not inserted into the DOM tree.

After processing the incremental snapshot of the new n1, we normally process and insert `bar`. After the replay is completed, we check whether the neighbor node id of `foo` points to a node which is in the missing node pool. If it matches, then it will be removed from the pool and be inserted into the DOM tree.

## Simulation Hover

CSS styles for the `:hover` selector are present in many web pages, but we can't trigger the hover state via JavaScript. So when playing back we need to simulate the hover state to make the style display correctly.

The specific method includes two parts:

1. Traverse the CSS stylesheet, adding the CSS rules for the `:hover` selector just like in the original, but with an additional special selector class, such as `.:hover`.
2. When playing back the mouse up mouse interaction event, add the `.:hover` class name to the event target and all its ancestors, and remove it when the mouse moves away again.

## Play from any point in time

In addition to the basic replay features, we also want players like `rrweb-player` to provide similar functionality to video players, such as dragging and dropping to the progress bar to any point in time.

In actual implementation, we pass a start time to the method. We can then divide the snapshot chain into two parts: The parts before and the part after the start time. Then, the snapshot chain before the start time is executed synchronously, and then the snapshot chain after the starting times uses the normal asynchronous execution. This way we can achieve starting replay from any point in time.

---

### Replay.Zh CN (`docs/replay.zh_CN.md`)

# 回放

rrweb 的设计原则是尽量少的在录制端进行处理，最大程度减少对被录制页面的影响，因此在回放端我们需要做一些特殊的处理。

## 高精度计时器

在回放时我们会一次性拿到完整的快照链，如果将所有快照依次同步执行我们可以直接获取被录制页面最后的状态，但是我们需要的是同步初始化第一个全量快照，再异步地按照正确的时间间隔依次重放每一个增量快照，这就需要一个高精度的计时器。

之所以强调**高精度**，是因为原生的 `setTimeout` 并不能保证在设置的延迟时间之后准确执行，例如主线程阻塞时就会被推迟。

对于我们的回放功能而言，这种不确定的推迟是不可接受的，可能会导致各种怪异现象的发生，因此我们通过 `requestAnimationFrame` 来实现一个不断校准的定时器，确保绝大部分情况下增量快照的重放延迟不超过一帧。

同时自定义的计时器也是我们实现“快进”功能的基础。

## 补全缺失节点

在[增量快照设计](./observer.zh_CN.md)中提到了 rrweb 使用 MutationObserver 时的延迟序列化策略，这一策略可能导致以下场景中我们不能记录完整的增量快照：

```
parent
  child2
  child1
```

1. parent 节点插入子节点 child1
2. parent 节点在 child1 之前插入子节点 child2

按照实际执行顺序 child1 会被 rrweb 先序列化，但是在序列化新增节点时我们除了记录父节点之外还需要记录相邻节点，从而保证回放时可以把新增节点放置在正确的位置。但是此时 child 1 相邻节点 child2 已经存在但是还未被序列化，我们会将其记录为 `id: -1`（不存在相邻节点时 id 为 null）。

重放时当我们处理到新增 child1 的增量快照时，我们可以通过其相邻节点 id 为 -1 这一特征知道帮助它定位的节点还未生成，然后将它临时放入”缺失节点池“中暂不插入 DOM 树中。

之后在处理到新增 child2 的增量快照时，我们正常处理并插入 child2，完成重放之后检查 child2 的相邻节点 id 是否指向缺失节点池中的某个待添加节点，如果吻合则将其从池中取出插入对应位置。

## 模拟 Hover

在许多前端页面中都会存在 `:hover` 选择器对应的 CSS 样式，但是我们并不能通过 JavaScript 触发 hover 事件。因此回放时我们需要模拟 hover 事件让样式正确显示。

具体方式包括两部分：

1. 遍历 CSS 样式表，对于 `:hover` 选择器相关 CSS 规则增加一条完全一致的规则，但是选择器为一个特殊的 class，例如 `.:hover`。
2. 当回放 mouse up 鼠标交互事件时，为事件目标及其所有祖先节点都添加 `.:hover` 类名，mouse down 时再对应移除。

## 从任意时间点开始播放

除了基础的回放功能之外，我们还希望 `rrweb-player` 这样的播放器可以提供和视频播放器类似的功能，如拖拽到进度条至任意时间点播放。

实际实现时我们通过给定的起始时间点将快照链分为两部分，分别是时间点之前和之后的部分。然后同步执行之前的快照链，再正常异步执行之后的快照链就可以做到从任意时间点开始播放的效果。

---

### Sandbox (`docs/sandbox.md`)

# Sandbox

In the [serialization design](./serialization.md) we mentioned the "de-scripting" process, that is, we will not execute any JavaScript in the recorded page during replay, but instead reproduce its effects the snapshots. The `script` tag is rewritten as a `noscript` tag to solve some of the problems. However, there are still some scripted behaviors that are not included in the `script` tag, such as inline scripts in HTML, form submissions, and so on.

There are many kinds of scripting behaviors. A filtering approach to getting rid of these scripts will never be a complete solution, and once a script slips through and is executed, it may cause irreversible unintended consequences. So we use the iframe sandbox feature provided by HTML for browser-level restrictions.

## iframe sandbox

We reconstruct the recorded DOM in an `iframe` element when we rebuild the snapshot. By setting its `sandbox` attribute, we can disable the following behavior:

- Form submission
- pop-up window such as `window.open`
- JS script (including inline event handlers and `javascript:` URLs)

This is in line with our expectations, especially when dealing with JS scripts is safer and more reliable than implementing this security ourselves.

`rrweb-snapshot.rebuild()` enforces this boundary in browser environments. Browser rebuilds should use `rebuildIntoSandboxedIframe()` or an iframe created by `createSandboxedIframe()`, which creates an iframe with exactly `sandbox="allow-same-origin"` before rebuilding into it. Direct `rebuild()` calls against caller-created browser documents must explicitly opt into an unprotected rebuild with `UNSAFE_allowUnprotectedRebuild: true`.

## Avoid link jumps

When you click the a element link, the default event is to jump to the URL corresponding to its href attribute. During replay, we will ensure visually correct replay by rebuilding the page DOM after the jump, and the original jump should be prohibited.

Usually we will capture all an elements click events through the event handler proxy and disable the default event via `event.preventDefault()`. But when we put the replay page in the sandbox, all the event handlers will not be executed, and we will not be able to implement the event delegation.

When replaying interactive events, note that replaying the JS `click` event is not necessary because click events do not have any impact when JS is disabled. However, in order to optimize the replay effect, we can add special animation effects to visualize elements being clicked with the mouse, to clearly show the viewer that a click has occurred.

## iframe style settings

Since we're rebuilding the DOM in an iframe, we can't affect the elements in the iframe through the CSS stylesheet of the parent page. But if JS scripts are not allowed to execute, the `noscript` tag will be displayed, and we want to hide it. So we need to dynamically add styles to the iframe. The sample code is as follows:

```typescript
const injectStyleRules: string[] = [
  'iframe { background: #f1f3f5 }',
  'noscript { display: none !important; }',
];

const styleEl = document.createElement('style');
const { documentElement, head } = this.iframe.contentDocument!;
documentElement!.insertBefore(styleEl, head);
for (let idx = 0; idx < injectStyleRules.length; idx++) {
  (styleEl.sheet! as CSSStyleSheet).insertRule(injectStyleRules[idx], idx);
}
```

Note that this inserted style element does not exist in the original recorded page, so we can't serialize it, otherwise the `id -> Node` mapping will be wrong.

---

### Sandbox.Zh CN (`docs/sandbox.zh_CN.md`)

# 沙盒

在[序列化设计](./serialization.zh_CN.md)中我们提到了“去脚本化”的处理，即在回放时我们不应该执行被录制页面中的 JavaScript，在重建快照的过程中我们将所有 `script` 标签改写为 `noscript` 标签解决了部分问题。但仍有一些脚本化的行为是不包含在 `script` 标签中的，例如 HTML 中的 inline script、表单提交等。

脚本化的行为多种多样，如果仅过滤已知场景难免有所疏漏，而一旦有脚本被执行就可能造成不可逆的非预期结果。因此我们通过 HTML 提供的 iframe 沙盒功能进行浏览器层面的限制。

## iframe sandbox

我们在重建快照时将被录制的 DOM 重建在一个 `iframe` 元素中，通过设置它的 `sandbox` 属性，我们可以禁止以下行为：

- 表单提交
- `window.open` 等弹出窗
- JS 脚本（包含 inline event handler 和 `<URL>` ）

这与我们的预期是相符的，尤其是对 JS 脚本的处理相比自行实现会更加安全、可靠。

`rrweb-snapshot.rebuild()` 会在浏览器环境中强制执行这一边界。浏览器重建应使用 `rebuildIntoSandboxedIframe()`，或使用由 `createSandboxedIframe()` 创建的 iframe；它们会先创建精确设置为 `sandbox="allow-same-origin"` 的 iframe，再在其中重建。直接对调用方创建的浏览器文档调用 `rebuild()` 时，必须通过 `UNSAFE_allowUnprotectedRebuild: true` 显式选择不受保护的重建。

## 避免链接跳转

当点击 a 元素链接时默认事件为跳转至它的 href 属性对应的 URL。在重放时我们会通过重建跳转后页面 DOM 的方式保证视觉上的正确重放，而原本的跳转则应该被禁止执行。

通常我们会通过事件代理捕获所有的 a 元素点击事件，再通过 `event.preventDefault()` 禁用默认事件。但当我们将回放页面放在沙盒内时，所有的 event handler 都将不被执行，我们也就无法实现事件代理。

重新查看我们回放交互事件增量快照的实现，我们会发现其实 `click` 事件是可以不被重放的。因为在禁用 JS 的情况下点击行为并不会产生视觉上的影响，也无需被感知。

不过为了优化回放的效果，我们可以在点击时给模拟的鼠标元素添加特殊的动画效果，用来提示观看者此处发生了一次点击。

## iframe 样式设置

由于我们将 DOM 重建在 iframe 中，所以我们无法通过父页面的 CSS 样式表影响 iframe 中的元素。但是在不允许 JS 脚本执行的情况下 `noscript` 标签会被显示，而我们希望将其隐藏，就需要动态的向 iframe 中添加样式，示例代码如下：

```typescript
const injectStyleRules: string[] = [
  'iframe { background: #f1f3f5 }',
  'noscript { display: none !important; }',
];

const styleEl = document.createElement('style');
const { documentElement, head } = this.iframe.contentDocument!;
documentElement!.insertBefore(styleEl, head);
for (let idx = 0; idx < injectStyleRules.length; idx++) {
  (styleEl.sheet! as CSSStyleSheet).insertRule(injectStyleRules[idx], idx);
}
```

需要注意的是这个插入的 style 元素在被录制页面中并不存在，所以我们不能将其序列化，否则 `id -> Node` 的映射将出现错误。

---

### Serialization (`docs/serialization.md`)

# Serialization

If you only need to record and replay changes within the browser locally, then we can simply save the current view by deep copying the DOM object. For example, the following code implementation (simplified example with jQuery, saves only the body part):

```javascript
// record
const snapshot = $('body').clone();
// replay
$('body').replaceWith(snapshot);
```

We now implemented a snapshot by saving the whole DOM object in memory.

But the object itself is not **serializable**, meaning we can't save it to a specific text format (such as JSON) for transmission. We need that to do remote recording, and thus we need to implement a method for serializing the DOM data.

We do not use an existing open source solutions such as [parse5](https://github.com/inikulin/parse5) for two reasons:

1. We need to implement a "non-standard" serialization method, which will be discussed in detail below.
2. This part of the code needs to run on the recorded page, and we want to control the amount of code as much as possible, only retaining the necessary functions.

## Special handling in serialization

The reason why our serialization method is non-standard is because we still need to do the following parts:

1. Output needs to be descriptive. All JavaScript in the original recorded page should not be executed on replay. In rrweb we do this by replacing `script` tags with placeholder `noscript` tags in snapshots. The content inside the script is no longer important. We instead record any changes to the DOM that scripts cause, and we ​​do not need to fully record large amounts of script content that may be present on the original web page.
2. Recording view state that is not reflected in the HTML. For example, the value of `<input type="text" />` will not be reflected in its HTML, but will be recorded by the `value` attribute. We need to read the value and store it as a property when serializing. So it will look like `<input type="text" value="recordValue" />`.
3. Relative paths are converted to absolute paths. During replay, we will place the recorded page in an `<iframe>`. The page URL at this time is the address of the replay page. If there are some relative paths in the recorded page, an error will occur when the user tries to open them, so when recording we need to convert relative paths. Relative paths in the CSS style sheet also need to be converted.
4. We want to record the contents of the CSS style sheet. If the recorded page links to external style sheets, we can get its parsed CSS rules from the browser, generate an inline style sheet containing all these rules. This way stylesheets that are not always accessible (for example, because they are located on an intranet or localhost) are included in the recording and can be replayed correctly.

## Uniquely identifies

At the same time, our serialization should also include both full and incremental types. Full serialization can transform a DOM tree into a corresponding tree data structure.

For example, the following DOM tree:

```html
<html>
  <body>
    <header></header>
  </body>
</html>
```

Will be serialized into a data structure like this:

```json
{
  "type": "Document",
  "childNodes": [
    {
      "type": "Element",
      "tagName": "html",
      "attributes": {},
      "childNodes": [
        {
          "type": "Element",
          "tagName": "head",
          "attributes": {},
          "childNodes": [],
          "id": 3
        },
        {
          "type": "Element",
          "tagName": "body",
          "attributes": {},
          "childNodes": [
            {
              "type": "Text",
              "textContent": "\n    ",
              "id": 5
            },
            {
              "type": "Element",
              "tagName": "header",
              "attributes": {},
              "childNodes": [
                {
                  "type": "Text",
                  "textContent": "\n    ",
                  "id": 7
                }
              ],
              "id": 6
            }
          ],
          "id": 4
        }
      ],
      "id": 2
    }
  ],
  "id": 1
}
```

There are two things to note in this serialization result:

1. When we traverse the DOM tree, we use Node as the unit. Therefore, in addition to the "element type" nodes in the DOM, we also include records of all other types of Nodes such as Text Node and Comment Node.
2. We add a unique identifier `id` to each Node, which is used for subsequent incremental snapshots.

Imagine if we recorded the click of a button on the same page and played it back, we can record the operation in the following format (that is what we call an incremental snapshot):

```typescript
type clickSnapshot = {
  source: 'MouseInteraction';
  type: 'Click';
  node: HTMLButtonElement;
};
```

The operation can be executed again by `snapshot.node.click()`.

However, in the actual scenario, although we have reconstructed the complete DOM, there is no way to associate the interacting DOM nodes in the incremental snapshot with the existing DOM.

This is the reason for the identifier `id`. We maintain the `id -> Node` mapping that is exactly the same over time on both the recording and replay sides, and they both are updated when DOM nodes are created and destroyed, ensuring that we use unique increasing numbers in the snapshots, and only the `id` needs to be recorded to find the corresponding DOM node during replay.

The data structure in the above example becomes correspondingly:

```typescript
type clickSnapshot = {
  source: 'MouseInteraction';
  type: 'Click';
  id: Number;
};
```

---

### Serialization.Zh CN (`docs/serialization.zh_CN.md`)

# 序列化

如果仅仅需要在本地录制和回放浏览器内的变化，那么我们可以简单地通过深拷贝 DOM 来实现当前视图的保存。例如通过以下的代码实现（使用 jQuery 简化示例，仅保存 body 部分）：

```javascript
// record
const snapshot = $('body').clone();
// replay
$('body').replaceWith(snapshot);
```

我们通过将 DOM 对象整体保存在内存中实现了快照。

但是这个对象本身并不是**可序列化**的，因此我们不能将其保存为特定的文本格式（例如 JSON）进行传输，也就无法做到远程录制，所以我们首先需要实现将 DOM 及其视图状态序列化的方法。在这里我们不使用一些开源方案例如 [parse5](https://github.com/inikulin/parse5) 的原因包含两个方面：

1. 我们需要实现一个“非标准”的序列化方法，下文会详细展开。
2. 此部分代码需要运行在被录制的页面中，要尽可能的控制代码量，只保留必要功能。

## 序列化中的特殊处理

之所以说我们的序列化方法是非标准的是因为我们还需要做以下几部分的处理：

1. 去脚本化。被录制页面中的所有 JavaScript 都不应该被执行，例如我们会在重建快照时将 `script` 标签改为 `noscript` 标签，此时 script 内部的内容就不再重要，录制时可以简单记录一个标记值而不需要将可能存在的大量脚本内容全部记录。
2. 记录没有反映在 HTML 中的视图状态。例如 `<input type="text" />` 输入后的值不会反映在其 HTML 中，而是通过 `value` 属性记录，我们在序列化时就需要读出该值并且以属性的形式回放成 `<input type="text" value="recordValue" />`。
3. 相对路径转换为绝对路径。回放时我们会将被录制的页面放置在一个 `<iframe>` 中，此时的页面 URL 为重放页面的地址，如果被录制页面中有一些相对路径就会产生错误，所以在录制时就要将相对路径进行转换，同样的 CSS 样式表中的相对路径也需要转换。
4. 尽量记录 CSS 样式表的内容。如果被录制页面加载了一些同源的 样式表，我们则可以获取到解析好的 CSS rules，录制时将能获取到的样式都 inline 化，这样可以让一些内网环境（如 localhost）的录制也有比较好的效果。

## 唯一标识

同时，我们的序列化还应该包含全量和增量两种类型，全量序列化可以将一个 DOM 树转化为对应的树状数据结构。

例如以下的 DOM 树：

```html
<html>
  <body>
    <header></header>
  </body>
</html>
```

会被序列化成类似这样的数据结构：

```json
{
  "type": "Document",
  "childNodes": [
    {
      "type": "Element",
      "tagName": "html",
      "attributes": {},
      "childNodes": [
        {
          "type": "Element",
          "tagName": "head",
          "attributes": {},
          "childNodes": [],
          "id": 3
        },
        {
          "type": "Element",
          "tagName": "body",
          "attributes": {},
          "childNodes": [
            {
              "type": "Text",
              "textContent": "\n    ",
              "id": 5
            },
            {
              "type": "Element",
              "tagName": "header",
              "attributes": {},
              "childNodes": [
                {
                  "type": "Text",
                  "textContent": "\n    ",
                  "id": 7
                }
              ],
              "id": 6
            }
          ],
          "id": 4
        }
      ],
      "id": 2
    }
  ],
  "id": 1
}
```

这个序列化的结果中有两点需要注意：

1. 我们遍历 DOM 树时是以 Node 为单位，因此除了场景的元素类型节点以外，还包括 Text Node、Comment Node 等所有 Node 的记录。
2. 我们给每一个 Node 都添加了唯一标识 `id`，这是为之后的增量快照做准备。

想象一下如果我们在同页面中记录一次点击按钮的操作并回放，我们可以用以下格式记录该操作（也就是我们所说的一次增量快照）：

```typescript
type clickSnapshot = {
  source: 'MouseInteraction';
  type: 'Click';
  node: HTMLButtonElement;
};
```

再通过 `snapshot.node.click()` 就能将操作再执行一次。

但是在实际场景中，虽然我们已经重建出了完整的 DOM，但是却没有办法将增量快照中被交互的 DOM 节点和已存在的 DOM 关联在一起。

这就是唯一标识 `id` 的作用，我们在录制端和回放端维护随时间变化完全一致的 `id -> Node` 映射，并随着 DOM 节点的创建和销毁进行同样的更新，保证我们在增量快照中只需要记录 `id` 就可以在回放时找到对应的 DOM 节点。

上述示例中的数据结构相应的变为：

```typescript
type clickSnapshot = {
  source: 'MouseInteraction';
  type: 'Click';
  id: Number;
};
```

---

### Styleguide (`docs/styleguide.md`)

# rrweb Documentation Style Guide

These are the guidelines for writing rrweb documentation.

## Headings

- Each page must have a single `#`-level title at the top.
- Chapters in the same page must have `##`-level headings.
- Sub-chapters need to increase the number of `#` in the heading according to
  their nesting depth.
- The page's title must follow [APA title case][title-case].
- All chapters must follow [APA sentence case][sentence-case].

Using `Quick Start` as example:

```markdown
# Quick Start

...

## Replay

...

## Record

...

## Events

...

### Understanding Events

...

### Custom Events

...
```

For API references, there are exceptions to this rule.

## Markdown rules

This repository uses the [`markdownlint`][markdownlint] package to enforce consistent
Markdown styling. For the exact rules, see the `.markdownlint.yaml` file in the root
folder.

There are a few style guidelines that aren't covered by the linter rules:



- Use `sh` instead of `cmd` in code blocks (due to the syntax highlighter).
- Keep line lengths between 80 and 100 characters if possible for readability
  purposes.
- No nesting lists more than 2 levels (due to the markdown renderer).
- All `js` and `javascript` code blocks are linted with
  [standard-markdown](https://www.npmjs.com/package/standard-markdown).
- For unordered lists, use asterisks instead of dashes.

## Picking words

- Use "will" over "would" when describing outcomes.
- Don't use words such as "just" or "simply". If it needs to be explained then it is not self evident.

## Documentation translations

When adding something to an English doc file, please add a translation if possible, or a placeholder in the respective \*.zh-CN.md files.

[title-case]: https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case
[sentence-case]: https://apastyle.apa.org/style-grammar-guidelines/capitalization/sentence-case
[markdownlint]: https://github.com/DavidAnson/markdownlint

---

### Adr 0001 Record Source Boundary Resolver (`docs/adr/0001-record-source-boundary-resolver.md`)

# Use a record-local source-boundary resolver

`@rrweb/record` needs to build without replay-only `rrweb-snapshot` code such as `postcss`, but adding public snapshot/rebuild subpath exports would expand the package API and create compatibility obligations. We will keep the public package API unchanged and add a resolver scoped to the `@rrweb/record` build that maps selected bare package imports to local source entrypoints, restoring Rollup's module visibility for tree-shaking.

## Follow-up Refactor

`rrweb-snapshot` now has internal snapshot-domain and rebuild-domain utility entrypoints. These entrypoints currently re-export from the legacy shared `utils.ts` module, preserving public API compatibility while making a future split of snapshot-only, rebuild-only, and shared helpers incremental.

---

### Adr 0001 Require Sandboxed Browser Rebuilds (`docs/adr/0001-require-sandboxed-browser-rebuilds.md`)

# Require Sandboxed Browser Rebuilds

`rrweb-snapshot.rebuild()` will reject unprotected browser documents by default. Browser replay security is enforced by iframe sandboxing, not by trying to sanitize every executable DOM, URL, CSS, SVG, or event-handler vector. Callers that intentionally rebuild into an unprotected browser document must use an explicit unsafe opt-out, while untrusted replay data should use a sandboxed iframe target or the safe helper API.

---

### Development Coding Style (`docs/development/coding-style.md`)

# Coding Style

These have been adapted from the style guidelines for coding in Electron.

You can run `yarn lint` to show any style issues detected by `eslint`.

## General Code

- End files with a newline.
- Using a plain `return` when returning explicitly at the end of a function.
  - Not `return null`, `return undefined`, `null` or `undefined`
- run `yarn format` to rewrite all files in the standard format
- run `yarn format:head` to rewrite files from your last commit

## Documentation

- Write [remark](https://github.com/remarkjs/remark) markdown style.



## TypeScript

- Write [standard](https://www.npmjs.com/package/standard) JavaScript style.
- File names should be concatenated with `-` instead of `_`, e.g.
  `file-name.js` rather than `file_name.js`, because in
  [github/atom](https://github.com/github/atom) module names are usually in
  the `module-name` form. This rule only applies to `.js` files.
- Use newer ES6/ES2015 syntax where appropriate
  - [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const)
    for requires and other constants. If the value is a primitive, use uppercase naming (eg `const NUMBER_OF_RETRIES = 5`).
  - [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let)
    for defining variables
  - [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
    instead of `function () { }`
  - [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals)
    instead of string concatenation using `+`

## Naming Things

Electron APIs uses the same capitalization scheme as Node.js:

- When the module itself is a class like `BrowserWindow`, use `PascalCase`.
- When the module is a set of APIs, like `globalShortcut`, use `camelCase`.
- When the API is a property of object, and it is complex enough to be in a
  separate chapter like `win.webContents`, use `mixedCase`.
- For other non-module APIs, use natural titles, like `<webview> Tag` or
  `Process Object`.

When creating a new API, it is preferred to use getters and setters instead of
jQuery's one-function style. For example, `.getText()` and `.setText(text)`
are preferred to `.text([text])`. There is a
[discussion](https://github.com/electron/electron/issues/46) on this.

---

### Recipes Annotations (`docs/recipes/annotations.md`)

# Annotations

Annotations have their own event type, separate from custom events. After recording
starts, use `record.addAnnotation()` to add captions or timeline markers.

```js
import { record } from '@rrweb/record';

const events = [];
record({ emit: (event) => events.push(event) });
```

## Captions

```js
// Set or replace the caption.
record.addAnnotation({
  type: 'caption',
  text: 'Choose a name for your project.',
});

// Clear the caption. Use an empty string, null, or false.
record.addAnnotation({ type: 'caption', text: '' });
```

A caption stays visible until the next caption replaces or clears it. Seeking
restores the caption for that replay time. If annotations share a timestamp, the
last one in event order wins.

## Timeline markers

```js
record.addAnnotation({
  type: 'timelineMarker',
  text: 'Saving also creates a default workspace.',
});
```

Hover or focus the timeline marker to open its text panel. Click the marker, or
press Enter or Space, to seek to its timestamp. Tab into the open text panel to
scroll with the keyboard; interacting with that panel does not seek. Escape
dismisses it.

Timeline markers do not change captions. Caption events do not create timeline
markers.

## Replay callbacks

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events);
replayer.on('annotation', (event) => {
  console.log(event.data.type, event.data.text);
});
```

The callback receives the full annotation event, including its timestamp, when
replay casts it. Applications can use it to render their own annotation UI.
Seeking can skip events before the latest metadata event, so custom renderers
that keep captions visible must also reconstruct caption state from the recording
at the seek time. rrweb-player does this automatically.

## Player options

```js
import rrwebPlayer from 'rrweb-player';

new rrwebPlayer({
  target: document.body,
  props: {
    events,
    showCaptions: true,
    skipInactive: false,
    timelineMarkerColor: '#159461',
  },
});
```

`showCaptions` defaults to `true`; set it to `false` to hide captions initially.
Recordings with nonempty captions show a CC
button when the controls are visible. For tutorials, use `skipInactive: false`
to preserve pauses that give viewers time to read. Annotations do not count as
user activity, so skipping inactive periods can shorten the time a caption is
visible.

`timelineMarkerColor` sets annotation marker colors and defaults to
`rgb(73, 80, 246)`. For ordinary custom-event marker colors, see the
[custom-event recipe](./custom-event.md#display-in-rrweb-player).

To clear captions, use `text: ''`, `text: null`, or `text: false`. Timeline markers
require nonempty text. Missing text, whitespace-only strings, other value types,
and unknown annotation types are ignored by the player.

Text supports line breaks and is rendered as plain text. HTML and Markdown are
not interpreted. The player renders captions outside the replay iframe, so they
remain readable as the replay scales.

## Keyboard controls

The playback position slider is also keyboard accessible. Tab to it and use the
Left and Right arrow keys to seek five seconds, or Home and End to jump to the
start or end of the recording.

## TypeScript

```ts
import type { annotationData } from '@rrweb/types';

const annotation = {
  type: 'caption',
  text: 'Your project is ready.',
} satisfies annotationData;

record.addAnnotation(annotation);
```

---

### Recipes Canvas (`docs/recipes/canvas.md`)

# Canvas

Canvas is a special HTML element, and will not be recorded by rrweb by default.
There are some options for recording and replaying Canvas.

Enable recording Canvas：

```js
import { record } from '@rrweb/record';

record({
  emit(event) {},
  recordCanvas: true,
});
```

Alternatively enable image snapshot recording of Canvas at a maximum of 15 frames per second：

```js
import { record } from '@rrweb/record';

record({
  emit(event) {},
  recordCanvas: true,
  sampling: {
    canvas: 15,
  },
  // optional image format settings
  dataURLOptions: {
    type: 'image/webp',
    quality: 0.6,
  },
});
```

Enable replaying Canvas：

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events, {
  UNSAFE_replayCanvas: true,
});
replayer.play();
```

**Enabling canvas replay adds `allow-scripts` to the replay iframe and opts out of rrweb's sandbox script-execution protection. Only use `UNSAFE_replayCanvas` for replay data whose risk you accept.**

Alternatively you can stream canvas elements via webrtc with the [rrweb-plugin-canvas-webrtc-record](../../packages/plugins/rrweb-plugin-canvas-webrtc-record/) & [rrweb-plugin-canvas-webrtc-replay](../../packages/plugins/rrweb-plugin-canvas-webrtc-replay) plugins.
For more information see [canvas-webrtc documentation](../../packages/plugins/rrweb-plugin-canvas-webrtc-record/README.md)

---

### Recipes Canvas.Zh CN (`docs/recipes/canvas.zh_CN.md`)

# Canvas

Canvas 是一种特殊的 HTML 元素，默认情况下其内容不会被 rrweb 观测。我们可以通过特定的配置让 rrweb 能够录制并回放 Canvas。

录制时包含 Canvas 内的内容：

```js
import { record } from '@rrweb/record';

record({
  emit(event) {},
  // 对 canvas 进行录制
  recordCanvas: true,
});
```

或者启用每秒 15 帧的 Canvas 图像快照记录：

```js
import { record } from '@rrweb/record';

record({
  emit(event) {},
  recordCanvas: true,
  sampling: {
    canvas: 15,
  },
  // 图像的格式
  dataURLOptions: {
    type: 'image/webp',
    quality: 0.6,
  },
});
```

回放时对 Canvas 进行回放：

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events, {
  UNSAFE_replayCanvas: true,
});
replayer.play();
```

**启用 canvas 回放会向回放 iframe 添加 `allow-scripts`，并退出 rrweb 沙盒的脚本执行保护。仅在你接受该风险的回放数据上使用 `UNSAFE_replayCanvas`。**

另外，您可以使用 [rrweb-plugin-canvas-webrtc-record](../../packages/plugins/rrweb-plugin-canvas-webrtc-record/) 和 [rrweb-plugin-canvas-webrtc-replay](../../packages/plugins/rrweb-plugin-canvas-webrtc-replay) 插件通过 WebRTC 流式传输 Canvas 元素。
有关更多信息，请参考 [canvas-webrtc 文档](../../packages/plugins/rrweb-plugin-canvas-webrtc-record/README.md)。

---

### Recipes Console (`docs/recipes/console.md`)

# Console Recorder and Replayer

Starting from v1.0.0, we add the plugin to record and play back console output.
This feature aims to provide developers with more information about the bug scene. There are some options for recording and replaying console output.

## Enable Console Recording

You can enable the logger using default option like this:

```js
import { record } from '@rrweb/record';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';

record({
  emit: function emit(event) {
    // you should use console.log in this way to avoid errors.
    const defaultLog = console.log['__rrweb_original__']
      ? console.log['__rrweb_original__']
      : console.log;
    defaultLog(event);
  },
  // to use default record option
  plugins: [getRecordConsolePlugin()],
});
```

**alert**: You shouldn't call console.log(warn, error .etc) in the emit function or you would get the error: `Uncaught RangeError: Maximum call stack size exceeded`.
You should call console.log.\_\_rrweb_original\_\_() instead.

You can also customize the behavior of logger like this:

```js
import { record } from '@rrweb/record';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';

record({
  emit: function emit(event) {
    // you should use console.log in this way to avoid errors.
    const defaultLog = console.log['__rrweb_original__']
      ? console.log['__rrweb_original__']
      : console.log;
    defaultLog(event);
  },
  // customized options
  plugins: [
    getRecordConsolePlugin({
      level: ['info', 'log', 'warn', 'error'],
      lengthThreshold: 10000,
      stringifyOptions: {
        stringLengthLimit: 1000,
        numOfKeysLimit: 100,
        depthOfLimit: 1,
      },
      logger: window.console,
    }),
  ],
});
```

All options are described below:
| key | default | description |
| ---------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| level | ['log','warn','error',...] | Default value contains names of all console functions. You can override it by setting console levels you need. |
| lengthThreshold | 1000 | Maximum number of records of console output. |
| stringifyOptions | { stringLengthLimit: undefined, numOfKeysLimit: 50, depthOfLimit: 4 } | If console output includes js objects, we need to stringify them. `stringLengthLimit` limits the string length of single value. `numOfKeysLimit` limits the number of keys in an object. `depthOfLimit` limits the depth of object. If an object contains more keys than this limit, we would only save object's name. You can reduce the size of events by setting these options. |
| logger | window.console | the console object we would record.You can set a console object from another execution environment where you would like to record. |

## Replay Console

If recorded events include data of console log type, we will automatically play them.

```js
import { Replayer } from '@rrweb/replay';
import { getReplayConsolePlugin } from '@rrweb/rrweb-plugin-console-replay';

const replayer = new Replayer(events, {
  plugins: [
    getReplayConsolePlugin({
      level: ['info', 'log', 'warn', 'error'],
    }),
  ],
});
replayer.play();
```

Description of replay option is as follows:

| key          | default                                                                                                                                  | description                                                                                                                             |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| level        | ['log','warn','error',...]                                                                                                               | You can set this option to play levels of log you need.                                                                                 |
| replayLogger | a console based object that implements the interface [ReplayLogger](../../packages/plugins/rrweb-plugin-console-replay/src/index.ts#L14) | You can also set a replay logger to replay the log messages in a simulated browser console by implementing the interface `ReplayLogger` |

---

### Recipes Console.Zh CN (`docs/recipes/console.zh_CN.md`)

# console 录制和播放

从 v1.0.0 版本开始，我们以插件的形式增加了录制和播放控制台输出的功能。这个功能旨在为开发者提供更多的 bug 信息。对这项功能我们还提供了一些设置选项。

## 开启录制 console 功能

可以通过如下代码使用默认的配置选项

```js
import { record } from '@rrweb/record';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';

record({
  emit: function emit(event) {
    // 如果要使用console来输出信息，请使用如下的写法
    const defaultLog = console.log['__rrweb_original__']
      ? console.log['__rrweb_original__']
      : console.log;
    defaultLog(event);
  },
  // 使用默认的配置选项
  plugins: [getRecordConsolePlugin()],
});
```

**警告**: 在 emit 函数中你不应该直接调用 console.log 等函数，否则将会得到报错：`Uncaught RangeError: Maximum call stack size exceeded`。
你应该调用 console.log.\_\_rrweb_original\_\_()来避免错误。

你也可以定制录制 console 的选项

```js
import { record } from '@rrweb/record';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';

record({
  emit: function emit(event) {
    // 如果要使用console来输出信息，请使用如下的写法
    const defaultLog = console.log['__rrweb_original__']
      ? console.log['__rrweb_original__']
      : console.log;
    defaultLog(event);
  },
  // 定制的选项
  plugins: [
    getRecordConsolePlugin({
      level: ['info', 'log', 'warn', 'error'],
      lengthThreshold: 10000,
      stringifyOptions: {
        stringLengthLimit: 1000,
        numOfKeysLimit: 100,
        depthOfLimit: 1,
      },
      logger: window.console,
    }),
  ],
});
```

如下是配置选项的详细说明：
| key | 默认值 | 功能 |
| ---------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| level | ['log','warn','error',...] | 默认值包含了 console 的全部函数，你也可以传入想要录制的 console 函数。 |
| lengthThreshold | 1000 | 录制 console 输出信息的最大条数。 |
| stringifyOptions | { stringLengthLimit: undefined, numOfKeysLimit: 50, depthOfLimit: 4 } | 如果 console 输出包含了 js 对象，我们需要对其进行序列化，`stringLengthLimit` 限制了单个值能转化的最大字符串长度，`numOfKeysLimit` 限制了一个被序列化的 js 对象能够包含的最大数量 key，`depthOfLimit` 限制了一个被序列化的 js 对象能够拥有的对象深度（层数），如果对象的 key 数量超过了这个限制，我们将只保留对象的名字。你能通过这些选项来减小生成的 events 的体积。 |
| logger | window.console | 要录制的 console 对象，你也可以传入一个想要录制的其他 js 执行环境的 console 对象。 |

## 播放 console 数据

如果 replayer 传入的 events 中包含了 console 类型的数据，我们将自动播放这些数据。

```js
import { Replayer } from '@rrweb/replay';
import { getReplayConsolePlugin } from '@rrweb/rrweb-plugin-console-replay';

const replayer = new Replayer(events, {
  plugins: [
    getReplayConsolePlugin({
      level: ['info', 'log', 'warn', 'error'],
    }),
  ],
});
replayer.play();
```

如下是对 replay 选项的描述：

| key          | 默认值                                                                                                             | 功能                                                                                                        |
| ------------ | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| level        | ['log','warn','error',...]                                                                                         | 你可以只播放想要的 console 函数类型                                                                         |
| replayLogger | 一个基于 console 的对接口[ReplayLogger](../../packages/plugins/rrweb-plugin-console-replay/src/index.ts#L14)的实现 | 你也可以通过传入一个`ReplayLogger`接口的自己的实现，用 html 模拟一个浏览器控制台，来播放录制的 console 数据 |

---

### Recipes Cross Origin Iframes (`docs/recipes/cross-origin-iframes.md`)

# Cross origin iframes

By default browsers make it difficult to access the contents of an iframe that is hosted on a different domain. This is a security feature to prevent malicious sites from accessing sensitive information on other sites. It is possible to work around this security feature, but it is not recommended unless you [are very strict](https://stackoverflow.com/a/21629575) about allowing only the sites you trust to embed your website inside of an iframe.
Since if you allow recording cross origin iframes, any malicious website can embed your website and as long as they have rrweb running they can record all the contents of your website.

## How to record cross origin iframes

Enable recording cross-origin iframes in your parent page:

```js
import { record } from '@rrweb/record';

record({
  emit(event) {}, // all events will be emitted here, including events from cross origin iframes
  recordCrossOriginIframes: true,
});
```

Enable replaying cross-origin iframes in your child page:

```js
import { record } from '@rrweb/record';

record({
  emit(event) {}, // this is required for rrweb, but the child page will not emit any events
  recordCrossOriginIframes: true,
});
```

## Considerations

When cross origin iframe recording is turned on rrweb will check to see if it is being run in a top level window.
If it isn't it'll send the events to the parent window via `postMessage`.

If you don't have rrweb running in the top level window, the events will be lost when `recordCrossOriginIframes` is turned on.

If the top level window is a malicious website it can listen to the events and send them to a server of its choosing.

Or if a malicious script is running in on your page they can listen in on `postMessage` and as communication between the child and parent window is not encrypted. And they can see the events.

## Options for injecting rrweb into cross origin iframes

### 1. Website owners, add rrweb in the iframes

If you own the website that with the iframe and the website that is being embedded in an iframe, you can add rrweb to both pages via a script tag.

### 2. Browser extension

See https://developer.chrome.com/docs/extensions/mv3/content_scripts/#functionality

### 3. Puppeteer script

```js
import puppeteer from 'puppeteer';

async function injectRecording(frame) {
  await frame.evaluate((rrwebCode) => {
    if (window.__IS_RECORDING__) return;
    window.__IS_RECORDING__ = true;

    (async () => {
      function loadScript(code) {
        const s = document.createElement('script');
        s.type = 'text/javascript';
        s.innerHTML = code;
        if (document.head) {
          document.head.append(s);
        } else {
          requestAnimationFrame(() => {
            document.head.append(s);
          });
        }
      }
      loadScript(rrwebCode);

      window.rrweb.record({
        emit: (event) => {
          window._captureEvent(event);
        },
        recordCrossOriginIframes: true,
      });
    })();
  }, code);
}

const browser = await puppeteer.launch();
const page = (await browser.pages())[0];

const events = []; // contains all events from all frames

await page.exposeFunction('_captureEvent', (event) => {
  events.push(event);
});

page.on('framenavigated', async (frame) => {
  await injectRecording(frame); // injects rrweb into the iframe
});

await page.goto('https://example.com');

// your events are in the events array
```

### 4. Electron

```ts
const win = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences: {
    preload: path.join(__dirname, 'rrweb-recording-script.js'),
    // this turns on preload inside iframes, but disables node integration
    nodeIntegrationInSubFrames: true,
    nodeIntegration: false,
  },
});
```

See https://www.electronjs.org/docs/latest/tutorial/tutorial-preload
And https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts

---

### Recipes Cross Origin Iframes.Zh CN (`docs/recipes/cross-origin-iframes.zh_CN.md`)

# 跨域 IFrame 录制

默认情况下，浏览器很难访问托管在不同域上的 iframe 的内容。 这是一项安全功能，可防止恶意站点访问其他站点上的敏感信息。 rrweb 提供了一个解决方案，但如果您对网站的安全有严格的要求https://stackoverflow.com/a/21629575， 只允许您信任的网站将您的网站嵌入 iframe 中，那我们不建议使用我们的解决方案。
因为如果您允许记录跨源 iframe，任何恶意网站都可以嵌入您的网站，并且只要它们运行 rrweb，它们就可以记录您网站的所有内容。

## 如何记录跨源 iframe

在父页面中启用录制跨域 iframe:

```js
import { record } from '@rrweb/record';

record({
  emit(event) {}, // 所有事件都将在此处发出，包括来自跨源 iframe 的事件
  recordCrossOriginIframes: true,
});
```

在您的子页面中启用重放跨域 iframe:

```js
import { record } from '@rrweb/record';

record({
  emit(event) {}, // 这是 rrweb 所必需的，但子页面不会发出任何事件
  recordCrossOriginIframes: true,
});
```

## 注意事项

当跨源 iframe 录制开启时，rrweb 将检查它是否正在顶级窗口中运行。 如果不是，它将通过 `postMessage` 将事件发送到父窗口。

如果您没有在顶层窗口中运行 rrweb，则打开 `recordCrossOriginIframes` 时事件将丢失。

如果顶层窗口是一个恶意网站，它可以监听事件并将它们发送到它选择的服务器。或者如果您的页面上正在运行恶意脚本，则它们可以监听“postMessage”，又因为子窗口和父窗口之间的通信未加密，所以他们可以获取到所有事件。

## 将 rrweb 注入跨源 iframe 的选项

### 1. 网站所有者，在 iframe 中添加 rrweb

如果您拥有使用 iframe 的网站和嵌入在 iframe 中的网站，您可以通过脚本标签将 rrweb 添加到这两个页面。

### 2. 浏览器扩展

请查看 https://developer.chrome.com/docs/extensions/mv3/content_scripts/#functionality

### 3. Puppeteer script

```js
import puppeteer from 'puppeteer';

async function injectRecording(frame) {
  await frame.evaluate((rrwebCode) => {
    if (window.__IS_RECORDING__) return;
    window.__IS_RECORDING__ = true;

    (async () => {
      function loadScript(code) {
        const s = document.createElement('script');
        s.type = 'text/javascript';
        s.innerHTML = code;
        if (document.head) {
          document.head.append(s);
        } else {
          requestAnimationFrame(() => {
            document.head.append(s);
          });
        }
      }
      loadScript(rrwebCode);

      window.rrweb.record({
        emit: (event) => {
          window._captureEvent(event);
        },
        recordCrossOriginIframes: true,
      });
    })();
  }, code);
}

const browser = await puppeteer.launch();
const page = (await browser.pages())[0];

const events = []; // 包含来自所有Frame的所有事件

await page.exposeFunction('_captureEvent', (event) => {
  events.push(event);
});

page.on('framenavigated', async (frame) => {
  await injectRecording(frame); // 将 rrweb 注入 iframe
});

await page.goto('https://example.com');

// 您的事件将在事件数组中
```

### 4. Electron

```ts
const win = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences: {
    preload: path.join(__dirname, 'rrweb-recording-script.js'),
    // 这会打开 iframe 内的预加载，但会禁用节点集成
    nodeIntegrationInSubFrames: true,
    nodeIntegration: false,
  },
});
```

请查看 https://www.electronjs.org/docs/latest/tutorial/tutorial-preload
和 https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts

---

### Recipes Custom Event (`docs/recipes/custom-event.md`)

# Custom events

After recording starts, call `record.addCustomEvent(tag, payload)` to add an event.
The tag is a string; the payload can be any serializable value.

```js
import { record } from '@rrweb/record';

const events = [];
record({ emit: (event) => events.push(event) });

record.addCustomEvent('submit-form', { name: 'Adam' });
record.addCustomEvent('some-error', { message: 'Could not submit the form.' });
```

Listen for custom events during replay:

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events);
replayer.on('custom-event', (event) => {
  console.log(event.data.tag, event.data.payload);
});
```

## Display in rrweb-player

Custom events appear as timeline markers in rrweb-player. Hover over a marker to
see its tag. Use the `tags` option to configure a color for each custom-event tag.

```js
import rrwebPlayer from 'rrweb-player';

new rrwebPlayer({
  target: document.body,
  props: {
    events,
    tags: {
      'submit-form': '#21e676',
      'some-error': 'red',
    },
  },
});
```

For captions and timeline markers, use the separate
[annotations API](./annotations.md).

---

### Recipes Custom Event.Zh CN (`docs/recipes/custom-event.zh_CN.md`)

# 自定义事件

录制时可能需要在特定的时间点记录一些特定含义的数据，如果希望这部分数据作为回放时的一部分，则可以通过自定义事件的方式实现。

开始录制后，我们就可以通过 `record.addCustomEvent` API 添加自定义事件：

```js
import { record } from '@rrweb/record';

// 开始录制
record({
  emit(event) {
    ...
  }
})

// 在开始录制后的任意时间点记录自定义事件，例如：
record.addCustomEvent('submit-form', {
  name: '姓名',
  age: 18
})
record.addCustomEvent('some-error', {
  error
})
```

`addCustomEvent` 接收两个参数，第一个是字符串类型的 `tag`，第二个是任意类型的 `payload`。

在回放时我们可以通过监听事件获取对应的事件，也可以通过配置 rrweb-player 在回放器 UI 的时间轴中展示对应事件。

**获取对应事件**

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events);

replayer.on('custom-event', (event) => {
  console.log(event.tag, event.payload);
});
```

**在 rrweb-player 中展示**

```js
new rrwebPlayer({
  target: document.body,
  props: {
    events,
    // 自定义各个 tag 在时间轴上的色值
    tags: {
      'submit-form': '#21e676',
      'some-error': 'red',
    },
  },
});
```

---

### Recipes Customize Replayer (`docs/recipes/customize-replayer.md`)

# Customize the Replayer

When `Replayer` and the [rrweb-player](../../packages/rrweb-player/) UI do not fit your need, you can customize your replayer UI.

There are several ways to do this:

1. Use [rrweb-player](../../packages/rrweb-player/), and customize its CSS.
2. Use [rrweb-player](../../packages/rrweb-player/), and set `showController: false` to hide the controller UI. With this config, you can implement your controller UI.
3. Use the `insertStyleRules` options to inject some CSS into the replay iframe.
4. Develop a new replayer UI with `Replayer`.

## Implement Your Controller UI

When using rrweb-player, you can hide its controller UI:

```js
import rrwebPlayer from 'rrweb-player';

new rrwebPlayer({
  target: document.body,
  props: {
    events,
    showController: false,
  },
});
```

When you are implementing a controller UI, you may need to interact with rrweb-player.

The follwing APIs show some common use case of a controller UI:

```js
// toggle between play and pause
rrwebPlayer.toggle();
// play
rrwebPlayer.play();
// pause
rrwebPlayer.pause();
// update the dimension
rrwebPlayer.$set({
  width: NEW_WIDTH,
  height: NEW_HEIGHT,
});
rrwebPlayer.triggerResize();
// toggle whether to skip the inactive time
rrwebPlayer.toggleSkipInactive();
// set replay speed
rrwebPlayer.setSpeed(2);
// go to some timing
rrwebPlayer.goto(3000);
```

And there are some ways to listen rrweb-player's state:

```js
// get current timing
rrwebPlayer.addEventListener('ui-update-current-time', (event) => {
  console.log(event.payload);
});

// get current state
rrwebPlayer.addEventListener('ui-update-player-state', (event) => {
  console.log(event.payload);
});

// get current progress
rrwebPlayer.addEventListener('ui-update-progress', (event) => {
  console.log(event.payload);
});
```

## Develop a new replayer UI with `Replayer`.

Please refer [rrweb-player](../../packages/rrweb-player/).

## Render annotations

Use the [annotation replay callback](./annotations.md#replay-callbacks) to render
captions or timeline markers in your own UI. The recipe also explains restoring
persistent caption state when seeking.

---

### Recipes Customize Replayer.Zh CN (`docs/recipes/customize-replayer.zh_CN.md`)

# 自定义回放 UI

当 `Replayer` 和 [rrweb-player](../../packages/rrweb-player/) 的 UI 不能满足需求时，可以通过自定义回放 UI 制作属于你自己的回放器。

你可以通过以下几种方式从不同角度自定义回放 UI：

1. 使用 [rrweb-player](../../packages/rrweb-player/) 时，通过覆盖 CSS 样式表定制 UI。
2. 使用 [rrweb-player](../../packages/rrweb-player/) 时，通过 `showController: false` 隐藏控制器 UI，重新实现控制器 UI。
3. 通过 `insertStyleRules` 在回放页面（iframe）内定制 CSS 样式。
4. 基于 `Replayer` 开发自己的回放器 UI。

## 实现控制器 UI

使用 rrweb-player 时，可以隐藏其控制器 UI：

```js
import rrwebPlayer from 'rrweb-player';

new rrwebPlayer({
  target: document.body,
  props: {
    events,
    showController: false,
  },
});
```

实现自己的控制器 UI 时，你可能需要与 rrweb-player 进行交互。

通过 API 控制 rrweb-player：

```js
// 在播放和暂停间切换
rrwebPlayer.toggle();
// 播放
rrwebPlayer.play();
// 暂停
rrwebPlayer.pause();
// 更新 rrweb-player 宽高
rrwebPlayer.$set({
  width: NEW_WIDTH,
  height: NEW_HEIGHT,
});
rrwebPlayer.triggerResize();
// 切换否跳过无操作时间
rrwebPlayer.toggleSkipInactive();
// 设置播放速度为 2 倍
rrwebPlayer.setSpeed(2);
// 跳转至播放 3 秒处
rrwebPlayer.goto(3000);
```

通过监听事件获得 rrweb-player 的状态：

```js
// 当前播放时间
rrwebPlayer.addEventListener('ui-update-current-time', (event) => {
  console.log(event.payload);
});

// 当前播放状态
rrwebPlayer.addEventListener('ui-update-player-state', (event) => {
  console.log(event.payload);
});

// 当前播放进度
rrwebPlayer.addEventListener('ui-update-progress', (event) => {
  console.log(event.payload);
});
```

## 基于 `Replayer` 开发自己的回放器 UI

可以参照 [rrweb-player](../../packages/rrweb-player/) 的方式进行开发。

---

### Recipes Export To Video (`docs/recipes/export-to-video.md`)

# Convert To Video

The event data recorded by rrweb is a performant, easy to compress, text-based format. And the replay is also pixel perfect.

But if you really need to convert it into a video format, there are some tools that can do this work.

Use [rrvideo](../../packages/rrvideo/README.md).

---

### Recipes Export To Video.Zh CN (`docs/recipes/export-to-video.zh_CN.md`)

# 转换为视频

rrweb 录制的数据是一种高效、易于压缩的文本格式，可以用于像素级的回放。但如果有进一步将录制数据转换为视频的需求，同样可以通过一些工具实现。

使用 [rrvideo](../../packages/rrvideo/README.zh_CN.md)。

---

### Recipes Index.Zh CN (`docs/recipes/index.zh_CN.md`)

# 场景示例

> 除场景示例外，你可能还想通过[使用指南](../../guide.zh_CN.md)掌握 rrweb 常用 API，或是通过[设计文档](../design/index.md)深入 rrweb 的技术细节。

## 场景列表

### 录制与回放

录制与回放是最常用的使用方式，适用于任何需要采集用户行为数据并重新查看的场景。

[链接](./record-and-replay.zh_CN.md)

### 异步加载数据

当录制的数据较多时，一次性加载至回放页面可能带来较大的网络开销和较长的等待时间。这时可以采取数据分页的方式，异步地加载数据并回放。

[链接](./pagination.zh_CN.md)

### 实时回放（直播）

如果希望持续、实时地看到录制的数据，达到类似直播的效果，则可以使用实时回放 API。这个方式也适用于一些实时协同的场景。

[链接](./live-mode.zh_CN.md)

### 自定义事件

录制时可能需要在特定的时间点记录一些特定含义的数据，如果希望这部分数据作为回放时的一部分，则可以通过自定义事件的方式实现。

[链接](./custom-event.zh_CN.md)

### 回放时与 UI 交互

回放时的 UI 默认不可交互，但在特定场景下也可以通过 API 允许用户与回放场景进行交互。

[链接](./interaction.zh_CN.md)

### 自定义回放 UI

当 `Replayer` 和 rrweb-player 的 UI 不能满足需求时，可以通过自定义回放 UI 制作属于你自己的回放器。

[链接](./customize-replayer.zh_CN.md)

### 转换为视频

rrweb 录制的数据是一种高效、易于压缩的文本格式，可以用于像素级的回放。但如果有进一步将录制数据转换为视频的需求，同样可以通过一些工具实现。

[链接](./export-to-video.zh_CN.md)

### 优化存储容量

在一些场景下 rrweb 的录制数据量可能高于你的预期，这部分文档可以帮助你选择适用于你的存储优化策略。

[链接](./optimize-storage.zh_CN.md)

### Canvas

Canvas 是一种特殊的 HTML 元素，默认情况下其内容不会被 rrweb 观测。我们可以通过特定的配置让 rrweb 能够录制并回放 Canvas。

[链接](./canvas.zh_CN.md)

### console 录制和播放

从 v1.0.0 版本开始，我们以插件的形式增加了录制和播放控制台输出的功能。这个功能旨在为开发者提供更多的 bug 信息。对这项功能我们还提供了一些设置选项。

[链接](./console.zh_CN.md)

### 插件 API

插件 API 旨在扩展 rrweb 的功能，同时不影响核心包的正常使用。

[链接](./plugin-api.zh_CN.md)

---

### Recipes Interaction (`docs/recipes/interaction.md`)

# Interact With UI During Replay

By default, the UI could not interact during replay. But you can use API to enable/disable this programmatically.

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events);

// enable user interact with the UI
replayer.enableInteract();

// disable user interact with the UI
replayer.disableInteract();
```

rrweb uses the `pointer-events: none` CSS property to disable interaction.

This will let the replay more stable and avoid some problems like navigate by clicking an external link.

If you want to enable user interaction, like input, then you can use the `enableInteract` API. But be sure you have handled the problems that may cause unstable replay.

---

### Recipes Interaction.Zh CN (`docs/recipes/interaction.zh_CN.md`)

# 回放时与 UI 交互

回放时的 UI 默认不可交互，但在特定场景下也可以通过 API 允许用户与回放场景进行交互。

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events);

// 允许用户在回放的 UI 中进行交互
replayer.enableInteract();

// 禁用用户在回放的 UI 中进行交互
replayer.disableInteract();
```

rrweb 使用 CSS 的 `pointer-events: none` 属性禁用交互。

这能够让回放更加稳定，例如避免用户点击回放中的超链接发生跳转等。

如果你希望允许用户交互，例如用户可以在回放时在输入框中进行输入，那么就可以调用 `enableInteract` API，但需要对不稳定的场景自行加以处理。

---

### Recipes Live Mode (`docs/recipes/live-mode.md`)

# Real-time Replay (Live Mode）

If you want to replay the events in a real-time way, you can use the live mode API. This API is also useful for some real-time collaboration usage.

When you use `Replayer` for real-time replay, configure `liveMode: true` and call `startLive()` to enable live mode.

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer([], {
  liveMode: true,
});
replayer.startLive();
```

Later when you receive new events (e.g. over websockets), you can add them using:

```
function onReceive(event) {
  replayer.addEvent(event);
}
```

If you have an ongoing recording that already has events, and wish to initiate play from a 'live' time, it's also possible to use the `play` function, supplied with an offset which corresponds to the current time:

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(EXISTING_EVENTS, {
  liveMode: true,
});
replayer.play(Date.now() - EXISTING_EVENTS[0].timestamp);
```

When calling the `startLive` API, there is an optional parameter to set the baseline time. By default, this is `Date.now()` so that events are applied as soon as they come in, however this may cause your replay to appear laggy. Because data transportation needs time (such as the delay of the network), And some events have been throttled(such as mouse movements) which has a delay by default.

Here is how you introduce a buffer:

```js
const BUFFER_MS = 1000;
replayer.startLive(Date.now() - BUFFER_MS);
```

This will let the replay always delay 1 second than the source. If the time of data transportation is not longer than 1 second, the user will not feel laggy.

The same can be done for `play` as follows: `replayer.play((Date.now() - EXISTING_EVENTS[0].timestamp) - BUFFER_MS)`

---

### Recipes Live Mode.Zh CN (`docs/recipes/live-mode.zh_CN.md`)

# 实时回放（直播）

如果希望持续、实时地看到录制的数据，达到类似直播的效果，则可以使用实时回放 API。这个方式也适用于一些实时协同的场景。

使用 `Replayer` 进行实时回放时，需要传入 `liveMode: true` 配置，并通过 `startLive` API 启动直播模式。

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer([], {
  liveMode: true,
});

replayer.startLive(FIRST_EVENT.timestamp - BUFFER);
```

使用 `startLive` API 启动直播模式时，你可以传入一个可选参数，用于设置基线时间戳，这对于需要一定缓冲时间的直播场景非常有用。

例如录制时的第一个事件记录于 1500 这个时间点，实时回放时传入 `startLive(1500)` 就会让回放器将基线时间戳定为 1500，并用于计算后续事件的延迟时间。

但这有时会让实时回放看起来卡顿，因为数据的传输需要一定的时间（例如网络延迟），同时一些事件因为节流的性能优化会延迟发出（例如鼠标移动）。

因此我们可以通过 `startLive` 传入一个较小值的方式来提供一个缓冲时间，例如 `startLive(500)` 就会让回放总是延迟 1 秒播放。如果传输延迟小于 1 秒，则观看者不会感到卡顿。

启动直播模式后，可以通过 `addEvent` API 不断将最新的事件传入回放器中：

```js
replayer.addEvent(NEW_EVENT);
```

---

### Recipes Network (`docs/recipes/network.md`)

# Network Recorder and Replayer

This feature aims to provide developers with more information about the bug scene. There are some options for recording and replaying network output.

## Enable Recording Network

You can enable using default option like this:

```js
import { record } from '@rrweb/record';
import { getRecordNetworkPlugin } from '@rrweb/rrweb-plugin-network-record';

record({
  emit: function emit(event) {
    events.push(event);
  },
  // to use default record option
  plugins: [getRecordNetworkPlugin()],
});
```

You can also customize the behavior of logger like this:

```js
import { record } from '@rrweb/record';
import { getRecordNetworkPlugin } from '@rrweb/rrweb-plugin-network-record';

const recordingId = crypto.randomUUID();

record({
  emit: function emit(event) {
    fetch(`https://api.rrweb.com/recordings/${recordingId}/events`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        events: [event],
      }),
    });
  },
  // customized record options
  plugins: [
    getRecordNetworkPlugin({
      initiatorTypes: ['fetch', 'xmlhttprequest'],
      // mask/block recording event for request
      transformRequestFn: (request) => {
        // request.name is url
        if (request.name.includes('api.rrweb.com')) return; // skip request
        delete request.requestHeaders?.Authorization; // remove sensitive data
        request.responseBody = maskTextFn(request.responseBody);
        return request;
      },
      recordHeaders: true,
      recordBody: true,
      recordInitialRequests: false,
    }),
  ],
});
```

**alert**: If you are uploading events to a server, you should always use `transformRequestFn` to mask/block recording events for these requests or else you will cause a nasty loop.

All options are described below:

| key                   | default                                | description                                                                                                                                                                                         |
| --------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| initiatorTypes        | `['fetch','xmlhttprequest','img',...]` | Default value contains names of all [initiator types](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/initiatorType). You can override it by setting the types you need. |
| transformRequestFn    | `(request) => request`                 | Transform recording event for request to block (skip) or mask/transform request (e.g. to hide sensitive data)                                                                                       |
| recordHeaders         | `false`                                | Record the request & response headers for `fetch` and `xmlhttprequest` requests                                                                                                                     |
| recordBody            | `false`                                | Record the request & response bodies for `fetch` and `xmlhttprequest` requests                                                                                                                      |
| recordInitialRequests | `false`                                | Record an event for all requests prior to `record()` being called                                                                                                                                   |

## Replay Network Requests

It is up to you to decide how to best replay your network events using the `onNetworkData` callback.

```js
import rrweb from 'rrweb';
import { getReplayNetworkPlugin } from '@rrweb/rrweb-plugin-network-replay';

const replayer = new rrweb.Replayer(events, {
  plugins: [
    getReplayNetworkPlugin({
      onNetworkData: ({ requests }) => {
        for (const request of requests) {
          const name = request.name; // url
          const method = request.method;
          const status = request.status;
          console.log(`${method} ${name} ${status}`);
        }
      },
    }),
  ],
});
replayer.play();
```

Description of replay option is as follows:

| key           | default     | description                                                                                |
| ------------- | ----------- | ------------------------------------------------------------------------------------------ |
| onNetworkData | `undefined` | You could use this interface to replay the network requests in a simulated browser console |

## Technical Implementation

This implementation records [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) by patching their object & methods. We record document navigation using [`PerformanceNavigationTiming`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming) and we use [`PerformanceResourceTiming`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming) for recording everything else (script, img, link etc.) via [`PerformanceObserver`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver) API.

For more information please see [[network-plugin] Feat: Capture network events #1105](https://github.com/rrweb-io/rrweb/pull/1105) PR.

---

### Recipes Optimize Storage (`docs/recipes/optimize-storage.md`)

# Optimize The Storage Size

In some Apps, rrweb may record an unexpected amount of data. This part will help to find a suitable way to optimize the storage.

Currently, we have the following optimize strategies:

- block some DOM element to reduce the recording area
- use sampling config to reduce the events
- use deduplication and compression to reduce storage size

## Block DOM element

Some DOM elements may emit lots of events, and some of them may not be the thing user cares about. So you can use the block class to ignore these kinds of elements.

Some common patterns may emit lots of events are:

- long list
- complex SVG
- element with JS controlled animation
- canvas animations

## Sampling

Use the sampling config in the recording can reduce the storage size by dropping some events:

**Scenario 1**

```js
import { record } from '@rrweb/record';

record({
  emit(event) {},
  sampling: {
    // do not record mouse movement
    mousemove: false
    // do not record mouse interaction
    mouseInteraction: false
    // set the interval of scrolling event
    scroll: 150 // do not emit twice in 150ms
    // set the interval of media interaction event
    media: 800
    // set the timing of record input
    input: 'last' // When input mulitple characters, only record the final input
  }
})
```

**Scenario 2**

```js
import { record } from '@rrweb/record';

record({
  emit(event) {},
  sampling: {
    // Configure which kinds of mouse interaction should be recorded
    mouseInteraction: {
      MouseUp: false,
      MouseDown: false,
      Click: false,
      ContextMenu: false,
      DblClick: false,
      Focus: false,
      Blur: false,
      TouchStart: false,
      TouchEnd: false,
    },
  },
});
```

## Compression

### Use packFn to compress every event

rrweb provides an fflate-based simple compress function in [@rrweb/packer](../../packages/packer/).

You can use it by passing it as the `packFn` in the recording.

```js
import { pack } from '@rrweb/packer';

record({
  emit(event) {},
  packFn: pack,
});
```

And you need to pass packer.unpack as the `unpackFn` in replaying.

```js
import { unpack } from '@rrweb/packer';
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events, {
  unpackFn: unpack,
});
```

### Compress the whole session

Use packFn to compress every event may not get the best result.

It's recommended to compress the whole session in the backend, which will have a more efficient compression ratio for some algorithms like deflate.

## Deduplication

Another optimizing strategy is deduplication.

Since we need to simulate hover in the replay, rrweb will try its best to inline CSS styles in the events.

So if we are applying rrweb to github.com, we may record many duplicate CSS styles across sessions.

We can iterate the events and extract CSS. Then we can only store one copy of the styles.

This strategy is also possible for the full snapshot across sessions.

---

### Recipes Optimize Storage.Zh CN (`docs/recipes/optimize-storage.zh_CN.md`)

# 优化存储容量

在一些场景下 rrweb 的录制数据量可能高于你的预期，这部分文档可以帮助你选择适用于你的存储优化策略。

优化策略分为以下几类：

- 通过屏蔽 DOM 元素，减少录制的内容
- 通过 sampling 配置抽样策略，减少录制的数据
- 通过去冗、压缩，减少数据存储体积

## 屏蔽 DOM 元素

一些特定 DOM 元素可能会产生大量的录制数据，而这些数据未必是回放时关注的，这种情况下可以选择将这些 DOM 元素进行屏蔽，不录制其内容。

常见的大数据量 DOM 元素包括：

- 长列表
- 复杂的 SVG
- 包含 JS 控制动画的元素
- canvas 动画

## 抽样策略

录制时通过 sampling 配置可以让特定数据以抽样的形式减少录制频率：

**示例 1**

```js
import { record } from '@rrweb/record';

record({
  emit(event) {},
  sampling: {
    // 不录制鼠标移动事件
    mousemove: false
    // 不录制鼠标交互事件
    mouseInteraction: false,
    // 设置滚动事件的触发频率
    scroll: 150 // 每 150ms 最多触发一次
    // set the interval of media interaction event
    media: 800
    // 设置输入事件的录制时机
    input: 'last' // 连续输入时，只录制最终值
  }
})
```

**示例 2**

```js
import { record } from '@rrweb/record';

record({
  emit(event) {},
  sampling: {
    // 定义不录制的鼠标交互事件类型，可以细粒度的开启或关闭对应交互录制
    mouseInteraction: {
      MouseUp: false,
      MouseDown: false,
      Click: false,
      ContextMenu: false,
      DblClick: false,
      Focus: false,
      Blur: false,
      TouchStart: false,
      TouchEnd: false,
    },
  },
});
```

## 压缩

### 基于 packFn 的单数据压缩

rrweb 提供了一个基于 fflate 的简单压缩函数，在 [@rrweb/packer](../../packages/packer/) 中可以作为 `packFn` 传入使用。

```js
import { pack } from '@rrweb/packer';

record({
  emit(event) {},
  packFn: pack,
});
```

回放时通用需要传入 packer.unpack 作为 `unpackFn` 传入。

```js
import { unpack } from '@rrweb/packer';
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events, {
  unpackFn: unpack,
});
```

### 批量压缩

基于 packFn 的单数据压缩以每个 event 数据为单位进行压缩，但这很多时候不能发挥 rrweb 录制数据易于压缩的优势。

因此**更加推荐**在服务端实现多个 event 的批量压缩，例如将单次用户操作产生的所有 event 数据进行一次压缩，对于 gzip 等压缩算法来说更为友好。

## 去冗

另一个优化存储容量的思路是去冗。

为了模拟 hover 等需求，rrweb 会尽可能的将 CSS 样式 inline 在录制数据中。

可以想象，如果使用 rrweb 录制每个用户对 github.com 的访问，则会在录制数据中保存大量重复的样式表内容。

可以通过遍历录制数据，将包含样式表的内容提取单独保存的方式，将这部分相同数据仅保存一份。

另一方面，全量快照类的数据也存在同样的问题，可以使用同样的思路去冗，减少存储总量。

---

### Recipes Pagination (`docs/recipes/pagination.md`)

# Load Events Asynchronous

When the size of the recorded events increased, load them in one request is not performant. You can paginate the events and load them as you need.

rrweb's API for loading async events is quite simple:

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events);

replayer.addEvent(NEW_EVENT);
```

When calling the `addEvent` API to add a new event, rrweb will resolve its timestamp and replay it as need.

If you need to load several events, you can do a loop like this:

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events);

for (const event of NEW_EVENTS) {
  replayer.addEvent(event);
}
```

---

### Recipes Pagination.Zh CN (`docs/recipes/pagination.zh_CN.md`)

# 异步加载数据

当录制的数据较多时，一次性加载至回放页面可能带来较大的网络开销和较长的等待时间。这时可以采取数据分页的方式，异步地加载数据并回放。

rrweb 中用于实现异步加载数据的 API 非常简单直观：

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events);

replayer.addEvent(NEW_EVENT);
```

只需要调用 `addEvent` 传入新的数据，rrweb 就会自动处理其中的时间关系，以最恰当的方式进行回放。

如果需要异步加载多个数据，只需这样使用：

```js
import { Replayer } from '@rrweb/replay';

const replayer = new Replayer(events);

for (const event of NEW_EVENTS) {
  replayer.addEvent(event);
}
```

---

### Recipes Plugin Api (`docs/recipes/plugin-api.md`)

# Plugins

The plugin API is designed to enable extending the functionality of rrweb without impacting regular use of the core packages.

# Available plugins

- [@rrweb/rrweb-plugin-console-record](packages/plugins/rrweb-plugin-console-record): A plugin for recording console logs.
- [@rrweb/rrweb-plugin-console-replay](packages/plugins/rrweb-plugin-console-replay): A plugin for replaying console logs.
- [@rrweb/rrweb-plugin-sequential-id-record](packages/plugins/rrweb-plugin-sequential-id-record): A plugin for recording sequential IDs.
- [@rrweb/rrweb-plugin-sequential-id-replay](packages/plugins/rrweb-plugin-sequential-id-replay): A plugin for replaying sequential IDs.
- [@rrweb/rrweb-plugin-canvas-webrtc-record](packages/plugins/rrweb-plugin-canvas-webrtc-record): A plugin for stream `<canvas>` via WebRTC.
- [@rrweb/rrweb-plugin-canvas-webrtc-replay](packages/plugins/rrweb-plugin-canvas-webrtc-replay): A plugin for playing streamed `<canvas>` via WebRTC.
- [@rrweb/rrweb-plugin-network-record](packages/plugins/rrweb-plugin-network-record): A plugin for recording network requests (xhr/fetch).
- [@rrweb/rrweb-plugin-network-replay](packages/plugins/rrweb-plugin-network-replay): A plugin for replaying network requests (xhr/fetch).

## Interface

Consistent with other functionality in rrweb, a plugin can implement record or replay or both features.

```ts
export type RecordPlugin<TOptions = unknown> = {
  name: string;
  observer: (cb: Function, options: TOptions) => listenerHandler;
  options: TOptions;
};

export type ReplayPlugin = {
  handler: (
    event: eventWithTime,
    isSync: boolean,
    context: { replayer: Replayer },
  ) => void;
};
```

Both record and replay plugins have a type interface.

### Example

#### Record Plugin

```ts
import { record } from '@rrweb/record';

const exampleRecordPlugin: RecordPlugin<{ foo: string }> = {
  name: 'my-scope/example@1',
  observer(cb, options) {
    const timer = setInterval(() => {
      cb({
        foo: options.foo,
        timestamp: Date.now(),
      });
    }, 1000);
    return () => clearInterval(timer);
  },
  options: {
    foo: 'bar',
  },
};

record({
  emit(event) {},
  plugins: [exampleRecordPlugin],
});
```

In this example, the record plugin will emit events like this:

```js
{
  type: 6,
  data: {
    plugin: 'my-scope/example@1',
    payload: {
      foo: 'bar',
      timestamp: 1624693882345,
    },
  },
  timestamp: 1624693882345,
}
```

#### Replay Plugin

```ts
import { Replayer } from '@rrweb/replay';

const exampleReplayPlugin: ReplayPlugin = {
  handler(event, isSync, context) {
    if (event.type === EventType.Plugin) {
      // do something with event.data.payload
      if (event.data.plugin === 'my-scope/example@1') {
        // handle example plugin data
      }
    }
  },
};

const replayer = new Replayer(events, {
  plugins: [exampleReplayPlugin],
});
```

A replay plugin can interact with the replayer by using `context.replayer`.

## Naming Plugins

A record plugin should have a unique name, and it will be stored in the event it emits.

**Since we will have both plugins in the rrweb repo and plugins in users' own codebase, which may cause naming conflicts in the future, we strongly recommended users naming their own plugins in this way:**

> scope/name@version

For example `rrweb/console@1` or `github/pr@2`.

---

### Recipes Plugin Api.Zh CN (`docs/recipes/plugin-api.zh_CN.md`)

# 插件

插件 API 旨在扩展 rrweb 的功能，同时不影响核心包的正常使用。

# 可用插件

- [@rrweb/rrweb-plugin-console-record](packages/plugins/rrweb-plugin-console-record)：一个用于记录控制台日志的插件。
- [@rrweb/rrweb-plugin-console-replay](packages/plugins/rrweb-plugin-console-replay)：一个用于回放控制台日志的插件。
- [@rrweb/rrweb-plugin-sequential-id-record](packages/plugins/rrweb-plugin-sequential-id-record)：一个用于记录顺序 ID 的插件。
- [@rrweb/rrweb-plugin-sequential-id-replay](packages/plugins/rrweb-plugin-sequential-id-replay)：一个用于回放顺序 ID 的插件。
- [@rrweb/rrweb-plugin-canvas-webrtc-record](packages/plugins/rrweb-plugin-canvas-webrtc-record)：一个用于通过 WebRTC 流式传输 `<canvas>` 的插件。
- [@rrweb/rrweb-plugin-canvas-webrtc-replay](packages/plugins/rrweb-plugin-canvas-webrtc-replay)：一个用于通过 WebRTC 播放流式 `<canvas>` 的插件。
- [@rrweb/rrweb-plugin-network-record](packages/plugins/rrweb-plugin-network-record)：一个用于记录网络请求的插件 (xhr/fetch)。
- [@rrweb/rrweb-plugin-network-replay](packages/plugins/rrweb-plugin-network-replay)：一个用于回放网络请求的插件 (xhr/fetch)。

## 接口

与 rrweb 其它功能相似，插件可以包含同时包含录制、回放侧的功能，也可以只实现其中任一。

```ts
export type RecordPlugin<TOptions = unknown> = {
  name: string;
  observer: (cb: Function, options: TOptions) => listenerHandler;
  options: TOptions;
};

export type ReplayPlugin = {
  handler: (
    event: eventWithTime,
    isSync: boolean,
    context: { replayer: Replayer },
  ) => void;
};
```

以上是录制和回放插件的接口类型。

### 示例

#### 录制侧插件

```ts
import { record } from '@rrweb/record';

const exampleRecordPlugin: RecordPlugin<{ foo: string }> = {
  name: 'my-scope/example@1',
  observer(cb, options) {
    const timer = setInterval(() => {
      cb({
        foo: options.foo,
        timestamp: Date.now(),
      });
    }, 1000);
    return () => clearInterval(timer);
  },
  options: {
    foo: 'bar',
  },
};

record({
  emit(event) {},
  plugins: [exampleRecordPlugin],
});
```

在这个示例中，录制侧插件将会输出这样的事件：

```js
{
  type: 6,
  data: {
    plugin: 'my-scope/example@1',
    payload: {
      foo: 'bar',
      timestamp: 1624693882345,
    },
  },
  timestamp: 1624693882345,
}
```

#### 回放侧插件

```ts
import { Replayer } from '@rrweb/replay';

const exampleReplayPlugin: ReplayPlugin = {
  handler(event, isSync, context) {
    if (event.type === EventType.Plugin) {
      // 使用 event.data.payload
      if (event.data.plugin === 'my-scope/example@1') {
        // 处理示例插件录制的数据
      }
    }
  },
};

const replayer = new Replayer(events, {
  plugins: [exampleReplayPlugin],
});
```

回放侧插件可以通过 `context.replayer` 与播放器进行交互。

## 插件命名

录制侧插件应该拥有全局唯一的名称，并且其名称会被记录在输出的事件中。

**由于会同时存在 rrweb 仓库中的官方插件与用户自行实现的自定义插件，所以我们推荐使用统一的命名规则避免冲突，命名方式如下：**

> scope/name@version

例如： `rrweb/console@1` 或 `github/pr@2`。

---

### Recipes Plugin (`docs/recipes/plugin.md`)

> This page has moved to [plugin-api.md](plugin-api.md).

---

### Recipes Plugin.Zh CN (`docs/recipes/plugin.zh_CN.md`)

> This page has moved to [plugin-api.zh_CN.md](plugin-api.zh_CN.md).

---

### Recipes Record And Replay (`docs/recipes/record-and-replay.md`)

# Record And Replay

Record and Replay is the most common use case, which is suitable for any scenario that needs to collect user behaviors and replay them.

You only need a simple API call to record the website:

```js
import { record } from '@rrweb/record';

const stopFn = record({
  emit(event) {
    // save the event
  },
});
```

You can use any approach to store the recorded events, like sending the events to your backend and save them into the database.

But you should guarantee:

- a set of events are sorted by its timestamp
- save every event

You can use the `stopFn` to stop the recording.

Replay is also as simple as passing events into `Replayer`.

```js
import { Replayer } from '@rrweb/replay';

const events = GET_YOUR_EVENTS;

const replayer = new Replayer(events);
replayer.play();
```

---

### Recipes Record And Replay.Zh CN (`docs/recipes/record-and-replay.zh_CN.md`)

# 录制与回放

录制与回放是最常用的使用方式，适用于任何需要采集用户行为数据并重新查看的场景。

仅需一个函数调用就可以录制当前页面：

```js
import { record } from '@rrweb/record';

const stopFn = record({
  emit(event) {
    // 保存获取到的 event 数据
  },
});
```

你可以使用任何方式保存录制的数据，例如通过网络请求将数据传入至后端持久化保存，但请确保：

- 一组录制的数据按照 event.timestamp 中的时间戳从小至大保存
- 完整保存数据，不缺失任何一个 event。

如果需要手动停止录制，可以调用返回的 `stopFn` 函数。

回放时只需要获取一段录制数据，并传入 `Replayer`：

```js
import { Replayer } from '@rrweb/replay';

const events = GET_YOUR_EVENTS;

const replayer = new Replayer(events);
replayer.play();
```

---

### Releases Next Channel (`docs/releases/next-channel.md`)

# Next release channel

The `next` branch is a long-lived prerelease channel for publishing packages before
they are promoted to the stable release flow. Releases from this branch publish
prerelease versions to npm under the `next` dist-tag, so users can install them
explicitly without moving the stable `latest` tag.

## Branch setup

The `next` branch intentionally carries `.changeset/pre.json`:

```json
{
  "mode": "pre",
  "tag": "next"
}
```

Keep this file in prerelease mode on `next`. The `tag` must stay `next` so
Changesets publishes prerelease packages with the npm `next` dist-tag.

Do not put `main` into the `next` prerelease mode. Current `main` should not
have `.changeset/pre.json`; stable releases from `main` continue to publish as
normal and move `latest` only through the regular stable release process.

## Regular operation

Bring stable branch updates into `next` as needed, either by merging `main` or
by cherry-picking specific commits:

```sh
git switch next
git fetch origin
git merge origin/main
```

If the merge conflicts on `.changeset/pre.json`, resolve the conflict by keeping
the `next` branch prerelease file with `"mode": "pre"` and `"tag": "next"`.

Feature work can also land directly on `next` when it needs prerelease validation
before promotion. Include normal Changesets for user-facing package changes; the
release workflow will turn those changesets into generated prerelease version
commits on `next`.

## Promoting work back to main

Do not merge `next` wholesale back to `main`. Promotion should copy only the
human-authored feature work that is ready for stable release:

```sh
git switch main
git fetch origin
git cherry-pick <feature-commit-sha>
```

Cherry-pick source, docs, tests, and normal changeset commits that describe the
feature. Never cherry-pick generated `Version Packages (next)` commits from
`next` to `main`.

When promoting, exclude release-generated files from the cherry-pick if they are
present in the selected commits:

- `.changeset/pre.json`
- package `CHANGELOG.md` files
- version-only `package.json` changes

Those files are generated separately by the stable release workflow after the
promoted changesets land on `main`.

If a cherry-pick includes both feature code and generated release changes, use a
non-committing cherry-pick and reset generated release files before committing.
Restore prerelease metadata and generated changelogs outright, then inspect
package manifests hunk-by-hunk so version-only changes do not move to `main`:

```sh
git cherry-pick --no-commit <feature-commit-sha>
git restore --staged --worktree \
  .changeset/pre.json \
  'packages/*/CHANGELOG.md' \
  'packages/plugins/*/CHANGELOG.md'
git restore --source=HEAD --staged --worktree -p \
  -- 'packages/*/package.json' 'packages/plugins/*/package.json'
git diff -- packages
git commit
```

During the interactive `git restore -p`, drop version-only package `package.json`
changes generated by the `next` release. Keep dependency, script, export, or
metadata changes only when they are part of the promoted feature.

## Release workflow and Chrome extension

The release workflow runs on both `main` and `next`. On `next`, Changesets
publishes prerelease packages to npm using the `next` dist-tag.

Chrome extension publication is also branch-specific:

- `main` publishes the production extension listing. Its extension ID is
  hard-coded in `.github/workflows/release.yml`, and it uses the
  `CWS_PUBLISHER_ID`, `CWS_CLIENT_ID`, `CWS_CLIENT_SECRET`, and
  `CWS_REFRESH_TOKEN` secrets.
- `next` publishes the prerelease extension listing with
  `NEXT_CWS_EXTENSION_ID`, `NEXT_CWS_PUBLISHER_ID`, `NEXT_CWS_CLIENT_ID`,
  `NEXT_CWS_CLIENT_SECRET`, and `NEXT_CWS_REFRESH_TOKEN`.

Keep those listings and credentials separate. Changes to the `next` listing
should not affect the production Chrome Web Store listing used by `main`.

## Manual Chrome extension recovery

Use this fallback only when the npm release was recovered separately and the
automatic Chrome step did not publish the matching extension version.

1. Check out the exact `main` or `next` release commit whose extension version
   should be published.
2. Install dependencies and build the archive:

   ```sh
   yarn install --frozen-lockfile
   NODE_OPTIONS='--max-old-space-size=4096' \
     DISABLE_WORKER_INLINING=true \
     yarn turbo run prepublish --filter=@rrweb/web-extension
   unzip -t packages/web-extension/dist/chrome.zip
   ```

3. Confirm that `packages/web-extension/dist/chrome/manifest.json` contains the
   intended version.
4. In the Chrome Web Store developer dashboard, select the production listing
   for `main` or the prerelease listing for `next`, upload
   `packages/web-extension/dist/chrome.zip`, and submit it for review.

GitHub Actions secrets cannot be read back for local CLI use, so dashboard upload
is the supported manual recovery path. Keep production and prerelease listings
separate.

---

### Superpowers Plans 2026 05 18 Sandboxed Rebuild (`docs/superpowers/plans/2026-05-18-sandboxed-rebuild.md`)

# Sandboxed Rebuild Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Make browser `rrweb-snapshot.rebuild()` reject unprotected rebuild targets by default, provide a safe iframe helper, and keep unsafe replay/canvas behavior explicit.

**Architecture:** `rrweb-snapshot` owns the rebuild target guard and the safe helper. `rrweb` continues to create the replay iframe, and passes the unsafe opt-out only when `UNSAFE_replayCanvas` makes the iframe policy unprotected. Documentation and the changeset describe the public behavior change and migration path.

**Tech Stack:** TypeScript, Vitest/jsdom, Puppeteer-backed rrweb replay tests, Yarn 1 workspaces, Changesets.

---

## Execution Prerequisites

Yarn 1.22.19 or direct `node_modules/.bin/*` commands must be available before running tests. In the Codex shell, `node_modules` is available but `yarn` may not be on `PATH`; use direct binaries if needed.

Baseline commands:

```bash
./node_modules/.bin/vitest run packages/rrweb-snapshot/test/rebuild.test.ts
./node_modules/.bin/cross-env PUPPETEER_HEADLESS=true ./node_modules/.bin/vitest run packages/rrweb/test/replayer.test.ts
```

Expected: dependencies install successfully; baseline tests pass before implementation work starts. If baseline tests fail before code changes, stop and report the pre-existing failures.

## File Structure

- `packages/rrweb-snapshot/src/rebuild.ts`: add rebuild target validation, the unsafe option, and the safe helper implementation.
- `packages/rrweb-snapshot/src/index.ts`: export `createSandboxedIframe` and `rebuildIntoSandboxedIframe`.
- `packages/rrweb-snapshot/test/rebuild.test.ts`: add jsdom tests for the guard, unsafe opt-out, and safe helper.
- `packages/rrweb/src/replay/index.ts`: pass `UNSAFE_allowUnprotectedRebuild` only when `UNSAFE_replayCanvas` is enabled.
- `packages/rrweb/test/replayer.test.ts`: add replay iframe sandbox tests for normal and unsafe canvas modes.
- `packages/rrweb-snapshot/README.md`: document safe browser rebuild usage.
- `docs/sandbox.md`, `guide.md`, `docs/recipes/canvas.md`: document the sandbox requirement and canvas opt-out.
- `.changeset/sandboxed-rebuilds.md`: release note for the breaking security-hardening change.

## Task 1: Guard Browser Rebuild Targets

**Files:**

- Modify: `packages/rrweb-snapshot/src/rebuild.ts`
- Test: `packages/rrweb-snapshot/test/rebuild.test.ts`

- [ ] **Step 1: Write failing tests for guarded `rebuild()`**

Add `rebuild` to the test imports in `packages/rrweb-snapshot/test/rebuild.test.ts`:

```ts
import rebuild, {
  adaptCssForReplay,
  buildNodeWithSN,
  createCache,
} from '../src/rebuild';
```

Inside the existing `describe('rebuild', function () { ... })`, after `beforeEach`, add this helper and tests:

```
/* Detailed source-code truncated for AI context efficiency. */
```

- [ ] **Step 2: Run tests to verify they fail**

Run:

```bash
./node_modules/.bin/vitest run packages/rrweb-snapshot/test/rebuild.test.ts
```

Expected: tests fail because `rebuild` does not reject unprotected browser documents and does not accept `UNSAFE_allowUnprotectedRebuild`.

- [ ] **Step 3: Implement the guard and option**

In `packages/rrweb-snapshot/src/rebuild.ts`, add these constants/types near `createCache()`:

```ts
const REBUILD_TARGET_ERROR =
  'rrweb-snapshot.rebuild() cannot rebuild into an unprotected browser document. Use rebuildIntoSandboxedIframe() or set UNSAFE_allowUnprotectedRebuild: true only when you accept the script-execution risk.';

type RebuildOptions = {
  doc: Document;
  onVisit?: (node: Node) => unknown;
  hackCss?: boolean;
  afterAppend?: (n: Node, id: number) => unknown;
  cache: BuildCache;
  mirror: Mirror;
  UNSAFE_allowUnprotectedRebuild?: boolean;
};

function isSupportedSandboxedIframe(
  frameElement: Element | null,
): frameElement is HTMLIFrameElement {
  if (
    !frameElement ||
    frameElement.tagName !== 'IFRAME' ||
    !('sandbox' in frameElement)
  ) {
    return false;
  }

  const sandboxTokens = Array.from((frameElement as HTMLIFrameElement).sandbox);
  return sandboxTokens.length === 1 && sandboxTokens[0] === 'allow-same-origin';
}

function assertRebuildTargetAllowed(options: RebuildOptions): void {
  if (options.UNSAFE_allowUnprotectedRebuild) {
    return;
  }

  const win = options.doc.defaultView;
  if (!win) {
    return;
  }

  if (isSupportedSandboxedIframe(win.frameElement)) {
    return;
  }

  throw new Error(REBUILD_TARGET_ERROR);
}
```

Replace the inline options type on `function rebuild(...)` with `options: RebuildOptions`, and call the guard before destructuring:

```ts
function rebuild(
  n: serializedNodeWithId,
  options: RebuildOptions,
): Node | null {
  assertRebuildTargetAllowed(options);

  const {
    doc,
    onVisit,
    hackCss = true,
    afterAppend,
    cache,
    mirror = new Mirror(),
  } = options;
```

- [ ] **Step 4: Run tests to verify they pass**

Run:

```bash
./node_modules/.bin/vitest run packages/rrweb-snapshot/test/rebuild.test.ts
```

Expected: `browser rebuild target guard` tests pass, and existing rebuild tests still pass.

- [ ] **Step 5: Commit Task 1**

```bash
git add packages/rrweb-snapshot/src/rebuild.ts packages/rrweb-snapshot/test/rebuild.test.ts
git commit -m "fix(snapshot): guard unprotected browser rebuilds"
```

## Task 2: Add Minimal Safe Iframe Helper

**Files:**

- Modify: `packages/rrweb-snapshot/src/rebuild.ts`
- Modify: `packages/rrweb-snapshot/src/index.ts`
- Test: `packages/rrweb-snapshot/test/rebuild.test.ts`

- [ ] **Step 1: Write failing tests for `rebuildIntoSandboxedIframe()`**

Update the imports in `packages/rrweb-snapshot/test/rebuild.test.ts`:

```ts
import rebuild, {
  adaptCssForReplay,
  buildNodeWithSN,
  createCache,
  rebuildIntoSandboxedIframe,
} from '../src/rebuild';
```

Add tests inside `describe('browser rebuild target guard', () => { ... })`:

```ts
it('rebuildIntoSandboxedIframe creates a fresh sandboxed iframe in the required root', () => {
  const root = document.createElement('div');
  document.body.appendChild(root);

  const { iframe, node } = rebuildIntoSandboxedIframe(simpleSnapshot, {
    root,
    cache,
    mirror,
  });

  expect(root.contains(iframe)).toBe(true);
  expect(iframe.getAttribute('sandbox')).toBe('allow-same-origin');
  expect(node).toBe(iframe.contentDocument);
  expect(iframe.contentDocument!.querySelector('body')).not.toBeNull();
  root.remove();
});

it('rebuildIntoSandboxedIframe does not allow callers to override sandbox', () => {
  const root = document.createElement('div');
  document.body.appendChild(root);

  const { iframe } = rebuildIntoSandboxedIframe(simpleSnapshot, {
    root,
    cache,
    mirror,
    iframeAttributes: {
      sandbox: 'allow-same-origin allow-scripts',
      title: 'Replay',
    },
  });

  expect(iframe.getAttribute('sandbox')).toBe('allow-same-origin');
  expect(iframe.getAttribute('title')).toBe('Replay');
  root.remove();
});
```

- [ ] **Step 2: Run tests to verify they fail**

Run:

```bash
./node_modules/.bin/vitest run packages/rrweb-snapshot/test/rebuild.test.ts
```

Expected: tests fail because `rebuildIntoSandboxedIframe` is not implemented or exported.

- [ ] **Step 3: Implement helper types and function**

In `packages/rrweb-snapshot/src/rebuild.ts`, add this type after `RebuildOptions`:

```ts
type RebuildIntoSandboxedIframeOptions = Omit<
  RebuildOptions,
  'doc' | 'UNSAFE_allowUnprotectedRebuild'
> & {
  root: Element;
  iframeAttributes?: Omit<Record<string, string>, 'sandbox'> & {
    sandbox?: string;
  };
};
```

Add the helper before `export default rebuild;`:

```ts
export function rebuildIntoSandboxedIframe(
  n: serializedNodeWithId,
  options: RebuildIntoSandboxedIframeOptions,
): { iframe: HTMLIFrameElement; node: Node | null } {
  const iframe = options.root.ownerDocument.createElement('iframe');

  for (const [name, value] of Object.entries(options.iframeAttributes || {})) {
    if (name === 'sandbox') {
      continue;
    }
    iframe.setAttribute(name, value);
  }

  iframe.setAttribute('sandbox', 'allow-same-origin');
  options.root.appendChild(iframe);

  const node = rebuild(n, {
    ...options,
    doc: iframe.contentDocument!,
  });

  return { iframe, node };
}
```

- [ ] **Step 4: Export helper from package entrypoint**

In `packages/rrweb-snapshot/src/index.ts`, update imports and exports:

```ts
import rebuild, {
  buildNodeWithSN,
  adaptCssForReplay,
  createCache,
  rebuildIntoSandboxedIframe,
} from './rebuild';
```

and:

```ts
export {
  snapshot,
  serializeNodeWithId,
  rebuild,
  rebuildIntoSandboxedIframe,
  buildNodeWithSN,
  adaptCssForReplay,
  createCache,
  transformAttribute,
  ignoreAttribute,
  slimDOMDefaults,
  visitSnapshot,
  cleanupSnapshot,
  needMaskingText,
  classMatchesRegex,
  IGNORED_NODE,
  genId,
};
```

- [ ] **Step 5: Run tests and typecheck**

Run:

```bash
./node_modules/.bin/vitest run packages/rrweb-snapshot/test/rebuild.test.ts
./node_modules/.bin/tsc --noEmit -p packages/rrweb-snapshot/tsconfig.json
```

Expected: rebuild tests pass and TypeScript reports no errors.

- [ ] **Step 6: Commit Task 2**

```bash
git add packages/rrweb-snapshot/src/rebuild.ts packages/rrweb-snapshot/src/index.ts packages/rrweb-snapshot/test/rebuild.test.ts
git commit -m "feat(snapshot): add sandboxed iframe rebuild helper"
```

## Task 3: Integrate Guard With rrweb Replayer

**Files:**

- Modify: `packages/rrweb/src/replay/index.ts`
- Test: `packages/rrweb/test/replayer.test.ts`

- [ ] **Step 1: Write failing replayer tests**

In `packages/rrweb/test/replayer.test.ts`, add these tests after `it('can get meta data', async () => { ... })`:

```ts
it('creates a replay iframe with the supported sandbox policy', async () => {
  const sandbox = await page.evaluate(`
      const { Replayer } = rrweb;
      const replayer = new Replayer(events);
      replayer.iframe.getAttribute('sandbox');
    `);

  expect(sandbox).toBe('allow-same-origin');
});

it('keeps UNSAFE_replayCanvas explicit by using an unprotected sandbox policy', async () => {
  const sandbox = await page.evaluate(`
      const { Replayer } = rrweb;
      const replayer = new Replayer(events, { UNSAFE_replayCanvas: true });
      replayer.iframe.getAttribute('sandbox');
    `);

  expect(sandbox).toBe('allow-same-origin allow-scripts');
});

it('can rebuild the first full snapshot when UNSAFE_replayCanvas is enabled', async () => {
  const rebuilt = await page.evaluate(`
      const { Replayer } = rrweb;
      const replayer = new Replayer(events, { UNSAFE_replayCanvas: true });
      replayer.play(0);
      Boolean(replayer.iframe.contentDocument.querySelector('html'));
    `);

  expect(rebuilt).toBe(true);
});
```

- [ ] **Step 2: Run tests to verify unsafe canvas rebuild fails**

Run:

```bash
./node_modules/.bin/cross-env PUPPETEER_HEADLESS=true ./node_modules/.bin/turbo run prepublish -F rrweb
./node_modules/.bin/cross-env PUPPETEER_HEADLESS=true ./node_modules/.bin/vitest run packages/rrweb/test/replayer.test.ts -t "UNSAFE_replayCanvas|supported sandbox policy"
```

Expected: default sandbox test passes. The unsafe canvas rebuild test fails until the replayer passes `UNSAFE_allowUnprotectedRebuild`.

- [ ] **Step 3: Pass unsafe option only for `UNSAFE_replayCanvas`**

In `packages/rrweb/src/replay/index.ts`, update the full snapshot rebuild call:

```ts
rebuild(event.data.node, {
  doc: this.iframe.contentDocument,
  afterAppend,
  cache: this.cache,
  mirror: this.mirror,
  UNSAFE_allowUnprotectedRebuild: this.config.UNSAFE_replayCanvas,
});
```

Do not pass the unsafe option from any other call site unless a test proves that call reaches the guarded `rebuild()` path with an unprotected browser target.

- [ ] **Step 4: Run focused rrweb tests**

Run:

```bash
./node_modules/.bin/cross-env PUPPETEER_HEADLESS=true ./node_modules/.bin/turbo run prepublish -F rrweb
./node_modules/.bin/cross-env PUPPETEER_HEADLESS=true ./node_modules/.bin/vitest run packages/rrweb/test/replayer.test.ts -t "UNSAFE_replayCanvas|supported sandbox policy"
```

Expected: focused replayer tests pass.

- [ ] **Step 5: Commit Task 3**

```bash
git add packages/rrweb/src/replay/index.ts packages/rrweb/test/replayer.test.ts
git commit -m "fix(rrweb): mark unsafe canvas rebuilds explicitly"
```

## Task 4: Update Public Docs

**Files:**

- Modify: `packages/rrweb-snapshot/README.md`
- Modify: `docs/sandbox.md`
- Modify: `guide.md`
- Modify: `docs/recipes/canvas.md`

- [ ] **Step 1: Update `packages/rrweb-snapshot/README.md`**

Replace the `#### rebuild` section with:

````md
#### rebuild

`rebuild` will build the DOM according to the taken snapshot.

In browser environments, `rebuild` is a low-level API and requires a document created by `rebuildIntoSandboxedIframe()` or `createSandboxedIframe()` by default. Untrusted replay data must not be rebuilt directly into the top-level `document` or a caller-created iframe document.

For browser usage, prefer `rebuildIntoSandboxedIframe`:

```ts
const { iframe, node } = rebuildIntoSandboxedIframe(snapshot, {
  root: document.body,
  cache,
  mirror,
});
```

If you intentionally accept the script-execution risk, pass `UNSAFE_allowUnprotectedRebuild: true` to `rebuild`.

There are several things will be done during rebuild:

1. Add data-rrid attribute if the Node is an Element.
2. Create some extra DOM node like text node to place inline CSS and some states.
3. Add data-extra-child-index attribute if Node has some extra child DOM.
````

- [ ] **Step 2: Update `docs/sandbox.md`**

After the paragraph ending `implementing this security ourselves.`, add:

```md
`rrweb-snapshot.rebuild()` enforces this boundary in browser environments. Browser rebuilds should use `rebuildIntoSandboxedIframe()` or an iframe created by `createSandboxedIframe()`, which creates an iframe with exactly `sandbox="allow-same-origin"` before rebuilding into it. Direct `rebuild()` calls against caller-created browser documents must explicitly opt into an unprotected rebuild with `UNSAFE_allowUnprotectedRebuild: true`.
```

- [ ] **Step 3: Update `guide.md` option text**

In the options table, replace the `UNSAFE_replayCanvas` description with:

```md
whether to replay the canvas element. **Enabling this adds `allow-scripts` to the replay iframe and opts out of the sandbox script-execution protection, which is unsafe.**
```

- [ ] **Step 4: Update `docs/recipes/canvas.md` warning**

Replace:

```md
**Enable replaying Canvas will remove the sandbox, which may cause a potential security issue.**
```

with:

```md
**Enabling canvas replay adds `allow-scripts` to the replay iframe and opts out of rrweb's sandbox script-execution protection. Only use `UNSAFE_replayCanvas` for replay data whose risk you accept.**
```

- [ ] **Step 5: Run markdown formatting**

Run:

```bash
./node_modules/.bin/prettier --write packages/rrweb-snapshot/README.md docs/sandbox.md guide.md docs/recipes/canvas.md
```

Expected: files format cleanly.

- [ ] **Step 6: Commit Task 4**

```bash
git add packages/rrweb-snapshot/README.md docs/sandbox.md guide.md docs/recipes/canvas.md
git commit -m "docs: explain sandboxed rebuild requirements"
```

## Task 5: Add Changeset And Full Verification

**Files:**

- Create: `.changeset/sandboxed-rebuilds.md`

- [ ] **Step 1: Add changeset**

Create `.changeset/sandboxed-rebuilds.md`:

```md
---
'rrweb-snapshot': major
'rrweb': patch
---

Require browser `rebuild()` calls to target a document created by `rebuildIntoSandboxedIframe()` or `createSandboxedIframe()` by default. Use these helpers for untrusted replay data, or pass `UNSAFE_allowUnprotectedRebuild: true` only when accepting the script-execution risk.

`rrweb` now marks `UNSAFE_replayCanvas` rebuilds as an explicit unsafe path because canvas replay adds script permission to the replay iframe.
```

- [ ] **Step 2: Run snapshot package verification**

Run:

```bash
./node_modules/.bin/tsc --noEmit -p packages/rrweb-snapshot/tsconfig.json
./node_modules/.bin/vitest run packages/rrweb-snapshot/test/rebuild.test.ts
```

Expected: TypeScript and focused tests pass.

- [ ] **Step 3: Run rrweb focused verification**

Run:

```bash
./node_modules/.bin/cross-env PUPPETEER_HEADLESS=true ./node_modules/.bin/turbo run prepublish -F rrweb
./node_modules/.bin/cross-env PUPPETEER_HEADLESS=true ./node_modules/.bin/vitest run packages/rrweb/test/replayer.test.ts -t "UNSAFE_replayCanvas|supported sandbox policy"
```

Expected: build succeeds and focused replayer tests pass.

- [ ] **Step 4: Run formatting on changed files**

Run:

```bash
./node_modules/.bin/prettier --write packages/rrweb-snapshot/src/rebuild.ts packages/rrweb-snapshot/src/index.ts packages/rrweb-snapshot/test/rebuild.test.ts packages/rrweb/src/replay/index.ts packages/rrweb/test/replayer.test.ts packages/rrweb-snapshot/README.md docs/sandbox.md guide.md docs/recipes/canvas.md .changeset/sandboxed-rebuilds.md
```

Expected: formatting completes without changing unrelated files.

- [ ] **Step 5: Check final diff**

Run:

```bash
git diff --check
git status --short
```

Expected: no whitespace errors; status shows only intended files.

- [ ] **Step 6: Commit Task 5**

```bash
git add .changeset/sandboxed-rebuilds.md
git commit -m "chore: add sandboxed rebuild changeset"
```

---

### Superpowers Plans 2026 05 19 Record Tree Shaking (`docs/superpowers/plans/2026-05-19-record-tree-shaking.md`)

# Record Tree-Shaking Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build `@rrweb/record` without bundling replay-only `postcss` code from `rrweb-snapshot`.

**Architecture:** Keep the public package API unchanged. Add a resolver scoped to `packages/record/vite.config.ts` so the record build sees local source module boundaries for `rrweb`, `rrweb-snapshot`, and `rrdom`; mark `rrweb-snapshot` as side-effect-free so Rollup can drop unused rebuild modules.

**Tech Stack:** Yarn workspaces, Turbo, Vite 6, Rollup plugin hooks, Vitest, TypeScript.

---

## File Structure

- Modify `packages/record/test/record.test.ts`: add build-output assertions for `postcss` absence and `dist/record.js` size.
- Modify `packages/rrweb-snapshot/package.json`: add `"sideEffects": false`.
- Modify `packages/record/vite.config.ts`: add an inline exact-bare-import `resolveId` plugin for record's build.
- Modify `packages/rrweb/src/entries/record.ts`: replace default-import-then-export with direct named re-export.

## Task 1: Confirm The Baseline

**Files:**

- Read: `packages/record/dist/record.js`
- Modify: none

- [ ] **Step 1: Build the current record package before implementation changes**

Run:

```bash
yarn workspace @rrweb/record build
```

Expected: build succeeds and writes `packages/record/dist/record.js`. In this workspace, the measured baseline build succeeded before implementation changes.

- [ ] **Step 2: Record the current ESM bundle size**

Run:

```bash
wc -c packages/record/dist/record.js
```

Expected: output is `397373 packages/record/dist/record.js`. This is the baseline byte count for Task 2's test constant.

- [ ] **Step 3: Confirm the current bundle contains `postcss`**

Run:

```bash
rg -n "postcss" packages/record/dist
```

Expected: at least one match in a built JavaScript artifact. In this workspace, `postcss` appears in `packages/record/dist/record.js` and `packages/record/dist/record.umd.min.cjs` before the fix.

## Task 2: Add Failing Bundle Regression Tests

**Files:**

- Modify: `packages/record/test/record.test.ts`

- [ ] **Step 1: Replace the test file with build-output checks**

```
/* Detailed source-code truncated for AI context efficiency. */
```

- [ ] **Step 2: Run the test and verify it fails for the current implementation**

Run:

```bash
yarn workspace @rrweb/record test
```

Expected: the new `postcss` assertion fails, or the size assertion fails, because the implementation change has not been made yet.

## Task 3: Mark rrweb-snapshot As Side-Effect-Free

**Files:**

- Modify: `packages/rrweb-snapshot/package.json`

- [ ] **Step 1: Add the package metadata**

Insert `"sideEffects": false` after the `"files"` array.

```json
  "files": [
    "umd",
    "dist",
    "package.json"
  ],
  "sideEffects": false,
  "author": "yanzhen@smartx.com",
```

- [ ] **Step 2: Validate JSON syntax**

Run:

```bash
node -e "JSON.parse(require('node:fs').readFileSync('packages/rrweb-snapshot/package.json', 'utf8')); console.log('ok')"
```

Expected: prints `ok`.

## Task 4: Add The Record-Local Source Resolver

**Files:**

- Modify: `packages/record/vite.config.ts`

- [ ] **Step 1: Replace the config with an inline exact-import resolver**

```ts
import path from 'path';
import type { Plugin } from 'vite';
import config from '../../vite.config.default';

const sourceEntryByPackageName = new Map([
  ['rrweb', path.resolve(__dirname, '../rrweb/src/entries/record.ts')],
  ['rrweb-snapshot', path.resolve(__dirname, '../rrweb-snapshot/src/index.ts')],
  ['rrdom', path.resolve(__dirname, '../rrdom/src/index.ts')],
]);

function resolveLocalSourceEntries(): Plugin {
  return {
    name: 'resolve-local-source-entries',
    enforce: 'pre',
    resolveId(source) {
      return sourceEntryByPackageName.get(source) || null;
    },
  };
}

export default config(path.resolve(__dirname, 'src/index.ts'), 'rrwebRecord', {
  plugins: [resolveLocalSourceEntries()],
});
```

- [ ] **Step 2: Run TypeScript checking for the record package**

Run:

```bash
yarn workspace @rrweb/record check-types
```

Expected: TypeScript check succeeds.

## Task 5: Make The rrweb Record Entry A Direct Re-Export

**Files:**

- Modify: `packages/rrweb/src/entries/record.ts`

- [ ] **Step 1: Replace default-import-then-export with a direct named re-export**

```ts
export { default as record } from '../record';
```

- [ ] **Step 2: Type-check rrweb**

Run:

```bash
yarn workspace rrweb check-types
```

Expected: TypeScript check succeeds.

## Task 6: Build And Set The Final Size Threshold

**Files:**

- Modify: `packages/record/test/record.test.ts`

- [ ] **Step 1: Build the fixed record package**

Run:

```bash
yarn workspace @rrweb/record build
```

Expected: build succeeds and writes `packages/record/dist/record.js`.

- [ ] **Step 2: Measure the fixed ESM bundle size**

Run:

```bash
wc -c packages/record/dist/record.js
```

Expected: output is at least `204800` bytes smaller than the baseline from Task 1.

- [ ] **Step 3: Update the test comment with measured before and after sizes**

Use the exact byte count printed in Step 2 in a new comment line above the threshold constants. Keep `BASELINE_RECORD_JS_BYTES` equal to `397373`. For example, if Step 2 prints `180245 packages/record/dist/record.js`, the threshold block becomes:

```ts
// Measured before the tree-shaking fix: 397373 bytes.
// Measured after the tree-shaking fix: 180245 bytes.
// The fixed ESM bundle must stay at least 200 KiB smaller.
const BASELINE_RECORD_JS_BYTES = 397373;
const MAX_RECORD_JS_BYTES = BASELINE_RECORD_JS_BYTES - 200 * 1024;
```

## Task 7: Verify The Regression Tests Pass

**Files:**

- Test: `packages/record/test/record.test.ts`

- [ ] **Step 1: Run the focused record tests**

Run:

```bash
yarn workspace @rrweb/record test
```

Expected: all tests in `packages/record/test/record.test.ts` pass.

- [ ] **Step 2: Confirm `postcss` is absent from emitted record JavaScript**

Run:

```bash
rg -n "postcss" packages/record/dist --glob '*.{js,cjs}'
```

Expected: no matches and exit code `1`.

## Task 8: Run Final Verification

**Files:**

- Test: `packages/rrweb-snapshot/package.json`
- Test: `packages/record/vite.config.ts`
- Test: `packages/rrweb/src/entries/record.ts`
- Test: `packages/record/test/record.test.ts`

- [ ] **Step 1: Check rrweb-snapshot types**

Run:

```bash
yarn workspace rrweb-snapshot check-types
```

Expected: succeeds.

- [ ] **Step 2: Rebuild record**

Run:

```bash
yarn workspace @rrweb/record build
```

Expected: succeeds.

- [ ] **Step 3: Run record tests**

Run:

```bash
yarn workspace @rrweb/record test
```

Expected: succeeds.

- [ ] **Step 4: Review the final diff**

Run:

```bash
git diff -- packages/rrweb-snapshot/package.json packages/record/vite.config.ts packages/rrweb/src/entries/record.ts packages/record/test/record.test.ts
```

Expected: diff only contains the planned implementation and test changes.

## Task 9: Commit The Implementation

**Files:**

- Stage: `packages/rrweb-snapshot/package.json`
- Stage: `packages/record/vite.config.ts`
- Stage: `packages/rrweb/src/entries/record.ts`
- Stage: `packages/record/test/record.test.ts`

- [ ] **Step 1: Stage implementation files only**

Run:

```bash
git add packages/rrweb-snapshot/package.json packages/record/vite.config.ts packages/rrweb/src/entries/record.ts packages/record/test/record.test.ts
```

Expected: staged diff excludes the pre-existing `packages/rrweb-player/.svelte-kit/ambient.d.ts` change.

- [ ] **Step 2: Commit**

Run:

```bash
git commit -m "fix: tree-shake postcss from record bundle"
```

Expected: commit succeeds.

---

### Superpowers Plans 2026 07 22 Chrome Extension Release Simplification (`docs/superpowers/plans/2026-07-22-chrome-extension-release-simplification.md`)

# Chrome Extension Release Simplification Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Remove manual dispatch from the privileged release workflow while preserving automatic Chrome Web Store API v2 publishing and documenting dashboard-based recovery.

**Architecture:** Keep `.github/workflows/release.yml` push-only and use the Changesets `published` output as the sole extension build/upload trigger. Put recovery instructions in the existing release-channel documentation, with a workflow comment linking maintainers to it.

**Tech Stack:** GitHub Actions YAML, Changesets, Yarn/Turborepo, `chrome-webstore-upload-cli`, Markdown, Node.js assertions, Prettier, actionlint

---

### Task 1: Simplify the release workflow

**Files:**

- Modify: `.github/workflows/release.yml:3-69`

- [ ] **Step 1: Run the failing workflow regression assertion**

```sh
node -e "const fs=require('fs');const s=fs.readFileSync('.github/workflows/release.yml','utf8');for(const x of ['workflow_dispatch','publish_chrome_extension','github.event.inputs'])if(s.includes(x))throw new Error('manual release logic remains: '+x);if((s.match(/steps\.changesets\.outputs\.published == 'true'/g)||[]).length!==3)throw new Error('automatic publish gates changed');console.log('push-only release assertions passed')"
```

Expected: FAIL with `manual release logic remains: workflow_dispatch`.

- [ ] **Step 2: Remove manual-dispatch behavior and add the recovery pointer**

Make the trigger and relevant steps equivalent to:

```yaml
on:
  push:
    branches:
      - main
      - next

jobs:
  release:
    name: Release
    # Existing setup and Changesets steps remain unchanged.

      # Manual recovery: build and upload through the Chrome Web Store dashboard.
      # See docs/releases/next-channel.md#manual-chrome-extension-recovery.
      - name: Build Chrome Extension
        if: steps.changesets.outputs.published == 'true'
        run: NODE_OPTIONS='--max-old-space-size=4096' DISABLE_WORKER_INLINING=true yarn turbo run prepublish --filter=@rrweb/web-extension

      - name: Publish Chrome Extension
        if: steps.changesets.outputs.published == 'true' && github.ref == 'refs/heads/main'
        run: npx --yes chrome-webstore-upload-cli@4.0.1 --source ./packages/web-extension/dist/chrome.zip

      - name: Publish Next Chrome Extension
        if: steps.changesets.outputs.published == 'true' && github.ref == 'refs/heads/next'
        run: npx --yes chrome-webstore-upload-cli@4.0.1 --source ./packages/web-extension/dist/chrome.zip
```

Preserve the existing environment mappings, including `CWS_PUBLISHER_ID` and `NEXT_CWS_PUBLISHER_ID`.

- [ ] **Step 3: Run workflow validation**

```sh
node -e "const fs=require('fs');const yaml=require('yaml');const s=fs.readFileSync('.github/workflows/release.yml','utf8');yaml.parse(s);for(const x of ['workflow_dispatch','publish_chrome_extension','github.event.inputs'])if(s.includes(x))throw new Error('manual release logic remains: '+x);if((s.match(/steps\.changesets\.outputs\.published == 'true'/g)||[]).length!==3)throw new Error('automatic publish gates changed');for(const x of ['chrome-webstore-upload-cli@4.0.1','CWS_PUBLISHER_ID','NEXT_CWS_PUBLISHER_ID'])if(!s.includes(x))throw new Error('missing '+x);console.log('push-only API v2 workflow assertions passed')"
actionlint -ignore 'runner of .* action is too old' .github/workflows/release.yml
yarn prettier --check .github/workflows/release.yml
git diff --check
```

Expected: all commands exit 0 and the assertion prints `push-only API v2 workflow assertions passed`.

### Task 2: Document dashboard-based manual recovery

**Files:**

- Modify: `docs/releases/next-channel.md:1-110`

- [ ] **Step 1: Run the failing documentation assertion**

```sh
node -e "const fs=require('fs');const s=fs.readFileSync('docs/releases/next-channel.md','utf8');if(/\bmaster\b/.test(s))throw new Error('stale master reference remains');for(const x of ['## Manual Chrome extension recovery','packages/web-extension/dist/chrome.zip','unzip -t packages/web-extension/dist/chrome.zip'])if(!s.includes(x))throw new Error('missing recovery documentation: '+x);console.log('release recovery documentation assertions passed')"
```

Expected: FAIL with `stale master reference remains`.

- [ ] **Step 2: Correct branch names and add exact recovery instructions**

Replace `master` with `main` throughout the document. Add this section after the branch-specific Chrome publication description:

````markdown
## Manual Chrome extension recovery

Use this fallback only when the npm release was recovered separately and the
automatic Chrome step did not publish the matching extension version.

1. Check out the exact `main` or `next` release commit whose extension version
   should be published.
2. Install dependencies and build the archive:

   ```sh
   yarn install --frozen-lockfile
   NODE_OPTIONS='--max-old-space-size=4096' \
     DISABLE_WORKER_INLINING=true \
     yarn turbo run prepublish --filter=@rrweb/web-extension
   unzip -t packages/web-extension/dist/chrome.zip
   ```

3. Confirm that `packages/web-extension/dist/chrome/manifest.json` contains the
   intended version.
4. In the Chrome Web Store developer dashboard, select the production listing
   for `main` or the prerelease listing for `next`, upload
   `packages/web-extension/dist/chrome.zip`, and submit it for review.

GitHub Actions secrets cannot be read back for local CLI use, so dashboard upload
is the supported manual recovery path. Keep production and prerelease listings
separate.
````

Add `CWS_PUBLISHER_ID` and `NEXT_CWS_PUBLISHER_ID` to the preceding secret lists.

- [ ] **Step 3: Run documentation validation**

```sh
node -e "const fs=require('fs');const s=fs.readFileSync('docs/releases/next-channel.md','utf8');if(/\bmaster\b/.test(s))throw new Error('stale master reference remains');for(const x of ['## Manual Chrome extension recovery','packages/web-extension/dist/chrome.zip','unzip -t packages/web-extension/dist/chrome.zip','CWS_PUBLISHER_ID','NEXT_CWS_PUBLISHER_ID'])if(!s.includes(x))throw new Error('missing recovery documentation: '+x);console.log('release recovery documentation assertions passed')"
yarn prettier --check docs/releases/next-channel.md
git diff --check
```

Expected: all commands exit 0 and the assertion prints `release recovery documentation assertions passed`.

- [ ] **Step 4: Verify the documented build commands**

```sh
NODE_OPTIONS='--max-old-space-size=4096' DISABLE_WORKER_INLINING=true yarn turbo run prepublish --filter=@rrweb/web-extension
unzip -t packages/web-extension/dist/chrome.zip
node -e "const m=require('./packages/web-extension/dist/chrome/manifest.json');if(!m.version)throw new Error('manifest version missing');console.log('Chrome extension version:',m.version)"
```

Expected: build exits 0, `unzip` reports no errors, and the manifest assertion prints a version.

### Task 3: Commit, update the pull request, and verify remotely

**Files:**

- Modify: GitHub pull request `rrweb-io/rrweb#1908` description

- [ ] **Step 1: Run final local verification**

Repeat Task 1 Step 3 and Task 2 Steps 3-4, then run:

```sh
git status --short
git diff --check
```

Expected: only the workflow, release documentation, and plan are changed; every command exits 0.

- [ ] **Step 2: Commit and push the implementation**

```sh
git add .github/workflows/release.yml docs/releases/next-channel.md docs/superpowers/plans/2026-07-22-chrome-extension-release-simplification.md
git commit -m "Simplify Chrome extension release recovery"
git push
```

Expected: the commit and push succeed with only the listed files.

- [ ] **Step 3: Update the PR description**

Use `gh pr edit 1908 --repo rrweb-io/rrweb --body-file -` with a body that states:

```markdown
## Summary

- migrate Chrome Web Store publishing to pinned API v2 tooling
- preserve automatic stable and `next` publishing after successful npm releases
- document dashboard-based recovery without broadening the npm release workflow
- correct stale `master` references in release documentation

## Required repository setup

Production uses `CWS_PUBLISHER_ID`; `next` uses `NEXT_CWS_PUBLISHER_ID` with its
existing `NEXT_CWS_*` credentials.

## Validation

- workflow assertions, YAML parsing, Prettier, actionlint, and diff checks pass
- extension build and ZIP integrity checks pass
```

Expected: `gh pr edit` prints the PR URL.

- [ ] **Step 4: Verify CI and review state**

```sh
gh pr checks 1908 --repo rrweb-io/rrweb --watch --interval 10
python3 /Users/justin/.codex/plugins/cache/openai-curated-remote/github/0.1.8-2841cf9749ae/skills/gh-address-comments/scripts/fetch_comments.py --repo rrweb-io/rrweb --pr 1908
```

Expected: all required checks pass. Do not reply to or resolve review threads without explicit authorization.

---

### Superpowers Plans 2026 09 08 Relative Ci Monorepo (`docs/superpowers/plans/2026-09-08-relative-ci-monorepo.md`)

# RelativeCI Monorepo Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Replace rrweb's custom bundle-size comparison with package-level RelativeCI reporting that keeps all API keys out of pull request workflows.

**Architecture:** The shared Vite configuration emits one webpack-compatible stats file for each tracked package when CI enables `RELATIVE_CI_STATS`. The file uses one canonical ESM chunk/module graph and the complete set of top-level distributable assets. The unprivileged ESLint workflow uploads all stats in one `relative-ci-stats` artifact. A successful `workflow_run` starts a static 19-entry matrix. Each job reads its stats path from the shared archive and sends it to the matching RelativeCI project with one package key.

**Tech Stack:** GitHub Actions, Vite 6, `rollup-plugin-webpack-stats` 3.1.3, `actions/upload-artifact` v4, `relative-ci/agent-action` v3, Yarn 1

---

### Task 1: Emit package-level RelativeCI stats

**Files:**

- Modify: `package.json`
- Modify: `yarn.lock`
- Modify: `turbo.json`
- Modify: `vite.config.default.ts`
- Modify: `packages/packer/vite.config.ts`

- [ ] **Step 1: Add the Vite stats dependency**

Run:

```bash
yarn add --dev --exact -W rollup-plugin-webpack-stats@3.1.3
```

Expected: `package.json` contains `"rollup-plugin-webpack-stats": "3.1.3"` and Yarn updates `yarn.lock`.

- [ ] **Step 2: Add opt-in stats generation to the shared Vite configuration**

Import `bundleToWebpackStats` from the package's public `/transform` export and extend the existing options type with `bundleStats?: boolean`. Default that option to `true`. Add a final opt-in plugin that captures the ESM output as the canonical chunk/module graph. In `closeBundle`, replace its asset list with the sizes of every top-level `.js`, `.cjs`, `.mjs`, and `.css` file in the output directory, then write `webpack-stats.json`. Do not merge the ESM and CommonJS module graphs: RelativeCI treats a module's chunk count as its instance count, so merging equivalent formats would create false duplicate-module metrics.

This keeps local builds unchanged and matches the old collector's top-level distributable selection while preserving valid RelativeCI module metrics.

- [ ] **Step 3: Pass the opt-in variable through Turbo**

Add `RELATIVE_CI_STATS` to the `prepublish` task's `env` array in `turbo.json`. This passes the variable to Vite and includes it in Turbo's cache key.

- [ ] **Step 4: Preserve the packer exclusion**

Pass `bundleStats: false` in `packages/packer/vite.config.ts`:

```ts
  {
    bundleStats: false,
  },
```

- [ ] **Step 5: Build one representative package with stats enabled**

Run:

```bash
RELATIVE_CI_STATS=true yarn workspace @rrweb/record vite build
jq -e '.assets and .chunks and .modules' packages/record/dist/webpack-stats.json
```

Expected: Vite exits 0 and `jq` exits 0 for the generated webpack-compatible stats object. Asset names and byte sizes match all tracked top-level files in `packages/record/dist`, while chunks and modules describe only `record.js`.

- [ ] **Step 6: Check formatting and types**

Run:

```bash
yarn prettier --check package.json turbo.json vite.config.default.ts packages/packer/vite.config.ts
yarn tsc --noEmit --skipLibCheck --allowSyntheticDefaultImports --moduleResolution node --module esnext --target es2020 vite.config.default.ts
```

Expected: both commands exit 0.

### Task 2: Upload shared stats from the unprivileged build

**Files:**

- Modify: `.github/workflows/eslint-check.yml`

- [ ] **Step 1: Enable stats during the existing package build**

Add `RELATIVE_CI_STATS: 'true'` to the `Build Packages` step's environment.

- [ ] **Step 2: Replace the custom PR size upload and base build**

Delete the `Measure PR bundle sizes` and `Upload PR bundle sizes` steps. Delete the complete `bundle_size_build` job.

- [ ] **Step 3: Upload one shared stats artifact**

After linting, add one `actions/upload-artifact` step pinned to the verified v4 commit `ea165f8d65b6e75b540449e92b4886f43607fa02`. Upload the stats glob as `relative-ci-stats` and fail if it matches no files. Keep RelativeCI keys out of this workflow.

The complete stats upload step is:

```yaml
- name: Upload bundle stats
  uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
  with:
    name: relative-ci-stats
    path: packages/**/dist/webpack-stats.json
    if-no-files-found: error
```

The archive omits the `packages/` prefix and preserves directories after the first wildcard. A top-level entry is `record/dist/webpack-stats.json`; a plugin entry is `plugins/rrweb-plugin-console-record/dist/webpack-stats.json`. Confirm those paths in the first uploaded archive. Packer continues to opt out of stats generation.

- [ ] **Step 4: Validate workflow formatting and artifact coverage**

Run:

```bash
yarn prettier --check .github/workflows/eslint-check.yml
rg -c 'name: relative-ci-stats' .github/workflows/eslint-check.yml
```

Expected: formatting passes and there is exactly one shared stats upload. The workflow must contain no `relative-ci/agent-upload-artifact-action` steps or RelativeCI keys.

### Task 3: Submit artifacts from the privileged workflow

**Files:**

- Modify: `.github/workflows/pr-checks-privileged.yml`

- [ ] **Step 1: Replace the bundle comment job with a RelativeCI matrix**

Rename the `comment` job to `relative_ci`. Run it only when the triggering ESLint workflow succeeded and its event was `push` or `pull_request`. Grant only `actions: read`.

Use a static matrix with one entry per tracked package, containing `stats`, `name`, and `secret`. Preserve the existing 19 package-to-secret mappings. The stats path is relative to the archive root, for example:

```yaml
- stats: record/dist/webpack-stats.json
  name: '@rrweb/record'
  secret: RELATIVE_CI_KEY_RECORD
```

Keep this matrix in the trusted workflow. Do not build it from artifacts or PR metadata. Extra stats files in the shared artifact must not select more projects or secrets.

- [ ] **Step 2: Add the pinned RelativeCI agent step**

The matrix job contains no checkout, shell step, installation, or executable configuration. Each job downloads the shared archive and reads its own JSON entry using commit `fcf45416581928e8dd62eded78ce98c78e5149f8`:

```yaml
- name: Send ${{ matrix.name }} bundle stats to RelativeCI
  uses: relative-ci/agent-action@fcf45416581928e8dd62eded78ce98c78e5149f8 # v3
  with:
    artifactName: relative-ci-stats
    webpackStatsFile: ${{ matrix.stats }}
    key: ${{ secrets[matrix.secret] }}
    token: ${{ secrets.GITHUB_TOKEN }}
```

- [ ] **Step 3: Preserve ESLint annotation**

Leave the existing `annotate` job and its pull-request-only condition unchanged.

- [ ] **Step 4: Validate workflow formatting and matrix coverage**

Run:

```bash
yarn prettier --check .github/workflows/pr-checks-privileged.yml
rg -c '^\s+- stats:' .github/workflows/pr-checks-privileged.yml
```

Expected: formatting passes and the count is `19`. Verify that stats paths and secret names are unique, each display name matches its package manifest, and the paths match the shared archive layout. Missing package stats must fail the corresponding agent job.

### Task 4: Remove the legacy bundle-size implementation

**Files:**

- Delete: `.github/scripts/measure-bundle-sizes.js`
- Delete: `.github/scripts/render-bundle-size-comment.js`

- [ ] **Step 1: Delete both custom scripts**

Remove the measurement and Markdown-rendering scripts after their workflow callers are gone.

- [ ] **Step 2: Prove the old implementation has no live references**

Run:

```bash
rg -n 'measure-bundle-sizes|render-bundle-size-comment|bundle-size-data|pr-sizes|base-sizes|sticky-pull-request-comment' .github
```

Expected: no matches.

### Task 5: Verify the complete integration

**Files:**

- Verify all files changed in Tasks 1 through 4

- [ ] **Step 1: Parse both workflows as YAML 1.2**

Use the repository's available YAML parser to load `.github/workflows/eslint-check.yml` and `.github/workflows/pr-checks-privileged.yml`.

Expected: both files parse without errors and retain a top-level `on` key. Run actionlint on both workflows, ignoring only the existing outdated-version warning for `actions/setup-node@v3`:

```bash
actionlint -ignore 'the runner of "actions/setup-node@v3" action is too old' .github/workflows/eslint-check.yml .github/workflows/pr-checks-privileged.yml
```

Expected: no other actionlint findings.

- [ ] **Step 2: Run focused formatting checks**

Run:

```bash
yarn prettier --check package.json turbo.json vite.config.default.ts packages/packer/vite.config.ts .github/workflows/eslint-check.yml .github/workflows/pr-checks-privileged.yml docs/superpowers/specs/2026-09-08-relative-ci-monorepo-design.md docs/superpowers/plans/2026-09-08-relative-ci-monorepo.md
```

Expected: Prettier reports every file as formatted.

- [ ] **Step 3: Inspect the security boundary and diff**

Run:

```bash
git diff --check
git diff --stat HEAD~1
git status --short
```

Confirm that the PR workflow references no `RELATIVE_CI_KEY`, the privileged RelativeCI job contains only the pinned agent step with `actions: read`, and new third-party actions use full commit SHAs. Check that successful `pull_request` and `push` builds still trigger submission so the default branch supplies baselines. Verify that the shared artifact name agrees between workflows and `webpackStatsFile` comes only from the static matrix. Do not run or update unrelated visual snapshots for this workflow refactor.

- [ ] **Step 4: Commit the implementation**

Run:

```bash
git add package.json yarn.lock turbo.json vite.config.default.ts packages/packer/vite.config.ts .github/workflows/eslint-check.yml .github/workflows/pr-checks-privileged.yml .github/scripts/measure-bundle-sizes.js .github/scripts/render-bundle-size-comment.js docs/superpowers/specs/2026-09-08-relative-ci-monorepo-design.md docs/superpowers/plans/2026-09-08-relative-ci-monorepo.md
git commit -m "ci: replace bundle size checks with RelativeCI"
```

Expected: Git creates one implementation commit containing the verified integration.

---

### Superpowers Specs 2026 05 18 Sandboxed Rebuild Design (`docs/superpowers/specs/2026-05-18-sandboxed-rebuild-design.md`)

# Sandboxed Rebuild Design

## Context

GitHub issue rrweb-io/rrweb#1817 reports that direct use of `rrweb-snapshot.rebuild()` can execute crafted replay content when callers rebuild into an unsandboxed browser document. The project already documents that filtering script-like behavior is incomplete and that replay should rely on iframe sandboxing as the browser-enforced security boundary.

The current `rrweb` replayer already creates a top-level iframe with `sandbox="allow-same-origin"` before rebuilding snapshots. The exposed `rrweb-snapshot.rebuild()` API is lower-level and can still target any caller-provided `Document`, including the top-level page document. That direct browser use is the gap this design closes.

## Goals

- Make unprotected direct browser rebuild fail by default.
- Preserve a deliberate escape hatch for callers that explicitly accept the script-execution risk.
- Provide a first-class safe API for users who want to rebuild snapshots directly.
- Keep the fix structural: use browser sandboxing instead of expanding attribute or URL sanitization.
- Preserve `UNSAFE_replayCanvas` as an explicit unsafe mode.

## Non-Goals

- Do not attempt to sanitize every executable HTML, SVG, CSS, URL, or event-handler vector.
- Do not redesign replay around a separate-origin or message-passing renderer.
- Do not remove `UNSAFE_replayCanvas` in this change.

## API Boundary

`rrweb-snapshot.rebuild()` will validate browser `Document` targets before mutating them. In a real browser document, protected rebuild is allowed only for a document created by `rebuildIntoSandboxedIframe()` or `createSandboxedIframe()`, whose iframe sandbox token set is exactly `allow-same-origin`.

This guard belongs to `rebuild()` only. Lower-level node builders such as `buildNodeWithSN()` do not own the browser sandbox policy in this change.

If validation fails, `rebuild()` throws a clear error:

```text
rrweb-snapshot.rebuild() cannot rebuild into an unprotected browser document. Use rebuildIntoSandboxedIframe() or set UNSAFE_allowUnprotectedRebuild: true only when you accept the script-execution risk.
```

The API will accept an explicit unsafe option for callers that accept the risk:

```ts
rebuild(node, {
  doc,
  cache,
  mirror,
  UNSAFE_allowUnprotectedRebuild: true,
});
```

This option makes unsafe usage visible in code review and keeps existing tests or tooling unblocked when they intentionally accept the risk.

## Safe Helper

Add a convenience API to `rrweb-snapshot`:

```ts
const { iframe, node } = rebuildIntoSandboxedIframe(snapshot, {
  root: document.body,
  cache,
  mirror,
});
```

The helper uses `createSandboxedIframe()` to create an iframe, apply `sandbox="allow-same-origin"` before appending it, append it to the configured root, then call guarded `rebuild()` with the iframe document. It returns both the iframe and the rebuilt root node so callers can size or manage the iframe while using the safe path by default. The `node` return value is `Node | null`, matching `rebuild()` semantics.

The helper requires an explicit `root` element and does not default to `document.body`. It always creates a fresh iframe rather than accepting a caller-provided iframe, because the sandbox must be set before the iframe is appended or used. It stays minimal in the first implementation and does not mirror replayer styling, scrolling, pointer-event, or injected-style behavior. Additional helper flags can be added later when a concrete use case needs them. Helper options must not allow callers to override the enforced sandbox token set.

## Replayer Behavior

The `rrweb` replayer keeps its current default iframe model. `setupDom()` uses `createSandboxedIframe()` for the top-level replay iframe, disables interaction, and then rebuilds snapshots into that iframe document. This satisfies the new `rebuild()` guard without using the unsafe escape hatch.

`UNSAFE_replayCanvas` remains available. In that mode the replay iframe may include `allow-scripts`, so it no longer satisfies the safe rebuild guard. Replayer calls into `rebuild()` must pass the explicit unsafe option when this mode is enabled. Documentation and tests must describe `UNSAFE_replayCanvas` as opting out of the script-execution protection.

Nested iframe sandbox attributes are not part of this issue fix. For normal rrweb replay, the top-level replay iframe's sandbox constrains browsing contexts nested inside it, so an inner serialized iframe cannot re-enable script execution that the ancestor sandbox removed. This change enforces the rebuild target document boundary rather than rewriting every nested iframe sandbox attribute.

## Error Handling And Compatibility

The guard applies when the target document has a live `defaultView`. If `doc.defaultView` is absent, the browser sandbox check does not run. If `doc.defaultView` exists, the target is allowed only when it was created by `rebuildIntoSandboxedIframe()` or `createSandboxedIframe()` and `doc.defaultView.frameElement` is an iframe whose sandbox token set is exactly `allow-same-origin`, unless `UNSAFE_allowUnprotectedRebuild: true` is set. Test environments that model browser documents can use the explicit unsafe option where they intentionally rebuild fixtures into the top-level document.

This is an intentional breaking change for unprotected direct browser use of `rrweb-snapshot.rebuild()`. The migration paths are:

- use `rebuildIntoSandboxedIframe()` or `createSandboxedIframe()` for untrusted replay content;
- set `UNSAFE_allowUnprotectedRebuild: true` only when you accept the script-execution risk.

## Testing

Add `rrweb-snapshot` tests for:

- `rebuild()` throws when targeting the top-level browser `document`;
- `rebuild()` throws for caller-supplied iframe documents, even when the iframe currently has `sandbox="allow-same-origin"`;
- `rebuild()` throws for any sandbox token set other than exactly `allow-same-origin`;
- `UNSAFE_allowUnprotectedRebuild: true` allows unprotected rebuild for callers that explicitly accept the risk;
- `rebuildIntoSandboxedIframe()` creates the iframe, applies sandbox before rebuild, and returns the rebuilt node;
- nested iframes cannot relax the target document's ancestor sandbox in normal replay.

Add `rrweb` replayer tests for:

- normal replay still rebuilds without the unsafe opt-out;
- `UNSAFE_replayCanvas` still works while using the explicit unsafe path;
- replay iframe snapshots expose the expected sandbox attributes.

These tests prove the browser-enforced boundary rather than enumerate every possible script vector.

## Documentation

Update:

- `docs/sandbox.md`;
- `guide.md`;
- `packages/rrweb-snapshot/README.md`;
- canvas replay docs that mention `UNSAFE_replayCanvas`.

The docs will say that `rebuild()` is a low-level API and that untrusted browser replay must use a sandboxed iframe. They will also state that `UNSAFE_replayCanvas` disables the script-execution protection.

## Release Note

Add a changeset for the public behavior change. The changeset should describe this as a breaking security-hardening change for `rrweb-snapshot`: browser rebuilds now require a sandboxed iframe target unless callers explicitly opt into an unprotected rebuild.

---

### Superpowers Specs 2026 05 19 Record Tree Shaking Design (`docs/superpowers/specs/2026-05-19-record-tree-shaking-design.md`)

# Record Tree-Shaking Design

## Context

`rrweb-snapshot` contains both record-time snapshot code and replay-time rebuild code. The rebuild path imports `postcss`, which is large and unnecessary for `@rrweb/record` consumers. Because `rrweb-snapshot` currently publishes a single barrel bundle, consumers that import record-only APIs can still end up with replay-only code in their output.

The goal is to reimplement the behavior targeted by rrweb PR #1784 without copying that implementation. The implementation should use the current repository structure and keep the change low risk.

## Goals

- Build `@rrweb/record` without bundling `postcss`.
- Preserve the current public API for `rrweb`, `rrweb-snapshot`, `rrdom`, and `@rrweb/record`.
- Keep the implementation narrow and easy to review.
- Add tests that catch both the direct dependency regression and the bundle-size impact.

## Non-Goals

- Do not add new public `rrweb-snapshot` subpath exports.
- Do not split `rrweb-snapshot` into separate published snapshot and rebuild packages.
- Do not refactor snapshot/rebuild utilities unless the narrow build fix cannot pass verification without it.
- Do not inspect or copy the PR #1784 implementation.

## Proposed Approach

Use a source-boundary build fix for `packages/record`.

1. Mark `rrweb-snapshot` as side-effect-free in `packages/rrweb-snapshot/package.json`.
2. Add a Vite/Rollup `resolveId` plugin to `packages/record/vite.config.ts`.
3. During the record package build, redirect bare imports of `rrweb`, `rrweb-snapshot`, and `rrdom` to their local source entry files instead of their pre-built package entrypoints.
4. Change `packages/rrweb/src/entries/record.ts` to directly re-export the named `record` export from the record module.
5. Extend `packages/record/test/record.test.ts` so it verifies the built record output does not contain `postcss` and records a significant file-size drop signal.

## Architecture

The public packages keep their existing import paths. The special behavior is limited to the `@rrweb/record` build config.

The record package currently imports `record` through `rrweb`. That package import can resolve to the pre-built `rrweb` bundle, which hides source module boundaries from Rollup. The custom resolver restores those boundaries by mapping:

- `rrweb` to `packages/rrweb/src/entries/record.ts`
- `rrweb-snapshot` to `packages/rrweb-snapshot/src/index.ts`
- `rrdom` to `packages/rrdom/src/index.ts`

The resolver should match only exact bare imports. It should not rewrite subpath imports such as `rrweb/foo` unless a future change explicitly adds that case.

The resolver should live inline in `packages/record/vite.config.ts`. It is a record-package build concern, not a shared monorepo helper.

Once Rollup sees source modules, `"sideEffects": false` on `rrweb-snapshot` allows unused rebuild modules to be removed when record consumers do not use rebuild exports. This should use plain `"sideEffects": false`, not a narrower allowlist. PR #1834's `rebuild.ts` changes do not add import-time side effects; its DOM work and sandbox checks happen only when exported functions are called.

The `rrweb` record entry should use a direct named re-export. This gives Rollup a simpler export graph than a default import followed by a re-export.

## Testing

Keep the existing smoke test that `record` is a function.

Add a build-output regression test for `@rrweb/record`:

- Inspect emitted `.js` and `.cjs` files in `packages/record/dist`.
- Assert no emitted `.js` or `.cjs` file contains the string `postcss`.
- Use `packages/record/dist/record.js` as the canonical size signal.
- Measure the current `dist/record.js` size locally before changing implementation files, then assert the fixed `dist/record.js` size is at least 200KB smaller than that baseline.

The implementation should encode that baseline-derived threshold as a constant in the test, with a comment documenting the measured before/after sizes. The test should compare durable built artifacts, not transient visualizer output or source maps. It should not run the build itself; if `dist/record.js` is missing, it should fail with a clear message telling the developer to run `yarn workspace @rrweb/record build` first.

## Verification

Run focused verification after implementation:

```sh
yarn workspace rrweb-snapshot check-types
yarn workspace @rrweb/record build
yarn workspace @rrweb/record test
```

## Risks

The resolver must be scoped to the record build. Applying it globally could change other package builds unexpectedly.

The file-size threshold must be strict enough to catch regressions but not so strict that unrelated output formatting or dependency updates cause noise. It should focus on the main JavaScript bundle and leave source maps out of the assertion.

Marking `rrweb-snapshot` as side-effect-free assumes its module-level code does not intentionally perform observable side effects on import. That matches the intended package usage for tree-shaking and remains compatible with PR #1834's `rebuild.ts` changes, but verification should include existing snapshot and record tests.

---

### Superpowers Specs 2026 07 22 Chrome Extension Release Simplification Design (`docs/superpowers/specs/2026-07-22-chrome-extension-release-simplification-design.md`)

# Chrome Extension Release Simplification

## Objective

Keep Chrome extension publishing automatic during normal npm releases while
removing the manual-dispatch logic that complicates and broadens the privileged
release workflow. Preserve a documented manual recovery path through the Chrome
Web Store dashboard.

## Workflow design

`.github/workflows/release.yml` will return to a push-only workflow for `main`
and `next`. The release job will run Changesets normally. Chrome extension build
and upload steps will run only when `steps.changesets.outputs.published` is
`true`.

The Chrome upload will continue using the pinned
`chrome-webstore-upload-cli@4.0.1` API v2 client and the publisher ID secrets
introduced by this pull request. Stable releases will use the production listing
and `CWS_*` secrets; `next` releases will use the prerelease listing and
`NEXT_CWS_*` secrets.

The following manual-dispatch behavior will be removed:

- the `workflow_dispatch` trigger and `publish_chrome_extension` input;
- the job-level ref guard added for arbitrary manual refs;
- event-name and event-input branches in Chrome build and publish conditions.

## Manual recovery

A short comment beside the Chrome build step will identify the manual fallback
and link to `docs/releases/next-channel.md`. That document will explain how to:

1. check out the intended release commit;
2. install dependencies and build `packages/web-extension/dist/chrome.zip`;
3. verify the archive before upload;
4. upload the archive to the appropriate Chrome Web Store listing through the
   developer dashboard.

The documentation will distinguish stable (`main`) from prerelease (`next`),
list the relevant publisher ID secrets, and replace stale `master` branch
references with `main`.

This fallback intentionally uses the dashboard instead of local API credentials,
because GitHub Actions secrets cannot be read back for local use.

## Pull request maintenance

The pull request description will be updated to describe API v2 migration,
automatic publishing, and the documented dashboard fallback. Existing review
threads made obsolete by removing manual dispatch will not be replied to or
resolved without separate authorization.

## Validation

Before pushing the implementation:

- parse the workflow as YAML;
- assert that `workflow_dispatch`, `publish_chrome_extension`, and manual event
  conditions are absent;
- assert that automatic stable and `next` publish conditions remain;
- assert that the API v2 CLI and publisher ID variables remain;
- run Prettier, `actionlint`, and `git diff --check`;
- verify the documented build command produces a valid Chrome ZIP archive.

After pushing, all required pull request checks must pass. No Changeset is
required because the change affects release infrastructure and documentation,
not a published package.

---

### Superpowers Specs 2026 09 08 Relative Ci Monorepo Design (`docs/superpowers/specs/2026-09-08-relative-ci-monorepo-design.md`)

# RelativeCI monorepo integration

## Goal

Replace rrweb's custom bundle-size comparison and privileged PR-comment code with RelativeCI's documented `workflow_run` integration. Preserve package-level bundle reporting without exposing repository secrets to pull request code.

## Scope

RelativeCI will track every Vite-built package covered by the existing bundle-size script, except `@rrweb/packer`, which the existing script already excludes:

- `@rrweb/all`
- `@rrweb/browser-client`
- `@rrweb/record`
- `@rrweb/replay`
- `rrdom`
- `rrdom-nodejs`
- `rrweb-player`
- `rrweb-snapshot`
- `rrweb`
- `@rrweb/types`
- `@rrweb/utils`
- `@rrweb/rrweb-plugin-canvas-webrtc-record`
- `@rrweb/rrweb-plugin-canvas-webrtc-replay`
- `@rrweb/rrweb-plugin-console-record`
- `@rrweb/rrweb-plugin-console-replay`
- `@rrweb/rrweb-plugin-network-record`
- `@rrweb/rrweb-plugin-network-replay`
- `@rrweb/rrweb-plugin-sequential-id-record`
- `@rrweb/rrweb-plugin-sequential-id-replay`

`rrvideo` and `@rrweb/web-extension` remain outside bundle reporting because the existing collector did not include their outputs.

## Build and artifact flow

Bundle reporting runs independently of lint and tests. `bundle-stats.yml` builds and uploads stats on pushes and pull requests; `relative-ci-upload.yml` consumes successful `Bundle Stats` runs. The ESLint workflow keeps its existing build and package-preview publishing but no longer generates or uploads bundle stats. This adds a dedicated build so lint, preview publishing, and test failures cannot block bundle feedback.

The shared Vite configuration will use `rollup-plugin-webpack-stats` when `RELATIVE_CI_STATS=true`. Each package build writes its own `webpack-stats.json`. The file uses the ESM output as the canonical chunk and module graph, avoiding false duplicate-module results from combining equivalent ESM and CommonJS graphs. Its asset list is finalized after all output hooks and includes every top-level `.js`, `.cjs`, `.mjs`, and `.css` distributable, including generated UMD, minified, and CSS files. The Bundle Stats workflow sets that environment variable for the build. Turbo will pass the variable to `prepublish` tasks and include it in their cache keys.

After the build, one pinned `actions/upload-artifact` step uploads `packages/**/dist/webpack-stats.json` as `relative-ci-stats`. The upload fails if no stats files exist. This workflow receives no RelativeCI keys and continues to run for forked pull requests.

The privileged `workflow_run` workflow replaces the custom base build, comparison, comment renderer, and sticky-comment action with a static 19-entry matrix. Each entry contains the stats path inside the shared artifact, a display name, and the existing repository secret name. Every job passes `artifactName: relative-ci-stats` and its matrix stats path as `webpackStatsFile` to the pinned `relative-ci/agent-action`. The action reads that JSON entry from the triggering run's archive and sends it to the matching RelativeCI project.

The upload glob preserves directories after its first wildcard and omits the `packages/` prefix. For example, the archive contains `record/dist/webpack-stats.json` and `plugins/rrweb-plugin-console-record/dist/webpack-stats.json`. The matrix is the only operational list of tracked packages and key mappings. Extra stats files in the archive do not add projects. A missing tracked package file fails its submission job.

Each of the 19 submission jobs downloads the shared archive. This adds download traffic but avoids upload fan-out jobs, generated YAML, and executable configuration in the privileged workflow.

## Security boundaries

- Pull request code never receives RelativeCI keys.
- The privileged job never checks out or executes pull request code.
- The privileged job runs only after a successful pull request or push build, so RelativeCI receives both comparisons and default-branch baselines.
- Each matrix job receives one package key.
- Stats paths and secret names come only from the static trusted workflow, never from an artifact or pull request metadata.
- The job's `GITHUB_TOKEN` has only `actions: read` permission.
- New third-party actions are pinned to full commit SHAs.
- The bundle-stats artifact remains attacker-controlled input. The pinned RelativeCI action is the only component that parses it in the privileged job.

Repository administrators must add one RelativeCI project key per package, using the secret names encoded in the workflow matrix.

## Removed code

Delete the custom measurement and Markdown-rendering scripts. Remove the base-branch bundle build, intermediate size artifacts, PR-number lookup, and sticky bundle-size comment. Keep the unrelated ESLint report and annotation flow.

## Validation

Run a representative Vite build with `RELATIVE_CI_STATS=true` and verify that it creates a parseable stats file whose asset names and sizes match every tracked top-level distributable. Verify that its chunk and module graph represents only the canonical ESM output. Parse both workflows as YAML 1.2, run Prettier and actionlint, scan for references to the deleted bundle-size implementation, and inspect the final diff. Ignore only actionlint's known outdated-version warning for the existing `actions/setup-node@v3` step.

Verify that there is one shared stats upload, exactly 19 unique matrix paths and secret names, and that every path matches its package and the archive layout. Check both a top-level package and a nested plugin. Confirm the successful pull request and push conditions, the absence of RelativeCI keys in the build workflow, and the privileged job's `actions: read` permission and single pinned action step. Confirm the archive paths in the first GitHub Actions run; local checks do not submit to RelativeCI.

---

--- METRICS ---
- Files Extracted: 79
- Estimated Token Budget: ~113576 tokens
- Canonical Reference: https://codewiki.google/github.com/rrweb-io/rrweb
