mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-22 09:36:43 +03:00
chore(extension): convert to TypeScript (#756)
* Start work * More work * stash * stash * stash * Eliminate relay * Fix * Define page script to content script messages * Define ContentScriptToPageScriptMessage * Not required * Fill out more types * More work on types * More type fixes * Add type * More improvements to types * More work on types * Fix more type errors * More changes * More work * Work * Fix build * Fix lint * Fix more lint * Fix bug * Fix tests
This commit is contained in:
parent
326cfdf217
commit
a418284a4a
|
@ -1,5 +1,9 @@
|
||||||
{
|
{
|
||||||
"presets": ["@babel/preset-env", "@babel/preset-react"],
|
"presets": [
|
||||||
|
"@babel/preset-env",
|
||||||
|
"@babel/preset-react",
|
||||||
|
"@babel/preset-typescript"
|
||||||
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
["@babel/plugin-proposal-decorators", { "legacy": true }],
|
["@babel/plugin-proposal-decorators", { "legacy": true }],
|
||||||
["@babel/plugin-proposal-class-properties", { "loose": true }]
|
["@babel/plugin-proposal-class-properties", { "loose": true }]
|
||||||
|
|
|
@ -28,7 +28,8 @@
|
||||||
"test:app": "cross-env BABEL_ENV=test jest test/app",
|
"test:app": "cross-env BABEL_ENV=test jest test/app",
|
||||||
"test:chrome": "jest test/chrome",
|
"test:chrome": "jest test/chrome",
|
||||||
"test:electron": "jest test/electron",
|
"test:electron": "jest test/electron",
|
||||||
"test": "npm run test:app && npm run build:extension && npm run test:chrome && npm run test:electron"
|
"test": "npm run test:app && npm run build:extension && npm run test:chrome && npm run test:electron",
|
||||||
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@redux-devtools/app": "^1.0.0-8",
|
"@redux-devtools/app": "^1.0.0-8",
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
"@redux-devtools/serialize": "^0.3.0",
|
"@redux-devtools/serialize": "^0.3.0",
|
||||||
"@redux-devtools/slider-monitor": "^2.0.0-8",
|
"@redux-devtools/slider-monitor": "^2.0.0-8",
|
||||||
"@redux-devtools/utils": "^1.0.0-6",
|
"@redux-devtools/utils": "^1.0.0-6",
|
||||||
|
"@types/jsan": "^3.1.2",
|
||||||
"jsan": "^3.1.13",
|
"jsan": "^3.1.13",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^16.14.0",
|
"react": "^16.14.0",
|
||||||
|
|
|
@ -1,18 +1,35 @@
|
||||||
import mapValues from 'lodash/mapValues';
|
import mapValues from 'lodash/mapValues';
|
||||||
|
import { Config } from '../../browser/extension/inject/pageScript';
|
||||||
|
import { Action } from 'redux';
|
||||||
|
import { LiftedState, PerformAction } from '@redux-devtools/instrument';
|
||||||
|
|
||||||
export const FilterState = {
|
export type FilterStateValue =
|
||||||
|
| 'DO_NOT_FILTER'
|
||||||
|
| 'BLACKLIST_SPECIFIC'
|
||||||
|
| 'WHITELIST_SPECIFIC';
|
||||||
|
|
||||||
|
export const FilterState: { [K in FilterStateValue]: FilterStateValue } = {
|
||||||
DO_NOT_FILTER: 'DO_NOT_FILTER',
|
DO_NOT_FILTER: 'DO_NOT_FILTER',
|
||||||
BLACKLIST_SPECIFIC: 'BLACKLIST_SPECIFIC',
|
BLACKLIST_SPECIFIC: 'BLACKLIST_SPECIFIC',
|
||||||
WHITELIST_SPECIFIC: 'WHITELIST_SPECIFIC',
|
WHITELIST_SPECIFIC: 'WHITELIST_SPECIFIC',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getLocalFilter(config) {
|
function isArray(arg: unknown): arg is readonly unknown[] {
|
||||||
|
return Array.isArray(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LocalFilter {
|
||||||
|
readonly whitelist: string | undefined;
|
||||||
|
readonly blacklist: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocalFilter(config: Config): LocalFilter | undefined {
|
||||||
if (config.actionsBlacklist || config.actionsWhitelist) {
|
if (config.actionsBlacklist || config.actionsWhitelist) {
|
||||||
return {
|
return {
|
||||||
whitelist: Array.isArray(config.actionsWhitelist)
|
whitelist: isArray(config.actionsWhitelist)
|
||||||
? config.actionsWhitelist.join('|')
|
? config.actionsWhitelist.join('|')
|
||||||
: config.actionsWhitelist,
|
: config.actionsWhitelist,
|
||||||
blacklist: Array.isArray(config.actionsBlacklist)
|
blacklist: isArray(config.actionsBlacklist)
|
||||||
? config.actionsBlacklist.join('|')
|
? config.actionsBlacklist.join('|')
|
||||||
: config.actionsBlacklist,
|
: config.actionsBlacklist,
|
||||||
};
|
};
|
||||||
|
@ -20,38 +37,48 @@ export function getLocalFilter(config) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const noFiltersApplied = (localFilter) =>
|
export const noFiltersApplied = (localFilter: LocalFilter | undefined) =>
|
||||||
// !predicate &&
|
// !predicate &&
|
||||||
!localFilter &&
|
!localFilter &&
|
||||||
(!window.devToolsOptions ||
|
(!window.devToolsOptions ||
|
||||||
!window.devToolsOptions.filter ||
|
!window.devToolsOptions.filter ||
|
||||||
window.devToolsOptions.filter === FilterState.DO_NOT_FILTER);
|
window.devToolsOptions.filter === FilterState.DO_NOT_FILTER);
|
||||||
|
|
||||||
export function isFiltered(action, localFilter) {
|
export function isFiltered<A extends Action<unknown>>(
|
||||||
|
action: A | string,
|
||||||
|
localFilter: LocalFilter | undefined
|
||||||
|
) {
|
||||||
if (
|
if (
|
||||||
noFiltersApplied(localFilter) ||
|
noFiltersApplied(localFilter) ||
|
||||||
(typeof action !== 'string' && typeof action.type.match !== 'function')
|
(typeof action !== 'string' &&
|
||||||
|
typeof (action.type as string).match !== 'function')
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { whitelist, blacklist } = localFilter || window.devToolsOptions || {};
|
const { whitelist, blacklist } = localFilter || window.devToolsOptions || {};
|
||||||
const actionType = action.type || action;
|
const actionType = ((action as A).type || action) as string;
|
||||||
return (
|
return (
|
||||||
(whitelist && !actionType.match(whitelist)) ||
|
(whitelist && !actionType.match(whitelist)) ||
|
||||||
(blacklist && actionType.match(blacklist))
|
(blacklist && actionType.match(blacklist))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterActions(actionsById, actionSanitizer) {
|
function filterActions<A extends Action<unknown>>(
|
||||||
|
actionsById: { [p: number]: PerformAction<A> },
|
||||||
|
actionSanitizer: ((action: A, id: number) => A) | undefined
|
||||||
|
): { [p: number]: PerformAction<A> } {
|
||||||
if (!actionSanitizer) return actionsById;
|
if (!actionSanitizer) return actionsById;
|
||||||
return mapValues(actionsById, (action, id) => ({
|
return mapValues(actionsById, (action, id) => ({
|
||||||
...action,
|
...action,
|
||||||
action: actionSanitizer(action.action, id),
|
action: actionSanitizer(action.action, id as unknown as number),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterStates(computedStates, stateSanitizer) {
|
function filterStates<S>(
|
||||||
|
computedStates: { state: S; error?: string | undefined }[],
|
||||||
|
stateSanitizer: ((state: S, index: number) => S) | undefined
|
||||||
|
) {
|
||||||
if (!stateSanitizer) return computedStates;
|
if (!stateSanitizer) return computedStates;
|
||||||
return computedStates.map((state, idx) => ({
|
return computedStates.map((state, idx) => ({
|
||||||
...state,
|
...state,
|
||||||
|
@ -59,23 +86,19 @@ function filterStates(computedStates, stateSanitizer) {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterState(
|
export function filterState<S, A extends Action<unknown>>(
|
||||||
state,
|
state: LiftedState<S, A, unknown>,
|
||||||
type,
|
localFilter: LocalFilter | undefined,
|
||||||
localFilter,
|
stateSanitizer: ((state: S, index: number) => S) | undefined,
|
||||||
stateSanitizer,
|
actionSanitizer: ((action: A, id: number) => A) | undefined,
|
||||||
actionSanitizer,
|
predicate: ((state: S, action: A) => boolean) | undefined
|
||||||
nextActionId,
|
): LiftedState<S, A, unknown> {
|
||||||
predicate
|
|
||||||
) {
|
|
||||||
if (type === 'ACTION') {
|
|
||||||
return !stateSanitizer ? state : stateSanitizer(state, nextActionId - 1);
|
|
||||||
} else if (type !== 'STATE') return state;
|
|
||||||
|
|
||||||
if (predicate || !noFiltersApplied(localFilter)) {
|
if (predicate || !noFiltersApplied(localFilter)) {
|
||||||
const filteredStagedActionIds = [];
|
const filteredStagedActionIds: number[] = [];
|
||||||
const filteredComputedStates = [];
|
const filteredComputedStates: { state: S; error?: string | undefined }[] =
|
||||||
const sanitizedActionsById = actionSanitizer && {};
|
[];
|
||||||
|
const sanitizedActionsById: { [p: number]: PerformAction<A> } | undefined =
|
||||||
|
actionSanitizer && {};
|
||||||
const { actionsById } = state;
|
const { actionsById } = state;
|
||||||
const { computedStates } = state;
|
const { computedStates } = state;
|
||||||
|
|
||||||
|
@ -97,7 +120,7 @@ export function filterState(
|
||||||
: liftedState
|
: liftedState
|
||||||
);
|
);
|
||||||
if (actionSanitizer) {
|
if (actionSanitizer) {
|
||||||
sanitizedActionsById[id] = {
|
sanitizedActionsById![id] = {
|
||||||
...liftedAction,
|
...liftedAction,
|
||||||
action: actionSanitizer(currAction, id),
|
action: actionSanitizer(currAction, id),
|
||||||
};
|
};
|
||||||
|
@ -120,14 +143,27 @@ export function filterState(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startingFrom(
|
export interface PartialLiftedState<S, A extends Action<unknown>> {
|
||||||
sendingActionId,
|
readonly actionsById: { [actionId: number]: PerformAction<A> };
|
||||||
state,
|
readonly computedStates: { state: S; error?: string }[];
|
||||||
localFilter,
|
readonly stagedActionIds: readonly number[];
|
||||||
stateSanitizer,
|
readonly currentStateIndex: number;
|
||||||
actionSanitizer,
|
readonly nextActionId: number;
|
||||||
predicate
|
readonly committedState?: S;
|
||||||
) {
|
}
|
||||||
|
|
||||||
|
export function startingFrom<S, A extends Action<unknown>>(
|
||||||
|
sendingActionId: number,
|
||||||
|
state: LiftedState<S, A, unknown>,
|
||||||
|
localFilter: LocalFilter | undefined,
|
||||||
|
stateSanitizer: (<S>(state: S, index: number) => S) | undefined,
|
||||||
|
actionSanitizer:
|
||||||
|
| (<A extends Action<unknown>>(action: A, id: number) => A)
|
||||||
|
| undefined,
|
||||||
|
predicate:
|
||||||
|
| (<S, A extends Action<unknown>>(state: S, action: A) => boolean)
|
||||||
|
| undefined
|
||||||
|
): LiftedState<S, A, unknown> | PartialLiftedState<S, A> | undefined {
|
||||||
const stagedActionIds = state.stagedActionIds;
|
const stagedActionIds = state.stagedActionIds;
|
||||||
if (sendingActionId <= stagedActionIds[1]) return state;
|
if (sendingActionId <= stagedActionIds[1]) return state;
|
||||||
const index = stagedActionIds.indexOf(sendingActionId);
|
const index = stagedActionIds.indexOf(sendingActionId);
|
||||||
|
@ -137,7 +173,7 @@ export function startingFrom(
|
||||||
const filteredStagedActionIds = shouldFilter ? [0] : stagedActionIds;
|
const filteredStagedActionIds = shouldFilter ? [0] : stagedActionIds;
|
||||||
const actionsById = state.actionsById;
|
const actionsById = state.actionsById;
|
||||||
const computedStates = state.computedStates;
|
const computedStates = state.computedStates;
|
||||||
const newActionsById = {};
|
const newActionsById: { [key: number]: PerformAction<A> } = {};
|
||||||
const newComputedStates = [];
|
const newComputedStates = [];
|
||||||
let key;
|
let key;
|
||||||
let currAction;
|
let currAction;
|
|
@ -1,5 +0,0 @@
|
||||||
let id = 0;
|
|
||||||
|
|
||||||
export default function generateId(instanceId) {
|
|
||||||
return instanceId || ++id;
|
|
||||||
}
|
|
5
extension/src/app/api/generateInstanceId.ts
Normal file
5
extension/src/app/api/generateInstanceId.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
let id = 0;
|
||||||
|
|
||||||
|
export default function generateId(instanceId: number | undefined) {
|
||||||
|
return instanceId || ++id;
|
||||||
|
}
|
|
@ -1,74 +0,0 @@
|
||||||
import mapValues from 'lodash/mapValues';
|
|
||||||
import jsan from 'jsan';
|
|
||||||
import seralizeImmutable from '@redux-devtools/serialize/lib/immutable/serialize';
|
|
||||||
|
|
||||||
function deprecate(param) {
|
|
||||||
// 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`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function importState(
|
|
||||||
state,
|
|
||||||
{ deserializeState, deserializeAction, serialize }
|
|
||||||
) {
|
|
||||||
if (!state) return undefined;
|
|
||||||
let parse = jsan.parse;
|
|
||||||
if (serialize) {
|
|
||||||
if (serialize.immutable) {
|
|
||||||
parse = (v) =>
|
|
||||||
jsan.parse(
|
|
||||||
v,
|
|
||||||
seralizeImmutable(
|
|
||||||
serialize.immutable,
|
|
||||||
serialize.refs,
|
|
||||||
serialize.replacer,
|
|
||||||
serialize.reviver
|
|
||||||
).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);
|
|
||||||
}
|
|
||||||
if (deserializeState) {
|
|
||||||
deprecate('deserializeState');
|
|
||||||
if (typeof nextLiftedState.computedStates !== 'undefined') {
|
|
||||||
nextLiftedState.computedStates = nextLiftedState.computedStates.map(
|
|
||||||
(computedState) => ({
|
|
||||||
...computedState,
|
|
||||||
state: deserializeState(computedState.state),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (typeof nextLiftedState.committedState !== 'undefined') {
|
|
||||||
nextLiftedState.committedState = deserializeState(
|
|
||||||
nextLiftedState.committedState
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (typeof preloadedState !== 'undefined') {
|
|
||||||
preloadedState = deserializeState(preloadedState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (deserializeAction) {
|
|
||||||
deprecate('deserializeAction');
|
|
||||||
nextLiftedState.actionsById = mapValues(
|
|
||||||
nextLiftedState.actionsById,
|
|
||||||
(liftedAction) => ({
|
|
||||||
...liftedAction,
|
|
||||||
action: deserializeAction(liftedAction.action),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { nextLiftedState, preloadedState };
|
|
||||||
}
|
|
116
extension/src/app/api/importState.ts
Normal file
116
extension/src/app/api/importState.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import mapValues from 'lodash/mapValues';
|
||||||
|
import jsan from 'jsan';
|
||||||
|
import seralizeImmutable from '@redux-devtools/serialize/lib/immutable/serialize';
|
||||||
|
import {
|
||||||
|
Config,
|
||||||
|
SerializeWithImmutable,
|
||||||
|
} from '../../browser/extension/inject/pageScript';
|
||||||
|
import Immutable from 'immutable';
|
||||||
|
import { LiftedState } from '@redux-devtools/instrument';
|
||||||
|
import { Action } from 'redux';
|
||||||
|
|
||||||
|
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`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializeWithRequiredImmutable extends SerializeWithImmutable {
|
||||||
|
readonly immutable: typeof Immutable;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSerializeWithImmutable(
|
||||||
|
serialize: boolean | SerializeWithImmutable
|
||||||
|
): serialize is SerializeWithRequiredImmutable {
|
||||||
|
return !!(serialize as SerializeWithImmutable).immutable;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializeWithRequiredReviver extends SerializeWithImmutable {
|
||||||
|
readonly reviver: (key: string, value: unknown) => unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSerializeWithReviver(
|
||||||
|
serialize: boolean | SerializeWithImmutable
|
||||||
|
): serialize is SerializeWithRequiredReviver {
|
||||||
|
return !!(serialize as SerializeWithImmutable).immutable;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParsedSerializedLiftedState {
|
||||||
|
readonly payload: string;
|
||||||
|
readonly preloadedState?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function importState<S, A extends Action<unknown>>(
|
||||||
|
state: string | undefined,
|
||||||
|
{ deserializeState, deserializeAction, serialize }: Config
|
||||||
|
) {
|
||||||
|
if (!state) return undefined;
|
||||||
|
let parse = jsan.parse;
|
||||||
|
if (serialize) {
|
||||||
|
if (isSerializeWithImmutable(serialize)) {
|
||||||
|
parse = (v) =>
|
||||||
|
jsan.parse(
|
||||||
|
v,
|
||||||
|
seralizeImmutable(
|
||||||
|
serialize.immutable,
|
||||||
|
serialize.refs,
|
||||||
|
serialize.replacer,
|
||||||
|
serialize.reviver
|
||||||
|
).reviver
|
||||||
|
);
|
||||||
|
} else if (isSerializeWithReviver(serialize)) {
|
||||||
|
parse = (v) => jsan.parse(v, serialize.reviver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedSerializedLiftedState:
|
||||||
|
| ParsedSerializedLiftedState
|
||||||
|
| LiftedState<S, A, unknown> = parse(state) as
|
||||||
|
| ParsedSerializedLiftedState
|
||||||
|
| LiftedState<S, A, unknown>;
|
||||||
|
let preloadedState =
|
||||||
|
'payload' in parsedSerializedLiftedState &&
|
||||||
|
parsedSerializedLiftedState.preloadedState
|
||||||
|
? (parse(parsedSerializedLiftedState.preloadedState) as S)
|
||||||
|
: undefined;
|
||||||
|
const nextLiftedState =
|
||||||
|
'payload' in parsedSerializedLiftedState
|
||||||
|
? (parse(parsedSerializedLiftedState.payload) as LiftedState<
|
||||||
|
S,
|
||||||
|
A,
|
||||||
|
unknown
|
||||||
|
>)
|
||||||
|
: parsedSerializedLiftedState;
|
||||||
|
if (deserializeState) {
|
||||||
|
deprecate('deserializeState');
|
||||||
|
if (typeof nextLiftedState.computedStates !== 'undefined') {
|
||||||
|
nextLiftedState.computedStates = nextLiftedState.computedStates.map(
|
||||||
|
(computedState) => ({
|
||||||
|
...computedState,
|
||||||
|
state: deserializeState(computedState.state),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof nextLiftedState.committedState !== 'undefined') {
|
||||||
|
nextLiftedState.committedState = deserializeState(
|
||||||
|
nextLiftedState.committedState
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof preloadedState !== 'undefined') {
|
||||||
|
preloadedState = deserializeState(preloadedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (deserializeAction) {
|
||||||
|
deprecate('deserializeAction');
|
||||||
|
nextLiftedState.actionsById = mapValues(
|
||||||
|
nextLiftedState.actionsById,
|
||||||
|
(liftedAction) => ({
|
||||||
|
...liftedAction,
|
||||||
|
action: deserializeAction(liftedAction.action),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { nextLiftedState, preloadedState };
|
||||||
|
}
|
|
@ -1,393 +0,0 @@
|
||||||
import jsan from 'jsan';
|
|
||||||
import throttle from 'lodash/throttle';
|
|
||||||
import seralizeImmutable from '@redux-devtools/serialize/lib/immutable/serialize';
|
|
||||||
import { getActionsArray } from '@redux-devtools/utils';
|
|
||||||
import { getLocalFilter, isFiltered } from './filters';
|
|
||||||
import importState from './importState';
|
|
||||||
import generateId from './generateInstanceId';
|
|
||||||
|
|
||||||
const listeners = {};
|
|
||||||
export const source = '@devtools-page';
|
|
||||||
|
|
||||||
function windowReplacer(key, value) {
|
|
||||||
if (value && value.window === value) {
|
|
||||||
return '[WINDOW]';
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryCatchStringify(obj) {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(obj);
|
|
||||||
} catch (err) {
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
console.log('Failed to stringify', err);
|
|
||||||
}
|
|
||||||
/* eslint-enable no-console */
|
|
||||||
return jsan.stringify(obj, windowReplacer, null, {
|
|
||||||
circular: '[CIRCULAR]',
|
|
||||||
date: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let stringifyWarned;
|
|
||||||
function stringify(obj, serialize) {
|
|
||||||
const str =
|
|
||||||
typeof serialize === 'undefined'
|
|
||||||
? tryCatchStringify(obj)
|
|
||||||
: jsan.stringify(obj, serialize.replacer, null, serialize.options);
|
|
||||||
|
|
||||||
if (!stringifyWarned && str && str.length > 16 * 1024 * 1024) {
|
|
||||||
// 16 MB
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
console.warn(
|
|
||||||
'Application state or actions payloads are too large making Redux DevTools serialization slow and consuming a lot of memory. See https://git.io/fpcP5 on how to configure it.'
|
|
||||||
);
|
|
||||||
/* eslint-enable no-console */
|
|
||||||
stringifyWarned = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSeralizeParameter(config, param) {
|
|
||||||
const serialize = config.serialize;
|
|
||||||
if (serialize) {
|
|
||||||
if (serialize === true) return { options: true };
|
|
||||||
if (serialize.immutable) {
|
|
||||||
const immutableSerializer = seralizeImmutable(
|
|
||||||
serialize.immutable,
|
|
||||||
serialize.refs,
|
|
||||||
serialize.replacer,
|
|
||||||
serialize.reviver
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
replacer: immutableSerializer.replacer,
|
|
||||||
reviver: immutableSerializer.reviver,
|
|
||||||
options:
|
|
||||||
typeof serialize.options === 'object'
|
|
||||||
? { ...immutableSerializer.options, ...serialize.options }
|
|
||||||
: immutableSerializer.options,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!serialize.replacer && !serialize.reviver) {
|
|
||||||
return { options: serialize.options };
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
replacer: serialize.replacer,
|
|
||||||
reviver: serialize.reviver,
|
|
||||||
options: serialize.options || true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = config[param];
|
|
||||||
if (typeof value === 'undefined') return undefined;
|
|
||||||
// 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`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (typeof serializeState === 'boolean') return { options: value };
|
|
||||||
if (typeof serializeState === 'function') return { replacer: value };
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function post(message) {
|
|
||||||
window.postMessage(message, '*');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStackTrace(config, toExcludeFromTrace) {
|
|
||||||
if (!config.trace) return undefined;
|
|
||||||
if (typeof config.trace === 'function') return config.trace();
|
|
||||||
|
|
||||||
let stack;
|
|
||||||
let extraFrames = 0;
|
|
||||||
let prevStackTraceLimit;
|
|
||||||
const traceLimit = config.traceLimit;
|
|
||||||
const error = Error();
|
|
||||||
if (Error.captureStackTrace) {
|
|
||||||
if (Error.stackTraceLimit < traceLimit) {
|
|
||||||
prevStackTraceLimit = Error.stackTraceLimit;
|
|
||||||
Error.stackTraceLimit = traceLimit;
|
|
||||||
}
|
|
||||||
Error.captureStackTrace(error, toExcludeFromTrace);
|
|
||||||
} else {
|
|
||||||
extraFrames = 3;
|
|
||||||
}
|
|
||||||
stack = error.stack;
|
|
||||||
if (prevStackTraceLimit) Error.stackTraceLimit = prevStackTraceLimit;
|
|
||||||
if (
|
|
||||||
extraFrames ||
|
|
||||||
typeof Error.stackTraceLimit !== 'number' ||
|
|
||||||
Error.stackTraceLimit > traceLimit
|
|
||||||
) {
|
|
||||||
const frames = stack.split('\n');
|
|
||||||
if (frames.length > traceLimit) {
|
|
||||||
stack = frames
|
|
||||||
.slice(0, traceLimit + extraFrames + (frames[0] === 'Error' ? 1 : 0))
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
function amendActionType(action, config, toExcludeFromTrace) {
|
|
||||||
let timestamp = Date.now();
|
|
||||||
let stack = getStackTrace(config, toExcludeFromTrace);
|
|
||||||
if (typeof action === 'string') {
|
|
||||||
return { action: { type: action }, timestamp, stack };
|
|
||||||
}
|
|
||||||
if (!action.type) return { action: { type: 'update' }, timestamp, stack };
|
|
||||||
if (action.action) return stack ? { stack, ...action } : action;
|
|
||||||
return { action, timestamp, stack };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toContentScript(message, serializeState, serializeAction) {
|
|
||||||
if (message.type === 'ACTION') {
|
|
||||||
message.action = stringify(message.action, serializeAction);
|
|
||||||
message.payload = stringify(message.payload, serializeState);
|
|
||||||
} else if (message.type === 'STATE' || message.type === 'PARTIAL_STATE') {
|
|
||||||
const { actionsById, computedStates, committedState, ...rest } =
|
|
||||||
message.payload;
|
|
||||||
message.payload = rest;
|
|
||||||
message.actionsById = stringify(actionsById, serializeAction);
|
|
||||||
message.computedStates = stringify(computedStates, serializeState);
|
|
||||||
message.committedState = typeof committedState !== 'undefined';
|
|
||||||
} else if (message.type === 'EXPORT') {
|
|
||||||
message.payload = stringify(message.payload, serializeAction);
|
|
||||||
if (typeof message.committedState !== 'undefined') {
|
|
||||||
message.committedState = stringify(
|
|
||||||
message.committedState,
|
|
||||||
serializeState
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendMessage(action, state, config, instanceId, name) {
|
|
||||||
let amendedAction = action;
|
|
||||||
if (typeof config !== 'object') {
|
|
||||||
// Legacy: sending actions not from connected part
|
|
||||||
config = {}; // eslint-disable-line no-param-reassign
|
|
||||||
if (action) amendedAction = amendActionType(action, config, sendMessage);
|
|
||||||
}
|
|
||||||
const message = {
|
|
||||||
type: action ? 'ACTION' : 'STATE',
|
|
||||||
action: amendedAction,
|
|
||||||
payload: state,
|
|
||||||
maxAge: config.maxAge,
|
|
||||||
source,
|
|
||||||
name: config.name || name,
|
|
||||||
instanceId: config.instanceId || instanceId || 1,
|
|
||||||
};
|
|
||||||
toContentScript(message, config.serialize, config.serialize);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMessages(event) {
|
|
||||||
if (process.env.BABEL_ENV !== 'test' && (!event || event.source !== window)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const message = event.data;
|
|
||||||
if (!message || message.source !== '@devtools-extension') return;
|
|
||||||
Object.keys(listeners).forEach((id) => {
|
|
||||||
if (message.id && id !== message.id) return;
|
|
||||||
if (typeof listeners[id] === 'function') listeners[id](message);
|
|
||||||
else {
|
|
||||||
listeners[id].forEach((fn) => {
|
|
||||||
fn(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setListener(onMessage, instanceId) {
|
|
||||||
listeners[instanceId] = onMessage;
|
|
||||||
window.addEventListener('message', handleMessages, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const liftListener = (listener, config) => (message) => {
|
|
||||||
let data = {};
|
|
||||||
if (message.type === 'IMPORT') {
|
|
||||||
data.type = 'DISPATCH';
|
|
||||||
data.payload = {
|
|
||||||
type: 'IMPORT_STATE',
|
|
||||||
...importState(message.state, config),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
data = message;
|
|
||||||
}
|
|
||||||
listener(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function disconnect() {
|
|
||||||
window.removeEventListener('message', handleMessages);
|
|
||||||
post({ type: 'DISCONNECT', source });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function connect(preConfig) {
|
|
||||||
const config = preConfig || {};
|
|
||||||
const id = generateId(config.instanceId);
|
|
||||||
if (!config.instanceId) config.instanceId = id;
|
|
||||||
if (!config.name) {
|
|
||||||
config.name =
|
|
||||||
document.title && id === 1 ? document.title : `Instance ${id}`;
|
|
||||||
}
|
|
||||||
if (config.serialize) config.serialize = getSeralizeParameter(config);
|
|
||||||
const actionCreators = config.actionCreators || {};
|
|
||||||
const latency = config.latency;
|
|
||||||
const predicate = config.predicate;
|
|
||||||
const localFilter = getLocalFilter(config);
|
|
||||||
const autoPause = config.autoPause;
|
|
||||||
let isPaused = autoPause;
|
|
||||||
let delayedActions = [];
|
|
||||||
let delayedStates = [];
|
|
||||||
|
|
||||||
const rootListiner = (action) => {
|
|
||||||
if (autoPause) {
|
|
||||||
if (action.type === 'START') isPaused = false;
|
|
||||||
else if (action.type === 'STOP') isPaused = true;
|
|
||||||
}
|
|
||||||
if (action.type === 'DISPATCH') {
|
|
||||||
const payload = action.payload;
|
|
||||||
if (payload.type === 'PAUSE_RECORDING') {
|
|
||||||
isPaused = payload.status;
|
|
||||||
toContentScript({
|
|
||||||
type: 'LIFTED',
|
|
||||||
liftedState: { isPaused },
|
|
||||||
instanceId: id,
|
|
||||||
source,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
listeners[id] = [rootListiner];
|
|
||||||
|
|
||||||
const subscribe = (listener) => {
|
|
||||||
if (!listener) return undefined;
|
|
||||||
const liftedListener = liftListener(listener, config);
|
|
||||||
listeners[id].push(liftedListener);
|
|
||||||
|
|
||||||
return function unsubscribe() {
|
|
||||||
const index = listeners[id].indexOf(liftedListener);
|
|
||||||
listeners[id].splice(index, 1);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const unsubscribe = () => {
|
|
||||||
delete listeners[id];
|
|
||||||
};
|
|
||||||
|
|
||||||
const sendDelayed = throttle(() => {
|
|
||||||
sendMessage(delayedActions, delayedStates, config);
|
|
||||||
delayedActions = [];
|
|
||||||
delayedStates = [];
|
|
||||||
}, latency);
|
|
||||||
|
|
||||||
const send = (action, state) => {
|
|
||||||
if (
|
|
||||||
isPaused ||
|
|
||||||
isFiltered(action, localFilter) ||
|
|
||||||
(predicate && !predicate(state, action))
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let amendedAction = action;
|
|
||||||
const amendedState = config.stateSanitizer
|
|
||||||
? config.stateSanitizer(state)
|
|
||||||
: state;
|
|
||||||
if (action) {
|
|
||||||
if (config.getActionType) {
|
|
||||||
amendedAction = config.getActionType(action);
|
|
||||||
if (typeof amendedAction !== 'object') {
|
|
||||||
amendedAction = {
|
|
||||||
action: { type: amendedAction },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (config.actionSanitizer) {
|
|
||||||
amendedAction = config.actionSanitizer(action);
|
|
||||||
}
|
|
||||||
amendedAction = amendActionType(amendedAction, config, send);
|
|
||||||
if (latency) {
|
|
||||||
delayedActions.push(amendedAction);
|
|
||||||
delayedStates.push(amendedState);
|
|
||||||
sendDelayed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sendMessage(amendedAction, amendedState, config);
|
|
||||||
};
|
|
||||||
|
|
||||||
const init = (state, liftedData) => {
|
|
||||||
const message = {
|
|
||||||
type: 'INIT',
|
|
||||||
payload: stringify(state, config.serialize),
|
|
||||||
instanceId: id,
|
|
||||||
source,
|
|
||||||
};
|
|
||||||
if (liftedData && Array.isArray(liftedData)) {
|
|
||||||
// Legacy
|
|
||||||
message.action = stringify(liftedData);
|
|
||||||
message.name = config.name;
|
|
||||||
} else {
|
|
||||||
if (liftedData) {
|
|
||||||
message.liftedState = liftedData;
|
|
||||||
if (liftedData.isPaused) isPaused = true;
|
|
||||||
}
|
|
||||||
message.libConfig = {
|
|
||||||
actionCreators: JSON.stringify(getActionsArray(actionCreators)),
|
|
||||||
name: config.name || document.title,
|
|
||||||
features: config.features,
|
|
||||||
serialize: !!config.serialize,
|
|
||||||
type: config.type,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
post(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
const error = (payload) => {
|
|
||||||
post({ type: 'ERROR', payload, id, source });
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('message', handleMessages, false);
|
|
||||||
|
|
||||||
post({ type: 'INIT_INSTANCE', instanceId: id, source });
|
|
||||||
|
|
||||||
return {
|
|
||||||
init,
|
|
||||||
subscribe,
|
|
||||||
unsubscribe,
|
|
||||||
send,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateStore(stores) {
|
|
||||||
return function (newStore, instanceId) {
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
console.warn(
|
|
||||||
'`__REDUX_DEVTOOLS_EXTENSION__.updateStore` is deprecated, remove it and just use ' +
|
|
||||||
"`__REDUX_DEVTOOLS_EXTENSION_COMPOSE__` instead of the extension's store enhancer: " +
|
|
||||||
'https://github.com/zalmoxisus/redux-devtools-extension#12-advanced-store-setup'
|
|
||||||
);
|
|
||||||
/* eslint-enable no-console */
|
|
||||||
const store = stores[instanceId || Object.keys(stores)[0]];
|
|
||||||
// Mutate the store in order to keep the reference
|
|
||||||
store.liftedStore = newStore.liftedStore;
|
|
||||||
store.getState = newStore.getState;
|
|
||||||
store.dispatch = newStore.dispatch;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isInIframe() {
|
|
||||||
try {
|
|
||||||
return window.self !== window.top;
|
|
||||||
} catch (e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
736
extension/src/app/api/index.ts
Normal file
736
extension/src/app/api/index.ts
Normal file
|
@ -0,0 +1,736 @@
|
||||||
|
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, PartialLiftedState } from './filters';
|
||||||
|
import importState from './importState';
|
||||||
|
import generateId from './generateInstanceId';
|
||||||
|
import { Config } from '../../browser/extension/inject/pageScript';
|
||||||
|
import { Action } from 'redux';
|
||||||
|
import {
|
||||||
|
EnhancedStore,
|
||||||
|
LiftedState,
|
||||||
|
PerformAction,
|
||||||
|
} from '@redux-devtools/instrument';
|
||||||
|
import { LibConfig } from '@redux-devtools/app/lib/actions';
|
||||||
|
import {
|
||||||
|
ContentScriptToPageScriptMessage,
|
||||||
|
ListenerMessage,
|
||||||
|
} from '../../browser/extension/inject/contentScript';
|
||||||
|
import { Position } from './openWindow';
|
||||||
|
|
||||||
|
const listeners: {
|
||||||
|
[instanceId: string]:
|
||||||
|
| ((message: ContentScriptToPageScriptMessage) => void)
|
||||||
|
| ((message: ContentScriptToPageScriptMessage) => void)[];
|
||||||
|
} = {};
|
||||||
|
export const source = '@devtools-page';
|
||||||
|
|
||||||
|
function windowReplacer(key: string, value: unknown) {
|
||||||
|
if (value && (value as Window).window === value) {
|
||||||
|
return '[WINDOW]';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryCatchStringify(obj: unknown) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(obj);
|
||||||
|
} catch (err) {
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
console.log('Failed to stringify', err);
|
||||||
|
}
|
||||||
|
/* eslint-enable no-console */
|
||||||
|
return jsan.stringify(obj, windowReplacer, undefined, {
|
||||||
|
circular: '[CIRCULAR]',
|
||||||
|
date: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stringifyWarned: boolean;
|
||||||
|
function stringify(obj: unknown, serialize?: Serialize | undefined) {
|
||||||
|
const str =
|
||||||
|
typeof serialize === 'undefined'
|
||||||
|
? tryCatchStringify(obj)
|
||||||
|
: jsan.stringify(obj, serialize.replacer, undefined, serialize.options);
|
||||||
|
|
||||||
|
if (!stringifyWarned && str && str.length > 16 * 1024 * 1024) {
|
||||||
|
// 16 MB
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
console.warn(
|
||||||
|
'Application state or actions payloads are too large making Redux DevTools serialization slow and consuming a lot of memory. See https://git.io/fpcP5 on how to configure it.'
|
||||||
|
);
|
||||||
|
/* eslint-enable no-console */
|
||||||
|
stringifyWarned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Serialize {
|
||||||
|
readonly replacer?: (key: string, value: unknown) => unknown;
|
||||||
|
readonly reviver?: (key: string, value: unknown) => unknown;
|
||||||
|
readonly options?: Options | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSerializeParameter(
|
||||||
|
config: Config,
|
||||||
|
param?: 'serializeState' | 'serializeAction'
|
||||||
|
) {
|
||||||
|
const serialize = config.serialize;
|
||||||
|
if (serialize) {
|
||||||
|
if (serialize === true) return { options: true };
|
||||||
|
if (serialize.immutable) {
|
||||||
|
const immutableSerializer = serializeImmutable(
|
||||||
|
serialize.immutable,
|
||||||
|
serialize.refs,
|
||||||
|
serialize.replacer,
|
||||||
|
serialize.reviver
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
replacer: immutableSerializer.replacer,
|
||||||
|
reviver: immutableSerializer.reviver,
|
||||||
|
options:
|
||||||
|
typeof serialize.options === 'object'
|
||||||
|
? { ...immutableSerializer.options, ...serialize.options }
|
||||||
|
: immutableSerializer.options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!serialize.replacer && !serialize.reviver) {
|
||||||
|
return { options: serialize.options };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
replacer: serialize.replacer,
|
||||||
|
reviver: serialize.reviver,
|
||||||
|
options: serialize.options || true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = config[param!];
|
||||||
|
if (typeof value === 'undefined') return undefined;
|
||||||
|
// 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`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (typeof value === 'boolean') return { options: value };
|
||||||
|
if (typeof value === 'function') return { replacer: value };
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InitInstancePageScriptToContentScriptMessage {
|
||||||
|
readonly type: 'INIT_INSTANCE';
|
||||||
|
readonly instanceId: number;
|
||||||
|
readonly source: typeof source;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DisconnectMessage {
|
||||||
|
readonly type: 'DISCONNECT';
|
||||||
|
readonly source: typeof source;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InitMessage<S, A extends Action<unknown>> {
|
||||||
|
readonly type: 'INIT';
|
||||||
|
readonly payload: string;
|
||||||
|
readonly instanceId: number;
|
||||||
|
readonly source: typeof source;
|
||||||
|
action?: string;
|
||||||
|
name?: string | undefined;
|
||||||
|
liftedState?: LiftedState<S, A, unknown>;
|
||||||
|
libConfig?: LibConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializedPartialLiftedState {
|
||||||
|
readonly stagedActionIds: readonly number[];
|
||||||
|
readonly currentStateIndex: number;
|
||||||
|
readonly nextActionId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializedPartialStateMessage {
|
||||||
|
readonly type: 'PARTIAL_STATE';
|
||||||
|
readonly payload: SerializedPartialLiftedState;
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly instanceId: number;
|
||||||
|
readonly maxAge: number;
|
||||||
|
readonly actionsById: string;
|
||||||
|
readonly computedStates: string;
|
||||||
|
readonly committedState: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializedExportMessage {
|
||||||
|
readonly type: 'EXPORT';
|
||||||
|
readonly payload: string;
|
||||||
|
readonly committedState: string | undefined;
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly instanceId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializedActionMessage {
|
||||||
|
readonly type: 'ACTION';
|
||||||
|
readonly payload: string;
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly instanceId: number;
|
||||||
|
readonly action: string;
|
||||||
|
readonly maxAge: number;
|
||||||
|
readonly nextActionId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializedStateMessage<S, A extends Action<unknown>> {
|
||||||
|
readonly type: 'STATE';
|
||||||
|
readonly payload: Omit<
|
||||||
|
LiftedState<S, A, unknown>,
|
||||||
|
'actionsById' | 'computedStates' | 'committedState'
|
||||||
|
>;
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly instanceId: number;
|
||||||
|
readonly libConfig?: LibConfig;
|
||||||
|
readonly actionsById: string;
|
||||||
|
readonly computedStates: string;
|
||||||
|
readonly committedState: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpenMessage {
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly type: 'OPEN';
|
||||||
|
readonly position: Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PageScriptToContentScriptMessageForwardedToMonitors<
|
||||||
|
S,
|
||||||
|
A extends Action<unknown>
|
||||||
|
> =
|
||||||
|
| InitMessage<S, A>
|
||||||
|
| LiftedMessage
|
||||||
|
| SerializedPartialStateMessage
|
||||||
|
| SerializedExportMessage
|
||||||
|
| SerializedActionMessage
|
||||||
|
| SerializedStateMessage<S, A>;
|
||||||
|
|
||||||
|
export type PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<
|
||||||
|
S,
|
||||||
|
A extends Action<unknown>
|
||||||
|
> =
|
||||||
|
| PageScriptToContentScriptMessageForwardedToMonitors<S, A>
|
||||||
|
| ErrorMessage
|
||||||
|
| GetReportMessage
|
||||||
|
| StopMessage
|
||||||
|
| OpenMessage;
|
||||||
|
|
||||||
|
export type PageScriptToContentScriptMessageWithoutDisconnect<
|
||||||
|
S,
|
||||||
|
A extends Action<unknown>
|
||||||
|
> =
|
||||||
|
| PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<S, A>
|
||||||
|
| InitInstancePageScriptToContentScriptMessage
|
||||||
|
| InitInstanceMessage;
|
||||||
|
|
||||||
|
export type PageScriptToContentScriptMessage<S, A extends Action<unknown>> =
|
||||||
|
| PageScriptToContentScriptMessageWithoutDisconnect<S, A>
|
||||||
|
| DisconnectMessage;
|
||||||
|
|
||||||
|
function post<S, A extends Action<unknown>>(
|
||||||
|
message: PageScriptToContentScriptMessage<S, A>
|
||||||
|
) {
|
||||||
|
window.postMessage(message, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStackTrace(
|
||||||
|
config: Config,
|
||||||
|
toExcludeFromTrace: Function | undefined
|
||||||
|
) {
|
||||||
|
if (!config.trace) return undefined;
|
||||||
|
if (typeof config.trace === 'function') return config.trace();
|
||||||
|
|
||||||
|
let stack;
|
||||||
|
let extraFrames = 0;
|
||||||
|
let prevStackTraceLimit;
|
||||||
|
const traceLimit = config.traceLimit;
|
||||||
|
const error = Error();
|
||||||
|
if (Error.captureStackTrace) {
|
||||||
|
if (Error.stackTraceLimit < traceLimit!) {
|
||||||
|
prevStackTraceLimit = Error.stackTraceLimit;
|
||||||
|
Error.stackTraceLimit = traceLimit!;
|
||||||
|
}
|
||||||
|
Error.captureStackTrace(error, toExcludeFromTrace);
|
||||||
|
} else {
|
||||||
|
extraFrames = 3;
|
||||||
|
}
|
||||||
|
stack = error.stack;
|
||||||
|
if (prevStackTraceLimit) Error.stackTraceLimit = prevStackTraceLimit;
|
||||||
|
if (
|
||||||
|
extraFrames ||
|
||||||
|
typeof Error.stackTraceLimit !== 'number' ||
|
||||||
|
Error.stackTraceLimit > traceLimit!
|
||||||
|
) {
|
||||||
|
const frames = stack!.split('\n');
|
||||||
|
if (frames.length > traceLimit!) {
|
||||||
|
stack = frames
|
||||||
|
.slice(0, traceLimit! + extraFrames + (frames[0] === 'Error' ? 1 : 0))
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
function amendActionType<A extends Action<unknown>>(
|
||||||
|
action:
|
||||||
|
| A
|
||||||
|
| StructuralPerformAction<A>
|
||||||
|
| StructuralPerformAction<A>[]
|
||||||
|
| string,
|
||||||
|
config: Config,
|
||||||
|
toExcludeFromTrace: Function | undefined
|
||||||
|
): StructuralPerformAction<A> {
|
||||||
|
let timestamp = Date.now();
|
||||||
|
let stack = getStackTrace(config, toExcludeFromTrace);
|
||||||
|
if (typeof action === 'string') {
|
||||||
|
return { action: { type: action } as A, timestamp, stack };
|
||||||
|
}
|
||||||
|
if (!(action as A).type)
|
||||||
|
return { action: { type: 'update' } as A, timestamp, stack };
|
||||||
|
if ((action as StructuralPerformAction<A>).action)
|
||||||
|
return (
|
||||||
|
stack ? { stack, ...action } : action
|
||||||
|
) as StructuralPerformAction<A>;
|
||||||
|
return { action, timestamp, stack } as StructuralPerformAction<A>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LiftedMessage {
|
||||||
|
readonly type: 'LIFTED';
|
||||||
|
readonly liftedState: { readonly isPaused: boolean | undefined };
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StructuralPerformAction<A extends Action<unknown>> {
|
||||||
|
readonly action: A;
|
||||||
|
readonly timestamp?: number;
|
||||||
|
readonly stack?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SingleUserAction<A extends Action<unknown>> =
|
||||||
|
| PerformAction<A>
|
||||||
|
| StructuralPerformAction<A>
|
||||||
|
| A;
|
||||||
|
type UserAction<A extends Action<unknown>> =
|
||||||
|
| SingleUserAction<A>
|
||||||
|
| readonly SingleUserAction<A>[];
|
||||||
|
|
||||||
|
interface ActionMessage<S, A extends Action<unknown>> {
|
||||||
|
readonly type: 'ACTION';
|
||||||
|
readonly payload: S;
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly instanceId: number;
|
||||||
|
readonly action: UserAction<A>;
|
||||||
|
readonly maxAge: number;
|
||||||
|
readonly nextActionId?: number;
|
||||||
|
readonly name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateMessage<S, A extends Action<unknown>> {
|
||||||
|
readonly type: 'STATE';
|
||||||
|
readonly payload: LiftedState<S, A, unknown>;
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly instanceId: number;
|
||||||
|
readonly libConfig?: LibConfig;
|
||||||
|
readonly action?: UserAction<A>;
|
||||||
|
readonly maxAge?: number;
|
||||||
|
readonly name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorMessage {
|
||||||
|
readonly type: 'ERROR';
|
||||||
|
readonly payload: string;
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly instanceId: number;
|
||||||
|
readonly message?: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InitInstanceMessage {
|
||||||
|
readonly type: 'INIT_INSTANCE';
|
||||||
|
readonly payload: undefined;
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly instanceId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetReportMessage {
|
||||||
|
readonly type: 'GET_REPORT';
|
||||||
|
readonly payload: string;
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly instanceId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StopMessage {
|
||||||
|
readonly type: 'STOP';
|
||||||
|
readonly payload: undefined;
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly instanceId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToContentScriptMessage<S, A extends Action<unknown>> =
|
||||||
|
| LiftedMessage
|
||||||
|
| PartialStateMessage<S, A>
|
||||||
|
| ExportMessage<S, A>
|
||||||
|
| ActionMessage<S, A>
|
||||||
|
| StateMessage<S, A>
|
||||||
|
| ErrorMessage
|
||||||
|
| InitInstanceMessage
|
||||||
|
| GetReportMessage
|
||||||
|
| StopMessage;
|
||||||
|
|
||||||
|
export function toContentScript<S, A extends Action<unknown>>(
|
||||||
|
message: ToContentScriptMessage<S, A>,
|
||||||
|
serializeState?: Serialize | undefined,
|
||||||
|
serializeAction?: Serialize | undefined
|
||||||
|
) {
|
||||||
|
if (message.type === 'ACTION') {
|
||||||
|
post({
|
||||||
|
...message,
|
||||||
|
action: stringify(message.action, serializeAction),
|
||||||
|
payload: stringify(message.payload, serializeState),
|
||||||
|
});
|
||||||
|
} else if (message.type === 'STATE') {
|
||||||
|
const { actionsById, computedStates, committedState, ...rest } =
|
||||||
|
message.payload;
|
||||||
|
post({
|
||||||
|
...message,
|
||||||
|
payload: rest,
|
||||||
|
actionsById: stringify(actionsById, serializeAction),
|
||||||
|
computedStates: stringify(computedStates, serializeState),
|
||||||
|
committedState: typeof committedState !== 'undefined',
|
||||||
|
});
|
||||||
|
} else if (message.type === 'PARTIAL_STATE') {
|
||||||
|
const { actionsById, computedStates, committedState, ...rest } =
|
||||||
|
message.payload;
|
||||||
|
post({
|
||||||
|
...message,
|
||||||
|
payload: rest,
|
||||||
|
actionsById: stringify(actionsById, serializeAction),
|
||||||
|
computedStates: stringify(computedStates, serializeState),
|
||||||
|
committedState: typeof committedState !== 'undefined',
|
||||||
|
});
|
||||||
|
} else if (message.type === 'EXPORT') {
|
||||||
|
post({
|
||||||
|
...message,
|
||||||
|
payload: stringify(message.payload, serializeAction),
|
||||||
|
committedState:
|
||||||
|
typeof message.committedState !== 'undefined'
|
||||||
|
? stringify(message.committedState, serializeState)
|
||||||
|
: (message.committedState as undefined),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
post(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendMessage<S, A extends Action<unknown>>(
|
||||||
|
action: StructuralPerformAction<A> | StructuralPerformAction<A>[],
|
||||||
|
state: LiftedState<S, A, unknown>,
|
||||||
|
config: Config,
|
||||||
|
instanceId?: number,
|
||||||
|
name?: string
|
||||||
|
) {
|
||||||
|
let amendedAction = action;
|
||||||
|
if (typeof config !== 'object') {
|
||||||
|
// Legacy: sending actions not from connected part
|
||||||
|
config = {}; // eslint-disable-line no-param-reassign
|
||||||
|
if (action) amendedAction = amendActionType(action, config, sendMessage);
|
||||||
|
}
|
||||||
|
if (action) {
|
||||||
|
toContentScript(
|
||||||
|
{
|
||||||
|
type: 'ACTION',
|
||||||
|
action: amendedAction,
|
||||||
|
payload: state,
|
||||||
|
maxAge: config.maxAge!,
|
||||||
|
source,
|
||||||
|
name: config.name || name,
|
||||||
|
instanceId: config.instanceId || instanceId || 1,
|
||||||
|
},
|
||||||
|
config.serialize as Serialize | undefined,
|
||||||
|
config.serialize as Serialize | undefined
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toContentScript<S, A>(
|
||||||
|
{
|
||||||
|
type: 'STATE',
|
||||||
|
action: amendedAction,
|
||||||
|
payload: state,
|
||||||
|
maxAge: config.maxAge,
|
||||||
|
source,
|
||||||
|
name: config.name || name,
|
||||||
|
instanceId: config.instanceId || instanceId || 1,
|
||||||
|
},
|
||||||
|
config.serialize as Serialize | undefined,
|
||||||
|
config.serialize as Serialize | undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMessages(event: MessageEvent<ContentScriptToPageScriptMessage>) {
|
||||||
|
if (process.env.BABEL_ENV !== 'test' && (!event || event.source !== window)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = event.data;
|
||||||
|
if (!message || message.source !== '@devtools-extension') return;
|
||||||
|
Object.keys(listeners).forEach((id) => {
|
||||||
|
if (message.id && id !== message.id) return;
|
||||||
|
const listenersForId = listeners[id];
|
||||||
|
if (typeof listenersForId === 'function') listenersForId(message);
|
||||||
|
else {
|
||||||
|
listenersForId.forEach((fn) => {
|
||||||
|
fn(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setListener(
|
||||||
|
onMessage: (message: ContentScriptToPageScriptMessage) => void,
|
||||||
|
instanceId: number
|
||||||
|
) {
|
||||||
|
listeners[instanceId] = onMessage;
|
||||||
|
window.addEventListener('message', handleMessages, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const liftListener =
|
||||||
|
<S, A extends Action<unknown>>(
|
||||||
|
listener: (message: ListenerMessage<S, A>) => void,
|
||||||
|
config: Config
|
||||||
|
) =>
|
||||||
|
(message: ContentScriptToPageScriptMessage) => {
|
||||||
|
if (message.type === 'IMPORT') {
|
||||||
|
listener({
|
||||||
|
type: 'DISPATCH',
|
||||||
|
payload: {
|
||||||
|
type: 'IMPORT_STATE',
|
||||||
|
...importState<S, A>(message.state, config)!,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
listener(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function disconnect() {
|
||||||
|
window.removeEventListener('message', handleMessages);
|
||||||
|
post({ type: 'DISCONNECT', source });
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConnectResponse {
|
||||||
|
init: <S, A extends Action<unknown>>(
|
||||||
|
state: S,
|
||||||
|
liftedData: LiftedState<S, A, unknown>
|
||||||
|
) => void;
|
||||||
|
subscribe: <S, A extends Action<unknown>>(
|
||||||
|
listener: (message: ListenerMessage<S, A>) => void
|
||||||
|
) => (() => void) | undefined;
|
||||||
|
unsubscribe: () => void;
|
||||||
|
send: <S, A extends Action<unknown>>(
|
||||||
|
action: A,
|
||||||
|
state: LiftedState<S, A, unknown>
|
||||||
|
) => void;
|
||||||
|
error: (payload: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function connect(preConfig: Config): ConnectResponse {
|
||||||
|
const config = preConfig || {};
|
||||||
|
const id = generateId(config.instanceId);
|
||||||
|
if (!config.instanceId) config.instanceId = id;
|
||||||
|
if (!config.name) {
|
||||||
|
config.name =
|
||||||
|
document.title && id === 1 ? document.title : `Instance ${id}`;
|
||||||
|
}
|
||||||
|
if (config.serialize) config.serialize = getSerializeParameter(config);
|
||||||
|
const actionCreators = config.actionCreators || {};
|
||||||
|
const latency = config.latency;
|
||||||
|
const predicate = config.predicate;
|
||||||
|
const localFilter = getLocalFilter(config);
|
||||||
|
const autoPause = config.autoPause;
|
||||||
|
let isPaused = autoPause;
|
||||||
|
let delayedActions: StructuralPerformAction<Action<unknown>>[] = [];
|
||||||
|
let delayedStates: LiftedState<unknown, Action<unknown>, unknown>[] = [];
|
||||||
|
|
||||||
|
const rootListener = (action: ContentScriptToPageScriptMessage) => {
|
||||||
|
if (autoPause) {
|
||||||
|
if (action.type === 'START') isPaused = false;
|
||||||
|
else if (action.type === 'STOP') isPaused = true;
|
||||||
|
}
|
||||||
|
if (action.type === 'DISPATCH') {
|
||||||
|
const payload = action.payload;
|
||||||
|
if (payload.type === 'PAUSE_RECORDING') {
|
||||||
|
isPaused = payload.status;
|
||||||
|
toContentScript({
|
||||||
|
type: 'LIFTED',
|
||||||
|
liftedState: { isPaused },
|
||||||
|
instanceId: id,
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
listeners[id] = [rootListener];
|
||||||
|
|
||||||
|
const subscribe = <S, A extends Action<unknown>>(
|
||||||
|
listener: (message: ListenerMessage<S, A>) => void
|
||||||
|
) => {
|
||||||
|
if (!listener) return undefined;
|
||||||
|
const liftedListener = liftListener(listener, config);
|
||||||
|
const listenersForId = listeners[id] as ((
|
||||||
|
message: ContentScriptToPageScriptMessage
|
||||||
|
) => void)[];
|
||||||
|
listenersForId.push(liftedListener);
|
||||||
|
|
||||||
|
return function unsubscribe() {
|
||||||
|
const index = listenersForId.indexOf(liftedListener);
|
||||||
|
listenersForId.splice(index, 1);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsubscribe = () => {
|
||||||
|
delete listeners[id];
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendDelayed = throttle(() => {
|
||||||
|
sendMessage(delayedActions, delayedStates as any, config);
|
||||||
|
delayedActions = [];
|
||||||
|
delayedStates = [];
|
||||||
|
}, latency);
|
||||||
|
|
||||||
|
const send = <S, A extends Action<unknown>>(
|
||||||
|
action: A,
|
||||||
|
state: LiftedState<S, A, unknown>
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
isPaused ||
|
||||||
|
isFiltered(action, localFilter) ||
|
||||||
|
(predicate && !predicate(state, action))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let amendedAction: A | StructuralPerformAction<A> = action;
|
||||||
|
const amendedState = config.stateSanitizer
|
||||||
|
? config.stateSanitizer(state)
|
||||||
|
: state;
|
||||||
|
if (action) {
|
||||||
|
if (config.getActionType) {
|
||||||
|
amendedAction = config.getActionType(action);
|
||||||
|
if (typeof amendedAction !== 'object') {
|
||||||
|
amendedAction = {
|
||||||
|
action: { type: amendedAction },
|
||||||
|
timestamp: Date.now(),
|
||||||
|
} as unknown as A;
|
||||||
|
}
|
||||||
|
} else if (config.actionSanitizer) {
|
||||||
|
amendedAction = config.actionSanitizer(action);
|
||||||
|
}
|
||||||
|
amendedAction = amendActionType(amendedAction, config, send);
|
||||||
|
if (latency) {
|
||||||
|
delayedActions.push(amendedAction);
|
||||||
|
delayedStates.push(amendedState);
|
||||||
|
sendDelayed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendMessage(
|
||||||
|
amendedAction as StructuralPerformAction<A>,
|
||||||
|
amendedState,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = <S, A extends Action<unknown>>(
|
||||||
|
state: S,
|
||||||
|
liftedData: LiftedState<S, A, unknown>
|
||||||
|
) => {
|
||||||
|
const message: InitMessage<S, A> = {
|
||||||
|
type: 'INIT',
|
||||||
|
payload: stringify(state, config.serialize as Serialize | undefined),
|
||||||
|
instanceId: id,
|
||||||
|
source,
|
||||||
|
};
|
||||||
|
if (liftedData && Array.isArray(liftedData)) {
|
||||||
|
// Legacy
|
||||||
|
message.action = stringify(liftedData);
|
||||||
|
message.name = config.name;
|
||||||
|
} else {
|
||||||
|
if (liftedData) {
|
||||||
|
message.liftedState = liftedData;
|
||||||
|
if (liftedData.isPaused) isPaused = true;
|
||||||
|
}
|
||||||
|
message.libConfig = {
|
||||||
|
actionCreators: JSON.stringify(getActionsArray(actionCreators)),
|
||||||
|
name: config.name || document.title,
|
||||||
|
features: config.features,
|
||||||
|
serialize: !!config.serialize,
|
||||||
|
type: config.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
post(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const error = (payload: string) => {
|
||||||
|
post({ type: 'ERROR', payload, instanceId: id, source });
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('message', handleMessages, false);
|
||||||
|
|
||||||
|
post({ type: 'INIT_INSTANCE', instanceId: id, source });
|
||||||
|
|
||||||
|
return {
|
||||||
|
init,
|
||||||
|
subscribe,
|
||||||
|
unsubscribe,
|
||||||
|
send,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateStore<S, A extends Action<unknown>>(
|
||||||
|
stores: {
|
||||||
|
[K in string | number]: EnhancedStore<S, A, unknown>;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return function (newStore: EnhancedStore<S, A, unknown>, instanceId: number) {
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
console.warn(
|
||||||
|
'`__REDUX_DEVTOOLS_EXTENSION__.updateStore` is deprecated, remove it and just use ' +
|
||||||
|
"`__REDUX_DEVTOOLS_EXTENSION_COMPOSE__` instead of the extension's store enhancer: " +
|
||||||
|
'https://github.com/zalmoxisus/redux-devtools-extension#12-advanced-store-setup'
|
||||||
|
);
|
||||||
|
/* eslint-enable no-console */
|
||||||
|
const store = stores[instanceId || Object.keys(stores)[0]];
|
||||||
|
// Mutate the store in order to keep the reference
|
||||||
|
store.liftedStore = newStore.liftedStore;
|
||||||
|
store.getState = newStore.getState;
|
||||||
|
store.dispatch = newStore.dispatch;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isInIframe() {
|
||||||
|
try {
|
||||||
|
return window.self !== window.top;
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
let handleError;
|
let handleError: (() => boolean) | undefined;
|
||||||
let lastTime = 0;
|
let lastTime = 0;
|
||||||
|
|
||||||
function createExpBackoffTimer(step) {
|
function createExpBackoffTimer(step: number) {
|
||||||
let count = 1;
|
let count = 1;
|
||||||
return function (reset) {
|
return function (reset?: boolean) {
|
||||||
// Reset call
|
// Reset call
|
||||||
if (reset) {
|
if (reset) {
|
||||||
count = 1;
|
count = 1;
|
||||||
|
@ -18,7 +18,7 @@ function createExpBackoffTimer(step) {
|
||||||
|
|
||||||
const nextErrorTimeout = createExpBackoffTimer(5000);
|
const nextErrorTimeout = createExpBackoffTimer(5000);
|
||||||
|
|
||||||
function postError(message) {
|
function postError(message: string) {
|
||||||
if (handleError && !handleError()) return;
|
if (handleError && !handleError()) return;
|
||||||
window.postMessage(
|
window.postMessage(
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ function postError(message) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function catchErrors(e) {
|
function catchErrors(e: ErrorEvent) {
|
||||||
if (
|
if (
|
||||||
(window.devToolsOptions && !window.devToolsOptions.shouldCatchErrors) ||
|
(window.devToolsOptions && !window.devToolsOptions.shouldCatchErrors) ||
|
||||||
e.timeStamp - lastTime < nextErrorTimeout()
|
e.timeStamp - lastTime < nextErrorTimeout()
|
||||||
|
@ -42,7 +42,7 @@ function catchErrors(e) {
|
||||||
postError(e.message);
|
postError(e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function notifyErrors(onError) {
|
export default function notifyErrors(onError?: () => boolean) {
|
||||||
handleError = onError;
|
handleError = onError;
|
||||||
window.addEventListener('error', catchErrors, false);
|
window.addEventListener('error', catchErrors, false);
|
||||||
}
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
export default function openWindow(position) {
|
|
||||||
window.postMessage(
|
|
||||||
{
|
|
||||||
source: '@devtools-page',
|
|
||||||
type: 'OPEN',
|
|
||||||
position: position || 'right',
|
|
||||||
},
|
|
||||||
'*'
|
|
||||||
);
|
|
||||||
}
|
|
18
extension/src/app/api/openWindow.ts
Normal file
18
extension/src/app/api/openWindow.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { Action } from 'redux';
|
||||||
|
import { PageScriptToContentScriptMessage } from './index';
|
||||||
|
|
||||||
|
export type Position = 'left' | 'right' | 'bottom' | 'panel' | 'remote';
|
||||||
|
|
||||||
|
function post<S, A extends Action<unknown>>(
|
||||||
|
message: PageScriptToContentScriptMessage<S, A>
|
||||||
|
) {
|
||||||
|
window.postMessage(message, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function openWindow(position?: Position) {
|
||||||
|
post({
|
||||||
|
source: '@devtools-page',
|
||||||
|
type: 'OPEN',
|
||||||
|
position: position || 'right',
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,19 +1,33 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect, ResolveThunks } from 'react-redux';
|
||||||
import { Container, Notification } from 'devui';
|
import { Container, Notification } from 'devui';
|
||||||
import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances';
|
import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances';
|
||||||
import Settings from '@redux-devtools/app/lib/components/Settings';
|
import Settings from '@redux-devtools/app/lib/components/Settings';
|
||||||
import Actions from '@redux-devtools/app/lib/containers/Actions';
|
import Actions from '@redux-devtools/app/lib/containers/Actions';
|
||||||
import Header from '@redux-devtools/app/lib/components/Header';
|
import Header from '@redux-devtools/app/lib/components/Header';
|
||||||
import { clearNotification } from '@redux-devtools/app/lib/actions';
|
import { clearNotification } from '@redux-devtools/app/lib/actions';
|
||||||
|
import { StoreState } from '@redux-devtools/app/lib/reducers';
|
||||||
|
import { SingleMessage } from '../middlewares/api';
|
||||||
|
import { Position } from '../api/openWindow';
|
||||||
|
|
||||||
class App extends Component {
|
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||||
openWindow = (position) => {
|
type DispatchProps = ResolveThunks<typeof actionCreators>;
|
||||||
chrome.runtime.sendMessage({ type: 'OPEN', position });
|
interface OwnProps {
|
||||||
|
readonly position: string;
|
||||||
|
}
|
||||||
|
type Props = StateProps & DispatchProps & OwnProps;
|
||||||
|
|
||||||
|
function sendMessage(message: SingleMessage) {
|
||||||
|
chrome.runtime.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class App extends Component<Props> {
|
||||||
|
openWindow = (position: Position) => {
|
||||||
|
sendMessage({ type: 'OPEN', position });
|
||||||
};
|
};
|
||||||
openOptionsPage = () => {
|
openOptionsPage = () => {
|
||||||
if (navigator.userAgent.indexOf('Firefox') !== -1) {
|
if (navigator.userAgent.indexOf('Firefox') !== -1) {
|
||||||
chrome.runtime.sendMessage({ type: 'OPEN_OPTIONS' });
|
sendMessage({ type: 'OPEN_OPTIONS' });
|
||||||
} else {
|
} else {
|
||||||
chrome.runtime.openOptionsPage();
|
chrome.runtime.openOptionsPage();
|
||||||
}
|
}
|
||||||
|
@ -62,7 +76,7 @@ class App extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state: StoreState) {
|
||||||
const instances = state.instances;
|
const instances = state.instances;
|
||||||
const id = getActiveInstance(instances);
|
const id = getActiveInstance(instances);
|
||||||
return {
|
return {
|
|
@ -1,264 +0,0 @@
|
||||||
import stringifyJSON from '@redux-devtools/app/lib/utils/stringifyJSON';
|
|
||||||
import {
|
|
||||||
UPDATE_STATE,
|
|
||||||
REMOVE_INSTANCE,
|
|
||||||
LIFTED_ACTION,
|
|
||||||
} from '@redux-devtools/app/lib/constants/actionTypes';
|
|
||||||
import { nonReduxDispatch } from '@redux-devtools/app/lib/utils/monitorActions';
|
|
||||||
import syncOptions from '../../browser/extension/options/syncOptions';
|
|
||||||
import openDevToolsWindow from '../../browser/extension/background/openWindow';
|
|
||||||
import { getReport } from '../../browser/extension/background/logging';
|
|
||||||
|
|
||||||
const CONNECTED = 'socket/CONNECTED';
|
|
||||||
const DISCONNECTED = 'socket/DISCONNECTED';
|
|
||||||
const connections = {
|
|
||||||
tab: {},
|
|
||||||
panel: {},
|
|
||||||
monitor: {},
|
|
||||||
};
|
|
||||||
const chunks = {};
|
|
||||||
let monitors = 0;
|
|
||||||
let isMonitored = false;
|
|
||||||
|
|
||||||
const getId = (sender, name) =>
|
|
||||||
sender.tab ? sender.tab.id : name || sender.id;
|
|
||||||
|
|
||||||
function toMonitors(action, tabId, verbose) {
|
|
||||||
Object.keys(connections.monitor).forEach((id) => {
|
|
||||||
connections.monitor[id].postMessage(
|
|
||||||
verbose || action.type === 'ERROR' ? action : { type: UPDATE_STATE }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
Object.keys(connections.panel).forEach((id) => {
|
|
||||||
connections.panel[id].postMessage(action);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toContentScript({ message, action, id, instanceId, state }) {
|
|
||||||
connections.tab[id].postMessage({
|
|
||||||
type: message,
|
|
||||||
action,
|
|
||||||
state: nonReduxDispatch(window.store, message, instanceId, action, state),
|
|
||||||
id: instanceId.toString().replace(/^[^\/]+\//, ''),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toAllTabs(msg) {
|
|
||||||
const tabs = connections.tab;
|
|
||||||
Object.keys(tabs).forEach((id) => {
|
|
||||||
tabs[id].postMessage(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function monitorInstances(shouldMonitor, id) {
|
|
||||||
if (!id && isMonitored === shouldMonitor) return;
|
|
||||||
const action = { type: shouldMonitor ? 'START' : 'STOP' };
|
|
||||||
if (id) {
|
|
||||||
if (connections.tab[id]) connections.tab[id].postMessage(action);
|
|
||||||
} else {
|
|
||||||
toAllTabs(action);
|
|
||||||
}
|
|
||||||
isMonitored = shouldMonitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getReducerError() {
|
|
||||||
const instancesState = window.store.getState().instances;
|
|
||||||
const payload = instancesState.states[instancesState.current];
|
|
||||||
const computedState = payload.computedStates[payload.currentStateIndex];
|
|
||||||
if (!computedState) return false;
|
|
||||||
return computedState.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
function togglePersist() {
|
|
||||||
const state = window.store.getState();
|
|
||||||
if (state.persistStates) {
|
|
||||||
Object.keys(state.instances.connections).forEach((id) => {
|
|
||||||
if (connections.tab[id]) return;
|
|
||||||
window.store.dispatch({ type: REMOVE_INSTANCE, id });
|
|
||||||
toMonitors({ type: 'NA', id });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive messages from content scripts
|
|
||||||
function messaging(request, sender, sendResponse) {
|
|
||||||
let tabId = getId(sender);
|
|
||||||
if (!tabId) return;
|
|
||||||
if (sender.frameId) tabId = `${tabId}-${sender.frameId}`;
|
|
||||||
|
|
||||||
if (request.type === 'STOP') {
|
|
||||||
if (!Object.keys(window.store.getState().instances.connections).length) {
|
|
||||||
window.store.dispatch({ type: DISCONNECTED });
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (request.type === 'OPEN_OPTIONS') {
|
|
||||||
chrome.runtime.openOptionsPage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (request.type === 'GET_OPTIONS') {
|
|
||||||
window.syncOptions.get((options) => {
|
|
||||||
sendResponse({ options });
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (request.type === 'GET_REPORT') {
|
|
||||||
getReport(request.payload, tabId, request.instanceId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (request.type === 'OPEN') {
|
|
||||||
let position = 'devtools-left';
|
|
||||||
if (
|
|
||||||
['remote', 'panel', 'left', 'right', 'bottom'].indexOf(
|
|
||||||
request.position
|
|
||||||
) !== -1
|
|
||||||
) {
|
|
||||||
position = 'devtools-' + request.position;
|
|
||||||
}
|
|
||||||
openDevToolsWindow(position);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (request.type === 'ERROR') {
|
|
||||||
if (request.payload) {
|
|
||||||
toMonitors(request, tabId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!request.message) return;
|
|
||||||
const reducerError = getReducerError();
|
|
||||||
chrome.notifications.create('app-error', {
|
|
||||||
type: 'basic',
|
|
||||||
title: reducerError
|
|
||||||
? 'An error occurred in the reducer'
|
|
||||||
: 'An error occurred in the app',
|
|
||||||
message: reducerError || request.message,
|
|
||||||
iconUrl: 'img/logo/48x48.png',
|
|
||||||
isClickable: !!reducerError,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const action = { type: UPDATE_STATE, request, id: tabId };
|
|
||||||
const instanceId = `${tabId}/${request.instanceId}`;
|
|
||||||
if (request.split) {
|
|
||||||
if (request.split === 'start') {
|
|
||||||
chunks[instanceId] = request;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (request.split === 'chunk') {
|
|
||||||
chunks[instanceId][request.chunk[0]] =
|
|
||||||
(chunks[instanceId][request.chunk[0]] || '') + request.chunk[1];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
action.request = chunks[instanceId];
|
|
||||||
delete chunks[instanceId];
|
|
||||||
}
|
|
||||||
if (request.instanceId) {
|
|
||||||
action.request.instanceId = instanceId;
|
|
||||||
}
|
|
||||||
window.store.dispatch(action);
|
|
||||||
|
|
||||||
if (request.type === 'EXPORT') {
|
|
||||||
toMonitors(action, tabId, true);
|
|
||||||
} else {
|
|
||||||
toMonitors(action, tabId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function disconnect(type, id, listener) {
|
|
||||||
return function disconnectListener() {
|
|
||||||
const p = connections[type][id];
|
|
||||||
if (listener && p) p.onMessage.removeListener(listener);
|
|
||||||
if (p) p.onDisconnect.removeListener(disconnectListener);
|
|
||||||
delete connections[type][id];
|
|
||||||
if (type === 'tab') {
|
|
||||||
if (!window.store.getState().persistStates) {
|
|
||||||
window.store.dispatch({ type: REMOVE_INSTANCE, id });
|
|
||||||
toMonitors({ type: 'NA', id });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
monitors--;
|
|
||||||
if (!monitors) monitorInstances(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function onConnect(port) {
|
|
||||||
let id;
|
|
||||||
let listener;
|
|
||||||
|
|
||||||
window.store.dispatch({ type: CONNECTED, port });
|
|
||||||
|
|
||||||
if (port.name === 'tab') {
|
|
||||||
id = getId(port.sender);
|
|
||||||
if (port.sender.frameId) id = `${id}-${port.sender.frameId}`;
|
|
||||||
connections.tab[id] = port;
|
|
||||||
listener = (msg) => {
|
|
||||||
if (msg.name === 'INIT_INSTANCE') {
|
|
||||||
if (typeof id === 'number') {
|
|
||||||
chrome.pageAction.show(id);
|
|
||||||
chrome.pageAction.setIcon({ tabId: id, path: 'img/logo/38x38.png' });
|
|
||||||
}
|
|
||||||
if (isMonitored) port.postMessage({ type: 'START' });
|
|
||||||
|
|
||||||
const state = window.store.getState();
|
|
||||||
if (state.persistStates) {
|
|
||||||
const instanceId = `${id}/${msg.instanceId}`;
|
|
||||||
const persistedState = state.instances.states[instanceId];
|
|
||||||
if (!persistedState) return;
|
|
||||||
toContentScript({
|
|
||||||
message: 'IMPORT',
|
|
||||||
id,
|
|
||||||
instanceId,
|
|
||||||
state: stringifyJSON(
|
|
||||||
persistedState,
|
|
||||||
state.instances.options[instanceId].serialize
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (msg.name === 'RELAY') {
|
|
||||||
messaging(msg.message, port.sender, id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
port.onMessage.addListener(listener);
|
|
||||||
port.onDisconnect.addListener(disconnect('tab', id, listener));
|
|
||||||
} else if (port.name && port.name.indexOf('monitor') === 0) {
|
|
||||||
id = getId(port.sender, port.name);
|
|
||||||
connections.monitor[id] = port;
|
|
||||||
monitorInstances(true);
|
|
||||||
monitors++;
|
|
||||||
port.onDisconnect.addListener(disconnect('monitor', id));
|
|
||||||
} else {
|
|
||||||
// devpanel
|
|
||||||
id = port.name || port.sender.frameId;
|
|
||||||
connections.panel[id] = port;
|
|
||||||
monitorInstances(true, port.name);
|
|
||||||
monitors++;
|
|
||||||
listener = (msg) => {
|
|
||||||
window.store.dispatch(msg);
|
|
||||||
};
|
|
||||||
port.onMessage.addListener(listener);
|
|
||||||
port.onDisconnect.addListener(disconnect('panel', id, listener));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chrome.runtime.onConnect.addListener(onConnect);
|
|
||||||
chrome.runtime.onConnectExternal.addListener(onConnect);
|
|
||||||
chrome.runtime.onMessage.addListener(messaging);
|
|
||||||
chrome.runtime.onMessageExternal.addListener(messaging);
|
|
||||||
|
|
||||||
chrome.notifications.onClicked.addListener((id) => {
|
|
||||||
chrome.notifications.clear(id);
|
|
||||||
openDevToolsWindow('devtools-right');
|
|
||||||
});
|
|
||||||
|
|
||||||
window.syncOptions = syncOptions(toAllTabs); // Expose to the options page
|
|
||||||
|
|
||||||
export default function api() {
|
|
||||||
return (next) => (action) => {
|
|
||||||
if (action.type === LIFTED_ACTION) toContentScript(action);
|
|
||||||
else if (action.type === 'TOGGLE_PERSIST') togglePersist();
|
|
||||||
return next(action);
|
|
||||||
};
|
|
||||||
}
|
|
591
extension/src/app/middlewares/api.ts
Normal file
591
extension/src/app/middlewares/api.ts
Normal file
|
@ -0,0 +1,591 @@
|
||||||
|
import stringifyJSON from '@redux-devtools/app/lib/utils/stringifyJSON';
|
||||||
|
import {
|
||||||
|
UPDATE_STATE,
|
||||||
|
REMOVE_INSTANCE,
|
||||||
|
LIFTED_ACTION,
|
||||||
|
} from '@redux-devtools/app/lib/constants/actionTypes';
|
||||||
|
import { nonReduxDispatch } from '@redux-devtools/app/lib/utils/monitorActions';
|
||||||
|
import syncOptions, {
|
||||||
|
Options,
|
||||||
|
OptionsMessage,
|
||||||
|
SyncOptions,
|
||||||
|
} from '../../browser/extension/options/syncOptions';
|
||||||
|
import openDevToolsWindow, {
|
||||||
|
DevToolsPosition,
|
||||||
|
} from '../../browser/extension/background/openWindow';
|
||||||
|
import { getReport } from '../../browser/extension/background/logging';
|
||||||
|
import {
|
||||||
|
CustomAction,
|
||||||
|
DispatchAction as AppDispatchAction,
|
||||||
|
LibConfig,
|
||||||
|
} from '@redux-devtools/app/lib/actions';
|
||||||
|
import { Action, Dispatch } from 'redux';
|
||||||
|
import {
|
||||||
|
ContentScriptToBackgroundMessage,
|
||||||
|
SplitMessage,
|
||||||
|
} from '../../browser/extension/inject/contentScript';
|
||||||
|
import {
|
||||||
|
ErrorMessage,
|
||||||
|
PageScriptToContentScriptMessageForwardedToMonitors,
|
||||||
|
PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance,
|
||||||
|
} from '../api';
|
||||||
|
import { LiftedState } from '@redux-devtools/instrument';
|
||||||
|
import {
|
||||||
|
BackgroundAction,
|
||||||
|
LiftedActionAction,
|
||||||
|
} from '../stores/backgroundStore';
|
||||||
|
import { Position } from '../api/openWindow';
|
||||||
|
|
||||||
|
interface TabMessageBase {
|
||||||
|
readonly type: string;
|
||||||
|
readonly state?: string | undefined;
|
||||||
|
readonly id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StartAction extends TabMessageBase {
|
||||||
|
readonly type: 'START';
|
||||||
|
readonly state?: never;
|
||||||
|
readonly id?: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StopAction extends TabMessageBase {
|
||||||
|
readonly type: 'STOP';
|
||||||
|
readonly state?: never;
|
||||||
|
readonly id?: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchAction extends TabMessageBase {
|
||||||
|
readonly type: 'DISPATCH';
|
||||||
|
readonly action: AppDispatchAction;
|
||||||
|
readonly state: string | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportAction extends TabMessageBase {
|
||||||
|
readonly type: 'IMPORT';
|
||||||
|
readonly action: undefined;
|
||||||
|
readonly state: string | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionAction extends TabMessageBase {
|
||||||
|
readonly type: 'ACTION';
|
||||||
|
readonly action: string | CustomAction;
|
||||||
|
readonly state: string | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportAction extends TabMessageBase {
|
||||||
|
readonly type: 'EXPORT';
|
||||||
|
readonly action: undefined;
|
||||||
|
readonly state: string | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NAAction {
|
||||||
|
readonly type: 'NA';
|
||||||
|
readonly id: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InitMessage<S, A extends Action<unknown>> {
|
||||||
|
readonly type: 'INIT';
|
||||||
|
readonly payload: string;
|
||||||
|
instanceId: string;
|
||||||
|
readonly source: '@devtools-page';
|
||||||
|
action?: string;
|
||||||
|
name?: string | undefined;
|
||||||
|
liftedState?: LiftedState<S, A, unknown>;
|
||||||
|
libConfig?: LibConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LiftedMessage {
|
||||||
|
type: 'LIFTED';
|
||||||
|
liftedState: { isPaused: boolean | undefined };
|
||||||
|
instanceId: number;
|
||||||
|
source: '@devtools-page';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializedPartialLiftedState {
|
||||||
|
readonly stagedActionIds: readonly number[];
|
||||||
|
readonly currentStateIndex: number;
|
||||||
|
readonly nextActionId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializedPartialStateMessage {
|
||||||
|
readonly type: 'PARTIAL_STATE';
|
||||||
|
readonly payload: SerializedPartialLiftedState;
|
||||||
|
readonly source: '@devtools-page';
|
||||||
|
instanceId: number;
|
||||||
|
readonly maxAge: number;
|
||||||
|
readonly actionsById: string;
|
||||||
|
readonly computedStates: string;
|
||||||
|
readonly committedState: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializedExportMessage {
|
||||||
|
readonly type: 'EXPORT';
|
||||||
|
readonly payload: string;
|
||||||
|
readonly committedState: string | undefined;
|
||||||
|
readonly source: '@devtools-page';
|
||||||
|
instanceId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializedActionMessage {
|
||||||
|
readonly type: 'ACTION';
|
||||||
|
readonly payload: string;
|
||||||
|
readonly source: '@devtools-page';
|
||||||
|
instanceId: number;
|
||||||
|
readonly action: string;
|
||||||
|
readonly maxAge: number;
|
||||||
|
readonly nextActionId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SerializedStateMessage<S, A extends Action<unknown>> {
|
||||||
|
readonly type: 'STATE';
|
||||||
|
readonly payload: Omit<
|
||||||
|
LiftedState<S, A, unknown>,
|
||||||
|
'actionsById' | 'computedStates' | 'committedState'
|
||||||
|
>;
|
||||||
|
readonly source: '@devtools-page';
|
||||||
|
instanceId: string;
|
||||||
|
readonly libConfig?: LibConfig;
|
||||||
|
readonly actionsById: string;
|
||||||
|
readonly computedStates: string;
|
||||||
|
readonly committedState: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateStateRequest<S, A extends Action<unknown>> =
|
||||||
|
| InitMessage<S, A>
|
||||||
|
| LiftedMessage
|
||||||
|
| SerializedPartialStateMessage
|
||||||
|
| SerializedExportMessage
|
||||||
|
| SerializedActionMessage
|
||||||
|
| SerializedStateMessage<S, A>;
|
||||||
|
|
||||||
|
export interface EmptyUpdateStateAction {
|
||||||
|
readonly type: typeof UPDATE_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateStateAction<S, A extends Action<unknown>> {
|
||||||
|
readonly type: typeof UPDATE_STATE;
|
||||||
|
request: UpdateStateRequest<S, A>;
|
||||||
|
readonly id: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TabMessage =
|
||||||
|
| StartAction
|
||||||
|
| StopAction
|
||||||
|
| OptionsMessage
|
||||||
|
| DispatchAction
|
||||||
|
| ImportAction
|
||||||
|
| ActionAction
|
||||||
|
| ExportAction;
|
||||||
|
export type PanelMessage<S, A extends Action<unknown>> =
|
||||||
|
| NAAction
|
||||||
|
| ErrorMessage
|
||||||
|
| UpdateStateAction<S, A>;
|
||||||
|
export type MonitorMessage = NAAction | ErrorMessage | EmptyUpdateStateAction;
|
||||||
|
|
||||||
|
type TabPort = Omit<chrome.runtime.Port, 'postMessage'> & {
|
||||||
|
postMessage: (message: TabMessage) => void;
|
||||||
|
};
|
||||||
|
type PanelPort = Omit<chrome.runtime.Port, 'postMessage'> & {
|
||||||
|
postMessage: <S, A extends Action<unknown>>(
|
||||||
|
message: PanelMessage<S, A>
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
type MonitorPort = Omit<chrome.runtime.Port, 'postMessage'> & {
|
||||||
|
postMessage: (message: MonitorMessage) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CONNECTED = 'socket/CONNECTED';
|
||||||
|
export const DISCONNECTED = 'socket/DISCONNECTED';
|
||||||
|
const connections: {
|
||||||
|
readonly tab: { [K in number | string]: TabPort };
|
||||||
|
readonly panel: { [K in number | string]: PanelPort };
|
||||||
|
readonly monitor: { [K in number | string]: MonitorPort };
|
||||||
|
} = {
|
||||||
|
tab: {},
|
||||||
|
panel: {},
|
||||||
|
monitor: {},
|
||||||
|
};
|
||||||
|
const chunks: {
|
||||||
|
[instanceId: string]: PageScriptToContentScriptMessageForwardedToMonitors<
|
||||||
|
unknown,
|
||||||
|
Action<unknown>
|
||||||
|
>;
|
||||||
|
} = {};
|
||||||
|
let monitors = 0;
|
||||||
|
let isMonitored = false;
|
||||||
|
|
||||||
|
const getId = (sender: chrome.runtime.MessageSender, name?: string) =>
|
||||||
|
sender.tab ? sender.tab.id! : name || sender.id!;
|
||||||
|
|
||||||
|
type MonitorAction<S, A extends Action<unknown>> =
|
||||||
|
| NAAction
|
||||||
|
| ErrorMessage
|
||||||
|
| UpdateStateAction<S, A>;
|
||||||
|
|
||||||
|
function toMonitors<S, A extends Action<unknown>>(
|
||||||
|
action: MonitorAction<S, A>,
|
||||||
|
tabId?: string | number,
|
||||||
|
verbose?: boolean
|
||||||
|
) {
|
||||||
|
Object.keys(connections.monitor).forEach((id) => {
|
||||||
|
connections.monitor[id].postMessage(
|
||||||
|
verbose || action.type === 'ERROR' ? action : { type: UPDATE_STATE }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Object.keys(connections.panel).forEach((id) => {
|
||||||
|
connections.panel[id].postMessage(action);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMessage {
|
||||||
|
readonly message: 'IMPORT';
|
||||||
|
readonly id: string | number;
|
||||||
|
readonly instanceId: string;
|
||||||
|
readonly state: string;
|
||||||
|
readonly action?: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToContentScriptMessage = ImportMessage | LiftedActionAction;
|
||||||
|
|
||||||
|
function toContentScript(messageBody: ToContentScriptMessage) {
|
||||||
|
if (messageBody.message === 'DISPATCH') {
|
||||||
|
const { message, action, id, instanceId, state } = messageBody;
|
||||||
|
connections.tab[id!].postMessage({
|
||||||
|
type: message,
|
||||||
|
action,
|
||||||
|
state: nonReduxDispatch(
|
||||||
|
window.store,
|
||||||
|
message,
|
||||||
|
instanceId,
|
||||||
|
action as AppDispatchAction,
|
||||||
|
state
|
||||||
|
),
|
||||||
|
id: instanceId.toString().replace(/^[^\/]+\//, ''),
|
||||||
|
});
|
||||||
|
} else if (messageBody.message === 'IMPORT') {
|
||||||
|
const { message, action, id, instanceId, state } = messageBody;
|
||||||
|
connections.tab[id!].postMessage({
|
||||||
|
type: message,
|
||||||
|
action,
|
||||||
|
state: nonReduxDispatch(
|
||||||
|
window.store,
|
||||||
|
message,
|
||||||
|
instanceId,
|
||||||
|
action as unknown as AppDispatchAction,
|
||||||
|
state
|
||||||
|
),
|
||||||
|
id: instanceId.toString().replace(/^[^\/]+\//, ''),
|
||||||
|
});
|
||||||
|
} else if (messageBody.message === 'ACTION') {
|
||||||
|
const { message, action, id, instanceId, state } = messageBody;
|
||||||
|
connections.tab[id!].postMessage({
|
||||||
|
type: message,
|
||||||
|
action,
|
||||||
|
state: nonReduxDispatch(
|
||||||
|
window.store,
|
||||||
|
message,
|
||||||
|
instanceId,
|
||||||
|
action as unknown as AppDispatchAction,
|
||||||
|
state
|
||||||
|
),
|
||||||
|
id: instanceId.toString().replace(/^[^\/]+\//, ''),
|
||||||
|
});
|
||||||
|
} else if (messageBody.message === 'EXPORT') {
|
||||||
|
const { message, action, id, instanceId, state } = messageBody;
|
||||||
|
connections.tab[id!].postMessage({
|
||||||
|
type: message,
|
||||||
|
action,
|
||||||
|
state: nonReduxDispatch(
|
||||||
|
window.store,
|
||||||
|
message,
|
||||||
|
instanceId,
|
||||||
|
action as unknown as AppDispatchAction,
|
||||||
|
state
|
||||||
|
),
|
||||||
|
id: instanceId.toString().replace(/^[^\/]+\//, ''),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const { message, action, id, instanceId, state } = messageBody;
|
||||||
|
connections.tab[id!].postMessage({
|
||||||
|
type: message,
|
||||||
|
action,
|
||||||
|
state: nonReduxDispatch(
|
||||||
|
window.store,
|
||||||
|
message,
|
||||||
|
instanceId,
|
||||||
|
action as AppDispatchAction,
|
||||||
|
state
|
||||||
|
),
|
||||||
|
id: (instanceId as number).toString().replace(/^[^\/]+\//, ''),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toAllTabs(msg: TabMessage) {
|
||||||
|
const tabs = connections.tab;
|
||||||
|
Object.keys(tabs).forEach((id) => {
|
||||||
|
tabs[id].postMessage(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function monitorInstances(shouldMonitor: boolean, id?: string) {
|
||||||
|
if (!id && isMonitored === shouldMonitor) return;
|
||||||
|
const action = {
|
||||||
|
type: shouldMonitor ? ('START' as const) : ('STOP' as const),
|
||||||
|
};
|
||||||
|
if (id) {
|
||||||
|
if (connections.tab[id]) connections.tab[id].postMessage(action);
|
||||||
|
} else {
|
||||||
|
toAllTabs(action);
|
||||||
|
}
|
||||||
|
isMonitored = shouldMonitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReducerError() {
|
||||||
|
const instancesState = window.store.getState().instances;
|
||||||
|
const payload = instancesState.states[instancesState.current];
|
||||||
|
const computedState = payload.computedStates[payload.currentStateIndex];
|
||||||
|
if (!computedState) return false;
|
||||||
|
return computedState.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePersist() {
|
||||||
|
const state = window.store.getState();
|
||||||
|
if (state.persistStates) {
|
||||||
|
Object.keys(state.instances.connections).forEach((id) => {
|
||||||
|
if (connections.tab[id]) return;
|
||||||
|
window.store.dispatch({ type: REMOVE_INSTANCE, id });
|
||||||
|
toMonitors({ type: 'NA', id });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpenMessage {
|
||||||
|
readonly type: 'OPEN';
|
||||||
|
readonly position: Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpenOptionsMessage {
|
||||||
|
readonly type: 'OPEN_OPTIONS';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetOptionsMessage {
|
||||||
|
readonly type: 'GET_OPTIONS';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SingleMessage =
|
||||||
|
| OpenMessage
|
||||||
|
| OpenOptionsMessage
|
||||||
|
| GetOptionsMessage;
|
||||||
|
|
||||||
|
type BackgroundStoreMessage<S, A extends Action<unknown>> =
|
||||||
|
| PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<S, A>
|
||||||
|
| SplitMessage
|
||||||
|
| SingleMessage;
|
||||||
|
type BackgroundStoreResponse = { readonly options: Options };
|
||||||
|
|
||||||
|
// Receive messages from content scripts
|
||||||
|
function messaging<S, A extends Action<unknown>>(
|
||||||
|
request: BackgroundStoreMessage<S, A>,
|
||||||
|
sender: chrome.runtime.MessageSender,
|
||||||
|
sendResponse?: (response?: BackgroundStoreResponse) => void
|
||||||
|
) {
|
||||||
|
let tabId = getId(sender);
|
||||||
|
if (!tabId) return;
|
||||||
|
if (sender.frameId) tabId = `${tabId}-${sender.frameId}`;
|
||||||
|
|
||||||
|
if (request.type === 'STOP') {
|
||||||
|
if (!Object.keys(window.store.getState().instances.connections).length) {
|
||||||
|
window.store.dispatch({ type: DISCONNECTED });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (request.type === 'OPEN_OPTIONS') {
|
||||||
|
chrome.runtime.openOptionsPage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (request.type === 'GET_OPTIONS') {
|
||||||
|
window.syncOptions.get((options) => {
|
||||||
|
sendResponse!({ options });
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (request.type === 'GET_REPORT') {
|
||||||
|
getReport(request.payload, tabId, request.instanceId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (request.type === 'OPEN') {
|
||||||
|
let position: DevToolsPosition = 'devtools-left';
|
||||||
|
if (
|
||||||
|
['remote', 'panel', 'left', 'right', 'bottom'].indexOf(
|
||||||
|
request.position
|
||||||
|
) !== -1
|
||||||
|
) {
|
||||||
|
position = ('devtools-' + request.position) as DevToolsPosition;
|
||||||
|
}
|
||||||
|
openDevToolsWindow(position);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (request.type === 'ERROR') {
|
||||||
|
if (request.payload) {
|
||||||
|
toMonitors(request, tabId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!request.message) return;
|
||||||
|
const reducerError = getReducerError();
|
||||||
|
chrome.notifications.create('app-error', {
|
||||||
|
type: 'basic',
|
||||||
|
title: reducerError
|
||||||
|
? 'An error occurred in the reducer'
|
||||||
|
: 'An error occurred in the app',
|
||||||
|
message: reducerError || request.message,
|
||||||
|
iconUrl: 'img/logo/48x48.png',
|
||||||
|
isClickable: !!reducerError,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const action: UpdateStateAction<S, A> = {
|
||||||
|
type: UPDATE_STATE,
|
||||||
|
request,
|
||||||
|
id: tabId,
|
||||||
|
} as UpdateStateAction<S, A>;
|
||||||
|
const instanceId = `${tabId}/${request.instanceId}`;
|
||||||
|
if ('split' in request) {
|
||||||
|
if (request.split === 'start') {
|
||||||
|
chunks[instanceId] = request as any;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (request.split === 'chunk') {
|
||||||
|
(chunks[instanceId] as any)[request.chunk[0]] =
|
||||||
|
((chunks[instanceId] as any)[request.chunk[0]] || '') +
|
||||||
|
request.chunk[1];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
action.request = chunks[instanceId] as any;
|
||||||
|
delete chunks[instanceId];
|
||||||
|
}
|
||||||
|
if (request.instanceId) {
|
||||||
|
action.request.instanceId = instanceId;
|
||||||
|
}
|
||||||
|
window.store.dispatch(action);
|
||||||
|
|
||||||
|
if (request.type === 'EXPORT') {
|
||||||
|
toMonitors(action, tabId, true);
|
||||||
|
} else {
|
||||||
|
toMonitors(action, tabId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnect(
|
||||||
|
type: 'tab' | 'monitor' | 'panel',
|
||||||
|
id: number | string,
|
||||||
|
listener?: (message: any, port: chrome.runtime.Port) => void
|
||||||
|
) {
|
||||||
|
return function disconnectListener() {
|
||||||
|
const p = connections[type][id];
|
||||||
|
if (listener && p) p.onMessage.removeListener(listener);
|
||||||
|
if (p) p.onDisconnect.removeListener(disconnectListener);
|
||||||
|
delete connections[type][id];
|
||||||
|
if (type === 'tab') {
|
||||||
|
if (!window.store.getState().persistStates) {
|
||||||
|
window.store.dispatch({ type: REMOVE_INSTANCE, id });
|
||||||
|
toMonitors({ type: 'NA', id });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
monitors--;
|
||||||
|
if (!monitors) monitorInstances(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConnect<S, A extends Action<unknown>>(port: chrome.runtime.Port) {
|
||||||
|
let id: number | string;
|
||||||
|
let listener;
|
||||||
|
|
||||||
|
window.store.dispatch({ type: CONNECTED, port });
|
||||||
|
|
||||||
|
if (port.name === 'tab') {
|
||||||
|
id = getId(port.sender!);
|
||||||
|
if (port.sender!.frameId) id = `${id}-${port.sender!.frameId}`;
|
||||||
|
connections.tab[id] = port;
|
||||||
|
listener = (msg: ContentScriptToBackgroundMessage<S, A>) => {
|
||||||
|
if (msg.name === 'INIT_INSTANCE') {
|
||||||
|
if (typeof id === 'number') {
|
||||||
|
chrome.pageAction.show(id);
|
||||||
|
chrome.pageAction.setIcon({ tabId: id, path: 'img/logo/38x38.png' });
|
||||||
|
}
|
||||||
|
if (isMonitored) port.postMessage({ type: 'START' });
|
||||||
|
|
||||||
|
const state = window.store.getState();
|
||||||
|
if (state.persistStates) {
|
||||||
|
const instanceId = `${id}/${msg.instanceId}`;
|
||||||
|
const persistedState = state.instances.states[instanceId];
|
||||||
|
if (!persistedState) return;
|
||||||
|
toContentScript({
|
||||||
|
message: 'IMPORT',
|
||||||
|
id,
|
||||||
|
instanceId,
|
||||||
|
state: stringifyJSON(
|
||||||
|
persistedState,
|
||||||
|
state.instances.options[instanceId].serialize
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (msg.name === 'RELAY') {
|
||||||
|
messaging(msg.message, port.sender!);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
port.onMessage.addListener(listener);
|
||||||
|
port.onDisconnect.addListener(disconnect('tab', id, listener));
|
||||||
|
} else if (port.name && port.name.indexOf('monitor') === 0) {
|
||||||
|
id = getId(port.sender!, port.name);
|
||||||
|
connections.monitor[id] = port;
|
||||||
|
monitorInstances(true);
|
||||||
|
monitors++;
|
||||||
|
port.onDisconnect.addListener(disconnect('monitor', id));
|
||||||
|
} else {
|
||||||
|
// devpanel
|
||||||
|
id = port.name || port.sender!.frameId!;
|
||||||
|
connections.panel[id] = port;
|
||||||
|
monitorInstances(true, port.name);
|
||||||
|
monitors++;
|
||||||
|
listener = (msg: BackgroundAction) => {
|
||||||
|
window.store.dispatch(msg);
|
||||||
|
};
|
||||||
|
port.onMessage.addListener(listener);
|
||||||
|
port.onDisconnect.addListener(disconnect('panel', id, listener));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.runtime.onConnect.addListener(onConnect);
|
||||||
|
chrome.runtime.onConnectExternal.addListener(onConnect);
|
||||||
|
chrome.runtime.onMessage.addListener(messaging);
|
||||||
|
chrome.runtime.onMessageExternal.addListener(messaging);
|
||||||
|
|
||||||
|
chrome.notifications.onClicked.addListener((id) => {
|
||||||
|
chrome.notifications.clear(id);
|
||||||
|
openDevToolsWindow('devtools-right');
|
||||||
|
});
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
syncOptions: SyncOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.syncOptions = syncOptions(toAllTabs); // Expose to the options page
|
||||||
|
|
||||||
|
export default function api() {
|
||||||
|
return (next: Dispatch<BackgroundAction>) => (action: BackgroundAction) => {
|
||||||
|
if (action.type === LIFTED_ACTION) toContentScript(action);
|
||||||
|
else if (action.type === 'TOGGLE_PERSIST') togglePersist();
|
||||||
|
return next(action);
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,9 +1,16 @@
|
||||||
|
import { Dispatch, MiddlewareAPI } from 'redux';
|
||||||
import {
|
import {
|
||||||
SELECT_INSTANCE,
|
SELECT_INSTANCE,
|
||||||
UPDATE_STATE,
|
UPDATE_STATE,
|
||||||
} from '@redux-devtools/app/lib/constants/actionTypes';
|
} from '@redux-devtools/app/lib/constants/actionTypes';
|
||||||
|
import { StoreAction } from '@redux-devtools/app/lib/actions';
|
||||||
|
import { StoreState } from '@redux-devtools/app/lib/reducers';
|
||||||
|
|
||||||
function selectInstance(tabId, store, next) {
|
function selectInstance(
|
||||||
|
tabId: number,
|
||||||
|
store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>,
|
||||||
|
next: Dispatch<StoreAction>
|
||||||
|
) {
|
||||||
const instances = store.getState().instances;
|
const instances = store.getState().instances;
|
||||||
if (instances.current === 'default') return;
|
if (instances.current === 'default') return;
|
||||||
const connections = instances.connections[tabId];
|
const connections = instances.connections[tabId];
|
||||||
|
@ -12,7 +19,7 @@ function selectInstance(tabId, store, next) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentTabId(next) {
|
function getCurrentTabId(next: (tabId: number) => void) {
|
||||||
chrome.tabs.query(
|
chrome.tabs.query(
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
|
@ -21,13 +28,15 @@ function getCurrentTabId(next) {
|
||||||
(tabs) => {
|
(tabs) => {
|
||||||
const tab = tabs[0];
|
const tab = tabs[0];
|
||||||
if (!tab) return;
|
if (!tab) return;
|
||||||
next(tab.id);
|
next(tab.id!);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function popupSelector(store) {
|
export default function popupSelector(
|
||||||
return (next) => (action) => {
|
store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>
|
||||||
|
) {
|
||||||
|
return (next: Dispatch<StoreAction>) => (action: StoreAction) => {
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
if (action.type === UPDATE_STATE) {
|
if (action.type === UPDATE_STATE) {
|
||||||
if (chrome.devtools && chrome.devtools.inspectedWindow) {
|
if (chrome.devtools && chrome.devtools.inspectedWindow) {
|
|
@ -1,31 +0,0 @@
|
||||||
import {
|
|
||||||
LIFTED_ACTION,
|
|
||||||
UPDATE_STATE,
|
|
||||||
SELECT_INSTANCE,
|
|
||||||
} from '@redux-devtools/app/lib/constants/actionTypes';
|
|
||||||
import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances';
|
|
||||||
|
|
||||||
function panelDispatcher(bgConnection) {
|
|
||||||
let autoselected = false;
|
|
||||||
const tabId = chrome.devtools.inspectedWindow.tabId;
|
|
||||||
|
|
||||||
return (store) => (next) => (action) => {
|
|
||||||
const result = next(action);
|
|
||||||
if (!autoselected && action.type === UPDATE_STATE && tabId) {
|
|
||||||
autoselected = true;
|
|
||||||
const connections = store.getState().instances.connections[tabId];
|
|
||||||
if (connections && connections.length === 1) {
|
|
||||||
next({ type: SELECT_INSTANCE, selected: connections[0] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (action.type === LIFTED_ACTION || action.type === 'TOGGLE_PERSIST') {
|
|
||||||
const instances = store.getState().instances;
|
|
||||||
const instanceId = getActiveInstance(instances);
|
|
||||||
const id = instances.options[instanceId].connectionId;
|
|
||||||
bgConnection.postMessage({ ...action, instanceId, id });
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default panelDispatcher;
|
|
38
extension/src/app/middlewares/panelSync.ts
Normal file
38
extension/src/app/middlewares/panelSync.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import {
|
||||||
|
LIFTED_ACTION,
|
||||||
|
UPDATE_STATE,
|
||||||
|
SELECT_INSTANCE,
|
||||||
|
} from '@redux-devtools/app/lib/constants/actionTypes';
|
||||||
|
import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances';
|
||||||
|
import { Dispatch, MiddlewareAPI } from 'redux';
|
||||||
|
import { StoreState } from '@redux-devtools/app/lib/reducers';
|
||||||
|
import { StoreActionWithTogglePersist } from '../stores/windowStore';
|
||||||
|
|
||||||
|
function panelDispatcher(bgConnection: chrome.runtime.Port) {
|
||||||
|
let autoselected = false;
|
||||||
|
const tabId = chrome.devtools.inspectedWindow.tabId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
store: MiddlewareAPI<Dispatch<StoreActionWithTogglePersist>, StoreState>
|
||||||
|
) =>
|
||||||
|
(next: Dispatch<StoreActionWithTogglePersist>) =>
|
||||||
|
(action: StoreActionWithTogglePersist) => {
|
||||||
|
const result = next(action);
|
||||||
|
if (!autoselected && action.type === UPDATE_STATE && tabId) {
|
||||||
|
autoselected = true;
|
||||||
|
const connections = store.getState().instances.connections[tabId];
|
||||||
|
if (connections && connections.length === 1) {
|
||||||
|
next({ type: SELECT_INSTANCE, selected: connections[0] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (action.type === LIFTED_ACTION || action.type === 'TOGGLE_PERSIST') {
|
||||||
|
const instances = store.getState().instances;
|
||||||
|
const instanceId = getActiveInstance(instances);
|
||||||
|
const id = instances.options[instanceId].connectionId;
|
||||||
|
bgConnection.postMessage({ ...action, instanceId, id });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default panelDispatcher;
|
|
@ -1,23 +0,0 @@
|
||||||
import {
|
|
||||||
UPDATE_STATE,
|
|
||||||
LIFTED_ACTION,
|
|
||||||
} from '@redux-devtools/app/lib/constants/actionTypes';
|
|
||||||
import { getActiveInstance } from '@redux-devtools/app/lib/reducers/instances';
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default syncStores;
|
|
36
extension/src/app/middlewares/windowSync.ts
Normal file
36
extension/src/app/middlewares/windowSync.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import {
|
||||||
|
UPDATE_STATE,
|
||||||
|
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';
|
||||||
|
import {
|
||||||
|
WindowStoreAction,
|
||||||
|
StoreActionWithTogglePersist,
|
||||||
|
} from '../stores/windowStore';
|
||||||
|
import { StoreState } from '@redux-devtools/app/lib/reducers';
|
||||||
|
import { BackgroundAction } from '../stores/backgroundStore';
|
||||||
|
|
||||||
|
const syncStores =
|
||||||
|
(baseStore: Store<BackgroundState, BackgroundAction>) =>
|
||||||
|
(store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>) =>
|
||||||
|
(next: Dispatch<WindowStoreAction>) =>
|
||||||
|
(action: StoreActionWithTogglePersist) => {
|
||||||
|
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 } as any);
|
||||||
|
}
|
||||||
|
return next(action);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default syncStores;
|
|
@ -1,10 +0,0 @@
|
||||||
import { combineReducers } from 'redux';
|
|
||||||
import instances from '@redux-devtools/app/lib/reducers/instances';
|
|
||||||
import persistStates from './persistStates';
|
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
|
||||||
instances,
|
|
||||||
persistStates,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default rootReducer;
|
|
19
extension/src/app/reducers/background/index.ts
Normal file
19
extension/src/app/reducers/background/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { combineReducers, Reducer } from 'redux';
|
||||||
|
import instances, {
|
||||||
|
InstancesState,
|
||||||
|
} from '@redux-devtools/app/lib/reducers/instances';
|
||||||
|
import persistStates from './persistStates';
|
||||||
|
import { BackgroundAction } from '../../stores/backgroundStore';
|
||||||
|
|
||||||
|
export interface BackgroundState {
|
||||||
|
readonly instances: InstancesState;
|
||||||
|
readonly persistStates: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootReducer: Reducer<BackgroundState, BackgroundAction> =
|
||||||
|
combineReducers<BackgroundState>({
|
||||||
|
instances,
|
||||||
|
persistStates,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default rootReducer;
|
|
@ -1,4 +0,0 @@
|
||||||
export default function persistStates(state = false, action) {
|
|
||||||
if (action.type === 'TOGGLE_PERSIST') return !state;
|
|
||||||
return state;
|
|
||||||
}
|
|
6
extension/src/app/reducers/background/persistStates.ts
Normal file
6
extension/src/app/reducers/background/persistStates.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { BackgroundAction } from '../../stores/backgroundStore';
|
||||||
|
|
||||||
|
export default function persistStates(state = false, action: BackgroundAction) {
|
||||||
|
if (action.type === 'TOGGLE_PERSIST') return !state;
|
||||||
|
return state;
|
||||||
|
}
|
|
@ -1,18 +0,0 @@
|
||||||
import { combineReducers } from 'redux';
|
|
||||||
import instances from '@redux-devtools/app/lib/reducers/instances';
|
|
||||||
import monitor from '@redux-devtools/app/lib/reducers/monitor';
|
|
||||||
import notification from '@redux-devtools/app/lib/reducers/notification';
|
|
||||||
import reports from '@redux-devtools/app/lib/reducers/reports';
|
|
||||||
import section from '@redux-devtools/app/lib/reducers/section';
|
|
||||||
import theme from '@redux-devtools/app/lib/reducers/theme';
|
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
|
||||||
instances,
|
|
||||||
monitor,
|
|
||||||
reports,
|
|
||||||
notification,
|
|
||||||
section,
|
|
||||||
theme,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default rootReducer;
|
|
25
extension/src/app/reducers/panel/index.ts
Normal file
25
extension/src/app/reducers/panel/index.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { combineReducers, Reducer } from 'redux';
|
||||||
|
import instances from '@redux-devtools/app/lib/reducers/instances';
|
||||||
|
import monitor from '@redux-devtools/app/lib/reducers/monitor';
|
||||||
|
import notification from '@redux-devtools/app/lib/reducers/notification';
|
||||||
|
import reports from '@redux-devtools/app/lib/reducers/reports';
|
||||||
|
import section from '@redux-devtools/app/lib/reducers/section';
|
||||||
|
import theme from '@redux-devtools/app/lib/reducers/theme';
|
||||||
|
import connection from '@redux-devtools/app/lib/reducers/connection';
|
||||||
|
import socket from '@redux-devtools/app/lib/reducers/socket';
|
||||||
|
import { StoreState } from '@redux-devtools/app/lib/reducers';
|
||||||
|
import { StoreActionWithTogglePersist } from '../../stores/windowStore';
|
||||||
|
|
||||||
|
const rootReducer: Reducer<StoreState, StoreActionWithTogglePersist> =
|
||||||
|
combineReducers<StoreState>({
|
||||||
|
instances,
|
||||||
|
monitor,
|
||||||
|
reports,
|
||||||
|
notification,
|
||||||
|
section,
|
||||||
|
theme,
|
||||||
|
connection,
|
||||||
|
socket,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default rootReducer;
|
|
@ -1,4 +1,4 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers, Reducer } from 'redux';
|
||||||
import instances from './instances';
|
import instances from './instances';
|
||||||
import monitor from '@redux-devtools/app/lib/reducers/monitor';
|
import monitor from '@redux-devtools/app/lib/reducers/monitor';
|
||||||
import notification from '@redux-devtools/app/lib/reducers/notification';
|
import notification from '@redux-devtools/app/lib/reducers/notification';
|
||||||
|
@ -6,15 +6,20 @@ import socket from '@redux-devtools/app/lib/reducers/socket';
|
||||||
import reports from '@redux-devtools/app/lib/reducers/reports';
|
import reports from '@redux-devtools/app/lib/reducers/reports';
|
||||||
import section from '@redux-devtools/app/lib/reducers/section';
|
import section from '@redux-devtools/app/lib/reducers/section';
|
||||||
import theme from '@redux-devtools/app/lib/reducers/theme';
|
import theme from '@redux-devtools/app/lib/reducers/theme';
|
||||||
|
import connection from '@redux-devtools/app/lib/reducers/connection';
|
||||||
|
import { StoreState } from '@redux-devtools/app/lib/reducers';
|
||||||
|
import { WindowStoreAction } from '../../stores/windowStore';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer: Reducer<StoreState, WindowStoreAction> =
|
||||||
instances,
|
combineReducers<StoreState>({
|
||||||
monitor,
|
instances,
|
||||||
socket,
|
monitor,
|
||||||
reports,
|
socket,
|
||||||
notification,
|
reports,
|
||||||
section,
|
notification,
|
||||||
theme,
|
section,
|
||||||
});
|
theme,
|
||||||
|
connection,
|
||||||
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
|
@ -7,11 +7,21 @@ import {
|
||||||
SELECT_INSTANCE,
|
SELECT_INSTANCE,
|
||||||
LIFTED_ACTION,
|
LIFTED_ACTION,
|
||||||
} from '@redux-devtools/app/lib/constants/actionTypes';
|
} from '@redux-devtools/app/lib/constants/actionTypes';
|
||||||
|
import {
|
||||||
|
ExpandedUpdateStateAction,
|
||||||
|
WindowStoreAction,
|
||||||
|
} from '../../stores/windowStore';
|
||||||
|
|
||||||
export default function instances(state = initialState, action) {
|
export default function instances(
|
||||||
|
state = initialState,
|
||||||
|
action: WindowStoreAction
|
||||||
|
) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case UPDATE_STATE:
|
case UPDATE_STATE:
|
||||||
return { ...action.instances, selected: state.selected };
|
return {
|
||||||
|
...(action as ExpandedUpdateStateAction).instances,
|
||||||
|
selected: state.selected,
|
||||||
|
};
|
||||||
case LIFTED_ACTION:
|
case LIFTED_ACTION:
|
||||||
if (action.message === 'DISPATCH') return dispatchAction(state, action);
|
if (action.message === 'DISPATCH') return dispatchAction(state, action);
|
||||||
return state;
|
return state;
|
|
@ -1,8 +1,32 @@
|
||||||
export default class Monitor {
|
import { Action } from 'redux';
|
||||||
constructor(update) {
|
import { LiftedState } from '@redux-devtools/instrument';
|
||||||
|
import { DispatchAction, LibConfig } from '@redux-devtools/app/lib/actions';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__REDUX_DEVTOOLS_EXTENSION_LOCKED__?: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Monitor<S, A extends Action<unknown>> {
|
||||||
|
update: (
|
||||||
|
liftedState?: LiftedState<S, A, unknown> | undefined,
|
||||||
|
libConfig?: LibConfig
|
||||||
|
) => void;
|
||||||
|
active?: boolean;
|
||||||
|
paused?: boolean;
|
||||||
|
lastAction?: string;
|
||||||
|
waitingTimeout?: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
update: (
|
||||||
|
liftedState?: LiftedState<S, A, unknown> | undefined,
|
||||||
|
libConfig?: LibConfig
|
||||||
|
) => void
|
||||||
|
) {
|
||||||
this.update = update;
|
this.update = update;
|
||||||
}
|
}
|
||||||
reducer = (state = {}, action) => {
|
reducer = (state = {}, action: DispatchAction) => {
|
||||||
if (!this.active) return state;
|
if (!this.active) return state;
|
||||||
this.lastAction = action.type;
|
this.lastAction = action.type;
|
||||||
if (action.type === 'LOCK_CHANGES') {
|
if (action.type === 'LOCK_CHANGES') {
|
||||||
|
@ -15,7 +39,7 @@ export default class Monitor {
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
start = (skipUpdate) => {
|
start = (skipUpdate: boolean) => {
|
||||||
this.active = true;
|
this.active = true;
|
||||||
if (!skipUpdate) this.update();
|
if (!skipUpdate) this.update();
|
||||||
};
|
};
|
|
@ -1,18 +0,0 @@
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
|
||||||
import rootReducer from '../reducers/background';
|
|
||||||
import api from '../middlewares/api';
|
|
||||||
|
|
||||||
export default function configureStore(preloadedState) {
|
|
||||||
return createStore(rootReducer, preloadedState, applyMiddleware(api));
|
|
||||||
/*
|
|
||||||
let enhancer;
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
|
||||||
enhancer = applyMiddleware(api);
|
|
||||||
} else {
|
|
||||||
const logger = require('redux-logger');
|
|
||||||
enhancer = applyMiddleware(api, logger());
|
|
||||||
}
|
|
||||||
|
|
||||||
return createStore(rootReducer, preloadedState, enhancer);
|
|
||||||
*/
|
|
||||||
}
|
|
84
extension/src/app/stores/backgroundStore.ts
Normal file
84
extension/src/app/stores/backgroundStore.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { createStore, applyMiddleware, PreloadedState } from 'redux';
|
||||||
|
import rootReducer, { BackgroundState } from '../reducers/background';
|
||||||
|
import api, { CONNECTED, DISCONNECTED } from '../middlewares/api';
|
||||||
|
import { LIFTED_ACTION } from '@redux-devtools/app/lib/constants/actionTypes';
|
||||||
|
import {
|
||||||
|
CustomAction,
|
||||||
|
DispatchAction,
|
||||||
|
StoreActionWithoutLiftedAction,
|
||||||
|
} from '@redux-devtools/app/lib/actions';
|
||||||
|
|
||||||
|
interface LiftedActionActionBase {
|
||||||
|
action?: DispatchAction | string | CustomAction;
|
||||||
|
state?: string;
|
||||||
|
toAll?: boolean;
|
||||||
|
readonly instanceId: string | number;
|
||||||
|
readonly id: string | number | undefined;
|
||||||
|
}
|
||||||
|
interface LiftedActionDispatchAction extends LiftedActionActionBase {
|
||||||
|
type: typeof LIFTED_ACTION;
|
||||||
|
message: 'DISPATCH';
|
||||||
|
action: DispatchAction;
|
||||||
|
toAll?: boolean;
|
||||||
|
}
|
||||||
|
interface LiftedActionImportAction extends LiftedActionActionBase {
|
||||||
|
type: typeof LIFTED_ACTION;
|
||||||
|
message: 'IMPORT';
|
||||||
|
state: string;
|
||||||
|
preloadedState?: unknown | undefined;
|
||||||
|
action?: never;
|
||||||
|
}
|
||||||
|
interface LiftedActionActionAction extends LiftedActionActionBase {
|
||||||
|
type: typeof LIFTED_ACTION;
|
||||||
|
message: 'ACTION';
|
||||||
|
action: string | CustomAction;
|
||||||
|
}
|
||||||
|
interface LiftedActionExportAction extends LiftedActionActionBase {
|
||||||
|
type: typeof LIFTED_ACTION;
|
||||||
|
message: 'EXPORT';
|
||||||
|
toExport: boolean;
|
||||||
|
action?: never;
|
||||||
|
}
|
||||||
|
export type LiftedActionAction =
|
||||||
|
| LiftedActionDispatchAction
|
||||||
|
| LiftedActionImportAction
|
||||||
|
| LiftedActionActionAction
|
||||||
|
| LiftedActionExportAction;
|
||||||
|
|
||||||
|
interface TogglePersistAction {
|
||||||
|
readonly type: 'TOGGLE_PERSIST';
|
||||||
|
readonly instanceId: string | number;
|
||||||
|
readonly id: string | number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConnectedAction {
|
||||||
|
readonly type: typeof CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DisconnectedAction {
|
||||||
|
readonly type: typeof DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BackgroundAction =
|
||||||
|
| StoreActionWithoutLiftedAction
|
||||||
|
| LiftedActionAction
|
||||||
|
| TogglePersistAction
|
||||||
|
| ConnectedAction
|
||||||
|
| DisconnectedAction;
|
||||||
|
|
||||||
|
export default function configureStore(
|
||||||
|
preloadedState?: PreloadedState<BackgroundState>
|
||||||
|
) {
|
||||||
|
return createStore(rootReducer, preloadedState, applyMiddleware(api));
|
||||||
|
/*
|
||||||
|
let enhancer;
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
enhancer = applyMiddleware(api);
|
||||||
|
} else {
|
||||||
|
const logger = require('redux-logger');
|
||||||
|
enhancer = applyMiddleware(api, logger());
|
||||||
|
}
|
||||||
|
|
||||||
|
return createStore(rootReducer, preloadedState, enhancer);
|
||||||
|
*/
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
import { createStore } from 'redux';
|
|
||||||
|
|
||||||
export default function configureStore(reducer, initialState, enhance) {
|
|
||||||
return createStore(reducer, initialState, enhance());
|
|
||||||
}
|
|
15
extension/src/app/stores/createStore.ts
Normal file
15
extension/src/app/stores/createStore.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {
|
||||||
|
Action,
|
||||||
|
createStore,
|
||||||
|
PreloadedState,
|
||||||
|
Reducer,
|
||||||
|
StoreEnhancer,
|
||||||
|
} from 'redux';
|
||||||
|
|
||||||
|
export default function configureStore<S, A extends Action<unknown>>(
|
||||||
|
reducer: Reducer<S, A>,
|
||||||
|
initialState: PreloadedState<S> | undefined,
|
||||||
|
enhance: () => StoreEnhancer
|
||||||
|
) {
|
||||||
|
return createStore(reducer, initialState, enhance());
|
||||||
|
}
|
|
@ -1,15 +1,31 @@
|
||||||
import { compose } from 'redux';
|
import { Action, compose, Reducer, StoreEnhancerStoreCreator } from 'redux';
|
||||||
import instrument from '@redux-devtools/instrument';
|
import instrument from '@redux-devtools/instrument';
|
||||||
import persistState from '@redux-devtools/core/lib/persistState';
|
import persistState from '@redux-devtools/core/lib/persistState';
|
||||||
|
import { ConfigWithExpandedMaxAge } from '../../browser/extension/inject/pageScript';
|
||||||
|
|
||||||
export function getUrlParam(key) {
|
export function getUrlParam(key: string) {
|
||||||
const matches = window.location.href.match(
|
const matches = window.location.href.match(
|
||||||
new RegExp(`[?&]${key}=([^&#]+)\\b`)
|
new RegExp(`[?&]${key}=([^&#]+)\\b`)
|
||||||
);
|
);
|
||||||
return matches && matches.length > 0 ? matches[1] : null;
|
return matches && matches.length > 0 ? matches[1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function configureStore(next, monitorReducer, config) {
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
shouldCatchErrors?: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function configureStore<
|
||||||
|
S,
|
||||||
|
A extends Action<unknown>,
|
||||||
|
MonitorState,
|
||||||
|
MonitorAction extends Action<unknown>
|
||||||
|
>(
|
||||||
|
next: StoreEnhancerStoreCreator,
|
||||||
|
monitorReducer: Reducer<MonitorState, MonitorAction>,
|
||||||
|
config: ConfigWithExpandedMaxAge
|
||||||
|
) {
|
||||||
return compose(
|
return compose(
|
||||||
instrument(monitorReducer, {
|
instrument(monitorReducer, {
|
||||||
maxAge: config.maxAge,
|
maxAge: config.maxAge,
|
|
@ -1,9 +1,14 @@
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
import { createStore, applyMiddleware, PreloadedState } from 'redux';
|
||||||
import exportState from '@redux-devtools/app/lib/middlewares/exportState';
|
import exportState from '@redux-devtools/app/lib/middlewares/exportState';
|
||||||
import panelDispatcher from '../middlewares/panelSync';
|
import panelDispatcher from '../middlewares/panelSync';
|
||||||
import rootReducer from '../reducers/panel';
|
import rootReducer from '../reducers/panel';
|
||||||
|
import { StoreState } from '@redux-devtools/app/lib/reducers';
|
||||||
|
|
||||||
export default function configureStore(position, bgConnection, preloadedState) {
|
export default function configureStore(
|
||||||
|
position: string,
|
||||||
|
bgConnection: chrome.runtime.Port,
|
||||||
|
preloadedState: PreloadedState<StoreState>
|
||||||
|
) {
|
||||||
const enhancer = applyMiddleware(exportState, panelDispatcher(bgConnection));
|
const enhancer = applyMiddleware(exportState, panelDispatcher(bgConnection));
|
||||||
return createStore(rootReducer, preloadedState, enhancer);
|
return createStore(rootReducer, preloadedState, enhancer);
|
||||||
}
|
}
|
|
@ -1,13 +1,51 @@
|
||||||
import { createStore, compose, applyMiddleware } from 'redux';
|
import {
|
||||||
|
createStore,
|
||||||
|
compose,
|
||||||
|
applyMiddleware,
|
||||||
|
Store,
|
||||||
|
PreloadedState,
|
||||||
|
StoreEnhancer,
|
||||||
|
} from 'redux';
|
||||||
import exportState from '@redux-devtools/app/lib/middlewares/exportState';
|
import exportState from '@redux-devtools/app/lib/middlewares/exportState';
|
||||||
import api from '@redux-devtools/app/lib/middlewares/api';
|
import api from '@redux-devtools/app/lib/middlewares/api';
|
||||||
import { CONNECT_REQUEST } from '@redux-devtools/app/lib/constants/socketActionTypes';
|
import { CONNECT_REQUEST } from '@redux-devtools/app/lib/constants/socketActionTypes';
|
||||||
|
import { StoreState } from '@redux-devtools/app/lib/reducers';
|
||||||
|
import {
|
||||||
|
StoreAction,
|
||||||
|
StoreActionWithoutUpdateState,
|
||||||
|
UpdateStateAction,
|
||||||
|
} from '@redux-devtools/app/lib/actions';
|
||||||
|
import { InstancesState } from '@redux-devtools/app/lib/reducers/instances';
|
||||||
import syncStores from '../middlewares/windowSync';
|
import syncStores from '../middlewares/windowSync';
|
||||||
import instanceSelector from '../middlewares/instanceSelector';
|
import instanceSelector from '../middlewares/instanceSelector';
|
||||||
import rootReducer from '../reducers/window';
|
import rootReducer from '../reducers/window';
|
||||||
|
import { BackgroundState } from '../reducers/background';
|
||||||
|
import { BackgroundAction } from './backgroundStore';
|
||||||
|
import { EmptyUpdateStateAction, NAAction } from '../middlewares/api';
|
||||||
|
|
||||||
export default function configureStore(baseStore, position, preloadedState) {
|
export interface TogglePersistAction {
|
||||||
let enhancer;
|
readonly type: 'TOGGLE_PERSIST';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StoreActionWithTogglePersist = StoreAction | TogglePersistAction;
|
||||||
|
|
||||||
|
export interface ExpandedUpdateStateAction extends UpdateStateAction {
|
||||||
|
readonly instances: InstancesState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WindowStoreAction =
|
||||||
|
| StoreActionWithoutUpdateState
|
||||||
|
| TogglePersistAction
|
||||||
|
| ExpandedUpdateStateAction
|
||||||
|
| NAAction
|
||||||
|
| EmptyUpdateStateAction;
|
||||||
|
|
||||||
|
export default function configureStore(
|
||||||
|
baseStore: Store<BackgroundState, BackgroundAction>,
|
||||||
|
position: string,
|
||||||
|
preloadedState: PreloadedState<StoreState>
|
||||||
|
) {
|
||||||
|
let enhancer: StoreEnhancer;
|
||||||
const middlewares = [exportState, api, syncStores(baseStore)];
|
const middlewares = [exportState, api, syncStores(baseStore)];
|
||||||
if (!position || position === '#popup') {
|
if (!position || position === '#popup') {
|
||||||
// select current tab instance for devPanel and pageAction
|
// select current tab instance for devPanel and pageAction
|
||||||
|
@ -20,7 +58,7 @@ export default function configureStore(baseStore, position, preloadedState) {
|
||||||
applyMiddleware(...middlewares),
|
applyMiddleware(...middlewares),
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__
|
window.__REDUX_DEVTOOLS_EXTENSION__
|
||||||
? window.__REDUX_DEVTOOLS_EXTENSION__()
|
? window.__REDUX_DEVTOOLS_EXTENSION__()
|
||||||
: (noop) => noop
|
: (noop: unknown) => noop
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const store = createStore(rootReducer, preloadedState, enhancer);
|
const store = createStore(rootReducer, preloadedState, enhancer);
|
||||||
|
@ -33,7 +71,7 @@ export default function configureStore(baseStore, position, preloadedState) {
|
||||||
hostname: options['s:hostname'],
|
hostname: options['s:hostname'],
|
||||||
port: options['s:port'],
|
port: options['s:port'],
|
||||||
secure: options['s:secure'],
|
secure: options['s:secure'],
|
||||||
},
|
} as any,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,10 +12,10 @@ export function createMenu() {
|
||||||
{ id: 'devtools-remote', title: 'Open Remote DevTools' },
|
{ id: 'devtools-remote', title: 'Open Remote DevTools' },
|
||||||
];
|
];
|
||||||
|
|
||||||
let shortcuts = {};
|
let shortcuts: { [commandName: string]: string | undefined } = {};
|
||||||
chrome.commands.getAll((commands) => {
|
chrome.commands.getAll((commands) => {
|
||||||
commands.forEach(({ name, shortcut }) => {
|
commands.forEach(({ name, shortcut }) => {
|
||||||
shortcuts[name] = shortcut;
|
shortcuts[name!] = shortcut;
|
||||||
});
|
});
|
||||||
|
|
||||||
menus.forEach(({ id, title }) => {
|
menus.forEach(({ id, title }) => {
|
|
@ -1,11 +1,17 @@
|
||||||
const getIfExists = (sel, template) =>
|
import { PreloadedState } from 'redux';
|
||||||
|
import { StoreState } from '@redux-devtools/app/lib/reducers';
|
||||||
|
|
||||||
|
const getIfExists = (sel: any, template: any) =>
|
||||||
typeof sel === 'undefined' ||
|
typeof sel === 'undefined' ||
|
||||||
typeof template === 'undefined' ||
|
typeof template === 'undefined' ||
|
||||||
typeof template[sel] === 'undefined'
|
typeof template[sel] === 'undefined'
|
||||||
? 0
|
? 0
|
||||||
: sel;
|
: sel;
|
||||||
|
|
||||||
export default function getPreloadedState(position, cb) {
|
export default function getPreloadedState(
|
||||||
|
position: string,
|
||||||
|
cb: (state: PreloadedState<StoreState>) => void
|
||||||
|
) {
|
||||||
chrome.storage.local.get(
|
chrome.storage.local.get(
|
||||||
[
|
[
|
||||||
'monitor' + position,
|
'monitor' + position,
|
||||||
|
@ -28,7 +34,7 @@ export default function getPreloadedState(position, cb) {
|
||||||
),
|
),
|
||||||
templates: options['test-templates'],
|
templates: options['test-templates'],
|
||||||
},
|
},
|
||||||
});
|
} as any);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,7 +1,17 @@
|
||||||
import configureStore from '../../../app/stores/backgroundStore';
|
import { Store } from 'redux';
|
||||||
import openDevToolsWindow from './openWindow';
|
import configureStore, {
|
||||||
|
BackgroundAction,
|
||||||
|
} from '../../../app/stores/backgroundStore';
|
||||||
|
import openDevToolsWindow, { DevToolsPosition } from './openWindow';
|
||||||
import { createMenu, removeMenu } from './contextMenus';
|
import { createMenu, removeMenu } from './contextMenus';
|
||||||
import syncOptions from '../options/syncOptions';
|
import syncOptions from '../options/syncOptions';
|
||||||
|
import { BackgroundState } from '../../../app/reducers/background';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
store: Store<BackgroundState, BackgroundAction>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Expose the extension's store globally to access it from the windows
|
// Expose the extension's store globally to access it from the windows
|
||||||
// via chrome.runtime.getBackgroundPage
|
// via chrome.runtime.getBackgroundPage
|
||||||
|
@ -9,7 +19,7 @@ window.store = configureStore();
|
||||||
|
|
||||||
// Listen for keyboard shortcuts
|
// Listen for keyboard shortcuts
|
||||||
chrome.commands.onCommand.addListener((shortcut) => {
|
chrome.commands.onCommand.addListener((shortcut) => {
|
||||||
openDevToolsWindow(shortcut);
|
openDevToolsWindow(shortcut as DevToolsPosition);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create the context menu when installed
|
// Create the context menu when installed
|
|
@ -1,6 +1,10 @@
|
||||||
import { LIFTED_ACTION } from '@redux-devtools/app/lib/constants/actionTypes';
|
import { LIFTED_ACTION } from '@redux-devtools/app/lib/constants/actionTypes';
|
||||||
|
|
||||||
export function getReport(reportId, tabId, instanceId) {
|
export function getReport(
|
||||||
|
reportId: string,
|
||||||
|
tabId: string | number,
|
||||||
|
instanceId: number
|
||||||
|
) {
|
||||||
chrome.storage.local.get(['s:hostname', 's:port', 's:secure'], (options) => {
|
chrome.storage.local.get(['s:hostname', 's:port', 's:secure'], (options) => {
|
||||||
if (!options['s:hostname'] || !options['s:port']) return;
|
if (!options['s:hostname'] || !options['s:port']) return;
|
||||||
const url = `${options['s:secure'] ? 'https' : 'http'}://${
|
const url = `${options['s:secure'] ? 'https' : 'http'}://${
|
|
@ -1,9 +1,20 @@
|
||||||
let windows = {};
|
export type DevToolsPosition =
|
||||||
let lastPosition = null;
|
| 'devtools-left'
|
||||||
|
| 'devtools-right'
|
||||||
|
| 'devtools-bottom'
|
||||||
|
| 'devtools-panel'
|
||||||
|
| 'devtools-remote';
|
||||||
|
|
||||||
export default function openDevToolsWindow(position) {
|
let windows: { [K in DevToolsPosition]?: number } = {};
|
||||||
function popWindow(action, url, customOptions) {
|
let lastPosition: DevToolsPosition | null = null;
|
||||||
function focusIfExist(callback) {
|
|
||||||
|
export default function openDevToolsWindow(position: DevToolsPosition) {
|
||||||
|
function popWindow(
|
||||||
|
action: string,
|
||||||
|
url: string,
|
||||||
|
customOptions: chrome.windows.CreateData & chrome.windows.UpdateInfo
|
||||||
|
) {
|
||||||
|
function focusIfExist(callback: () => void) {
|
||||||
if (!windows[position]) {
|
if (!windows[position]) {
|
||||||
callback();
|
callback();
|
||||||
lastPosition = position;
|
lastPosition = position;
|
||||||
|
@ -12,7 +23,7 @@ export default function openDevToolsWindow(position) {
|
||||||
if (lastPosition !== position && position !== 'devtools-panel') {
|
if (lastPosition !== position && position !== 'devtools-panel') {
|
||||||
params = { ...params, ...customOptions };
|
params = { ...params, ...customOptions };
|
||||||
}
|
}
|
||||||
chrome.windows.update(windows[position], params, () => {
|
chrome.windows.update(windows[position]!, params, () => {
|
||||||
lastPosition = null;
|
lastPosition = null;
|
||||||
if (chrome.runtime.lastError) callback();
|
if (chrome.runtime.lastError) callback();
|
||||||
});
|
});
|
||||||
|
@ -20,7 +31,7 @@ export default function openDevToolsWindow(position) {
|
||||||
}
|
}
|
||||||
|
|
||||||
focusIfExist(() => {
|
focusIfExist(() => {
|
||||||
let options = {
|
let options: chrome.windows.CreateData = {
|
||||||
type: 'popup',
|
type: 'popup',
|
||||||
...customOptions,
|
...customOptions,
|
||||||
};
|
};
|
||||||
|
@ -29,16 +40,19 @@ export default function openDevToolsWindow(position) {
|
||||||
url + '#' + position.substr(position.indexOf('-') + 1)
|
url + '#' + position.substr(position.indexOf('-') + 1)
|
||||||
);
|
);
|
||||||
chrome.windows.create(options, (win) => {
|
chrome.windows.create(options, (win) => {
|
||||||
windows[position] = win.id;
|
windows[position] = win!.id;
|
||||||
if (navigator.userAgent.indexOf('Firefox') !== -1) {
|
if (navigator.userAgent.indexOf('Firefox') !== -1) {
|
||||||
chrome.windows.update(win.id, { focused: true, ...customOptions });
|
chrome.windows.update(win!.id!, {
|
||||||
|
focused: true,
|
||||||
|
...customOptions,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let params = {
|
let params: chrome.windows.CreateData & chrome.windows.UpdateInfo = {
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
width: 380,
|
width: 380,
|
||||||
|
@ -48,7 +62,9 @@ export default function openDevToolsWindow(position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 'devtools-right':
|
case 'devtools-right':
|
||||||
params.left =
|
params.left =
|
||||||
window.screen.availLeft + window.screen.availWidth - params.width;
|
(window.screen as unknown as { availLeft: number }).availLeft +
|
||||||
|
window.screen.availWidth -
|
||||||
|
params.width!;
|
||||||
break;
|
break;
|
||||||
case 'devtools-bottom':
|
case 'devtools-bottom':
|
||||||
params.height = 420;
|
params.height = 420;
|
|
@ -1,48 +1,48 @@
|
||||||
// Mock not supported chrome.* API for Firefox and Electron
|
// Mock not supported chrome.* API for Firefox and Electron
|
||||||
|
|
||||||
window.isElectron =
|
(window as any).isElectron =
|
||||||
window.navigator && window.navigator.userAgent.indexOf('Electron') !== -1;
|
window.navigator && window.navigator.userAgent.indexOf('Electron') !== -1;
|
||||||
|
|
||||||
const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1;
|
const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1;
|
||||||
|
|
||||||
// Background page only
|
// Background page only
|
||||||
if (
|
if (
|
||||||
(window.isElectron &&
|
((window as any).isElectron &&
|
||||||
location.pathname === '/_generated_background_page.html') ||
|
location.pathname === '/_generated_background_page.html') ||
|
||||||
isFirefox
|
isFirefox
|
||||||
) {
|
) {
|
||||||
chrome.runtime.onConnectExternal = {
|
(chrome.runtime as any).onConnectExternal = {
|
||||||
addListener() {},
|
addListener() {},
|
||||||
};
|
};
|
||||||
chrome.runtime.onMessageExternal = {
|
(chrome.runtime as any).onMessageExternal = {
|
||||||
addListener() {},
|
addListener() {},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (window.isElectron) {
|
if ((window as any).isElectron) {
|
||||||
chrome.notifications = {
|
(chrome.notifications as any) = {
|
||||||
onClicked: {
|
onClicked: {
|
||||||
addListener() {},
|
addListener() {},
|
||||||
},
|
},
|
||||||
create() {},
|
create() {},
|
||||||
clear() {},
|
clear() {},
|
||||||
};
|
};
|
||||||
chrome.contextMenus = {
|
(chrome.contextMenus as any) = {
|
||||||
onClicked: {
|
onClicked: {
|
||||||
addListener() {},
|
addListener() {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
chrome.storage.sync = chrome.storage.local;
|
(chrome.storage as any).sync = chrome.storage.local;
|
||||||
chrome.runtime.onInstalled = {
|
(chrome.runtime as any).onInstalled = {
|
||||||
addListener: (cb) => cb(),
|
addListener: (cb: any) => cb(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.isElectron) {
|
if ((window as any).isElectron) {
|
||||||
if (!chrome.storage.local || !chrome.storage.local.remove) {
|
if (!chrome.storage.local || !chrome.storage.local.remove) {
|
||||||
chrome.storage.local = {
|
(chrome.storage as any).local = {
|
||||||
set(obj, callback) {
|
set(obj: any, callback: any) {
|
||||||
Object.keys(obj).forEach((key) => {
|
Object.keys(obj).forEach((key) => {
|
||||||
localStorage.setItem(key, obj[key]);
|
localStorage.setItem(key, obj[key]);
|
||||||
});
|
});
|
||||||
|
@ -50,8 +50,8 @@ if (window.isElectron) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
get(obj, callback) {
|
get(obj: any, callback: any) {
|
||||||
const result = {};
|
const result: any = {};
|
||||||
Object.keys(obj).forEach((key) => {
|
Object.keys(obj).forEach((key) => {
|
||||||
result[key] = localStorage.getItem(key) || obj[key];
|
result[key] = localStorage.getItem(key) || obj[key];
|
||||||
});
|
});
|
||||||
|
@ -60,7 +60,7 @@ if (window.isElectron) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Electron ~ 1.4.6
|
// Electron ~ 1.4.6
|
||||||
remove(items, callback) {
|
remove(items: any, callback: any) {
|
||||||
if (Array.isArray(items)) {
|
if (Array.isArray(items)) {
|
||||||
items.forEach((name) => {
|
items.forEach((name) => {
|
||||||
localStorage.removeItem(name);
|
localStorage.removeItem(name);
|
||||||
|
@ -75,7 +75,7 @@ if (window.isElectron) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Avoid error: chrome.runtime.sendMessage is not supported responseCallback
|
// Avoid error: chrome.runtime.sendMessage is not supported responseCallback
|
||||||
const originSendMessage = chrome.runtime.sendMessage;
|
const originSendMessage = (chrome.runtime as any).sendMessage;
|
||||||
chrome.runtime.sendMessage = function () {
|
chrome.runtime.sendMessage = function () {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
return originSendMessage(...arguments);
|
return originSendMessage(...arguments);
|
||||||
|
@ -87,6 +87,6 @@ if (window.isElectron) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFirefox || window.isElectron) {
|
if (isFirefox || (window as any).isElectron) {
|
||||||
chrome.storage.sync = chrome.storage.local;
|
(chrome.storage as any).sync = chrome.storage.local;
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { CSSProperties } from 'react';
|
||||||
import { render, unmountComponentAtNode } from 'react-dom';
|
import { render, unmountComponentAtNode } from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { REMOVE_INSTANCE } from '@redux-devtools/app/lib/constants/actionTypes';
|
import { REMOVE_INSTANCE } from '@redux-devtools/app/lib/constants/actionTypes';
|
||||||
|
@ -7,15 +7,23 @@ import configureStore from '../../../app/stores/panelStore';
|
||||||
import getPreloadedState from '../background/getPreloadedState';
|
import getPreloadedState from '../background/getPreloadedState';
|
||||||
|
|
||||||
import '../../views/devpanel.pug';
|
import '../../views/devpanel.pug';
|
||||||
|
import { Action, PreloadedState, Store } from 'redux';
|
||||||
|
import { StoreState } from '@redux-devtools/app/lib/reducers';
|
||||||
|
import { PanelMessage } from '../../../app/middlewares/api';
|
||||||
|
import { StoreActionWithTogglePersist } from '../../../app/stores/windowStore';
|
||||||
|
|
||||||
const position = location.hash;
|
const position = location.hash;
|
||||||
const messageStyle = { padding: '20px', width: '100%', textAlign: 'center' };
|
const messageStyle: CSSProperties = {
|
||||||
|
padding: '20px',
|
||||||
|
width: '100%',
|
||||||
|
textAlign: 'center',
|
||||||
|
};
|
||||||
|
|
||||||
let rendered;
|
let rendered: boolean | undefined;
|
||||||
let store;
|
let store: Store<StoreState, StoreActionWithTogglePersist> | undefined;
|
||||||
let bgConnection;
|
let bgConnection: chrome.runtime.Port;
|
||||||
let naTimeout;
|
let naTimeout: NodeJS.Timeout;
|
||||||
let preloadedState;
|
let preloadedState: PreloadedState<StoreState>;
|
||||||
|
|
||||||
const isChrome = navigator.userAgent.indexOf('Firefox') === -1;
|
const isChrome = navigator.userAgent.indexOf('Firefox') === -1;
|
||||||
|
|
||||||
|
@ -25,7 +33,7 @@ getPreloadedState(position, (state) => {
|
||||||
|
|
||||||
function renderDevTools() {
|
function renderDevTools() {
|
||||||
const node = document.getElementById('root');
|
const node = document.getElementById('root');
|
||||||
unmountComponentAtNode(node);
|
unmountComponentAtNode(node!);
|
||||||
clearTimeout(naTimeout);
|
clearTimeout(naTimeout);
|
||||||
store = configureStore(position, bgConnection, preloadedState);
|
store = configureStore(position, bgConnection, preloadedState);
|
||||||
render(
|
render(
|
||||||
|
@ -71,33 +79,35 @@ function renderNA() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = document.getElementById('root');
|
const node = document.getElementById('root');
|
||||||
unmountComponentAtNode(node);
|
unmountComponentAtNode(node!);
|
||||||
render(message, node);
|
render(message, node);
|
||||||
store = undefined;
|
store = undefined;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const node = document.getElementById('root');
|
const node = document.getElementById('root');
|
||||||
unmountComponentAtNode(node);
|
unmountComponentAtNode(node!);
|
||||||
render(message, node);
|
render(message, node);
|
||||||
store = undefined;
|
store = undefined;
|
||||||
}
|
}
|
||||||
}, 3500);
|
}, 3500);
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(id) {
|
function init(id: number) {
|
||||||
renderNA();
|
renderNA();
|
||||||
bgConnection = chrome.runtime.connect({
|
bgConnection = chrome.runtime.connect({
|
||||||
name: id ? id.toString() : undefined,
|
name: id ? id.toString() : undefined,
|
||||||
});
|
});
|
||||||
bgConnection.onMessage.addListener((message) => {
|
bgConnection.onMessage.addListener(
|
||||||
if (message.type === 'NA') {
|
<S, A extends Action<unknown>>(message: PanelMessage<S, A>) => {
|
||||||
if (message.id === id) renderNA();
|
if (message.type === 'NA') {
|
||||||
else store.dispatch({ type: REMOVE_INSTANCE, id: message.id });
|
if (message.id === id) renderNA();
|
||||||
} else {
|
else store!.dispatch({ type: REMOVE_INSTANCE, id: message.id });
|
||||||
if (!rendered) renderDevTools();
|
} else {
|
||||||
store.dispatch(message);
|
if (!rendered) renderDevTools();
|
||||||
|
store!.dispatch(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
init(chrome.devtools.inspectedWindow.tabId);
|
init(chrome.devtools.inspectedWindow.tabId);
|
|
@ -1,6 +1,6 @@
|
||||||
import '../../views/devtools.pug';
|
import '../../views/devtools.pug';
|
||||||
|
|
||||||
function createPanel(url) {
|
function createPanel(url: string) {
|
||||||
chrome.devtools.panels.create(
|
chrome.devtools.panels.create(
|
||||||
'Redux',
|
'Redux',
|
||||||
'img/logo/scalable.png',
|
'img/logo/scalable.png',
|
|
@ -1,132 +0,0 @@
|
||||||
import {
|
|
||||||
injectOptions,
|
|
||||||
getOptionsFromBg,
|
|
||||||
isAllowed,
|
|
||||||
} from '../options/syncOptions';
|
|
||||||
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
|
|
||||||
const maxChromeMsgSize = 32 * 1024 * 1024;
|
|
||||||
let connected = false;
|
|
||||||
let bg;
|
|
||||||
|
|
||||||
function connect() {
|
|
||||||
// Connect to the background script
|
|
||||||
connected = true;
|
|
||||||
const name = 'tab';
|
|
||||||
if (window.devToolsExtensionID) {
|
|
||||||
bg = chrome.runtime.connect(window.devToolsExtensionID, { name });
|
|
||||||
} else {
|
|
||||||
bg = chrome.runtime.connect({ name });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Relay background script messages to the page script
|
|
||||||
bg.onMessage.addListener((message) => {
|
|
||||||
if (message.action) {
|
|
||||||
window.postMessage(
|
|
||||||
{
|
|
||||||
type: message.type,
|
|
||||||
payload: message.action,
|
|
||||||
state: message.state,
|
|
||||||
id: message.id,
|
|
||||||
source,
|
|
||||||
},
|
|
||||||
'*'
|
|
||||||
);
|
|
||||||
} else if (message.options) {
|
|
||||||
injectOptions(message.options);
|
|
||||||
} else {
|
|
||||||
window.postMessage(
|
|
||||||
{
|
|
||||||
type: message.type,
|
|
||||||
state: message.state,
|
|
||||||
id: message.id,
|
|
||||||
source,
|
|
||||||
},
|
|
||||||
'*'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bg.onDisconnect.addListener(handleDisconnect);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDisconnect() {
|
|
||||||
window.removeEventListener('message', handleMessages);
|
|
||||||
window.postMessage({ type: 'STOP', failed: true, source }, '*');
|
|
||||||
bg = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryCatch(fn, args) {
|
|
||||||
try {
|
|
||||||
return fn(args);
|
|
||||||
} catch (err) {
|
|
||||||
if (err.message === 'Message length exceeded maximum allowed length.') {
|
|
||||||
const instanceId = args.instanceId;
|
|
||||||
const newArgs = { split: 'start' };
|
|
||||||
const toSplit = [];
|
|
||||||
let size = 0;
|
|
||||||
let arg;
|
|
||||||
Object.keys(args).map((key) => {
|
|
||||||
arg = args[key];
|
|
||||||
if (typeof arg === 'string') {
|
|
||||||
size += arg.length;
|
|
||||||
if (size > maxChromeMsgSize) {
|
|
||||||
toSplit.push([key, arg]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newArgs[key] = arg;
|
|
||||||
});
|
|
||||||
fn(newArgs);
|
|
||||||
for (let i = 0; i < toSplit.length; i++) {
|
|
||||||
for (let j = 0; j < toSplit[i][1].length; j += maxChromeMsgSize) {
|
|
||||||
fn({
|
|
||||||
instanceId,
|
|
||||||
source: pageSource,
|
|
||||||
split: 'chunk',
|
|
||||||
chunk: [toSplit[i][0], toSplit[i][1].substr(j, maxChromeMsgSize)],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fn({ instanceId, source: pageSource, split: 'end' });
|
|
||||||
}
|
|
||||||
handleDisconnect();
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
console.error('Failed to send message', err);
|
|
||||||
}
|
|
||||||
/* eslint-enable no-console */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function send(message) {
|
|
||||||
if (!connected) connect();
|
|
||||||
if (message.type === 'INIT_INSTANCE') {
|
|
||||||
getOptionsFromBg();
|
|
||||||
bg.postMessage({ name: 'INIT_INSTANCE', instanceId: message.instanceId });
|
|
||||||
} else {
|
|
||||||
bg.postMessage({ name: 'RELAY', message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resend messages from the page to the background script
|
|
||||||
function handleMessages(event) {
|
|
||||||
if (!isAllowed()) return;
|
|
||||||
if (!event || event.source !== window || typeof event.data !== 'object') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const message = event.data;
|
|
||||||
if (message.source !== pageSource) return;
|
|
||||||
if (message.type === 'DISCONNECT') {
|
|
||||||
if (bg) {
|
|
||||||
bg.disconnect();
|
|
||||||
connected = false;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tryCatch(send, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('message', handleMessages, false);
|
|
313
extension/src/browser/extension/inject/contentScript.ts
Normal file
313
extension/src/browser/extension/inject/contentScript.ts
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
import {
|
||||||
|
injectOptions,
|
||||||
|
getOptionsFromBg,
|
||||||
|
isAllowed,
|
||||||
|
} from '../options/syncOptions';
|
||||||
|
import { TabMessage } from '../../../app/middlewares/api';
|
||||||
|
import {
|
||||||
|
PageScriptToContentScriptMessage,
|
||||||
|
PageScriptToContentScriptMessageWithoutDisconnect,
|
||||||
|
PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance,
|
||||||
|
} from '../../../app/api';
|
||||||
|
import { Action } from 'redux';
|
||||||
|
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
|
||||||
|
const maxChromeMsgSize = 32 * 1024 * 1024;
|
||||||
|
let connected = false;
|
||||||
|
let bg: chrome.runtime.Port | undefined;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
devToolsExtensionID?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StartAction {
|
||||||
|
readonly type: 'START';
|
||||||
|
readonly state: undefined;
|
||||||
|
readonly id: undefined;
|
||||||
|
readonly source: typeof source;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StopAction {
|
||||||
|
readonly type: 'STOP';
|
||||||
|
readonly state: undefined;
|
||||||
|
readonly id: undefined;
|
||||||
|
readonly source: typeof source;
|
||||||
|
readonly failed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchAction {
|
||||||
|
readonly type: 'DISPATCH';
|
||||||
|
readonly payload: AppDispatchAction;
|
||||||
|
readonly state: string | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
readonly source: typeof source;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportAction {
|
||||||
|
readonly type: 'IMPORT';
|
||||||
|
readonly payload: undefined;
|
||||||
|
readonly state: string | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
readonly source: typeof source;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionAction {
|
||||||
|
readonly type: 'ACTION';
|
||||||
|
readonly payload: string | CustomAction;
|
||||||
|
readonly state: string | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
readonly source: typeof source;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportAction {
|
||||||
|
readonly type: 'EXPORT';
|
||||||
|
readonly payload: undefined;
|
||||||
|
readonly state: string | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
readonly source: typeof source;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateAction {
|
||||||
|
readonly type: 'UPDATE';
|
||||||
|
readonly state: string | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
readonly source: typeof source;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ContentScriptToPageScriptMessage =
|
||||||
|
| StartAction
|
||||||
|
| StopAction
|
||||||
|
| DispatchAction
|
||||||
|
| ImportAction
|
||||||
|
| ActionAction
|
||||||
|
| ExportAction
|
||||||
|
| UpdateAction;
|
||||||
|
|
||||||
|
interface ImportStatePayload<S, A extends Action<unknown>> {
|
||||||
|
readonly type: 'IMPORT_STATE';
|
||||||
|
readonly nextLiftedState: LiftedState<S, A, unknown> | readonly A[];
|
||||||
|
readonly preloadedState?: S;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportStateDispatchAction<S, A extends Action<unknown>> {
|
||||||
|
readonly type: 'DISPATCH';
|
||||||
|
readonly payload: ImportStatePayload<S, A>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ListenerMessage<S, A extends Action<unknown>> =
|
||||||
|
| StartAction
|
||||||
|
| StopAction
|
||||||
|
| DispatchAction
|
||||||
|
| ImportAction
|
||||||
|
| ActionAction
|
||||||
|
| ExportAction
|
||||||
|
| UpdateAction
|
||||||
|
| ImportStateDispatchAction<S, A>;
|
||||||
|
|
||||||
|
function postToPageScript(message: ContentScriptToPageScriptMessage) {
|
||||||
|
window.postMessage(message, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
// Connect to the background script
|
||||||
|
connected = true;
|
||||||
|
const name = 'tab';
|
||||||
|
if (window.devToolsExtensionID) {
|
||||||
|
bg = chrome.runtime.connect(window.devToolsExtensionID, { name });
|
||||||
|
} else {
|
||||||
|
bg = chrome.runtime.connect({ name });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relay background script messages to the page script
|
||||||
|
bg.onMessage.addListener((message: TabMessage) => {
|
||||||
|
if ('action' in message) {
|
||||||
|
if (message.type === 'DISPATCH') {
|
||||||
|
postToPageScript({
|
||||||
|
type: message.type,
|
||||||
|
payload: message.action,
|
||||||
|
state: message.state,
|
||||||
|
id: message.id,
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
} else if (message.type === 'ACTION') {
|
||||||
|
postToPageScript({
|
||||||
|
type: message.type,
|
||||||
|
payload: message.action,
|
||||||
|
state: message.state,
|
||||||
|
id: message.id,
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
postToPageScript({
|
||||||
|
type: message.type,
|
||||||
|
payload: message.action,
|
||||||
|
state: message.state,
|
||||||
|
id: message.id,
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if ('options' in message) {
|
||||||
|
injectOptions(message.options);
|
||||||
|
} else {
|
||||||
|
postToPageScript({
|
||||||
|
type: message.type,
|
||||||
|
state: message.state,
|
||||||
|
id: message.id,
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bg.onDisconnect.addListener(handleDisconnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDisconnect() {
|
||||||
|
window.removeEventListener('message', handleMessages);
|
||||||
|
window.postMessage({ type: 'STOP', failed: true, source }, '*');
|
||||||
|
bg = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SplitMessageBase {
|
||||||
|
readonly type?: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SplitMessageStart extends SplitMessageBase {
|
||||||
|
readonly instanceId: number;
|
||||||
|
readonly source: typeof pageSource;
|
||||||
|
readonly split: 'start';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SplitMessageChunk extends SplitMessageBase {
|
||||||
|
readonly instanceId: number;
|
||||||
|
readonly source: typeof pageSource;
|
||||||
|
readonly split: 'chunk';
|
||||||
|
readonly chunk: [string, string];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SplitMessageEnd extends SplitMessageBase {
|
||||||
|
readonly instanceId: number;
|
||||||
|
readonly source: typeof pageSource;
|
||||||
|
readonly split: 'end';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SplitMessage =
|
||||||
|
| SplitMessageStart
|
||||||
|
| SplitMessageChunk
|
||||||
|
| SplitMessageEnd;
|
||||||
|
|
||||||
|
function tryCatch<S, A extends Action<unknown>>(
|
||||||
|
fn: (
|
||||||
|
args: PageScriptToContentScriptMessageWithoutDisconnect<S, A> | SplitMessage
|
||||||
|
) => void,
|
||||||
|
args: PageScriptToContentScriptMessageWithoutDisconnect<S, A>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return fn(args);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message === 'Message length exceeded maximum allowed length.') {
|
||||||
|
const instanceId = (args as any).instanceId;
|
||||||
|
const newArgs = {
|
||||||
|
split: 'start',
|
||||||
|
};
|
||||||
|
const toSplit: [string, string][] = [];
|
||||||
|
let size = 0;
|
||||||
|
let arg;
|
||||||
|
Object.keys(args).map((key) => {
|
||||||
|
arg = args[key as keyof typeof args];
|
||||||
|
if (typeof arg === 'string') {
|
||||||
|
size += arg.length;
|
||||||
|
if (size > maxChromeMsgSize) {
|
||||||
|
toSplit.push([key, arg]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newArgs[key as keyof typeof newArgs] = arg;
|
||||||
|
});
|
||||||
|
fn(newArgs as any);
|
||||||
|
for (let i = 0; i < toSplit.length; i++) {
|
||||||
|
for (let j = 0; j < toSplit[i][1].length; j += maxChromeMsgSize) {
|
||||||
|
fn({
|
||||||
|
instanceId,
|
||||||
|
source: pageSource,
|
||||||
|
split: 'chunk',
|
||||||
|
chunk: [toSplit[i][0], toSplit[i][1].substr(j, maxChromeMsgSize)],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fn({ instanceId, source: pageSource, split: 'end' });
|
||||||
|
}
|
||||||
|
handleDisconnect();
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
console.error('Failed to send message', err);
|
||||||
|
}
|
||||||
|
/* eslint-enable no-console */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InitInstanceContentScriptToBackgroundMessage {
|
||||||
|
readonly name: 'INIT_INSTANCE';
|
||||||
|
readonly instanceId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RelayMessage<S, A extends Action<unknown>> {
|
||||||
|
readonly name: 'RELAY';
|
||||||
|
readonly message:
|
||||||
|
| PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<S, A>
|
||||||
|
| SplitMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ContentScriptToBackgroundMessage<S, A extends Action<unknown>> =
|
||||||
|
| InitInstanceContentScriptToBackgroundMessage
|
||||||
|
| RelayMessage<S, A>;
|
||||||
|
|
||||||
|
function postToBackground<S, A extends Action<unknown>>(
|
||||||
|
message: ContentScriptToBackgroundMessage<S, A>
|
||||||
|
) {
|
||||||
|
bg!.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function send<S, A extends Action<unknown>>(
|
||||||
|
message:
|
||||||
|
| PageScriptToContentScriptMessageWithoutDisconnect<S, A>
|
||||||
|
| SplitMessage
|
||||||
|
) {
|
||||||
|
if (!connected) connect();
|
||||||
|
if (message.type === 'INIT_INSTANCE') {
|
||||||
|
getOptionsFromBg();
|
||||||
|
postToBackground({ name: 'INIT_INSTANCE', instanceId: message.instanceId });
|
||||||
|
} else {
|
||||||
|
postToBackground({ name: 'RELAY', message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resend messages from the page to the background script
|
||||||
|
function handleMessages<S, A extends Action<unknown>>(
|
||||||
|
event: MessageEvent<PageScriptToContentScriptMessage<S, A>>
|
||||||
|
) {
|
||||||
|
if (!isAllowed()) return;
|
||||||
|
if (!event || event.source !== window || typeof event.data !== 'object') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = event.data;
|
||||||
|
if (message.source !== pageSource) return;
|
||||||
|
if (message.type === 'DISCONNECT') {
|
||||||
|
if (bg) {
|
||||||
|
bg.disconnect();
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tryCatch(send, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', handleMessages, false);
|
|
@ -1,6 +1,8 @@
|
||||||
// Include this script in Chrome apps and extensions for remote debugging
|
// Include this script in Chrome apps and extensions for remote debugging
|
||||||
// <script src="chrome-extension://lmhkpmbekcpmknklioeibfkpmmfibljd/js/redux-devtools-extension.bundle.js"></script>
|
// <script src="chrome-extension://lmhkpmbekcpmknklioeibfkpmmfibljd/js/redux-devtools-extension.bundle.js"></script>
|
||||||
|
|
||||||
|
import { Options } from '../options/syncOptions';
|
||||||
|
|
||||||
window.devToolsExtensionID = 'lmhkpmbekcpmknklioeibfkpmmfibljd';
|
window.devToolsExtensionID = 'lmhkpmbekcpmknklioeibfkpmmfibljd';
|
||||||
require('./contentScript');
|
require('./contentScript');
|
||||||
require('./pageScript');
|
require('./pageScript');
|
||||||
|
@ -8,7 +10,7 @@ require('./pageScript');
|
||||||
chrome.runtime.sendMessage(
|
chrome.runtime.sendMessage(
|
||||||
window.devToolsExtensionID,
|
window.devToolsExtensionID,
|
||||||
{ type: 'GET_OPTIONS' },
|
{ type: 'GET_OPTIONS' },
|
||||||
function (response) {
|
function (response: { readonly options: Options }) {
|
||||||
if (!response.options.inject) {
|
if (!response.options.inject) {
|
||||||
const urls = response.options.urls.split('\n').filter(Boolean).join('|');
|
const urls = response.options.urls.split('\n').filter(Boolean).join('|');
|
||||||
if (!location.href.match(new RegExp(urls))) return;
|
if (!location.href.match(new RegExp(urls))) return;
|
|
@ -1,486 +0,0 @@
|
||||||
import { getActionsArray, evalAction } from '@redux-devtools/utils';
|
|
||||||
import throttle from 'lodash/throttle';
|
|
||||||
import createStore from '../../../app/stores/createStore';
|
|
||||||
import configureStore, { getUrlParam } from '../../../app/stores/enhancerStore';
|
|
||||||
import { isAllowed } from '../options/syncOptions';
|
|
||||||
import Monitor from '../../../app/service/Monitor';
|
|
||||||
import {
|
|
||||||
noFiltersApplied,
|
|
||||||
getLocalFilter,
|
|
||||||
isFiltered,
|
|
||||||
filterState,
|
|
||||||
startingFrom,
|
|
||||||
} from '../../../app/api/filters';
|
|
||||||
import notifyErrors from '../../../app/api/notifyErrors';
|
|
||||||
import importState from '../../../app/api/importState';
|
|
||||||
import openWindow from '../../../app/api/openWindow';
|
|
||||||
import generateId from '../../../app/api/generateInstanceId';
|
|
||||||
import {
|
|
||||||
updateStore,
|
|
||||||
toContentScript,
|
|
||||||
sendMessage,
|
|
||||||
setListener,
|
|
||||||
connect,
|
|
||||||
disconnect,
|
|
||||||
isInIframe,
|
|
||||||
getSeralizeParameter,
|
|
||||||
} from '../../../app/api';
|
|
||||||
|
|
||||||
const source = '@devtools-page';
|
|
||||||
let stores = {};
|
|
||||||
let reportId;
|
|
||||||
|
|
||||||
function deprecateParam(oldParam, newParam) {
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
console.warn(
|
|
||||||
`${oldParam} parameter is deprecated, use ${newParam} instead: https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md`
|
|
||||||
);
|
|
||||||
/* eslint-enable no-console */
|
|
||||||
}
|
|
||||||
|
|
||||||
const __REDUX_DEVTOOLS_EXTENSION__ = function (
|
|
||||||
reducer,
|
|
||||||
preloadedState,
|
|
||||||
config
|
|
||||||
) {
|
|
||||||
/* eslint-disable no-param-reassign */
|
|
||||||
if (typeof reducer === 'object') {
|
|
||||||
config = reducer;
|
|
||||||
reducer = undefined;
|
|
||||||
} else if (typeof config !== 'object') config = {};
|
|
||||||
/* eslint-enable no-param-reassign */
|
|
||||||
if (!window.devToolsOptions) window.devToolsOptions = {};
|
|
||||||
|
|
||||||
let store;
|
|
||||||
let errorOccurred = false;
|
|
||||||
let maxAge;
|
|
||||||
let actionCreators;
|
|
||||||
let sendingActionId = 1;
|
|
||||||
const instanceId = generateId(config.instanceId);
|
|
||||||
const localFilter = getLocalFilter(config);
|
|
||||||
const serializeState = getSeralizeParameter(config, 'serializeState');
|
|
||||||
const serializeAction = getSeralizeParameter(config, 'serializeAction');
|
|
||||||
let {
|
|
||||||
statesFilter,
|
|
||||||
actionsFilter,
|
|
||||||
stateSanitizer,
|
|
||||||
actionSanitizer,
|
|
||||||
predicate,
|
|
||||||
latency = 500,
|
|
||||||
} = config;
|
|
||||||
|
|
||||||
// Deprecate statesFilter and actionsFilter
|
|
||||||
if (statesFilter) {
|
|
||||||
deprecateParam('statesFilter', 'stateSanitizer');
|
|
||||||
stateSanitizer = statesFilter; // eslint-disable-line no-param-reassign
|
|
||||||
}
|
|
||||||
if (actionsFilter) {
|
|
||||||
deprecateParam('actionsFilter', 'actionSanitizer');
|
|
||||||
actionSanitizer = actionsFilter; // eslint-disable-line no-param-reassign
|
|
||||||
}
|
|
||||||
|
|
||||||
const monitor = new Monitor(relayState);
|
|
||||||
if (config.getMonitor) {
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
console.warn(
|
|
||||||
"Redux DevTools extension's `getMonitor` parameter is deprecated and will be not " +
|
|
||||||
'supported in the next version, please remove it and just use ' +
|
|
||||||
'`__REDUX_DEVTOOLS_EXTENSION_COMPOSE__` instead: ' +
|
|
||||||
'https://github.com/zalmoxisus/redux-devtools-extension#12-advanced-store-setup'
|
|
||||||
);
|
|
||||||
/* eslint-enable no-console */
|
|
||||||
config.getMonitor(monitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
function exportState() {
|
|
||||||
const liftedState = store.liftedStore.getState();
|
|
||||||
const actionsById = liftedState.actionsById;
|
|
||||||
const payload = [];
|
|
||||||
liftedState.stagedActionIds.slice(1).forEach((id) => {
|
|
||||||
// if (isFiltered(actionsById[id].action, localFilter)) return;
|
|
||||||
payload.push(actionsById[id].action);
|
|
||||||
});
|
|
||||||
toContentScript(
|
|
||||||
{
|
|
||||||
type: 'EXPORT',
|
|
||||||
payload,
|
|
||||||
committedState: liftedState.committedState,
|
|
||||||
source,
|
|
||||||
instanceId,
|
|
||||||
},
|
|
||||||
serializeState,
|
|
||||||
serializeAction
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function relay(type, state, action, nextActionId, libConfig) {
|
|
||||||
const message = {
|
|
||||||
type,
|
|
||||||
payload: filterState(
|
|
||||||
state,
|
|
||||||
type,
|
|
||||||
localFilter,
|
|
||||||
stateSanitizer,
|
|
||||||
actionSanitizer,
|
|
||||||
nextActionId,
|
|
||||||
predicate
|
|
||||||
),
|
|
||||||
source,
|
|
||||||
instanceId,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (type === 'ACTION') {
|
|
||||||
message.action = !actionSanitizer
|
|
||||||
? action
|
|
||||||
: actionSanitizer(action.action, nextActionId - 1);
|
|
||||||
message.maxAge = getMaxAge();
|
|
||||||
message.nextActionId = nextActionId;
|
|
||||||
} else if (libConfig) {
|
|
||||||
message.libConfig = libConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
toContentScript(message, serializeState, serializeAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
const relayState = throttle((liftedState, libConfig) => {
|
|
||||||
relayAction.cancel();
|
|
||||||
const state = liftedState || store.liftedStore.getState();
|
|
||||||
sendingActionId = state.nextActionId;
|
|
||||||
relay('STATE', state, undefined, undefined, libConfig);
|
|
||||||
}, latency);
|
|
||||||
|
|
||||||
const relayAction = throttle(() => {
|
|
||||||
const liftedState = store.liftedStore.getState();
|
|
||||||
const nextActionId = liftedState.nextActionId;
|
|
||||||
const currentActionId = nextActionId - 1;
|
|
||||||
const liftedAction = liftedState.actionsById[currentActionId];
|
|
||||||
|
|
||||||
// Send a single action
|
|
||||||
if (sendingActionId === currentActionId) {
|
|
||||||
sendingActionId = nextActionId;
|
|
||||||
const action = liftedAction.action;
|
|
||||||
const computedStates = liftedState.computedStates;
|
|
||||||
if (
|
|
||||||
isFiltered(action, localFilter) ||
|
|
||||||
(predicate &&
|
|
||||||
!predicate(computedStates[computedStates.length - 1].state, action))
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const state =
|
|
||||||
liftedState.computedStates[liftedState.computedStates.length - 1].state;
|
|
||||||
relay(
|
|
||||||
'ACTION',
|
|
||||||
state,
|
|
||||||
liftedState.actionsById[nextActionId - 1],
|
|
||||||
nextActionId
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send multiple actions
|
|
||||||
const payload = startingFrom(
|
|
||||||
sendingActionId,
|
|
||||||
liftedState,
|
|
||||||
localFilter,
|
|
||||||
stateSanitizer,
|
|
||||||
actionSanitizer,
|
|
||||||
predicate
|
|
||||||
);
|
|
||||||
sendingActionId = nextActionId;
|
|
||||||
if (typeof payload === 'undefined') return;
|
|
||||||
if (typeof payload.skippedActionIds !== 'undefined') {
|
|
||||||
relay('STATE', payload);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
toContentScript(
|
|
||||||
{
|
|
||||||
type: 'PARTIAL_STATE',
|
|
||||||
payload,
|
|
||||||
source,
|
|
||||||
instanceId,
|
|
||||||
maxAge: getMaxAge(),
|
|
||||||
},
|
|
||||||
serializeState,
|
|
||||||
serializeAction
|
|
||||||
);
|
|
||||||
}, latency);
|
|
||||||
|
|
||||||
function dispatchRemotely(action) {
|
|
||||||
if (config.features && !config.features.dispatch) return;
|
|
||||||
try {
|
|
||||||
const result = evalAction(action, actionCreators);
|
|
||||||
(store.initialDispatch || store.dispatch)(result);
|
|
||||||
} catch (e) {
|
|
||||||
relay('ERROR', e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function importPayloadFrom(state) {
|
|
||||||
if (config.features && !config.features.import) return;
|
|
||||||
try {
|
|
||||||
const nextLiftedState = importState(state, config);
|
|
||||||
if (!nextLiftedState) return;
|
|
||||||
store.liftedStore.dispatch({ type: 'IMPORT_STATE', ...nextLiftedState });
|
|
||||||
} catch (e) {
|
|
||||||
relay('ERROR', e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dispatchMonitorAction(action) {
|
|
||||||
const type = action.type;
|
|
||||||
const features = config.features;
|
|
||||||
if (features) {
|
|
||||||
if (
|
|
||||||
!features.jump &&
|
|
||||||
(type === 'JUMP_TO_STATE' || type === 'JUMP_TO_ACTION')
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!features.skip && type === 'TOGGLE_ACTION') return;
|
|
||||||
if (!features.reorder && type === 'REORDER_ACTION') return;
|
|
||||||
if (!features.import && type === 'IMPORT_STATE') return;
|
|
||||||
if (!features.lock && type === 'LOCK_CHANGES') return;
|
|
||||||
if (!features.pause && type === 'PAUSE_RECORDING') return;
|
|
||||||
}
|
|
||||||
if (type === 'JUMP_TO_STATE') {
|
|
||||||
const liftedState = store.liftedStore.getState();
|
|
||||||
const index = liftedState.stagedActionIds.indexOf(action.actionId);
|
|
||||||
if (index === -1) return;
|
|
||||||
store.liftedStore.dispatch({ type, index });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
store.liftedStore.dispatch(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMessage(message) {
|
|
||||||
switch (message.type) {
|
|
||||||
case 'DISPATCH':
|
|
||||||
dispatchMonitorAction(message.payload);
|
|
||||||
return;
|
|
||||||
case 'ACTION':
|
|
||||||
dispatchRemotely(message.payload);
|
|
||||||
return;
|
|
||||||
case 'IMPORT':
|
|
||||||
importPayloadFrom(message.state);
|
|
||||||
return;
|
|
||||||
case 'EXPORT':
|
|
||||||
exportState();
|
|
||||||
return;
|
|
||||||
case 'UPDATE':
|
|
||||||
relayState();
|
|
||||||
return;
|
|
||||||
case 'START':
|
|
||||||
monitor.start(true);
|
|
||||||
if (!actionCreators && config.actionCreators) {
|
|
||||||
actionCreators = getActionsArray(config.actionCreators);
|
|
||||||
}
|
|
||||||
relayState(undefined, {
|
|
||||||
name: config.name || document.title,
|
|
||||||
actionCreators: JSON.stringify(actionCreators),
|
|
||||||
features: config.features,
|
|
||||||
serialize: !!config.serialize,
|
|
||||||
type: 'redux',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (reportId) {
|
|
||||||
relay('GET_REPORT', reportId);
|
|
||||||
reportId = null;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case 'STOP':
|
|
||||||
monitor.stop();
|
|
||||||
relayAction.cancel();
|
|
||||||
relayState.cancel();
|
|
||||||
if (!message.failed) relay('STOP');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredActionIds = []; // simple circular buffer of non-excluded actions with fixed maxAge-1 length
|
|
||||||
const getMaxAge = (liftedAction, liftedState) => {
|
|
||||||
let m = (config && config.maxAge) || window.devToolsOptions.maxAge || 50;
|
|
||||||
if (
|
|
||||||
!liftedAction ||
|
|
||||||
noFiltersApplied(localFilter) ||
|
|
||||||
!liftedAction.action
|
|
||||||
) {
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
if (!maxAge || maxAge < m) maxAge = m; // it can be modified in process on options page
|
|
||||||
if (isFiltered(liftedAction.action, localFilter)) {
|
|
||||||
// TODO: check also predicate && !predicate(state, action) with current state
|
|
||||||
maxAge++;
|
|
||||||
} else {
|
|
||||||
filteredActionIds.push(liftedState.nextActionId);
|
|
||||||
if (filteredActionIds.length >= m) {
|
|
||||||
const stagedActionIds = liftedState.stagedActionIds;
|
|
||||||
let i = 1;
|
|
||||||
while (
|
|
||||||
maxAge > m &&
|
|
||||||
filteredActionIds.indexOf(stagedActionIds[i]) === -1
|
|
||||||
) {
|
|
||||||
maxAge--;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
filteredActionIds.shift();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maxAge;
|
|
||||||
};
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
setListener(onMessage, instanceId);
|
|
||||||
notifyErrors(() => {
|
|
||||||
errorOccurred = true;
|
|
||||||
const state = store.liftedStore.getState();
|
|
||||||
if (state.computedStates[state.currentStateIndex].error) {
|
|
||||||
relayState(state);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
relay('INIT_INSTANCE');
|
|
||||||
store.subscribe(handleChange);
|
|
||||||
|
|
||||||
if (typeof reportId === 'undefined') {
|
|
||||||
reportId = getUrlParam('remotedev_report');
|
|
||||||
if (reportId) openWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleChange() {
|
|
||||||
if (!monitor.active) return;
|
|
||||||
if (!errorOccurred && !monitor.isMonitorAction()) {
|
|
||||||
relayAction();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (monitor.isPaused() || monitor.isLocked() || monitor.isTimeTraveling()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const liftedState = store.liftedStore.getState();
|
|
||||||
if (
|
|
||||||
errorOccurred &&
|
|
||||||
!liftedState.computedStates[liftedState.currentStateIndex].error
|
|
||||||
) {
|
|
||||||
errorOccurred = false;
|
|
||||||
}
|
|
||||||
relayState(liftedState);
|
|
||||||
}
|
|
||||||
|
|
||||||
const enhance = () => (next) => {
|
|
||||||
return (reducer_, initialState_, enhancer_) => {
|
|
||||||
if (!isAllowed(window.devToolsOptions)) {
|
|
||||||
return next(reducer_, initialState_, enhancer_);
|
|
||||||
}
|
|
||||||
|
|
||||||
store = stores[instanceId] = configureStore(next, monitor.reducer, {
|
|
||||||
...config,
|
|
||||||
maxAge: getMaxAge,
|
|
||||||
})(reducer_, initialState_, enhancer_);
|
|
||||||
|
|
||||||
if (isInIframe()) setTimeout(init, 3000);
|
|
||||||
else init();
|
|
||||||
|
|
||||||
return store;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!reducer) return enhance();
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
console.warn(
|
|
||||||
'Creating a Redux store directly from DevTools extension is discouraged and will not be supported in future major version. For more details see: https://git.io/fphCe'
|
|
||||||
);
|
|
||||||
/* eslint-enable no-console */
|
|
||||||
return createStore(reducer, preloadedState, enhance);
|
|
||||||
};
|
|
||||||
|
|
||||||
// noinspection JSAnnotator
|
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__ = __REDUX_DEVTOOLS_EXTENSION__;
|
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__.open = openWindow;
|
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__.updateStore = updateStore(stores);
|
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__.notifyErrors = notifyErrors;
|
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__.send = sendMessage;
|
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__.listen = setListener;
|
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__.connect = connect;
|
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__.disconnect = disconnect;
|
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
let varNameDeprecatedWarned;
|
|
||||||
const varNameDeprecatedWarn = () => {
|
|
||||||
if (varNameDeprecatedWarned) return;
|
|
||||||
console.warn(
|
|
||||||
'`window.devToolsExtension` is deprecated in favor of `window.__REDUX_DEVTOOLS_EXTENSION__`, and will be removed in next version of Redux DevTools: https://git.io/fpEJZ'
|
|
||||||
);
|
|
||||||
varNameDeprecatedWarned = true;
|
|
||||||
};
|
|
||||||
/* eslint-enable no-console */
|
|
||||||
window.devToolsExtension = (...args) => {
|
|
||||||
varNameDeprecatedWarn();
|
|
||||||
return __REDUX_DEVTOOLS_EXTENSION__.apply(null, args);
|
|
||||||
};
|
|
||||||
window.devToolsExtension.open = (...args) => {
|
|
||||||
varNameDeprecatedWarn();
|
|
||||||
return openWindow.apply(null, args);
|
|
||||||
};
|
|
||||||
window.devToolsExtension.updateStore = (...args) => {
|
|
||||||
varNameDeprecatedWarn();
|
|
||||||
return updateStore(stores).apply(null, args);
|
|
||||||
};
|
|
||||||
window.devToolsExtension.notifyErrors = (...args) => {
|
|
||||||
varNameDeprecatedWarn();
|
|
||||||
return notifyErrors.apply(null, args);
|
|
||||||
};
|
|
||||||
window.devToolsExtension.send = (...args) => {
|
|
||||||
varNameDeprecatedWarn();
|
|
||||||
return sendMessage.apply(null, args);
|
|
||||||
};
|
|
||||||
window.devToolsExtension.listen = (...args) => {
|
|
||||||
varNameDeprecatedWarn();
|
|
||||||
return setListener.apply(null, args);
|
|
||||||
};
|
|
||||||
window.devToolsExtension.connect = (...args) => {
|
|
||||||
varNameDeprecatedWarn();
|
|
||||||
return connect.apply(null, args);
|
|
||||||
};
|
|
||||||
window.devToolsExtension.disconnect = (...args) => {
|
|
||||||
varNameDeprecatedWarn();
|
|
||||||
return disconnect.apply(null, args);
|
|
||||||
};
|
|
||||||
|
|
||||||
const preEnhancer =
|
|
||||||
(instanceId) => (next) => (reducer, preloadedState, enhancer) => {
|
|
||||||
const store = next(reducer, preloadedState, enhancer);
|
|
||||||
|
|
||||||
if (stores[instanceId]) {
|
|
||||||
stores[instanceId].initialDispatch = store.dispatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...store,
|
|
||||||
dispatch: (...args) =>
|
|
||||||
!window.__REDUX_DEVTOOLS_EXTENSION_LOCKED__ && store.dispatch(...args),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const extensionCompose =
|
|
||||||
(config) =>
|
|
||||||
(...funcs) => {
|
|
||||||
return (...args) => {
|
|
||||||
const instanceId = generateId(config.instanceId);
|
|
||||||
return [preEnhancer(instanceId), ...funcs].reduceRight(
|
|
||||||
(composed, f) => f(composed),
|
|
||||||
__REDUX_DEVTOOLS_EXTENSION__({ ...config, instanceId })(...args)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = (...funcs) => {
|
|
||||||
if (funcs.length === 0) {
|
|
||||||
return __REDUX_DEVTOOLS_EXTENSION__();
|
|
||||||
}
|
|
||||||
if (funcs.length === 1 && typeof funcs[0] === 'object') {
|
|
||||||
return extensionCompose(funcs[0]);
|
|
||||||
}
|
|
||||||
return extensionCompose({})(...funcs);
|
|
||||||
};
|
|
681
extension/src/browser/extension/inject/pageScript.ts
Normal file
681
extension/src/browser/extension/inject/pageScript.ts
Normal file
|
@ -0,0 +1,681 @@
|
||||||
|
import {
|
||||||
|
getActionsArray,
|
||||||
|
evalAction,
|
||||||
|
ActionCreatorObject,
|
||||||
|
} from '@redux-devtools/utils';
|
||||||
|
import throttle from 'lodash/throttle';
|
||||||
|
import {
|
||||||
|
Action,
|
||||||
|
ActionCreator,
|
||||||
|
Dispatch,
|
||||||
|
PreloadedState,
|
||||||
|
Reducer,
|
||||||
|
Store,
|
||||||
|
StoreEnhancer,
|
||||||
|
StoreEnhancerStoreCreator,
|
||||||
|
} from 'redux';
|
||||||
|
import Immutable from 'immutable';
|
||||||
|
import { EnhancedStore, PerformAction } from '@redux-devtools/instrument';
|
||||||
|
import createStore from '../../../app/stores/createStore';
|
||||||
|
import configureStore, { getUrlParam } from '../../../app/stores/enhancerStore';
|
||||||
|
import { isAllowed, Options } from '../options/syncOptions';
|
||||||
|
import Monitor from '../../../app/service/Monitor';
|
||||||
|
import {
|
||||||
|
noFiltersApplied,
|
||||||
|
getLocalFilter,
|
||||||
|
isFiltered,
|
||||||
|
filterState,
|
||||||
|
startingFrom,
|
||||||
|
} from '../../../app/api/filters';
|
||||||
|
import notifyErrors from '../../../app/api/notifyErrors';
|
||||||
|
import importState from '../../../app/api/importState';
|
||||||
|
import openWindow, { Position } from '../../../app/api/openWindow';
|
||||||
|
import generateId from '../../../app/api/generateInstanceId';
|
||||||
|
import {
|
||||||
|
updateStore,
|
||||||
|
toContentScript,
|
||||||
|
sendMessage,
|
||||||
|
setListener,
|
||||||
|
connect,
|
||||||
|
disconnect,
|
||||||
|
isInIframe,
|
||||||
|
getSerializeParameter,
|
||||||
|
Serialize,
|
||||||
|
StructuralPerformAction,
|
||||||
|
ConnectResponse,
|
||||||
|
} from '../../../app/api';
|
||||||
|
import { LiftedAction, LiftedState } from '@redux-devtools/instrument';
|
||||||
|
import {
|
||||||
|
CustomAction,
|
||||||
|
DispatchAction,
|
||||||
|
LibConfig,
|
||||||
|
} from '@redux-devtools/app/lib/actions';
|
||||||
|
import { ContentScriptToPageScriptMessage } from './contentScript';
|
||||||
|
import { Features } from '@redux-devtools/app/lib/reducers/instances';
|
||||||
|
|
||||||
|
type EnhancedStoreWithInitialDispatch<
|
||||||
|
S,
|
||||||
|
A extends Action<unknown>,
|
||||||
|
MonitorState
|
||||||
|
> = EnhancedStore<S, A, MonitorState> & { initialDispatch: Dispatch<A> };
|
||||||
|
|
||||||
|
const source = '@devtools-page';
|
||||||
|
let stores: {
|
||||||
|
[K in string | number]: EnhancedStoreWithInitialDispatch<
|
||||||
|
unknown,
|
||||||
|
Action<unknown>,
|
||||||
|
unknown
|
||||||
|
>;
|
||||||
|
} = {};
|
||||||
|
let reportId: string | null | undefined;
|
||||||
|
|
||||||
|
function deprecateParam(oldParam: string, newParam: string) {
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
console.warn(
|
||||||
|
`${oldParam} parameter is deprecated, use ${newParam} instead: https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md`
|
||||||
|
);
|
||||||
|
/* eslint-enable no-console */
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializeWithImmutable extends Serialize {
|
||||||
|
readonly immutable?: typeof Immutable;
|
||||||
|
readonly refs?: (new (data: any) => unknown)[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigWithExpandedMaxAge {
|
||||||
|
instanceId?: number;
|
||||||
|
readonly actionsBlacklist?: string | readonly string[];
|
||||||
|
readonly actionsWhitelist?: string | readonly string[];
|
||||||
|
serialize?: boolean | SerializeWithImmutable;
|
||||||
|
readonly serializeState?:
|
||||||
|
| boolean
|
||||||
|
| ((key: string, value: unknown) => unknown)
|
||||||
|
| Serialize;
|
||||||
|
readonly serializeAction?:
|
||||||
|
| boolean
|
||||||
|
| ((key: string, value: unknown) => unknown)
|
||||||
|
| Serialize;
|
||||||
|
readonly statesFilter?: <S>(state: S, index?: number) => S;
|
||||||
|
readonly actionsFilter?: <A extends Action<unknown>>(
|
||||||
|
action: A,
|
||||||
|
id?: number
|
||||||
|
) => A;
|
||||||
|
readonly stateSanitizer?: <S>(state: S, index?: number) => S;
|
||||||
|
readonly actionSanitizer?: <A extends Action<unknown>>(
|
||||||
|
action: A,
|
||||||
|
id?: number
|
||||||
|
) => A;
|
||||||
|
readonly predicate?: <S, A extends Action<unknown>>(
|
||||||
|
state: S,
|
||||||
|
action: A
|
||||||
|
) => boolean;
|
||||||
|
readonly latency?: number;
|
||||||
|
readonly getMonitor?: <S, A extends Action<unknown>>(
|
||||||
|
monitor: Monitor<S, A>
|
||||||
|
) => void;
|
||||||
|
readonly maxAge?:
|
||||||
|
| number
|
||||||
|
| (<S, A extends Action<unknown>>(
|
||||||
|
currentLiftedAction: LiftedAction<S, A, unknown>,
|
||||||
|
previousLiftedState: LiftedState<S, A, unknown> | undefined
|
||||||
|
) => number);
|
||||||
|
readonly trace?: boolean | (() => string | undefined);
|
||||||
|
readonly traceLimit?: number;
|
||||||
|
readonly shouldCatchErrors?: boolean;
|
||||||
|
readonly shouldHotReload?: boolean;
|
||||||
|
readonly shouldRecordChanges?: boolean;
|
||||||
|
readonly shouldStartLocked?: boolean;
|
||||||
|
readonly pauseActionType?: unknown;
|
||||||
|
readonly deserializeState?: <S>(state: S) => S;
|
||||||
|
readonly deserializeAction?: <A extends Action<unknown>>(action: A) => A;
|
||||||
|
name?: string;
|
||||||
|
readonly autoPause?: boolean;
|
||||||
|
readonly features?: Features;
|
||||||
|
readonly type?: string;
|
||||||
|
readonly getActionType?: <A extends Action<unknown>>(action: A) => A;
|
||||||
|
readonly actionCreators?: {
|
||||||
|
readonly [key: string]: ActionCreator<Action<unknown>>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Config extends ConfigWithExpandedMaxAge {
|
||||||
|
readonly maxAge?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReduxDevtoolsExtension {
|
||||||
|
<S, A extends Action<unknown>>(
|
||||||
|
reducer: Reducer<S, A>,
|
||||||
|
preloadedState?: PreloadedState<S>,
|
||||||
|
config?: Config
|
||||||
|
): Store<S, A>;
|
||||||
|
(config?: Config): StoreEnhancer;
|
||||||
|
open: (position?: Position) => void;
|
||||||
|
updateStore: (
|
||||||
|
newStore: EnhancedStore<unknown, Action<unknown>, unknown>,
|
||||||
|
instanceId: number
|
||||||
|
) => void;
|
||||||
|
notifyErrors: (onError?: () => boolean) => void;
|
||||||
|
send: <S, A extends Action<unknown>>(
|
||||||
|
action: StructuralPerformAction<A> | StructuralPerformAction<A>[],
|
||||||
|
state: LiftedState<S, A, unknown>,
|
||||||
|
config: Config,
|
||||||
|
instanceId?: number,
|
||||||
|
name?: string
|
||||||
|
) => void;
|
||||||
|
listen: (
|
||||||
|
onMessage: (message: ContentScriptToPageScriptMessage) => void,
|
||||||
|
instanceId: number
|
||||||
|
) => void;
|
||||||
|
connect: (preConfig: Config) => ConnectResponse;
|
||||||
|
disconnect: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
devToolsOptions: Options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<unknown>>(
|
||||||
|
reducer?: Reducer<S, A>,
|
||||||
|
preloadedState?: PreloadedState<S>,
|
||||||
|
config?: Config
|
||||||
|
): Store<S, A>;
|
||||||
|
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
|
||||||
|
): Store<S, A> | StoreEnhancer {
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
if (typeof reducer === 'object') {
|
||||||
|
config = reducer;
|
||||||
|
reducer = undefined;
|
||||||
|
} else if (typeof config !== 'object') config = {};
|
||||||
|
/* eslint-enable no-param-reassign */
|
||||||
|
if (!window.devToolsOptions) window.devToolsOptions = {} as any;
|
||||||
|
|
||||||
|
let store: EnhancedStoreWithInitialDispatch<S, A, unknown>;
|
||||||
|
let errorOccurred = false;
|
||||||
|
let maxAge: number | undefined;
|
||||||
|
let actionCreators: readonly ActionCreatorObject[];
|
||||||
|
let sendingActionId = 1;
|
||||||
|
const instanceId = generateId(config.instanceId);
|
||||||
|
const localFilter = getLocalFilter(config);
|
||||||
|
const serializeState = getSerializeParameter(config, 'serializeState');
|
||||||
|
const serializeAction = getSerializeParameter(config, 'serializeAction');
|
||||||
|
let {
|
||||||
|
statesFilter,
|
||||||
|
actionsFilter,
|
||||||
|
stateSanitizer,
|
||||||
|
actionSanitizer,
|
||||||
|
predicate,
|
||||||
|
latency = 500,
|
||||||
|
} = config;
|
||||||
|
|
||||||
|
// Deprecate statesFilter and actionsFilter
|
||||||
|
if (statesFilter) {
|
||||||
|
deprecateParam('statesFilter', 'stateSanitizer');
|
||||||
|
stateSanitizer = statesFilter; // eslint-disable-line no-param-reassign
|
||||||
|
}
|
||||||
|
if (actionsFilter) {
|
||||||
|
deprecateParam('actionsFilter', 'actionSanitizer');
|
||||||
|
actionSanitizer = actionsFilter; // eslint-disable-line no-param-reassign
|
||||||
|
}
|
||||||
|
|
||||||
|
const relayState = throttle(
|
||||||
|
(
|
||||||
|
liftedState?: LiftedState<S, A, unknown> | undefined,
|
||||||
|
libConfig?: LibConfig
|
||||||
|
) => {
|
||||||
|
relayAction.cancel();
|
||||||
|
const state = liftedState || store.liftedStore.getState();
|
||||||
|
sendingActionId = state.nextActionId;
|
||||||
|
toContentScript(
|
||||||
|
{
|
||||||
|
type: 'STATE',
|
||||||
|
payload: filterState(
|
||||||
|
state,
|
||||||
|
localFilter,
|
||||||
|
stateSanitizer,
|
||||||
|
actionSanitizer,
|
||||||
|
predicate
|
||||||
|
),
|
||||||
|
source,
|
||||||
|
instanceId,
|
||||||
|
libConfig,
|
||||||
|
},
|
||||||
|
serializeState,
|
||||||
|
serializeAction
|
||||||
|
);
|
||||||
|
},
|
||||||
|
latency
|
||||||
|
);
|
||||||
|
|
||||||
|
const monitor = new Monitor(relayState);
|
||||||
|
if (config.getMonitor) {
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
console.warn(
|
||||||
|
"Redux DevTools extension's `getMonitor` parameter is deprecated and will be not " +
|
||||||
|
'supported in the next version, please remove it and just use ' +
|
||||||
|
'`__REDUX_DEVTOOLS_EXTENSION_COMPOSE__` instead: ' +
|
||||||
|
'https://github.com/zalmoxisus/redux-devtools-extension#12-advanced-store-setup'
|
||||||
|
);
|
||||||
|
/* eslint-enable no-console */
|
||||||
|
config.getMonitor(monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportState() {
|
||||||
|
const liftedState = store.liftedStore.getState();
|
||||||
|
const actionsById = liftedState.actionsById;
|
||||||
|
const payload: A[] = [];
|
||||||
|
liftedState.stagedActionIds.slice(1).forEach((id) => {
|
||||||
|
// if (isFiltered(actionsById[id].action, localFilter)) return;
|
||||||
|
payload.push(actionsById[id].action);
|
||||||
|
});
|
||||||
|
toContentScript(
|
||||||
|
{
|
||||||
|
type: 'EXPORT',
|
||||||
|
payload,
|
||||||
|
committedState: liftedState.committedState,
|
||||||
|
source,
|
||||||
|
instanceId,
|
||||||
|
},
|
||||||
|
serializeState,
|
||||||
|
serializeAction
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const relayAction = throttle(() => {
|
||||||
|
const liftedState = store.liftedStore.getState();
|
||||||
|
const nextActionId = liftedState.nextActionId;
|
||||||
|
const currentActionId = nextActionId - 1;
|
||||||
|
const liftedAction = liftedState.actionsById[currentActionId];
|
||||||
|
|
||||||
|
// Send a single action
|
||||||
|
if (sendingActionId === currentActionId) {
|
||||||
|
sendingActionId = nextActionId;
|
||||||
|
const action = liftedAction.action;
|
||||||
|
const computedStates = liftedState.computedStates;
|
||||||
|
if (
|
||||||
|
isFiltered(action, localFilter) ||
|
||||||
|
(predicate &&
|
||||||
|
!predicate(computedStates[computedStates.length - 1].state, action))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const state =
|
||||||
|
liftedState.computedStates[liftedState.computedStates.length - 1].state;
|
||||||
|
toContentScript(
|
||||||
|
{
|
||||||
|
type: 'ACTION',
|
||||||
|
payload: !stateSanitizer
|
||||||
|
? state
|
||||||
|
: stateSanitizer(state, nextActionId - 1),
|
||||||
|
source,
|
||||||
|
instanceId,
|
||||||
|
action: !actionSanitizer
|
||||||
|
? liftedState.actionsById[nextActionId - 1]
|
||||||
|
: actionSanitizer(
|
||||||
|
liftedState.actionsById[nextActionId - 1].action,
|
||||||
|
nextActionId - 1
|
||||||
|
),
|
||||||
|
maxAge: getMaxAge(),
|
||||||
|
nextActionId,
|
||||||
|
},
|
||||||
|
serializeState,
|
||||||
|
serializeAction
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send multiple actions
|
||||||
|
const payload = startingFrom(
|
||||||
|
sendingActionId,
|
||||||
|
liftedState,
|
||||||
|
localFilter,
|
||||||
|
stateSanitizer,
|
||||||
|
actionSanitizer,
|
||||||
|
predicate
|
||||||
|
);
|
||||||
|
sendingActionId = nextActionId;
|
||||||
|
if (typeof payload === 'undefined') return;
|
||||||
|
if ('skippedActionIds' in payload) {
|
||||||
|
toContentScript(
|
||||||
|
{
|
||||||
|
type: 'STATE',
|
||||||
|
payload: filterState(
|
||||||
|
payload,
|
||||||
|
localFilter,
|
||||||
|
stateSanitizer,
|
||||||
|
actionSanitizer,
|
||||||
|
predicate
|
||||||
|
),
|
||||||
|
source,
|
||||||
|
instanceId,
|
||||||
|
},
|
||||||
|
serializeState,
|
||||||
|
serializeAction
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toContentScript(
|
||||||
|
{
|
||||||
|
type: 'PARTIAL_STATE',
|
||||||
|
payload,
|
||||||
|
source,
|
||||||
|
instanceId,
|
||||||
|
maxAge: getMaxAge(),
|
||||||
|
},
|
||||||
|
serializeState,
|
||||||
|
serializeAction
|
||||||
|
);
|
||||||
|
}, latency);
|
||||||
|
|
||||||
|
function dispatchRemotely(action: string | CustomAction) {
|
||||||
|
if (config!.features && !config!.features.dispatch) return;
|
||||||
|
try {
|
||||||
|
const result = evalAction(action, actionCreators);
|
||||||
|
(store.initialDispatch || store.dispatch)(result);
|
||||||
|
} catch (e) {
|
||||||
|
toContentScript(
|
||||||
|
{
|
||||||
|
type: 'ERROR',
|
||||||
|
payload: e.message,
|
||||||
|
source,
|
||||||
|
instanceId,
|
||||||
|
},
|
||||||
|
serializeState,
|
||||||
|
serializeAction
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function importPayloadFrom(state: string | undefined) {
|
||||||
|
if (config!.features && !config!.features.import) return;
|
||||||
|
try {
|
||||||
|
const nextLiftedState = importState<S, A>(state, config!);
|
||||||
|
if (!nextLiftedState) return;
|
||||||
|
store.liftedStore.dispatch({ type: 'IMPORT_STATE', ...nextLiftedState });
|
||||||
|
} catch (e) {
|
||||||
|
toContentScript(
|
||||||
|
{
|
||||||
|
type: 'ERROR',
|
||||||
|
payload: e.message,
|
||||||
|
source,
|
||||||
|
instanceId,
|
||||||
|
},
|
||||||
|
serializeState,
|
||||||
|
serializeAction
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatchMonitorAction(action: DispatchAction) {
|
||||||
|
const features = config!.features;
|
||||||
|
if (features) {
|
||||||
|
if (
|
||||||
|
!features.jump &&
|
||||||
|
(action.type === 'JUMP_TO_STATE' || action.type === 'JUMP_TO_ACTION')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!features.skip && action.type === 'TOGGLE_ACTION') return;
|
||||||
|
if (!features.reorder && action.type === 'REORDER_ACTION') return;
|
||||||
|
if (!features.import && action.type === 'IMPORT_STATE') return;
|
||||||
|
if (!features.lock && action.type === 'LOCK_CHANGES') return;
|
||||||
|
if (!features.pause && action.type === 'PAUSE_RECORDING') return;
|
||||||
|
}
|
||||||
|
if (action.type === 'JUMP_TO_STATE') {
|
||||||
|
const liftedState = store.liftedStore.getState();
|
||||||
|
const index = liftedState.stagedActionIds.indexOf(action.actionId);
|
||||||
|
if (index === -1) return;
|
||||||
|
store.liftedStore.dispatch({ type: action.type, index });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
store.liftedStore.dispatch(action as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMessage(message: ContentScriptToPageScriptMessage) {
|
||||||
|
switch (message.type) {
|
||||||
|
case 'DISPATCH':
|
||||||
|
dispatchMonitorAction(message.payload);
|
||||||
|
return;
|
||||||
|
case 'ACTION':
|
||||||
|
dispatchRemotely(message.payload);
|
||||||
|
return;
|
||||||
|
case 'IMPORT':
|
||||||
|
importPayloadFrom(message.state);
|
||||||
|
return;
|
||||||
|
case 'EXPORT':
|
||||||
|
exportState();
|
||||||
|
return;
|
||||||
|
case 'UPDATE':
|
||||||
|
relayState();
|
||||||
|
return;
|
||||||
|
case 'START':
|
||||||
|
monitor.start(true);
|
||||||
|
if (!actionCreators && config!.actionCreators) {
|
||||||
|
actionCreators = getActionsArray(config!.actionCreators);
|
||||||
|
}
|
||||||
|
relayState(undefined, {
|
||||||
|
name: config!.name || document.title,
|
||||||
|
actionCreators: JSON.stringify(actionCreators),
|
||||||
|
features: config!.features,
|
||||||
|
serialize: !!config!.serialize,
|
||||||
|
type: 'redux',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (reportId) {
|
||||||
|
toContentScript(
|
||||||
|
{
|
||||||
|
type: 'GET_REPORT',
|
||||||
|
payload: reportId,
|
||||||
|
source,
|
||||||
|
instanceId,
|
||||||
|
},
|
||||||
|
serializeState,
|
||||||
|
serializeAction
|
||||||
|
);
|
||||||
|
reportId = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case 'STOP':
|
||||||
|
monitor.stop();
|
||||||
|
relayAction.cancel();
|
||||||
|
relayState.cancel();
|
||||||
|
if (!message.failed) {
|
||||||
|
toContentScript(
|
||||||
|
{
|
||||||
|
type: 'STOP',
|
||||||
|
payload: undefined,
|
||||||
|
source,
|
||||||
|
instanceId,
|
||||||
|
},
|
||||||
|
serializeState,
|
||||||
|
serializeAction
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredActionIds: number[] = []; // simple circular buffer of non-excluded actions with fixed maxAge-1 length
|
||||||
|
const getMaxAge = (
|
||||||
|
liftedAction?: LiftedAction<S, A, unknown>,
|
||||||
|
liftedState?: LiftedState<S, A, unknown> | undefined
|
||||||
|
) => {
|
||||||
|
let m = (config && config.maxAge) || window.devToolsOptions.maxAge || 50;
|
||||||
|
if (
|
||||||
|
!liftedAction ||
|
||||||
|
noFiltersApplied(localFilter) ||
|
||||||
|
!(liftedAction as PerformAction<A>).action
|
||||||
|
) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
if (!maxAge || maxAge < m) maxAge = m; // it can be modified in process on options page
|
||||||
|
if (isFiltered((liftedAction as PerformAction<A>).action, localFilter)) {
|
||||||
|
// TODO: check also predicate && !predicate(state, action) with current state
|
||||||
|
maxAge++;
|
||||||
|
} else {
|
||||||
|
filteredActionIds.push(liftedState!.nextActionId);
|
||||||
|
if (filteredActionIds.length >= m) {
|
||||||
|
const stagedActionIds = liftedState!.stagedActionIds;
|
||||||
|
let i = 1;
|
||||||
|
while (
|
||||||
|
maxAge > m &&
|
||||||
|
filteredActionIds.indexOf(stagedActionIds[i]) === -1
|
||||||
|
) {
|
||||||
|
maxAge--;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
filteredActionIds.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxAge;
|
||||||
|
};
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
setListener(onMessage, instanceId);
|
||||||
|
notifyErrors(() => {
|
||||||
|
errorOccurred = true;
|
||||||
|
const state = store.liftedStore.getState();
|
||||||
|
if (state.computedStates[state.currentStateIndex].error) {
|
||||||
|
relayState(state);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
toContentScript(
|
||||||
|
{
|
||||||
|
type: 'INIT_INSTANCE',
|
||||||
|
payload: undefined,
|
||||||
|
source,
|
||||||
|
instanceId,
|
||||||
|
},
|
||||||
|
serializeState,
|
||||||
|
serializeAction
|
||||||
|
);
|
||||||
|
store.subscribe(handleChange);
|
||||||
|
|
||||||
|
if (typeof reportId === 'undefined') {
|
||||||
|
reportId = getUrlParam('remotedev_report');
|
||||||
|
if (reportId) openWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChange() {
|
||||||
|
if (!monitor.active) return;
|
||||||
|
if (!errorOccurred && !monitor.isMonitorAction()) {
|
||||||
|
relayAction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (monitor.isPaused() || monitor.isLocked() || monitor.isTimeTraveling()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const liftedState = store.liftedStore.getState();
|
||||||
|
if (
|
||||||
|
errorOccurred &&
|
||||||
|
!liftedState.computedStates[liftedState.currentStateIndex].error
|
||||||
|
) {
|
||||||
|
errorOccurred = false;
|
||||||
|
}
|
||||||
|
relayState(liftedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
const enhance =
|
||||||
|
(): StoreEnhancer =>
|
||||||
|
<NextExt, NextStateExt>(
|
||||||
|
next: StoreEnhancerStoreCreator<NextExt, NextStateExt>
|
||||||
|
): any => {
|
||||||
|
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 as any,
|
||||||
|
})(reducer_, initialState_) as any;
|
||||||
|
|
||||||
|
if (isInIframe()) setTimeout(init, 3000);
|
||||||
|
else init();
|
||||||
|
|
||||||
|
return store;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!reducer) return enhance();
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
console.warn(
|
||||||
|
'Creating a Redux store directly from DevTools extension is discouraged and will not be supported in future major version. For more details see: https://git.io/fphCe'
|
||||||
|
);
|
||||||
|
/* eslint-enable no-console */
|
||||||
|
return createStore(reducer, preloadedState, enhance);
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__REDUX_DEVTOOLS_EXTENSION__: ReduxDevtoolsExtension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection JSAnnotator
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__ = __REDUX_DEVTOOLS_EXTENSION__ as any;
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__.open = openWindow;
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__.updateStore = updateStore(stores);
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__.notifyErrors = notifyErrors;
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__.send = sendMessage;
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__.listen = setListener;
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__.connect = connect;
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__.disconnect = disconnect;
|
||||||
|
|
||||||
|
const preEnhancer =
|
||||||
|
(instanceId: number): StoreEnhancer =>
|
||||||
|
(next) =>
|
||||||
|
(reducer, preloadedState) => {
|
||||||
|
const store = next(reducer, preloadedState);
|
||||||
|
|
||||||
|
if (stores[instanceId]) {
|
||||||
|
(stores[instanceId].initialDispatch as any) = store.dispatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...store,
|
||||||
|
dispatch: (...args: any[]) =>
|
||||||
|
!window.__REDUX_DEVTOOLS_EXTENSION_LOCKED__ &&
|
||||||
|
(store.dispatch as any)(...args),
|
||||||
|
} as any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const extensionCompose =
|
||||||
|
(config: Config) =>
|
||||||
|
(...funcs: StoreEnhancer[]) => {
|
||||||
|
return (...args: any[]) => {
|
||||||
|
const instanceId = generateId(config.instanceId);
|
||||||
|
return [preEnhancer(instanceId), ...funcs].reduceRight(
|
||||||
|
(composed, f) => f(composed),
|
||||||
|
(__REDUX_DEVTOOLS_EXTENSION__({ ...config, instanceId }) as any)(
|
||||||
|
...args
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = (...funcs: any[]) => {
|
||||||
|
if (funcs.length === 0) {
|
||||||
|
return __REDUX_DEVTOOLS_EXTENSION__();
|
||||||
|
}
|
||||||
|
if (funcs.length === 1 && typeof funcs[0] === 'object') {
|
||||||
|
return extensionCompose(funcs[0]);
|
||||||
|
}
|
||||||
|
return extensionCompose({})(...funcs);
|
||||||
|
};
|
|
@ -5,11 +5,13 @@ if (process.env.NODE_ENV === 'production') {
|
||||||
const { default: script } = require('raw-loader!tmp/page.bundle.js');
|
const { default: script } = require('raw-loader!tmp/page.bundle.js');
|
||||||
s.appendChild(document.createTextNode(script));
|
s.appendChild(document.createTextNode(script));
|
||||||
(document.head || document.documentElement).appendChild(s);
|
(document.head || document.documentElement).appendChild(s);
|
||||||
s.parentNode.removeChild(s);
|
s.parentNode!.removeChild(s);
|
||||||
} else {
|
} else {
|
||||||
s.src = chrome.extension.getURL('page.bundle.js');
|
s.src = chrome.extension.getURL('page.bundle.js');
|
||||||
s.onload = function () {
|
s.onload = function () {
|
||||||
this.parentNode.removeChild(this);
|
(this as HTMLScriptElement).parentNode!.removeChild(
|
||||||
|
this as HTMLScriptElement
|
||||||
|
);
|
||||||
};
|
};
|
||||||
(document.head || document.documentElement).appendChild(s);
|
(document.head || document.documentElement).appendChild(s);
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { OptionsProps } from './Options';
|
||||||
|
|
||||||
export default ({ options, saveOption }) => {
|
export default ({ options, saveOption }: OptionsProps) => {
|
||||||
const AllowToRunState = {
|
const AllowToRunState = {
|
||||||
EVERYWHERE: true,
|
EVERYWHERE: true,
|
||||||
ON_SPECIFIC_URLS: false,
|
ON_SPECIFIC_URLS: false,
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { OptionsProps } from './Options';
|
||||||
|
|
||||||
export default ({ options, saveOption }) => {
|
export default ({ options, saveOption }: OptionsProps) => {
|
||||||
return (
|
return (
|
||||||
<fieldset className="option-group">
|
<fieldset className="option-group">
|
||||||
<legend className="option-group__title">Context Menu</legend>
|
<legend className="option-group__title">Context Menu</legend>
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { OptionsProps } from './Options';
|
||||||
|
|
||||||
export default ({ options, saveOption }) => {
|
export default ({ options, saveOption }: OptionsProps) => {
|
||||||
const EditorState = {
|
const EditorState = {
|
||||||
BROWSER: 0,
|
BROWSER: 0,
|
||||||
EXTERNAL: 1,
|
EXTERNAL: 1,
|
||||||
|
@ -45,7 +46,7 @@ export default ({ options, saveOption }) => {
|
||||||
className="option__element"
|
className="option__element"
|
||||||
id="editor"
|
id="editor"
|
||||||
type="text"
|
type="text"
|
||||||
size="33"
|
size={33}
|
||||||
maxLength={30}
|
maxLength={30}
|
||||||
placeholder="vscode, atom, webstorm, sublime..."
|
placeholder="vscode, atom, webstorm, sublime..."
|
||||||
value={options.editor}
|
value={options.editor}
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FilterState } from '../../../app/api/filters';
|
import { FilterState } from '../../../app/api/filters';
|
||||||
|
import { OptionsProps } from './Options';
|
||||||
|
|
||||||
export default ({ options, saveOption }) => {
|
export default ({ options, saveOption }: OptionsProps) => {
|
||||||
return (
|
return (
|
||||||
<fieldset className="option-group">
|
<fieldset className="option-group">
|
||||||
<legend className="option-group__title">
|
<legend className="option-group__title">
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { OptionsProps } from './Options';
|
||||||
|
|
||||||
export default ({ options, saveOption }) => {
|
export default ({ options, saveOption }: OptionsProps) => {
|
||||||
const browserName = navigator.userAgent.includes('Firefox')
|
const browserName = navigator.userAgent.includes('Firefox')
|
||||||
? 'Firefox'
|
? 'Firefox'
|
||||||
: 'Chrome';
|
: 'Chrome';
|
|
@ -4,8 +4,17 @@ import FilterGroup from './FilterGroup';
|
||||||
import AllowToRunGroup from './AllowToRunGroup';
|
import AllowToRunGroup from './AllowToRunGroup';
|
||||||
import MiscellaneousGroup from './MiscellaneousGroup';
|
import MiscellaneousGroup from './MiscellaneousGroup';
|
||||||
import ContextMenuGroup from './ContextMenuGroup';
|
import ContextMenuGroup from './ContextMenuGroup';
|
||||||
|
import { Options } from './syncOptions';
|
||||||
|
|
||||||
export default (props) => (
|
export interface OptionsProps {
|
||||||
|
readonly options: Options;
|
||||||
|
readonly saveOption: <K extends keyof Options>(
|
||||||
|
name: K,
|
||||||
|
value: Options[K]
|
||||||
|
) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (props: OptionsProps) => (
|
||||||
<div>
|
<div>
|
||||||
<EditorGroup {...props} />
|
<EditorGroup {...props} />
|
||||||
<FilterGroup {...props} />
|
<FilterGroup {...props} />
|
|
@ -1,19 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import Options from './Options';
|
import OptionsComponent from './Options';
|
||||||
|
import { Options } from './syncOptions';
|
||||||
|
|
||||||
import '../../views/options.pug';
|
import '../../views/options.pug';
|
||||||
|
|
||||||
chrome.runtime.getBackgroundPage((background) => {
|
chrome.runtime.getBackgroundPage((background) => {
|
||||||
const syncOptions = background.syncOptions;
|
const syncOptions = background!.syncOptions;
|
||||||
|
|
||||||
const saveOption = (name, value) => {
|
const saveOption = <K extends keyof Options>(name: K, value: Options[K]) => {
|
||||||
syncOptions.save(name, value);
|
syncOptions.save(name, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderOptions = (options) => {
|
const renderOptions = (options: Options) => {
|
||||||
render(
|
render(
|
||||||
<Options options={options} saveOption={saveOption} />,
|
<OptionsComponent options={options} saveOption={saveOption} />,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -1,108 +0,0 @@
|
||||||
import { FilterState } from '../../../app/api/filters';
|
|
||||||
|
|
||||||
let options;
|
|
||||||
let subscribers = [];
|
|
||||||
|
|
||||||
const save = (toAllTabs) => (key, value) => {
|
|
||||||
let obj = {};
|
|
||||||
obj[key] = value;
|
|
||||||
chrome.storage.sync.set(obj);
|
|
||||||
options[key] = value;
|
|
||||||
toAllTabs({ options: options });
|
|
||||||
subscribers.forEach((s) => s(options));
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrateOldOptions = (oldOptions) => {
|
|
||||||
let newOptions = Object.assign({}, oldOptions);
|
|
||||||
|
|
||||||
// Migrate the old `filter` option from 2.2.1
|
|
||||||
if (typeof oldOptions.filter === 'boolean') {
|
|
||||||
if (oldOptions.filter && oldOptions.whitelist.length > 0) {
|
|
||||||
newOptions.filter = FilterState.WHITELIST_SPECIFIC;
|
|
||||||
} else if (oldOptions.filter) {
|
|
||||||
newOptions.filter = FilterState.BLACKLIST_SPECIFIC;
|
|
||||||
} else {
|
|
||||||
newOptions.filter = FilterState.DO_NOT_FILTER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newOptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
const get = (callback) => {
|
|
||||||
if (options) callback(options);
|
|
||||||
else {
|
|
||||||
chrome.storage.sync.get(
|
|
||||||
{
|
|
||||||
useEditor: 0,
|
|
||||||
editor: '',
|
|
||||||
projectPath: '',
|
|
||||||
maxAge: 50,
|
|
||||||
filter: FilterState.DO_NOT_FILTER,
|
|
||||||
whitelist: '',
|
|
||||||
blacklist: '',
|
|
||||||
shouldCatchErrors: false,
|
|
||||||
inject: true,
|
|
||||||
urls: '^https?://localhost|0\\.0\\.0\\.0:\\d+\n^https?://.+\\.github\\.io',
|
|
||||||
showContextMenus: true,
|
|
||||||
},
|
|
||||||
function (items) {
|
|
||||||
options = migrateOldOptions(items);
|
|
||||||
callback(options);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const subscribe = (callback) => {
|
|
||||||
subscribers = subscribers.concat(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toReg = (str) =>
|
|
||||||
str !== '' ? str.split('\n').filter(Boolean).join('|') : null;
|
|
||||||
|
|
||||||
export const injectOptions = (newOptions) => {
|
|
||||||
if (!newOptions) return;
|
|
||||||
if (newOptions.filter !== FilterState.DO_NOT_FILTER) {
|
|
||||||
newOptions.whitelist = toReg(newOptions.whitelist);
|
|
||||||
newOptions.blacklist = toReg(newOptions.blacklist);
|
|
||||||
}
|
|
||||||
|
|
||||||
options = newOptions;
|
|
||||||
let s = document.createElement('script');
|
|
||||||
s.type = 'text/javascript';
|
|
||||||
s.appendChild(
|
|
||||||
document.createTextNode(
|
|
||||||
'window.devToolsOptions = Object.assign(window.devToolsOptions||{},' +
|
|
||||||
JSON.stringify(options) +
|
|
||||||
');'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
(document.head || document.documentElement).appendChild(s);
|
|
||||||
s.parentNode.removeChild(s);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getOptionsFromBg = () => {
|
|
||||||
/* chrome.runtime.sendMessage({ type: 'GET_OPTIONS' }, response => {
|
|
||||||
if (response && response.options) injectOptions(response.options);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
get((newOptions) => {
|
|
||||||
injectOptions(newOptions);
|
|
||||||
}); // Legacy
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isAllowed = (localOptions = options) =>
|
|
||||||
!localOptions ||
|
|
||||||
localOptions.inject ||
|
|
||||||
!localOptions.urls ||
|
|
||||||
location.href.match(toReg(localOptions.urls));
|
|
||||||
|
|
||||||
export default function syncOptions(toAllTabs) {
|
|
||||||
if (toAllTabs && !options) get(() => {}); // Initialize
|
|
||||||
return {
|
|
||||||
save: save(toAllTabs),
|
|
||||||
get: get,
|
|
||||||
subscribe: subscribe,
|
|
||||||
};
|
|
||||||
}
|
|
152
extension/src/browser/extension/options/syncOptions.ts
Normal file
152
extension/src/browser/extension/options/syncOptions.ts
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
import { FilterState, FilterStateValue } from '../../../app/api/filters';
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
readonly useEditor: number;
|
||||||
|
readonly editor: string;
|
||||||
|
readonly projectPath: string;
|
||||||
|
readonly maxAge: number;
|
||||||
|
readonly filter: FilterStateValue;
|
||||||
|
readonly whitelist: string;
|
||||||
|
readonly blacklist: string;
|
||||||
|
readonly shouldCatchErrors: boolean;
|
||||||
|
readonly inject: boolean;
|
||||||
|
readonly urls: string;
|
||||||
|
readonly showContextMenus: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OldOrNewOptions {
|
||||||
|
readonly useEditor: number;
|
||||||
|
readonly editor: string;
|
||||||
|
readonly projectPath: string;
|
||||||
|
readonly maxAge: number;
|
||||||
|
readonly filter: FilterStateValue | boolean;
|
||||||
|
readonly whitelist: string;
|
||||||
|
readonly blacklist: string;
|
||||||
|
readonly shouldCatchErrors: boolean;
|
||||||
|
readonly inject: boolean;
|
||||||
|
readonly urls: string;
|
||||||
|
readonly showContextMenus: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let options: Options | undefined;
|
||||||
|
let subscribers: ((options: Options) => void)[] = [];
|
||||||
|
|
||||||
|
export interface OptionsMessage {
|
||||||
|
readonly options: Options;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToAllTabs = (msg: OptionsMessage) => void;
|
||||||
|
|
||||||
|
const save =
|
||||||
|
(toAllTabs: ToAllTabs | undefined) =>
|
||||||
|
<K extends keyof Options>(key: K, value: Options[K]) => {
|
||||||
|
let obj: { [K1 in keyof Options]?: Options[K1] } = {};
|
||||||
|
obj[key] = value;
|
||||||
|
chrome.storage.sync.set(obj);
|
||||||
|
options![key] = value;
|
||||||
|
toAllTabs!({ options: options! });
|
||||||
|
subscribers.forEach((s) => s(options!));
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrateOldOptions = (oldOptions: OldOrNewOptions): Options => ({
|
||||||
|
...oldOptions,
|
||||||
|
filter:
|
||||||
|
// Migrate the old `filter` option from 2.2.1
|
||||||
|
typeof oldOptions.filter === 'boolean'
|
||||||
|
? oldOptions.filter && oldOptions.whitelist.length > 0
|
||||||
|
? FilterState.WHITELIST_SPECIFIC
|
||||||
|
: oldOptions.filter
|
||||||
|
? FilterState.BLACKLIST_SPECIFIC
|
||||||
|
: FilterState.DO_NOT_FILTER
|
||||||
|
: oldOptions.filter,
|
||||||
|
});
|
||||||
|
|
||||||
|
const get = (callback: (options: Options) => void) => {
|
||||||
|
if (options) callback(options);
|
||||||
|
else {
|
||||||
|
chrome.storage.sync.get(
|
||||||
|
{
|
||||||
|
useEditor: 0,
|
||||||
|
editor: '',
|
||||||
|
projectPath: '',
|
||||||
|
maxAge: 50,
|
||||||
|
filter: FilterState.DO_NOT_FILTER,
|
||||||
|
whitelist: '',
|
||||||
|
blacklist: '',
|
||||||
|
shouldCatchErrors: false,
|
||||||
|
inject: true,
|
||||||
|
urls: '^https?://localhost|0\\.0\\.0\\.0:\\d+\n^https?://.+\\.github\\.io',
|
||||||
|
showContextMenus: true,
|
||||||
|
},
|
||||||
|
function (items) {
|
||||||
|
options = migrateOldOptions(items as OldOrNewOptions);
|
||||||
|
callback(options);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscribe = (callback: (options: Options) => void) => {
|
||||||
|
subscribers = subscribers.concat(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toReg = (str: string) =>
|
||||||
|
str !== '' ? str.split('\n').filter(Boolean).join('|') : null;
|
||||||
|
|
||||||
|
export const injectOptions = (newOptions: Options) => {
|
||||||
|
if (!newOptions) return;
|
||||||
|
|
||||||
|
options = {
|
||||||
|
...newOptions,
|
||||||
|
whitelist:
|
||||||
|
newOptions.filter !== FilterState.DO_NOT_FILTER
|
||||||
|
? toReg(newOptions.whitelist)!
|
||||||
|
: newOptions.whitelist,
|
||||||
|
blacklist:
|
||||||
|
newOptions.filter !== FilterState.DO_NOT_FILTER
|
||||||
|
? toReg(newOptions.blacklist)!
|
||||||
|
: newOptions.blacklist,
|
||||||
|
};
|
||||||
|
let s = document.createElement('script');
|
||||||
|
s.type = 'text/javascript';
|
||||||
|
s.appendChild(
|
||||||
|
document.createTextNode(
|
||||||
|
'window.devToolsOptions = Object.assign(window.devToolsOptions||{},' +
|
||||||
|
JSON.stringify(options) +
|
||||||
|
');'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
(document.head || document.documentElement).appendChild(s);
|
||||||
|
s.parentNode!.removeChild(s);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOptionsFromBg = () => {
|
||||||
|
/* chrome.runtime.sendMessage({ type: 'GET_OPTIONS' }, response => {
|
||||||
|
if (response && response.options) injectOptions(response.options);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
get((newOptions) => {
|
||||||
|
injectOptions(newOptions);
|
||||||
|
}); // Legacy
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isAllowed = (localOptions = options) =>
|
||||||
|
!localOptions ||
|
||||||
|
localOptions.inject ||
|
||||||
|
!localOptions.urls ||
|
||||||
|
location.href.match(toReg(localOptions.urls)!);
|
||||||
|
|
||||||
|
export interface SyncOptions {
|
||||||
|
readonly save: <K extends keyof Options>(key: K, value: Options[K]) => void;
|
||||||
|
readonly get: (callback: (options: Options) => void) => void;
|
||||||
|
readonly subscribe: (callback: (options: Options) => void) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function syncOptions(toAllTabs?: ToAllTabs): SyncOptions {
|
||||||
|
if (toAllTabs && !options) get(() => {}); // Initialize
|
||||||
|
return {
|
||||||
|
save: save(toAllTabs),
|
||||||
|
get: get,
|
||||||
|
subscribe: subscribe,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,27 +1,31 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
|
import { PreloadedState } from 'redux';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { UPDATE_STATE } from '@redux-devtools/app/lib/constants/actionTypes';
|
import { UPDATE_STATE } from '@redux-devtools/app/lib/constants/actionTypes';
|
||||||
|
import { StoreState } from '@redux-devtools/app/lib/reducers';
|
||||||
import App from '../../../app/containers/App';
|
import App from '../../../app/containers/App';
|
||||||
import configureStore from '../../../app/stores/windowStore';
|
import configureStore from '../../../app/stores/windowStore';
|
||||||
import getPreloadedState from '../background/getPreloadedState';
|
import getPreloadedState from '../background/getPreloadedState';
|
||||||
|
|
||||||
import '../../views/window.pug';
|
import '../../views/window.pug';
|
||||||
|
import { MonitorMessage } from '../../../app/middlewares/api';
|
||||||
|
|
||||||
const position = location.hash;
|
const position = location.hash;
|
||||||
let preloadedState;
|
let preloadedState: PreloadedState<StoreState>;
|
||||||
getPreloadedState(position, (state) => {
|
getPreloadedState(position, (state) => {
|
||||||
preloadedState = state;
|
preloadedState = state;
|
||||||
});
|
});
|
||||||
|
|
||||||
chrome.runtime.getBackgroundPage(({ store }) => {
|
chrome.runtime.getBackgroundPage((window) => {
|
||||||
|
const { store } = window!;
|
||||||
const localStore = configureStore(store, position, preloadedState);
|
const localStore = configureStore(store, position, preloadedState);
|
||||||
let name = 'monitor';
|
let name = 'monitor';
|
||||||
if (chrome && chrome.devtools && chrome.devtools.inspectedWindow) {
|
if (chrome && chrome.devtools && chrome.devtools.inspectedWindow) {
|
||||||
name += chrome.devtools.inspectedWindow.tabId;
|
name += chrome.devtools.inspectedWindow.tabId;
|
||||||
}
|
}
|
||||||
const bg = chrome.runtime.connect({ name });
|
const bg = chrome.runtime.connect({ name });
|
||||||
const update = (action) => {
|
const update = (action?: MonitorMessage) => {
|
||||||
localStore.dispatch(action || { type: UPDATE_STATE });
|
localStore.dispatch(action || { type: UPDATE_STATE });
|
||||||
};
|
};
|
||||||
bg.onMessage.addListener(update);
|
bg.onMessage.addListener(update);
|
|
@ -14,12 +14,12 @@ chrome.storage.local.get(
|
||||||
's:secure': null,
|
's:secure': null,
|
||||||
},
|
},
|
||||||
(options) => {
|
(options) => {
|
||||||
|
const AppAsAny = App as any;
|
||||||
render(
|
render(
|
||||||
<App
|
<AppAsAny
|
||||||
selectMonitor={options['select-monitor']}
|
selectMonitor={options['select-monitor']}
|
||||||
testTemplates={options['test-templates']}
|
testTemplates={options['test-templates']}
|
||||||
selectedTemplate={options['test-templates-sel']}
|
selectedTemplate={options['test-templates-sel']}
|
||||||
testTemplates={options['test-templates']}
|
|
||||||
useCodemirror
|
useCodemirror
|
||||||
socketOptions={
|
socketOptions={
|
||||||
options['s:hostname'] && options['s:port']
|
options['s:hostname'] && options['s:port']
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import configureStore from '../../../src/app/stores/windowStore';
|
import configureStore from '../../../src/app/stores/windowStore';
|
||||||
import App from '../../../src/app/containers/App.js';
|
import App from '../../../src/app/containers/App';
|
||||||
|
|
||||||
const store = configureStore(store);
|
const store = configureStore(store);
|
||||||
const component = mount(
|
const component = mount(
|
||||||
|
|
4
extension/tsconfig.json
Normal file
4
extension/tsconfig.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.react.base.json",
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import webpack from 'webpack';
|
||||||
import CopyPlugin from 'copy-webpack-plugin';
|
import CopyPlugin from 'copy-webpack-plugin';
|
||||||
|
|
||||||
const extpath = path.join(__dirname, '../src/browser/extension/');
|
const extpath = path.join(__dirname, '../src/browser/extension/');
|
||||||
const mock = `${extpath}chromeAPIMock.js`;
|
const mock = `${extpath}chromeAPIMock`;
|
||||||
|
|
||||||
const baseConfig = (params) => ({
|
const baseConfig = (params) => ({
|
||||||
// devtool: 'source-map',
|
// devtool: 'source-map',
|
||||||
|
@ -64,7 +64,7 @@ const baseConfig = (params) => ({
|
||||||
app: path.join(__dirname, '../src/app'),
|
app: path.join(__dirname, '../src/app'),
|
||||||
tmp: path.join(__dirname, '../build/tmp'),
|
tmp: path.join(__dirname, '../build/tmp'),
|
||||||
},
|
},
|
||||||
extensions: ['.js'],
|
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
@ -72,7 +72,7 @@ const baseConfig = (params) => ({
|
||||||
? params.loaders
|
? params.loaders
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.(js|ts)x?$/,
|
||||||
use: 'babel-loader',
|
use: 'babel-loader',
|
||||||
exclude: /(node_modules|tmp\/page\.bundle)/,
|
exclude: /(node_modules|tmp\/page\.bundle)/,
|
||||||
},
|
},
|
||||||
|
|
|
@ -45,6 +45,7 @@ import { Features, State } from '../reducers/instances';
|
||||||
import { MonitorStateMonitorState } from '../reducers/monitor';
|
import { MonitorStateMonitorState } from '../reducers/monitor';
|
||||||
import { LiftedAction } from '@redux-devtools/core';
|
import { LiftedAction } from '@redux-devtools/core';
|
||||||
import { Data } from '../reducers/reports';
|
import { Data } from '../reducers/reports';
|
||||||
|
import { LiftedState } from '@redux-devtools/instrument';
|
||||||
|
|
||||||
let monitorReducer: (
|
let monitorReducer: (
|
||||||
monitorProps: unknown,
|
monitorProps: unknown,
|
||||||
|
@ -53,7 +54,7 @@ let monitorReducer: (
|
||||||
) => unknown;
|
) => unknown;
|
||||||
let monitorProps: unknown = {};
|
let monitorProps: unknown = {};
|
||||||
|
|
||||||
interface ChangeSectionAction {
|
export interface ChangeSectionAction {
|
||||||
readonly type: typeof CHANGE_SECTION;
|
readonly type: typeof CHANGE_SECTION;
|
||||||
readonly section: string;
|
readonly section: string;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +70,7 @@ interface ChangeThemeFormData {
|
||||||
interface ChangeThemeData {
|
interface ChangeThemeData {
|
||||||
readonly formData: ChangeThemeFormData;
|
readonly formData: ChangeThemeFormData;
|
||||||
}
|
}
|
||||||
interface ChangeThemeAction {
|
export interface ChangeThemeAction {
|
||||||
readonly type: typeof CHANGE_THEME;
|
readonly type: typeof CHANGE_THEME;
|
||||||
readonly theme: Theme;
|
readonly theme: Theme;
|
||||||
readonly scheme: Scheme;
|
readonly scheme: Scheme;
|
||||||
|
@ -119,13 +120,28 @@ export interface LockChangesAction {
|
||||||
}
|
}
|
||||||
export interface ToggleActionAction {
|
export interface ToggleActionAction {
|
||||||
type: 'TOGGLE_ACTION';
|
type: 'TOGGLE_ACTION';
|
||||||
|
id: number;
|
||||||
}
|
}
|
||||||
export interface RollbackAction {
|
export interface RollbackAction {
|
||||||
type: 'ROLLBACK';
|
type: 'ROLLBACK';
|
||||||
|
timestamp: number;
|
||||||
}
|
}
|
||||||
export interface SweepAction {
|
export interface SweepAction {
|
||||||
type: 'SWEEP';
|
type: 'SWEEP';
|
||||||
}
|
}
|
||||||
|
interface ReorderActionAction {
|
||||||
|
type: 'REORDER_ACTION';
|
||||||
|
actionId: number;
|
||||||
|
beforeActionId: number;
|
||||||
|
}
|
||||||
|
interface ImportStateAction {
|
||||||
|
type: 'IMPORT_STATE';
|
||||||
|
nextLiftedState:
|
||||||
|
| LiftedState<unknown, Action<unknown>, unknown>
|
||||||
|
| readonly Action<unknown>[];
|
||||||
|
preloadedState?: unknown;
|
||||||
|
noRecompute?: boolean | undefined;
|
||||||
|
}
|
||||||
export type DispatchAction =
|
export type DispatchAction =
|
||||||
| JumpToStateAction
|
| JumpToStateAction
|
||||||
| JumpToActionAction
|
| JumpToActionAction
|
||||||
|
@ -133,7 +149,9 @@ export type DispatchAction =
|
||||||
| LockChangesAction
|
| LockChangesAction
|
||||||
| ToggleActionAction
|
| ToggleActionAction
|
||||||
| RollbackAction
|
| RollbackAction
|
||||||
| SweepAction;
|
| SweepAction
|
||||||
|
| ReorderActionAction
|
||||||
|
| ImportStateAction;
|
||||||
interface LiftedActionActionBase {
|
interface LiftedActionActionBase {
|
||||||
action?: DispatchAction | string | CustomAction;
|
action?: DispatchAction | string | CustomAction;
|
||||||
state?: string;
|
state?: string;
|
||||||
|
@ -145,18 +163,18 @@ export interface LiftedActionDispatchAction extends LiftedActionActionBase {
|
||||||
action: DispatchAction;
|
action: DispatchAction;
|
||||||
toAll?: boolean;
|
toAll?: boolean;
|
||||||
}
|
}
|
||||||
interface LiftedActionImportAction extends LiftedActionActionBase {
|
export interface LiftedActionImportAction extends LiftedActionActionBase {
|
||||||
type: typeof LIFTED_ACTION;
|
type: typeof LIFTED_ACTION;
|
||||||
message: 'IMPORT';
|
message: 'IMPORT';
|
||||||
state: string;
|
state: string;
|
||||||
preloadedState: unknown | undefined;
|
preloadedState: unknown | undefined;
|
||||||
}
|
}
|
||||||
interface LiftedActionActionAction extends LiftedActionActionBase {
|
export interface LiftedActionActionAction extends LiftedActionActionBase {
|
||||||
type: typeof LIFTED_ACTION;
|
type: typeof LIFTED_ACTION;
|
||||||
message: 'ACTION';
|
message: 'ACTION';
|
||||||
action: string | CustomAction;
|
action: string | CustomAction;
|
||||||
}
|
}
|
||||||
interface LiftedActionExportAction extends LiftedActionActionBase {
|
export interface LiftedActionExportAction extends LiftedActionActionBase {
|
||||||
type: typeof LIFTED_ACTION;
|
type: typeof LIFTED_ACTION;
|
||||||
message: 'EXPORT';
|
message: 'EXPORT';
|
||||||
toExport: boolean;
|
toExport: boolean;
|
||||||
|
@ -192,15 +210,15 @@ export function liftedDispatch(
|
||||||
} as LiftedActionDispatchAction;
|
} as LiftedActionDispatchAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SelectInstanceAction {
|
export interface SelectInstanceAction {
|
||||||
type: typeof SELECT_INSTANCE;
|
type: typeof SELECT_INSTANCE;
|
||||||
selected: string;
|
selected: string | number;
|
||||||
}
|
}
|
||||||
export function selectInstance(selected: string): SelectInstanceAction {
|
export function selectInstance(selected: string): SelectInstanceAction {
|
||||||
return { type: SELECT_INSTANCE, selected };
|
return { type: SELECT_INSTANCE, selected };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SelectMonitorAction {
|
export interface SelectMonitorAction {
|
||||||
type: typeof SELECT_MONITOR;
|
type: typeof SELECT_MONITOR;
|
||||||
monitor: string;
|
monitor: string;
|
||||||
monitorState?: MonitorStateMonitorState;
|
monitorState?: MonitorStateMonitorState;
|
||||||
|
@ -219,7 +237,7 @@ interface NextState {
|
||||||
subTabName: string;
|
subTabName: string;
|
||||||
inspectedStatePath?: string[];
|
inspectedStatePath?: string[];
|
||||||
}
|
}
|
||||||
interface UpdateMonitorStateAction {
|
export interface UpdateMonitorStateAction {
|
||||||
type: typeof UPDATE_MONITOR_STATE;
|
type: typeof UPDATE_MONITOR_STATE;
|
||||||
nextState: NextState;
|
nextState: NextState;
|
||||||
}
|
}
|
||||||
|
@ -240,7 +258,7 @@ export function importState(
|
||||||
return { type: LIFTED_ACTION, message: 'IMPORT', state, preloadedState };
|
return { type: LIFTED_ACTION, message: 'IMPORT', state, preloadedState };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExportAction {
|
export interface ExportAction {
|
||||||
type: typeof EXPORT;
|
type: typeof EXPORT;
|
||||||
}
|
}
|
||||||
export function exportState(): ExportAction {
|
export function exportState(): ExportAction {
|
||||||
|
@ -268,7 +286,7 @@ export function pauseRecording(status: boolean): LiftedActionDispatchAction {
|
||||||
export interface CustomAction {
|
export interface CustomAction {
|
||||||
name: string;
|
name: string;
|
||||||
selected: number;
|
selected: number;
|
||||||
args: (string | undefined)[];
|
args: string[];
|
||||||
rest: string;
|
rest: string;
|
||||||
}
|
}
|
||||||
export function dispatchRemotely(
|
export function dispatchRemotely(
|
||||||
|
@ -277,28 +295,28 @@ export function dispatchRemotely(
|
||||||
return { type: LIFTED_ACTION, message: 'ACTION', action };
|
return { type: LIFTED_ACTION, message: 'ACTION', action };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TogglePersistAction {
|
export interface TogglePersistAction {
|
||||||
type: typeof TOGGLE_PERSIST;
|
type: typeof TOGGLE_PERSIST;
|
||||||
}
|
}
|
||||||
export function togglePersist(): TogglePersistAction {
|
export function togglePersist(): TogglePersistAction {
|
||||||
return { type: TOGGLE_PERSIST };
|
return { type: TOGGLE_PERSIST };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ToggleSyncAction {
|
export interface ToggleSyncAction {
|
||||||
type: typeof TOGGLE_SYNC;
|
type: typeof TOGGLE_SYNC;
|
||||||
}
|
}
|
||||||
export function toggleSync(): ToggleSyncAction {
|
export function toggleSync(): ToggleSyncAction {
|
||||||
return { type: TOGGLE_SYNC };
|
return { type: TOGGLE_SYNC };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ToggleSliderAction {
|
export interface ToggleSliderAction {
|
||||||
type: typeof TOGGLE_SLIDER;
|
type: typeof TOGGLE_SLIDER;
|
||||||
}
|
}
|
||||||
export function toggleSlider(): ToggleSliderAction {
|
export function toggleSlider(): ToggleSliderAction {
|
||||||
return { type: TOGGLE_SLIDER };
|
return { type: TOGGLE_SLIDER };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ToggleDispatcherAction {
|
export interface ToggleDispatcherAction {
|
||||||
type: typeof TOGGLE_DISPATCHER;
|
type: typeof TOGGLE_DISPATCHER;
|
||||||
}
|
}
|
||||||
export function toggleDispatcher(): ToggleDispatcherAction {
|
export function toggleDispatcher(): ToggleDispatcherAction {
|
||||||
|
@ -312,7 +330,7 @@ export interface ConnectionOptions {
|
||||||
readonly port: number;
|
readonly port: number;
|
||||||
readonly secure: boolean;
|
readonly secure: boolean;
|
||||||
}
|
}
|
||||||
interface ReconnectAction {
|
export interface ReconnectAction {
|
||||||
readonly type: typeof RECONNECT;
|
readonly type: typeof RECONNECT;
|
||||||
readonly options: ConnectionOptions;
|
readonly options: ConnectionOptions;
|
||||||
}
|
}
|
||||||
|
@ -326,7 +344,7 @@ interface Notification {
|
||||||
readonly type: 'error';
|
readonly type: 'error';
|
||||||
readonly message: string;
|
readonly message: string;
|
||||||
}
|
}
|
||||||
interface ShowNotificationAction {
|
export interface ShowNotificationAction {
|
||||||
readonly type: typeof SHOW_NOTIFICATION;
|
readonly type: typeof SHOW_NOTIFICATION;
|
||||||
readonly notification: Notification;
|
readonly notification: Notification;
|
||||||
}
|
}
|
||||||
|
@ -334,14 +352,14 @@ export function showNotification(message: string): ShowNotificationAction {
|
||||||
return { type: SHOW_NOTIFICATION, notification: { type: 'error', message } };
|
return { type: SHOW_NOTIFICATION, notification: { type: 'error', message } };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ClearNotificationAction {
|
export interface ClearNotificationAction {
|
||||||
readonly type: typeof CLEAR_NOTIFICATION;
|
readonly type: typeof CLEAR_NOTIFICATION;
|
||||||
}
|
}
|
||||||
export function clearNotification(): ClearNotificationAction {
|
export function clearNotification(): ClearNotificationAction {
|
||||||
return { type: CLEAR_NOTIFICATION };
|
return { type: CLEAR_NOTIFICATION };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetReportRequest {
|
export interface GetReportRequest {
|
||||||
readonly type: typeof GET_REPORT_REQUEST;
|
readonly type: typeof GET_REPORT_REQUEST;
|
||||||
readonly report: unknown;
|
readonly report: unknown;
|
||||||
}
|
}
|
||||||
|
@ -354,7 +372,7 @@ export interface ActionCreator {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LibConfig {
|
export interface LibConfig {
|
||||||
actionCreators?: string;
|
actionCreators?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
@ -363,10 +381,10 @@ interface LibConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestBase {
|
export interface RequestBase {
|
||||||
id: string;
|
id?: string;
|
||||||
instanceId?: string;
|
instanceId?: string | number;
|
||||||
action?: string;
|
action?: string;
|
||||||
name?: string;
|
name?: string | undefined;
|
||||||
libConfig?: LibConfig;
|
libConfig?: LibConfig;
|
||||||
actionsById?: string;
|
actionsById?: string;
|
||||||
computedStates?: string;
|
computedStates?: string;
|
||||||
|
@ -376,14 +394,15 @@ export interface RequestBase {
|
||||||
}
|
}
|
||||||
interface InitRequest extends RequestBase {
|
interface InitRequest extends RequestBase {
|
||||||
type: 'INIT';
|
type: 'INIT';
|
||||||
action: string;
|
action?: string;
|
||||||
|
payload?: string;
|
||||||
}
|
}
|
||||||
interface ActionRequest extends RequestBase {
|
interface ActionRequest extends RequestBase {
|
||||||
type: 'ACTION';
|
type: 'ACTION';
|
||||||
isExcess: boolean;
|
isExcess?: boolean;
|
||||||
nextActionId: number;
|
nextActionId: number;
|
||||||
maxAge: number;
|
maxAge: number;
|
||||||
batched: boolean;
|
batched?: boolean;
|
||||||
}
|
}
|
||||||
interface StateRequest extends RequestBase {
|
interface StateRequest extends RequestBase {
|
||||||
type: 'STATE';
|
type: 'STATE';
|
||||||
|
@ -409,23 +428,23 @@ export type Request =
|
||||||
| LiftedRequest
|
| LiftedRequest
|
||||||
| ExportRequest;
|
| ExportRequest;
|
||||||
|
|
||||||
interface UpdateStateAction {
|
export interface UpdateStateAction {
|
||||||
type: typeof UPDATE_STATE;
|
type: typeof UPDATE_STATE;
|
||||||
request?: Request;
|
request?: Request;
|
||||||
id?: string;
|
id?: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SetStateAction {
|
export interface SetStateAction {
|
||||||
type: typeof SET_STATE;
|
type: typeof SET_STATE;
|
||||||
newState: State;
|
newState: State;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RemoveInstanceAction {
|
export interface RemoveInstanceAction {
|
||||||
type: typeof REMOVE_INSTANCE;
|
type: typeof REMOVE_INSTANCE;
|
||||||
id: string;
|
id: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConnectRequestAction {
|
export interface ConnectRequestAction {
|
||||||
type: typeof CONNECT_REQUEST;
|
type: typeof CONNECT_REQUEST;
|
||||||
options: ConnectionOptions;
|
options: ConnectionOptions;
|
||||||
}
|
}
|
||||||
|
@ -435,58 +454,58 @@ interface ConnectSuccessPayload {
|
||||||
authState: AuthStates;
|
authState: AuthStates;
|
||||||
socketState: States;
|
socketState: States;
|
||||||
}
|
}
|
||||||
interface ConnectSuccessAction {
|
export interface ConnectSuccessAction {
|
||||||
type: typeof CONNECT_SUCCESS;
|
type: typeof CONNECT_SUCCESS;
|
||||||
payload: ConnectSuccessPayload;
|
payload: ConnectSuccessPayload;
|
||||||
error: Error | undefined;
|
error: Error | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConnectErrorAction {
|
export interface ConnectErrorAction {
|
||||||
type: typeof CONNECT_ERROR;
|
type: typeof CONNECT_ERROR;
|
||||||
error: Error | undefined;
|
error: Error | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthRequestAction {
|
export interface AuthRequestAction {
|
||||||
type: typeof AUTH_REQUEST;
|
type: typeof AUTH_REQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthSuccessAction {
|
export interface AuthSuccessAction {
|
||||||
type: typeof AUTH_SUCCESS;
|
type: typeof AUTH_SUCCESS;
|
||||||
baseChannel: string;
|
baseChannel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthErrorAction {
|
export interface AuthErrorAction {
|
||||||
type: typeof AUTH_ERROR;
|
type: typeof AUTH_ERROR;
|
||||||
error: Error;
|
error: Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DisconnectedAction {
|
export interface DisconnectedAction {
|
||||||
type: typeof DISCONNECTED;
|
type: typeof DISCONNECTED;
|
||||||
code: number;
|
code: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeauthenticateAction {
|
export interface DeauthenticateAction {
|
||||||
type: typeof DEAUTHENTICATE;
|
type: typeof DEAUTHENTICATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SubscribeRequestAction {
|
export interface SubscribeRequestAction {
|
||||||
type: typeof SUBSCRIBE_REQUEST;
|
type: typeof SUBSCRIBE_REQUEST;
|
||||||
channel: string;
|
channel: string;
|
||||||
subscription: typeof UPDATE_STATE | typeof UPDATE_REPORTS;
|
subscription: typeof UPDATE_STATE | typeof UPDATE_REPORTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SubscribeSuccessAction {
|
export interface SubscribeSuccessAction {
|
||||||
type: typeof SUBSCRIBE_SUCCESS;
|
type: typeof SUBSCRIBE_SUCCESS;
|
||||||
channel: string;
|
channel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SubscribeErrorAction {
|
export interface SubscribeErrorAction {
|
||||||
type: typeof SUBSCRIBE_ERROR;
|
type: typeof SUBSCRIBE_ERROR;
|
||||||
error: Error;
|
error: Error;
|
||||||
status: string;
|
status: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UnsubscribeAction {
|
export interface UnsubscribeAction {
|
||||||
type: typeof UNSUBSCRIBE;
|
type: typeof UNSUBSCRIBE;
|
||||||
channel: string;
|
channel: string;
|
||||||
}
|
}
|
||||||
|
@ -494,8 +513,8 @@ interface UnsubscribeAction {
|
||||||
export interface EmitAction {
|
export interface EmitAction {
|
||||||
type: typeof EMIT;
|
type: typeof EMIT;
|
||||||
message: string;
|
message: string;
|
||||||
id?: string | false;
|
id?: string | number | false;
|
||||||
instanceId?: string;
|
instanceId?: string | number;
|
||||||
action?: unknown;
|
action?: unknown;
|
||||||
state?: unknown;
|
state?: unknown;
|
||||||
}
|
}
|
||||||
|
@ -514,31 +533,30 @@ interface RemoveRequest {
|
||||||
id: unknown;
|
id: unknown;
|
||||||
}
|
}
|
||||||
export type UpdateReportsRequest = ListRequest | AddRequest | RemoveRequest;
|
export type UpdateReportsRequest = ListRequest | AddRequest | RemoveRequest;
|
||||||
interface UpdateReportsAction {
|
export interface UpdateReportsAction {
|
||||||
type: typeof UPDATE_REPORTS;
|
type: typeof UPDATE_REPORTS;
|
||||||
request: UpdateReportsRequest;
|
request: UpdateReportsRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetReportError {
|
export interface GetReportError {
|
||||||
type: typeof GET_REPORT_ERROR;
|
type: typeof GET_REPORT_ERROR;
|
||||||
error: Error;
|
error: Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetReportSuccess {
|
export interface GetReportSuccess {
|
||||||
type: typeof GET_REPORT_SUCCESS;
|
type: typeof GET_REPORT_SUCCESS;
|
||||||
data: { payload: string };
|
data: { payload: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ErrorAction {
|
export interface ErrorAction {
|
||||||
type: typeof ERROR;
|
type: typeof ERROR;
|
||||||
payload: string;
|
payload: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StoreAction =
|
export type StoreActionWithoutUpdateStateOrLiftedAction =
|
||||||
| ChangeSectionAction
|
| ChangeSectionAction
|
||||||
| ChangeThemeAction
|
| ChangeThemeAction
|
||||||
| MonitorActionAction
|
| MonitorActionAction
|
||||||
| LiftedActionAction
|
|
||||||
| SelectInstanceAction
|
| SelectInstanceAction
|
||||||
| SelectMonitorAction
|
| SelectMonitorAction
|
||||||
| UpdateMonitorStateAction
|
| UpdateMonitorStateAction
|
||||||
|
@ -552,7 +570,6 @@ export type StoreAction =
|
||||||
| ClearNotificationAction
|
| ClearNotificationAction
|
||||||
| GetReportRequest
|
| GetReportRequest
|
||||||
| SetStateAction
|
| SetStateAction
|
||||||
| UpdateStateAction
|
|
||||||
| RemoveInstanceAction
|
| RemoveInstanceAction
|
||||||
| ConnectRequestAction
|
| ConnectRequestAction
|
||||||
| ConnectSuccessAction
|
| ConnectSuccessAction
|
||||||
|
@ -571,3 +588,13 @@ export type StoreAction =
|
||||||
| GetReportError
|
| GetReportError
|
||||||
| GetReportSuccess
|
| GetReportSuccess
|
||||||
| ErrorAction;
|
| ErrorAction;
|
||||||
|
|
||||||
|
export type StoreActionWithoutUpdateState =
|
||||||
|
| StoreActionWithoutUpdateStateOrLiftedAction
|
||||||
|
| LiftedActionAction;
|
||||||
|
|
||||||
|
export type StoreActionWithoutLiftedAction =
|
||||||
|
| StoreActionWithoutUpdateStateOrLiftedAction
|
||||||
|
| UpdateStateAction;
|
||||||
|
|
||||||
|
export type StoreAction = StoreActionWithoutUpdateState | UpdateStateAction;
|
||||||
|
|
|
@ -9,7 +9,7 @@ type DispatchProps = ResolveThunks<typeof actionCreators>;
|
||||||
type Props = StateProps & DispatchProps;
|
type Props = StateProps & DispatchProps;
|
||||||
|
|
||||||
class InstanceSelector extends Component<Props> {
|
class InstanceSelector extends Component<Props> {
|
||||||
select?: { readonly value: string; readonly label: string }[];
|
select?: { readonly value: string; readonly label: string | number }[];
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.select = [{ value: '', label: 'Autoselect instances' }];
|
this.select = [{ value: '', label: 'Autoselect instances' }];
|
||||||
|
|
|
@ -55,7 +55,7 @@ type Props = DispatchProps & OwnProps;
|
||||||
interface State {
|
interface State {
|
||||||
selected: 'default' | number;
|
selected: 'default' | number;
|
||||||
customAction: string;
|
customAction: string;
|
||||||
args: (string | undefined)[];
|
args: string[];
|
||||||
rest: string;
|
rest: string;
|
||||||
changed: boolean;
|
changed: boolean;
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ class Dispatcher extends Component<Props, State> {
|
||||||
handleArg = (argIndex: number) => (value: string) => {
|
handleArg = (argIndex: number) => (value: string) => {
|
||||||
const args = [
|
const args = [
|
||||||
...this.state.args.slice(0, argIndex),
|
...this.state.args.slice(0, argIndex),
|
||||||
value || undefined,
|
(value || undefined)!,
|
||||||
...this.state.args.slice(argIndex + 1),
|
...this.state.args.slice(argIndex + 1),
|
||||||
];
|
];
|
||||||
this.setState({ args, changed: true });
|
this.setState({ args, changed: true });
|
||||||
|
|
|
@ -30,7 +30,7 @@ let socket: SCClientSocket;
|
||||||
let store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>;
|
let store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>;
|
||||||
|
|
||||||
function emit({ message: type, id, instanceId, action, state }: EmitAction) {
|
function emit({ message: type, id, instanceId, action, state }: EmitAction) {
|
||||||
socket.emit(id ? 'sc-' + id : 'respond', { type, action, state, instanceId });
|
socket.emit(id ? `sc-${id}` : 'respond', { type, action, state, instanceId });
|
||||||
}
|
}
|
||||||
|
|
||||||
function startMonitoring(channel: string) {
|
function startMonitoring(channel: string) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Dispatch, MiddlewareAPI } from 'redux';
|
||||||
import { ExportRequest, StoreAction } from '../actions';
|
import { ExportRequest, StoreAction } from '../actions';
|
||||||
import { StoreState } from '../reducers';
|
import { StoreState } from '../reducers';
|
||||||
|
|
||||||
let toExport: string | undefined;
|
let toExport: string | number | undefined;
|
||||||
|
|
||||||
function download(state: string) {
|
function download(state: string) {
|
||||||
const blob = new Blob([state], { type: 'octet/stream' });
|
const blob = new Blob([state], { type: 'octet/stream' });
|
||||||
|
|
|
@ -34,8 +34,8 @@ export interface Features {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
name?: string;
|
name?: string | number;
|
||||||
connectionId?: string;
|
connectionId?: string | number;
|
||||||
explicitLib?: string;
|
explicitLib?: string;
|
||||||
lib?: string;
|
lib?: string;
|
||||||
actionCreators?: ActionCreator[];
|
actionCreators?: ActionCreator[];
|
||||||
|
@ -56,10 +56,10 @@ export interface State {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstancesState {
|
export interface InstancesState {
|
||||||
selected: string | null;
|
selected: string | number | null;
|
||||||
current: string;
|
current: string | number;
|
||||||
sync: boolean;
|
sync: boolean;
|
||||||
connections: { [id: string]: string[] };
|
connections: { [id: string]: (string | number)[] };
|
||||||
options: { [id: string]: Options };
|
options: { [id: string]: Options };
|
||||||
states: { [id: string]: State };
|
states: { [id: string]: State };
|
||||||
persisted?: boolean;
|
persisted?: boolean;
|
||||||
|
@ -86,7 +86,7 @@ export const initialState: InstancesState = {
|
||||||
function updateState(
|
function updateState(
|
||||||
state: { [id: string]: State },
|
state: { [id: string]: State },
|
||||||
request: Request,
|
request: Request,
|
||||||
id: string,
|
id: string | number,
|
||||||
serialize: boolean | undefined
|
serialize: boolean | undefined
|
||||||
) {
|
) {
|
||||||
let payload: State = request.payload as State;
|
let payload: State = request.payload as State;
|
||||||
|
@ -231,7 +231,7 @@ export function dispatchAction(
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeState(state: InstancesState, connectionId: string) {
|
function removeState(state: InstancesState, connectionId: string | number) {
|
||||||
const instanceIds = state.connections[connectionId];
|
const instanceIds = state.connections[connectionId];
|
||||||
if (!instanceIds) return state;
|
if (!instanceIds) return state;
|
||||||
|
|
||||||
|
@ -268,8 +268,8 @@ function removeState(state: InstancesState, connectionId: string) {
|
||||||
|
|
||||||
function init(
|
function init(
|
||||||
{ type, action, name, libConfig = {} }: Request,
|
{ type, action, name, libConfig = {} }: Request,
|
||||||
connectionId: string,
|
connectionId: string | number,
|
||||||
current: string
|
current: string | number
|
||||||
): Options {
|
): Options {
|
||||||
let lib;
|
let lib;
|
||||||
let actionCreators;
|
let actionCreators;
|
||||||
|
@ -310,7 +310,7 @@ export default function instances(
|
||||||
case UPDATE_STATE: {
|
case UPDATE_STATE: {
|
||||||
const { request } = action;
|
const { request } = action;
|
||||||
if (!request) return state;
|
if (!request) return state;
|
||||||
const connectionId = action.id || request.id;
|
const connectionId = (action.id || request.id)!;
|
||||||
const current = request.instanceId || connectionId;
|
const current = request.instanceId || connectionId;
|
||||||
let connections = state.connections;
|
let connections = state.connections;
|
||||||
let options = state.options;
|
let options = state.options;
|
||||||
|
|
|
@ -18,7 +18,7 @@ export interface MonitorStateMonitorState {
|
||||||
}
|
}
|
||||||
export interface MonitorState {
|
export interface MonitorState {
|
||||||
selected: string;
|
selected: string;
|
||||||
monitorState: MonitorStateMonitorState | undefined;
|
monitorState?: MonitorStateMonitorState | undefined;
|
||||||
sliderIsOpen: boolean;
|
sliderIsOpen: boolean;
|
||||||
dispatcherIsOpen: boolean;
|
dispatcherIsOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,7 @@ import stringifyJSON from './stringifyJSON';
|
||||||
import { SET_STATE } from '../constants/actionTypes';
|
import { SET_STATE } from '../constants/actionTypes';
|
||||||
import { InstancesState, State } from '../reducers/instances';
|
import { InstancesState, State } from '../reducers/instances';
|
||||||
import { Dispatch, MiddlewareAPI } from 'redux';
|
import { Dispatch, MiddlewareAPI } from 'redux';
|
||||||
import { DispatchAction, StoreAction } from '../actions';
|
import { DispatchAction, StoreActionWithoutLiftedAction } from '../actions';
|
||||||
import { StoreState } from '../reducers';
|
|
||||||
|
|
||||||
export function sweep(state: State): State {
|
export function sweep(state: State): State {
|
||||||
return {
|
return {
|
||||||
|
@ -21,12 +20,15 @@ export function sweep(state: State): State {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nonReduxDispatch(
|
export function nonReduxDispatch(
|
||||||
store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>,
|
store: MiddlewareAPI<
|
||||||
|
Dispatch<StoreActionWithoutLiftedAction>,
|
||||||
|
{ readonly instances: InstancesState }
|
||||||
|
>,
|
||||||
message: string,
|
message: string,
|
||||||
instanceId: string,
|
instanceId: string | number,
|
||||||
action: DispatchAction,
|
action: DispatchAction,
|
||||||
initialState: string | undefined,
|
initialState: string | undefined,
|
||||||
preInstances: InstancesState
|
preInstances?: InstancesState
|
||||||
) {
|
) {
|
||||||
const instances = preInstances || store.getState().instances;
|
const instances = preInstances || store.getState().instances;
|
||||||
const state = instances.states[instanceId];
|
const state = instances.states[instanceId];
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
"@redux-devtools/inspector-monitor": "^1.0.0",
|
"@redux-devtools/inspector-monitor": "^1.0.0",
|
||||||
"@types/es6template": "^1.0.0",
|
"@types/es6template": "^1.0.0",
|
||||||
"@types/history": "^4.7.8",
|
"@types/history": "^4.7.8",
|
||||||
"@types/jsan": "^3.1.0",
|
"@types/jsan": "^3.1.2",
|
||||||
"@types/lodash.shuffle": "^4.2.6",
|
"@types/lodash.shuffle": "^4.2.6",
|
||||||
"@types/object-path": "^0.11.0",
|
"@types/object-path": "^0.11.0",
|
||||||
"@types/react": "^16.14.8",
|
"@types/react": "^16.14.8",
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.14.5",
|
"@babel/code-frame": "^7.14.5",
|
||||||
"@types/chrome": "^0.0.124",
|
"@types/chrome": "^0.0.145",
|
||||||
"anser": "^1.4.10",
|
"anser": "^1.4.10",
|
||||||
"html-entities": "^1.4.0",
|
"html-entities": "^1.4.0",
|
||||||
"redux-devtools-themes": "^1.0.0",
|
"redux-devtools-themes": "^1.0.0",
|
||||||
|
|
|
@ -32,11 +32,11 @@ function openAndCloseTab(url: string) {
|
||||||
const removeTab = () => {
|
const removeTab = () => {
|
||||||
chrome.windows.onFocusChanged.removeListener(removeTab);
|
chrome.windows.onFocusChanged.removeListener(removeTab);
|
||||||
if (tab && tab.id) {
|
if (tab && tab.id) {
|
||||||
chrome.tabs.remove(tab.id, () => {
|
chrome.tabs.remove(tab.id, async () => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
if (chrome.runtime.lastError) console.log(chrome.runtime.lastError);
|
if (chrome.runtime.lastError) console.log(chrome.runtime.lastError);
|
||||||
else if (chrome.devtools && chrome.devtools.inspectedWindow) {
|
else if (chrome.devtools && chrome.devtools.inspectedWindow) {
|
||||||
chrome.tabs.update(chrome.devtools.inspectedWindow.tabId, {
|
await chrome.tabs.update(chrome.devtools.inspectedWindow.tabId, {
|
||||||
active: true,
|
active: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ interface ImportStateAction<S, A extends Action<unknown>, MonitorState> {
|
||||||
type: typeof ActionTypes.IMPORT_STATE;
|
type: typeof ActionTypes.IMPORT_STATE;
|
||||||
nextLiftedState: LiftedState<S, A, MonitorState> | readonly A[];
|
nextLiftedState: LiftedState<S, A, MonitorState> | readonly A[];
|
||||||
preloadedState?: S;
|
preloadedState?: S;
|
||||||
noRecompute: boolean | undefined;
|
noRecompute?: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LockChangesAction {
|
interface LockChangesAction {
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
"jsan": "^3.1.13"
|
"jsan": "^3.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jsan": "^3.1.0",
|
"@types/jsan": "^3.1.2",
|
||||||
"immutable": "^4.0.0-rc.12"
|
"immutable": "^4.0.0-rc.12"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
|
@ -3,22 +3,24 @@ import jsan from 'jsan';
|
||||||
import { nanoid } from 'nanoid/non-secure';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
import { immutableSerialize } from '@redux-devtools/serialize';
|
import { immutableSerialize } from '@redux-devtools/serialize';
|
||||||
import Immutable from 'immutable';
|
import Immutable from 'immutable';
|
||||||
import { Action } from 'redux';
|
import { Action, ActionCreator } from 'redux';
|
||||||
|
|
||||||
export function generateId(id: string | undefined) {
|
export function generateId(id: string | undefined) {
|
||||||
return id || nanoid(7);
|
return id || nanoid(7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ActionCreatorObject {
|
||||||
|
readonly name: string;
|
||||||
|
readonly func: ActionCreator<Action<unknown>>;
|
||||||
|
readonly args: readonly string[];
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
function flatTree(
|
function flatTree(
|
||||||
obj: { [key: string]: (...args: any[]) => unknown },
|
obj: { [key: string]: ActionCreator<Action<unknown>> },
|
||||||
namespace = ''
|
namespace = ''
|
||||||
) {
|
) {
|
||||||
let functions: {
|
let functions: ActionCreatorObject[] = [];
|
||||||
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') {
|
||||||
|
@ -63,7 +65,7 @@ export function getMethods(obj: unknown) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getActionsArray(actionCreators: {
|
export function getActionsArray(actionCreators: {
|
||||||
[key: string]: (...args: any[]) => unknown;
|
[key: string]: ActionCreator<Action<unknown>>;
|
||||||
}) {
|
}) {
|
||||||
if (Array.isArray(actionCreators)) return actionCreators;
|
if (Array.isArray(actionCreators)) return actionCreators;
|
||||||
return flatTree(actionCreators);
|
return flatTree(actionCreators);
|
||||||
|
@ -81,10 +83,8 @@ function evalArgs(inArgs: string[], restArgs: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalAction(
|
export function evalAction(
|
||||||
action: string | { args: string[]; rest: string; selected: string },
|
action: string | { args: string[]; rest: string; selected: number },
|
||||||
actionCreators: {
|
actionCreators: readonly ActionCreatorObject[]
|
||||||
[selected: string]: { func: (...args: any[]) => Action<unknown> };
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
if (typeof action === 'string') {
|
if (typeof action === 'string') {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
||||||
|
|
23
yarn.lock
23
yarn.lock
|
@ -3520,7 +3520,7 @@ __metadata:
|
||||||
"@redux-devtools/inspector-monitor": ^1.0.0
|
"@redux-devtools/inspector-monitor": ^1.0.0
|
||||||
"@types/es6template": ^1.0.0
|
"@types/es6template": ^1.0.0
|
||||||
"@types/history": ^4.7.8
|
"@types/history": ^4.7.8
|
||||||
"@types/jsan": ^3.1.0
|
"@types/jsan": ^3.1.2
|
||||||
"@types/lodash.shuffle": ^4.2.6
|
"@types/lodash.shuffle": ^4.2.6
|
||||||
"@types/object-path": ^0.11.0
|
"@types/object-path": ^0.11.0
|
||||||
"@types/prop-types": ^15.7.3
|
"@types/prop-types": ^15.7.3
|
||||||
|
@ -3567,7 +3567,7 @@ __metadata:
|
||||||
"@redux-devtools/core": ^3.9.0
|
"@redux-devtools/core": ^3.9.0
|
||||||
"@redux-devtools/inspector-monitor": ^1.0.0
|
"@redux-devtools/inspector-monitor": ^1.0.0
|
||||||
"@types/babel__code-frame": ^7.0.2
|
"@types/babel__code-frame": ^7.0.2
|
||||||
"@types/chrome": ^0.0.124
|
"@types/chrome": ^0.0.145
|
||||||
"@types/enzyme": ^3.10.8
|
"@types/enzyme": ^3.10.8
|
||||||
"@types/enzyme-adapter-react-16": ^1.0.6
|
"@types/enzyme-adapter-react-16": ^1.0.6
|
||||||
"@types/html-entities": ^1.3.4
|
"@types/html-entities": ^1.3.4
|
||||||
|
@ -3682,7 +3682,7 @@ __metadata:
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@redux-devtools/serialize@workspace:packages/redux-devtools-serialize"
|
resolution: "@redux-devtools/serialize@workspace:packages/redux-devtools-serialize"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/jsan": ^3.1.0
|
"@types/jsan": ^3.1.2
|
||||||
immutable: ^4.0.0-rc.12
|
immutable: ^4.0.0-rc.12
|
||||||
jsan: ^3.1.13
|
jsan: ^3.1.13
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -4825,13 +4825,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/chrome@npm:^0.0.124":
|
"@types/chrome@npm:^0.0.145":
|
||||||
version: 0.0.124
|
version: 0.0.145
|
||||||
resolution: "@types/chrome@npm:0.0.124"
|
resolution: "@types/chrome@npm:0.0.145"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/filesystem": "*"
|
"@types/filesystem": "*"
|
||||||
"@types/har-format": "*"
|
"@types/har-format": "*"
|
||||||
checksum: 6499edca5f608dd48651b20d57d9fb30bbcb02cd695cd94d879a11dba7b9492c618edc6b8b5f718e82b58eceea94fb920c871546c2c3bc867595cb7dd020d527
|
checksum: f826d0a071ac7ea68aa97b2f8e34a944c470fbd036fdd6a413987fea03062d354fb14708cc0b59693b4a78ec88f9c5b1fce0c622a8e29e3e4b0917509eaf2a1a
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
@ -5246,10 +5246,10 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/jsan@npm:^3.1.0":
|
"@types/jsan@npm:^3.1.2":
|
||||||
version: 3.1.0
|
version: 3.1.2
|
||||||
resolution: "@types/jsan@npm:3.1.0"
|
resolution: "@types/jsan@npm:3.1.2"
|
||||||
checksum: a0670d90e4bee7110504be73eefff9196b46235faf490062865136b1cbad4d3bac2adb9303e308c523f07716c026bb8e72a12ae7d47a948424d4ca4d7883587d
|
checksum: 2ff652807d6067bbc650aaefcda4e3c07b54ddfd7d72283d7c1f1892ad1e18e907b1bbdbee7d0a163efa9e8aed9af5fa9f4ed8e2f27243c46383d31e1181fc11
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
@ -23231,6 +23231,7 @@ fsevents@^1.2.7:
|
||||||
"@redux-devtools/serialize": ^0.3.0
|
"@redux-devtools/serialize": ^0.3.0
|
||||||
"@redux-devtools/slider-monitor": ^2.0.0-8
|
"@redux-devtools/slider-monitor": ^2.0.0-8
|
||||||
"@redux-devtools/utils": ^1.0.0-6
|
"@redux-devtools/utils": ^1.0.0-6
|
||||||
|
"@types/jsan": ^3.1.2
|
||||||
bestzip: ^2.2.0
|
bestzip: ^2.2.0
|
||||||
chromedriver: ^91.0.1
|
chromedriver: ^91.0.1
|
||||||
electron: ^13.1.2
|
electron: ^13.1.2
|
||||||
|
|
Loading…
Reference in New Issue
Block a user