npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

webext-messaging

v1.0.2

Published

Port abstraction to make messaging within web extensions simpler.

Downloads

2

Readme

webext-messaging

Intro

Messaging in web extensions can be a little tricky to get right. There are quite a few pitfalls:

  • It's common to have huge onMessage event listeners that handle all manner of different types of messages. It's much nicer to split apart that logic so that each type of message has a separate handling function.
  • There are lots of edge-cases when sending messages between the different types of windows. For example, devtools panel "windows" all share the tab ID of -1 where as the popup "window" has no tab ID at all.
  • Messaging from content scripts sometimes works differently than when messaging from the background. For example, a content script cannot directly send a message to another window.
  • When you add asynchronous code into the mix this all gets harder.
  • Long-lived connections are sometimes necessary for performance reasons, but they work in a totally different way. Having two paths for messages can lead to a big mess in an extension's code.

While working on Adblock Plus we constantly banged our heads against these problems and eventually came up with this port abstraction. It's likely useful to other extensions too, so we split it out into this library.

Requirements

If targetting a browser which has not yet "Promisified" their extension APIs, you must use a polyfill or manually alter browser.runtime.sendMessage and browser.tabs.sendMessage yourself. Ensure that those functions return a Promise instead of accepting a callback argument. Here's how Adblock Plus does that. Take care to do that in every extension context (e.g. background, options page) from which you use this library.

Setup

Assuming you're using a bundler and that it supports importing directly from a Node.js module, you can do something like this:

// package.json
{
  ...
  "dependencies": {
    ...
    "webext-messaging": "1.0.1",
    ...
  }
  ...
}
// From the background.
import {addConnection, dispatch, port} from "webext-messaging";

// Start listening for messages.
browser.runtime.onConnect.addListener(addConnection);
browser.runtime.onMessage.addListener(dispatch);

// Set up message forwarding, so that content script can message other windows.
port.on("forward", (message, sender) =>
{
  let {target, message: {type}} = message;
  message = message.message;

  if (target.tab.id === "self")
    target = sender.tab.id;

  return port.send(target, type, message);
});
// Each other context (e.g. popup window or content script).
import {addConnection, dispatch} from "webext-messaging";

// Start listening for messages.
browser.runtime.onMessage.addListener(dispatch);

// Optionally open a long-lived connection to the background. Do this if you
// need to send/receive messages (which don't need a response) more efficiently.
addConnection(browser.runtime.connect());

Usage

// From any context (e.g. background, options page, popup window).
import {port} from "webext-messaging";

// Listen for messages of type "randomNumber.get", return a response whenever
// they are received.
port.on(
  "randomNumber.get",
  (message, sender) => message.prefix + Math.random().toString()
);

// Listen for messages of a different type. Handle those asynchronously, before
// responding.
port.on(
  "randomNumber.asyncGet",
  async (message, sender) => Math.random()
);

// Listen for "logmessage" messages. Handle them, but don't worry about sending
// a response.
port.on(
  "logmessage",
  (message, sender) =>
  {
    console.log("Message logged!");
  }
);
// From a different context.
import {port} from "webext-messaging";

// Send a message to the background page.
port.send(
  "randomNumber.get",
  {prefix: "Random: "}
).then(
  message =>
  {
    console.log("Random number with prefix received", message);
  }
);

// Send a message to the background page which was handled asynchronously.
// (No difference.)
port.send(
  "randomNumber.asyncGet"
).then(
  message =>
  {
    console.log("Random number received", message);
  }
);

// Send a message which doesn't warrant a response to the background page.
// Note: If long-lived connection was opened earlier (see above) the message
//       will automatically be sent over the long-lived connection, but
//       otherwise handled the same.
port.post(
  "logmessage"
);

// Send a message to another window.
// Note: Message forwarding must be set up in the background (see above) if you
//       are sending this message from a context other than the background.
port.send(
  otherWindowTabId,
  "messageName"
).then(
  message =>
  {
    console.log("Response received", message);
  }
);

// Send a message to a specific frame in another window. Note that the target
// Note: The target Object has the same signature as the `sender` Object that is
//       provided with incoming messages.
port.send(
  {tab: {id: otherWindowTabId}, frameId: otherFrameId},
  "messageName"
).then(
  message =>
  {
    console.log("Response received", message);
  }
);

// Send a message (from context other than background) using the standard API in
// Chrome. So long as you include the `type` string, the message handler above
// will work. In other words, it's OK to use the `port` abstraction for just the
// sending/receiving and the standard browser APIs on the other end if you like.
chrome.runtime.sendMessage(
  {type: "randomNumber.get", prefix: "example - "},
  response =>
  {
    console.log("Random number with prefix received", response);
  }
);

Further reading

The port API is documented in webext-messaging.js, take a look through the JSDoc comments for more detailed information. There is more functionality than documented in the above examples, for example you can stop listening for messages (port.off) and also listen for when long-lived connections are closed (port.onConnectionDisconnect and port.offConnectionDisconnect).

Linting

You can lint the code as follows:

npm run lint

Notes:

  • We assume you won't send a message from a context to itself, e.g. from the background to the background. It's undefined what will happen if you do.
  • It is expected that you won't open multiple long-lived connections from a given frame to the background. It's undefined what will happen if you do.
  • You can only have one listener for each message type per context. If you have multiple such listeners it is undefined what will happen.
  • When a long-lived connection is opened from a context which does not provide an unique tab ID, we assign a random number instead. This works fine for most use-cases, but obviously the random number will not function as a tab ID for other browser APIs and you will need to take care to use port.post instead of port.send when targetting such a context. When in doubt, if the sender's tab ID is a floating point number, it was randomly generated.
  • With Manifest v3 long-lived connections will generally close after a few minutes. It's up to you to listen for connection.onDisconnect (from the context which opened the connection, not the background) and open a new connection to replace it.