This commit is contained in:
Nathan Bierema 2020-10-25 17:29:27 -04:00
parent 5b6a0adbfd
commit d3aa75aeaa
11 changed files with 235 additions and 87 deletions

View File

@ -9,7 +9,7 @@ import { StoreState } from './reducers';
import { ConnectionOptions, StoreAction } from './actions';
interface Props {
socketOptions: ConnectionOptions;
socketOptions?: ConnectionOptions;
}
class Root extends Component<Props> {

View File

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

View File

@ -1,6 +1,6 @@
import React from 'react';
import { render } from 'react-dom';
import App from './src/app';
import App from './app';
render(<App />, document.getElementById('root'));

View File

@ -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<Action<unknown>> },
actionsFilter: (action: Action<unknown>, 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<unknown>> | Action<unknown>,
localFilter?: LocalFilter
) {
const { type } = (action as PerformAction<Action<unknown>>).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<unknown>, id: number) => Action,
nextActionId: number,
predicate: (currState: unknown, currAction: Action<unknown>) => 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<Action<unknown>>;
} = actionSanitizer && {};
const { actionsById } = state;
const { computedStates } = state;

View File

@ -0,0 +1,4 @@
declare module 'get-params' {
function getParams(func: (...args: any[]) => unknown): string[];
export default getParams;
}

View File

@ -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<unknown>;
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),
})
);
}

View File

@ -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<unknown> };
}
) {
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))

View File

@ -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<unknown, unknown, Component>;
const store = createStore(rootReducer, applyMiddleware(exportState, api));

View File

@ -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}`),

View File

@ -35,3 +35,4 @@ export default function (
serialize: serialize,
};
}
export { default as serialize } from './serialize';

View File

@ -1,5 +1,4 @@
import immutable from './immutable';
module.exports = {
immutable: immutable,
};
export {
default as immutable,
serialize as immutableSerialize,
} from './immutable';