diff --git a/extension/src/app/api/index.ts b/extension/src/app/api/index.ts index db266e6a..a3a7d8ed 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 306d1789..8a88f18f 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 StartAction { - readonly type: 'START'; +interface TabMessageBase { + readonly type: string; + readonly state?: string | undefined; + readonly id?: string; } -interface StopAction { +interface StartAction extends TabMessageBase { + readonly type: 'START'; + readonly state: never; + readonly id: never; +} + +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 210e288d..36166e21 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 2611ff2c..671a7ae9 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 b5f8b223..3fbf7c5c 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 3eda8bb8..a6e5ae43 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 63ed71e5..4a6a1746 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 1e5fc604..0889bfcc 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];