Define ContentScriptToPageScriptMessage

This commit is contained in:
Nathan Bierema 2021-07-17 18:03:28 -04:00
parent eb35441e17
commit d23bf68b15
8 changed files with 219 additions and 60 deletions

View File

@ -8,6 +8,7 @@ import generateId from './generateInstanceId';
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';
const listeners = {};
export const source = '@devtools-page';
@ -125,7 +126,7 @@ interface InitMessage<S, A extends Action<unknown>> {
action?: string;
name?: string | undefined;
liftedState?: LiftedState<S, A, unknown>;
libConfig?: unknown;
libConfig?: LibConfig;
}
interface SerializedPartialLiftedState<S, A extends Action<unknown>> {
@ -171,17 +172,15 @@ interface SerializedStateMessage<S, A extends Action<unknown>> {
>;
readonly source: typeof source;
readonly instanceId: number;
readonly libConfig?: unknown;
readonly libConfig?: LibConfig;
readonly actionsById: string;
readonly computedStates: string;
readonly committedState: boolean;
}
export type PageScriptToContentScriptMessageWithoutDisconnect<
export type PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<
S,
A extends Action<unknown>
> =
| InitInstancePageScriptToContentScriptMessage
| InitMessage<S, A>
| LiftedMessage
| SerializedPartialStateMessage<S, A>
@ -193,6 +192,13 @@ export type PageScriptToContentScriptMessageWithoutDisconnect<
| GetReportMessage
| StopMessage;
export type PageScriptToContentScriptMessageWithoutDisconnect<
S,
A extends Action<unknown>
> =
| PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<S, A>
| InitInstancePageScriptToContentScriptMessage;
export type PageScriptToContentScriptMessage<S, A extends Action<unknown>> =
| PageScriptToContentScriptMessageWithoutDisconnect<S, A>
| DisconnectMessage;
@ -258,7 +264,7 @@ function amendActionType(
export interface LiftedMessage {
readonly type: 'LIFTED';
readonly liftedState: { readonly isPaused: boolean };
readonly liftedState: { readonly isPaused: boolean | undefined };
readonly instanceId: number;
readonly source: typeof source;
}
@ -294,7 +300,7 @@ interface StateMessage<S, A extends Action<unknown>> {
readonly payload: LiftedState<S, A, unknown>;
readonly source: typeof source;
readonly instanceId: number;
readonly libConfig?: unknown;
readonly libConfig?: LibConfig;
}
interface ErrorMessage {
@ -447,7 +453,7 @@ export function disconnect() {
post({ type: 'DISCONNECT', source });
}
export function connect(preConfig) {
export function connect(preConfig: Config) {
const config = preConfig || {};
const id = generateId(config.instanceId);
if (!config.instanceId) config.instanceId = id;

View File

@ -6,20 +6,65 @@ import {
} 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 from '../../browser/extension/background/openWindow';
import { getReport } from '../../browser/extension/background/logging';
import { StoreAction } from '@redux-devtools/app/lib/actions';
import { Dispatch } from 'redux';
import {
CustomAction,
DispatchAction as AppDispatchAction,
LiftedActionAction,
StoreAction,
} from '@redux-devtools/app/lib/actions';
import { Action, Dispatch } from 'redux';
import { ContentScriptToBackgroundMessage } from '../../browser/extension/inject/contentScript';
interface StartAction {
readonly type: 'START';
interface TabMessageBase {
readonly type: string;
readonly state?: string | undefined;
readonly id?: string;
}
interface StopAction {
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;
}
interface NAAction {
@ -31,7 +76,14 @@ interface UpdateStateAction {
readonly type: typeof UPDATE_STATE;
}
export type TabMessage = StartAction | StopAction | OptionsMessage;
export type TabMessage =
| StartAction
| StopAction
| OptionsMessage
| DispatchAction
| ImportAction
| ActionAction
| ExportAction;
type PanelMessage = NAAction;
type MonitorMessage = UpdateStateAction;
@ -87,7 +139,7 @@ interface ImportMessage {
readonly state: string;
}
type ToContentScriptMessage = ImportMessage;
type ToContentScriptMessage = ImportMessage | LiftedActionAction;
function toContentScript({
message,
@ -144,13 +196,13 @@ function togglePersist() {
}
type BackgroundStoreMessage = unknown;
type BackgroundStoreResponse = never;
type BackgroundStoreResponse = { readonly options: Options };
// Receive messages from content scripts
function messaging(
request: BackgroundStoreMessage,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: BackgroundStoreResponse) => void
sendResponse?: (response?: BackgroundStoreResponse) => void
) {
let tabId = getId(sender);
if (!tabId) return;
@ -168,7 +220,7 @@ function messaging(
}
if (request.type === 'GET_OPTIONS') {
window.syncOptions.get((options) => {
sendResponse({ options });
sendResponse!({ options });
});
return;
}
@ -256,7 +308,7 @@ function disconnect(
};
}
function onConnect(port: chrome.runtime.Port) {
function onConnect<S, A extends Action<unknown>>(port: chrome.runtime.Port) {
let id: number | string;
let listener;
@ -266,7 +318,7 @@ function onConnect(port: chrome.runtime.Port) {
id = getId(port.sender!);
if (port.sender!.frameId) id = `${id}-${port.sender!.frameId}`;
connections.tab[id] = port;
listener = (msg) => {
listener = (msg: ContentScriptToBackgroundMessage<S, A>) => {
if (msg.name === 'INIT_INSTANCE') {
if (typeof id === 'number') {
chrome.pageAction.show(id);
@ -292,7 +344,7 @@ function onConnect(port: chrome.runtime.Port) {
return;
}
if (msg.name === 'RELAY') {
messaging(msg.message, port.sender!, id);
messaging(msg.message, port.sender!);
}
};
port.onMessage.addListener(listener);

View File

@ -1,6 +1,6 @@
import { Action } from 'redux';
import { LiftedState } from '@redux-devtools/instrument';
import { StoreAction } from '@redux-devtools/app/lib/actions';
import { LibConfig, StoreAction } from '@redux-devtools/app/lib/actions';
declare global {
interface Window {
@ -11,7 +11,7 @@ declare global {
export default class Monitor<S, A extends Action<unknown>> {
update: (
liftedState?: LiftedState<S, A, unknown> | undefined,
libConfig?: unknown
libConfig?: LibConfig
) => void;
active?: boolean;
paused?: boolean;
@ -19,7 +19,7 @@ export default class Monitor<S, A extends Action<unknown>> {
constructor(
update: (
liftedState?: LiftedState<S, A, unknown> | undefined,
libConfig?: unknown
libConfig?: LibConfig
) => void
) {
this.update = update;

View File

@ -7,8 +7,13 @@ 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';
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
@ -22,6 +27,73 @@ declare global {
}
}
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;
function postToPageScript(message: ContentScriptToPageScriptMessage) {
window.postMessage(message, '*');
}
function connect() {
// Connect to the background script
connected = true;
@ -35,28 +107,40 @@ function connect() {
// Relay background script messages to the page script
bg.onMessage.addListener((message: TabMessage) => {
if ('action' in message) {
window.postMessage(
{
if (message.type === 'DISPATCH') {
postToPageScript({
type: message.type,
payload: message.action,
state: message.state,
id: message.id,
source,
},
'*'
);
} else if ('options' in message) {
injectOptions(message.options);
} else {
window.postMessage(
{
});
} 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,
});
}
});
@ -93,7 +177,9 @@ interface SplitMessageEnd extends SplitMessageBase {
type SplitMessage = SplitMessageStart | SplitMessageChunk | SplitMessageEnd;
function tryCatch<S, A extends Action<unknown>>(
fn: (args: PageScriptToContentScriptMessage<S, A> | SplitMessage) => void,
fn: (
args: PageScriptToContentScriptMessageWithoutDisconnect<S, A> | SplitMessage
) => void,
args: PageScriptToContentScriptMessageWithoutDisconnect<S, A>
) {
try {
@ -145,21 +231,27 @@ interface InitInstanceContentScriptToBackgroundMessage {
readonly instanceId: number;
}
interface RelayMessage {
interface RelayMessage<S, A extends Action<unknown>> {
readonly name: 'RELAY';
readonly message: unknown;
readonly message:
| PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<S, A>
| SplitMessage;
}
export type ContentScriptToBackgroundMessage =
export type ContentScriptToBackgroundMessage<S, A extends Action<unknown>> =
| InitInstanceContentScriptToBackgroundMessage
| RelayMessage;
| RelayMessage<S, A>;
function postToBackground(message: ContentScriptToBackgroundMessage) {
function postToBackground<S, A extends Action<unknown>>(
message: ContentScriptToBackgroundMessage<S, A>
) {
bg!.postMessage(message);
}
function send<S, A extends Action<unknown>>(
message: PageScriptToContentScriptMessage<S, A> | SplitMessage
message:
| PageScriptToContentScriptMessageWithoutDisconnect<S, A>
| SplitMessage
) {
if (!connected) connect();
if (message.type === 'INIT_INSTANCE') {

View File

@ -41,6 +41,13 @@ import {
LiftedState,
PerformAction,
} 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';
const source = '@devtools-page';
let stores: {
@ -62,10 +69,10 @@ interface SerializeWithImmutable extends Serialize {
}
export interface ConfigWithExpandedMaxAge {
readonly instanceId?: number;
instanceId?: number;
readonly actionsBlacklist?: string | readonly string[];
readonly actionsWhitelist?: string | readonly string[];
readonly serialize?: boolean | SerializeWithImmutable;
serialize?: boolean | SerializeWithImmutable;
readonly serializeState?:
| boolean
| ((key: string, value: unknown) => unknown)
@ -107,7 +114,9 @@ export interface ConfigWithExpandedMaxAge {
readonly pauseActionType?: unknown;
readonly deserializeState?: <S>(state: S) => S;
readonly deserializeAction?: <A extends Action<unknown>>(action: A) => A;
readonly name?: string;
name?: string;
readonly autoPause?: boolean;
readonly features?: Features;
}
export interface Config extends ConfigWithExpandedMaxAge {
@ -182,7 +191,7 @@ function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<unknown>>(
const relayState = throttle(
(
liftedState?: LiftedState<S, A, unknown> | undefined,
libConfig?: unknown
libConfig?: LibConfig
) => {
relayAction.cancel();
const state = liftedState || store.liftedStore.getState();
@ -328,8 +337,8 @@ function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<unknown>>(
);
}, latency);
function dispatchRemotely(action) {
if (config.features && !config.features.dispatch) return;
function dispatchRemotely(action: string | CustomAction) {
if (config!.features && !config!.features.dispatch) return;
try {
const result = evalAction(action, actionCreators);
(store.initialDispatch || store.dispatch)(result);
@ -367,7 +376,7 @@ function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<unknown>>(
}
}
function dispatchMonitorAction(action) {
function dispatchMonitorAction(action: DispatchAction) {
const type = action.type;
const features = config.features;
if (features) {
@ -393,7 +402,7 @@ function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<unknown>>(
store.liftedStore.dispatch(action);
}
function onMessage(message) {
function onMessage(message: ContentScriptToPageScriptMessage) {
switch (message.type) {
case 'DISPATCH':
dispatchMonitorAction(message.payload);
@ -416,10 +425,10 @@ function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<unknown>>(
actionCreators = getActionsArray(config.actionCreators);
}
relayState(undefined, {
name: config.name || document.title,
name: config!.name || document.title,
actionCreators: JSON.stringify(actionCreators),
features: config.features,
serialize: !!config.serialize,
features: config!.features,
serialize: !!config!.serialize,
type: 'redux',
});

View File

@ -268,7 +268,7 @@ export function pauseRecording(status: boolean): LiftedActionDispatchAction {
export interface CustomAction {
name: string;
selected: number;
args: (string | undefined)[];
args: string[];
rest: string;
}
export function dispatchRemotely(
@ -354,7 +354,7 @@ export interface ActionCreator {
name: string;
}
interface LibConfig {
export interface LibConfig {
actionCreators?: string;
name?: string;
type?: string;

View File

@ -55,7 +55,7 @@ type Props = DispatchProps & OwnProps;
interface State {
selected: 'default' | number;
customAction: string;
args: (string | undefined)[];
args: string[];
rest: string;
changed: boolean;
}
@ -108,7 +108,7 @@ class Dispatcher extends Component<Props, State> {
handleArg = (argIndex: number) => (value: string) => {
const args = [
...this.state.args.slice(0, argIndex),
value || undefined,
(value || undefined)!,
...this.state.args.slice(argIndex + 1),
];
this.setState({ args, changed: true });

View File

@ -26,7 +26,7 @@ export function nonReduxDispatch(
instanceId: string,
action: DispatchAction,
initialState: string | undefined,
preInstances: InstancesState
preInstances?: InstancesState
) {
const instances = preInstances || store.getState().instances;
const state = instances.states[instanceId];