├── .cache └── .gitkeep ├── .changeset ├── README.md ├── attribute-text-reductions.md ├── brave-numbers-joke.md ├── breezy-cats-heal.md ├── breezy-mice-breathe.md ├── calm-bulldogs-speak.md ├── calm-oranges-sin.md ├── chatty-cherries-train.md ├── clean-plants-play.md ├── clean-shrimps-lay.md ├── cold-eyes-hunt.md ├── cold-hounds-teach.md ├── config.json ├── controller-finish-flag.md ├── cuddly-readers-warn.md ├── curvy-apples-lay.md ├── curvy-balloons-brake.md ├── date-now-guard.md ├── eight-terms-hunt.md ├── eighty-teachers-smash.md ├── eleven-bobcats-peel.md ├── eleven-toys-vanish.md ├── empty-bikes-cheer.md ├── event-single-wrap.md ├── fair-dragons-greet.md ├── fair-ducks-clean.md ├── fast-chefs-smell.md ├── fast-pets-exist.md ├── few-rockets-travel.md ├── few-turkeys-reflect.md ├── five-peas-lay.md ├── fluffy-planes-retire.md ├── format-head-prettier.md ├── forty-elephants-attack.md ├── fresh-cars-impress.md ├── fresh-spoons-drive.md ├── friendly-numbers-leave.md ├── gold-apples-joke.md ├── gold-terms-look.md ├── grumpy-ways-own.md ├── hip-worms-relax.md ├── hungry-dodos-taste.md ├── inlineImage-maybeNot-crossOrigin.md ├── itchy-dryers-double.md ├── khaki-dots-bathe.md ├── kind-kids-design.md ├── large-ants-prove.md ├── lazy-squids-draw.md ├── lazy-toes-confess.md ├── lemon-lamps-switch.md ├── light-fireants-exercise.md ├── little-moons-camp.md ├── little-radios-thank.md ├── little-suits-leave.md ├── loud-seals-raise.md ├── lovely-pears-cross.md ├── lovely-students-boil.md ├── mean-tips-impress.md ├── mighty-bulldogs-begin.md ├── mighty-frogs-sparkle.md ├── modern-doors-watch.md ├── moody-dots-refuse.md ├── nervous-buses-pump.md ├── nervous-kiwis-nail.md ├── nervous-mirrors-perform.md ├── nervous-poets-grin.md ├── nervous-tables-travel.md ├── new-snakes-call.md ├── nice-pugs-reply.md ├── no-neg-lookbehind.md ├── old-dryers-hide.md ├── polite-olives-wave.md ├── pre.json ├── pretty-plums-rescue.md ├── pretty-schools-remember.md ├── proud-clocks-hope.md ├── proud-experts-jam.md ├── purple-carrots-film.md ├── real-masks-explode.md ├── real-trains-switch.md ├── rich-crews-protect.md ├── rich-dots-lay.md ├── rich-jars-remember.md ├── serious-ants-juggle.md ├── serious-eggs-greet.md ├── shadow-dom-unbusify.md ├── silent-plants-perform.md ├── silver-windows-float.md ├── sixty-impalas-laugh.md ├── small-olives-arrive.md ├── smart-ears-refuse.md ├── smooth-papayas-boil.md ├── smooth-poems-bake.md ├── spotty-bees-destroy.md ├── swift-dancers-rest.md ├── swift-peas-film.md ├── thirty-baboons-punch.md ├── three-baboons-bow.md ├── tidy-swans-repair.md ├── tidy-yaks-joke.md ├── tiny-buckets-love.md ├── tiny-candles-whisper.md ├── tiny-chairs-build.md ├── tricky-panthers-guess.md ├── twenty-goats-kneel.md ├── twenty-lies-switch.md ├── twenty-planets-repeat.md ├── twenty-tables-call.md ├── violet-melons-itch.md ├── violet-zebras-cry.md ├── wise-spiders-jog.md ├── witty-kids-talk.md ├── yellow-mails-cheat.md └── young-timers-grow.md ├── .craft.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── config.yml └── workflows │ ├── ci-cd.yml │ ├── release.yml │ └── style-check.yml ├── .gitignore ├── .markdownlint.yml ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .puppeteerrc.cjs ├── .release-it.json ├── .size-limit.js ├── .vscode └── rrweb-monorepo.code-workspace ├── .yarn └── releases │ └── yarn-1.23.0-20220130.1630.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README.zh_CN.md ├── docs ├── development │ └── coding-style.md ├── observer.md ├── observer.zh_CN.md ├── recipes │ ├── canvas.md │ ├── canvas.zh_CN.md │ ├── console.md │ ├── console.zh_CN.md │ ├── cross-origin-iframes.md │ ├── cross-origin-iframes.zh_CN.md │ ├── custom-event.md │ ├── custom-event.zh_CN.md │ ├── customize-replayer.md │ ├── customize-replayer.zh_CN.md │ ├── dive-into-event.md │ ├── dive-into-event.zh_CN.md │ ├── export-to-video.md │ ├── export-to-video.zh_CN.md │ ├── index.md │ ├── index.zh_CN.md │ ├── interaction.md │ ├── interaction.zh_CN.md │ ├── live-mode.md │ ├── live-mode.zh_CN.md │ ├── optimize-storage.md │ ├── optimize-storage.zh_CN.md │ ├── pagination.md │ ├── pagination.zh_CN.md │ ├── plugin.md │ ├── plugin.zh_CN.md │ ├── record-and-replay.md │ └── record-and-replay.zh_CN.md ├── replay.md ├── replay.zh_CN.md ├── sandbox.md ├── sandbox.zh_CN.md ├── serialization.md ├── serialization.zh_CN.md └── styleguilde.md ├── guide.md ├── guide.zh_CN.md ├── lerna.json ├── package.json ├── packages ├── all │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ ├── test │ │ ├── __snapshots__ │ │ │ └── cross-origin-iframe-packer.test.ts.snap │ │ ├── cross-origin-iframe-packer.test.ts │ │ ├── html │ │ │ └── blank.html │ │ └── utils.ts │ ├── tsconfig.json │ ├── vite.config.ts │ └── vitest.config.ts ├── packer │ ├── .gitignore │ ├── README.md │ ├── pack │ │ └── package.json │ ├── package.json │ ├── src │ │ ├── base.ts │ │ ├── index.ts │ │ ├── pack.ts │ │ └── unpack.ts │ ├── test │ │ ├── __snapshots__ │ │ │ └── packer.test.ts.snap │ │ └── packer.test.ts │ ├── tsconfig.json │ ├── unpack │ │ └── package.json │ └── vite.config.ts ├── plugins │ ├── rrweb-plugin-canvas-webrtc-record │ │ ├── Readme.md │ │ ├── package.json │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── simple-peer-light.d.ts │ │ │ └── types.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── rrweb-plugin-canvas-webrtc-replay │ │ ├── Readme.md │ │ ├── package.json │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── simple-peer-light.d.ts │ │ │ └── types.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── rrweb-plugin-console-record │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── error-stack-parser.ts │ │ │ ├── index.ts │ │ │ └── stringify.ts │ │ ├── test │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.ts.snap │ │ │ ├── html │ │ │ │ ├── index.ts │ │ │ │ └── log.html │ │ │ ├── index.test.ts │ │ │ └── stringify.test.ts │ │ ├── tsconfig.json │ │ ├── vite.config.ts │ │ └── vitest.config.ts │ ├── rrweb-plugin-console-replay │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── rrweb-plugin-sequential-id-record │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── rrweb-plugin-sequential-id-replay │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts ├── record │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ ├── test │ │ └── record.test.ts │ ├── tsconfig.json │ ├── vite.config.ts │ └── vitest.config.ts ├── replay │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ ├── test │ │ └── replay.test.ts │ ├── tsconfig.json │ ├── vite.config.ts │ └── vitest.config.ts ├── rrdom-nodejs │ ├── .gitignore │ ├── .vscode │ │ ├── extensions.json │ │ └── launch.json │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── document-nodejs.ts │ │ ├── index.ts │ │ └── polyfill.ts │ ├── test │ │ ├── document-nodejs.test.ts │ │ └── polyfill.test.ts │ ├── tsconfig.json │ ├── vite.config.js │ └── vitest.config.ts ├── rrdom │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── diff.ts │ │ ├── document.ts │ │ ├── index.ts │ │ ├── style.ts │ │ └── util.ts │ ├── test │ │ ├── __snapshots__ │ │ │ └── virtual-dom.test.ts.snap │ │ ├── diff.test.ts │ │ ├── document.test.ts │ │ ├── html │ │ │ ├── iframe.html │ │ │ ├── main.html │ │ │ └── shadow-dom.html │ │ ├── utils.ts │ │ └── virtual-dom.test.ts │ ├── tsconfig.json │ ├── vite.config.js │ └── vitest.config.ts ├── rrvideo │ ├── CHANGELOG.md │ ├── README.md │ ├── README.zh_CN.md │ ├── demo │ │ └── demo.gif │ ├── jest.config.js │ ├── package.json │ ├── rrvideo.config.example.json │ ├── src │ │ ├── cli.ts │ │ └── index.ts │ ├── test │ │ ├── cli.test.ts │ │ ├── events │ │ │ └── example.ts │ │ └── tsconfig.json │ └── tsconfig.json ├── rrweb-player │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .prettierignore │ ├── .release-it.json │ ├── .svelte-kit │ │ ├── ambient.d.ts │ │ ├── generated │ │ │ └── client │ │ │ │ ├── app.js │ │ │ │ ├── matchers.js │ │ │ │ └── nodes │ │ │ │ ├── 0.js │ │ │ │ └── 1.js │ │ ├── non-ambient.d.ts │ │ └── tsconfig.json │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.html │ ├── package.json │ ├── public │ │ ├── events.js │ │ └── global.css │ ├── rollup.config.js │ ├── src │ │ ├── Controller.svelte │ │ ├── Player.svelte │ │ ├── components │ │ │ └── Switch.svelte │ │ ├── main.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── svelte.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite-env.d.ts │ └── vite.config.ts ├── rrweb-snapshot │ ├── .gitignore │ ├── .release-it.json │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── jsr.json │ ├── package.json │ ├── src │ │ ├── css.ts │ │ ├── index.ts │ │ ├── rebuild.ts │ │ ├── snapshot.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── test │ │ ├── __snapshots__ │ │ │ └── integration.test.ts.snap │ │ ├── css.test.ts │ │ ├── css │ │ │ ├── benchmark.css │ │ │ ├── style-with-import.css │ │ │ └── style.css │ │ ├── html │ │ │ ├── about-mozilla.html │ │ │ ├── background-clip-text.html │ │ │ ├── basic.html │ │ │ ├── block-element.html │ │ │ ├── compat-mode.html │ │ │ ├── cors-style-sheet.html │ │ │ ├── dynamic-stylesheet.html │ │ │ ├── form-fields-sensitive-update.html │ │ │ ├── form-fields-sensitive.html │ │ │ ├── form-fields.html │ │ │ ├── hover.html │ │ │ ├── iframe-inner.html │ │ │ ├── iframe.html │ │ │ ├── invalid-attribute.html │ │ │ ├── invalid-doctype.html │ │ │ ├── invalid-tagname.html │ │ │ ├── mask-text.html │ │ │ ├── picture-blob-in-frame.html │ │ │ ├── picture-blob.html │ │ │ ├── picture-in-frame.html │ │ │ ├── picture-with-inline-onload.html │ │ │ ├── picture.html │ │ │ ├── preload.html │ │ │ ├── shadow-dom.html │ │ │ ├── svg.html │ │ │ ├── video.html │ │ │ ├── with-relative-res.html │ │ │ ├── with-script.html │ │ │ ├── with-style-sheet-with-import.html │ │ │ └── with-style-sheet.html │ │ ├── iframe-html │ │ │ ├── frame1.html │ │ │ ├── frame2.html │ │ │ └── main.html │ │ ├── images │ │ │ ├── compat-bottom.png │ │ │ ├── compat-top-left.png │ │ │ ├── compat-top-right.png │ │ │ ├── robot.png │ │ │ ├── rrweb-favicon-20x20.png │ │ │ └── symbol-defs.svg │ │ ├── integration.test.ts │ │ ├── js │ │ │ └── a.js │ │ ├── rebuild.test.ts │ │ ├── snapshot.test.ts │ │ ├── utils.test.ts │ │ └── utils.ts │ ├── tsconfig.json │ ├── vite.config.js │ └── vitest.config.ts ├── rrweb-worker │ ├── .eslintrc.json │ ├── .gitignore │ ├── package.json │ ├── rollup.config.mjs │ ├── src │ │ ├── _image-bitmap-data-url-worker.ts │ │ ├── getScaledDimensions.test.ts │ │ ├── getScaledDimensions.ts │ │ ├── image-bitmap-data-url-worker.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.types.json │ ├── vite.config.js │ └── vitest.config.ts ├── rrweb │ ├── .gitignore │ ├── .release-it.json │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── rrweb-record │ │ └── package.json │ ├── rrweb-replay │ │ └── package.json │ ├── scripts │ │ ├── repl.js │ │ ├── stream.js │ │ └── utils.js │ ├── src │ │ ├── entries │ │ │ ├── record.ts │ │ │ └── replay.ts │ │ ├── index.ts │ │ ├── record │ │ │ ├── cross-origin-iframe-mirror.ts │ │ │ ├── error-handler.ts │ │ │ ├── iframe-manager.ts │ │ │ ├── index.ts │ │ │ ├── mutation.ts │ │ │ ├── observer.ts │ │ │ ├── observers │ │ │ │ └── canvas │ │ │ │ │ ├── 2d.ts │ │ │ │ │ ├── canvas-manager.ts │ │ │ │ │ ├── canvas.ts │ │ │ │ │ ├── serialize-args.ts │ │ │ │ │ └── webgl.ts │ │ │ ├── processed-node-manager.ts │ │ │ ├── shadow-dom-manager.ts │ │ │ ├── stylesheet-manager.ts │ │ │ └── workers │ │ │ │ └── tsconfig.json │ │ ├── replay │ │ │ ├── canvas │ │ │ │ ├── 2d.ts │ │ │ │ ├── deserialize-args.ts │ │ │ │ ├── index.ts │ │ │ │ └── webgl.ts │ │ │ ├── index.ts │ │ │ ├── machine.ts │ │ │ ├── smoothscroll.ts │ │ │ ├── styles │ │ │ │ ├── inject-style.ts │ │ │ │ └── style.css │ │ │ └── timer.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── test │ │ ├── __snapshots__ │ │ │ ├── integration.test.ts.snap │ │ │ ├── record.test.ts.snap │ │ │ └── replayer.test.ts.snap │ │ ├── benchmark │ │ │ ├── dom-mutation.test.ts │ │ │ └── replay-fast-forward.test.ts │ │ ├── e2e │ │ │ ├── __image_snapshots__ │ │ │ │ ├── webgl-test-ts-test-e-2-e-webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-image-1-snap.png │ │ │ │ └── webgl-test-ts-test-e-2-e-webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-square-1-snap.png │ │ │ └── webgl.test.ts │ │ ├── events │ │ │ ├── adopted-style-sheet-modification.ts │ │ │ ├── adopted-style-sheet.ts │ │ │ ├── canvas-in-iframe.ts │ │ │ ├── custom-element-define-class.ts │ │ │ ├── document-replacement.ts │ │ │ ├── hover.ts │ │ │ ├── iframe-shadowdom-hover.ts │ │ │ ├── iframe.ts │ │ │ ├── input.ts │ │ │ ├── is-sync-tap.ts │ │ │ ├── mouse.ts │ │ │ ├── moving-style-sheet-on-diff.ts │ │ │ ├── ordering.ts │ │ │ ├── scroll-with-parent-styles.ts │ │ │ ├── scroll.ts │ │ │ ├── selection.ts │ │ │ ├── shadow-dom.ts │ │ │ ├── style-sheet-rule-events.ts │ │ │ ├── style-sheet-text-mutation.ts │ │ │ ├── touch-all-pointer-id.ts │ │ │ ├── touch-some-pointer-id.ts │ │ │ └── webgl.ts │ │ ├── html │ │ │ ├── assets │ │ │ │ ├── 1-minute-of-silence.mp3 │ │ │ │ ├── robot.png │ │ │ │ ├── style.css │ │ │ │ └── webgl-utils.js │ │ │ ├── audio.html │ │ │ ├── benchmark-dom-mutation-add-and-move.html │ │ │ ├── benchmark-dom-mutation-add-and-remove.html │ │ │ ├── benchmark-dom-mutation-attributes.html │ │ │ ├── benchmark-dom-mutation-deep-nested.html │ │ │ ├── benchmark-dom-mutation-multiple-descendant-add.html │ │ │ ├── benchmark-dom-mutation.html │ │ │ ├── benchmark-text-masking.html │ │ │ ├── blank.html │ │ │ ├── block.html │ │ │ ├── blocked-unblocked.html │ │ │ ├── canvas-iframe.html │ │ │ ├── canvas-shadow-dom.html │ │ │ ├── canvas-webgl-image.html │ │ │ ├── canvas-webgl-shader.html │ │ │ ├── canvas-webgl-square.html │ │ │ ├── canvas-webgl.html │ │ │ ├── canvas.html │ │ │ ├── empty.html │ │ │ ├── form.html │ │ │ ├── frame-image-blob-url.html │ │ │ ├── frame1.html │ │ │ ├── frame2.html │ │ │ ├── hello-world.html │ │ │ ├── ignore.html │ │ │ ├── image-blob-url.html │ │ │ ├── link.html │ │ │ ├── main.html │ │ │ ├── mask-text.html │ │ │ ├── move-node.html │ │ │ ├── mutation-observer.html │ │ │ ├── password.html │ │ │ ├── polyfilled-shadowdom-mutation.html │ │ │ ├── react-styled-components.html │ │ │ ├── select2.html │ │ │ ├── shadow-dom.html │ │ │ ├── shuffle.html │ │ │ ├── unblock.html │ │ │ └── unmask-text.html │ │ ├── integration.test.ts │ │ ├── machine.test.ts │ │ ├── record.test.ts │ │ ├── record │ │ │ ├── __snapshots__ │ │ │ │ ├── cross-origin-iframes.test.ts.snap │ │ │ │ └── webgl.test.ts.snap │ │ │ ├── cross-origin-iframes.test.ts │ │ │ ├── error-handler.test.ts │ │ │ ├── serialize-args.test.ts │ │ │ └── webgl.test.ts │ │ ├── replay │ │ │ ├── 2d-mutation.test.ts │ │ │ ├── __image_snapshots__ │ │ │ │ ├── hover-test-ts-test-replay-hover-test-ts-replayer-hover-should-trigger-hover-on-mouse-down-1-snap.png │ │ │ │ └── webgl-test-ts-test-replay-webgl-test-ts-replayer-webgl-should-output-simple-webgl-object-1-snap.png │ │ │ ├── deserialize-args.test.ts │ │ │ ├── hover.test.ts │ │ │ ├── preload-all-images.test.ts │ │ │ ├── webgl-mutation.test.ts │ │ │ └── webgl.test.ts │ │ ├── replayer.test.ts │ │ ├── rrdom.test.ts │ │ ├── util.test.ts │ │ └── utils.ts │ ├── tsconfig.json │ ├── vite.config.entries.js │ ├── vite.config.js │ └── vitest.config.ts ├── types │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── vite.config.js └── web-extension │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ ├── assets │ │ ├── icon128.png │ │ ├── icon16.png │ │ └── icon48.png │ ├── background │ │ └── index.ts │ ├── components │ │ ├── CircleButton.tsx │ │ └── SidebarWithHeader.tsx │ ├── content │ │ ├── index.ts │ │ └── inject.ts │ ├── manifest.json │ ├── options │ │ ├── App.tsx │ │ ├── index.html │ │ └── index.tsx │ ├── pages │ │ ├── App.tsx │ │ ├── Player.tsx │ │ ├── SessionList.tsx │ │ ├── index.html │ │ └── index.tsx │ ├── popup │ │ ├── App.tsx │ │ ├── Timer.tsx │ │ ├── index.tsx │ │ └── popup.html │ ├── types.ts │ └── utils │ │ ├── channel.ts │ │ ├── index.ts │ │ ├── recording.ts │ │ └── storage.ts │ ├── tsconfig.json │ └── vite.config.ts ├── scripts ├── craft-pre-release.sh └── lint-packages.sh ├── tsconfig.base.json ├── tsconfig.eslint.json ├── tsconfig.json ├── turbo.json ├── vite.config.default.ts ├── vitest.config.ts ├── vitest.workspace.ts └── yarn.lock /.cache/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/.cache/.gitkeep -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/attribute-text-reductions.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Don't include redundant data from text/attribute mutations on just-added nodes 6 | -------------------------------------------------------------------------------- /.changeset/brave-numbers-joke.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/breezy-cats-heal.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | fix: createImageBitmap throws DOMException if source is 0 width or height 6 | -------------------------------------------------------------------------------- /.changeset/breezy-mice-breathe.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | safely capture BigInt values with the console log plugin" 6 | -------------------------------------------------------------------------------- /.changeset/calm-bulldogs-speak.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/calm-oranges-sin.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | fix: Fix checking for `patchTarget` in `initAdoptedStyleSheetObserver` 6 | -------------------------------------------------------------------------------- /.changeset/chatty-cherries-train.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Fix the statement which is getting changed by Microbundle 6 | -------------------------------------------------------------------------------- /.changeset/clean-plants-play.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | '@rrweb/types': patch 4 | --- 5 | 6 | Compact style mutation fixes and improvements 7 | 8 | - fixes when style updates contain a 'var()' on a shorthand property #1246 9 | - further ensures that style mutations are compact by reverting to string method if it is shorter 10 | -------------------------------------------------------------------------------- /.changeset/clean-shrimps-lay.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | feat: Add `ignoreSelector` option 6 | 7 | Similar to ignoreClass, but accepts a CSS selector so that you can use any CSS selector. 8 | -------------------------------------------------------------------------------- /.changeset/cold-eyes-hunt.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrdom': patch 3 | --- 4 | 5 | Fix: rrdom bugs 6 | 7 | 1. Fix a bug in the diff algorithm. 8 | 2. Omit the 'srcdoc' attribute of iframes to avoid overwriting content. 9 | -------------------------------------------------------------------------------- /.changeset/cold-hounds-teach.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "rrweb-io/rrweb" }], 4 | "commit": false, 5 | "fixed": [ 6 | [ 7 | "rrweb", 8 | "rrweb-snapshot", 9 | "rrdom", 10 | "rrdom-nodejs", 11 | "rrweb-player", 12 | "@rrweb/types", 13 | "@rrweb/web-extension", 14 | "rrvideo" 15 | ] 16 | ], 17 | "linked": [], 18 | "access": "public", 19 | "baseBranch": "master", 20 | "updateInternalDependencies": "patch", 21 | "ignore": [] 22 | } 23 | -------------------------------------------------------------------------------- /.changeset/controller-finish-flag.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-player': patch 3 | 'rrweb': patch 4 | --- 5 | 6 | Reset the finished flag in Controller `goto` instead of `handleProgressClick` so that it is properly handled if `goto` is called directly. 7 | -------------------------------------------------------------------------------- /.changeset/cuddly-readers-warn.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/curvy-apples-lay.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | 'rrweb': patch 4 | --- 5 | 6 | Extend to run fixBrowserCompatibilityIssuesInCSS over inline stylesheets 7 | -------------------------------------------------------------------------------- /.changeset/curvy-balloons-brake.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/date-now-guard.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Guard against presence of older 3rd party javascript libraries which redefine Date.now() 6 | -------------------------------------------------------------------------------- /.changeset/eight-terms-hunt.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Fix: Switch from virtual dom to real dom before rebuilding fullsnapshot 6 | -------------------------------------------------------------------------------- /.changeset/eighty-teachers-smash.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@rrweb/rrweb-plugin-canvas-webrtc-replay": patch 3 | "@rrweb/rrweb-plugin-sequential-id-replay": patch 4 | "@rrweb/rrweb-plugin-console-replay": patch 5 | "rrweb": patch 6 | --- 7 | 8 | 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';` 9 | -------------------------------------------------------------------------------- /.changeset/eleven-bobcats-peel.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb-snapshot": patch 3 | "rrweb": patch 4 | --- 5 | 6 | better support for coexistence with older libraries (e.g. MooTools & Prototype.js) which modify the in-built `Array.from` function 7 | -------------------------------------------------------------------------------- /.changeset/eleven-toys-vanish.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/empty-bikes-cheer.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/event-single-wrap.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | perf: Avoid an extra function call and object clone during event emission 6 | -------------------------------------------------------------------------------- /.changeset/fair-dragons-greet.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/fair-ducks-clean.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb-snapshot": patch 3 | "rrweb": patch 4 | --- 5 | 6 | Fix and test for bug #1457 which was affecting replay of complex tailwind css 7 | -------------------------------------------------------------------------------- /.changeset/fast-chefs-smell.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Fix: [#1178](https://github.com/rrweb-io/rrweb/issues/1178) remove warning related to worker_threads while building 6 | -------------------------------------------------------------------------------- /.changeset/fast-pets-exist.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb-snapshot": patch 3 | "rrweb": patch 4 | --- 5 | 6 | Fixup for multiple background-clip replacement 7 | -------------------------------------------------------------------------------- /.changeset/few-rockets-travel.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/few-turkeys-reflect.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/five-peas-lay.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/fluffy-planes-retire.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Feat: Add support for replaying :defined pseudo-class of custom elements 6 | -------------------------------------------------------------------------------- /.changeset/format-head-prettier.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/forty-elephants-attack.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/fresh-cars-impress.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/fresh-spoons-drive.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Fix: processed-node-manager is created even in the environment that doesn't need a recorder 6 | -------------------------------------------------------------------------------- /.changeset/friendly-numbers-leave.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/gold-apples-joke.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | ref: Avoid unnecessary cloning of objects or arrays 6 | -------------------------------------------------------------------------------- /.changeset/gold-terms-look.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | perf: optimize performance of the DoubleLinkedList get 6 | -------------------------------------------------------------------------------- /.changeset/grumpy-ways-own.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | --- 4 | 5 | Fix: CSS transitions are incorrectly being applied upon rebuild in Firefox. Presumably FF doesn't finished parsing the styles in time, and applies e.g. a default margin:0 to elements which have a non-zero margin set in CSS, along with a transition on them. 6 | 7 | Related bug report to Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1816672​ 8 | -------------------------------------------------------------------------------- /.changeset/hip-worms-relax.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/hungry-dodos-taste.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | --- 4 | 5 | Avoid recreating the same element every time, instead, we cache and we just update the element. 6 | 7 | Before: 779k ops/s 8 | After: 860k ops/s 9 | 10 | Benchmark: https://jsbench.me/ktlqztuf95/1 11 | -------------------------------------------------------------------------------- /.changeset/inlineImage-maybeNot-crossOrigin.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb": patch 3 | "rrweb-snapshot": patch 4 | --- 5 | 6 | inlineImages: during snapshot avoid adding an event listener for inlining of same-origin images (async listener mutates the snapshot which can be problematic) 7 | -------------------------------------------------------------------------------- /.changeset/itchy-dryers-double.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-player': patch 3 | 'rrweb-snapshot': patch 4 | 'rrweb': patch 5 | '@rrweb/types': patch 6 | --- 7 | 8 | Upgrade all projects to typescript 4.9.5 9 | -------------------------------------------------------------------------------- /.changeset/khaki-dots-bathe.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/kind-kids-design.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb": patch 3 | --- 4 | 5 | Optimize performance of isParentRemoved by converting it to an iterative procedure 6 | -------------------------------------------------------------------------------- /.changeset/large-ants-prove.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | 'rrweb': patch 4 | --- 5 | 6 | Fix: Make relative path detection in stylesheet URLs to detect more types of URL protocols when inlining stylesheets. 7 | -------------------------------------------------------------------------------- /.changeset/lazy-squids-draw.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | export the canvasMutation function 6 | -------------------------------------------------------------------------------- /.changeset/lazy-toes-confess.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrvideo': patch 3 | --- 4 | 5 | Refactor: Improve the video quality and add a progress bar for the CLI tool 6 | -------------------------------------------------------------------------------- /.changeset/lemon-lamps-switch.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | export eventWithTime for consumption by typescript code 6 | -------------------------------------------------------------------------------- /.changeset/light-fireants-exercise.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/little-moons-camp.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb-snapshot": minor 3 | "rrweb": minor 4 | --- 5 | 6 | feat: Better masking of option/radio/checkbox values 7 | -------------------------------------------------------------------------------- /.changeset/little-radios-thank.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Guard against redefinition of Date.now by third party libraries which are also present on a page alongside rrweb 6 | -------------------------------------------------------------------------------- /.changeset/little-suits-leave.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': minor 3 | '@rrweb/types': minor 4 | --- 5 | 6 | click events now include a `.pointerType` attribute which distinguishes between ['pen', 'mouse' and 'touch' events](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType). There is no new PenDown/PenUp events, but these can be detected with a MouseDown/MouseUp + pointerType=pen 7 | -------------------------------------------------------------------------------- /.changeset/loud-seals-raise.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | 'rrweb': patch 4 | --- 5 | 6 | Fix duplicated shadow doms 7 | -------------------------------------------------------------------------------- /.changeset/lovely-pears-cross.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@rrweb/web-extension': patch 3 | --- 4 | 5 | Add rrweb browser extension 6 | -------------------------------------------------------------------------------- /.changeset/lovely-students-boil.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | 'rrweb': patch 4 | --- 5 | 6 | fix: Ensure attributes are lowercased when checking 7 | -------------------------------------------------------------------------------- /.changeset/mean-tips-impress.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | perf: optimize the performance of record in processMutation phase 6 | -------------------------------------------------------------------------------- /.changeset/mighty-bulldogs-begin.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@rrweb/web-extension': patch 3 | --- 4 | 5 | Update `vite.config.ts` to account for all potential entry types. 6 | -------------------------------------------------------------------------------- /.changeset/mighty-frogs-sparkle.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/modern-doors-watch.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | --- 4 | 5 | better nested css selector splitting when commas or brackets happen to be in quoted text 6 | -------------------------------------------------------------------------------- /.changeset/moody-dots-refuse.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | use WeakMap for faster attributeCursor lookup while processing attribute mutations 6 | -------------------------------------------------------------------------------- /.changeset/nervous-buses-pump.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | fix: Resize and MediaInteraction events repeat generated after the iframe appeared 6 | -------------------------------------------------------------------------------- /.changeset/nervous-kiwis-nail.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | 'rrweb': patch 4 | --- 5 | 6 | Bugfix after #1434 perf improvements: fix that blob urls persist on the shared anchor element and can't be later modified 7 | -------------------------------------------------------------------------------- /.changeset/nervous-mirrors-perform.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | 'rrweb': patch 4 | --- 5 | 6 | Perf: Avoid creation of intermediary array when iterating over style rules 7 | -------------------------------------------------------------------------------- /.changeset/nervous-poets-grin.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrdom': patch 3 | 'rrdom-nodejs': patch 4 | 'rrweb': patch 5 | 'rrweb-player': patch 6 | 'rrweb-snapshot': patch 7 | --- 8 | 9 | Refactor all suffix of bundled scripts with commonjs module from 'js' to cjs [#1087](https://github.com/rrweb-io/rrweb/pull/1087). 10 | -------------------------------------------------------------------------------- /.changeset/nervous-tables-travel.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': minor 3 | 'rrweb': minor 4 | --- 5 | 6 | feat: Extends maskInputFn to pass the HTMLElement to the deciding function 7 | -------------------------------------------------------------------------------- /.changeset/new-snakes-call.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': minor 3 | 'rrweb': minor 4 | --- 5 | 6 | feat: Ensure password inputs remain masked when switching input type 7 | -------------------------------------------------------------------------------- /.changeset/nice-pugs-reply.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | fix: Ensure getting the type of inputs works 6 | -------------------------------------------------------------------------------- /.changeset/no-neg-lookbehind.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb-snapshot": patch 3 | "rrweb": patch 4 | --- 5 | 6 | Replay: Replace negative lookbehind in regexes from css parser as it causes issues with Safari 16 7 | -------------------------------------------------------------------------------- /.changeset/old-dryers-hide.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': minor 3 | --- 4 | 5 | feat: Ignore `autoplay` attribute on video/audio elements 6 | -------------------------------------------------------------------------------- /.changeset/polite-olives-wave.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Add 'recordDOM' config option to turn off recording of DOM (making recordings unreplayable). Specialist use case e.g. only heatmap click/scroll recording 6 | -------------------------------------------------------------------------------- /.changeset/pretty-plums-rescue.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': minor 3 | --- 4 | 5 | feat: Allow to pass `errorHandler` as record option 6 | -------------------------------------------------------------------------------- /.changeset/pretty-schools-remember.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | --- 4 | 5 | Improve: Add try catch to snapshot.ts 's masking text function. Fixes [#1118](https://github.com/rrweb-io/rrweb/issues/1118). 6 | -------------------------------------------------------------------------------- /.changeset/proud-clocks-hope.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb-snapshot": patch 3 | --- 4 | 5 | (when `recordCanvas: true`): ensure we use doc.createElement instead of document.createElement to allow use in non-browser e.g. jsdom environments 6 | -------------------------------------------------------------------------------- /.changeset/proud-experts-jam.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | For a mutation which removes a node, reduce the number of spurious warnings to take into account that an anscestor (rather than just a parent) may have been just removed 6 | -------------------------------------------------------------------------------- /.changeset/purple-carrots-film.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb": patch 3 | --- 4 | 5 | Fix: some nested cross-origin iframes can't be recorded 6 | -------------------------------------------------------------------------------- /.changeset/real-masks-explode.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrdom': patch 3 | --- 4 | 5 | Fix: If RRNode appends a single child twice, children of the node will become an infinite link list. 6 | -------------------------------------------------------------------------------- /.changeset/real-trains-switch.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrdom': patch 3 | 'rrweb': patch 4 | --- 5 | 6 | Fix: improve rrdom robustness [#1091](https://github.com/rrweb-io/rrweb/pull/1091). 7 | -------------------------------------------------------------------------------- /.changeset/rich-crews-protect.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/rich-dots-lay.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | --- 4 | 5 | Fix css parsing errors 6 | -------------------------------------------------------------------------------- /.changeset/rich-jars-remember.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | --- 4 | 5 | Add workaround for Chrome/Edge CSS `@import` escaping bug: https://bugs.chromium.org/p/chromium/issues/detail?id=1472259 6 | -------------------------------------------------------------------------------- /.changeset/serious-ants-juggle.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrdom': major 3 | 'rrdom-nodejs': major 4 | 'rrweb': patch 5 | --- 6 | 7 | Refactor: Improve performance by 80% in a super large benchmark case. 8 | 9 | 1. Refactor: change the data structure of childNodes from array to linked list 10 | 2. Improve the performance of the "contains" function. New algorithm will reduce the complexity from O(n) to O(logn) 11 | -------------------------------------------------------------------------------- /.changeset/serious-eggs-greet.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/shadow-dom-unbusify.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb": patch 3 | --- 4 | 5 | Refactor to preclude the need for a continuous raf loop running in the background which is related to shadowDom 6 | -------------------------------------------------------------------------------- /.changeset/silent-plants-perform.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb": patch 3 | --- 4 | 5 | Return early for child same origin frames 6 | -------------------------------------------------------------------------------- /.changeset/silver-windows-float.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/sixty-impalas-laugh.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Only apply touch-active styling on flush 6 | -------------------------------------------------------------------------------- /.changeset/small-olives-arrive.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | --- 4 | 5 | Fix: [#1172](https://github.com/rrweb-io/rrweb/issues/1172) don't replace original onload function of Images 6 | -------------------------------------------------------------------------------- /.changeset/smart-ears-refuse.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | --- 4 | 5 | Feat: Add 'isCustom' flag to serialized elements. 6 | 7 | This flag is used to indicate whether the element is a custom element or not. This is useful for replaying the :defined pseudo-class of custom elements. 8 | -------------------------------------------------------------------------------- /.changeset/smooth-papayas-boil.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | 'rrweb': patch 4 | --- 5 | 6 | Capture stylesheets designated as `rel="preload"` 7 | -------------------------------------------------------------------------------- /.changeset/smooth-poems-bake.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-player': patch 3 | --- 4 | 5 | Fix `player.getMirror`, `player.playRange`, `player.$set` types in rrwebPlayer 6 | -------------------------------------------------------------------------------- /.changeset/spotty-bees-destroy.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrdom': patch 3 | --- 4 | 5 | fix: scrolling may not be applied when fast-forwarding 6 | -------------------------------------------------------------------------------- /.changeset/swift-dancers-rest.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': minor 3 | 'rrweb': minor 4 | --- 5 | 6 | Extends maskTextFn to pass the HTMLElement to the deciding function 7 | -------------------------------------------------------------------------------- /.changeset/swift-peas-film.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | 'rrweb': patch 4 | --- 5 | 6 | fix: Explicitly handle `null` attribute values 7 | -------------------------------------------------------------------------------- /.changeset/thirty-baboons-punch.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb-snapshot': patch 3 | --- 4 | 5 | Fix CSS rules captured in Safari 6 | -------------------------------------------------------------------------------- /.changeset/three-baboons-bow.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | fix: protect against missing parentNode 6 | -------------------------------------------------------------------------------- /.changeset/tidy-swans-repair.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@rrweb/web-extension': patch 3 | --- 4 | 5 | 🎈 perf(web-extension): conditional check in Player component 6 | -------------------------------------------------------------------------------- /.changeset/tidy-yaks-joke.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Fix: outdated ':hover' styles can't be removed from iframes or shadow doms 6 | -------------------------------------------------------------------------------- /.changeset/tiny-buckets-love.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Handle case where `event` is null/undefined 6 | -------------------------------------------------------------------------------- /.changeset/tiny-candles-whisper.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@rrweb/web-extension': patch 3 | --- 4 | 5 | 🐞 fix(web-extension): typo 6 | -------------------------------------------------------------------------------- /.changeset/tiny-chairs-build.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Canvas FPS recording: override `preserveDrawingBuffer: true` on canvas creation. 6 | Canvas replay: fix flickering canvas elemenrs. 7 | Canvas FPS recording: fix bug that wipes webgl(2) canvas backgrounds while recording. 8 | -------------------------------------------------------------------------------- /.changeset/tricky-panthers-guess.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrvideo': patch 3 | --- 4 | 5 | Refactor: Move rrvideo to rrweb's monorepo 6 | -------------------------------------------------------------------------------- /.changeset/twenty-goats-kneel.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/twenty-planets-repeat.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | -------------------------------------------------------------------------------- /.changeset/twenty-tables-call.md: -------------------------------------------------------------------------------- 1 | --- 2 | "rrweb-snapshot": patch 3 | "rrweb": patch 4 | --- 5 | 6 | Add `maskAttributesFn` to be called when transforming an attribute. This is typically used to determine if an attribute should be masked or not. 7 | -------------------------------------------------------------------------------- /.changeset/violet-melons-itch.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | fix: Recursive logging bug with console recording 6 | -------------------------------------------------------------------------------- /.changeset/violet-zebras-cry.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Trigger mouse movement and hover with mouse up and mouse down events when replayer.pause(...) is called. 6 | -------------------------------------------------------------------------------- /.changeset/wise-spiders-jog.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@rrweb/types': patch 3 | --- 4 | 5 | Fix type error when using `"moduleResolution": "NodeNext"`. 6 | -------------------------------------------------------------------------------- /.changeset/witty-kids-talk.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@rrweb/web-extension': patch 3 | --- 4 | 5 | 🐞 fix(web-extension): beforeunload logic 6 | -------------------------------------------------------------------------------- /.changeset/yellow-mails-cheat.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | Fix: some websites rebuild imcomplete 6 | 7 | 1. Some websites, addedSet in emit function is not empty, but the result converted from Array.from is empty. 8 | 2. Some websites polyfill classList functions of HTML elements. Their implementation may throw errors and cause the snapshot to fail. I add try-catch statements to make the code robust. 9 | -------------------------------------------------------------------------------- /.changeset/young-timers-grow.md: -------------------------------------------------------------------------------- 1 | --- 2 | 'rrweb': patch 3 | --- 4 | 5 | For users of userTriggeredOnInput setting: also set userTriggered to false on Input attribute modifications; this was previously empty this variant of IncrementalSource.Input 6 | -------------------------------------------------------------------------------- /.craft.yml: -------------------------------------------------------------------------------- 1 | github: 2 | owner: getsentry 3 | repo: rrweb 4 | changelogPolicy: auto 5 | preReleaseCommand: bash scripts/craft-pre-release.sh 6 | requireNames: 7 | - /^sentry-internal-rrweb-snapshot-.*\.tgz$/ 8 | - /^sentry-internal-rrweb-player-.*\.tgz$/ 9 | - /^sentry-internal-rrweb-.*\.tgz$/ 10 | - /^sentry-internal-rrdom-.*\.tgz$/ 11 | targets: 12 | - name: github 13 | includeNames: /^sentry-.*.tgz$/ 14 | - name: npm 15 | includeNames: /^sentry-.*.tgz$/ 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # initialized from https://prettier.io/docs/en/configuration.html#editorconfig 4 | [*] 5 | charset = utf-8 6 | insert_final_newline = true 7 | end_of_line = lf 8 | indent_style = space 9 | indent_size = 2 10 | max_line_length = 80 11 | quote_type = single 12 | 13 | [.changeset/*.md] 14 | quote_type = double 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /dist 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // TODO: add .eslintignore. More info: https://bobbyhadz.com/blog/typescript-parseroptions-project-has-been-set-for 2 | module.exports = { 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | node: true, 7 | 'jest/globals': true, 8 | }, 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 13 | 'plugin:compat/recommended', 14 | ], 15 | parser: '@typescript-eslint/parser', 16 | parserOptions: { 17 | ecmaVersion: 'latest', 18 | sourceType: 'module', 19 | tsconfigRootDir: __dirname, 20 | project: ['./tsconfig.eslint.json', './packages/**/tsconfig.json'], 21 | }, 22 | plugins: ['@typescript-eslint', 'eslint-plugin-tsdoc', 'jest', 'compat'], 23 | rules: { 24 | 'tsdoc/syntax': 'warn', 25 | '@typescript-eslint/prefer-as-const': 'warn', 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/releases/** binary 2 | /.yarn/plugins/** binary -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Yuyz0112] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: rrweb 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | # Comment to be posted to on PRs from first time contributors in your repository 2 | newPRWelcomeComment: | 3 | 💖 Thanks for opening this pull request! 💖 4 | 5 | Things that will help get your PR across the finish line: 6 | 7 | - Follow the TypeScript [coding style](https://github.com/rrweb-io/rrweb/blob/master/docs/development/coding-style.md). 8 | - Run `yarn lint` locally to catch formatting errors earlier. 9 | - Document any user-facing changes you've made following the [documentation styleguide](https://github.com/rrweb-io/rrweb/blob/master/blob/main/docs/styleguide.md). 10 | - Include tests when adding/changing behavior. 11 | - Include screenshots and animated GIFs whenever possible. 12 | 13 | We get a lot of pull requests on this repo, so please be patient and we will get back to you as soon as we can. 14 | 15 | # Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge 16 | 17 | # Comment to be posted to on pull requests merged by a first time user 18 | firstPRMergeComment: > 19 | Congrats on merging your first pull request! 🎉🎉🎉Hallo 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Prepare Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: Version to release 7 | required: true 8 | force: 9 | description: Force a release even when there are release-blockers (optional) 10 | required: false 11 | merge_target: 12 | description: Target branch to merge into. Uses the default branch (sentry-v2) as a fallback (optional) 13 | required: false 14 | jobs: 15 | release: 16 | runs-on: ubuntu-latest 17 | name: 'Release a new version' 18 | steps: 19 | - name: Get auth token 20 | id: token 21 | uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 22 | with: 23 | app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} 24 | private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} 25 | - uses: actions/checkout@v3 26 | with: 27 | token: ${{ steps.token.outputs.token }} 28 | fetch-depth: 0 29 | - name: Prepare release 30 | uses: getsentry/action-prepare-release@v1 31 | env: 32 | GITHUB_TOKEN: ${{ steps.token.outputs.token }} 33 | with: 34 | version: ${{ github.event.inputs.version }} 35 | force: ${{ github.event.inputs.force }} 36 | merge_target: ${{ github.event.inputs.merge_target }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | !/.vscode/rrweb-monorepo.code-workspace 3 | .idea 4 | node_modules 5 | package-lock.json 6 | tsconfig.tsbuildinfo 7 | 8 | temp 9 | 10 | *.log 11 | 12 | .env 13 | 14 | .DS_Store 15 | 16 | # output of `yarn build` 17 | build 18 | dist 19 | 20 | # turbo cache 21 | .turbo 22 | 23 | # needed to store puppeteer binaries 24 | .cache/* 25 | !.gitkeep 26 | 27 | # emacs working files end in a tilde 28 | *~ 29 | 30 | # `.yarn/install-state.gz` is an optimization file that you shouldn't ever have to commit. 31 | # 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. 32 | .yarn/install-state.gz 33 | 34 | # TBD if we need these still, probably for 3.8 35 | typings 36 | typings-ts3.8 37 | dist-ts3.8 38 | 39 | # for vite 40 | vite.config.js.timestamp-* 41 | vite.config.ts.timestamp-* 42 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | node_modules 4 | package-lock.json 5 | yarn.lock 6 | temp 7 | *.log 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all" 3 | } 4 | -------------------------------------------------------------------------------- /.puppeteerrc.cjs: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | /** 4 | * @type {import("puppeteer").Configuration} 5 | */ 6 | module.exports = { 7 | // Changes the cache location for Puppeteer. 8 | cacheDirectory: join(__dirname, '.cache', 'puppeteer'), 9 | browserRevision: '115.0.5763.0', 10 | }; 11 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": {}, 3 | "git": { 4 | "commit": false, 5 | "tag": false, 6 | "push": false 7 | }, 8 | "npm": { 9 | "publish": false 10 | }, 11 | "github": { 12 | "release": true, 13 | "releaseName": "Release ${version}" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.size-limit.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | // Main browser webpack builds 3 | { 4 | name: 'rrweb - record only (gzipped)', 5 | path: 'packages/rrweb/es/rrweb/packages/rrweb/src/entries/all.js', 6 | import: '{ record }', 7 | gzip: true, 8 | }, 9 | { 10 | name: 'rrweb - record & CanvasManager only (gzipped)', 11 | path: 'packages/rrweb/es/rrweb/packages/rrweb/src/entries/all.js', 12 | import: '{ record, CanvasManager }', 13 | gzip: true, 14 | }, 15 | { 16 | name: 'rrweb - record only (min)', 17 | path: 'packages/rrweb/es/rrweb/packages/rrweb/src/entries/all.js', 18 | import: '{ record }', 19 | gzip: false, 20 | }, 21 | { 22 | name: 'rrweb - record with treeshaking flags (gzipped)', 23 | path: 'packages/rrweb/es/rrweb/packages/rrweb/src/entries/all.js', 24 | import: '{ record }', 25 | gzip: true, 26 | modifyWebpackConfig: function (config) { 27 | const webpack = require('webpack'); 28 | config.plugins.push( 29 | new webpack.DefinePlugin({ 30 | __RRWEB_EXCLUDE_SHADOW_DOM__: true, 31 | __RRWEB_EXCLUDE_IFRAME__: true, 32 | }), 33 | ); 34 | return config; 35 | }, 36 | }, 37 | { 38 | name: 'rrweb - Replayer', 39 | path: 'packages/rrweb/es/rrweb/packages/rrweb/src/entries/all.js', 40 | import: '{ Replayer }', 41 | gzip: true, 42 | }, 43 | ]; 44 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | yarnPath: '.yarn/releases/yarn-1.23.0-20220130.1630.cjs' 2 | -------------------------------------------------------------------------------- /docs/recipes/canvas.md: -------------------------------------------------------------------------------- 1 | # Canvas 2 | 3 | Canvas is a special HTML element, and will not be recorded by rrweb by default. 4 | There are some options for recording and replaying Canvas. 5 | 6 | Enable recording Canvas: 7 | 8 | ```js 9 | rrweb.record({ 10 | emit(event) {}, 11 | recordCanvas: true, 12 | }); 13 | ``` 14 | 15 | Alternatively enable image snapshot recording of Canvas at a maximum of 15 frames per second: 16 | 17 | ```js 18 | rrweb.record({ 19 | emit(event) {}, 20 | recordCanvas: true, 21 | sampling: { 22 | canvas: 15, 23 | }, 24 | // optional image format settings 25 | dataURLOptions: { 26 | type: 'image/webp', 27 | quality: 0.6, 28 | }, 29 | }); 30 | ``` 31 | 32 | Enable replaying Canvas: 33 | 34 | ```js 35 | const replayer = new rrweb.Replayer(events, { 36 | UNSAFE_replayCanvas: true, 37 | }); 38 | replayer.play(); 39 | ``` 40 | 41 | **Enable replaying Canvas will remove the sandbox, which may cause a potential security issue.** 42 | 43 | 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. 44 | For more information see [canvas-webrtc documentation](../../packages/plugins/rrweb-plugin-canvas-webrtc-record/Readme.md) 45 | -------------------------------------------------------------------------------- /docs/recipes/canvas.zh_CN.md: -------------------------------------------------------------------------------- 1 | # Canvas 2 | 3 | Canvas 是一种特殊的 HTML 元素,默认情况下其内容不会被 rrweb 观测。我们可以通过特定的配置让 rrweb 能够录制并回放 Canvas。 4 | 5 | 录制时包含 Canvas 内的内容: 6 | 7 | ```js 8 | rrweb.record({ 9 | emit(event) {}, 10 | // 对 canvas 进行录制 11 | recordCanvas: true, 12 | }); 13 | ``` 14 | 15 | 或者启用每秒 15 帧的 Canvas 图像快照记录: 16 | 17 | ```js 18 | rrweb.record({ 19 | emit(event) {}, 20 | recordCanvas: true, 21 | sampling: { 22 | canvas: 15, 23 | }, 24 | // 图像的格式 25 | dataURLOptions: { 26 | type: 'image/webp', 27 | quality: 0.6, 28 | }, 29 | }); 30 | ``` 31 | 32 | 回放时对 Canvas 进行回放: 33 | 34 | ```js 35 | const replayer = new rrweb.Replayer(events, { 36 | UNSAFE_replayCanvas: true, 37 | }); 38 | replayer.play(); 39 | ``` 40 | 41 | **回放 Canvas 将会关闭沙盒策略,导致一定风险**。 42 | 43 | 另外,您可以使用 [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 元素。 44 | 有关更多信息,请参考 [canvas-webrtc 文档](../../packages/plugins/rrweb-plugin-canvas-webrtc-record/Readme.md)。 45 | -------------------------------------------------------------------------------- /docs/recipes/custom-event.md: -------------------------------------------------------------------------------- 1 | # Custom Event 2 | 3 | You may need to record some custom events along with the rrweb events, and let them be played as other events. The custom event API was designed for this. 4 | 5 | After starting the recording, we can call the `record.addCustomEvent` API to add a custom event. 6 | 7 | ```js 8 | // start recording 9 | rrweb.record({ 10 | emit(event) { 11 | ... 12 | } 13 | }) 14 | 15 | // record some custom events at any time 16 | rrweb.record.addCustomEvent('submit-form', { 17 | name: 'Adam', 18 | age: 18 19 | }) 20 | rrweb.record.addCustomEvent('some-error', { 21 | error 22 | }) 23 | ``` 24 | 25 | `addCustomEvent` accepts two parameters. The first one is a string-type `tag`, while the second one is an any-type `payload`. 26 | 27 | During the replay, we can add an event listener to custom events, or configure the style of custom events in rrweb-player's timeline. 28 | 29 | **Listen to custom events** 30 | 31 | ```js 32 | const replayer = new rrweb.Replayer(events); 33 | 34 | replayer.on('custom-event', (event) => { 35 | console.log(event.tag, event.payload); 36 | }); 37 | ``` 38 | 39 | **Display in rrweb-player** 40 | 41 | ```js 42 | new rrwebPlayer({ 43 | target: document.body, 44 | props: { 45 | events, 46 | // configure the color of tag which will be displayed on the timeline 47 | tags: { 48 | 'submit-form': '#21e676', 49 | 'some-error': 'red', 50 | }, 51 | }, 52 | }); 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/recipes/custom-event.zh_CN.md: -------------------------------------------------------------------------------- 1 | # 自定义事件 2 | 3 | 录制时可能需要在特定的时间点记录一些特定含义的数据,如果希望这部分数据作为回放时的一部分,则可以通过自定义事件的方式实现。 4 | 5 | 开始录制后,我们就可以通过 `record.addCustomEvent` API 添加自定义事件: 6 | 7 | ```js 8 | // 开始录制 9 | rrweb.record({ 10 | emit(event) { 11 | ... 12 | } 13 | }) 14 | 15 | // 在开始录制后的任意时间点记录自定义事件,例如: 16 | rrweb.record.addCustomEvent('submit-form', { 17 | name: '姓名', 18 | age: 18 19 | }) 20 | rrweb.record.addCustomEvent('some-error', { 21 | error 22 | }) 23 | ``` 24 | 25 | `addCustomEvent` 接收两个参数,第一个是字符串类型的 `tag`,第二个是任意类型的 `payload`。 26 | 27 | 在回放时我们可以通过监听事件获取对应的事件,也可以通过配置 rrweb-player 在回放器 UI 的时间轴中展示对应事件。 28 | 29 | **获取对应事件** 30 | 31 | ```js 32 | const replayer = new rrweb.Replayer(events); 33 | 34 | replayer.on('custom-event', (event) => { 35 | console.log(event.tag, event.payload); 36 | }); 37 | ``` 38 | 39 | **在 rrweb-player 中展示** 40 | 41 | ```js 42 | new rrwebPlayer({ 43 | target: document.body, 44 | props: { 45 | events, 46 | // 自定义各个 tag 在时间轴上的色值 47 | tags: { 48 | 'submit-form': '#21e676', 49 | 'some-error': 'red', 50 | }, 51 | }, 52 | }); 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/recipes/export-to-video.md: -------------------------------------------------------------------------------- 1 | # Convert To Video 2 | 3 | The event data recorded by rrweb is a performant, easy to compress, text-based format. And the replay is also pixel perfect. 4 | 5 | But if you really need to convert it into a video format, there are some tools that can do this work. 6 | 7 | Use [rrvideo](https://github.com/rrweb-io/rrweb/blob/master/packages/rrvideo/README.md). 8 | -------------------------------------------------------------------------------- /docs/recipes/export-to-video.zh_CN.md: -------------------------------------------------------------------------------- 1 | # 转换为视频 2 | 3 | rrweb 录制的数据是一种高效、易于压缩的文本格式,可以用于像素级的回放。但如果有进一步将录制数据转换为视频的需求,同样可以通过一些工具实现。 4 | 5 | 使用 [rrvideo](https://github.com/rrweb-io/rrweb/blob/master/packages/rrvideo/README.zh_CN.md)。 6 | -------------------------------------------------------------------------------- /docs/recipes/index.zh_CN.md: -------------------------------------------------------------------------------- 1 | # 场景示例 2 | 3 | > 除场景示例外,你可能还想通过[使用指南](../../guide.zh_CN.md)掌握 rrweb 常用 API,或是通过[设计文档](../)深入 rrweb 的技术细节。 4 | 5 | ## 场景列表 6 | 7 | ### 录制与回放 8 | 9 | 录制与回放是最常用的使用方式,适用于任何需要采集用户行为数据并重新查看的场景。 10 | 11 | [链接](./record-and-replay.zh_CN.md) 12 | 13 | ### 深入录制数据 14 | 15 | 录制数据是一组类型严格的 JSON 数据,通过熟悉其格式,可以更灵活的使用录制数据。 16 | 17 | [链接](./dive-into-event.zh_CN.md) 18 | 19 | ### 异步加载数据 20 | 21 | 当录制的数据较多时,一次性加载至回放页面可能带来较大的网络开销和较长的等待时间。这时可以采取数据分页的方式,异步地加载数据并回放。 22 | 23 | [链接](./pagination.zh_CN.md) 24 | 25 | ### 实时回放(直播) 26 | 27 | 如果希望持续、实时地看到录制的数据,达到类似直播的效果,则可以使用实时回放 API。这个方式也适用于一些实时协同的场景。 28 | 29 | [链接](./live-mode.zh_CN.md) 30 | 31 | ### 自定义事件 32 | 33 | 录制时可能需要在特定的时间点记录一些特定含义的数据,如果希望这部分数据作为回放时的一部分,则可以通过自定义事件的方式实现。 34 | 35 | [链接](./custom-event.zh_CN.md) 36 | 37 | ### 回放时与 UI 交互 38 | 39 | 回放时的 UI 默认不可交互,但在特定场景下也可以通过 API 允许用户与回放场景进行交互。 40 | 41 | [链接](./interaction.zh_CN.md) 42 | 43 | ### 自定义回放 UI 44 | 45 | 当 rrweb Replayer 和 rrweb-player 的 UI 不能满足需求时,可以通过自定义回放 UI 制作属于你自己的回放器。 46 | 47 | [链接](./customize-replayer.zh_CN.md) 48 | 49 | ### 转换为视频 50 | 51 | rrweb 录制的数据是一种高效、易于压缩的文本格式,可以用于像素级的回放。但如果有进一步将录制数据转换为视频的需求,同样可以通过一些工具实现。 52 | 53 | [链接](./export-to-video.zh_CN.md) 54 | 55 | ### 优化存储容量 56 | 57 | 在一些场景下 rrweb 的录制数据量可能高于你的预期,这部分文档可以帮助你选择适用于你的存储优化策略。 58 | 59 | [链接](./optimize-storage.zh_CN.md) 60 | 61 | ### Canvas 62 | 63 | Canvas 是一种特殊的 HTML 元素,默认情况下其内容不会被 rrweb 观测。我们可以通过特定的配置让 rrweb 能够录制并回放 Canvas。 64 | 65 | [链接](./canvas.zh_CN.md) 66 | 67 | ### console 录制和播放 68 | 69 | 从 v1.0.0 版本开始,我们以插件的形式增加了录制和播放控制台输出的功能。这个功能旨在为开发者提供更多的 bug 信息。对这项功能我们还提供了一些设置选项。 70 | 71 | [链接](./console.zh_CN.md) 72 | 73 | ### 插件 74 | 75 | 插件 API 的设计目标是在不增加 rrweb 核心部分大小和复杂性的前提下,扩展 rrweb 的功能。 76 | 77 | [链接](./plugin.zh_CN.md) 78 | -------------------------------------------------------------------------------- /docs/recipes/interaction.md: -------------------------------------------------------------------------------- 1 | # Interact With UI During Replay 2 | 3 | By default, the UI could not interact during replay. But you can use API to enable/disable this programmatically. 4 | 5 | ```js 6 | const replayer = new rrweb.Replayer(events); 7 | 8 | // enable user interact with the UI 9 | replayer.enableInteract(); 10 | 11 | // disable user interact with the UI 12 | replayer.disableInteract(); 13 | ``` 14 | 15 | rrweb uses the `pointer-events: none` CSS property to disable interaction. 16 | 17 | This will let the replay more stable and avoid some problems like navigate by clicking an external link. 18 | 19 | If you want to enable user interaction, like input, then you can use the `enableInteract` API. But be sure you have handled the problems that may cause unstable replay. 20 | -------------------------------------------------------------------------------- /docs/recipes/interaction.zh_CN.md: -------------------------------------------------------------------------------- 1 | # 回放时与 UI 交互 2 | 3 | 回放时的 UI 默认不可交互,但在特定场景下也可以通过 API 允许用户与回放场景进行交互。 4 | 5 | ```js 6 | const replayer = new rrweb.Replayer(events); 7 | 8 | // 允许用户在回放的 UI 中进行交互 9 | replayer.enableInteract(); 10 | 11 | // 禁用用户在回放的 UI 中进行交互 12 | replayer.disableInteract(); 13 | ``` 14 | 15 | rrweb 使用 CSS 的 `pointer-events: none` 属性禁用交互。 16 | 17 | 这能够让回放更加稳定,例如避免用户点击回放中的超链接发生跳转等。 18 | 19 | 如果你希望允许用户交互,例如用户可以在回放时在输入框中进行输入,那么就可以调用 `enableInteract` API,但需要对不稳定的场景自行加以处理。 20 | -------------------------------------------------------------------------------- /docs/recipes/live-mode.zh_CN.md: -------------------------------------------------------------------------------- 1 | # 实时回放(直播) 2 | 3 | 如果希望持续、实时地看到录制的数据,达到类似直播的效果,则可以使用实时回放 API。这个方式也适用于一些实时协同的场景。 4 | 5 | 使用 rrweb Replayer 进行实时回放时,需要传入 `liveMode: true` 配置,并通过 `startLive` API 启动直播模式。 6 | 7 | ```js 8 | const replayer = new rrweb.Replayer([], { 9 | liveMode: true, 10 | }); 11 | 12 | replayer.startLive(FIRST_EVENT.timestamp - BUFFER); 13 | ``` 14 | 15 | 使用 `startLive` API 启动直播模式时,你可以传入一个可选参数,用于设置基线时间戳,这对于需要一定缓冲时间的直播场景非常有用。 16 | 17 | 例如录制时的第一个事件记录于 1500 这个时间点,实时回放时传入 `startLive(1500)` 就会让回放器将基线时间戳定为 1500,并用于计算后续事件的延迟时间。 18 | 19 | 但这有时会让实时回放看起来卡顿,因为数据的传输需要一定的时间(例如网络延迟),同时一些事件因为节流的性能优化会延迟发出(例如鼠标移动)。 20 | 21 | 因此我们可以通过 `startLive` 传入一个较小值的方式来提供一个缓冲时间,例如 `startLive(500)` 就会让回放总是延迟 1 秒播放。如果传输延迟小于 1 秒,则观看者不会感到卡顿。 22 | 23 | 启动直播模式后,可以通过 `addEvent` API 不断将最新的事件传入回放器中: 24 | 25 | ```js 26 | replayer.addEvent(NEW_EVENT); 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/recipes/pagination.md: -------------------------------------------------------------------------------- 1 | # Load Events Asynchronous 2 | 3 | When the size of the recorded events increased, load them in one request is not performant. You can paginate the events and load them as you need. 4 | 5 | rrweb's API for loading async events is quite simple: 6 | 7 | ```js 8 | const replayer = new rrweb.Replayer(events); 9 | 10 | replayer.addEvent(NEW_EVENT); 11 | ``` 12 | 13 | When calling the `addEvent` API to add a new event, rrweb will resolve its timestamp and replay it as need. 14 | 15 | If you need to load several events, you can do a loop like this: 16 | 17 | ```js 18 | const replayer = new rrweb.Replayer(events); 19 | 20 | for (const event of NEW_EVENTS) { 21 | replayer.addEvent(event); 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/recipes/pagination.zh_CN.md: -------------------------------------------------------------------------------- 1 | # 异步加载数据 2 | 3 | 当录制的数据较多时,一次性加载至回放页面可能带来较大的网络开销和较长的等待时间。这时可以采取数据分页的方式,异步地加载数据并回放。 4 | 5 | rrweb 中用于实现异步加载数据的 API 非常简单直观: 6 | 7 | ```js 8 | const replayer = new rrweb.Replayer(events); 9 | 10 | replayer.addEvent(NEW_EVENT); 11 | ``` 12 | 13 | 只需要调用 `addEvent` 传入新的数据,rrweb 就会自动处理其中的时间关系,以最恰当的方式进行回放。 14 | 15 | 如果需要异步加载多个数据,只需这样使用: 16 | 17 | ```js 18 | const replayer = new rrweb.Replayer(events); 19 | 20 | for (const event of NEW_EVENTS) { 21 | replayer.addEvent(event); 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/recipes/record-and-replay.md: -------------------------------------------------------------------------------- 1 | # Record And Replay 2 | 3 | Record and Replay is the most common use case, which is suitable for any scenario that needs to collect user behaviors and replay them. 4 | 5 | You only need a simple API call to record the website: 6 | 7 | ```js 8 | const stopFn = rrweb.record({ 9 | emit(event) { 10 | // save the event 11 | }, 12 | }); 13 | ``` 14 | 15 | You can use any approach to store the recorded events, like sending the events to your backend and save them into the database. 16 | 17 | But you should guarantee: 18 | 19 | - a set of events are sorted by its timestamp 20 | - save every event 21 | 22 | You can use the `stopFn` to stop the recording. 23 | 24 | The replay is also as simple as putting events into rrweb's Replayer. 25 | 26 | ```js 27 | const events = GET_YOUR_EVENTS; 28 | 29 | const replayer = new rrweb.Replayer(events); 30 | replayer.play(); 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/recipes/record-and-replay.zh_CN.md: -------------------------------------------------------------------------------- 1 | # 录制与回放 2 | 3 | 录制与回放是最常用的使用方式,适用于任何需要采集用户行为数据并重新查看的场景。 4 | 5 | 仅需一个函数调用就可以录制当前页面: 6 | 7 | ```js 8 | const stopFn = rrweb.record({ 9 | emit(event) { 10 | // 保存获取到的 event 数据 11 | }, 12 | }); 13 | ``` 14 | 15 | 你可以使用任何方式保存录制的数据,例如通过网络请求将数据传入至后端持久化保存,但请确保: 16 | 17 | - 一组录制的数据按照 event.timestamp 中的时间戳从小至大保存 18 | - 完整保存数据,不缺失任何一个 event。 19 | 20 | 如果需要手动停止录制,可以调用返回的 `stopFn` 函数。 21 | 22 | 回放时只需要获取一段录制数据,并传入 rrweb 提供的 Replayer: 23 | 24 | ```js 25 | const events = GET_YOUR_EVENTS; 26 | 27 | const replayer = new rrweb.Replayer(events); 28 | replayer.play(); 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/replay.zh_CN.md: -------------------------------------------------------------------------------- 1 | # 回放 2 | 3 | rrweb 的设计原则是尽量少的在录制端进行处理,最大程度减少对被录制页面的影响,因此在回放端我们需要做一些特殊的处理。 4 | 5 | ## 高精度计时器 6 | 7 | 在回放时我们会一次性拿到完整的快照链,如果将所有快照依次同步执行我们可以直接获取被录制页面最后的状态,但是我们需要的是同步初始化第一个全量快照,再异步地按照正确的时间间隔依次重放每一个增量快照,这就需要一个高精度的计时器。 8 | 9 | 之所以强调**高精度**,是因为原生的 `setTimeout` 并不能保证在设置的延迟时间之后准确执行,例如主线程阻塞时就会被推迟。 10 | 11 | 对于我们的回放功能而言,这种不确定的推迟是不可接受的,可能会导致各种怪异现象的发生,因此我们通过 `requestAnimationFrame` 来实现一个不断校准的定时器,确保绝大部分情况下增量快照的重放延迟不超过一帧。 12 | 13 | 同时自定义的计时器也是我们实现“快进”功能的基础。 14 | 15 | ## 补全缺失节点 16 | 17 | 在[增量快照设计](./observer.zh_CN.md)中提到了 rrweb 使用 MutationObserver 时的延迟序列化策略,这一策略可能导致以下场景中我们不能记录完整的增量快照: 18 | 19 | ``` 20 | parent 21 | child2 22 | child1 23 | ``` 24 | 25 | 1. parent 节点插入子节点 child1 26 | 2. parent 节点在 child1 之前插入子节点 child2 27 | 28 | 按照实际执行顺序 child1 会被 rrweb 先序列化,但是在序列化新增节点时我们除了记录父节点之外还需要记录相邻节点,从而保证回放时可以把新增节点放置在正确的位置。但是此时 child 1 相邻节点 child2 已经存在但是还未被序列化,我们会将其记录为 `id: -1`(不存在相邻节点时 id 为 null)。 29 | 30 | 重放时当我们处理到新增 child1 的增量快照时,我们可以通过其相邻节点 id 为 -1 这一特征知道帮助它定位的节点还未生成,然后将它临时放入”缺失节点池“中暂不插入 DOM 树中。 31 | 32 | 之后在处理到新增 child2 的增量快照时,我们正常处理并插入 child2,完成重放之后检查 child2 的相邻节点 id 是否指向缺失节点池中的某个待添加节点,如果吻合则将其从池中取出插入对应位置。 33 | 34 | ## 模拟 Hover 35 | 36 | 在许多前端页面中都会存在 `:hover` 选择器对应的 CSS 样式,但是我们并不能通过 JavaScript 触发 hover 事件。因此回放时我们需要模拟 hover 事件让样式正确显示。 37 | 38 | 具体方式包括两部分: 39 | 40 | 1. 遍历 CSS 样式表,对于 `:hover` 选择器相关 CSS 规则增加一条完全一致的规则,但是选择器为一个特殊的 class,例如 `.:hover`。 41 | 2. 当回放 mouse up 鼠标交互事件时,为事件目标及其所有祖先节点都添加 `.:hover` 类名,mouse down 时再对应移除。 42 | 43 | ## 从任意时间点开始播放 44 | 45 | 除了基础的回放功能之外,我们还希望 `rrweb-player` 这样的播放器可以提供和视频播放器类似的功能,如拖拽到进度条至任意时间点播放。 46 | 47 | 实际实现时我们通过给定的起始时间点将快照链分为两部分,分别是时间点之前和之后的部分。然后同步执行之前的快照链,再正常异步执行之后的快照链就可以做到从任意时间点开始播放的效果。 48 | -------------------------------------------------------------------------------- /docs/sandbox.zh_CN.md: -------------------------------------------------------------------------------- 1 | # 沙盒 2 | 3 | 在[序列化设计](./serialization.zh_CN.md)中我们提到了“去脚本化”的处理,即在回放时我们不应该执行被录制页面中的 JavaScript,在重建快照的过程中我们将所有 `script` 标签改写为 `noscript` 标签解决了部分问题。但仍有一些脚本化的行为是不包含在 `script` 标签中的,例如 HTML 中的 inline script、表单提交等。 4 | 5 | 脚本化的行为多种多样,如果仅过滤已知场景难免有所疏漏,而一旦有脚本被执行就可能造成不可逆的非预期结果。因此我们通过 HTML 提供的 iframe 沙盒功能进行浏览器层面的限制。 6 | 7 | ## iframe sandbox 8 | 9 | 我们在重建快照时将被录制的 DOM 重建在一个 `iframe` 元素中,通过设置它的 `sandbox` 属性,我们可以禁止以下行为: 10 | 11 | - 表单提交 12 | - `window.open` 等弹出窗 13 | - JS 脚本(包含 inline event handler 和 `` ) 14 | 15 | 这与我们的预期是相符的,尤其是对 JS 脚本的处理相比自行实现会更加安全、可靠。 16 | 17 | ## 避免链接跳转 18 | 19 | 当点击 a 元素链接时默认事件为跳转至它的 href 属性对应的 URL。在重放时我们会通过重建跳转后页面 DOM 的方式保证视觉上的正确重放,而原本的跳转则应该被禁止执行。 20 | 21 | 通常我们会通过事件代理捕获所有的 a 元素点击事件,再通过 `event.preventDefault()` 禁用默认事件。但当我们将回放页面放在沙盒内时,所有的 event handler 都将不被执行,我们也就无法实现事件代理。 22 | 23 | 重新查看我们回放交互事件增量快照的实现,我们会发现其实 `click` 事件是可以不被重放的。因为在禁用 JS 的情况下点击行为并不会产生视觉上的影响,也无需被感知。 24 | 25 | 不过为了优化回放的效果,我们可以在点击时给模拟的鼠标元素添加特殊的动画效果,用来提示观看者此处发生了一次点击。 26 | 27 | ## iframe 样式设置 28 | 29 | 由于我们将 DOM 重建在 iframe 中,所以我们无法通过父页面的 CSS 样式表影响 iframe 中的元素。但是在不允许 JS 脚本执行的情况下 `noscript` 标签会被显示,而我们希望将其隐藏,就需要动态的向 iframe 中添加样式,示例代码如下: 30 | 31 | ```typescript 32 | const injectStyleRules: string[] = [ 33 | 'iframe { background: #f1f3f5 }', 34 | 'noscript { display: none !important; }', 35 | ]; 36 | 37 | const styleEl = document.createElement('style'); 38 | const { documentElement, head } = this.iframe.contentDocument!; 39 | documentElement!.insertBefore(styleEl, head); 40 | for (let idx = 0; idx < injectStyleRules.length; idx++) { 41 | (styleEl.sheet! as CSSStyleSheet).insertRule(injectStyleRules[idx], idx); 42 | } 43 | ``` 44 | 45 | 需要注意的是这个插入的 style 元素在被录制页面中并不存在,所以我们不能将其序列化,否则 `id -> Node` 的映射将出现错误。 46 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independent", 3 | "npmClient": "yarn", 4 | "command": { 5 | "publish": { 6 | "message": "chore(release): publish new version" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/all/.gitignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | dist 3 | node_modules 4 | yarn-error.log -------------------------------------------------------------------------------- /packages/all/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@sentry-internal/rrweb'; 2 | export * from '@sentry-internal/rrweb-packer'; 3 | // export * from '@rrweb/rrweb-plugin-console-record'; 4 | // export * from '@rrweb/rrweb-plugin-console-replay'; 5 | -------------------------------------------------------------------------------- /packages/all/test/html/blank.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/all/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "compilerOptions": { 7 | "rootDir": "src", 8 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 9 | }, 10 | "references": [ 11 | { 12 | "path": "../rrweb" 13 | }, 14 | { 15 | "path": "../packer" 16 | }, 17 | { 18 | "path": "../types" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/all/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import config from '../../vite.config.default'; 3 | 4 | export default config(path.resolve(__dirname, 'src/index.ts'), 'rrweb'); 5 | -------------------------------------------------------------------------------- /packages/all/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineProject, mergeConfig } from 'vitest/config'; 3 | import configShared from '../../vitest.config'; 4 | 5 | export default mergeConfig(configShared, defineProject({})); 6 | -------------------------------------------------------------------------------- /packages/packer/.gitignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | dist 3 | node_modules 4 | yarn-error.log -------------------------------------------------------------------------------- /packages/packer/pack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../dist/pack.cjs", 3 | "types": "../dist/pack.d.ts" 4 | } 5 | -------------------------------------------------------------------------------- /packages/packer/src/base.ts: -------------------------------------------------------------------------------- 1 | import type { eventWithTime } from '@sentry-internal/rrweb-types'; 2 | 3 | export type eventWithTimeAndPacker = eventWithTime & { 4 | v: string; 5 | }; 6 | 7 | export const MARK = 'v1'; 8 | -------------------------------------------------------------------------------- /packages/packer/src/index.ts: -------------------------------------------------------------------------------- 1 | export { pack } from './pack'; 2 | export { unpack } from './unpack'; 3 | -------------------------------------------------------------------------------- /packages/packer/src/pack.ts: -------------------------------------------------------------------------------- 1 | import { strFromU8, strToU8, zlibSync } from 'fflate'; 2 | import type { PackFn, eventWithTime } from '@sentry-internal/rrweb-types'; 3 | import { eventWithTimeAndPacker, MARK } from './base'; 4 | 5 | export const pack: PackFn = (event: eventWithTime) => { 6 | const _e: eventWithTimeAndPacker = { 7 | ...event, 8 | v: MARK, 9 | }; 10 | return strFromU8(zlibSync(strToU8(JSON.stringify(_e))), true); 11 | }; 12 | -------------------------------------------------------------------------------- /packages/packer/src/unpack.ts: -------------------------------------------------------------------------------- 1 | import { strFromU8, strToU8, unzlibSync } from 'fflate'; 2 | import { eventWithTimeAndPacker, MARK } from './base'; 3 | import type { UnpackFn, eventWithTime } from '@sentry-internal/rrweb-types'; 4 | 5 | export const unpack: UnpackFn = (raw: string) => { 6 | if (typeof raw !== 'string') { 7 | return raw; 8 | } 9 | try { 10 | const e: eventWithTime = JSON.parse(raw) as eventWithTime; 11 | if (e.timestamp) { 12 | return e; 13 | } 14 | } catch (error) { 15 | // ignore and continue 16 | } 17 | try { 18 | const e: eventWithTimeAndPacker = JSON.parse( 19 | strFromU8(unzlibSync(strToU8(raw, true))), 20 | ) as eventWithTimeAndPacker; 21 | if (e.v === MARK) { 22 | return e; 23 | } 24 | throw new Error( 25 | `These events were packed with packer ${e.v} which is incompatible with current packer ${MARK}.`, 26 | ); 27 | } catch (error) { 28 | console.error(error); 29 | throw new Error('Unknown data format.'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /packages/packer/test/__snapshots__/packer.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`pack > can pack event 1`] = `"xœ«V*©,HU²2ÐQJI,IT²ª®ÕQ*ÉÌM-.IÌ-P²2457·06³0¥2%+¥2C¥ZÛË"`; 4 | -------------------------------------------------------------------------------- /packages/packer/test/packer.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { pack, unpack } from '../src'; 3 | import { type eventWithTime, EventType } from '@sentry-internal/rrweb-types'; 4 | import { MARK } from '../src/base'; 5 | 6 | const event: eventWithTime = { 7 | type: EventType.DomContentLoaded, 8 | data: {}, 9 | timestamp: new Date('2020-01-01').getTime(), 10 | }; 11 | 12 | describe('pack', () => { 13 | it('can pack event', () => { 14 | const packedData = pack(event); 15 | expect(packedData).toMatchSnapshot(); 16 | }); 17 | }); 18 | 19 | describe('unpack', () => { 20 | it('is compatible with unpacked data 1', () => { 21 | const result = unpack(event as unknown as string); 22 | expect(result).toEqual(event); 23 | }); 24 | 25 | it('is compatible with unpacked data 2', () => { 26 | const result = unpack(JSON.stringify(event)); 27 | expect(result).toEqual(event); 28 | }); 29 | 30 | it('stop on unknown data format', () => { 31 | const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); 32 | 33 | expect(() => unpack('[""]')).toThrow(''); 34 | 35 | expect(consoleSpy).toHaveBeenCalled(); 36 | vi.resetAllMocks(); 37 | }); 38 | 39 | it('can unpack packed data', () => { 40 | const packedData = pack(event); 41 | const result = unpack(packedData); 42 | expect(result).toEqual({ 43 | ...event, 44 | v: MARK, 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/packer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "exclude": ["vite.config.ts", "test"], 5 | "compilerOptions": { 6 | "rootDir": "src", 7 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 8 | }, 9 | "references": [ 10 | { 11 | "path": "../types" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/packer/unpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../dist/unpack.cjs", 3 | "types": "../dist/unpack.d.ts" 4 | } 5 | -------------------------------------------------------------------------------- /packages/packer/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import config from '../../vite.config.default'; 3 | 4 | export default config( 5 | { 6 | packer: path.resolve(__dirname, 'src/index.ts'), 7 | pack: path.resolve(__dirname, 'src/pack.ts'), 8 | unpack: path.resolve(__dirname, 'src/unpack.ts'), 9 | }, 10 | 'rrwebPacker', 11 | ); 12 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-canvas-webrtc-record/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rrweb/rrweb-plugin-canvas-webrtc-record", 3 | "version": "2.35.0", 4 | "description": "", 5 | "type": "module", 6 | "main": "./dist/rrweb-plugin-canvas-webrtc-record.umd.cjs", 7 | "module": "./dist/rrweb-plugin-canvas-webrtc-record.js", 8 | "unpkg": "./dist/rrweb-plugin-canvas-webrtc-record.umd.cjs", 9 | "typings": "dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": { 13 | "types": "./dist/index.d.ts", 14 | "default": "./dist/rrweb-plugin-canvas-webrtc-record.js" 15 | }, 16 | "require": { 17 | "types": "./dist/index.d.cts", 18 | "default": "./dist/rrweb-plugin-canvas-webrtc-record.cjs" 19 | } 20 | } 21 | }, 22 | "files": [ 23 | "dist", 24 | "package.json" 25 | ], 26 | "scripts": { 27 | "dev": "vite build --watch", 28 | "build": "tsc -noEmit && vite build", 29 | "check-types": "tsc -noEmit", 30 | "prepack": "npm run build" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/rrweb-io/rrweb.git" 35 | }, 36 | "keywords": [ 37 | "rrweb" 38 | ], 39 | "author": "justin@recordonce.com", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/rrweb-io/rrweb/issues" 43 | }, 44 | "homepage": "https://github.com/rrweb-io/rrweb#readme", 45 | "devDependencies": { 46 | "@sentry-internal/rrweb": "2.35.0", 47 | "typescript": "^4.7.3", 48 | "vite": "^5.2.8", 49 | "vite-plugin-dts": "^3.8.1" 50 | }, 51 | "peerDependencies": { 52 | "@sentry-internal/rrweb": "^2.0.0-alpha.14" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-canvas-webrtc-record/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface WebRTCDataChannel { 2 | nodeId: number; 3 | streamId: string; 4 | } 5 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-canvas-webrtc-record/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "exclude": [ 7 | "vite.config.ts" 8 | ], 9 | "compilerOptions": { 10 | "rootDir": "src", 11 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 12 | }, 13 | "references": [ 14 | { 15 | "path": "../../rrweb" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-canvas-webrtc-record/vite.config.ts: -------------------------------------------------------------------------------- 1 | import config from '../../../vite.config.default'; 2 | 3 | export default config('src/index.ts', 'rrwebPluginCanvasWebRTCRecord'); 4 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-canvas-webrtc-replay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rrweb/rrweb-plugin-canvas-webrtc-replay", 3 | "version": "2.35.0", 4 | "description": "", 5 | "type": "module", 6 | "main": "./dist/rrweb-plugin-canvas-webrtc-replay.umd.cjs", 7 | "module": "./dist/rrweb-plugin-canvas-webrtc-replay.js", 8 | "unpkg": "./dist/rrweb-plugin-canvas-webrtc-replay.umd.cjs", 9 | "typings": "dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": { 13 | "types": "./dist/index.d.ts", 14 | "default": "./dist/rrweb-plugin-canvas-webrtc-replay.js" 15 | }, 16 | "require": { 17 | "types": "./dist/index.d.cts", 18 | "default": "./dist/rrweb-plugin-canvas-webrtc-replay.cjs" 19 | } 20 | } 21 | }, 22 | "files": [ 23 | "dist", 24 | "package.json" 25 | ], 26 | "scripts": { 27 | "dev": "vite build --watch", 28 | "build": "tsc -noEmit && vite build", 29 | "check-types": "tsc -noEmit", 30 | "prepack": "npm run build" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/rrweb-io/rrweb.git" 35 | }, 36 | "keywords": [ 37 | "rrweb" 38 | ], 39 | "author": "justin@recordonce.com", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/rrweb-io/rrweb/issues" 43 | }, 44 | "homepage": "https://github.com/rrweb-io/rrweb#readme", 45 | "devDependencies": { 46 | "@sentry-internal/rrweb": "2.35.0", 47 | "typescript": "^4.7.3", 48 | "vite": "^5.2.8", 49 | "vite-plugin-dts": "^3.8.1" 50 | }, 51 | "peerDependencies": { 52 | "@sentry-internal/rrweb": "^2.0.0-alpha.14" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-canvas-webrtc-replay/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface WebRTCDataChannel { 2 | nodeId: number; 3 | streamId: string; 4 | } 5 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-canvas-webrtc-replay/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "exclude": [ 7 | "vite.config.ts" 8 | ], 9 | "compilerOptions": { 10 | "rootDir": "src", 11 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 12 | }, 13 | "references": [ 14 | { 15 | "path": "../../rrweb" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-canvas-webrtc-replay/vite.config.ts: -------------------------------------------------------------------------------- 1 | import config from '../../../vite.config.default'; 2 | 3 | export default config('src/index.ts', 'rrwebPluginCanvasWebRTCReplay'); 4 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-console-record/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rrweb/rrweb-plugin-console-record", 3 | "version": "2.35.0", 4 | "description": "", 5 | "type": "module", 6 | "main": "./dist/rrweb-plugin-console-record.umd.cjs", 7 | "module": "./dist/rrweb-plugin-console-record.js", 8 | "unpkg": "./dist/rrweb-plugin-console-record.umd.cjs", 9 | "typings": "dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": { 13 | "types": "./dist/index.d.ts", 14 | "default": "./dist/rrweb-plugin-console-record.js" 15 | }, 16 | "require": { 17 | "types": "./dist/index.d.cts", 18 | "default": "./dist/rrweb-plugin-console-record.umd.cjs" 19 | } 20 | } 21 | }, 22 | "files": [ 23 | "dist", 24 | "package.json" 25 | ], 26 | "scripts": { 27 | "dev": "vite build --watch", 28 | "test": "vitest run", 29 | "test:watch": "vitest watch", 30 | "build": "tsc -noEmit && vite build", 31 | "check-types": "tsc -noEmit", 32 | "prepack": "npm run build" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/rrweb-io/rrweb.git" 37 | }, 38 | "keywords": [ 39 | "rrweb" 40 | ], 41 | "author": "yanzhen@smartx.com", 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/rrweb-io/rrweb/issues" 45 | }, 46 | "homepage": "https://github.com/rrweb-io/rrweb#readme", 47 | "devDependencies": { 48 | "@sentry-internal/rrweb": "2.35.0", 49 | "puppeteer": "^20.9.0", 50 | "typescript": "^4.7.3", 51 | "vite": "^5.2.8", 52 | "vite-plugin-dts": "^3.8.1", 53 | "vitest": "^1.4.0" 54 | }, 55 | "peerDependencies": { 56 | "@sentry-internal/rrweb": "^2.0.0-alpha.14" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-console-record/test/html/index.ts: -------------------------------------------------------------------------------- 1 | import type { eventWithTime } from '@sentry-internal/rrweb-types'; 2 | import { record } from '@sentry-internal/rrweb'; 3 | import { getRecordConsolePlugin } from '../../src/index'; 4 | 5 | window.Date.now = () => new Date(Date.UTC(2018, 10, 15, 8)).valueOf(); 6 | const snapshots: eventWithTime[] = ((window as any).snapshots = []); 7 | record({ 8 | emit: (event) => { 9 | snapshots.push(event); 10 | }, 11 | // maskTextSelector: ${JSON.stringify(options.maskTextSelector)}, 12 | // maskAllInputs: ${options.maskAllInputs}, 13 | // maskInputOptions: ${JSON.stringify(options.maskAllInputs)}, 14 | // userTriggeredOnInput: ${options.userTriggeredOnInput}, 15 | // maskTextFn: ${options.maskTextFn}, 16 | // recordCanvas: ${options.recordCanvas}, 17 | // inlineImages: ${options.inlineImages}, 18 | plugins: [getRecordConsolePlugin()], 19 | }); 20 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-console-record/test/html/log.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Log record 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-console-record/test/stringify.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @vitest-environment jsdom 3 | */ 4 | 5 | import { stringify } from '../src/stringify'; 6 | import { describe, it, expect } from 'vitest'; 7 | 8 | describe('console record plugin', () => { 9 | it('can stringify bigint', () => { 10 | expect(stringify(BigInt(1))).toEqual('"1n"'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-console-record/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "exclude": [ 7 | "vite.config.ts", 8 | "vitest.config.ts", 9 | "test" 10 | ], 11 | "compilerOptions": { 12 | "rootDir": "src", 13 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo", 14 | // TODO: enable me in the future 15 | // at time of writing (April 2024) there are 6 errors in this package 16 | "strict": false 17 | }, 18 | "references": [ 19 | { 20 | "path": "../../rrweb" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-console-record/vite.config.ts: -------------------------------------------------------------------------------- 1 | import config from '../../../vite.config.default'; 2 | 3 | export default config('src/index.ts', 'rrwebPluginConsoleRecord'); 4 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-console-record/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineProject, mergeConfig } from 'vitest/config'; 3 | import configShared from '../../../vitest.config.ts'; 4 | 5 | export default mergeConfig(configShared, defineProject({})); 6 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-console-replay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rrweb/rrweb-plugin-console-replay", 3 | "version": "2.35.0", 4 | "description": "", 5 | "type": "module", 6 | "main": "./dist/rrweb-plugin-console-replay.umd.cjs", 7 | "module": "./dist/rrweb-plugin-console-replay.js", 8 | "unpkg": "./dist/rrweb-plugin-console-replay.umd.cjs", 9 | "typings": "dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": { 13 | "types": "./dist/index.d.ts", 14 | "default": "./dist/rrweb-plugin-console-replay.js" 15 | }, 16 | "require": { 17 | "types": "./dist/index.d.cts", 18 | "default": "./dist/rrweb-plugin-console-replay.umd.cjs" 19 | } 20 | } 21 | }, 22 | "files": [ 23 | "dist", 24 | "package.json" 25 | ], 26 | "scripts": { 27 | "dev": "vite build --watch", 28 | "build": "tsc -noEmit && vite build", 29 | "check-types": "tsc -noEmit", 30 | "prepack": "npm run build" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/rrweb-io/rrweb.git" 35 | }, 36 | "keywords": [ 37 | "rrweb" 38 | ], 39 | "author": "yanzhen@smartx.com", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/rrweb-io/rrweb/issues" 43 | }, 44 | "homepage": "https://github.com/rrweb-io/rrweb#readme", 45 | "devDependencies": { 46 | "@rrweb/rrweb-plugin-console-record": "2.35.0", 47 | "@sentry-internal/rrweb": "2.35.0", 48 | "typescript": "^4.7.3", 49 | "vite": "^5.2.8", 50 | "vite-plugin-dts": "^3.8.1" 51 | }, 52 | "peerDependencies": { 53 | "@sentry-internal/rrweb": "^2.0.0-alpha.14" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-console-replay/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "exclude": [ 7 | "vite.config.ts", 8 | "dist", 9 | "tsconfig.json" 10 | ], 11 | "compilerOptions": { 12 | "rootDir": ".", 13 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 14 | }, 15 | "references": [ 16 | { 17 | "path": "../rrweb-plugin-console-record" 18 | }, 19 | { 20 | "path": "../../rrweb" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-console-replay/vite.config.ts: -------------------------------------------------------------------------------- 1 | import config from '../../../vite.config.default'; 2 | 3 | export default config('src/index.ts', 'rrwebPluginConsoleReplay'); 4 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-sequential-id-record/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rrweb/rrweb-plugin-sequential-id-record", 3 | "version": "2.35.0", 4 | "description": "", 5 | "type": "module", 6 | "main": "./dist/rrweb-plugin-sequential-id-record.umd.cjs", 7 | "module": "./dist/rrweb-plugin-sequential-id-record.js", 8 | "unpkg": "./dist/rrweb-plugin-sequential-id-record.umd.cjs", 9 | "typings": "dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": { 13 | "types": "./dist/index.d.ts", 14 | "default": "./dist/rrweb-plugin-sequential-id-record.js" 15 | }, 16 | "require": { 17 | "types": "./dist/index.d.cts", 18 | "default": "./dist/rrweb-plugin-sequential-id-record.umd.cjs" 19 | } 20 | } 21 | }, 22 | "files": [ 23 | "dist", 24 | "package.json" 25 | ], 26 | "scripts": { 27 | "dev": "vite build --watch", 28 | "build": "tsc -noEmit && vite build", 29 | "check-types": "tsc -noEmit", 30 | "prepack": "npm run build" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/rrweb-io/rrweb.git" 35 | }, 36 | "keywords": [ 37 | "rrweb" 38 | ], 39 | "author": "yanzhen@smartx.com", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/rrweb-io/rrweb/issues" 43 | }, 44 | "homepage": "https://github.com/rrweb-io/rrweb#readme", 45 | "devDependencies": { 46 | "@sentry-internal/rrweb": "2.35.0", 47 | "typescript": "^4.7.3", 48 | "vite": "^5.2.8", 49 | "vite-plugin-dts": "^3.8.1" 50 | }, 51 | "peerDependencies": { 52 | "@sentry-internal/rrweb": "^2.0.0-alpha.14" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-sequential-id-record/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { RecordPlugin } from '@sentry-internal/rrweb-types'; 2 | 3 | export type SequentialIdOptions = { 4 | key: string; 5 | }; 6 | 7 | const defaultOptions: SequentialIdOptions = { 8 | key: '_sid', 9 | }; 10 | 11 | export const PLUGIN_NAME = 'rrweb/sequential-id@1'; 12 | 13 | export const getRecordSequentialIdPlugin: ( 14 | options?: Partial, 15 | ) => RecordPlugin = (options) => { 16 | const _options = options 17 | ? Object.assign({}, defaultOptions, options) 18 | : defaultOptions; 19 | let id = 0; 20 | 21 | return { 22 | name: PLUGIN_NAME, 23 | eventProcessor(event) { 24 | Object.assign(event, { 25 | [_options.key]: ++id, 26 | }); 27 | return event; 28 | }, 29 | options: _options, 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-sequential-id-record/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "exclude": [ 7 | "vite.config.ts" 8 | ], 9 | "compilerOptions": { 10 | "rootDir": "src", 11 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 12 | }, 13 | "references": [ 14 | { 15 | "path": "../../rrweb" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-sequential-id-record/vite.config.ts: -------------------------------------------------------------------------------- 1 | import config from '../../../vite.config.default'; 2 | 3 | export default config('src/index.ts', 'rrwebPluginSequentialIdRecord'); 4 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-sequential-id-replay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rrweb/rrweb-plugin-sequential-id-replay", 3 | "version": "2.35.0", 4 | "description": "", 5 | "type": "module", 6 | "main": "./dist/rrweb-plugin-sequential-id-replay.umd.cjs", 7 | "module": "./dist/rrweb-plugin-sequential-id-replay.js", 8 | "unpkg": "./dist/rrweb-plugin-sequential-id-replay.umd.cjs", 9 | "typings": "dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": { 13 | "types": "./dist/index.d.ts", 14 | "default": "./dist/rrweb-plugin-sequential-id-replay.js" 15 | }, 16 | "require": { 17 | "types": "./dist/index.d.cts", 18 | "default": "./dist/rrweb-plugin-sequential-id-replay.umd.cjs" 19 | } 20 | } 21 | }, 22 | "files": [ 23 | "dist", 24 | "package.json" 25 | ], 26 | "scripts": { 27 | "dev": "vite build --watch", 28 | "build": "tsc -noEmit && vite build", 29 | "check-types": "tsc -noEmit", 30 | "prepack": "npm run build" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/rrweb-io/rrweb.git" 35 | }, 36 | "keywords": [ 37 | "rrweb" 38 | ], 39 | "author": "yanzhen@smartx.com", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/rrweb-io/rrweb/issues" 43 | }, 44 | "homepage": "https://github.com/rrweb-io/rrweb#readme", 45 | "devDependencies": { 46 | "@rrweb/rrweb-plugin-sequential-id-record": "2.35.0", 47 | "@sentry-internal/rrweb": "2.35.0", 48 | "typescript": "^4.7.3", 49 | "vite": "^5.2.8", 50 | "vite-plugin-dts": "^3.8.1" 51 | }, 52 | "peerDependencies": { 53 | "@sentry-internal/rrweb": "^2.0.0-alpha.14" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-sequential-id-replay/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { SequentialIdOptions } from '@rrweb/rrweb-plugin-sequential-id-record'; 2 | import type { ReplayPlugin } from '@sentry-internal/rrweb'; 3 | import type { eventWithTime } from '@sentry-internal/rrweb-types'; 4 | 5 | type Options = SequentialIdOptions & { 6 | warnOnMissingId: boolean; 7 | }; 8 | 9 | const defaultOptions: Options = { 10 | key: '_sid', 11 | warnOnMissingId: true, 12 | }; 13 | 14 | export const getReplaySequentialIdPlugin: ( 15 | options?: Partial, 16 | ) => ReplayPlugin = (options) => { 17 | const { key, warnOnMissingId } = options 18 | ? Object.assign({}, defaultOptions, options) 19 | : defaultOptions; 20 | let currentId = 1; 21 | 22 | return { 23 | handler(event: eventWithTime) { 24 | if (key in event) { 25 | const id = (event as unknown as Record)[key]; 26 | if (id !== currentId) { 27 | console.error( 28 | `[sequential-id-plugin]: expect to get an id with value "${currentId}", but got "${id}"`, 29 | ); 30 | } else { 31 | currentId++; 32 | } 33 | } else if (warnOnMissingId) { 34 | console.warn( 35 | `[sequential-id-plugin]: failed to get id in key: "${key}"`, 36 | ); 37 | } 38 | }, 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-sequential-id-replay/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "exclude": [ 7 | "vite.config.ts", 8 | "dist", 9 | "tsconfig.json" 10 | ], 11 | "compilerOptions": { 12 | "rootDir": ".", 13 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 14 | }, 15 | "references": [ 16 | { 17 | "path": "../rrweb-plugin-sequential-id-record" 18 | }, 19 | { 20 | "path": "../../rrweb" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/plugins/rrweb-plugin-sequential-id-replay/vite.config.ts: -------------------------------------------------------------------------------- 1 | import config from '../../../vite.config.default'; 2 | 3 | export default config('src/index.ts', 'rrwebPluginSequentialIdReplay'); 4 | -------------------------------------------------------------------------------- /packages/record/.gitignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | dist 3 | node_modules 4 | yarn-error.log -------------------------------------------------------------------------------- /packages/record/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rrweb/record", 3 | "version": "2.35.0", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "keywords": [ 8 | "rrweb", 9 | "@rrweb/record" 10 | ], 11 | "scripts": { 12 | "dev": "vite build --watch", 13 | "build": "tsc -noEmit && vite build", 14 | "test": "vitest run", 15 | "test:watch": "vitest watch", 16 | "check-types": "tsc -noEmit", 17 | "prepack": "npm run build", 18 | "lint": "yarn eslint src/**/*.ts" 19 | }, 20 | "homepage": "https://github.com/rrweb-io/rrweb/tree/main/packages/@rrweb/record#readme", 21 | "bugs": { 22 | "url": "https://github.com/rrweb-io/rrweb/issues" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/rrweb-io/rrweb.git" 27 | }, 28 | "license": "MIT", 29 | "type": "module", 30 | "main": "./dist/record.cjs", 31 | "module": "./dist/record.js", 32 | "unpkg": "./dist/record.umd.cjs", 33 | "typings": "dist/index.d.ts", 34 | "exports": { 35 | ".": { 36 | "import": { 37 | "types": "./dist/index.d.ts", 38 | "default": "./dist/record.js" 39 | }, 40 | "require": { 41 | "types": "./dist/index.d.cts", 42 | "default": "./dist/record.cjs" 43 | } 44 | } 45 | }, 46 | "files": [ 47 | "dist", 48 | "package.json" 49 | ], 50 | "devDependencies": { 51 | "puppeteer": "^20.9.0", 52 | "typescript": "^4.7.3", 53 | "vite": "^5.2.8", 54 | "vite-plugin-dts": "^3.8.1", 55 | "vitest": "^1.4.0" 56 | }, 57 | "dependencies": { 58 | "@sentry-internal/rrweb": "2.35.0", 59 | "@sentry-internal/rrweb-types": "2.35.0" 60 | }, 61 | "browserslist": [ 62 | "supports es6-class" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /packages/record/src/index.ts: -------------------------------------------------------------------------------- 1 | import { record } from '@sentry-internal/rrweb'; 2 | 3 | export { record }; 4 | -------------------------------------------------------------------------------- /packages/record/test/record.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { record } from '../src/index'; 3 | 4 | describe('record', () => { 5 | it('should be a function', () => { 6 | expect(typeof record).toBe('function'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/record/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "compilerOptions": { 7 | "rootDir": "src", 8 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 9 | }, 10 | "references": [ 11 | { 12 | "path": "../rrweb" 13 | }, 14 | { 15 | "path": "../types" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/record/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import config from '../../vite.config.default'; 3 | 4 | export default config(path.resolve(__dirname, 'src/index.ts'), 'rrweb'); 5 | -------------------------------------------------------------------------------- /packages/record/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineProject, mergeConfig } from 'vitest/config'; 3 | import configShared from '../../vitest.config'; 4 | 5 | export default mergeConfig(configShared, defineProject({})); 6 | -------------------------------------------------------------------------------- /packages/replay/.gitignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | dist 3 | node_modules 4 | yarn-error.log -------------------------------------------------------------------------------- /packages/replay/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Replayer, 3 | type playerConfig, 4 | type PlayerMachineState, 5 | type SpeedMachineState, 6 | } from '@sentry-internal/rrweb'; 7 | import '@sentry-internal/rrweb/dist/style.css'; 8 | 9 | export { Replayer, playerConfig, PlayerMachineState, SpeedMachineState }; 10 | -------------------------------------------------------------------------------- /packages/replay/test/replay.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { Replayer } from '../src/index'; 3 | 4 | describe('Replayer', () => { 5 | it('should work', () => { 6 | expect(() => new Replayer([])).toThrowErrorMatchingInlineSnapshot( 7 | `[Error: Replayer need at least 2 events.]`, 8 | ); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/replay/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "compilerOptions": { 7 | "rootDir": "src", 8 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 9 | }, 10 | "references": [ 11 | { 12 | "path": "../rrweb" 13 | }, 14 | { 15 | "path": "../types" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/replay/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import config from '../../vite.config.default'; 3 | 4 | export default config(path.resolve(__dirname, 'src/index.ts'), 'rrweb'); 5 | -------------------------------------------------------------------------------- /packages/replay/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineProject, mergeConfig } from 'vitest/config'; 3 | import configShared from '../../vitest.config'; 4 | 5 | export default mergeConfig(configShared, defineProject({})); 6 | -------------------------------------------------------------------------------- /packages/rrdom-nodejs/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | es 3 | lib 4 | typings 5 | -------------------------------------------------------------------------------- /packages/rrdom-nodejs/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["vitest.explorer"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/rrdom-nodejs/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "type": "node", 7 | "request": "launch", 8 | "name": "Debug Current Test File", 9 | "autoAttachChildProcesses": true, 10 | "skipFiles": ["/**", "**/node_modules/**"], 11 | "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", 12 | "args": ["run", "${relativeFile}"], 13 | "smartStep": true, 14 | "console": "integratedTerminal" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/rrdom-nodejs/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | polyfillPerformance, 3 | polyfillRAF, 4 | polyfillEvent, 5 | polyfillNode, 6 | polyfillDocument, 7 | } from './polyfill'; 8 | polyfillPerformance(); 9 | polyfillRAF(); 10 | polyfillEvent(); 11 | polyfillNode(); 12 | polyfillDocument(); 13 | export * from './document-nodejs'; 14 | -------------------------------------------------------------------------------- /packages/rrdom-nodejs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "rootDir": "src", 6 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 7 | }, 8 | "references": [ 9 | { 10 | "path": "../rrdom" 11 | }, 12 | { 13 | "path": "../rrweb-snapshot" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/rrdom-nodejs/vite.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import config from '../../vite.config.default'; 3 | 4 | export default config(path.resolve(__dirname, 'src/index.ts'), 'rrdomNodejs'); 5 | -------------------------------------------------------------------------------- /packages/rrdom-nodejs/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineProject, mergeConfig } from 'vitest/config'; 3 | import configShared from '../../vitest.config'; 4 | 5 | export default mergeConfig(configShared, defineProject({})); 6 | -------------------------------------------------------------------------------- /packages/rrdom/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | es 3 | lib 4 | typings 5 | -------------------------------------------------------------------------------- /packages/rrdom/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ 2 | export default { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | /** 6 | * Keeps old (pre-jest 29) snapshot format 7 | * its a bit ugly and harder to read than the new format, 8 | * so we might want to remove this in its own PR 9 | */ 10 | snapshotFormat: { 11 | escapeString: true, 12 | printBasicPrototype: true, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/rrdom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sentry-internal/rrdom", 3 | "version": "2.35.0", 4 | "homepage": "https://github.com/rrweb-io/rrweb/tree/main/packages/rrdom#readme", 5 | "license": "MIT", 6 | "type": "module", 7 | "main": "./dist/rrdom.cjs", 8 | "module": "./dist/rrdom.js", 9 | "unpkg": "./dist/rrdom.umd.cjs", 10 | "types": "./dist/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "import": { 14 | "types": "./dist/index.d.ts", 15 | "default": "./dist/rrdom.js" 16 | }, 17 | "require": { 18 | "types": "./dist/index.d.cts", 19 | "default": "./dist/rrdom.cjs" 20 | } 21 | } 22 | }, 23 | "files": [ 24 | "dist", 25 | "package.json" 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/rrweb-io/rrweb.git" 30 | }, 31 | "scripts": { 32 | "dev": "vite build --watch", 33 | "build": "yarn run check-types && vite build", 34 | "build:tarball": "npm pack", 35 | "check-types": "tsc --noEmit", 36 | "test": "vitest run", 37 | "test:watch": "vitest", 38 | "prepack": "yarn run build", 39 | "lint": "yarn eslint src/**/*.ts" 40 | }, 41 | "bugs": { 42 | "url": "https://github.com/rrweb-io/rrweb/issues" 43 | }, 44 | "devDependencies": { 45 | "@sentry-internal/rrweb-types": "2.35.0", 46 | "@types/puppeteer": "^5.4.4", 47 | "@typescript-eslint/eslint-plugin": "^5.23.0", 48 | "@typescript-eslint/parser": "^5.23.0", 49 | "eslint": "^8.15.0", 50 | "puppeteer": "^17.1.3", 51 | "typescript": "^4.9.0", 52 | "vite": "^5.2.8", 53 | "vite-plugin-dts": "^3.8.1" 54 | }, 55 | "dependencies": { 56 | "@sentry-internal/rrweb-snapshot": "2.35.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/rrdom/src/style.ts: -------------------------------------------------------------------------------- 1 | export function parseCSSText(cssText: string): Record { 2 | const res: Record = {}; 3 | const listDelimiter = /;(?![^(]*\))/g; 4 | const propertyDelimiter = /:(.+)/; 5 | const comment = /\/\*.*?\*\//g; 6 | cssText 7 | .replace(comment, '') 8 | .split(listDelimiter) 9 | .forEach(function (item) { 10 | if (item) { 11 | const tmp = item.split(propertyDelimiter); 12 | tmp.length > 1 && (res[camelize(tmp[0].trim())] = tmp[1].trim()); 13 | } 14 | }); 15 | return res; 16 | } 17 | 18 | export function toCSSText(style: Record): string { 19 | const properties = []; 20 | for (const name in style) { 21 | const value = style[name]; 22 | if (typeof value !== 'string') continue; 23 | const normalizedName = hyphenate(name); 24 | properties.push(`${normalizedName}: ${value};`); 25 | } 26 | return properties.join(' '); 27 | } 28 | 29 | /** 30 | * Camelize a hyphen-delimited string. 31 | */ 32 | const camelizeRE = /-([a-z])/g; 33 | const CUSTOM_PROPERTY_REGEX = /^--[a-zA-Z0-9-]+$/; 34 | export const camelize = (str: string): string => { 35 | if (CUSTOM_PROPERTY_REGEX.test(str)) return str; 36 | return str.replace(camelizeRE, (_, c: string) => (c ? c.toUpperCase() : '')); 37 | }; 38 | 39 | /** 40 | * Hyphenate a camelCase string. 41 | */ 42 | const hyphenateRE = /\B([A-Z])/g; 43 | export const hyphenate = (str: string): string => { 44 | return str.replace(hyphenateRE, '-$1').toLowerCase(); 45 | }; 46 | -------------------------------------------------------------------------------- /packages/rrdom/src/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the content document of an iframe. 3 | * Catching errors is necessary because some older browsers block access to the content document of a sandboxed iframe. 4 | */ 5 | export function getIFrameContentDocument(iframe?: HTMLIFrameElement) { 6 | try { 7 | return (iframe as HTMLIFrameElement).contentDocument; 8 | } catch (e) { 9 | // noop 10 | } 11 | } 12 | 13 | /** 14 | * Get the content window of an iframe. 15 | * Catching errors is necessary because some older browsers block access to the content document of a sandboxed iframe. 16 | */ 17 | export function getIFrameContentWindow(iframe?: HTMLIFrameElement) { 18 | try { 19 | return (iframe as HTMLIFrameElement).contentWindow; 20 | } catch (e) { 21 | // noop 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/rrdom/test/html/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Iframe 7 | 8 | 9 | 26 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /packages/rrdom/test/html/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Main 7 | 8 | 24 | 25 | 26 |

This is a h1 heading

27 |

This is a h1 heading with styles

28 |
29 |
30 | Text 1 31 |
32 |

This is a paragraph

33 | 34 |
35 | Text 2 36 |
37 | This is an image 38 | 39 |
40 | 41 |
42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /packages/rrdom/test/html/shadow-dom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | shadow dom 7 | 8 | 9 |
10 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/rrdom/test/utils.ts: -------------------------------------------------------------------------------- 1 | import * as rollup from 'rollup'; 2 | import * as typescript from 'rollup-plugin-typescript2'; 3 | import resolve from '@rollup/plugin-node-resolve'; 4 | const _typescript = typescript as unknown as typeof typescript.default; 5 | 6 | /** 7 | * Use rollup to compile an input TS script into JS code string. 8 | */ 9 | export async function compileTSCode(inputFilePath: string) { 10 | const bundle = await rollup.rollup({ 11 | cache: false, // caching causes failures on github actions 12 | input: inputFilePath, 13 | plugins: [ 14 | resolve() as unknown as rollup.Plugin, 15 | _typescript({ 16 | tsconfigOverride: { compilerOptions: { module: 'ESNext' } }, 17 | clean: true, 18 | }) as unknown as rollup.Plugin, 19 | ], 20 | }); 21 | const { 22 | output: [{ code: _code }], 23 | } = await bundle.generate({ 24 | name: 'rrdom', 25 | format: 'iife', 26 | }); 27 | return _code; 28 | } 29 | -------------------------------------------------------------------------------- /packages/rrdom/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "rootDir": "src", 6 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 7 | }, 8 | "references": [ 9 | { 10 | "path": "../rrweb-snapshot" 11 | }, 12 | { 13 | "path": "../types" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/rrdom/vite.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import config from '../../vite.config.default'; 3 | 4 | export default config(path.resolve(__dirname, 'src/index.ts'), 'rrdom'); 5 | -------------------------------------------------------------------------------- /packages/rrdom/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineProject, mergeConfig } from 'vitest/config'; 3 | import configShared from '../../vitest.config'; 4 | 5 | export default mergeConfig( 6 | configShared, 7 | defineProject({ 8 | test: { 9 | globals: true, 10 | }, 11 | }), 12 | ); 13 | -------------------------------------------------------------------------------- /packages/rrvideo/demo/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrvideo/demo/demo.gif -------------------------------------------------------------------------------- /packages/rrvideo/jest.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line tsdoc/syntax 2 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 3 | module.exports = { 4 | preset: 'ts-jest', 5 | testEnvironment: 'node', 6 | }; 7 | -------------------------------------------------------------------------------- /packages/rrvideo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rrvideo", 3 | "version": "2.35.0", 4 | "description": "transform rrweb session into video", 5 | "main": "build/index.js", 6 | "bin": { 7 | "rrvideo": "build/cli.js" 8 | }, 9 | "files": [ 10 | "build", 11 | "package.json" 12 | ], 13 | "types": "build/index.d.ts", 14 | "scripts": { 15 | "install": "playwright install", 16 | "build": "tsc", 17 | "test": "jest", 18 | "check-types": "tsc -noEmit", 19 | "prepack": "yarn build" 20 | }, 21 | "author": "yanzhen@smartx.com", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "@sentry-internal/rrweb-types": "2.35.0", 25 | "@types/fs-extra": "11.0.1", 26 | "@types/jest": "^27.4.1", 27 | "@types/minimist": "^1.2.1", 28 | "@types/node": "^18.15.11", 29 | "jest": "^27.5.1", 30 | "ts-jest": "^27.1.3" 31 | }, 32 | "dependencies": { 33 | "@open-tech-world/cli-progress-bar": "^2.0.2", 34 | "@sentry-internal/rrweb-player": "2.35.0", 35 | "fs-extra": "^11.1.1", 36 | "minimist": "^1.2.5", 37 | "playwright": "^1.32.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/rrvideo/rrvideo.config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "width": 1400, 3 | "height": 900, 4 | "speed": 4, 5 | "skipInactive": true, 6 | "mouseTail": { 7 | "strokeStyle": "green", 8 | "lineWidth": 2 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/rrvideo/src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import minimist from 'minimist'; 5 | import { ProgressBar } from '@open-tech-world/cli-progress-bar'; 6 | import type Player from '@sentry-internal/rrweb-player'; 7 | import { transformToVideo } from './index'; 8 | 9 | const argv = minimist(process.argv.slice(2)); 10 | 11 | if (!argv.input) { 12 | throw new Error('please pass --input to your rrweb events file'); 13 | } 14 | 15 | let config = {}; 16 | 17 | if (argv.config) { 18 | const configPathStr = argv.config as string; 19 | const configPath = path.isAbsolute(configPathStr) 20 | ? configPathStr 21 | : path.resolve(process.cwd(), configPathStr); 22 | config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as Omit< 23 | ConstructorParameters[0]['props'], 24 | 'events' 25 | >; 26 | } 27 | 28 | const pBar = new ProgressBar({ prefix: 'Transforming' }); 29 | const onProgressUpdate = (percent: number) => { 30 | if (percent < 1) pBar.run({ value: percent * 100, total: 100 }); 31 | else 32 | pBar.run({ value: 100, total: 100, prefix: 'Transformation Completed!' }); 33 | }; 34 | 35 | transformToVideo({ 36 | input: argv.input as string, 37 | output: argv.output as string, 38 | rrwebPlayer: config, 39 | onProgressUpdate, 40 | }) 41 | .then((file) => { 42 | console.log(`Successfully transformed into "${file}".`); 43 | }) 44 | .catch((error) => { 45 | console.log('Failed to transform this session.'); 46 | console.error(error); 47 | process.exit(1); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/rrvideo/test/cli.test.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import * as fs from 'fs-extra'; 3 | import * as path from 'path'; 4 | import exampleEvents from './events/example'; 5 | 6 | describe('should be able to run cli', () => { 7 | beforeAll(() => { 8 | fs.mkdirSync(path.resolve(__dirname, './generated')); 9 | fs.writeJsonSync( 10 | path.resolve(__dirname, './generated/example.json'), 11 | exampleEvents, 12 | { 13 | spaces: 2, 14 | }, 15 | ); 16 | }); 17 | afterAll(async () => { 18 | await fs.remove(path.resolve(__dirname, './generated')); 19 | }); 20 | 21 | it('should throw error without input path', () => { 22 | expect(() => { 23 | execSync('node ./build/cli.js', { stdio: 'pipe' }); 24 | }).toThrowError(/.*please pass --input to your rrweb events file.*/); 25 | }); 26 | 27 | it('should generate a video without output path', () => { 28 | execSync('node ./build/cli.js --input ./test/generated/example.json', { 29 | stdio: 'pipe', 30 | }); 31 | const outputFile = path.resolve(__dirname, '../rrvideo-output.webm'); 32 | expect(fs.existsSync(outputFile)).toBe(true); 33 | fs.removeSync(outputFile); 34 | }); 35 | 36 | it('should generate a video with specific output path', () => { 37 | const outputFile = path.resolve(__dirname, './generated/output.webm'); 38 | execSync( 39 | `node ./build/cli.js --input ./test/generated/example.json --output ${outputFile}`, 40 | { stdio: 'pipe' }, 41 | ); 42 | expect(fs.existsSync(outputFile)).toBe(true); 43 | fs.removeSync(outputFile); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/rrvideo/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": {} 3 | } 4 | -------------------------------------------------------------------------------- /packages/rrvideo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "target": "ES2020", 5 | "module": "commonjs", 6 | "declaration": true, 7 | "sourceMap": true, 8 | "outDir": "./build", 9 | "rootDir": "./src", 10 | "strictNullChecks": true, 11 | "noImplicitAny": true, 12 | "strictBindCallApply": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true 18 | }, 19 | "exclude": ["build", "node_modules", "test"], 20 | "references": [ 21 | { 22 | "path": "../rrweb-player" 23 | }, 24 | { 25 | "path": "../types" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /packages/rrweb-player/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | types 3 | vite.config.ts 4 | vite-env.d.ts 5 | svelte.config.js 6 | public/events.js 7 | src/**/*.d.ts -------------------------------------------------------------------------------- /packages/rrweb-player/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type { import("eslint").Linter.Config } */ 2 | module.exports = { 3 | root: true, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:svelte/recommended', 8 | '../../.eslintrc.js', 9 | ], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['@typescript-eslint'], 12 | parserOptions: { 13 | sourceType: 'module', 14 | ecmaVersion: 2020, 15 | extraFileExtensions: ['.svelte'], 16 | }, 17 | env: { 18 | browser: true, 19 | es2017: true, 20 | node: true, 21 | }, 22 | overrides: [ 23 | { 24 | files: ['*.svelte'], 25 | parser: 'svelte-eslint-parser', 26 | parserOptions: { 27 | parser: '@typescript-eslint/parser', 28 | }, 29 | }, 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /packages/rrweb-player/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | package-lock.json 4 | yarn.lock 5 | 6 | .vscode 7 | temp 8 | 9 | dist 10 | 11 | *.log 12 | 13 | # Svelte definitions are generated by vite 14 | src/**/*.svelte.d.ts 15 | types -------------------------------------------------------------------------------- /packages/rrweb-player/.prettierignore: -------------------------------------------------------------------------------- 1 | # files generated by svelte-kit 2 | .svelte-kit/generated/* 3 | .svelte-kit/ambient.d.ts 4 | .svelte-kit/non-ambient.d.ts 5 | -------------------------------------------------------------------------------- /packages/rrweb-player/.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "non-interactive": false, 3 | "buildCommand": "npm run build", 4 | "requireCleanWorkingDir": false 5 | } 6 | -------------------------------------------------------------------------------- /packages/rrweb-player/.svelte-kit/generated/client/app.js: -------------------------------------------------------------------------------- 1 | export { matchers } from './matchers.js'; 2 | 3 | export const nodes = [ 4 | () => import('./nodes/0'), 5 | () => import('./nodes/1') 6 | ]; 7 | 8 | export const server_loads = []; 9 | 10 | export const dictionary = { 11 | 12 | }; 13 | 14 | export const hooks = { 15 | handleError: (({ error }) => { console.error(error) }), 16 | 17 | reroute: (() => {}), 18 | transport: {} 19 | }; 20 | 21 | export const decoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.decode])); 22 | 23 | export const decode = (type, value) => decoders[type](value); 24 | 25 | export { default as root } from '../root.svelte'; -------------------------------------------------------------------------------- /packages/rrweb-player/.svelte-kit/generated/client/matchers.js: -------------------------------------------------------------------------------- 1 | export const matchers = {}; -------------------------------------------------------------------------------- /packages/rrweb-player/.svelte-kit/generated/client/nodes/0.js: -------------------------------------------------------------------------------- 1 | export { default as component } from "../../../../../../node_modules/@sveltejs/kit/src/runtime/components/svelte-4/layout.svelte"; -------------------------------------------------------------------------------- /packages/rrweb-player/.svelte-kit/generated/client/nodes/1.js: -------------------------------------------------------------------------------- 1 | export { default as component } from "../../../../../../node_modules/@sveltejs/kit/src/runtime/components/svelte-4/error.svelte"; -------------------------------------------------------------------------------- /packages/rrweb-player/.svelte-kit/non-ambient.d.ts: -------------------------------------------------------------------------------- 1 | 2 | // this file is generated — do not edit it 3 | 4 | 5 | declare module "svelte/elements" { 6 | export interface HTMLAttributes { 7 | 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; 8 | 'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null; 9 | 'data-sveltekit-preload-code'?: 10 | | true 11 | | '' 12 | | 'eager' 13 | | 'viewport' 14 | | 'hover' 15 | | 'tap' 16 | | 'off' 17 | | undefined 18 | | null; 19 | 'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null; 20 | 'data-sveltekit-reload'?: true | '' | 'off' | undefined | null; 21 | 'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null; 22 | } 23 | } 24 | 25 | export {}; 26 | -------------------------------------------------------------------------------- /packages/rrweb-player/.svelte-kit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": {}, 4 | "rootDirs": [ 5 | "..", 6 | "./types" 7 | ], 8 | "verbatimModuleSyntax": true, 9 | "isolatedModules": true, 10 | "lib": [ 11 | "esnext", 12 | "DOM", 13 | "DOM.Iterable" 14 | ], 15 | "moduleResolution": "bundler", 16 | "module": "esnext", 17 | "noEmit": true, 18 | "target": "esnext" 19 | }, 20 | "include": [ 21 | "ambient.d.ts", 22 | "non-ambient.d.ts", 23 | "./types/**/$types.d.ts", 24 | "../vite.config.js", 25 | "../vite.config.ts", 26 | "../src/**/*.js", 27 | "../src/**/*.ts", 28 | "../src/**/*.svelte", 29 | "../tests/**/*.js", 30 | "../tests/**/*.ts", 31 | "../tests/**/*.svelte" 32 | ], 33 | "exclude": [ 34 | "../node_modules/**", 35 | "../src/service-worker.js", 36 | "../src/service-worker/**/*.js", 37 | "../src/service-worker.ts", 38 | "../src/service-worker/**/*.ts", 39 | "../src/service-worker.d.ts", 40 | "../src/service-worker/**/*.d.ts" 41 | ] 42 | } -------------------------------------------------------------------------------- /packages/rrweb-player/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 36 | 37 | -------------------------------------------------------------------------------- /packages/rrweb-player/public/global.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | position: relative; 4 | width: 100%; 5 | height: 100%; 6 | } 7 | 8 | body { 9 | box-sizing: border-box; 10 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 11 | Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; 12 | } 13 | -------------------------------------------------------------------------------- /packages/rrweb-player/src/main.ts: -------------------------------------------------------------------------------- 1 | import _Player from './Player.svelte'; 2 | import type { RRwebPlayerOptions } from './types'; 3 | export class Player extends _Player { 4 | constructor( 5 | options: { 6 | // for compatibility 7 | data?: RRwebPlayerOptions['props']; 8 | } & RRwebPlayerOptions, 9 | ) { 10 | super({ 11 | target: options.target, 12 | props: options.data || options.props, 13 | }); 14 | } 15 | } 16 | 17 | export default Player; 18 | -------------------------------------------------------------------------------- /packages/rrweb-player/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter(), 15 | }, 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /packages/rrweb-player/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "exclude": [ 4 | "package.json", 5 | "vite.config.ts" 6 | ], 7 | "include": [ 8 | "src/**/*", 9 | "vite-env.d.ts" 10 | ], 11 | "compilerOptions": { 12 | "composite": true, 13 | "rootDir": "./src", 14 | // defaults for svelte 15 | "useDefineForClassFields": true, 16 | "resolveJsonModule": true, 17 | /** 18 | * Typecheck JS in `.svelte` and `.js` files by default. 19 | * Disable checkJs if you'd like to use dynamic types in JS. 20 | * Note that setting allowJs false does not prevent the use 21 | * of JS in `.svelte` files. 22 | */ 23 | "allowJs": true, 24 | "checkJs": true 25 | }, 26 | "references": [ 27 | { 28 | "path": "../rrweb" 29 | }, 30 | { 31 | "path": "../packer" 32 | }, 33 | { 34 | "path": "../types" 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /packages/rrweb-player/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "strict": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/rrweb-player/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | package-lock.json 4 | build 5 | dist 6 | es 7 | lib 8 | temp 9 | typings 10 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "non-interactive": true, 3 | "hooks": { 4 | "before:init": ["npm run bundle", "npm run typings"] 5 | }, 6 | "git": { 7 | "requireCleanWorkingDir": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/jsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rrweb/snapshot", 3 | "version": "2.0.0-alpha.14", 4 | "exports": "./src/index.ts" 5 | } 6 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/src/index.ts: -------------------------------------------------------------------------------- 1 | import snapshot, { 2 | serializeNodeWithId, 3 | transformAttribute, 4 | ignoreAttribute, 5 | visitSnapshot, 6 | cleanupSnapshot, 7 | distanceToMatch, 8 | createMatchPredicate, 9 | needMaskingText, 10 | classMatchesRegex, 11 | IGNORED_NODE, 12 | genId, 13 | } from './snapshot'; 14 | import rebuild, { 15 | buildNodeWithSN, 16 | addHoverClass, 17 | createCache, 18 | } from './rebuild'; 19 | export * from './types'; 20 | export * from './utils'; 21 | 22 | export { 23 | snapshot, 24 | serializeNodeWithId, 25 | rebuild, 26 | buildNodeWithSN, 27 | addHoverClass, 28 | createCache, 29 | transformAttribute, 30 | ignoreAttribute, 31 | visitSnapshot, 32 | cleanupSnapshot, 33 | distanceToMatch, 34 | createMatchPredicate, 35 | needMaskingText, 36 | classMatchesRegex, 37 | IGNORED_NODE, 38 | genId, 39 | }; 40 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/css/style-with-import.css: -------------------------------------------------------------------------------- 1 | @import '//fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&family=Roboto:wght@100;300;400;500;700&display=swap"'; 2 | @import './style.css'; 3 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | background: url('../a.jpg'); 4 | border-image: url('data:image/svg+xml;utf8,'); 5 | } 6 | p { 7 | color: red; 8 | background: url('./b.jpg'); 9 | } 10 | body > p { 11 | color: yellow; 12 | } 13 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/about-mozilla.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | The Book of Mozilla, 11:9 6 | 37 | 38 | 39 | 40 | 41 |

42 | Mammon slept. And the beast reborn spread over the earth and its numbers 43 | grew legion. And they proclaimed the times and sacrificed crops unto the 44 | fire, with the cunning of foxes. And they built a new world in their own 45 | image as promised by the 46 | sacred words, and spoke 47 | of the beast with their children. Mammon awoke, and lo! it was 48 | naught but a follower. 49 |

50 | 51 |

52 | from The Book of Mozilla, 11:9
(10th Edition) 53 |

54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/background-clip-text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |

The background is clipped to the foreground text.

12 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |

Title

13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/block-element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 19 | 20 | 21 | 22 |
block 1
23 |
record 2
24 |
block 3
25 |
block 3
26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/compat-mode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Compat Mode; image resizing 5 | 6 | 7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/cors-style-sheet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | with style sheet 8 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/dynamic-stylesheet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | dynamic stylesheet 8 | 9 | 16 | 17 | 18 |

p tag

19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/form-fields-sensitive-update.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | form fields 8 | 9 | 10 | 11 |
12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 |
37 | 38 | 43 | 44 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/form-fields-sensitive.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | form fields 8 | 9 | 10 | 11 |
12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/form-fields.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | form fields 8 | 9 | 10 | 11 |
12 | 15 | 18 | 21 | 24 | 27 | 33 | 36 |
37 | 38 | 46 | 47 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/hover.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | hover selector 9 | 25 | 26 | 27 | 28 |
hover me
29 | 30 | 31 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/iframe-inner.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | iframe 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/invalid-attribute.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/invalid-doctype.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Invalid Doctype 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/invalid-tagname.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | Hello 11 | Hello 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/mask-text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |

mask 1

12 |
13 | mask 2 14 |
15 |
mask 3
16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/picture-blob-in-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/picture-blob.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | This is a robot 4 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/picture-in-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/picture-with-inline-onload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | This is a robot 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/picture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | This is a robot 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/preload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | video 8 | 9 | 10 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/with-relative-res.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | Hello 12 | Hello 13 | Hello 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/with-script.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | with script 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/with-style-sheet-with-import.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | with style sheet with import 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/html/with-style-sheet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | with style sheet 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/iframe-html/frame1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Frame 1 7 | 8 | 9 | frame 1 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/iframe-html/frame2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Frame 2 7 | 8 | 9 | frame 2 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/iframe-html/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Main 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/images/compat-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrweb-snapshot/test/images/compat-bottom.png -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/images/compat-top-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrweb-snapshot/test/images/compat-top-left.png -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/images/compat-top-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrweb-snapshot/test/images/compat-top-right.png -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/images/robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrweb-snapshot/test/images/robot.png -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/images/rrweb-favicon-20x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrweb-snapshot/test/images/rrweb-favicon-20x20.png -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/images/symbol-defs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | behance 5 | 6 | 7 | 8 | linkedin 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/js/a.js: -------------------------------------------------------------------------------- 1 | var a = 1 + 1; 2 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/test/utils.ts: -------------------------------------------------------------------------------- 1 | import * as puppeteer from 'puppeteer'; 2 | import * as http from 'http'; 3 | 4 | export async function waitForRAF(page: puppeteer.Page) { 5 | return await page.evaluate(() => { 6 | return new Promise((resolve) => { 7 | requestAnimationFrame(() => { 8 | requestAnimationFrame(resolve); 9 | }); 10 | }); 11 | }); 12 | } 13 | 14 | export function getServerURL(server: http.Server): string { 15 | const address = server.address(); 16 | if (address && typeof address !== 'string') { 17 | return `http://localhost:${address.port}`; 18 | } else { 19 | return `${address}`; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "exclude": ["vite.config.ts", "vitest.config.ts", "test"], 5 | "compilerOptions": { 6 | "rootDir": "src", 7 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 8 | }, 9 | "references": [] 10 | } 11 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/vite.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import config from '../../vite.config.default'; 3 | 4 | export default config(path.resolve(__dirname, 'src/index.ts'), 'rrwebSnapshot'); 5 | -------------------------------------------------------------------------------- /packages/rrweb-snapshot/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineProject, mergeConfig } from 'vitest/config'; 3 | import configShared from '../../vitest.config.ts'; 4 | 5 | export default mergeConfig( 6 | configShared, 7 | defineProject({ 8 | test: { 9 | // ... custom test config here 10 | }, 11 | }), 12 | ); 13 | -------------------------------------------------------------------------------- /packages/rrweb-worker/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.js"], 3 | "rules": { 4 | "prefer-template": "off", 5 | "@typescript-eslint/restrict-plus-operands": "off" 6 | } 7 | } -------------------------------------------------------------------------------- /packages/rrweb-worker/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | node_modules 4 | package-lock.json 5 | # yarn.lock 6 | *.tsbuildinfo 7 | build 8 | dist 9 | es 10 | lib 11 | typings 12 | 13 | temp 14 | 15 | *.log 16 | 17 | .env 18 | __diff_output__ -------------------------------------------------------------------------------- /packages/rrweb-worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sentry-internal/rrweb-worker", 3 | "version": "2.35.0", 4 | "description": "Worker for rrweb", 5 | "main": "dist/rrweb-worker/index.js", 6 | "module": "dist/rrweb-worker/index.mjs", 7 | "types": "dist/typings/index.d.ts", 8 | "sideEffects": false, 9 | "private": true, 10 | "scripts": { 11 | "build": "rollup --config", 12 | "typings": "tsc -p tsconfig.types.json", 13 | "prepack": "yarn run typings && yarn run build", 14 | "check-types": "tsc -noEmit", 15 | "test": "vitest run", 16 | "test:watch": "vitest watch", 17 | "test:update": "vitest run --update", 18 | "lint": "yarn eslint src" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/getsentry/rrweb.git" 23 | }, 24 | "author": "Sentry", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/getsentry/rrweb/issues" 28 | }, 29 | "dependencies": { 30 | "@sentry-internal/rrweb-snapshot": "2.35.0", 31 | "@sentry-internal/rrweb-types": "2.35.0" 32 | }, 33 | "devDependencies": { 34 | "@rollup/plugin-commonjs": "^28.0.2", 35 | "@rollup/plugin-node-resolve": "^16.0.0", 36 | "@types/css-font-loading-module": "0.0.7", 37 | "rollup": "^4.28.1", 38 | "rollup-plugin-terser": "^7.0.2", 39 | "rollup-plugin-typescript2": "^0.36.0", 40 | "vite": "^5.2.8", 41 | "vitest": "^1.4.0" 42 | }, 43 | "engines": { 44 | "node": ">=12" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/rrweb-worker/src/getScaledDimensions.ts: -------------------------------------------------------------------------------- 1 | export function getScaledDimensions( 2 | width: number, 3 | height: number, 4 | maxSize?: [width: number, height: number], 5 | ) { 6 | if (!maxSize) { 7 | return [width, height]; 8 | } 9 | const [maxWidth, maxHeight] = maxSize; 10 | 11 | // Nothing to do here 12 | if (width <= maxWidth && height <= maxHeight) { 13 | return [width, height]; 14 | } 15 | 16 | let targetWidth = width; 17 | let targetHeight = height; 18 | 19 | // TODO: memoization could be a nice optimization here as canvas sizes should 20 | // not be too dynamic 21 | 22 | // scale down each dimension 23 | if (targetWidth > maxWidth) { 24 | targetHeight = Math.floor((maxWidth * height) / width); 25 | targetWidth = maxWidth; 26 | } 27 | if (targetHeight > maxHeight) { 28 | targetWidth = Math.floor((maxHeight * width) / height); 29 | targetHeight = maxHeight; 30 | } 31 | 32 | return [targetWidth, targetHeight]; 33 | } 34 | -------------------------------------------------------------------------------- /packages/rrweb-worker/src/image-bitmap-data-url-worker.ts: -------------------------------------------------------------------------------- 1 | // This is replaced at build-time with the content from _image-bitmap-data-url-worker.ts, wrapped as a string. 2 | // This is just a placeholder so that types etc. are correct. 3 | export default '' as string; 4 | -------------------------------------------------------------------------------- /packages/rrweb-worker/src/index.ts: -------------------------------------------------------------------------------- 1 | import workerString from './image-bitmap-data-url-worker'; 2 | 3 | /** 4 | * Get the URL for a web worker. 5 | */ 6 | export function getImageBitmapDataUrlWorkerURL(): string { 7 | const workerBlob = new Blob([workerString]); 8 | return URL.createObjectURL(workerBlob); 9 | } 10 | -------------------------------------------------------------------------------- /packages/rrweb-worker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "moduleResolution": "Node", 5 | "target": "ES2020", 6 | "noImplicitAny": true, 7 | "strictNullChecks": true, 8 | "removeComments": true, 9 | "preserveConstEnums": true, 10 | "rootDir": "src", 11 | "outDir": "dist", 12 | "lib": [ 13 | "webworker", 14 | "scripthost" 15 | ], 16 | "allowSyntheticDefaultImports": true, 17 | "declarationMap": false, 18 | "skipLibCheck": true, 19 | "composite": true 20 | }, 21 | "references": [ 22 | { 23 | "path": "../rrweb-snapshot" 24 | }, 25 | { 26 | "path": "../types" 27 | } 28 | ], 29 | "exclude": [ 30 | "test", 31 | "scripts" 32 | ], 33 | "include": [ 34 | "src/**/*.ts" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/rrweb-worker/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/*.ts"], 4 | "compilerOptions": { 5 | "declarationMap": true, 6 | "emitDeclarationOnly": true, 7 | "outDir": "dist/typings" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/rrweb-worker/vite.config.js: -------------------------------------------------------------------------------- 1 | import config from '../../vite.config.default'; 2 | 3 | // export default config('src/index.ts', 'rrweb', { outputDir: 'dist/main' }); 4 | export default config('src/index.ts', 'rrweb-worker'); 5 | -------------------------------------------------------------------------------- /packages/rrweb-worker/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineProject, mergeConfig } from 'vitest/config'; 3 | import configShared from '../../vitest.config.ts'; 4 | 5 | export default mergeConfig( 6 | configShared, 7 | defineProject({ 8 | test: { 9 | // ... custom test config here 10 | }, 11 | }), 12 | ); 13 | -------------------------------------------------------------------------------- /packages/rrweb/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | node_modules 4 | package-lock.json 5 | # yarn.lock 6 | tsconfig.tsbuildinfo 7 | build 8 | dist 9 | es 10 | lib 11 | typings 12 | 13 | temp 14 | 15 | *.log 16 | 17 | .env 18 | __diff_output__ -------------------------------------------------------------------------------- /packages/rrweb/.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "non-interactive": true, 3 | "hooks": { 4 | "before:init": ["npm run bundle", "npm run typings"] 5 | }, 6 | "git": { 7 | "requireCleanWorkingDir": false 8 | }, 9 | "github": { 10 | "release": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/rrweb/rrweb-record/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../dist/rrweb-record.cjs", 3 | "types": "../dist/rrweb-record.d.ts" 4 | } 5 | -------------------------------------------------------------------------------- /packages/rrweb/rrweb-replay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "../dist/rrweb-replay.cjs", 3 | "types": "../dist/rrweb-replay.d.ts" 4 | } 5 | -------------------------------------------------------------------------------- /packages/rrweb/src/entries/record.ts: -------------------------------------------------------------------------------- 1 | import record from '../record'; 2 | 3 | export { record }; 4 | -------------------------------------------------------------------------------- /packages/rrweb/src/entries/replay.ts: -------------------------------------------------------------------------------- 1 | export * from '../replay'; 2 | -------------------------------------------------------------------------------- /packages/rrweb/src/index.ts: -------------------------------------------------------------------------------- 1 | import record from './record'; 2 | import { 3 | Replayer, 4 | type playerConfig, 5 | type PlayerState, 6 | type SpeedState, 7 | type PlayerMachineState, 8 | type SpeedMachineState, 9 | } from './replay'; 10 | import canvasMutation from './replay/canvas'; 11 | import * as utils from './utils'; 12 | 13 | export { 14 | EventType, 15 | IncrementalSource, 16 | MouseInteractions, 17 | ReplayerEvents, 18 | } from '@sentry-internal/rrweb-types'; 19 | 20 | export type { 21 | canvasMutationParam, 22 | canvasMutationData, 23 | eventWithTime, 24 | fullSnapshotEvent, 25 | incrementalSnapshotEvent, 26 | inputData, 27 | } from '@sentry-internal/rrweb-types'; 28 | 29 | export type { recordOptions, ReplayPlugin } from './types'; 30 | export { deserializeArg } from './replay/canvas/deserialize-args'; 31 | export { addCustomEvent, freezePage, takeFullSnapshot } from './record'; 32 | export type { CanvasManagerConstructorOptions } from './record'; 33 | export { CanvasManager } from './record/observers/canvas/canvas-manager'; 34 | 35 | export { 36 | record, 37 | Replayer, 38 | type playerConfig, 39 | type PlayerState, 40 | type SpeedState, 41 | type PlayerMachineState, 42 | type SpeedMachineState, 43 | canvasMutation, 44 | utils, 45 | }; 46 | -------------------------------------------------------------------------------- /packages/rrweb/src/record/error-handler.ts: -------------------------------------------------------------------------------- 1 | import type { ErrorHandler } from '../types'; 2 | 3 | type Callback = (...args: unknown[]) => unknown; 4 | 5 | let errorHandler: ErrorHandler | undefined; 6 | 7 | export function registerErrorHandler(handler: ErrorHandler | undefined) { 8 | errorHandler = handler; 9 | } 10 | 11 | export function unregisterErrorHandler() { 12 | errorHandler = undefined; 13 | } 14 | 15 | /** 16 | * Wrap callbacks in a wrapper that allows to pass errors to a configured `errorHandler` method. 17 | */ 18 | export const callbackWrapper = (cb: T): T => { 19 | if (!errorHandler) { 20 | return cb; 21 | } 22 | 23 | const rrwebWrapped = ((...rest: unknown[]) => { 24 | try { 25 | return cb(...rest); 26 | } catch (error) { 27 | if (errorHandler && errorHandler(error) === true) { 28 | return () => { 29 | // This will get called by `record()`'s cleanup function 30 | }; 31 | } 32 | 33 | throw error; 34 | } 35 | }) as unknown as T; 36 | 37 | return rrwebWrapped; 38 | }; 39 | -------------------------------------------------------------------------------- /packages/rrweb/src/record/processed-node-manager.ts: -------------------------------------------------------------------------------- 1 | import { onRequestAnimationFrame } from '../utils'; 2 | import type MutationBuffer from './mutation'; 3 | 4 | /** 5 | * Keeps a log of nodes that could show up in multiple mutation buffer but shouldn't be handled twice. 6 | */ 7 | export default class ProcessedNodeManager { 8 | private nodeMap: WeakMap> = new WeakMap(); 9 | 10 | private active = false; 11 | 12 | public inOtherBuffer(node: Node, thisBuffer: MutationBuffer) { 13 | const buffers = this.nodeMap.get(node); 14 | return ( 15 | buffers && Array.from(buffers).some((buffer) => buffer !== thisBuffer) 16 | ); 17 | } 18 | 19 | public add(node: Node, buffer: MutationBuffer) { 20 | if (!this.active) { 21 | this.active = true; 22 | onRequestAnimationFrame(() => { 23 | this.nodeMap = new WeakMap(); 24 | this.active = false; 25 | }); 26 | } 27 | this.nodeMap.set(node, (this.nodeMap.get(node) || new Set()).add(buffer)); 28 | } 29 | 30 | public destroy() { 31 | // cleanup no longer needed 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/rrweb/src/record/workers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["webworker"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/rrweb/src/replay/styles/inject-style.ts: -------------------------------------------------------------------------------- 1 | const rules: (blockClass: string) => string[] = (blockClass: string) => [ 2 | `.${blockClass} { background: currentColor }`, 3 | 'noscript { display: none !important; }', 4 | ]; 5 | 6 | export default rules; 7 | -------------------------------------------------------------------------------- /packages/rrweb/test/e2e/__image_snapshots__/webgl-test-ts-test-e-2-e-webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-image-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrweb/test/e2e/__image_snapshots__/webgl-test-ts-test-e-2-e-webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-image-1-snap.png -------------------------------------------------------------------------------- /packages/rrweb/test/e2e/__image_snapshots__/webgl-test-ts-test-e-2-e-webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-square-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrweb/test/e2e/__image_snapshots__/webgl-test-ts-test-e-2-e-webgl-test-ts-e-2-e-webgl-will-record-and-replay-a-webgl-square-1-snap.png -------------------------------------------------------------------------------- /packages/rrweb/test/html/assets/1-minute-of-silence.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrweb/test/html/assets/1-minute-of-silence.mp3 -------------------------------------------------------------------------------- /packages/rrweb/test/html/assets/robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrweb/test/html/assets/robot.png -------------------------------------------------------------------------------- /packages/rrweb/test/html/assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: pink; 3 | } 4 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/audio.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Audio 8 | 9 | 10 |

1 minute of silence

11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/benchmark-dom-mutation-add-and-move.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 37 | 38 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/benchmark-dom-mutation-add-and-remove.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 46 | 47 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/benchmark-dom-mutation-attributes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/benchmark-dom-mutation-deep-nested.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31 | 32 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/benchmark-dom-mutation-multiple-descendant-add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/benchmark-dom-mutation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 27 | 28 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/benchmark-text-masking.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 30 | 31 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/blank.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/block.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Block record 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/canvas-iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/canvas-shadow-dom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 30 | 31 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/canvas-webgl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | canvas 7 | 8 | 9 | 15 | 16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | canvas 7 | 8 | 9 | 15 | 16 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Empty 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/frame-image-blob-url.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Frame with image 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/frame1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Frame 1 7 | 8 | 9 | frame 1 10 | 11 | 12 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/frame2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Frame 2 7 | 8 | 9 | frame 2 10 | 11 | 18 | 19 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/hello-world.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello World! 8 | 9 | 10 | Hello world! 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/ignore.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ignore fields 8 | 9 | 10 | 11 |
12 | 15 | 18 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/image-blob-url.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Image with blob:url 8 | 9 | 10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/link.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Link click 8 | 9 | 10 | 11 | not link 12 | link 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Main 7 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/mask-text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Mask text 8 | 9 | 10 |

mask1

11 |
12 | mask2 13 |
14 |
15 |
16 |
mask3
17 |
18 |
19 | 20 |
mask4
21 | 22 | 23 |

24 | unmask1 25 |

26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/move-node.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

6 |
7 | 8 | 9 | 1 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/mutation-observer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

mutation observer

4 |
    5 |
  • 6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/password.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/polyfilled-shadowdom-mutation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 |
10 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/select2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Select2 3.5 8 | 9 | 10 | 11 |
12 | Select2 is a jQuery replacement for select boxes. 13 |
14 | In the 3.5 version it use a quite complicated DOM generation strategy which is a good battle-test for rrweb's recorder. 15 |
16 | 20 | 21 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/shuffle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | shuffle 7 | 8 | 9 | 10 |
  • 1
  • 2
  • 3
  • 4
  • 5
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/unblock.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Unblock record 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/rrweb/test/html/unmask-text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Unmask text 8 | 9 | 10 |
unmask1 11 |
12 | mask1 13 |
14 |
15 |
16 |
17 |
mask2
18 |
unmask2
19 |
20 |
    21 |
    22 |
    masked from maskAllText
    23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/rrweb/test/machine.test.ts: -------------------------------------------------------------------------------- 1 | import { discardPriorSnapshots } from '../src/replay/machine'; 2 | import { sampleEvents } from './utils'; 3 | import { EventType } from '@sentry-internal/rrweb-types'; 4 | 5 | const events = sampleEvents.filter( 6 | (e) => ![EventType.DomContentLoaded, EventType.Load].includes(e.type), 7 | ); 8 | const nextEvents = events.map((e) => ({ 9 | ...e, 10 | timestamp: e.timestamp + 1000, 11 | })); 12 | const nextNextEvents = nextEvents.map((e) => ({ 13 | ...e, 14 | timestamp: e.timestamp + 1000, 15 | })); 16 | 17 | describe('get last session', () => { 18 | it('will return all the events when there is only one session', () => { 19 | expect(discardPriorSnapshots(events, events[0].timestamp)).toEqual(events); 20 | }); 21 | 22 | it('will return last session when there is more than one in the events', () => { 23 | const multiple = events.concat(nextEvents).concat(nextNextEvents); 24 | expect( 25 | discardPriorSnapshots( 26 | multiple, 27 | nextNextEvents[nextNextEvents.length - 1].timestamp, 28 | ), 29 | ).toEqual(nextNextEvents); 30 | }); 31 | 32 | it('will return last session when baseline time is future time', () => { 33 | const multiple = events.concat(nextEvents).concat(nextNextEvents); 34 | expect( 35 | discardPriorSnapshots( 36 | multiple, 37 | nextNextEvents[nextNextEvents.length - 1].timestamp + 1000, 38 | ), 39 | ).toEqual(nextNextEvents); 40 | }); 41 | 42 | it('will return all sessions when baseline time is prior time', () => { 43 | expect(discardPriorSnapshots(events, events[0].timestamp - 1000)).toEqual( 44 | events, 45 | ); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/rrweb/test/replay/__image_snapshots__/hover-test-ts-test-replay-hover-test-ts-replayer-hover-should-trigger-hover-on-mouse-down-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrweb/test/replay/__image_snapshots__/hover-test-ts-test-replay-hover-test-ts-replayer-hover-should-trigger-hover-on-mouse-down-1-snap.png -------------------------------------------------------------------------------- /packages/rrweb/test/replay/__image_snapshots__/webgl-test-ts-test-replay-webgl-test-ts-replayer-webgl-should-output-simple-webgl-object-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/rrweb/test/replay/__image_snapshots__/webgl-test-ts-test-replay-webgl-test-ts-replayer-webgl-should-output-simple-webgl-object-1-snap.png -------------------------------------------------------------------------------- /packages/rrweb/test/replay/webgl-mutation.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @vitest-environment jsdom 3 | */ 4 | import { vi } from 'vitest'; 5 | import { polyfillWebGLGlobals } from '../utils'; 6 | polyfillWebGLGlobals(); 7 | 8 | import webglMutation from '../../src/replay/canvas/webgl'; 9 | import { CanvasContext } from '@sentry-internal/rrweb-types'; 10 | import { variableListFor } from '../../src/replay/canvas/deserialize-args'; 11 | 12 | let canvas: HTMLCanvasElement; 13 | describe('webglMutation', () => { 14 | beforeEach(() => { 15 | canvas = document.createElement('canvas'); 16 | }); 17 | afterEach(() => { 18 | vi.clearAllMocks(); 19 | }); 20 | 21 | it('should create webgl variables', async () => { 22 | const createShaderMock = vi.fn().mockImplementation(() => { 23 | return new WebGLShader(); 24 | }); 25 | const context = { 26 | createShader: createShaderMock, 27 | } as unknown as WebGLRenderingContext; 28 | vi.spyOn(canvas, 'getContext').mockImplementation(() => { 29 | return context; 30 | }); 31 | 32 | expect(variableListFor(context, 'WebGLShader')).toHaveLength(0); 33 | 34 | await webglMutation({ 35 | mutation: { 36 | property: 'createShader', 37 | args: [35633], 38 | }, 39 | type: CanvasContext.WebGL, 40 | target: canvas, 41 | imageMap: new Map(), 42 | errorHandler: () => {}, 43 | }); 44 | 45 | expect(createShaderMock).toHaveBeenCalledWith(35633); 46 | expect(variableListFor(context, 'WebGLShader')).toHaveLength(1); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/rrweb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "target": "ES2020", 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "removeComments": true, 11 | "preserveConstEnums": true, 12 | "outDir": "build", 13 | "lib": [ 14 | "es6", 15 | "dom", 16 | "es2021.WeakRef" 17 | ], 18 | "rootDir": "src", 19 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo", 20 | "types": [ 21 | // from tsconfig.base.json 22 | "vite/client", 23 | "@types/dom-mediacapture-transform", 24 | "@types/offscreencanvas", 25 | 26 | // rrweb specific: 27 | /* 28 | * @see https://vitest.dev/config/#globals 29 | * if we remove the --globals flag from the vite test command, we can remove this 30 | * to remove the flag, we need to add vitest imports in the test files 31 | */ 32 | "vitest/globals" 33 | ], 34 | 35 | // TODO: enable me in the future, this is quite a large project 36 | // at time of writing (April 2024) there are over 100 errors in rrweb 37 | "strict": false 38 | }, 39 | "references": [ 40 | { 41 | "path": "../rrdom" 42 | }, 43 | { 44 | "path": "../rrweb-snapshot" 45 | }, 46 | { 47 | "path": "../types" 48 | }, 49 | { 50 | "path": "../rrweb-worker" 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /packages/rrweb/vite.config.entries.js: -------------------------------------------------------------------------------- 1 | import config from '../../vite.config.default'; 2 | 3 | export default config( 4 | { 5 | // rrweb: 'src/index.ts', 6 | 'rrweb-record': 'src/entries/record.ts', 7 | 'rrweb-replay': 'src/entries/replay.ts', 8 | }, 9 | 'rrweb', 10 | // { outputDir: 'dist/alt' }, 11 | { outputDir: 'dist' }, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/rrweb/vite.config.js: -------------------------------------------------------------------------------- 1 | import config from '../../vite.config.default'; 2 | 3 | // export default config('src/index.ts', 'rrweb', { outputDir: 'dist/main' }); 4 | export default config('src/index.ts', 'rrweb'); 5 | -------------------------------------------------------------------------------- /packages/rrweb/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineProject, mergeConfig } from 'vitest/config'; 3 | import configShared from '../../vitest.config'; 4 | 5 | export default mergeConfig( 6 | configShared, 7 | defineProject({ 8 | test: { 9 | globals: true, 10 | }, 11 | }), 12 | ); 13 | -------------------------------------------------------------------------------- /packages/types/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | es 3 | lib 4 | typings 5 | -------------------------------------------------------------------------------- /packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "exclude": ["vite.config.ts"], 5 | "compilerOptions": { 6 | "rootDir": "src", 7 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 8 | }, 9 | "references": [ 10 | { 11 | "path": "../rrweb-snapshot" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/types/vite.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import config from '../../vite.config.default'; 3 | 4 | export default config(path.resolve(__dirname, 'src/index.ts'), 'rrwebTypes'); 5 | -------------------------------------------------------------------------------- /packages/web-extension/src/assets/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/web-extension/src/assets/icon128.png -------------------------------------------------------------------------------- /packages/web-extension/src/assets/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/web-extension/src/assets/icon16.png -------------------------------------------------------------------------------- /packages/web-extension/src/assets/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/rrweb/6b8c4596bfc89967f2b7279bd0c054a3d8694fa7/packages/web-extension/src/assets/icon48.png -------------------------------------------------------------------------------- /packages/web-extension/src/components/CircleButton.tsx: -------------------------------------------------------------------------------- 1 | import { Button, ButtonProps } from '@chakra-ui/react'; 2 | 3 | interface CircleButtonProps extends ButtonProps { 4 | diameter: number; 5 | onClick?: () => void; 6 | children?: React.ReactNode; 7 | title?: string; 8 | } 9 | 10 | export function CircleButton({ 11 | diameter, 12 | onClick, 13 | children, 14 | title, 15 | ...rest 16 | }: CircleButtonProps) { 17 | return ( 18 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /packages/web-extension/src/options/App.tsx: -------------------------------------------------------------------------------- 1 | import { Route, Routes } from 'react-router-dom'; 2 | import SidebarWithHeader from '~/components/SidebarWithHeader'; 3 | import { FiList, FiSettings } from 'react-icons/fi'; 4 | import { Box } from '@chakra-ui/react'; 5 | 6 | export default function App() { 7 | return ( 8 | 24 | 25 | 26 | } /> 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/web-extension/src/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | rrweb settings 4 | 5 | 6 |
    7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/web-extension/src/options/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ChakraProvider } from '@chakra-ui/react'; 3 | import * as ReactDOM from 'react-dom/client'; 4 | import { createHashRouter, RouterProvider } from 'react-router-dom'; 5 | import App from './App'; 6 | 7 | const rootElement = document.getElementById('root'); 8 | const router = createHashRouter([ 9 | { 10 | path: '/*', 11 | element: , 12 | }, 13 | ]); 14 | 15 | rootElement && 16 | ReactDOM.createRoot(rootElement).render( 17 | 18 | 19 | 20 | 21 | , 22 | ); 23 | -------------------------------------------------------------------------------- /packages/web-extension/src/pages/App.tsx: -------------------------------------------------------------------------------- 1 | import { Route, Routes } from 'react-router-dom'; 2 | import SidebarWithHeader from '~/components/SidebarWithHeader'; 3 | import { SessionList } from './SessionList'; 4 | import { FiList, FiSettings } from 'react-icons/fi'; 5 | import Player from './Player'; 6 | 7 | export default function App() { 8 | return ( 9 | 31 | 32 | } /> 33 | } /> 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /packages/web-extension/src/pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | rrweb 4 | 5 | 6 |
    7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/web-extension/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ChakraProvider } from '@chakra-ui/react'; 3 | import * as ReactDOM from 'react-dom/client'; 4 | import { createHashRouter, RouterProvider } from 'react-router-dom'; 5 | import App from './App'; 6 | 7 | const rootElement = document.getElementById('root'); 8 | const router = createHashRouter([ 9 | { 10 | path: '/*', 11 | element: , 12 | }, 13 | ]); 14 | 15 | rootElement && 16 | ReactDOM.createRoot(rootElement).render( 17 | 18 | 19 | 20 | 21 | , 22 | ); 23 | -------------------------------------------------------------------------------- /packages/web-extension/src/popup/Timer.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { Stat, StatNumber } from '@chakra-ui/react'; 3 | import { formatTime } from '~/utils'; 4 | 5 | export function Timer({ 6 | startTime, 7 | ticking, 8 | }: { 9 | startTime: number; 10 | ticking: boolean; 11 | }) { 12 | const [time, setTime] = useState(Date.now() - startTime); 13 | useEffect(() => { 14 | if (!ticking) return; 15 | const interval = setInterval(() => { 16 | setTime(Date.now() - startTime); 17 | }, 100); 18 | return () => clearInterval(interval); 19 | }, [startTime, ticking]); 20 | return ( 21 | 22 | {formatTime(time)} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/web-extension/src/popup/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ChakraProvider } from '@chakra-ui/react'; 3 | import * as ReactDOM from 'react-dom/client'; 4 | import { App } from './App'; 5 | 6 | const rootElement = document.getElementById('root'); 7 | 8 | rootElement && 9 | ReactDOM.createRoot(rootElement).render( 10 | 11 | 12 | 13 | 14 | , 15 | ); 16 | -------------------------------------------------------------------------------- /packages/web-extension/src/popup/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
    6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/web-extension/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function isFirefox(): boolean { 2 | return window.navigator.userAgent.toLowerCase().indexOf('firefox') > -1; 3 | } 4 | 5 | export function isInCrossOriginIFrame(): boolean { 6 | if (window.parent !== window) { 7 | try { 8 | void window.parent.location.origin; 9 | } catch (error) { 10 | return true; 11 | } 12 | } 13 | return false; 14 | } 15 | 16 | const SECOND = 1000; 17 | const MINUTE = 60 * SECOND; 18 | const HOUR = 60 * MINUTE; 19 | 20 | export function formatTime(ms: number): string { 21 | if (ms <= 0) { 22 | return '00:00'; 23 | } 24 | const hour = Math.floor(ms / HOUR); 25 | ms = ms % HOUR; 26 | const minute = Math.floor(ms / MINUTE); 27 | ms = ms % MINUTE; 28 | const second = Math.floor(ms / SECOND); 29 | if (hour) { 30 | return `${padZero(hour)}:${padZero(minute)}:${padZero(second)}`; 31 | } 32 | return `${padZero(minute)}:${padZero(second)}`; 33 | } 34 | 35 | function padZero(num: number, len = 2): string { 36 | let str = String(num); 37 | const threshold = Math.pow(10, len - 1); 38 | if (num < threshold) { 39 | while (String(threshold).length > str.length) { 40 | str = `0${num}`; 41 | } 42 | } 43 | return str; 44 | } 45 | -------------------------------------------------------------------------------- /packages/web-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "baseUrl": ".", 6 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo", 7 | "esModuleInterop": true, 8 | "incremental": true, 9 | "resolveJsonModule": true, 10 | "paths": { 11 | "~/*": [ 12 | "src/*" 13 | ] 14 | }, 15 | "jsx": "react-jsx" 16 | }, 17 | "exclude": ["dist", "node_modules", "vite.config.ts"], 18 | "references": [ 19 | { 20 | "path": "../rrweb" 21 | }, 22 | { 23 | "path": "../rrweb-player" 24 | }, 25 | { 26 | "path": "../types" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /scripts/craft-pre-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eux 3 | 4 | # Move to the project root 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 6 | cd $SCRIPT_DIR/.. 7 | OLD_VERSION="${1}" 8 | NEW_VERSION="${2}" 9 | 10 | # Do not tag and commit changes made by "npm version" 11 | export npm_config_git_tag_version=false 12 | 13 | yarn install --frozen-lockfile 14 | # --force-publish - force publish all packages, this will skip the lerna changed check for changed packages and forces a package that didn't have a git diff change to be updated. 15 | # --exact - specify updated dependencies in updated packages exactly (with no punctuation), instead of as semver compatible (with a ^). 16 | # --no-git-tag-version - don't commit changes to package.json files and don't tag the release. 17 | # --no-push - don't push committed and tagged changes. 18 | # --include-merged-tags - include tags from merged branches when detecting changed packages. 19 | # --yes - skip all confirmation prompts 20 | yarn lerna version --force-publish --exact --no-git-tag-version --no-push --include-merged-tags --yes "${NEW_VERSION}" 21 | -------------------------------------------------------------------------------- /scripts/lint-packages.sh: -------------------------------------------------------------------------------- 1 | script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 2 | packages_dir="$script_dir/../packages" 3 | 4 | for dir in "$packages_dir"/*/ "$packages_dir/plugins"/*/ ; do 5 | if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then 6 | ( 7 | cd "$dir" || exit 8 | npx publint --strict 9 | attw --pack . --exclude-entrypoints dist/style.css 10 | ) 11 | fi 12 | done -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | /** 5 | * @see https://vitejs.dev/guide/features.html#target 6 | */ 7 | "esModuleInterop": true, 8 | "target": "ESNext", 9 | "module": "ESNext", 10 | "moduleResolution": "Node", 11 | "rootDir": "src", 12 | "outDir": "dist", 13 | "lib": ["es6", "dom"], 14 | "sourceMap": true, 15 | "skipLibCheck": true, 16 | "declaration": true, 17 | "importsNotUsedAsValues": "error", 18 | "strict": true, 19 | "removeComments": true, 20 | "noImplicitAny": true, 21 | "strictNullChecks": true, 22 | "preserveConstEnums": true, 23 | "strictBindCallApply": true, 24 | "noUnusedLocals": true, 25 | "noUnusedParameters": true, 26 | "forceConsistentCasingInFileNames": true, 27 | "downlevelIteration": true, 28 | 29 | // needed for vite 30 | /** 31 | * @see https://vitejs.dev/guide/features.html#isolatedmodules 32 | */ 33 | "isolatedModules": true, 34 | 35 | "types": [ 36 | "node", 37 | /** 38 | * needed as long as we have jest tests 39 | * they add globals like `test` and `expect` 40 | */ 41 | "jest", 42 | /** 43 | * @see https://vitejs.dev/guide/features.html#client-types 44 | */ 45 | "vite/client", 46 | "@types/dom-mediacapture-transform", 47 | "@types/offscreencanvas" 48 | ] 49 | }, 50 | "exclude": ["**/vite.config.ts", "**/vitest.config.ts", "**/test"], 51 | "compileOnSave": true 52 | } 53 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [".eslintrc.js"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "references": [ 3 | { 4 | "path": "packages/rrdom" 5 | }, 6 | { 7 | "path": "packages/rrdom-nodejs" 8 | }, 9 | { 10 | "path": "packages/rrweb" 11 | }, 12 | { 13 | "path": "packages/rrweb-player" 14 | }, 15 | { 16 | "path": "packages/rrweb-snapshot" 17 | }, 18 | { 19 | "path": "packages/types" 20 | }, 21 | { 22 | "path": "packages/plugins/rrweb-plugin-console-replay" 23 | }, 24 | { 25 | "path": "packages/plugins/rrweb-plugin-console-record" 26 | }, 27 | { 28 | "path": "packages/rrvideo" 29 | }, 30 | { 31 | "path": "packages/web-extension" 32 | }, 33 | { 34 | "path": "packages/rrweb-worker" 35 | }, 36 | ], 37 | "files": [], 38 | "include": [], 39 | "exclude": [], 40 | "compilerOptions": { 41 | "rootDir": "." 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | // These root workspace files are reused in workspaces and may affect their build output 4 | "globalDependencies": [ 5 | ".eslintrc.js", 6 | ".prettierrc", 7 | "lerna.json", 8 | "vite.config.defaults.ts", 9 | "tsconfig.json" 10 | ], 11 | "tasks": { 12 | "prepack": { 13 | "dependsOn": ["^prepack"], 14 | "outputs": [ 15 | "lib/**", 16 | "es/**", 17 | "dist/**", 18 | "typings/**", 19 | ".svelte-kit/**", 20 | "types/**" 21 | ] 22 | }, 23 | "test": { 24 | "dependsOn": ["^prepack"], 25 | "passThroughEnv": ["PUPPETEER_HEADLESS"] 26 | }, 27 | "test:watch": { 28 | "persistent": true, 29 | "passThroughEnv": ["PUPPETEER_HEADLESS"] 30 | }, 31 | "test:update": { 32 | "dependsOn": ["^prepack"], 33 | "passThroughEnv": ["PUPPETEER_HEADLESS"] 34 | }, 35 | "dev": { 36 | // "dependsOn": ["^prepack"], 37 | "persistent": true, 38 | "cache": false, 39 | "passThroughEnv": ["CLEAR_DIST_DIR"] 40 | }, 41 | "lint": {}, 42 | "typings": {}, 43 | "check-types": {} 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | test: { 3 | /** 4 | * Keeps old (pre-jest 29) snapshot format 5 | * its a bit ugly and harder to read than the new format, 6 | * so we might want to remove this in its own PR 7 | */ 8 | snapshotFormat: { 9 | escapeString: true, 10 | printBasicPrototype: true, 11 | }, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from 'vitest/config'; 2 | 3 | export default defineWorkspace(['packages/**/vitest.config.ts']); 4 | --------------------------------------------------------------------------------