diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml
new file mode 100644
index 00000000..6d856d22
--- /dev/null
+++ b/.github/workflows/eslint.yml
@@ -0,0 +1,47 @@
+name: Eslint Check
+
+on: [pull_request]
+
+jobs:
+ eslint_check_upload:
+ runs-on: ubuntu-latest
+ name: ESLint Check and Report Upload
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 16
+ cache: 'yarn'
+ - name: Install Dependencies
+ run: YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn
+ - name: Build Packages
+ run: yarn build:all
+ - name: Test Code Linting
+ run: yarn turbo run lint
+ - name: Save Code Linting Report JSON
+ run: yarn lint:report
+ # Continue to the next step even if this fails
+ continue-on-error: true
+ - name: Upload ESLint report
+ uses: actions/upload-artifact@v3
+ with:
+ name: eslint_report.json
+ path: eslint_report.json
+
+ Annotation:
+ # Skip the annotation action in PRs from the forked repositories
+ if: github.event.pull_request.head.repo.full_name == 'rrweb-io/rrweb'
+ needs: eslint_check_upload
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/download-artifact@v3
+ with:
+ name: eslint_report.json
+ - name: Annotate Code Linting Results
+ uses: ataylorme/eslint-annotate-action@v2
+ with:
+ repo-token: '${{ secrets.GITHUB_TOKEN }}'
+ report-json: 'eslint_report.json'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..5026d620
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,29 @@
+name: Release Rrweb
+on: push
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Setup Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: 16.15.0
+ registry-url: https://registry.npmjs.org
+ - name: Install
+ run: YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn
+ - name: Build all
+ run: yarn build:all
+ # - name: Test all
+ # run: yarn test
+ - name: Setup Publish Env
+ run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > ~/.npmrc
+ env:
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ - name: Publish
+ if: github.ref == 'refs/heads/master'
+ run: yarn lerna publish from-package -y
+ env:
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/style-check.yml b/.github/workflows/style-check.yml
index 50498ee9..054e7989 100644
--- a/.github/workflows/style-check.yml
+++ b/.github/workflows/style-check.yml
@@ -18,7 +18,7 @@ jobs:
node-version: 16
cache: 'yarn'
- name: Install Dependencies
- run: yarn
+ run: YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn
- name: Build Packages
run: yarn build:all
- name: Eslint Check
@@ -56,16 +56,13 @@ jobs:
name: Format Check
steps:
- uses: actions/checkout@v3
+ - name: Setup Node.js
+ uses: actions/setup-node@v1
with:
- repository: ${{ github.event.pull_request.head.repo.full_name }}
- ref: ${{ github.head_ref }}
- - name: Setup Node
- uses: actions/setup-node@v3
- with:
- node-version: 16
- cache: 'yarn'
- - name: Install Dependencies
- run: yarn
+ node-version: 16.15.0
+ registry-url: https://registry.npmjs.org
+ - name: Install
+ run: YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn
- name: Prettier Check
run: yarn prettier --check '**/*.{ts,md}'
@@ -76,16 +73,13 @@ jobs:
name: Format Code
steps:
- uses: actions/checkout@v3
+ - name: Setup Node.js
+ uses: actions/setup-node@v1
with:
- repository: ${{ github.event.pull_request.head.repo.full_name }}
- ref: ${{ github.head_ref }}
- - name: Setup Node
- uses: actions/setup-node@v3
- with:
- node-version: 16
- cache: 'yarn'
- - name: Install Dependencies
- run: yarn
+ node-version: 16.15.0
+ registry-url: https://registry.npmjs.org
+ - name: Install
+ run: YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn
- name: Prettify Code
run: yarn prettier --write '**/*.{ts,md}'
- name: Commit Changes
diff --git a/README.md b/README.md
index 3e6490f1..ee3a7a31 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,14 @@ rrweb refers to 'record and replay the web', which is a tool for recording and r
[**🍳 Recipes 🍳**](./docs/recipes/index.md)
+## Version History
+
+### 2.0.0
+
+This version updates the highlight rrweb fork on rrweb 1.1.3 April 2022 Release.
+Because this is a major update, it may not be suitable for customers looking for stable recording and replay functionality.
+However, the major update brings lots of rrweb features that have been in development. See the rrweb release notes for more details.
+
## Project Structure
rrweb is mainly composed of 3 parts:
diff --git a/guide.md b/guide.md
index 76f781d0..b1e65547 100644
--- a/guide.md
+++ b/guide.md
@@ -140,11 +140,11 @@ The parameter of `rrweb.record` accepts the following options.
| emit | required | the callback function to get emitted events |
| checkoutEveryNth | - | take a full snapshot after every N events
refer to the [checkout](#checkout) chapter |
| checkoutEveryNms | - | take a full snapshot after every N ms
refer to the [checkout](#checkout) chapter |
-| blockClass | 'rr-block' | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) chapter |
+| blockClass | 'highlight-block' | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) chapter |
| blockSelector | null | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter |
-| ignoreClass | 'rr-ignore' | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter |
+| ignoreClass | 'highlight-ignore' | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter |
| ignoreCSSAttributes | null | array of CSS attributes that should be ignored |
-| maskTextClass | 'rr-mask' | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter |
+| maskTextClass | 'highlight-mask' | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter |
| maskTextSelector | null | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter |
| maskAllInputs | false | mask all input content as \* |
| maskInputOptions | { password: true } | mask some kinds of input \*
refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95) |
@@ -167,9 +167,9 @@ The parameter of `rrweb.record` accepts the following options.
You may find some contents on the webpage which are not willing to be recorded, then you can use the following approaches:
-- An element with the class name `.rr-block` will not be recorded. Instead, it will replay as a placeholder with the same dimension.
-- An element with the class name `.rr-ignore` will not record its input events.
-- All text of elements with the class name `.rr-mask` and their children will be masked.
+- An element with the class name `.highlight-block` will not be recorded. Instead, it will replay as a placeholder with the same dimension.
+- An element with the class name `.highlight-ignore` will not record its input events.
+- All text of elements with the class name `.highlight-mask` and their children will be masked.
- `input[type="password"]` will be masked by default.
- Mask options to mask the content in input elements.
@@ -300,7 +300,7 @@ The replayer accepts options as its constructor's second parameter, and it has t
| skipInactive | false | whether to skip inactive time |
| showWarning | true | whether to print warning messages during replay |
| showDebug | false | whether to print debug messages during replay |
-| blockClass | 'rr-block' | element with the class name will display as a blocked area |
+| blockClass | 'highlight-block' | element with the class name will display as a blocked area |
| liveMode | false | whether to enable live mode |
| insertStyleRules | [] | accepts multiple CSS rule string, which will be injected into the replay iframe |
| triggerFocus | true | whether to trigger focus during replay |
diff --git a/guide.zh_CN.md b/guide.zh_CN.md
index 1093dbb3..c9b9fd9a 100644
--- a/guide.zh_CN.md
+++ b/guide.zh_CN.md
@@ -136,11 +136,11 @@ setInterval(save, 10 * 1000);
| emit | 必填 | 获取当前录制的数据 |
| checkoutEveryNth | - | 每 N 次事件重新制作一次全量快照
详见[“重新制作快照”](#重新制作快照)章节 |
| checkoutEveryNms | - | 每 N 毫秒重新制作一次全量快照
详见[“重新制作快照”](#重新制作快照)章节 |
-| blockClass | 'rr-block' | 字符串或正则表达式,可用于自定义屏蔽元素的类名,详见[“隐私”](#隐私)章节 |
+| blockClass | 'highlight-block' | 字符串或正则表达式,可用于自定义屏蔽元素的类名,详见[“隐私”](#隐私)章节 |
| blockSelector | null | 所有 element.matches(blockSelector)为 true 的元素都不会被录制,回放时取而代之的是一个同等宽高的占位元素 |
-| ignoreClass | 'rr-ignore' | 字符串或正则表达式,可用于自定义忽略元素的类名,详见[“隐私”](#隐私)章节 |
+| ignoreClass | 'highlight-ignore' | 字符串或正则表达式,可用于自定义忽略元素的类名,详见[“隐私”](#隐私)章节 |
| ignoreCSSAttributes | null | 应该被忽略的 CSS 属性数组 |
-| maskTextClass | 'rr-mask' | 字符串或正则表达式,可用于自定义忽略元素 text 内容的类名,详见[“隐私”](#隐私)章节 |
+| maskTextClass | 'highlight-mask' | 字符串或正则表达式,可用于自定义忽略元素 text 内容的类名,详见[“隐私”](#隐私)章节 |
| maskTextSelector | null | 所有 element.matches(maskTextSelector)为 true 的元素及其子元素的 text 内容将会被屏蔽 |
| maskAllInputs | false | 将所有输入内容记录为 \* |
| maskInputOptions | { password: true } | 选择将特定类型的输入框内容记录为 \*
类型详见[列表](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95) |
@@ -163,9 +163,9 @@ setInterval(save, 10 * 1000);
页面中可能存在一些隐私相关的内容不希望被录制,rrweb 为此做了以下支持:
-- 在 HTML 元素中添加类名 `.rr-block` 将会避免该元素及其子元素被录制,回放时取而代之的是一个同等宽高的占位元素。
-- 在 HTML 元素中添加类名 `.rr-ignore` 将会避免录制该元素的输入事件。
-- 所有带有`.rr-mask`类名的元素及其子元素的 text 内容将会被屏蔽。
+- 在 HTML 元素中添加类名 `.highlight-block` 将会避免该元素及其子元素被录制,回放时取而代之的是一个同等宽高的占位元素。
+- 在 HTML 元素中添加类名 `.highlight-ignore` 将会避免录制该元素的输入事件。
+- 所有带有`.highlight-mask`类名的元素及其子元素的 text 内容将会被屏蔽。
- `input[type="password"]` 类型的密码输入框默认不会录制输入事件。
- 配置中还有更为丰富的隐私保护选项。
@@ -296,7 +296,7 @@ replayer.destroy();
| skipInactive | false | 是否快速跳过无用户操作的阶段 |
| showWarning | true | 是否在回放过程中打印警告信息 |
| showDebug | false | 是否在回放过程中打印 debug 信息 |
-| blockClass | 'rr-block' | 需要在回放时展示为隐藏区域的元素类名 |
+| blockClass | 'highlight-block' | 需要在回放时展示为隐藏区域的元素类名 |
| liveMode | false | 是否开启直播模式 |
| insertStyleRules | [] | 可以传入多个 CSS rule string,用于自定义回放时 iframe 内的样式 |
| triggerFocus | true | 回放时是否回放 focus 交互 |
diff --git a/packages/rrdom-nodejs/LICENSE b/packages/rrdom-nodejs/LICENSE
new file mode 100644
index 00000000..fce28eb8
--- /dev/null
+++ b/packages/rrdom-nodejs/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Contributors (https://github.com/rrweb-io/rrweb/graphs/contributors) and SmartX Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/rrdom-nodejs/package.json b/packages/rrdom-nodejs/package.json
index 84bcc457..f301cfa2 100644
--- a/packages/rrdom-nodejs/package.json
+++ b/packages/rrdom-nodejs/package.json
@@ -1,6 +1,6 @@
{
- "name": "rrdom-nodejs",
- "version": "0.1.6",
+ "name": "@highlight-run/rrdom-nodejs",
+ "version": "0.1.8",
"scripts": {
"dev": "rollup -c -w",
"bundle": "rollup --config",
@@ -46,11 +46,11 @@
"typescript": "^4.7.3"
},
"dependencies": {
+ "@highlight-run/rrdom": "0.1.17",
+ "@highlight-run/rrweb-snapshot": "1.1.31",
"cssom": "^0.5.0",
"cssstyle": "^2.3.0",
- "nwsapi": "^2.2.0",
- "rrdom": "^0.1.6",
- "rrweb-snapshot": "^2.0.0-alpha.3"
+ "nwsapi": "^2.2.0"
},
"browserslist": [
"supports es6-class"
diff --git a/packages/rrdom-nodejs/src/document-nodejs.ts b/packages/rrdom-nodejs/src/document-nodejs.ts
index 69f85b87..517167e5 100644
--- a/packages/rrdom-nodejs/src/document-nodejs.ts
+++ b/packages/rrdom-nodejs/src/document-nodejs.ts
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
-import { NodeType as RRNodeType } from 'rrweb-snapshot';
+import { NodeType as RRNodeType } from '@highlight-run/rrweb-snapshot';
import type { NWSAPI } from 'nwsapi';
import type { CSSStyleDeclaration as CSSStyleDeclarationType } from 'cssstyle';
import {
@@ -14,7 +14,7 @@ import {
ClassList,
IRRDocument,
CSSStyleDeclaration,
-} from 'rrdom';
+} from '@highlight-run/rrdom';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
const nwsapi = require('nwsapi');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
diff --git a/packages/rrdom-nodejs/test/document-nodejs.test.ts b/packages/rrdom-nodejs/test/document-nodejs.test.ts
index ba3c6144..81469c87 100644
--- a/packages/rrdom-nodejs/test/document-nodejs.test.ts
+++ b/packages/rrdom-nodejs/test/document-nodejs.test.ts
@@ -3,7 +3,7 @@
*/
import * as fs from 'fs';
import * as path from 'path';
-import { NodeType as RRNodeType } from 'rrweb-snapshot';
+import { NodeType as RRNodeType } from '@highlight-run/rrweb-snapshot';
import {
RRCanvasElement,
RRCDATASection,
@@ -16,7 +16,7 @@ import {
RRStyleElement,
RRText,
} from '../src/document-nodejs';
-import { buildFromDom } from 'rrdom';
+import { buildFromDom } from '@highlight-run/rrdom';
describe('RRDocument for nodejs environment', () => {
describe('RRDocument API', () => {
diff --git a/packages/rrdom/LICENSE b/packages/rrdom/LICENSE
new file mode 100644
index 00000000..fce28eb8
--- /dev/null
+++ b/packages/rrdom/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Contributors (https://github.com/rrweb-io/rrweb/graphs/contributors) and SmartX Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/rrdom/package.json b/packages/rrdom/package.json
index 74ef10f1..e09ed0f0 100644
--- a/packages/rrdom/package.json
+++ b/packages/rrdom/package.json
@@ -1,6 +1,6 @@
{
- "name": "rrdom",
- "version": "0.1.6",
+ "name": "@highlight-run/rrdom",
+ "version": "0.1.18",
"homepage": "https://github.com/rrweb-io/rrweb/tree/main/packages/rrdom#readme",
"license": "MIT",
"main": "lib/rrdom.js",
@@ -46,6 +46,6 @@
"typescript": "^4.7.3"
},
"dependencies": {
- "rrweb-snapshot": "^2.0.0-alpha.3"
+ "@highlight-run/rrweb-snapshot": "1.1.31"
}
}
diff --git a/packages/rrdom/rollup.config.js b/packages/rrdom/rollup.config.js
index 5bd34667..a69dc3ad 100644
--- a/packages/rrdom/rollup.config.js
+++ b/packages/rrdom/rollup.config.js
@@ -24,8 +24,8 @@ const basePlugins = [
const baseConfigs = [
{
input: './src/index.ts',
- name: pkg.name,
- path: pkg.name,
+ name: 'rrdom',
+ path: 'rrdom',
},
];
@@ -40,7 +40,7 @@ for (let config of baseConfigs) {
output: [
{
format: 'esm',
- file: pkg.module.replace(pkg.name, config.path),
+ file: pkg.module.replace('rrdom', config.path),
},
],
},
diff --git a/packages/rrdom/src/diff.ts b/packages/rrdom/src/diff.ts
index 1cb4dd80..d8642943 100644
--- a/packages/rrdom/src/diff.ts
+++ b/packages/rrdom/src/diff.ts
@@ -1,4 +1,4 @@
-import { NodeType as RRNodeType, Mirror as NodeMirror } from 'rrweb-snapshot';
+import { NodeType as RRNodeType, Mirror as NodeMirror } from '@highlight-run/rrweb-snapshot';
import type {
canvasMutationData,
canvasEventWithTime,
@@ -6,7 +6,7 @@ import type {
scrollData,
styleDeclarationData,
styleSheetRuleData,
-} from 'rrweb/src/types';
+} from '@highlight-run/rrweb/src/types';
import type {
IRRCDATASection,
IRRComment,
diff --git a/packages/rrdom/src/document.ts b/packages/rrdom/src/document.ts
index f8ee5b86..b2f0310e 100644
--- a/packages/rrdom/src/document.ts
+++ b/packages/rrdom/src/document.ts
@@ -1,4 +1,4 @@
-import { NodeType as RRNodeType } from 'rrweb-snapshot';
+import { NodeType as RRNodeType } from '@highlight-run/rrweb-snapshot';
import { parseCSSText, camelize, toCSSText } from './style';
export interface IRRNode {
parentElement: IRRNode | null;
diff --git a/packages/rrdom/src/index.ts b/packages/rrdom/src/index.ts
index da96b12d..694de7af 100644
--- a/packages/rrdom/src/index.ts
+++ b/packages/rrdom/src/index.ts
@@ -1,12 +1,12 @@
import {
NodeType as RRNodeType,
createMirror as createNodeMirror,
-} from 'rrweb-snapshot';
+} from '@highlight-run/rrweb-snapshot';
import type {
Mirror as NodeMirror,
IMirror,
serializedNodeWithId,
-} from 'rrweb-snapshot';
+} from '@highlight-run/rrweb-snapshot';
import type {
canvasMutationData,
canvasEventWithTime,
@@ -14,7 +14,7 @@ import type {
scrollData,
styleSheetRuleData,
styleDeclarationData,
-} from 'rrweb/src/types';
+} from '@highlight-run/rrweb/src/types';
import {
BaseRRNode as RRNode,
BaseRRCDATASectionImpl,
diff --git a/packages/rrdom/test/diff.test.ts b/packages/rrdom/test/diff.test.ts
index 723b2a3e..e5d05b5c 100644
--- a/packages/rrdom/test/diff.test.ts
+++ b/packages/rrdom/test/diff.test.ts
@@ -8,16 +8,16 @@ import {
serializedNodeWithId,
createMirror,
Mirror,
-} from 'rrweb-snapshot';
+} from '@highlight-run/rrweb-snapshot';
import type { IRRNode } from '../src/document';
-import { Replayer } from 'rrweb';
+import { Replayer } from '@highlight-run/rrweb';
import type {
canvasMutationData,
styleDeclarationData,
styleSheetRuleData,
-} from 'rrweb/src/types';
-import { EventType, IncrementalSource } from 'rrweb/src/types';
-import type { eventWithTime } from 'rrweb/typings/types';
+} from '@highlight-run/rrweb/src/types';
+import { EventType, IncrementalSource } from '@highlight-run/rrweb/src/types';
+import type { eventWithTime } from '@highlight-run/typings/types';
const elementSn = {
type: RRNodeType.Element,
diff --git a/packages/rrdom/test/document.test.ts b/packages/rrdom/test/document.test.ts
index c9ee13c3..1b75e2aa 100644
--- a/packages/rrdom/test/document.test.ts
+++ b/packages/rrdom/test/document.test.ts
@@ -1,7 +1,7 @@
/**
* @jest-environment jsdom
*/
-import { NodeType as RRNodeType } from 'rrweb-snapshot';
+import { NodeType as RRNodeType } from '@highlight-run/rrweb-snapshot';
import {
BaseRRDocumentImpl,
BaseRRDocumentTypeImpl,
diff --git a/packages/rrdom/test/virtual-dom.test.ts b/packages/rrdom/test/virtual-dom.test.ts
index e414e230..2a9cfc37 100644
--- a/packages/rrdom/test/virtual-dom.test.ts
+++ b/packages/rrdom/test/virtual-dom.test.ts
@@ -18,7 +18,7 @@ import {
NodeType,
NodeType as RRNodeType,
textNode,
-} from 'rrweb-snapshot';
+} from '@highlight-run/rrweb-snapshot';
import {
buildFromDom,
buildFromNode,
diff --git a/packages/rrweb-player/LICENSE b/packages/rrweb-player/LICENSE
new file mode 100644
index 00000000..fce28eb8
--- /dev/null
+++ b/packages/rrweb-player/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Contributors (https://github.com/rrweb-io/rrweb/graphs/contributors) and SmartX Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/rrweb-player/package.json b/packages/rrweb-player/package.json
index 9489effe..8ec492f0 100644
--- a/packages/rrweb-player/package.json
+++ b/packages/rrweb-player/package.json
@@ -1,6 +1,6 @@
{
- "name": "rrweb-player",
- "version": "1.0.0-alpha.3",
+ "name": "@highlight-run/rrweb-player",
+ "version": "1.0.1",
"devDependencies": {
"@rollup/plugin-commonjs": "^22.0.0",
"@rollup/plugin-node-resolve": "^13.2.1",
@@ -23,8 +23,8 @@
"typescript": "^4.7.3"
},
"dependencies": {
- "@tsconfig/svelte": "^1.0.0",
- "rrweb": "^2.0.0-alpha.3"
+ "@highlight-run/rrweb": "2.1.10",
+ "@tsconfig/svelte": "^1.0.1"
},
"scripts": {
"build": "rollup -c",
diff --git a/packages/rrweb-player/src/main.ts b/packages/rrweb-player/src/main.ts
index 341e39aa..23847f5e 100644
--- a/packages/rrweb-player/src/main.ts
+++ b/packages/rrweb-player/src/main.ts
@@ -1,4 +1,4 @@
-import type { eventWithTime } from 'rrweb/typings/types';
+import type { eventWithTime } from '@highlight-run/rrweb/typings/types';
import _Player from './Player.svelte';
type PlayerProps = {
diff --git a/packages/rrweb-player/typings/index.d.ts b/packages/rrweb-player/typings/index.d.ts
index b6d980b0..5534898a 100644
--- a/packages/rrweb-player/typings/index.d.ts
+++ b/packages/rrweb-player/typings/index.d.ts
@@ -1,5 +1,5 @@
-import { eventWithTime, playerConfig } from 'rrweb/typings/types';
-import { Replayer, mirror } from 'rrweb';
+import { eventWithTime, playerConfig } from '@highlight-run/rrweb/typings/types';
+import { Replayer, mirror } from '@highlight-run/rrweb';
import { SvelteComponent } from 'svelte';
export type RRwebPlayerOptions = {
diff --git a/packages/rrweb-snapshot/LICENSE b/packages/rrweb-snapshot/LICENSE
new file mode 100644
index 00000000..fce28eb8
--- /dev/null
+++ b/packages/rrweb-snapshot/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Contributors (https://github.com/rrweb-io/rrweb/graphs/contributors) and SmartX Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/rrweb-snapshot/jest.config.js b/packages/rrweb-snapshot/jest.config.js
index 46cb05c3..9749329f 100644
--- a/packages/rrweb-snapshot/jest.config.js
+++ b/packages/rrweb-snapshot/jest.config.js
@@ -3,4 +3,7 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/**.test.ts'],
+ globals: {
+ window: {}
+ }
};
diff --git a/packages/rrweb-snapshot/package.json b/packages/rrweb-snapshot/package.json
index 9cc83921..361b8678 100644
--- a/packages/rrweb-snapshot/package.json
+++ b/packages/rrweb-snapshot/package.json
@@ -1,6 +1,6 @@
{
- "name": "rrweb-snapshot",
- "version": "2.0.0-alpha.3",
+ "name": "@highlight-run/rrweb-snapshot",
+ "version": "2.0.1",
"description": "rrweb's component to take a snapshot of DOM, aka DOM serializer",
"scripts": {
"prepare": "npm run prepack",
diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts
index a491122d..6792a217 100644
--- a/packages/rrweb-snapshot/src/rebuild.ts
+++ b/packages/rrweb-snapshot/src/rebuild.ts
@@ -63,9 +63,22 @@ function escapeRegExp(str: string) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
+declare global {
+ interface Window {
+ HIG_CONFIGURATION?: {
+ enableOnHoverClass?: boolean;
+ };
+ }
+}
+
const HOVER_SELECTOR = /([^\\]):hover/;
const HOVER_SELECTOR_GLOBAL = new RegExp(HOVER_SELECTOR.source, 'g');
export function addHoverClass(cssText: string, cache: BuildCache): string {
+ /* Begin Highlight Code */
+ if (!window?.HIG_CONFIGURATION?.enableOnHoverClass) {
+ return cssText;
+ }
+ /* End Highlight Code */
const cachedStyle = cache?.stylesWithHoverClass.get(cssText);
if (cachedStyle) return cachedStyle;
@@ -181,6 +194,49 @@ function buildNode(
const isRemoteOrDynamicCss = tagName === 'style' && name === '_cssText';
if (isRemoteOrDynamicCss && hackCss && typeof value === 'string') {
value = addHoverClass(value, cache);
+
+ /** Start of Highlight Code */
+ /**
+ * Find all remote fonts in the style tag.
+ * We need to find and replace the URLs with a proxy URL so we can bypass CORS.
+ */
+ if (typeof value === 'string') {
+ const regex = /url\(\"https:\/\/\S*(.eot|.woff2|.ttf|.woff)\S*\"\)/gm;
+ let m;
+ const fontUrls: { originalUrl: string; proxyUrl: string }[] = [];
+
+ const PROXY_URL = 'https://replay-cors-proxy.highlightrun.workers.dev' as const;
+ while ((m = regex.exec(value)) !== null) {
+ // This is necessary to avoid infinite loops with zero-width matches
+ if (m.index === regex.lastIndex) {
+ regex.lastIndex++;
+ }
+
+ // The result can be accessed through the `m`-variable.
+ m.forEach((match, groupIndex) => {
+ if (groupIndex === 0) {
+ // Trim the start and end
+ // example: url("https://app.boardgent.com/fonts/MaterialIcons-Regular.53354891.woff2")
+ // gets trimmed to https://app.boardgent.com/fonts/MaterialIcons-Regular.53354891.woff2
+ const url = match.slice(5, match.length - 2);
+
+ fontUrls.push({
+ originalUrl: url,
+ proxyUrl: url.replace(url, `${PROXY_URL}?url=${url}`),
+ });
+ }
+ });
+ }
+
+ // Replace all references to the old URL to our proxy URL in the stylesheet.
+ fontUrls.forEach((urlPair) => {
+ value = (value as string).replace(
+ urlPair.originalUrl,
+ urlPair.proxyUrl,
+ );
+ });
+ }
+ /** End of Highlight Code */
}
if ((isTextarea || isRemoteOrDynamicCss) && typeof value === 'string') {
const child = doc.createTextNode(value);
diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 99a23ff7..cae9075f 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -18,6 +18,7 @@ import {
isElement,
isShadowRoot,
maskInputValue,
+ obfuscateText,
isNativeShadowDom,
getCssRulesString,
} from './utils';
@@ -426,6 +427,9 @@ function serializeNode(
* `newlyAddedElement: true` skips scrollTop and scrollLeft check
*/
newlyAddedElement?: boolean;
+ /** Highlight Options Start */
+ enableStrictPrivacy: boolean;
+ /** Highlight Options End */
},
): serializedNode | false {
const {
@@ -444,6 +448,7 @@ function serializeNode(
recordCanvas,
keepIframeSrcFn,
newlyAddedElement = false,
+ enableStrictPrivacy,
} = options;
// Only record root id when document object is not the base document
const rootId = getRootId(doc, mirror);
@@ -477,11 +482,13 @@ function serializeNode(
inlineStylesheet,
maskInputOptions,
maskInputFn,
+ maskTextClass,
dataURLOptions,
inlineImages,
recordCanvas,
keepIframeSrcFn,
newlyAddedElement,
+ enableStrictPrivacy,
rootId,
});
case n.TEXT_NODE:
@@ -489,6 +496,7 @@ function serializeNode(
maskTextClass,
maskTextSelector,
maskTextFn,
+ enableStrictPrivacy,
rootId,
});
case n.CDATA_SECTION_NODE:
@@ -520,16 +528,25 @@ function serializeTextNode(
maskTextClass: string | RegExp;
maskTextSelector: string | null;
maskTextFn: MaskTextFn | undefined;
+ enableStrictPrivacy: boolean;
rootId: number | undefined;
},
): serializedNode {
- const { maskTextClass, maskTextSelector, maskTextFn, rootId } = options;
+ const {
+ maskTextClass,
+ maskTextSelector,
+ maskTextFn,
+ enableStrictPrivacy,
+ rootId,
+ } = options;
// The parent node may not be a html element which has a tagName attribute.
// So just let it be undefined which is ok in this use case.
const parentTagName = n.parentNode && (n.parentNode as HTMLElement).tagName;
let textContent = n.textContent;
const isStyle = parentTagName === 'STYLE' ? true : undefined;
const isScript = parentTagName === 'SCRIPT' ? true : undefined;
+ /** Determines if this node has been handled already. */
+ let textContentHandled = false;
if (isStyle && textContent) {
try {
// try to read style sheet
@@ -550,9 +567,14 @@ function serializeTextNode(
);
}
textContent = absoluteToStylesheet(textContent, getHref());
+ textContentHandled = true;
}
if (isScript) {
textContent = 'SCRIPT_PLACEHOLDER';
+ textContentHandled = true;
+ } else if (parentTagName === 'NOSCRIPT') {
+ textContent = '';
+ textContentHandled = true;
}
if (
!isStyle &&
@@ -565,6 +587,24 @@ function serializeTextNode(
: textContent.replace(/[\S]/g, '*');
}
+ /* Start of Highlight */
+ // Randomizes the text content to a string of the same length.
+ if (enableStrictPrivacy && !textContentHandled && parentTagName) {
+ const IGNORE_TAG_NAMES = new Set([
+ 'HEAD',
+ 'TITLE',
+ 'STYLE',
+ 'SCRIPT',
+ 'HTML',
+ 'BODY',
+ 'NOSCRIPT',
+ ]);
+ if (!IGNORE_TAG_NAMES.has(parentTagName) && textContent) {
+ textContent = obfuscateText(textContent);
+ }
+ }
+ /* End of Highlight */
+
return {
type: NodeType.Text,
textContent: textContent || '',
@@ -582,6 +622,7 @@ function serializeElementNode(
inlineStylesheet: boolean;
maskInputOptions: MaskInputOptions;
maskInputFn: MaskInputFn | undefined;
+ maskTextClass: string | RegExp;
dataURLOptions?: DataURLOptions;
inlineImages: boolean;
recordCanvas: boolean;
@@ -590,6 +631,7 @@ function serializeElementNode(
* `newlyAddedElement: true` skips scrollTop and scrollLeft check
*/
newlyAddedElement?: boolean;
+ enableStrictPrivacy: boolean;
rootId: number | undefined;
},
): serializedNode | false {
@@ -600,14 +642,17 @@ function serializeElementNode(
inlineStylesheet,
maskInputOptions = {},
maskInputFn,
+ maskTextClass,
dataURLOptions = {},
inlineImages,
recordCanvas,
keepIframeSrcFn,
newlyAddedElement = false,
+ enableStrictPrivacy,
rootId,
} = options;
- const needBlock = _isBlockedElement(n, blockClass, blockSelector);
+ let needBlock = _isBlockedElement(n, blockClass, blockSelector);
+ const needMask = _isBlockedElement(n, maskTextClass, null);
const tagName = getValidTagName(n);
let attributes: attributes = {};
const len = n.attributes.length;
@@ -713,7 +758,13 @@ function serializeElementNode(
}
}
// save image offline
- if (tagName === 'img' && inlineImages) {
+ if (
+ tagName === 'img' &&
+ inlineImages &&
+ !needBlock &&
+ !needMask &&
+ !enableStrictPrivacy
+ ) {
if (!canvasService) {
canvasService = doc.createElement('canvas');
canvasCtx = canvasService.getContext('2d');
@@ -764,13 +815,16 @@ function serializeElementNode(
}
}
// block element
- if (needBlock) {
+ if (needBlock || needMask || (tagName === 'img' && enableStrictPrivacy)) {
const { width, height } = n.getBoundingClientRect();
attributes = {
class: attributes.class,
rr_width: `${width}px`,
rr_height: `${height}px`,
};
+ if (enableStrictPrivacy) {
+ needBlock = true;
+ }
}
// iframe
if (tagName === 'iframe' && !keepIframeSrcFn(attributes.src as string)) {
@@ -789,6 +843,7 @@ function serializeElementNode(
childNodes: [],
isSVG: isSVGElement(n as Element) || undefined,
needBlock,
+ needMask,
rootId,
};
}
@@ -919,6 +974,7 @@ export function serializeNodeWithId(
node: serializedElementNodeWithId,
) => unknown;
iframeLoadTimeout?: number;
+ enableStrictPrivacy: boolean;
onStylesheetLoad?: (
linkNode: HTMLLinkElement,
node: serializedElementNodeWithId,
@@ -949,6 +1005,7 @@ export function serializeNodeWithId(
stylesheetLoadTimeout = 5000,
keepIframeSrcFn = () => false,
newlyAddedElement = false,
+ enableStrictPrivacy,
} = options;
let { preserveWhiteSpace = true } = options;
const _serializedNode = serializeNode(n, {
@@ -967,6 +1024,7 @@ export function serializeNodeWithId(
recordCanvas,
keepIframeSrcFn,
newlyAddedElement,
+ enableStrictPrivacy,
});
if (!_serializedNode) {
// TODO: dev only
@@ -1002,10 +1060,26 @@ export function serializeNodeWithId(
onSerialize(n);
}
let recordChild = !skipChild;
+ let strictPrivacy = enableStrictPrivacy;
if (serializedNode.type === NodeType.Element) {
recordChild = recordChild && !serializedNode.needBlock;
- // this property was not needed in replay side
+ strictPrivacy =
+ enableStrictPrivacy ||
+ !!serializedNode.needBlock ||
+ !!serializedNode.needMask;
+
+ /** Highlight Code Begin */
+ // Remove the image's src if enableStrictPrivacy.
+ if (strictPrivacy && serializedNode.tagName === 'img') {
+ const clone = n.cloneNode();
+ ((clone as unknown) as HTMLImageElement).src = '';
+ mirror.add(clone, serializedNode);
+ }
+ /** Highlight Code End */
+
+ // these properties was not needed in replay side
delete serializedNode.needBlock;
+ delete serializedNode.needMask;
const shadowRoot = (n as HTMLElement).shadowRoot;
if (shadowRoot && isNativeShadowDom(shadowRoot))
serializedNode.isShadowHost = true;
@@ -1046,6 +1120,7 @@ export function serializeNodeWithId(
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn,
+ enableStrictPrivacy: strictPrivacy,
};
for (const childN of Array.from(n.childNodes)) {
const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
@@ -1106,6 +1181,7 @@ export function serializeNodeWithId(
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn,
+ enableStrictPrivacy,
});
if (serializedIframeNode) {
@@ -1153,6 +1229,7 @@ export function serializeNodeWithId(
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn,
+ enableStrictPrivacy,
});
if (serializedLinkNode) {
@@ -1199,13 +1276,14 @@ function snapshot(
) => unknown;
stylesheetLoadTimeout?: number;
keepIframeSrcFn?: KeepIframeSrcFn;
+ enableStrictPrivacy: boolean;
},
): serializedNodeWithId | null {
const {
mirror = new Mirror(),
- blockClass = 'rr-block',
+ blockClass = 'highlight-block',
blockSelector = null,
- maskTextClass = 'rr-mask',
+ maskTextClass = 'highlight-mask',
maskTextSelector = null,
inlineStylesheet = true,
inlineImages = false,
@@ -1222,6 +1300,7 @@ function snapshot(
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn = () => false,
+ enableStrictPrivacy = false,
} = options || {};
const maskInputOptions: MaskInputOptions =
maskAllInputs === true
@@ -1290,6 +1369,7 @@ function snapshot(
stylesheetLoadTimeout,
keepIframeSrcFn,
newlyAddedElement: false,
+ enableStrictPrivacy,
});
}
diff --git a/packages/rrweb-snapshot/src/types.ts b/packages/rrweb-snapshot/src/types.ts
index dcbf0439..01b59378 100644
--- a/packages/rrweb-snapshot/src/types.ts
+++ b/packages/rrweb-snapshot/src/types.ts
@@ -38,6 +38,7 @@ export type elementNode = {
childNodes: serializedNodeWithId[];
isSVG?: true;
needBlock?: boolean;
+ needMask?: boolean;
};
export type textNode = {
diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index 8f63e44f..a44038cf 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -185,3 +185,21 @@ export function is2DCanvasBlank(canvas: HTMLCanvasElement): boolean {
}
return true;
}
+
+/** Start of Highlight Code
+ * Returns a string of the same length that has been obfuscated.
+ */
+export function obfuscateText(text: string): string {
+ // We remove non-printing characters.
+ // For example: '' is a character that isn't shown visibly or takes up layout space on the screen. However if you take the length of the string, it's counted as 1.
+ // For example: "1"'s length is 2 but visually it's only taking up 1 character width.
+ // If we don't filter does out, our string obfuscation could have more characters than what was originally presented.
+ text = text.replace(/[^ -~]+/g, '');
+ text =
+ text
+ ?.split(' ')
+ .map((word) => Math.random().toString(20).substr(2, word.length))
+ .join(' ') || '';
+ return text;
+}
+/* End of Highlight Code */
diff --git a/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap b/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap
index 0706eba1..64b8f30f 100644
--- a/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap
+++ b/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap
@@ -167,10 +167,10 @@ exports[`integration tests [html file]: block-element.html 1`] = `