One-off Messaging
Relevant source files
The following files were used as context for generating this wiki page:webext-bridge provides a simplified, promise-based API for one-off messaging between different parts of a web extension. It abstracts the complexity of runtime.sendMessage, tabs.sendMessage, and runtime.Port to offer a consistent and easy-to-use interface for communication between contexts like background, content-script, popup, options, devtools, and even sandboxed window scripts.
The core of this system is designed to be robust, handling message routing, replies, and connection management automatically. It ensures that data can be sent and a response can be awaited using modern async/await syntax, reducing the boilerplate code typically associated with extension messaging.
Sources: README.md, src/internal/endpoint-runtime.ts
Architecture Overview
The messaging architecture of webext-bridge is centralized around the background script, which acts as a message router for all other extension contexts. Each context establishes a connection to the background script and uses it to dispatch messages to their intended destinations.
Sources: src/background.ts
Components
| Component | Description | Source File(s) |
|---|---|---|
| EndpointRuntime | The core logic unit in each context. It exposes sendMessage and onMessage methods and manages the lifecycle of outgoing and incoming messages, including tracking replies via transactions. | src/internal/endpoint-runtime.ts |
| PersistentPort | A wrapper around browser.runtime.connect that ensures a stable connection to the background script, automatically reconnecting if the background service worker is terminated (Manifest V3). | src/internal/persistent-port.ts |
| Background Router | The central logic in the background script that listens for connections, receives all messages, and routes them to the correct destination endpoint based on the message's destination property. | src/background.ts |
| PostMessaging | A specialized mechanism using window.postMessage to bridge communication between the isolated content-script context and the page's window context. | src/internal/post-message.ts |
Communication Flow
The following diagram illustrates the high-level communication paths between different extension contexts, with the background script serving as the central hub.
This diagram shows that most contexts communicate with the background script via a PersistentPort. The content-script has a unique role, as it can also communicate directly with scripts running in the window context of the web page.
Sources: src/background.ts, src/content-script.ts, src/popup.ts, src/options.ts, src/window.ts
Message Lifecycle
A one-off message begins with a call to sendMessage and completes when a response is received or the transaction fails. The EndpointRuntime manages this lifecycle using a system of transactions.
Sources: src/internal/endpoint-runtime.ts
Sending a Message
When sendMessage is called, the EndpointRuntime performs the following steps:
- It creates a unique
transactionId. - It creates a
Promiseand stores itsresolveandrejectfunctions in a map (openTransactions) keyed by thetransactionId. - It constructs an
InternalMessageobject containing the payload, destination, origin, and transaction details. - It passes this message to its routing function (e.g.,
port.postMessageorwin.postMessage).
Sources: src/internal/endpoint-runtime.ts:167-191
Message Routing and Reply
The following sequence diagram details the entire flow of a message from a content script to the background script and the subsequent reply.
This diagram shows how a message travels from the content script's EndpointRuntime through the PersistentPort to the background script, where it is handled. The reply follows the reverse path, ultimately resolving the Promise that was returned by the initial sendMessage call.
Sources: src/internal/endpoint-runtime.ts, src/background.ts, src/content-script.ts, src/internal/persistent-port.ts
The InternalMessage Object
All messages are wrapped in an InternalMessage object for routing and state management.
| Property | Type | Description |
|---|---|---|
origin | Endpoint | The context where the message originated. |
destination | Endpoint | The target context for the message. |
transactionId | string | A unique ID to track the request-response cycle. |
hops | string[] | An array of runtime IDs the message has passed through. |
messageID | string | The user-defined ID (name) of the message. |
messageType | 'message' | 'reply' | Indicates if it's a new message or a reply. |
data | JsonValue | The payload of the message. |
err | JsonValue | Contains a serialized error if the handler throws. |
timestamp | number | The creation timestamp of the message. |
Sources: src/types.ts:20-31
Window and Content Script Communication
A special communication channel exists between the content-script and the page's window context. Since these scripts operate in different JavaScript environments (isolated world vs. main world), webext-bridge uses window.postMessage to facilitate communication.
This is managed by the usePostMessaging hook and requires explicit setup.
Sources: src/internal/post-message.ts, src/content-script.ts, src/window.ts
Setup
- Namespace Configuration: To avoid conflicts with other scripts on the page that might also use
postMessage, a unique namespace must be defined. - Enabling Communication: The content script must explicitly allow messaging from the window context.
// In content-script.ts
import { allowWindowMessaging } from 'webext-bridge/content-script'
allowWindowMessaging('my-app-namespace');
Sources: src/content-script.ts:40-43
// In a script running in the window context (injected into the page)
import { setNamespace } from 'webext-bridge/window'
setNamespace('my-app-namespace');
Sources: src/window.ts:16-19
Flow
The getMessagePort function establishes a MessageChannel between the two contexts, which is then used for all subsequent communication, providing a private and efficient channel.
This flow shows that both contexts must agree on a namespace. Once configured, they establish a MessageChannel to securely exchange messages without interfering with the global window.postMessage event listeners.
Sources: src/internal/post-message.ts, src/internal/message-port.ts
Reliability
The messaging system includes features to handle common issues in the extension environment, such as disconnected ports and terminated scripts.
Persistent Connections
In Manifest V3, the background service worker can be terminated after a period of inactivity. The createPersistentPort function mitigates this by automatically re-establishing the connection port when it's disconnected. It also queues any messages sent while the port was down and attempts to redeliver them upon reconnection.
Sources: src/internal/persistent-port.ts:1-80
Recipient Termination
If a message recipient's context is destroyed (e.g., a tab is closed) before it can reply, the sendMessage promise in the sender's context will be rejected. This prevents the sender from waiting indefinitely for a response that will never arrive. The background script tracks active sessions and notifies senders if a session they are trying to reach has been terminated.
Sources: src/background.ts:291-306, CHANGELOG.md:27-29