TypeScript Integration
Relevant source files
The following files were used as context for generating this wiki page:
webext-bridge is built with TypeScript to provide strong typing and enhance developer experience when creating communication channels within a web extension. This integration ensures type safety for messages, their payloads, and return values across different extension contexts like background scripts, content scripts, and popups. By leveraging TypeScript's features, the library minimizes runtime errors and allows for static analysis and autocompletion in modern IDEs.
The core of this integration is a user-extendable ProtocolMap interface, which allows developers to define a contract for all messages exchanged through the bridge. This provides compile-time safety, ensuring that message listeners and senders adhere to the defined data structures. The library uses TypeScript generics to automatically infer payload and return types for the sendMessage and onMessage APIs based on the ProtocolMap.
Core Type Definitions
The foundation of webext-bridge's type system is defined in src/types.ts. These types describe the structure of messages and the endpoints they travel between.
Key Interfaces
| Interface/Type | Description | Source - |
| RuntimeContext | A string literal type representing the possible execution contexts: 'devtools', 'background', 'popup', 'options', 'content-script', 'window'. | src/types.ts:3-9 - |
- |
Endpoint| An object identifying a specific location in the extension, containingcontext,tabId, and an optionalframeId. |src/types.ts:11-15- | - |
BridgeMessage<T>| Represents a public-facing message, containing thesenderendpoint,id,dataof generic typeT, and atimestamp. |src/types.ts:17-22- | - |
InternalMessage| A more detailed message format used for internal routing, includingorigin,destination,transactionId,hops, and more. |src/types.ts:27-38- |
This diagram illustrates the composition of the main data structures for messaging.
Sources: src/types.ts:3-38
Extending Type Safety with ProtocolMap
The primary mechanism for achieving end-to-end type safety is the ProtocolMap interface. This is an empty interface in the library's source code, designed to be extended by the user in their own project. By declaring a ProtocolMap in a global declaration file (.d.ts), developers can define all their message keys, their expected data payloads, and their expected return types.
Sources: src/types.ts:50-54
How It Works
- Declaration Merging: The user creates a
*.d.tsfile and uses TypeScript's declaration merging to add properties to theProtocolMapinterface. - Generic Helpers: The library provides two generic helper types,
GetDataType<K>andGetReturnType<K>, which inspect the user-definedProtocolMapto extract the payload and return types for a given message keyK. - API Integration: The
sendMessageandonMessagefunctions are generic and use these helpers to provide full type inference.
Here is the core implementation of the type helpers:
// src/types.ts:50-86
/**
* Extendable by user.
*/
export interface ProtocolMap {
// foo: { id: number, name: string }
// bar: ProtocolWithReturn<string, number>
}
export type DataTypeKey = keyof ProtocolMap extends never
? string
: keyof ProtocolMap
export type GetDataType<
K extends DataTypeKey,
Fallback extends JsonValue = undefined,
> = K extends keyof ProtocolMap
? ProtocolMap[K] extends (...args: infer Args) => any
? Args['length'] extends 0
? undefined
: Args[0]
: ProtocolMap[K] extends ProtocolWithReturn<infer Data, any>
? Data
: ProtocolMap[K]
: Fallback;
export type GetReturnType<
K extends DataTypeKey,
Fallback extends JsonValue = undefined
> = K extends keyof ProtocolMap
? ProtocolMap[K] extends (...args: any[]) => infer R
? R
: ProtocolMap[K] extends ProtocolWithReturn<any, infer Return>
? Return
: void
: Fallback;
For messages that expect a response, the ProtocolWithReturn<Data, Return> utility type can be used to define both the payload and return types explicitly.
Sources: src/types.ts:40-48
Type-Safe Messaging API
The sendMessage and onMessage functions, exposed from the endpointRuntime, are the primary touchpoints for developers. Their TypeScript signatures are designed to leverage the ProtocolMap.
Sources: src/internal/endpoint-runtime.ts:11-25
sendMessage
The sendMessage function is an async function that returns a Promise. The type of the resolved value of this promise is automatically inferred from the return type defined in the ProtocolMap for the given message ID.
// src/internal/endpoint-runtime.ts:13-20
sendMessage: <
ReturnType extends JsonValue,
K extends DataTypeKey = DataTypeKey,
>(
messageID: K,
data: GetDataType<K, JsonValue>,
destination?: Destination
) => Promise<GetReturnType<K, ReturnType>>
onMessage
The onMessage function registers a listener. The callback function's message parameter is automatically typed with the payload defined in the ProtocolMap. The return type of the callback must also match the return type in the protocol, ensuring the contract is fulfilled.
// src/internal/endpoint-runtime.ts:21-25
onMessage: <Data extends JsonValue, K extends DataTypeKey = DataTypeKey>(
messageID: K,
callback: OnMessageCallback<GetDataType<K, Data>, GetReturnType<K, any>>
) => (() => void)
Type-Safe Message Flow
This sequence diagram illustrates how types are checked at compile time during a message exchange between a popup script and a background script.
Typed Streams
Beyond simple request-response messaging, webext-bridge also provides a Stream class for continuous, bi-directional communication. This feature is also fully typed. When opening or listening for a stream, the data sent and received through the stream's send() and onMessage() methods can be typed.
The stream.onMessage method accepts a callback where the message parameter is typed.
// src/internal/stream.ts:86-88
public onMessage<T extends JsonValue>(callback: (msg?: T) => void): HybridUnsubscriber {
return this.getDisposable('message', callback)
}
This allows developers to create strongly-typed communication channels for real-time data exchange.
Sources: src/internal/stream.ts:86-88, src/internal/stream.ts:107-133
Build and Declaration File Generation
The project uses tsup to compile the TypeScript source code into JavaScript and generate TypeScript declaration files (.d.ts). This is configured in package.json.
Sources: package.json:15
The build script command is:
"build": "tsup src/index.ts src/background.ts src/content-script.ts src/devtools.ts src/options.ts src/popup.ts src/window.ts --format esm,cjs --dts"
The --dts flag instructs tsup to generate declaration files for all entry points. The typesVersions field in package.json then maps these generated declaration files to the corresponding package exports, ensuring that when a user imports from a specific entry point (e.g., webext-bridge/background), they get the correct types for that context.
| package.json field | Purpose - |
| exports | Defines the module entry points for both ESM (import) and CJS (require). - |
| typesVersions | Maps the package exports to their corresponding .d.ts files, enabling context-aware type resolution for the consumer. - |
This diagram shows the build process flow.
Sources: package.json:15-56
Conclusion
The TypeScript integration in webext-bridge is a cornerstone of the library's design, providing a robust and developer-friendly way to manage inter-context communication in web extensions. By allowing developers to define a strict ProtocolMap, it shifts error detection from runtime to compile-time, drastically improving code quality and maintainability. The generation of precise declaration files for each extension context further solidifies its commitment to a type-safe development workflow.