From 9af6e54649173baadc910c3ae8e2fe7c48aceb07 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 18 Jul 2021 17:44:48 -0400 Subject: [PATCH] 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 85d01559..3bcb356d 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 6015b6b4..cee9ea49 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 f163ddff..e960c0d5 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 930f655e..29252275 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 {