mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[PM-18039] Add initial verison of IpcServices to client (#13373)
* feat: add foreground ipc service * refactor: create abstract ipc service in libs * wip: remove IPC service complexity The code was making some wrong assumptions about how IPC is going to work. I'm removing everything and starting the content-script instead * feat: working message sending from page to background * refactor: move into common * feat: somewhat complete web <-> browser link * wip: ping command from web * fix: import path * fix: wip urls * wip: add console log * feat: successfull message sending (not receiving) * feat: implement IPC using new refactored framework * wip: add some console logs * wip: almost working ping/pong * feat: working ping/pong * chore: clean-up ping/pong and some console logs * chore: remove unused file * fix: override lint rule * chore: remove unused ping message * feat: add tests for message queue * fix: adapt to name changes and modifications to SDK branch * fix: missing import * fix: remove content script from manifest The feature is not ready for prodution code yet. We will add dynamic injection with feature-flag support in a follow-up PR * fix: remove fileless lp * fix: make same changes to manifest v2 * fix: initialization functions Add missing error handling, wait for the SDK to load and properly depend on the log service * feat: use named id field * chore: update sdk version to include IPC changes * fix: remove messages$ buffer * fix: forgot to commit package-lock * feat: add additional destination check * feat: only import type in ipc-message * fix: typing issues * feat: check message origin
This commit is contained in:
2
libs/common/src/platform/ipc/index.ts
Normal file
2
libs/common/src/platform/ipc/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./ipc-message";
|
||||
export * from "./ipc.service";
|
||||
10
libs/common/src/platform/ipc/ipc-message.ts
Normal file
10
libs/common/src/platform/ipc/ipc-message.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { OutgoingMessage } from "@bitwarden/sdk-internal";
|
||||
|
||||
export interface IpcMessage {
|
||||
type: "bitwarden-ipc-message";
|
||||
message: OutgoingMessage;
|
||||
}
|
||||
|
||||
export function isIpcMessage(message: any): message is IpcMessage {
|
||||
return message.type === "bitwarden-ipc-message";
|
||||
}
|
||||
51
libs/common/src/platform/ipc/ipc.service.ts
Normal file
51
libs/common/src/platform/ipc/ipc.service.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Observable, shareReplay } from "rxjs";
|
||||
|
||||
import { IpcClient, IncomingMessage, OutgoingMessage } from "@bitwarden/sdk-internal";
|
||||
|
||||
export abstract class IpcService {
|
||||
private _client?: IpcClient;
|
||||
protected get client(): IpcClient {
|
||||
if (!this._client) {
|
||||
throw new Error("IpcService not initialized");
|
||||
}
|
||||
return this._client;
|
||||
}
|
||||
|
||||
private _messages$?: Observable<IncomingMessage>;
|
||||
protected get messages$(): Observable<IncomingMessage> {
|
||||
if (!this._messages$) {
|
||||
throw new Error("IpcService not initialized");
|
||||
}
|
||||
return this._messages$;
|
||||
}
|
||||
|
||||
abstract init(): Promise<void>;
|
||||
|
||||
protected async initWithClient(client: IpcClient): Promise<void> {
|
||||
this._client = client;
|
||||
this._messages$ = new Observable<IncomingMessage>((subscriber) => {
|
||||
let isSubscribed = true;
|
||||
|
||||
const receiveLoop = async () => {
|
||||
while (isSubscribed) {
|
||||
try {
|
||||
const message = await this.client.receive();
|
||||
subscriber.next(message);
|
||||
} catch (error) {
|
||||
subscriber.error(error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
void receiveLoop();
|
||||
|
||||
return () => {
|
||||
isSubscribed = false;
|
||||
};
|
||||
}).pipe(shareReplay({ bufferSize: 0, refCount: true }));
|
||||
}
|
||||
|
||||
async send(message: OutgoingMessage) {
|
||||
await this.client.send(message);
|
||||
}
|
||||
}
|
||||
48
libs/common/src/platform/ipc/message-queue.spec.ts
Normal file
48
libs/common/src/platform/ipc/message-queue.spec.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { MessageQueue } from "./message-queue";
|
||||
|
||||
type Message = symbol;
|
||||
|
||||
describe("MessageQueue", () => {
|
||||
let messageQueue!: MessageQueue<Message>;
|
||||
|
||||
beforeEach(() => {
|
||||
messageQueue = new MessageQueue<Message>();
|
||||
});
|
||||
|
||||
it("waits for a new message when queue is empty", async () => {
|
||||
const message = createMessage();
|
||||
|
||||
// Start a promise to dequeue a message
|
||||
let dequeuedValue: Message | undefined;
|
||||
void messageQueue.dequeue().then((value) => {
|
||||
dequeuedValue = value;
|
||||
});
|
||||
|
||||
// No message is enqueued yet
|
||||
expect(dequeuedValue).toBeUndefined();
|
||||
|
||||
// Enqueue a message
|
||||
await messageQueue.enqueue(message);
|
||||
|
||||
// Expect the message to be dequeued
|
||||
await new Promise(process.nextTick);
|
||||
expect(dequeuedValue).toBe(message);
|
||||
});
|
||||
|
||||
it("returns existing message when queue is not empty", async () => {
|
||||
const message = createMessage();
|
||||
|
||||
// Enqueue a message
|
||||
await messageQueue.enqueue(message);
|
||||
|
||||
// Dequeue the message
|
||||
const dequeuedValue = await messageQueue.dequeue();
|
||||
|
||||
// Expect the message to be dequeued
|
||||
expect(dequeuedValue).toBe(message);
|
||||
});
|
||||
});
|
||||
|
||||
function createMessage(name?: string): symbol {
|
||||
return Symbol(name);
|
||||
}
|
||||
20
libs/common/src/platform/ipc/message-queue.ts
Normal file
20
libs/common/src/platform/ipc/message-queue.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { firstValueFrom, Subject } from "rxjs";
|
||||
|
||||
export class MessageQueue<T> {
|
||||
private queue: T[] = [];
|
||||
private messageAvailable$ = new Subject<void>();
|
||||
|
||||
async enqueue(message: T): Promise<void> {
|
||||
this.queue.push(message);
|
||||
this.messageAvailable$.next();
|
||||
}
|
||||
|
||||
async dequeue(): Promise<T> {
|
||||
if (this.queue.length > 0) {
|
||||
return this.queue.shift() as T;
|
||||
}
|
||||
|
||||
await firstValueFrom(this.messageAvailable$);
|
||||
return this.queue.shift() as T;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user