Common Recipes and Patterns
Relevant source files
The following files were used as context for generating this wiki page:webext-bridge is a library designed to simplify messaging between the different parts of a web extension. It provides a consistent API for communication across contexts like background scripts, content scripts, popups, and devtools pages. This document outlines the core architectural patterns and common usage recipes based on the library's source code.
The fundamental design involves a central routing hub in the background script, with other contexts establishing persistent or message-based connections to it. The library abstracts away the complexities of runtime.Port management, message serialization, and transaction tracking, offering developers a clean, promise-based API for request/response messaging and a stream-based API for continuous, bi-directional communication.
Core Messaging (sendMessage / onMessage)
The primary pattern is a request-response model facilitated by sendMessage and onMessage. A script in one context can send a message and await a response from a script in another context.
Sources: src/internal/endpoint-runtime.ts, src/types.ts
Architecture
Every extension context (popup, content-script, etc.) initializes its own EndpointRuntime. This runtime manages message listeners and tracks "open transactions"—messages that have been sent and are awaiting a reply.
The sendMessage function returns a Promise that resolves with the return value from the onMessage handler in the destination context or rejects if an error occurs or the transaction is terminated prematurely.
Sources: src/internal/endpoint-runtime.ts:167-188
Message Flow
This diagram illustrates the sequence of events for a simple message transaction.
This diagram shows a message being sent from Context A to Context B and the corresponding reply.
Sources: src/internal/endpoint-runtime.ts:43-148
Internal Message Structure
All messages are wrapped in an InternalMessage object to facilitate routing and transaction management.
| Field | Type | Description |
|---|---|---|
origin | Endpoint | The source context of the message. |
destination | Endpoint | The target context for the message. |
transactionId | string | A unique ID to match a reply to its original message. |
hops | string[] | An array of runtime IDs the message has passed through. |
messageID | string | The user-defined ID (event name) for the message. |
messageType | 'message' | 'reply' | Indicates if it's a new message or a reply. |
data | JsonValue | The payload of the message. |
err | JsonValue | A serialized error object if an exception occurred. |
timestamp | number | The creation timestamp of the message. |
Sources: src/types.ts:24-34
The Background Script Hub
The background script acts as the central message broker for all other extension contexts. It maintains active connections and routes messages between them.
Sources: src/background.ts
Connection Management
The background script uses a Map to keep track of all active port connections from other contexts. Each connection is identified by a unique endpoint string (e.g., content-script@123.45) and associated with a port object and a session fingerprint.
This diagram shows various extension contexts connecting to the central connMap in the background script.
Sources: src/background.ts:21, src/background.ts:271-283
When a message arrives, the background script resolves the sender and destination endpoints, looks up the destination port in connMap, and forwards the message. If a destination is not immediately available (e.g., a content script in a not-yet-loaded tab), the message can be queued.
Sources: src/background.ts:184-228
Stream-Based Communication
For continuous, bi-directional communication, webext-bridge provides a Stream class. This is useful for scenarios requiring more than a single request/response, such as state synchronization or event pushing.
Sources: src/internal/stream.ts
Stream Lifecycle
A stream is established on a named channel between two endpoints.
- Listener Setup: One context calls
onOpenStreamChannel('my-channel', callback)to listen for incoming stream requests. - Stream Opening: Another context calls
openStream('my-channel', destination)to initiate the connection. This sends a__crx_bridge_stream_open__message. - Connection Established: The listener's callback is invoked with a
Streaminstance, and thePromisefromopenStreamresolves with anotherStreaminstance. - Communication: Both sides can now use
stream.send(data)and listen withstream.onMessage(callback). - Closing: Either side can call
stream.close(), which notifies the other end.
This diagram illustrates the process of opening, using, and closing a communication stream between two contexts.
Sources: src/internal/stream.ts:104-147
Context-Specific Initialization
The library must be imported from a specific entry point depending on the script's execution context. This is necessary because the underlying communication mechanism differs between contexts.
| Context | Entry Point | Communication Method |
|---|---|---|
background | webext-bridge/background | runtime.onConnect |
content-script | webext-bridge/content-script | runtime.connect & window.postMessage |
popup | webext-bridge/popup | runtime.connect |
options | webext-bridge/options | runtime.connect |
devtools | webext-bridge/devtools | runtime.connect |
window | webext-bridge/window | window.postMessage |
This pattern is enforced by the exports and typesVersions fields in package.json.
Sources: package.json:23-50, CHANGELOG.md:11-12
Content Script & Window Communication
A special pattern exists for communication between a content script and a regular, non-extension script running in the page's window context. This is achieved using window.postMessage.
To enable this, the content script must explicitly allow it by calling allowWindowMessaging(namespace). The window script must then call setNamespace(namespace) with the same namespace string. This prevents conflicts with other scripts on the same page that might also be using webext-bridge.
This diagram shows the setup required for communication between a content script and a window script.
Sources: src/content-script.ts:37-40, src/window.ts:18-21, src/internal/post-message.ts:41-50
Resilience and Disconnection Handling
To support Manifest V3's non-persistent background service workers, the library uses a PersistentPort wrapper. If the service worker is terminated, the port connection is lost. PersistentPort automatically attempts to reconnect when the service worker comes back online.
Upon reconnection, it performs a sync operation, sending its queue of undelivered messages and pending responses to the newly-awakened background script to reconcile the state.
If a recipient's tab is closed or its script otherwise terminates, any pending sendMessage promises waiting for a response from it will be rejected with an error, preventing indefinite hangs. This is handled by the background script, which monitors port disconnections and notifies the original sender.
Sources: src/internal/persistent-port.ts:1-89, src/background.ts:296-310, CHANGELOG.md:21-23