Skip to content

Commit

Permalink
remove external messaging (connecting to other extensions)
Browse files Browse the repository at this point in the history
Previously, we used ports to know which external
contexts were interested in store updates. We
don't use ports anymore, so there isn't an obvious
mechanism to know which external contexts are
interested in store updates.

This means webext-redux can't be used across
extensions anymore. I'm not sure how often this
was used [1], but if there's use-cases for this,
we could consider tracking contexts that recently
requested messages and broadcast state updates to
them, in addition to broadcasting state updates to
the open tabs and our own extension pages.

1: Tried searching GitHub but found no results.
This indicates the `extensionId` option is never
used in open-source code. Or I didn't use the
right search query.

https://github.com/search?q=webext-redux+extensionId&type=code
  • Loading branch information
SidneyNemzer committed Jun 12, 2024
1 parent bec03ae commit 5c7d998
Show file tree
Hide file tree
Showing 4 changed files with 16 additions and 42 deletions.
11 changes: 4 additions & 7 deletions src/store/Store.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const backgroundErrPrefix = '\nLooks like there is an error in the background pa
const defaultOpts = {
portName: DEFAULT_PORT_NAME,
state: {},
extensionId: null,
serializer: noop,
deserializer: noop,
patchStrategy: shallowDiff
Expand All @@ -27,9 +26,9 @@ const defaultOpts = {
class Store {
/**
* Creates a new Proxy store
* @param {object} options An object of form {portName, state, extensionId, serializer, deserializer, diffStrategy}, where `portName` is a required string and defines the name of the port for state transition changes, `state` is the initial state of this store (default `{}`) `extensionId` is the extension id as defined by browserAPI when extension is loaded (default `''`), `serializer` is a function to serialize outgoing message payloads (default is passthrough), `deserializer` is a function to deserialize incoming message payloads (default is passthrough), and patchStrategy is one of the included patching strategies (default is shallow diff) or a custom patching function.
* @param {object} options An object of form {portName, state, serializer, deserializer, diffStrategy}, where `portName` is a required string and defines the name of the port for state transition changes, `state` is the initial state of this store (default `{}`) `serializer` is a function to serialize outgoing message payloads (default is passthrough), `deserializer` is a function to deserialize incoming message payloads (default is passthrough), and patchStrategy is one of the included patching strategies (default is shallow diff) or a custom patching function.
*/
constructor({portName = defaultOpts.portName, state = defaultOpts.state, extensionId = defaultOpts.extensionId, serializer = defaultOpts.serializer, deserializer = defaultOpts.deserializer, patchStrategy = defaultOpts.patchStrategy} = defaultOpts) {
constructor({portName = defaultOpts.portName, state = defaultOpts.state, serializer = defaultOpts.serializer, deserializer = defaultOpts.deserializer, patchStrategy = defaultOpts.patchStrategy} = defaultOpts) {
if (!portName) {
throw new Error('portName is required in options');
}
Expand All @@ -48,17 +47,16 @@ class Store {
this.readyPromise = new Promise(resolve => this.readyResolve = resolve);

this.browserAPI = getBrowserAPI();
this.extensionId = extensionId; // keep the extensionId as an instance variable
this.initializeStore = this.initializeStore.bind(this);

// We request the latest available state data to initialise our store
this.browserAPI.runtime.sendMessage(
this.extensionId, { type: FETCH_STATE_TYPE, portName }, undefined, this.initializeStore
{ type: FETCH_STATE_TYPE, portName }, undefined, this.initializeStore
);

this.deserializer = deserializer;
this.serializedPortListener = withDeserializer(deserializer)((...args) => this.browserAPI.runtime.onMessage.addListener(...args));
this.serializedMessageSender = withSerializer(serializer)((...args) => this.browserAPI.runtime.sendMessage(...args), 1);
this.serializedMessageSender = withSerializer(serializer)((...args) => this.browserAPI.runtime.sendMessage(...args), 0);
this.listeners = [];
this.state = state;
this.patchStrategy = patchStrategy;
Expand Down Expand Up @@ -157,7 +155,6 @@ class Store {
dispatch(data) {
return new Promise((resolve, reject) => {
this.serializedMessageSender(
this.extensionId,
{
type: DISPATCH_TYPE,
portName: this.portName,
Expand Down
6 changes: 0 additions & 6 deletions src/wrap-store/wrapStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,6 @@ export default () => {
browserAPI.runtime.onMessage.addListener(stateProviderListener.listener);
browserAPI.runtime.onMessage.addListener(actionListener.listener);

if (browserAPI.runtime.onMessageExternal) {
browserAPI.runtime.onMessageExternal.addListener(actionListener.listener);
} else {
console.warn("runtime.onMessageExternal is not supported");
}

return (
store,
{
Expand Down
39 changes: 11 additions & 28 deletions test/Store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe("Store", function () {
},
};
},
sendMessage(extensionId, data, options, cb) {
sendMessage(data, options, cb) {
cb();
},
onMessage: {
Expand Down Expand Up @@ -61,7 +61,7 @@ describe("Store", function () {

spy.calledOnce.should.eql(true);
spy
.alwaysCalledWith(null, {
.alwaysCalledWith({
type: FETCH_STATE_TYPE,
portName,
})
Expand Down Expand Up @@ -149,7 +149,7 @@ describe("Store", function () {
const initializeStoreListener = [];

// override mock chrome API for this test
self.chrome.runtime.sendMessage = (extensionId, message, options, listener) => {
self.chrome.runtime.sendMessage = (message, options, listener) => {
initializeStoreListener.push(listener);
};

Expand Down Expand Up @@ -323,31 +323,16 @@ describe("Store", function () {
});

describe("#dispatch()", function () {
it("should send a message with the correct dispatch type and payload given an extensionId", function () {
const spy = (self.chrome.runtime.sendMessage = sinon.spy());
const store = new Store({ portName, extensionId: "xxxxxxxxxxxx" });

store.dispatch({ a: "a" });

spy.callCount.should.eql(2);
spy.args[0][0].should.eql("xxxxxxxxxxxx");
spy.args[0][1].should.eql({ type: FETCH_STATE_TYPE, portName: "test" });
spy.args[1][0].should.eql("xxxxxxxxxxxx");
spy.args[1][1].should.eql({ type: DISPATCH_TYPE, portName: "test", payload: { a: "a" } });
});

it("should send a message with the correct dispatch type and payload not given an extensionId", function () {
it("should send a message with the correct dispatch type and payload", function () {
const spy = (self.chrome.runtime.sendMessage = sinon.spy()),
store = new Store({ portName });

store.dispatch({ a: "a" });

spy.callCount.should.eql(2);

should(spy.args[0][0]).eql(null);
spy.args[0][1].should.eql({ type: FETCH_STATE_TYPE, portName: "test" });
should(spy.args[1][0]).eql(null);
spy.args[1][1].should.eql({ type: DISPATCH_TYPE, portName: "test", payload: { a: "a" } });
spy.args[0][0].should.eql({ type: FETCH_STATE_TYPE, portName: "test" });
spy.args[1][0].should.eql({ type: DISPATCH_TYPE, portName: "test", payload: { a: "a" } });
});

it("should serialize payloads before sending", function () {
Expand All @@ -360,14 +345,12 @@ describe("Store", function () {

spy.callCount.should.eql(2);

should(spy.args[0][0]).eql(null);
spy.args[0][1].should.eql({ type: FETCH_STATE_TYPE, portName: "test" });
should(spy.args[1][0]).eql(null);
spy.args[1][1].should.eql({ type: DISPATCH_TYPE, portName: "test", payload: JSON.stringify({ a: "a" }) });
spy.args[0][0].should.eql({ type: FETCH_STATE_TYPE, portName: "test" });
spy.args[1][0].should.eql({ type: DISPATCH_TYPE, portName: "test", payload: JSON.stringify({ a: "a" }) });
});

it("should return a promise that resolves with successful action", function () {
self.chrome.runtime.sendMessage = (extensionId, data, options, cb) => {
self.chrome.runtime.sendMessage = (data, options, cb) => {
cb({ value: { payload: "hello" } });
};

Expand All @@ -378,7 +361,7 @@ describe("Store", function () {
});

it("should return a promise that rejects with an action error", function () {
self.chrome.runtime.sendMessage = (extensionId, data, options, cb) => {
self.chrome.runtime.sendMessage = (data, options, cb) => {
cb({ value: { payload: "hello" }, error: { extraMsg: "test" } });
};

Expand All @@ -389,7 +372,7 @@ describe("Store", function () {
});

it("should return a promise that resolves with undefined for an undefined return value", function () {
self.chrome.runtime.sendMessage = (extensionId, data, options, cb) => {
self.chrome.runtime.sendMessage = (data, options, cb) => {
cb({ value: undefined });
};

Expand Down
2 changes: 1 addition & 1 deletion test/applyMiddleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('applyMiddleware', function () {
});

it('passes recursive dispatches through the middleware chain', () => {
self.chrome.runtime.sendMessage = (extensionId, data, options, cb) => {
self.chrome.runtime.sendMessage = (data, options, cb) => {
cb(data.payload);
};
function test(spyOnMethods) {
Expand Down

0 comments on commit 5c7d998

Please sign in to comment.