diff --git a/packages/redux-devtools-core/src/app/index.tsx b/packages/redux-devtools-core/src/app/index.tsx index 335e2c3e..52641595 100644 --- a/packages/redux-devtools-core/src/app/index.tsx +++ b/packages/redux-devtools-core/src/app/index.tsx @@ -9,7 +9,7 @@ import { StoreState } from './reducers'; import { ConnectionOptions, StoreAction } from './actions'; interface Props { - socketOptions: ConnectionOptions; + socketOptions?: ConnectionOptions; } class Root extends Component { diff --git a/packages/redux-devtools-core/src/index.ts b/packages/redux-devtools-core/src/index.ts deleted file mode 100644 index 29d541ff..00000000 --- a/packages/redux-devtools-core/src/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -function injectedScript() { - /* eslint-disable-next-line no-console */ - console.error( - "Not implemented yet. WIP. If you're looking for utils, import `redux-devtools-core/lib/utils`." - ); -} - -export default injectedScript; diff --git a/packages/redux-devtools-core/index.tsx b/packages/redux-devtools-core/src/index.tsx similarity index 94% rename from packages/redux-devtools-core/index.tsx rename to packages/redux-devtools-core/src/index.tsx index bd23dae6..c40f8b2a 100644 --- a/packages/redux-devtools-core/index.tsx +++ b/packages/redux-devtools-core/src/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render } from 'react-dom'; -import App from './src/app'; +import App from './app'; render(, document.getElementById('root')); diff --git a/packages/redux-devtools-core/src/utils/filters.ts b/packages/redux-devtools-core/src/utils/filters.ts index d6607f6b..9c9644b1 100644 --- a/packages/redux-devtools-core/src/utils/filters.ts +++ b/packages/redux-devtools-core/src/utils/filters.ts @@ -1,4 +1,7 @@ import mapValues from 'lodash/mapValues'; +import { PerformAction } from 'redux-devtools-instrument'; +import { Action } from 'redux'; +import { State } from '../app/reducers/instances'; export const FilterState = { DO_NOT_FILTER: 'DO_NOT_FILTER', @@ -6,19 +9,25 @@ export const FilterState = { WHITELIST_SPECIFIC: 'WHITELIST_SPECIFIC', }; -export function arrToRegex(v) { +export function arrToRegex(v: string | string[]) { return typeof v === 'string' ? v : v.join('|'); } -function filterActions(actionsById, actionsFilter) { +function filterActions( + actionsById: { [actionId: number]: PerformAction> }, + actionsFilter: (action: Action, id: number) => Action +) { if (!actionsFilter) return actionsById; - return mapValues(actionsById, (action, id) => ({ + return mapValues(actionsById, (action, id: number) => ({ ...action, action: actionsFilter(action.action, id), })); } -function filterStates(computedStates, statesFilter) { +function filterStates( + computedStates: { state: unknown; error?: string | undefined }[], + statesFilter: (state: unknown, actionId: number) => unknown +) { if (!statesFilter) return computedStates; return computedStates.map((state, idx) => ({ ...state, @@ -26,7 +35,17 @@ function filterStates(computedStates, statesFilter) { })); } -export function getLocalFilter(config) { +interface Config { + actionsBlacklist?: string[]; + actionsWhitelist?: string[]; +} + +interface LocalFilter { + whitelist?: string; + blacklist?: string; +} + +export function getLocalFilter(config: Config): LocalFilter | undefined { if (config.actionsBlacklist || config.actionsWhitelist) { return { whitelist: config.actionsWhitelist && config.actionsWhitelist.join('|'), @@ -36,33 +55,53 @@ export function getLocalFilter(config) { return undefined; } +interface DevToolsOptions { + filter?: + | typeof FilterState.DO_NOT_FILTER + | typeof FilterState.BLACKLIST_SPECIFIC + | typeof FilterState.WHITELIST_SPECIFIC; + whitelist?: string; + blacklist?: string; +} function getDevToolsOptions() { - return (typeof window !== 'undefined' && window.devToolsOptions) || {}; + return ( + (typeof window !== 'undefined' && + (window as { devToolsOptions?: DevToolsOptions }).devToolsOptions) || + {} + ); } -export function isFiltered(action, localFilter) { - const { type } = action.action || action; +export function isFiltered( + action: PerformAction> | Action, + localFilter?: LocalFilter +) { + const { type } = (action as PerformAction>).action || action; const opts = getDevToolsOptions(); if ( (!localFilter && opts.filter && opts.filter === FilterState.DO_NOT_FILTER) || - (type && typeof type.match !== 'function') + (type && typeof (type as string).match !== 'function') ) return false; const { whitelist, blacklist } = localFilter || opts; return ( - (whitelist && !type.match(whitelist)) || - (blacklist && type.match(blacklist)) + // eslint-disable-next-line @typescript-eslint/prefer-regexp-exec + (whitelist && !(type as string).match(whitelist)) || + // eslint-disable-next-line @typescript-eslint/prefer-regexp-exec + (blacklist && (type as string).match(blacklist)) ); } -export function filterStagedActions(state, filters) { +export function filterStagedActions(state: State, filters: LocalFilter) { if (!filters) return state; - const filteredStagedActionIds = []; - const filteredComputedStates = []; + const filteredStagedActionIds: number[] = []; + const filteredComputedStates: { + state: unknown; + error?: string | undefined; + }[] = []; state.stagedActionIds.forEach((id, idx) => { if (!isFiltered(state.actionsById[id], filters)) { @@ -79,13 +118,13 @@ export function filterStagedActions(state, filters) { } export function filterState( - state, - type, - localFilter, - stateSanitizer, - actionSanitizer, - nextActionId, - predicate + state: State, + type: string, + localFilter: LocalFilter, + stateSanitizer: (state: unknown, actionId: number) => unknown, + actionSanitizer: (action: Action, id: number) => Action, + nextActionId: number, + predicate: (currState: unknown, currAction: Action) => boolean ) { if (type === 'ACTION') return !stateSanitizer ? state : stateSanitizer(state, nextActionId - 1); @@ -97,9 +136,14 @@ export function filterState( localFilter || (filter && filter !== FilterState.DO_NOT_FILTER) ) { - const filteredStagedActionIds = []; - const filteredComputedStates = []; - const sanitizedActionsById = actionSanitizer && {}; + const filteredStagedActionIds: number[] = []; + const filteredComputedStates: { + state: unknown; + error?: string | undefined; + }[] = []; + const sanitizedActionsById: { + [id: number]: PerformAction>; + } = actionSanitizer && {}; const { actionsById } = state; const { computedStates } = state; diff --git a/packages/redux-devtools-core/src/utils/get-params.ts b/packages/redux-devtools-core/src/utils/get-params.ts new file mode 100644 index 00000000..417ec500 --- /dev/null +++ b/packages/redux-devtools-core/src/utils/get-params.ts @@ -0,0 +1,4 @@ +declare module 'get-params' { + function getParams(func: (...args: any[]) => unknown): string[]; + export default getParams; +} diff --git a/packages/redux-devtools-core/src/utils/importState.ts b/packages/redux-devtools-core/src/utils/importState.ts index 11cf3222..1afe997f 100644 --- a/packages/redux-devtools-core/src/utils/importState.ts +++ b/packages/redux-devtools-core/src/utils/importState.ts @@ -1,8 +1,11 @@ import mapValues from 'lodash/mapValues'; import jsan from 'jsan'; -import seralizeImmutable from 'remotedev-serialize/immutable/serialize'; +import { immutableSerialize } from 'redux-devtools-serialize'; +import { Action } from 'redux'; +import Immutable from 'immutable'; +import { State } from '../app/reducers/instances'; -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:` + @@ -11,8 +14,20 @@ function deprecate(param) { } export default function importState( - state, - { deserializeState, deserializeAction, serialize } + state: string, + { + deserializeState, + deserializeAction, + serialize, + }: { + deserializeState?: (state: string) => unknown; + deserializeAction?: (action: string) => Action; + serialize?: { + immutable?: typeof Immutable; + refs?: (new (data: any) => unknown)[] | null; + reviver?: (key: string, value: unknown) => unknown; + }; + } ) { if (!state) return undefined; let parse = jsan.parse; @@ -21,19 +36,38 @@ export default function importState( parse = (v) => jsan.parse( v, - seralizeImmutable(serialize.immutable, serialize.refs).reviver + immutableSerialize(serialize.immutable!, serialize.refs).reviver ); } else if (serialize.reviver) { parse = (v) => jsan.parse(v, serialize.reviver); } } - let preloadedState; - let nextLiftedState = parse(state); - if (nextLiftedState.payload) { - if (nextLiftedState.preloadedState) - preloadedState = parse(nextLiftedState.preloadedState); - nextLiftedState = parse(nextLiftedState.payload); + let preloadedState: State | undefined; + let nextLiftedState: State = parse(state) as State; + if ( + ((nextLiftedState as unknown) as { + payload?: string; + preloadedState?: string; + }).payload + ) { + if ( + ((nextLiftedState as unknown) as { + payload: string; + preloadedState?: string; + }).preloadedState + ) + preloadedState = parse( + ((nextLiftedState as unknown) as { + payload: string; + preloadedState: string; + }).preloadedState + ) as State; + nextLiftedState = parse( + ((nextLiftedState as unknown) as { + payload: string; + }).payload + ) as State; } if (deserializeState) { deprecate('deserializeState'); @@ -41,17 +75,19 @@ export default function importState( nextLiftedState.computedStates = nextLiftedState.computedStates.map( (computedState) => ({ ...computedState, - state: deserializeState(computedState.state), + state: deserializeState(computedState.state as string), }) ); } if (typeof nextLiftedState.committedState !== 'undefined') { nextLiftedState.committedState = deserializeState( - nextLiftedState.committedState + nextLiftedState.committedState as string ); } if (typeof preloadedState !== 'undefined') { - preloadedState = deserializeState(preloadedState); + preloadedState = deserializeState( + (preloadedState as unknown) as string + ) as State; } } if (deserializeAction) { @@ -60,7 +96,7 @@ export default function importState( nextLiftedState.actionsById, (liftedAction) => ({ ...liftedAction, - action: deserializeAction(liftedAction.action), + action: deserializeAction((liftedAction.action as unknown) as string), }) ); } diff --git a/packages/redux-devtools-core/src/utils/index.ts b/packages/redux-devtools-core/src/utils/index.ts index 45bd643c..c343b293 100644 --- a/packages/redux-devtools-core/src/utils/index.ts +++ b/packages/redux-devtools-core/src/utils/index.ts @@ -1,14 +1,24 @@ import getParams from 'get-params'; import jsan from 'jsan'; import { nanoid } from 'nanoid/non-secure'; -import seralizeImmutable from 'remotedev-serialize/immutable/serialize'; +import { immutableSerialize } from 'redux-devtools-serialize'; +import Immutable from 'immutable'; +import { Action } from 'redux'; -export function generateId(id) { +export function generateId(id: string | undefined) { return id || nanoid(7); } -function flatTree(obj, namespace = '') { - let functions = []; +// eslint-disable-next-line @typescript-eslint/ban-types +function flatTree( + obj: { [key: string]: (...args: any[]) => unknown }, + namespace = '' +) { + let functions: { + name: string; + func: (...args: any[]) => unknown; + args: string[]; + }[] = []; Object.keys(obj).forEach((key) => { const prop = obj[key]; if (typeof prop === 'function') { @@ -24,18 +34,23 @@ function flatTree(obj, namespace = '') { return functions; } -export function getMethods(obj) { +export function getMethods(obj: unknown) { if (typeof obj !== 'object') return undefined; - let functions; - let m; - if (obj.__proto__) m = obj.__proto__.__proto__; - if (!m) m = obj; + let functions: + | { + name: string; + args: string[]; + }[] + | undefined; + let m: { [key: string]: (...args: any[]) => unknown } | undefined; + if ((obj as any).__proto__) m = (obj as any).__proto__.__proto__; + if (!m) m = obj as any; Object.getOwnPropertyNames(m).forEach((key) => { const propDescriptor = Object.getOwnPropertyDescriptor(m, key); if (!propDescriptor || 'get' in propDescriptor || 'set' in propDescriptor) return; - const prop = m[key]; + const prop = m![key]; if (typeof prop === 'function' && key !== 'constructor') { if (!functions) functions = []; functions.push({ @@ -47,15 +62,17 @@ export function getMethods(obj) { return functions; } -export function getActionsArray(actionCreators) { +export function getActionsArray(actionCreators: { + [key: string]: (...args: any[]) => unknown; +}) { if (Array.isArray(actionCreators)) return actionCreators; return flatTree(actionCreators); } -/* eslint-disable no-new-func */ -const interpretArg = (arg) => new Function('return ' + arg)(); +// eslint-disable-next-line @typescript-eslint/no-implied-eval +const interpretArg = (arg: string) => new Function('return ' + arg)(); -function evalArgs(inArgs, restArgs) { +function evalArgs(inArgs: string[], restArgs: string) { const args = inArgs.map(interpretArg); if (!restArgs) return args; const rest = interpretArg(restArgs); @@ -63,8 +80,14 @@ function evalArgs(inArgs, restArgs) { throw new Error('rest must be an array'); } -export function evalAction(action, actionCreators) { +export function evalAction( + action: string | { args: string[]; rest: string; selected: string }, + actionCreators: { + [selected: string]: { func: (...args: any[]) => Action }; + } +) { if (typeof action === 'string') { + // eslint-disable-next-line @typescript-eslint/no-implied-eval return new Function('return ' + action)(); } @@ -73,12 +96,17 @@ export function evalAction(action, actionCreators) { return actionCreator(...args); } -export function evalMethod(action, obj) { +export function evalMethod( + action: string | { args: string[]; rest: string; name: string }, + obj: unknown +) { if (typeof action === 'string') { + // eslint-disable-next-line @typescript-eslint/no-implied-eval return new Function('return ' + action).call(obj); } const args = evalArgs(action.args, action.rest); + // eslint-disable-next-line @typescript-eslint/no-implied-eval return new Function('args', `return this.${action.name}(args)`).apply( obj, args @@ -86,7 +114,7 @@ export function evalMethod(action, obj) { } /* eslint-enable */ -function tryCatchStringify(obj) { +function tryCatchStringify(obj: unknown) { try { return JSON.stringify(obj); } catch (err) { @@ -94,11 +122,26 @@ function tryCatchStringify(obj) { if (process.env.NODE_ENV !== 'production') console.log('Failed to stringify', err); /* eslint-enable no-console */ - return jsan.stringify(obj, null, null, { circular: '[CIRCULAR]' }); + return jsan.stringify( + obj, + (null as unknown) as undefined, + (null as unknown) as undefined, + ({ + circular: '[CIRCULAR]', + } as unknown) as boolean + ); } } -export function stringify(obj, serialize) { +export function stringify( + obj: unknown, + serialize?: + | { + replacer?: (key: string, value: unknown) => unknown; + options?: unknown | boolean; + } + | true +) { if (typeof serialize === 'undefined') { return tryCatchStringify(obj); } @@ -106,23 +149,44 @@ export function stringify(obj, serialize) { return jsan.stringify( obj, function (key, value) { - if (value && typeof value.toJS === 'function') return value.toJS(); + if (value && typeof (value as any).toJS === 'function') + return (value as any).toJS(); return value; }, - null, + (null as unknown) as undefined, true ); } - return jsan.stringify(obj, serialize.replacer, null, serialize.options); + return jsan.stringify( + obj, + serialize.replacer, + (null as unknown) as undefined, + serialize.options as boolean + ); } -export function getSeralizeParameter(config, param) { +export function getSeralizeParameter( + config: { + serialize?: { + immutable?: typeof Immutable; + refs?: (new (data: any) => unknown)[] | null; + replacer?: (key: string, value: unknown) => unknown; + options?: unknown | boolean; + }; + }, + param: string +): + | { + replacer?: (key: string, value: unknown) => unknown; + options: unknown | boolean; + } + | undefined { const serialize = config.serialize; if (serialize) { if (serialize === true) return { options: true }; if (serialize.immutable) { return { - replacer: seralizeImmutable(serialize.immutable, serialize.refs) + replacer: immutableSerialize(serialize.immutable, serialize.refs) .replacer, options: serialize.options || true, }; @@ -131,7 +195,12 @@ export function getSeralizeParameter(config, param) { return { replacer: serialize.replacer, options: serialize.options || true }; } - const value = config[param]; + const value = (config as { + [param: string]: { + replacer?: (key: string, value: unknown) => unknown; + options: unknown | boolean; + }; + })[param]; if (typeof value === 'undefined') return undefined; // eslint-disable-next-line no-console console.warn( @@ -139,12 +208,15 @@ export function getSeralizeParameter(config, param) { ' 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 }; return value; } -export function getStackTrace(config, toExcludeFromTrace) { +export function getStackTrace( + // eslint-disable-next-line @typescript-eslint/ban-types + config: { trace?: () => {}; traceLimit: number }, + // eslint-disable-next-line @typescript-eslint/ban-types + toExcludeFromTrace?: Function | undefined +) { if (!config.trace) return undefined; if (typeof config.trace === 'function') return config.trace(); @@ -169,7 +241,7 @@ export 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)) diff --git a/packages/redux-devtools-core/test/app.spec.tsx b/packages/redux-devtools-core/test/app.spec.tsx index 71b42159..d7ce85ee 100644 --- a/packages/redux-devtools-core/test/app.spec.tsx +++ b/packages/redux-devtools-core/test/app.spec.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { Component } from 'react'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; -import { mount } from 'enzyme'; +import { mount, ReactWrapper } from 'enzyme'; // import { mountToJson } from 'enzyme-to-json'; import App from '../src/app/containers/App'; import api from '../src/app/middlewares/api'; @@ -10,7 +10,7 @@ import rootReducer from '../src/app/reducers'; import { DATA_TYPE_KEY } from '../src/app/constants/dataTypes'; import stringifyJSON from '../src/app/utils/stringifyJSON'; -let wrapper; +let wrapper: ReactWrapper; const store = createStore(rootReducer, applyMiddleware(exportState, api)); diff --git a/packages/redux-devtools-core/webpack.config.ts b/packages/redux-devtools-core/webpack.config.ts index 342b9751..75d1c5ef 100644 --- a/packages/redux-devtools-core/webpack.config.ts +++ b/packages/redux-devtools-core/webpack.config.ts @@ -6,7 +6,7 @@ import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; module.exports = (env: { development?: boolean; platform?: string } = {}) => ({ mode: env.development ? 'development' : 'production', entry: { - app: './index', + app: './src/index', }, output: { path: path.resolve(__dirname, `build/${env.platform as string}`), diff --git a/packages/redux-devtools-serialize/src/immutable/index.ts b/packages/redux-devtools-serialize/src/immutable/index.ts index b8fd874b..08382573 100644 --- a/packages/redux-devtools-serialize/src/immutable/index.ts +++ b/packages/redux-devtools-serialize/src/immutable/index.ts @@ -35,3 +35,4 @@ export default function ( serialize: serialize, }; } +export { default as serialize } from './serialize'; diff --git a/packages/redux-devtools-serialize/src/index.ts b/packages/redux-devtools-serialize/src/index.ts index 600d22c0..c2f42ae8 100644 --- a/packages/redux-devtools-serialize/src/index.ts +++ b/packages/redux-devtools-serialize/src/index.ts @@ -1,5 +1,4 @@ -import immutable from './immutable'; - -module.exports = { - immutable: immutable, -}; +export { + default as immutable, + serialize as immutableSerialize, +} from './immutable';