Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialization hooks #126

Merged
merged 18 commits into from
Apr 16, 2018
Merged

Conversation

ethanroday
Copy link
Contributor

@ethanroday ethanroday commented Jan 12, 2018

  • Add new serializer and deserializer options for wrapStore and Store, which will serialize all outgoing messages and deserialize all incoming messages.
  • Update README

Note: When testing in an actual extension, I found it necessary to wrap all of the messaging functions (e.g. port.postMessage or chrome.runtime.onMessage.addListener) in anonymous functions, rather than passing the function references directly. If I didn't, Chrome gave me the following error:

Uncaught TypeError: Method called without a valid receiver (this). Did you forget to call .bind()?
  at getPrivateImpl (extensions::utils:119:16)
  at publicClassPrototype.(anonymous function) (extensions::utils:137:20)
...

I'm not sure what's causing this, but would welcome ideas. It feels unnecessary to have to wrap all of the calls.

@tshaddix tshaddix requested review from vhmth and tshaddix January 14, 2018 19:39
@vhmth
Copy link
Collaborator

vhmth commented Jan 15, 2018

@ethanroday yeah by default our store functions aren't bound to the instance (we can get around this in the future by using Class properties). I will get to reviewing this PR in a bit today. Thank you so much for this beast. The documentation looks stellar, and I think this is a sane entrypoint into giving people flexibility without sacrificing the default, out-of-the-box redux behavior.

@ethanroday
Copy link
Contributor Author

@vhmth got it. Glad you think it makes sense! It’s a little suboptimal in the sense that it exposes some internals to the user (patch message payloads are more than just slices of the store’s state), but I imagine in most cases that will be okay.

@ethanroday
Copy link
Contributor Author

Hey @vhmth, have you had a chance to look this over?

@ethanroday
Copy link
Contributor Author

@vhmth, @tshaddix have either of you had a chance to review?

@tshaddix
Copy link
Owner

@ethanroday Have this on my todos. Trying to get to this today!

Copy link
Owner

@tshaddix tshaddix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vhmth This looks good to me after going through it. Would love your thoughts as well.

The only change that is required is to fix whatever is breaking the build after the merge from master. The master merge just contained a simple addition from #134 to make the extension id null instead of an empty string.

{
id: 1,
text: 'Write a Chrome extension',
created: {}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true? I swore it turned it into a string last time I checked... am I crazy?

Copy link
Contributor Author

@ethanroday ethanroday Mar 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vanilla JSON.parse(JSON.stringify(new Date())) will give you a string, but Chrome's default serialization/deserialization for messaging gives you an empty object for some reason or another.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it - must have been confusing the two. Thanks

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow this is really good to know. I could see us having a serializers dir with defaults for things like dates (not necessary for this PR ofc).


wrapStore(store, {
portName: 'MY_APP',
serializer: payload => JSON.stringify(payload, dateReplacer),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that these were added as options rather than additional parameters to wrap store 👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the names too (dateReplacer and dateReviver). Makes it clear what's going on

@@ -0,0 +1,91 @@
export const noop = (payload) => payload;

const transformPayload = (message, transformer = noop) => ({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to mention here why a new object is required rather than mutating the message. It took me a second to realize that manipulations here would manipulate the original action object which could cause unintentional side-effects.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add a comment.

@ethanroday
Copy link
Contributor Author

The '' to null change caused one of my tests to fail since it was expecting chrome.runtime.sendMessage to have been called with ('', ...). Should be good now.

@tshaddix
Copy link
Owner

@vhmth would love a second pass from you! This is a pretty big change!

@tshaddix
Copy link
Owner

tshaddix commented Apr 7, 2018

@vhmth I'm tempted to merge this and #124 and release under a prerelease as they are fairly large changes. Thoughts?

@@ -9,6 +9,8 @@ export class Store<S = any, A extends redux.Action = redux.Action> {
portName: string,
state?: any,
extensionId?: string,
serializer?: Function,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for adding the TS defs

// just return a copy of the message.
// We return a copy rather than the original message so that we're not
// mutating the original action object.
...(message.payload ? {payload: transformer(message.payload)} : {})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice - love the cascading spread

@@ -255,6 +298,21 @@ describe('Store', function () {
}).should.eql(true);
});

it('should serialize payloads before sending', function () {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are incredible. Mind adding one more test that serializes/deserializes a date object and checks the input's (date originally stored in action) getDate to the output's (date received after deserialization)? Another check in this test that would be good is checking that the two date objects are not triple-equal to each other to confirm that serialization/deserialization indeed happened.

Copy link
Contributor Author

@ethanroday ethanroday Apr 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed the boat on this one, but hopefully the existing tests are sufficient. If it helps, I've been using this in production for a couple of months and all seems well.

@@ -294,4 +346,24 @@ describe('Store', function () {
return p.should.be.fulfilledWith(undefined);
});
});

describe("when validating options", function () {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very very nice!

@vhmth
Copy link
Collaborator

vhmth commented Apr 16, 2018

@tshaddix @ethanroday

https://www.useloom.com/share/86d043f30c5a4515be7238b2909b4fc3

Copy link
Collaborator

@vhmth vhmth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than my comment about adding another test, everything else makes so much sense. This PR is awesome!

@tshaddix
Copy link
Owner

Great! Let's finalize this thing!

@tshaddix tshaddix merged commit 306f6de into tshaddix:master Apr 16, 2018
@tshaddix
Copy link
Owner

tshaddix commented Apr 16, 2018

@vhmth Given this, I'm thinking we just release under a next tag on NPM. Thoughts?

Internal tag would be v1.6.0-alpha.1 in GitHub. I don't think we need to increment a major version as this doesn't change the API from the past.

@vhmth
Copy link
Collaborator

vhmth commented Apr 16, 2018

Absolutely down. My vote would be to actually increase by a major version but seems like I missed the boat on that one! Ah well - cool with 1.6.0-alpha.1 anyhow.

@ethanroday
Copy link
Contributor Author

Thanks for the shoutout, @vhmth! Happy to be contributing to something that's been so useful to me 😄

@tshaddix
Copy link
Owner

@vhmth We can always bump it to 2.0 alpha! Why would you vote major, just curious?

@ethanroday Yes, thank you very much for your fantastic contribution. I echo @vhmth 's sentiment completely. Regarding the additional test, you can always add it as a separate PR :)

@vhmth
Copy link
Collaborator

vhmth commented Apr 20, 2018

@tshaddix primary reason for the bump to major is I look at "plug and play" layers as a step towards increasing complexity in many ways. In this case we've introduced a custom serialization that diverges away from the standard Chrome behavior. I smell lots of issues potentially coming up since this interferes with the core communication mechanism b/w background and content processes.

@tshaddix
Copy link
Owner

@vhmth Ah, I see your point and I see my point 😄

I'm happy to bump major when on next publish.

@vhmth
Copy link
Collaborator

vhmth commented Apr 21, 2018

@tshaddix that works!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants