API: onMessage
Relevant source files
The following files were used as context for generating this wiki page:
The onMessage function is the primary mechanism for receiving messages within the webext-bridge library. It allows different parts of a web extension—such as the background script, content scripts, popups, and devtools pages—to register listeners for specific message IDs. When a message is sent using sendMessage, onMessage enables the destination endpoint to receive and process the message payload, and optionally send a response.
This API is fundamental to the library's goal of simplifying cross-context communication in web extensions. It provides a consistent, declarative way to define message handlers, abstracting away the complexities of the underlying runtime.sendMessage, tabs.sendMessage, and runtime.Port APIs.
Core Functionality
The onMessage function is a product of the createEndpointRuntime factory, which is instantiated in each extension context. Its core responsibility is to register a callback function that will be executed when a message with a corresponding ID arrives at the current endpoint.
Sources: src/internal/endpoint-runtime.ts:121-124
Registering a Listener
To listen for messages, you call onMessage with two arguments:
messageID: A string identifier for the message type you want to listen for.callback: A function to be executed when a message with thatmessageIDis received.
The function stores the callback in an internal Map called onMessageListeners, keyed by the messageID. It returns an "unsubscriber" function that, when called, removes the listener from the map.
Sources: src/internal/endpoint-runtime.ts:39, src/internal/endpoint-runtime.ts:121-124
The following diagram illustrates the process of registering a listener.
This diagram shows that calling onMessage results in the provided callback being stored in the onMessageListeners map within the endpoint's runtime.
The OnMessageCallback
The callback function passed to onMessage receives a single argument: a BridgeMessage object. This object contains the message data and metadata about the sender. The callback can be synchronous or asynchronous (returning a Promise). Any value returned by the callback will be sent back to the original sender as a reply.
Sources: src/types.ts:22-24, src/internal/endpoint-runtime.ts:83-84
BridgeMessage Object Structure
The BridgeMessage object passed to the callback has the following structure:
| Property | Type | Description |
|---|---|---|
sender | Endpoint | An object identifying the source of the message (context, tabId, etc.). |
id | string | The messageID of the message. |
data | JsonValue | The payload sent with the message. |
timestamp | number | The Date.now() timestamp when the message was created. |
Sources: src/types.ts:15-20
Endpoint Object Structure
The sender property is an Endpoint object with the following fields:
| Property | Type | Description |
|---|---|---|
context | RuntimeContext | The context where the message originated (e.g., 'background', 'popup'). |
tabId | number | The ID of the tab associated with the sender. |
frameId | number | (Optional) The ID of the frame associated with the sender. |
Sources: src/types.ts:8-13
Message Handling Flow
When a message arrives at an endpoint, the internal handleMessage function is invoked. This function is the counterpart to onMessage and is responsible for dispatching the message to the correct listener.
Sources: src/internal/endpoint-runtime.ts:41-119
The flow is as follows:
handleMessagereceives anInternalMessage.- It checks if the message's destination matches the current context.
- It retrieves the
messageIDfrom the message. - It looks up the corresponding callback in the
onMessageListenersmap. - If a callback is found, it is executed with a
BridgeMessageobject containing thedata,sender, and other metadata. - The return value from the callback is wrapped in a new
InternalMessageof typereplyand sent back to the original sender. - If no callback is found for the
messageID, an error is created and sent back as a reply.
The sequence diagram below visualizes the entire lifecycle from sending a message to receiving a reply via an onMessage handler.
This diagram shows the end-to-end flow of a message from sendMessage in one context to an onMessage handler in another, including the return of a reply.
Usage in Different Contexts
The onMessage API is exported from each context-specific entry point, ensuring a consistent interface regardless of where your code is running.
Sources: src/background.ts:285, src/content-script.ts:46, src/popup.ts:13, src/window.ts:20
Content Script (content-script.ts)
In a content script, the endpoint runtime is configured to handle messages from two sources:
- A persistent port connected to the background script.
- The
windowobject, for messages from in-page scripts (viapostMessage).
The endpointRuntime.handleMessage function is registered as the handler for both, unifying the message-handling logic.
// src/content-script.ts:14-22
const endpointRuntime = createEndpointRuntime('content-script', (message) => {
if (message.destination.context === 'window') win.postMessage(message)
else port.postMessage(message)
})
win.onMessage((message: InternalMessage) => {
endpointRuntime.handleMessage(Object.assign({}, message, {origin: {
context: "window",
tabId: null
}}))
})
port.onMessage(endpointRuntime.handleMessage)
Sources: src/content-script.ts:9-26
Window Script (window.ts)
For scripts running in a regular web page, onMessage works by listening to postMessage events from the content script. The setNamespace function must be called first to establish a secure communication channel.
// src/window.ts:8-14
const endpointRuntime = createEndpointRuntime('window', message =>
win.postMessage(message),
)
win.onMessage((msg) => {
if ('type' in msg && 'transactionID' in msg)
endpointRuntime.endTransaction(msg.transactionID)
else endpointRuntime.handleMessage(msg)
})
Sources: src/window.ts:6-14
Unsubscribing
The onMessage function returns a function that, when called, will unregister the listener. This is useful for cleaning up listeners in components that have a lifecycle, preventing memory leaks and unintended behavior.
// Example of unsubscribing
import { onMessage } from 'webext-bridge/popup';
const unsubscribe = onMessage('some-event', (message) => {
console.log('This will only be logged once.');
unsubscribe(); // Stop listening after the first message.
});
This works by calling onMessageListeners.delete(messageID) within the returned function.
Sources: src/internal/endpoint-runtime.ts:122-123
Summary
onMessage is a cornerstone of the webext-bridge library, providing a robust and developer-friendly API for handling incoming messages. By abstracting the underlying browser APIs and unifying the handling logic across different extension contexts, it allows developers to focus on application logic rather than the boilerplate of messaging infrastructure. Its pairing with sendMessage and its support for asynchronous replies make it a powerful tool for building complex, data-driven web extensions.