This commit is contained in:
Nathan Bierema 2021-07-16 23:57:57 -04:00
parent a3df1df2eb
commit 82c8f142e6
13 changed files with 182 additions and 93 deletions

View File

@ -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<S, A extends Action<unknown>>(
state: LiftedState<S, A, unknown>,
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<A> } | 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<S, A extends Action<unknown>> {
readonly actionsById: { [actionId: number]: PerformAction<A> };
readonly computedStates: { state: S; error?: string }[];
readonly stagedActionIds: readonly number[];
readonly currentStateIndex: number;
readonly nextActionId: number;
}
export function startingFrom<S, A extends Action<unknown>>(
sendingActionId: number,
state: LiftedState<S, A, unknown>,
@ -148,7 +158,7 @@ export function startingFrom<S, A extends Action<unknown>>(
predicate:
| (<S, A extends Action<unknown>>(state: S, action: A) => boolean)
| undefined
) {
): LiftedState<S, A, unknown> | PartialLiftedState<S, A> | undefined {
const stagedActionIds = state.stagedActionIds;
if (sendingActionId <= stagedActionIds[1]) return state;
const index = stagedActionIds.indexOf(sendingActionId);

View File

@ -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`

View File

@ -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<S, A extends Action<unknown>> {
readonly type: 'PARTIAL_STATE';
readonly payload: PartialLiftedState<S, A>;
readonly source: typeof source;
readonly instanceId: number;
readonly maxAge: number;
}
interface ExportMessage<S, A extends Action<unknown>> {
readonly type: 'EXPORT';
readonly payload: readonly A[];
readonly committedState: S;
readonly source: typeof source;
readonly instanceId: number;
}
type ToContentScriptMessage<S, A extends Action<unknown>> =
| LiftedMessage
| PartialStateMessage<S, A>
| ExportMessage<S, A>;
export function toContentScript<S, A extends Action<unknown>>(
message: ToContentScriptMessage<S, A>,
serializeState?: Serialize | undefined,
serializeAction?: Serialize | undefined
) {
if (message.type === 'ACTION') {
message.action = stringify(message.action, serializeAction);

View File

@ -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);

View File

@ -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<StoreState, StoreAction>,
store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>,
next: Dispatch<StoreAction>
) {
const instances = store.getState().instances;
@ -33,7 +33,9 @@ function getCurrentTabId(next: (tabId: number) => void) {
);
}
export default function popupSelector(store: Store<StoreState, StoreAction>) {
export default function popupSelector(
store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>
) {
return (next: Dispatch<StoreAction>) => (action: StoreAction) => {
const result = next(action);
if (action.type === UPDATE_STATE) {

View File

@ -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';

View File

@ -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<BackgroundState, StoreAction>) =>
(store: MiddlewareAPI<Dispatch<StoreAction>>) =>
(next: Dispatch<StoreAction>) =>
(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;

View File

@ -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;
}

View File

@ -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<S, A extends Action<unknown>> {
) {
this.update = update;
}
reducer = (state = {}, action) => {
reducer = (state = {}, action: StoreAction) => {
if (!this.active) return state;
this.lastAction = action.type;
if (action.type === 'LOCK_CHANGES') {

View File

@ -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<unknown>,
MonitorState,
MonitorAction extends Action<unknown>
>(
next: StoreEnhancerStoreCreator,
monitorReducer: Reducer,
monitorReducer: Reducer<MonitorState, MonitorAction>,
config: ConfigWithExpandedMaxAge
) {
return compose(

View File

@ -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(

View File

@ -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<unknown, Action<unknown>> } = {};
let stores: {
[instanceId: number]: EnhancedStore<unknown, Action<unknown>, unknown>;
} = {};
let reportId: string | null | undefined;
function deprecateParam(oldParam: string, newParam: string) {
@ -89,9 +98,7 @@ export interface ConfigWithExpandedMaxAge {
currentLiftedAction: LiftedAction<S, A, unknown>,
previousLiftedState: LiftedState<S, A, unknown> | undefined
) => number);
readonly trace?:
| boolean
| (<A extends Action<unknown>>(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<S>,
config?: Config
): Store<S, A>;
(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<S, A extends Action<unknown>>(
function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<unknown>>(
reducer?: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
config?: Config
): Store<S, A>;
function reduxDevtoolsExtension(config: Config): StoreEnhancer;
function reduxDevtoolsExtension<S, A extends Action<unknown>>(
function __REDUX_DEVTOOLS_EXTENSION__(config: Config): StoreEnhancer;
function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<unknown>>(
reducer?: Reducer<S, A> | Config | undefined,
preloadedState?: PreloadedState<S>,
config?: Config
@ -146,7 +151,7 @@ function reduxDevtoolsExtension<S, A extends Action<unknown>>(
/* eslint-enable no-param-reassign */
if (!window.devToolsOptions) window.devToolsOptions = {};
let store: Store<S, A> & InstrumentExt<S, A, unknown>;
let store: EnhancedStore<S, A, unknown>;
let errorOccurred = false;
let maxAge: number | undefined;
let actionCreators;
@ -313,7 +318,7 @@ function reduxDevtoolsExtension<S, A extends Action<unknown>>(
);
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<S, A extends Action<unknown>>(
relayState(liftedState);
}
const enhance = (): StoreEnhancer => (next) => {
return (reducer_, initialState_) => {
if (!isAllowed(window.devToolsOptions)) {
return next(reducer_, initialState_);
}
const enhance =
() =>
<NextExt, NextStateExt>(
next: StoreEnhancerStoreCreator<NextExt, NextStateExt>
) => {
return <S2 extends S, A2 extends A>(
reducer_: Reducer<S2, A2>,
initialState_?: PreloadedState<S2>
) => {
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;

View File

@ -100,7 +100,7 @@ interface ImportStateAction<S, A extends Action<unknown>, MonitorState> {
type: typeof ActionTypes.IMPORT_STATE;
nextLiftedState: LiftedState<S, A, MonitorState> | readonly A[];
preloadedState?: S;
noRecompute: boolean | undefined;
noRecompute?: boolean | undefined;
}
interface LockChangesAction {