Fill out more types

This commit is contained in:
Nathan Bierema 2021-07-18 15:19:44 -04:00
parent e07896be6a
commit 3cc1a62177
9 changed files with 251 additions and 75 deletions

View File

@ -1,6 +1,11 @@
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';
function deprecate(param: string) {
// eslint-disable-next-line no-console
@ -9,14 +14,34 @@ function deprecate(param: string) {
);
}
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;
}
export default function importState(
state,
{ deserializeState, deserializeAction, serialize }
state: string | undefined,
{ deserializeState, deserializeAction, serialize }: Config
) {
if (!state) return undefined;
let parse = jsan.parse;
if (serialize) {
if (serialize.immutable) {
if (isSerializeWithImmutable(serialize)) {
parse = (v) =>
jsan.parse(
v,
@ -27,7 +52,7 @@ export default function importState(
serialize.reviver
).reviver
);
} else if (serialize.reviver) {
} else if (isSerializeWithReviver(serialize)) {
parse = (v) => jsan.parse(v, serialize.reviver);
}
}

View File

@ -9,8 +9,14 @@ import { Config } from '../../browser/extension/inject/pageScript';
import { Action } from 'redux';
import { LiftedState, PerformAction } from '@redux-devtools/instrument';
import { LibConfig } from '@redux-devtools/app/lib/actions';
import { ContentScriptToPageScriptMessage } from '../../browser/extension/inject/contentScript';
import { Position } from './openWindow';
const listeners = {};
const listeners: {
[instanceId: string]:
| ((message: ContentScriptToPageScriptMessage) => void)
| ((message: ContentScriptToPageScriptMessage) => void)[];
} = {};
export const source = '@devtools-page';
function windowReplacer(key: string, value: unknown) {
@ -129,15 +135,15 @@ interface InitMessage<S, A extends Action<unknown>> {
libConfig?: LibConfig;
}
interface SerializedPartialLiftedState<S, A extends Action<unknown>> {
interface SerializedPartialLiftedState {
readonly stagedActionIds: readonly number[];
readonly currentStateIndex: number;
readonly nextActionId: number;
}
interface SerializedPartialStateMessage<S, A extends Action<unknown>> {
interface SerializedPartialStateMessage {
readonly type: 'PARTIAL_STATE';
readonly payload: SerializedPartialLiftedState<S, A>;
readonly payload: SerializedPartialLiftedState;
readonly source: typeof source;
readonly instanceId: number;
readonly maxAge: number;
@ -177,27 +183,41 @@ interface SerializedStateMessage<S, A extends Action<unknown>> {
readonly computedStates: string;
readonly committedState: boolean;
}
export type PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<
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<S, A>
| SerializedPartialStateMessage
| SerializedExportMessage
| SerializedActionMessage
| SerializedStateMessage<S, A>
| SerializedStateMessage<S, A>;
export type PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<
S,
A extends Action<unknown>
> =
| PageScriptToContentScriptMessageForwardedToMonitors<S, A>
| ErrorMessage
| InitInstanceMessage
| GetReportMessage
| StopMessage;
| StopMessage
| OpenMessage;
export type PageScriptToContentScriptMessageWithoutDisconnect<
S,
A extends Action<unknown>
> =
| PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<S, A>
| InitInstancePageScriptToContentScriptMessage;
| InitInstancePageScriptToContentScriptMessage
| InitInstanceMessage;
export type PageScriptToContentScriptMessage<S, A extends Action<unknown>> =
| PageScriptToContentScriptMessageWithoutDisconnect<S, A>
@ -262,7 +282,7 @@ function amendActionType(
return { action, timestamp, stack };
}
export interface LiftedMessage {
interface LiftedMessage {
readonly type: 'LIFTED';
readonly liftedState: { readonly isPaused: boolean | undefined };
readonly instanceId: number;
@ -303,7 +323,7 @@ interface StateMessage<S, A extends Action<unknown>> {
readonly libConfig?: LibConfig;
}
interface ErrorMessage {
export interface ErrorMessage {
readonly type: 'ERROR';
readonly payload: unknown;
readonly source: typeof source;
@ -412,7 +432,7 @@ export function sendMessage(
toContentScript(message, config.serialize, config.serialize);
}
function handleMessages(event) {
function handleMessages(event: MessageEvent<ContentScriptToPageScriptMessage>) {
if (process.env.BABEL_ENV !== 'test' && (!event || event.source !== window)) {
return;
}
@ -420,21 +440,26 @@ function handleMessages(event) {
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);
const listenersForId = listeners[id];
if (typeof listenersForId === 'function') listenersForId(message);
else {
listeners[id].forEach((fn) => {
listenersForId.forEach((fn) => {
fn(message);
});
}
});
}
export function setListener(onMessage, instanceId) {
export function setListener(
onMessage: (message: ContentScriptToPageScriptMessage) => void,
instanceId: number
) {
listeners[instanceId] = onMessage;
window.addEventListener('message', handleMessages, false);
}
const liftListener = (listener, config) => (message) => {
const liftListener =
(listener, config: Config) => (message: ContentScriptToPageScriptMessage) => {
let data = {};
if (message.type === 'IMPORT') {
data.type = 'DISPATCH';
@ -471,7 +496,7 @@ export function connect(preConfig: Config) {
let delayedActions = [];
let delayedStates = [];
const rootListener = (action) => {
const rootListener = (action: ContentScriptToPageScriptMessage) => {
if (autoPause) {
if (action.type === 'START') isPaused = false;
else if (action.type === 'STOP') isPaused = true;
@ -495,11 +520,14 @@ export function connect(preConfig: Config) {
const subscribe = (listener) => {
if (!listener) return undefined;
const liftedListener = liftListener(listener, config);
listeners[id].push(liftedListener);
const listenersForId = listeners[id] as ((
message: ContentScriptToPageScriptMessage
) => void)[];
listenersForId.push(liftedListener);
return function unsubscribe() {
const index = listeners[id].indexOf(liftedListener);
listeners[id].splice(index, 1);
const index = listenersForId.indexOf(liftedListener);
listenersForId.splice(index, 1);
};
};

View File

@ -1,4 +1,4 @@
let handleError: () => boolean;
let handleError: (() => boolean) | undefined;
let lastTime = 0;
function createExpBackoffTimer(step: number) {
@ -42,7 +42,7 @@ function catchErrors(e: ErrorEvent) {
postError(e.message);
}
export default function notifyErrors(onError: () => boolean) {
export default function notifyErrors(onError?: () => boolean) {
handleError = onError;
window.addEventListener('error', catchErrors, false);
}

View File

@ -1,12 +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) {
window.postMessage(
{
post({
source: '@devtools-page',
type: 'OPEN',
position: position || 'right',
},
'*'
);
});
}

View File

@ -15,11 +15,21 @@ import { getReport } from '../../browser/extension/background/logging';
import {
CustomAction,
DispatchAction as AppDispatchAction,
LibConfig,
LiftedActionAction,
StoreAction,
} from '@redux-devtools/app/lib/actions';
import { Action, Dispatch } from 'redux';
import { ContentScriptToBackgroundMessage } from '../../browser/extension/inject/contentScript';
import {
ContentScriptToBackgroundMessage,
SplitMessage,
} from '../../browser/extension/inject/contentScript';
import {
ErrorMessage,
PageScriptToContentScriptMessageForwardedToMonitors,
PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance,
} from '../api';
import { LiftedState } from '@redux-devtools/instrument';
interface TabMessageBase {
readonly type: string;
@ -72,8 +82,85 @@ interface NAAction {
readonly id: string;
}
interface UpdateStateAction {
interface InitMessage<S, A extends Action<unknown>> {
readonly type: 'INIT';
readonly payload: string;
readonly instanceId: string;
readonly source: '@devtools-page';
action?: string;
name?: string | undefined;
liftedState?: LiftedState<S, A, unknown>;
libConfig?: LibConfig;
}
interface LiftedMessage {
readonly type: 'LIFTED';
readonly liftedState: { readonly isPaused: boolean | undefined };
readonly instanceId: string;
readonly 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';
readonly instanceId: string;
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';
readonly instanceId: string;
}
interface SerializedActionMessage {
readonly type: 'ACTION';
readonly payload: string;
readonly source: '@devtools-page';
readonly instanceId: string;
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';
readonly 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>;
interface UpdateStateAction<S, A extends Action<unknown>> {
readonly type: typeof UPDATE_STATE;
readonly request?: UpdateStateRequest<S, A>;
readonly id?: string | number;
}
export type TabMessage =
@ -84,17 +171,27 @@ export type TabMessage =
| ImportAction
| ActionAction
| ExportAction;
type PanelMessage = NAAction;
type MonitorMessage = UpdateStateAction;
type PanelMessage<S, A extends Action<unknown>> =
| NAAction
| ErrorMessage
| UpdateStateAction<S, A>;
type MonitorMessage<S, A extends Action<unknown>> =
| NAAction
| ErrorMessage
| UpdateStateAction<S, A>;
type TabPort = Omit<chrome.runtime.Port, 'postMessage'> & {
postMessage: (message: TabMessage) => void;
};
type PanelPort = Omit<chrome.runtime.Port, 'postMessage'> & {
postMessage: (message: PanelMessage) => void;
postMessage: <S, A extends Action<unknown>>(
message: PanelMessage<S, A>
) => void;
};
type MonitorPort = Omit<chrome.runtime.Port, 'postMessage'> & {
postMessage: (message: MonitorMessage) => void;
postMessage: <S, A extends Action<unknown>>(
message: MonitorMessage<S, A>
) => void;
};
const CONNECTED = 'socket/CONNECTED';
@ -108,17 +205,25 @@ const connections: {
panel: {},
monitor: {},
};
const chunks = {};
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 = NAAction;
type MonitorAction<S, A extends Action<unknown>> =
| NAAction
| ErrorMessage
| UpdateStateAction<S, A>;
function toMonitors(
action: MonitorAction,
function toMonitors<S, A extends Action<unknown>>(
action: MonitorAction<S, A>,
tabId?: string | number,
verbose?: boolean
) {
@ -195,12 +300,14 @@ function togglePersist() {
}
}
type BackgroundStoreMessage = unknown;
type BackgroundStoreMessage<S, A extends Action<unknown>> =
| PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<S, A>
| SplitMessage;
type BackgroundStoreResponse = { readonly options: Options };
// Receive messages from content scripts
function messaging(
request: BackgroundStoreMessage,
function messaging<S, A extends Action<unknown>>(
request: BackgroundStoreMessage<S, A>,
sender: chrome.runtime.MessageSender,
sendResponse?: (response?: BackgroundStoreResponse) => void
) {
@ -259,9 +366,13 @@ function messaging(
return;
}
const action = { type: UPDATE_STATE, request, id: tabId };
const action: UpdateStateAction<S, A> = {
type: UPDATE_STATE,
request,
id: tabId,
};
const instanceId = `${tabId}/${request.instanceId}`;
if (request.split) {
if ('split' in request) {
if (request.split === 'start') {
chunks[instanceId] = request;
return;

View File

@ -1,6 +1,10 @@
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) => {
if (!options['s:hostname'] || !options['s:port']) return;
const url = `${options['s:secure'] ? 'https' : 'http'}://${

View File

@ -18,7 +18,7 @@ const messageStyle: CSSProperties = {
textAlign: 'center',
};
let rendered: boolean;
let rendered: boolean | undefined;
let store: Store<StoreState, StoreAction> | undefined;
let bgConnection: chrome.runtime.Port;
let naTimeout: NodeJS.Timeout;

View File

@ -158,6 +158,8 @@ interface SplitMessageBase {
}
interface SplitMessageStart extends SplitMessageBase {
readonly instanceId: number;
readonly source: typeof pageSource;
readonly split: 'start';
}
@ -174,7 +176,10 @@ interface SplitMessageEnd extends SplitMessageBase {
readonly split: 'end';
}
type SplitMessage = SplitMessageStart | SplitMessageChunk | SplitMessageEnd;
export type SplitMessage =
| SplitMessageStart
| SplitMessageChunk
| SplitMessageEnd;
function tryCatch<S, A extends Action<unknown>>(
fn: (

View File

@ -36,11 +36,7 @@ import {
getSerializeParameter,
Serialize,
} from '../../../app/api';
import {
LiftedAction,
LiftedState,
PerformAction,
} from '@redux-devtools/instrument';
import { LiftedAction, LiftedState } from '@redux-devtools/instrument';
import {
CustomAction,
DispatchAction,
@ -63,7 +59,7 @@ function deprecateParam(oldParam: string, newParam: string) {
/* eslint-enable no-console */
}
interface SerializeWithImmutable extends Serialize {
export interface SerializeWithImmutable extends Serialize {
readonly immutable?: typeof Immutable;
readonly refs?: (new (data: any) => unknown)[] | null;
}
@ -81,12 +77,12 @@ export interface ConfigWithExpandedMaxAge {
| boolean
| ((key: string, value: unknown) => unknown)
| Serialize;
readonly statesFilter?: <S>(state: S, index: number) => S;
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 stateSanitizer?: <S>(state: S, index?: number) => S;
readonly actionSanitizer?: <A extends Action<unknown>>(
action: A,
id: number
@ -117,6 +113,7 @@ export interface ConfigWithExpandedMaxAge {
name?: string;
readonly autoPause?: boolean;
readonly features?: Features;
readonly type?: string;
}
export interface Config extends ConfigWithExpandedMaxAge {
@ -131,7 +128,7 @@ interface ReduxDevtoolsExtension {
): Store<S, A>;
(config?: Config): StoreEnhancer;
open: (position?: Position) => void;
notifyErrors: (onError: () => boolean) => void;
notifyErrors: (onError?: () => boolean) => void;
disconnect: () => void;
}