0
votes

I'm developing a chrome extension that uses chrome's runtime sendMessage API to send one time messages back and forth between the extension and the pages the content script is injected into. The problem is that I need some messages to take priority in this system. I've implemented a custom port to do the priority messaging through, however I can't seem to replicate the sendResponse API with the port solution.

The Flow

The client calls a method that returns a promise, which stores a nonce for the request and resolve method, sends a message to its parent via parent.postMessage({data}, '*') and then resolves it when windows message listener is invoked with data containing the same nonce.

The extension's content script listens for messages:

const port = chrome.runtime.connect({name: 'priorityPort'})
function getResponse(data) {
  return new Promise((resolve, reject) => {
    if (priority) {
      port.postMessage({data})
      port.onMessage.addListener(msg => {
        resolve(msg)
      })
    } else {
      chrome.runtime.sendMessage({data}, resolve)
    }
  })
}

window.addEventListener('message', async event => {
  const response = await getResponse(event.data)
  event.source.postMessage({response})
})

and then the background page acts as the other side of the connection

// normal
chrome.runtime.onMessage.addListener((obj, sender, sendResponse: data => {
  ;(async () => {
    sendResponse({ status: 'success', body })
  })()
  return true
})
// priority
chrome.runtime.onConnect.addListener((port) => {
  ;(async () => {
    port.onMessage.addListener(async msg => {
      port.postMessage({ status: 'successs', body })
    })
  })()
  return true
})

However, this doesn't get the response back to the requester as sendResponse does. Any help is greatly appreciated.

1
You're adding a new port listener each time a priority message is sent, which makes all the previous listeners trigger on a new message, it also leaks memory, and might be causing the problem you observe. BTW no need for "return true" in onConnect listener as it doesn't have a sendResponse parameter. - wOxxOm
You've probably made this more complicated then it has to be. You can do all that using a long-lived connection (a port connection). - Titus
@Titus sure, but i'm asking how to achieve sendResponse functionality with a long-lived connection - Brandon Deo

1 Answers

0
votes

You could create a queue, something like this:

class Queue {
 constructor() {
   this.arr = [];
   this.awaiting = [];
 }

 enqueue(msg) {
   if (this.awaiting.length) {
     this.awaiting.shift()(msg)
   }else {
     this.arr.push(msg)
   }
 }
   
 dequeue() {
   if (this.arr.length) {
     return this.arr.shift()
   }
   return new Promise(resolve => {
     this.awaiting.push(resolve);
   }); 
 }
}

and here is how you use it:

const queue = new Queue();
const port = chrome.runtime.connect({name: 'priorityPort'})

port.onMessage.addListener(msg => {
  queue.enqueue(msg);
})


window.addEventListener('message', async event => {
  port.postMessage({date: event.data})
  const response = await queue.dequeue()
  event.source.postMessage({response})
})

You could develop some kind of communication protocol and have multiple queues to deal with different kinds of messages. In the port's messages listener, check the message's type and add it to the appropriate queue.

This is just an idea, I'm not sure exactly what you're trying to do, I've probably made this way to complicated for your use case.