1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

feat: add ipc service usage docs (#16000)

This commit is contained in:
Andreas Coroiu
2025-08-21 14:42:56 +02:00
committed by GitHub
parent 89bae6bb74
commit 0daa6913d2

View File

@@ -2,25 +2,136 @@ import { Observable, shareReplay } from "rxjs";
import { IpcClient, IncomingMessage, OutgoingMessage } from "@bitwarden/sdk-internal";
/**
* Entry point for inter-process communication (IPC).
*
* - {@link IpcService.init} should be called in the initialization phase of the client.
* - This service owns the underlying {@link IpcClient} lifecycle and starts it during initialization.
*
* ## Usage
*
* ### Publish / Subscribe
* There are 2 main ways of sending and receiving messages over IPC in TypeScript:
*
* #### 1. TypeScript only JSON-based messages
* This is the simplest form of IPC, where messages are sent as untyped JSON objects.
* This is useful for simple message passing without the need for Rust code.
*
* ```typescript
* // Send a message
* await ipcService.send(OutgoingMessage.new_json_payload({ my: "data" }, "BrowserBackground", "my-topic"));
*
* // Receive messages
* ipcService.messages$.subscribe((message: IncomingMessage) => {
* if (message.topic === "my-topic") {
* const data = incomingMessage.parse_payload_as_json();
* console.log("Received message:", data);
* }
* });
* ```
*
* #### 2. Rust compatible messages
* If you need to send messages that can also be handled by Rust code you can use typed Rust structs
* together with Rust functions to send and receive messages. For more information on typed structs
* refer to `TypedOutgoingMessage` and `TypedIncomingMessage` in the SDK.
*
* For examples on how to use the RPC framework with Rust see the section below.
*
* ### RPC (Request / Response)
* The RPC functionality is more complex than simple message passing and requires Rust code
* to send and receive calls. For this reason, the service also exposes the underlying
* {@link IpcClient} so it can be passed directly into Rust code.
*
* #### Rust code
* ```rust
* #[wasm_bindgen(js_name = ipcRegisterPingHandler)]
* pub async fn ipc_register_ping_handler(ipc_client: &JsIpcClient) {
* ipc_client
* .client
* // See Rust docs for more information on how to implement a handler
* .register_rpc_handler(PingHandler::new())
* .await;
* }
*
* #[wasm_bindgen(js_name = ipcRequestPing)]
* pub async fn ipc_request_ping(
* ipc_client: &JsIpcClient,
* destination: Endpoint,
* abort_signal: Option<AbortSignal>,
* ) -> Result<PingResponse, RequestError> {
* ipc_client
* .client
* .request(
* PingRequest,
* destination,
* abort_signal.map(|c| c.to_cancellation_token()),
* )
* .await
* }
* ```
*
* #### TypeScript code
* ```typescript
* import { IpcService } from "@bitwarden/common/platform/ipc";
* import { IpcClient, ipcRegisterPingHandler, ipcRequestPing } from "@bitwarden/sdk-internal";
*
* class MyService {
* constructor(private ipcService: IpcService) {}
*
* async init() {
* await ipcRegisterPingHandler(this.ipcService.client);
* }
*
* async ping(destination: Endpoint): Promise<PingResponse> {
* return await ipcRequestPing(this.ipcService.client, destination);
* }
* }
*/
export abstract class IpcService {
private _client?: IpcClient;
/**
* Access to the underlying {@link IpcClient} for advanced/Rust RPC usage.
*
* @throws If the service has not been initialized.
*/
get client(): IpcClient {
if (!this._client) {
throw new Error("IpcService not initialized");
throw new Error("IpcService not initialized. Call init() first.");
}
return this._client;
}
private _messages$?: Observable<IncomingMessage>;
protected get messages$(): Observable<IncomingMessage> {
/**
* Hot stream of {@link IncomingMessage} from the IPC layer.
*
* @remarks
* - Uses `shareReplay({ bufferSize: 0, refCount: true })`, so no events are replayed to late subscribers.
* Subscribe early if you must not miss messages.
*
* @throws If the service has not been initialized.
*/
get messages$(): Observable<IncomingMessage> {
if (!this._messages$) {
throw new Error("IpcService not initialized");
throw new Error("IpcService not initialized. Call init() first.");
}
return this._messages$;
}
/**
* Initializes the service and starts the IPC client.
*/
abstract init(): Promise<void>;
/**
* Wires the provided {@link IpcClient}, starts it, and sets up the message stream.
*
* - Starts the client via `client.start()`.
* - Subscribes to the client's receive loop and exposes it through {@link messages$}.
* - Implementations may override `init` but should call this helper exactly once.
*/
protected async initWithClient(client: IpcClient): Promise<void> {
this._client = client;
await this._client.start();
@@ -47,6 +158,12 @@ export abstract class IpcService {
}).pipe(shareReplay({ bufferSize: 0, refCount: true }));
}
/**
* Sends an {@link OutgoingMessage} over IPC.
*
* @param message The message to send.
* @throws If the service is not initialized or the underlying client fails to send.
*/
async send(message: OutgoingMessage) {
await this.client.send(message);
}