From 696a669bd00a3b761377df091a3e2ebf8754107d Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 19 Jun 2021 23:57:38 -0400 Subject: [PATCH 01/25] Start work --- extension/.babelrc | 6 +++- .../app/reducers/panel/{index.js => index.ts} | 0 .../stores/{panelStore.js => panelStore.ts} | 6 +++- .../{contextMenus.js => contextMenus.ts} | 4 +-- .../{openWindow.js => openWindow.ts} | 36 +++++++++++++------ .../devpanel/{index.js => index.tsx} | 22 +++++++----- extension/tsconfig.json | 4 +++ packages/redux-devtools-app/tsconfig.json | 2 +- .../package.json | 2 +- yarn.lock | 10 +++--- 10 files changed, 61 insertions(+), 31 deletions(-) rename extension/src/app/reducers/panel/{index.js => index.ts} (100%) rename extension/src/app/stores/{panelStore.js => panelStore.ts} (76%) rename extension/src/browser/extension/background/{contextMenus.js => contextMenus.ts} (89%) rename extension/src/browser/extension/background/{openWindow.js => openWindow.ts} (62%) rename extension/src/browser/extension/devpanel/{index.js => index.tsx} (87%) create mode 100644 extension/tsconfig.json diff --git a/extension/.babelrc b/extension/.babelrc index d63fe0cea1..bbb465e2e7 100644 --- a/extension/.babelrc +++ b/extension/.babelrc @@ -1,5 +1,9 @@ { - "presets": ["@babel/preset-env", "@babel/preset-react"], + "presets": [ + "@babel/preset-env", + "@babel/preset-react", + "@babel/preset-typescript" + ], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }] diff --git a/extension/src/app/reducers/panel/index.js b/extension/src/app/reducers/panel/index.ts similarity index 100% rename from extension/src/app/reducers/panel/index.js rename to extension/src/app/reducers/panel/index.ts diff --git a/extension/src/app/stores/panelStore.js b/extension/src/app/stores/panelStore.ts similarity index 76% rename from extension/src/app/stores/panelStore.js rename to extension/src/app/stores/panelStore.ts index 2255bc2218..e15266e922 100644 --- a/extension/src/app/stores/panelStore.js +++ b/extension/src/app/stores/panelStore.ts @@ -3,7 +3,11 @@ import exportState from '@redux-devtools/app/lib/middlewares/exportState'; import panelDispatcher from '../middlewares/panelSync'; import rootReducer from '../reducers/panel'; -export default function configureStore(position, bgConnection, preloadedState) { +export default function configureStore( + position: string, + bgConnection: chrome.runtime.Port, + preloadedState +) { const enhancer = applyMiddleware(exportState, panelDispatcher(bgConnection)); return createStore(rootReducer, preloadedState, enhancer); } diff --git a/extension/src/browser/extension/background/contextMenus.js b/extension/src/browser/extension/background/contextMenus.ts similarity index 89% rename from extension/src/browser/extension/background/contextMenus.js rename to extension/src/browser/extension/background/contextMenus.ts index a439e4fd9d..070a1a3b1f 100644 --- a/extension/src/browser/extension/background/contextMenus.js +++ b/extension/src/browser/extension/background/contextMenus.ts @@ -12,10 +12,10 @@ export function createMenu() { { id: 'devtools-remote', title: 'Open Remote DevTools' }, ]; - let shortcuts = {}; + let shortcuts: { [commandName: string]: string | undefined } = {}; chrome.commands.getAll((commands) => { commands.forEach(({ name, shortcut }) => { - shortcuts[name] = shortcut; + shortcuts[name!] = shortcut; }); menus.forEach(({ id, title }) => { diff --git a/extension/src/browser/extension/background/openWindow.js b/extension/src/browser/extension/background/openWindow.ts similarity index 62% rename from extension/src/browser/extension/background/openWindow.js rename to extension/src/browser/extension/background/openWindow.ts index 4b81564f18..0e60a8729c 100644 --- a/extension/src/browser/extension/background/openWindow.js +++ b/extension/src/browser/extension/background/openWindow.ts @@ -1,9 +1,20 @@ -let windows = {}; -let lastPosition = null; +export type DevToolsPosition = + | 'devtools-left' + | 'devtools-right' + | 'devtools-bottom' + | 'devtools-panel' + | 'devtools-remote'; -export default function openDevToolsWindow(position) { - function popWindow(action, url, customOptions) { - function focusIfExist(callback) { +let windows: { [K in DevToolsPosition]?: number } = {}; +let lastPosition: DevToolsPosition | null = null; + +export default function openDevToolsWindow(position: DevToolsPosition) { + function popWindow( + action: string, + url: string, + customOptions: chrome.windows.CreateData & chrome.windows.UpdateInfo + ) { + function focusIfExist(callback: () => void) { if (!windows[position]) { callback(); lastPosition = position; @@ -12,7 +23,7 @@ export default function openDevToolsWindow(position) { if (lastPosition !== position && position !== 'devtools-panel') { params = { ...params, ...customOptions }; } - chrome.windows.update(windows[position], params, () => { + chrome.windows.update(windows[position]!, params, () => { lastPosition = null; if (chrome.runtime.lastError) callback(); }); @@ -20,7 +31,7 @@ export default function openDevToolsWindow(position) { } focusIfExist(() => { - let options = { + let options: chrome.windows.CreateData = { type: 'popup', ...customOptions, }; @@ -29,16 +40,19 @@ export default function openDevToolsWindow(position) { url + '#' + position.substr(position.indexOf('-') + 1) ); chrome.windows.create(options, (win) => { - windows[position] = win.id; + windows[position] = win!.id; if (navigator.userAgent.indexOf('Firefox') !== -1) { - chrome.windows.update(win.id, { focused: true, ...customOptions }); + chrome.windows.update(win!.id!, { + focused: true, + ...customOptions, + }); } }); } }); } - let params = { + let params: chrome.windows.CreateData & chrome.windows.UpdateInfo = { left: 0, top: 0, width: 380, @@ -48,7 +62,7 @@ export default function openDevToolsWindow(position) { switch (position) { case 'devtools-right': params.left = - window.screen.availLeft + window.screen.availWidth - params.width; + window.screen.availLeft + window.screen.availWidth - params.width!; break; case 'devtools-bottom': params.height = 420; diff --git a/extension/src/browser/extension/devpanel/index.js b/extension/src/browser/extension/devpanel/index.tsx similarity index 87% rename from extension/src/browser/extension/devpanel/index.js rename to extension/src/browser/extension/devpanel/index.tsx index b9957d1c83..f5422c4acf 100644 --- a/extension/src/browser/extension/devpanel/index.js +++ b/extension/src/browser/extension/devpanel/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { CSSProperties } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Provider } from 'react-redux'; import { REMOVE_INSTANCE } from '@redux-devtools/app/lib/constants/actionTypes'; @@ -9,12 +9,16 @@ import getPreloadedState from '../background/getPreloadedState'; import '../../views/devpanel.pug'; const position = location.hash; -const messageStyle = { padding: '20px', width: '100%', textAlign: 'center' }; +const messageStyle: CSSProperties = { + padding: '20px', + width: '100%', + textAlign: 'center', +}; -let rendered; +let rendered: boolean; let store; -let bgConnection; -let naTimeout; +let bgConnection: chrome.runtime.Port; +let naTimeout: NodeJS.Timeout; let preloadedState; const isChrome = navigator.userAgent.indexOf('Firefox') === -1; @@ -25,7 +29,7 @@ getPreloadedState(position, (state) => { function renderDevTools() { const node = document.getElementById('root'); - unmountComponentAtNode(node); + unmountComponentAtNode(node!); clearTimeout(naTimeout); store = configureStore(position, bgConnection, preloadedState); render( @@ -71,20 +75,20 @@ function renderNA() { } const node = document.getElementById('root'); - unmountComponentAtNode(node); + unmountComponentAtNode(node!); render(message, node); store = undefined; }); } else { const node = document.getElementById('root'); - unmountComponentAtNode(node); + unmountComponentAtNode(node!); render(message, node); store = undefined; } }, 3500); } -function init(id) { +function init(id: number) { renderNA(); bgConnection = chrome.runtime.connect({ name: id ? id.toString() : undefined, diff --git a/extension/tsconfig.json b/extension/tsconfig.json new file mode 100644 index 0000000000..90a1290b4b --- /dev/null +++ b/extension/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.react.base.json", + "include": ["src"] +} diff --git a/packages/redux-devtools-app/tsconfig.json b/packages/redux-devtools-app/tsconfig.json index b742078e18..7b7d1492e2 100644 --- a/packages/redux-devtools-app/tsconfig.json +++ b/packages/redux-devtools-app/tsconfig.json @@ -3,5 +3,5 @@ "compilerOptions": { "outDir": "lib" }, - "include": ["demo", "src"] + "include": ["src"] } diff --git a/packages/redux-devtools-inspector-monitor-trace-tab/package.json b/packages/redux-devtools-inspector-monitor-trace-tab/package.json index 9a1efda3f6..299d2ec298 100644 --- a/packages/redux-devtools-inspector-monitor-trace-tab/package.json +++ b/packages/redux-devtools-inspector-monitor-trace-tab/package.json @@ -29,7 +29,7 @@ }, "dependencies": { "@babel/code-frame": "^7.14.5", - "@types/chrome": "^0.0.124", + "@types/chrome": "^0.0.145", "anser": "^1.4.10", "html-entities": "^1.4.0", "redux-devtools-themes": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 90a9dbce8a..d0eef9f9a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3567,7 +3567,7 @@ __metadata: "@redux-devtools/core": ^3.9.0 "@redux-devtools/inspector-monitor": ^1.0.0 "@types/babel__code-frame": ^7.0.2 - "@types/chrome": ^0.0.124 + "@types/chrome": ^0.0.145 "@types/enzyme": ^3.10.8 "@types/enzyme-adapter-react-16": ^1.0.6 "@types/html-entities": ^1.3.4 @@ -4825,13 +4825,13 @@ __metadata: languageName: node linkType: hard -"@types/chrome@npm:^0.0.124": - version: 0.0.124 - resolution: "@types/chrome@npm:0.0.124" +"@types/chrome@npm:^0.0.145": + version: 0.0.145 + resolution: "@types/chrome@npm:0.0.145" dependencies: "@types/filesystem": "*" "@types/har-format": "*" - checksum: 6499edca5f608dd48651b20d57d9fb30bbcb02cd695cd94d879a11dba7b9492c618edc6b8b5f718e82b58eceea94fb920c871546c2c3bc867595cb7dd020d527 + checksum: f826d0a071ac7ea68aa97b2f8e34a944c470fbd036fdd6a413987fea03062d354fb14708cc0b59693b4a78ec88f9c5b1fce0c622a8e29e3e4b0917509eaf2a1a languageName: node linkType: hard From 084f9b94296ceb6c37cf3c47639cdc15ef50244e Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 20 Jun 2021 16:34:04 -0400 Subject: [PATCH 02/25] More work --- .../src/app/api/{filters.js => filters.ts} | 7 +- .../src/app/containers/{App.js => App.tsx} | 16 +- ...nstanceSelector.js => instanceSelector.ts} | 17 ++- extension/src/app/middlewares/panelSync.js | 31 ---- extension/src/app/middlewares/panelSync.ts | 36 +++++ .../{windowSync.js => windowSync.ts} | 0 .../src/app/reducers/background/index.js | 10 -- .../src/app/reducers/background/index.ts | 17 +++ .../{persistStates.js => persistStates.ts} | 0 extension/src/app/reducers/panel/index.ts | 8 +- .../reducers/window/{index.js => index.ts} | 6 +- .../window/{instances.js => instances.ts} | 3 +- ...{backgroundStore.js => backgroundStore.ts} | 8 +- extension/src/app/stores/panelStore.ts | 5 +- .../stores/{windowStore.js => windowStore.ts} | 17 ++- ...PreloadedState.js => getPreloadedState.ts} | 8 +- .../background/{index.js => index.ts} | 0 .../src/browser/extension/devpanel/index.tsx | 11 +- .../extension/devtools/{index.js => index.ts} | 2 +- .../{deprecatedWarn.js => deprecatedWarn.ts} | 0 .../extension/inject/{index.js => index.ts} | 0 .../{pageScriptWrap.js => pageScriptWrap.ts} | 2 +- ...AllowToRunGroup.js => AllowToRunGroup.tsx} | 3 +- ...ntextMenuGroup.js => ContextMenuGroup.tsx} | 3 +- .../{EditorGroup.js => EditorGroup.tsx} | 5 +- .../{FilterGroup.js => FilterGroup.tsx} | 3 +- ...laneousGroup.js => MiscellaneousGroup.tsx} | 3 +- .../options/{Options.js => Options.tsx} | 11 +- .../extension/options/{index.js => index.tsx} | 0 .../browser/extension/options/syncOptions.js | 108 ------------- .../browser/extension/options/syncOptions.ts | 142 ++++++++++++++++++ .../extension/window/{index.js => index.tsx} | 4 +- .../window/{remote.js => remote.tsx} | 1 - 33 files changed, 302 insertions(+), 185 deletions(-) rename extension/src/app/api/{filters.js => filters.ts} (96%) rename extension/src/app/containers/{App.js => App.tsx} (81%) rename extension/src/app/middlewares/{instanceSelector.js => instanceSelector.ts} (63%) delete mode 100644 extension/src/app/middlewares/panelSync.js create mode 100644 extension/src/app/middlewares/panelSync.ts rename extension/src/app/middlewares/{windowSync.js => windowSync.ts} (100%) delete mode 100644 extension/src/app/reducers/background/index.js create mode 100644 extension/src/app/reducers/background/index.ts rename extension/src/app/reducers/background/{persistStates.js => persistStates.ts} (100%) rename extension/src/app/reducers/window/{index.js => index.ts} (68%) rename extension/src/app/reducers/window/{instances.js => instances.ts} (80%) rename extension/src/app/stores/{backgroundStore.js => backgroundStore.ts} (62%) rename extension/src/app/stores/{windowStore.js => windowStore.ts} (75%) rename extension/src/browser/extension/background/{getPreloadedState.js => getPreloadedState.ts} (78%) rename extension/src/browser/extension/background/{index.js => index.ts} (100%) rename extension/src/browser/extension/devtools/{index.js => index.ts} (92%) rename extension/src/browser/extension/inject/{deprecatedWarn.js => deprecatedWarn.ts} (100%) rename extension/src/browser/extension/inject/{index.js => index.ts} (100%) rename extension/src/browser/extension/inject/{pageScriptWrap.js => pageScriptWrap.ts} (93%) rename extension/src/browser/extension/options/{AllowToRunGroup.js => AllowToRunGroup.tsx} (93%) rename extension/src/browser/extension/options/{ContextMenuGroup.js => ContextMenuGroup.tsx} (87%) rename extension/src/browser/extension/options/{EditorGroup.js => EditorGroup.tsx} (95%) rename extension/src/browser/extension/options/{FilterGroup.js => FilterGroup.tsx} (95%) rename extension/src/browser/extension/options/{MiscellaneousGroup.js => MiscellaneousGroup.tsx} (94%) rename extension/src/browser/extension/options/{Options.js => Options.tsx} (81%) rename extension/src/browser/extension/options/{index.js => index.tsx} (100%) delete mode 100644 extension/src/browser/extension/options/syncOptions.js create mode 100644 extension/src/browser/extension/options/syncOptions.ts rename extension/src/browser/extension/window/{index.js => index.tsx} (88%) rename extension/src/browser/extension/window/{remote.js => remote.tsx} (94%) diff --git a/extension/src/app/api/filters.js b/extension/src/app/api/filters.ts similarity index 96% rename from extension/src/app/api/filters.js rename to extension/src/app/api/filters.ts index 609dcac495..e072437d70 100644 --- a/extension/src/app/api/filters.js +++ b/extension/src/app/api/filters.ts @@ -1,6 +1,11 @@ import mapValues from 'lodash/mapValues'; -export const FilterState = { +export type FilterStateValue = + | 'DO_NOT_FILTER' + | 'BLACKLIST_SPECIFIC' + | 'WHITELIST_SPECIFIC'; + +export const FilterState: { [K in FilterStateValue]: FilterStateValue } = { DO_NOT_FILTER: 'DO_NOT_FILTER', BLACKLIST_SPECIFIC: 'BLACKLIST_SPECIFIC', WHITELIST_SPECIFIC: 'WHITELIST_SPECIFIC', diff --git a/extension/src/app/containers/App.js b/extension/src/app/containers/App.tsx similarity index 81% rename from extension/src/app/containers/App.js rename to extension/src/app/containers/App.tsx index df23bcee9a..c030f400f2 100644 --- a/extension/src/app/containers/App.js +++ b/extension/src/app/containers/App.tsx @@ -1,14 +1,22 @@ import React, { Component } from 'react'; -import { connect } from 'react-redux'; +import { connect, ResolveThunks } from 'react-redux'; import { Container, Notification } from 'devui'; import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances'; import Settings from '@redux-devtools/app/lib/components/Settings'; import Actions from '@redux-devtools/app/lib/containers/Actions'; 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'; -class App extends Component { - openWindow = (position) => { +type StateProps = ReturnType; +type DispatchProps = ResolveThunks; +interface OwnProps { + readonly position: string; +} +type Props = StateProps & DispatchProps & OwnProps; + +class App extends Component { + openWindow = (position: string) => { chrome.runtime.sendMessage({ type: 'OPEN', position }); }; openOptionsPage = () => { @@ -62,7 +70,7 @@ class App extends Component { } } -function mapStateToProps(state) { +function mapStateToProps(state: StoreState) { const instances = state.instances; const id = getActiveInstance(instances); return { diff --git a/extension/src/app/middlewares/instanceSelector.js b/extension/src/app/middlewares/instanceSelector.ts similarity index 63% rename from extension/src/app/middlewares/instanceSelector.js rename to extension/src/app/middlewares/instanceSelector.ts index 72c6d03dd2..cc89f97ca3 100644 --- a/extension/src/app/middlewares/instanceSelector.js +++ b/extension/src/app/middlewares/instanceSelector.ts @@ -1,9 +1,16 @@ +import { Dispatch, Store } 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, store, next) { +function selectInstance( + tabId: number, + store: Store, + next: Dispatch +) { const instances = store.getState().instances; if (instances.current === 'default') return; const connections = instances.connections[tabId]; @@ -12,7 +19,7 @@ function selectInstance(tabId, store, next) { } } -function getCurrentTabId(next) { +function getCurrentTabId(next: (tabId: number) => void) { chrome.tabs.query( { active: true, @@ -21,13 +28,13 @@ function getCurrentTabId(next) { (tabs) => { const tab = tabs[0]; if (!tab) return; - next(tab.id); + next(tab.id!); } ); } -export default function popupSelector(store) { - return (next) => (action) => { +export default function popupSelector(store: Store) { + return (next: Dispatch) => (action: StoreAction) => { const result = next(action); if (action.type === UPDATE_STATE) { if (chrome.devtools && chrome.devtools.inspectedWindow) { diff --git a/extension/src/app/middlewares/panelSync.js b/extension/src/app/middlewares/panelSync.js deleted file mode 100644 index f88f421dce..0000000000 --- a/extension/src/app/middlewares/panelSync.js +++ /dev/null @@ -1,31 +0,0 @@ -import { - LIFTED_ACTION, - UPDATE_STATE, - SELECT_INSTANCE, -} from '@redux-devtools/app/lib/constants/actionTypes'; -import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances'; - -function panelDispatcher(bgConnection) { - let autoselected = false; - const tabId = chrome.devtools.inspectedWindow.tabId; - - return (store) => (next) => (action) => { - 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; diff --git a/extension/src/app/middlewares/panelSync.ts b/extension/src/app/middlewares/panelSync.ts new file mode 100644 index 0000000000..aae303ad9a --- /dev/null +++ b/extension/src/app/middlewares/panelSync.ts @@ -0,0 +1,36 @@ +import { + LIFTED_ACTION, + UPDATE_STATE, + SELECT_INSTANCE, +} from '@redux-devtools/app/lib/constants/actionTypes'; +import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances'; +import { Dispatch, MiddlewareAPI, Store } 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, StoreState>) => + (next: Dispatch) => + (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; diff --git a/extension/src/app/middlewares/windowSync.js b/extension/src/app/middlewares/windowSync.ts similarity index 100% rename from extension/src/app/middlewares/windowSync.js rename to extension/src/app/middlewares/windowSync.ts diff --git a/extension/src/app/reducers/background/index.js b/extension/src/app/reducers/background/index.js deleted file mode 100644 index 81bd41c6bf..0000000000 --- a/extension/src/app/reducers/background/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import { combineReducers } from 'redux'; -import instances from '@redux-devtools/app/lib/reducers/instances'; -import persistStates from './persistStates'; - -const rootReducer = combineReducers({ - instances, - persistStates, -}); - -export default rootReducer; diff --git a/extension/src/app/reducers/background/index.ts b/extension/src/app/reducers/background/index.ts new file mode 100644 index 0000000000..12124418e4 --- /dev/null +++ b/extension/src/app/reducers/background/index.ts @@ -0,0 +1,17 @@ +import { combineReducers } from 'redux'; +import instances, { + InstancesState, +} from '@redux-devtools/app/lib/reducers/instances'; +import persistStates from './persistStates'; + +export interface BackgroundState { + readonly instances: InstancesState; + readonly persistStates: boolean; +} + +const rootReducer = combineReducers({ + instances, + persistStates, +}); + +export default rootReducer; diff --git a/extension/src/app/reducers/background/persistStates.js b/extension/src/app/reducers/background/persistStates.ts similarity index 100% rename from extension/src/app/reducers/background/persistStates.js rename to extension/src/app/reducers/background/persistStates.ts diff --git a/extension/src/app/reducers/panel/index.ts b/extension/src/app/reducers/panel/index.ts index 42cc531fba..b628688d7a 100644 --- a/extension/src/app/reducers/panel/index.ts +++ b/extension/src/app/reducers/panel/index.ts @@ -5,14 +5,20 @@ import notification from '@redux-devtools/app/lib/reducers/notification'; 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 socket from '@redux-devtools/app/lib/reducers/socket'; +import { StoreState } from '@redux-devtools/app/lib/reducers'; +import { StoreAction } from '@redux-devtools/app/lib/actions'; -const rootReducer = combineReducers({ +const rootReducer = combineReducers({ instances, monitor, reports, notification, section, theme, + connection, + socket, }); export default rootReducer; diff --git a/extension/src/app/reducers/window/index.js b/extension/src/app/reducers/window/index.ts similarity index 68% rename from extension/src/app/reducers/window/index.js rename to extension/src/app/reducers/window/index.ts index 5420e736ae..c715b55a7c 100644 --- a/extension/src/app/reducers/window/index.js +++ b/extension/src/app/reducers/window/index.ts @@ -6,8 +6,11 @@ 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 { StoreAction } from '@redux-devtools/app/lib/actions'; -const rootReducer = combineReducers({ +const rootReducer = combineReducers({ instances, monitor, socket, @@ -15,6 +18,7 @@ const rootReducer = combineReducers({ notification, section, theme, + connection, }); export default rootReducer; diff --git a/extension/src/app/reducers/window/instances.js b/extension/src/app/reducers/window/instances.ts similarity index 80% rename from extension/src/app/reducers/window/instances.js rename to extension/src/app/reducers/window/instances.ts index 7454eb90a7..6a50d08b07 100644 --- a/extension/src/app/reducers/window/instances.js +++ b/extension/src/app/reducers/window/instances.ts @@ -7,8 +7,9 @@ import { SELECT_INSTANCE, LIFTED_ACTION, } from '@redux-devtools/app/lib/constants/actionTypes'; +import { StoreAction } from '@redux-devtools/app/lib/actions'; -export default function instances(state = initialState, action) { +export default function instances(state = initialState, action: StoreAction) { switch (action.type) { case UPDATE_STATE: return { ...action.instances, selected: state.selected }; diff --git a/extension/src/app/stores/backgroundStore.js b/extension/src/app/stores/backgroundStore.ts similarity index 62% rename from extension/src/app/stores/backgroundStore.js rename to extension/src/app/stores/backgroundStore.ts index 2a9568e2f3..2348804b0e 100644 --- a/extension/src/app/stores/backgroundStore.js +++ b/extension/src/app/stores/backgroundStore.ts @@ -1,8 +1,10 @@ -import { createStore, applyMiddleware } from 'redux'; -import rootReducer from '../reducers/background'; +import { createStore, applyMiddleware, PreloadedState } from 'redux'; +import rootReducer, { BackgroundState } from '../reducers/background'; import api from '../middlewares/api'; -export default function configureStore(preloadedState) { +export default function configureStore( + preloadedState: PreloadedState +) { return createStore(rootReducer, preloadedState, applyMiddleware(api)); /* let enhancer; diff --git a/extension/src/app/stores/panelStore.ts b/extension/src/app/stores/panelStore.ts index e15266e922..977ed96e13 100644 --- a/extension/src/app/stores/panelStore.ts +++ b/extension/src/app/stores/panelStore.ts @@ -1,12 +1,13 @@ -import { createStore, applyMiddleware } from 'redux'; +import { createStore, applyMiddleware, PreloadedState } from 'redux'; import exportState from '@redux-devtools/app/lib/middlewares/exportState'; import panelDispatcher from '../middlewares/panelSync'; import rootReducer from '../reducers/panel'; +import { StoreState } from '@redux-devtools/app/lib/reducers'; export default function configureStore( position: string, bgConnection: chrome.runtime.Port, - preloadedState + preloadedState: PreloadedState ) { const enhancer = applyMiddleware(exportState, panelDispatcher(bgConnection)); return createStore(rootReducer, preloadedState, enhancer); diff --git a/extension/src/app/stores/windowStore.js b/extension/src/app/stores/windowStore.ts similarity index 75% rename from extension/src/app/stores/windowStore.js rename to extension/src/app/stores/windowStore.ts index 2e67ef96d8..99aed5f31f 100644 --- a/extension/src/app/stores/windowStore.js +++ b/extension/src/app/stores/windowStore.ts @@ -1,12 +1,25 @@ -import { createStore, compose, applyMiddleware } from 'redux'; +import { + createStore, + compose, + applyMiddleware, + Store, + PreloadedState, +} from 'redux'; 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 { StoreState } from '@redux-devtools/app/lib/reducers'; +import { StoreAction } from '@redux-devtools/app/lib/actions'; import syncStores from '../middlewares/windowSync'; import instanceSelector from '../middlewares/instanceSelector'; import rootReducer from '../reducers/window'; +import { BackgroundState } from '../reducers/background'; -export default function configureStore(baseStore, position, preloadedState) { +export default function configureStore( + baseStore: Store, + position: string, + preloadedState: PreloadedState +) { let enhancer; const middlewares = [exportState, api, syncStores(baseStore)]; if (!position || position === '#popup') { diff --git a/extension/src/browser/extension/background/getPreloadedState.js b/extension/src/browser/extension/background/getPreloadedState.ts similarity index 78% rename from extension/src/browser/extension/background/getPreloadedState.js rename to extension/src/browser/extension/background/getPreloadedState.ts index 89d49d50c2..00ea732ebb 100644 --- a/extension/src/browser/extension/background/getPreloadedState.js +++ b/extension/src/browser/extension/background/getPreloadedState.ts @@ -1,3 +1,6 @@ +import { PreloadedState } from 'redux'; +import { StoreState } from '@redux-devtools/app/lib/reducers'; + const getIfExists = (sel, template) => typeof sel === 'undefined' || typeof template === 'undefined' || @@ -5,7 +8,10 @@ const getIfExists = (sel, template) => ? 0 : sel; -export default function getPreloadedState(position, cb) { +export default function getPreloadedState( + position: string, + cb: (state: PreloadedState) => void +) { chrome.storage.local.get( [ 'monitor' + position, diff --git a/extension/src/browser/extension/background/index.js b/extension/src/browser/extension/background/index.ts similarity index 100% rename from extension/src/browser/extension/background/index.js rename to extension/src/browser/extension/background/index.ts diff --git a/extension/src/browser/extension/devpanel/index.tsx b/extension/src/browser/extension/devpanel/index.tsx index f5422c4acf..5b58b88497 100644 --- a/extension/src/browser/extension/devpanel/index.tsx +++ b/extension/src/browser/extension/devpanel/index.tsx @@ -7,6 +7,9 @@ import configureStore from '../../../app/stores/panelStore'; import getPreloadedState from '../background/getPreloadedState'; import '../../views/devpanel.pug'; +import { PreloadedState, Store } from 'redux'; +import { StoreState } from '@redux-devtools/app/lib/reducers'; +import { StoreAction } from '@redux-devtools/app/lib/actions'; const position = location.hash; const messageStyle: CSSProperties = { @@ -16,10 +19,10 @@ const messageStyle: CSSProperties = { }; let rendered: boolean; -let store; +let store: Store | undefined; let bgConnection: chrome.runtime.Port; let naTimeout: NodeJS.Timeout; -let preloadedState; +let preloadedState: PreloadedState; const isChrome = navigator.userAgent.indexOf('Firefox') === -1; @@ -96,10 +99,10 @@ function init(id: number) { bgConnection.onMessage.addListener((message) => { if (message.type === 'NA') { if (message.id === id) renderNA(); - else store.dispatch({ type: REMOVE_INSTANCE, id: message.id }); + else store!.dispatch({ type: REMOVE_INSTANCE, id: message.id }); } else { if (!rendered) renderDevTools(); - store.dispatch(message); + store!.dispatch(message); } }); } diff --git a/extension/src/browser/extension/devtools/index.js b/extension/src/browser/extension/devtools/index.ts similarity index 92% rename from extension/src/browser/extension/devtools/index.js rename to extension/src/browser/extension/devtools/index.ts index 9365ec61fb..de6abaaadd 100644 --- a/extension/src/browser/extension/devtools/index.js +++ b/extension/src/browser/extension/devtools/index.ts @@ -1,6 +1,6 @@ import '../../views/devtools.pug'; -function createPanel(url) { +function createPanel(url: string) { chrome.devtools.panels.create( 'Redux', 'img/logo/scalable.png', diff --git a/extension/src/browser/extension/inject/deprecatedWarn.js b/extension/src/browser/extension/inject/deprecatedWarn.ts similarity index 100% rename from extension/src/browser/extension/inject/deprecatedWarn.js rename to extension/src/browser/extension/inject/deprecatedWarn.ts diff --git a/extension/src/browser/extension/inject/index.js b/extension/src/browser/extension/inject/index.ts similarity index 100% rename from extension/src/browser/extension/inject/index.js rename to extension/src/browser/extension/inject/index.ts diff --git a/extension/src/browser/extension/inject/pageScriptWrap.js b/extension/src/browser/extension/inject/pageScriptWrap.ts similarity index 93% rename from extension/src/browser/extension/inject/pageScriptWrap.js rename to extension/src/browser/extension/inject/pageScriptWrap.ts index 0a8cef099e..7f28cfea3a 100644 --- a/extension/src/browser/extension/inject/pageScriptWrap.js +++ b/extension/src/browser/extension/inject/pageScriptWrap.ts @@ -5,7 +5,7 @@ if (process.env.NODE_ENV === 'production') { const { default: script } = require('raw-loader!tmp/page.bundle.js'); s.appendChild(document.createTextNode(script)); (document.head || document.documentElement).appendChild(s); - s.parentNode.removeChild(s); + s.parentNode!.removeChild(s); } else { s.src = chrome.extension.getURL('page.bundle.js'); s.onload = function () { diff --git a/extension/src/browser/extension/options/AllowToRunGroup.js b/extension/src/browser/extension/options/AllowToRunGroup.tsx similarity index 93% rename from extension/src/browser/extension/options/AllowToRunGroup.js rename to extension/src/browser/extension/options/AllowToRunGroup.tsx index d7c0cd82d2..f08990aeca 100644 --- a/extension/src/browser/extension/options/AllowToRunGroup.js +++ b/extension/src/browser/extension/options/AllowToRunGroup.tsx @@ -1,6 +1,7 @@ import React from 'react'; +import { OptionsProps } from './Options'; -export default ({ options, saveOption }) => { +export default ({ options, saveOption }: OptionsProps) => { const AllowToRunState = { EVERYWHERE: true, ON_SPECIFIC_URLS: false, diff --git a/extension/src/browser/extension/options/ContextMenuGroup.js b/extension/src/browser/extension/options/ContextMenuGroup.tsx similarity index 87% rename from extension/src/browser/extension/options/ContextMenuGroup.js rename to extension/src/browser/extension/options/ContextMenuGroup.tsx index 8ceb80f8b7..727dda985e 100644 --- a/extension/src/browser/extension/options/ContextMenuGroup.js +++ b/extension/src/browser/extension/options/ContextMenuGroup.tsx @@ -1,6 +1,7 @@ import React from 'react'; +import { OptionsProps } from './Options'; -export default ({ options, saveOption }) => { +export default ({ options, saveOption }: OptionsProps) => { return (
Context Menu diff --git a/extension/src/browser/extension/options/EditorGroup.js b/extension/src/browser/extension/options/EditorGroup.tsx similarity index 95% rename from extension/src/browser/extension/options/EditorGroup.js rename to extension/src/browser/extension/options/EditorGroup.tsx index 321c30b26a..130ef824d0 100644 --- a/extension/src/browser/extension/options/EditorGroup.js +++ b/extension/src/browser/extension/options/EditorGroup.tsx @@ -1,6 +1,7 @@ import React from 'react'; +import { OptionsProps } from './Options'; -export default ({ options, saveOption }) => { +export default ({ options, saveOption }: OptionsProps) => { const EditorState = { BROWSER: 0, EXTERNAL: 1, @@ -45,7 +46,7 @@ export default ({ options, saveOption }) => { className="option__element" id="editor" type="text" - size="33" + size={33} maxLength={30} placeholder="vscode, atom, webstorm, sublime..." value={options.editor} diff --git a/extension/src/browser/extension/options/FilterGroup.js b/extension/src/browser/extension/options/FilterGroup.tsx similarity index 95% rename from extension/src/browser/extension/options/FilterGroup.js rename to extension/src/browser/extension/options/FilterGroup.tsx index 3567249f05..4bc24536bb 100644 --- a/extension/src/browser/extension/options/FilterGroup.js +++ b/extension/src/browser/extension/options/FilterGroup.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { FilterState } from '../../../app/api/filters'; +import { OptionsProps } from './Options'; -export default ({ options, saveOption }) => { +export default ({ options, saveOption }: OptionsProps) => { return (
diff --git a/extension/src/browser/extension/options/MiscellaneousGroup.js b/extension/src/browser/extension/options/MiscellaneousGroup.tsx similarity index 94% rename from extension/src/browser/extension/options/MiscellaneousGroup.js rename to extension/src/browser/extension/options/MiscellaneousGroup.tsx index c335c37277..1c3c9a05a6 100644 --- a/extension/src/browser/extension/options/MiscellaneousGroup.js +++ b/extension/src/browser/extension/options/MiscellaneousGroup.tsx @@ -1,6 +1,7 @@ import React from 'react'; +import { OptionsProps } from './Options'; -export default ({ options, saveOption }) => { +export default ({ options, saveOption }: OptionsProps) => { const browserName = navigator.userAgent.includes('Firefox') ? 'Firefox' : 'Chrome'; diff --git a/extension/src/browser/extension/options/Options.js b/extension/src/browser/extension/options/Options.tsx similarity index 81% rename from extension/src/browser/extension/options/Options.js rename to extension/src/browser/extension/options/Options.tsx index 693a6f4a0f..579dd3ac41 100644 --- a/extension/src/browser/extension/options/Options.js +++ b/extension/src/browser/extension/options/Options.tsx @@ -4,8 +4,17 @@ import FilterGroup from './FilterGroup'; import AllowToRunGroup from './AllowToRunGroup'; import MiscellaneousGroup from './MiscellaneousGroup'; import ContextMenuGroup from './ContextMenuGroup'; +import { Options } from './syncOptions'; -export default (props) => ( +export interface OptionsProps { + readonly options: Options; + readonly saveOption: ( + name: K, + value: Options[K] + ) => void; +} + +export default (props: OptionsProps) => (
diff --git a/extension/src/browser/extension/options/index.js b/extension/src/browser/extension/options/index.tsx similarity index 100% rename from extension/src/browser/extension/options/index.js rename to extension/src/browser/extension/options/index.tsx diff --git a/extension/src/browser/extension/options/syncOptions.js b/extension/src/browser/extension/options/syncOptions.js deleted file mode 100644 index 04827b5c35..0000000000 --- a/extension/src/browser/extension/options/syncOptions.js +++ /dev/null @@ -1,108 +0,0 @@ -import { FilterState } from '../../../app/api/filters'; - -let options; -let subscribers = []; - -const save = (toAllTabs) => (key, value) => { - let obj = {}; - obj[key] = value; - chrome.storage.sync.set(obj); - options[key] = value; - toAllTabs({ options: options }); - subscribers.forEach((s) => s(options)); -}; - -const migrateOldOptions = (oldOptions) => { - let newOptions = Object.assign({}, oldOptions); - - // Migrate the old `filter` option from 2.2.1 - if (typeof oldOptions.filter === 'boolean') { - if (oldOptions.filter && oldOptions.whitelist.length > 0) { - newOptions.filter = FilterState.WHITELIST_SPECIFIC; - } else if (oldOptions.filter) { - newOptions.filter = FilterState.BLACKLIST_SPECIFIC; - } else { - newOptions.filter = FilterState.DO_NOT_FILTER; - } - } - - return newOptions; -}; - -const get = (callback) => { - if (options) callback(options); - else { - chrome.storage.sync.get( - { - useEditor: 0, - editor: '', - projectPath: '', - maxAge: 50, - filter: FilterState.DO_NOT_FILTER, - whitelist: '', - blacklist: '', - shouldCatchErrors: false, - inject: true, - urls: '^https?://localhost|0\\.0\\.0\\.0:\\d+\n^https?://.+\\.github\\.io', - showContextMenus: true, - }, - function (items) { - options = migrateOldOptions(items); - callback(options); - } - ); - } -}; - -const subscribe = (callback) => { - subscribers = subscribers.concat(callback); -}; - -const toReg = (str) => - str !== '' ? str.split('\n').filter(Boolean).join('|') : null; - -export const injectOptions = (newOptions) => { - if (!newOptions) return; - if (newOptions.filter !== FilterState.DO_NOT_FILTER) { - newOptions.whitelist = toReg(newOptions.whitelist); - newOptions.blacklist = toReg(newOptions.blacklist); - } - - options = newOptions; - let s = document.createElement('script'); - s.type = 'text/javascript'; - s.appendChild( - document.createTextNode( - 'window.devToolsOptions = Object.assign(window.devToolsOptions||{},' + - JSON.stringify(options) + - ');' - ) - ); - (document.head || document.documentElement).appendChild(s); - s.parentNode.removeChild(s); -}; - -export const getOptionsFromBg = () => { - /* chrome.runtime.sendMessage({ type: 'GET_OPTIONS' }, response => { - if (response && response.options) injectOptions(response.options); - }); -*/ - get((newOptions) => { - injectOptions(newOptions); - }); // Legacy -}; - -export const isAllowed = (localOptions = options) => - !localOptions || - localOptions.inject || - !localOptions.urls || - location.href.match(toReg(localOptions.urls)); - -export default function syncOptions(toAllTabs) { - if (toAllTabs && !options) get(() => {}); // Initialize - return { - save: save(toAllTabs), - get: get, - subscribe: subscribe, - }; -} diff --git a/extension/src/browser/extension/options/syncOptions.ts b/extension/src/browser/extension/options/syncOptions.ts new file mode 100644 index 0000000000..051095a586 --- /dev/null +++ b/extension/src/browser/extension/options/syncOptions.ts @@ -0,0 +1,142 @@ +import { FilterState, FilterStateValue } from '../../../app/api/filters'; + +export interface Options { + readonly useEditor: number; + readonly editor: string; + readonly projectPath: string; + readonly maxAge: number; + readonly filter: FilterStateValue; + readonly whitelist: string; + readonly blacklist: string; + readonly shouldCatchErrors: boolean; + readonly inject: boolean; + readonly urls: string; + readonly showContextMenus: boolean; +} + +interface OldOrNewOptions { + readonly useEditor: number; + readonly editor: string; + readonly projectPath: string; + readonly maxAge: number; + readonly filter: FilterStateValue | boolean; + readonly whitelist: string; + readonly blacklist: string; + readonly shouldCatchErrors: boolean; + readonly inject: boolean; + readonly urls: string; + readonly showContextMenus: boolean; +} + +let options: Options | undefined; +let subscribers: ((options: Options) => void)[] = []; + +type ToAllTabs = (msg: { readonly options: Options }) => void; + +const save = + (toAllTabs: ToAllTabs | undefined) => + (key: K, value: Options[K]) => { + let obj: { [K1 in keyof Options]?: Options[K1] } = {}; + obj[key] = value; + chrome.storage.sync.set(obj); + options![key] = value; + toAllTabs!({ options: options! }); + subscribers.forEach((s) => s(options!)); + }; + +const migrateOldOptions = (oldOptions: OldOrNewOptions): Options => ({ + ...oldOptions, + filter: + // Migrate the old `filter` option from 2.2.1 + typeof oldOptions.filter === 'boolean' + ? oldOptions.filter && oldOptions.whitelist.length > 0 + ? FilterState.WHITELIST_SPECIFIC + : oldOptions.filter + ? FilterState.BLACKLIST_SPECIFIC + : FilterState.DO_NOT_FILTER + : oldOptions.filter, +}); + +const get = (callback: (options: Options) => void) => { + if (options) callback(options); + else { + chrome.storage.sync.get( + { + useEditor: 0, + editor: '', + projectPath: '', + maxAge: 50, + filter: FilterState.DO_NOT_FILTER, + whitelist: '', + blacklist: '', + shouldCatchErrors: false, + inject: true, + urls: '^https?://localhost|0\\.0\\.0\\.0:\\d+\n^https?://.+\\.github\\.io', + showContextMenus: true, + }, + function (items) { + options = migrateOldOptions(items as OldOrNewOptions); + callback(options); + } + ); + } +}; + +const subscribe = (callback: (options: Options) => void) => { + subscribers = subscribers.concat(callback); +}; + +const toReg = (str: string) => + str !== '' ? str.split('\n').filter(Boolean).join('|') : null; + +export const injectOptions = (newOptions: Options) => { + if (!newOptions) return; + + options = { + ...newOptions, + whitelist: + newOptions.filter !== FilterState.DO_NOT_FILTER + ? toReg(newOptions.whitelist)! + : newOptions.whitelist, + blacklist: + newOptions.filter !== FilterState.DO_NOT_FILTER + ? toReg(newOptions.blacklist)! + : newOptions.blacklist, + }; + let s = document.createElement('script'); + s.type = 'text/javascript'; + s.appendChild( + document.createTextNode( + 'window.devToolsOptions = Object.assign(window.devToolsOptions||{},' + + JSON.stringify(options) + + ');' + ) + ); + (document.head || document.documentElement).appendChild(s); + s.parentNode!.removeChild(s); +}; + +export const getOptionsFromBg = () => { + /* chrome.runtime.sendMessage({ type: 'GET_OPTIONS' }, response => { + if (response && response.options) injectOptions(response.options); + }); +*/ + get((newOptions) => { + injectOptions(newOptions); + }); // Legacy +}; + +export const isAllowed = (localOptions = options) => + !localOptions || + localOptions.inject || + !localOptions.urls || + location.href.match(toReg(localOptions.urls)!); + +export default function syncOptions(toAllTabs?: ToAllTabs) { + if (toAllTabs && !options) get(() => {}); // Initialize + return { + save: save(toAllTabs), + get: get, + subscribe: subscribe, + }; +} diff --git a/extension/src/browser/extension/window/index.js b/extension/src/browser/extension/window/index.tsx similarity index 88% rename from extension/src/browser/extension/window/index.js rename to extension/src/browser/extension/window/index.tsx index e1f1260f1b..14aaedead2 100644 --- a/extension/src/browser/extension/window/index.js +++ b/extension/src/browser/extension/window/index.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { render } from 'react-dom'; +import { PreloadedState } from 'redux'; import { Provider } from 'react-redux'; import { UPDATE_STATE } from '@redux-devtools/app/lib/constants/actionTypes'; +import { StoreState } from '@redux-devtools/app/lib/reducers'; import App from '../../../app/containers/App'; import configureStore from '../../../app/stores/windowStore'; import getPreloadedState from '../background/getPreloadedState'; @@ -9,7 +11,7 @@ import getPreloadedState from '../background/getPreloadedState'; import '../../views/window.pug'; const position = location.hash; -let preloadedState; +let preloadedState: PreloadedState; getPreloadedState(position, (state) => { preloadedState = state; }); diff --git a/extension/src/browser/extension/window/remote.js b/extension/src/browser/extension/window/remote.tsx similarity index 94% rename from extension/src/browser/extension/window/remote.js rename to extension/src/browser/extension/window/remote.tsx index e4e271e6ed..3c31bb66d3 100644 --- a/extension/src/browser/extension/window/remote.js +++ b/extension/src/browser/extension/window/remote.tsx @@ -19,7 +19,6 @@ chrome.storage.local.get( selectMonitor={options['select-monitor']} testTemplates={options['test-templates']} selectedTemplate={options['test-templates-sel']} - testTemplates={options['test-templates']} useCodemirror socketOptions={ options['s:hostname'] && options['s:port'] From 7b4698225bf2f1adbbd2c1dbc17877acdf00768b Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 20 Jun 2021 16:34:11 -0400 Subject: [PATCH 03/25] stash --- extension/src/browser/extension/options/index.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/extension/src/browser/extension/options/index.tsx b/extension/src/browser/extension/options/index.tsx index 0dbc871997..7753aaaafe 100644 --- a/extension/src/browser/extension/options/index.tsx +++ b/extension/src/browser/extension/options/index.tsx @@ -1,19 +1,20 @@ import React from 'react'; import { render } from 'react-dom'; -import Options from './Options'; +import OptionsComponent from './Options'; +import { Options } from './syncOptions'; import '../../views/options.pug'; chrome.runtime.getBackgroundPage((background) => { const syncOptions = background.syncOptions; - const saveOption = (name, value) => { + const saveOption = (name: K, value: Options[K]) => { syncOptions.save(name, value); }; - const renderOptions = (options) => { + const renderOptions = (options: Options) => { render( - , + , document.getElementById('root') ); }; From a3df1df2eb32e489f8d3580e1cda7431320eb3b6 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Fri, 16 Jul 2021 19:15:26 -0400 Subject: [PATCH 04/25] stash --- extension/src/app/api/filters.ts | 38 ++- extension/src/app/api/generateInstanceId.js | 5 - extension/src/app/api/generateInstanceId.ts | 5 + .../api/{importState.js => importState.ts} | 0 extension/src/app/api/{index.js => index.ts} | 63 +++-- .../api/{notifyErrors.js => notifyErrors.ts} | 12 +- .../app/api/{openWindow.js => openWindow.ts} | 4 +- .../src/app/middlewares/{api.js => api.ts} | 105 ++++++-- .../app/service/{Monitor.js => Monitor.ts} | 27 +- extension/src/app/stores/backgroundStore.ts | 2 +- extension/src/app/stores/createStore.js | 5 - extension/src/app/stores/createStore.ts | 15 ++ .../{enhancerStore.js => enhancerStore.ts} | 19 +- .../src/browser/extension/background/index.ts | 13 +- .../background/{logging.js => logging.ts} | 0 .../{chromeAPIMock.js => chromeAPIMock.ts} | 0 .../{contentScript.js => contentScript.ts} | 54 +++- .../inject/{pageScript.js => pageScript.ts} | 253 ++++++++++++------ .../src/browser/extension/options/index.tsx | 2 +- .../browser/extension/options/syncOptions.ts | 14 +- .../src/browser/extension/window/index.tsx | 3 +- 21 files changed, 470 insertions(+), 169 deletions(-) delete mode 100644 extension/src/app/api/generateInstanceId.js create mode 100644 extension/src/app/api/generateInstanceId.ts rename extension/src/app/api/{importState.js => importState.ts} (100%) rename extension/src/app/api/{index.js => index.ts} (85%) rename extension/src/app/api/{notifyErrors.js => notifyErrors.ts} (75%) rename extension/src/app/api/{openWindow.js => openWindow.ts} (51%) rename extension/src/app/middlewares/{api.js => api.ts} (73%) rename extension/src/app/service/{Monitor.js => Monitor.ts} (70%) delete mode 100644 extension/src/app/stores/createStore.js create mode 100644 extension/src/app/stores/createStore.ts rename extension/src/app/stores/{enhancerStore.js => enhancerStore.ts} (64%) rename extension/src/browser/extension/background/{logging.js => logging.ts} (100%) rename extension/src/browser/extension/{chromeAPIMock.js => chromeAPIMock.ts} (100%) rename extension/src/browser/extension/inject/{contentScript.js => contentScript.ts} (71%) rename extension/src/browser/extension/inject/{pageScript.js => pageScript.ts} (67%) diff --git a/extension/src/app/api/filters.ts b/extension/src/app/api/filters.ts index e072437d70..4f13a36794 100644 --- a/extension/src/app/api/filters.ts +++ b/extension/src/app/api/filters.ts @@ -1,4 +1,7 @@ import mapValues from 'lodash/mapValues'; +import { Config } from '../../browser/extension/inject/pageScript'; +import { Action } from 'redux'; +import { LiftedState, PerformAction } from '@redux-devtools/instrument'; export type FilterStateValue = | 'DO_NOT_FILTER' @@ -11,13 +14,22 @@ export const FilterState: { [K in FilterStateValue]: FilterStateValue } = { WHITELIST_SPECIFIC: 'WHITELIST_SPECIFIC', }; -export function getLocalFilter(config) { +function isArray(arg: unknown): arg is readonly unknown[] { + return Array.isArray(arg); +} + +interface LocalFilter { + readonly whitelist: string | undefined; + readonly blacklist: string | undefined; +} + +export function getLocalFilter(config: Config): LocalFilter | undefined { if (config.actionsBlacklist || config.actionsWhitelist) { return { - whitelist: Array.isArray(config.actionsWhitelist) + whitelist: isArray(config.actionsWhitelist) ? config.actionsWhitelist.join('|') : config.actionsWhitelist, - blacklist: Array.isArray(config.actionsBlacklist) + blacklist: isArray(config.actionsBlacklist) ? config.actionsBlacklist.join('|') : config.actionsBlacklist, }; @@ -125,13 +137,17 @@ export function filterState( }; } -export function startingFrom( - sendingActionId, - state, - localFilter, - stateSanitizer, - actionSanitizer, - predicate +export function startingFrom>( + sendingActionId: number, + state: LiftedState, + localFilter: LocalFilter | undefined, + stateSanitizer: ((state: S, index: number) => S) | undefined, + actionSanitizer: + | (>(action: A, id: number) => A) + | undefined, + predicate: + | (>(state: S, action: A) => boolean) + | undefined ) { const stagedActionIds = state.stagedActionIds; if (sendingActionId <= stagedActionIds[1]) return state; @@ -142,7 +158,7 @@ export function startingFrom( const filteredStagedActionIds = shouldFilter ? [0] : stagedActionIds; const actionsById = state.actionsById; const computedStates = state.computedStates; - const newActionsById = {}; + const newActionsById: { [key: number]: PerformAction } = {}; const newComputedStates = []; let key; let currAction; diff --git a/extension/src/app/api/generateInstanceId.js b/extension/src/app/api/generateInstanceId.js deleted file mode 100644 index 4471d9cb9d..0000000000 --- a/extension/src/app/api/generateInstanceId.js +++ /dev/null @@ -1,5 +0,0 @@ -let id = 0; - -export default function generateId(instanceId) { - return instanceId || ++id; -} diff --git a/extension/src/app/api/generateInstanceId.ts b/extension/src/app/api/generateInstanceId.ts new file mode 100644 index 0000000000..ae1215684a --- /dev/null +++ b/extension/src/app/api/generateInstanceId.ts @@ -0,0 +1,5 @@ +let id = 0; + +export default function generateId(instanceId: number | undefined) { + return instanceId || ++id; +} diff --git a/extension/src/app/api/importState.js b/extension/src/app/api/importState.ts similarity index 100% rename from extension/src/app/api/importState.js rename to extension/src/app/api/importState.ts diff --git a/extension/src/app/api/index.js b/extension/src/app/api/index.ts similarity index 85% rename from extension/src/app/api/index.js rename to extension/src/app/api/index.ts index 5590dbfb54..fc98e74cf0 100644 --- a/extension/src/app/api/index.js +++ b/extension/src/app/api/index.ts @@ -1,22 +1,24 @@ -import jsan from 'jsan'; +import jsan, { Options } from 'jsan'; import throttle from 'lodash/throttle'; -import seralizeImmutable from '@redux-devtools/serialize/lib/immutable/serialize'; +import serializeImmutable from '@redux-devtools/serialize/lib/immutable/serialize'; import { getActionsArray } from '@redux-devtools/utils'; import { getLocalFilter, isFiltered } from './filters'; import importState from './importState'; import generateId from './generateInstanceId'; +import { PageScriptToContentScriptMessage } from '../../browser/extension/inject/contentScript'; +import { Config } from '../../browser/extension/inject/pageScript'; const listeners = {}; export const source = '@devtools-page'; -function windowReplacer(key, value) { - if (value && value.window === value) { +function windowReplacer(key: string, value: unknown) { + if (value && (value as Window).window === value) { return '[WINDOW]'; } return value; } -function tryCatchStringify(obj) { +function tryCatchStringify(obj: unknown) { try { return JSON.stringify(obj); } catch (err) { @@ -25,19 +27,19 @@ function tryCatchStringify(obj) { console.log('Failed to stringify', err); } /* eslint-enable no-console */ - return jsan.stringify(obj, windowReplacer, null, { + return jsan.stringify(obj, windowReplacer, undefined, { circular: '[CIRCULAR]', date: true, }); } } -let stringifyWarned; -function stringify(obj, serialize) { +let stringifyWarned: boolean; +function stringify(obj: unknown, serialize?: Serialize | undefined) { const str = typeof serialize === 'undefined' ? tryCatchStringify(obj) - : jsan.stringify(obj, serialize.replacer, null, serialize.options); + : jsan.stringify(obj, serialize.replacer, undefined, serialize.options); if (!stringifyWarned && str && str.length > 16 * 1024 * 1024) { // 16 MB @@ -52,12 +54,21 @@ function stringify(obj, serialize) { return str; } -export function getSeralizeParameter(config, param) { +export interface Serialize { + readonly replacer?: (key: string, value: unknown) => unknown; + readonly reviver?: (key: string, value: unknown) => unknown; + readonly options?: Options | boolean; +} + +export function getSerializeParameter( + config: Config, + param?: 'serializeState' | 'serializeAction' +) { const serialize = config.serialize; if (serialize) { if (serialize === true) return { options: true }; if (serialize.immutable) { - const immutableSerializer = seralizeImmutable( + const immutableSerializer = serializeImmutable( serialize.immutable, serialize.refs, serialize.replacer, @@ -82,23 +93,23 @@ export function getSeralizeParameter(config, param) { }; } - const value = config[param]; + const value = config[param!]; if (typeof value === 'undefined') return undefined; // eslint-disable-next-line no-console console.warn( `\`${param}\` parameter for Redux DevTools Extension is deprecated. Use \`serialize\` parameter instead: https://github.com/zalmoxisus/redux-devtools-extension/releases/tag/v2.12.1` ); - if (typeof serializeState === 'boolean') return { options: value }; - if (typeof serializeState === 'function') return { replacer: value }; + if (typeof value === 'boolean') return { options: value }; + if (typeof value === 'function') return { replacer: value }; return value; } -function post(message) { +function post(message: PageScriptToContentScriptMessage) { window.postMessage(message, '*'); } -function getStackTrace(config, toExcludeFromTrace) { +function getStackTrace(config, toExcludeFromTrace: Function | undefined) { if (!config.trace) return undefined; if (typeof config.trace === 'function') return config.trace(); @@ -123,7 +134,7 @@ function getStackTrace(config, toExcludeFromTrace) { typeof Error.stackTraceLimit !== 'number' || Error.stackTraceLimit > traceLimit ) { - const frames = stack.split('\n'); + const frames = stack!.split('\n'); if (frames.length > traceLimit) { stack = frames .slice(0, traceLimit + extraFrames + (frames[0] === 'Error' ? 1 : 0)) @@ -133,7 +144,11 @@ function getStackTrace(config, toExcludeFromTrace) { return stack; } -function amendActionType(action, config, toExcludeFromTrace) { +function amendActionType( + action, + config, + toExcludeFromTrace: Function | undefined +) { let timestamp = Date.now(); let stack = getStackTrace(config, toExcludeFromTrace); if (typeof action === 'string') { @@ -144,7 +159,11 @@ function amendActionType(action, config, toExcludeFromTrace) { return { action, timestamp, stack }; } -export function toContentScript(message, serializeState, serializeAction) { +export function toContentScript( + message, + serializeState: Serialize | undefined, + serializeAction: Serialize | undefined +) { if (message.type === 'ACTION') { message.action = stringify(message.action, serializeAction); message.payload = stringify(message.payload, serializeState); @@ -235,7 +254,7 @@ export function connect(preConfig) { config.name = document.title && id === 1 ? document.title : `Instance ${id}`; } - if (config.serialize) config.serialize = getSeralizeParameter(config); + if (config.serialize) config.serialize = getSerializeParameter(config); const actionCreators = config.actionCreators || {}; const latency = config.latency; const predicate = config.predicate; @@ -245,7 +264,7 @@ export function connect(preConfig) { let delayedActions = []; let delayedStates = []; - const rootListiner = (action) => { + const rootListener = (action) => { if (autoPause) { if (action.type === 'START') isPaused = false; else if (action.type === 'STOP') isPaused = true; @@ -264,7 +283,7 @@ export function connect(preConfig) { } }; - listeners[id] = [rootListiner]; + listeners[id] = [rootListener]; const subscribe = (listener) => { if (!listener) return undefined; diff --git a/extension/src/app/api/notifyErrors.js b/extension/src/app/api/notifyErrors.ts similarity index 75% rename from extension/src/app/api/notifyErrors.js rename to extension/src/app/api/notifyErrors.ts index 49235a4288..8b22694517 100644 --- a/extension/src/app/api/notifyErrors.js +++ b/extension/src/app/api/notifyErrors.ts @@ -1,9 +1,9 @@ -let handleError; +let handleError: () => boolean; let lastTime = 0; -function createExpBackoffTimer(step) { +function createExpBackoffTimer(step: number) { let count = 1; - return function (reset) { + return function (reset?: boolean) { // Reset call if (reset) { count = 1; @@ -18,7 +18,7 @@ function createExpBackoffTimer(step) { const nextErrorTimeout = createExpBackoffTimer(5000); -function postError(message) { +function postError(message: string) { if (handleError && !handleError()) return; window.postMessage( { @@ -30,7 +30,7 @@ function postError(message) { ); } -function catchErrors(e) { +function catchErrors(e: ErrorEvent) { if ( (window.devToolsOptions && !window.devToolsOptions.shouldCatchErrors) || e.timeStamp - lastTime < nextErrorTimeout() @@ -42,7 +42,7 @@ function catchErrors(e) { postError(e.message); } -export default function notifyErrors(onError) { +export default function notifyErrors(onError: () => boolean) { handleError = onError; window.addEventListener('error', catchErrors, false); } diff --git a/extension/src/app/api/openWindow.js b/extension/src/app/api/openWindow.ts similarity index 51% rename from extension/src/app/api/openWindow.js rename to extension/src/app/api/openWindow.ts index 58ed5344a3..c997d10473 100644 --- a/extension/src/app/api/openWindow.js +++ b/extension/src/app/api/openWindow.ts @@ -1,4 +1,6 @@ -export default function openWindow(position) { +export type Position = 'left' | 'right' | 'bottom' | 'panel' | 'remote'; + +export default function openWindow(position?: Position) { window.postMessage( { source: '@devtools-page', diff --git a/extension/src/app/middlewares/api.js b/extension/src/app/middlewares/api.ts similarity index 73% rename from extension/src/app/middlewares/api.js rename to extension/src/app/middlewares/api.ts index 785056d469..f861318a1b 100644 --- a/extension/src/app/middlewares/api.js +++ b/extension/src/app/middlewares/api.ts @@ -5,13 +5,53 @@ import { LIFTED_ACTION, } from '@redux-devtools/app/lib/constants/actionTypes'; import { nonReduxDispatch } from '@redux-devtools/app/lib/utils/monitorActions'; -import syncOptions from '../../browser/extension/options/syncOptions'; +import syncOptions, { + OptionsMessage, + SyncOptions, +} from '../../browser/extension/options/syncOptions'; import openDevToolsWindow from '../../browser/extension/background/openWindow'; import { getReport } from '../../browser/extension/background/logging'; +import { StoreAction } from '@redux-devtools/app/lib/actions'; +import { Dispatch } from 'redux'; + +interface StartAction { + readonly type: 'START'; +} + +interface StopAction { + readonly type: 'STOP'; +} + +interface NAAction { + readonly type: 'NA'; + readonly id: string; +} + +interface UpdateStateAction { + readonly type: typeof UPDATE_STATE; +} + +type TabMessage = StartAction | StopAction | OptionsMessage; +type PanelMessage = NAAction; +type MonitorMessage = UpdateStateAction; + +type TabPort = Omit & { + postMessage: (message: TabMessage) => void; +}; +type PanelPort = Omit & { + postMessage: (message: PanelMessage) => void; +}; +type MonitorPort = Omit & { + postMessage: (message: MonitorMessage) => void; +}; const CONNECTED = 'socket/CONNECTED'; const DISCONNECTED = 'socket/DISCONNECTED'; -const connections = { +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: {}, @@ -20,10 +60,16 @@ const chunks = {}; let monitors = 0; let isMonitored = false; -const getId = (sender, name) => - sender.tab ? sender.tab.id : name || sender.id; +const getId = (sender: chrome.runtime.MessageSender, name?: string) => + sender.tab ? sender.tab.id! : name || sender.id!; -function toMonitors(action, tabId, verbose) { +type MonitorAction = NAAction; + +function toMonitors( + action: MonitorAction, + tabId?: string | number, + verbose?: boolean +) { Object.keys(connections.monitor).forEach((id) => { connections.monitor[id].postMessage( verbose || action.type === 'ERROR' ? action : { type: UPDATE_STATE } @@ -43,16 +89,18 @@ function toContentScript({ message, action, id, instanceId, state }) { }); } -function toAllTabs(msg) { +function toAllTabs(msg: TabMessage) { const tabs = connections.tab; Object.keys(tabs).forEach((id) => { tabs[id].postMessage(msg); }); } -function monitorInstances(shouldMonitor, id) { +function monitorInstances(shouldMonitor: boolean, id?: string) { if (!id && isMonitored === shouldMonitor) return; - const action = { type: shouldMonitor ? 'START' : 'STOP' }; + const action = { + type: shouldMonitor ? ('START' as const) : ('STOP' as const), + }; if (id) { if (connections.tab[id]) connections.tab[id].postMessage(action); } else { @@ -80,8 +128,15 @@ function togglePersist() { } } +type BackgroundStoreMessage = unknown; +type BackgroundStoreResponse = never; + // Receive messages from content scripts -function messaging(request, sender, sendResponse) { +function messaging( + request: BackgroundStoreMessage, + sender: chrome.runtime.MessageSender, + sendResponse: (response?: BackgroundStoreResponse) => void +) { let tabId = getId(sender); if (!tabId) return; if (sender.frameId) tabId = `${tabId}-${sender.frameId}`; @@ -164,7 +219,11 @@ function messaging(request, sender, sendResponse) { } } -function disconnect(type, id, listener) { +function disconnect( + type: 'tab' | 'monitor' | 'panel', + id: number | string, + listener?: (message: any, port: chrome.runtime.Port) => void +) { return function disconnectListener() { const p = connections[type][id]; if (listener && p) p.onMessage.removeListener(listener); @@ -182,17 +241,17 @@ function disconnect(type, id, listener) { }; } -function onConnect(port) { - let id; +function onConnect(port: chrome.runtime.Port) { + let id: number | string; let listener; window.store.dispatch({ type: CONNECTED, port }); if (port.name === 'tab') { - id = getId(port.sender); - if (port.sender.frameId) id = `${id}-${port.sender.frameId}`; + id = getId(port.sender!); + if (port.sender!.frameId) id = `${id}-${port.sender!.frameId}`; connections.tab[id] = port; - listener = (msg) => { + listener = (msg: TabToBackgroundMessage) => { if (msg.name === 'INIT_INSTANCE') { if (typeof id === 'number') { chrome.pageAction.show(id); @@ -218,24 +277,24 @@ function onConnect(port) { return; } if (msg.name === 'RELAY') { - messaging(msg.message, port.sender, id); + messaging(msg.message, port.sender!, id); } }; port.onMessage.addListener(listener); port.onDisconnect.addListener(disconnect('tab', id, listener)); } else if (port.name && port.name.indexOf('monitor') === 0) { - id = getId(port.sender, port.name); + id = getId(port.sender!, port.name); connections.monitor[id] = port; monitorInstances(true); monitors++; port.onDisconnect.addListener(disconnect('monitor', id)); } else { // devpanel - id = port.name || port.sender.frameId; + id = port.name || port.sender!.frameId!; connections.panel[id] = port; monitorInstances(true, port.name); monitors++; - listener = (msg) => { + listener = (msg: StoreAction) => { window.store.dispatch(msg); }; port.onMessage.addListener(listener); @@ -253,10 +312,16 @@ chrome.notifications.onClicked.addListener((id) => { openDevToolsWindow('devtools-right'); }); +declare global { + interface Window { + syncOptions: SyncOptions; + } +} + window.syncOptions = syncOptions(toAllTabs); // Expose to the options page export default function api() { - return (next) => (action) => { + return (next: Dispatch) => (action: StoreAction) => { if (action.type === LIFTED_ACTION) toContentScript(action); else if (action.type === 'TOGGLE_PERSIST') togglePersist(); return next(action); diff --git a/extension/src/app/service/Monitor.js b/extension/src/app/service/Monitor.ts similarity index 70% rename from extension/src/app/service/Monitor.js rename to extension/src/app/service/Monitor.ts index c3036e9f5d..67bf411ff7 100644 --- a/extension/src/app/service/Monitor.js +++ b/extension/src/app/service/Monitor.ts @@ -1,5 +1,26 @@ -export default class Monitor { - constructor(update) { +import { Action } from 'redux'; +import { LiftedState } from '@redux-devtools/instrument'; + +declare global { + interface Window { + __REDUX_DEVTOOLS_EXTENSION_LOCKED__?: boolean; + } +} + +export default class Monitor> { + update: ( + liftedState?: LiftedState | undefined, + libConfig?: unknown + ) => void; + active?: boolean; + paused?: boolean; + + constructor( + update: ( + liftedState?: LiftedState | undefined, + libConfig?: unknown + ) => void + ) { this.update = update; } reducer = (state = {}, action) => { @@ -15,7 +36,7 @@ export default class Monitor { } return state; }; - start = (skipUpdate) => { + start = (skipUpdate: boolean) => { this.active = true; if (!skipUpdate) this.update(); }; diff --git a/extension/src/app/stores/backgroundStore.ts b/extension/src/app/stores/backgroundStore.ts index 2348804b0e..62638cb311 100644 --- a/extension/src/app/stores/backgroundStore.ts +++ b/extension/src/app/stores/backgroundStore.ts @@ -3,7 +3,7 @@ import rootReducer, { BackgroundState } from '../reducers/background'; import api from '../middlewares/api'; export default function configureStore( - preloadedState: PreloadedState + preloadedState?: PreloadedState ) { return createStore(rootReducer, preloadedState, applyMiddleware(api)); /* diff --git a/extension/src/app/stores/createStore.js b/extension/src/app/stores/createStore.js deleted file mode 100644 index ecd8492c2e..0000000000 --- a/extension/src/app/stores/createStore.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createStore } from 'redux'; - -export default function configureStore(reducer, initialState, enhance) { - return createStore(reducer, initialState, enhance()); -} diff --git a/extension/src/app/stores/createStore.ts b/extension/src/app/stores/createStore.ts new file mode 100644 index 0000000000..f91dd54894 --- /dev/null +++ b/extension/src/app/stores/createStore.ts @@ -0,0 +1,15 @@ +import { + Action, + createStore, + PreloadedState, + Reducer, + StoreEnhancer, +} from 'redux'; + +export default function configureStore>( + reducer: Reducer, + initialState: PreloadedState | undefined, + enhance: () => StoreEnhancer +) { + return createStore(reducer, initialState, enhance()); +} diff --git a/extension/src/app/stores/enhancerStore.js b/extension/src/app/stores/enhancerStore.ts similarity index 64% rename from extension/src/app/stores/enhancerStore.js rename to extension/src/app/stores/enhancerStore.ts index fdf2a8500c..715c56540a 100644 --- a/extension/src/app/stores/enhancerStore.js +++ b/extension/src/app/stores/enhancerStore.ts @@ -1,15 +1,26 @@ -import { compose } from 'redux'; -import instrument from '@redux-devtools/instrument'; +import { Action, compose, Reducer, StoreEnhancerStoreCreator } from 'redux'; +import instrument, { + LiftedAction, + LiftedState, +} from '@redux-devtools/instrument'; import persistState from '@redux-devtools/core/lib/persistState'; +import { + Config, + ConfigWithExpandedMaxAge, +} from '../../browser/extension/inject/pageScript'; -export function getUrlParam(key) { +export function getUrlParam(key: string) { const matches = window.location.href.match( new RegExp(`[?&]${key}=([^&#]+)\\b`) ); return matches && matches.length > 0 ? matches[1] : null; } -export default function configureStore(next, monitorReducer, config) { +export default function configureStore( + next: StoreEnhancerStoreCreator, + monitorReducer: Reducer, + config: ConfigWithExpandedMaxAge +) { return compose( instrument(monitorReducer, { maxAge: config.maxAge, diff --git a/extension/src/browser/extension/background/index.ts b/extension/src/browser/extension/background/index.ts index f92a9af503..307e2c4f30 100644 --- a/extension/src/browser/extension/background/index.ts +++ b/extension/src/browser/extension/background/index.ts @@ -1,7 +1,16 @@ +import { Store } from 'redux'; +import { StoreAction } from '@redux-devtools/app/lib/actions'; import configureStore from '../../../app/stores/backgroundStore'; -import openDevToolsWindow from './openWindow'; +import openDevToolsWindow, { DevToolsPosition } from './openWindow'; import { createMenu, removeMenu } from './contextMenus'; import syncOptions from '../options/syncOptions'; +import { BackgroundState } from '../../../app/reducers/background'; + +declare global { + interface Window { + store: Store; + } +} // Expose the extension's store globally to access it from the windows // via chrome.runtime.getBackgroundPage @@ -9,7 +18,7 @@ window.store = configureStore(); // Listen for keyboard shortcuts chrome.commands.onCommand.addListener((shortcut) => { - openDevToolsWindow(shortcut); + openDevToolsWindow(shortcut as DevToolsPosition); }); // Create the context menu when installed diff --git a/extension/src/browser/extension/background/logging.js b/extension/src/browser/extension/background/logging.ts similarity index 100% rename from extension/src/browser/extension/background/logging.js rename to extension/src/browser/extension/background/logging.ts diff --git a/extension/src/browser/extension/chromeAPIMock.js b/extension/src/browser/extension/chromeAPIMock.ts similarity index 100% rename from extension/src/browser/extension/chromeAPIMock.js rename to extension/src/browser/extension/chromeAPIMock.ts diff --git a/extension/src/browser/extension/inject/contentScript.js b/extension/src/browser/extension/inject/contentScript.ts similarity index 71% rename from extension/src/browser/extension/inject/contentScript.js rename to extension/src/browser/extension/inject/contentScript.ts index 5f59d5dada..f243b6dff8 100644 --- a/extension/src/browser/extension/inject/contentScript.js +++ b/extension/src/browser/extension/inject/contentScript.ts @@ -8,7 +8,13 @@ const pageSource = '@devtools-page'; // Chrome message limit is 64 MB, but we're using 32 MB to include other object's parts const maxChromeMsgSize = 32 * 1024 * 1024; let connected = false; -let bg; +let bg: chrome.runtime.Port | undefined; + +declare global { + interface Window { + devToolsExtensionID?: string; + } +} function connect() { // Connect to the background script @@ -57,7 +63,10 @@ function handleDisconnect() { bg = undefined; } -function tryCatch(fn, args) { +function tryCatch( + fn: (args: PageScriptToContentScriptMessage) => void, + args: PageScriptToContentScriptMessage +) { try { return fn(args); } catch (err) { @@ -100,18 +109,51 @@ function tryCatch(fn, args) { } } -function send(message) { +interface InitInstancePageScriptToContentScriptMessage { + readonly type: 'INIT_INSTANCE'; + readonly instanceId: number; + readonly source: typeof pageSource; +} + +interface DisconnectMessage { + readonly type: 'DISCONNECT'; + readonly source: typeof pageSource; +} + +export type PageScriptToContentScriptMessage = + | InitInstancePageScriptToContentScriptMessage + | DisconnectMessage; + +interface InitInstanceContentScriptToBackgroundMessage { + readonly name: 'INIT_INSTANCE'; + readonly instanceId: number; +} + +interface RelayMessage { + readonly name: 'RELAY'; + readonly message: unknown; +} + +export type ContentScriptToBackgroundMessage = + | InitInstanceContentScriptToBackgroundMessage + | RelayMessage; + +function postToBackground(message: ContentScriptToBackgroundMessage) { + bg!.postMessage(message); +} + +function send(message: never) { if (!connected) connect(); if (message.type === 'INIT_INSTANCE') { getOptionsFromBg(); - bg.postMessage({ name: 'INIT_INSTANCE', instanceId: message.instanceId }); + postToBackground({ name: 'INIT_INSTANCE', instanceId: message.instanceId }); } else { - bg.postMessage({ name: 'RELAY', message }); + postToBackground({ name: 'RELAY', message }); } } // Resend messages from the page to the background script -function handleMessages(event) { +function handleMessages(event: MessageEvent) { if (!isAllowed()) return; if (!event || event.source !== window || typeof event.data !== 'object') { return; diff --git a/extension/src/browser/extension/inject/pageScript.js b/extension/src/browser/extension/inject/pageScript.ts similarity index 67% rename from extension/src/browser/extension/inject/pageScript.js rename to extension/src/browser/extension/inject/pageScript.ts index a2b6d5a11d..a3828748cb 100644 --- a/extension/src/browser/extension/inject/pageScript.js +++ b/extension/src/browser/extension/inject/pageScript.ts @@ -1,8 +1,10 @@ import { getActionsArray, evalAction } from '@redux-devtools/utils'; import throttle from 'lodash/throttle'; +import { Action, PreloadedState, Reducer, Store, StoreEnhancer } from 'redux'; +import Immutable from 'immutable'; import createStore from '../../../app/stores/createStore'; import configureStore, { getUrlParam } from '../../../app/stores/enhancerStore'; -import { isAllowed } from '../options/syncOptions'; +import { isAllowed, Options } from '../options/syncOptions'; import Monitor from '../../../app/service/Monitor'; import { noFiltersApplied, @@ -13,7 +15,7 @@ import { } from '../../../app/api/filters'; import notifyErrors from '../../../app/api/notifyErrors'; import importState from '../../../app/api/importState'; -import openWindow from '../../../app/api/openWindow'; +import openWindow, { Position } from '../../../app/api/openWindow'; import generateId from '../../../app/api/generateInstanceId'; import { updateStore, @@ -23,14 +25,21 @@ import { connect, disconnect, isInIframe, - getSeralizeParameter, + getSerializeParameter, + Serialize, } from '../../../app/api'; +import { + InstrumentExt, + LiftedAction, + LiftedState, + PerformAction, +} from '@redux-devtools/instrument'; const source = '@devtools-page'; -let stores = {}; -let reportId; +let stores: { [instanceId: number]: Store> } = {}; +let reportId: string | null | undefined; -function deprecateParam(oldParam, newParam) { +function deprecateParam(oldParam: string, newParam: string) { /* eslint-disable no-console */ console.warn( `${oldParam} parameter is deprecated, use ${newParam} instead: https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md` @@ -38,10 +47,96 @@ function deprecateParam(oldParam, newParam) { /* eslint-enable no-console */ } -const __REDUX_DEVTOOLS_EXTENSION__ = function ( - reducer, - preloadedState, - config +interface SerializeWithImmutable extends Serialize { + readonly immutable?: typeof Immutable; + readonly refs?: (new (data: any) => unknown)[] | null; +} + +export interface ConfigWithExpandedMaxAge { + readonly instanceId?: number; + readonly actionsBlacklist?: string | readonly string[]; + readonly actionsWhitelist?: string | readonly string[]; + readonly serialize?: boolean | SerializeWithImmutable; + readonly serializeState?: + | boolean + | ((key: string, value: unknown) => unknown) + | Serialize; + readonly serializeAction?: + | boolean + | ((key: string, value: unknown) => unknown) + | Serialize; + readonly statesFilter?: (state: S, index: number) => S; + readonly actionsFilter?: >( + action: A, + id: number + ) => A; + readonly stateSanitizer?: (state: S, index: number) => S; + readonly actionSanitizer?: >( + action: A, + id: number + ) => A; + readonly predicate?: >( + state: S, + action: A + ) => boolean; + readonly latency?: number; + readonly getMonitor?: >( + monitor: Monitor + ) => void; + readonly maxAge?: + | number + | (>( + currentLiftedAction: LiftedAction, + previousLiftedState: LiftedState | undefined + ) => number); + readonly trace?: + | boolean + | (>(action: A) => string | undefined); + readonly traceLimit?: number; + readonly shouldCatchErrors?: boolean; + readonly shouldHotReload?: boolean; + readonly shouldRecordChanges?: boolean; + readonly shouldStartLocked?: boolean; + readonly pauseActionType?: unknown; + readonly deserializeState?: (state: S) => S; + readonly deserializeAction?: >(action: A) => A; + readonly name?: string; +} + +export interface Config extends ConfigWithExpandedMaxAge { + readonly maxAge?: number; +} + +interface ReduxDevtoolsExtension { + >( + reducer: Reducer, + preloadedState?: PreloadedState, + config?: Config + ): Store; + (config: Config): StoreEnhancer; + open: (position?: Position) => void; + notifyErrors: (onError: () => boolean) => void; + disconnect: () => void; +} + +declare global { + interface Window { + devToolsOptions: Options; + } +} + +const __REDUX_DEVTOOLS_EXTENSION__ = reduxDevtoolsExtension; + +function reduxDevtoolsExtension>( + reducer?: Reducer, + preloadedState?: PreloadedState, + config?: Config +): Store; +function reduxDevtoolsExtension(config: Config): StoreEnhancer; +function reduxDevtoolsExtension>( + reducer?: Reducer | Config | undefined, + preloadedState?: PreloadedState, + config?: Config ) { /* eslint-disable no-param-reassign */ if (typeof reducer === 'object') { @@ -51,15 +146,15 @@ const __REDUX_DEVTOOLS_EXTENSION__ = function ( /* eslint-enable no-param-reassign */ if (!window.devToolsOptions) window.devToolsOptions = {}; - let store; + let store: Store & InstrumentExt; let errorOccurred = false; - let maxAge; + let maxAge: number | undefined; let actionCreators; let sendingActionId = 1; const instanceId = generateId(config.instanceId); const localFilter = getLocalFilter(config); - const serializeState = getSeralizeParameter(config, 'serializeState'); - const serializeAction = getSeralizeParameter(config, 'serializeAction'); + const serializeState = getSerializeParameter(config, 'serializeState'); + const serializeAction = getSerializeParameter(config, 'serializeAction'); let { statesFilter, actionsFilter, @@ -79,6 +174,19 @@ const __REDUX_DEVTOOLS_EXTENSION__ = function ( actionSanitizer = actionsFilter; // eslint-disable-line no-param-reassign } + const relayState = throttle( + ( + liftedState?: LiftedState | undefined, + libConfig?: unknown + ) => { + relayAction.cancel(); + const state = liftedState || store.liftedStore.getState(); + sendingActionId = state.nextActionId; + relay('STATE', state, undefined, undefined, libConfig); + }, + latency + ); + const monitor = new Monitor(relayState); if (config.getMonitor) { /* eslint-disable no-console */ @@ -95,7 +203,7 @@ const __REDUX_DEVTOOLS_EXTENSION__ = function ( function exportState() { const liftedState = store.liftedStore.getState(); const actionsById = liftedState.actionsById; - const payload = []; + const payload: A[] = []; liftedState.stagedActionIds.slice(1).forEach((id) => { // if (isFiltered(actionsById[id].action, localFilter)) return; payload.push(actionsById[id].action); @@ -113,7 +221,30 @@ const __REDUX_DEVTOOLS_EXTENSION__ = function ( ); } - function relay(type, state, action, nextActionId, libConfig) { + function relay( + type: 'ACTION', + state: S, + action: PerformAction, + nextActionId: number + ): void; + function relay( + type: 'STATE', + state: LiftedState, + action?: undefined, + nextActionId?: undefined, + libConfig?: unknown + ): void; + function relay(type: 'ERROR', message: unknown): void; + function relay(type: 'INIT_INSTANCE'): void; + function relay(type: 'GET_REPORT', reportId: string): void; + function relay(type: 'STOP'): void; + function relay( + type: string, + state?: S | LiftedState | unknown, + action?: PerformAction | undefined, + nextActionId?: number | undefined, + libConfig?: unknown + ) { const message = { type, payload: filterState( @@ -142,13 +273,6 @@ const __REDUX_DEVTOOLS_EXTENSION__ = function ( toContentScript(message, serializeState, serializeAction); } - const relayState = throttle((liftedState, libConfig) => { - relayAction.cancel(); - const state = liftedState || store.liftedStore.getState(); - sendingActionId = state.nextActionId; - relay('STATE', state, undefined, undefined, libConfig); - }, latency); - const relayAction = throttle(() => { const liftedState = store.liftedStore.getState(); const nextActionId = liftedState.nextActionId; @@ -296,8 +420,11 @@ const __REDUX_DEVTOOLS_EXTENSION__ = function ( } } - const filteredActionIds = []; // simple circular buffer of non-excluded actions with fixed maxAge-1 length - const getMaxAge = (liftedAction, liftedState) => { + const filteredActionIds: number[] = []; // simple circular buffer of non-excluded actions with fixed maxAge-1 length + const getMaxAge = ( + liftedAction?: LiftedAction, + liftedState?: LiftedState | undefined + ) => { let m = (config && config.maxAge) || window.devToolsOptions.maxAge || 50; if ( !liftedAction || @@ -311,9 +438,9 @@ const __REDUX_DEVTOOLS_EXTENSION__ = function ( // TODO: check also predicate && !predicate(state, action) with current state maxAge++; } else { - filteredActionIds.push(liftedState.nextActionId); + filteredActionIds.push(liftedState!.nextActionId); if (filteredActionIds.length >= m) { - const stagedActionIds = liftedState.stagedActionIds; + const stagedActionIds = liftedState!.stagedActionIds; let i = 1; while ( maxAge > m && @@ -367,16 +494,16 @@ const __REDUX_DEVTOOLS_EXTENSION__ = function ( relayState(liftedState); } - const enhance = () => (next) => { - return (reducer_, initialState_, enhancer_) => { + const enhance = (): StoreEnhancer => (next) => { + return (reducer_, initialState_) => { if (!isAllowed(window.devToolsOptions)) { - return next(reducer_, initialState_, enhancer_); + return next(reducer_, initialState_); } store = stores[instanceId] = configureStore(next, monitor.reducer, { ...config, maxAge: getMaxAge, - })(reducer_, initialState_, enhancer_); + })(reducer_, initialState_); if (isInIframe()) setTimeout(init, 3000); else init(); @@ -392,10 +519,16 @@ const __REDUX_DEVTOOLS_EXTENSION__ = function ( ); /* eslint-enable no-console */ return createStore(reducer, preloadedState, enhance); -}; +} + +declare global { + interface Window { + __REDUX_DEVTOOLS_EXTENSION__: ReduxDevtoolsExtension; + } +} // noinspection JSAnnotator -window.__REDUX_DEVTOOLS_EXTENSION__ = __REDUX_DEVTOOLS_EXTENSION__; +window.__REDUX_DEVTOOLS_EXTENSION__ = __REDUX_DEVTOOLS_EXTENSION__ as any; window.__REDUX_DEVTOOLS_EXTENSION__.open = openWindow; window.__REDUX_DEVTOOLS_EXTENSION__.updateStore = updateStore(stores); window.__REDUX_DEVTOOLS_EXTENSION__.notifyErrors = notifyErrors; @@ -404,50 +537,6 @@ window.__REDUX_DEVTOOLS_EXTENSION__.listen = setListener; window.__REDUX_DEVTOOLS_EXTENSION__.connect = connect; window.__REDUX_DEVTOOLS_EXTENSION__.disconnect = disconnect; -// Deprecated -/* eslint-disable no-console */ -let varNameDeprecatedWarned; -const varNameDeprecatedWarn = () => { - if (varNameDeprecatedWarned) return; - console.warn( - '`window.devToolsExtension` is deprecated in favor of `window.__REDUX_DEVTOOLS_EXTENSION__`, and will be removed in next version of Redux DevTools: https://git.io/fpEJZ' - ); - varNameDeprecatedWarned = true; -}; -/* eslint-enable no-console */ -window.devToolsExtension = (...args) => { - varNameDeprecatedWarn(); - return __REDUX_DEVTOOLS_EXTENSION__.apply(null, args); -}; -window.devToolsExtension.open = (...args) => { - varNameDeprecatedWarn(); - return openWindow.apply(null, args); -}; -window.devToolsExtension.updateStore = (...args) => { - varNameDeprecatedWarn(); - return updateStore(stores).apply(null, args); -}; -window.devToolsExtension.notifyErrors = (...args) => { - varNameDeprecatedWarn(); - return notifyErrors.apply(null, args); -}; -window.devToolsExtension.send = (...args) => { - varNameDeprecatedWarn(); - return sendMessage.apply(null, args); -}; -window.devToolsExtension.listen = (...args) => { - varNameDeprecatedWarn(); - return setListener.apply(null, args); -}; -window.devToolsExtension.connect = (...args) => { - varNameDeprecatedWarn(); - return connect.apply(null, args); -}; -window.devToolsExtension.disconnect = (...args) => { - varNameDeprecatedWarn(); - return disconnect.apply(null, args); -}; - const preEnhancer = (instanceId) => (next) => (reducer, preloadedState, enhancer) => { const store = next(reducer, preloadedState, enhancer); @@ -464,8 +553,8 @@ const preEnhancer = }; const extensionCompose = - (config) => - (...funcs) => { + (config: Config) => + (...funcs: StoreEnhancer[]) => { return (...args) => { const instanceId = generateId(config.instanceId); return [preEnhancer(instanceId), ...funcs].reduceRight( @@ -475,6 +564,12 @@ const extensionCompose = }; }; +declare global { + interface Window { + __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: unknown; + } +} + window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = (...funcs) => { if (funcs.length === 0) { return __REDUX_DEVTOOLS_EXTENSION__(); diff --git a/extension/src/browser/extension/options/index.tsx b/extension/src/browser/extension/options/index.tsx index 7753aaaafe..5580598532 100644 --- a/extension/src/browser/extension/options/index.tsx +++ b/extension/src/browser/extension/options/index.tsx @@ -6,7 +6,7 @@ import { Options } from './syncOptions'; import '../../views/options.pug'; chrome.runtime.getBackgroundPage((background) => { - const syncOptions = background.syncOptions; + const syncOptions = background!.syncOptions; const saveOption = (name: K, value: Options[K]) => { syncOptions.save(name, value); diff --git a/extension/src/browser/extension/options/syncOptions.ts b/extension/src/browser/extension/options/syncOptions.ts index 051095a586..2164fee1d3 100644 --- a/extension/src/browser/extension/options/syncOptions.ts +++ b/extension/src/browser/extension/options/syncOptions.ts @@ -31,7 +31,11 @@ interface OldOrNewOptions { let options: Options | undefined; let subscribers: ((options: Options) => void)[] = []; -type ToAllTabs = (msg: { readonly options: Options }) => void; +export interface OptionsMessage { + readonly options: Options; +} + +type ToAllTabs = (msg: OptionsMessage) => void; const save = (toAllTabs: ToAllTabs | undefined) => @@ -132,7 +136,13 @@ export const isAllowed = (localOptions = options) => !localOptions.urls || location.href.match(toReg(localOptions.urls)!); -export default function syncOptions(toAllTabs?: ToAllTabs) { +export interface SyncOptions { + readonly save: (key: K, value: Options[K]) => void; + readonly get: (callback: (options: Options) => void) => void; + readonly subscribe: (callback: (options: Options) => void) => void; +} + +export default function syncOptions(toAllTabs?: ToAllTabs): SyncOptions { if (toAllTabs && !options) get(() => {}); // Initialize return { save: save(toAllTabs), diff --git a/extension/src/browser/extension/window/index.tsx b/extension/src/browser/extension/window/index.tsx index 14aaedead2..2af7f6ca91 100644 --- a/extension/src/browser/extension/window/index.tsx +++ b/extension/src/browser/extension/window/index.tsx @@ -16,7 +16,8 @@ getPreloadedState(position, (state) => { preloadedState = state; }); -chrome.runtime.getBackgroundPage(({ store }) => { +chrome.runtime.getBackgroundPage((window) => { + const { store } = window!; const localStore = configureStore(store, position, preloadedState); let name = 'monitor'; if (chrome && chrome.devtools && chrome.devtools.inspectedWindow) { From 82c8f142e673f45e72ffd4040bec998a51198ee6 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Fri, 16 Jul 2021 23:57:57 -0400 Subject: [PATCH 05/25] stash --- extension/src/app/api/filters.ts | 38 ++++++---- extension/src/app/api/importState.ts | 2 +- extension/src/app/api/index.ts | 54 +++++++++++--- extension/src/app/middlewares/api.ts | 21 +++++- .../src/app/middlewares/instanceSelector.ts | 8 ++- extension/src/app/middlewares/panelSync.ts | 2 +- extension/src/app/middlewares/windowSync.ts | 37 ++++++---- .../app/reducers/background/persistStates.ts | 4 +- extension/src/app/service/Monitor.ts | 3 +- extension/src/app/stores/enhancerStore.ts | 25 ++++--- .../browser/extension/inject/contentScript.ts | 7 +- .../browser/extension/inject/pageScript.ts | 72 +++++++++++-------- .../src/instrument.ts | 2 +- 13 files changed, 182 insertions(+), 93 deletions(-) diff --git a/extension/src/app/api/filters.ts b/extension/src/app/api/filters.ts index 4f13a36794..f4f6374d93 100644 --- a/extension/src/app/api/filters.ts +++ b/extension/src/app/api/filters.ts @@ -37,14 +37,14 @@ export function getLocalFilter(config: Config): LocalFilter | undefined { return undefined; } -export const noFiltersApplied = (localFilter) => +export const noFiltersApplied = (localFilter: LocalFilter | undefined) => // !predicate && !localFilter && (!window.devToolsOptions || !window.devToolsOptions.filter || window.devToolsOptions.filter === FilterState.DO_NOT_FILTER); -export function isFiltered(action, localFilter) { +export function isFiltered(action, localFilter: LocalFilter | undefined) { if ( noFiltersApplied(localFilter) || (typeof action !== 'string' && typeof action.type.match !== 'function') @@ -76,23 +76,25 @@ function filterStates(computedStates, stateSanitizer) { })); } -export function filterState( - state, +export function filterState>( + state: LiftedState, type, - localFilter, - stateSanitizer, - actionSanitizer, - nextActionId, - predicate + localFilter: LocalFilter | undefined, + stateSanitizer: ((state: S, index: number) => S) | undefined, + actionSanitizer: ((action: A, id: number) => A) | undefined, + nextActionId: number | undefined, + predicate: ((state: S, action: A) => boolean) | undefined ) { if (type === 'ACTION') { return !stateSanitizer ? state : stateSanitizer(state, nextActionId - 1); } else if (type !== 'STATE') return state; if (predicate || !noFiltersApplied(localFilter)) { - const filteredStagedActionIds = []; - const filteredComputedStates = []; - const sanitizedActionsById = actionSanitizer && {}; + const filteredStagedActionIds: number[] = []; + const filteredComputedStates: { state: S; error?: string | undefined }[] = + []; + const sanitizedActionsById: { [p: number]: PerformAction } | undefined = + actionSanitizer && {}; const { actionsById } = state; const { computedStates } = state; @@ -114,7 +116,7 @@ export function filterState( : liftedState ); if (actionSanitizer) { - sanitizedActionsById[id] = { + sanitizedActionsById![id] = { ...liftedAction, action: actionSanitizer(currAction, id), }; @@ -137,6 +139,14 @@ export function filterState( }; } +export interface PartialLiftedState> { + readonly actionsById: { [actionId: number]: PerformAction }; + readonly computedStates: { state: S; error?: string }[]; + readonly stagedActionIds: readonly number[]; + readonly currentStateIndex: number; + readonly nextActionId: number; +} + export function startingFrom>( sendingActionId: number, state: LiftedState, @@ -148,7 +158,7 @@ export function startingFrom>( predicate: | (>(state: S, action: A) => boolean) | undefined -) { +): LiftedState | PartialLiftedState | undefined { const stagedActionIds = state.stagedActionIds; if (sendingActionId <= stagedActionIds[1]) return state; const index = stagedActionIds.indexOf(sendingActionId); diff --git a/extension/src/app/api/importState.ts b/extension/src/app/api/importState.ts index 02ae74477f..74f8da51d3 100644 --- a/extension/src/app/api/importState.ts +++ b/extension/src/app/api/importState.ts @@ -2,7 +2,7 @@ import mapValues from 'lodash/mapValues'; import jsan from 'jsan'; import seralizeImmutable from '@redux-devtools/serialize/lib/immutable/serialize'; -function deprecate(param) { +function deprecate(param: string) { // eslint-disable-next-line no-console console.warn( `\`${param}\` parameter for Redux DevTools Extension is deprecated. Use \`serialize\` parameter instead: https://github.com/zalmoxisus/redux-devtools-extension/releases/tag/v2.12.1` diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index fc98e74cf0..24d24e9da2 100644 --- a/extension/src/app/api/index.ts +++ b/extension/src/app/api/index.ts @@ -2,11 +2,12 @@ import jsan, { Options } from 'jsan'; import throttle from 'lodash/throttle'; import serializeImmutable from '@redux-devtools/serialize/lib/immutable/serialize'; import { getActionsArray } from '@redux-devtools/utils'; -import { getLocalFilter, isFiltered } from './filters'; +import { getLocalFilter, isFiltered, PartialLiftedState } from './filters'; import importState from './importState'; import generateId from './generateInstanceId'; import { PageScriptToContentScriptMessage } from '../../browser/extension/inject/contentScript'; import { Config } from '../../browser/extension/inject/pageScript'; +import { Action } from 'redux'; const listeners = {}; export const source = '@devtools-page'; @@ -109,7 +110,10 @@ function post(message: PageScriptToContentScriptMessage) { window.postMessage(message, '*'); } -function getStackTrace(config, toExcludeFromTrace: Function | undefined) { +function getStackTrace( + config: Config, + toExcludeFromTrace: Function | undefined +) { if (!config.trace) return undefined; if (typeof config.trace === 'function') return config.trace(); @@ -119,9 +123,9 @@ function getStackTrace(config, toExcludeFromTrace: Function | undefined) { const traceLimit = config.traceLimit; const error = Error(); if (Error.captureStackTrace) { - if (Error.stackTraceLimit < traceLimit) { + if (Error.stackTraceLimit < traceLimit!) { prevStackTraceLimit = Error.stackTraceLimit; - Error.stackTraceLimit = traceLimit; + Error.stackTraceLimit = traceLimit!; } Error.captureStackTrace(error, toExcludeFromTrace); } else { @@ -132,12 +136,12 @@ function getStackTrace(config, toExcludeFromTrace: Function | undefined) { if ( extraFrames || typeof Error.stackTraceLimit !== 'number' || - Error.stackTraceLimit > traceLimit + Error.stackTraceLimit > traceLimit! ) { const frames = stack!.split('\n'); - if (frames.length > traceLimit) { + if (frames.length > traceLimit!) { stack = frames - .slice(0, traceLimit + extraFrames + (frames[0] === 'Error' ? 1 : 0)) + .slice(0, traceLimit! + extraFrames + (frames[0] === 'Error' ? 1 : 0)) .join('\n'); } } @@ -159,10 +163,38 @@ function amendActionType( return { action, timestamp, stack }; } -export function toContentScript( - message, - serializeState: Serialize | undefined, - serializeAction: Serialize | undefined +interface LiftedMessage { + readonly type: 'LIFTED'; + readonly liftedState: { readonly isPaused: boolean }; + readonly instanceId: number; + readonly source: typeof source; +} + +interface PartialStateMessage> { + readonly type: 'PARTIAL_STATE'; + readonly payload: PartialLiftedState; + readonly source: typeof source; + readonly instanceId: number; + readonly maxAge: number; +} + +interface ExportMessage> { + readonly type: 'EXPORT'; + readonly payload: readonly A[]; + readonly committedState: S; + readonly source: typeof source; + readonly instanceId: number; +} + +type ToContentScriptMessage> = + | LiftedMessage + | PartialStateMessage + | ExportMessage; + +export function toContentScript>( + message: ToContentScriptMessage, + serializeState?: Serialize | undefined, + serializeAction?: Serialize | undefined ) { if (message.type === 'ACTION') { message.action = stringify(message.action, serializeAction); diff --git a/extension/src/app/middlewares/api.ts b/extension/src/app/middlewares/api.ts index f861318a1b..306d178945 100644 --- a/extension/src/app/middlewares/api.ts +++ b/extension/src/app/middlewares/api.ts @@ -31,7 +31,7 @@ interface UpdateStateAction { readonly type: typeof UPDATE_STATE; } -type TabMessage = StartAction | StopAction | OptionsMessage; +export type TabMessage = StartAction | StopAction | OptionsMessage; type PanelMessage = NAAction; type MonitorMessage = UpdateStateAction; @@ -80,7 +80,22 @@ function toMonitors( }); } -function toContentScript({ message, action, id, instanceId, state }) { +interface ImportMessage { + readonly message: 'IMPORT'; + readonly id: string | number; + readonly instanceId: string; + readonly state: string; +} + +type ToContentScriptMessage = ImportMessage; + +function toContentScript({ + message, + action, + id, + instanceId, + state, +}: ToContentScriptMessage) { connections.tab[id].postMessage({ type: message, action, @@ -251,7 +266,7 @@ function onConnect(port: chrome.runtime.Port) { id = getId(port.sender!); if (port.sender!.frameId) id = `${id}-${port.sender!.frameId}`; connections.tab[id] = port; - listener = (msg: TabToBackgroundMessage) => { + listener = (msg) => { if (msg.name === 'INIT_INSTANCE') { if (typeof id === 'number') { chrome.pageAction.show(id); diff --git a/extension/src/app/middlewares/instanceSelector.ts b/extension/src/app/middlewares/instanceSelector.ts index cc89f97ca3..fd87bd93ad 100644 --- a/extension/src/app/middlewares/instanceSelector.ts +++ b/extension/src/app/middlewares/instanceSelector.ts @@ -1,4 +1,4 @@ -import { Dispatch, Store } from 'redux'; +import { Dispatch, MiddlewareAPI } from 'redux'; import { SELECT_INSTANCE, UPDATE_STATE, @@ -8,7 +8,7 @@ import { StoreState } from '@redux-devtools/app/lib/reducers'; function selectInstance( tabId: number, - store: Store, + store: MiddlewareAPI, StoreState>, next: Dispatch ) { const instances = store.getState().instances; @@ -33,7 +33,9 @@ function getCurrentTabId(next: (tabId: number) => void) { ); } -export default function popupSelector(store: Store) { +export default function popupSelector( + store: MiddlewareAPI, StoreState> +) { return (next: Dispatch) => (action: StoreAction) => { const result = next(action); if (action.type === UPDATE_STATE) { diff --git a/extension/src/app/middlewares/panelSync.ts b/extension/src/app/middlewares/panelSync.ts index aae303ad9a..7a3c618792 100644 --- a/extension/src/app/middlewares/panelSync.ts +++ b/extension/src/app/middlewares/panelSync.ts @@ -4,7 +4,7 @@ import { SELECT_INSTANCE, } from '@redux-devtools/app/lib/constants/actionTypes'; import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances'; -import { Dispatch, MiddlewareAPI, Store } from 'redux'; +import { Dispatch, MiddlewareAPI } from 'redux'; import { StoreState } from '@redux-devtools/app/lib/reducers'; import { StoreAction } from '@redux-devtools/app/lib/actions'; diff --git a/extension/src/app/middlewares/windowSync.ts b/extension/src/app/middlewares/windowSync.ts index 442d316279..afc79ffab8 100644 --- a/extension/src/app/middlewares/windowSync.ts +++ b/extension/src/app/middlewares/windowSync.ts @@ -3,21 +3,28 @@ import { LIFTED_ACTION, } 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'; -const syncStores = (baseStore) => (store) => (next) => (action) => { - 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 }); - } - return next(action); -}; +const syncStores = + (baseStore: Store) => + (store: MiddlewareAPI>) => + (next: Dispatch) => + (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 }); + } + return next(action); + }; export default syncStores; diff --git a/extension/src/app/reducers/background/persistStates.ts b/extension/src/app/reducers/background/persistStates.ts index 0505da3384..6ef4a36a4b 100644 --- a/extension/src/app/reducers/background/persistStates.ts +++ b/extension/src/app/reducers/background/persistStates.ts @@ -1,4 +1,6 @@ -export default function persistStates(state = false, action) { +import { StoreAction } from '@redux-devtools/app/lib/actions'; + +export default function persistStates(state = false, action: StoreAction) { if (action.type === 'TOGGLE_PERSIST') return !state; return state; } diff --git a/extension/src/app/service/Monitor.ts b/extension/src/app/service/Monitor.ts index 67bf411ff7..210e288d9f 100644 --- a/extension/src/app/service/Monitor.ts +++ b/extension/src/app/service/Monitor.ts @@ -1,5 +1,6 @@ import { Action } from 'redux'; import { LiftedState } from '@redux-devtools/instrument'; +import { StoreAction } from '@redux-devtools/app/lib/actions'; declare global { interface Window { @@ -23,7 +24,7 @@ export default class Monitor> { ) { this.update = update; } - reducer = (state = {}, action) => { + reducer = (state = {}, action: StoreAction) => { if (!this.active) return state; this.lastAction = action.type; if (action.type === 'LOCK_CHANGES') { diff --git a/extension/src/app/stores/enhancerStore.ts b/extension/src/app/stores/enhancerStore.ts index 715c56540a..75d83cd647 100644 --- a/extension/src/app/stores/enhancerStore.ts +++ b/extension/src/app/stores/enhancerStore.ts @@ -1,13 +1,7 @@ import { Action, compose, Reducer, StoreEnhancerStoreCreator } from 'redux'; -import instrument, { - LiftedAction, - LiftedState, -} from '@redux-devtools/instrument'; +import instrument from '@redux-devtools/instrument'; import persistState from '@redux-devtools/core/lib/persistState'; -import { - Config, - ConfigWithExpandedMaxAge, -} from '../../browser/extension/inject/pageScript'; +import { ConfigWithExpandedMaxAge } from '../../browser/extension/inject/pageScript'; export function getUrlParam(key: string) { const matches = window.location.href.match( @@ -16,9 +10,20 @@ export function getUrlParam(key: string) { return matches && matches.length > 0 ? matches[1] : null; } -export default function configureStore( +declare global { + interface Window { + shouldCatchErrors?: boolean; + } +} + +export default function configureStore< + S, + A extends Action, + MonitorState, + MonitorAction extends Action +>( next: StoreEnhancerStoreCreator, - monitorReducer: Reducer, + monitorReducer: Reducer, config: ConfigWithExpandedMaxAge ) { return compose( diff --git a/extension/src/browser/extension/inject/contentScript.ts b/extension/src/browser/extension/inject/contentScript.ts index f243b6dff8..963e7b3c78 100644 --- a/extension/src/browser/extension/inject/contentScript.ts +++ b/extension/src/browser/extension/inject/contentScript.ts @@ -3,6 +3,7 @@ import { getOptionsFromBg, isAllowed, } from '../options/syncOptions'; +import { TabMessage } from '../../../app/middlewares/api'; const source = '@devtools-extension'; const pageSource = '@devtools-page'; // Chrome message limit is 64 MB, but we're using 32 MB to include other object's parts @@ -27,8 +28,8 @@ function connect() { } // Relay background script messages to the page script - bg.onMessage.addListener((message) => { - if (message.action) { + bg.onMessage.addListener((message: TabMessage) => { + if ('action' in message) { window.postMessage( { type: message.type, @@ -39,7 +40,7 @@ function connect() { }, '*' ); - } else if (message.options) { + } else if ('options' in message) { injectOptions(message.options); } else { window.postMessage( diff --git a/extension/src/browser/extension/inject/pageScript.ts b/extension/src/browser/extension/inject/pageScript.ts index a3828748cb..046688a0f2 100644 --- a/extension/src/browser/extension/inject/pageScript.ts +++ b/extension/src/browser/extension/inject/pageScript.ts @@ -1,7 +1,15 @@ import { getActionsArray, evalAction } from '@redux-devtools/utils'; import throttle from 'lodash/throttle'; -import { Action, PreloadedState, Reducer, Store, StoreEnhancer } from 'redux'; +import { + Action, + PreloadedState, + Reducer, + Store, + StoreEnhancer, + StoreEnhancerStoreCreator, +} from 'redux'; import Immutable from 'immutable'; +import { EnhancedStore } from '@redux-devtools/instrument'; import createStore from '../../../app/stores/createStore'; import configureStore, { getUrlParam } from '../../../app/stores/enhancerStore'; import { isAllowed, Options } from '../options/syncOptions'; @@ -29,14 +37,15 @@ import { Serialize, } from '../../../app/api'; import { - InstrumentExt, LiftedAction, LiftedState, PerformAction, } from '@redux-devtools/instrument'; const source = '@devtools-page'; -let stores: { [instanceId: number]: Store> } = {}; +let stores: { + [instanceId: number]: EnhancedStore, unknown>; +} = {}; let reportId: string | null | undefined; function deprecateParam(oldParam: string, newParam: string) { @@ -89,9 +98,7 @@ export interface ConfigWithExpandedMaxAge { currentLiftedAction: LiftedAction, previousLiftedState: LiftedState | undefined ) => number); - readonly trace?: - | boolean - | (>(action: A) => string | undefined); + readonly trace?: boolean | (() => string | undefined); readonly traceLimit?: number; readonly shouldCatchErrors?: boolean; readonly shouldHotReload?: boolean; @@ -113,7 +120,7 @@ interface ReduxDevtoolsExtension { preloadedState?: PreloadedState, config?: Config ): Store; - (config: Config): StoreEnhancer; + (config?: Config): StoreEnhancer; open: (position?: Position) => void; notifyErrors: (onError: () => boolean) => void; disconnect: () => void; @@ -125,15 +132,13 @@ declare global { } } -const __REDUX_DEVTOOLS_EXTENSION__ = reduxDevtoolsExtension; - -function reduxDevtoolsExtension>( +function __REDUX_DEVTOOLS_EXTENSION__>( reducer?: Reducer, preloadedState?: PreloadedState, config?: Config ): Store; -function reduxDevtoolsExtension(config: Config): StoreEnhancer; -function reduxDevtoolsExtension>( +function __REDUX_DEVTOOLS_EXTENSION__(config: Config): StoreEnhancer; +function __REDUX_DEVTOOLS_EXTENSION__>( reducer?: Reducer | Config | undefined, preloadedState?: PreloadedState, config?: Config @@ -146,7 +151,7 @@ function reduxDevtoolsExtension>( /* eslint-enable no-param-reassign */ if (!window.devToolsOptions) window.devToolsOptions = {}; - let store: Store & InstrumentExt; + let store: EnhancedStore; let errorOccurred = false; let maxAge: number | undefined; let actionCreators; @@ -313,7 +318,7 @@ function reduxDevtoolsExtension>( ); sendingActionId = nextActionId; if (typeof payload === 'undefined') return; - if (typeof payload.skippedActionIds !== 'undefined') { + if ('skippedActionIds' in payload) { relay('STATE', payload); return; } @@ -494,23 +499,30 @@ function reduxDevtoolsExtension>( relayState(liftedState); } - const enhance = (): StoreEnhancer => (next) => { - return (reducer_, initialState_) => { - if (!isAllowed(window.devToolsOptions)) { - return next(reducer_, initialState_); - } + const enhance = + () => + ( + next: StoreEnhancerStoreCreator + ) => { + return ( + reducer_: Reducer, + initialState_?: PreloadedState + ) => { + if (!isAllowed(window.devToolsOptions)) { + return next(reducer_, initialState_); + } - store = stores[instanceId] = configureStore(next, monitor.reducer, { - ...config, - maxAge: getMaxAge, - })(reducer_, initialState_); + return configureStore(next, monitor.reducer, { + ...config, + maxAge: getMaxAge, + })(reducer_, initialState_); - if (isInIframe()) setTimeout(init, 3000); - else init(); + if (isInIframe()) setTimeout(init, 3000); + else init(); - return store; + return store; + }; }; - }; if (!reducer) return enhance(); /* eslint-disable no-console */ @@ -538,8 +550,10 @@ window.__REDUX_DEVTOOLS_EXTENSION__.connect = connect; window.__REDUX_DEVTOOLS_EXTENSION__.disconnect = disconnect; const preEnhancer = - (instanceId) => (next) => (reducer, preloadedState, enhancer) => { - const store = next(reducer, preloadedState, enhancer); + (instanceId: number): StoreEnhancer => + (next) => + (reducer, preloadedState) => { + const store = next(reducer, preloadedState); if (stores[instanceId]) { stores[instanceId].initialDispatch = store.dispatch; diff --git a/packages/redux-devtools-instrument/src/instrument.ts b/packages/redux-devtools-instrument/src/instrument.ts index 4f6d35aa01..91a7772fe0 100644 --- a/packages/redux-devtools-instrument/src/instrument.ts +++ b/packages/redux-devtools-instrument/src/instrument.ts @@ -100,7 +100,7 @@ interface ImportStateAction, MonitorState> { type: typeof ActionTypes.IMPORT_STATE; nextLiftedState: LiftedState | readonly A[]; preloadedState?: S; - noRecompute: boolean | undefined; + noRecompute?: boolean | undefined; } interface LockChangesAction { From 86c270fa91adaa387b6802e74a98eb43038e0386 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 17 Jul 2021 08:47:05 -0400 Subject: [PATCH 06/25] Eliminate relay --- extension/src/app/api/filters.ts | 6 - extension/src/app/api/index.ts | 55 ++++++- .../browser/extension/inject/pageScript.ts | 148 ++++++++++-------- 3 files changed, 138 insertions(+), 71 deletions(-) diff --git a/extension/src/app/api/filters.ts b/extension/src/app/api/filters.ts index f4f6374d93..a631281a06 100644 --- a/extension/src/app/api/filters.ts +++ b/extension/src/app/api/filters.ts @@ -78,17 +78,11 @@ function filterStates(computedStates, stateSanitizer) { export function filterState>( state: LiftedState, - type, localFilter: LocalFilter | undefined, stateSanitizer: ((state: S, index: number) => S) | undefined, actionSanitizer: ((action: A, id: number) => A) | undefined, - nextActionId: number | undefined, predicate: ((state: S, action: A) => boolean) | undefined ) { - if (type === 'ACTION') { - return !stateSanitizer ? state : stateSanitizer(state, nextActionId - 1); - } else if (type !== 'STATE') return state; - if (predicate || !noFiltersApplied(localFilter)) { const filteredStagedActionIds: number[] = []; const filteredComputedStates: { state: S; error?: string | undefined }[] = diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index 24d24e9da2..bc7406461c 100644 --- a/extension/src/app/api/index.ts +++ b/extension/src/app/api/index.ts @@ -8,6 +8,7 @@ import generateId from './generateInstanceId'; import { PageScriptToContentScriptMessage } from '../../browser/extension/inject/contentScript'; import { Config } from '../../browser/extension/inject/pageScript'; import { Action } from 'redux'; +import { LiftedState, PerformAction } from '@redux-devtools/instrument'; const listeners = {}; export const source = '@devtools-page'; @@ -186,10 +187,62 @@ interface ExportMessage> { readonly instanceId: number; } +interface ActionMessage> { + readonly type: 'ACTION'; + readonly payload: S; + readonly source: typeof source; + readonly instanceId: number; + readonly action: PerformAction | A; + readonly maxAge: number; + readonly nextActionId: number; +} + +interface StateMessage> { + readonly type: 'STATE'; + readonly payload: LiftedState; + readonly source: typeof source; + readonly instanceId: number; + readonly libConfig?: unknown; +} + +interface ErrorMessage { + readonly type: 'ERROR'; + readonly payload: unknown; + readonly source: typeof source; + readonly instanceId: number; +} + +interface InitInstanceMessage { + readonly type: 'INIT_INSTANCE'; + readonly payload: undefined; + readonly source: typeof source; + readonly instanceId: number; +} + +interface GetReportMessage { + readonly type: 'GET_REPORT'; + readonly payload: string; + readonly source: typeof source; + readonly instanceId: number; +} + +interface StopMessage { + readonly type: 'STOP'; + readonly payload: undefined; + readonly source: typeof source; + readonly instanceId: number; +} + type ToContentScriptMessage> = | LiftedMessage | PartialStateMessage - | ExportMessage; + | ExportMessage + | ActionMessage + | StateMessage + | ErrorMessage + | InitInstanceMessage + | GetReportMessage + | StopMessage; export function toContentScript>( message: ToContentScriptMessage, diff --git a/extension/src/browser/extension/inject/pageScript.ts b/extension/src/browser/extension/inject/pageScript.ts index 046688a0f2..a50b0df458 100644 --- a/extension/src/browser/extension/inject/pageScript.ts +++ b/extension/src/browser/extension/inject/pageScript.ts @@ -187,7 +187,23 @@ function __REDUX_DEVTOOLS_EXTENSION__>( relayAction.cancel(); const state = liftedState || store.liftedStore.getState(); sendingActionId = state.nextActionId; - relay('STATE', state, undefined, undefined, libConfig); + toContentScript( + { + type: 'STATE', + payload: filterState( + state, + localFilter, + stateSanitizer, + actionSanitizer, + predicate + ), + source, + instanceId, + libConfig, + }, + serializeState, + serializeAction + ); }, latency ); @@ -226,58 +242,6 @@ function __REDUX_DEVTOOLS_EXTENSION__>( ); } - function relay( - type: 'ACTION', - state: S, - action: PerformAction, - nextActionId: number - ): void; - function relay( - type: 'STATE', - state: LiftedState, - action?: undefined, - nextActionId?: undefined, - libConfig?: unknown - ): void; - function relay(type: 'ERROR', message: unknown): void; - function relay(type: 'INIT_INSTANCE'): void; - function relay(type: 'GET_REPORT', reportId: string): void; - function relay(type: 'STOP'): void; - function relay( - type: string, - state?: S | LiftedState | unknown, - action?: PerformAction | undefined, - nextActionId?: number | undefined, - libConfig?: unknown - ) { - const message = { - type, - payload: filterState( - state, - type, - localFilter, - stateSanitizer, - actionSanitizer, - nextActionId, - predicate - ), - source, - instanceId, - }; - - if (type === 'ACTION') { - message.action = !actionSanitizer - ? action - : actionSanitizer(action.action, nextActionId - 1); - message.maxAge = getMaxAge(); - message.nextActionId = nextActionId; - } else if (libConfig) { - message.libConfig = libConfig; - } - - toContentScript(message, serializeState, serializeAction); - } - const relayAction = throttle(() => { const liftedState = store.liftedStore.getState(); const nextActionId = liftedState.nextActionId; @@ -298,11 +262,25 @@ function __REDUX_DEVTOOLS_EXTENSION__>( } const state = liftedState.computedStates[liftedState.computedStates.length - 1].state; - relay( - 'ACTION', - state, - liftedState.actionsById[nextActionId - 1], - nextActionId + toContentScript( + { + type: 'ACTION', + payload: !stateSanitizer + ? state + : stateSanitizer(state, nextActionId - 1), + source, + instanceId, + action: !actionSanitizer + ? liftedState.actionsById[nextActionId - 1] + : actionSanitizer( + liftedState.actionsById[nextActionId - 1].action, + nextActionId - 1 + ), + maxAge: getMaxAge(), + nextActionId, + }, + serializeState, + serializeAction ); return; } @@ -319,7 +297,22 @@ function __REDUX_DEVTOOLS_EXTENSION__>( sendingActionId = nextActionId; if (typeof payload === 'undefined') return; if ('skippedActionIds' in payload) { - relay('STATE', payload); + toContentScript( + { + type: 'STATE', + payload: filterState( + payload, + localFilter, + stateSanitizer, + actionSanitizer, + predicate + ), + source, + instanceId, + }, + serializeState, + serializeAction + ); return; } toContentScript( @@ -341,7 +334,12 @@ function __REDUX_DEVTOOLS_EXTENSION__>( const result = evalAction(action, actionCreators); (store.initialDispatch || store.dispatch)(result); } catch (e) { - relay('ERROR', e.message); + toContentScript({ + type: 'ERROR', + payload: e.message, + source, + instanceId, + }); } } @@ -352,7 +350,12 @@ function __REDUX_DEVTOOLS_EXTENSION__>( if (!nextLiftedState) return; store.liftedStore.dispatch({ type: 'IMPORT_STATE', ...nextLiftedState }); } catch (e) { - relay('ERROR', e.message); + toContentScript({ + type: 'ERROR', + payload: e.message, + source, + instanceId, + }); } } @@ -413,7 +416,12 @@ function __REDUX_DEVTOOLS_EXTENSION__>( }); if (reportId) { - relay('GET_REPORT', reportId); + toContentScript({ + type: 'GET_REPORT', + payload: reportId, + source, + instanceId, + }); reportId = null; } return; @@ -421,7 +429,14 @@ function __REDUX_DEVTOOLS_EXTENSION__>( monitor.stop(); relayAction.cancel(); relayState.cancel(); - if (!message.failed) relay('STOP'); + if (!message.failed) { + toContentScript({ + type: 'STOP', + payload: undefined, + source, + instanceId, + }); + } } } @@ -471,7 +486,12 @@ function __REDUX_DEVTOOLS_EXTENSION__>( return true; }); - relay('INIT_INSTANCE'); + toContentScript({ + type: 'INIT_INSTANCE', + payload: undefined, + source, + instanceId, + }); store.subscribe(handleChange); if (typeof reportId === 'undefined') { From 425c78b8fc9df53fae15b94509095b976ad4dda3 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 17 Jul 2021 08:48:38 -0400 Subject: [PATCH 07/25] Fix --- .../browser/extension/inject/pageScript.ts | 80 ++++++++++++------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/extension/src/browser/extension/inject/pageScript.ts b/extension/src/browser/extension/inject/pageScript.ts index a50b0df458..b5f8b223d0 100644 --- a/extension/src/browser/extension/inject/pageScript.ts +++ b/extension/src/browser/extension/inject/pageScript.ts @@ -334,12 +334,16 @@ function __REDUX_DEVTOOLS_EXTENSION__>( const result = evalAction(action, actionCreators); (store.initialDispatch || store.dispatch)(result); } catch (e) { - toContentScript({ - type: 'ERROR', - payload: e.message, - source, - instanceId, - }); + toContentScript( + { + type: 'ERROR', + payload: e.message, + source, + instanceId, + }, + serializeState, + serializeAction + ); } } @@ -350,12 +354,16 @@ function __REDUX_DEVTOOLS_EXTENSION__>( if (!nextLiftedState) return; store.liftedStore.dispatch({ type: 'IMPORT_STATE', ...nextLiftedState }); } catch (e) { - toContentScript({ - type: 'ERROR', - payload: e.message, - source, - instanceId, - }); + toContentScript( + { + type: 'ERROR', + payload: e.message, + source, + instanceId, + }, + serializeState, + serializeAction + ); } } @@ -416,12 +424,16 @@ function __REDUX_DEVTOOLS_EXTENSION__>( }); if (reportId) { - toContentScript({ - type: 'GET_REPORT', - payload: reportId, - source, - instanceId, - }); + toContentScript( + { + type: 'GET_REPORT', + payload: reportId, + source, + instanceId, + }, + serializeState, + serializeAction + ); reportId = null; } return; @@ -430,12 +442,16 @@ function __REDUX_DEVTOOLS_EXTENSION__>( relayAction.cancel(); relayState.cancel(); if (!message.failed) { - toContentScript({ - type: 'STOP', - payload: undefined, - source, - instanceId, - }); + toContentScript( + { + type: 'STOP', + payload: undefined, + source, + instanceId, + }, + serializeState, + serializeAction + ); } } } @@ -486,12 +502,16 @@ function __REDUX_DEVTOOLS_EXTENSION__>( return true; }); - toContentScript({ - type: 'INIT_INSTANCE', - payload: undefined, - source, - instanceId, - }); + toContentScript( + { + type: 'INIT_INSTANCE', + payload: undefined, + source, + instanceId, + }, + serializeState, + serializeAction + ); store.subscribe(handleChange); if (typeof reportId === 'undefined') { From eb35441e173c50fead1080201206b0730c0e7045 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 17 Jul 2021 15:21:14 -0400 Subject: [PATCH 08/25] Define page script to content script messages --- extension/src/app/api/filters.ts | 1 + extension/src/app/api/index.ts | 165 +++++++++++++++--- .../browser/extension/inject/contentScript.ts | 67 ++++--- 3 files changed, 186 insertions(+), 47 deletions(-) diff --git a/extension/src/app/api/filters.ts b/extension/src/app/api/filters.ts index a631281a06..85d015594c 100644 --- a/extension/src/app/api/filters.ts +++ b/extension/src/app/api/filters.ts @@ -139,6 +139,7 @@ export interface PartialLiftedState> { readonly stagedActionIds: readonly number[]; readonly currentStateIndex: number; readonly nextActionId: number; + readonly committedState?: S; } export function startingFrom>( diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index bc7406461c..db266e6a10 100644 --- a/extension/src/app/api/index.ts +++ b/extension/src/app/api/index.ts @@ -5,7 +5,6 @@ import { getActionsArray } from '@redux-devtools/utils'; import { getLocalFilter, isFiltered, PartialLiftedState } from './filters'; import importState from './importState'; import generateId from './generateInstanceId'; -import { PageScriptToContentScriptMessage } from '../../browser/extension/inject/contentScript'; import { Config } from '../../browser/extension/inject/pageScript'; import { Action } from 'redux'; import { LiftedState, PerformAction } from '@redux-devtools/instrument'; @@ -107,7 +106,100 @@ export function getSerializeParameter( return value; } -function post(message: PageScriptToContentScriptMessage) { +interface InitInstancePageScriptToContentScriptMessage { + readonly type: 'INIT_INSTANCE'; + readonly instanceId: number; + readonly source: typeof source; +} + +interface DisconnectMessage { + readonly type: 'DISCONNECT'; + readonly source: typeof source; +} + +interface InitMessage> { + readonly type: 'INIT'; + readonly payload: string; + readonly instanceId: number; + readonly source: typeof source; + action?: string; + name?: string | undefined; + liftedState?: LiftedState; + libConfig?: unknown; +} + +interface SerializedPartialLiftedState> { + readonly stagedActionIds: readonly number[]; + readonly currentStateIndex: number; + readonly nextActionId: number; +} + +interface SerializedPartialStateMessage> { + readonly type: 'PARTIAL_STATE'; + readonly payload: SerializedPartialLiftedState; + readonly source: typeof source; + readonly instanceId: number; + readonly maxAge: number; + readonly actionsById: string; + readonly computedStates: string; + readonly committedState: boolean; +} + +interface SerializedExportMessage { + readonly type: 'EXPORT'; + readonly payload: string; + readonly committedState: string | undefined; + readonly source: typeof source; + readonly instanceId: number; +} + +interface SerializedActionMessage { + readonly type: 'ACTION'; + readonly payload: string; + readonly source: typeof source; + readonly instanceId: number; + readonly action: string; + readonly maxAge: number; + readonly nextActionId: number; +} + +interface SerializedStateMessage> { + readonly type: 'STATE'; + readonly payload: Omit< + LiftedState, + 'actionsById' | 'computedStates' | 'committedState' + >; + readonly source: typeof source; + readonly instanceId: number; + readonly libConfig?: unknown; + readonly actionsById: string; + readonly computedStates: string; + readonly committedState: boolean; +} + +export type PageScriptToContentScriptMessageWithoutDisconnect< + S, + A extends Action +> = + | InitInstancePageScriptToContentScriptMessage + | InitMessage + | LiftedMessage + | SerializedPartialStateMessage + | SerializedExportMessage + | SerializedActionMessage + | SerializedStateMessage + | ErrorMessage + | InitInstanceMessage + | GetReportMessage + | StopMessage; + +export type PageScriptToContentScriptMessage> = + | PageScriptToContentScriptMessageWithoutDisconnect + | DisconnectMessage; + +function post>( + message: PageScriptToContentScriptMessage +) { window.postMessage(message, '*'); } @@ -164,7 +256,7 @@ function amendActionType( return { action, timestamp, stack }; } -interface LiftedMessage { +export interface LiftedMessage { readonly type: 'LIFTED'; readonly liftedState: { readonly isPaused: boolean }; readonly instanceId: number; @@ -250,28 +342,52 @@ export function toContentScript>( serializeAction?: Serialize | undefined ) { if (message.type === 'ACTION') { - message.action = stringify(message.action, serializeAction); - message.payload = stringify(message.payload, serializeState); - } else if (message.type === 'STATE' || message.type === 'PARTIAL_STATE') { + post({ + ...message, + action: stringify(message.action, serializeAction), + payload: stringify(message.payload, serializeState), + }); + } else if (message.type === 'STATE') { + const { actionsById, computedStates, committedState, ...rest } = + message.payload; + post({ + ...message, + payload: rest, + actionsById: stringify(actionsById, serializeAction), + computedStates: stringify(computedStates, serializeState), + committedState: typeof committedState !== 'undefined', + }); + } else if (message.type === 'PARTIAL_STATE') { const { actionsById, computedStates, committedState, ...rest } = message.payload; - message.payload = rest; - message.actionsById = stringify(actionsById, serializeAction); - message.computedStates = stringify(computedStates, serializeState); - message.committedState = typeof committedState !== 'undefined'; + post({ + ...message, + payload: rest, + actionsById: stringify(actionsById, serializeAction), + computedStates: stringify(computedStates, serializeState), + committedState: typeof committedState !== 'undefined', + }); } else if (message.type === 'EXPORT') { - message.payload = stringify(message.payload, serializeAction); - if (typeof message.committedState !== 'undefined') { - message.committedState = stringify( - message.committedState, - serializeState - ); - } + post({ + ...message, + payload: stringify(message.payload, serializeAction), + committedState: + typeof message.committedState !== 'undefined' + ? stringify(message.committedState, serializeState) + : (message.committedState as undefined), + }); + } else { + post(message); } - post(message); } -export function sendMessage(action, state, config, instanceId, name) { +export function sendMessage( + action, + state, + config: Config, + instanceId?: number, + name?: string +) { let amendedAction = action; if (typeof config !== 'object') { // Legacy: sending actions not from connected part @@ -427,8 +543,11 @@ export function connect(preConfig) { sendMessage(amendedAction, amendedState, config); }; - const init = (state, liftedData) => { - const message = { + const init = >( + state: S, + liftedData: LiftedState + ) => { + const message: InitMessage = { type: 'INIT', payload: stringify(state, config.serialize), instanceId: id, @@ -454,8 +573,8 @@ export function connect(preConfig) { post(message); }; - const error = (payload) => { - post({ type: 'ERROR', payload, id, source }); + const error = (payload: unknown) => { + post({ type: 'ERROR', payload, instanceId: id, source }); }; window.addEventListener('message', handleMessages, false); diff --git a/extension/src/browser/extension/inject/contentScript.ts b/extension/src/browser/extension/inject/contentScript.ts index 963e7b3c78..2611ff2cb2 100644 --- a/extension/src/browser/extension/inject/contentScript.ts +++ b/extension/src/browser/extension/inject/contentScript.ts @@ -4,6 +4,11 @@ import { isAllowed, } from '../options/syncOptions'; import { TabMessage } from '../../../app/middlewares/api'; +import { + PageScriptToContentScriptMessage, + PageScriptToContentScriptMessageWithoutDisconnect, +} from '../../../app/api'; +import { Action } from 'redux'; const source = '@devtools-extension'; const pageSource = '@devtools-page'; // Chrome message limit is 64 MB, but we're using 32 MB to include other object's parts @@ -64,21 +69,46 @@ function handleDisconnect() { bg = undefined; } -function tryCatch( - fn: (args: PageScriptToContentScriptMessage) => void, - args: PageScriptToContentScriptMessage +interface SplitMessageBase { + readonly type?: never; +} + +interface SplitMessageStart extends SplitMessageBase { + readonly split: 'start'; +} + +interface SplitMessageChunk extends SplitMessageBase { + readonly instanceId: number; + readonly source: typeof pageSource; + readonly split: 'chunk'; + readonly chunk: [string, string]; +} + +interface SplitMessageEnd extends SplitMessageBase { + readonly instanceId: number; + readonly source: typeof pageSource; + readonly split: 'end'; +} + +type SplitMessage = SplitMessageStart | SplitMessageChunk | SplitMessageEnd; + +function tryCatch>( + fn: (args: PageScriptToContentScriptMessage | SplitMessage) => void, + args: PageScriptToContentScriptMessageWithoutDisconnect ) { try { return fn(args); } catch (err) { if (err.message === 'Message length exceeded maximum allowed length.') { const instanceId = args.instanceId; - const newArgs = { split: 'start' }; - const toSplit = []; + const newArgs: SplitMessageStart = { + split: 'start', + }; + const toSplit: [string, string][] = []; let size = 0; let arg; Object.keys(args).map((key) => { - arg = args[key]; + arg = args[key as keyof typeof args]; if (typeof arg === 'string') { size += arg.length; if (size > maxChromeMsgSize) { @@ -86,7 +116,7 @@ function tryCatch( return; } } - newArgs[key] = arg; + newArgs[key as keyof typeof newArgs] = arg; }); fn(newArgs); for (let i = 0; i < toSplit.length; i++) { @@ -110,21 +140,6 @@ function tryCatch( } } -interface InitInstancePageScriptToContentScriptMessage { - readonly type: 'INIT_INSTANCE'; - readonly instanceId: number; - readonly source: typeof pageSource; -} - -interface DisconnectMessage { - readonly type: 'DISCONNECT'; - readonly source: typeof pageSource; -} - -export type PageScriptToContentScriptMessage = - | InitInstancePageScriptToContentScriptMessage - | DisconnectMessage; - interface InitInstanceContentScriptToBackgroundMessage { readonly name: 'INIT_INSTANCE'; readonly instanceId: number; @@ -143,7 +158,9 @@ function postToBackground(message: ContentScriptToBackgroundMessage) { bg!.postMessage(message); } -function send(message: never) { +function send>( + message: PageScriptToContentScriptMessage | SplitMessage +) { if (!connected) connect(); if (message.type === 'INIT_INSTANCE') { getOptionsFromBg(); @@ -154,7 +171,9 @@ function send(message: never) { } // Resend messages from the page to the background script -function handleMessages(event: MessageEvent) { +function handleMessages>( + event: MessageEvent> +) { if (!isAllowed()) return; if (!event || event.source !== window || typeof event.data !== 'object') { return; From d23bf68b1573932c705ca7f79bd69d646c03376e Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 17 Jul 2021 18:03:28 -0400 Subject: [PATCH 09/25] Define ContentScriptToPageScriptMessage --- extension/src/app/api/index.ts | 22 +-- extension/src/app/middlewares/api.ts | 76 ++++++++-- extension/src/app/service/Monitor.ts | 6 +- .../browser/extension/inject/contentScript.ts | 132 +++++++++++++++--- .../browser/extension/inject/pageScript.ts | 31 ++-- .../redux-devtools-app/src/actions/index.ts | 4 +- .../src/containers/monitors/Dispatcher.tsx | 4 +- .../src/utils/monitorActions.ts | 2 +- 8 files changed, 218 insertions(+), 59 deletions(-) diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index db266e6a10..a3a7d8edff 100644 --- a/extension/src/app/api/index.ts +++ b/extension/src/app/api/index.ts @@ -8,6 +8,7 @@ import generateId from './generateInstanceId'; import { Config } from '../../browser/extension/inject/pageScript'; import { Action } from 'redux'; import { LiftedState, PerformAction } from '@redux-devtools/instrument'; +import { LibConfig } from '@redux-devtools/app/lib/actions'; const listeners = {}; export const source = '@devtools-page'; @@ -125,7 +126,7 @@ interface InitMessage> { action?: string; name?: string | undefined; liftedState?: LiftedState; - libConfig?: unknown; + libConfig?: LibConfig; } interface SerializedPartialLiftedState> { @@ -171,17 +172,15 @@ interface SerializedStateMessage> { >; readonly source: typeof source; readonly instanceId: number; - readonly libConfig?: unknown; + readonly libConfig?: LibConfig; readonly actionsById: string; readonly computedStates: string; readonly committedState: boolean; } - -export type PageScriptToContentScriptMessageWithoutDisconnect< +export type PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance< S, A extends Action > = - | InitInstancePageScriptToContentScriptMessage | InitMessage | LiftedMessage | SerializedPartialStateMessage @@ -193,6 +192,13 @@ export type PageScriptToContentScriptMessageWithoutDisconnect< | GetReportMessage | StopMessage; +export type PageScriptToContentScriptMessageWithoutDisconnect< + S, + A extends Action +> = + | PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance + | InitInstancePageScriptToContentScriptMessage; + export type PageScriptToContentScriptMessage> = | PageScriptToContentScriptMessageWithoutDisconnect | DisconnectMessage; @@ -258,7 +264,7 @@ function amendActionType( export interface LiftedMessage { readonly type: 'LIFTED'; - readonly liftedState: { readonly isPaused: boolean }; + readonly liftedState: { readonly isPaused: boolean | undefined }; readonly instanceId: number; readonly source: typeof source; } @@ -294,7 +300,7 @@ interface StateMessage> { readonly payload: LiftedState; readonly source: typeof source; readonly instanceId: number; - readonly libConfig?: unknown; + readonly libConfig?: LibConfig; } interface ErrorMessage { @@ -447,7 +453,7 @@ export function disconnect() { post({ type: 'DISCONNECT', source }); } -export function connect(preConfig) { +export function connect(preConfig: Config) { const config = preConfig || {}; const id = generateId(config.instanceId); if (!config.instanceId) config.instanceId = id; diff --git a/extension/src/app/middlewares/api.ts b/extension/src/app/middlewares/api.ts index 306d178945..8a88f18f5d 100644 --- a/extension/src/app/middlewares/api.ts +++ b/extension/src/app/middlewares/api.ts @@ -6,20 +6,65 @@ import { } 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 from '../../browser/extension/background/openWindow'; import { getReport } from '../../browser/extension/background/logging'; -import { StoreAction } from '@redux-devtools/app/lib/actions'; -import { Dispatch } from 'redux'; +import { + CustomAction, + DispatchAction as AppDispatchAction, + LiftedActionAction, + StoreAction, +} from '@redux-devtools/app/lib/actions'; +import { Action, Dispatch } from 'redux'; +import { ContentScriptToBackgroundMessage } from '../../browser/extension/inject/contentScript'; + +interface TabMessageBase { + readonly type: string; + readonly state?: string | undefined; + readonly id?: string; +} -interface StartAction { +interface StartAction extends TabMessageBase { readonly type: 'START'; + readonly state: never; + readonly id: never; } -interface StopAction { +interface StopAction extends TabMessageBase { readonly type: 'STOP'; + readonly state: never; + readonly id: never; +} + +interface DispatchAction extends TabMessageBase { + readonly type: 'DISPATCH'; + readonly action: AppDispatchAction; + readonly state: string | undefined; + readonly id: string; +} + +interface ImportAction extends TabMessageBase { + readonly type: 'IMPORT'; + readonly action: undefined; + readonly state: string | undefined; + readonly id: string; +} + +interface ActionAction extends TabMessageBase { + readonly type: 'ACTION'; + readonly action: string | CustomAction; + readonly state: string | undefined; + readonly id: string; +} + +interface ExportAction extends TabMessageBase { + readonly type: 'EXPORT'; + readonly action: undefined; + readonly state: string | undefined; + readonly id: string; } interface NAAction { @@ -31,7 +76,14 @@ interface UpdateStateAction { readonly type: typeof UPDATE_STATE; } -export type TabMessage = StartAction | StopAction | OptionsMessage; +export type TabMessage = + | StartAction + | StopAction + | OptionsMessage + | DispatchAction + | ImportAction + | ActionAction + | ExportAction; type PanelMessage = NAAction; type MonitorMessage = UpdateStateAction; @@ -87,7 +139,7 @@ interface ImportMessage { readonly state: string; } -type ToContentScriptMessage = ImportMessage; +type ToContentScriptMessage = ImportMessage | LiftedActionAction; function toContentScript({ message, @@ -144,13 +196,13 @@ function togglePersist() { } type BackgroundStoreMessage = unknown; -type BackgroundStoreResponse = never; +type BackgroundStoreResponse = { readonly options: Options }; // Receive messages from content scripts function messaging( request: BackgroundStoreMessage, sender: chrome.runtime.MessageSender, - sendResponse: (response?: BackgroundStoreResponse) => void + sendResponse?: (response?: BackgroundStoreResponse) => void ) { let tabId = getId(sender); if (!tabId) return; @@ -168,7 +220,7 @@ function messaging( } if (request.type === 'GET_OPTIONS') { window.syncOptions.get((options) => { - sendResponse({ options }); + sendResponse!({ options }); }); return; } @@ -256,7 +308,7 @@ function disconnect( }; } -function onConnect(port: chrome.runtime.Port) { +function onConnect>(port: chrome.runtime.Port) { let id: number | string; let listener; @@ -266,7 +318,7 @@ function onConnect(port: chrome.runtime.Port) { id = getId(port.sender!); if (port.sender!.frameId) id = `${id}-${port.sender!.frameId}`; connections.tab[id] = port; - listener = (msg) => { + listener = (msg: ContentScriptToBackgroundMessage) => { if (msg.name === 'INIT_INSTANCE') { if (typeof id === 'number') { chrome.pageAction.show(id); @@ -292,7 +344,7 @@ function onConnect(port: chrome.runtime.Port) { return; } if (msg.name === 'RELAY') { - messaging(msg.message, port.sender!, id); + messaging(msg.message, port.sender!); } }; port.onMessage.addListener(listener); diff --git a/extension/src/app/service/Monitor.ts b/extension/src/app/service/Monitor.ts index 210e288d9f..36166e2105 100644 --- a/extension/src/app/service/Monitor.ts +++ b/extension/src/app/service/Monitor.ts @@ -1,6 +1,6 @@ import { Action } from 'redux'; import { LiftedState } from '@redux-devtools/instrument'; -import { StoreAction } from '@redux-devtools/app/lib/actions'; +import { LibConfig, StoreAction } from '@redux-devtools/app/lib/actions'; declare global { interface Window { @@ -11,7 +11,7 @@ declare global { export default class Monitor> { update: ( liftedState?: LiftedState | undefined, - libConfig?: unknown + libConfig?: LibConfig ) => void; active?: boolean; paused?: boolean; @@ -19,7 +19,7 @@ export default class Monitor> { constructor( update: ( liftedState?: LiftedState | undefined, - libConfig?: unknown + libConfig?: LibConfig ) => void ) { this.update = update; diff --git a/extension/src/browser/extension/inject/contentScript.ts b/extension/src/browser/extension/inject/contentScript.ts index 2611ff2cb2..671a7ae9b2 100644 --- a/extension/src/browser/extension/inject/contentScript.ts +++ b/extension/src/browser/extension/inject/contentScript.ts @@ -7,8 +7,13 @@ import { TabMessage } from '../../../app/middlewares/api'; import { PageScriptToContentScriptMessage, PageScriptToContentScriptMessageWithoutDisconnect, + PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance, } from '../../../app/api'; import { Action } from 'redux'; +import { + CustomAction, + DispatchAction as AppDispatchAction, +} from '@redux-devtools/app/lib/actions'; const source = '@devtools-extension'; const pageSource = '@devtools-page'; // Chrome message limit is 64 MB, but we're using 32 MB to include other object's parts @@ -22,6 +27,73 @@ declare global { } } +interface StartAction { + readonly type: 'START'; + readonly state: undefined; + readonly id: undefined; + readonly source: typeof source; +} + +interface StopAction { + readonly type: 'STOP'; + readonly state: undefined; + readonly id: undefined; + readonly source: typeof source; + readonly failed?: boolean; +} + +interface DispatchAction { + readonly type: 'DISPATCH'; + readonly payload: AppDispatchAction; + readonly state: string | undefined; + readonly id: string; + readonly source: typeof source; +} + +interface ImportAction { + readonly type: 'IMPORT'; + readonly payload: undefined; + readonly state: string | undefined; + readonly id: string; + readonly source: typeof source; +} + +interface ActionAction { + readonly type: 'ACTION'; + readonly payload: string | CustomAction; + readonly state: string | undefined; + readonly id: string; + readonly source: typeof source; +} + +interface ExportAction { + readonly type: 'EXPORT'; + readonly payload: undefined; + readonly state: string | undefined; + readonly id: string; + readonly source: typeof source; +} + +interface UpdateAction { + readonly type: 'UPDATE'; + readonly state: string | undefined; + readonly id: string; + readonly source: typeof source; +} + +export type ContentScriptToPageScriptMessage = + | StartAction + | StopAction + | DispatchAction + | ImportAction + | ActionAction + | ExportAction + | UpdateAction; + +function postToPageScript(message: ContentScriptToPageScriptMessage) { + window.postMessage(message, '*'); +} + function connect() { // Connect to the background script connected = true; @@ -35,28 +107,40 @@ function connect() { // Relay background script messages to the page script bg.onMessage.addListener((message: TabMessage) => { if ('action' in message) { - window.postMessage( - { + if (message.type === 'DISPATCH') { + postToPageScript({ type: message.type, payload: message.action, state: message.state, id: message.id, source, - }, - '*' - ); - } else if ('options' in message) { - injectOptions(message.options); - } else { - window.postMessage( - { + }); + } else if (message.type === 'ACTION') { + postToPageScript({ + type: message.type, + payload: message.action, + state: message.state, + id: message.id, + source, + }); + } else { + postToPageScript({ type: message.type, + payload: message.action, state: message.state, id: message.id, source, - }, - '*' - ); + }); + } + } else if ('options' in message) { + injectOptions(message.options); + } else { + postToPageScript({ + type: message.type, + state: message.state, + id: message.id, + source, + }); } }); @@ -93,7 +177,9 @@ interface SplitMessageEnd extends SplitMessageBase { type SplitMessage = SplitMessageStart | SplitMessageChunk | SplitMessageEnd; function tryCatch>( - fn: (args: PageScriptToContentScriptMessage | SplitMessage) => void, + fn: ( + args: PageScriptToContentScriptMessageWithoutDisconnect | SplitMessage + ) => void, args: PageScriptToContentScriptMessageWithoutDisconnect ) { try { @@ -145,21 +231,27 @@ interface InitInstanceContentScriptToBackgroundMessage { readonly instanceId: number; } -interface RelayMessage { +interface RelayMessage> { readonly name: 'RELAY'; - readonly message: unknown; + readonly message: + | PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance + | SplitMessage; } -export type ContentScriptToBackgroundMessage = +export type ContentScriptToBackgroundMessage> = | InitInstanceContentScriptToBackgroundMessage - | RelayMessage; + | RelayMessage; -function postToBackground(message: ContentScriptToBackgroundMessage) { +function postToBackground>( + message: ContentScriptToBackgroundMessage +) { bg!.postMessage(message); } function send>( - message: PageScriptToContentScriptMessage | SplitMessage + message: + | PageScriptToContentScriptMessageWithoutDisconnect + | SplitMessage ) { if (!connected) connect(); if (message.type === 'INIT_INSTANCE') { diff --git a/extension/src/browser/extension/inject/pageScript.ts b/extension/src/browser/extension/inject/pageScript.ts index b5f8b223d0..3fbf7c5c89 100644 --- a/extension/src/browser/extension/inject/pageScript.ts +++ b/extension/src/browser/extension/inject/pageScript.ts @@ -41,6 +41,13 @@ import { LiftedState, PerformAction, } from '@redux-devtools/instrument'; +import { + CustomAction, + DispatchAction, + LibConfig, +} from '@redux-devtools/app/lib/actions'; +import { ContentScriptToPageScriptMessage } from './contentScript'; +import { Features } from '@redux-devtools/app/lib/reducers/instances'; const source = '@devtools-page'; let stores: { @@ -62,10 +69,10 @@ interface SerializeWithImmutable extends Serialize { } export interface ConfigWithExpandedMaxAge { - readonly instanceId?: number; + instanceId?: number; readonly actionsBlacklist?: string | readonly string[]; readonly actionsWhitelist?: string | readonly string[]; - readonly serialize?: boolean | SerializeWithImmutable; + serialize?: boolean | SerializeWithImmutable; readonly serializeState?: | boolean | ((key: string, value: unknown) => unknown) @@ -107,7 +114,9 @@ export interface ConfigWithExpandedMaxAge { readonly pauseActionType?: unknown; readonly deserializeState?: (state: S) => S; readonly deserializeAction?: >(action: A) => A; - readonly name?: string; + name?: string; + readonly autoPause?: boolean; + readonly features?: Features; } export interface Config extends ConfigWithExpandedMaxAge { @@ -182,7 +191,7 @@ function __REDUX_DEVTOOLS_EXTENSION__>( const relayState = throttle( ( liftedState?: LiftedState | undefined, - libConfig?: unknown + libConfig?: LibConfig ) => { relayAction.cancel(); const state = liftedState || store.liftedStore.getState(); @@ -328,8 +337,8 @@ function __REDUX_DEVTOOLS_EXTENSION__>( ); }, latency); - function dispatchRemotely(action) { - if (config.features && !config.features.dispatch) return; + function dispatchRemotely(action: string | CustomAction) { + if (config!.features && !config!.features.dispatch) return; try { const result = evalAction(action, actionCreators); (store.initialDispatch || store.dispatch)(result); @@ -367,7 +376,7 @@ function __REDUX_DEVTOOLS_EXTENSION__>( } } - function dispatchMonitorAction(action) { + function dispatchMonitorAction(action: DispatchAction) { const type = action.type; const features = config.features; if (features) { @@ -393,7 +402,7 @@ function __REDUX_DEVTOOLS_EXTENSION__>( store.liftedStore.dispatch(action); } - function onMessage(message) { + function onMessage(message: ContentScriptToPageScriptMessage) { switch (message.type) { case 'DISPATCH': dispatchMonitorAction(message.payload); @@ -416,10 +425,10 @@ function __REDUX_DEVTOOLS_EXTENSION__>( actionCreators = getActionsArray(config.actionCreators); } relayState(undefined, { - name: config.name || document.title, + name: config!.name || document.title, actionCreators: JSON.stringify(actionCreators), - features: config.features, - serialize: !!config.serialize, + features: config!.features, + serialize: !!config!.serialize, type: 'redux', }); diff --git a/packages/redux-devtools-app/src/actions/index.ts b/packages/redux-devtools-app/src/actions/index.ts index 3eda8bb87d..a6e5ae4326 100644 --- a/packages/redux-devtools-app/src/actions/index.ts +++ b/packages/redux-devtools-app/src/actions/index.ts @@ -268,7 +268,7 @@ export function pauseRecording(status: boolean): LiftedActionDispatchAction { export interface CustomAction { name: string; selected: number; - args: (string | undefined)[]; + args: string[]; rest: string; } export function dispatchRemotely( @@ -354,7 +354,7 @@ export interface ActionCreator { name: string; } -interface LibConfig { +export interface LibConfig { actionCreators?: string; name?: string; type?: string; diff --git a/packages/redux-devtools-app/src/containers/monitors/Dispatcher.tsx b/packages/redux-devtools-app/src/containers/monitors/Dispatcher.tsx index 63ed71e506..4a6a17467e 100644 --- a/packages/redux-devtools-app/src/containers/monitors/Dispatcher.tsx +++ b/packages/redux-devtools-app/src/containers/monitors/Dispatcher.tsx @@ -55,7 +55,7 @@ type Props = DispatchProps & OwnProps; interface State { selected: 'default' | number; customAction: string; - args: (string | undefined)[]; + args: string[]; rest: string; changed: boolean; } @@ -108,7 +108,7 @@ class Dispatcher extends Component { handleArg = (argIndex: number) => (value: string) => { const args = [ ...this.state.args.slice(0, argIndex), - value || undefined, + (value || undefined)!, ...this.state.args.slice(argIndex + 1), ]; this.setState({ args, changed: true }); diff --git a/packages/redux-devtools-app/src/utils/monitorActions.ts b/packages/redux-devtools-app/src/utils/monitorActions.ts index 1e5fc604a1..0889bfcce7 100644 --- a/packages/redux-devtools-app/src/utils/monitorActions.ts +++ b/packages/redux-devtools-app/src/utils/monitorActions.ts @@ -26,7 +26,7 @@ export function nonReduxDispatch( instanceId: string, action: DispatchAction, initialState: string | undefined, - preInstances: InstancesState + preInstances?: InstancesState ) { const instances = preInstances || store.getState().instances; const state = instances.states[instanceId]; From e07896be6a95973318ad7e6a38d1669a4b819256 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 17 Jul 2021 20:02:53 -0400 Subject: [PATCH 10/25] Not required --- extension/src/app/middlewares/api.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extension/src/app/middlewares/api.ts b/extension/src/app/middlewares/api.ts index 8a88f18f5d..7625c8d98d 100644 --- a/extension/src/app/middlewares/api.ts +++ b/extension/src/app/middlewares/api.ts @@ -29,14 +29,14 @@ interface TabMessageBase { interface StartAction extends TabMessageBase { readonly type: 'START'; - readonly state: never; - readonly id: never; + readonly state?: never; + readonly id?: never; } interface StopAction extends TabMessageBase { readonly type: 'STOP'; - readonly state: never; - readonly id: never; + readonly state?: never; + readonly id?: never; } interface DispatchAction extends TabMessageBase { From 3cc1a62177dd65287a5552f381e4daabf75715fc Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 18 Jul 2021 15:19:44 -0400 Subject: [PATCH 11/25] Fill out more types --- extension/src/app/api/importState.ts | 33 +++- extension/src/app/api/index.ts | 96 +++++++----- extension/src/app/api/notifyErrors.ts | 4 +- extension/src/app/api/openWindow.ts | 22 ++- extension/src/app/middlewares/api.ts | 141 ++++++++++++++++-- .../browser/extension/background/logging.ts | 6 +- .../src/browser/extension/devpanel/index.tsx | 2 +- .../browser/extension/inject/contentScript.ts | 7 +- .../browser/extension/inject/pageScript.ts | 15 +- 9 files changed, 251 insertions(+), 75 deletions(-) diff --git a/extension/src/app/api/importState.ts b/extension/src/app/api/importState.ts index 74f8da51d3..bacd30b9c6 100644 --- a/extension/src/app/api/importState.ts +++ b/extension/src/app/api/importState.ts @@ -1,6 +1,11 @@ import mapValues from 'lodash/mapValues'; import jsan from 'jsan'; import seralizeImmutable from '@redux-devtools/serialize/lib/immutable/serialize'; +import { + Config, + SerializeWithImmutable, +} from '../../browser/extension/inject/pageScript'; +import Immutable from 'immutable'; function deprecate(param: string) { // eslint-disable-next-line no-console @@ -9,14 +14,34 @@ function deprecate(param: string) { ); } +interface SerializeWithRequiredImmutable extends SerializeWithImmutable { + readonly immutable: typeof Immutable; +} + +function isSerializeWithImmutable( + serialize: boolean | SerializeWithImmutable +): serialize is SerializeWithRequiredImmutable { + return !!(serialize as SerializeWithImmutable).immutable; +} + +interface SerializeWithRequiredReviver extends SerializeWithImmutable { + readonly reviver: (key: string, value: unknown) => unknown; +} + +function isSerializeWithReviver( + serialize: boolean | SerializeWithImmutable +): serialize is SerializeWithRequiredReviver { + return !!(serialize as SerializeWithImmutable).immutable; +} + export default function importState( - state, - { deserializeState, deserializeAction, serialize } + state: string | undefined, + { deserializeState, deserializeAction, serialize }: Config ) { if (!state) return undefined; let parse = jsan.parse; if (serialize) { - if (serialize.immutable) { + if (isSerializeWithImmutable(serialize)) { parse = (v) => jsan.parse( v, @@ -27,7 +52,7 @@ export default function importState( serialize.reviver ).reviver ); - } else if (serialize.reviver) { + } else if (isSerializeWithReviver(serialize)) { parse = (v) => jsan.parse(v, serialize.reviver); } } diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index a3a7d8edff..d10768f534 100644 --- a/extension/src/app/api/index.ts +++ b/extension/src/app/api/index.ts @@ -9,8 +9,14 @@ import { Config } from '../../browser/extension/inject/pageScript'; import { Action } from 'redux'; import { LiftedState, PerformAction } from '@redux-devtools/instrument'; import { LibConfig } from '@redux-devtools/app/lib/actions'; - -const listeners = {}; +import { ContentScriptToPageScriptMessage } from '../../browser/extension/inject/contentScript'; +import { Position } from './openWindow'; + +const listeners: { + [instanceId: string]: + | ((message: ContentScriptToPageScriptMessage) => void) + | ((message: ContentScriptToPageScriptMessage) => void)[]; +} = {}; export const source = '@devtools-page'; function windowReplacer(key: string, value: unknown) { @@ -129,15 +135,15 @@ interface InitMessage> { libConfig?: LibConfig; } -interface SerializedPartialLiftedState> { +interface SerializedPartialLiftedState { readonly stagedActionIds: readonly number[]; readonly currentStateIndex: number; readonly nextActionId: number; } -interface SerializedPartialStateMessage> { +interface SerializedPartialStateMessage { readonly type: 'PARTIAL_STATE'; - readonly payload: SerializedPartialLiftedState; + readonly payload: SerializedPartialLiftedState; readonly source: typeof source; readonly instanceId: number; readonly maxAge: number; @@ -177,27 +183,41 @@ interface SerializedStateMessage> { readonly computedStates: string; readonly committedState: boolean; } -export type PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance< + +interface OpenMessage { + readonly source: typeof source; + readonly type: 'OPEN'; + readonly position: Position; +} + +export type PageScriptToContentScriptMessageForwardedToMonitors< S, A extends Action > = | InitMessage | LiftedMessage - | SerializedPartialStateMessage + | SerializedPartialStateMessage | SerializedExportMessage | SerializedActionMessage - | SerializedStateMessage + | SerializedStateMessage; + +export type PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance< + S, + A extends Action +> = + | PageScriptToContentScriptMessageForwardedToMonitors | ErrorMessage - | InitInstanceMessage | GetReportMessage - | StopMessage; + | StopMessage + | OpenMessage; export type PageScriptToContentScriptMessageWithoutDisconnect< S, A extends Action > = | PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance - | InitInstancePageScriptToContentScriptMessage; + | InitInstancePageScriptToContentScriptMessage + | InitInstanceMessage; export type PageScriptToContentScriptMessage> = | PageScriptToContentScriptMessageWithoutDisconnect @@ -262,7 +282,7 @@ function amendActionType( return { action, timestamp, stack }; } -export interface LiftedMessage { +interface LiftedMessage { readonly type: 'LIFTED'; readonly liftedState: { readonly isPaused: boolean | undefined }; readonly instanceId: number; @@ -303,7 +323,7 @@ interface StateMessage> { readonly libConfig?: LibConfig; } -interface ErrorMessage { +export interface ErrorMessage { readonly type: 'ERROR'; readonly payload: unknown; readonly source: typeof source; @@ -412,7 +432,7 @@ export function sendMessage( toContentScript(message, config.serialize, config.serialize); } -function handleMessages(event) { +function handleMessages(event: MessageEvent) { if (process.env.BABEL_ENV !== 'test' && (!event || event.source !== window)) { return; } @@ -420,33 +440,38 @@ function handleMessages(event) { if (!message || message.source !== '@devtools-extension') return; Object.keys(listeners).forEach((id) => { if (message.id && id !== message.id) return; - if (typeof listeners[id] === 'function') listeners[id](message); + const listenersForId = listeners[id]; + if (typeof listenersForId === 'function') listenersForId(message); else { - listeners[id].forEach((fn) => { + listenersForId.forEach((fn) => { fn(message); }); } }); } -export function setListener(onMessage, instanceId) { +export function setListener( + onMessage: (message: ContentScriptToPageScriptMessage) => void, + instanceId: number +) { listeners[instanceId] = onMessage; window.addEventListener('message', handleMessages, false); } -const liftListener = (listener, config) => (message) => { - let data = {}; - if (message.type === 'IMPORT') { - data.type = 'DISPATCH'; - data.payload = { - type: 'IMPORT_STATE', - ...importState(message.state, config), - }; - } else { - data = message; - } - listener(data); -}; +const liftListener = + (listener, config: Config) => (message: ContentScriptToPageScriptMessage) => { + let data = {}; + if (message.type === 'IMPORT') { + data.type = 'DISPATCH'; + data.payload = { + type: 'IMPORT_STATE', + ...importState(message.state, config), + }; + } else { + data = message; + } + listener(data); + }; export function disconnect() { window.removeEventListener('message', handleMessages); @@ -471,7 +496,7 @@ export function connect(preConfig: Config) { let delayedActions = []; let delayedStates = []; - const rootListener = (action) => { + const rootListener = (action: ContentScriptToPageScriptMessage) => { if (autoPause) { if (action.type === 'START') isPaused = false; else if (action.type === 'STOP') isPaused = true; @@ -495,11 +520,14 @@ export function connect(preConfig: Config) { const subscribe = (listener) => { if (!listener) return undefined; const liftedListener = liftListener(listener, config); - listeners[id].push(liftedListener); + const listenersForId = listeners[id] as (( + message: ContentScriptToPageScriptMessage + ) => void)[]; + listenersForId.push(liftedListener); return function unsubscribe() { - const index = listeners[id].indexOf(liftedListener); - listeners[id].splice(index, 1); + const index = listenersForId.indexOf(liftedListener); + listenersForId.splice(index, 1); }; }; diff --git a/extension/src/app/api/notifyErrors.ts b/extension/src/app/api/notifyErrors.ts index 8b22694517..569cbb3a78 100644 --- a/extension/src/app/api/notifyErrors.ts +++ b/extension/src/app/api/notifyErrors.ts @@ -1,4 +1,4 @@ -let handleError: () => boolean; +let handleError: (() => boolean) | undefined; let lastTime = 0; function createExpBackoffTimer(step: number) { @@ -42,7 +42,7 @@ function catchErrors(e: ErrorEvent) { postError(e.message); } -export default function notifyErrors(onError: () => boolean) { +export default function notifyErrors(onError?: () => boolean) { handleError = onError; window.addEventListener('error', catchErrors, false); } diff --git a/extension/src/app/api/openWindow.ts b/extension/src/app/api/openWindow.ts index c997d10473..51f9d1458a 100644 --- a/extension/src/app/api/openWindow.ts +++ b/extension/src/app/api/openWindow.ts @@ -1,12 +1,18 @@ +import { Action } from 'redux'; +import { PageScriptToContentScriptMessage } from './index'; + export type Position = 'left' | 'right' | 'bottom' | 'panel' | 'remote'; +function post>( + message: PageScriptToContentScriptMessage +) { + window.postMessage(message, '*'); +} + export default function openWindow(position?: Position) { - window.postMessage( - { - source: '@devtools-page', - type: 'OPEN', - position: position || 'right', - }, - '*' - ); + post({ + source: '@devtools-page', + type: 'OPEN', + position: position || 'right', + }); } diff --git a/extension/src/app/middlewares/api.ts b/extension/src/app/middlewares/api.ts index 7625c8d98d..7e605729fe 100644 --- a/extension/src/app/middlewares/api.ts +++ b/extension/src/app/middlewares/api.ts @@ -15,11 +15,21 @@ import { getReport } from '../../browser/extension/background/logging'; import { CustomAction, DispatchAction as AppDispatchAction, + LibConfig, LiftedActionAction, StoreAction, } from '@redux-devtools/app/lib/actions'; import { Action, Dispatch } from 'redux'; -import { ContentScriptToBackgroundMessage } from '../../browser/extension/inject/contentScript'; +import { + ContentScriptToBackgroundMessage, + SplitMessage, +} from '../../browser/extension/inject/contentScript'; +import { + ErrorMessage, + PageScriptToContentScriptMessageForwardedToMonitors, + PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance, +} from '../api'; +import { LiftedState } from '@redux-devtools/instrument'; interface TabMessageBase { readonly type: string; @@ -72,8 +82,85 @@ interface NAAction { readonly id: string; } -interface UpdateStateAction { +interface InitMessage> { + readonly type: 'INIT'; + readonly payload: string; + readonly instanceId: string; + readonly source: '@devtools-page'; + action?: string; + name?: string | undefined; + liftedState?: LiftedState; + libConfig?: LibConfig; +} + +interface LiftedMessage { + readonly type: 'LIFTED'; + readonly liftedState: { readonly isPaused: boolean | undefined }; + readonly instanceId: string; + readonly source: '@devtools-page'; +} + +interface SerializedPartialLiftedState { + readonly stagedActionIds: readonly number[]; + readonly currentStateIndex: number; + readonly nextActionId: number; +} + +interface SerializedPartialStateMessage { + readonly type: 'PARTIAL_STATE'; + readonly payload: SerializedPartialLiftedState; + readonly source: '@devtools-page'; + readonly instanceId: string; + readonly maxAge: number; + readonly actionsById: string; + readonly computedStates: string; + readonly committedState: boolean; +} + +interface SerializedExportMessage { + readonly type: 'EXPORT'; + readonly payload: string; + readonly committedState: string | undefined; + readonly source: '@devtools-page'; + readonly instanceId: string; +} + +interface SerializedActionMessage { + readonly type: 'ACTION'; + readonly payload: string; + readonly source: '@devtools-page'; + readonly instanceId: string; + readonly action: string; + readonly maxAge: number; + readonly nextActionId: number; +} + +interface SerializedStateMessage> { + readonly type: 'STATE'; + readonly payload: Omit< + LiftedState, + 'actionsById' | 'computedStates' | 'committedState' + >; + readonly source: '@devtools-page'; + readonly instanceId: string; + readonly libConfig?: LibConfig; + readonly actionsById: string; + readonly computedStates: string; + readonly committedState: boolean; +} + +type UpdateStateRequest> = + | InitMessage + | LiftedMessage + | SerializedPartialStateMessage + | SerializedExportMessage + | SerializedActionMessage + | SerializedStateMessage; + +interface UpdateStateAction> { readonly type: typeof UPDATE_STATE; + readonly request?: UpdateStateRequest; + readonly id?: string | number; } export type TabMessage = @@ -84,17 +171,27 @@ export type TabMessage = | ImportAction | ActionAction | ExportAction; -type PanelMessage = NAAction; -type MonitorMessage = UpdateStateAction; +type PanelMessage> = + | NAAction + | ErrorMessage + | UpdateStateAction; +type MonitorMessage> = + | NAAction + | ErrorMessage + | UpdateStateAction; type TabPort = Omit & { postMessage: (message: TabMessage) => void; }; type PanelPort = Omit & { - postMessage: (message: PanelMessage) => void; + postMessage: >( + message: PanelMessage + ) => void; }; type MonitorPort = Omit & { - postMessage: (message: MonitorMessage) => void; + postMessage: >( + message: MonitorMessage + ) => void; }; const CONNECTED = 'socket/CONNECTED'; @@ -108,17 +205,25 @@ const connections: { panel: {}, monitor: {}, }; -const chunks = {}; +const chunks: { + [instanceId: string]: PageScriptToContentScriptMessageForwardedToMonitors< + unknown, + Action + >; +} = {}; let monitors = 0; let isMonitored = false; const getId = (sender: chrome.runtime.MessageSender, name?: string) => sender.tab ? sender.tab.id! : name || sender.id!; -type MonitorAction = NAAction; +type MonitorAction> = + | NAAction + | ErrorMessage + | UpdateStateAction; -function toMonitors( - action: MonitorAction, +function toMonitors>( + action: MonitorAction, tabId?: string | number, verbose?: boolean ) { @@ -195,12 +300,14 @@ function togglePersist() { } } -type BackgroundStoreMessage = unknown; +type BackgroundStoreMessage> = + | PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance + | SplitMessage; type BackgroundStoreResponse = { readonly options: Options }; // Receive messages from content scripts -function messaging( - request: BackgroundStoreMessage, +function messaging>( + request: BackgroundStoreMessage, sender: chrome.runtime.MessageSender, sendResponse?: (response?: BackgroundStoreResponse) => void ) { @@ -259,9 +366,13 @@ function messaging( return; } - const action = { type: UPDATE_STATE, request, id: tabId }; + const action: UpdateStateAction = { + type: UPDATE_STATE, + request, + id: tabId, + }; const instanceId = `${tabId}/${request.instanceId}`; - if (request.split) { + if ('split' in request) { if (request.split === 'start') { chunks[instanceId] = request; return; diff --git a/extension/src/browser/extension/background/logging.ts b/extension/src/browser/extension/background/logging.ts index bc5a231ca0..3873525349 100644 --- a/extension/src/browser/extension/background/logging.ts +++ b/extension/src/browser/extension/background/logging.ts @@ -1,6 +1,10 @@ import { LIFTED_ACTION } from '@redux-devtools/app/lib/constants/actionTypes'; -export function getReport(reportId, tabId, instanceId) { +export function getReport( + reportId: string, + tabId: string | number, + instanceId: number +) { chrome.storage.local.get(['s:hostname', 's:port', 's:secure'], (options) => { if (!options['s:hostname'] || !options['s:port']) return; const url = `${options['s:secure'] ? 'https' : 'http'}://${ diff --git a/extension/src/browser/extension/devpanel/index.tsx b/extension/src/browser/extension/devpanel/index.tsx index 5b58b88497..acf68e3692 100644 --- a/extension/src/browser/extension/devpanel/index.tsx +++ b/extension/src/browser/extension/devpanel/index.tsx @@ -18,7 +18,7 @@ const messageStyle: CSSProperties = { textAlign: 'center', }; -let rendered: boolean; +let rendered: boolean | undefined; let store: Store | undefined; let bgConnection: chrome.runtime.Port; let naTimeout: NodeJS.Timeout; diff --git a/extension/src/browser/extension/inject/contentScript.ts b/extension/src/browser/extension/inject/contentScript.ts index 671a7ae9b2..f163ddffa4 100644 --- a/extension/src/browser/extension/inject/contentScript.ts +++ b/extension/src/browser/extension/inject/contentScript.ts @@ -158,6 +158,8 @@ interface SplitMessageBase { } interface SplitMessageStart extends SplitMessageBase { + readonly instanceId: number; + readonly source: typeof pageSource; readonly split: 'start'; } @@ -174,7 +176,10 @@ interface SplitMessageEnd extends SplitMessageBase { readonly split: 'end'; } -type SplitMessage = SplitMessageStart | SplitMessageChunk | SplitMessageEnd; +export type SplitMessage = + | SplitMessageStart + | SplitMessageChunk + | SplitMessageEnd; function tryCatch>( fn: ( diff --git a/extension/src/browser/extension/inject/pageScript.ts b/extension/src/browser/extension/inject/pageScript.ts index 3fbf7c5c89..930f655e42 100644 --- a/extension/src/browser/extension/inject/pageScript.ts +++ b/extension/src/browser/extension/inject/pageScript.ts @@ -36,11 +36,7 @@ import { getSerializeParameter, Serialize, } from '../../../app/api'; -import { - LiftedAction, - LiftedState, - PerformAction, -} from '@redux-devtools/instrument'; +import { LiftedAction, LiftedState } from '@redux-devtools/instrument'; import { CustomAction, DispatchAction, @@ -63,7 +59,7 @@ function deprecateParam(oldParam: string, newParam: string) { /* eslint-enable no-console */ } -interface SerializeWithImmutable extends Serialize { +export interface SerializeWithImmutable extends Serialize { readonly immutable?: typeof Immutable; readonly refs?: (new (data: any) => unknown)[] | null; } @@ -81,12 +77,12 @@ export interface ConfigWithExpandedMaxAge { | boolean | ((key: string, value: unknown) => unknown) | Serialize; - readonly statesFilter?: (state: S, index: number) => S; + readonly statesFilter?: (state: S, index?: number) => S; readonly actionsFilter?: >( action: A, id: number ) => A; - readonly stateSanitizer?: (state: S, index: number) => S; + readonly stateSanitizer?: (state: S, index?: number) => S; readonly actionSanitizer?: >( action: A, id: number @@ -117,6 +113,7 @@ export interface ConfigWithExpandedMaxAge { name?: string; readonly autoPause?: boolean; readonly features?: Features; + readonly type?: string; } export interface Config extends ConfigWithExpandedMaxAge { @@ -131,7 +128,7 @@ interface ReduxDevtoolsExtension { ): Store; (config?: Config): StoreEnhancer; open: (position?: Position) => void; - notifyErrors: (onError: () => boolean) => void; + notifyErrors: (onError?: () => boolean) => void; disconnect: () => void; } From b1a71b4a723a24303b854bd271c05e3648071bc9 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 18 Jul 2021 15:44:42 -0400 Subject: [PATCH 12/25] More work on types --- extension/src/app/api/index.ts | 4 ++-- extension/src/app/middlewares/api.ts | 19 ++++++++--------- .../src/browser/extension/devpanel/index.tsx | 21 +++++++++++-------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index d10768f534..6015b6b4f9 100644 --- a/extension/src/app/api/index.ts +++ b/extension/src/app/api/index.ts @@ -325,7 +325,7 @@ interface StateMessage> { export interface ErrorMessage { readonly type: 'ERROR'; - readonly payload: unknown; + readonly payload: string; readonly source: typeof source; readonly instanceId: number; } @@ -607,7 +607,7 @@ export function connect(preConfig: Config) { post(message); }; - const error = (payload: unknown) => { + const error = (payload: string) => { post({ type: 'ERROR', payload, instanceId: id, source }); }; diff --git a/extension/src/app/middlewares/api.ts b/extension/src/app/middlewares/api.ts index 7e605729fe..c205d39bc5 100644 --- a/extension/src/app/middlewares/api.ts +++ b/extension/src/app/middlewares/api.ts @@ -157,10 +157,14 @@ type UpdateStateRequest> = | SerializedActionMessage | SerializedStateMessage; +interface EmptyUpdateStateAction { + readonly type: typeof UPDATE_STATE; +} + interface UpdateStateAction> { readonly type: typeof UPDATE_STATE; - readonly request?: UpdateStateRequest; - readonly id?: string | number; + readonly request: UpdateStateRequest; + readonly id: string | number; } export type TabMessage = @@ -171,14 +175,11 @@ export type TabMessage = | ImportAction | ActionAction | ExportAction; -type PanelMessage> = - | NAAction - | ErrorMessage - | UpdateStateAction; -type MonitorMessage> = +export type PanelMessage> = | NAAction | ErrorMessage | UpdateStateAction; +export type MonitorMessage = NAAction | ErrorMessage | EmptyUpdateStateAction; type TabPort = Omit & { postMessage: (message: TabMessage) => void; @@ -189,9 +190,7 @@ type PanelPort = Omit & { ) => void; }; type MonitorPort = Omit & { - postMessage: >( - message: MonitorMessage - ) => void; + postMessage: (message: MonitorMessage) => void; }; const CONNECTED = 'socket/CONNECTED'; diff --git a/extension/src/browser/extension/devpanel/index.tsx b/extension/src/browser/extension/devpanel/index.tsx index acf68e3692..dfb2437236 100644 --- a/extension/src/browser/extension/devpanel/index.tsx +++ b/extension/src/browser/extension/devpanel/index.tsx @@ -7,9 +7,10 @@ import configureStore from '../../../app/stores/panelStore'; import getPreloadedState from '../background/getPreloadedState'; import '../../views/devpanel.pug'; -import { PreloadedState, Store } from 'redux'; +import { Action, PreloadedState, Store } from 'redux'; import { StoreState } from '@redux-devtools/app/lib/reducers'; import { StoreAction } from '@redux-devtools/app/lib/actions'; +import { PanelMessage } from '../../../app/middlewares/api'; const position = location.hash; const messageStyle: CSSProperties = { @@ -96,15 +97,17 @@ function init(id: number) { bgConnection = chrome.runtime.connect({ name: id ? id.toString() : undefined, }); - bgConnection.onMessage.addListener((message) => { - if (message.type === 'NA') { - if (message.id === id) renderNA(); - else store!.dispatch({ type: REMOVE_INSTANCE, id: message.id }); - } else { - if (!rendered) renderDevTools(); - store!.dispatch(message); + bgConnection.onMessage.addListener( + >(message: PanelMessage) => { + if (message.type === 'NA') { + if (message.id === id) renderNA(); + else store!.dispatch({ type: REMOVE_INSTANCE, id: message.id }); + } else { + if (!rendered) renderDevTools(); + store!.dispatch(message); + } } - }); + ); } init(chrome.devtools.inspectedWindow.tabId); From f19e1fd1cbfa932c61f091e0f4d4a7de877c6cdb Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 18 Jul 2021 16:34:30 -0400 Subject: [PATCH 13/25] More type fixes --- .../redux-devtools-app/src/actions/index.ts | 18 ++++++++++-------- .../src/components/InstanceSelector.tsx | 2 +- .../src/middlewares/exportState.ts | 2 +- .../src/reducers/instances.ts | 16 ++++++++-------- .../src/utils/monitorActions.ts | 2 +- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/packages/redux-devtools-app/src/actions/index.ts b/packages/redux-devtools-app/src/actions/index.ts index a6e5ae4326..12c798ac76 100644 --- a/packages/redux-devtools-app/src/actions/index.ts +++ b/packages/redux-devtools-app/src/actions/index.ts @@ -45,6 +45,7 @@ import { Features, State } from '../reducers/instances'; import { MonitorStateMonitorState } from '../reducers/monitor'; import { LiftedAction } from '@redux-devtools/core'; import { Data } from '../reducers/reports'; +import { LiftedState } from '@redux-devtools/instrument'; let monitorReducer: ( monitorProps: unknown, @@ -363,10 +364,10 @@ export interface LibConfig { } export interface RequestBase { - id: string; + id?: string; instanceId?: string; action?: string; - name?: string; + name?: string | undefined; libConfig?: LibConfig; actionsById?: string; computedStates?: string; @@ -376,14 +377,15 @@ export interface RequestBase { } interface InitRequest extends RequestBase { type: 'INIT'; - action: string; + action?: string; + payload?: string; } interface ActionRequest extends RequestBase { type: 'ACTION'; - isExcess: boolean; + isExcess?: boolean; nextActionId: number; maxAge: number; - batched: boolean; + batched?: boolean; } interface StateRequest extends RequestBase { type: 'STATE'; @@ -412,7 +414,7 @@ export type Request = interface UpdateStateAction { type: typeof UPDATE_STATE; request?: Request; - id?: string; + id?: string | number; } interface SetStateAction { @@ -494,8 +496,8 @@ interface UnsubscribeAction { export interface EmitAction { type: typeof EMIT; message: string; - id?: string | false; - instanceId?: string; + id?: string | number | false; + instanceId?: string | number; action?: unknown; state?: unknown; } diff --git a/packages/redux-devtools-app/src/components/InstanceSelector.tsx b/packages/redux-devtools-app/src/components/InstanceSelector.tsx index 357351b704..81680a3804 100644 --- a/packages/redux-devtools-app/src/components/InstanceSelector.tsx +++ b/packages/redux-devtools-app/src/components/InstanceSelector.tsx @@ -9,7 +9,7 @@ type DispatchProps = ResolveThunks; type Props = StateProps & DispatchProps; class InstanceSelector extends Component { - select?: { readonly value: string; readonly label: string }[]; + select?: { readonly value: string; readonly label: string | number }[]; render() { this.select = [{ value: '', label: 'Autoselect instances' }]; diff --git a/packages/redux-devtools-app/src/middlewares/exportState.ts b/packages/redux-devtools-app/src/middlewares/exportState.ts index 40e9cd0a97..ffefe85181 100644 --- a/packages/redux-devtools-app/src/middlewares/exportState.ts +++ b/packages/redux-devtools-app/src/middlewares/exportState.ts @@ -5,7 +5,7 @@ import { Dispatch, MiddlewareAPI } from 'redux'; import { ExportRequest, StoreAction } from '../actions'; import { StoreState } from '../reducers'; -let toExport: string | undefined; +let toExport: string | number | undefined; function download(state: string) { const blob = new Blob([state], { type: 'octet/stream' }); diff --git a/packages/redux-devtools-app/src/reducers/instances.ts b/packages/redux-devtools-app/src/reducers/instances.ts index 9b2aa01e03..85ddde6b0c 100644 --- a/packages/redux-devtools-app/src/reducers/instances.ts +++ b/packages/redux-devtools-app/src/reducers/instances.ts @@ -34,8 +34,8 @@ export interface Features { } export interface Options { - name?: string; - connectionId?: string; + name?: string | number; + connectionId?: string | number; explicitLib?: string; lib?: string; actionCreators?: ActionCreator[]; @@ -57,9 +57,9 @@ export interface State { export interface InstancesState { selected: string | null; - current: string; + current: string | number; sync: boolean; - connections: { [id: string]: string[] }; + connections: { [id: string]: (string | number)[] }; options: { [id: string]: Options }; states: { [id: string]: State }; persisted?: boolean; @@ -86,7 +86,7 @@ export const initialState: InstancesState = { function updateState( state: { [id: string]: State }, request: Request, - id: string, + id: string | number, serialize: boolean | undefined ) { let payload: State = request.payload as State; @@ -268,8 +268,8 @@ function removeState(state: InstancesState, connectionId: string) { function init( { type, action, name, libConfig = {} }: Request, - connectionId: string, - current: string + connectionId: string | number, + current: string | number ): Options { let lib; let actionCreators; @@ -310,7 +310,7 @@ export default function instances( case UPDATE_STATE: { const { request } = action; if (!request) return state; - const connectionId = action.id || request.id; + const connectionId = (action.id || request.id)!; const current = request.instanceId || connectionId; let connections = state.connections; let options = state.options; diff --git a/packages/redux-devtools-app/src/utils/monitorActions.ts b/packages/redux-devtools-app/src/utils/monitorActions.ts index 0889bfcce7..7c295c5767 100644 --- a/packages/redux-devtools-app/src/utils/monitorActions.ts +++ b/packages/redux-devtools-app/src/utils/monitorActions.ts @@ -23,7 +23,7 @@ export function sweep(state: State): State { export function nonReduxDispatch( store: MiddlewareAPI, StoreState>, message: string, - instanceId: string, + instanceId: string | number, action: DispatchAction, initialState: string | undefined, preInstances?: InstancesState From 091571412737cb59ed5d6b8025318d614397e9e9 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 18 Jul 2021 16:56:04 -0400 Subject: [PATCH 14/25] Add type --- extension/src/browser/extension/window/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extension/src/browser/extension/window/index.tsx b/extension/src/browser/extension/window/index.tsx index 2af7f6ca91..d583cbbfa8 100644 --- a/extension/src/browser/extension/window/index.tsx +++ b/extension/src/browser/extension/window/index.tsx @@ -9,6 +9,7 @@ import configureStore from '../../../app/stores/windowStore'; import getPreloadedState from '../background/getPreloadedState'; import '../../views/window.pug'; +import { MonitorMessage } from '../../../app/middlewares/api'; const position = location.hash; let preloadedState: PreloadedState; @@ -24,7 +25,7 @@ chrome.runtime.getBackgroundPage((window) => { name += chrome.devtools.inspectedWindow.tabId; } const bg = chrome.runtime.connect({ name }); - const update = (action) => { + const update = (action?: MonitorMessage) => { localStore.dispatch(action || { type: UPDATE_STATE }); }; bg.onMessage.addListener(update); From 9af6e54649173baadc910c3ae8e2fe7c48aceb07 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 18 Jul 2021 17:44:48 -0400 Subject: [PATCH 15/25] More improvements to types --- extension/src/app/api/filters.ts | 22 ++-- extension/src/app/api/index.ts | 100 ++++++++++++------ .../browser/extension/inject/contentScript.ts | 22 ++++ .../browser/extension/inject/pageScript.ts | 7 +- 4 files changed, 111 insertions(+), 40 deletions(-) diff --git a/extension/src/app/api/filters.ts b/extension/src/app/api/filters.ts index 85d015594c..3bcb356d25 100644 --- a/extension/src/app/api/filters.ts +++ b/extension/src/app/api/filters.ts @@ -44,31 +44,41 @@ export const noFiltersApplied = (localFilter: LocalFilter | undefined) => !window.devToolsOptions.filter || window.devToolsOptions.filter === FilterState.DO_NOT_FILTER); -export function isFiltered(action, localFilter: LocalFilter | undefined) { +export function isFiltered>( + action: A | string, + 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; } const { whitelist, blacklist } = localFilter || window.devToolsOptions || {}; - const actionType = action.type || action; + const actionType = ((action as A).type || action) as string; return ( (whitelist && !actionType.match(whitelist)) || (blacklist && actionType.match(blacklist)) ); } -function filterActions(actionsById, actionSanitizer) { +function filterActions>( + actionsById: { [p: number]: PerformAction }, + actionSanitizer: ((action: A, id: number) => A) | undefined +) { if (!actionSanitizer) return actionsById; - return mapValues(actionsById, (action, id) => ({ + return mapValues(actionsById, (action, id: number) => ({ ...action, action: actionSanitizer(action.action, id), })); } -function filterStates(computedStates, stateSanitizer) { +function filterStates( + computedStates: { state: S; error?: string | undefined }[], + stateSanitizer: ((state: S, index: number) => S) | undefined +) { if (!stateSanitizer) return computedStates; return computedStates.map((state, idx) => ({ ...state, diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index 6015b6b4f9..cee9ea49c7 100644 --- a/extension/src/app/api/index.ts +++ b/extension/src/app/api/index.ts @@ -7,9 +7,16 @@ import importState from './importState'; import generateId from './generateInstanceId'; import { Config } from '../../browser/extension/inject/pageScript'; import { Action } from 'redux'; -import { LiftedState, PerformAction } from '@redux-devtools/instrument'; +import { + EnhancedStore, + LiftedState, + PerformAction, +} from '@redux-devtools/instrument'; import { LibConfig } from '@redux-devtools/app/lib/actions'; -import { ContentScriptToPageScriptMessage } from '../../browser/extension/inject/contentScript'; +import { + ContentScriptToPageScriptMessage, + ListenerMessage, +} from '../../browser/extension/inject/contentScript'; import { Position } from './openWindow'; const listeners: { @@ -267,19 +274,23 @@ function getStackTrace( return stack; } -function amendActionType( - action, - config, +function amendActionType>( + action: A | StructuralPerformAction | string, + config: Config, toExcludeFromTrace: Function | undefined -) { +): StructuralPerformAction { let timestamp = Date.now(); let stack = getStackTrace(config, toExcludeFromTrace); if (typeof action === 'string') { - return { action: { type: action }, timestamp, stack }; + return { action: { type: action } as A, timestamp, stack }; } - if (!action.type) return { action: { type: 'update' }, timestamp, stack }; - if (action.action) return stack ? { stack, ...action } : action; - return { action, timestamp, stack }; + if (!(action as A).type) + return { action: { type: 'update' } as A, timestamp, stack }; + if ((action as StructuralPerformAction).action) + return ( + stack ? { stack, ...action } : action + ) as StructuralPerformAction; + return { action, timestamp, stack } as StructuralPerformAction; } interface LiftedMessage { @@ -305,12 +316,26 @@ interface ExportMessage> { readonly instanceId: number; } +interface StructuralPerformAction> { + readonly action: A; + readonly timestamp?: number; + readonly stack?: string; +} + +type SingleUserAction> = + | PerformAction + | StructuralPerformAction + | A; +type UserAction> = + | SingleUserAction + | readonly SingleUserAction[]; + interface ActionMessage> { readonly type: 'ACTION'; readonly payload: S; readonly source: typeof source; readonly instanceId: number; - readonly action: PerformAction | A; + readonly action: UserAction; readonly maxAge: number; readonly nextActionId: number; } @@ -407,9 +432,9 @@ export function toContentScript>( } } -export function sendMessage( - action, - state, +export function sendMessage>( + action: StructuralPerformAction | StructuralPerformAction[], + state: S, config: Config, instanceId?: number, name?: string @@ -459,18 +484,22 @@ export function setListener( } const liftListener = - (listener, config: Config) => (message: ContentScriptToPageScriptMessage) => { - let data = {}; + >( + listener: (message: ListenerMessage) => void, + config: Config + ) => + (message: ContentScriptToPageScriptMessage) => { if (message.type === 'IMPORT') { - data.type = 'DISPATCH'; - data.payload = { - type: 'IMPORT_STATE', - ...importState(message.state, config), - }; + listener({ + type: 'DISPATCH', + payload: { + type: 'IMPORT_STATE', + ...importState(message.state, config), + }, + }); } else { - data = message; + listener(message); } - listener(data); }; export function disconnect() { @@ -493,8 +522,8 @@ export function connect(preConfig: Config) { const localFilter = getLocalFilter(config); const autoPause = config.autoPause; let isPaused = autoPause; - let delayedActions = []; - let delayedStates = []; + let delayedActions: StructuralPerformAction>[] = []; + let delayedStates: unknown[] = []; const rootListener = (action: ContentScriptToPageScriptMessage) => { if (autoPause) { @@ -517,7 +546,9 @@ export function connect(preConfig: Config) { listeners[id] = [rootListener]; - const subscribe = (listener) => { + const subscribe = >( + listener: (message: ListenerMessage) => void + ) => { if (!listener) return undefined; const liftedListener = liftListener(listener, config); const listenersForId = listeners[id] as (( @@ -541,7 +572,7 @@ export function connect(preConfig: Config) { delayedStates = []; }, latency); - const send = (action, state) => { + const send = >(action: A, state: S) => { if ( isPaused || isFiltered(action, localFilter) || @@ -550,7 +581,7 @@ export function connect(preConfig: Config) { return; } - let amendedAction = action; + let amendedAction: A | StructuralPerformAction = action; const amendedState = config.stateSanitizer ? config.stateSanitizer(state) : state; @@ -561,7 +592,7 @@ export function connect(preConfig: Config) { amendedAction = { action: { type: amendedAction }, timestamp: Date.now(), - }; + } as unknown as A; } } else if (config.actionSanitizer) { amendedAction = config.actionSanitizer(action); @@ -624,8 +655,15 @@ export function connect(preConfig: Config) { }; } -export function updateStore(stores) { - return function (newStore, instanceId) { +export function updateStore>( + stores: { + [K in string | number]: EnhancedStore, unknown>; + } +) { + return function ( + newStore: EnhancedStore, unknown>, + instanceId: number + ) { /* eslint-disable no-console */ console.warn( '`__REDUX_DEVTOOLS_EXTENSION__.updateStore` is deprecated, remove it and just use ' + diff --git a/extension/src/browser/extension/inject/contentScript.ts b/extension/src/browser/extension/inject/contentScript.ts index f163ddffa4..e960c0d5c1 100644 --- a/extension/src/browser/extension/inject/contentScript.ts +++ b/extension/src/browser/extension/inject/contentScript.ts @@ -14,6 +14,7 @@ import { CustomAction, DispatchAction as AppDispatchAction, } from '@redux-devtools/app/lib/actions'; +import { LiftedState } from '@redux-devtools/instrument'; const source = '@devtools-extension'; const pageSource = '@devtools-page'; // Chrome message limit is 64 MB, but we're using 32 MB to include other object's parts @@ -90,6 +91,27 @@ export type ContentScriptToPageScriptMessage = | ExportAction | UpdateAction; +interface ImportStatePayload> { + readonly type: 'IMPORT_STATE'; + readonly nextLiftedState: LiftedState | readonly A[]; + readonly preloadedState?: S; +} + +interface ImportStateDispatchAction> { + readonly type: 'DISPATCH'; + readonly payload: ImportStatePayload; +} + +export type ListenerMessage> = + | StartAction + | StopAction + | DispatchAction + | ImportAction + | ActionAction + | ExportAction + | UpdateAction + | ImportStateDispatchAction; + function postToPageScript(message: ContentScriptToPageScriptMessage) { window.postMessage(message, '*'); } diff --git a/extension/src/browser/extension/inject/pageScript.ts b/extension/src/browser/extension/inject/pageScript.ts index 930f655e42..29252275b2 100644 --- a/extension/src/browser/extension/inject/pageScript.ts +++ b/extension/src/browser/extension/inject/pageScript.ts @@ -47,7 +47,7 @@ import { Features } from '@redux-devtools/app/lib/reducers/instances'; const source = '@devtools-page'; let stores: { - [instanceId: number]: EnhancedStore, unknown>; + [K in string | number]: EnhancedStore, unknown>; } = {}; let reportId: string | null | undefined; @@ -80,12 +80,12 @@ export interface ConfigWithExpandedMaxAge { readonly statesFilter?: (state: S, index?: number) => S; readonly actionsFilter?: >( action: A, - id: number + id?: number ) => A; readonly stateSanitizer?: (state: S, index?: number) => S; readonly actionSanitizer?: >( action: A, - id: number + id?: number ) => A; readonly predicate?: >( state: S, @@ -114,6 +114,7 @@ export interface ConfigWithExpandedMaxAge { readonly autoPause?: boolean; readonly features?: Features; readonly type?: string; + readonly getActionType?: >(action: A) => A; } export interface Config extends ConfigWithExpandedMaxAge { From fcfe18c1db993f6b2032530b674bbd2199edfa6a Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Mon, 19 Jul 2021 23:15:22 -0400 Subject: [PATCH 16/25] More work on types --- .../extension/background/openWindow.ts | 4 ++- .../browser/extension/inject/contentScript.ts | 2 +- .../browser/extension/inject/pageScript.ts | 35 +++++++++++-------- .../extension/inject/pageScriptWrap.ts | 4 ++- .../redux-devtools-app/src/actions/index.ts | 20 ++++++++++- packages/redux-devtools-utils/src/index.ts | 24 ++++++------- 6 files changed, 59 insertions(+), 30 deletions(-) diff --git a/extension/src/browser/extension/background/openWindow.ts b/extension/src/browser/extension/background/openWindow.ts index 0e60a8729c..2793ff8f71 100644 --- a/extension/src/browser/extension/background/openWindow.ts +++ b/extension/src/browser/extension/background/openWindow.ts @@ -62,7 +62,9 @@ export default function openDevToolsWindow(position: DevToolsPosition) { switch (position) { case 'devtools-right': params.left = - window.screen.availLeft + window.screen.availWidth - params.width!; + (window.screen as unknown as { availLeft: number }).availLeft + + window.screen.availWidth - + params.width!; break; case 'devtools-bottom': params.height = 420; diff --git a/extension/src/browser/extension/inject/contentScript.ts b/extension/src/browser/extension/inject/contentScript.ts index e960c0d5c1..8675f1d08c 100644 --- a/extension/src/browser/extension/inject/contentScript.ts +++ b/extension/src/browser/extension/inject/contentScript.ts @@ -214,7 +214,7 @@ function tryCatch>( } catch (err) { if (err.message === 'Message length exceeded maximum allowed length.') { const instanceId = args.instanceId; - const newArgs: SplitMessageStart = { + const newArgs = { split: 'start', }; const toSplit: [string, string][] = []; diff --git a/extension/src/browser/extension/inject/pageScript.ts b/extension/src/browser/extension/inject/pageScript.ts index 29252275b2..6b1fff7db2 100644 --- a/extension/src/browser/extension/inject/pageScript.ts +++ b/extension/src/browser/extension/inject/pageScript.ts @@ -1,7 +1,12 @@ -import { getActionsArray, evalAction } from '@redux-devtools/utils'; +import { + getActionsArray, + evalAction, + ActionCreatorObject, +} from '@redux-devtools/utils'; import throttle from 'lodash/throttle'; import { Action, + ActionCreator, PreloadedState, Reducer, Store, @@ -115,6 +120,9 @@ export interface ConfigWithExpandedMaxAge { readonly features?: Features; readonly type?: string; readonly getActionType?: >(action: A) => A; + readonly actionCreators: { + readonly [key: string]: ActionCreator>; + }; } export interface Config extends ConfigWithExpandedMaxAge { @@ -161,7 +169,7 @@ function __REDUX_DEVTOOLS_EXTENSION__>( let store: EnhancedStore; let errorOccurred = false; let maxAge: number | undefined; - let actionCreators; + let actionCreators: readonly ActionCreatorObject[]; let sendingActionId = 1; const instanceId = generateId(config.instanceId); const localFilter = getLocalFilter(config); @@ -375,26 +383,25 @@ function __REDUX_DEVTOOLS_EXTENSION__>( } function dispatchMonitorAction(action: DispatchAction) { - const type = action.type; - const features = config.features; + const features = config!.features; if (features) { if ( !features.jump && - (type === 'JUMP_TO_STATE' || type === 'JUMP_TO_ACTION') + (action.type === 'JUMP_TO_STATE' || action.type === 'JUMP_TO_ACTION') ) { return; } - if (!features.skip && type === 'TOGGLE_ACTION') return; - if (!features.reorder && type === 'REORDER_ACTION') return; - if (!features.import && type === 'IMPORT_STATE') return; - if (!features.lock && type === 'LOCK_CHANGES') return; - if (!features.pause && type === 'PAUSE_RECORDING') return; + if (!features.skip && action.type === 'TOGGLE_ACTION') return; + if (!features.reorder && action.type === 'REORDER_ACTION') return; + if (!features.import && action.type === 'IMPORT_STATE') return; + if (!features.lock && action.type === 'LOCK_CHANGES') return; + if (!features.pause && action.type === 'PAUSE_RECORDING') return; } - if (type === 'JUMP_TO_STATE') { + if (action.type === 'JUMP_TO_STATE') { const liftedState = store.liftedStore.getState(); const index = liftedState.stagedActionIds.indexOf(action.actionId); if (index === -1) return; - store.liftedStore.dispatch({ type, index }); + store.liftedStore.dispatch({ type: action.type, index }); return; } store.liftedStore.dispatch(action); @@ -419,8 +426,8 @@ function __REDUX_DEVTOOLS_EXTENSION__>( return; case 'START': monitor.start(true); - if (!actionCreators && config.actionCreators) { - actionCreators = getActionsArray(config.actionCreators); + if (!actionCreators && config!.actionCreators) { + actionCreators = getActionsArray(config!.actionCreators); } relayState(undefined, { name: config!.name || document.title, diff --git a/extension/src/browser/extension/inject/pageScriptWrap.ts b/extension/src/browser/extension/inject/pageScriptWrap.ts index 7f28cfea3a..3c7f514735 100644 --- a/extension/src/browser/extension/inject/pageScriptWrap.ts +++ b/extension/src/browser/extension/inject/pageScriptWrap.ts @@ -9,7 +9,9 @@ if (process.env.NODE_ENV === 'production') { } else { s.src = chrome.extension.getURL('page.bundle.js'); s.onload = function () { - this.parentNode.removeChild(this); + (this as HTMLScriptElement).parentNode!.removeChild( + this as HTMLScriptElement + ); }; (document.head || document.documentElement).appendChild(s); } diff --git a/packages/redux-devtools-app/src/actions/index.ts b/packages/redux-devtools-app/src/actions/index.ts index 12c798ac76..4f5d79f590 100644 --- a/packages/redux-devtools-app/src/actions/index.ts +++ b/packages/redux-devtools-app/src/actions/index.ts @@ -120,13 +120,28 @@ export interface LockChangesAction { } export interface ToggleActionAction { type: 'TOGGLE_ACTION'; + id: number; } export interface RollbackAction { type: 'ROLLBACK'; + timestamp: number; } export interface SweepAction { type: 'SWEEP'; } +interface ReorderActionAction { + type: 'REORDER_ACTION'; + actionId: number; + beforeActionId: number; +} +interface ImportStateAction { + type: 'IMPORT_STATE'; + nextLiftedState: + | LiftedState, unknown> + | readonly Action[]; + preloadedState?: unknown; + noRecompute?: boolean | undefined; +} export type DispatchAction = | JumpToStateAction | JumpToActionAction @@ -134,7 +149,9 @@ export type DispatchAction = | LockChangesAction | ToggleActionAction | RollbackAction - | SweepAction; + | SweepAction + | ReorderActionAction + | ImportStateAction; interface LiftedActionActionBase { action?: DispatchAction | string | CustomAction; state?: string; @@ -151,6 +168,7 @@ interface LiftedActionImportAction extends LiftedActionActionBase { message: 'IMPORT'; state: string; preloadedState: unknown | undefined; + id?: string | number; } interface LiftedActionActionAction extends LiftedActionActionBase { type: typeof LIFTED_ACTION; diff --git a/packages/redux-devtools-utils/src/index.ts b/packages/redux-devtools-utils/src/index.ts index fd4ab9fce8..b4af6b8a2a 100644 --- a/packages/redux-devtools-utils/src/index.ts +++ b/packages/redux-devtools-utils/src/index.ts @@ -3,22 +3,24 @@ import jsan from 'jsan'; import { nanoid } from 'nanoid/non-secure'; import { immutableSerialize } from '@redux-devtools/serialize'; import Immutable from 'immutable'; -import { Action } from 'redux'; +import { Action, ActionCreator } from 'redux'; export function generateId(id: string | undefined) { return id || nanoid(7); } +export interface ActionCreatorObject { + readonly name: string; + readonly func: ActionCreator>; + readonly args: readonly string[]; +} + // eslint-disable-next-line @typescript-eslint/ban-types function flatTree( - obj: { [key: string]: (...args: any[]) => unknown }, + obj: { [key: string]: ActionCreator> }, namespace = '' ) { - let functions: { - name: string; - func: (...args: any[]) => unknown; - args: string[]; - }[] = []; + let functions: ActionCreatorObject[] = []; Object.keys(obj).forEach((key) => { const prop = obj[key]; if (typeof prop === 'function') { @@ -63,7 +65,7 @@ export function getMethods(obj: unknown) { } export function getActionsArray(actionCreators: { - [key: string]: (...args: any[]) => unknown; + [key: string]: ActionCreator>; }) { if (Array.isArray(actionCreators)) return actionCreators; return flatTree(actionCreators); @@ -81,10 +83,8 @@ function evalArgs(inArgs: string[], restArgs: string) { } export function evalAction( - action: string | { args: string[]; rest: string; selected: string }, - actionCreators: { - [selected: string]: { func: (...args: any[]) => Action }; - } + action: string | { args: string[]; rest: string; selected: number }, + actionCreators: readonly ActionCreatorObject[] ) { if (typeof action === 'string') { // eslint-disable-next-line @typescript-eslint/no-implied-eval From 386d7ee786d78c707e507f7f428e905e6607c56e Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Tue, 20 Jul 2021 23:32:54 -0400 Subject: [PATCH 17/25] Fix more type errors --- extension/src/app/middlewares/api.ts | 13 +-- extension/src/app/middlewares/panelSync.ts | 10 ++- extension/src/app/middlewares/windowSync.ts | 14 ++- .../src/app/reducers/background/index.ts | 12 +-- .../app/reducers/background/persistStates.ts | 4 +- extension/src/app/reducers/panel/index.ts | 25 +++--- extension/src/app/reducers/window/index.ts | 25 +++--- .../src/app/reducers/window/instances.ts | 7 +- extension/src/app/service/Monitor.ts | 6 +- extension/src/app/stores/backgroundStore.ts | 52 +++++++++++ extension/src/app/stores/windowStore.ts | 30 ++++++- .../src/browser/extension/background/index.ts | 7 +- .../redux-devtools-app/src/actions/index.ts | 88 ++++++++++--------- .../src/reducers/instances.ts | 2 +- .../src/utils/monitorActions.ts | 5 +- 15 files changed, 203 insertions(+), 97 deletions(-) diff --git a/extension/src/app/middlewares/api.ts b/extension/src/app/middlewares/api.ts index c205d39bc5..b09557c690 100644 --- a/extension/src/app/middlewares/api.ts +++ b/extension/src/app/middlewares/api.ts @@ -16,8 +16,6 @@ import { CustomAction, DispatchAction as AppDispatchAction, LibConfig, - LiftedActionAction, - StoreAction, } from '@redux-devtools/app/lib/actions'; import { Action, Dispatch } from 'redux'; import { @@ -30,6 +28,10 @@ import { PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance, } from '../api'; import { LiftedState } from '@redux-devtools/instrument'; +import { + BackgroundAction, + LiftedActionAction, +} from '../stores/backgroundStore'; interface TabMessageBase { readonly type: string; @@ -241,6 +243,7 @@ interface ImportMessage { readonly id: string | number; readonly instanceId: string; readonly state: string; + readonly action?: never; } type ToContentScriptMessage = ImportMessage | LiftedActionAction; @@ -252,7 +255,7 @@ function toContentScript({ instanceId, state, }: ToContentScriptMessage) { - connections.tab[id].postMessage({ + connections.tab[id!].postMessage({ type: message, action, state: nonReduxDispatch(window.store, message, instanceId, action, state), @@ -471,7 +474,7 @@ function onConnect>(port: chrome.runtime.Port) { connections.panel[id] = port; monitorInstances(true, port.name); monitors++; - listener = (msg: StoreAction) => { + listener = (msg: BackgroundAction) => { window.store.dispatch(msg); }; port.onMessage.addListener(listener); @@ -498,7 +501,7 @@ declare global { window.syncOptions = syncOptions(toAllTabs); // Expose to the options page export default function api() { - return (next: Dispatch) => (action: StoreAction) => { + return (next: Dispatch) => (action: BackgroundAction) => { if (action.type === LIFTED_ACTION) toContentScript(action); else if (action.type === 'TOGGLE_PERSIST') togglePersist(); return next(action); diff --git a/extension/src/app/middlewares/panelSync.ts b/extension/src/app/middlewares/panelSync.ts index 7a3c618792..488708b30d 100644 --- a/extension/src/app/middlewares/panelSync.ts +++ b/extension/src/app/middlewares/panelSync.ts @@ -6,15 +6,17 @@ import { 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'; +import { StoreActionWithTogglePersist } from '../stores/windowStore'; function panelDispatcher(bgConnection: chrome.runtime.Port) { let autoselected = false; const tabId = chrome.devtools.inspectedWindow.tabId; - return (store: MiddlewareAPI, StoreState>) => - (next: Dispatch) => - (action: StoreAction) => { + return ( + store: MiddlewareAPI, StoreState> + ) => + (next: Dispatch) => + (action: StoreActionWithTogglePersist) => { const result = next(action); if (!autoselected && action.type === UPDATE_STATE && tabId) { autoselected = true; diff --git a/extension/src/app/middlewares/windowSync.ts b/extension/src/app/middlewares/windowSync.ts index afc79ffab8..8de3be55e5 100644 --- a/extension/src/app/middlewares/windowSync.ts +++ b/extension/src/app/middlewares/windowSync.ts @@ -6,12 +6,18 @@ 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, + StoreActionWithTogglePersist, +} from '../stores/windowStore'; +import { StoreState } from '@redux-devtools/app/lib/reducers'; +import { BackgroundAction } from '../stores/backgroundStore'; const syncStores = - (baseStore: Store) => - (store: MiddlewareAPI>) => - (next: Dispatch) => - (action: StoreAction) => { + (baseStore: Store) => + (store: MiddlewareAPI, StoreState>) => + (next: Dispatch) => + (action: StoreActionWithTogglePersist) => { if (action.type === UPDATE_STATE) { return next({ ...action, diff --git a/extension/src/app/reducers/background/index.ts b/extension/src/app/reducers/background/index.ts index 12124418e4..d2d607edbf 100644 --- a/extension/src/app/reducers/background/index.ts +++ b/extension/src/app/reducers/background/index.ts @@ -1,17 +1,19 @@ -import { combineReducers } from 'redux'; +import { combineReducers, Reducer } from 'redux'; import instances, { InstancesState, } from '@redux-devtools/app/lib/reducers/instances'; import persistStates from './persistStates'; +import { BackgroundAction } from '../../stores/backgroundStore'; export interface BackgroundState { readonly instances: InstancesState; readonly persistStates: boolean; } -const rootReducer = combineReducers({ - instances, - persistStates, -}); +const rootReducer: Reducer = + combineReducers({ + instances, + persistStates, + }); export default rootReducer; diff --git a/extension/src/app/reducers/background/persistStates.ts b/extension/src/app/reducers/background/persistStates.ts index 6ef4a36a4b..90b5c5c7b0 100644 --- a/extension/src/app/reducers/background/persistStates.ts +++ b/extension/src/app/reducers/background/persistStates.ts @@ -1,6 +1,6 @@ -import { StoreAction } from '@redux-devtools/app/lib/actions'; +import { BackgroundAction } from '../../stores/backgroundStore'; -export default function persistStates(state = false, action: StoreAction) { +export default function persistStates(state = false, action: BackgroundAction) { if (action.type === 'TOGGLE_PERSIST') return !state; return state; } diff --git a/extension/src/app/reducers/panel/index.ts b/extension/src/app/reducers/panel/index.ts index b628688d7a..a463a24e01 100644 --- a/extension/src/app/reducers/panel/index.ts +++ b/extension/src/app/reducers/panel/index.ts @@ -1,4 +1,4 @@ -import { combineReducers } from 'redux'; +import { combineReducers, Reducer } from 'redux'; import instances from '@redux-devtools/app/lib/reducers/instances'; import monitor from '@redux-devtools/app/lib/reducers/monitor'; import notification from '@redux-devtools/app/lib/reducers/notification'; @@ -8,17 +8,18 @@ import theme from '@redux-devtools/app/lib/reducers/theme'; import connection from '@redux-devtools/app/lib/reducers/connection'; import socket from '@redux-devtools/app/lib/reducers/socket'; import { StoreState } from '@redux-devtools/app/lib/reducers'; -import { StoreAction } from '@redux-devtools/app/lib/actions'; +import { StoreActionWithTogglePersist } from '../../stores/windowStore'; -const rootReducer = combineReducers({ - instances, - monitor, - reports, - notification, - section, - theme, - connection, - socket, -}); +const rootReducer: Reducer = + combineReducers({ + instances, + monitor, + reports, + notification, + section, + theme, + connection, + socket, + }); export default rootReducer; diff --git a/extension/src/app/reducers/window/index.ts b/extension/src/app/reducers/window/index.ts index c715b55a7c..c4bf93291a 100644 --- a/extension/src/app/reducers/window/index.ts +++ b/extension/src/app/reducers/window/index.ts @@ -1,4 +1,4 @@ -import { combineReducers } from 'redux'; +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'; @@ -8,17 +8,18 @@ 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 { StoreAction } from '@redux-devtools/app/lib/actions'; +import { WindowStoreAction } from '../../stores/windowStore'; -const rootReducer = combineReducers({ - instances, - monitor, - socket, - reports, - notification, - section, - theme, - connection, -}); +const rootReducer: Reducer = + combineReducers({ + instances, + monitor, + socket, + reports, + notification, + section, + theme, + connection, + }); export default rootReducer; diff --git a/extension/src/app/reducers/window/instances.ts b/extension/src/app/reducers/window/instances.ts index 6a50d08b07..f95f098259 100644 --- a/extension/src/app/reducers/window/instances.ts +++ b/extension/src/app/reducers/window/instances.ts @@ -7,9 +7,12 @@ import { SELECT_INSTANCE, LIFTED_ACTION, } from '@redux-devtools/app/lib/constants/actionTypes'; -import { StoreAction } from '@redux-devtools/app/lib/actions'; +import { WindowStoreAction } from '../../stores/windowStore'; -export default function instances(state = initialState, action: StoreAction) { +export default function instances( + state = initialState, + action: WindowStoreAction +) { switch (action.type) { case UPDATE_STATE: return { ...action.instances, selected: state.selected }; diff --git a/extension/src/app/service/Monitor.ts b/extension/src/app/service/Monitor.ts index 36166e2105..a3d0dd33ba 100644 --- a/extension/src/app/service/Monitor.ts +++ b/extension/src/app/service/Monitor.ts @@ -1,6 +1,6 @@ import { Action } from 'redux'; import { LiftedState } from '@redux-devtools/instrument'; -import { LibConfig, StoreAction } from '@redux-devtools/app/lib/actions'; +import { DispatchAction, LibConfig } from '@redux-devtools/app/lib/actions'; declare global { interface Window { @@ -15,6 +15,8 @@ export default class Monitor> { ) => void; active?: boolean; paused?: boolean; + lastAction?: string; + waitingTimeout?: number; constructor( update: ( @@ -24,7 +26,7 @@ export default class Monitor> { ) { this.update = update; } - reducer = (state = {}, action: StoreAction) => { + reducer = (state = {}, action: DispatchAction) => { if (!this.active) return state; this.lastAction = action.type; if (action.type === 'LOCK_CHANGES') { diff --git a/extension/src/app/stores/backgroundStore.ts b/extension/src/app/stores/backgroundStore.ts index 62638cb311..5b5ec8e4d7 100644 --- a/extension/src/app/stores/backgroundStore.ts +++ b/extension/src/app/stores/backgroundStore.ts @@ -1,6 +1,58 @@ import { createStore, applyMiddleware, PreloadedState } from 'redux'; import rootReducer, { BackgroundState } from '../reducers/background'; import api from '../middlewares/api'; +import { LIFTED_ACTION } from '@redux-devtools/app/lib/constants/actionTypes'; +import { + CustomAction, + DispatchAction, + StoreActionWithoutLiftedAction, +} from '@redux-devtools/app/lib/actions'; + +interface LiftedActionActionBase { + action?: DispatchAction | string | CustomAction; + state?: string; + toAll?: boolean; + readonly instanceId: string | number; + readonly id: string | number | undefined; +} +interface LiftedActionDispatchAction extends LiftedActionActionBase { + type: typeof LIFTED_ACTION; + message: 'DISPATCH'; + action: DispatchAction; + toAll?: boolean; +} +interface LiftedActionImportAction extends LiftedActionActionBase { + type: typeof LIFTED_ACTION; + message: 'IMPORT'; + state: string; + preloadedState?: unknown | undefined; +} +interface LiftedActionActionAction extends LiftedActionActionBase { + type: typeof LIFTED_ACTION; + message: 'ACTION'; + action: string | CustomAction; +} +interface LiftedActionExportAction extends LiftedActionActionBase { + type: typeof LIFTED_ACTION; + message: 'EXPORT'; + toExport: boolean; +} +export type LiftedActionAction = + | LiftedActionDispatchAction + | LiftedActionImportAction + | LiftedActionActionAction + | LiftedActionExportAction; + +interface TogglePersistAction { + readonly type: 'TOGGLE_PERSIST'; + readonly instanceId: string | number; + readonly id: string | number | undefined; +} + +export type BackgroundAction = + | StoreActionWithoutLiftedAction + | LiftedActionAction + | TogglePersistAction; export default function configureStore( preloadedState?: PreloadedState diff --git a/extension/src/app/stores/windowStore.ts b/extension/src/app/stores/windowStore.ts index 99aed5f31f..9eb8b351c5 100644 --- a/extension/src/app/stores/windowStore.ts +++ b/extension/src/app/stores/windowStore.ts @@ -4,23 +4,45 @@ import { applyMiddleware, Store, PreloadedState, + StoreEnhancer, } from 'redux'; 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 { StoreState } from '@redux-devtools/app/lib/reducers'; -import { StoreAction } from '@redux-devtools/app/lib/actions'; +import { + StoreAction, + 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'; + +export interface TogglePersistAction { + readonly type: 'TOGGLE_PERSIST'; +} + +export type StoreActionWithTogglePersist = StoreAction | TogglePersistAction; + +interface ExpandedUpdateStateAction extends UpdateStateAction { + readonly instances: InstancesState; +} + +export type WindowStoreAction = + | StoreActionWithoutUpdateState + | TogglePersistAction + | ExpandedUpdateStateAction; export default function configureStore( - baseStore: Store, + baseStore: Store, position: string, preloadedState: PreloadedState ) { - let enhancer; + let enhancer: StoreEnhancer; const middlewares = [exportState, api, syncStores(baseStore)]; if (!position || position === '#popup') { // select current tab instance for devPanel and pageAction @@ -33,7 +55,7 @@ export default function configureStore( applyMiddleware(...middlewares), window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() - : (noop) => noop + : (noop: unknown) => noop ); } const store = createStore(rootReducer, preloadedState, enhancer); diff --git a/extension/src/browser/extension/background/index.ts b/extension/src/browser/extension/background/index.ts index 307e2c4f30..165b1af09c 100644 --- a/extension/src/browser/extension/background/index.ts +++ b/extension/src/browser/extension/background/index.ts @@ -1,6 +1,7 @@ import { Store } from 'redux'; -import { StoreAction } from '@redux-devtools/app/lib/actions'; -import configureStore from '../../../app/stores/backgroundStore'; +import configureStore, { + BackgroundAction, +} from '../../../app/stores/backgroundStore'; import openDevToolsWindow, { DevToolsPosition } from './openWindow'; import { createMenu, removeMenu } from './contextMenus'; import syncOptions from '../options/syncOptions'; @@ -8,7 +9,7 @@ import { BackgroundState } from '../../../app/reducers/background'; declare global { interface Window { - store: Store; + store: Store; } } diff --git a/packages/redux-devtools-app/src/actions/index.ts b/packages/redux-devtools-app/src/actions/index.ts index 4f5d79f590..95cd5d9d29 100644 --- a/packages/redux-devtools-app/src/actions/index.ts +++ b/packages/redux-devtools-app/src/actions/index.ts @@ -54,7 +54,7 @@ let monitorReducer: ( ) => unknown; let monitorProps: unknown = {}; -interface ChangeSectionAction { +export interface ChangeSectionAction { readonly type: typeof CHANGE_SECTION; readonly section: string; } @@ -70,7 +70,7 @@ interface ChangeThemeFormData { interface ChangeThemeData { readonly formData: ChangeThemeFormData; } -interface ChangeThemeAction { +export interface ChangeThemeAction { readonly type: typeof CHANGE_THEME; readonly theme: Theme; readonly scheme: Scheme; @@ -163,19 +163,18 @@ export interface LiftedActionDispatchAction extends LiftedActionActionBase { action: DispatchAction; toAll?: boolean; } -interface LiftedActionImportAction extends LiftedActionActionBase { +export interface LiftedActionImportAction extends LiftedActionActionBase { type: typeof LIFTED_ACTION; message: 'IMPORT'; state: string; preloadedState: unknown | undefined; - id?: string | number; } -interface LiftedActionActionAction extends LiftedActionActionBase { +export interface LiftedActionActionAction extends LiftedActionActionBase { type: typeof LIFTED_ACTION; message: 'ACTION'; action: string | CustomAction; } -interface LiftedActionExportAction extends LiftedActionActionBase { +export interface LiftedActionExportAction extends LiftedActionActionBase { type: typeof LIFTED_ACTION; message: 'EXPORT'; toExport: boolean; @@ -211,15 +210,15 @@ export function liftedDispatch( } as LiftedActionDispatchAction; } -interface SelectInstanceAction { +export interface SelectInstanceAction { type: typeof SELECT_INSTANCE; - selected: string; + selected: string | number; } export function selectInstance(selected: string): SelectInstanceAction { return { type: SELECT_INSTANCE, selected }; } -interface SelectMonitorAction { +export interface SelectMonitorAction { type: typeof SELECT_MONITOR; monitor: string; monitorState?: MonitorStateMonitorState; @@ -238,7 +237,7 @@ interface NextState { subTabName: string; inspectedStatePath?: string[]; } -interface UpdateMonitorStateAction { +export interface UpdateMonitorStateAction { type: typeof UPDATE_MONITOR_STATE; nextState: NextState; } @@ -259,7 +258,7 @@ export function importState( return { type: LIFTED_ACTION, message: 'IMPORT', state, preloadedState }; } -interface ExportAction { +export interface ExportAction { type: typeof EXPORT; } export function exportState(): ExportAction { @@ -296,28 +295,28 @@ export function dispatchRemotely( return { type: LIFTED_ACTION, message: 'ACTION', action }; } -interface TogglePersistAction { +export interface TogglePersistAction { type: typeof TOGGLE_PERSIST; } export function togglePersist(): TogglePersistAction { return { type: TOGGLE_PERSIST }; } -interface ToggleSyncAction { +export interface ToggleSyncAction { type: typeof TOGGLE_SYNC; } export function toggleSync(): ToggleSyncAction { return { type: TOGGLE_SYNC }; } -interface ToggleSliderAction { +export interface ToggleSliderAction { type: typeof TOGGLE_SLIDER; } export function toggleSlider(): ToggleSliderAction { return { type: TOGGLE_SLIDER }; } -interface ToggleDispatcherAction { +export interface ToggleDispatcherAction { type: typeof TOGGLE_DISPATCHER; } export function toggleDispatcher(): ToggleDispatcherAction { @@ -331,7 +330,7 @@ export interface ConnectionOptions { readonly port: number; readonly secure: boolean; } -interface ReconnectAction { +export interface ReconnectAction { readonly type: typeof RECONNECT; readonly options: ConnectionOptions; } @@ -345,7 +344,7 @@ interface Notification { readonly type: 'error'; readonly message: string; } -interface ShowNotificationAction { +export interface ShowNotificationAction { readonly type: typeof SHOW_NOTIFICATION; readonly notification: Notification; } @@ -353,14 +352,14 @@ export function showNotification(message: string): ShowNotificationAction { return { type: SHOW_NOTIFICATION, notification: { type: 'error', message } }; } -interface ClearNotificationAction { +export interface ClearNotificationAction { readonly type: typeof CLEAR_NOTIFICATION; } export function clearNotification(): ClearNotificationAction { return { type: CLEAR_NOTIFICATION }; } -interface GetReportRequest { +export interface GetReportRequest { readonly type: typeof GET_REPORT_REQUEST; readonly report: unknown; } @@ -429,23 +428,23 @@ export type Request = | LiftedRequest | ExportRequest; -interface UpdateStateAction { +export interface UpdateStateAction { type: typeof UPDATE_STATE; request?: Request; id?: string | number; } -interface SetStateAction { +export interface SetStateAction { type: typeof SET_STATE; newState: State; } -interface RemoveInstanceAction { +export interface RemoveInstanceAction { type: typeof REMOVE_INSTANCE; id: string; } -interface ConnectRequestAction { +export interface ConnectRequestAction { type: typeof CONNECT_REQUEST; options: ConnectionOptions; } @@ -455,58 +454,58 @@ interface ConnectSuccessPayload { authState: AuthStates; socketState: States; } -interface ConnectSuccessAction { +export interface ConnectSuccessAction { type: typeof CONNECT_SUCCESS; payload: ConnectSuccessPayload; error: Error | undefined; } -interface ConnectErrorAction { +export interface ConnectErrorAction { type: typeof CONNECT_ERROR; error: Error | undefined; } -interface AuthRequestAction { +export interface AuthRequestAction { type: typeof AUTH_REQUEST; } -interface AuthSuccessAction { +export interface AuthSuccessAction { type: typeof AUTH_SUCCESS; baseChannel: string; } -interface AuthErrorAction { +export interface AuthErrorAction { type: typeof AUTH_ERROR; error: Error; } -interface DisconnectedAction { +export interface DisconnectedAction { type: typeof DISCONNECTED; code: number; } -interface DeauthenticateAction { +export interface DeauthenticateAction { type: typeof DEAUTHENTICATE; } -interface SubscribeRequestAction { +export interface SubscribeRequestAction { type: typeof SUBSCRIBE_REQUEST; channel: string; subscription: typeof UPDATE_STATE | typeof UPDATE_REPORTS; } -interface SubscribeSuccessAction { +export interface SubscribeSuccessAction { type: typeof SUBSCRIBE_SUCCESS; channel: string; } -interface SubscribeErrorAction { +export interface SubscribeErrorAction { type: typeof SUBSCRIBE_ERROR; error: Error; status: string; } -interface UnsubscribeAction { +export interface UnsubscribeAction { type: typeof UNSUBSCRIBE; channel: string; } @@ -534,27 +533,27 @@ interface RemoveRequest { id: unknown; } export type UpdateReportsRequest = ListRequest | AddRequest | RemoveRequest; -interface UpdateReportsAction { +export interface UpdateReportsAction { type: typeof UPDATE_REPORTS; request: UpdateReportsRequest; } -interface GetReportError { +export interface GetReportError { type: typeof GET_REPORT_ERROR; error: Error; } -interface GetReportSuccess { +export interface GetReportSuccess { type: typeof GET_REPORT_SUCCESS; data: { payload: string }; } -interface ErrorAction { +export interface ErrorAction { type: typeof ERROR; payload: string; } -export type StoreAction = +export type StoreActionWithoutUpdateStateOrLiftedAction = | ChangeSectionAction | ChangeThemeAction | MonitorActionAction @@ -572,7 +571,6 @@ export type StoreAction = | ClearNotificationAction | GetReportRequest | SetStateAction - | UpdateStateAction | RemoveInstanceAction | ConnectRequestAction | ConnectSuccessAction @@ -591,3 +589,13 @@ export type StoreAction = | GetReportError | GetReportSuccess | ErrorAction; + +export type StoreActionWithoutUpdateState = + | StoreActionWithoutUpdateStateOrLiftedAction + | LiftedActionAction; + +export type StoreActionWithoutLiftedAction = + | StoreActionWithoutUpdateStateOrLiftedAction + | UpdateStateAction; + +export type StoreAction = StoreActionWithoutUpdateState | UpdateStateAction; diff --git a/packages/redux-devtools-app/src/reducers/instances.ts b/packages/redux-devtools-app/src/reducers/instances.ts index 85ddde6b0c..ddd9c8f50f 100644 --- a/packages/redux-devtools-app/src/reducers/instances.ts +++ b/packages/redux-devtools-app/src/reducers/instances.ts @@ -56,7 +56,7 @@ export interface State { } export interface InstancesState { - selected: string | null; + selected: string | number | null; current: string | number; sync: boolean; connections: { [id: string]: (string | number)[] }; diff --git a/packages/redux-devtools-app/src/utils/monitorActions.ts b/packages/redux-devtools-app/src/utils/monitorActions.ts index 7c295c5767..c45dbd58c8 100644 --- a/packages/redux-devtools-app/src/utils/monitorActions.ts +++ b/packages/redux-devtools-app/src/utils/monitorActions.ts @@ -21,7 +21,10 @@ export function sweep(state: State): State { } export function nonReduxDispatch( - store: MiddlewareAPI, StoreState>, + store: MiddlewareAPI< + Dispatch, + { readonly instances: InstancesState } + >, message: string, instanceId: string | number, action: DispatchAction, From c80473329ebafd36d239ff38585a94709099cf79 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Fri, 13 Aug 2021 21:22:58 -0400 Subject: [PATCH 18/25] More changes --- extension/package.json | 1 + extension/src/app/api/importState.ts | 35 ++++++++++++++----- extension/src/app/api/index.ts | 10 ++++-- .../browser/extension/inject/pageScript.ts | 8 ++--- .../package.json | 2 +- .../redux-devtools-serialize/package.json | 2 +- yarn.lock | 13 +++---- 7 files changed, 47 insertions(+), 24 deletions(-) diff --git a/extension/package.json b/extension/package.json index be107b76fa..926e0281cf 100644 --- a/extension/package.json +++ b/extension/package.json @@ -37,6 +37,7 @@ "@redux-devtools/serialize": "^0.3.0", "@redux-devtools/slider-monitor": "^2.0.0-8", "@redux-devtools/utils": "^1.0.0-6", + "@types/jsan": "^3.1.2", "jsan": "^3.1.13", "lodash": "^4.17.21", "react": "^16.14.0", diff --git a/extension/src/app/api/importState.ts b/extension/src/app/api/importState.ts index bacd30b9c6..ba2bc7d503 100644 --- a/extension/src/app/api/importState.ts +++ b/extension/src/app/api/importState.ts @@ -6,6 +6,8 @@ import { SerializeWithImmutable, } from '../../browser/extension/inject/pageScript'; import Immutable from 'immutable'; +import { LiftedState } from '@redux-devtools/instrument'; +import { Action } from 'redux'; function deprecate(param: string) { // eslint-disable-next-line no-console @@ -34,7 +36,12 @@ function isSerializeWithReviver( return !!(serialize as SerializeWithImmutable).immutable; } -export default function importState( +interface ParsedSerializedLiftedState { + readonly payload: string; + readonly preloadedState?: string; +} + +export default function importState>( state: string | undefined, { deserializeState, deserializeAction, serialize }: Config ) { @@ -57,14 +64,24 @@ export default function importState( } } - let preloadedState; - let nextLiftedState = parse(state); - if (nextLiftedState.payload) { - if (nextLiftedState.preloadedState) { - preloadedState = parse(nextLiftedState.preloadedState); - } - nextLiftedState = parse(nextLiftedState.payload); - } + const parsedSerializedLiftedState: + | ParsedSerializedLiftedState + | LiftedState = parse(state) as + | ParsedSerializedLiftedState + | LiftedState; + let preloadedState = + 'payload' in parsedSerializedLiftedState && + parsedSerializedLiftedState.preloadedState + ? (parse(parsedSerializedLiftedState.preloadedState) as S) + : undefined; + const nextLiftedState = + 'payload' in parsedSerializedLiftedState + ? (parse(parsedSerializedLiftedState.payload) as LiftedState< + S, + A, + unknown + >) + : parsedSerializedLiftedState; if (deserializeState) { deprecate('deserializeState'); if (typeof nextLiftedState.computedStates !== 'undefined') { diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index cee9ea49c7..db8ee46bc7 100644 --- a/extension/src/app/api/index.ts +++ b/extension/src/app/api/index.ts @@ -275,7 +275,11 @@ function getStackTrace( } function amendActionType>( - action: A | StructuralPerformAction | string, + action: + | A + | StructuralPerformAction + | StructuralPerformAction[] + | string, config: Config, toExcludeFromTrace: Function | undefined ): StructuralPerformAction { @@ -494,7 +498,7 @@ const liftListener = type: 'DISPATCH', payload: { type: 'IMPORT_STATE', - ...importState(message.state, config), + ...importState(message.state, config)!, }, }); } else { @@ -614,7 +618,7 @@ export function connect(preConfig: Config) { ) => { const message: InitMessage = { type: 'INIT', - payload: stringify(state, config.serialize), + payload: stringify(state, config.serialize as Serialize | undefined), instanceId: id, source, }; diff --git a/extension/src/browser/extension/inject/pageScript.ts b/extension/src/browser/extension/inject/pageScript.ts index 6b1fff7db2..88e6b2f5c5 100644 --- a/extension/src/browser/extension/inject/pageScript.ts +++ b/extension/src/browser/extension/inject/pageScript.ts @@ -120,7 +120,7 @@ export interface ConfigWithExpandedMaxAge { readonly features?: Features; readonly type?: string; readonly getActionType?: >(action: A) => A; - readonly actionCreators: { + readonly actionCreators?: { readonly [key: string]: ActionCreator>; }; } @@ -362,10 +362,10 @@ function __REDUX_DEVTOOLS_EXTENSION__>( } } - function importPayloadFrom(state) { - if (config.features && !config.features.import) return; + function importPayloadFrom(state: string | undefined) { + if (config!.features && !config!.features.import) return; try { - const nextLiftedState = importState(state, config); + const nextLiftedState = importState(state, config!); if (!nextLiftedState) return; store.liftedStore.dispatch({ type: 'IMPORT_STATE', ...nextLiftedState }); } catch (e) { diff --git a/packages/redux-devtools-inspector-monitor-test-tab/package.json b/packages/redux-devtools-inspector-monitor-test-tab/package.json index 4de697fe28..6d529f82e1 100644 --- a/packages/redux-devtools-inspector-monitor-test-tab/package.json +++ b/packages/redux-devtools-inspector-monitor-test-tab/package.json @@ -59,7 +59,7 @@ "@redux-devtools/inspector-monitor": "^1.0.0", "@types/es6template": "^1.0.0", "@types/history": "^4.7.8", - "@types/jsan": "^3.1.0", + "@types/jsan": "^3.1.2", "@types/lodash.shuffle": "^4.2.6", "@types/object-path": "^0.11.0", "@types/react": "^16.14.8", diff --git a/packages/redux-devtools-serialize/package.json b/packages/redux-devtools-serialize/package.json index ebca64f518..84d9120bec 100644 --- a/packages/redux-devtools-serialize/package.json +++ b/packages/redux-devtools-serialize/package.json @@ -35,7 +35,7 @@ "jsan": "^3.1.13" }, "devDependencies": { - "@types/jsan": "^3.1.0", + "@types/jsan": "^3.1.2", "immutable": "^4.0.0-rc.12" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index d0eef9f9a9..83bb3385ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3520,7 +3520,7 @@ __metadata: "@redux-devtools/inspector-monitor": ^1.0.0 "@types/es6template": ^1.0.0 "@types/history": ^4.7.8 - "@types/jsan": ^3.1.0 + "@types/jsan": ^3.1.2 "@types/lodash.shuffle": ^4.2.6 "@types/object-path": ^0.11.0 "@types/prop-types": ^15.7.3 @@ -3682,7 +3682,7 @@ __metadata: version: 0.0.0-use.local resolution: "@redux-devtools/serialize@workspace:packages/redux-devtools-serialize" dependencies: - "@types/jsan": ^3.1.0 + "@types/jsan": ^3.1.2 immutable: ^4.0.0-rc.12 jsan: ^3.1.13 peerDependencies: @@ -5246,10 +5246,10 @@ __metadata: languageName: node linkType: hard -"@types/jsan@npm:^3.1.0": - version: 3.1.0 - resolution: "@types/jsan@npm:3.1.0" - checksum: a0670d90e4bee7110504be73eefff9196b46235faf490062865136b1cbad4d3bac2adb9303e308c523f07716c026bb8e72a12ae7d47a948424d4ca4d7883587d +"@types/jsan@npm:^3.1.2": + version: 3.1.2 + resolution: "@types/jsan@npm:3.1.2" + checksum: 2ff652807d6067bbc650aaefcda4e3c07b54ddfd7d72283d7c1f1892ad1e18e907b1bbdbee7d0a163efa9e8aed9af5fa9f4ed8e2f27243c46383d31e1181fc11 languageName: node linkType: hard @@ -23231,6 +23231,7 @@ fsevents@^1.2.7: "@redux-devtools/serialize": ^0.3.0 "@redux-devtools/slider-monitor": ^2.0.0-8 "@redux-devtools/utils": ^1.0.0-6 + "@types/jsan": ^3.1.2 bestzip: ^2.2.0 chromedriver: ^91.0.1 electron: ^13.1.2 From ca681179219e86f2bcc70a46e19e68d315af7697 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Wed, 18 Aug 2021 21:33:46 -0400 Subject: [PATCH 19/25] More work --- extension/package.json | 3 +- extension/src/app/api/index.ts | 45 ++++-- extension/src/app/containers/App.tsx | 12 +- extension/src/app/middlewares/api.ts | 144 ++++++++++++++---- extension/src/app/stores/backgroundStore.ts | 16 +- .../src/browser/extension/chromeAPIMock.ts | 38 ++--- .../src/browser/extension/inject/index.ts | 4 +- .../src/utils/monitorActions.ts | 1 - 8 files changed, 194 insertions(+), 69 deletions(-) diff --git a/extension/package.json b/extension/package.json index 926e0281cf..94fa0b81a8 100644 --- a/extension/package.json +++ b/extension/package.json @@ -28,7 +28,8 @@ "test:app": "cross-env BABEL_ENV=test jest test/app", "test:chrome": "jest test/chrome", "test:electron": "jest test/electron", - "test": "npm run test:app && npm run build:extension && npm run test:chrome && npm run test:electron" + "test": "npm run test:app && npm run build:extension && npm run test:chrome && npm run test:electron", + "type-check": "tsc --noEmit" }, "dependencies": { "@redux-devtools/app": "^1.0.0-8", diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index db8ee46bc7..d23120adb4 100644 --- a/extension/src/app/api/index.ts +++ b/extension/src/app/api/index.ts @@ -357,6 +357,7 @@ export interface ErrorMessage { readonly payload: string; readonly source: typeof source; readonly instanceId: number; + readonly message?: string | undefined; } interface InitInstanceMessage { @@ -449,16 +450,34 @@ export function sendMessage>( config = {}; // eslint-disable-line no-param-reassign if (action) amendedAction = amendActionType(action, config, sendMessage); } - const message = { - type: action ? 'ACTION' : 'STATE', - action: amendedAction, - payload: state, - maxAge: config.maxAge, - source, - name: config.name || name, - instanceId: config.instanceId || instanceId || 1, - }; - toContentScript(message, config.serialize, config.serialize); + if (action) { + toContentScript( + { + type: 'ACTION', + action: amendedAction, + payload: state, + maxAge: config.maxAge, + source, + name: config.name || name, + instanceId: config.instanceId || instanceId || 1, + }, + config.serialize, + config.serialize + ); + } + toContentScript( + { + type: 'STATE', + action: amendedAction, + payload: state, + maxAge: config.maxAge, + source, + name: config.name || name, + instanceId: config.instanceId || instanceId || 1, + }, + config.serialize, + config.serialize + ); } function handleMessages(event: MessageEvent) { @@ -609,7 +628,11 @@ export function connect(preConfig: Config) { return; } } - sendMessage(amendedAction, amendedState, config); + sendMessage( + amendedAction as StructuralPerformAction, + amendedState, + config + ); }; const init = >( diff --git a/extension/src/app/containers/App.tsx b/extension/src/app/containers/App.tsx index c030f400f2..85d13db0f3 100644 --- a/extension/src/app/containers/App.tsx +++ b/extension/src/app/containers/App.tsx @@ -7,6 +7,8 @@ import Actions from '@redux-devtools/app/lib/containers/Actions'; 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 { SingleMessage } from '../middlewares/api'; +import { Position } from '../api/openWindow'; type StateProps = ReturnType; type DispatchProps = ResolveThunks; @@ -15,13 +17,17 @@ interface OwnProps { } type Props = StateProps & DispatchProps & OwnProps; +function sendMessage(message: SingleMessage) { + chrome.runtime.sendMessage(message); +} + class App extends Component { - openWindow = (position: string) => { - chrome.runtime.sendMessage({ type: 'OPEN', position }); + openWindow = (position: Position) => { + sendMessage({ type: 'OPEN', position }); }; openOptionsPage = () => { if (navigator.userAgent.indexOf('Firefox') !== -1) { - chrome.runtime.sendMessage({ type: 'OPEN_OPTIONS' }); + sendMessage({ type: 'OPEN_OPTIONS' }); } else { chrome.runtime.openOptionsPage(); } diff --git a/extension/src/app/middlewares/api.ts b/extension/src/app/middlewares/api.ts index b09557c690..4896c0dafe 100644 --- a/extension/src/app/middlewares/api.ts +++ b/extension/src/app/middlewares/api.ts @@ -10,7 +10,9 @@ import syncOptions, { OptionsMessage, SyncOptions, } from '../../browser/extension/options/syncOptions'; -import openDevToolsWindow from '../../browser/extension/background/openWindow'; +import openDevToolsWindow, { + DevToolsPosition, +} from '../../browser/extension/background/openWindow'; import { getReport } from '../../browser/extension/background/logging'; import { CustomAction, @@ -32,6 +34,7 @@ import { BackgroundAction, LiftedActionAction, } from '../stores/backgroundStore'; +import { Position } from '../api/openWindow'; interface TabMessageBase { readonly type: string; @@ -87,7 +90,7 @@ interface NAAction { interface InitMessage> { readonly type: 'INIT'; readonly payload: string; - readonly instanceId: string; + instanceId: string; readonly source: '@devtools-page'; action?: string; name?: string | undefined; @@ -98,7 +101,7 @@ interface InitMessage> { interface LiftedMessage { readonly type: 'LIFTED'; readonly liftedState: { readonly isPaused: boolean | undefined }; - readonly instanceId: string; + instanceId: number; readonly source: '@devtools-page'; } @@ -112,7 +115,7 @@ interface SerializedPartialStateMessage { readonly type: 'PARTIAL_STATE'; readonly payload: SerializedPartialLiftedState; readonly source: '@devtools-page'; - readonly instanceId: string; + instanceId: number; readonly maxAge: number; readonly actionsById: string; readonly computedStates: string; @@ -124,14 +127,14 @@ interface SerializedExportMessage { readonly payload: string; readonly committedState: string | undefined; readonly source: '@devtools-page'; - readonly instanceId: string; + instanceId: number; } interface SerializedActionMessage { readonly type: 'ACTION'; readonly payload: string; readonly source: '@devtools-page'; - readonly instanceId: string; + instanceId: number; readonly action: string; readonly maxAge: number; readonly nextActionId: number; @@ -144,7 +147,7 @@ interface SerializedStateMessage> { 'actionsById' | 'computedStates' | 'committedState' >; readonly source: '@devtools-page'; - readonly instanceId: string; + instanceId: string; readonly libConfig?: LibConfig; readonly actionsById: string; readonly computedStates: string; @@ -165,7 +168,7 @@ interface EmptyUpdateStateAction { interface UpdateStateAction> { readonly type: typeof UPDATE_STATE; - readonly request: UpdateStateRequest; + request: UpdateStateRequest; readonly id: string | number; } @@ -195,8 +198,8 @@ type MonitorPort = Omit & { postMessage: (message: MonitorMessage) => void; }; -const CONNECTED = 'socket/CONNECTED'; -const DISCONNECTED = 'socket/DISCONNECTED'; +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 }; @@ -248,19 +251,78 @@ interface ImportMessage { type ToContentScriptMessage = ImportMessage | LiftedActionAction; -function toContentScript({ - message, - action, - id, - instanceId, - state, -}: ToContentScriptMessage) { - connections.tab[id!].postMessage({ - type: message, - action, - state: nonReduxDispatch(window.store, message, instanceId, action, state), - id: instanceId.toString().replace(/^[^\/]+\//, ''), - }); +function toContentScript(messageBody: ToContentScriptMessage) { + if (messageBody.message === 'DISPATCH') { + const { message, action, id, instanceId, state } = messageBody; + connections.tab[id!].postMessage({ + type: message, + action, + 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; + connections.tab[id!].postMessage({ + type: message, + action, + state: nonReduxDispatch( + window.store, + message, + instanceId, + action as unknown as AppDispatchAction, + state + ), + id: instanceId.toString().replace(/^[^\/]+\//, ''), + }); + } else if (messageBody.message === 'ACTION') { + const { message, action, id, instanceId, state } = messageBody; + connections.tab[id!].postMessage({ + type: message, + action, + state: nonReduxDispatch( + window.store, + message, + instanceId, + action as unknown as AppDispatchAction, + state + ), + id: instanceId.toString().replace(/^[^\/]+\//, ''), + }); + } else if (messageBody.message === 'EXPORT') { + const { message, action, id, instanceId, state } = messageBody; + connections.tab[id!].postMessage({ + type: message, + action, + state: nonReduxDispatch( + window.store, + message, + instanceId, + action as unknown as AppDispatchAction, + state + ), + id: instanceId.toString().replace(/^[^\/]+\//, ''), + }); + } else { + const { message, action, id, instanceId, state } = messageBody; + connections.tab[id!].postMessage({ + type: message, + action, + state: nonReduxDispatch( + window.store, + message, + instanceId, + action as AppDispatchAction, + state + ), + id: (instanceId as number).toString().replace(/^[^\/]+\//, ''), + }); + } } function toAllTabs(msg: TabMessage) { @@ -302,9 +364,28 @@ function togglePersist() { } } +interface OpenMessage { + readonly type: 'OPEN'; + readonly position: Position; +} + +interface OpenOptionsMessage { + readonly type: 'OPEN_OPTIONS'; +} + +interface GetOptionsMessage { + readonly type: 'GET_OPTIONS'; +} + +export type SingleMessage = + | OpenMessage + | OpenOptionsMessage + | GetOptionsMessage; + type BackgroundStoreMessage> = | PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance - | SplitMessage; + | SplitMessage + | SingleMessage; type BackgroundStoreResponse = { readonly options: Options }; // Receive messages from content scripts @@ -338,13 +419,13 @@ function messaging>( return; } if (request.type === 'OPEN') { - let position = 'devtools-left'; + let position: DevToolsPosition = 'devtools-left'; if ( ['remote', 'panel', 'left', 'right', 'bottom'].indexOf( request.position ) !== -1 ) { - position = 'devtools-' + request.position; + position = ('devtools-' + request.position) as DevToolsPosition; } openDevToolsWindow(position); return; @@ -372,19 +453,20 @@ function messaging>( type: UPDATE_STATE, request, id: tabId, - }; + } as UpdateStateAction; const instanceId = `${tabId}/${request.instanceId}`; if ('split' in request) { if (request.split === 'start') { - chunks[instanceId] = request; + chunks[instanceId] = request as any; return; } if (request.split === 'chunk') { - chunks[instanceId][request.chunk[0]] = - (chunks[instanceId][request.chunk[0]] || '') + request.chunk[1]; + (chunks[instanceId] as any)[request.chunk[0]] = + ((chunks[instanceId] as any)[request.chunk[0]] || '') + + request.chunk[1]; return; } - action.request = chunks[instanceId]; + action.request = chunks[instanceId] as any; delete chunks[instanceId]; } if (request.instanceId) { diff --git a/extension/src/app/stores/backgroundStore.ts b/extension/src/app/stores/backgroundStore.ts index 5b5ec8e4d7..ace3e77e85 100644 --- a/extension/src/app/stores/backgroundStore.ts +++ b/extension/src/app/stores/backgroundStore.ts @@ -1,6 +1,6 @@ import { createStore, applyMiddleware, PreloadedState } from 'redux'; import rootReducer, { BackgroundState } from '../reducers/background'; -import api from '../middlewares/api'; +import api, { CONNECTED, DISCONNECTED } from '../middlewares/api'; import { LIFTED_ACTION } from '@redux-devtools/app/lib/constants/actionTypes'; import { CustomAction, @@ -26,6 +26,7 @@ interface LiftedActionImportAction extends LiftedActionActionBase { message: 'IMPORT'; state: string; preloadedState?: unknown | undefined; + action?: never; } interface LiftedActionActionAction extends LiftedActionActionBase { type: typeof LIFTED_ACTION; @@ -36,6 +37,7 @@ interface LiftedActionExportAction extends LiftedActionActionBase { type: typeof LIFTED_ACTION; message: 'EXPORT'; toExport: boolean; + action?: never; } export type LiftedActionAction = | LiftedActionDispatchAction @@ -49,10 +51,20 @@ interface TogglePersistAction { readonly id: string | number | undefined; } +interface ConnectedAction { + readonly type: typeof CONNECTED; +} + +interface DisconnectedAction { + readonly type: typeof DISCONNECTED; +} + export type BackgroundAction = | StoreActionWithoutLiftedAction | LiftedActionAction - | TogglePersistAction; + | TogglePersistAction + | ConnectedAction + | DisconnectedAction; export default function configureStore( preloadedState?: PreloadedState diff --git a/extension/src/browser/extension/chromeAPIMock.ts b/extension/src/browser/extension/chromeAPIMock.ts index 1081f37aee..0ee79b89da 100644 --- a/extension/src/browser/extension/chromeAPIMock.ts +++ b/extension/src/browser/extension/chromeAPIMock.ts @@ -1,48 +1,48 @@ // Mock not supported chrome.* API for Firefox and Electron -window.isElectron = +(window as any).isElectron = window.navigator && window.navigator.userAgent.indexOf('Electron') !== -1; const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1; // Background page only if ( - (window.isElectron && + ((window as any).isElectron && location.pathname === '/_generated_background_page.html') || isFirefox ) { - chrome.runtime.onConnectExternal = { + (chrome.runtime as any).onConnectExternal = { addListener() {}, }; - chrome.runtime.onMessageExternal = { + (chrome.runtime as any).onMessageExternal = { addListener() {}, }; - if (window.isElectron) { - chrome.notifications = { + if ((window as any).isElectron) { + (chrome.notifications as any) = { onClicked: { addListener() {}, }, create() {}, clear() {}, }; - chrome.contextMenus = { + (chrome.contextMenus as any) = { onClicked: { addListener() {}, }, }; } else { - chrome.storage.sync = chrome.storage.local; - chrome.runtime.onInstalled = { - addListener: (cb) => cb(), + (chrome.storage as any).sync = chrome.storage.local; + (chrome.runtime as any).onInstalled = { + addListener: (cb: any) => cb(), }; } } -if (window.isElectron) { +if ((window as any).isElectron) { if (!chrome.storage.local || !chrome.storage.local.remove) { - chrome.storage.local = { - set(obj, callback) { + (chrome.storage as any).local = { + set(obj: any, callback: any) { Object.keys(obj).forEach((key) => { localStorage.setItem(key, obj[key]); }); @@ -50,8 +50,8 @@ if (window.isElectron) { callback(); } }, - get(obj, callback) { - const result = {}; + get(obj: any, callback: any) { + const result: any = {}; Object.keys(obj).forEach((key) => { result[key] = localStorage.getItem(key) || obj[key]; }); @@ -60,7 +60,7 @@ if (window.isElectron) { } }, // Electron ~ 1.4.6 - remove(items, callback) { + remove(items: any, callback: any) { if (Array.isArray(items)) { items.forEach((name) => { localStorage.removeItem(name); @@ -75,7 +75,7 @@ if (window.isElectron) { }; } // Avoid error: chrome.runtime.sendMessage is not supported responseCallback - const originSendMessage = chrome.runtime.sendMessage; + const originSendMessage = (chrome.runtime as any).sendMessage; chrome.runtime.sendMessage = function () { if (process.env.NODE_ENV === 'development') { return originSendMessage(...arguments); @@ -87,6 +87,6 @@ if (window.isElectron) { }; } -if (isFirefox || window.isElectron) { - chrome.storage.sync = chrome.storage.local; +if (isFirefox || (window as any).isElectron) { + (chrome.storage as any).sync = chrome.storage.local; } diff --git a/extension/src/browser/extension/inject/index.ts b/extension/src/browser/extension/inject/index.ts index 9537c08154..dfb369b77b 100644 --- a/extension/src/browser/extension/inject/index.ts +++ b/extension/src/browser/extension/inject/index.ts @@ -1,6 +1,8 @@ // Include this script in Chrome apps and extensions for remote debugging // +import { Options } from '../options/syncOptions'; + window.devToolsExtensionID = 'lmhkpmbekcpmknklioeibfkpmmfibljd'; require('./contentScript'); require('./pageScript'); @@ -8,7 +10,7 @@ require('./pageScript'); chrome.runtime.sendMessage( window.devToolsExtensionID, { type: 'GET_OPTIONS' }, - function (response) { + function (response: { readonly options: Options }) { if (!response.options.inject) { const urls = response.options.urls.split('\n').filter(Boolean).join('|'); if (!location.href.match(new RegExp(urls))) return; diff --git a/packages/redux-devtools-app/src/utils/monitorActions.ts b/packages/redux-devtools-app/src/utils/monitorActions.ts index c45dbd58c8..063e3fa68c 100644 --- a/packages/redux-devtools-app/src/utils/monitorActions.ts +++ b/packages/redux-devtools-app/src/utils/monitorActions.ts @@ -5,7 +5,6 @@ import { SET_STATE } from '../constants/actionTypes'; import { InstancesState, State } from '../reducers/instances'; import { Dispatch, MiddlewareAPI } from 'redux'; import { DispatchAction, StoreAction } from '../actions'; -import { StoreState } from '../reducers'; export function sweep(state: State): State { return { From 3415f577574c2e69ddcda6653d7ba439fe689a54 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 22 Aug 2021 18:29:08 -0400 Subject: [PATCH 20/25] Work --- extension/src/app/api/filters.ts | 8 +-- extension/src/app/api/index.ts | 58 ++++++++++------ extension/src/app/middlewares/api.ts | 12 ++-- extension/src/app/middlewares/windowSync.ts | 2 +- .../src/app/reducers/window/instances.ts | 10 ++- extension/src/app/stores/windowStore.ts | 9 ++- .../extension/background/getPreloadedState.ts | 4 +- .../src/browser/extension/devpanel/index.tsx | 4 +- .../browser/extension/inject/contentScript.ts | 4 +- .../browser/extension/inject/pageScript.ts | 68 ++++++++++++++----- .../src/browser/extension/window/remote.tsx | 3 +- .../redux-devtools-app/src/actions/index.ts | 5 +- .../src/reducers/instances.ts | 2 +- .../src/reducers/monitor.ts | 2 +- .../src/utils/monitorActions.ts | 4 +- 15 files changed, 128 insertions(+), 67 deletions(-) diff --git a/extension/src/app/api/filters.ts b/extension/src/app/api/filters.ts index 3bcb356d25..37fa4ae9cd 100644 --- a/extension/src/app/api/filters.ts +++ b/extension/src/app/api/filters.ts @@ -67,11 +67,11 @@ export function isFiltered>( function filterActions>( actionsById: { [p: number]: PerformAction }, actionSanitizer: ((action: A, id: number) => A) | undefined -) { +): { [p: number]: PerformAction } { if (!actionSanitizer) return actionsById; - return mapValues(actionsById, (action, id: number) => ({ + return mapValues(actionsById, (action, id) => ({ ...action, - action: actionSanitizer(action.action, id), + action: actionSanitizer(action.action, id as unknown as number), })); } @@ -92,7 +92,7 @@ export function filterState>( stateSanitizer: ((state: S, index: number) => S) | undefined, actionSanitizer: ((action: A, id: number) => A) | undefined, predicate: ((state: S, action: A) => boolean) | undefined -) { +): LiftedState { if (predicate || !noFiltersApplied(localFilter)) { const filteredStagedActionIds: number[] = []; const filteredComputedStates: { state: S; error?: string | undefined }[] = diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index d23120adb4..217540ac31 100644 --- a/extension/src/app/api/index.ts +++ b/extension/src/app/api/index.ts @@ -174,7 +174,7 @@ interface SerializedActionMessage { readonly instanceId: number; readonly action: string; readonly maxAge: number; - readonly nextActionId: number; + readonly nextActionId?: number; } interface SerializedStateMessage> { @@ -320,7 +320,7 @@ interface ExportMessage> { readonly instanceId: number; } -interface StructuralPerformAction> { +export interface StructuralPerformAction> { readonly action: A; readonly timestamp?: number; readonly stack?: string; @@ -341,7 +341,8 @@ interface ActionMessage> { readonly instanceId: number; readonly action: UserAction; readonly maxAge: number; - readonly nextActionId: number; + readonly nextActionId?: number; + readonly name?: string; } interface StateMessage> { @@ -350,6 +351,9 @@ interface StateMessage> { readonly source: typeof source; readonly instanceId: number; readonly libConfig?: LibConfig; + readonly action?: UserAction; + readonly maxAge?: number; + readonly name?: string; } export interface ErrorMessage { @@ -439,7 +443,7 @@ export function toContentScript>( export function sendMessage>( action: StructuralPerformAction | StructuralPerformAction[], - state: S, + state: LiftedState, config: Config, instanceId?: number, name?: string @@ -456,16 +460,16 @@ export function sendMessage>( type: 'ACTION', action: amendedAction, payload: state, - maxAge: config.maxAge, + maxAge: config.maxAge!, source, name: config.name || name, instanceId: config.instanceId || instanceId || 1, }, - config.serialize, - config.serialize + config.serialize as Serialize | undefined, + config.serialize as Serialize | undefined ); } - toContentScript( + toContentScript( { type: 'STATE', action: amendedAction, @@ -475,8 +479,8 @@ export function sendMessage>( name: config.name || name, instanceId: config.instanceId || instanceId || 1, }, - config.serialize, - config.serialize + config.serialize as Serialize | undefined, + config.serialize as Serialize | undefined ); } @@ -530,7 +534,23 @@ export function disconnect() { post({ type: 'DISCONNECT', source }); } -export function connect(preConfig: Config) { +export interface ConnectResponse { + init: >( + state: S, + liftedData: LiftedState + ) => void; + subscribe: >( + listener: (message: ListenerMessage) => void + ) => (() => void) | undefined; + unsubscribe: () => void; + send: >( + action: A, + state: LiftedState + ) => void; + error: (payload: string) => void; +} + +export function connect(preConfig: Config): ConnectResponse { const config = preConfig || {}; const id = generateId(config.instanceId); if (!config.instanceId) config.instanceId = id; @@ -546,7 +566,7 @@ export function connect(preConfig: Config) { const autoPause = config.autoPause; let isPaused = autoPause; let delayedActions: StructuralPerformAction>[] = []; - let delayedStates: unknown[] = []; + let delayedStates: LiftedState, unknown>[] = []; const rootListener = (action: ContentScriptToPageScriptMessage) => { if (autoPause) { @@ -590,12 +610,15 @@ export function connect(preConfig: Config) { }; const sendDelayed = throttle(() => { - sendMessage(delayedActions, delayedStates, config); + sendMessage(delayedActions, delayedStates as any, config); delayedActions = []; delayedStates = []; }, latency); - const send = >(action: A, state: S) => { + const send = >( + action: A, + state: LiftedState + ) => { if ( isPaused || isFiltered(action, localFilter) || @@ -684,13 +707,10 @@ export function connect(preConfig: Config) { export function updateStore>( stores: { - [K in string | number]: EnhancedStore, unknown>; + [K in string | number]: EnhancedStore; } ) { - return function ( - newStore: EnhancedStore, unknown>, - instanceId: number - ) { + return function (newStore: EnhancedStore, instanceId: number) { /* eslint-disable no-console */ console.warn( '`__REDUX_DEVTOOLS_EXTENSION__.updateStore` is deprecated, remove it and just use ' + diff --git a/extension/src/app/middlewares/api.ts b/extension/src/app/middlewares/api.ts index 4896c0dafe..b6dd67c08e 100644 --- a/extension/src/app/middlewares/api.ts +++ b/extension/src/app/middlewares/api.ts @@ -82,9 +82,9 @@ interface ExportAction extends TabMessageBase { readonly id: string; } -interface NAAction { +export interface NAAction { readonly type: 'NA'; - readonly id: string; + readonly id: string | number; } interface InitMessage> { @@ -99,10 +99,10 @@ interface InitMessage> { } interface LiftedMessage { - readonly type: 'LIFTED'; - readonly liftedState: { readonly isPaused: boolean | undefined }; + type: 'LIFTED'; + liftedState: { isPaused: boolean | undefined }; instanceId: number; - readonly source: '@devtools-page'; + source: '@devtools-page'; } interface SerializedPartialLiftedState { @@ -162,7 +162,7 @@ type UpdateStateRequest> = | SerializedActionMessage | SerializedStateMessage; -interface EmptyUpdateStateAction { +export interface EmptyUpdateStateAction { readonly type: typeof UPDATE_STATE; } diff --git a/extension/src/app/middlewares/windowSync.ts b/extension/src/app/middlewares/windowSync.ts index 8de3be55e5..f8823ba8a9 100644 --- a/extension/src/app/middlewares/windowSync.ts +++ b/extension/src/app/middlewares/windowSync.ts @@ -28,7 +28,7 @@ const syncStores = const instances = store.getState().instances; const instanceId = getActiveInstance(instances); const id = instances.options[instanceId].connectionId; - baseStore.dispatch({ ...action, instanceId, id }); + baseStore.dispatch({ ...action, instanceId, id } as any); } return next(action); }; diff --git a/extension/src/app/reducers/window/instances.ts b/extension/src/app/reducers/window/instances.ts index f95f098259..1db1a6ec00 100644 --- a/extension/src/app/reducers/window/instances.ts +++ b/extension/src/app/reducers/window/instances.ts @@ -7,7 +7,10 @@ import { SELECT_INSTANCE, LIFTED_ACTION, } from '@redux-devtools/app/lib/constants/actionTypes'; -import { WindowStoreAction } from '../../stores/windowStore'; +import { + ExpandedUpdateStateAction, + WindowStoreAction, +} from '../../stores/windowStore'; export default function instances( state = initialState, @@ -15,7 +18,10 @@ export default function instances( ) { switch (action.type) { case UPDATE_STATE: - return { ...action.instances, selected: state.selected }; + return { + ...(action as ExpandedUpdateStateAction).instances, + selected: state.selected, + }; case LIFTED_ACTION: if (action.message === 'DISPATCH') return dispatchAction(state, action); return state; diff --git a/extension/src/app/stores/windowStore.ts b/extension/src/app/stores/windowStore.ts index 9eb8b351c5..fa1b406cc8 100644 --- a/extension/src/app/stores/windowStore.ts +++ b/extension/src/app/stores/windowStore.ts @@ -21,6 +21,7 @@ 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'; export interface TogglePersistAction { readonly type: 'TOGGLE_PERSIST'; @@ -28,14 +29,16 @@ export interface TogglePersistAction { export type StoreActionWithTogglePersist = StoreAction | TogglePersistAction; -interface ExpandedUpdateStateAction extends UpdateStateAction { +export interface ExpandedUpdateStateAction extends UpdateStateAction { readonly instances: InstancesState; } export type WindowStoreAction = | StoreActionWithoutUpdateState | TogglePersistAction - | ExpandedUpdateStateAction; + | ExpandedUpdateStateAction + | NAAction + | EmptyUpdateStateAction; export default function configureStore( baseStore: Store, @@ -68,7 +71,7 @@ export default function configureStore( hostname: options['s:hostname'], port: options['s:port'], secure: options['s:secure'], - }, + } as any, }); }); diff --git a/extension/src/browser/extension/background/getPreloadedState.ts b/extension/src/browser/extension/background/getPreloadedState.ts index 00ea732ebb..e4cfa83aba 100644 --- a/extension/src/browser/extension/background/getPreloadedState.ts +++ b/extension/src/browser/extension/background/getPreloadedState.ts @@ -1,7 +1,7 @@ import { PreloadedState } from 'redux'; import { StoreState } from '@redux-devtools/app/lib/reducers'; -const getIfExists = (sel, template) => +const getIfExists = (sel: any, template: any) => typeof sel === 'undefined' || typeof template === 'undefined' || typeof template[sel] === 'undefined' @@ -34,7 +34,7 @@ export default function getPreloadedState( ), templates: options['test-templates'], }, - }); + } as any); } ); } diff --git a/extension/src/browser/extension/devpanel/index.tsx b/extension/src/browser/extension/devpanel/index.tsx index dfb2437236..484e5e38b7 100644 --- a/extension/src/browser/extension/devpanel/index.tsx +++ b/extension/src/browser/extension/devpanel/index.tsx @@ -9,8 +9,8 @@ import getPreloadedState from '../background/getPreloadedState'; import '../../views/devpanel.pug'; import { Action, PreloadedState, Store } from 'redux'; import { StoreState } from '@redux-devtools/app/lib/reducers'; -import { StoreAction } from '@redux-devtools/app/lib/actions'; import { PanelMessage } from '../../../app/middlewares/api'; +import { StoreActionWithTogglePersist } from '../../../app/stores/windowStore'; const position = location.hash; const messageStyle: CSSProperties = { @@ -20,7 +20,7 @@ const messageStyle: CSSProperties = { }; let rendered: boolean | undefined; -let store: Store | undefined; +let store: Store | undefined; let bgConnection: chrome.runtime.Port; let naTimeout: NodeJS.Timeout; let preloadedState: PreloadedState; diff --git a/extension/src/browser/extension/inject/contentScript.ts b/extension/src/browser/extension/inject/contentScript.ts index 8675f1d08c..c1abac3483 100644 --- a/extension/src/browser/extension/inject/contentScript.ts +++ b/extension/src/browser/extension/inject/contentScript.ts @@ -213,7 +213,7 @@ function tryCatch>( return fn(args); } catch (err) { if (err.message === 'Message length exceeded maximum allowed length.') { - const instanceId = args.instanceId; + const instanceId = (args as any).instanceId; const newArgs = { split: 'start', }; @@ -231,7 +231,7 @@ function tryCatch>( } newArgs[key as keyof typeof newArgs] = arg; }); - fn(newArgs); + fn(newArgs as any); for (let i = 0; i < toSplit.length; i++) { for (let j = 0; j < toSplit[i][1].length; j += maxChromeMsgSize) { fn({ diff --git a/extension/src/browser/extension/inject/pageScript.ts b/extension/src/browser/extension/inject/pageScript.ts index 88e6b2f5c5..24a219b074 100644 --- a/extension/src/browser/extension/inject/pageScript.ts +++ b/extension/src/browser/extension/inject/pageScript.ts @@ -7,6 +7,7 @@ import throttle from 'lodash/throttle'; import { Action, ActionCreator, + Dispatch, PreloadedState, Reducer, Store, @@ -14,7 +15,7 @@ import { StoreEnhancerStoreCreator, } from 'redux'; import Immutable from 'immutable'; -import { EnhancedStore } from '@redux-devtools/instrument'; +import { EnhancedStore, PerformAction } from '@redux-devtools/instrument'; import createStore from '../../../app/stores/createStore'; import configureStore, { getUrlParam } from '../../../app/stores/enhancerStore'; import { isAllowed, Options } from '../options/syncOptions'; @@ -40,6 +41,8 @@ import { isInIframe, getSerializeParameter, Serialize, + StructuralPerformAction, + ConnectResponse, } from '../../../app/api'; import { LiftedAction, LiftedState } from '@redux-devtools/instrument'; import { @@ -50,9 +53,19 @@ import { import { ContentScriptToPageScriptMessage } from './contentScript'; import { Features } from '@redux-devtools/app/lib/reducers/instances'; +type EnhancedStoreWithInitialDispatch< + S, + A extends Action, + MonitorState +> = EnhancedStore & { initialDispatch: Dispatch }; + const source = '@devtools-page'; let stores: { - [K in string | number]: EnhancedStore, unknown>; + [K in string | number]: EnhancedStoreWithInitialDispatch< + unknown, + Action, + unknown + >; } = {}; let reportId: string | null | undefined; @@ -137,7 +150,23 @@ interface ReduxDevtoolsExtension { ): Store; (config?: Config): StoreEnhancer; open: (position?: Position) => void; + updateStore: ( + newStore: EnhancedStore, unknown>, + instanceId: number + ) => void; notifyErrors: (onError?: () => boolean) => void; + send: >( + action: StructuralPerformAction | StructuralPerformAction[], + state: LiftedState, + config: Config, + instanceId?: number, + name?: string + ) => void; + listen: ( + onMessage: (message: ContentScriptToPageScriptMessage) => void, + instanceId: number + ) => void; + connect: (preConfig: Config) => ConnectResponse; disconnect: () => void; } @@ -157,16 +186,16 @@ function __REDUX_DEVTOOLS_EXTENSION__>( reducer?: Reducer | Config | undefined, preloadedState?: PreloadedState, config?: Config -) { +): Store | StoreEnhancer { /* eslint-disable no-param-reassign */ if (typeof reducer === 'object') { config = reducer; reducer = undefined; } else if (typeof config !== 'object') config = {}; /* eslint-enable no-param-reassign */ - if (!window.devToolsOptions) window.devToolsOptions = {}; + if (!window.devToolsOptions) window.devToolsOptions = {} as any; - let store: EnhancedStore; + let store: EnhancedStoreWithInitialDispatch; let errorOccurred = false; let maxAge: number | undefined; let actionCreators: readonly ActionCreatorObject[]; @@ -404,7 +433,7 @@ function __REDUX_DEVTOOLS_EXTENSION__>( store.liftedStore.dispatch({ type: action.type, index }); return; } - store.liftedStore.dispatch(action); + store.liftedStore.dispatch(action as any); } function onMessage(message: ContentScriptToPageScriptMessage) { @@ -479,12 +508,12 @@ function __REDUX_DEVTOOLS_EXTENSION__>( if ( !liftedAction || noFiltersApplied(localFilter) || - !liftedAction.action + !(liftedAction as PerformAction).action ) { return m; } if (!maxAge || maxAge < m) maxAge = m; // it can be modified in process on options page - if (isFiltered(liftedAction.action, localFilter)) { + if (isFiltered((liftedAction as PerformAction).action, localFilter)) { // TODO: check also predicate && !predicate(state, action) with current state maxAge++; } else { @@ -554,10 +583,10 @@ function __REDUX_DEVTOOLS_EXTENSION__>( } const enhance = - () => + (): StoreEnhancer => ( next: StoreEnhancerStoreCreator - ) => { + ): any => { return ( reducer_: Reducer, initialState_?: PreloadedState @@ -568,7 +597,7 @@ function __REDUX_DEVTOOLS_EXTENSION__>( return configureStore(next, monitor.reducer, { ...config, - maxAge: getMaxAge, + maxAge: getMaxAge as any, })(reducer_, initialState_); if (isInIframe()) setTimeout(init, 3000); @@ -610,24 +639,27 @@ const preEnhancer = const store = next(reducer, preloadedState); if (stores[instanceId]) { - stores[instanceId].initialDispatch = store.dispatch; + (stores[instanceId].initialDispatch as any) = store.dispatch; } return { ...store, - dispatch: (...args) => - !window.__REDUX_DEVTOOLS_EXTENSION_LOCKED__ && store.dispatch(...args), - }; + dispatch: (...args: any[]) => + !window.__REDUX_DEVTOOLS_EXTENSION_LOCKED__ && + (store.dispatch as any)(...args), + } as any; }; const extensionCompose = (config: Config) => (...funcs: StoreEnhancer[]) => { - return (...args) => { + return (...args: any[]) => { const instanceId = generateId(config.instanceId); return [preEnhancer(instanceId), ...funcs].reduceRight( (composed, f) => f(composed), - __REDUX_DEVTOOLS_EXTENSION__({ ...config, instanceId })(...args) + (__REDUX_DEVTOOLS_EXTENSION__({ ...config, instanceId }) as any)( + ...args + ) ); }; }; @@ -638,7 +670,7 @@ declare global { } } -window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = (...funcs) => { +window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = (...funcs: any[]) => { if (funcs.length === 0) { return __REDUX_DEVTOOLS_EXTENSION__(); } diff --git a/extension/src/browser/extension/window/remote.tsx b/extension/src/browser/extension/window/remote.tsx index 3c31bb66d3..a2ec9e8682 100644 --- a/extension/src/browser/extension/window/remote.tsx +++ b/extension/src/browser/extension/window/remote.tsx @@ -14,8 +14,9 @@ chrome.storage.local.get( 's:secure': null, }, (options) => { + const AppAsAny = App as any; render( - , + Dispatch, { readonly instances: InstancesState } >, message: string, From 621d45e0852357ebda5422b6247c811a6a14818b Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Tue, 24 Aug 2021 21:15:01 -0400 Subject: [PATCH 21/25] Fix build --- extension/webpack/base.config.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/webpack/base.config.js b/extension/webpack/base.config.js index f2221bd90e..3c17f4910f 100644 --- a/extension/webpack/base.config.js +++ b/extension/webpack/base.config.js @@ -3,7 +3,7 @@ import webpack from 'webpack'; import CopyPlugin from 'copy-webpack-plugin'; const extpath = path.join(__dirname, '../src/browser/extension/'); -const mock = `${extpath}chromeAPIMock.js`; +const mock = `${extpath}chromeAPIMock`; const baseConfig = (params) => ({ // devtool: 'source-map', @@ -64,7 +64,7 @@ const baseConfig = (params) => ({ app: path.join(__dirname, '../src/app'), tmp: path.join(__dirname, '../build/tmp'), }, - extensions: ['.js'], + extensions: ['.js', '.jsx', '.ts', '.tsx'], }, module: { rules: [ @@ -72,7 +72,7 @@ const baseConfig = (params) => ({ ? params.loaders : [ { - test: /\.js$/, + test: /\.(js|ts)x?$/, use: 'babel-loader', exclude: /(node_modules|tmp\/page\.bundle)/, }, From c4b34bcd075644d70d23bb043d30ca3256496628 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Tue, 24 Aug 2021 21:53:18 -0400 Subject: [PATCH 22/25] Fix lint --- .../src/openFile.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/redux-devtools-inspector-monitor-trace-tab/src/openFile.ts b/packages/redux-devtools-inspector-monitor-trace-tab/src/openFile.ts index aa8e22f505..a542fa4084 100644 --- a/packages/redux-devtools-inspector-monitor-trace-tab/src/openFile.ts +++ b/packages/redux-devtools-inspector-monitor-trace-tab/src/openFile.ts @@ -32,11 +32,11 @@ function openAndCloseTab(url: string) { const removeTab = () => { chrome.windows.onFocusChanged.removeListener(removeTab); if (tab && tab.id) { - chrome.tabs.remove(tab.id, () => { + chrome.tabs.remove(tab.id, async () => { // eslint-disable-next-line no-console if (chrome.runtime.lastError) console.log(chrome.runtime.lastError); else if (chrome.devtools && chrome.devtools.inspectedWindow) { - chrome.tabs.update(chrome.devtools.inspectedWindow.tabId, { + await chrome.tabs.update(chrome.devtools.inspectedWindow.tabId, { active: true, }); } From 7e10f53b95d7066eb6b0e436f1c8770883362fe3 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Tue, 24 Aug 2021 22:12:45 -0400 Subject: [PATCH 23/25] Fix more lint --- packages/redux-devtools-app/src/middlewares/api.ts | 2 +- packages/redux-devtools-app/tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/redux-devtools-app/src/middlewares/api.ts b/packages/redux-devtools-app/src/middlewares/api.ts index 2b1353695d..f5c29ae18e 100644 --- a/packages/redux-devtools-app/src/middlewares/api.ts +++ b/packages/redux-devtools-app/src/middlewares/api.ts @@ -30,7 +30,7 @@ let socket: SCClientSocket; let store: MiddlewareAPI, StoreState>; function emit({ message: type, id, instanceId, action, state }: EmitAction) { - socket.emit(id ? 'sc-' + id : 'respond', { type, action, state, instanceId }); + socket.emit(id ? `sc-${id}` : 'respond', { type, action, state, instanceId }); } function startMonitoring(channel: string) { diff --git a/packages/redux-devtools-app/tsconfig.json b/packages/redux-devtools-app/tsconfig.json index 7b7d1492e2..b742078e18 100644 --- a/packages/redux-devtools-app/tsconfig.json +++ b/packages/redux-devtools-app/tsconfig.json @@ -3,5 +3,5 @@ "compilerOptions": { "outDir": "lib" }, - "include": ["src"] + "include": ["demo", "src"] } From 96dabbd4f50d9639309c4da3f06fe70ac770dd19 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Tue, 24 Aug 2021 23:15:38 -0400 Subject: [PATCH 24/25] Fix bug --- extension/src/browser/extension/inject/pageScript.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/src/browser/extension/inject/pageScript.ts b/extension/src/browser/extension/inject/pageScript.ts index 24a219b074..2fb007a933 100644 --- a/extension/src/browser/extension/inject/pageScript.ts +++ b/extension/src/browser/extension/inject/pageScript.ts @@ -595,10 +595,10 @@ function __REDUX_DEVTOOLS_EXTENSION__>( return next(reducer_, initialState_); } - return configureStore(next, monitor.reducer, { + store = stores[instanceId] = configureStore(next, monitor.reducer, { ...config, maxAge: getMaxAge as any, - })(reducer_, initialState_); + })(reducer_, initialState_) as any; if (isInIframe()) setTimeout(init, 3000); else init(); From 3377d2165ba297aade014e1427810c2491a799ea Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Wed, 25 Aug 2021 00:00:34 -0400 Subject: [PATCH 25/25] Fix tests --- extension/src/app/api/index.ts | 27 ++++++++++++----------- extension/test/app/containers/App.spec.js | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index 217540ac31..b4ca5594cb 100644 --- a/extension/src/app/api/index.ts +++ b/extension/src/app/api/index.ts @@ -468,20 +468,21 @@ export function sendMessage>( config.serialize as Serialize | undefined, config.serialize as Serialize | undefined ); + } else { + toContentScript( + { + type: 'STATE', + action: amendedAction, + payload: state, + maxAge: config.maxAge, + source, + name: config.name || name, + instanceId: config.instanceId || instanceId || 1, + }, + config.serialize as Serialize | undefined, + config.serialize as Serialize | undefined + ); } - toContentScript( - { - type: 'STATE', - action: amendedAction, - payload: state, - maxAge: config.maxAge, - source, - name: config.name || name, - instanceId: config.instanceId || instanceId || 1, - }, - config.serialize as Serialize | undefined, - config.serialize as Serialize | undefined - ); } function handleMessages(event: MessageEvent) { diff --git a/extension/test/app/containers/App.spec.js b/extension/test/app/containers/App.spec.js index 011054c5ff..3e9194164a 100644 --- a/extension/test/app/containers/App.spec.js +++ b/extension/test/app/containers/App.spec.js @@ -2,7 +2,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { Provider } from 'react-redux'; import configureStore from '../../../src/app/stores/windowStore'; -import App from '../../../src/app/containers/App.js'; +import App from '../../../src/app/containers/App'; const store = configureStore(store); const component = mount(