Error Handling
Relevant source files
The following files were used as context for generating this wiki page:
webext-bridge provides a robust error handling system that covers both application-level exceptions within message handlers and transport-level failures, such as an endpoint becoming unreachable. The library ensures that senders are notified when messages cannot be delivered or when the recipient encounters an error, preventing promises from hanging indefinitely and allowing for graceful failure management.
Errors are generally propagated back to the sendMessage caller, which will reject with a detailed error object. The system uses error serialization to transmit meaningful error information across different extension contexts. It also handles specific scenarios like port disconnections and communication failures between the content script and the main window context.
Application-Level Error Propagation
When an onMessage listener throws an error, webext-bridge catches it, serializes it, and sends it back to the original caller as a reply. The sendMessage promise on the caller's side is then rejected with a rehydrated version of the error.
Sources: src/internal/endpoint-runtime.ts:80-116
Error Serialization and Hydration
The endpoint-runtime is responsible for this process.
- An error is caught in the
handleNewMessagefunction. - It is serialized using
serialize-error. This converts theErrorobject into a plain JSON object, preserving properties likenameandmessage. - The serialized error is attached to the
errproperty of the replyInternalMessage. - When the reply is received by the original sender's
endpoint-runtime, thehandleReplyfunction checks for theerrproperty. - If an error is present, it's "rehydrated" by attempting to find a matching error constructor (e.g.,
TypeError,Error) in the global scope and creating a new error instance with the original properties. - The promise associated with the transaction is rejected with this new, hydrated error object.
Sources: src/internal/endpoint-runtime.ts:49-67, src/internal/endpoint-runtime.ts:98-100
The following sequence diagram illustrates the flow of an error from a message handler back to the caller.
This diagram shows how an error thrown in an onMessage callback is caught, serialized, and used to reject the sendMessage promise.
Sources: src/internal/endpoint-runtime.ts:80-116
Unhandled Message Errors
If a message is sent to an endpoint that has no registered handler for that message's ID, an error is thrown. This error is also serialized and sent back to the caller, ensuring the sendMessage promise is rejected with a clear message indicating that no handler was found.
// src/internal/endpoint-runtime.ts:93-97
noHandlerFoundError = true
throw new Error(
`[webext-bridge] No handler registered in '${thisContext}' to accept messages with id '${messageID}'`,
)
Sources: src/internal/endpoint-runtime.ts:93-97
Transport-Level Failures
The library handles various scenarios where the communication channel itself fails, such as when a recipient context is closed or a port disconnects.
Endpoint Termination
If a recipient's context (e.g., a tab is closed, or a service worker is terminated) is destroyed before it can reply to a message, the sender is notified. This prevents the sendMessage promise from waiting indefinitely.
The background script tracks all active connections. When a port disconnects, it identifies any pending messages that were expecting a reply from that endpoint.
- A
runtime.Portdisconnects, triggering theonDisconnectlistener inbackground.ts. - The
backgroundscript finds all pending responses (rogueMsgs) destined for the now-terminated session. - For each
rogueMessage, it notifies the original sender that the session has ended. - The sender's
persistent-portreceives aterminatedstatus message, finds the corresponding pending transaction, and calls theonFailurelistener, which ultimately rejects thesendMessagepromise.
Sources: CHANGELOG.md:27-29, src/background.ts:223-235, src/internal/persistent-port.ts:98-104
The diagram below shows the process of handling a terminated session.
This diagram illustrates how the background script detects a disconnected recipient and notifies the original sender, causing the pending promise to fail.
Sources: src/background.ts:223-235, src/internal/persistent-port.ts:98-104
Undeliverable Messages and Queuing
When a message is sent to an endpoint that is not yet connected (e.g., a content script in a tab that is still loading), the background script marks the message as undeliverable and notifies the sender. The sender's persistent-port then queues the message. When the target endpoint eventually connects, the background script informs the sender's port that the destination is now "deliverable," and the queued message is sent.
This flow is visualized below:
This flowchart shows the logic for handling messages to destinations that are not immediately available.
Sources: src/background.ts:182-192, src/internal/persistent-port.ts:41-65
Window-to-Content-Script Failures
Communication between a window context (a page's script) and a content-script uses window.postMessage. If the content script fails to establish a connection with the background script (e.g., the extension is reloaded), it can't forward messages. In this case, the content-script will post an EndpointWontRespondError back to the window to terminate the transaction.
| Error Type | File | Description |
|---|---|---|
EndpointWontRespondError | src/internal/post-message.ts | An error object sent from the content script to the window if the background port fails. |
The window's runtime listens for this specific error message and uses it to end the corresponding transaction, rejecting the sendMessage promise.
// src/window.ts:13-15
win.onMessage((msg) => {
if ('type' in msg && 'transactionID' in msg)
endpointRuntime.endTransaction(msg.transactionID)
else endpointRuntime.handleMessage(msg)
})
Sources: src/content-script.ts:32-40, src/window.ts:13-16, src/internal/post-message.ts:5-9
Stream Error Handling
The Stream class has its own error handling mechanisms.
- Sending on a closed stream: If
stream.send()is called after the stream has been closed (viastream.close()or by the other endpoint), it will throw an error. This provides immediate feedback that the communication channel is no longer active. - onClose listener: Both endpoints can register a callback using
stream.onClose()to be notified when the stream is terminated. This allows for cleanup of resources or state associated with the stream.
// src/internal/stream.ts:63-66
public send(msg?: JsonValue): void {
if (this.isClosed)
throw new Error('Attempting to send a message over closed stream. Use stream.onClose(<callback>) to keep an eye on stream status')
// ...
}
Sources: src/internal/stream.ts:63-66, src/internal/stream.ts:91-93
Summary of Error States
The table below summarizes the primary error conditions and how they are handled by webext-bridge.
| Condition | Handling Mechanism | Key Files |
|---|---|---|
Exception in onMessage handler | Error is serialized, sent back as a reply, and sendMessage promise is rejected. | src/internal/endpoint-runtime.ts |
| No handler for a message ID | An error is generated and sent back to the caller. | src/internal/endpoint-runtime.ts |
| Recipient endpoint terminates | onDisconnect is triggered; background script notifies sender to fail the transaction. | src/background.ts, src/internal/persistent-port.ts |
| Message sent to an offline endpoint | Message is queued by the sender's persistent-port and sent when the endpoint becomes available. | src/internal/persistent-port.ts, src/background.ts |
Sending on a closed Stream | A synchronous Error is thrown. | src/internal/stream.ts |
content-script port fails | content-script sends EndpointWontRespondError to window to terminate the transaction. | src/content-script.ts, src/internal/post-message.ts |
This comprehensive approach ensures that developers can reliably handle failures, whether they originate from application logic or the underlying extension communication infrastructure.