1
0
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:
Andreas Coroiu
2025-04-08 15:06:39 +02:00
committed by GitHub
parent b488253722
commit 772b42f5b5
16 changed files with 340 additions and 6 deletions

View File

@@ -0,0 +1,2 @@
export * from "./ipc-message";
export * from "./ipc.service";

View 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";
}

View 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);
}
}

View 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);
}

View 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;
}
}