1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-09 05:00:10 +00:00

Add messaging docs

This commit is contained in:
Justin Baur
2025-09-12 15:28:28 -04:00
parent 279d16999a
commit 3f69bec004
4 changed files with 95 additions and 15 deletions

View File

@@ -1,9 +1,6 @@
import { map, share } from "rxjs";
import { Message } from "@bitwarden/common/platform/messaging";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal";
import { Message, tagAsExternal } from "@bitwarden/messaging";
import { fromChromeEvent } from "../browser/from-chrome-event";

View File

@@ -1,9 +1,6 @@
import { fromEventPattern, share } from "rxjs";
import { Message } from "@bitwarden/common/platform/messaging";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal";
import { Message, tagAsExternal } from "@bitwarden/messaging";
/**
* Creates an observable that when subscribed to will listen to messaging events through IPC.

View File

@@ -3,3 +3,93 @@
Owned by: platform
Services for sending and recieving messages from different contexts of the same application.
## Usage
The best way to use messaging is along with a `CommandDefinition` to help encourage type safety on
either side of the message. An overload of `MessageSender.send` exists that takes a `string` for the
command and a `Record<string, unknown>` for the payload, this exists for backwards compatibility but
all new uses are highly encouraged to use the `CommandDefinition<T>` overload.
```typescript
import { MessageSender, MessageListener, CommandDefinition } from "@bitwarden/messaging";
const MY_MESSAGE = new CommandDefinition<{ data: number }>("myMessage");
const MY_EVENT = new CommandDefinition<{ name: string }>("myEvent");
class MyService {
myEvent$: Observable<{ name: string }>;
contructor(private messageSender: MessageSender, private messageListener: MessageListener) {
this.myEvent$ = this.messageListener.messages$(MY_EVENT);
}
async doThing() {
this.messageSender.send(MY_MESSAGE, { data: 5 });
}
}
```
## Implementation
### MessageSender
MessageSender has 4 implementations to help sending messages to all the different contexts of a
Bitwarden application.
#### SubjectMessageSender
The `SubjectMessageSender` takes an rxjs `Subject` and nexts the given payload along with a
`command` property with the given command name.
#### MultiMessageSender
This implementation just takes an array of other `MessageSender` implementations and sends all the
messages it gets to all of those implementations. This exists especially so we can use a
`SubjectMessageSender` to handle messages from the same context and use another client specific
implementation to handle messages with another context.
#### ChromeMessageSender
This implementation uses [`chrome.runtime.sendMessage`][chrome-runtime-sendMessage] to send messages
to other contexts of our browser extension. This API is the only one of the bunch that is
asynchronous under the hood but since the `send` method forces all `MessageSender`s to be sync. We
take care of this by handling the promise through logging.
### ElectronMainMessageSender
This implementation sends messages through the electron
[`BrowserWindow.webContents.send`][electron-send] API. This makes the message available through
listeners added in the `ipcRenderer.addListener` API.
### MessageListener
#### Observable
There is only one implementation implementation of `MessageListener` and it just takes an observable
of all messages. Similar to how `MultiMessageSender` sends messages to multiple sources this
listener can listen to multiple sources by using the RXJS operator `merge` to listen to multiple
observable sources of messages. So far all of our message sources are able to be converted pretty
easily to observables using some type of `fromEventPattern` function.
#### Subject
The same subject that is passed to `SubjectMessageSender` can also be converted to an observable
using `asObservable()`.
#### `chrome.runtime.onMessage`
For browser, We use the `chrome.runtime.onMessage` API and convert it to an observable using
`fromChromeEvent`. Messages from this source are also tagged as being an external message (meaning
not the same exact context) such that consumers could filter them out using
`filter(isExternalMessage)`. When messages are read from this API in Angular contexts we also use
our `runInsideAngular` operator to force the messages into the Angular zone so that messages are
able to affect the UI without every consumer doing that themselves.
#### IPC
Similar to the extensions implementation the browser renderer process wraps `ipc.platform.onMessage`
to convert those messages into an observable.
[chrome-runtime-sendMessage]: [https://developer.chrome.com/docs/extensions/reference/api/runtime#method-sendMessage]
[electron-send]: [https://www.electronjs.org/docs/latest/api/web-contents#contentssendchannel-args]

View File

@@ -8,7 +8,9 @@ class MultiMessageSender implements MessageSender {
payload: Record<string, unknown> | T = {},
): void {
for (const messageSender of this.innerMessageSenders) {
messageSender.send(commandDefinition, payload);
// This is technically an invalid cast but we just have to tell TypeScript
// it's one of the two so its okay with us passing the value along.
messageSender.send(commandDefinition as CommandDefinition<T>, payload);
}
}
}
@@ -43,12 +45,6 @@ export abstract class MessageSender {
*/
abstract send(command: string, payload?: Record<string, unknown>): void;
/** Implementation of the other two overloads, read their docs instead. */
abstract send<T extends Record<string, unknown>>(
commandDefinition: CommandDefinition<T> | string,
payload: T | Record<string, unknown>,
): void;
/**
* A helper method for combine multiple {@link MessageSender}'s.
* @param messageSenders The message senders that should be combined.