Open rcoopr opened 3 months ago
More vague details: I have also tried switching to window messaging, but had issues in the other direction - sending messages to the injected script. However, I have not made a minimal repro and don't remember the specifics - it may have been user error
I can confirm, same issue on my side. I try to communicate from pop up to injected script and vice versa.
The same issue here with WindowMessaging, below is a detailed report
I got a weird error with WindowMessaging
Error: No response at Object.<anonymous> (chrome-extension://hfpnfjaneoimodeagddennghcdfanldp/c…}
content.js:1 Uncaught (in promise) Error: No response
at Object.<anonymous> (content.js:1:3870)
at Generator.next (<anonymous>)
at p (content.js:1:3308)
(anonymous) @ content.js:1
p @ content.js:1
await in p (async)
(anonymous) @ VM336:1
Promise.then (async)
(anonymous) @ VM336:1
A very simple setup - an injected script and a content script, each side has an instance of messenger, a listener and a function that sends a message to other side. When a message is sent from the injected script to the content script everything is fine, however every time when a message is sent from the content script to the injected script I get the error.
The actual code is below
messaging.ts
export interface TestProtocol {
sendHello(msg: string): void
sendGoodbye(msg: string): void
}
injected.ts
import { defineWindowMessaging } from "@webext-core/messaging/page"
import { TestProtocol } from "@/messaging"
export default defineUnlistedScript(() => {
const messenger = defineWindowMessaging<TestProtocol>({ namespace: 'my_test_namespace', logger: console})
messenger.onMessage('sendGoodbye', (data): void => {
console.log('Received : ', data)
})
return {
sendHello: (): void => {
messenger.sendMessage('sendHello', 'hello!').then(() => {
console.log('Sent hello!')
})
}
}
})
content.ts
import { defineWindowMessaging } from "@webext-core/messaging/page";
import { TestProtocol } from "@/messaging";
export default defineContentScript({
main() {
const messenger = defineWindowMessaging<TestProtocol>({ namespace: 'my_test_namespace', logger: console })
// Inject our dom script for communications.
const injectScript = (url: string): void => {
<... skipped...>
}
if (document instanceof Document) {
injectScript(chrome.runtime.getURL('injected.js'))
}
messenger.onMessage('sendHello', (data):void => {
console.log('Recieved :', data)
})
return {
sendGoodbye: (): void => {
messenger.sendMessage('sendGoodbye', 'bye').then(() => {
console.log('Message sent')
})
}
}
}
});
Note, that I exposed sendGoodbye and sendHello so I can easy test it in Chrome's console.
Now, when I call sendHello from the injected script context, it works as it should
_injected.then((i) => { i.sendHello() })
injected.js:1 [messaging] sendMessage {id=8607} ─ᐅ {id: 8607, type: 'sendHello', data: 'hello!', timestamp: 1717072633884}
Promise {<fulfilled>: undefined}
<---note order --->
content.js:1 [messaging] Received message {id: 8607, type: 'sendHello', data: 'hello!', timestamp: 1717072633884}
content.js:1 Recieved : {id: 8607, type: 'sendHello', data: 'hello!', timestamp: 1717072633884}
content.js:1 [messaging] onMessage {id=8607} ─ᐅ {res: undefined}
<--- response part --->
injected.js:1 [messaging] Received message {id: 8607, type: 'sendHello', data: 'hello!', timestamp: 1717072633884}
injected.js:1 [messaging] sendMessage {id=8607} ᐊ─ {res: undefined, err: undefined}
injected.js:1 Sent hello!
However when I call sendGoodbye from the content script context, it fails. Note that the message goes through and received, but it appears that response back order is messed up somehow
_content.then((i) => { i.sendGoodbye() })
content.js:1 [messaging] sendMessage {id=3206} ─ᐅ {id: 3206, type: 'sendGoodbye', data: 'bye', timestamp: 1717072652064}
Promise {<fulfilled>: undefined}
<-- out of order step -->
content.js:1 [messaging] Received message {id: 3206, type: 'sendGoodbye', data: 'bye', timestamp: 1717072652064}
injected.js:1 [messaging] Received message {id: 3206, type: 'sendGoodbye', data: 'by', timestamp: 1717072652064}
injected.js:1 Received : {id: 3206, type: 'sendGoodbye', data: 'bye', timestamp: 1717072652064}
injected.js:1 [messaging] onMessage {id=3206} ─ᐅ {res: undefined}
<--- i guess it should appear here --->
content.js:1 [messaging] sendMessage {id=3206} ᐊ─ {res: undefined, err: Error: No response
at Object.<anonymous> (chrome-extension://hfpnfjaneoimodeagddennghcdfanldp/c…}
content.js:1 Uncaught (in promise) Error: No response
at Object.<anonymous> (content.js:1:3870)
at Generator.next (<anonymous>)
at h (content.js:1:3308)
(anonymous) @ content.js:1
h @ content.js:1
Promise.then (async)
sendGoodbye @ content.js:1
(anonymous) @ VM277:1
Promise.then (async)
(anonymous) @ VM277:1
No error thrown
Chrome: 125.0.6422.113
wxt: ^0.18.0 => 0.18.3
@webext-core/messaging: "^1.4.0",
Thanks for the additional info everyone! Looked into this today, and it appears the messenger instance that sends the message is responding to the message with undefined
, instead of the second instance in a different context, causing the error.
In this image, you can see the first line, the message is sent by
content.ts
. The second line indicatescontent.ts
received the message, then the injected script received it. There's no log showing the injected script returning a value, but I added123
to be returned inside the injected script. So that means the response is coming from somewhere else... The only other place being the original content script.
So for the window messenger, each instance just needs to ignore messages sent by itself. So in addition to a namespace, each messenger also needs an ID to check where the message came from, and if the sender ID is the same as the instance ID, the message should be ignored.
So in addition to a namespace, each messenger also needs an ID to check where the message came from, and if the sender ID is the same as the instance ID, the message should be ignored.
Is that to be implemented in userland or can it be implemented internally?
@rcoopr I believe that it will require changes in the core, the handler for processing a response to the initial message is crafted at the time of sending that message, effectively juggling with a low level stuff, like addListeners and postMessage from Window API.
An alternative would be a custom written code, just to handle communication between content and injected scripts, or you have to bring in 3rd party library that is designed specifically for such a task. I am currently adapting 'bridge' from Quasar Bex, seems like they figured out how to handle this.
Unfortunately, communication/messaging seems to be a weakest point for all extension frameworks that I've tested.
The aforementioned Quasar has own problems with its bridge but on the opposite side, they have issues with background <-> content messaging.
Using only example code from the webext-core site, injected script messaging causes an error (No error when sending messages to the injected script) Potentially related to #55 ?
Error:
Minimal repro: https://github.com/rcoopr/wxtcore-messaging-repro
pnpm i
pnpm dev
What steps did I do to create example:
pnpm dlx wxt@latest init <project-name>
pnpm i @webext-core/messaging
browser.runtime.getUrl
is actuallygetURL
)pnpm dev
and check consoleThe messages are recieved, yet there is an error anyway. The issue is also present in the built output. Only tested on Chrome 122