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'; import { ConnectionOptions, StoreAction } from './actions';
interface Props { interface Props {
socketOptions: ConnectionOptions; socketOptions?: ConnectionOptions;
} }
class Root extends Component<Props> { 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 React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import App from './src/app'; import App from './app';
render(<App />, document.getElementById('root')); render(<App />, document.getElementById('root'));

View File

@ -1,4 +1,7 @@
import mapValues from 'lodash/mapValues'; import mapValues from 'lodash/mapValues';
import { PerformAction } from 'redux-devtools-instrument';
import { Action } from 'redux';
import { State } from '../app/reducers/instances';
export const FilterState = { export const FilterState = {
DO_NOT_FILTER: 'DO_NOT_FILTER', DO_NOT_FILTER: 'DO_NOT_FILTER',
@ -6,19 +9,25 @@ export const FilterState = {
WHITELIST_SPECIFIC: 'WHITELIST_SPECIFIC', WHITELIST_SPECIFIC: 'WHITELIST_SPECIFIC',
}; };
export function arrToRegex(v) { export function arrToRegex(v: string | string[]) {
return typeof v === 'string' ? v : v.join('|'); 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; if (!actionsFilter) return actionsById;
return mapValues(actionsById, (action, id) => ({ return mapValues(actionsById, (action, id: number) => ({
...action, ...action,
action: actionsFilter(action.action, id), 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; if (!statesFilter) return computedStates;
return computedStates.map((state, idx) => ({ return computedStates.map((state, idx) => ({
...state, ...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) { if (config.actionsBlacklist || config.actionsWhitelist) {
return { return {
whitelist: config.actionsWhitelist && config.actionsWhitelist.join('|'), whitelist: config.actionsWhitelist && config.actionsWhitelist.join('|'),
@ -36,33 +55,53 @@ export function getLocalFilter(config) {
return undefined; return undefined;
} }
interface DevToolsOptions {
filter?:
| typeof FilterState.DO_NOT_FILTER
| typeof FilterState.BLACKLIST_SPECIFIC
| typeof FilterState.WHITELIST_SPECIFIC;
whitelist?: string;
blacklist?: string;
}
function getDevToolsOptions() { function getDevToolsOptions() {
return (typeof window !== 'undefined' && window.devToolsOptions) || {}; return (
(typeof window !== 'undefined' &&
(window as { devToolsOptions?: DevToolsOptions }).devToolsOptions) ||
{}
);
} }
export function isFiltered(action, localFilter) { export function isFiltered(
const { type } = action.action || action; action: PerformAction<Action<unknown>> | Action<unknown>,
localFilter?: LocalFilter
) {
const { type } = (action as PerformAction<Action<unknown>>).action || action;
const opts = getDevToolsOptions(); const opts = getDevToolsOptions();
if ( if (
(!localFilter && (!localFilter &&
opts.filter && opts.filter &&
opts.filter === FilterState.DO_NOT_FILTER) || opts.filter === FilterState.DO_NOT_FILTER) ||
(type && typeof type.match !== 'function') (type && typeof (type as string).match !== 'function')
) )
return false; return false;
const { whitelist, blacklist } = localFilter || opts; const { whitelist, blacklist } = localFilter || opts;
return ( return (
(whitelist && !type.match(whitelist)) || // eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
(blacklist && type.match(blacklist)) (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; if (!filters) return state;
const filteredStagedActionIds = []; const filteredStagedActionIds: number[] = [];
const filteredComputedStates = []; const filteredComputedStates: {
state: unknown;
error?: string | undefined;
}[] = [];
state.stagedActionIds.forEach((id, idx) => { state.stagedActionIds.forEach((id, idx) => {
if (!isFiltered(state.actionsById[id], filters)) { if (!isFiltered(state.actionsById[id], filters)) {
@ -79,13 +118,13 @@ export function filterStagedActions(state, filters) {
} }
export function filterState( export function filterState(
state, state: State,
type, type: string,
localFilter, localFilter: LocalFilter,
stateSanitizer, stateSanitizer: (state: unknown, actionId: number) => unknown,
actionSanitizer, actionSanitizer: (action: Action<unknown>, id: number) => Action,
nextActionId, nextActionId: number,
predicate predicate: (currState: unknown, currAction: Action<unknown>) => boolean
) { ) {
if (type === 'ACTION') if (type === 'ACTION')
return !stateSanitizer ? state : stateSanitizer(state, nextActionId - 1); return !stateSanitizer ? state : stateSanitizer(state, nextActionId - 1);
@ -97,9 +136,14 @@ export function filterState(
localFilter || localFilter ||
(filter && filter !== FilterState.DO_NOT_FILTER) (filter && filter !== FilterState.DO_NOT_FILTER)
) { ) {
const filteredStagedActionIds = []; const filteredStagedActionIds: number[] = [];
const filteredComputedStates = []; const filteredComputedStates: {
const sanitizedActionsById = actionSanitizer && {}; state: unknown;
error?: string | undefined;
}[] = [];
const sanitizedActionsById: {
[id: number]: PerformAction<Action<unknown>>;
} = actionSanitizer && {};
const { actionsById } = state; const { actionsById } = state;
const { computedStates } = 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 mapValues from 'lodash/mapValues';
import jsan from 'jsan'; 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 // eslint-disable-next-line no-console
console.warn( console.warn(
`\`${param}\` parameter for Redux DevTools Extension is deprecated. Use \`serialize\` parameter instead:` + `\`${param}\` parameter for Redux DevTools Extension is deprecated. Use \`serialize\` parameter instead:` +
@ -11,8 +14,20 @@ function deprecate(param) {
} }
export default function importState( export default function importState(
state, state: string,
{ deserializeState, deserializeAction, serialize } {
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; if (!state) return undefined;
let parse = jsan.parse; let parse = jsan.parse;
@ -21,19 +36,38 @@ export default function importState(
parse = (v) => parse = (v) =>
jsan.parse( jsan.parse(
v, v,
seralizeImmutable(serialize.immutable, serialize.refs).reviver immutableSerialize(serialize.immutable!, serialize.refs).reviver
); );
} else if (serialize.reviver) { } else if (serialize.reviver) {
parse = (v) => jsan.parse(v, serialize.reviver); parse = (v) => jsan.parse(v, serialize.reviver);
} }
} }
let preloadedState; let preloadedState: State | undefined;
let nextLiftedState = parse(state); let nextLiftedState: State = parse(state) as State;
if (nextLiftedState.payload) { if (
if (nextLiftedState.preloadedState) ((nextLiftedState as unknown) as {
preloadedState = parse(nextLiftedState.preloadedState); payload?: string;
nextLiftedState = parse(nextLiftedState.payload); 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) { if (deserializeState) {
deprecate('deserializeState'); deprecate('deserializeState');
@ -41,17 +75,19 @@ export default function importState(
nextLiftedState.computedStates = nextLiftedState.computedStates.map( nextLiftedState.computedStates = nextLiftedState.computedStates.map(
(computedState) => ({ (computedState) => ({
...computedState, ...computedState,
state: deserializeState(computedState.state), state: deserializeState(computedState.state as string),
}) })
); );
} }
if (typeof nextLiftedState.committedState !== 'undefined') { if (typeof nextLiftedState.committedState !== 'undefined') {
nextLiftedState.committedState = deserializeState( nextLiftedState.committedState = deserializeState(
nextLiftedState.committedState nextLiftedState.committedState as string
); );
} }
if (typeof preloadedState !== 'undefined') { if (typeof preloadedState !== 'undefined') {
preloadedState = deserializeState(preloadedState); preloadedState = deserializeState(
(preloadedState as unknown) as string
) as State;
} }
} }
if (deserializeAction) { if (deserializeAction) {
@ -60,7 +96,7 @@ export default function importState(
nextLiftedState.actionsById, nextLiftedState.actionsById,
(liftedAction) => ({ (liftedAction) => ({
...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 getParams from 'get-params';
import jsan from 'jsan'; import jsan from 'jsan';
import { nanoid } from 'nanoid/non-secure'; 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); return id || nanoid(7);
} }
function flatTree(obj, namespace = '') { // eslint-disable-next-line @typescript-eslint/ban-types
let functions = []; function flatTree(
obj: { [key: string]: (...args: any[]) => unknown },
namespace = ''
) {
let functions: {
name: string;
func: (...args: any[]) => unknown;
args: string[];
}[] = [];
Object.keys(obj).forEach((key) => { Object.keys(obj).forEach((key) => {
const prop = obj[key]; const prop = obj[key];
if (typeof prop === 'function') { if (typeof prop === 'function') {
@ -24,18 +34,23 @@ function flatTree(obj, namespace = '') {
return functions; return functions;
} }
export function getMethods(obj) { export function getMethods(obj: unknown) {
if (typeof obj !== 'object') return undefined; if (typeof obj !== 'object') return undefined;
let functions; let functions:
let m; | {
if (obj.__proto__) m = obj.__proto__.__proto__; name: string;
if (!m) m = obj; 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) => { Object.getOwnPropertyNames(m).forEach((key) => {
const propDescriptor = Object.getOwnPropertyDescriptor(m, key); const propDescriptor = Object.getOwnPropertyDescriptor(m, key);
if (!propDescriptor || 'get' in propDescriptor || 'set' in propDescriptor) if (!propDescriptor || 'get' in propDescriptor || 'set' in propDescriptor)
return; return;
const prop = m[key]; const prop = m![key];
if (typeof prop === 'function' && key !== 'constructor') { if (typeof prop === 'function' && key !== 'constructor') {
if (!functions) functions = []; if (!functions) functions = [];
functions.push({ functions.push({
@ -47,15 +62,17 @@ export function getMethods(obj) {
return functions; return functions;
} }
export function getActionsArray(actionCreators) { export function getActionsArray(actionCreators: {
[key: string]: (...args: any[]) => unknown;
}) {
if (Array.isArray(actionCreators)) return actionCreators; if (Array.isArray(actionCreators)) return actionCreators;
return flatTree(actionCreators); return flatTree(actionCreators);
} }
/* eslint-disable no-new-func */ // eslint-disable-next-line @typescript-eslint/no-implied-eval
const interpretArg = (arg) => new Function('return ' + arg)(); const interpretArg = (arg: string) => new Function('return ' + arg)();
function evalArgs(inArgs, restArgs) { function evalArgs(inArgs: string[], restArgs: string) {
const args = inArgs.map(interpretArg); const args = inArgs.map(interpretArg);
if (!restArgs) return args; if (!restArgs) return args;
const rest = interpretArg(restArgs); const rest = interpretArg(restArgs);
@ -63,8 +80,14 @@ function evalArgs(inArgs, restArgs) {
throw new Error('rest must be an array'); 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') { if (typeof action === 'string') {
// eslint-disable-next-line @typescript-eslint/no-implied-eval
return new Function('return ' + action)(); return new Function('return ' + action)();
} }
@ -73,12 +96,17 @@ export function evalAction(action, actionCreators) {
return actionCreator(...args); 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') { if (typeof action === 'string') {
// eslint-disable-next-line @typescript-eslint/no-implied-eval
return new Function('return ' + action).call(obj); return new Function('return ' + action).call(obj);
} }
const args = evalArgs(action.args, action.rest); 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( return new Function('args', `return this.${action.name}(args)`).apply(
obj, obj,
args args
@ -86,7 +114,7 @@ export function evalMethod(action, obj) {
} }
/* eslint-enable */ /* eslint-enable */
function tryCatchStringify(obj) { function tryCatchStringify(obj: unknown) {
try { try {
return JSON.stringify(obj); return JSON.stringify(obj);
} catch (err) { } catch (err) {
@ -94,11 +122,26 @@ function tryCatchStringify(obj) {
if (process.env.NODE_ENV !== 'production') if (process.env.NODE_ENV !== 'production')
console.log('Failed to stringify', err); console.log('Failed to stringify', err);
/* eslint-enable no-console */ /* 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') { if (typeof serialize === 'undefined') {
return tryCatchStringify(obj); return tryCatchStringify(obj);
} }
@ -106,23 +149,44 @@ export function stringify(obj, serialize) {
return jsan.stringify( return jsan.stringify(
obj, obj,
function (key, value) { 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; return value;
}, },
null, (null as unknown) as undefined,
true 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; const serialize = config.serialize;
if (serialize) { if (serialize) {
if (serialize === true) return { options: true }; if (serialize === true) return { options: true };
if (serialize.immutable) { if (serialize.immutable) {
return { return {
replacer: seralizeImmutable(serialize.immutable, serialize.refs) replacer: immutableSerialize(serialize.immutable, serialize.refs)
.replacer, .replacer,
options: serialize.options || true, options: serialize.options || true,
}; };
@ -131,7 +195,12 @@ export function getSeralizeParameter(config, param) {
return { replacer: serialize.replacer, options: serialize.options || true }; 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; if (typeof value === 'undefined') return undefined;
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.warn( console.warn(
@ -139,12 +208,15 @@ export function getSeralizeParameter(config, param) {
' https://github.com/zalmoxisus/redux-devtools-extension/releases/tag/v2.12.1' ' 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; 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 (!config.trace) return undefined;
if (typeof config.trace === 'function') return config.trace(); if (typeof config.trace === 'function') return config.trace();
@ -169,7 +241,7 @@ export function getStackTrace(config, toExcludeFromTrace) {
typeof Error.stackTraceLimit !== 'number' || typeof Error.stackTraceLimit !== 'number' ||
Error.stackTraceLimit > traceLimit Error.stackTraceLimit > traceLimit
) { ) {
const frames = stack.split('\n'); const frames = stack!.split('\n');
if (frames.length > traceLimit) { if (frames.length > traceLimit) {
stack = frames stack = frames
.slice(0, traceLimit + extraFrames + (frames[0] === 'Error' ? 1 : 0)) .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 { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux'; import { createStore, applyMiddleware } from 'redux';
import { mount } from 'enzyme'; import { mount, ReactWrapper } from 'enzyme';
// import { mountToJson } from 'enzyme-to-json'; // import { mountToJson } from 'enzyme-to-json';
import App from '../src/app/containers/App'; import App from '../src/app/containers/App';
import api from '../src/app/middlewares/api'; 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 { DATA_TYPE_KEY } from '../src/app/constants/dataTypes';
import stringifyJSON from '../src/app/utils/stringifyJSON'; import stringifyJSON from '../src/app/utils/stringifyJSON';
let wrapper; let wrapper: ReactWrapper<unknown, unknown, Component>;
const store = createStore(rootReducer, applyMiddleware(exportState, api)); 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 } = {}) => ({ module.exports = (env: { development?: boolean; platform?: string } = {}) => ({
mode: env.development ? 'development' : 'production', mode: env.development ? 'development' : 'production',
entry: { entry: {
app: './index', app: './src/index',
}, },
output: { output: {
path: path.resolve(__dirname, `build/${env.platform as string}`), path: path.resolve(__dirname, `build/${env.platform as string}`),

View File

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

View File

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