Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
b70254c
feat: add rrweb web-extension package
YunFeng0817 Oct 16, 2022
be295bd
refactor: make the extension suitable for manifest v3
YunFeng0817 Oct 17, 2022
6d7ae5d
update tsconfig.json
YunFeng0817 Oct 31, 2022
c99b049
use version_name rather than recorder_version in manifest.json
YunFeng0817 Oct 31, 2022
e87d539
update manifest.json
YunFeng0817 Nov 1, 2022
579d5b8
enable to keep recording after changing tabs
YunFeng0817 Nov 1, 2022
8739a6f
enable to record between tabs and urls
YunFeng0817 Nov 2, 2022
6fd4f2a
fix CI error
YunFeng0817 Nov 2, 2022
7fbc406
Merge branch 'master' into extension
YunFeng0817 Nov 2, 2022
3209471
try to fix CI error
YunFeng0817 Nov 2, 2022
f134ddc
feat: add pause and resume buttons
YunFeng0817 Nov 3, 2022
ab0b588
feat: add a link to new session after recording
YunFeng0817 Nov 3, 2022
0d633e5
improve session list
YunFeng0817 Nov 5, 2022
8246457
refactor: migrate session storage from chrome local storage to indexedDB
YunFeng0817 Nov 5, 2022
5fe51f1
feat: add pagination to session list
YunFeng0817 Nov 5, 2022
1266dae
fix: multiple recorders are started after pausing and resuming process
YunFeng0817 Nov 5, 2022
6186f6a
fix: can't stop recording on firefox browser
YunFeng0817 Nov 5, 2022
0c7e5ef
Merge remote-tracking branch 'origin/master' into extension
YunFeng0817 Nov 5, 2022
5340919
update type import of 'eventWithTime'
YunFeng0817 Nov 5, 2022
9670fd3
fix CI error
YunFeng0817 Nov 5, 2022
196082b
doc: add readme
YunFeng0817 Nov 5, 2022
558ec5d
Merge branch 'master' into extension
YunFeng0817 Nov 7, 2022
c0126c9
Apply suggestions from Justin's code review
YunFeng0817 Nov 12, 2022
2d35e10
refactor: make use of webNavigation API to implement recording consis…
YunFeng0817 Nov 12, 2022
2fbd399
fix firefox compatibility issue and add title to pages
YunFeng0817 Nov 12, 2022
4a56727
add mouseleave listener to enhance the recording liability
YunFeng0817 Nov 13, 2022
6abfb40
fix firefox compatibility issue and improve the experience of recordi…
YunFeng0817 Nov 13, 2022
c931c51
Merge branch 'master' into extension
YunFeng0817 Nov 13, 2022
da99bc2
update tsconfig
YunFeng0817 Nov 13, 2022
d65135c
upgrade vite-plugin-web-extension config to fix some bugs on facebook…
YunFeng0817 Nov 17, 2022
f08f334
update import links
YunFeng0817 Nov 17, 2022
46140be
refactor: cross tab recording mechanism
YunFeng0817 Nov 17, 2022
6665ca4
Merge branch 'master' into extension
YunFeng0817 Nov 18, 2022
36248e2
refactor: slipt util/index.ts into multiple files
YunFeng0817 Dec 1, 2022
d978ab6
implement cross-origin iframe recording
YunFeng0817 Dec 2, 2022
70e6155
fix: regression of issue: ShadowHost can't be a string (issue 941)
YunFeng0817 Jan 13, 2023
407a48a
refactor shadow dom recording to make tests cover key code
YunFeng0817 Jan 13, 2023
719ca76
Merge branch 'master' into extension
YunFeng0817 Jan 13, 2023
2409f76
Merge branch 'fix-shadow-dom-bug' into extension
YunFeng0817 Jan 17, 2023
1174194
Merge branch 'master' into extension
YunFeng0817 Feb 11, 2023
4e779f9
Apply formatting changes
Feb 11, 2023
36a8c00
increase the node memory limitation to avoid CI failure
YunFeng0817 Feb 11, 2023
6101714
Merge remote-tracking branch 'origin/master' into extension
YunFeng0817 Feb 12, 2023
1041fac
Create lovely-pears-cross.md
Juice10 Feb 13, 2023
1e4c582
Apply formatting changes
Juice10 Feb 13, 2023
86a88e0
Update packages/web-extension/package.json
Juice10 Feb 13, 2023
1f8a29a
Update .changeset/lovely-pears-cross.md
Juice10 Feb 13, 2023
3e5ab6f
Merge branch 'master' into extension
YunFeng0817 Feb 13, 2023
802b9b2
update change logs
YunFeng0817 Feb 13, 2023
832d7eb
delete duplicated property
YunFeng0817 Feb 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .changeset/beige-numbers-enjoy.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/lovely-pears-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rrweb/web-extension': patch
---

Add rrweb browser extension
2 changes: 1 addition & 1 deletion .changeset/nervous-poets-grin.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
'rrweb-snapshot': patch
---

- [`fe69bd6`](https://github.com/rrweb-io/rrweb/commit/fe69bd6456cead304bfc77cf72c9db0f8c030842) [#1087](https://github.com/rrweb-io/rrweb/pull/1087) Thanks [@YunFeng0817](https://github.com/YunFeng0817)! - Refactor all suffix of bundled scripts with commonjs module from 'js' to cjs.
Refactor all suffix of bundled scripts with commonjs module from 'js' to cjs [#1087](https://github.com/rrweb-io/rrweb/pull/1087).
2 changes: 0 additions & 2 deletions .changeset/olive-worms-pump.md

This file was deleted.

2 changes: 1 addition & 1 deletion .changeset/real-trains-switch.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
'rrweb': patch
---

- [`4ee86fe`](https://github.com/rrweb-io/rrweb/commit/4ee86fe66d3e1fe7071f9c8764d82a6fa5c71d57) [#1091](https://github.com/rrweb-io/rrweb/pull/1091) Thanks [@YunFeng0817](https://github.com/YunFeng0817)! - Fix: improve rrdom robustness.
Fix: improve rrdom robustness [#1091](https://github.com/rrweb-io/rrweb/pull/1091).
2 changes: 1 addition & 1 deletion .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
run: yarn

- name: Build Project
run: yarn build:all
run: NODE_OPTIONS='--max-old-space-size=4096' yarn build:all

- name: Check types
run: yarn turbo run check-types
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
id: changesets
uses: changesets/action@v1
with:
publish: yarn run release
publish: NODE_OPTIONS='--max-old-space-size=4096' yarn run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/style-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Install Dependencies
run: yarn
- name: Build Packages
run: yarn build:all
run: NODE_OPTIONS='--max-old-space-size=4096' yarn build:all
- name: Eslint Check
run: yarn turbo run lint
- name: Save Code Linting Report JSON
Expand Down
7 changes: 4 additions & 3 deletions .vscode/rrweb-monorepo.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
"path": "../packages/rrweb-snapshot"
},
{
"name": "@rrweb/types",
"path": "../packages/types"
}
"name": "web-extension (package)",
"path": "../packages/web-extension"
},
{ "name": "@rrweb/types", "path": "../packages/types" }
],
"settings": {
"jest.disabledWorkspaceFolders": [
Expand Down
32 changes: 32 additions & 0 deletions packages/web-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<p align="center">
<img width="100px" height="100px" src="https://www.rrweb.io/favicon.png">
</p>

# rrweb extension

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

## Installation

```
yarn install
```

## Build

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

# build for firefox
yarn build:firefox
```

## Development

```bash
# start a development chrome browser
yarn dev:chrome
# start a development firefox browser
yarn dev:firefox
```
47 changes: 47 additions & 0 deletions packages/web-extension/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@rrweb/web-extension",
"private": true,
"version": "2.0.0",
"description": "The web extension of rrweb which helps to run rrweb on any website out of box",
"author": "rrweb-io",
"license": "MIT",
"scripts": {
"dev:chrome": "cross-env TARGET_BROWSER=chrome vite dev",
"dev:firefox": "cross-env TARGET_BROWSER=firefox vite dev",
"build:chrome": "cross-env TARGET_BROWSER=chrome vite build",
"build:firefox": "cross-env TARGET_BROWSER=firefox vite build",
"pack:chrome": "cross-env TARGET_BROWSER=chrome ZIP=true vite build",
"pack:firefox": "cross-env TARGET_BROWSER=firefox ZIP=true vite build",
"check-types": "tsc -noEmit",
"prepublish": "npm run pack:chrome && npm run pack:firefox"
},
"devDependencies": {
"@rrweb/types": "^2.0.0-alpha.4",
"@types/react-dom": "^18.0.6",
"@types/webextension-polyfill": "^0.9.1",
"@vitejs/plugin-react": "^2.1.0",
"cross-env": "^7.0.3",
"type-fest": "^2.19.0",
"typescript": "^4.7.3",
"vite": "^3.1.8",
"vite-plugin-web-extension": "^1.4.5",
"vite-plugin-zip": "^1.0.1",
"webextension-polyfill": "^0.10.0"
},
"dependencies": {
"@chakra-ui/react": "^2.3.4",
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@tanstack/react-table": "^8.5.22",
"framer-motion": "^7.3.6",
"idb": "^7.1.1",
"mitt": "^3.0.0",
"nanoid": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.4.0",
"react-router-dom": "^6.4.1",
"rrweb": "^2.0.0-alpha.4",
"rrweb-player": "^1.0.0-alpha.4"
}
}
Binary file added packages/web-extension/src/assets/icon128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/web-extension/src/assets/icon16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/web-extension/src/assets/icon48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
162 changes: 162 additions & 0 deletions packages/web-extension/src/background/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import Browser from 'webextension-polyfill';
import type { eventWithTime } from '@rrweb/types';
import Channel from '~/utils/channel';
import {
LocalData,
LocalDataKey,
RecorderStatus,
Settings,
SyncData,
SyncDataKey,
} from '~/types';
import { pauseRecording, resumeRecording } from '~/utils/recording';

const channel = new Channel();

void (async () => {
// assign default value to settings of this extension
const result =
((await Browser.storage.sync.get(SyncDataKey.settings)) as SyncData) ||
undefined;
const defaultSettings: Settings = {};
let settings = defaultSettings;
if (result && result.settings) {
setDefaultSettings(result.settings, defaultSettings);
settings = result.settings;
}
await Browser.storage.sync.set({
settings,
} as SyncData);

// When tab is changed during the recording process, pause recording in the old tab and start a new one in the new tab.
Browser.tabs.onActivated.addListener((activeInfo) => {
Browser.storage.local
.get(LocalDataKey.recorderStatus)
.then(async (data) => {
const localData = data as LocalData;
if (!localData || !localData[LocalDataKey.recorderStatus]) return;
let statusData = localData[LocalDataKey.recorderStatus];
let { status } = statusData;
let bufferedEvents: eventWithTime[] | undefined;

if (status === RecorderStatus.RECORDING) {
const result = await pauseRecording(
channel,
RecorderStatus.PausedSwitch,
statusData,
).catch(async () => {
/**
* This error happen when the old tab is closed.
* In this case, the recording process would be stopped through Browser.tabs.onRemoved API.
* So we just read the new status here.
*/
const localData = (await Browser.storage.local.get(
LocalDataKey.recorderStatus,
)) as LocalData;
return {
status: localData[LocalDataKey.recorderStatus],
bufferedEvents,
};
});
if (!result) return;
statusData = result.status;
status = statusData.status;
bufferedEvents = result.bufferedEvents;
}
if (status === RecorderStatus.PausedSwitch)
await resumeRecording(
channel,
activeInfo.tabId,
statusData,
bufferedEvents,
);
})
.catch(() => {
// the extension can't access to the tab
});
});

// If the recording can't start on an invalid tab, resume it when the tab content is updated.
Browser.tabs.onUpdated.addListener(function (tabId, info) {
if (info.status !== 'complete') return;
Browser.storage.local
.get(LocalDataKey.recorderStatus)
.then(async (data) => {
const localData = data as LocalData;
if (!localData || !localData[LocalDataKey.recorderStatus]) return;
const { status, activeTabId } = localData[LocalDataKey.recorderStatus];
if (status !== RecorderStatus.PausedSwitch || activeTabId === tabId)
return;
await resumeRecording(
channel,
tabId,
localData[LocalDataKey.recorderStatus],
);
})
.catch(() => {
// the extension can't access to the tab
});
});

/**
* When the current tab is closed, the recording events will be lost because this event is fired after it is closed.
* This event listener is just used to make sure the recording status is updated.
*/
Browser.tabs.onRemoved.addListener((tabId) => {
Browser.storage.local
.get(LocalDataKey.recorderStatus)
.then(async (data) => {
const localData = data as LocalData;
if (!localData || !localData[LocalDataKey.recorderStatus]) return;
const { status, activeTabId, startTimestamp } =
localData[LocalDataKey.recorderStatus];
if (activeTabId !== tabId || status !== RecorderStatus.RECORDING)
return;

// Update the recording status to make it resumable after users switch to other tabs.
const statusData: LocalData[LocalDataKey.recorderStatus] = {
status: RecorderStatus.PausedSwitch,
activeTabId,
startTimestamp,
pausedTimestamp: Date.now(),
};
await Browser.storage.local.set({
[LocalDataKey.recorderStatus]: statusData,
});
})
.catch((err) => {
console.error(err);
});
});
})();

/**
* Update existed settings with new settings.
* Set new setting values if these properties don't exist in older versions.
*/
function setDefaultSettings(
existedSettings: Record<string, unknown>,
newSettings: Record<string, unknown>,
) {
for (const i in newSettings) {
// settings[i] contains key-value settings
if (
typeof newSettings[i] === 'object' &&
!(newSettings[i] instanceof Array) &&
Object.keys(newSettings[i] as Record<string, unknown>).length > 0
) {
if (existedSettings[i]) {
setDefaultSettings(
existedSettings[i] as Record<string, unknown>,
newSettings[i] as Record<string, unknown>,
);
} else {
// settings[i] contains several setting items but these have not been set before
existedSettings[i] = newSettings[i];
}
} else if (existedSettings[i] === undefined) {
// settings[i] is a single setting item and it has not been set before
existedSettings[i] = newSettings[i];
}
}
}
33 changes: 33 additions & 0 deletions packages/web-extension/src/components/CircleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Button, ButtonProps } from '@chakra-ui/react';

interface CircleButtonProps extends ButtonProps {
diameter: number;
onClick?: () => void;
children?: React.ReactNode;
title?: string;
}

export function CircleButton({
diameter,
onClick,
children,
title,
...rest
}: CircleButtonProps) {
return (
<Button
w={`${diameter}rem`}
h={`${diameter}rem`}
padding={`${diameter / 2}rem`}
borderRadius={9999}
textAlign="center"
bgColor="gray.100"
boxSizing="content-box"
onClick={onClick}
title={title}
{...rest}
>
{children}
</Button>
);
}
Loading