1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

[PM-7489] Introduce MessageSender & MessageListener (#8709)

* Introduce MessageSender

* Update `messageSenderFactory`

* Remove Comment

* Use BrowserApi

* Update Comment

* Rename to CommandDefinition

* Add More Documentation to MessageSender

* Add `EMPTY` helpers and remove NoopMessageSender

* Calm Down Logging

* Limit Logging On Known Errors

* Use `messageStream` Parameter

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>

* Add eslint rules

* Update Error Handling

Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com>

* Delete Lazy Classes In Favor of Observable Factories

* Remove Fido Messages

---------

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com>
This commit is contained in:
Justin Baur
2024-04-19 14:02:40 -05:00
committed by GitHub
parent 9a4279c8bb
commit 395ed3f5d4
44 changed files with 855 additions and 361 deletions

View File

@@ -1,4 +1,5 @@
import { APP_INITIALIZER, NgModule } from "@angular/core";
import { Subject, merge } from "rxjs";
import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
import {
@@ -14,6 +15,7 @@ import {
SYSTEM_THEME_OBSERVABLE,
SafeInjectionToken,
STATE_FACTORY,
INTRAPROCESS_MESSAGING_SUBJECT,
} from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
@@ -23,7 +25,6 @@ import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/ab
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@@ -42,6 +43,9 @@ import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service";
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
// eslint-disable-next-line no-restricted-imports -- Used for dependency injection
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
@@ -63,11 +67,12 @@ import {
ELECTRON_SUPPORTS_SECURE_STORAGE,
ElectronPlatformUtilsService,
} from "../../platform/services/electron-platform-utils.service";
import { ElectronRendererMessagingService } from "../../platform/services/electron-renderer-messaging.service";
import { ElectronRendererMessageSender } from "../../platform/services/electron-renderer-message.sender";
import { ElectronRendererSecureStorageService } from "../../platform/services/electron-renderer-secure-storage.service";
import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service";
import { ElectronStateService } from "../../platform/services/electron-state.service";
import { I18nRendererService } from "../../platform/services/i18n.renderer.service";
import { fromIpcMessaging } from "../../platform/utils/from-ipc-messaging";
import { fromIpcSystemTheme } from "../../platform/utils/from-ipc-system-theme";
import { EncryptedMessageHandlerService } from "../../services/encrypted-message-handler.service";
import { NativeMessageHandlerService } from "../../services/native-message-handler.service";
@@ -138,9 +143,24 @@ const safeProviders: SafeProvider[] = [
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY, GlobalStateProvider],
}),
safeProvider({
provide: MessagingServiceAbstraction,
useClass: ElectronRendererMessagingService,
deps: [BroadcasterServiceAbstraction],
provide: MessageSender,
useFactory: (subject: Subject<Message<object>>) =>
MessageSender.combine(
new ElectronRendererMessageSender(), // Communication with main process
new SubjectMessageSender(subject), // Communication with ourself
),
deps: [INTRAPROCESS_MESSAGING_SUBJECT],
}),
safeProvider({
provide: MessageListener,
useFactory: (subject: Subject<Message<object>>) =>
new MessageListener(
merge(
subject.asObservable(), // For messages from the same context
fromIpcMessaging(), // For messages from the main process
),
),
deps: [INTRAPROCESS_MESSAGING_SUBJECT],
}),
safeProvider({
provide: AbstractStorageService,

View File

@@ -1,7 +1,7 @@
import * as path from "path";
import { app } from "electron";
import { firstValueFrom } from "rxjs";
import { Subject, firstValueFrom } from "rxjs";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
@@ -11,6 +11,9 @@ import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwar
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DefaultBiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { Message, MessageSender } from "@bitwarden/common/platform/messaging";
// eslint-disable-next-line no-restricted-imports -- For dependency creation
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
@@ -18,7 +21,6 @@ import { KeyGenerationService } from "@bitwarden/common/platform/services/key-ge
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-messaging.service";
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed */
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
@@ -59,7 +61,7 @@ export class Main {
storageService: ElectronStorageService;
memoryStorageService: MemoryStorageService;
memoryStorageForStateProviders: MemoryStorageServiceForStateProviders;
messagingService: ElectronMainMessagingService;
messagingService: MessageSender;
stateService: StateService;
environmentService: DefaultEnvironmentService;
mainCryptoFunctionService: MainCryptoFunctionService;
@@ -131,7 +133,7 @@ export class Main {
this.i18nService = new I18nMainService("en", "./locales/", globalStateProvider);
const accountService = new AccountServiceImplementation(
new NoopMessagingService(),
MessageSender.EMPTY,
this.logService,
globalStateProvider,
);
@@ -223,7 +225,13 @@ export class Main {
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain);
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.desktopSettingsService);
this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => {
const messageSubject = new Subject<Message<object>>();
this.messagingService = MessageSender.combine(
new SubjectMessageSender(messageSubject), // For local messages
new ElectronMainMessagingService(this.windowMain),
);
messageSubject.asObservable().subscribe((message) => {
this.messagingMain.onMessage(message);
});

View File

@@ -1,6 +1,7 @@
import { powerMonitor } from "electron";
import { ElectronMainMessagingService } from "../services/electron-main-messaging.service";
import { MessageSender } from "@bitwarden/common/platform/messaging";
import { isSnapStore } from "../utils";
// tslint:disable-next-line
@@ -10,7 +11,7 @@ const IdleCheckInterval = 30 * 1000; // 30 seconds
export class PowerMonitorMain {
private idle = false;
constructor(private messagingService: ElectronMainMessagingService) {}
constructor(private messagingService: MessageSender) {}
init() {
// ref: https://github.com/electron/electron/issues/13767

View File

@@ -124,12 +124,21 @@ export default {
sendMessage: (message: { command: string } & any) =>
ipcRenderer.send("messagingService", message),
onMessage: (callback: (message: { command: string } & any) => void) => {
ipcRenderer.on("messagingService", (_event, message: any) => {
if (message.command) {
callback(message);
}
});
onMessage: {
addListener: (callback: (message: { command: string } & any) => void) => {
ipcRenderer.addListener("messagingService", (_event, message: any) => {
if (message.command) {
callback(message);
}
});
},
removeListener: (callback: (message: { command: string } & any) => void) => {
ipcRenderer.removeListener("messagingService", (_event, message: any) => {
if (message.command) {
callback(message);
}
});
},
},
launchUri: (uri: string) => ipcRenderer.invoke("launchUri", uri),

View File

@@ -0,0 +1,12 @@
import { MessageSender, CommandDefinition } from "@bitwarden/common/platform/messaging";
import { getCommand } from "@bitwarden/common/platform/messaging/internal";
export class ElectronRendererMessageSender implements MessageSender {
send<T extends object>(
commandDefinition: CommandDefinition<T> | string,
payload: object | T = {},
): void {
const command = getCommand(commandDefinition);
ipc.platform.sendMessage(Object.assign({}, { command: command }, payload));
}
}

View File

@@ -1,20 +0,0 @@
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
export class ElectronRendererMessagingService implements MessagingService {
constructor(private broadcasterService: BroadcasterService) {
ipc.platform.onMessage((message) => this.sendMessage(message.command, message, false));
}
send(subscriber: string, arg: any = {}) {
this.sendMessage(subscriber, arg, true);
}
private sendMessage(subscriber: string, arg: any = {}, toMain: boolean) {
const message = Object.assign({}, { command: subscriber }, arg);
this.broadcasterService.send(message);
if (toMain) {
ipc.platform.sendMessage(message);
}
}
}

View File

@@ -0,0 +1,15 @@
import { fromEventPattern, share } from "rxjs";
import { Message } from "@bitwarden/common/platform/messaging";
import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal";
/**
* Creates an observable that when subscribed to will listen to messaging events through IPC.
* @returns An observable stream of messages.
*/
export const fromIpcMessaging = () => {
return fromEventPattern<Message<object>>(
(handler) => ipc.platform.onMessage.addListener(handler),
(handler) => ipc.platform.onMessage.removeListener(handler),
).pipe(tagAsExternal, share());
};

View File

@@ -2,18 +2,17 @@ import * as path from "path";
import { app, dialog, ipcMain, Menu, MenuItem, nativeTheme, Notification, shell } from "electron";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { MessageSender, CommandDefinition } from "@bitwarden/common/platform/messaging";
// eslint-disable-next-line no-restricted-imports -- Using implementation helper in implementation
import { getCommand } from "@bitwarden/common/platform/messaging/internal";
import { SafeUrls } from "@bitwarden/common/platform/misc/safe-urls";
import { WindowMain } from "../main/window.main";
import { RendererMenuItem } from "../utils";
export class ElectronMainMessagingService implements MessagingService {
constructor(
private windowMain: WindowMain,
private onMessage: (message: any) => void,
) {
export class ElectronMainMessagingService implements MessageSender {
constructor(private windowMain: WindowMain) {
ipcMain.handle("appVersion", () => {
return app.getVersion();
});
@@ -88,9 +87,9 @@ export class ElectronMainMessagingService implements MessagingService {
});
}
send(subscriber: string, arg: any = {}) {
const message = Object.assign({}, { command: subscriber }, arg);
this.onMessage(message);
send<T extends object>(commandDefinition: CommandDefinition<T> | string, arg: T | object = {}) {
const command = getCommand(commandDefinition);
const message = Object.assign({}, { command: command }, arg);
if (this.windowMain.win != null) {
this.windowMain.win.webContents.send("messagingService", message);
}