Contributing Guide
Relevant source files
The following files were used as context for generating this wiki page:Welcome to the webext-bridge contributing guide. This document provides a technical overview of the project's architecture and development process to help you contribute effectively. As an open-source project, we value community contributions, from bug fixes and documentation improvements to new features.
This guide will walk you through the core concepts, project structure, and key modules. Understanding these fundamentals is essential for navigating the codebase and making meaningful contributions.
Sources: README.md:30-33
Getting Started
Before you begin, you'll need to set up the development environment. The project uses TypeScript and is built using tsup.
Project Setup
- Clone the repository.
- Install dependencies using
npm install.
Development Scripts
The following scripts defined in package.json are essential for development:
| Script | Description |
|---|---|
build | Compiles the TypeScript source from src/ into ESM and CJS modules in the dist/ directory. |
watch | Runs the build script in watch mode, automatically recompiling on file changes. |
release | Bumps the package version, commits, tags, pushes, builds, and publishes to npm. Used for new releases. |
Sources: package.json:20-24
Core Architecture
webext-bridge is designed to provide a consistent messaging API across all parts of a web extension. The architecture is centered around the concepts of Endpoints, Runtimes, and a central Router in the background script. Since v6.0.0, runtime contexts are no longer auto-detected; you must import the specific entry point for the context you are in (e.g., webext-bridge/content-script).
Sources: CHANGELOG.md:10-12
Endpoints and Runtimes
An Endpoint represents a specific location within the extension, such as a content script in a particular tab or the devtools panel. Each part of the extension that uses the bridge initializes its own Endpoint Runtime.
Sources: src/types.ts:7-11, src/internal/endpoint-runtime.ts
Runtime Contexts
The library defines several RuntimeContext types, each corresponding to a part of a web extension.
| Context | Initialization File | Description |
|---|---|---|
background | src/background.ts | The central service worker or background script. Acts as the message router. |
content-script | src/content-script.ts | Injected into web pages. Communicates with background and window contexts. |
devtools | src/devtools.ts | The devtools panel. Identified by the inspected window's tab ID. |
options | src/options.ts | The extension's options page. |
popup | src/popup.ts (inferred) | The extension's popup action. |
window | src/window.ts | A script running in the page's main window context, not a content script. |
Sources: src/types.ts:3-9, src/background.ts, src/content-script.ts, src/devtools.ts:7, src/options.ts:6, src/window.ts:7
Endpoint Runtime (createEndpointRuntime)
The createEndpointRuntime function is the heart of each context's messaging capability. It is responsible for:
- Sending messages and managing transaction promises (
sendMessage). - Registering and invoking listeners for incoming messages (
onMessage). - Handling message replies and errors.
- Routing messages to the appropriate transport layer (e.g.,
runtime.PortorpostMessage).
This function returns the sendMessage and onMessage methods that form the public API for each context.
Sources: src/internal/endpoint-runtime.ts:28-144
Message Structure
All communication uses a standardized InternalMessage format. This ensures that messages can be correctly routed and processed.
| Property | Type | Description |
|---|---|---|
origin | Endpoint | The endpoint where the message was created. |
destination | Endpoint | The target endpoint for the message. |
transactionId | string | A unique ID to track a message and its reply. |
hops | string[] | An array of contexts the message has passed through. |
messageID | string | The user-defined ID (channel name) for the message. |
messageType | 'message'|'reply' | Indicates if it's a new message or a reply to a previous one. |
data | JsonValue | The payload of the message. |
timestamp | number | The creation timestamp of the message. |
err | JsonValue | Contains a serialized error if one occurred during processing. |
Sources: src/types.ts:22-32
Communication Channels
webext-bridge uses two primary transport mechanisms for routing messages between endpoints.
1. Persistent Ports (runtime.connect)
For communication between the background script and other extension contexts (content-script, devtools, options, popup), the library uses browser.runtime.connect. The createPersistentPort utility wraps the standard runtime.Port to provide resilience against service worker termination in Manifest V3. If the port disconnects, it automatically attempts to reconnect.
The background script (background.ts) listens for these connections via browser.runtime.onConnect and manages a map of all active connections.
This diagram shows how a content script establishes and uses a persistent port to communicate with the background script.
2. Window postMessage
For communication between a content-script and a window script (e.g., a script on the host page or in an iframe), the library uses the standard window.postMessage API. This is necessary because window scripts do not have access to the browser.runtime APIs.
To avoid conflicts with other scripts using postMessage, a unique namespace must be configured.
- In a content script, call
allowWindowMessaging("my-namespace"). - In a window script, call
setNamespace("my-namespace").
The content-script acts as a bridge, forwarding messages between the window and the background script.
This diagram illustrates the flow of a message from a window script to the background script.
The Stream API
On top of the core request-response messaging, webext-bridge provides a Stream API for continuous, bi-directional communication between two endpoints. This is useful for features that require a persistent connection, like state synchronization.
A stream is established on a named channel. One endpoint calls onOpenStreamChannel to listen for incoming stream requests, and another calls openStream to initiate the connection.
The Stream class provides:
send(data): Sends data to the other endpoint.onMessage(callback): Listens for incoming data.close(): Terminates the stream.onClose(callback): Listens for the stream termination event.
This diagram shows the lifecycle of a stream.
Sources: src/internal/stream.ts
How to Contribute
The general workflow for contributing is as follows:
- Find an issue or propose a feature: Look for existing issues or start a discussion for a new feature.
- Set up the project: Follow the Getting Started steps.
- Implement changes:
- Run
npm run watchto have your changes compiled automatically. - Identify the relevant source files in the
src/directory. Most logic resides insrc/internal/or the context-specific files likesrc/background.ts. - If you are adding new message types or changing data structures, update the
ProtocolMapinsrc/types.ts.
- Run
- Test your changes: Manually test your changes in a browser extension environment.
- Submit a Pull Request: Push your changes to a fork and open a PR against the main repository.
Sources: README.md:35-42, package.json:21-23