More work

This commit is contained in:
Nathan Bierema 2021-08-18 21:33:46 -04:00
parent c80473329e
commit ca68117921
8 changed files with 194 additions and 69 deletions

View File

@ -28,7 +28,8 @@
"test:app": "cross-env BABEL_ENV=test jest test/app",
"test:chrome": "jest test/chrome",
"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": {
"@redux-devtools/app": "^1.0.0-8",

View File

@ -357,6 +357,7 @@ export interface ErrorMessage {
readonly payload: string;
readonly source: typeof source;
readonly instanceId: number;
readonly message?: string | undefined;
}
interface InitInstanceMessage {
@ -449,16 +450,34 @@ export function sendMessage<S, A extends Action<unknown>>(
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);
if (action) {
toContentScript(
{
type: 'ACTION',
action: amendedAction,
payload: state,
maxAge: config.maxAge,
source,
name: config.name || name,
instanceId: config.instanceId || instanceId || 1,
},
config.serialize,
config.serialize
);
}
toContentScript(
{
type: 'STATE',
action: amendedAction,
payload: state,
maxAge: config.maxAge,
source,
name: config.name || name,
instanceId: config.instanceId || instanceId || 1,
},
config.serialize,
config.serialize
);
}
function handleMessages(event: MessageEvent<ContentScriptToPageScriptMessage>) {
@ -609,7 +628,11 @@ export function connect(preConfig: Config) {
return;
}
}
sendMessage(amendedAction, amendedState, config);
sendMessage(
amendedAction as StructuralPerformAction<A>,
amendedState,
config
);
};
const init = <S, A extends Action<unknown>>(

View File

@ -7,6 +7,8 @@ import Actions from '@redux-devtools/app/lib/containers/Actions';
import Header from '@redux-devtools/app/lib/components/Header';
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';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
@ -15,13 +17,17 @@ interface OwnProps {
}
type Props = StateProps & DispatchProps & OwnProps;
function sendMessage(message: SingleMessage) {
chrome.runtime.sendMessage(message);
}
class App extends Component<Props> {
openWindow = (position: string) => {
chrome.runtime.sendMessage({ type: 'OPEN', position });
openWindow = (position: Position) => {
sendMessage({ type: 'OPEN', position });
};
openOptionsPage = () => {
if (navigator.userAgent.indexOf('Firefox') !== -1) {
chrome.runtime.sendMessage({ type: 'OPEN_OPTIONS' });
sendMessage({ type: 'OPEN_OPTIONS' });
} else {
chrome.runtime.openOptionsPage();
}

View File

@ -10,7 +10,9 @@ import syncOptions, {
OptionsMessage,
SyncOptions,
} from '../../browser/extension/options/syncOptions';
import openDevToolsWindow from '../../browser/extension/background/openWindow';
import openDevToolsWindow, {
DevToolsPosition,
} from '../../browser/extension/background/openWindow';
import { getReport } from '../../browser/extension/background/logging';
import {
CustomAction,
@ -32,6 +34,7 @@ import {
BackgroundAction,
LiftedActionAction,
} from '../stores/backgroundStore';
import { Position } from '../api/openWindow';
interface TabMessageBase {
readonly type: string;
@ -87,7 +90,7 @@ interface NAAction {
interface InitMessage<S, A extends Action<unknown>> {
readonly type: 'INIT';
readonly payload: string;
readonly instanceId: string;
instanceId: string;
readonly source: '@devtools-page';
action?: string;
name?: string | undefined;
@ -98,7 +101,7 @@ interface InitMessage<S, A extends Action<unknown>> {
interface LiftedMessage {
readonly type: 'LIFTED';
readonly liftedState: { readonly isPaused: boolean | undefined };
readonly instanceId: string;
instanceId: number;
readonly source: '@devtools-page';
}
@ -112,7 +115,7 @@ interface SerializedPartialStateMessage {
readonly type: 'PARTIAL_STATE';
readonly payload: SerializedPartialLiftedState;
readonly source: '@devtools-page';
readonly instanceId: string;
instanceId: number;
readonly maxAge: number;
readonly actionsById: string;
readonly computedStates: string;
@ -124,14 +127,14 @@ interface SerializedExportMessage {
readonly payload: string;
readonly committedState: string | undefined;
readonly source: '@devtools-page';
readonly instanceId: string;
instanceId: number;
}
interface SerializedActionMessage {
readonly type: 'ACTION';
readonly payload: string;
readonly source: '@devtools-page';
readonly instanceId: string;
instanceId: number;
readonly action: string;
readonly maxAge: number;
readonly nextActionId: number;
@ -144,7 +147,7 @@ interface SerializedStateMessage<S, A extends Action<unknown>> {
'actionsById' | 'computedStates' | 'committedState'
>;
readonly source: '@devtools-page';
readonly instanceId: string;
instanceId: string;
readonly libConfig?: LibConfig;
readonly actionsById: string;
readonly computedStates: string;
@ -165,7 +168,7 @@ interface EmptyUpdateStateAction {
interface UpdateStateAction<S, A extends Action<unknown>> {
readonly type: typeof UPDATE_STATE;
readonly request: UpdateStateRequest<S, A>;
request: UpdateStateRequest<S, A>;
readonly id: string | number;
}
@ -195,8 +198,8 @@ type MonitorPort = Omit<chrome.runtime.Port, 'postMessage'> & {
postMessage: (message: MonitorMessage) => void;
};
const CONNECTED = 'socket/CONNECTED';
const DISCONNECTED = 'socket/DISCONNECTED';
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 };
@ -248,19 +251,78 @@ interface ImportMessage {
type ToContentScriptMessage = ImportMessage | LiftedActionAction;
function toContentScript({
message,
action,
id,
instanceId,
state,
}: ToContentScriptMessage) {
connections.tab[id!].postMessage({
type: message,
action,
state: nonReduxDispatch(window.store, message, instanceId, action, state),
id: instanceId.toString().replace(/^[^\/]+\//, ''),
});
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) {
@ -302,9 +364,28 @@ function togglePersist() {
}
}
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;
| SplitMessage
| SingleMessage;
type BackgroundStoreResponse = { readonly options: Options };
// Receive messages from content scripts
@ -338,13 +419,13 @@ function messaging<S, A extends Action<unknown>>(
return;
}
if (request.type === 'OPEN') {
let position = 'devtools-left';
let position: DevToolsPosition = 'devtools-left';
if (
['remote', 'panel', 'left', 'right', 'bottom'].indexOf(
request.position
) !== -1
) {
position = 'devtools-' + request.position;
position = ('devtools-' + request.position) as DevToolsPosition;
}
openDevToolsWindow(position);
return;
@ -372,19 +453,20 @@ function messaging<S, A extends Action<unknown>>(
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;
chunks[instanceId] = request as any;
return;
}
if (request.split === 'chunk') {
chunks[instanceId][request.chunk[0]] =
(chunks[instanceId][request.chunk[0]] || '') + request.chunk[1];
(chunks[instanceId] as any)[request.chunk[0]] =
((chunks[instanceId] as any)[request.chunk[0]] || '') +
request.chunk[1];
return;
}
action.request = chunks[instanceId];
action.request = chunks[instanceId] as any;
delete chunks[instanceId];
}
if (request.instanceId) {

View File

@ -1,6 +1,6 @@
import { createStore, applyMiddleware, PreloadedState } from 'redux';
import rootReducer, { BackgroundState } from '../reducers/background';
import api from '../middlewares/api';
import api, { CONNECTED, DISCONNECTED } from '../middlewares/api';
import { LIFTED_ACTION } from '@redux-devtools/app/lib/constants/actionTypes';
import {
CustomAction,
@ -26,6 +26,7 @@ interface LiftedActionImportAction extends LiftedActionActionBase {
message: 'IMPORT';
state: string;
preloadedState?: unknown | undefined;
action?: never;
}
interface LiftedActionActionAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
@ -36,6 +37,7 @@ interface LiftedActionExportAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
message: 'EXPORT';
toExport: boolean;
action?: never;
}
export type LiftedActionAction =
| LiftedActionDispatchAction
@ -49,10 +51,20 @@ interface TogglePersistAction {
readonly id: string | number | undefined;
}
interface ConnectedAction {
readonly type: typeof CONNECTED;
}
interface DisconnectedAction {
readonly type: typeof DISCONNECTED;
}
export type BackgroundAction =
| StoreActionWithoutLiftedAction
| LiftedActionAction
| TogglePersistAction;
| TogglePersistAction
| ConnectedAction
| DisconnectedAction;
export default function configureStore(
preloadedState?: PreloadedState<BackgroundState>

View File

@ -1,48 +1,48 @@
// Mock not supported chrome.* API for Firefox and Electron
window.isElectron =
(window as any).isElectron =
window.navigator && window.navigator.userAgent.indexOf('Electron') !== -1;
const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1;
// Background page only
if (
(window.isElectron &&
((window as any).isElectron &&
location.pathname === '/_generated_background_page.html') ||
isFirefox
) {
chrome.runtime.onConnectExternal = {
(chrome.runtime as any).onConnectExternal = {
addListener() {},
};
chrome.runtime.onMessageExternal = {
(chrome.runtime as any).onMessageExternal = {
addListener() {},
};
if (window.isElectron) {
chrome.notifications = {
if ((window as any).isElectron) {
(chrome.notifications as any) = {
onClicked: {
addListener() {},
},
create() {},
clear() {},
};
chrome.contextMenus = {
(chrome.contextMenus as any) = {
onClicked: {
addListener() {},
},
};
} else {
chrome.storage.sync = chrome.storage.local;
chrome.runtime.onInstalled = {
addListener: (cb) => cb(),
(chrome.storage as any).sync = chrome.storage.local;
(chrome.runtime as any).onInstalled = {
addListener: (cb: any) => cb(),
};
}
}
if (window.isElectron) {
if ((window as any).isElectron) {
if (!chrome.storage.local || !chrome.storage.local.remove) {
chrome.storage.local = {
set(obj, callback) {
(chrome.storage as any).local = {
set(obj: any, callback: any) {
Object.keys(obj).forEach((key) => {
localStorage.setItem(key, obj[key]);
});
@ -50,8 +50,8 @@ if (window.isElectron) {
callback();
}
},
get(obj, callback) {
const result = {};
get(obj: any, callback: any) {
const result: any = {};
Object.keys(obj).forEach((key) => {
result[key] = localStorage.getItem(key) || obj[key];
});
@ -60,7 +60,7 @@ if (window.isElectron) {
}
},
// Electron ~ 1.4.6
remove(items, callback) {
remove(items: any, callback: any) {
if (Array.isArray(items)) {
items.forEach((name) => {
localStorage.removeItem(name);
@ -75,7 +75,7 @@ if (window.isElectron) {
};
}
// 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 () {
if (process.env.NODE_ENV === 'development') {
return originSendMessage(...arguments);
@ -87,6 +87,6 @@ if (window.isElectron) {
};
}
if (isFirefox || window.isElectron) {
chrome.storage.sync = chrome.storage.local;
if (isFirefox || (window as any).isElectron) {
(chrome.storage as any).sync = chrome.storage.local;
}

View File

@ -1,6 +1,8 @@
// Include this script in Chrome apps and extensions for remote debugging
// <script src="chrome-extension://lmhkpmbekcpmknklioeibfkpmmfibljd/js/redux-devtools-extension.bundle.js"></script>
import { Options } from '../options/syncOptions';
window.devToolsExtensionID = 'lmhkpmbekcpmknklioeibfkpmmfibljd';
require('./contentScript');
require('./pageScript');
@ -8,7 +10,7 @@ require('./pageScript');
chrome.runtime.sendMessage(
window.devToolsExtensionID,
{ type: 'GET_OPTIONS' },
function (response) {
function (response: { readonly options: Options }) {
if (!response.options.inject) {
const urls = response.options.urls.split('\n').filter(Boolean).join('|');
if (!location.href.match(new RegExp(urls))) return;

View File

@ -5,7 +5,6 @@ import { SET_STATE } from '../constants/actionTypes';
import { InstancesState, State } from '../reducers/instances';
import { Dispatch, MiddlewareAPI } from 'redux';
import { DispatchAction, StoreAction } from '../actions';
import { StoreState } from '../reducers';
export function sweep(state: State): State {
return {