Tohiko
Tohiko

Reputation: 1982

sendResponse in Port.postmessage()

I have the following code

browser.runtime.onConnect.addListener(function (externalPort) {
    externalPort.onMessage.addListener((message, sender, sendResponse) => {
      sendResponse(42);
    }
});

However, it seems that listeners for Port.onMessage do not get called with a sendResponse as listeners for browser.runtime.onMessage.

Any idea how to send responses for messages to ports?

Upvotes: 1

Views: 1164

Answers (2)

woxxom
woxxom

Reputation: 73806

Port-based messaging doesn't use sendResponse. Simply post another message to the port.

Here's a very simplified example of a port-based messaging system. It doesn't transfer errors or exceptions, doesn't have a timeout. The idea is to pass an id, save the callback for the id in a map, and use the same id in the response to call that saved callback.

Unlike browser.runtime.sendMessage that creates a new port each time (a relatively expensive operation in case you send a lot of messages), we reuse the same port.

sender:

const port = browser.runtime.connect({name: 'foo'});
const portMap = new Map();
let portMessageId = 0;

port.onMessage.addListener(msg => {
  const {id, data} = msg;
  const resolve = portMap.get(id);
  portMap.delete(id);
  resolve(data);
});

function send(data) {
  return new Promise(resolve => {
    const id = ++portMessageId;
    portMap.set(id, resolve);
    port.postMessage({id, data});
  });
}

usage:

(async () => {
  const response = await send({foo: 'whatever'});
  console.log(response);
})();

receiver:

/** @param {chrome.runtime.Port} port */
browser.runtime.onConnect.addListener(port => {
  if (port.name === 'foo') {
    port.onMessage.addListener(msg => {
      const {id, data} = msg;
      port.postMessage({id, data: processMessage(data)});
    });
  }
});

Upvotes: 1

Piro
Piro

Reputation: 309

The Port.postMessage() is a push-only messaging method, so you need to use regular runtime.sendMessage() method in parallel. Here is an example:

manifest.json:

{
  "name": "panel example",
  "version": "1",
  "manifest_version": 2,
  "background": {
    "scripts": ["background.js"]
  },
  "browser_action": {
    "default_title": "panel",
    "default_popup": "panel.html"
  },
  "permissions": [
    "tabs"
  ]
}

background.js:

browser.runtime.onConnect.addListener(port => {
  let tabId;
  const listenerForPort = (message, sender) => {
    if (message &&
        typeof message == 'object' &&
        message.portName == port.name) {
      switch (message.type) {
        case 'get-tabId':
          return Promise.resolve(tabId);
      }
    }
  };
  browser.runtime.onMessage.addListener(listenerForPort);
  port.onMessage.addListener(message => {
    if (message &&
        typeof message == 'object' &&
        message.tabId)
      tabId = message.tabId;
  });
  port.onDisconnect.addListener(port => {
    browser.runtime.onMessage.removeListener(listenerForPort);
    if (tabId)
      browser.tabs.remove(tabId);
  });
});

panel.html:

<!DOCTYPE html>
<script type="application/javascript" src="panel.js"></script>
<button id="button">Click Me</button>

panel.js:

browser.windows.getCurrent({ populate: true }).then(win => {
  const portName = `port for window ${win.id}`;
  const activeTab = win.tabs.find(tab => tab.active);
  const port = browser.runtime.connect({
    name: portName
  });
  port.postMessage({ tabId: activeTab.id });

  const button = document.getElementById('button');
  button.addEventListener('click', async event => {
    const tabIdFromBackground = await browser.runtime.sendMessage({
      type: 'get-tabId',
      portName
    });
    button.textContent = tabIdFromBackground;
  });
});

In this example, there is a listener corresponding to a connection and it is designed to respond only to messages sent with the corresponding port name.

Upvotes: 1

Related Questions