plasmo
plasmo copied to clipboard
[RFC] @plasmohq/rpc
How do you envision this feature/change to look/work like?
One of the deceptively tricky parts of browser extension development is figuring out how to pass a message from your background service worker to a tab's content script.
The path forward is filled with edge cases!
-
What if the tab is a chrome:// or chrome.google.com protected URL?
-
What if your extension updated and the content script in the tab is of an old version?
-
What if the tab you're on doesn't have your content script at all because your extension was installed after the web page loaded up?
These are all things you need to consider when passing a message from the background service worker to the tab.
Plasmo should abstract away all of these nuances so that the developer doesn't need to worry about them.
For example,
In background.ts
Library API could look something like this:
plasmo.sendMessage(tabId, message, callbackFn)
the sendMessage
function could abstract away the business logic for checking if the tab has a content script already inside, etc.
What is the purpose of this change/feature? Why?
Most of the time, it makes the most sense for a content script to initiate a message to the background service worker. However, there are times when background service workers need to send a message to the active tab, instead. In those times, a developer needs to understand all the nuances and edge cases involved with sending a message to the tab.
It feels like a good call to have Plasmo deal with those edge cases.
(OPTIONAL) Example implementations
No response
(OPTIONAL) Contact Details
No response
Verify canary release
- [X] I verified that the issue exists in
plasmo
canary release
Code of Conduct
- [X] I agree to follow this project's Code of Conduct
- [X] I checked the current issues for duplicate problems.
Excited for this.
Iām not sure if plasmo should remain un-opinionated (maybe limiting this to providing a with-example that works with webextension-polyfill & webext-bridge), or abstract all of this away by exposing this sendMessage wrapper.
I personally would use it but the downside is that it feels like some magic will be hidden away.
Overall, the background -> content communication is a very valid use case which was too complicated for me to start with. So addressing this is great.
ā
Here some thoughts an your proposal:
Promise based API vs callback-based API Instead of adopting the chrome specific API with the callback function as a param, what do you think about adopting a promised based pattern?
const res = await sendMessage('get-selection', payload, 'content-script')
For non extension and/or non chrome extension develops, my assumption is that passing on this callback is somewhat strange these days.
As a benefit, it adopts the same pattern as theWebExtension-polyfill
Type safety
It would be great to be able to define define payload and response types. Similar to what webext-bridge does.
As we are likely to use sendMessage and onMessage in different context, keep the type consistent could be hard and easy to make mistakes. webext-bridge provide a smarter way to make the type for protocols much easier.
Create shim.d.ts file with the following content and make sure it's been included in tsconfig.json.
// shim.d.ts import { ProtocolWithReturn } from 'webext-bridge' declare module 'webext-bridge' { export interface ProtocolMap { foo: { title: string } // to specify the return type of the message, // use the `ProtocolWithReturn` type wrapper bar: ProtocolWithReturn<CustomDataType, CustomReturnType> } }
import { onMessage } from 'webext-bridge' onMessage('foo', ({ data }) => { // type of `data` will be `{ title: string }` console.log(data.title) }
import { sendMessage } from 'webext-bridge' const returnData = await sendMessage('bar', { /* ... */ }) // type of `returnData` will be `CustomReturnType` as specified
in fact, given that webext-bridge already takes care of some APIs*, I wonder if that plasmo utility could work as a wrapper around it for the specific use case of background -> content (which is very valid I think).
*eg it handles:
- chrome.runtime.sendMessage or
- chrome.runtime.onConnect or
- chrome.runtime.connect
@ColdSauce I've updated an example repo that outlines this issue & show cases the error I had encountered.
I think this case is tricky especially when using webext-bridge
.
This discussion might be relevant š https://github.com/jlalmes/trpc-chrome/pull/1
@jlalmes thanks for the headsup!
I'm posting Loom's use case as which has the following features:
- inject a portal container to the content-script
- inject a ui element to the content-script
- this ui element can toggle the portal's content UI
- overwrite the onAction click handler to toggle the portal's content UI
Here is a walkthrough of this example: https://www.loom.com/share/d764409295eb4612a05d689cd9155fd1
I'm currently using trpc-chrome
. Appears that you can use it with tRPC subscription procedures (two-way messaging) for this use-case.
For visibility, here's a complete Plasmo example: https://github.com/jlalmes/trpc-chrome/tree/master/examples/with-plasmo
@jlalmes I'm using a React Portal for this use case so that I can sync the state with React. I think this is in this case possible (or even easier) as it's just one WebExtension entity: a content-script. You can see a public example here.
As soon as there are multiple entities involved (e.g. sync popover & content-script), your trpc-chrome implementation looks very interesting.
I've used @plasmo/useStorage but what I prefer about the tRPC implementation is type-safety & validation included.
Some links:
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/Tabs/sendMessage
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#connection-based_messaging
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/registerContentScripts
https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
https://developer.chrome.com/docs/extensions/mv3/messaging/#connect