Enable linting for extension

This commit is contained in:
Nathan Bierema 2024-09-19 22:30:14 -04:00
parent b934e80d23
commit 42ba91917a
37 changed files with 280 additions and 186 deletions

View File

@ -0,0 +1,43 @@
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,3 +0,0 @@
node_modules
dist
examples

View File

@ -1,31 +0,0 @@
{
"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"]
}

View File

@ -0,0 +1,38 @@
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

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

View File

@ -17,9 +17,10 @@
"clean": "rimraf dist && rimraf chrome/dist && rimraf edge/dist && rimraf firefox/dist", "clean": "rimraf dist && rimraf chrome/dist && rimraf edge/dist && rimraf firefox/dist",
"test:app": "cross-env BABEL_ENV=test jest test/app", "test:app": "cross-env BABEL_ENV=test jest test/app",
"test:chrome": "jest test/chrome", "test:chrome": "jest test/chrome",
"build:test:electron:fixture": "webpack --config test/electron/fixture/webpack.config.js",
"test:electron": "pnpm run build:test:electron:fixture && jest test/electron", "test:electron": "pnpm run build:test:electron:fixture && jest test/electron",
"test": "pnpm run test:app && pnpm run test:chrome && pnpm run test:electron", "test": "pnpm run test:app && pnpm run test:chrome && pnpm run test:electron",
"build:test:electron:fixture": "webpack --config test/electron/fixture/webpack.config.js", "lint": "eslint .",
"type-check": "tsc --noEmit" "type-check": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
@ -64,12 +65,7 @@
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"electron": "^31.6.0", "electron": "^31.6.0",
"esbuild": "^0.23.1", "esbuild": "^0.23.1",
"eslint": "^8.57.1", "globals": "^15.9.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react": "^7.36.1",
"eslint-plugin-react-hooks": "^4.6.2",
"immutable": "^4.3.7", "immutable": "^4.3.7",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0", "jest-environment-jsdom": "^29.7.0",

View File

@ -31,19 +31,19 @@ type Props = StateProps & DispatchProps & OwnProps;
const isElectron = navigator.userAgent.includes('Electron'); const isElectron = navigator.userAgent.includes('Electron');
function sendMessage(message: SingleMessage) { async function sendMessage(message: SingleMessage) {
chrome.runtime.sendMessage(message); await chrome.runtime.sendMessage(message);
} }
class Actions extends Component<Props> { class Actions extends Component<Props> {
openWindow = (position: Position) => { openWindow = async (position: Position) => {
sendMessage({ type: 'OPEN', position }); await sendMessage({ type: 'OPEN', position });
}; };
openOptionsPage = () => { openOptionsPage = async () => {
if (navigator.userAgent.indexOf('Firefox') !== -1) { if (navigator.userAgent.includes('Firefox')) {
sendMessage({ type: 'OPEN_OPTIONS' }); await sendMessage({ type: 'OPEN_OPTIONS' });
} else { } else {
chrome.runtime.openOptionsPage(); await chrome.runtime.openOptionsPage();
} }
}; };
@ -85,7 +85,7 @@ class Actions extends Component<Props> {
{features.import && <ImportButton />} {features.import && <ImportButton />}
{position && {position &&
(position !== '#popup' || (position !== '#popup' ||
navigator.userAgent.indexOf('Firefox') !== -1) && <PrintButton />} navigator.userAgent.includes('Firefox')) && <PrintButton />}
<Divider /> <Divider />
<MonitorSelector /> <MonitorSelector />
<Divider /> <Divider />
@ -96,8 +96,8 @@ class Actions extends Component<Props> {
<Divider /> <Divider />
{!isElectron && ( {!isElectron && (
<Button <Button
onClick={() => { onClick={async () => {
this.openWindow('window'); await this.openWindow('window');
}} }}
> >
<MdOutlineWindow /> <MdOutlineWindow />
@ -105,8 +105,8 @@ class Actions extends Component<Props> {
)} )}
{!isElectron && ( {!isElectron && (
<Button <Button
onClick={() => { onClick={async () => {
this.openWindow('remote'); await this.openWindow('remote');
}} }}
> >
<GoBroadcast /> <GoBroadcast />

View File

@ -27,6 +27,7 @@ class App extends Component<Props> {
<a <a
href="https://github.com/zalmoxisus/redux-devtools-extension#usage" href="https://github.com/zalmoxisus/redux-devtools-extension#usage"
target="_blank" target="_blank"
rel="noreferrer"
> >
the instructions the instructions
</a> </a>

View File

@ -6,7 +6,7 @@ export function createMenu() {
{ id: 'devtools-remote', title: 'Open Remote DevTools' }, { id: 'devtools-remote', title: 'Open Remote DevTools' },
]; ];
let shortcuts: { [commandName: string]: string | undefined } = {}; const shortcuts: { [commandName: string]: string | undefined } = {};
chrome.commands.getAll((commands) => { chrome.commands.getAll((commands) => {
for (const { name, shortcut } of commands) { for (const { name, shortcut } of commands) {
shortcuts[name!] = shortcut; shortcuts[name!] = shortcut;

View File

@ -15,7 +15,7 @@ chrome.commands.onCommand.addListener((shortcut) => {
// Disable the action by default and create the context menu when installed // Disable the action by default and create the context menu when installed
chrome.runtime.onInstalled.addListener(() => { chrome.runtime.onInstalled.addListener(() => {
chrome.action.disable(); void chrome.action.disable();
getOptions((option) => { getOptions((option) => {
if (option.showContextMenus) createMenu(); if (option.showContextMenus) createMenu();
@ -32,6 +32,7 @@ chrome.storage.onChanged.addListener((changes) => {
// https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers#keep_a_service_worker_alive_continuously // https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers#keep_a_service_worker_alive_continuously
setInterval( setInterval(
() => chrome.storage.local.set({ 'last-heartbeat': new Date().getTime() }), () =>
void chrome.storage.local.set({ 'last-heartbeat': new Date().getTime() }),
20000, 20000,
); );

View File

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

View File

@ -324,14 +324,8 @@ function toContentScript(messageBody: ToContentScriptMessage) {
connections.tab[id!].postMessage({ connections.tab[id!].postMessage({
type: message, type: message,
action, action,
state: nonReduxDispatch( state: nonReduxDispatch(store, message, instanceId, action, state),
store, id: instanceId.toString().replace(/^[^/]+\//, ''),
message,
instanceId,
action as AppDispatchAction,
state,
),
id: instanceId.toString().replace(/^[^\/]+\//, ''),
}); });
} else if (messageBody.message === 'IMPORT') { } else if (messageBody.message === 'IMPORT') {
const { message, action, id, instanceId, state } = messageBody; const { message, action, id, instanceId, state } = messageBody;
@ -345,7 +339,7 @@ function toContentScript(messageBody: ToContentScriptMessage) {
action as unknown as AppDispatchAction, action as unknown as AppDispatchAction,
state, state,
), ),
id: instanceId.toString().replace(/^[^\/]+\//, ''), id: instanceId.toString().replace(/^[^/]+\//, ''),
}); });
} else if (messageBody.message === 'ACTION') { } else if (messageBody.message === 'ACTION') {
const { message, action, id, instanceId, state } = messageBody; const { message, action, id, instanceId, state } = messageBody;
@ -359,7 +353,7 @@ function toContentScript(messageBody: ToContentScriptMessage) {
action as unknown as AppDispatchAction, action as unknown as AppDispatchAction,
state, state,
), ),
id: instanceId.toString().replace(/^[^\/]+\//, ''), id: instanceId.toString().replace(/^[^/]+\//, ''),
}); });
} else if (messageBody.message === 'EXPORT') { } else if (messageBody.message === 'EXPORT') {
const { message, action, id, instanceId, state } = messageBody; const { message, action, id, instanceId, state } = messageBody;
@ -373,11 +367,11 @@ function toContentScript(messageBody: ToContentScriptMessage) {
action as unknown as AppDispatchAction, action as unknown as AppDispatchAction,
state, state,
), ),
id: instanceId.toString().replace(/^[^\/]+\//, ''), id: instanceId.toString().replace(/^[^/]+\//, ''),
}); });
} else { } else {
const { message, action, id, instanceId, state } = messageBody; const { message, action, id, instanceId, state } = messageBody;
connections.tab[id!].postMessage({ connections.tab[id].postMessage({
type: message, type: message,
action, action,
state: nonReduxDispatch( state: nonReduxDispatch(
@ -387,7 +381,7 @@ function toContentScript(messageBody: ToContentScriptMessage) {
action as AppDispatchAction, action as AppDispatchAction,
state, state,
), ),
id: (instanceId as number).toString().replace(/^[^\/]+\//, ''), id: (instanceId as number).toString().replace(/^[^/]+\//, ''),
}); });
} }
} }
@ -452,7 +446,7 @@ function messaging<S, A extends Action<string>>(
return; return;
} }
if (request.type === 'OPEN_OPTIONS') { if (request.type === 'OPEN_OPTIONS') {
chrome.runtime.openOptionsPage(); void chrome.runtime.openOptionsPage();
return; return;
} }
if (request.type === 'OPTIONS') { if (request.type === 'OPTIONS') {
@ -557,8 +551,8 @@ function onConnect<S, A extends Action<string>>(port: chrome.runtime.Port) {
console.log(`Message from tab ${id}: ${msg.name}`); console.log(`Message from tab ${id}: ${msg.name}`);
if (msg.name === 'INIT_INSTANCE') { if (msg.name === 'INIT_INSTANCE') {
if (typeof id === 'number') { if (typeof id === 'number') {
chrome.action.enable(id); void chrome.action.enable(id);
chrome.action.setIcon({ tabId: id, path: 'img/logo/38x38.png' }); void chrome.action.setIcon({ tabId: id, path: 'img/logo/38x38.png' });
} }
if (monitors > 0) port.postMessage({ type: 'START' }); if (monitors > 0) port.postMessage({ type: 'START' });
@ -611,6 +605,7 @@ chrome.notifications.onClicked.addListener((id) => {
openDevToolsWindow('devtools-window'); openDevToolsWindow('devtools-window');
}); });
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
const api: Middleware<{}, BackgroundState, Dispatch<BackgroundAction>> = const api: Middleware<{}, BackgroundState, Dispatch<BackgroundAction>> =
(store) => (next) => (untypedAction) => { (store) => (next) => (untypedAction) => {
const action = untypedAction as BackgroundAction; const action = untypedAction as BackgroundAction;

View File

@ -9,28 +9,42 @@ if (
isFirefox isFirefox
) { ) {
(chrome.runtime as any).onConnectExternal = { (chrome.runtime as any).onConnectExternal = {
addListener() {}, addListener() {
// do nothing.
},
}; };
(chrome.runtime as any).onMessageExternal = { (chrome.runtime as any).onMessageExternal = {
addListener() {}, addListener() {
// do nothing.
},
}; };
if (isElectron) { if (isElectron) {
(chrome.notifications as any) = { (chrome.notifications as any) = {
onClicked: { onClicked: {
addListener() {}, addListener() {
// do nothing.
},
},
create() {
// do nothing.
},
clear() {
// do nothing.
}, },
create() {},
clear() {},
}; };
(chrome.contextMenus as any) = { (chrome.contextMenus as any) = {
onClicked: { onClicked: {
addListener() {}, addListener() {
// do nothing.
},
}, },
}; };
(chrome.commands as any) = { (chrome.commands as any) = {
onCommand: { onCommand: {
addListener() {}, addListener() {
// do nothing.
},
}, },
}; };
} else { } else {
@ -44,31 +58,36 @@ if (
if (isElectron) { if (isElectron) {
if (!chrome.storage.local || !chrome.storage.local.remove) { if (!chrome.storage.local || !chrome.storage.local.remove) {
(chrome.storage as any).local = { (chrome.storage as any).local = {
set(obj: any, callback: any) { set(items: { [key: string]: string }, callback: () => void) {
Object.keys(obj).forEach((key) => { for (const [key, value] of Object.entries(items)) {
localStorage.setItem(key, obj[key]); localStorage.setItem(key, value);
}); }
if (callback) { if (callback) {
callback(); callback();
} }
}, },
get(obj: any, callback: any) { get(
const result: any = {}; keys: { [key: string]: any },
Object.keys(obj).forEach((key) => { callback: (items: { [key: string]: any }) => void,
result[key] = localStorage.getItem(key) || obj[key]; ) {
}); const result = Object.fromEntries(
Object.entries(keys).map(([key, value]) => [
key,
localStorage.getItem(key) ?? value,
]),
);
if (callback) { if (callback) {
callback(result); callback(result);
} }
}, },
// Electron ~ 1.4.6 // Electron ~ 1.4.6
remove(items: any, callback: any) { remove(keys: string | string[], callback: () => void) {
if (Array.isArray(items)) { if (Array.isArray(keys)) {
items.forEach((name) => { for (const key of keys) {
localStorage.removeItem(name); localStorage.removeItem(key);
}); }
} else { } else {
localStorage.removeItem(items); localStorage.removeItem(keys);
} }
if (callback) { if (callback) {
callback(); callback();
@ -78,14 +97,14 @@ if (isElectron) {
} }
// Avoid error: chrome.runtime.sendMessage is not supported responseCallback // Avoid error: chrome.runtime.sendMessage is not supported responseCallback
const originSendMessage = (chrome.runtime as any).sendMessage; const originSendMessage = (chrome.runtime as any).sendMessage;
chrome.runtime.sendMessage = function () { (chrome.runtime as any).sendMessage = function (...args: unknown[]) {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
return originSendMessage(...arguments); return originSendMessage(...args);
} }
if (typeof arguments[arguments.length - 1] === 'function') { if (typeof args[arguments.length - 1] === 'function') {
Array.prototype.pop.call(arguments); Array.prototype.pop.call(args);
} }
return originSendMessage(...arguments); return originSendMessage(...args);
}; };
} }

View File

@ -254,7 +254,7 @@ function tryCatch<S, A extends Action<string>>(
} }
newArgs[key as keyof typeof newArgs] = arg; newArgs[key as keyof typeof newArgs] = arg;
}); });
fn(newArgs as any); fn(newArgs as SplitMessage);
for (let i = 0; i < toSplit.length; i++) { for (let i = 0; i < toSplit.length; i++) {
for (let j = 0; j < toSplit[i][1].length; j += maxChromeMsgSize) { for (let j = 0; j < toSplit[i][1].length; j += maxChromeMsgSize) {
fn({ fn({

View File

@ -36,7 +36,7 @@ let persistor: Persistor | undefined;
let bgConnection: chrome.runtime.Port; let bgConnection: chrome.runtime.Port;
let naTimeout: NodeJS.Timeout; let naTimeout: NodeJS.Timeout;
const isChrome = navigator.userAgent.indexOf('Firefox') === -1; const isChrome = !navigator.userAgent.includes('Firefox');
function renderNodeAtRoot(node: ReactNode) { function renderNodeAtRoot(node: ReactNode) {
if (currentRoot) currentRoot.unmount(); if (currentRoot) currentRoot.unmount();
@ -67,6 +67,7 @@ function renderNA() {
<a <a
href="https://github.com/zalmoxisus/redux-devtools-extension#usage" href="https://github.com/zalmoxisus/redux-devtools-extension#usage"
target="_blank" target="_blank"
rel="noreferrer"
> >
the instructions the instructions
</a> </a>
@ -87,6 +88,7 @@ function renderNA() {
<a <a
href="https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/Troubleshooting.md#access-file-url-file" href="https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/Troubleshooting.md#access-file-url-file"
target="_blank" target="_blank"
rel="noreferrer"
> >
See details See details
</a> </a>
@ -139,12 +141,18 @@ function init() {
} }
if (request.split === 'chunk') { if (request.split === 'chunk') {
if ((splitMessage as Record<string, unknown>)[request.chunk[0]]) { if (
(splitMessage as Record<string, unknown>)[request.chunk[0]] += (splitMessage as unknown as Record<string, string>)[
request.chunk[1]; request.chunk[0]
]
) {
(splitMessage as unknown as Record<string, string>)[
request.chunk[0]
] += request.chunk[1];
} else { } else {
(splitMessage as Record<string, unknown>)[request.chunk[0]] = (splitMessage as unknown as Record<string, string>)[
request.chunk[1]; request.chunk[0]
] = request.chunk[1];
} }
return; return;
} }

View File

@ -38,6 +38,7 @@ function getCurrentTabId(next: (tabId: number) => void) {
function panelDispatcher( function panelDispatcher(
bgConnection: chrome.runtime.Port, bgConnection: chrome.runtime.Port,
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
): Middleware<{}, StoreState, Dispatch<StoreAction>> { ): Middleware<{}, StoreState, Dispatch<StoreAction>> {
let autoselected = false; let autoselected = false;

View File

@ -2,5 +2,7 @@ chrome.devtools.panels.create(
'Redux', 'Redux',
'img/logo/scalable.png', 'img/logo/scalable.png',
'devpanel.html', 'devpanel.html',
() => {}, () => {
// do nothing.
},
); );

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { OptionsProps } from './Options'; import { OptionsProps } from './Options';
export default ({ options, saveOption }: OptionsProps) => { export default function AllowToRunGroup({ options, saveOption }: OptionsProps) {
const AllowToRunState = { const AllowToRunState = {
EVERYWHERE: true, EVERYWHERE: true,
ON_SPECIFIC_URLS: false, ON_SPECIFIC_URLS: false,
@ -50,4 +50,4 @@ export default ({ options, saveOption }: OptionsProps) => {
</div> </div>
</fieldset> </fieldset>
); );
}; }

View File

@ -1,7 +1,10 @@
import React from 'react'; import React from 'react';
import { OptionsProps } from './Options'; import { OptionsProps } from './Options';
export default ({ options, saveOption }: OptionsProps) => { export default function ContextMenuGroup({
options,
saveOption,
}: OptionsProps) {
return ( return (
<fieldset className="option-group"> <fieldset className="option-group">
<legend className="option-group__title">Context Menu</legend> <legend className="option-group__title">Context Menu</legend>
@ -23,4 +26,4 @@ export default ({ options, saveOption }: OptionsProps) => {
</div> </div>
</fieldset> </fieldset>
); );
}; }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { OptionsProps } from './Options'; import { OptionsProps } from './Options';
export default ({ options, saveOption }: OptionsProps) => { export default function EditorGroup({ options, saveOption }: OptionsProps) {
const EditorState = { const EditorState = {
BROWSER: 0, BROWSER: 0,
EXTERNAL: 1, EXTERNAL: 1,
@ -21,7 +21,7 @@ export default ({ options, saveOption }: OptionsProps) => {
onChange={() => saveOption('useEditor', EditorState.BROWSER)} onChange={() => saveOption('useEditor', EditorState.BROWSER)}
/> />
<label className="option__label" htmlFor="editor-browser"> <label className="option__label" htmlFor="editor-browser">
{navigator.userAgent.indexOf('Firefox') !== -1 {navigator.userAgent.includes('Firefox')
? "Don't open in external editor" ? "Don't open in external editor"
: "Use browser's debugger (from browser devpanel only)"} : "Use browser's debugger (from browser devpanel only)"}
</label> </label>
@ -80,4 +80,4 @@ export default ({ options, saveOption }: OptionsProps) => {
</div> </div>
</fieldset> </fieldset>
); );
}; }

View File

@ -2,7 +2,7 @@ import React from 'react';
import { FilterState } from '../pageScript/api/filters'; import { FilterState } from '../pageScript/api/filters';
import { OptionsProps } from './Options'; import { OptionsProps } from './Options';
export default ({ options, saveOption }: OptionsProps) => { export default function FilterGroup({ options, saveOption }: OptionsProps) {
return ( return (
<fieldset className="option-group"> <fieldset className="option-group">
<legend className="option-group__title"> <legend className="option-group__title">
@ -68,4 +68,4 @@ export default ({ options, saveOption }: OptionsProps) => {
</div> </div>
</fieldset> </fieldset>
); );
}; }

View File

@ -1,7 +1,10 @@
import React from 'react'; import React from 'react';
import { OptionsProps } from './Options'; import { OptionsProps } from './Options';
export default ({ options, saveOption }: OptionsProps) => { export default function MiscellaneousGroup({
options,
saveOption,
}: OptionsProps) {
return ( return (
<fieldset className="option-group"> <fieldset className="option-group">
<legend className="option-group__title">Miscellaneous</legend> <legend className="option-group__title">Miscellaneous</legend>
@ -47,4 +50,4 @@ export default ({ options, saveOption }: OptionsProps) => {
</div> </div>
</fieldset> </fieldset>
); );
}; }

View File

@ -14,34 +14,38 @@ export interface OptionsProps {
) => void; ) => void;
} }
export default (props: OptionsProps) => ( export default function OptionsComponent(props: OptionsProps) {
<div> return (
<EditorGroup {...props} /> <div>
<FilterGroup {...props} /> <EditorGroup {...props} />
<AllowToRunGroup {...props} /> <FilterGroup {...props} />
<MiscellaneousGroup {...props} /> <AllowToRunGroup {...props} />
<ContextMenuGroup {...props} /> <MiscellaneousGroup {...props} />
<div style={{ color: 'red' }}> <ContextMenuGroup {...props} />
<br /> <div style={{ color: 'red' }}>
<hr /> <br />
Setting options here is discouraged, and will not be possible in the next <hr />
major release. Please{' '} Setting options here is discouraged, and will not be possible in the
<a next major release. Please{' '}
href="https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md" <a
target="_blank" href="https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md"
style={{ color: 'red' }} target="_blank"
> rel="noreferrer"
specify them as parameters style={{ color: 'red' }}
</a> >
. See{' '} specify them as parameters
<a </a>
href="https://github.com/zalmoxisus/redux-devtools-extension/issues/296" . See{' '}
target="_blank" <a
style={{ color: 'red' }} href="https://github.com/zalmoxisus/redux-devtools-extension/issues/296"
> target="_blank"
the issue rel="noreferrer"
</a>{' '} style={{ color: 'red' }}
for more details. >
the issue
</a>{' '}
for more details.
</div>
</div> </div>
</div> );
); }

View File

@ -12,7 +12,7 @@ import {
subscribeToOptions((options) => { subscribeToOptions((options) => {
const message: OptionsMessage = { type: 'OPTIONS', options }; const message: OptionsMessage = { type: 'OPTIONS', options };
chrome.runtime.sendMessage(message); void chrome.runtime.sendMessage(message);
}); });
const renderOptions = (options: Options) => { const renderOptions = (options: Options) => {

View File

@ -46,9 +46,9 @@ export const saveOption = <K extends keyof Options>(
key: K, key: K,
value: Options[K], value: Options[K],
) => { ) => {
let obj: { [K1 in keyof Options]?: Options[K1] } = {}; const obj: { [K1 in keyof Options]?: Options[K1] } = {};
obj[key] = value; obj[key] = value;
chrome.storage.sync.set(obj); void chrome.storage.sync.set(obj);
options![key] = value; options![key] = value;
for (const subscriber of subscribers) { for (const subscriber of subscribers) {
subscriber(options!); subscriber(options!);
@ -99,7 +99,10 @@ export const getOptions = (callback: (options: Options) => void) => {
} }
}; };
export const prefetchOptions = () => getOptions(() => {}); export const prefetchOptions = () =>
getOptions(() => {
// do nothing.
});
export const subscribeToOptions = (callback: (options: Options) => void) => { export const subscribeToOptions = (callback: (options: Options) => void) => {
subscribers = subscribers.concat(callback); subscribers = subscribers.concat(callback);

View File

@ -26,8 +26,7 @@ export function isFiltered<A extends Action<string>>(
) { ) {
if ( if (
noFiltersApplied(localFilter) || noFiltersApplied(localFilter) ||
(typeof action !== 'string' && (typeof action !== 'string' && typeof action.type.match !== 'function')
typeof (action.type as string).match !== 'function')
) { ) {
return false; return false;
} }

View File

@ -58,7 +58,7 @@ export default function importState<S, A extends Action<string>>(
| LiftedState<S, A, unknown> = parse(state) as | LiftedState<S, A, unknown> = parse(state) as
| ParsedSerializedLiftedState | ParsedSerializedLiftedState
| LiftedState<S, A, unknown>; | LiftedState<S, A, unknown>;
let preloadedState = const preloadedState =
'payload' in parsedSerializedLiftedState && 'payload' in parsedSerializedLiftedState &&
parsedSerializedLiftedState.preloadedState parsedSerializedLiftedState.preloadedState
? (parse(parsedSerializedLiftedState.preloadedState) as S) ? (parse(parsedSerializedLiftedState.preloadedState) as S)

View File

@ -222,6 +222,7 @@ function post<S, A extends Action<string>>(
function getStackTrace( function getStackTrace(
config: Config, config: Config,
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
toExcludeFromTrace: Function | undefined, toExcludeFromTrace: Function | undefined,
) { ) {
if (!config.trace) return undefined; if (!config.trace) return undefined;
@ -248,6 +249,7 @@ function getStackTrace(
typeof Error.stackTraceLimit !== 'number' || typeof Error.stackTraceLimit !== 'number' ||
Error.stackTraceLimit > traceLimit! Error.stackTraceLimit > traceLimit!
) { ) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const frames = stack!.split('\n'); const frames = stack!.split('\n');
if (frames.length > traceLimit!) { if (frames.length > traceLimit!) {
stack = frames stack = frames
@ -265,10 +267,11 @@ function amendActionType<A extends Action<string>>(
| StructuralPerformAction<A>[] | StructuralPerformAction<A>[]
| string, | string,
config: Config, config: Config,
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
toExcludeFromTrace: Function | undefined, toExcludeFromTrace: Function | undefined,
): StructuralPerformAction<A> { ): StructuralPerformAction<A> {
let timestamp = Date.now(); const timestamp = Date.now();
let stack = getStackTrace(config, toExcludeFromTrace); const stack = getStackTrace(config, toExcludeFromTrace);
if (typeof action === 'string') { if (typeof action === 'string') {
return { action: { type: action } as A, timestamp, stack }; return { action: { type: action } as A, timestamp, stack };
} }
@ -595,7 +598,11 @@ export function connect(preConfig: Config): ConnectResponse {
}; };
const sendDelayed = throttle(() => { const sendDelayed = throttle(() => {
sendMessage(delayedActions, delayedStates as any, config); sendMessage(
delayedActions,
delayedStates as unknown as LiftedState<unknown, Action<string>, unknown>,
config,
);
delayedActions = []; delayedActions = [];
delayedStates = []; delayedStates = [];
}, latency); }, latency);

View File

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

View File

@ -4,8 +4,8 @@ import { persistState } from '@redux-devtools/core';
import type { ConfigWithExpandedMaxAge } from './index'; import type { ConfigWithExpandedMaxAge } from './index';
export function getUrlParam(key: string) { export function getUrlParam(key: string) {
const matches = window.location.href.match( const matches = new RegExp(`[?&]${key}=([^&#]+)\\b`).exec(
new RegExp(`[?&]${key}=([^&#]+)\\b`), window.location.href,
); );
return matches && matches.length > 0 ? matches[1] : null; return matches && matches.length > 0 ? matches[1] : null;
} }

View File

@ -53,7 +53,7 @@ type EnhancedStoreWithInitialDispatch<
> = EnhancedStore<S, A, MonitorState> & { initialDispatch: Dispatch<A> }; > = EnhancedStore<S, A, MonitorState> & { initialDispatch: Dispatch<A> };
const source = '@devtools-page'; const source = '@devtools-page';
let stores: { const stores: {
[K in string | number]: EnhancedStoreWithInitialDispatch< [K in string | number]: EnhancedStoreWithInitialDispatch<
unknown, unknown,
Action<string>, Action<string>,
@ -167,7 +167,7 @@ function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<string>>(
const localFilter = getLocalFilter(config); const localFilter = getLocalFilter(config);
const serializeState = getSerializeParameter(config); const serializeState = getSerializeParameter(config);
const serializeAction = getSerializeParameter(config); const serializeAction = getSerializeParameter(config);
let { stateSanitizer, actionSanitizer, predicate, latency = 500 } = config; const { stateSanitizer, actionSanitizer, predicate, latency = 500 } = config;
// Deprecate actionsWhitelist and actionsBlacklist // Deprecate actionsWhitelist and actionsBlacklist
if (config.actionsWhitelist) { if (config.actionsWhitelist) {
@ -447,7 +447,7 @@ function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<string>>(
liftedAction?: LiftedAction<S, A, unknown>, liftedAction?: LiftedAction<S, A, unknown>,
liftedState?: LiftedState<S, A, unknown> | undefined, liftedState?: LiftedState<S, A, unknown> | undefined,
) => { ) => {
let m = (config && config.maxAge) || window.devToolsOptions.maxAge || 50; const m = (config && config.maxAge) || window.devToolsOptions.maxAge || 50;
if ( if (
!liftedAction || !liftedAction ||
noFiltersApplied(localFilter) || noFiltersApplied(localFilter) ||
@ -464,10 +464,7 @@ function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<string>>(
if (filteredActionIds.length >= m) { if (filteredActionIds.length >= m) {
const stagedActionIds = liftedState!.stagedActionIds; const stagedActionIds = liftedState!.stagedActionIds;
let i = 1; let i = 1;
while ( while (maxAge > m && !filteredActionIds.includes(stagedActionIds[i])) {
maxAge > m &&
filteredActionIds.indexOf(stagedActionIds[i]) === -1
) {
maxAge--; maxAge--;
i++; i++;
} }
@ -539,7 +536,7 @@ function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<string>>(
...config, ...config,
maxAge: getMaxAge as any, maxAge: getMaxAge as any,
}) as any }) as any
)(reducer_, initialState_) as any; )(reducer_, initialState_);
if (isInIframe()) setTimeout(init, 3000); if (isInIframe()) setTimeout(init, 3000);
else init(); else init();
@ -591,18 +588,19 @@ export type InferComposedStoreExt<StoreEnhancers> = StoreEnhancers extends [
? HeadStoreEnhancer extends StoreEnhancer<infer StoreExt> ? HeadStoreEnhancer extends StoreEnhancer<infer StoreExt>
? StoreExt & InferComposedStoreExt<RestStoreEnhancers> ? StoreExt & InferComposedStoreExt<RestStoreEnhancers>
: never : never
: {}; : // eslint-disable-next-line @typescript-eslint/no-empty-object-type
{};
const extensionCompose = const extensionCompose =
(config: Config) => (config: Config) =>
<StoreEnhancers extends readonly StoreEnhancer[]>( <StoreEnhancers extends readonly StoreEnhancer[]>(
...funcs: StoreEnhancers ...funcs: StoreEnhancers
): StoreEnhancer<InferComposedStoreExt<StoreEnhancers>> => { ): StoreEnhancer<InferComposedStoreExt<StoreEnhancers>> => {
// @ts-ignore FIXME // @ts-expect-error FIXME
return (...args) => { return (...args) => {
const instanceId = generateId(config.instanceId); const instanceId = generateId(config.instanceId);
return [preEnhancer(instanceId), ...funcs].reduceRight( return [preEnhancer(instanceId), ...funcs].reduceRight(
// @ts-ignore FIXME // @ts-expect-error FIXME
(composed, f) => f(composed), (composed, f) => f(composed),
__REDUX_DEVTOOLS_EXTENSION__({ ...config, instanceId })(...args), __REDUX_DEVTOOLS_EXTENSION__({ ...config, instanceId })(...args),
); );

View File

@ -0,0 +1 @@
module.exports = {};

View File

@ -1 +0,0 @@
export default {};

View File

@ -71,6 +71,7 @@ describe('Chrome extension', function () {
}); });
Object.keys(switchMonitorTests).forEach((description) => Object.keys(switchMonitorTests).forEach((description) =>
// eslint-disable-next-line jest/expect-expect,jest/valid-title
it(description, () => switchMonitorTests[description](driver)), it(description, () => switchMonitorTests[description](driver)),
); );
}); });

View File

@ -76,6 +76,7 @@ describe('DevTools panel for Electron', function () {
expect(className).not.toMatch(/hidden/); // not hidden expect(className).not.toMatch(/hidden/); // not hidden
}); });
// eslint-disable-next-line jest/expect-expect
it('should have Redux DevTools UI on current tab', async () => { it('should have Redux DevTools UI on current tab', async () => {
await driver await driver
.switchTo() .switchTo()
@ -107,9 +108,11 @@ describe('DevTools panel for Electron', function () {
}); });
Object.keys(switchMonitorTests).forEach((description) => Object.keys(switchMonitorTests).forEach((description) =>
// eslint-disable-next-line jest/expect-expect,jest/valid-title
it(description, () => switchMonitorTests[description](driver)), it(description, () => switchMonitorTests[description](driver)),
); );
// eslint-disable-next-line jest/no-commented-out-tests
/* it('should be no logs in console of main window', async () => { /* it('should be no logs in console of main window', async () => {
const handles = await driver.getAllWindowHandles(); const handles = await driver.getAllWindowHandles();
await driver.switchTo().window(handles[1]); // Change to main window await driver.switchTo().window(handles[1]); // Change to main window

View File

@ -31,7 +31,7 @@ importers:
version: 9.1.0(eslint@8.57.1) version: 9.1.0(eslint@8.57.1)
eslint-plugin-jest: eslint-plugin-jest:
specifier: ^28.8.3 specifier: ^28.8.3
version: 28.8.3(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(jest@29.7.0(@types/node@22.5.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.5.5)(typescript@5.5.4)))(typescript@5.5.4) version: 28.8.3(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(jest@29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.5)(typescript@5.5.4)))(typescript@5.5.4)
eslint-plugin-react: eslint-plugin-react:
specifier: ^7.36.1 specifier: ^7.36.1
version: 7.36.1(eslint@8.57.1) version: 7.36.1(eslint@8.57.1)
@ -40,7 +40,7 @@ importers:
version: 4.6.2(eslint@8.57.1) version: 4.6.2(eslint@8.57.1)
jest: jest:
specifier: ^29.7.0 specifier: ^29.7.0
version: 29.7.0(@types/node@22.5.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.5.5)(typescript@5.5.4)) version: 29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.5)(typescript@5.5.4))
nx: nx:
specifier: ^19.7.3 specifier: ^19.7.3
version: 19.7.3(@swc/core@1.7.26(@swc/helpers@0.5.13)) version: 19.7.3(@swc/core@1.7.26(@swc/helpers@0.5.13))
@ -192,12 +192,15 @@ importers:
eslint-plugin-react-hooks: eslint-plugin-react-hooks:
specifier: ^4.6.2 specifier: ^4.6.2
version: 4.6.2(eslint@8.57.1) version: 4.6.2(eslint@8.57.1)
globals:
specifier: ^15.9.0
version: 15.9.0
immutable: immutable:
specifier: ^4.3.7 specifier: ^4.3.7
version: 4.3.7 version: 4.3.7
jest: jest:
specifier: ^29.7.0 specifier: ^29.7.0
version: 29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.5)(typescript@5.5.4)) version: 29.7.0(@types/node@22.5.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.5.5)(typescript@5.5.4))
jest-environment-jsdom: jest-environment-jsdom:
specifier: ^29.7.0 specifier: ^29.7.0
version: 29.7.0 version: 29.7.0
@ -215,7 +218,7 @@ importers:
version: 3.0.1 version: 3.0.1
ts-jest: ts-jest:
specifier: ^29.2.5 specifier: ^29.2.5
version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.5)(typescript@5.5.4)))(typescript@5.5.4) version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.5.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.5.5)(typescript@5.5.4)))(typescript@5.5.4)
typescript: typescript:
specifier: ~5.5.4 specifier: ~5.5.4
version: 5.5.4 version: 5.5.4
@ -17007,13 +17010,13 @@ snapshots:
- eslint-import-resolver-webpack - eslint-import-resolver-webpack
- supports-color - supports-color
eslint-plugin-jest@28.8.3(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(jest@29.7.0(@types/node@22.5.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.5.5)(typescript@5.5.4)))(typescript@5.5.4): eslint-plugin-jest@28.8.3(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(jest@29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.5)(typescript@5.5.4)))(typescript@5.5.4):
dependencies: dependencies:
'@typescript-eslint/utils': 8.6.0(eslint@8.57.1)(typescript@5.5.4) '@typescript-eslint/utils': 8.6.0(eslint@8.57.1)(typescript@5.5.4)
eslint: 8.57.1 eslint: 8.57.1
optionalDependencies: optionalDependencies:
'@typescript-eslint/eslint-plugin': 8.6.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(typescript@5.5.4) '@typescript-eslint/eslint-plugin': 8.6.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(typescript@5.5.4)
jest: 29.7.0(@types/node@22.5.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.5.5)(typescript@5.5.4)) jest: 29.7.0(@types/node@20.16.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@20.16.5)(typescript@5.5.4))
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript