-
Notifications
You must be signed in to change notification settings - Fork 2
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
Add background page receiver #8
Conversation
To clarify the types a bit: There's a {
type: "functionName"
} This object might have:
|
To run the tests, build the extension with: npm run demo:watch Then open it in the browser with the usual web-ext run and then open the console. You might need to refresh the page once. To run TypeScript on the library: npm run watch For linter and Prettier at once: npm run fix |
If you write as a constant you get a bit better error message It's saying that the type is wrong because if: 1) I have something of type message, 2) I can't substitute sumMethod for it because the args might not be numbers The types need to look more like this (but this is still not exactly right)
|
I don't understand these fields, and there's no examples in the tests |
index.ts
Outdated
P extends BasePayload = BasePayload, | ||
> = { | ||
type: T; | ||
parameters: P; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not call this payload? I think there's two ways to go:
- RPC-style naming, of operationId and args
- Message-style naming (a la https://github.com/redux-utilities/flux-standard-action): type and payload
Also, note the difference between parameters and arguments: https://developer.mozilla.org/en-US/docs/Glossary/Parameter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My idea is to only allow RPC-style messaging instead of exposing two interfaces (one for RPC and one for general messages, which isn't necessary if the first one works well).
Names aren't thought out yet, no strong preference since they're going to be on a private object, but since it's RPC-style-only it makes sense to make it an array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed everything to type
and args
to keep them short. type
is the only one visible by the user (I don’t think we'll match the RPC method fully).
I think at some point we'll want to add a library-specific key so we can safely catch non-handled messages with it:
browser.runtime.onMessage.addListener(function onMessage(message) {
if (!message.__messenger__) {
// Completely ignore message, this was not sent by the library
// and should not be handled by it
return;
}
const handler = handlers.get(message.type);
if (!handler) {
// Even if we don't find the handler, we can safely throw an error,
// because we're sure it was *supposed* to be handled
throw new Error('No handler registered for ' + message.type);
}
return handler.call(sender, ...message.args);
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My idea is to only allow RPC-style messaging instead of exposing two interfaces
Sure happy to give it a try. In my experience array typing in TypeScript is more annoying that object typing. Arrays also historically are tricky for subtyping: https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Arrays. I'm not familiar with how gracefully TypeScript handles subtyping arrays
The final thing I'll say in support of object payloads is it matches more closely to the browser's sendMessage API
From a functionality standpoint, they're equivalent. E.g., (arg1: Record), {args: unknown[]}. So I'd err on whatever gives the best TypeScript ergonomics.
The benefit of the args based approach I guess is that you can point it to any existing method... Is that what you're going for?
I think at some point we'll want to add a library-specific key so we can safely catch non-handled messages with it
I agree seems helpful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The final thing I'll say in support of object payloads is it matches more closely to the browser's sendMessage API
I'm confused by this. Arrays are objects and the sendMessage API accepts any serializable value, not just objects. Even primitives are accepted. The actual message is still a {t:string,a:array}
object.
The only concern we have is about typing, i.e. ensuring that the handler's type is carried over to the calling point, which is already taken care of.
...addends: number[] | ||
): Promise<number> { | ||
return addends.reduce((a, b) => a + b); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I'll comment in review threads so that the conversation is easier to follow)
If you write as a constant you get a bit better error message
It's saying that the type is wrong because if: 1) I have something of type message, 2) I can't substitute sumMethod for it because the args might not be numbers
Indeed, as shown by your example:
async function inner(
_: browser.runtime.MessageSender,
...addends: number[]
): Promise<number> {
return addends.reduce((a, b) => a + b);
};
const outer: Method = inner;
This essentially tells tsc to discard any types of inner
and just set outer
to Method
, so outer
has no number
parameters when called.
In practice this isn't a problem because outer
is called with whatever onMessage
receives.
What's important is that:
index.ts
Outdated
method: M; | ||
publicMethod: P; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The publicMethod/method fields are only for type enforcement, they're not meant to be used by users of the library. You can see them in index.ts and that's the only place they'll ever appear. Names can be improved.
The difference in types is to hide the implementation details from the caller, which appears as a regular function, this is publicMethod's signature:
- the caller calls
foo(a, b, c)
While the handler must also receive meta information, which is defined by the method
signature:
- the handler will have the signature
foo(meta, a, b, c)
The previous POC had a similar pattern, I think:
foo([a, b, c]) // caller, “public method”
foo([a, b, c], meta) // handler, “method”
I chose to avoid this array-based signature because it's more awkward to type, as a user of the library:
function foo(meta: Meta, a: string, b: number, c: Map) {}
// versus
function foo([a, b, c]: [string, number, Map]) {}
I just realized though that most lifters currently in the extension do not have access to this meta at all, only the dev tools have a target
/port
. I only added it here because the POC had it.
I can probably replace it with this
though, 🤔 this would hide it from most handlers:
// Regular handler, completely standard function
function foo(a: string, b: number, c: Map) {}
// Handler that needs the meta
function fooWithThis(this: Meta, a: string, b: number, c: Map) {
if (this.immediateSender.url) {
return true;
}
}
Both functions have the same signature in practice since this
is optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The publicMethod/method fields are only for type enforcement
Thanks for the explanation. I think I was confused about a comment somewhere saying it was concrete
I just realized though that most lifters currently in the extension do not have access to this meta at all
And that was a mistake. As a result there's places in the extension code I had to fall back to using raw browser message handling. The point of this messaging project is to unify how we're messaging
I can probably replace it with this though
I'm not following that comment - is this
referring to a class instance somehow?
import ../index was importing the js file instead of the TS file
The information from the sender can be accessed on `this` as it's "contextual" information.
This is ready to be merged for me |
I just tested this locally and it appears that it can be used by any context towards the background. So I should be able to replace most liftBackground calls in the extension (still gradually though) |
Related:
While automated tests might be out of reach for now, nothing stops us from at least have some semi-automated ones where we just have to open each context and look at the results in the console.
I'd like to start using this in the extension as soon as possible to see if it works for us. The only problem in that regard is a type issue for functions with parameters, can you look into it?