aklinker1 / webext-core

Collection of essential libraries and tools for building web extensions
https://webext-core.aklinker1.io
MIT License
95 stars 11 forks source link

@webext-core/messaging errors with No response when sending data back from an injected script #57

Open rcoopr opened 3 months ago

rcoopr commented 3 months ago

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:

Uncaught (in promise) Error: No response
    at Object.<anonymous> (chunk-ALTBYIMW.js:80:67)
    at Generator.next (<anonymous>)
    at fulfilled (chunk-ALTBYIMW.js:36:24)

Minimal repro: https://github.com/rcoopr/wxtcore-messaging-repro

What steps did I do to create example:

The messages are recieved, yet there is an error anyway. The issue is also present in the built output. Only tested on Chrome 122

rcoopr commented 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

maaasyn commented 1 month ago

I can confirm, same issue on my side. I try to communicate from pop up to injected script and vice versa.

drbolsen commented 1 month ago

The same issue here with WindowMessaging, below is a detailed report

Describe the bug

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

Expected behavior

No error thrown

Environment

Chrome: 125.0.6422.113
 wxt: ^0.18.0 => 0.18.3
@webext-core/messaging: "^1.4.0",
aklinker1 commented 1 month ago

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.

Screenshot 2024-05-31 at 12 43 08 AM

In this image, you can see the first line, the message is sent by content.ts. The second line indicates content.ts received the message, then the injected script received it. There's no log showing the injected script returning a value, but I added 123 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.

rcoopr commented 1 month ago

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?

drbolsen commented 1 month ago

@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.