mirror of
https://github.com/bitwarden/browser
synced 2026-02-09 05:00:10 +00:00
Add messaging docs
This commit is contained in:
@@ -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";
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user