How can I listen for messages from the extension popup in ALL (there could be many) active contents?

I have the following configuration for a UI content-script (although I don't actually need the UI -- the content script is only used to add listeners for UI interactions and instrument HTTP requests from the injected page automatically, it just seemed like the messaging hooks might be more ergonomic):
export const config: PlasmoCSConfig = {
matches: ["<all_urls>"],
world: "MAIN",
run_at: "document_start"
}
export const config: PlasmoCSConfig = {
matches: ["<all_urls>"],
world: "MAIN",
run_at: "document_start"
}
I'd like to be able to receive updates in the content script when the popup makes changes to a configuration variable, so I added in test code to the content-script to listen to a port:
const content = () => {
const mailPort = usePort("mail")

return <>
{mailPort.data?.message}
</>
}

export default content
const content = () => {
const mailPort = usePort("mail")

return <>
{mailPort.data?.message}
</>
}

export default content
But this results in the following error:
content.0c6b4643.js:1 Uncaught Error: Extension runtime is not available
3 Replies
matrix
matrix•12mo ago
in the main world many chrome extension apis are not available @tbrockman
tbrockman
tbrockmanOP•12mo ago
For anyone curious that ends up finding this question I dug into it a bit. Scripts injected into the main world do have a limited chrome.runtime API-subset, which makes sense given that they'll be injected in the same context as other Javascript. You can still use chrome.runtime.connect('extensionId') from your content script in order to establish a connection (create a port) to your extension background worker if you allow it to be externally connectable (https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable), i.e setting something like this in manifest.json:
"externally_connectable": {
"matches": [
"https://*/*"
]
},
"externally_connectable": {
"matches": [
"https://*/*"
]
},
This does mean, however, that you'll be allowing any code running on those webpages to connect to your extension, so you should consider the security implications of who you're allowing to send messages, and what data you would be sharing with them. In your background script, you can then have something like the following, allowing you to communicate with your injected content scripts:
const connected = (p) => {
console.debug('connected', p)

p.onMessage.addListener((m) => {
console.log("In background script, received message from content script");
console.log(m.greeting);
});

setTimeout(() => {
p.postMessage({ greeting: "hi there content script!" });
}, 2000)
}
chrome.runtime.onConnectExternal.addListener(connected);
const connected = (p) => {
console.debug('connected', p)

p.onMessage.addListener((m) => {
console.log("In background script, received message from content script");
console.log(m.greeting);
});

setTimeout(() => {
p.postMessage({ greeting: "hi there content script!" });
}, 2000)
}
chrome.runtime.onConnectExternal.addListener(connected);
yoavz
yoavz•10mo ago
This is exactly my issue, thank you for the detailed writeup!
Want results from more Discord servers?
Add your server