diff --git a/src/constants/index.js b/src/constants/index.js index 2fdd7184c..196f03a70 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,3 +1,7 @@ +// Message type for store action events from +// background to Proxy Stores +export const ACTION_TYPE = 'chromex.action'; + // Message type used for dispatch events // from the Proxy Stores to background export const DISPATCH_TYPE = 'chromex.dispatch'; diff --git a/src/store/Store.js b/src/store/Store.js index 3f071a29c..b052d893d 100644 --- a/src/store/Store.js +++ b/src/store/Store.js @@ -1,6 +1,7 @@ import assignIn from 'lodash.assignin'; import { + ACTION_TYPE, DISPATCH_TYPE, STATE_TYPE, PATCH_STATE_TYPE, @@ -62,6 +63,10 @@ class Store { // Don't use shouldDeserialize here, since no one else should be using this port this.serializedPortListener(message => { switch (message.type) { + case ACTION_TYPE: + this.dispatch({ ...message.payload, fromBackground: true }); + break; + case STATE_TYPE: this.replaceState(message.payload); @@ -148,7 +153,11 @@ class Store { * @param {object} data The action data to dispatch * @return {Promise} Promise that will resolve/reject based on the action response from the background */ - dispatch(data) { + dispatch({ fromBackground, ...data } = {}) { + if (Boolean(fromBackground)) { + // don't need to message this dispatch back to the background because it originated there + return Promise.resolve(true) + } return new Promise((resolve, reject) => { this.serializedMessageSender( this.extensionId, diff --git a/src/wrap-store/wrapStore.js b/src/wrap-store/wrapStore.js index 01eb7c76d..3647bca63 100644 --- a/src/wrap-store/wrapStore.js +++ b/src/wrap-store/wrapStore.js @@ -1,4 +1,6 @@ +import applyMiddleware from "../store/applyMiddleware"; import { + ACTION_TYPE, DISPATCH_TYPE, STATE_TYPE, PATCH_STATE_TYPE, @@ -90,6 +92,21 @@ export default (store, { } }; + let actionListeners = []; + const subscribeToActions = (fn) => { + actionListeners.push(fn); + // return unsubscribe function + return () => { + actionListeners = actionListeners.filter((f) => f !== fn); + }; + }; + const postActionToListeners = ({ _sender, ...action }) => { + // actions with _sender came down from remote proxy stores so don't need to send them back + if (!Boolean(_sender)) { + actionListeners.forEach((fn) => fn(action)); + } + }; + /** * Setup for state updates */ @@ -116,11 +133,23 @@ export default (store, { } }; + // Send event message down connected port on every redux action + const unsubscribeActionsSubscription = subscribeToActions((action) => { + serializedMessagePoster({ + type: ACTION_TYPE, + payload: action, + }); + }); // Send patched state down connected port on every redux store state change - const unsubscribe = store.subscribe(patchState); + const unsubscribeStoreSubscription = store.subscribe(patchState); + + const unsubscribeAll = () => { + unsubscribeActionsSubscription(); + unsubscribeStoreSubscription(); + }; // when the port disconnects, unsubscribe the sendState listener - port.onDisconnect.addListener(unsubscribe); + port.onDisconnect.addListener(unsubscribeAll); // Send store's initial state through port serializedMessagePoster({ @@ -178,4 +207,10 @@ export default (store, { // TODO: Find use case for this. Ommiting until then. // browserAPI.runtime.sendMessage(null, {action: 'storeReady'}); + const postActionMiddleware = (_) => (next) => (action) => { + postActionToListeners(action); + return next(action); + }; + + applyMiddleware(store, postActionMiddleware); };