Compare commits

..

No commits in common. "main" and "react-json-tree-example@1.1.3" have entirely different histories.

970 changed files with 43811 additions and 35611 deletions

View File

@ -1,13 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@1.6.4/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
}

View File

@ -7,5 +7,5 @@ build
coverage
node_modules
__snapshots__
.yarn/*
storybook-static
.vscode/*

15
.gitattributes vendored
View File

@ -1 +1,14 @@
* text=auto eol=lf
*.js text eol=lf
*.jsx text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.json text eol=lf
*.css text eol=lf
*.html text eol=lf
*.md text eol=lf
*.yml text eol=lf
*.graphql text eol=lf
.eslintrc text eol=lf
.prettierrc text eol=lf
.babelrc text eol=lf
.stylelintrc text eol=lf

1
.github/FUNDING.yml vendored
View File

@ -1,2 +1 @@
github: Methuselah96
open_collective: redux-devtools-extension

View File

@ -8,24 +8,15 @@ on:
jobs:
build:
runs-on: 'ubuntu-22.04'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- run: yarn install
- run: yarn format:check
- run: yarn build:all
- run: yarn lint:all
- name: Run yarn test:all
uses: GabrielBB/xvfb-action@v1
with:
node-version: 'lts/*'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Check formatting
run: pnpm run format:check
- name: Build
run: pnpm run build:all
- name: Lint
run: pnpm run lint:all
- name: Test
uses: coactions/setup-xvfb@v1
with:
run: pnpm run test:all
run: yarn test:all

View File

@ -1,58 +0,0 @@
name: Release
on:
push:
branches:
- main
permissions: write-all
jobs:
release:
name: Release
runs-on: 'ubuntu-22.04'
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
fetch-depth: 0
- uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
cache: 'pnpm'
- name: Install Dependencies
run: pnpm install
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1
with:
version: pnpm changeset version
publish: pnpm run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Archive Chrome Extension
uses: actions/upload-artifact@v4
with:
name: chrome
path: extension/chrome/dist
- name: Archive Edge Extension
uses: actions/upload-artifact@v4
with:
name: edge
path: extension/edge/dist
- name: Archive Firefox Extension
uses: actions/upload-artifact@v4
with:
name: firefox
path: extension/firefox/dist

8
.gitignore vendored
View File

@ -9,4 +9,10 @@ coverage
.idea
.eslintcache
!packages/redux-devtools-slider-monitor/examples/todomvc/dist/index.html
.nx
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*

View File

@ -8,7 +8,7 @@ coverage
node_modules
__snapshots__
dev
.yarn/*
.pnp.*
**/demo/public/**
storybook-static
.vscode/*
pnpm-lock.yaml

631
.yarn/releases/yarn-3.0.2.cjs vendored Normal file

File diff suppressed because one or more lines are too long

47
.yarnrc.yml Normal file
View File

@ -0,0 +1,47 @@
yarnPath: .yarn/releases/yarn-3.0.2.cjs
packageExtensions:
'http-proxy-middleware@^2.0.0':
dependencies:
'@types/express': '^4.17.13'
'@storybook/addons@^6.3.8':
dependencies:
'@types/webpack-env': '^1.16.0'
'@storybook/react@^6.3.8':
dependencies:
'@storybook/client-api': '^6.3.8'
'@types/node': '*'
'@storybook/theming@^6.3.8':
dependencies:
'@emotion/serialize': '^0.11.16'
'@emotion/utils': '^0.11.3'
'redux-persist@^6.0.0':
dependencies:
'@types/react': '*'
'react': '*'
'apollo-server-core@^3.4.0':
dependencies:
'@types/node': '*'
'tarn@^3.0.1':
dependencies:
'@types/node': '*'
'@chakra-ui/switch@^1.2.10':
peerDependencies:
'framer-motion': '3.x || 4.x'
'@chakra-ui/media-query@^1.1.2':
peerDependencies:
'@emotion/react': '^11.0.0'
'@emotion/styled': '^11.0.0'
'@chakra-ui/skeleton@^1.1.18':
peerDependencies:
'@emotion/react': '^11.0.0'
'@emotion/styled': '^11.0.0'
'@chakra-ui/accordion@^1.3.6':
peerDependencies:
'framer-motion': '3.x || 4.x'
'@mswjs/data@^0.7.0':
dependencies:
'debug': '^4.3.2'
'msw@^0.35.0':
dependencies:
'debug': '^4.3.2'

4
CHANGELOG.md Normal file
View File

@ -0,0 +1,4 @@
# Change log
This project adheres to [Semantic Versioning](http://semver.org/).
See all notable changes on [Releases](https://github.com/gaearon/redux-devtools/releases) page.

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015-present Dan Abramov
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.

View File

@ -1,100 +1,25 @@
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/reduxjs/redux-devtools/CI)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=round-square)](https://github.com/reduxjs/redux-devtools/pulls)
[![OpenCollective](https://opencollective.com/redux-devtools-extension/backers/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/redux-devtools-extension/sponsors/badge.svg)](#sponsors)
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/reduxjs/redux-devtools/CI) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=round-square)](https://github.com/reduxjs/redux-devtools/pulls)
# Redux DevTools
Developer Tools to power-up [Redux](https://redux.js.org/) development workflow or any other architecture which handles the state change (see [integrations](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Integrations.md)).
Developer Tools to power-up [Redux](https://github.com/reactjs/redux) development workflow or any other architecture which handles the state change (see [integrations](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/Integrations.md)).
It can be used as a browser extension (for [Chrome](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd), [Edge](https://microsoftedge.microsoft.com/addons/detail/redux-devtools/nnkgneoiohoecpdiaponcejilbhhikei) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/)), as [a standalone app](https://github.com/reduxjs/redux-devtools/tree/main/packages/redux-devtools-app) or as [a React component](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools) integrated in the client app.
It can be used as a browser extension (for [Chrome](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd), [Edge](https://microsoftedge.microsoft.com/addons/detail/redux-devtools/nnkgneoiohoecpdiaponcejilbhhikei) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/)), as [a standalone app](https://github.com/zalmoxisus/remotedev-app) or as [a React component](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools) integrated in the client app.
![image](https://user-images.githubusercontent.com/7957859/48663602-3aac4900-ea9b-11e8-921f-97059cbb599c.png)
## Documentation
> Note that this repository is work in progress for [the monorepo](https://github.com/reduxjs/redux-devtools/issues/412), which will merge all the packages. Please refer to [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension) and [Redux DevTools package](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools).
- [Browser Extension Installation and Configuration](https://github.com/reduxjs/redux-devtools/tree/main/extension#installation)
### Documentation
- [Browser Extension Installation and Configuration](https://github.com/zalmoxisus/redux-devtools-extension#installation)
- [Manual Integration as a React Component](./docs/Walkthrough.md#manual-integration)
- [Extension Options (Arguments)](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md)
- [Extension Methods (Advanced API)](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Methods.md)
- [Extension Options (Arguments)](https://github.com/zalmoxisus/redux-devtools-extension/tree/master/docs/API/Arguments.md)
- [Extension Methods (Advanced API)](https://github.com/zalmoxisus/redux-devtools-extension/tree/master/docs/API/Methods.md)
- [Remote monitoring](./docs/Integrations/Remote.md)
- [Troubleshooting](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Troubleshooting.md)
- [Recipes](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Recipes.md)
- [FAQ](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/FAQ.md)
## Development
This is a monorepo powered by [pnpm](https://pnpm.io/). [Install pnpm](https://pnpm.io/installation) and run `pnpm install` to get started. Each package's dependencies need to be built before the package itself can be built. You can either build all the packages (i.e., `pnpm run build:all`) or use pnpm workspace commands to build only the packages necessary for the packages you're working on (i.e., `pnpm --filter "remotedev-redux-devtools-extension" build`).
## Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/redux-devtools-extension#backer)]
<a href="https://opencollective.com/redux-devtools-extension/backer/0/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/1/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/2/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/3/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/4/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/5/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/6/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/7/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/8/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/9/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/10/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/11/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/12/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/13/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/14/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/15/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/16/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/17/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/18/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/19/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/20/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/21/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/22/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/23/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/24/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/25/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/26/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/27/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/28/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/29/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/29/avatar.svg"></a>
## Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/redux-devtools-extension#sponsor)]
<a href="https://opencollective.com/redux-devtools-extension/sponsor/0/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/1/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/2/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/3/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/4/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/5/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/6/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/7/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/8/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/9/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/10/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/11/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/12/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/13/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/14/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/15/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/16/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/17/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/18/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/19/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/20/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/21/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/22/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/23/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/24/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/25/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/26/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/27/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/28/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/29/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/29/avatar.svg"></a>
- [Troubleshooting](https://github.com/zalmoxisus/redux-devtools-extension/tree/master/docs/Troubleshooting.md)
- [Recipes](https://github.com/zalmoxisus/redux-devtools-extension/tree/master/docs/Recipes.md)
- [FAQ](https://github.com/zalmoxisus/redux-devtools-extension/tree/master/docs/FAQ.md)
### License

View File

@ -52,7 +52,7 @@ const DevTools = createDevTools(
defaultIsVisible={true}
>
<LogMonitor theme="tomorrow" />
</DockMonitor>,
</DockMonitor>
);
export default DevTools;
@ -88,7 +88,7 @@ const enhancer = compose(
// Middleware you want to use in development:
applyMiddleware(d1, d2, d3),
// Required! Enable Redux DevTools with the monitors you chose
DevTools.instrument(),
DevTools.instrument()
);
export default function configureStore(initialState) {
@ -100,8 +100,8 @@ export default function configureStore(initialState) {
if (module.hot) {
module.hot.accept('../reducers', () =>
store.replaceReducer(
require('../reducers') /*.default if you use Babel 6+ */,
),
require('../reducers') /*.default if you use Babel 6+ */
)
);
}
@ -121,7 +121,7 @@ const enhancer = compose(
// Required! Enable Redux DevTools with the monitors you chose
DevTools.instrument(),
// Optional. Lets you write ?debug_session=<key> in address bar to persist debug sessions
persistState(getDebugSessionKey()),
persistState(getDebugSessionKey())
);
function getDebugSessionKey() {
@ -200,7 +200,7 @@ const enhancer = compose(
// Required! Enable Redux DevTools with the monitors you chose
DevTools.instrument(),
// Optional. Lets you write ?debug_session=<key> in address bar to persist debug sessions
persistState(getDebugSessionKey()),
persistState(getDebugSessionKey())
);
function getDebugSessionKey() {
@ -219,8 +219,8 @@ export default function configureStore(initialState) {
if (module.hot) {
module.hot.accept('../reducers', () =>
store.replaceReducer(
require('../reducers') /*.default if you use Babel 6+ */,
),
require('../reducers') /*.default if you use Babel 6+ */
)
);
}
@ -333,7 +333,7 @@ render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
document.getElementById('root')
);
if (process.env.NODE_ENV !== 'production') {
@ -353,7 +353,7 @@ export default function showDevTools(store) {
const popup = window.open(
null,
'Redux DevTools',
'menubar=no,location=no,resizable=yes,scrollbars=no,status=no',
'menubar=no,location=no,resizable=yes,scrollbars=no,status=no'
);
// Reload in case it already exists
popup.location.reload();
@ -362,7 +362,7 @@ export default function showDevTools(store) {
popup.document.write('<div id="react-devtools-root"></div>');
render(
<DevTools store={store} />,
popup.document.getElementById('react-devtools-root'),
popup.document.getElementById('react-devtools-root')
);
}, 10);
}

View File

@ -1,4 +0,0 @@
import eslint from '@eslint/js';
import eslintConfigPrettier from 'eslint-config-prettier';
export default [eslint.configs.recommended, eslintConfigPrettier];

View File

@ -1,43 +0,0 @@
import eslint from '@eslint/js';
import react from 'eslint-plugin-react';
import { fixupPluginRules } from '@eslint/compat';
import eslintPluginReactHooks from 'eslint-plugin-react-hooks';
import jest from 'eslint-plugin-jest';
import eslintConfigPrettier from 'eslint-config-prettier';
export default [
{
files: ['test/**/*.js', 'test/**/*.jsx'],
...eslint.configs.recommended,
},
{
files: ['test/**/*.js', 'test/**/*.jsx'],
...react.configs.flat.recommended,
},
{
files: ['test/**/*.js', 'test/**/*.jsx'],
settings: {
react: {
version: 'detect',
},
},
},
{
files: ['test/**/*.js', 'test/**/*.jsx'],
plugins: {
'react-hooks': fixupPluginRules(eslintPluginReactHooks),
},
},
{
files: ['test/**/*.js', 'test/**/*.jsx'],
...jest.configs['flat/recommended'],
},
{
files: ['test/**/*.js', 'test/**/*.jsx'],
...jest.configs['jest/style'],
},
{
files: ['test/**/*.js', 'test/**/*.jsx'],
...eslintConfigPrettier,
},
];

View File

@ -1,55 +0,0 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintConfigPrettier from 'eslint-config-prettier';
export default (tsconfigRootDir, files = ['**/*.ts'], project = true) => [
{
files,
...eslint.configs.recommended,
},
...tseslint.configs.recommendedTypeChecked.map((config) => ({
files,
...config,
})),
...tseslint.configs.stylisticTypeChecked.map((config) => ({
files,
...config,
})),
{
files,
languageOptions: {
parserOptions: {
project,
tsconfigRootDir,
},
},
},
{
files,
...eslintConfigPrettier,
},
{
files,
rules: {
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/prefer-optional-chain': 'off',
'@typescript-eslint/no-base-to-string': 'off',
'@typescript-eslint/consistent-indexed-object-style': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/prefer-for-of': 'off',
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
'@typescript-eslint/class-literal-property-style': 'off',
'@typescript-eslint/no-redundant-type-constituents': 'off',
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
'@typescript-eslint/no-duplicate-type-constituents': 'off',
'@typescript-eslint/array-type': 'off',
'@typescript-eslint/prefer-function-type': 'off',
},
},
];

View File

@ -1,64 +0,0 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import jest from 'eslint-plugin-jest';
import eslintConfigPrettier from 'eslint-config-prettier';
export default (tsconfigRootDir) => [
{
files: ['test/**/*.ts'],
...eslint.configs.recommended,
},
...tseslint.configs.recommendedTypeChecked.map((config) => ({
files: ['test/**/*.ts'],
...config,
})),
...tseslint.configs.stylisticTypeChecked.map((config) => ({
files: ['test/**/*.ts'],
...config,
})),
{
files: ['test/**/*.ts'],
languageOptions: {
parserOptions: {
project: ['./tsconfig.test.json'],
tsconfigRootDir,
},
},
},
{
files: ['test/**/*.ts'],
...jest.configs['flat/recommended'],
},
{
files: ['test/**/*.ts'],
...jest.configs['jest/style'],
},
{
files: ['test/**/*.ts'],
...eslintConfigPrettier,
},
{
files: ['test/**/*.ts'],
rules: {
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/prefer-optional-chain': 'off',
'@typescript-eslint/no-base-to-string': 'off',
'@typescript-eslint/consistent-indexed-object-style': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/prefer-for-of': 'off',
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
'@typescript-eslint/class-literal-property-style': 'off',
'@typescript-eslint/no-redundant-type-constituents': 'off',
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
'@typescript-eslint/no-duplicate-type-constituents': 'off',
'@typescript-eslint/array-type': 'off',
'@typescript-eslint/prefer-function-type': 'off',
},
},
];

View File

@ -1,89 +0,0 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import react from 'eslint-plugin-react';
import { fixupPluginRules } from '@eslint/compat';
import eslintPluginReactHooks from 'eslint-plugin-react-hooks';
import eslintConfigPrettier from 'eslint-config-prettier';
export default (
tsconfigRootDir,
files = ['**/*.ts', '**/*.tsx'],
project = true,
) => [
{
files,
...eslint.configs.recommended,
},
...tseslint.configs.recommendedTypeChecked.map((config) => ({
files,
...config,
})),
...tseslint.configs.stylisticTypeChecked.map((config) => ({
files,
...config,
})),
{
files,
languageOptions: {
parserOptions: {
project,
tsconfigRootDir,
},
},
},
{
files,
...react.configs.flat.recommended,
},
{
files,
settings: {
react: {
version: 'detect',
},
},
},
{
files,
plugins: {
'react-hooks': fixupPluginRules(eslintPluginReactHooks),
},
},
{
files,
...eslintConfigPrettier,
},
{
files,
rules: {
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-misused-promises': [
'error',
{
checksVoidReturn: {
attributes: false,
},
},
],
'@typescript-eslint/prefer-optional-chain': 'off',
'@typescript-eslint/no-base-to-string': 'off',
'@typescript-eslint/consistent-indexed-object-style': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/prefer-for-of': 'off',
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
'@typescript-eslint/class-literal-property-style': 'off',
'@typescript-eslint/no-redundant-type-constituents': 'off',
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
'@typescript-eslint/no-duplicate-type-constituents': 'off',
'@typescript-eslint/array-type': 'off',
'@typescript-eslint/prefer-function-type': 'off',
'react/prop-types': 'off',
},
},
];

View File

@ -1,85 +0,0 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import react from 'eslint-plugin-react';
import { fixupPluginRules } from '@eslint/compat';
import eslintPluginReactHooks from 'eslint-plugin-react-hooks';
import jest from 'eslint-plugin-jest';
import eslintConfigPrettier from 'eslint-config-prettier';
export default (tsconfigRootDir) => [
{
files: ['test/**/*.ts', 'test/**/*.tsx'],
...eslint.configs.recommended,
},
...tseslint.configs.recommendedTypeChecked.map((config) => ({
files: ['test/**/*.ts', 'test/**/*.tsx'],
...config,
})),
...tseslint.configs.stylisticTypeChecked.map((config) => ({
files: ['test/**/*.ts', 'test/**/*.tsx'],
...config,
})),
{
files: ['test/**/*.ts', 'test/**/*.tsx'],
languageOptions: {
parserOptions: {
project: ['./tsconfig.test.json'],
tsconfigRootDir,
},
},
},
{
files: ['test/**/*.ts', 'test/**/*.tsx'],
...react.configs.flat.recommended,
},
{
files: ['test/**/*.ts', 'test/**/*.tsx'],
settings: {
react: {
version: 'detect',
},
},
},
{
files: ['test/**/*.ts', 'test/**/*.tsx'],
plugins: {
'react-hooks': fixupPluginRules(eslintPluginReactHooks),
},
},
{
files: ['test/**/*.ts', 'test/**/*.tsx'],
...jest.configs['flat/recommended'],
},
{
files: ['test/**/*.ts', 'test/**/*.tsx'],
...jest.configs['jest/style'],
},
{
files: ['test/**/*.ts', 'test/**/*.tsx'],
...eslintConfigPrettier,
},
{
files: ['test/**/*.ts', 'test/**/*.tsx'],
rules: {
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/prefer-optional-chain': 'off',
'@typescript-eslint/no-base-to-string': 'off',
'@typescript-eslint/consistent-indexed-object-style': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/prefer-for-of': 'off',
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
'@typescript-eslint/class-literal-property-style': 'off',
'@typescript-eslint/no-redundant-type-constituents': 'off',
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
'@typescript-eslint/no-duplicate-type-constituents': 'off',
'@typescript-eslint/array-type': 'off',
'@typescript-eslint/prefer-function-type': 'off',
},
},
];

17
eslintrc.ts.base.json Normal file
View File

@ -0,0 +1,17 @@
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"prettier"
],
"rules": {
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off"
}
}

View File

@ -0,0 +1,18 @@
{
"plugins": ["jest"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:jest/recommended",
"plugin:jest/style",
"prettier"
],
"rules": {
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off"
}
}

View File

@ -0,0 +1,29 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}
},
"plugins": ["@typescript-eslint", "react"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"prettier"
],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off"
}
}

View File

@ -0,0 +1,20 @@
{
"plugins": ["jest"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jest/recommended",
"plugin:jest/style",
"prettier"
],
"rules": {
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off"
}
}

View File

@ -1,6 +1,6 @@
{
"presets": [
["@babel/preset-env", { "targets": "defaults" }],
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
]

8
extension/.bookignore Normal file
View File

@ -0,0 +1,8 @@
src/
build/
dev/
examples/
npm-package/
test/
package.json
webpack/

7
extension/.eslintignore Normal file
View File

@ -0,0 +1,7 @@
node_modules
build
dev
webpack/replace
examples
npm-package
_book

31
extension/.eslintrc Normal file
View File

@ -0,0 +1,31 @@
{
"root": true,
"extends": "eslint-config-airbnb",
"globals": {
"chrome": true,
"__DEVELOPMENT__": true
},
"env": {
"browser": true,
"node": true
},
"rules": {
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/react-in-jsx-scope": 2,
"react/jsx-quotes": 0,
"block-scoped-var": 0,
"padded-blocks": 0,
"quotes": [1, "single"],
"comma-style": [2, "last"],
"no-use-before-define": [0, "nofunc"],
"func-names": 0,
"prefer-const": 0,
"comma-dangle": 0,
"id-length": 0,
"indent": [2, 2, { "SwitchCase": 1 }],
"new-cap": [2, { "capIsNewExceptions": ["Test"] }],
"default-case": 0
},
"plugins": ["react"]
}

16
extension/.gitignore vendored
View File

@ -1,2 +1,16 @@
node_modules
dist
npm-debug.log
.DS_Store
.idea/
dist/
build/
dev/
tmp/
_book
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*

View File

@ -1,238 +1,4 @@
# remotedev-redux-devtools-extension
# Change Log
## 3.2.10
### Patch Changes
- @redux-devtools/app@6.2.2
## 3.2.9
### Patch Changes
- Updated dependencies [91f21b2]
- @redux-devtools/core@4.1.1
- @redux-devtools/slider-monitor@5.1.1
- @redux-devtools/utils@3.1.1
- @redux-devtools/app@6.2.1
## 3.2.8
### Patch Changes
- Updated dependencies [6830118]
- react-json-tree@0.20.0
- @redux-devtools/app@6.2.0
- @redux-devtools/slider-monitor@6.0.0
- @redux-devtools/ui@1.4.0
- @redux-devtools/core@4.1.0
- @redux-devtools/utils@4.0.0
## 3.2.7
### Patch Changes
- b25bf13: Send state from background when monitor connects
## 3.2.6
### Patch Changes
- 50d7682: Fix DevTools from losing connection
## 3.2.5
### Patch Changes
- eb3ac09: Add logging to background service worker
## 3.2.4
### Patch Changes
- f1d6158: Fix mocking Chrome API for Electron
## 3.2.3
### Patch Changes
- fd9f950: Fix monitoring on opening panel
- e49708d: Fix manifest.json for Edge
## 3.2.1
### Patch Changes
- abd03a7: Fix: only send data to extension if DevTools are open
## 3.2.0
### Minor Changes
- 83b2c19: Upgrade to Manifest V3
## 3.1.11
### Patch Changes
- 73688e1: Fix releasing Firefox extension
## 3.1.10
### Patch Changes
- 2163bc3: Split large messages sent from background page to devpanel
## 3.1.9
### Patch Changes
- Updated dependencies [bbb1a40]
- react-json-tree@0.19.0
- @redux-devtools/slider-monitor@5.0.1
- @redux-devtools/ui@1.3.2
## 3.1.8
### Patch Changes
- 191d419: Convert d3 packages to ESM
- Updated dependencies [191d419]
- @redux-devtools/app@6.0.1
## 3.1.7
### Patch Changes
- Updated dependencies [5cfe3e5]
- Updated dependencies [decc035]
- @redux-devtools/app@6.0.0
- @redux-devtools/slider-monitor@5.0.0
- @redux-devtools/core@4.0.0
- @redux-devtools/utils@3.0.0
## 3.1.6
### Patch Changes
- Updated dependencies [158ba2c]
- @redux-devtools/app@5.0.0
## 3.1.5
### Patch Changes
- 65205f90: Replace Action<unknown> with Action<string>
- Updated dependencies [65205f90]
- @redux-devtools/app@4.0.1
- @redux-devtools/core@3.13.2
## 3.1.4
### Patch Changes
- Updated dependencies [e57bcb39]
- @redux-devtools/app@4.0.0
## 3.1.3
### Patch Changes
- bca76009: Fix missing CSS for code editor
## 3.1.2
### Patch Changes
- 64ed81b0: Fix extension in Firefox and Chrome Incognito
## 3.1.1
### Patch Changes
- d18525b5: Increase min-width of popup
- Updated dependencies [57751ff9]
- @redux-devtools/app@3.0.0
## 3.1.0
### Minor Changes
- d54adb76: Option to sort State Tree keys alphabetically
Option to disable collapsing of object keys
### Patch Changes
- @redux-devtools/app@2.2.2
## 3.0.19
### Patch Changes
- 450cde6e: Fix responsive layout
## 3.0.18
### Patch Changes
- Updated dependencies [81926f32]
- react-json-tree@0.18.0
- @redux-devtools/app@2.2.1
## 3.0.17
### Patch Changes
- 1aa6c4f7: Fix remounting root for devpanel
## 3.0.16
### Patch Changes
- 20ebf725: Remove source map from page wrap bundle
## 3.0.14
### Patch Changes
- 24f60a7a: bump min popup window width to 760px #1126 #1129
## 3.0.13
### Patch Changes
- Updated dependencies [8a7eae4]
- react-json-tree@0.17.0
- @redux-devtools/app@2.2.0
- @redux-devtools/slider-monitor@4.0.0
- @redux-devtools/ui@1.3.0
- @redux-devtools/core@3.13.0
- @redux-devtools/utils@2.0.0
## 3.0.12
### Patch Changes
- Updated dependencies [4891bf6]
- @redux-devtools/core@3.12.0
- @redux-devtools/slider-monitor@3.1.2
- @redux-devtools/utils@1.2.1
- @redux-devtools/app@2.1.4
## 3.0.11
### Patch Changes
- ab3c0e2: Avoid persisting the selected action index between sessions
- Updated dependencies [ab3c0e2]
- Updated dependencies [4c9a890]
- @redux-devtools/app@2.1.3
- react-json-tree@0.16.2
## 3.0.10
### Patch Changes
- 55cc37e: Fix filter to show state-controlled search value
- Updated dependencies [55cc37e]
- @redux-devtools/app@2.1.2
This project adheres to [Semantic Versioning](http://semver.org/).
Every release, along with the migration instructions, is documented on the Github [Releases](https://github.com/zalmoxisus/redux-devtools-extension/releases) page.

View File

@ -13,8 +13,8 @@
- from [Chrome Web Store](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd);
- or download `extension.zip` from [last releases](https://github.com/zalmoxisus/redux-devtools-extension/releases), unzip, open `chrome://extensions` url and turn on developer mode from top left and then click; on `Load Unpacked` and select the extracted folder for use
- or build it with `npm i && npm run build:extension` and [load the extension's folder](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-unpacked) `./build/extension`;
- or run it in dev mode with `npm i && npm start` and [load the extension's folder](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-unpacked) `./dev`.
- or build it with `npm i && npm run build:extension` and [load the extension's folder](https://developer.chrome.com/extensions/getstarted#unpacked) `./build/extension`;
- or run it in dev mode with `npm i && npm start` and [load the extension's folder](https://developer.chrome.com/extensions/getstarted#unpacked) `./dev`.
### 2. For Firefox
@ -57,7 +57,7 @@ const composeEnhancers =
compose;
```
> For TypeScript use [`redux-devtools-extension` npm package](#13-use-redux-devtoolsextension-package-from-npm), which contains all the definitions, or just use `(window as any)` (see [Recipes](/docs/Recipes.md#using-in-a-typescript-project) for an example).
> For TypeScript use [`redux-devtools-extension` npm package](#13-use-redux-devtools-extension-package-from-npm), which contains all the definitions, or just use `(window as any)` (see [Recipes](/docs/Recipes.md#using-in-a-typescript-project) for an example).
```js
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
@ -105,7 +105,7 @@ const composeEnhancers =
: compose;
const enhancer = composeEnhancers(
applyMiddleware(...middleware),
applyMiddleware(...middleware)
// other store enhancers if any
);
const store = createStore(reducer, enhancer);
@ -113,26 +113,26 @@ const store = createStore(reducer, enhancer);
> [See the post for more details](https://medium.com/@zalmoxis/improve-your-development-workflow-with-redux-devtools-extension-f0379227ff83).
### 1.3 Use `@redux-devtools/extension` package from npm
### 1.3 Use `redux-devtools-extension` package from npm
To make things easier, there's an npm package to install:
```
npm install --save @redux-devtools/extension
npm install --save redux-devtools-extension
```
and to use like so:
```js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from '@redux-devtools/extension';
import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(
reducer,
composeWithDevTools(
applyMiddleware(...middleware),
applyMiddleware(...middleware)
// other store enhancers if any
),
)
);
```
@ -140,7 +140,7 @@ To specify [extensions options](https://github.com/zalmoxisus/redux-devtools-
```js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from '@redux-devtools/extension';
import { composeWithDevTools } from 'redux-devtools-extension';
const composeEnhancers = composeWithDevTools({
// Specify name here, actionsDenylist, actionsCreators and other options if needed
@ -148,23 +148,23 @@ const composeEnhancers = composeWithDevTools({
const store = createStore(
reducer,
/* preloadedState, */ composeEnhancers(
applyMiddleware(...middleware),
applyMiddleware(...middleware)
// other store enhancers if any
),
)
);
```
> There are just a [few lines of code](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/index.js) added to your bundle.
> Therere just [few lines of code](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/index.js) added to your bundle.
In case you don't include other enhancers and middlewares, just use `devToolsEnhancer`:
```js
import { createStore } from 'redux';
import { devToolsEnhancer } from '@redux-devtools/extension';
import { devToolsEnhancer } from 'redux-devtools-extension';
const store = createStore(
reducer,
/* preloadedState, */ devToolsEnhancer(),
/* preloadedState, */ devToolsEnhancer()
// Specify name here, actionsDenylist, actionsCreators and other options if needed
);
```
@ -173,15 +173,15 @@ const store = createStore(
It's useful to include the extension in production as well. Usually you [can use it for development](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f).
If you want to restrict it there, use `composeWithDevToolsLogOnlyInProduction` or `devToolsEnhancerLogOnlyInProduction`:
If you want to restrict it there, use `redux-devtools-extension/logOnlyInProduction`:
```js
import { createStore } from 'redux';
import { devToolsEnhancerLogOnlyInProduction } from '@redux-devtools/extension';
import { devToolsEnhancer } from 'redux-devtools-extension/logOnlyInProduction';
const store = createStore(
reducer,
/* preloadedState, */ devToolsEnhancerLogOnlyInProduction(),
/* preloadedState, */ devToolsEnhancer()
// options like actionSanitizer, stateSanitizer
);
```
@ -190,25 +190,25 @@ or with middlewares and enhancers:
```js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevToolsLogOnlyInProduction } from '@redux-devtools/extension';
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
const composeEnhancers = composeWithDevToolsLogOnlyInProduction({
const composeEnhancers = composeWithDevTools({
// options like actionSanitizer, stateSanitizer
});
const store = createStore(
reducer,
/* preloadedState, */ composeEnhancers(
applyMiddleware(...middleware),
applyMiddleware(...middleware)
// other store enhancers if any
),
)
);
```
> You'll have to add `'process.env.NODE_ENV': JSON.stringify('production')` in your Webpack config for the production bundle ([to envify](https://github.com/gaearon/redux-devtools/blob/master/docs/Walkthrough.md#exclude-devtools-from-production-builds)). If you use `create-react-app`, [it already does it for you.](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/config/webpack.config.prod.js#L253-L257)
If you're already checking `process.env.NODE_ENV` when creating the store, import `composeWithDevToolsLogOnly` or `devToolsEnhancerLogOnly` for production environment.
If you're already checking `process.env.NODE_ENV` when creating the store, include `redux-devtools-extension/logOnly` for production environment.
If you dont want to allow the extension in production, just use `composeWithDevToolsDevelopmentOnly` or `devToolsEnhancerDevelopmentOnly`.
If you dont want to allow the extension in production, just use `redux-devtools-extension/developmentOnly`.
> See [the article](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f) for more details.

1
extension/SUMMARY.md Normal file
View File

@ -0,0 +1 @@
./docs/README.md

18
extension/appveyor.yml Normal file
View File

@ -0,0 +1,18 @@
environment:
matrix:
- nodejs_version: '6'
cache:
- '%LOCALAPPDATA%/Yarn'
- node_modules
install:
- ps: Install-Product node $env:nodejs_version
- yarn install
test_script:
- node --version
- yarn --version
- yarn test
build: off

18
extension/book.json Normal file
View File

@ -0,0 +1,18 @@
{
"gitbook": "3.2.2",
"title": "Redux DevTools Extension",
"plugins": ["edit-link", "prism", "-highlight", "github", "anchorjs"],
"pluginsConfig": {
"edit-link": {
"base": "https://github.com/zalmoxisus/redux-devtools-extension/tree/master",
"label": "Edit This Page"
},
"github": {
"url": "https://github.com/zalmoxisus/redux-devtools-extension/"
},
"sharing": {
"facebook": true,
"twitter": true
}
}
}

View File

@ -1,55 +0,0 @@
import * as fs from 'node:fs';
import * as esbuild from 'esbuild';
import pug from 'pug';
const args = process.argv.slice(2);
const prod = !args.includes('--dev');
await esbuild.build({
bundle: true,
logLevel: 'info',
outdir: 'dist',
minify: prod,
sourcemap: !prod,
define: {
'process.env.NODE_ENV': prod ? '"production"' : '"development"',
'process.env.BABEL_ENV': prod ? '"production"' : '"development"',
},
entryPoints: [
{ out: 'background.bundle', in: 'src/background/index.ts' },
{ out: 'options.bundle', in: 'src/options/index.tsx' },
{ out: 'remote.bundle', in: 'src/remote/index.tsx' },
{ out: 'devpanel.bundle', in: 'src/devpanel/index.tsx' },
{ out: 'devtools.bundle', in: 'src/devtools/index.ts' },
{ out: 'content.bundle', in: 'src/contentScript/index.ts' },
{ out: 'page.bundle', in: 'src/pageScript/index.ts' },
],
loader: {
'.woff2': 'file',
},
});
console.log();
console.log('Creating HTML files...');
const htmlFiles = ['devpanel', 'devtools', 'options', 'remote'];
for (const htmlFile of htmlFiles) {
fs.writeFileSync(
`dist/${htmlFile}.html`,
pug.renderFile(`src/${htmlFile}/${htmlFile}.pug`),
);
}
console.log('Copying manifest.json...');
fs.copyFileSync('chrome/manifest.json', 'dist/manifest.json');
console.log('Copying assets...');
fs.cpSync('src/assets', 'dist', { recursive: true });
console.log('Copying dist for each browser...');
fs.cpSync('dist', 'chrome/dist', { recursive: true });
fs.copyFileSync('chrome/manifest.json', 'chrome/dist/manifest.json');
fs.cpSync('dist', 'edge/dist', { recursive: true });
fs.copyFileSync('edge/manifest.json', 'edge/dist/manifest.json');
fs.cpSync('dist', 'firefox/dist', { recursive: true });
fs.copyFileSync('firefox/manifest.json', 'firefox/dist/manifest.json');

View File

@ -5,18 +5,18 @@ Use with
- `window.__REDUX_DEVTOOLS_EXTENSION__([options])`
- `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__([options])()`
- `window.__REDUX_DEVTOOLS_EXTENSION__.connect([options])`
- `@redux-devtools/extension` npm package:
- `redux-devtools-extension` npm package:
```js
import { composeWithDevTools } from '@redux-devtools/extension';
import { composeWithDevTools } from 'redux-devtools-extension';
const composeEnhancers = composeWithDevTools(options);
const store = createStore(
reducer,
/* preloadedState, */ composeEnhancers(
applyMiddleware(...middleware),
applyMiddleware(...middleware)
// other store enhancers if any
),
)
);
```
@ -70,7 +70,7 @@ _boolean_ or _object_ which contains:
},
},
},
}),
})
);
```
@ -87,7 +87,7 @@ _boolean_ or _object_ which contains:
replacer: (key, value) =>
value && mori.isMap(value) ? mori.toJs(value) : value,
},
}),
})
);
```
@ -109,7 +109,7 @@ _boolean_ or _object_ which contains:
}
},
},
}),
})
);
```
@ -134,7 +134,7 @@ _boolean_ or _object_ which contains:
}
},
},
}),
})
);
```
@ -174,7 +174,7 @@ _boolean_ or _object_ which contains:
immutable: Immutable,
refs: [ABRecord],
},
}),
})
);
```
@ -185,7 +185,7 @@ In the example bellow it will always send `{ component: '[React]' }`, regardless
```js
function component(
state = { component: null, toJSON: () => ({ component: '[React]' }) },
action,
action
) {
switch (action.type) {
case 'ADD_COMPONENT':
@ -206,7 +206,7 @@ function counter(
return { conter: this.count * 10 };
},
},
action,
action
) {
switch (action.type) {
case 'INCREMENT':
@ -236,7 +236,7 @@ const store = createStore(
actionSanitizer,
stateSanitizer: (state) =>
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state,
}),
})
);
```
@ -254,7 +254,7 @@ createStore(
actionsDenylist: 'SOME_ACTION',
// or actionsDenylist: ['SOME_ACTION', 'SOME_OTHER_ACTION']
// or just actionsDenylist: 'SOME_' to omit both
}),
})
);
```
@ -270,7 +270,7 @@ const store = createStore(
window.__REDUX_DEVTOOLS_EXTENSION__({
predicate: (state, action) =>
state.dev.logLevel === VERBOSE && !action.forwarded,
}),
})
);
```

View File

@ -42,7 +42,7 @@ devTools.init({ value: 'initial state' });
devTools.send('change state', { value: 'state changed' });
```
See [redux enhancer's example](https://github.com/reduxjs/redux-devtools/blob/main/packages/redux-devtools-extension/src/logOnly.ts), [react example](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/examples/react-counter-messaging/components/Counter.js) and [blog post](https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f) for more details.
See [redux enhancer's example](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/logOnly.js), [react example](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/examples/react-counter-messaging/components/Counter.js) and [blog post](https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f) for more details.
### disconnect()

View File

@ -1,37 +0,0 @@
# Architecture Notes
This document exists to keep track of how the different parts of the Redux DevTools interact, since it's easy to forget how it all works together. This is intended for internal purposes and is just a collection of notes to myself.
## Entry Points
### Window
This is the default view that is shown in the Redux DevTools popup, the Chrome DevTools tab (if direct access to the background page is available), and new popup windows that are created. It has direct access to the background page via `chrome.runtime.getBackgroundPage`.
### DevPanel
This is the view that is shown in the Chrome DevTools tab if direct access to the background page is not available.
Initially this was the view that was always used for the Chrome DevTools tab, but when support to directly access the background page from the DevTools tab was added, [the Window View became the preferred view](https://github.com/zalmoxisus/redux-devtools-extension/pull/580).
### Remote
This does not interact with the other parts of the extension at all, it just renders the `App` component from `@redux-devtools/app`.
It can be triggered by hitting the "Remote" button in any of the other views, which calls `chrome.windows.create` and creates a new window.
### DevTools
This is the script that adds the Redux panel in the Chrome DevTools using `chrome.devtools.panels.create`.
It creates a Window View if it has direct access to the background page, otherwise it creates a DevPanel View.
Note that this used to always show the DevPanel View, but [started using the Window View by default](https://github.com/zalmoxisus/redux-devtools-extension/pull/580) once direct access to the background page was added to Chrome DevTools tabs.
### Content Script
Passes messages between the injected page script and the background page.
It listens for messages from the injected page script using `window.addEventListener('message', ...)`. It knows the message is from the injected page script if `message.source` is `'@devtools-page'`. See the Chrome DevTools docs where this approach [is documented](https://developer.chrome.com/docs/extensions/how-to/devtools/extend-devtools#evaluated-scripts-to-devtools).
It creates a connection to the background page using `chrome.runtime.connect` with the name `'tab'` when it receives the first message from the injected page script.

View File

@ -32,7 +32,7 @@ import { inspectProps } from 'react-inspect-props';
compose(
withState('count', 'setCount', 0),
inspectProps('Counter inspector'),
inspectProps('Counter inspector')
)(Counter);
```
@ -167,7 +167,7 @@ run(App, {
{ id: newId(), num: 0 },
{ id: newId(), num: 0 },
],
}),
})
),
});
```

View File

@ -2,7 +2,7 @@
### Using in a typescript project
The recommended way is to use [`@redux-devtools/extension` npm package](/README.md#13-use-redux-devtools-extension-package-from-npm), which contains all typescript definitions. Or you can just use `window as any`:
The recommended way is to use [`redux-devtools-extension` npm package](/README.md#13-use-redux-devtools-extension-package-from-npm), which contains all typescript definitions. Or you can just use `window as any`:
```js
const store = createStore(
@ -15,7 +15,7 @@ const store = createStore(
Note that you many need to set `no-any` to false in your `tslint.json` file.
Alternatively you can use type-guard in order to avoid
Alternatively you can use typeguard in order to avoid
casting to any.
```typescript
@ -28,7 +28,7 @@ type WindowWithDevTools = Window & {
};
const isReduxDevtoolsExtenstionExist = (
arg: Window | WindowWithDevTools,
arg: Window | WindowWithDevTools
): arg is WindowWithDevTools => {
return '__REDUX_DEVTOOLS_EXTENSION__' in arg;
};
@ -40,7 +40,7 @@ const store = createStore(
initialState,
isReduxDevtoolsExtenstionExist(window)
? window.__REDUX_DEVTOOLS_EXTENSION__()
: undefined,
: undefined
);
```
@ -54,25 +54,25 @@ The extension is not sharing `store` object, so you should take care of that.
### Applying multiple times with different sets of options
We're [not allowing that from instrumentation part](https://github.com/reduxjs/redux-devtools/blob/main/packages/redux-devtools-extension/src/logOnly.ts), which can be used it like so:
We're [not allowing that from instrumentation part](https://github.com/zalmoxisus/redux-devtools-instrument/blob/master/src/instrument.js#L676), because that would re-dispatch every app action in case we'd have many liftedStores, but there's [a helper for logging only](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/logOnly.js), which can be used it like so:
```js
import { createStore, compose } from 'redux';
import { devToolsEnhancerLogOnly } from '@redux-devtools/extension';
import { devToolsEnhancer } from 'redux-devtools-extension/logOnly';
const store = createStore(
reducer,
/* preloadedState, */ compose(
devToolsEnhancerLogOnly({
devToolsEnhancer({
instaceID: 1,
name: 'Denylisted',
actionsDenylist: '...',
}),
devToolsEnhancerLogOnly({
devToolsEnhancer({
instaceID: 2,
name: 'Allowlisted',
actionsAllowlist: '...',
}),
),
})
)
);
```

View File

@ -16,8 +16,6 @@ If you develop on your local filesystem, make sure to allow Redux DevTools acces
Most likely you mutate the state. Check it by [adding `redux-immutable-state-invariant` middleware](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/examples/counter/store/configureStore.js#L3).
Another cause could be that you are creating multiple stores, which means that the devtools get attached to one but the application uses another. See [https://github.com/reduxjs/redux-toolkit/issues/2753](this issue).
### @@INIT or REPLACE action resets the state of the app or last actions RE-APPLIED
`@@redux/REPLACE` (or `@@INIT`) is used internally when the application is hot reloaded. When you use `store.replaceReducer` the effect will be the same as for hot-reloading, where the extension is recomputing all the history again. To avoid that set [`shouldHotReload`](/docs/API/Arguments.md#shouldhotreload) parameter to `false`.
@ -35,8 +33,8 @@ const store = createStore(
window.__REDUX_DEVTOOLS_EXTENSION__
? window.__REDUX_DEVTOOLS_EXTENSION__()
: (noop) => noop,
batchedSubscribe(/* ... */),
),
batchedSubscribe(/* ... */)
)
);
```
@ -60,7 +58,7 @@ const store = createStore(
actionSanitizer,
stateSanitizer: (state) =>
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state,
}),
})
);
```
@ -124,7 +122,7 @@ const store = Redux.createStore(
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__({
serialize: true,
}),
})
);
```

View File

@ -1,62 +0,0 @@
{
"version": "3.2.10",
"name": "Redux DevTools",
"description": "Redux DevTools for debugging application's state changes.",
"homepage_url": "https://github.com/reduxjs/redux-devtools",
"manifest_version": 3,
"action": {
"default_icon": "img/logo/gray.png",
"default_title": "Redux DevTools",
"default_popup": "devpanel.html#popup"
},
"commands": {
"devtools-window": {
"description": "DevTools window"
},
"devtools-remote": {
"description": "Remote DevTools"
},
"_execute_action": {
"suggested_key": {
"default": "Ctrl+Shift+E"
}
}
},
"icons": {
"16": "img/logo/16x16.png",
"48": "img/logo/48x48.png",
"128": "img/logo/128x128.png"
},
"options_ui": {
"page": "options.html"
},
"background": {
"service_worker": "background.bundle.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"exclude_globs": ["https://www.google*"],
"js": ["content.bundle.js"],
"run_at": "document_start",
"all_frames": true
},
{
"matches": ["<all_urls>"],
"exclude_globs": ["https://www.google*"],
"js": ["page.bundle.js"],
"run_at": "document_start",
"all_frames": true,
"world": "MAIN"
}
],
"devtools_page": "devtools.html",
"externally_connectable": {
"ids": ["*"]
},
"permissions": ["notifications", "contextMenus", "storage"],
"host_permissions": ["file:///*", "http://*/*", "https://*/*"],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;"
}
}

View File

@ -1,38 +0,0 @@
import globals from 'globals';
import eslintJs from '../eslint.js.config.base.mjs';
import eslintTsReact from '../eslint.ts.react.config.base.mjs';
import eslintJsReactJest from '../eslint.js.react.jest.config.base.mjs';
export default [
...eslintJs,
...eslintTsReact(import.meta.dirname),
...eslintJsReactJest,
{
ignores: [
'chrome',
'dist',
'edge',
'examples',
'firefox',
'test/electron/fixture/dist',
],
},
{
files: ['build.mjs'],
languageOptions: {
globals: {
...globals.nodeBuiltin,
},
},
},
{
files: ['test/**/*.js', 'test/**/*.jsx'],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
EUI: true,
},
},
},
];

View File

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<title>Redux counter example</title>

View File

@ -10,5 +10,5 @@ render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
document.getElementById('root')
);

View File

@ -11,7 +11,7 @@ app.use(
webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: config.output.publicPath,
}),
})
);
app.use(webpackHotMiddleware(compiler));
@ -26,7 +26,7 @@ app.listen(port, function (error) {
console.info(
'==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.',
port,
port,
port
);
}
});

View File

@ -14,7 +14,7 @@ export default function configureStore(preloadedState) {
const store = createStore(
reducer,
preloadedState,
composeEnhancers(applyMiddleware(invariant(), thunk)),
composeEnhancers(applyMiddleware(invariant(), thunk))
);
if (module.hot) {

View File

@ -37,7 +37,7 @@ function mockStore(getState, expectedActions, onLastAction) {
}
const mockStoreWithMiddleware = applyMiddleware(...middlewares)(
mockStoreWithoutMiddleware,
mockStoreWithoutMiddleware
);
return mockStoreWithMiddleware();

View File

@ -11,7 +11,7 @@ function setup() {
decrement: expect.createSpy(),
};
const component = TestUtils.renderIntoDocument(
<Counter counter={1} {...actions} />,
<Counter counter={1} {...actions} />
);
return {
component: component,

View File

@ -10,7 +10,7 @@ function setup(initialState) {
const app = TestUtils.renderIntoDocument(
<Provider store={store}>
<App />
</Provider>,
</Provider>
);
return {
app: app,

View File

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<title>React counter example</title>

View File

@ -69,7 +69,7 @@ class MainSection extends Component {
const filteredTodos = todos.filter(TODO_FILTERS[filter]);
const completedCount = todos.reduce(
(count, todo) => (todo.completed ? count + 1 : count),
0,
0
);
return (

View File

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<title>Redux TodoMVC example</title>

View File

@ -12,5 +12,5 @@ render(
<Provider store={store}>
<Root />
</Provider>,
document.getElementById('root'),
document.getElementById('root')
);

View File

@ -34,14 +34,14 @@ export default function todos(state = initialState, action) {
return state.map((todo) =>
todo.id === action.id
? Object.assign({}, todo, { text: action.text })
: todo,
: todo
);
case COMPLETE_TODO:
return state.map((todo) =>
todo.id === action.id
? Object.assign({}, todo, { completed: !todo.completed })
: todo,
: todo
);
case COMPLETE_ALL:
@ -49,7 +49,7 @@ export default function todos(state = initialState, action) {
return state.map((todo) =>
Object.assign({}, todo, {
completed: !areAllMarked,
}),
})
);
case CLEAR_COMPLETED:

View File

@ -11,7 +11,7 @@ app.use(
webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: config.output.publicPath,
}),
})
);
app.use(webpackHotMiddleware(compiler));
@ -26,7 +26,7 @@ app.listen(port, function (error) {
console.info(
'==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.',
port,
port,
port
);
}
});

View File

@ -11,7 +11,7 @@ import rootReducer from '../reducers';
export default function configureStore(initialState) {
let finalCreateStore = compose(
reduxReactRouter({ createHistory }),
global.devToolsExtension ? global.devToolsExtension() : (f) => f,
global.devToolsExtension ? global.devToolsExtension() : (f) => f
)(createStore);
const store = finalCreateStore(rootReducer, initialState);

View File

@ -13,7 +13,7 @@ function setup(propOverrides) {
onClearCompleted: expect.createSpy(),
onShow: expect.createSpy(),
},
propOverrides,
propOverrides
);
const renderer = TestUtils.createRenderer();
@ -72,7 +72,7 @@ describe('components', () => {
0: 'All',
1: 'Active',
2: 'Completed',
}[i],
}[i]
);
});
});

View File

@ -29,7 +29,7 @@ function setup(propOverrides) {
clearCompleted: expect.createSpy(),
},
},
propOverrides,
propOverrides
);
const renderer = TestUtils.createRenderer();

View File

@ -12,7 +12,7 @@ function setup(propOverrides) {
editing: false,
newTodo: false,
},
propOverrides,
propOverrides
);
const renderer = TestUtils.createRenderer();

View File

@ -18,7 +18,7 @@ describe('todos reducer', () => {
todos([], {
type: types.ADD_TODO,
text: 'Run the tests',
}),
})
).toEqual([
{
text: 'Run the tests',
@ -39,8 +39,8 @@ describe('todos reducer', () => {
{
type: types.ADD_TODO,
text: 'Run the tests',
},
),
}
)
).toEqual([
{
text: 'Run the tests',
@ -71,8 +71,8 @@ describe('todos reducer', () => {
{
type: types.ADD_TODO,
text: 'Fix the tests',
},
),
}
)
).toEqual([
{
text: 'Fix the tests',
@ -110,8 +110,8 @@ describe('todos reducer', () => {
{
type: types.DELETE_TODO,
id: 1,
},
),
}
)
).toEqual([
{
text: 'Use Redux',
@ -140,8 +140,8 @@ describe('todos reducer', () => {
type: types.EDIT_TODO,
text: 'Fix the tests',
id: 1,
},
),
}
)
).toEqual([
{
text: 'Fix the tests',
@ -174,8 +174,8 @@ describe('todos reducer', () => {
{
type: types.COMPLETE_TODO,
id: 1,
},
),
}
)
).toEqual([
{
text: 'Run the tests',
@ -207,8 +207,8 @@ describe('todos reducer', () => {
],
{
type: types.COMPLETE_ALL,
},
),
}
)
).toEqual([
{
text: 'Run the tests',
@ -239,8 +239,8 @@ describe('todos reducer', () => {
],
{
type: types.COMPLETE_ALL,
},
),
}
)
).toEqual([
{
text: 'Run the tests',
@ -272,8 +272,8 @@ describe('todos reducer', () => {
],
{
type: types.CLEAR_COMPLETED,
},
),
}
)
).toEqual([
{
text: 'Use Redux',
@ -308,7 +308,7 @@ describe('todos reducer', () => {
completed: false,
text: 'Write tests',
},
]),
])
).toEqual([
{
text: 'Write more tests',

View File

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />

View File

@ -20,7 +20,7 @@ const composeEnhancers =
compose;
const store = createStore(
reducer,
composeEnhancers(applyMiddleware(sagaMiddleware)),
composeEnhancers(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(rootSaga);
@ -35,7 +35,7 @@ function render() {
onIncrementIfOdd={() => action('INCREMENT_IF_ODD')}
onIncrementAsync={() => action('INCREMENT_ASYNC')}
/>,
document.getElementById('root'),
document.getElementById('root')
);
}

View File

@ -70,7 +70,7 @@ class MainSection extends Component {
const filteredTodos = todos.filter(TODO_FILTERS[filter]);
const completedCount = todos.reduce(
(count, todo) => (todo.completed ? count + 1 : count),
0,
0
);
return (

View File

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<title>Redux TodoMVC example</title>

View File

@ -12,5 +12,5 @@ render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
document.getElementById('root')
);

View File

@ -36,7 +36,7 @@ export default function todos(state = initialState, action) {
return state.map((todo) =>
todo.id === action.id
? Object.assign({}, todo, { text: action.text, modified: new Date() })
: todo,
: todo
);
case COMPLETE_TODO:
@ -46,7 +46,7 @@ export default function todos(state = initialState, action) {
completed: !todo.completed,
modified: new Date(),
})
: todo,
: todo
);
case COMPLETE_ALL:
@ -55,7 +55,7 @@ export default function todos(state = initialState, action) {
Object.assign({}, todo, {
completed: !areAllMarked,
modified: new Date(),
}),
})
);
case CLEAR_COMPLETED:

View File

@ -11,7 +11,7 @@ app.use(
webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: config.output.publicPath,
}),
})
);
app.use(webpackHotMiddleware(compiler));
@ -26,7 +26,7 @@ app.listen(port, function (error) {
console.info(
'==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.',
port,
port,
port
);
}
});

View File

@ -13,7 +13,7 @@ export default function configureStore(preloadedState) {
if (!enhancer) {
console.warn(
'Install Redux DevTools Extension to inspect the app state: ' +
'https://github.com/zalmoxisus/redux-devtools-extension#installation',
'https://github.com/zalmoxisus/redux-devtools-extension#installation'
);
}

View File

@ -13,7 +13,7 @@ function setup(propOverrides) {
onClearCompleted: expect.createSpy(),
onShow: expect.createSpy(),
},
propOverrides,
propOverrides
);
const renderer = TestUtils.createRenderer();
@ -72,7 +72,7 @@ describe('components', () => {
0: 'All',
1: 'Active',
2: 'Completed',
}[i],
}[i]
);
});
});

View File

@ -29,7 +29,7 @@ function setup(propOverrides) {
clearCompleted: expect.createSpy(),
},
},
propOverrides,
propOverrides
);
const renderer = TestUtils.createRenderer();

View File

@ -12,7 +12,7 @@ function setup(propOverrides) {
editing: false,
newTodo: false,
},
propOverrides,
propOverrides
);
const renderer = TestUtils.createRenderer();

View File

@ -18,7 +18,7 @@ describe('todos reducer', () => {
todos([], {
type: types.ADD_TODO,
text: 'Run the tests',
}),
})
).toEqual([
{
text: 'Run the tests',
@ -39,8 +39,8 @@ describe('todos reducer', () => {
{
type: types.ADD_TODO,
text: 'Run the tests',
},
),
}
)
).toEqual([
{
text: 'Run the tests',
@ -71,8 +71,8 @@ describe('todos reducer', () => {
{
type: types.ADD_TODO,
text: 'Fix the tests',
},
),
}
)
).toEqual([
{
text: 'Fix the tests',
@ -110,8 +110,8 @@ describe('todos reducer', () => {
{
type: types.DELETE_TODO,
id: 1,
},
),
}
)
).toEqual([
{
text: 'Use Redux',
@ -140,8 +140,8 @@ describe('todos reducer', () => {
type: types.EDIT_TODO,
text: 'Fix the tests',
id: 1,
},
),
}
)
).toEqual([
{
text: 'Fix the tests',
@ -174,8 +174,8 @@ describe('todos reducer', () => {
{
type: types.COMPLETE_TODO,
id: 1,
},
),
}
)
).toEqual([
{
text: 'Run the tests',
@ -207,8 +207,8 @@ describe('todos reducer', () => {
],
{
type: types.COMPLETE_ALL,
},
),
}
)
).toEqual([
{
text: 'Run the tests',
@ -239,8 +239,8 @@ describe('todos reducer', () => {
],
{
type: types.COMPLETE_ALL,
},
),
}
)
).toEqual([
{
text: 'Run the tests',
@ -272,8 +272,8 @@ describe('todos reducer', () => {
],
{
type: types.CLEAR_COMPLETED,
},
),
}
)
).toEqual([
{
text: 'Use Redux',
@ -308,7 +308,7 @@ describe('todos reducer', () => {
completed: false,
text: 'Write tests',
},
]),
])
).toEqual([
{
text: 'Write more tests',

View File

@ -1,11 +0,0 @@
module.exports = {
setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
testPathIgnorePatterns: ['<rootDir>/examples'],
testEnvironment: 'jsdom',
moduleNameMapper: {
'\\.css$': '<rootDir>/test/__mocks__/styleMock.js',
},
transformIgnorePatterns: [
'node_modules/(?!.pnpm|@babel/code-frame|@babel/highlight|@babel/helper-validator-identifier|chalk|d3|dateformat|delaunator|internmap|jsondiffpatch|lodash-es|nanoid|robust-predicates|uuid)',
],
};

8
extension/jest.config.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
testPathIgnorePatterns: ['<rootDir>/examples'],
testEnvironment: 'jsdom',
moduleNameMapper: {
'\\.css$': '<rootDir>/test/__mocks__/styleMock.ts',
},
};

View File

@ -1,7 +1,6 @@
{
"private": true,
"name": "remotedev-redux-devtools-extension",
"version": "3.2.10",
"version": "3.0.0-rc.1",
"description": "Redux Developer Tools for debugging application state changes.",
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/extension",
"license": "MIT",
@ -11,71 +10,95 @@
"url": "https://github.com/reduxjs/redux-devtools.git"
},
"scripts": {
"build": "pnpm run build:extension && pnpm run type-check",
"build:extension": "node build.mjs",
"start": "webpack --config webpack/dev.config.babel.js",
"build": "yarn run build:extension && yarn run build:firefox",
"build:extension": "rimraf build/extension && webpack --config webpack/wrap.config.babel.js && webpack --config webpack/prod.config.babel.js",
"build:firefox": "webpack --config webpack/prod.firefox.config.babel.js",
"build:examples": "babel-node examples/buildAll.js",
"clean": "rimraf dist && rimraf chrome/dist && rimraf edge/dist && rimraf firefox/dist",
"precompress:extension": "yarn run lint && yarn run test:app && yarn run build:extension && yarn run test:chrome && yarn run test:electron",
"precompress:firefox": "yarn run lint && yarn run build:firefox && yarn run test:app",
"compress:extension": "bestzip build/extension.zip build/extension",
"compress:firefox": "bestzip build/extension.zip build/extension",
"docs:clean": "rimraf _book",
"docs:prepare": "gitbook install",
"docs:build": "yarn run docs:prepare && gitbook build",
"docs:watch": "yarn run docs:prepare && gitbook serve",
"docs:publish": "yarn run docs:clean && yarn run docs:build && cd _book && git init && git commit --allow-empty -m 'update book' && git checkout -b gh-pages && touch .nojekyll && git add . && git commit -am 'update book' && git push git@github.com:zalmoxisus/redux-devtools-extension gh-pages --force",
"clean": "rimraf build/ && rimraf dev/",
"test:app": "cross-env BABEL_ENV=test jest test/app",
"test:chrome": "jest test/chrome",
"test:electron": "yarn run build:test:electron:fixture && jest test/electron",
"test": "yarn run test:app && yarn run build:extension && yarn run test:chrome && yarn run test:electron",
"build:test:electron:fixture": "webpack --config test/electron/fixture/webpack.config.js",
"test:electron": "pnpm run build:test:electron:fixture && jest test/electron",
"test": "pnpm run test:app && pnpm run test:chrome && pnpm run test:electron",
"lint": "eslint .",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@redux-devtools/app": "workspace:^",
"@redux-devtools/core": "workspace:^",
"@redux-devtools/instrument": "workspace:^",
"@redux-devtools/serialize": "workspace:^",
"@redux-devtools/slider-monitor": "workspace:^",
"@redux-devtools/ui": "workspace:^",
"@redux-devtools/utils": "workspace:^",
"@reduxjs/toolkit": "^2.6.0",
"@types/jsan": "^3.1.5",
"jsan": "^3.1.14",
"@babel/polyfill": "^7.12.1",
"@redux-devtools/app": "^1.0.0-9",
"@redux-devtools/core": "^3.9.1",
"@redux-devtools/instrument": "^1.11.1",
"@redux-devtools/serialize": "^0.3.1",
"@redux-devtools/slider-monitor": "^2.0.0-9",
"@redux-devtools/ui": "^1.0.0-10",
"@redux-devtools/utils": "^1.0.0-7",
"@types/jsan": "^3.1.2",
"jsan": "^3.1.13",
"localforage": "^1.10.0",
"lodash-es": "^4.17.21",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.5.0",
"react-is": "^18.3.1",
"react-json-tree": "workspace:^",
"react-redux": "^9.2.0",
"redux": "^5.0.1",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-icons": "^4.3.1",
"react-is": "^17.0.2",
"react-json-tree": "^0.15.1",
"react-redux": "^7.2.6",
"redux": "^4.1.2",
"redux-persist": "^6.0.0",
"styled-components": "^5.3.11"
"styled-components": "^5.3.3"
},
"devDependencies": {
"@babel/core": "^7.26.9",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"@babel/register": "^7.25.9",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@types/chrome": "^0.0.308",
"@types/lodash-es": "^4.17.12",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/styled-components": "^5.1.34",
"chromedriver": "^126.0.5",
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"@babel/preset-react": "^7.16.0",
"@babel/preset-typescript": "^7.16.0",
"@babel/register": "^7.16.0",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.1.2",
"@types/chrome": "^0.0.162",
"@types/lodash": "^4.14.176",
"@types/react": "^17.0.33",
"@types/react-dom": "^17.0.10",
"@types/react-redux": "^7.1.20",
"@types/styled-components": "^5.1.15",
"babel-loader": "^8.2.3",
"bestzip": "^2.2.0",
"chromedriver": "^94.0.0",
"copy-webpack-plugin": "^9.0.1",
"cross-env": "^7.0.3",
"electron": "^31.7.7",
"esbuild": "^0.25.0",
"globals": "^15.15.0",
"immutable": "^5.0.3",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"pug": "^3.0.3",
"rimraf": "^6.0.1",
"selenium-webdriver": "^4.29.0",
"css-loader": "^6.5.0",
"electron": "^15.3.0",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^6.4.0",
"gitbook-cli": "^2.3.2",
"immutable": "^4.0.0",
"jest": "^27.3.1",
"path-browserify": "^1.0.1",
"pug-html-loader": "^1.1.5",
"raw-loader": "^4.0.2",
"react-transform-catch-errors": "^1.0.2",
"react-transform-hmr": "^1.0.4",
"rimraf": "^3.0.2",
"selenium-webdriver": "^4.0.0",
"sinon-chrome": "^3.0.1",
"ts-jest": "^29.2.6",
"typescript": "~5.8.2",
"webpack": "^5.98.0",
"webpack-cli": "^6.0.1"
"style-loader": "^3.3.1",
"ts-jest": "^27.0.7",
"typescript": "~4.4.4",
"webpack": "^5.61.0",
"webpack-cli": "^4.9.1"
}
}

View File

@ -1,141 +0,0 @@
import React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { Button, Container, Divider, Toolbar } from '@redux-devtools/ui';
import {
DevTools,
Dispatcher,
DispatcherButton,
ExportButton,
getActiveInstance,
getReport,
ImportButton,
liftedDispatch,
MonitorSelector,
PrintButton,
SliderButton,
SliderMonitor,
StoreState,
TopButtons,
} from '@redux-devtools/app';
import { GoBroadcast } from 'react-icons/go';
import { MdOutlineWindow } from 'react-icons/md';
import type { Position } from '../pageScript/api/openWindow';
import type { SingleMessage } from '../background/store/apiMiddleware';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
interface OwnProps {
readonly position: string;
}
type Props = StateProps & DispatchProps & OwnProps;
const isElectron = navigator.userAgent.includes('Electron');
async function sendMessage(message: SingleMessage) {
await chrome.runtime.sendMessage(message);
}
class Actions extends Component<Props> {
openWindow = async (position: Position) => {
await sendMessage({ type: 'OPEN', position });
};
openOptionsPage = async () => {
if (navigator.userAgent.includes('Firefox')) {
await sendMessage({ type: 'OPEN_OPTIONS' });
} else {
await chrome.runtime.openOptionsPage();
}
};
render() {
const {
monitor,
dispatcherIsOpen,
sliderIsOpen,
options,
liftedState,
liftedDispatch,
position,
stateTreeSettings,
} = this.props;
const { features } = options;
return (
<Container>
<TopButtons
dispatch={liftedDispatch}
liftedState={liftedState}
options={options}
/>
<DevTools
monitor={monitor}
liftedState={liftedState}
monitorState={this.props.monitorState}
dispatch={liftedDispatch}
features={options.features}
stateTreeSettings={stateTreeSettings}
/>
{sliderIsOpen && options.connectionId && options.features.jump && (
<SliderMonitor liftedState={liftedState} dispatch={liftedDispatch} />
)}
{dispatcherIsOpen &&
options.connectionId &&
options.features.dispatch && <Dispatcher options={options} />}
<Toolbar borderPosition="top">
{features.export && <ExportButton />}
{features.import && <ImportButton />}
{position &&
(position !== '#popup' ||
navigator.userAgent.includes('Firefox')) && <PrintButton />}
<Divider />
<MonitorSelector />
<Divider />
{features.jump && <SliderButton isOpen={this.props.sliderIsOpen} />}
{features.dispatch && (
<DispatcherButton dispatcherIsOpen={this.props.dispatcherIsOpen} />
)}
<Divider />
{!isElectron && (
<Button
onClick={async () => {
await this.openWindow('window');
}}
>
<MdOutlineWindow />
</Button>
)}
{!isElectron && (
<Button
onClick={async () => {
await this.openWindow('remote');
}}
>
<GoBroadcast />
</Button>
)}
</Toolbar>
</Container>
);
}
}
const mapStateToProps = (state: StoreState) => {
const instances = state.instances;
const id = getActiveInstance(instances);
return {
liftedState: instances.states[id],
monitorState: state.monitor.monitorState,
options: instances.options[id],
monitor: state.monitor.selected,
dispatcherIsOpen: state.monitor.dispatcherIsOpen,
sliderIsOpen: state.monitor.sliderIsOpen,
reports: state.reports.data,
stateTreeSettings: state.stateTreeSettings,
};
};
const actionCreators = {
liftedDispatch,
getReport,
};
export default connect(mapStateToProps, actionCreators)(Actions);

View File

@ -1,6 +1,7 @@
import mapValues from 'lodash/mapValues';
import { Action } from 'redux';
import { LiftedState, PerformAction } from '@redux-devtools/instrument';
import { LocalFilter } from '@redux-devtools/utils';
import { LocalFilter } from '@redux-devtools/utils/lib/filters';
export type FilterStateValue =
| 'DO_NOT_FILTER'
@ -20,13 +21,14 @@ export const noFiltersApplied = (localFilter: LocalFilter | undefined) =>
!window.devToolsOptions.filter ||
window.devToolsOptions.filter === FilterState.DO_NOT_FILTER);
export function isFiltered<A extends Action<string>>(
export function isFiltered<A extends Action<unknown>>(
action: A | string,
localFilter: LocalFilter | undefined,
localFilter: LocalFilter | undefined
) {
if (
noFiltersApplied(localFilter) ||
(typeof action !== 'string' && typeof action.type.match !== 'function')
(typeof action !== 'string' &&
typeof (action.type as string).match !== 'function')
) {
return false;
}
@ -39,25 +41,20 @@ export function isFiltered<A extends Action<string>>(
);
}
function filterActions<A extends Action<string>>(
function filterActions<A extends Action<unknown>>(
actionsById: { [p: number]: PerformAction<A> },
actionSanitizer: ((action: A, id: number) => A) | undefined,
actionSanitizer: ((action: A, id: number) => A) | undefined
): { [p: number]: PerformAction<A> } {
if (!actionSanitizer) return actionsById;
return Object.fromEntries(
Object.entries(actionsById).map(([actionId, action]) => [
actionId,
{
...action,
action: actionSanitizer(action.action, actionId as unknown as number),
},
]),
);
return mapValues(actionsById, (action, id) => ({
...action,
action: actionSanitizer(action.action, id as unknown as number),
}));
}
function filterStates<S>(
computedStates: { state: S; error?: string | undefined }[],
stateSanitizer: ((state: S, index: number) => S) | undefined,
stateSanitizer: ((state: S, index: number) => S) | undefined
) {
if (!stateSanitizer) return computedStates;
return computedStates.map((state, idx) => ({
@ -66,12 +63,12 @@ function filterStates<S>(
}));
}
export function filterState<S, A extends Action<string>>(
export function filterState<S, A extends Action<unknown>>(
state: LiftedState<S, A, unknown>,
localFilter: LocalFilter | undefined,
stateSanitizer: ((state: S, index: number) => S) | undefined,
actionSanitizer: ((action: A, id: number) => A) | undefined,
predicate: ((state: S, action: A) => boolean) | undefined,
predicate: ((state: S, action: A) => boolean) | undefined
): LiftedState<S, A, unknown> {
if (predicate || !noFiltersApplied(localFilter)) {
const filteredStagedActionIds: number[] = [];
@ -97,7 +94,7 @@ export function filterState<S, A extends Action<string>>(
filteredComputedStates.push(
stateSanitizer
? { ...liftedState, state: stateSanitizer(currState, idx) }
: liftedState,
: liftedState
);
if (actionSanitizer) {
sanitizedActionsById![id] = {
@ -123,7 +120,7 @@ export function filterState<S, A extends Action<string>>(
};
}
export interface PartialLiftedState<S, A extends Action<string>> {
export interface PartialLiftedState<S, A extends Action<unknown>> {
readonly actionsById: { [actionId: number]: PerformAction<A> };
readonly computedStates: { state: S; error?: string }[];
readonly stagedActionIds: readonly number[];
@ -132,17 +129,17 @@ export interface PartialLiftedState<S, A extends Action<string>> {
readonly committedState?: S;
}
export function startingFrom<S, A extends Action<string>>(
export function startingFrom<S, A extends Action<unknown>>(
sendingActionId: number,
state: LiftedState<S, A, unknown>,
localFilter: LocalFilter | undefined,
stateSanitizer: (<S>(state: S, index: number) => S) | undefined,
actionSanitizer:
| (<A extends Action<string>>(action: A, id: number) => A)
| (<A extends Action<unknown>>(action: A, id: number) => A)
| undefined,
predicate:
| (<S, A extends Action<string>>(state: S, action: A) => boolean)
| undefined,
| (<S, A extends Action<unknown>>(state: S, action: A) => boolean)
| undefined
): LiftedState<S, A, unknown> | PartialLiftedState<S, A> | undefined {
const stagedActionIds = state.stagedActionIds;
if (sendingActionId <= stagedActionIds[1]) return state;
@ -181,7 +178,7 @@ export function startingFrom<S, A extends Action<string>>(
newComputedStates.push(
!stateSanitizer
? currState
: { ...currState, state: stateSanitizer(currState.state, i) },
: { ...currState, state: stateSanitizer(currState.state, i) }
);
}

View File

@ -1,6 +1,9 @@
import jsan from 'jsan';
import { immutableSerialize } from '@redux-devtools/serialize';
import type { Config, SerializeWithImmutable } from '../index';
import seralizeImmutable from '@redux-devtools/serialize/lib/immutable/serialize';
import {
Config,
SerializeWithImmutable,
} from '../../browser/extension/inject/pageScript';
import Immutable from 'immutable';
import { LiftedState } from '@redux-devtools/instrument';
import { Action } from 'redux';
@ -10,7 +13,7 @@ interface SerializeWithRequiredImmutable extends SerializeWithImmutable {
}
function isSerializeWithImmutable(
serialize: boolean | SerializeWithImmutable,
serialize: boolean | SerializeWithImmutable
): serialize is SerializeWithRequiredImmutable {
return !!(serialize as SerializeWithImmutable).immutable;
}
@ -20,7 +23,7 @@ interface SerializeWithRequiredReviver extends SerializeWithImmutable {
}
function isSerializeWithReviver(
serialize: boolean | SerializeWithImmutable,
serialize: boolean | SerializeWithImmutable
): serialize is SerializeWithRequiredReviver {
return !!(serialize as SerializeWithImmutable).immutable;
}
@ -30,9 +33,9 @@ interface ParsedSerializedLiftedState {
readonly preloadedState?: string;
}
export default function importState<S, A extends Action<string>>(
export default function importState<S, A extends Action<unknown>>(
state: string | undefined,
{ serialize }: Config,
{ serialize }: Config
) {
if (!state) return undefined;
let parse = jsan.parse;
@ -41,12 +44,12 @@ export default function importState<S, A extends Action<string>>(
parse = (v) =>
jsan.parse(
v,
immutableSerialize(
seralizeImmutable(
serialize.immutable,
serialize.refs,
serialize.replacer,
serialize.reviver,
).reviver,
serialize.reviver
).reviver
);
} else if (isSerializeWithReviver(serialize)) {
parse = (v) => jsan.parse(v, serialize.reviver);
@ -58,7 +61,7 @@ export default function importState<S, A extends Action<string>>(
| LiftedState<S, A, unknown> = parse(state) as
| ParsedSerializedLiftedState
| LiftedState<S, A, unknown>;
const preloadedState =
let preloadedState =
'payload' in parsedSerializedLiftedState &&
parsedSerializedLiftedState.preloadedState
? (parse(parsedSerializedLiftedState.preloadedState) as S)

View File

@ -1,19 +1,20 @@
import jsan, { Options } from 'jsan';
import { throttle } from 'lodash-es';
import { immutableSerialize } from '@redux-devtools/serialize';
import { getActionsArray, getLocalFilter } from '@redux-devtools/utils';
import throttle from 'lodash/throttle';
import serializeImmutable from '@redux-devtools/serialize/lib/immutable/serialize';
import { getActionsArray } from '@redux-devtools/utils';
import { getLocalFilter } from '@redux-devtools/utils/lib/filters';
import { isFiltered, PartialLiftedState } from './filters';
import importState from './importState';
import generateId from './generateInstanceId';
import type { Config } from '../index';
import { Config } from '../../browser/extension/inject/pageScript';
import { Action } from 'redux';
import { LiftedState, PerformAction } from '@redux-devtools/instrument';
import { LibConfig } from '@redux-devtools/app';
import type {
import { LibConfig } from '@redux-devtools/app/lib/actions';
import {
ContentScriptToPageScriptMessage,
ListenerMessage,
} from '../../contentScript';
import type { Position } from './openWindow';
} from '../../browser/extension/inject/contentScript';
import { Position } from './openWindow';
const listeners: {
[instanceId: string]:
@ -56,7 +57,7 @@ function stringify(obj: unknown, serialize?: Serialize | undefined) {
// 16 MB
/* eslint-disable no-console */
console.warn(
'Application state or actions payloads are too large making Redux DevTools serialization slow and consuming a lot of memory. See https://github.com/reduxjs/redux-devtools-extension/blob/master/docs/Troubleshooting.md#excessive-use-of-memory-and-cpu on how to configure it.',
'Application state or actions payloads are too large making Redux DevTools serialization slow and consuming a lot of memory. See https://git.io/fpcP5 on how to configure it.'
);
/* eslint-enable no-console */
stringifyWarned = true;
@ -76,11 +77,11 @@ export function getSerializeParameter(config: Config) {
if (serialize) {
if (serialize === true) return { options: true };
if (serialize.immutable) {
const immutableSerializer = immutableSerialize(
const immutableSerializer = serializeImmutable(
serialize.immutable,
serialize.refs,
serialize.replacer,
serialize.reviver,
serialize.reviver
);
return {
replacer: immutableSerializer.replacer,
@ -115,7 +116,7 @@ interface DisconnectMessage {
readonly source: typeof source;
}
interface InitMessage<S, A extends Action<string>> {
interface InitMessage<S, A extends Action<unknown>> {
readonly type: 'INIT';
readonly payload: string;
readonly instanceId: number;
@ -161,7 +162,7 @@ interface SerializedActionMessage {
readonly nextActionId?: number;
}
interface SerializedStateMessage<S, A extends Action<string>> {
interface SerializedStateMessage<S, A extends Action<unknown>> {
readonly type: 'STATE';
readonly payload: Omit<
LiftedState<S, A, unknown>,
@ -183,7 +184,7 @@ interface OpenMessage {
export type PageScriptToContentScriptMessageForwardedToMonitors<
S,
A extends Action<string>,
A extends Action<unknown>
> =
| InitMessage<S, A>
| LiftedMessage
@ -194,7 +195,7 @@ export type PageScriptToContentScriptMessageForwardedToMonitors<
export type PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<
S,
A extends Action<string>,
A extends Action<unknown>
> =
| PageScriptToContentScriptMessageForwardedToMonitors<S, A>
| ErrorMessage
@ -204,26 +205,25 @@ export type PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<
export type PageScriptToContentScriptMessageWithoutDisconnect<
S,
A extends Action<string>,
A extends Action<unknown>
> =
| PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<S, A>
| InitInstancePageScriptToContentScriptMessage
| InitInstanceMessage;
export type PageScriptToContentScriptMessage<S, A extends Action<string>> =
export type PageScriptToContentScriptMessage<S, A extends Action<unknown>> =
| PageScriptToContentScriptMessageWithoutDisconnect<S, A>
| DisconnectMessage;
function post<S, A extends Action<string>>(
message: PageScriptToContentScriptMessage<S, A>,
function post<S, A extends Action<unknown>>(
message: PageScriptToContentScriptMessage<S, A>
) {
window.postMessage(message, '*');
}
function getStackTrace(
config: Config,
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
toExcludeFromTrace: Function | undefined,
toExcludeFromTrace: Function | undefined
) {
if (!config.trace) return undefined;
if (typeof config.trace === 'function') return config.trace();
@ -249,7 +249,6 @@ function getStackTrace(
typeof Error.stackTraceLimit !== 'number' ||
Error.stackTraceLimit > traceLimit!
) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const frames = stack!.split('\n');
if (frames.length > traceLimit!) {
stack = frames
@ -260,18 +259,17 @@ function getStackTrace(
return stack;
}
function amendActionType<A extends Action<string>>(
function amendActionType<A extends Action<unknown>>(
action:
| A
| StructuralPerformAction<A>
| StructuralPerformAction<A>[]
| string,
config: Config,
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
toExcludeFromTrace: Function | undefined,
toExcludeFromTrace: Function | undefined
): StructuralPerformAction<A> {
const timestamp = Date.now();
const stack = getStackTrace(config, toExcludeFromTrace);
let timestamp = Date.now();
let stack = getStackTrace(config, toExcludeFromTrace);
if (typeof action === 'string') {
return { action: { type: action } as A, timestamp, stack };
}
@ -291,7 +289,7 @@ interface LiftedMessage {
readonly source: typeof source;
}
interface PartialStateMessage<S, A extends Action<string>> {
interface PartialStateMessage<S, A extends Action<unknown>> {
readonly type: 'PARTIAL_STATE';
readonly payload: PartialLiftedState<S, A>;
readonly source: typeof source;
@ -299,7 +297,7 @@ interface PartialStateMessage<S, A extends Action<string>> {
readonly maxAge: number;
}
interface ExportMessage<S, A extends Action<string>> {
interface ExportMessage<S, A extends Action<unknown>> {
readonly type: 'EXPORT';
readonly payload: readonly A[];
readonly committedState: S;
@ -307,21 +305,21 @@ interface ExportMessage<S, A extends Action<string>> {
readonly instanceId: number;
}
export interface StructuralPerformAction<A extends Action<string>> {
export interface StructuralPerformAction<A extends Action<unknown>> {
readonly action: A;
readonly timestamp?: number;
readonly stack?: string;
}
type SingleUserAction<A extends Action<string>> =
type SingleUserAction<A extends Action<unknown>> =
| PerformAction<A>
| StructuralPerformAction<A>
| A;
type UserAction<A extends Action<string>> =
type UserAction<A extends Action<unknown>> =
| SingleUserAction<A>
| readonly SingleUserAction<A>[];
interface ActionMessage<S, A extends Action<string>> {
interface ActionMessage<S, A extends Action<unknown>> {
readonly type: 'ACTION';
readonly payload: S;
readonly source: typeof source;
@ -332,7 +330,7 @@ interface ActionMessage<S, A extends Action<string>> {
readonly name?: string;
}
interface StateMessage<S, A extends Action<string>> {
interface StateMessage<S, A extends Action<unknown>> {
readonly type: 'STATE';
readonly payload: LiftedState<S, A, unknown>;
readonly source: typeof source;
@ -372,7 +370,7 @@ interface StopMessage {
readonly instanceId: number;
}
type ToContentScriptMessage<S, A extends Action<string>> =
type ToContentScriptMessage<S, A extends Action<unknown>> =
| LiftedMessage
| PartialStateMessage<S, A>
| ExportMessage<S, A>
@ -383,10 +381,10 @@ type ToContentScriptMessage<S, A extends Action<string>> =
| GetReportMessage
| StopMessage;
export function toContentScript<S, A extends Action<string>>(
export function toContentScript<S, A extends Action<unknown>>(
message: ToContentScriptMessage<S, A>,
serializeState?: Serialize | undefined,
serializeAction?: Serialize | undefined,
serializeAction?: Serialize | undefined
) {
if (message.type === 'ACTION') {
post({
@ -428,12 +426,12 @@ export function toContentScript<S, A extends Action<string>>(
}
}
export function sendMessage<S, A extends Action<string>>(
export function sendMessage<S, A extends Action<unknown>>(
action: StructuralPerformAction<A> | StructuralPerformAction<A>[],
state: LiftedState<S, A, unknown>,
config: Config,
instanceId?: number,
name?: string,
name?: string
) {
let amendedAction = action;
if (typeof config !== 'object') {
@ -453,7 +451,7 @@ export function sendMessage<S, A extends Action<string>>(
instanceId: config.instanceId || instanceId || 1,
},
config.serialize as Serialize | undefined,
config.serialize as Serialize | undefined,
config.serialize as Serialize | undefined
);
} else {
toContentScript<S, A>(
@ -467,7 +465,7 @@ export function sendMessage<S, A extends Action<string>>(
instanceId: config.instanceId || instanceId || 1,
},
config.serialize as Serialize | undefined,
config.serialize as Serialize | undefined,
config.serialize as Serialize | undefined
);
}
}
@ -492,16 +490,16 @@ function handleMessages(event: MessageEvent<ContentScriptToPageScriptMessage>) {
export function setListener(
onMessage: (message: ContentScriptToPageScriptMessage) => void,
instanceId: number,
instanceId: number
) {
listeners[instanceId] = onMessage;
window.addEventListener('message', handleMessages, false);
}
const liftListener =
<S, A extends Action<string>>(
<S, A extends Action<unknown>>(
listener: (message: ListenerMessage<S, A>) => void,
config: Config,
config: Config
) =>
(message: ContentScriptToPageScriptMessage) => {
if (message.type === 'IMPORT') {
@ -523,17 +521,17 @@ export function disconnect() {
}
export interface ConnectResponse {
init: <S, A extends Action<string>>(
init: <S, A extends Action<unknown>>(
state: S,
liftedData?: LiftedState<S, A, unknown>,
liftedData?: LiftedState<S, A, unknown>
) => void;
subscribe: <S, A extends Action<string>>(
listener: (message: ListenerMessage<S, A>) => void,
subscribe: <S, A extends Action<unknown>>(
listener: (message: ListenerMessage<S, A>) => void
) => (() => void) | undefined;
unsubscribe: () => void;
send: <S, A extends Action<string>>(
send: <S, A extends Action<unknown>>(
action: A,
state: LiftedState<S, A, unknown>,
state: LiftedState<S, A, unknown>
) => void;
error: (payload: string) => void;
}
@ -553,8 +551,8 @@ export function connect(preConfig: Config): ConnectResponse {
const localFilter = getLocalFilter(config);
const autoPause = config.autoPause;
let isPaused = autoPause;
let delayedActions: StructuralPerformAction<Action<string>>[] = [];
let delayedStates: LiftedState<unknown, Action<string>, unknown>[] = [];
let delayedActions: StructuralPerformAction<Action<unknown>>[] = [];
let delayedStates: LiftedState<unknown, Action<unknown>, unknown>[] = [];
const rootListener = (action: ContentScriptToPageScriptMessage) => {
if (autoPause) {
@ -577,13 +575,13 @@ export function connect(preConfig: Config): ConnectResponse {
listeners[id] = [rootListener];
const subscribe = <S, A extends Action<string>>(
listener: (message: ListenerMessage<S, A>) => void,
const subscribe = <S, A extends Action<unknown>>(
listener: (message: ListenerMessage<S, A>) => void
) => {
if (!listener) return undefined;
const liftedListener = liftListener(listener, config);
const listenersForId = listeners[id] as ((
message: ContentScriptToPageScriptMessage,
message: ContentScriptToPageScriptMessage
) => void)[];
listenersForId.push(liftedListener);
@ -598,18 +596,14 @@ export function connect(preConfig: Config): ConnectResponse {
};
const sendDelayed = throttle(() => {
sendMessage(
delayedActions,
delayedStates as unknown as LiftedState<unknown, Action<string>, unknown>,
config,
);
sendMessage(delayedActions, delayedStates as any, config);
delayedActions = [];
delayedStates = [];
}, latency);
const send = <S, A extends Action<string>>(
const send = <S, A extends Action<unknown>>(
action: A,
state: LiftedState<S, A, unknown>,
state: LiftedState<S, A, unknown>
) => {
if (
isPaused ||
@ -646,13 +640,13 @@ export function connect(preConfig: Config): ConnectResponse {
sendMessage(
amendedAction as StructuralPerformAction<A>,
amendedState,
config,
config
);
};
const init = <S, A extends Action<string>>(
const init = <S, A extends Action<unknown>>(
state: S,
liftedData?: LiftedState<S, A, unknown>,
liftedData?: LiftedState<S, A, unknown>
) => {
const message: InitMessage<S, A> = {
type: 'INIT',

View File

@ -10,7 +10,7 @@ function createExpBackoffTimer(step: number) {
return 0;
}
// Calculate next timeout
const timeout = Math.pow(2, count - 1);
let timeout = Math.pow(2, count - 1);
if (count < 5) count += 1;
return timeout * step;
};
@ -26,7 +26,7 @@ function postError(message: string) {
type: 'ERROR',
message: message,
},
'*',
'*'
);
}

View File

@ -0,0 +1,18 @@
import { Action } from 'redux';
import { PageScriptToContentScriptMessage } from './index';
export type Position = 'left' | 'right' | 'bottom' | 'panel' | 'remote';
function post<S, A extends Action<unknown>>(
message: PageScriptToContentScriptMessage<S, A>
) {
window.postMessage(message, '*');
}
export default function openWindow(position?: Position) {
post({
source: '@devtools-page',
type: 'OPEN',
position: position || 'right',
});
}

View File

@ -0,0 +1,165 @@
import React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { Button, Container, Divider, Toolbar } from '@redux-devtools/ui';
import SliderMonitor from '@redux-devtools/app/lib/containers/monitors/Slider';
import { liftedDispatch, getReport } from '@redux-devtools/app/lib/actions';
import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances';
import DevTools from '@redux-devtools/app/lib/containers/DevTools';
import Dispatcher from '@redux-devtools/app/lib/containers/monitors/Dispatcher';
import TopButtons from '@redux-devtools/app/lib/components/TopButtons';
import ExportButton from '@redux-devtools/app/lib/components/buttons/ExportButton';
import ImportButton from '@redux-devtools/app/lib/components/buttons/ImportButton';
import PrintButton from '@redux-devtools/app/lib/components/buttons/PrintButton';
import MonitorSelector from '@redux-devtools/app/lib/components/MonitorSelector';
import SliderButton from '@redux-devtools/app/lib/components/buttons/SliderButton';
import DispatcherButton from '@redux-devtools/app/lib/components/buttons/DispatcherButton';
import { StoreState } from '@redux-devtools/app/lib/reducers';
import { GoRadioTower } from 'react-icons/go';
import {
MdBorderBottom,
MdBorderLeft,
MdBorderRight,
MdSave,
} from 'react-icons/md';
import { Position } from '../api/openWindow';
import { SingleMessage } from '../middlewares/api';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
interface OwnProps {
readonly position: string;
}
type Props = StateProps & DispatchProps & OwnProps;
declare global {
interface Window {
isElectron?: boolean;
}
}
function sendMessage(message: SingleMessage) {
chrome.runtime.sendMessage(message);
}
class Actions extends Component<Props> {
openWindow = (position: Position) => {
sendMessage({ type: 'OPEN', position });
};
openOptionsPage = () => {
if (navigator.userAgent.indexOf('Firefox') !== -1) {
sendMessage({ type: 'OPEN_OPTIONS' });
} else {
chrome.runtime.openOptionsPage();
}
};
render() {
const {
monitor,
dispatcherIsOpen,
sliderIsOpen,
options,
liftedState,
liftedDispatch,
position,
} = this.props;
const { features } = options;
return (
<Container>
<TopButtons
dispatch={liftedDispatch}
liftedState={liftedState}
options={options}
/>
<DevTools
monitor={monitor}
liftedState={liftedState}
monitorState={this.props.monitorState}
dispatch={liftedDispatch}
features={options.features}
/>
{sliderIsOpen && options.connectionId && options.features.jump && (
<SliderMonitor liftedState={liftedState} dispatch={liftedDispatch} />
)}
{dispatcherIsOpen &&
options.connectionId &&
options.features.dispatch && <Dispatcher options={options} />}
<Toolbar borderPosition="top">
{features.export && (
<Button title="Save a report" tooltipPosition="top-right">
<MdSave />
</Button>
)}
{features.export && <ExportButton />}
{features.import && <ImportButton />}
<PrintButton />
<Divider />
<MonitorSelector />
<Divider />
{features.jump && <SliderButton isOpen={this.props.sliderIsOpen} />}
{features.dispatch && (
<DispatcherButton dispatcherIsOpen={this.props.dispatcherIsOpen} />
)}
<Divider />
{!window.isElectron && position !== '#left' && (
<Button
onClick={() => {
this.openWindow('left');
}}
>
<MdBorderLeft />
</Button>
)}
{!window.isElectron && position !== '#right' && (
<Button
onClick={() => {
this.openWindow('right');
}}
>
<MdBorderRight />
</Button>
)}
{!window.isElectron && position !== '#bottom' && (
<Button
onClick={() => {
this.openWindow('bottom');
}}
>
<MdBorderBottom />
</Button>
)}
{!window.isElectron && (
<Button
onClick={() => {
this.openWindow('remote');
}}
>
<GoRadioTower />
</Button>
)}
</Toolbar>
</Container>
);
}
}
const mapStateToProps = (state: StoreState) => {
const instances = state.instances;
const id = getActiveInstance(instances);
return {
liftedState: instances.states[id],
monitorState: state.monitor.monitorState,
options: instances.options[id],
monitor: state.monitor.selected,
dispatcherIsOpen: state.monitor.dispatcherIsOpen,
sliderIsOpen: state.monitor.sliderIsOpen,
reports: state.reports.data,
};
};
const actionCreators = {
liftedDispatch,
getReport,
};
export default connect(mapStateToProps, actionCreators)(Actions);

View File

@ -1,13 +1,11 @@
import React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { Container, Notification } from '@redux-devtools/ui';
import {
clearNotification,
getActiveInstance,
Header,
Settings,
StoreState,
} from '@redux-devtools/app';
import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances';
import Settings from '@redux-devtools/app/lib/components/Settings';
import Header from '@redux-devtools/app/lib/components/Header';
import { clearNotification } from '@redux-devtools/app/lib/actions';
import { StoreState } from '@redux-devtools/app/lib/reducers';
import Actions from './Actions';
type StateProps = ReturnType<typeof mapStateToProps>;
@ -27,7 +25,6 @@ class App extends Component<Props> {
<a
href="https://github.com/zalmoxisus/redux-devtools-extension#usage"
target="_blank"
rel="noreferrer"
>
the instructions
</a>

View File

@ -1,34 +1,44 @@
import stringifyJSON from '@redux-devtools/app/lib/utils/stringifyJSON';
import {
UPDATE_STATE,
REMOVE_INSTANCE,
LIFTED_ACTION,
TOGGLE_PERSIST,
SET_PERSIST,
} from '@redux-devtools/app/lib/constants/actionTypes';
import { nonReduxDispatch } from '@redux-devtools/app/lib/utils/monitorActions';
import syncOptions, {
Options,
OptionsMessage,
SyncOptions,
} from '../../browser/extension/options/syncOptions';
import openDevToolsWindow, {
DevToolsPosition,
} from '../../browser/extension/background/openWindow';
import { getReport } from '../../browser/extension/background/logging';
import {
CustomAction,
DispatchAction as AppDispatchAction,
LibConfig,
LIFTED_ACTION,
nonReduxDispatch,
REMOVE_INSTANCE,
SET_PERSIST,
SetPersistAction,
stringifyJSON,
TOGGLE_PERSIST,
UPDATE_STATE,
} from '@redux-devtools/app';
import type { Options, OptionsMessage } from '../../options/syncOptions';
import openDevToolsWindow, { DevToolsPosition } from '../openWindow';
import { getReport } from '../logging';
import { Action, Dispatch, Middleware } from 'redux';
import type {
} from '@redux-devtools/app/lib/actions';
import { Action, Dispatch, MiddlewareAPI } from 'redux';
import {
ContentScriptToBackgroundMessage,
SplitMessage,
} from '../../contentScript';
import type {
} from '../../browser/extension/inject/contentScript';
import {
ErrorMessage,
PageScriptToContentScriptMessageForwardedToMonitors,
PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance,
} from '../../pageScript/api';
} from '../api';
import { LiftedState } from '@redux-devtools/instrument';
import type { BackgroundAction, LiftedActionAction } from './backgroundStore';
import type { Position } from '../../pageScript/api/openWindow';
import type { BackgroundState } from './backgroundReducer';
import { store } from '../index';
import {
BackgroundAction,
LiftedActionAction,
} from '../stores/backgroundStore';
import { Position } from '../api/openWindow';
import { BackgroundState } from '../reducers/background';
interface TabMessageBase {
readonly type: string;
@ -48,11 +58,6 @@ interface StopAction extends TabMessageBase {
readonly id?: never;
}
interface OptionsAction {
readonly type: 'OPTIONS';
readonly options: Options;
}
interface DispatchAction extends TabMessageBase {
readonly type: 'DISPATCH';
readonly action: AppDispatchAction;
@ -86,7 +91,7 @@ export interface NAAction {
readonly id: string | number;
}
interface InitMessage<S, A extends Action<string>> {
interface InitMessage<S, A extends Action<unknown>> {
readonly type: 'INIT';
readonly payload: string;
instanceId: string;
@ -139,7 +144,7 @@ interface SerializedActionMessage {
readonly nextActionId: number;
}
interface SerializedStateMessage<S, A extends Action<string>> {
interface SerializedStateMessage<S, A extends Action<unknown>> {
readonly type: 'STATE';
readonly payload: Omit<
LiftedState<S, A, unknown>,
@ -153,7 +158,7 @@ interface SerializedStateMessage<S, A extends Action<string>> {
readonly committedState: boolean;
}
export type UpdateStateRequest<S, A extends Action<string>> =
type UpdateStateRequest<S, A extends Action<unknown>> =
| InitMessage<S, A>
| LiftedMessage
| SerializedPartialStateMessage
@ -161,149 +166,91 @@ export type UpdateStateRequest<S, A extends Action<string>> =
| SerializedActionMessage
| SerializedStateMessage<S, A>;
interface UpdateStateAction<S, A extends Action<string>> {
export interface EmptyUpdateStateAction {
readonly type: typeof UPDATE_STATE;
}
interface UpdateStateAction<S, A extends Action<unknown>> {
readonly type: typeof UPDATE_STATE;
request: UpdateStateRequest<S, A>;
readonly id: string | number;
}
type SplitUpdateStateRequestStart<S, A extends Action<string>> = {
split: 'start';
} & Partial<UpdateStateRequest<S, A>>;
interface SplitUpdateStateRequestChunk {
readonly split: 'chunk';
readonly chunk: [string, string];
}
interface SplitUpdateStateRequestEnd {
readonly split: 'end';
}
export type SplitUpdateStateRequest<S, A extends Action<string>> =
| SplitUpdateStateRequestStart<S, A>
| SplitUpdateStateRequestChunk
| SplitUpdateStateRequestEnd;
interface SplitUpdateStateAction<S, A extends Action<string>> {
readonly type: typeof UPDATE_STATE;
request: SplitUpdateStateRequest<S, A>;
readonly id: string | number;
}
export type TabMessage =
| StartAction
| StopAction
| OptionsAction
| OptionsMessage
| DispatchAction
| ImportAction
| ActionAction
| ExportAction;
export type PanelMessageWithoutNA<S, A extends Action<string>> =
export type PanelMessage<S, A extends Action<unknown>> =
| NAAction
| ErrorMessage
| UpdateStateAction<S, A>
| SetPersistAction;
export type PanelMessage<S, A extends Action<string>> =
| PanelMessageWithoutNA<S, A>
| NAAction;
export type PanelMessageWithSplitAction<S, A extends Action<string>> =
| PanelMessage<S, A>
| SplitUpdateStateAction<S, A>;
export type MonitorMessage =
| NAAction
| ErrorMessage
| EmptyUpdateStateAction
| SetPersistAction;
type TabPort = Omit<chrome.runtime.Port, 'postMessage'> & {
postMessage: (message: TabMessage) => void;
};
type PanelPort = Omit<chrome.runtime.Port, 'postMessage'> & {
postMessage: <S, A extends Action<string>>(
message: PanelMessageWithSplitAction<S, A>,
postMessage: <S, A extends Action<unknown>>(
message: PanelMessage<S, A>
) => void;
};
type MonitorPort = Omit<chrome.runtime.Port, 'postMessage'> & {
postMessage: (message: MonitorMessage) => void;
};
export const CONNECTED = 'socket/CONNECTED';
export const DISCONNECTED = 'socket/DISCONNECTED';
const connections: {
readonly tab: { [K in number | string]: TabPort };
readonly panel: { [K in number | string]: PanelPort };
readonly monitor: { [K in number | string]: MonitorPort };
} = {
tab: {},
panel: {},
monitor: {},
};
const chunks: {
[instanceId: string]: PageScriptToContentScriptMessageForwardedToMonitors<
unknown,
Action<string>
Action<unknown>
>;
} = {};
let monitors = 0;
let isMonitored = false;
const getId = (sender: chrome.runtime.MessageSender, name?: string) =>
sender.tab ? sender.tab.id! : name || sender.id!;
type MonitorAction<S, A extends Action<string>> =
type MonitorAction<S, A extends Action<unknown>> =
| NAAction
| ErrorMessage
| UpdateStateAction<S, A>
| SetPersistAction;
// Chrome message limit is 64 MB, but we're using 32 MB to include other object's parts
const maxChromeMsgSize = 32 * 1024 * 1024;
function toMonitors<S, A extends Action<string>>(action: MonitorAction<S, A>) {
console.log(`Message to monitors: ${action.type}`);
for (const port of Object.values(connections.panel)) {
try {
port.postMessage(action);
} catch (err) {
if (
action.type !== UPDATE_STATE ||
err == null ||
(err as Error).message !==
'Message length exceeded maximum allowed length.'
) {
throw err;
}
const splitMessageStart: SplitUpdateStateRequestStart<S, A> = {
split: 'start',
};
const toSplit: [string, string][] = [];
let size = 0;
for (const [key, value] of Object.entries(
action.request as unknown as Record<string, unknown>,
)) {
if (typeof value === 'string') {
size += value.length;
if (size > maxChromeMsgSize) {
toSplit.push([key, value]);
continue;
}
}
(splitMessageStart as any)[key as keyof typeof splitMessageStart] =
value;
}
port.postMessage({ ...action, request: splitMessageStart });
for (let i = 0; i < toSplit.length; i++) {
for (let j = 0; j < toSplit[i][1].length; j += maxChromeMsgSize) {
port.postMessage({
...action,
request: {
split: 'chunk',
chunk: [
toSplit[i][0],
toSplit[i][1].substring(j, j + maxChromeMsgSize),
],
},
});
}
}
port.postMessage({ ...action, request: { split: 'end' } });
}
}
function toMonitors<S, A extends Action<unknown>>(
action: MonitorAction<S, A>,
tabId?: string | number,
verbose?: boolean
) {
Object.keys(connections.monitor).forEach((id) => {
connections.monitor[id].postMessage(
verbose || action.type === 'ERROR' || action.type === SET_PERSIST
? action
: { type: UPDATE_STATE }
);
});
Object.keys(connections.panel).forEach((id) => {
connections.panel[id].postMessage(action);
});
}
interface ImportMessage {
@ -317,15 +264,19 @@ interface ImportMessage {
type ToContentScriptMessage = ImportMessage | LiftedActionAction;
function toContentScript(messageBody: ToContentScriptMessage) {
console.log(`Message to tab ${messageBody.id}: ${messageBody.message}`);
if (messageBody.message === 'DISPATCH') {
const { message, action, id, instanceId, state } = messageBody;
connections.tab[id!].postMessage({
type: message,
action,
state: nonReduxDispatch(store, message, instanceId, action, state),
id: instanceId.toString().replace(/^[^/]+\//, ''),
state: nonReduxDispatch(
window.store,
message,
instanceId,
action as AppDispatchAction,
state
),
id: instanceId.toString().replace(/^[^\/]+\//, ''),
});
} else if (messageBody.message === 'IMPORT') {
const { message, action, id, instanceId, state } = messageBody;
@ -333,13 +284,13 @@ function toContentScript(messageBody: ToContentScriptMessage) {
type: message,
action,
state: nonReduxDispatch(
store,
window.store,
message,
instanceId,
action as unknown as AppDispatchAction,
state,
state
),
id: instanceId.toString().replace(/^[^/]+\//, ''),
id: instanceId.toString().replace(/^[^\/]+\//, ''),
});
} else if (messageBody.message === 'ACTION') {
const { message, action, id, instanceId, state } = messageBody;
@ -347,13 +298,13 @@ function toContentScript(messageBody: ToContentScriptMessage) {
type: message,
action,
state: nonReduxDispatch(
store,
window.store,
message,
instanceId,
action as unknown as AppDispatchAction,
state,
state
),
id: instanceId.toString().replace(/^[^/]+\//, ''),
id: instanceId.toString().replace(/^[^\/]+\//, ''),
});
} else if (messageBody.message === 'EXPORT') {
const { message, action, id, instanceId, state } = messageBody;
@ -361,41 +312,53 @@ function toContentScript(messageBody: ToContentScriptMessage) {
type: message,
action,
state: nonReduxDispatch(
store,
window.store,
message,
instanceId,
action as unknown as AppDispatchAction,
state,
state
),
id: instanceId.toString().replace(/^[^/]+\//, ''),
id: instanceId.toString().replace(/^[^\/]+\//, ''),
});
} else {
const { message, action, id, instanceId, state } = messageBody;
connections.tab[id].postMessage({
connections.tab[id!].postMessage({
type: message,
action,
state: nonReduxDispatch(
store,
window.store,
message,
instanceId,
action as AppDispatchAction,
state,
state
),
id: (instanceId as number).toString().replace(/^[^/]+\//, ''),
id: (instanceId as number).toString().replace(/^[^\/]+\//, ''),
});
}
}
function toAllTabs(msg: TabMessage) {
console.log(`Message to all tabs: ${msg.type}`);
const tabs = connections.tab;
Object.keys(tabs).forEach((id) => {
tabs[id].postMessage(msg);
});
}
for (const tabPort of Object.values(connections.tab)) {
tabPort.postMessage(msg);
function monitorInstances(shouldMonitor: boolean, id?: string) {
if (!id && isMonitored === shouldMonitor) return;
const action = {
type: shouldMonitor ? ('START' as const) : ('STOP' as const),
};
if (id) {
if (connections.tab[id]) connections.tab[id].postMessage(action);
} else {
toAllTabs(action);
}
isMonitored = shouldMonitor;
}
function getReducerError() {
const instancesState = store.getState().instances;
const instancesState = window.store.getState().instances;
const payload = instancesState.states[instancesState.current];
const computedState = payload.computedStates[payload.currentStateIndex];
if (!computedState) return false;
@ -403,13 +366,13 @@ function getReducerError() {
}
function togglePersist() {
const state = store.getState();
const state = window.store.getState();
if (state.instances.persisted) {
for (const id of Object.keys(state.instances.connections)) {
Object.keys(state.instances.connections).forEach((id) => {
if (connections.tab[id]) return;
store.dispatch({ type: REMOVE_INSTANCE, id });
window.store.dispatch({ type: REMOVE_INSTANCE, id });
toMonitors({ type: 'NA', id });
}
});
}
}
@ -422,35 +385,45 @@ interface OpenOptionsMessage {
readonly type: 'OPEN_OPTIONS';
}
export type SingleMessage = OpenMessage | OpenOptionsMessage | OptionsMessage;
interface GetOptionsMessage {
readonly type: 'GET_OPTIONS';
}
type BackgroundStoreMessage<S, A extends Action<string>> =
export type SingleMessage =
| OpenMessage
| OpenOptionsMessage
| GetOptionsMessage;
type BackgroundStoreMessage<S, A extends Action<unknown>> =
| PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<S, A>
| SplitMessage
| SingleMessage;
type BackgroundStoreResponse = { readonly options: Options };
// Receive messages from content scripts
function messaging<S, A extends Action<string>>(
function messaging<S, A extends Action<unknown>>(
request: BackgroundStoreMessage<S, A>,
sender: chrome.runtime.MessageSender,
sendResponse?: (response?: BackgroundStoreResponse) => void
) {
let tabId = getId(sender);
console.log(`Message from tab ${tabId}: ${request.type ?? request.split}`);
if (!tabId) return;
if (sender.frameId) tabId = `${tabId}-${sender.frameId}`;
if (request.type === 'STOP') {
if (!Object.keys(store.getState().instances.connections).length) {
store.dispatch({ type: DISCONNECTED });
if (!Object.keys(window.store.getState().instances.connections).length) {
window.store.dispatch({ type: DISCONNECTED });
}
return;
}
if (request.type === 'OPEN_OPTIONS') {
void chrome.runtime.openOptionsPage();
chrome.runtime.openOptionsPage();
return;
}
if (request.type === 'OPTIONS') {
toAllTabs({ type: 'OPTIONS', options: request.options });
if (request.type === 'GET_OPTIONS') {
window.syncOptions.get((options) => {
sendResponse!({ options });
});
return;
}
if (request.type === 'GET_REPORT') {
@ -458,8 +431,12 @@ function messaging<S, A extends Action<string>>(
return;
}
if (request.type === 'OPEN') {
let position: DevToolsPosition = 'devtools-window';
if (['remote', 'window'].includes(request.position)) {
let position: DevToolsPosition = 'devtools-left';
if (
['remote', 'panel', 'left', 'right', 'bottom'].indexOf(
request.position
) !== -1
) {
position = ('devtools-' + request.position) as DevToolsPosition;
}
openDevToolsWindow(position);
@ -467,7 +444,7 @@ function messaging<S, A extends Action<string>>(
}
if (request.type === 'ERROR') {
if (request.payload) {
toMonitors(request);
toMonitors(request, tabId);
return;
}
if (!request.message) return;
@ -507,56 +484,56 @@ function messaging<S, A extends Action<string>>(
if (request.instanceId) {
action.request.instanceId = instanceId;
}
store.dispatch(action);
window.store.dispatch(action);
toMonitors(action);
if (request.type === 'EXPORT') {
toMonitors(action, tabId, true);
} else {
toMonitors(action, tabId);
}
}
function disconnect(
type: 'tab' | 'panel',
type: 'tab' | 'monitor' | 'panel',
id: number | string,
listener: (message: any, port: chrome.runtime.Port) => void,
listener?: (message: any, port: chrome.runtime.Port) => void
) {
return function disconnectListener() {
console.log(`Disconnected from ${type} ${id}`);
const p = connections[type][id];
if (listener && p) p.onMessage.removeListener(listener);
if (p) p.onDisconnect.removeListener(disconnectListener);
delete connections[type][id];
if (type === 'tab') {
if (!store.getState().instances.persisted) {
store.dispatch({ type: REMOVE_INSTANCE, id });
if (!window.store.getState().instances.persisted) {
window.store.dispatch({ type: REMOVE_INSTANCE, id });
toMonitors({ type: 'NA', id });
}
} else {
monitors--;
if (monitors === 0) toAllTabs({ type: 'STOP' });
if (!monitors) monitorInstances(false);
}
};
}
function onConnect<S, A extends Action<string>>(port: chrome.runtime.Port) {
function onConnect<S, A extends Action<unknown>>(port: chrome.runtime.Port) {
let id: number | string;
let listener;
store.dispatch({ type: CONNECTED, port });
window.store.dispatch({ type: CONNECTED, port });
if (port.name === 'tab') {
id = getId(port.sender!);
console.log(`Connected to tab ${id}`);
if (port.sender!.frameId) id = `${id}-${port.sender!.frameId}`;
connections.tab[id] = port;
listener = (msg: ContentScriptToBackgroundMessage<S, A>) => {
console.log(`Message from tab ${id}: ${msg.name}`);
if (msg.name === 'INIT_INSTANCE') {
if (typeof id === 'number') {
void chrome.action.enable(id);
void chrome.action.setIcon({ tabId: id, path: 'img/logo/38x38.png' });
chrome.pageAction.show(id);
chrome.pageAction.setIcon({ tabId: id, path: 'img/logo/38x38.png' });
}
if (monitors > 0) port.postMessage({ type: 'START' });
if (isMonitored) port.postMessage({ type: 'START' });
const state = store.getState();
const state = window.store.getState();
if (state.instances.persisted) {
const instanceId = `${id}/${msg.instanceId}`;
const persistedState = state.instances.states[instanceId];
@ -567,7 +544,7 @@ function onConnect<S, A extends Action<string>>(port: chrome.runtime.Port) {
instanceId,
state: stringifyJSON(
persistedState,
state.instances.options[instanceId].serialize,
state.instances.options[instanceId].serialize
),
});
}
@ -580,45 +557,22 @@ function onConnect<S, A extends Action<string>>(port: chrome.runtime.Port) {
port.onMessage.addListener(listener);
port.onDisconnect.addListener(disconnect('tab', id, listener));
} else if (port.name && port.name.indexOf('monitor') === 0) {
// devpanel
id = getId(port.sender!, port.name);
console.log(`Connected to monitor ${id}`);
connections.panel[id] = port;
connections.monitor[id] = port;
monitorInstances(true);
monitors++;
port.onDisconnect.addListener(disconnect('monitor', id));
} else {
// devpanel
id = port.name || port.sender!.frameId!;
connections.panel[id] = port;
monitorInstances(true, port.name);
monitors++;
toAllTabs({ type: 'START' });
listener = (msg: BackgroundAction) => {
console.log(`Message from monitor ${id}: ${msg.type}`);
store.dispatch(msg);
window.store.dispatch(msg);
};
port.onMessage.addListener(listener);
port.onDisconnect.addListener(disconnect('panel', id, listener));
const { current } = store.getState().instances;
if (current !== 'default') {
const connectionId = Object.entries(
store.getState().instances.connections,
).find(([, instanceIds]) => instanceIds.includes(current))?.[0];
const options = store.getState().instances.options[current];
const state = store.getState().instances.states[current];
const { actionsById, computedStates, committedState, ...rest } = state;
toMonitors({
type: UPDATE_STATE,
request: {
type: 'STATE',
payload: rest as Omit<
LiftedState<S, A, unknown>,
'actionsById' | 'computedStates' | 'committedState'
>,
source: '@devtools-page',
instanceId:
typeof current === 'number' ? current.toString() : current,
actionsById: stringifyJSON(actionsById, options.serialize),
computedStates: stringifyJSON(computedStates, options.serialize),
committedState: typeof committedState !== 'undefined',
},
id: connectionId ?? current,
});
}
}
}
@ -629,14 +583,21 @@ chrome.runtime.onMessageExternal.addListener(messaging);
chrome.notifications.onClicked.addListener((id) => {
chrome.notifications.clear(id);
openDevToolsWindow('devtools-window');
openDevToolsWindow('devtools-right');
});
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
const api: Middleware<{}, BackgroundState, Dispatch<BackgroundAction>> =
(store) => (next) => (untypedAction) => {
const action = untypedAction as BackgroundAction;
declare global {
interface Window {
syncOptions: SyncOptions;
}
}
window.syncOptions = syncOptions(toAllTabs); // Expose to the options page
export default function api(
store: MiddlewareAPI<Dispatch<BackgroundAction>, BackgroundState>
) {
return (next: Dispatch<BackgroundAction>) => (action: BackgroundAction) => {
if (action.type === LIFTED_ACTION) toContentScript(action);
else if (action.type === TOGGLE_PERSIST) {
togglePersist();
@ -647,5 +608,4 @@ const api: Middleware<{}, BackgroundState, Dispatch<BackgroundAction>> =
}
return next(action);
};
export default api;
}

View File

@ -0,0 +1,50 @@
import { Dispatch, MiddlewareAPI } from 'redux';
import {
SELECT_INSTANCE,
UPDATE_STATE,
} from '@redux-devtools/app/lib/constants/actionTypes';
import { StoreAction } from '@redux-devtools/app/lib/actions';
import { StoreState } from '@redux-devtools/app/lib/reducers';
function selectInstance(
tabId: number,
store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>,
next: Dispatch<StoreAction>
) {
const instances = store.getState().instances;
if (instances.current === 'default') return;
const connections = instances.connections[tabId];
if (connections && connections.length === 1) {
next({ type: SELECT_INSTANCE, selected: connections[0] });
}
}
function getCurrentTabId(next: (tabId: number) => void) {
chrome.tabs.query(
{
active: true,
lastFocusedWindow: true,
},
(tabs) => {
const tab = tabs[0];
if (!tab) return;
next(tab.id!);
}
);
}
export default function popupSelector(
store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>
) {
return (next: Dispatch<StoreAction>) => (action: StoreAction) => {
const result = next(action);
if (action.type === UPDATE_STATE) {
if (chrome.devtools && chrome.devtools.inspectedWindow) {
selectInstance(chrome.devtools.inspectedWindow.tabId, store, next);
} else {
getCurrentTabId((tabId) => selectInstance(tabId, store, next));
}
}
return result;
};
}

View File

@ -0,0 +1,37 @@
import {
LIFTED_ACTION,
UPDATE_STATE,
SELECT_INSTANCE,
TOGGLE_PERSIST,
} from '@redux-devtools/app/lib/constants/actionTypes';
import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances';
import { Dispatch, MiddlewareAPI } from 'redux';
import { StoreState } from '@redux-devtools/app/lib/reducers';
import { StoreAction } from '@redux-devtools/app/lib/actions';
function panelDispatcher(bgConnection: chrome.runtime.Port) {
let autoselected = false;
const tabId = chrome.devtools.inspectedWindow.tabId;
return (store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>) =>
(next: Dispatch<StoreAction>) =>
(action: StoreAction) => {
const result = next(action);
if (!autoselected && action.type === UPDATE_STATE && tabId) {
autoselected = true;
const connections = store.getState().instances.connections[tabId];
if (connections && connections.length === 1) {
next({ type: SELECT_INSTANCE, selected: connections[0] });
}
}
if (action.type === LIFTED_ACTION || action.type === TOGGLE_PERSIST) {
const instances = store.getState().instances;
const instanceId = getActiveInstance(instances);
const id = instances.options[instanceId].connectionId;
bgConnection.postMessage({ ...action, instanceId, id });
}
return result;
};
}
export default panelDispatcher;

View File

@ -0,0 +1,34 @@
import {
UPDATE_STATE,
LIFTED_ACTION,
TOGGLE_PERSIST,
} from '@redux-devtools/app/lib/constants/actionTypes';
import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances';
import { Dispatch, MiddlewareAPI, Store } from 'redux';
import { BackgroundState } from '../reducers/background';
import { StoreAction } from '@redux-devtools/app/lib/actions';
import { WindowStoreAction } from '../stores/windowStore';
import { StoreState } from '@redux-devtools/app/lib/reducers';
import { BackgroundAction } from '../stores/backgroundStore';
const syncStores =
(baseStore: Store<BackgroundState, BackgroundAction>) =>
(store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>) =>
(next: Dispatch<WindowStoreAction>) =>
(action: StoreAction) => {
if (action.type === UPDATE_STATE) {
return next({
...action,
instances: baseStore.getState().instances,
});
}
if (action.type === LIFTED_ACTION || action.type === TOGGLE_PERSIST) {
const instances = store.getState().instances;
const instanceId = getActiveInstance(instances);
const id = instances.options[instanceId].connectionId;
baseStore.dispatch({ ...action, instanceId, id } as any);
}
return next(action);
};
export default syncStores;

View File

@ -0,0 +1,16 @@
import { combineReducers, Reducer } from 'redux';
import instances, {
InstancesState,
} from '@redux-devtools/app/lib/reducers/instances';
import { BackgroundAction } from '../../stores/backgroundStore';
export interface BackgroundState {
readonly instances: InstancesState;
}
const rootReducer: Reducer<BackgroundState, BackgroundAction> =
combineReducers<BackgroundState>({
instances,
});
export default rootReducer;

View File

@ -0,0 +1,44 @@
import { combineReducers, Reducer } from 'redux';
import instances, {
InstancesState,
} from '@redux-devtools/app/lib/reducers/instances';
import monitor, {
MonitorState,
} from '@redux-devtools/app/lib/reducers/monitor';
import notification, {
NotificationState,
} from '@redux-devtools/app/lib/reducers/notification';
import reports, {
ReportsState,
} from '@redux-devtools/app/lib/reducers/reports';
import section, {
SectionState,
} from '@redux-devtools/app/lib/reducers/section';
import theme, { ThemeState } from '@redux-devtools/app/lib/reducers/theme';
import connection, {
ConnectionState,
} from '@redux-devtools/app/lib/reducers/connection';
import { StoreAction } from '@redux-devtools/app/lib/actions';
export interface StoreStateWithoutSocket {
readonly section: SectionState;
readonly theme: ThemeState;
readonly connection: ConnectionState;
readonly monitor: MonitorState;
readonly instances: InstancesState;
readonly reports: ReportsState;
readonly notification: NotificationState;
}
const rootReducer: Reducer<StoreStateWithoutSocket, StoreAction> =
combineReducers<StoreStateWithoutSocket>({
instances,
monitor,
reports,
notification,
section,
theme,
connection,
});
export default rootReducer;

View File

@ -0,0 +1,25 @@
import { combineReducers, Reducer } from 'redux';
import instances from './instances';
import monitor from '@redux-devtools/app/lib/reducers/monitor';
import notification from '@redux-devtools/app/lib/reducers/notification';
import socket from '@redux-devtools/app/lib/reducers/socket';
import reports from '@redux-devtools/app/lib/reducers/reports';
import section from '@redux-devtools/app/lib/reducers/section';
import theme from '@redux-devtools/app/lib/reducers/theme';
import connection from '@redux-devtools/app/lib/reducers/connection';
import { StoreState } from '@redux-devtools/app/lib/reducers';
import { WindowStoreAction } from '../../stores/windowStore';
const rootReducer: Reducer<StoreState, WindowStoreAction> =
combineReducers<StoreState>({
instances,
monitor,
socket,
reports,
notification,
section,
theme,
connection,
});
export default rootReducer;

View File

@ -0,0 +1,36 @@
import {
initialState,
dispatchAction,
} from '@redux-devtools/app/lib/reducers/instances';
import {
UPDATE_STATE,
SELECT_INSTANCE,
LIFTED_ACTION,
SET_PERSIST,
} from '@redux-devtools/app/lib/constants/actionTypes';
import {
ExpandedUpdateStateAction,
WindowStoreAction,
} from '../../stores/windowStore';
export default function instances(
state = initialState,
action: WindowStoreAction
) {
switch (action.type) {
case UPDATE_STATE:
return {
...(action as ExpandedUpdateStateAction).instances,
selected: state.selected,
};
case LIFTED_ACTION:
if (action.message === 'DISPATCH') return dispatchAction(state, action);
return state;
case SELECT_INSTANCE:
return { ...state, selected: action.selected };
case SET_PERSIST:
return { ...state, persisted: action.payload };
default:
return state;
}
}

View File

@ -1,6 +1,6 @@
import { Action } from 'redux';
import { LiftedState } from '@redux-devtools/instrument';
import { DispatchAction, LibConfig } from '@redux-devtools/app';
import { DispatchAction, LibConfig } from '@redux-devtools/app/lib/actions';
declare global {
interface Window {
@ -8,10 +8,10 @@ declare global {
}
}
export default class Monitor<S, A extends Action<string>> {
export default class Monitor<S, A extends Action<unknown>> {
update: (
liftedState?: LiftedState<S, A, unknown> | undefined,
libConfig?: LibConfig,
libConfig?: LibConfig
) => void;
active?: boolean;
paused?: boolean;
@ -21,8 +21,8 @@ export default class Monitor<S, A extends Action<string>> {
constructor(
update: (
liftedState?: LiftedState<S, A, unknown> | undefined,
libConfig?: LibConfig,
) => void,
libConfig?: LibConfig
) => void
) {
this.update = update;
}
@ -51,8 +51,7 @@ export default class Monitor<S, A extends Action<string>> {
this.lastAction && /^@@redux\/(INIT|REPLACE)/.test(this.lastAction);
isMonitorAction = () =>
this.lastAction && this.lastAction !== 'PERFORM_ACTION';
isTimeTraveling = () =>
this.lastAction === 'JUMP_TO_STATE' || this.lastAction === 'JUMP_TO_ACTION';
isTimeTraveling = () => this.lastAction === 'JUMP_TO_STATE';
isPaused = () => {
if (this.paused) {
if (this.lastAction !== 'BLOCKED') {

View File

@ -1,12 +1,12 @@
import { createStore, applyMiddleware } from 'redux';
import { createStore, applyMiddleware, PreloadedState } from 'redux';
import rootReducer, { BackgroundState } from '../reducers/background';
import api, { CONNECTED, DISCONNECTED } from '../middlewares/api';
import { LIFTED_ACTION } from '@redux-devtools/app/lib/constants/actionTypes';
import {
CustomAction,
DispatchAction,
LIFTED_ACTION,
StoreActionWithoutLiftedAction,
} from '@redux-devtools/app';
import rootReducer, { BackgroundState } from './backgroundReducer';
import api, { CONNECTED, DISCONNECTED } from './apiMiddleware';
} from '@redux-devtools/app/lib/actions';
interface LiftedActionActionBase {
action?: DispatchAction | string | CustomAction;
@ -60,7 +60,7 @@ export type BackgroundAction =
| DisconnectedAction;
export default function configureStore(
preloadedState?: Partial<BackgroundState>,
preloadedState?: PreloadedState<BackgroundState>
) {
return createStore(rootReducer, preloadedState, applyMiddleware(api));
/*

View File

@ -0,0 +1,15 @@
import {
Action,
createStore,
PreloadedState,
Reducer,
StoreEnhancer,
} from 'redux';
export default function configureStore<S, A extends Action<unknown>>(
reducer: Reducer<S, A>,
initialState: PreloadedState<S> | undefined,
enhance: () => StoreEnhancer
) {
return createStore(reducer, initialState, enhance());
}

View File

@ -1,11 +1,11 @@
import { Action, compose, Reducer, StoreEnhancerStoreCreator } from 'redux';
import { instrument } from '@redux-devtools/instrument';
import { persistState } from '@redux-devtools/core';
import type { ConfigWithExpandedMaxAge } from './index';
import instrument from '@redux-devtools/instrument';
import persistState from '@redux-devtools/core/lib/persistState';
import { ConfigWithExpandedMaxAge } from '../../browser/extension/inject/pageScript';
export function getUrlParam(key: string) {
const matches = new RegExp(`[?&]${key}=([^&#]+)\\b`).exec(
window.location.href,
const matches = window.location.href.match(
new RegExp(`[?&]${key}=([^&#]+)\\b`)
);
return matches && matches.length > 0 ? matches[1] : null;
}
@ -18,13 +18,13 @@ declare global {
export default function configureStore<
S,
A extends Action<string>,
A extends Action<unknown>,
MonitorState,
MonitorAction extends Action<string>,
MonitorAction extends Action<unknown>
>(
next: StoreEnhancerStoreCreator,
monitorReducer: Reducer<MonitorState, MonitorAction>,
config: ConfigWithExpandedMaxAge,
config: ConfigWithExpandedMaxAge
) {
return compose(
instrument(monitorReducer, {
@ -37,6 +37,6 @@ export default function configureStore<
shouldStartLocked: config.shouldStartLocked,
pauseActionType: config.pauseActionType || '@@PAUSED',
}),
persistState(getUrlParam('debug_session')),
persistState(getUrlParam('debug_session'))
)(next);
}

View File

@ -0,0 +1,26 @@
import { createStore, applyMiddleware, PreloadedState, Reducer } from 'redux';
import localForage from 'localforage';
import { persistReducer, persistStore } from 'redux-persist';
import exportState from '@redux-devtools/app/lib/middlewares/exportState';
import panelDispatcher from '../middlewares/panelSync';
import rootReducer, { StoreStateWithoutSocket } from '../reducers/panel';
import { StoreAction } from '@redux-devtools/app/lib/actions';
const persistConfig = {
key: 'redux-devtools',
blacklist: ['instances', 'socket'],
storage: localForage,
};
const persistedReducer: Reducer<StoreStateWithoutSocket, StoreAction> =
persistReducer(persistConfig, rootReducer) as any;
export default function configureStore(
position: string,
bgConnection: chrome.runtime.Port
) {
const enhancer = applyMiddleware(exportState, panelDispatcher(bgConnection));
const store = createStore(persistedReducer, enhancer);
const persistor = persistStore(store);
return { store, persistor };
}

View File

@ -0,0 +1,78 @@
import {
createStore,
compose,
applyMiddleware,
Store,
StoreEnhancer,
Reducer,
} from 'redux';
import localForage from 'localforage';
import { persistReducer, persistStore } from 'redux-persist';
import exportState from '@redux-devtools/app/lib/middlewares/exportState';
import api from '@redux-devtools/app/lib/middlewares/api';
import { CONNECT_REQUEST } from '@redux-devtools/app/lib/constants/socketActionTypes';
import {
StoreActionWithoutUpdateState,
UpdateStateAction,
} from '@redux-devtools/app/lib/actions';
import { InstancesState } from '@redux-devtools/app/lib/reducers/instances';
import syncStores from '../middlewares/windowSync';
import instanceSelector from '../middlewares/instanceSelector';
import rootReducer from '../reducers/window';
import { BackgroundState } from '../reducers/background';
import { BackgroundAction } from './backgroundStore';
import { EmptyUpdateStateAction, NAAction } from '../middlewares/api';
import { StoreState } from '@redux-devtools/app/lib/reducers';
export interface ExpandedUpdateStateAction extends UpdateStateAction {
readonly instances: InstancesState;
}
export type WindowStoreAction =
| StoreActionWithoutUpdateState
| ExpandedUpdateStateAction
| NAAction
| EmptyUpdateStateAction;
const persistConfig = {
key: 'redux-devtools',
blacklist: ['instances', 'socket'],
storage: localForage,
};
const persistedReducer: Reducer<StoreState, WindowStoreAction> = persistReducer(
persistConfig,
rootReducer
) as any;
export default function configureStore(
baseStore: Store<BackgroundState, BackgroundAction>,
position: string
) {
let enhancer: StoreEnhancer;
const middlewares = [exportState, api, syncStores(baseStore)];
if (!position || position === '#popup') {
// select current tab instance for devPanel and pageAction
middlewares.push(instanceSelector);
}
if (process.env.NODE_ENV === 'production') {
enhancer = applyMiddleware(...middlewares);
} else {
enhancer = compose(
applyMiddleware(...middlewares),
window.__REDUX_DEVTOOLS_EXTENSION__
? window.__REDUX_DEVTOOLS_EXTENSION__()
: (noop: unknown) => noop
);
}
const store = createStore(persistedReducer, enhancer);
const persistor = persistStore(store, null, () => {
if (store.getState().connection.type !== 'disabled') {
store.dispatch({
type: CONNECT_REQUEST,
});
}
});
return { store, persistor };
}

View File

@ -1,34 +0,0 @@
export type DevToolsPosition = 'devtools-window' | 'devtools-remote';
const windows: { [K in DevToolsPosition]?: number } = {};
export default function openDevToolsWindow(position: DevToolsPosition) {
if (!windows[position]) {
createWindow(position);
} else {
chrome.windows.update(windows[position], { focused: true }, () => {
if (chrome.runtime.lastError) createWindow(position);
});
}
}
function createWindow(position: DevToolsPosition) {
const url = chrome.runtime.getURL(getPath(position));
chrome.windows.create({ type: 'popup', url }, (win) => {
windows[position] = win!.id;
if (navigator.userAgent.includes('Firefox')) {
void chrome.windows.update(win!.id!, { focused: true });
}
});
}
function getPath(position: DevToolsPosition) {
switch (position) {
case 'devtools-window':
return 'devpanel.html';
case 'devtools-remote':
return 'remote.html';
default:
throw new Error('Unrecognized position');
}
}

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