Web Extension Messenger
webext-messenger is a focused JavaScript library designed to streamline inter-component communication within browser extensions. It provides a robust framework for message passing between different parts of an extension, such as background scripts, content scripts, and web pages, as well as offscreen documents. The current stable version is 0.35.0, with frequent minor releases indicating active development, often introducing new features and internal optimizations. A key differentiator is its emphasis on minimizing external dependencies, aiming for a lightweight footprint, as evidenced by recent efforts to drop libraries like `p-retry` and `webextension-polyfill` for core functionality. This library simplifies complex messaging patterns, abstracting away the underlying browser `runtime.sendMessage` and `runtime.onMessage` APIs to offer a more developer-friendly interface for building robust and scalable browser extensions.
Common errors
-
Uncaught ReferenceError: messenger is not defined
cause Attempting to use `messenger` without proper ESM import or in a CommonJS environment without transpilation.fixEnsure you are using `import messenger from 'webext-messenger';` at the top of your module. If in a CommonJS context (e.g., older Node.js scripts), you might need `const messenger = require('webext-messenger').default;` or transpile your code to ESM. -
Error: Could not establish connection. Receiving end does not exist.
cause A common browser extension error indicating that the target component (e.g., content script, background script) is either not loaded, not listening for messages, or not accessible from the sending context.fixVerify that the target script (`background.js`, `content.js`) is correctly registered in your `manifest.json`, is loaded in the browser, and has initialized `messenger.init()` with the correct name and an `onMessage` listener for the message type being sent. Ensure contexts can communicate (e.g., content script cannot directly message another content script).
Warnings
- breaking The package made several changes regarding `webextension-polyfill` usage. It was explicitly used in v0.31.0 and then dropped as a direct dependency in v0.32.0. This means extensions relying on `webextension-polyfill` for the `browser` API object might need to explicitly install and manage it, or adapt to using `chrome` APIs directly if `webext-messenger` no longer polyfills internally.
- gotcha In v0.35.0, the package dropped `p-retry` and addressed 'retry bugs'. While intended as a fix, this change might alter the retry behavior of message sending for users who implicitly relied on `p-retry`'s specific logic. Applications with sensitive retry requirements should re-test message resilience.
- breaking The introduction of 'web -> background messaging' in v0.33.0 means web pages can initiate messages to the background script. While a new feature, ensure proper input validation and origin checks are in place within your background script's `onMessage` listeners to prevent potential security vulnerabilities from untrusted web content.
Install
-
npm install webext-messenger -
yarn add webext-messenger -
pnpm add webext-messenger
Imports
- messenger
const messenger = require('webext-messenger');import messenger from 'webext-messenger';
- MessengerError
import messenger from 'webext-messenger'; messenger.MessengerError; // incorrect direct access
import { MessengerError } from 'webext-messenger'; - * as Messenger
import * as Messenger from 'webext-messenger';
Quickstart
/* === manifest.json (Manifest V3) === */
{
"manifest_version": 3,
"name": "Webext Messenger Demo",
"version": "1.0",
"permissions": ["activeTab", "scripting"],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"host_permissions": ["<all_urls>"]
}
/* === background.js === */
import messenger from 'webext-messenger';
const backgroundMessenger = messenger.init('background');
backgroundMessenger.onMessage('ping', (data, sender) => {
console.log('Background received ping:', data, 'from', sender.tab?.url);
return { response: 'pong from background', receivedData: data };
});
backgroundMessenger.onMessage('logTabUrl', async (data, sender) => {
console.log('Background received logTabUrl request for tab ID:', data.tabId);
try {
const tab = await chrome.tabs.get(data.tabId);
return { url: tab.url };
} catch (error) {
console.error('Error getting tab:', error);
return { error: error.message };
}
});
console.log('Background script initialized with webext-messenger');
/* === content.js === */
import messenger from 'webext-messenger';
const contentMessenger = messenger.init('content-script');
contentMessenger.onMessage('alertUser', (message) => {
alert('Message from background: ' + message);
return 'Alert shown!';
});
async function sendPingToBackground() {
console.log('Content script sending ping...');
try {
const response = await contentMessenger.sendMessage('background', 'ping', { message: 'Hello from content!' });
console.log('Content received response from background:', response);
} catch (error) {
console.error('Content script failed to send ping:', error);
}
}
async function requestBackgroundForTabUrl() {
console.log('Content script requesting current tab URL from background...');
try {
const tabId = await new Promise(resolve => chrome.tabs.getCurrent(tab => resolve(tab.id)));
const response = await contentMessenger.sendMessage('background', 'logTabUrl', { tabId: tabId });
console.log('Content received tab URL from background:', response.url);
} catch (error) {
console.error('Content script failed to request tab URL:', error);
}
}
// Run these after a short delay to ensure messenger is ready
setTimeout(() => {
sendPingToBackground();
requestBackgroundForTabUrl();
}, 1000);
console.log('Content script initialized with webext-messenger');