sendToBackground in MAIN world doesn't work even with extensionId provided
Hello, thanks for the great framework! I've almost got my MVP done but have run into a brick wall with content scripts.
Following the example on https://docs.plasmo.com/framework/messaging#message-flow, this snippet is what i'm after:
To send a message from a content script thats in the main world you'll have to include your extension's id in the request. Your extension's id can be found in chrome's extension manager window once you've built and added it to your browser.Following the example above, here's my attempt: I have a
background/messages/results.ts
ready and listening, and i've confirmed that this sendTestResults
function IS getting called by my executeScript call in background/index.ts
. (Basically, i'm triggering a script in the window context to test for accessibility issues, and i'm called window.sendTestResults in its callback. The function is being triggered because i'm seeing console logs in it!)
I keep getting an error: Uncaught (in promise) Error: Extension runtime is not available
.
I've tried this approach, ports, AND relays, but nothing seems to work in the MAIN world. Does anyone have a working example of a content script in the MAIN world triggering a message in the background script? Or at least a strong recommendation for the Plasmo way to send the results of a function called on the window of the page to my background script?
Thanks in advance for the help, i'm at the end of my rope!Plasmo Docs
Messaging API – Plasmo
The Plasmo messaging API is a powerful tool for sending real-time messages between different parts of your extension.
50 Replies
For some added detail, I've also tried bypassing plasmo messaging with the native APIs by adding "chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) " but i get a different error in the console when i put it in my content.ts file:
main world won't be able to access extension apis ... you pretty much would need to use window.postMessage and have a content script listen for those messages and foward them off to the background
Thanks @filthytone , so as long as i fire a postMessage from the content script you're saying the background should be able to pick it up? I'll give that a shot today, thank you! Should I file an issue on the repo to get that example removed from the docs since it doesn't sound like it'd work? Here's teh example:
contents/componentInTheMainWorld.tsx:
Gave +1 Rep to @filthytone (current:
#4
- 22
)@RandomJay has reached level 1. GG!
I thought sendToBackground was a wrapper for postMessage but apparently not?
it ends up calling chrome.runtime.sendMessage(extensionId, blah)
that might work actually .. what's the error you get
Uncaught (in promise) Error: Extension runtime is not available.
So i think you're right that the extension runtime isn't available in the main world script, i thought there was some kind of parcel magic that was injecting a polyfill into the content script or something, but i'll admit i didn't go too deep into Plasmo's source code to confirmyou can try setting this too https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable
Chrome for Developers
externally_connectable | Chrome Extensions | Chrome for Develop...
Reference documentation for the externally_connectable property of manifest.json.
bc technically it's in a web page context now it might need that whitelisted
just keep in mind all messaging is basically untrusted from main world
one sec, i'll try that right now
Well, the good news is that the error is gone! Not receiving the message in the background script though, just double checking that everything is named properly
Yea so now it’s an external message
So instead of /messaging/whatever.ts
You’d have /messaging/external/whatever.ts
oh! trying that one sec
And again, anything that comes into that message handler is untrusted
So you have to do type checks , sender checks etc
Even if i explicitly defined my extension id in the manifest?
That might be ok but you’ll want to test
btw, that worked! I was able to receive the message via the external route, thank you for the advice there. For anyone finding this later, the correct path was
messages/external/handler.ts
What did you put for the full value
of the manifest? one sec
@RandomJay has reached level 2. GG!
Yea so that’s like an or statement
You can test by visiting a page , open devtools, and do a chrome.runtime.sendMessage call
If you see it then it’s exposed to any site
Got it, so to be safe i should pass along some kind of payload for verification when i trigger the extension manually.
Basically, my extension runs an automated accessibility test script against any webpage, and then reports back the findings via this external script so that I can process and highlight the issues in a dev-friendly way
Yea so if you have to be in main, you have to assume untrusted .. so any validation is good
so if i generate an id from the backend when the test is kicked off to ensure that the call is legitimate when i run the processing, that might be a good way to validate that the request is legit
and at least make sure it's attributed to the right user. I already have auth enabled here, so i can probably use that too
Another way is use the scripting api to initiate the injection of code from background but yea anything you can do to validate the incoming data will be important
I actually started with that, but unfortunately there's no way to report the results BACK to the background without being in main unfortunately. I tried passing a callback along, but since functions aren't serializable I can't actually get the results back. Unless there's an easy way to do that that you know of? I'd much prefer that approach if there's a way
basically I do this:
but that last one is the kicker, the response only returns a promise with the inject result, not the result of the function itself
If you inject a function , whatever it returns will be the result of the execute script call
Have you tried
sendToBackgroundViaRelay
?
That is how I do it, sorry for jumping in so late.I did, that resulted in a different error for me, unfortunately. Are you kidding?! i appreciate any and all help!
I didn't want to discount any of the work and discussion you've done so far.
Knowing that I have the external script to fall back on, i'm happy to experiment with that too, let me switch over to relay and i'll tell you about that error. Does relay also require the
externally_connectable
flag in the manifest to be set?I don't believe so. I do have my extension configured with
host_permissions
. Not sure if that is even needed though.
To get the relay working though I had to register it... 1 sec I'm getting the code.While we're playing with that, MASSIVE thank you, @filthytone , you really helped me out here
Gave +1 Rep to @filthytone (current:
#4
- 23
)OK so in my example I have a file at
src/background/messages/export.ts
. To use that message from a MAIN world content script I "register" it in an isolated world script. src/contents/main-world-handlers.ts
Then inside my MAIN work script I can call it with
Yea that’s what I was saying early on is you have to have a isolated content script proxy it
Yeah @filthytone has a ton of knowledge around core extension development.
This is also untrusted data
BUT this approach is better bc it works in Firefox too
Or use the scripting API .. but all of these are viable options depending on what you need to do
Thank you @Sam, this is a great example. I'll play with this and see if i can get it working. I'm 99% of the way there already.
and @filthytone , the scripting API is my favorite approach becuase it's so simple, I don't really need to inject more than the window.axe object to run tests against. Maybe it's just the way i'm running the test that's preventing me from getting the results from the execution back, i'm probably stuck in a closure or something so i'll start there and then work my way back to the less trusted but more flexible options 👍
Gave +1 Rep to @Sam (current:
#11
- 4
)sounds good .. i use it in production to do something similar where it returns me results and it def works
i think the result of executeScript is a Promise<Array<any>>
so you just have to iterate it to get the results
I'll def take a look. Here's the function at its most basic:
I think it's that the results only exist in the callback function, so i need to see if i can hoist it outta there 🙂
i don't know that api, but i'd play w/ returning a Promise that resolves
Dang, you're 100% right, I was overcomplicating this by a lot. All I needed to do was:
And it works swimingly, all results displayed in the background script directly 🤦♂️
Thank you, ended up being a newbie question after all
🤘
Thank you again @filthytone and @Sam , I learned a lot here about some pretty esoteric things. Appreciate you both taking the time!
Gave +1 Rep to @filthytone (current:
#4
- 24
)Get rid of your externally connectable too if you’re gonna go with scripting
Then you’re back to safe
Definitely, thank you. Much cleaner this way