Chore: Migrate build to vite (#1033)

* Chore: Add move most types from rrweb to @rrweb/types package

* Split off type imports

* Split off type import to its own line

* Get vite to generate type definitions

* Apply formatting changes

* noEmit not allowed in tsconfig, moved it to build step

* Migrate rrdom-nodejs build to vite

* Apply formatting changes

* Migrate rrweb-snapshot to vite

* Unify configs

* Chore: Migrate rrdom to vite

Turns out what we where doing by overwriting `public textContent: string | undefined` as a getter in a subclass is something that isn't allowed in typescript. Because we where using `// @ts-ignore`  to hide this error our bundler chose to allow the overwrite. Vite choses to disallow the overwrite making all subclasses' `textContent` undefined.
To mitigate this we're using an abstract class, which does allow sub classes to decide if they wan't to use getters or not.

* Chore: Migrate rrweb to vite WIP

* build:browser was removed (for now)

* BREAKING: moved rrweb-plugin-console to its own npm module

This removes console from rrweb-all.js

* Support cjs files in startServer

* Move canvas-webrtc plugin to its own package

* Chore: move sequential-id plugin to its own package

* Chore: Configure rrweb's vite bundling

* `Id` had lowercase `d` before, making it lowercase again

* Test: Move console tests to their own package

* remove unused utils from rrdom

* pull in latest version of master
something when wrong earlier when resolving merge conflicts, this should be correct

* Fix type casting issue in diff.ts

* Fix typo

* Fix duplicate entries in package.json and tsconfig.json

* Apply formatting changes

* Update dependencies in package.json files

* Update dependencies to use Vite 5.2.8 in package.json files

* Get tests passing for rrdom

`apply virtual style rules to node` tests need to be moved to rrweb to avoid circular dependencies

* Fix image loading issue in integration tests

* Move pack/unpack to its own @rrweb/packer module

* Get tests to work in rrdom-nodejs

* Port tests in rrweb-snapshot to vitest and fix them

* Fix tests for rrweb-plugin-console-record

* Add @rrweb/all package

* Fix publint and attw errors for rrdom and @rrweb/types

* Use shared vitest.config.ts in rrweb-snapshot package

* Fix publint and attw issues for rrweb-snapshot

* Export `ReplayPlugin` type directly from rrweb

* Fix publint and attw issues for packages

* Fix publint & attw issue.

I was bumping into this issue: 3729bc2a3c/docs/problems/NoResolution.md

And had to choose one of these three methods described here:
https://github.com/andrewbranch/example-subpath-exports-ts-compat?tab=readme-ov-file#typescript-friendly-strategies-for-packagejson-subpath-exports-compatibility
And I ended up going for the method described here:
1ffe3425b0/examples/node_modules/package-json-redirects (package-json-redirects)

The redirect method seemed the least invasive and most effective.

* Fix publint & attw issue.

I was bumping into this issue: 3729bc2a3c/docs/problems/NoResolution.md

And had to choose one of these three methods described here:
https://github.com/andrewbranch/example-subpath-exports-ts-compat?tab=readme-ov-file#typescript-friendly-strategies-for-packagejson-subpath-exports-compatibility
And I ended up going for the method described here:
1ffe3425b0/examples/node_modules/package-json-redirects (package-json-redirects)

The redirect method seemed the least invasive and most effective.

* move some rrdom tests that require rrweb to rrweb package

* Use pre-jest 29 syntax for snapshotting

* get rrweb passing publint and attw

* const enum does not work with isolated modules flag

* Fix script tag type in webgl.test.ts.snap and update rrweb.umd.cjs path in webgl.test.ts

* Fix paths

* Move tests for console record plugin and fix bundle path

* Fix tests for rrweb

* pack integration tests were moved to @rrweb/all

* Update rrweb bundle path in test files

* Fix flaky scroll emit from test

* Migrate rrweb's tests over to vitest and make them pass

* Make sure benchmarks & updating tests work

* Remove jest from rrweb

* Fix paths

* always use rrweb's own cssom

* Update tsconfig.json for rrweb-plugin-sequential-id-record

Fixes this error:
Error: @rrweb/rrweb-plugin-sequential-id-record:prepublish: tsconfig.json(9,5): error TS6377: Cannot write file '/home/runner/work/rrweb/rrweb/tsconfig.tsbuildinfo' because it will overwrite '.tsbuildinfo' file generated by referenced project '/home/runner/work/rrweb/rrweb/packages/rrweb'

* Add tsbuildinfo config to extended tsconfig files

* Move rrdom over to vitest

* Apply formatting changes

* Update rrweb imports to use the new package structure

* extend rrweb-snapshot's tsconfig from monorepo base config

* extend @rrweb/types's tsconfig from monorepo base config

* extend rrdom's tsconfig from monorepo base config

* extend rrdom-nodejs's tsconfig from monorepo base config

* extend web-extension's tsconfig from monorepo base config

* unify tsconfigs

* Continue when tests fail

* Add stricter type checking

* Add check-types global command

* remove jest

* Remove unused code

* Add check-types command to build script

* Fix linting issues

* Add setup Chrome action for CI/CD workflow

* Update puppeteer version in package.json for rrweb

* Update Chrome setup in CI/CD workflow

* Update Chrome setup in CI/CD workflow

* Add Chrome setup and test cache location

* Update CI/CD workflow to test chrome cache location

* Add chrome installation step to CI/CD workflow

* Update Puppeteer configuration for headless testing

* Update dependencies and workflow configuration

* Use same version of chrome on CI as is run locally

* Use version of chrome that seems to work with rrdom tests

* Try using puppeteerrc to define chrome version

* Add .cache directory to .gitignore

* Move global flag to vitest config

* Update puppeteer version to 20.9.0

* Update console log messages in rrweb-plugin-console-record for new puppeteer version

* Remove redundant Chrome setup from CI/CD workflow

* Add minification and umd for all built files

* Update import paths for rrweb dist files

* Add @rrweb/replay and @rrweb/record

* Add script to lint packages

* Apply formatting changes

* exclude styles export from typescript package type checking

* WIP Move rrweb-player over to vite

* Apply formatting changes

* chore: Update rrweb plugin import paths

* Remove rollup from rrweb-player

* Fix typing issues

* Fix typing issues

* chore: Update rrweb-player to use vite for build process

* Apply formatting changes

* chore: Export Player class in rrweb-player/src/main.ts

Makes attw happy

* Apply formatting changes

* Gets wiped by yarn workspaces-to-typescript-project-references

* Add .eslintignore and .eslintrc.cjs files for rrweb-player package

* Apply formatting changes

* Update dependencies in rrweb-player/package.json

* Apply formatting changes

* chore: Update eslint configuration for rrweb-player package

* Apply formatting changes

* chore: Remove unused files from rrweb-player package

* Apply formatting changes

* chore: Update rrweb-player import path to use rrweb-player.cjs

* chore: Update addEventListener signature in rrweb-player

* Apply formatting changes

* Add .eslintignore and update .gitignore files for to root

* Apply formatting changes

* Update documentation

* Update @rrweb/types package description

* Apply formatting changes

* Update build and run commands in CONTRIBUTING.md

* Apply formatting changes

* Update package versions to 2.0.0-alpha.13

* Apply formatting changes

* Apply formatting changes

* Fix import statement in media/index.ts

* Apply formatting changes

* chore: Update .gitignore to exclude build and dist directories

* Apply formatting changes

* Apply formatting changes

* Migrate setTimeout to vitest

* Apply formatting changes

* Apply formatting changes

* Fix isNativeShadowDom function signature in utils.ts

* try out jsr

* Apply formatting changes

* Update package versions to 2.0.0-alpha.14

* Apply formatting changes

* Fix name of rrwebSnapshot object

* Apply formatting changes

* Remove unused lock files

* Apply formatting changes

* Update rrweb bundle path to use umd.cjs format

* Apply formatting changes

* Trigger tests to run again

* Rename snapshots for vitest

* Apply formatting changes

* Ping CI

* Apply formatting changes

* Ping CI

* Apply formatting changes

* Ignore files generated by svelte-kit for prettier

* Correct Player object
This commit is contained in:
Justin Halsall
2026-04-01 12:00:00 +08:00
committed by GitHub
parent 4055c69e26
commit 5a85ddf013
266 changed files with 11616 additions and 9614 deletions

0
.cache/.gitkeep Normal file
View File

View File

@@ -0,0 +1,8 @@
---
"@rrweb/rrweb-plugin-canvas-webrtc-replay": patch
"@rrweb/rrweb-plugin-sequential-id-replay": patch
"@rrweb/rrweb-plugin-console-replay": patch
"rrweb": patch
---
Export `ReplayPlugin` from rrweb directly. Previously we had to do `import type { ReplayPlugin } from 'rrweb/dist/types';` now we can do `import type { ReplayPlugin } from 'rrweb';`

View File

@@ -1,5 +1,5 @@
---
'rrweb': patch
"rrweb": patch
---
Refactor to preclude the need for a continuous raf loop running in the background which is related to shadowDom

13
.eslintignore Normal file
View File

@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/dist
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@@ -1,3 +1,4 @@
// TODO: add .eslintignore. More info: https://bobbyhadz.com/blog/typescript-parseroptions-project-has-been-set-for
module.exports = {
env: {
browser: true,
@@ -16,7 +17,7 @@ module.exports = {
ecmaVersion: 'latest',
sourceType: 'module',
tsconfigRootDir: __dirname,
project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
project: ['./tsconfig.eslint.json', './packages/**/tsconfig.json'],
},
plugins: ['@typescript-eslint', 'eslint-plugin-tsdoc', 'jest', 'compat'],
rules: {

View File

@@ -19,7 +19,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: lts/*
- name: Install Dependencies
run: yarn install --frozen-lockfile
@@ -27,9 +27,10 @@ jobs:
run: NODE_OPTIONS='--max-old-space-size=4096' yarn build:all
- name: Check types
run: yarn turbo run check-types
run: yarn check-types
- name: Run tests
# run: PUPPETEER_EXECUTABLE_PATH=${{ steps.setup-chrome.outputs.chrome-path }} PUPPETEER_HEADLESS=true xvfb-run --server-args="-screen 0 1920x1080x24" yarn test
run: PUPPETEER_HEADLESS=true xvfb-run --server-args="-screen 0 1920x1080x24" yarn test
- name: Upload diff images to GitHub

13
.gitignore vendored
View File

@@ -13,14 +13,25 @@ temp
.DS_Store
# output of `yarn build`
build
dist
# turbo cache
.turbo
# needed to store puppeteer binaries
.cache/*
!.gitkeep
# emacs working files end in a tilde
*~
# `.yarn/install-state.gz` is an optimization file that you shouldn't ever have to commit.
# It simply stores the exact state of your project so that the next commands can boot without having to resolve your workspaces all over again.
.yarn/install-state.gz
.yarn/install-state.gz
# for vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

10
.puppeteerrc.cjs Normal file
View File

@@ -0,0 +1,10 @@
const { join } = require('path');
/**
* @type {import("puppeteer").Configuration}
*/
module.exports = {
// Changes the cache location for Puppeteer.
cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
browserRevision: '115.0.5763.0',
};

View File

@@ -24,18 +24,76 @@
"name": "rrweb-snapshot (package)",
"path": "../packages/rrweb-snapshot"
},
{
"name": "@rrweb/all",
"path": "../packages/all"
},
{
"name": "@rrweb/record",
"path": "../packages/record"
},
{
"name": "@rrweb/replay",
"path": "../packages/replay"
},
{
"name": "@rrweb/types",
"path": "../packages/types"
},
{
"name": "@rrweb/packer",
"path": "../packages/packer"
},
{
"name": "web-extension (package)",
"path": "../packages/web-extension"
},
{ "name": "rrvideo (package)", "path": "../packages/rrvideo" },
{ "name": "@rrweb/types", "path": "../packages/types" }
{
"name": "@rrweb/rrweb-plugin-console-record",
"path": "../packages/plugins/rrweb-plugin-console-record"
},
{
"name": "@rrweb/rrweb-plugin-console-replay",
"path": "../packages/plugins/rrweb-plugin-console-replay"
},
{
"name": "@rrweb/rrweb-plugin-sequential-id-record",
"path": "../packages/plugins/rrweb-plugin-sequential-id-record"
},
{
"name": "@rrweb/rrweb-plugin-sequential-id-replay",
"path": "../packages/plugins/rrweb-plugin-sequential-id-replay"
},
{
"name": "@rrweb/rrweb-plugin-canvas-webrtc-record",
"path": "../packages/plugins/rrweb-plugin-canvas-webrtc-record"
},
{
"name": "@rrweb/rrweb-plugin-canvas-webrtc-replay",
"path": "../packages/plugins/rrweb-plugin-canvas-webrtc-replay"
}
],
"settings": {
"vitest.workspaceConfig": "../vitest.workspace.ts",
"vitest.commandLine": "yarn vitest",
"jest.disabledWorkspaceFolders": [
" rrweb monorepo",
"rrweb (package)",
"rrweb-player (package)",
"@rrweb/types"
"rrweb-snapshot (package)",
"rrdom (package)",
"rrdom-nodejs (package)",
"@rrweb/all",
"@rrweb/record",
"@rrweb/replay",
"@rrweb/types",
"@rrweb/packer",
"@rrweb/rrweb-plugin-console-record",
"@rrweb/rrweb-plugin-console-replay",
"@rrweb/rrweb-plugin-sequential-id",
"@rrweb/rrweb-plugin-canvas-webrtc-record",
"@rrweb/rrweb-plugin-canvas-webrtc-replay"
]
}
}

View File

@@ -27,8 +27,10 @@ clear and has sufficient instructions to be able to reproduce the issue.
## Run locally
- Install dependencies: `yarn`
- Run recorder on a website: `yarn repl`
- Run a cobrowsing/mirroring session locally: `yarn live-stream`
- Build all packages: (in `/`) `yarn build:all` or `yarn dev`
- Run recorder on a website: (in `/packages/rrweb`) `yarn repl`
- Run a cobrowsing/mirroring session locally: (in `/packages/rrweb`) `yarn live-stream`
- Build individual packages: `yarn build` or `yarn dev`
- Test: `yarn test` or `yarn test:watch`
- Lint: `yarn lint`
- Rewrite files with prettier: `yarn format` or `yarn format:head`

View File

@@ -11,8 +11,8 @@
[![Join the chat at slack](https://img.shields.io/badge/slack-@rrweb-teal.svg?logo=slack)](https://join.slack.com/t/rrweb/shared_invite/zt-siwoc6hx-uWay3s2wyG8t5GpZVb8rWg)
[![Twitter Follow](https://img.shields.io/badge/twitter-@rrweb__io-teal.svg?logo=twitter)](https://twitter.com/rrweb_io)
![total gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.js?compression=gzip&label=total%20gzip%20size)
![recorder gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.js?compression=gzip&label=recorder%20gzip%20size)
![total gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.cjs?compression=gzip&label=total%20gzip%20size)
![recorder gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.cjs?compression=gzip&label=recorder%20gzip%20size)
[![](https://data.jsdelivr.com/v1/package/npm/rrweb/badge)](https://www.jsdelivr.com/package/npm/rrweb)
[中文文档](./README.zh_CN.md)
@@ -39,9 +39,7 @@ rrweb is mainly composed of 3 parts:
## Roadmap
- rrdom: an ad-hoc DOM for rrweb session data [#419](https://github.com/rrweb-io/rrweb/issues/419)
- storage engine: do deduplication on a large number of rrweb sessions
- more end-to-end tests
- compact mutation data in common patterns
- provide plugins via the new plugin API, including:
- XHR plugin
@@ -166,7 +164,7 @@ In addition to adding integration tests and unit tests, rrweb also provides a RE
</a>
</td>
<td align="center">
<a href="https://github.com/Juice10">
<a href="https://github.com/juice10">
<img
src="https://avatars.githubusercontent.com/u/4106?s=100"
width="100px;"
@@ -199,15 +197,15 @@ In addition to adding integration tests and unit tests, rrweb also provides a RE
</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 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://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 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">

View File

@@ -10,8 +10,8 @@
**[rrweb 纪录片(中文)](https://www.bilibili.com/video/BV1wL4y1B7wN?share_source=copy_web)**
[![Join the chat at slack](https://img.shields.io/badge/slack-@rrweb-teal.svg?logo=slack)](https://join.slack.com/t/rrweb/shared_invite/zt-siwoc6hx-uWay3s2wyG8t5GpZVb8rWg)
![total gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.js?compression=gzip&label=total%20gzip%20size)
![recorder gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.js?compression=gzip&label=recorder%20gzip%20size)
![total gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.cjs?compression=gzip&label=total%20gzip%20size)
![recorder gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.cjs?compression=gzip&label=recorder%20gzip%20size)
[![](https://data.jsdelivr.com/v1/package/npm/rrweb/badge)](https://www.jsdelivr.com/package/npm/rrweb)
> 我已开通 Github Sponsor 您可以通过赞助的形式帮助 rrweb 的开发。
@@ -34,9 +34,7 @@ rrweb 主要由 3 部分组成:
## Roadmap
- rrdom: rrweb 数据专用的 DOM 实现 [#419](https://github.com/rrweb-io/rrweb/issues/419)
- storage engine: 对大规模 rrweb 数据进行去重
- 更多的 E2E 测试
- 在常见场景下对 mutation 数据进行压缩
- 基于新的插件 API 提供更多插件,包括:
- XHR 插件
@@ -66,6 +64,58 @@ rrweb 主要由 3 部分组成:
[使用 REPL 工具](./guide.zh_CN.md#REPL-工具)
## 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>
@@ -78,16 +128,18 @@ rrweb 主要由 3 部分组成:
alt=""
/>
<br /><sub><b>Yuyz0112</b></sub>
<br /><br />
</a>
</td>
<td align="center">
<a href="https://github.com/Mark-Fenng">
<a href="https://github.com/YunFeng0817">
<img
src="https://avatars.githubusercontent.com/u/27533910?s=100"
width="100px;"
alt=""
/>
<br /><sub><b>Mark-Fenng</b></sub>
<br /><sub><b>Yun Feng</b></sub>
<br /><br />
</a>
</td>
<td align="center">
@@ -98,6 +150,7 @@ rrweb 主要由 3 部分组成:
alt=""
/>
<br /><sub><b>eoghanmurray</b></sub>
<br /><br />
</a>
</td>
<td align="center">
@@ -108,12 +161,13 @@ rrweb 主要由 3 部分组成:
alt=""
/>
<br /><sub><b>Juice10</b></sub>
<br /><sub>open for rrweb consulting</sub>
</a>
</td>
</tr>
</table>
## Who's using rrweb
## Who's using rrweb?
<table>
<tr>
@@ -133,15 +187,15 @@ rrweb 主要由 3 部分组成:
</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 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://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 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">
@@ -166,10 +220,20 @@ rrweb 主要由 3 部分组成:
<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>

View File

@@ -40,5 +40,5 @@ replayer.play();
**Enable replaying Canvas will remove the sandbox, which may cause a potential security issue.**
Alternatively you can stream canvas elements via webrtc with the canvas-webrtc plugin.
For more information see [canvas-webrtc documentation](../../packages/rrweb/src/plugins/canvas-webrtc/Readme.md)
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)

View File

@@ -40,5 +40,5 @@ replayer.play();
**回放 Canvas 将会关闭沙盒策略,导致一定风险**
另外,您可以使用 canvas-webrtc 插件通过 WEBRTC 流式传输 Canvas 元素。
有关更多信息,请参考[canvas-webrtc 文档](../../packages/rrweb/src/plugins/canvas-webrtc/Readme.md)
另外,您可以使用 [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)

View File

@@ -8,6 +8,9 @@ This feature aims to provide developers with more information about the bug scen
You can enable the logger using default option like this:
```js
import rrweb from 'rrweb';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';
rrweb.record({
emit: function emit(event) {
// you should use console.log in this way to avoid errors.
@@ -17,7 +20,7 @@ rrweb.record({
defaultLog(event);
},
// to use default record option
plugins: [rrweb.getRecordConsolePlugin()],
plugins: [getRecordConsolePlugin()],
});
```
@@ -27,6 +30,9 @@ You should call console.log.\_\_rrweb_original\_\_() instead.
You can also customize the behavior of logger like this:
```js
import rrweb from 'rrweb';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';
rrweb.record({
emit: function emit(event) {
// you should use console.log in this way to avoid errors.
@@ -37,7 +43,7 @@ rrweb.record({
},
// customized options
plugins: [
rrweb.getRecordConsolePlugin({
getRecordConsolePlugin({
level: ['info', 'log', 'warn', 'error'],
lengthThreshold: 10000,
stringifyOptions: {
@@ -64,9 +70,12 @@ All options are described below:
If recorded events include data of console log type, we will automatically play them.
```js
import rrweb from 'rrweb';
import { getReplayConsolePlugin } from '@rrweb/rrweb-plugin-console-replay';
const replayer = new rrweb.Replayer(events, {
plugins: [
rrweb.getReplayConsolePlugin({
getReplayConsolePlugin({
level: ['info', 'log', 'warn', 'error'],
}),
],

View File

@@ -7,7 +7,10 @@
可以通过如下代码使用默认的配置选项
```js
rrweb.record({
import rrweb from 'rrweb';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';
rweb.record({
emit: function emit(event) {
// 如果要使用console来输出信息请使用如下的写法
const defaultLog = console.log['__rrweb_original__']
@@ -16,7 +19,7 @@ rrweb.record({
defaultLog(event);
},
// 使用默认的配置选项
plugins: [rrweb.getRecordConsolePlugin()],
plugins: [getRecordConsolePlugin()],
});
```
@@ -26,6 +29,9 @@ rrweb.record({
你也可以定制录制 console 的选项
```js
import rrweb from 'rrweb';
import { getRecordConsolePlugin } from '@rrweb/rrweb-plugin-console-record';
rrweb.record({
emit: function emit(event) {
// 如果要使用console来输出信息请使用如下的写法
@@ -36,7 +42,7 @@ rrweb.record({
},
// 定制的选项
plugins: [
rrweb.getRecordConsolePlugin({
getRecordConsolePlugin({
level: ['info', 'log', 'warn', 'error'],
lengthThreshold: 10000,
stringifyOptions: {
@@ -63,9 +69,12 @@ rrweb.record({
如果 replayer 传入的 events 中包含了 console 类型的数据,我们将自动播放这些数据。
```js
import rrweb from 'rrweb';
import { getReplayConsolePlugin } from '@rrweb/rrweb-plugin-console-replay';
const replayer = new rrweb.Replayer(events, {
plugins: [
rrweb.getReplayConsolePlugin({
getReplayConsolePlugin({
level: ['info', 'log', 'warn', 'error'],
}),
],

View File

@@ -1,11 +1,11 @@
# Customize the Replayer
When rrweb's Replayer and the rrweb-player UI do not fit your need, you can customize your replayer UI.
When rrweb's 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, and customize its CSS.
2. Use rrweb-player, and set `showController: false` to hide the controller UI. With this config, you can implement your controller UI.
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 rrweb's Replayer.
@@ -14,6 +14,8 @@ There are several ways to do this:
When using rrweb-player, you can hide its controller UI:
```js
import rrwebPlayer from 'rrweb-player';
new rrwebPlayer({
target: document.body,
props: {

View File

@@ -1,11 +1,11 @@
# 自定义回放 UI
当 rrweb Replayer 和 rrweb-player 的 UI 不能满足需求时,可以通过自定义回放 UI 制作属于你自己的回放器。
当 rrweb Replayer 和 [rrweb-player](../../packages/rrweb-player/) 的 UI 不能满足需求时,可以通过自定义回放 UI 制作属于你自己的回放器。
你可以通过以下几种方式从不同角度自定义回放 UI
1. 使用 rrweb-player 时,通过覆盖 CSS 样式表定制 UI。
2. 使用 rrweb-player 时,通过 `showController: false` 隐藏控制器 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. 基于 rrweb Replayer 开发自己的回放器 UI。
@@ -14,6 +14,8 @@
使用 rrweb-player 时,可以隐藏其控制器 UI
```js
import rrwebPlayer from 'rrweb-player';
new rrwebPlayer({
target: document.body,
props: {

View File

@@ -69,20 +69,24 @@ rrweb.record({
### Use packFn to compress every event
rrweb provides an fflate-based simple compress function rrweb.pack.
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';
rrweb.record({
emit(event) {},
packFn: rrweb.pack,
});
```
And you need to pass rrweb.unpack as the `unpackFn` in replaying.
And you need to pass packer.unpack as the `unpackFn` in replaying.
```js
import { unpack } from '@rrweb/packer';
const replayer = new rrweb.Replayer(events, {
unpackFn: rrweb.unpack,
});

View File

@@ -69,18 +69,22 @@ rrweb.record({
### 基于 packFn 的单数据压缩
rrweb 内包含了基于 fflate 的简单压缩 rrweb.pack在录制时可以作为 `packFn` 传入。
rrweb 提供了一个基于 fflate 的简单压缩函数,在 [@rrweb/packer](../../packages/packer/) 中可以作为 `packFn` 传入使用
```js
import { pack } from '@rrweb/packer';
rrweb.record({
emit(event) {},
packFn: rrweb.pack,
});
```
回放时通用需要传入 rrweb.unpack 作为 `unpackFn` 传入。
回放时通用需要传入 packer.unpack 作为 `unpackFn` 传入。
```js
import { unpack } from '@rrweb/packer';
const replayer = new rrweb.Replayer(events, {
unpackFn: rrweb.unpack,
});

View File

@@ -4,7 +4,12 @@ The plugin API is designed to extend the function of rrweb without bump the size
# Available plugins
- [console](./console.md)
- [@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.
## Interface

View File

@@ -4,7 +4,12 @@
# 可用插件
- [console](./console.zh_CN.md)
- [@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>` 的插件。
## 接口

View File

@@ -20,7 +20,7 @@ When you click the a element link, the default event is to jump to the URL corre
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 nessecary 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.
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

View File

@@ -13,52 +13,48 @@ You are recommended to install rrweb via jsdelivr's CDN service:
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.css"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.umd.min.cjs"></script>
```
Also, you can link to a specific version number that you can update manually:
```html
<script src="https://cdn.jsdelivr.net/npm/rrweb@0.7.0/dist/rrweb.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.14/dist/rrweb.umd.min.cjs"></script>
```
#### Only include the recorder code
rrweb's code includes both the record and the replay parts. Most of the time you only need to include the record part into your targeted web Apps.
This also can be done by using the CDN service:
This also can be done by using the `@rrweb/record` package and the CDN service:
```html
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@rrweb/record@latest/dist/record.umd.min.cjs"></script>
```
#### Other bundles
#### Other packages
Besides the `record/rrweb-record.min.js` entry, rrweb also provides other bundles for different usage.
Besides the `rrweb` and `@rrweb/record` packages, rrweb also provides other packages for different usage.
```shell
# Include record, replay, compression, and decompression.
rrweb-all.js
rrweb-all.min.js
# Include record and replay.
rrweb.js
rrweb.min.js
# Include the styles for replay.
rrweb.min.css
# Record
record/rrweb-record.js
record/rrweb-record.min.js
# Data compression.
record/rrweb-record-pack.js
record/rrweb-record-pack.min.js
# Replay
replay/rrweb-replay.js
replay/rrweb-replay.min.js
# Data decompression.
replay/rrweb-replay-unpack.js
replay/rrweb-replay-unpack.min.js
```
- [rrweb](packages/rrweb): The core package of rrweb, including record and replay functions.
- [rrweb-player](packages/rrweb-player): A GUI for rrweb, providing a timeline and buttons for things like pause, fast-forward, and speedup.
- [rrweb-snapshot](packages/rrweb-snapshot): Handles snapshot and rebuilding features, converting the DOM and its state into a serializable data structure.
- [rrdom](packages/rrdom): A virtual dom package rrweb.
- [rrdom-nodejs](packages/rrdom-nodejs): The Node.js version of rrdom for server-side DOM operations.
- [@rrweb/all](packages/all): A package that includes `rrweb` and `@rrweb/packer` for easy install.
- [@rrweb/record](packages/record): A package for recording rrweb sessions.
- [@rrweb/replay](packages/replay): A package for replaying rrweb sessions.
- [@rrweb/packer](packages/packer): A package for packing and unpacking rrweb data.
- [@rrweb/types](packages/types): Contains types shared across rrweb packages.
- [web-extension](packages/web-extension): A web extension for rrweb.
- [rrvideo](packages/rrvideo): A package for handling video operations in rrweb.
- [@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.
### NPM
@@ -76,7 +72,6 @@ rrweb does **not** support IE11 and below because it uses the `MutationObserver`
### Record
**If you only included the record code with `<script>`**, then you must use the global variable `rrwebRecord` instead of `rrweb.record`.
The following sample code will use the variable `rrweb` which is the default exporter of this library.
```js
@@ -257,7 +252,7 @@ You need to include the style sheet before replay:
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.css"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/style.css"
/>
```
@@ -319,7 +314,7 @@ The replayer accepts options as its constructor's second parameter, and it has t
#### Use rrweb-player
Since rrweb's replayer only provides a basic UI, you can choose rrweb-replayer which is based on rrweb's public APIs but has a feature-rich replayer UI.
Since rrweb's replayer ([@rrweb/replay](packages/replay/)) only provides a basic UI, you can choose [rrweb-player](packages/rrweb-player/) which is based on rrweb's public APIs but has a feature-rich replayer UI.
##### Installation

View File

@@ -11,51 +11,47 @@
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.css"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/style.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.umd.min.cjs"></script>
```
也可以在 URL 中指定具体的版本号,例如:
```html
<script src="https://cdn.jsdelivr.net/npm/rrweb@0.7.0/dist/rrweb.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.14/dist/rrweb.umd.min.cjs"></script>
```
#### 仅引入录制部分
rrweb 代码分为录制和回放两部分,大多数时候用户在被录制的应用中只需要引入录制部分代码同样可以通过 CDN 安装
rrweb 代码分为录制和回放两部分,大多数时候用户在被录制的应用中只需要引入录制部分代码同样可以通过使用 @rrweb/record 包和 CDN 服务来实现
```html
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@rrweb/record@latest/dist/record.umd.min.cjs"></script>
```
#### 其它按需引入方式
#### 其他包
除了仅包含录制代码的 `record/rrweb-record-min.js` 之外rrweb 还提供了其它多种可选的打包文件。所有包含 `.min` 的文件为同名文件的压缩版
除了 `rrweb``@rrweb/record` 之外rrweb 还提供了其他不同用途的包
```shell
# 包含录制、回放、压缩数据、解压缩数据
rrweb-all.js
rrweb-all.min.js
# 包含录制、回放
rrweb.js
rrweb.min.js
# 回放所需的样式文件
rrweb.min.css
# 录制
record/rrweb-record.js
record/rrweb-record.min.js
# 压缩数据
record/rrweb-record-pack.js
record/rrweb-record-pack.min.js
# 回放
replay/rrweb-replay.js
replay/rrweb-replay.min.js
# 解压缩数据
replay/rrweb-replay-unpack.js
replay/rrweb-replay-unpack.min.js
```
- [rrweb](packages/rrweb)rrweb 的核心包,包括录制和回放功能。
- [rrweb-player](packages/rrweb-player)rrweb 的图形用户界面,提供时间线和暂停、快进、加速等按钮。
- [rrweb-snapshot](packages/rrweb-snapshot):处理快照和重建功能,将 DOM 及其状态转换为可序列化的数据结构。
- [rrdom](packages/rrdom)rrweb 的虚拟 dom 包。
- [rrdom-nodejs](packages/rrdom-nodejs):用于服务器端 DOM 操作的 rrdom 的 Node.js 版本。
- [@rrweb/all](packages/all):一个包含 `rrweb``@rrweb/packer`,便于安装的包。
- [@rrweb/record](packages/record):一个用于录制 rrweb 会话的包。
- [@rrweb/replay](packages/replay):一个用于回放 rrweb 会话的包。
- [@rrweb/packer](packages/packer):一个用于打包和解包 rrweb 数据的包。
- [@rrweb/types](packages/types):包含 rrweb 中使用的类型定义。
- [web-extension](packages/web-extension)rrweb 的网页扩展。
- [rrvideo](packages/rrvideo):一个用于处理 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>` 的插件。
### 通过 npm 引入
@@ -252,7 +248,7 @@ window.onerror = function () {
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.css"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/style.css"
/>
```

View File

@@ -15,7 +15,8 @@
"private": true,
"homepage": "https://github.com/rrweb-io/rrweb#readme",
"workspaces": [
"packages/*"
"packages/*",
"packages/plugins/*"
],
"devDependencies": {
"@changesets/changelog-github": "^0.5.0",
@@ -25,6 +26,7 @@
"@typescript-eslint/parser": "^5.62.0",
"browserslist": "^4.22.1",
"concurrently": "^7.1.0",
"esbuild-plugin-umd-wrapper": "^2.0.0",
"eslint": "^8.53.0",
"eslint-plugin-compat": "^4.2.0",
"eslint-plugin-jest": "^27.6.0",
@@ -37,9 +39,10 @@
},
"scripts": {
"build:all": "NODE_OPTIONS='--max-old-space-size=4096' yarn run concurrently --success=all -r -m=1 'yarn workspaces-to-typescript-project-references' 'yarn turbo run prepublish'",
"test": "yarn run concurrently --success=all -r -m=1 'yarn workspaces-to-typescript-project-references --check' 'yarn turbo run test --concurrency=1'",
"test": "yarn run concurrently --success=all -r -m=1 'yarn workspaces-to-typescript-project-references --check' 'yarn turbo run test --concurrency=1 --continue'",
"test:watch": "yarn turbo run test:watch",
"test:update": "yarn turbo run test:update",
"check-types": "yarn turbo run check-types --continue",
"format": "yarn prettier --write '**/*.{ts,md}'",
"format:head": "git diff --name-only HEAD^ |grep '\\.ts$\\|\\.md$' |xargs yarn prettier --write",
"dev": "yarn turbo run dev",
@@ -50,7 +53,7 @@
"release": "yarn build:all && changeset publish"
},
"resolutions": {
"**/jsdom/cssom": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
"**/cssom": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
"**/@types/dom-webcodecs": "0.1.5"
},
"browserslist": [

4
packages/all/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.turbo
dist
node_modules
yarn-error.log

200
packages/all/README.md Normal file
View File

@@ -0,0 +1,200 @@
# @rrweb/all
Convenience package that includes a bundle of rrweb packages.
Includes the following packages:
- [rrweb](../rrweb)
- [@rrweb/record](../record)
- [@rrweb/replay](../replay)
- [@rrweb/packer](../packer)
## Installation
```bash
npm install @rrweb/all
```
## Usage
```js
import { record, replay, pack, unpack } from '@rrweb/all';
// use record, replay, pack, unpack as you would with the individual 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>

66
packages/all/package.json Normal file
View File

@@ -0,0 +1,66 @@
{
"name": "@rrweb/all",
"version": "2.0.0-alpha.14",
"publishConfig": {
"access": "public"
},
"keywords": [
"rrweb",
"@rrweb/all"
],
"scripts": {
"dev": "vite build --watch",
"build": "tsc -noEmit && vite build",
"test": "vitest run",
"test:watch": "vitest watch",
"check-types": "tsc -noEmit",
"prepublish": "npm run build",
"lint": "yarn eslint src/**/*.ts"
},
"homepage": "https://github.com/rrweb-io/rrweb/tree/main/packages/@rrweb/all#readme",
"bugs": {
"url": "https://github.com/rrweb-io/rrweb/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rrweb-io/rrweb.git"
},
"license": "MIT",
"type": "module",
"main": "./dist/all.cjs",
"module": "./dist/all.js",
"unpkg": "./dist/all.umd.cjs",
"typings": "dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/all.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/all.cjs"
}
}
},
"files": [
"build",
"dist",
"package.json"
],
"devDependencies": {
"puppeteer": "^20.9.0",
"vite": "^5.2.8",
"vite-plugin-dts": "^3.8.1",
"vitest": "^1.4.0",
"typescript": "^4.7.3"
},
"dependencies": {
"@rrweb/types": "^2.0.0-alpha.14",
"@rrweb/packer": "^2.0.0-alpha.14",
"rrweb": "^2.0.0-alpha.14"
},
"browserslist": [
"supports es6-class"
]
}

View File

@@ -0,0 +1,4 @@
export * from 'rrweb';
export * from '@rrweb/packer';
// export * from '@rrweb/rrweb-plugin-console-record';
// export * from '@rrweb/rrweb-plugin-console-replay';

View File

@@ -0,0 +1,170 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`cross origin iframes & packer > blank.html > should support packFn option in record() > 1`] = `
"[
{
\\"type\\": 4,
\\"data\\": {
\\"href\\": \\"about:blank\\",
\\"width\\": 1920,
\\"height\\": 1080
},
\\"v\\": \\"v1\\"
},
{
\\"type\\": 2,
\\"data\\": {
\\"node\\": {
\\"type\\": 0,
\\"childNodes\\": [
{
\\"type\\": 1,
\\"name\\": \\"html\\",
\\"publicId\\": \\"\\",
\\"systemId\\": \\"\\",
\\"id\\": 2
},
{
\\"type\\": 2,
\\"tagName\\": \\"html\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"head\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"script\\",
\\"attributes\\": {
\\"type\\": \\"text/javascript\\"
},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
\\"id\\": 6
}
],
\\"id\\": 5
}
],
\\"id\\": 4
},
{
\\"type\\": 2,
\\"tagName\\": \\"body\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 8
},
{
\\"type\\": 2,
\\"tagName\\": \\"iframe\\",
\\"attributes\\": {
\\"rr_src\\": \\"http://localhost:3030/html/blank.html\\"
},
\\"childNodes\\": [],
\\"id\\": 9
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\\\n \\\\n \\",
\\"id\\": 10
}
],
\\"id\\": 7
}
],
\\"id\\": 3
}
],
\\"id\\": 1
},
\\"initialOffset\\": {
\\"left\\": 0,
\\"top\\": 0
}
},
\\"v\\": \\"v1\\"
},
{
\\"type\\": 3,
\\"data\\": {
\\"source\\": 0,
\\"adds\\": [
{
\\"parentId\\": 9,
\\"nextId\\": null,
\\"node\\": {
\\"type\\": 0,
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"html\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"head\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"script\\",
\\"attributes\\": {
\\"type\\": \\"text/javascript\\"
},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
\\"rootId\\": 11,
\\"id\\": 15
}
],
\\"rootId\\": 11,
\\"id\\": 14
}
],
\\"rootId\\": 11,
\\"id\\": 13
},
{
\\"type\\": 2,
\\"tagName\\": \\"body\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n\\\\n\\",
\\"rootId\\": 11,
\\"id\\": 17
}
],
\\"rootId\\": 11,
\\"id\\": 16
}
],
\\"rootId\\": 11,
\\"id\\": 12
}
],
\\"compatMode\\": \\"BackCompat\\",
\\"id\\": 11
}
}
],
\\"removes\\": [],
\\"texts\\": [],
\\"attributes\\": [],
\\"isAttachIframe\\": true
},
\\"v\\": \\"v1\\"
}
]"
`;

View File

@@ -0,0 +1,156 @@
import {
describe,
it,
vi,
beforeAll,
beforeEach,
afterEach,
afterAll,
} from 'vitest';
import type {
eventWithTime,
listenerHandler,
mutationData,
} from '@rrweb/types';
import { unpack } from '@rrweb/packer';
import * as fs from 'fs';
import * as path from 'path';
import type * as puppeteer from 'puppeteer';
import type { recordOptions } from 'rrweb';
import type {} from '@rrweb/types';
import { EventType } from '@rrweb/types';
import {
assertSnapshot,
getServerURL,
launchPuppeteer,
startServer,
waitForRAF,
} from './utils';
import type * as http from 'http';
interface ISuite {
code: string;
browser: puppeteer.Browser;
page: puppeteer.Page;
events: eventWithTime[];
server: http.Server;
serverURL: string;
}
interface IWindow extends Window {
rrweb: {
record: (
options: recordOptions<eventWithTime>,
) => listenerHandler | undefined;
addCustomEvent<T>(tag: string, payload: T): void;
pack: (e: eventWithTime) => string;
};
emit: (e: eventWithTime) => undefined;
snapshots: eventWithTime[];
}
type ExtraOptions = {
usePackFn?: boolean;
};
async function injectRecordScript(
frame: puppeteer.Frame,
options?: ExtraOptions,
) {
await frame.addScriptTag({
path: path.resolve(__dirname, '../dist/all.umd.cjs'),
});
options = options || {};
await frame.evaluate((options) => {
(window as unknown as IWindow).snapshots = [];
const { record, pack } = (window as unknown as IWindow).rrweb;
const config: recordOptions<eventWithTime> = {
recordCrossOriginIframes: true,
recordCanvas: true,
emit(event) {
(window as unknown as IWindow).snapshots.push(event);
(window as unknown as IWindow).emit(event);
},
};
if (options.usePackFn) {
config.packFn = pack;
}
record(config);
}, options);
for (const child of frame.childFrames()) {
await injectRecordScript(child, options);
}
}
const setup = function (
this: ISuite,
content: string,
options?: ExtraOptions,
): ISuite {
const ctx = {} as ISuite;
beforeAll(async () => {
ctx.browser = await launchPuppeteer();
ctx.server = await startServer();
ctx.serverURL = getServerURL(ctx.server);
});
beforeEach(async () => {
ctx.page = await ctx.browser.newPage();
await ctx.page.goto('about:blank');
await ctx.page.setContent(
content.replace(/\{SERVER_URL\}/g, ctx.serverURL),
);
ctx.events = [];
await ctx.page.exposeFunction('emit', (e: eventWithTime) => {
if (e.type === EventType.DomContentLoaded || e.type === EventType.Load) {
return;
}
ctx.events.push(e);
});
ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
await injectRecordScript(ctx.page.mainFrame(), options);
});
afterEach(async () => {
await ctx.page.close();
});
afterAll(async () => {
await ctx.browser.close();
ctx.server.close();
});
return ctx;
};
describe('cross origin iframes & packer', function (this: ISuite) {
vi.setConfig({ testTimeout: 100_000 });
describe('blank.html', function (this: ISuite) {
const content = `
<!DOCTYPE html>
<html>
<body>
<iframe src="{SERVER_URL}/html/blank.html"></iframe>
</body>
</html>
`;
const ctx = setup.call(this, content, { usePackFn: true });
describe('should support packFn option in record()', () => {
it('', async () => {
const frame = ctx.page.mainFrame().childFrames()[0];
await waitForRAF(frame);
const packedSnapshots = (await ctx.page.evaluate(
'window.snapshots',
)) as string[];
const unpackedSnapshots = packedSnapshots.map((packed) =>
unpack(packed),
) as eventWithTime[];
assertSnapshot(unpackedSnapshots);
});
});
});
});

View File

@@ -0,0 +1,3 @@
<html>
<body></body>
</html>

278
packages/all/test/utils.ts Normal file
View File

@@ -0,0 +1,278 @@
import { NodeType } from 'rrweb-snapshot';
import { expect } from 'vitest';
import {
EventType,
IncrementalSource,
eventWithTime,
Optional,
mouseInteractionData,
event,
pluginEvent,
} from '@rrweb/types';
import * as puppeteer from 'puppeteer';
import * as path from 'path';
import * as http from 'http';
import * as url from 'url';
import * as fs from 'fs';
export async function launchPuppeteer(
options?: Parameters<(typeof puppeteer)['launch']>[0],
) {
return await puppeteer.launch({
headless: process.env.PUPPETEER_HEADLESS ? 'new' : false,
defaultViewport: {
width: 1920,
height: 1080,
},
args: ['--no-sandbox'],
...options,
});
}
interface IMimeType {
[key: string]: string;
}
export const startServer = (defaultPort = 3030) =>
new Promise<http.Server>((resolve) => {
const mimeType: IMimeType = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
};
const s = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url!);
const sanitizePath = path
.normalize(parsedUrl.pathname!)
.replace(/^(\.\.[\/\\])+/, '');
let pathname = path.join(__dirname, sanitizePath);
if (/^\/rrweb.*\.c?js.*/.test(sanitizePath)) {
pathname = path.join(__dirname, `../dist`, sanitizePath);
}
try {
const data = fs.readFileSync(pathname);
const ext = path.parse(pathname).ext;
res.setHeader('Content-type', mimeType[ext] || 'text/plain');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET');
res.setHeader('Access-Control-Allow-Headers', 'Content-type');
setTimeout(() => {
res.end(data);
// mock delay
}, 100);
} catch (error) {
res.end();
}
});
s.listen(defaultPort)
.on('listening', () => {
resolve(s);
})
.on('error', (e) => {
s.listen().on('listening', () => {
resolve(s);
});
});
});
export function getServerURL(server: http.Server): string {
const address = server.address();
if (address && typeof address !== 'string') {
return `http://localhost:${address.port}`;
} else {
return `${address}`;
}
}
/**
* Puppeteer may cast random mouse move which make our tests flaky.
* So we only do snapshot test with filtered events.
* Also remove timestamp from event.
* @param snapshots incrementalSnapshotEvent[]
*/
function stringifySnapshots(snapshots: eventWithTime[]): string {
return JSON.stringify(
snapshots
.filter((s) => {
if (
s.type === EventType.IncrementalSnapshot &&
(s.data.source === IncrementalSource.MouseMove ||
s.data.source === IncrementalSource.ViewportResize)
) {
return false;
}
return true;
})
.map((s) => {
if (s.type === EventType.Meta) {
s.data.href = 'about:blank';
}
// FIXME: travis coordinates seems different with my laptop
const coordinatesReg =
/(bottom|top|left|right|width|height): \d+(\.\d+)?px/g;
if (
s.type === EventType.IncrementalSnapshot &&
s.data.source === IncrementalSource.MouseInteraction
) {
delete (s.data as Optional<mouseInteractionData, 'x'>).x;
delete (s.data as Optional<mouseInteractionData, 'y'>).y;
}
if (
s.type === EventType.IncrementalSnapshot &&
s.data.source === IncrementalSource.Mutation
) {
s.data.attributes.forEach((a) => {
if ('style' in a.attributes && a.attributes.style) {
if (typeof a.attributes.style === 'object') {
for (const [k, v] of Object.entries(a.attributes.style)) {
if (Array.isArray(v)) {
if (coordinatesReg.test(k + ': ' + v[0])) {
// TODO: could round the number here instead depending on what's coming out of various test envs
a.attributes.style[k] = ['Npx', v[1]];
}
} else if (typeof v === 'string') {
if (coordinatesReg.test(k + ': ' + v)) {
a.attributes.style[k] = 'Npx';
}
}
coordinatesReg.lastIndex = 0; // wow, a real wart in ECMAScript
}
} else if (coordinatesReg.test(a.attributes.style)) {
a.attributes.style = a.attributes.style.replace(
coordinatesReg,
'$1: Npx',
);
}
}
// strip blob:urls as they are different every time
stripBlobURLsFromAttributes(a);
});
s.data.adds.forEach((add) => {
if (add.node.type === NodeType.Element) {
if (
'style' in add.node.attributes &&
typeof add.node.attributes.style === 'string' &&
coordinatesReg.test(add.node.attributes.style)
) {
add.node.attributes.style = add.node.attributes.style.replace(
coordinatesReg,
'$1: Npx',
);
}
coordinatesReg.lastIndex = 0; // wow, a real wart in ECMAScript
// strip blob:urls as they are different every time
stripBlobURLsFromAttributes(add.node);
// strip rr_dataURL as they are not consistent
if (
'rr_dataURL' in add.node.attributes &&
add.node.attributes.rr_dataURL &&
typeof add.node.attributes.rr_dataURL === 'string'
) {
add.node.attributes.rr_dataURL =
add.node.attributes.rr_dataURL.replace(/,.+$/, ',...');
}
}
});
} else if (
s.type === EventType.IncrementalSnapshot &&
s.data.source === IncrementalSource.MediaInteraction
) {
// round the currentTime to 1 decimal place
if (s.data.currentTime) {
s.data.currentTime = Math.round(s.data.currentTime * 10) / 10;
}
} else if (
s.type === EventType.Plugin &&
s.data.plugin === 'rrweb/console@1'
) {
const pluginPayload = (
s as pluginEvent<{
trace: string[];
payload: string[];
}>
).data.payload;
if (pluginPayload?.trace.length) {
pluginPayload.trace = pluginPayload.trace.map((trace) => {
return trace.replace(
/^pptr:evaluate;.*?:(\d+:\d+)/,
'__puppeteer_evaluation_script__:$1',
);
});
}
if (pluginPayload?.payload.length) {
pluginPayload.payload = pluginPayload.payload.map((payload) => {
return payload.replace(
/pptr:evaluate;.*?:(\d+:\d+)/g,
'__puppeteer_evaluation_script__:$1',
);
});
}
}
delete (s as Optional<eventWithTime, 'timestamp'>).timestamp;
return s as event;
}),
null,
2,
).replace(
// servers might get run on a random port,
// so we need to normalize the port number
/http:\/\/localhost:\d+/g,
'http://localhost:3030',
);
}
function stripBlobURLsFromAttributes(node: {
attributes: {
src?: string;
};
}) {
if (
'src' in node.attributes &&
node.attributes.src &&
typeof node.attributes.src === 'string' &&
node.attributes.src.startsWith('blob:')
) {
node.attributes.src = node.attributes.src
.replace(/[\w-]+$/, '...')
.replace(/:[0-9]+\//, ':xxxx/');
}
}
export async function assertSnapshot(
snapshotsOrPage: eventWithTime[] | puppeteer.Page,
) {
let snapshots: eventWithTime[];
if (!Array.isArray(snapshotsOrPage)) {
// make sure page has finished executing js
await waitForRAF(snapshotsOrPage);
await snapshotsOrPage.waitForFunction(
'window.snapshots && window.snapshots.length > 0',
);
snapshots = (await snapshotsOrPage.evaluate(
'window.snapshots',
)) as eventWithTime[];
} else {
snapshots = snapshotsOrPage;
}
expect(snapshots).toBeDefined();
expect(stringifySnapshots(snapshots)).toMatchSnapshot();
}
export async function waitForRAF(
pageOrFrame: puppeteer.Page | puppeteer.Frame,
) {
return await pageOrFrame.evaluate(() => {
return new Promise((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(resolve);
});
});
});
}

View File

@@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"compilerOptions": {
"rootDir": "src",
"tsBuildInfoFile": "./tsconfig.tsbuildinfo"
},
"references": [
{
"path": "../types"
},
{
"path": "../packer"
},
{
"path": "../rrweb"
}
]
}

View File

@@ -0,0 +1,4 @@
import path from 'path';
import config from '../../vite.config.default';
export default config(path.resolve(__dirname, 'src/index.ts'), 'rrweb');

View File

@@ -0,0 +1,5 @@
/// <reference types="vitest" />
import { defineProject, mergeConfig } from 'vitest/config';
import configShared from '../../vitest.config';
export default mergeConfig(configShared, defineProject({}));

4
packages/packer/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.turbo
dist
node_modules
yarn-error.log

180
packages/packer/README.md Normal file
View File

@@ -0,0 +1,180 @@
# @rrweb/packer
`@rrweb/packer` is a tool to compress rrweb events into a smaller size.
See the [storage recipe](../../docs/recipes/optimize-storage.md#compression) for more info on how this 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://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>

View File

@@ -0,0 +1,4 @@
{
"main": "../dist/pack.cjs",
"types": "../dist/pack.d.ts"
}

View File

@@ -0,0 +1,86 @@
{
"name": "@rrweb/packer",
"version": "2.0.0-alpha.14",
"publishConfig": {
"access": "public"
},
"keywords": [
"rrweb",
"@rrweb/packer"
],
"scripts": {
"dev": "vite build --watch",
"build": "tsc -noEmit && vite build",
"test": "vitest run",
"test:watch": "vitest watch",
"check-types": "tsc -noEmit",
"prepublish": "npm run build",
"lint": "yarn eslint src/**/*.ts"
},
"homepage": "https://github.com/rrweb-io/rrweb/tree/main/packages/@rrweb/packer#readme",
"bugs": {
"url": "https://github.com/rrweb-io/rrweb/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rrweb-io/rrweb.git"
},
"license": "MIT",
"type": "module",
"main": "./dist/packer.cjs",
"module": "./dist/packer.js",
"unpkg": "./dist/packer.js",
"typings": "dist/packer.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/packer.d.ts",
"default": "./dist/packer.js"
},
"require": {
"types": "./dist/packer.d.cts",
"default": "./dist/packer.cjs"
}
},
"./pack": {
"import": {
"types": "./dist/pack.d.ts",
"default": "./dist/pack.js"
},
"require": {
"types": "./dist/pack.d.cts",
"default": "./dist/pack.cjs"
}
},
"./unpack": {
"import": {
"types": "./dist/unpack.d.ts",
"default": "./dist/unpack.js"
},
"require": {
"types": "./dist/unpack.d.cts",
"default": "./dist/unpack.cjs"
}
}
},
"files": [
"pack",
"unpack",
"build",
"dist",
"package.json"
],
"devDependencies": {
"vite": "^5.2.8",
"vite-plugin-dts": "^3.8.1",
"vitest": "^1.4.0",
"typescript": "^4.7.3"
},
"dependencies": {
"fflate": "^0.4.4",
"@rrweb/types": "^2.0.0-alpha.14"
},
"browserslist": [
"supports es6-class"
]
}

View File

@@ -1,8 +1,5 @@
import type { eventWithTime } from '@rrweb/types';
export type PackFn = (event: eventWithTime) => string;
export type UnpackFn = (raw: string) => eventWithTime;
export type eventWithTimeAndPacker = eventWithTime & {
v: string;
};

View File

@@ -1,5 +1,6 @@
import { strFromU8, strToU8, zlibSync } from 'fflate';
import { PackFn, MARK, eventWithTimeAndPacker } from './base';
import type { PackFn } from '@rrweb/types';
import { eventWithTimeAndPacker, MARK } from './base';
export const pack: PackFn = (event) => {
const _e: eventWithTimeAndPacker = {

View File

@@ -1,6 +1,6 @@
import { strFromU8, strToU8, unzlibSync } from 'fflate';
import { UnpackFn, eventWithTimeAndPacker, MARK } from './base';
import type { eventWithTime } from '@rrweb/types';
import { eventWithTimeAndPacker, MARK } from './base';
import type { UnpackFn, eventWithTime } from '@rrweb/types';
export const unpack: UnpackFn = (raw: string) => {
if (typeof raw !== 'string') {

Binary file not shown.

View File

@@ -1,6 +1,7 @@
import { pack, unpack } from '../src/packer';
import { eventWithTime, EventType } from '@rrweb/types';
import { MARK } from '../src/packer/base';
import { describe, it, expect, vi } from 'vitest';
import { pack, unpack } from '../src';
import { type eventWithTime, EventType } from '@rrweb/types';
import { MARK } from '../src/base';
const event: eventWithTime = {
type: EventType.DomContentLoaded,
@@ -27,14 +28,12 @@ describe('unpack', () => {
});
it('stop on unknown data format', () => {
const consoleSpy = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
expect(() => unpack('[""]')).toThrow('');
expect(consoleSpy).toHaveBeenCalled();
jest.resetAllMocks();
vi.resetAllMocks();
});
it('can unpack packed data', () => {

View File

@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"exclude": ["vite.config.ts", "test"],
"compilerOptions": {
"rootDir": "src",
"tsBuildInfoFile": "./tsconfig.tsbuildinfo"
},
"references": [
{
"path": "../types"
}
]
}

View File

@@ -0,0 +1,4 @@
{
"main": "../dist/unpack.cjs",
"types": "../dist/unpack.d.ts"
}

View File

@@ -0,0 +1,11 @@
import path from 'path';
import config from '../../vite.config.default';
export default config(
{
packer: path.resolve(__dirname, 'src/index.ts'),
pack: path.resolve(__dirname, 'src/pack.ts'),
unpack: path.resolve(__dirname, 'src/unpack.ts'),
},
'rrwebPacker',
);

View File

@@ -0,0 +1,54 @@
{
"name": "@rrweb/rrweb-plugin-canvas-webrtc-record",
"version": "2.0.0-alpha.14",
"description": "",
"type": "module",
"main": "./dist/rrweb-plugin-canvas-webrtc-record.umd.cjs",
"module": "./dist/rrweb-plugin-canvas-webrtc-record.js",
"unpkg": "./dist/rrweb-plugin-canvas-webrtc-record.umd.cjs",
"typings": "dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/rrweb-plugin-canvas-webrtc-record.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/rrweb-plugin-canvas-webrtc-record.cjs"
}
}
},
"files": [
"dist",
"package.json"
],
"scripts": {
"dev": "vite build --watch",
"build": "tsc -noEmit && vite build",
"check-types": "tsc -noEmit",
"prepublish": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rrweb-io/rrweb.git"
},
"keywords": [
"rrweb"
],
"author": "justin@recordonce.com",
"license": "MIT",
"bugs": {
"url": "https://github.com/rrweb-io/rrweb/issues"
},
"homepage": "https://github.com/rrweb-io/rrweb#readme",
"devDependencies": {
"rrweb": "^2.0.0-alpha.14",
"typescript": "^4.7.3",
"vite": "^5.2.8",
"vite-plugin-dts": "^3.8.1"
},
"peerDependencies": {
"rrweb": "^2.0.0-alpha.14"
}
}

View File

@@ -1,7 +1,7 @@
import type { Mirror } from 'rrweb-snapshot';
import SimplePeer from 'simple-peer-light';
import type { RecordPlugin, ICrossOriginIframeMirror } from '@rrweb/types';
import type { WebRTCDataChannel } from '../types';
import type { WebRTCDataChannel } from './types';
export const PLUGIN_NAME = 'rrweb/canvas-webrtc@1';
@@ -25,8 +25,8 @@ export type CrossOriginIframeMessageEventContent = {
export class RRWebPluginCanvasWebRTCRecord {
private peer: SimplePeer.Instance | null = null;
private mirror: Mirror;
private crossOriginIframeMirror: ICrossOriginIframeMirror;
private mirror: Mirror | undefined;
private crossOriginIframeMirror: ICrossOriginIframeMirror | undefined;
private streamMap: Map<number, MediaStream> = new Map();
private incomingStreams = new Set<MediaStream>();
private outgoingStreams = new Set<MediaStream>();
@@ -44,13 +44,11 @@ export class RRWebPluginCanvasWebRTCRecord {
peer?: SimplePeer.Instance;
}) {
this.signalSendCallback = signalSendCallback;
window.addEventListener(
'message',
this.windowPostMessageHandler.bind(this),
window.addEventListener('message', (event: MessageEvent) =>
this.windowPostMessageHandler(event),
);
if (peer) this.peer = peer;
}
public initPlugin(): RecordPlugin {
return {
name: PLUGIN_NAME,
@@ -193,7 +191,7 @@ export class RRWebPluginCanvasWebRTCRecord {
}
public setupStream(id: number, rootId?: number): boolean | MediaStream {
if (id === -1) return false;
if (id === -1 || !this.mirror) return false;
let stream: MediaStream | undefined = this.streamMap.get(rootId || id);
if (stream) return stream;
@@ -241,6 +239,7 @@ export class RRWebPluginCanvasWebRTCRecord {
document.querySelectorAll('iframe').forEach((iframe) => {
if (found) return;
if (!this.crossOriginIframeMirror) return;
const remoteId = this.crossOriginIframeMirror.getRemoteId(iframe, id);
if (remoteId === -1) return;

View File

@@ -0,0 +1,14 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src"],
"exclude": ["vite.config.ts"],
"compilerOptions": {
"rootDir": "src",
"tsBuildInfoFile": "./tsconfig.tsbuildinfo"
},
"references": [
{
"path": "../../rrweb"
}
]
}

View File

@@ -0,0 +1,3 @@
import config from '../../../vite.config.default';
export default config('src/index.ts', 'rrwebPluginCanvasWebRTCRecord');

View File

@@ -0,0 +1,248 @@
# 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 rrweb from 'rrweb';
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
},
});
rrweb.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 rrweb from 'rrweb';
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 rrweb.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
```
## 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>

View File

@@ -0,0 +1,54 @@
{
"name": "@rrweb/rrweb-plugin-canvas-webrtc-replay",
"version": "2.0.0-alpha.14",
"description": "",
"type": "module",
"main": "./dist/rrweb-plugin-canvas-webrtc-replay.umd.cjs",
"module": "./dist/rrweb-plugin-canvas-webrtc-replay.js",
"unpkg": "./dist/rrweb-plugin-canvas-webrtc-replay.umd.cjs",
"typings": "dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/rrweb-plugin-canvas-webrtc-replay.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/rrweb-plugin-canvas-webrtc-replay.cjs"
}
}
},
"files": [
"dist",
"package.json"
],
"scripts": {
"dev": "vite build --watch",
"build": "tsc -noEmit && vite build",
"check-types": "tsc -noEmit",
"prepublish": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rrweb-io/rrweb.git"
},
"keywords": [
"rrweb"
],
"author": "justin@recordonce.com",
"license": "MIT",
"bugs": {
"url": "https://github.com/rrweb-io/rrweb/issues"
},
"homepage": "https://github.com/rrweb-io/rrweb#readme",
"devDependencies": {
"rrweb": "^2.0.0-alpha.14",
"typescript": "^4.7.3",
"vite": "^5.2.8",
"vite-plugin-dts": "^3.8.1"
},
"peerDependencies": {
"rrweb": "^2.0.0-alpha.14"
}
}

View File

@@ -1,9 +1,8 @@
import type { RRNode } from 'rrdom';
import type { Mirror } from 'rrweb-snapshot';
import SimplePeer from 'simple-peer-light';
import type { Replayer } from '../../../replay';
import type { ReplayPlugin } from '../../../types';
import type { WebRTCDataChannel } from '../types';
import type { ReplayPlugin, Replayer } from 'rrweb';
import type { WebRTCDataChannel } from './types';
// TODO: restrict callback to real nodes only, or make sure callback gets called when real node gets added to dom as well
@@ -13,7 +12,7 @@ export class RRWebPluginCanvasWebRTCReplay {
context: { id: number; replayer: Replayer },
) => void;
private signalSendCallback: (signal: RTCSessionDescriptionInit) => void;
private mirror: Mirror;
private mirror: Mirror | undefined;
constructor({
canvasFoundCallback,
@@ -156,7 +155,7 @@ export class RRWebPluginCanvasWebRTCReplay {
this.streams.forEach((stream) => {
const nodeId = this.streamNodeMap.get(stream.id);
if (!nodeId) return;
const target = this.mirror.getNode(nodeId) as
const target = this.mirror?.getNode(nodeId) as
| HTMLCanvasElement
| HTMLVideoElement
| null;

View File

@@ -0,0 +1,309 @@
/* eslint-disable */
/// <reference types="node" />
declare module 'simple-peer-light' {
import * as stream from 'stream';
const SimplePeer: SimplePeer.SimplePeer;
namespace SimplePeer {
interface Options {
/** set to `true` if this is the initiating peer */
initiator?: boolean | undefined;
/** custom webrtc data channel configuration (used by [`createDataChannel`](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel)) */
channelConfig?: RTCDataChannelInit | undefined;
/** custom webrtc data channel name */
channelName?: string | undefined;
/** custom webrtc configuration (used by [`RTCPeerConnection`](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) constructor) */
config?: RTCConfiguration | undefined;
/** custom offer options (used by [`createOffer`](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createOffer) method) */
offerOptions?: RTCOfferOptions | undefined;
/** custom answer options (used by [`createAnswer`](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createAnswer) method) */
answerOptions?: RTCAnswerOptions | undefined;
/** function to transform the generated SDP signaling data (for advanced users) */
sdpTransform?: ((this: Instance, sdp: string) => string) | undefined;
/** if video/voice is desired, pass stream returned from [`getUserMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) */
stream?: MediaStream | undefined;
/** an array of MediaStreams returned from [`getUserMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) */
streams?: MediaStream[] | undefined;
/** set to `false` to disable [trickle ICE](http://webrtchacks.com/trickle-ice/) and get a single 'signal' event (slower) */
trickle?: boolean | undefined;
/** similar to `trickle`, needs to be set to `false` to disable trickling, defaults to `false` */
allowHalfTrickle?: boolean | undefined;
/** if `trickle` is set to `false`, determines how long to wait before providing an offer or answer; default value is 5000 milliseconds */
iceCompleteTimeout?: number | undefined;
/** custom webrtc implementation, mainly useful in node to specify in the [wrtc](https://npmjs.com/package/wrtc) package. */
wrtc?:
| {
RTCPeerConnection: typeof RTCPeerConnection;
RTCSessionDescription: typeof RTCSessionDescription;
RTCIceCandidate: typeof RTCIceCandidate;
}
| undefined;
/** set to true to create the stream in Object Mode. In this mode, incoming string data is not automatically converted to Buffer objects. */
objectMode?: boolean | undefined;
}
interface SimplePeer {
prototype: Instance;
/**
* Create a new WebRTC peer connection.
*
* A "data channel" for text/binary communication is always established, because it's cheap and often useful. For video/voice communication, pass the stream option.
*
* If opts is specified, then the default options (see <https://github.com/feross/simple-peer#peer--new-peeropts>) will be overridden.
*/
new (opts?: Options): Instance;
/** Detect native WebRTC support in the javascript environment. */
readonly WEBRTC_SUPPORT: boolean;
// ********************************
// methods which are not documented
// ********************************
/**
* Expose peer and data channel config for overriding all Peer
* instances. Otherwise, just set opts.config or opts.channelConfig
* when constructing a Peer.
*/
config: RTCConfiguration;
/**
* Expose peer and data channel config for overriding all Peer
* instances. Otherwise, just set opts.config or opts.channelConfig
* when constructing a Peer.
*/
channelConfig: RTCDataChannelInit;
}
type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array;
type SimplePeerData = string | Buffer | TypedArray | ArrayBuffer | Blob;
type SignalData =
| {
type: 'transceiverRequest';
transceiverRequest: {
kind: string;
init?: RTCRtpTransceiverInit | undefined;
};
}
| {
type: 'renegotiate';
renegotiate: true;
}
| {
type: 'candidate';
candidate: RTCIceCandidate;
}
| RTCSessionDescriptionInit;
interface Instance extends stream.Duplex {
/**
* Call this method whenever the remote peer emits a `peer.on('signal')` event.
*
* The `data` will encapsulate a webrtc offer, answer, or ice candidate. These messages help
* the peers to eventually establish a direct connection to each other. The contents of these
* strings are an implementation detail that can be ignored by the user of this module;
* simply pass the data from 'signal' events to the remote peer and call `peer.signal(data)`
* to get connected.
*/
signal(data: string | SignalData): void;
/**
* Send text/binary data to the remote peer. `data` can be any of several types: `String`,
* `Buffer` (see [buffer](https://github.com/feross/buffer)), `ArrayBufferView` (`Uint8Array`,
* etc.), `ArrayBuffer`, or `Blob` (in browsers that support it).
*
* Note: If this method is called before the `peer.on('connect')` event has fired,
* then an exception will be thrown. Use `peer.write(data)`
* (which is inherited from the node.js [duplex stream](https://nodejs.org/api/stream.html) interface)
* if you want this data to be buffered instead.
*/
send(data: SimplePeerData): void;
/** Add a `MediaStream` to the connection. */
addStream(stream: MediaStream): void;
/** Remove a `MediaStream` from the connection. */
removeStream(stream: MediaStream): void;
/** Add a `MediaStreamTrack` to the connection. Must also pass the `MediaStream` you want to attach it to. */
addTrack(track: MediaStreamTrack, stream: MediaStream): void;
/** Remove a `MediaStreamTrack` from the connection. Must also pass the `MediaStream` that it was attached to. */
removeTrack(track: MediaStreamTrack, stream: MediaStream): void;
/** Replace a `MediaStreamTrack` with another track. Must also pass the `MediaStream` that the old track was attached to. */
replaceTrack(
oldTrack: MediaStreamTrack,
newTrack: MediaStreamTrack,
stream: MediaStream,
): void;
/** Add a `RTCRtpTransceiver` to the connection. Can be used to add transceivers before adding tracks. Automatically called as necessary by `addTrack`. */
addTransceiver(kind: string, init?: RTCRtpTransceiverInit): void;
// TODO: https://github.com/feross/simple-peer/blob/d972548299a50f836ca91c36e39304ef0f9474b7/index.js#L427
// destroy(onclose?: () => void): void;
/**
* Destroy and cleanup this peer connection.
*
* If the optional `err` parameter is passed, then it will be emitted as an `'error'`
* event on the stream.
*/
destroy(error?: Error): any;
// ********************************
// methods which are not documented
// ********************************
readonly bufferSize: number;
readonly connected: boolean;
address():
| { port: undefined; family: undefined; address: undefined }
| { port: number; family: 'IPv6' | 'IPv4'; address: string };
// used for debug logging
_debug(message?: any, ...optionalParams: any[]): void;
// ******
// events
// ******
addListener(
event: 'connect' | 'close' | 'end' | 'pause' | 'readable' | 'resume',
listener: () => void,
): this;
addListener(event: 'signal', listener: (data: SignalData) => void): this;
addListener(
event: 'stream',
listener: (stream: MediaStream) => void,
): this;
addListener(
event: 'track',
listener: (track: MediaStreamTrack, stream: MediaStream) => void,
): this;
addListener(event: 'data', listener: (chunk: any) => void): this;
addListener(event: 'error', listener: (err: Error) => void): this;
addListener(
event: string | symbol,
listener: (...args: any[]) => void,
): this;
emit(
event: 'connect' | 'close' | 'end' | 'pause' | 'readable' | 'resume',
): boolean;
emit(event: 'signal', data: SignalData): this;
emit(event: 'stream', stream: MediaStream): this;
emit(event: 'track', track: MediaStreamTrack, stream: MediaStream): this;
emit(event: 'data', chunk: any): boolean;
emit(event: 'error', err: Error): boolean;
emit(event: string | symbol, ...args: any[]): boolean;
on(
event: 'connect' | 'close' | 'end' | 'pause' | 'readable' | 'resume',
listener: () => void,
): this;
on(event: 'signal', listener: (data: SignalData) => void): this;
on(event: 'stream', listener: (stream: MediaStream) => void): this;
on(
event: 'track',
listener: (track: MediaStreamTrack, stream: MediaStream) => void,
): this;
on(event: 'data', listener: (chunk: any) => void): this;
on(event: 'error', listener: (err: Error) => void): this;
on(event: string | symbol, listener: (...args: any[]) => void): this;
once(
event: 'connect' | 'close' | 'end' | 'pause' | 'readable' | 'resume',
listener: () => void,
): this;
once(event: 'signal', listener: (data: SignalData) => void): this;
once(event: 'stream', listener: (stream: MediaStream) => void): this;
once(
event: 'track',
listener: (track: MediaStreamTrack, stream: MediaStream) => void,
): this;
once(event: 'data', listener: (chunk: any) => void): this;
once(event: 'error', listener: (err: Error) => void): this;
once(event: string | symbol, listener: (...args: any[]) => void): this;
prependListener(
event: 'connect' | 'close' | 'end' | 'pause' | 'readable' | 'resume',
listener: () => void,
): this;
prependListener(
event: 'signal',
listener: (data: SignalData) => void,
): this;
prependListener(
event: 'stream',
listener: (stream: MediaStream) => void,
): this;
prependListener(
event: 'track',
listener: (track: MediaStreamTrack, stream: MediaStream) => void,
): this;
prependListener(event: 'data', listener: (chunk: any) => void): this;
prependListener(event: 'error', listener: (err: Error) => void): this;
prependListener(
event: string | symbol,
listener: (...args: any[]) => void,
): this;
prependOnceListener(
event: 'connect' | 'close' | 'end' | 'pause' | 'readable' | 'resume',
listener: () => void,
): this;
prependOnceListener(
event: 'signal',
listener: (data: SignalData) => void,
): this;
prependOnceListener(
event: 'stream',
listener: (stream: MediaStream) => void,
): this;
prependOnceListener(
event: 'track',
listener: (track: MediaStreamTrack, stream: MediaStream) => void,
): this;
prependOnceListener(event: 'data', listener: (chunk: any) => void): this;
prependOnceListener(event: 'error', listener: (err: Error) => void): this;
prependOnceListener(
event: string | symbol,
listener: (...args: any[]) => void,
): this;
removeListener(
event: 'connect' | 'close' | 'end' | 'pause' | 'readable' | 'resume',
listener: () => void,
): this;
removeListener(
event: 'signal',
listener: (data: SignalData) => void,
): this;
removeListener(
event: 'stream',
listener: (stream: MediaStream) => void,
): this;
removeListener(
event: 'track',
listener: (track: MediaStreamTrack, stream: MediaStream) => void,
): this;
removeListener(event: 'data', listener: (chunk: any) => void): this;
removeListener(event: 'error', listener: (err: Error) => void): this;
removeListener(
event: string | symbol,
listener: (...args: any[]) => void,
): this;
}
}
export default SimplePeer;
}

View File

@@ -0,0 +1,4 @@
export interface WebRTCDataChannel {
nodeId: number;
streamId: string;
}

View File

@@ -0,0 +1,14 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src"],
"exclude": ["vite.config.ts"],
"compilerOptions": {
"rootDir": "src",
"tsBuildInfoFile": "./tsconfig.tsbuildinfo"
},
"references": [
{
"path": "../../rrweb"
}
]
}

View File

@@ -0,0 +1,3 @@
import config from '../../../vite.config.default';
export default config('src/index.ts', 'rrwebPluginCanvasWebRTCReplay');

View File

@@ -0,0 +1,178 @@
# @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://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>

View File

@@ -0,0 +1,58 @@
{
"name": "@rrweb/rrweb-plugin-console-record",
"version": "2.0.0-alpha.14",
"description": "",
"type": "module",
"main": "./dist/rrweb-plugin-console-record.umd.cjs",
"module": "./dist/rrweb-plugin-console-record.js",
"unpkg": "./dist/rrweb-plugin-console-record.umd.cjs",
"typings": "dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/rrweb-plugin-console-record.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/rrweb-plugin-console-record.umd.cjs"
}
}
},
"files": [
"dist",
"package.json"
],
"scripts": {
"dev": "vite build --watch",
"test": "vitest run",
"test:watch": "vitest watch",
"build": "tsc -noEmit && vite build",
"check-types": "tsc -noEmit",
"prepublish": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rrweb-io/rrweb.git"
},
"keywords": [
"rrweb"
],
"author": "yanzhen@smartx.com",
"license": "MIT",
"bugs": {
"url": "https://github.com/rrweb-io/rrweb/issues"
},
"homepage": "https://github.com/rrweb-io/rrweb#readme",
"devDependencies": {
"rrweb": "^2.0.0-alpha.14",
"typescript": "^4.7.3",
"vite": "^5.2.8",
"vite-plugin-dts": "^3.8.1",
"vitest": "^1.4.0",
"puppeteer": "^20.9.0"
},
"peerDependencies": {
"rrweb": "^2.0.0-alpha.14"
}
}

View File

@@ -1,5 +1,5 @@
import type { listenerHandler, RecordPlugin, IWindow } from '@rrweb/types';
import { patch } from '../../../utils';
import { utils } from 'rrweb';
import { ErrorStackParser, StackFrame } from './error-stack-parser';
import { stringify } from './stringify';
@@ -183,7 +183,7 @@ function initLogObserver(
};
}
// replace the logger.{level}. return a restore function
return patch(
return utils.patch(
_logger,
level,
(original: (...args: Array<unknown>) => void) => {

View File

@@ -0,0 +1,697 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`rrweb-plugin-console-record > should handle recursive console messages 1`] = `
"[
{
\\"type\\": 4,
\\"data\\": {
\\"href\\": \\"about:blank\\",
\\"width\\": 1920,
\\"height\\": 1080
}
},
{
\\"type\\": 2,
\\"data\\": {
\\"node\\": {
\\"type\\": 0,
\\"childNodes\\": [
{
\\"type\\": 1,
\\"name\\": \\"html\\",
\\"publicId\\": \\"\\",
\\"systemId\\": \\"\\",
\\"id\\": 2
},
{
\\"type\\": 2,
\\"tagName\\": \\"html\\",
\\"attributes\\": {
\\"lang\\": \\"en\\"
},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"head\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 5
},
{
\\"type\\": 2,
\\"tagName\\": \\"script\\",
\\"attributes\\": {
\\"type\\": \\"module\\",
\\"src\\": \\"http://localhost:3030/@vite/client\\"
},
\\"childNodes\\": [],
\\"id\\": 6
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n\\\\n \\",
\\"id\\": 7
},
{
\\"type\\": 2,
\\"tagName\\": \\"meta\\",
\\"attributes\\": {
\\"charset\\": \\"UTF-8\\"
},
\\"childNodes\\": [],
\\"id\\": 8
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 9
},
{
\\"type\\": 2,
\\"tagName\\": \\"meta\\",
\\"attributes\\": {
\\"name\\": \\"viewport\\",
\\"content\\": \\"width=device-width, initial-scale=1.0\\"
},
\\"childNodes\\": [],
\\"id\\": 10
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 11
},
{
\\"type\\": 2,
\\"tagName\\": \\"meta\\",
\\"attributes\\": {
\\"http-equiv\\": \\"X-UA-Compatible\\",
\\"content\\": \\"ie=edge\\"
},
\\"childNodes\\": [],
\\"id\\": 12
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 13
},
{
\\"type\\": 2,
\\"tagName\\": \\"title\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"Log record\\",
\\"id\\": 15
}
],
\\"id\\": 14
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 16
},
{
\\"type\\": 2,
\\"tagName\\": \\"script\\",
\\"attributes\\": {
\\"src\\": \\"http://localhost:3030/test/html/index.ts\\",
\\"type\\": \\"module\\"
},
\\"childNodes\\": [],
\\"id\\": 17
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 18
}
],
\\"id\\": 4
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 19
},
{
\\"type\\": 2,
\\"tagName\\": \\"body\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n\\\\n\\",
\\"id\\": 21
}
],
\\"id\\": 20
}
],
\\"id\\": 3
}
],
\\"id\\": 1
},
\\"initialOffset\\": {
\\"left\\": 0,
\\"top\\": 0
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"log\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:21:15\\"
],
\\"payload\\": [
\\"\\\\\\"Proxied object:\\\\\\"\\",
\\"\\\\\\"[object Object]\\\\\\"\\"
]
}
}
}
]"
`;
exports[`rrweb-plugin-console-record > should record console messages 1`] = `
"[
{
\\"type\\": 4,
\\"data\\": {
\\"href\\": \\"about:blank\\",
\\"width\\": 1920,
\\"height\\": 1080
}
},
{
\\"type\\": 2,
\\"data\\": {
\\"node\\": {
\\"type\\": 0,
\\"childNodes\\": [
{
\\"type\\": 1,
\\"name\\": \\"html\\",
\\"publicId\\": \\"\\",
\\"systemId\\": \\"\\",
\\"id\\": 2
},
{
\\"type\\": 2,
\\"tagName\\": \\"html\\",
\\"attributes\\": {
\\"lang\\": \\"en\\"
},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"head\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 5
},
{
\\"type\\": 2,
\\"tagName\\": \\"script\\",
\\"attributes\\": {
\\"type\\": \\"module\\",
\\"src\\": \\"http://localhost:3030/@vite/client\\"
},
\\"childNodes\\": [],
\\"id\\": 6
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n\\\\n \\",
\\"id\\": 7
},
{
\\"type\\": 2,
\\"tagName\\": \\"meta\\",
\\"attributes\\": {
\\"charset\\": \\"UTF-8\\"
},
\\"childNodes\\": [],
\\"id\\": 8
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 9
},
{
\\"type\\": 2,
\\"tagName\\": \\"meta\\",
\\"attributes\\": {
\\"name\\": \\"viewport\\",
\\"content\\": \\"width=device-width, initial-scale=1.0\\"
},
\\"childNodes\\": [],
\\"id\\": 10
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 11
},
{
\\"type\\": 2,
\\"tagName\\": \\"meta\\",
\\"attributes\\": {
\\"http-equiv\\": \\"X-UA-Compatible\\",
\\"content\\": \\"ie=edge\\"
},
\\"childNodes\\": [],
\\"id\\": 12
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 13
},
{
\\"type\\": 2,
\\"tagName\\": \\"title\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"Log record\\",
\\"id\\": 15
}
],
\\"id\\": 14
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 16
},
{
\\"type\\": 2,
\\"tagName\\": \\"script\\",
\\"attributes\\": {
\\"src\\": \\"http://localhost:3030/test/html/index.ts\\",
\\"type\\": \\"module\\"
},
\\"childNodes\\": [],
\\"id\\": 17
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 18
}
],
\\"id\\": 4
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 19
},
{
\\"type\\": 2,
\\"tagName\\": \\"body\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n\\\\n\\",
\\"id\\": 21
}
],
\\"id\\": 20
}
],
\\"id\\": 3
}
],
\\"id\\": 1
},
\\"initialOffset\\": {
\\"left\\": 0,
\\"top\\": 0
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"assert\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:2:15\\"
],
\\"payload\\": [
\\"true\\",
\\"\\\\\\"assert\\\\\\"\\"
]
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"count\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:3:15\\"
],
\\"payload\\": [
\\"\\\\\\"count\\\\\\"\\"
]
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"countReset\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:4:15\\"
],
\\"payload\\": [
\\"\\\\\\"count\\\\\\"\\"
]
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"debug\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:5:15\\"
],
\\"payload\\": [
\\"\\\\\\"debug\\\\\\"\\"
]
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"dir\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:6:15\\"
],
\\"payload\\": [
\\"\\\\\\"dir\\\\\\"\\"
]
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"dirxml\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:7:15\\"
],
\\"payload\\": [
\\"\\\\\\"dirxml\\\\\\"\\"
]
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"group\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:8:15\\"
],
\\"payload\\": []
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"groupCollapsed\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:9:15\\"
],
\\"payload\\": []
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"info\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:10:15\\"
],
\\"payload\\": [
\\"\\\\\\"info\\\\\\"\\"
]
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"log\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:11:15\\"
],
\\"payload\\": [
\\"\\\\\\"log\\\\\\"\\"
]
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"table\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:12:15\\"
],
\\"payload\\": [
\\"\\\\\\"table\\\\\\"\\"
]
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"time\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:13:15\\"
],
\\"payload\\": []
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"timeEnd\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:14:15\\"
],
\\"payload\\": []
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"timeLog\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:15:15\\"
],
\\"payload\\": []
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"trace\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:16:15\\"
],
\\"payload\\": [
\\"\\\\\\"trace\\\\\\"\\"
]
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"warn\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:17:15\\"
],
\\"payload\\": [
\\"\\\\\\"warn\\\\\\"\\"
]
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"clear\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:18:15\\"
],
\\"payload\\": []
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"log\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:19:15\\"
],
\\"payload\\": [
\\"\\\\\\"TypeError: a message\\\\\\\\n at __puppeteer_evaluation_script__:19:19\\\\\\\\nEnd of stack for Error object\\\\\\"\\"
]
}
}
},
{
\\"type\\": 3,
\\"data\\": {
\\"source\\": 0,
\\"texts\\": [],
\\"attributes\\": [],
\\"removes\\": [],
\\"adds\\": [
{
\\"parentId\\": 20,
\\"nextId\\": null,
\\"node\\": {
\\"type\\": 2,
\\"tagName\\": \\"iframe\\",
\\"attributes\\": {},
\\"childNodes\\": [],
\\"id\\": 22
}
}
]
}
},
{
\\"type\\": 3,
\\"data\\": {
\\"source\\": 0,
\\"adds\\": [
{
\\"parentId\\": 22,
\\"nextId\\": null,
\\"node\\": {
\\"type\\": 0,
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"html\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"head\\",
\\"attributes\\": {},
\\"childNodes\\": [],
\\"rootId\\": 23,
\\"id\\": 25
},
{
\\"type\\": 2,
\\"tagName\\": \\"body\\",
\\"attributes\\": {},
\\"childNodes\\": [],
\\"rootId\\": 23,
\\"id\\": 26
}
],
\\"rootId\\": 23,
\\"id\\": 24
}
],
\\"compatMode\\": \\"BackCompat\\",
\\"id\\": 23
}
}
],
\\"removes\\": [],
\\"texts\\": [],
\\"attributes\\": [],
\\"isAttachIframe\\": true
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"log\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:2:15\\"
],
\\"payload\\": [
\\"\\\\\\"from iframe\\\\\\"\\"
]
}
}
}
]"
`;

View File

@@ -0,0 +1,19 @@
import type { eventWithTime } from '@rrweb/types';
import { record } from 'rrweb';
import { getRecordConsolePlugin } from '../../src/index';
window.Date.now = () => new Date(Date.UTC(2018, 10, 15, 8)).valueOf();
const snapshots: eventWithTime[] = ((window as any).snapshots = []);
record({
emit: (event) => {
snapshots.push(event);
},
// maskTextSelector: ${JSON.stringify(options.maskTextSelector)},
// maskAllInputs: ${options.maskAllInputs},
// maskInputOptions: ${JSON.stringify(options.maskAllInputs)},
// userTriggeredOnInput: ${options.userTriggeredOnInput},
// maskTextFn: ${options.maskTextFn},
// recordCanvas: ${options.recordCanvas},
// inlineImages: ${options.inlineImages},
plugins: [getRecordConsolePlugin()],
});

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Log record</title>
<script src="./index.ts" type="module"></script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,128 @@
import { describe, it, beforeAll, afterAll, expect, vi } from 'vitest';
import { stringifySnapshots } from '../../../rrweb/test/utils';
import { createServer, ViteDevServer } from 'vite';
import * as puppeteer from 'puppeteer';
import type { Browser, Page } from 'puppeteer';
import type { eventWithTime } from '@rrweb/types';
export async function launchPuppeteer(
options?: Parameters<(typeof puppeteer)['launch']>[0],
) {
return await puppeteer.launch({
headless: process.env.PUPPETEER_HEADLESS ? true : false,
defaultViewport: {
width: 1920,
height: 1080,
},
args: ['--no-sandbox'],
...options,
});
}
export function assertSnapshot(snapshots: eventWithTime[]) {
expect(snapshots).toBeDefined();
expect(stringifySnapshots(snapshots)).toMatchSnapshot();
}
describe('rrweb-plugin-console-record', () => {
// vi.setConfig({ testTimeout: 120_000 });
let server: ViteDevServer;
let serverUrl: string;
let browser: Browser;
let page: Page;
beforeAll(async () => {
server = await createServer({
preview: { port: 3000 },
mode: 'test',
// hmr calls `console.debug('[vite] connected')` and messes up our snapshots
// so we disable it
server: { hmr: false },
});
await server.listen();
serverUrl = server.resolvedUrls!.local[0];
browser = await launchPuppeteer();
page = await browser.newPage();
});
afterAll(async () => {
await browser.close();
await server.close();
});
it('should handle recursive console messages', async () => {
await page.goto(`${serverUrl}test/html/log.html`);
await page.evaluate(() => {
// Some frameworks like Vue.js use proxies to implement reactivity.
// This can cause infinite loops when logging objects.
let recursiveTarget = { foo: 'bar', proxied: 'i-am', proxy: null };
let count = 0;
const handler = {
get(target: any, prop: any, ...args: any[]) {
if (prop === 'proxied') {
if (count > 9) {
return;
}
count++; // We don't want out test to get into an infinite loop...
console.warn(
'proxied was accessed so triggering a console.warn',
target,
);
}
return Reflect.get(target, prop, ...args);
},
};
const proxy = new Proxy(recursiveTarget, handler);
recursiveTarget.proxy = proxy;
console.log('Proxied object:', proxy);
});
// await waitForRAF(page);
const snapshots = (await page.evaluate(
'window.snapshots',
)) as eventWithTime[];
// The snapshots should containe 1 console log, not multiple.
assertSnapshot(snapshots);
});
it('should record console messages', async () => {
await page.goto(`${serverUrl}test/html/log.html`);
await page.evaluate(() => {
console.assert(0 === 0, 'assert');
console.count('count');
console.countReset('count');
console.debug('debug');
console.dir('dir');
console.dirxml('dirxml');
console.group();
console.groupCollapsed();
console.info('info');
console.log('log');
console.table('table');
console.time();
console.timeEnd();
console.timeLog();
console.trace('trace');
console.warn('warn');
console.clear();
console.log(new TypeError('a message'));
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
});
await page.frames()[1].evaluate(() => {
console.log('from iframe');
});
const snapshots = (await page.evaluate(
'window.snapshots',
)) as eventWithTime[];
assertSnapshot(snapshots);
});
});

View File

@@ -1,8 +1,9 @@
/**
* @jest-environment jsdom
* @vitest-environment jsdom
*/
import { stringify } from '../../../src/plugins/console/record/stringify';
import { stringify } from '../src/stringify';
import { describe, it, expect } from 'vitest';
describe('console record plugin', () => {
it('can stringify bigint', () => {

View File

@@ -0,0 +1,18 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src"],
"exclude": ["vite.config.ts", "vitest.config.ts", "test"],
"compilerOptions": {
"rootDir": "src",
"tsBuildInfoFile": "./tsconfig.tsbuildinfo",
// TODO: enable me in the future
// at time of writing (April 2024) there are 6 errors in this package
"strict": false
},
"references": [
{
"path": "../../rrweb"
}
]
}

View File

@@ -0,0 +1,3 @@
import config from '../../../vite.config.default';
export default config('src/index.ts', 'rrwebPluginConsoleRecord');

View File

@@ -0,0 +1,5 @@
/// <reference types="vitest" />
import { defineProject, mergeConfig } from 'vitest/config';
import configShared from '../../../vitest.config.ts';
export default mergeConfig(configShared, defineProject({}));

View File

@@ -0,0 +1,178 @@
# @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://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>

View File

@@ -0,0 +1,55 @@
{
"name": "@rrweb/rrweb-plugin-console-replay",
"version": "2.0.0-alpha.14",
"description": "",
"type": "module",
"main": "./dist/rrweb-plugin-console-replay.umd.cjs",
"module": "./dist/rrweb-plugin-console-replay.js",
"unpkg": "./dist/rrweb-plugin-console-replay.umd.cjs",
"typings": "dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/rrweb-plugin-console-replay.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/rrweb-plugin-console-replay.umd.cjs"
}
}
},
"files": [
"dist",
"package.json"
],
"scripts": {
"dev": "vite build --watch",
"build": "tsc -noEmit && vite build",
"check-types": "tsc -noEmit",
"prepublish": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rrweb-io/rrweb.git"
},
"keywords": [
"rrweb"
],
"author": "yanzhen@smartx.com",
"license": "MIT",
"bugs": {
"url": "https://github.com/rrweb-io/rrweb/issues"
},
"homepage": "https://github.com/rrweb-io/rrweb#readme",
"devDependencies": {
"@rrweb/rrweb-plugin-console-record": "^2.0.0-alpha.14",
"rrweb": "^2.0.0-alpha.14",
"typescript": "^4.7.3",
"vite": "^5.2.8",
"vite-plugin-dts": "^3.8.1"
},
"peerDependencies": {
"rrweb": "^2.0.0-alpha.14"
}
}

View File

@@ -1,7 +1,11 @@
import { LogLevel, LogData, PLUGIN_NAME } from '../record';
import {
LogLevel,
LogData,
PLUGIN_NAME,
} from '@rrweb/rrweb-plugin-console-record';
import type { eventWithTime } from '@rrweb/types';
import { EventType, IncrementalSource } from '@rrweb/types';
import type { ReplayPlugin } from '../../../types';
import type { ReplayPlugin, Replayer } from 'rrweb';
/**
* define an interface to replay log records
@@ -112,7 +116,11 @@ export const getReplayConsolePlugin: (
options?.replayLogger || new LogReplayPlugin(options).getConsoleLogger();
return {
handler(event: eventWithTime, _isSync, context) {
handler(
event: eventWithTime,
_isSync: boolean,
context: { replayer: Replayer },
) {
let logData: LogData | null = null;
if (
event.type === EventType.IncrementalSnapshot &&

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src"],
"exclude": ["vite.config.ts", "dist", "tsconfig.json"],
"compilerOptions": {
"rootDir": ".",
"tsBuildInfoFile": "./tsconfig.tsbuildinfo"
},
"references": [
{
"path": "../rrweb-plugin-console-record"
},
{
"path": "../../rrweb"
}
]
}

View File

@@ -0,0 +1,3 @@
import config from '../../../vite.config.default';
export default config('src/index.ts', 'rrwebPluginConsoleReplay');

View File

@@ -0,0 +1,202 @@
# @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 rrweb from 'rrweb';
import { getRecordSequentialIdPlugin } from '@rrweb/rrweb-plugin-sequential-id-record';
rrweb.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://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>

View File

@@ -0,0 +1,54 @@
{
"name": "@rrweb/rrweb-plugin-sequential-id-record",
"version": "2.0.0-alpha.14",
"description": "",
"type": "module",
"main": "./dist/rrweb-plugin-sequential-id-record.umd.cjs",
"module": "./dist/rrweb-plugin-sequential-id-record.js",
"unpkg": "./dist/rrweb-plugin-sequential-id-record.umd.cjs",
"typings": "dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/rrweb-plugin-sequential-id-record.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/rrweb-plugin-sequential-id-record.umd.cjs"
}
}
},
"files": [
"dist",
"package.json"
],
"scripts": {
"dev": "vite build --watch",
"build": "tsc -noEmit && vite build",
"check-types": "tsc -noEmit",
"prepublish": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rrweb-io/rrweb.git"
},
"keywords": [
"rrweb"
],
"author": "yanzhen@smartx.com",
"license": "MIT",
"bugs": {
"url": "https://github.com/rrweb-io/rrweb/issues"
},
"homepage": "https://github.com/rrweb-io/rrweb#readme",
"devDependencies": {
"rrweb": "^2.0.0-alpha.14",
"typescript": "^4.7.3",
"vite": "^5.2.8",
"vite-plugin-dts": "^3.8.1"
},
"peerDependencies": {
"rrweb": "^2.0.0-alpha.14"
}
}

View File

@@ -0,0 +1,14 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src"],
"exclude": ["vite.config.ts"],
"compilerOptions": {
"rootDir": "src",
"tsBuildInfoFile": "./tsconfig.tsbuildinfo"
},
"references": [
{
"path": "../../rrweb"
}
]
}

View File

@@ -0,0 +1,3 @@
import config from '../../../vite.config.default';
export default config('src/index.ts', 'rrwebPluginSequentialIdRecord');

View File

@@ -0,0 +1,202 @@
# @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 rrweb from 'rrweb';
import { getReplaySequentialIdPlugin } from '@rrweb/rrweb-plugin-sequential-id-replay';
const replayer = new rrweb.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://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>

View File

@@ -0,0 +1,55 @@
{
"name": "@rrweb/rrweb-plugin-sequential-id-replay",
"version": "2.0.0-alpha.14",
"description": "",
"type": "module",
"main": "./dist/rrweb-plugin-sequential-id-replay.umd.cjs",
"module": "./dist/rrweb-plugin-sequential-id-replay.js",
"unpkg": "./dist/rrweb-plugin-sequential-id-replay.umd.cjs",
"typings": "dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/rrweb-plugin-sequential-id-replay.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/rrweb-plugin-sequential-id-replay.umd.cjs"
}
}
},
"files": [
"dist",
"package.json"
],
"scripts": {
"dev": "vite build --watch",
"build": "tsc -noEmit && vite build",
"check-types": "tsc -noEmit",
"prepublish": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rrweb-io/rrweb.git"
},
"keywords": [
"rrweb"
],
"author": "yanzhen@smartx.com",
"license": "MIT",
"bugs": {
"url": "https://github.com/rrweb-io/rrweb/issues"
},
"homepage": "https://github.com/rrweb-io/rrweb#readme",
"devDependencies": {
"@rrweb/rrweb-plugin-sequential-id-record": "^2.0.0-alpha.14",
"rrweb": "^2.0.0-alpha.14",
"typescript": "^4.7.3",
"vite": "^5.2.8",
"vite-plugin-dts": "^3.8.1"
},
"peerDependencies": {
"rrweb": "^2.0.0-alpha.14"
}
}

View File

@@ -1,5 +1,5 @@
import type { SequentialIdOptions } from '../record';
import type { ReplayPlugin } from '../../../types';
import type { SequentialIdOptions } from '@rrweb/rrweb-plugin-sequential-id-record';
import type { ReplayPlugin } from 'rrweb';
import type { eventWithTime } from '@rrweb/types';
type Options = SequentialIdOptions & {

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src"],
"exclude": ["vite.config.ts", "dist", "tsconfig.json"],
"compilerOptions": {
"rootDir": ".",
"tsBuildInfoFile": "./tsconfig.tsbuildinfo"
},
"references": [
{
"path": "../rrweb-plugin-sequential-id-record"
},
{
"path": "../../rrweb"
}
]
}

View File

@@ -0,0 +1,3 @@
import config from '../../../vite.config.default';
export default config('src/index.ts', 'rrwebPluginSequentialIdReplay');

4
packages/record/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.turbo
dist
node_modules
yarn-error.log

201
packages/record/README.md Normal file
View File

@@ -0,0 +1,201 @@
# @rrweb/record
This package contains all the record related code in rrweb.
See the [guide](../../guide.md) for more info on rrweb.
## Installation
```bash
npm install @rrweb/record
```
## 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://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>

View File

@@ -0,0 +1,64 @@
{
"name": "@rrweb/record",
"version": "2.0.0-alpha.14",
"publishConfig": {
"access": "public"
},
"keywords": [
"rrweb",
"@rrweb/record"
],
"scripts": {
"dev": "vite build --watch",
"build": "tsc -noEmit && vite build",
"test": "vitest run",
"test:watch": "vitest watch",
"check-types": "tsc -noEmit",
"prepublish": "npm run build",
"lint": "yarn eslint src/**/*.ts"
},
"homepage": "https://github.com/rrweb-io/rrweb/tree/main/packages/@rrweb/record#readme",
"bugs": {
"url": "https://github.com/rrweb-io/rrweb/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rrweb-io/rrweb.git"
},
"license": "MIT",
"type": "module",
"main": "./dist/record.cjs",
"module": "./dist/record.js",
"unpkg": "./dist/record.umd.cjs",
"typings": "dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/record.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/record.cjs"
}
}
},
"files": [
"dist",
"package.json"
],
"devDependencies": {
"puppeteer": "^20.9.0",
"vite": "^5.2.8",
"vite-plugin-dts": "^3.8.1",
"vitest": "^1.4.0",
"typescript": "^4.7.3"
},
"dependencies": {
"@rrweb/types": "^2.0.0-alpha.14",
"rrweb": "^2.0.0-alpha.14"
},
"browserslist": [
"supports es6-class"
]
}

View File

@@ -0,0 +1,3 @@
import { record } from 'rrweb';
export { record };

View File

@@ -0,0 +1,8 @@
import { describe, it, expect } from 'vitest';
import { record } from '../src/index';
describe('record', () => {
it('should be a function', () => {
expect(typeof record).toBe('function');
});
});

View File

@@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"compilerOptions": {
"rootDir": "src",
"tsBuildInfoFile": "./tsconfig.tsbuildinfo"
},
"references": [
{
"path": "../types"
},
{
"path": "../rrweb"
}
]
}

View File

@@ -0,0 +1,4 @@
import path from 'path';
import config from '../../vite.config.default';
export default config(path.resolve(__dirname, 'src/index.ts'), 'rrweb');

View File

@@ -0,0 +1,5 @@
/// <reference types="vitest" />
import { defineProject, mergeConfig } from 'vitest/config';
import configShared from '../../vitest.config';
export default mergeConfig(configShared, defineProject({}));

Some files were not shown because too many files have changed in this diff Show More