Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-18039] Add initial verison of IpcServices to client #13373

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/browser/src/background/main.background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ import {
} from "@bitwarden/common/platform/abstractions/storage.service";
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { IpcService } from "@bitwarden/common/platform/ipc";
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
// eslint-disable-next-line no-restricted-imports -- Used for dependency creation
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
Expand Down Expand Up @@ -253,6 +254,7 @@ import { SafariApp } from "../browser/safariApp";
import { BackgroundBrowserBiometricsService } from "../key-management/biometrics/background-browser-biometrics.service";
import { BrowserApi } from "../platform/browser/browser-api";
import { flagEnabled } from "../platform/flags";
import { IpcBackgroundService } from "../platform/ipc/ipc-background.service";
import { UpdateBadge } from "../platform/listeners/update-badge";
/* eslint-disable no-restricted-imports */
import { ChromeMessageSender } from "../platform/messaging/chrome-message.sender";
Expand Down Expand Up @@ -394,6 +396,8 @@ export default class MainBackground {
cipherAuthorizationService: CipherAuthorizationService;
inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;

ipcService: IpcService;

onUpdatedRan: boolean;
onReplacedRan: boolean;
loginToAutoFill: CipherView = null;
Expand Down Expand Up @@ -1278,6 +1282,8 @@ export default class MainBackground {
);

this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();

this.ipcService = new IpcBackgroundService(this.sdkService);
}

async bootstrap() {
Expand Down Expand Up @@ -1344,6 +1350,7 @@ export default class MainBackground {
}

await this.initOverlayAndTabsBackground();
await this.ipcService.init();

return new Promise<void>((resolve) => {
setTimeout(async () => {
Expand Down
12 changes: 12 additions & 0 deletions apps/browser/src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@
"matches": ["*://*/*", "file:///*"],
"exclude_matches": ["*://*/*.xml*", "file:///*.xml*"],
"run_at": "document_start"
},
{
"all_frames": false,
"js": ["content/lp-fileless-importer.js"],
"matches": ["https://lastpass.com/export.php"],
"run_at": "document_start"
},
{
"all_frames": false,
"js": ["content/ipc-content-script.js"],
"matches": ["https://vault.bitwarden.com/*", "https://localhost:8080/*"],
"run_at": "document_start"
}
],
"background": {
Expand Down
12 changes: 12 additions & 0 deletions apps/browser/src/manifest.v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@
"matches": ["*://*/*", "file:///*"],
"exclude_matches": ["*://*/*.xml*", "file:///*.xml*"],
"run_at": "document_start"
},
{
"all_frames": false,
"js": ["content/lp-fileless-importer.js"],
"matches": ["https://lastpass.com/export.php"],
"run_at": "document_start"
},
{
"all_frames": false,
"js": ["content/ipc-content-script.js"],
"matches": ["https://vault.bitwarden.com/*", "https://localhost:8080/*"],
"run_at": "document_start"
}
],
"background": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { IpcMessage, isIpcMessage } from "@bitwarden/common/platform/ipc";
import { MessageQueue } from "@bitwarden/common/platform/ipc/message-queue";
import { CommunicationProvider, Message } from "@bitwarden/sdk-internal";

import { BrowserApi } from "../browser/browser-api";

export class BackgroundCommunicationProvider implements CommunicationProvider {
private queue = new MessageQueue<Message>();

constructor() {
BrowserApi.messageListener("platform.ipc", (message, sender) => {
if (!isIpcMessage(message)) {
return;
}

void this.queue.enqueue({ ...message.message, source: { Web: sender.tab.id } });
});
}

async send(message: Message): Promise<void> {
if (typeof message.destination === "object") {
await BrowserApi.tabSendMessage(
{ id: message.destination.Web } as chrome.tabs.Tab,
{ type: "bitwarden-ipc-message", message } satisfies IpcMessage,
{ frameId: 0 },
);
return;
}

throw new Error("Destination not supported.");
}

async receive(): Promise<Message> {
return this.queue.dequeue();
}
}
47 changes: 47 additions & 0 deletions apps/browser/src/platform/ipc/content/ipc-content-script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// TODO: This content script should be dynamically reloaded when the extension is updated,
// to avoid "Extension context invalidated." errors.

import { isIpcMessage } from "@bitwarden/common/platform/ipc/ipc-message";

// Web -> Background
export function sendExtensionMessage(message: unknown) {
if (
typeof browser !== "undefined" &&
typeof browser.runtime !== "undefined" &&
typeof browser.runtime.sendMessage !== "undefined"
) {
void browser.runtime.sendMessage(message);
return;
}

void chrome.runtime.sendMessage(message);
}

window.addEventListener("message", (event) => {
if (isIpcMessage(event.data)) {
sendExtensionMessage(event.data);
}
});

// Background -> Web
function setupMessageListener() {
function listener(message: unknown) {
if (isIpcMessage(message)) {
void window.postMessage(message);
}
}

if (
typeof browser !== "undefined" &&
typeof browser.runtime !== "undefined" &&
typeof browser.runtime.onMessage !== "undefined"
) {
browser.runtime.onMessage.addListener(listener);
return;
}

// eslint-disable-next-line no-restricted-syntax -- This doesn't run in the popup but in the content script
chrome.runtime.onMessage.addListener(listener);
}

setupMessageListener();
23 changes: 23 additions & 0 deletions apps/browser/src/platform/ipc/ipc-background.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { inject } from "@angular/core";

import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { IpcService } from "@bitwarden/common/platform/ipc";
import { Manager } from "@bitwarden/sdk-internal";

import { BackgroundCommunicationProvider } from "./background-communication-provider";

export class IpcBackgroundService extends IpcService {
private logService = inject(LogService);
private communicationProvider: BackgroundCommunicationProvider;

override async init() {
try {
this.communicationProvider = new BackgroundCommunicationProvider();
this.manager = new Manager(this.communicationProvider);

await super.init();
} catch (e) {
this.logService.error("[IPC] Initialization failed", e);
}
}
}
1 change: 1 addition & 0 deletions apps/browser/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ const mainConfig = {
"content/content-message-handler": "./src/autofill/content/content-message-handler.ts",
"content/fido2-content-script": "./src/autofill/fido2/content/fido2-content-script.ts",
"content/fido2-page-script": "./src/autofill/fido2/content/fido2-page-script.ts",
"content/ipc-content-script": "./src/platform/ipc/content/ipc-content-script.ts",
"notification/bar": "./src/autofill/notification/bar.ts",
"overlay/menu-button":
"./src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts",
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/app/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sd
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { IpcService } from "@bitwarden/common/platform/ipc";
// eslint-disable-next-line no-restricted-imports -- Needed for DI
import {
UnsupportedWebPushConnectionService,
Expand Down Expand Up @@ -116,6 +117,7 @@ import { I18nService } from "../core/i18n.service";
import { WebLockComponentService } from "../key-management/lock/services/web-lock-component.service";
import { WebProcessReloadService } from "../key-management/services/web-process-reload.service";
import { WebBiometricsService } from "../key-management/web-biometric.service";
import { WebIpcService } from "../platform/ipc/web-ipc.service";
import { WebEnvironmentService } from "../platform/web-environment.service";
import { WebMigrationRunner } from "../platform/web-migration-runner";
import { WebSdkLoadService } from "../platform/web-sdk-load.service";
Expand Down Expand Up @@ -332,6 +334,11 @@ const safeProviders: SafeProvider[] = [
useClass: WebLoginDecryptionOptionsService,
deps: [MessagingService, RouterService, AcceptOrganizationInviteService],
}),
safeProvider({
provide: IpcService,
useClass: WebIpcService,
deps: [SdkService],
}),
];

@NgModule({
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/app/core/init.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { IpcService } from "@bitwarden/common/platform/ipc";
import { NotificationsService } from "@bitwarden/common/platform/notifications";
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
Expand All @@ -36,6 +37,7 @@ export class InitService {
private userAutoUnlockKeyService: UserAutoUnlockKeyService,
private accountService: AccountService,
private versionService: VersionService,
private ipcService: IpcService,
private sdkLoadService: SdkLoadService,
@Inject(DOCUMENT) private document: Document,
) {}
Expand All @@ -61,6 +63,7 @@ export class InitService {
htmlEl.classList.add("locale_" + this.i18nService.translationLocale);
this.themingService.applyThemeChangesTo(this.document);
this.versionService.applyVersionToWindow();
void this.ipcService.init();

const containerService = new ContainerService(this.keyService, this.encryptService);
containerService.attachToGlobal(this.win);
Expand Down
34 changes: 34 additions & 0 deletions apps/web/src/app/platform/ipc/web-communication-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { IpcMessage, isIpcMessage } from "@bitwarden/common/platform/ipc";
import { MessageQueue } from "@bitwarden/common/platform/ipc/message-queue";
import { CommunicationProvider, Message } from "@bitwarden/sdk-internal";

export class WebCommunicationProvider implements CommunicationProvider {
private queue = new MessageQueue<Message>();

constructor() {
window.addEventListener("message", async (event: MessageEvent) => {
const message = event.data;
if (!isIpcMessage(message)) {
return;
}

await this.queue.enqueue(message.message);
});
}

async send(message: Message): Promise<void> {
if (message.destination === "BrowserBackground") {
window.postMessage(
{ type: "bitwarden-ipc-message", message } satisfies IpcMessage,
window.location.origin,
);
return;
}

throw new Error(`Destination not supported: ${message.destination}`);
}

receive(): Promise<Message> {
return this.queue.dequeue();
}
}
19 changes: 19 additions & 0 deletions apps/web/src/app/platform/ipc/web-ipc.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { inject } from "@angular/core";

import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { IpcService } from "@bitwarden/common/platform/ipc";
import { Manager } from "@bitwarden/sdk-internal";

import { WebCommunicationProvider } from "./web-communication-provider";

export class WebIpcService extends IpcService {
private logService = inject(LogService);
private communicationProvider: WebCommunicationProvider;

async init() {
this.communicationProvider = new WebCommunicationProvider();
this.manager = new Manager(this.communicationProvider);

await super.init();
}
}
2 changes: 2 additions & 0 deletions libs/common/src/platform/ipc/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./ipc-message";
export * from "./ipc.service";
10 changes: 10 additions & 0 deletions libs/common/src/platform/ipc/ipc-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Message } from "@bitwarden/sdk-internal";

export interface IpcMessage {
type: "bitwarden-ipc-message";
message: Message;
}

export function isIpcMessage(message: any): message is IpcMessage {
return message.type === "bitwarden-ipc-message";
}
36 changes: 36 additions & 0 deletions libs/common/src/platform/ipc/ipc.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Observable, shareReplay } from "rxjs";

import { Manager, Message } from "@bitwarden/sdk-internal";

export abstract class IpcService {
protected manager: Manager;

messages$: Observable<Message>;

async init(): Promise<void> {
this.messages$ = new Observable<Message>((subscriber) => {
let isSubscribed = true;

const receiveLoop = async () => {
while (isSubscribed) {
try {
const message = await this.manager.receive();
subscriber.next(message);
} catch (error) {
subscriber.error(error);
break;
}
}
};
void receiveLoop();

return () => {
isSubscribed = false;
};
}).pipe(shareReplay({ bufferSize: 1, refCount: true }));
}

async send(message: Message) {
await this.manager.send(message);
}
}
Loading
Loading