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

View File

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

View File

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

View File

@ -7,8 +7,13 @@ import { TabMessage } from '../../../app/middlewares/api';
import { import {
PageScriptToContentScriptMessage, PageScriptToContentScriptMessage,
PageScriptToContentScriptMessageWithoutDisconnect, PageScriptToContentScriptMessageWithoutDisconnect,
PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance,
} from '../../../app/api'; } from '../../../app/api';
import { Action } from 'redux'; import { Action } from 'redux';
import {
CustomAction,
DispatchAction as AppDispatchAction,
} from '@redux-devtools/app/lib/actions';
const source = '@devtools-extension'; const source = '@devtools-extension';
const pageSource = '@devtools-page'; const pageSource = '@devtools-page';
// Chrome message limit is 64 MB, but we're using 32 MB to include other object's parts // 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() { function connect() {
// Connect to the background script // Connect to the background script
connected = true; connected = true;
@ -35,28 +107,40 @@ function connect() {
// Relay background script messages to the page script // Relay background script messages to the page script
bg.onMessage.addListener((message: TabMessage) => { bg.onMessage.addListener((message: TabMessage) => {
if ('action' in message) { if ('action' in message) {
window.postMessage( if (message.type === 'DISPATCH') {
{ postToPageScript({
type: message.type, type: message.type,
payload: message.action, payload: message.action,
state: message.state, state: message.state,
id: message.id, id: message.id,
source, source,
}, });
'*' } else if (message.type === 'ACTION') {
); postToPageScript({
} else if ('options' in message) {
injectOptions(message.options);
} else {
window.postMessage(
{
type: message.type, type: message.type,
payload: message.action,
state: message.state, state: message.state,
id: message.id, id: message.id,
source, 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; type SplitMessage = SplitMessageStart | SplitMessageChunk | SplitMessageEnd;
function tryCatch<S, A extends Action<unknown>>( 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> args: PageScriptToContentScriptMessageWithoutDisconnect<S, A>
) { ) {
try { try {
@ -145,21 +231,27 @@ interface InitInstanceContentScriptToBackgroundMessage {
readonly instanceId: number; readonly instanceId: number;
} }
interface RelayMessage { interface RelayMessage<S, A extends Action<unknown>> {
readonly name: 'RELAY'; readonly name: 'RELAY';
readonly message: unknown; readonly message:
| PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<S, A>
| SplitMessage;
} }
export type ContentScriptToBackgroundMessage = export type ContentScriptToBackgroundMessage<S, A extends Action<unknown>> =
| InitInstanceContentScriptToBackgroundMessage | InitInstanceContentScriptToBackgroundMessage
| RelayMessage; | RelayMessage<S, A>;
function postToBackground(message: ContentScriptToBackgroundMessage) { function postToBackground<S, A extends Action<unknown>>(
message: ContentScriptToBackgroundMessage<S, A>
) {
bg!.postMessage(message); bg!.postMessage(message);
} }
function send<S, A extends Action<unknown>>( function send<S, A extends Action<unknown>>(
message: PageScriptToContentScriptMessage<S, A> | SplitMessage message:
| PageScriptToContentScriptMessageWithoutDisconnect<S, A>
| SplitMessage
) { ) {
if (!connected) connect(); if (!connected) connect();
if (message.type === 'INIT_INSTANCE') { if (message.type === 'INIT_INSTANCE') {

View File

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

View File

@ -268,7 +268,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(
@ -354,7 +354,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;

View File

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

View File

@ -26,7 +26,7 @@ export function nonReduxDispatch(
instanceId: string, instanceId: string,
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];