mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +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:
@@ -1,4 +1,4 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { Subject, firstValueFrom, merge } from "rxjs";
|
||||
|
||||
import {
|
||||
PinCryptoServiceAbstraction,
|
||||
@@ -82,7 +82,6 @@ import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/co
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||
import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
@@ -95,6 +94,9 @@ import {
|
||||
DefaultBiometricStateService,
|
||||
} 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 creation
|
||||
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
||||
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
|
||||
@@ -208,13 +210,14 @@ import { Account } from "../models/account";
|
||||
import { BrowserApi } from "../platform/browser/browser-api";
|
||||
import { flagEnabled } from "../platform/flags";
|
||||
import { UpdateBadge } from "../platform/listeners/update-badge";
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import { ChromeMessageSender } from "../platform/messaging/chrome-message.sender";
|
||||
/* eslint-enable no-restricted-imports */
|
||||
import { BrowserStateService as StateServiceAbstraction } from "../platform/services/abstractions/browser-state.service";
|
||||
import { BrowserCryptoService } from "../platform/services/browser-crypto.service";
|
||||
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
||||
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
|
||||
import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service";
|
||||
import BrowserMessagingPrivateModeBackgroundService from "../platform/services/browser-messaging-private-mode-background.service";
|
||||
import BrowserMessagingService from "../platform/services/browser-messaging.service";
|
||||
import { BrowserScriptInjectorService } from "../platform/services/browser-script-injector.service";
|
||||
import { DefaultBrowserStateService } from "../platform/services/default-browser-state.service";
|
||||
import I18nService from "../platform/services/i18n.service";
|
||||
@@ -223,6 +226,7 @@ import { BackgroundPlatformUtilsService } from "../platform/services/platform-ut
|
||||
import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
|
||||
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
|
||||
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
||||
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
|
||||
import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
|
||||
import FilelessImporterBackground from "../tools/background/fileless-importer.background";
|
||||
import { Fido2Background as Fido2BackgroundAbstraction } from "../vault/fido2/background/abstractions/fido2.background";
|
||||
@@ -236,7 +240,7 @@ import { NativeMessagingBackground } from "./nativeMessaging.background";
|
||||
import RuntimeBackground from "./runtime.background";
|
||||
|
||||
export default class MainBackground {
|
||||
messagingService: MessagingServiceAbstraction;
|
||||
messagingService: MessageSender;
|
||||
storageService: BrowserLocalStorageService;
|
||||
secureStorageService: AbstractStorageService;
|
||||
memoryStorageService: AbstractMemoryStorageService;
|
||||
@@ -326,6 +330,8 @@ export default class MainBackground {
|
||||
stateEventRunnerService: StateEventRunnerService;
|
||||
ssoLoginService: SsoLoginServiceAbstraction;
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService;
|
||||
// eslint-disable-next-line rxjs/no-exposed-subjects -- Needed to give access to services module
|
||||
intraprocessMessagingSubject: Subject<Message<object>>;
|
||||
userKeyInitService: UserKeyInitService;
|
||||
scriptInjectorService: BrowserScriptInjectorService;
|
||||
|
||||
@@ -369,15 +375,25 @@ export default class MainBackground {
|
||||
const logoutCallback = async (expired: boolean, userId?: UserId) =>
|
||||
await this.logout(expired, userId);
|
||||
|
||||
this.messagingService =
|
||||
this.isPrivateMode && BrowserApi.isManifestVersion(2)
|
||||
? new BrowserMessagingPrivateModeBackgroundService()
|
||||
: new BrowserMessagingService();
|
||||
this.logService = new ConsoleLogService(false);
|
||||
this.cryptoFunctionService = new WebCryptoFunctionService(self);
|
||||
this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService);
|
||||
this.storageService = new BrowserLocalStorageService();
|
||||
|
||||
this.intraprocessMessagingSubject = new Subject<Message<object>>();
|
||||
|
||||
this.messagingService = MessageSender.combine(
|
||||
new SubjectMessageSender(this.intraprocessMessagingSubject),
|
||||
new ChromeMessageSender(this.logService),
|
||||
);
|
||||
|
||||
const messageListener = new MessageListener(
|
||||
merge(
|
||||
this.intraprocessMessagingSubject.asObservable(), // For messages from the same context
|
||||
fromChromeRuntimeMessaging(), // For messages from other contexts
|
||||
),
|
||||
);
|
||||
|
||||
const mv3MemoryStorageCreator = (partitionName: string) => {
|
||||
// TODO: Consider using multithreaded encrypt service in popup only context
|
||||
return new LocalBackedSessionStorageService(
|
||||
@@ -560,21 +576,6 @@ export default class MainBackground {
|
||||
|
||||
this.twoFactorService = new TwoFactorService(this.i18nService, this.platformUtilsService);
|
||||
|
||||
// eslint-disable-next-line
|
||||
const that = this;
|
||||
const backgroundMessagingService = new (class extends MessagingServiceAbstraction {
|
||||
// AuthService should send the messages to the background not popup.
|
||||
send = (subscriber: string, arg: any = {}) => {
|
||||
if (BrowserApi.isManifestVersion(3)) {
|
||||
that.messagingService.send(subscriber, arg);
|
||||
return;
|
||||
}
|
||||
|
||||
const message = Object.assign({}, { command: subscriber }, arg);
|
||||
void that.runtimeBackground.processMessage(message, that as any);
|
||||
};
|
||||
})();
|
||||
|
||||
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
|
||||
|
||||
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
|
||||
@@ -605,7 +606,7 @@ export default class MainBackground {
|
||||
|
||||
this.authService = new AuthService(
|
||||
this.accountService,
|
||||
backgroundMessagingService,
|
||||
this.messagingService,
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.stateService,
|
||||
@@ -626,7 +627,7 @@ export default class MainBackground {
|
||||
this.tokenService,
|
||||
this.appIdService,
|
||||
this.platformUtilsService,
|
||||
backgroundMessagingService,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
this.keyConnectorService,
|
||||
this.environmentService,
|
||||
@@ -914,6 +915,7 @@ export default class MainBackground {
|
||||
this.logService,
|
||||
this.configService,
|
||||
this.fido2Background,
|
||||
messageListener,
|
||||
);
|
||||
this.nativeMessagingBackground = new NativeMessagingBackground(
|
||||
this.accountService,
|
||||
|
||||
@@ -399,7 +399,7 @@ export class NativeMessagingBackground {
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.runtimeBackground.processMessage({ command: "unlocked" }, null);
|
||||
this.runtimeBackground.processMessage({ command: "unlocked" });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { firstValueFrom, mergeMap } from "rxjs";
|
||||
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
|
||||
@@ -10,6 +10,7 @@ import { SystemService } from "@bitwarden/common/platform/abstractions/system.se
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
|
||||
import { MessageListener } from "../../../../libs/common/src/platform/messaging";
|
||||
import {
|
||||
closeUnlockPopout,
|
||||
openSsoAuthResultPopout,
|
||||
@@ -44,6 +45,7 @@ export default class RuntimeBackground {
|
||||
private logService: LogService,
|
||||
private configService: ConfigService,
|
||||
private fido2Background: Fido2Background,
|
||||
private messageListener: MessageListener,
|
||||
) {
|
||||
// onInstalled listener must be wired up before anything else, so we do it in the ctor
|
||||
chrome.runtime.onInstalled.addListener((details: any) => {
|
||||
@@ -60,92 +62,47 @@ export default class RuntimeBackground {
|
||||
const backgroundMessageListener = (
|
||||
msg: any,
|
||||
sender: chrome.runtime.MessageSender,
|
||||
sendResponse: any,
|
||||
sendResponse: (response: any) => void,
|
||||
) => {
|
||||
const messagesWithResponse = ["biometricUnlock"];
|
||||
|
||||
if (messagesWithResponse.includes(msg.command)) {
|
||||
this.processMessage(msg, sender).then(
|
||||
this.processMessageWithSender(msg, sender).then(
|
||||
(value) => sendResponse({ result: value }),
|
||||
(error) => sendResponse({ error: { ...error, message: error.message } }),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
this.processMessage(msg, sender).catch((e) => this.logService.error(e));
|
||||
void this.processMessageWithSender(msg, sender).catch((err) =>
|
||||
this.logService.error(
|
||||
`Error while processing message in RuntimeBackground '${msg?.command}'. Error: ${err?.message ?? "Unknown Error"}`,
|
||||
),
|
||||
);
|
||||
return false;
|
||||
};
|
||||
|
||||
this.messageListener.allMessages$
|
||||
.pipe(
|
||||
mergeMap(async (message: any) => {
|
||||
await this.processMessage(message);
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// For messages that require the full on message interface
|
||||
BrowserApi.messageListener("runtime.background", backgroundMessageListener);
|
||||
if (this.main.popupOnlyContext) {
|
||||
(self as any).bitwardenBackgroundMessageListener = backgroundMessageListener;
|
||||
}
|
||||
}
|
||||
|
||||
async processMessage(msg: any, sender: chrome.runtime.MessageSender) {
|
||||
// Messages that need the chrome sender and send back a response need to be registered in this method.
|
||||
async processMessageWithSender(msg: any, sender: chrome.runtime.MessageSender) {
|
||||
switch (msg.command) {
|
||||
case "loggedIn":
|
||||
case "unlocked": {
|
||||
let item: LockedVaultPendingNotificationsData;
|
||||
|
||||
if (msg.command === "loggedIn") {
|
||||
await this.sendBwInstalledMessageToVault();
|
||||
}
|
||||
|
||||
if (this.lockedVaultPendingNotifications?.length > 0) {
|
||||
item = this.lockedVaultPendingNotifications.pop();
|
||||
await closeUnlockPopout();
|
||||
}
|
||||
|
||||
await this.notificationsService.updateConnection(msg.command === "loggedIn");
|
||||
await this.main.refreshBadge();
|
||||
await this.main.refreshMenu(false);
|
||||
this.systemService.cancelProcessReload();
|
||||
|
||||
if (item) {
|
||||
await BrowserApi.focusWindow(item.commandToRetry.sender.tab.windowId);
|
||||
await BrowserApi.focusTab(item.commandToRetry.sender.tab.id);
|
||||
await BrowserApi.tabSendMessageData(
|
||||
item.commandToRetry.sender.tab,
|
||||
"unlockCompleted",
|
||||
item,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "addToLockedVaultPendingNotifications":
|
||||
this.lockedVaultPendingNotifications.push(msg.data);
|
||||
break;
|
||||
case "logout":
|
||||
await this.main.logout(msg.expired, msg.userId);
|
||||
break;
|
||||
case "syncCompleted":
|
||||
if (msg.successfully) {
|
||||
setTimeout(async () => {
|
||||
await this.main.refreshBadge();
|
||||
await this.main.refreshMenu();
|
||||
}, 2000);
|
||||
await this.configService.ensureConfigFetched();
|
||||
}
|
||||
break;
|
||||
case "openPopup":
|
||||
await this.main.openPopup();
|
||||
break;
|
||||
case "triggerAutofillScriptInjection":
|
||||
await this.autofillService.injectAutofillScripts(sender.tab, sender.frameId);
|
||||
break;
|
||||
case "bgCollectPageDetails":
|
||||
await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId);
|
||||
break;
|
||||
case "bgUpdateContextMenu":
|
||||
case "editedCipher":
|
||||
case "addedCipher":
|
||||
case "deletedCipher":
|
||||
await this.main.refreshBadge();
|
||||
await this.main.refreshMenu();
|
||||
break;
|
||||
case "bgReseedStorage":
|
||||
await this.main.reseedStorage();
|
||||
break;
|
||||
case "collectPageDetailsResponse":
|
||||
switch (msg.sender) {
|
||||
case "autofiller":
|
||||
@@ -209,6 +166,72 @@ export default class RuntimeBackground {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "biometricUnlock": {
|
||||
const result = await this.main.biometricUnlock();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async processMessage(msg: any) {
|
||||
switch (msg.command) {
|
||||
case "loggedIn":
|
||||
case "unlocked": {
|
||||
let item: LockedVaultPendingNotificationsData;
|
||||
|
||||
if (msg.command === "loggedIn") {
|
||||
await this.sendBwInstalledMessageToVault();
|
||||
}
|
||||
|
||||
if (this.lockedVaultPendingNotifications?.length > 0) {
|
||||
item = this.lockedVaultPendingNotifications.pop();
|
||||
await closeUnlockPopout();
|
||||
}
|
||||
|
||||
await this.notificationsService.updateConnection(msg.command === "loggedIn");
|
||||
await this.main.refreshBadge();
|
||||
await this.main.refreshMenu(false);
|
||||
this.systemService.cancelProcessReload();
|
||||
|
||||
if (item) {
|
||||
await BrowserApi.focusWindow(item.commandToRetry.sender.tab.windowId);
|
||||
await BrowserApi.focusTab(item.commandToRetry.sender.tab.id);
|
||||
await BrowserApi.tabSendMessageData(
|
||||
item.commandToRetry.sender.tab,
|
||||
"unlockCompleted",
|
||||
item,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "addToLockedVaultPendingNotifications":
|
||||
this.lockedVaultPendingNotifications.push(msg.data);
|
||||
break;
|
||||
case "logout":
|
||||
await this.main.logout(msg.expired, msg.userId);
|
||||
break;
|
||||
case "syncCompleted":
|
||||
if (msg.successfully) {
|
||||
setTimeout(async () => {
|
||||
await this.main.refreshBadge();
|
||||
await this.main.refreshMenu();
|
||||
}, 2000);
|
||||
await this.configService.ensureConfigFetched();
|
||||
}
|
||||
break;
|
||||
case "openPopup":
|
||||
await this.main.openPopup();
|
||||
break;
|
||||
case "bgUpdateContextMenu":
|
||||
case "editedCipher":
|
||||
case "addedCipher":
|
||||
case "deletedCipher":
|
||||
await this.main.refreshBadge();
|
||||
await this.main.refreshMenu();
|
||||
break;
|
||||
case "bgReseedStorage":
|
||||
await this.main.reseedStorage();
|
||||
break;
|
||||
case "authResult": {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const vaultUrl = env.getWebVaultUrl();
|
||||
@@ -265,9 +288,6 @@ export default class RuntimeBackground {
|
||||
await this.main.clearClipboard(msg.clipboardValue, msg.timeoutMs);
|
||||
break;
|
||||
}
|
||||
case "biometricUnlock": {
|
||||
return await this.main.biometricUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MessageSender } from "@bitwarden/common/platform/messaging";
|
||||
|
||||
import { CachedServices, factory, FactoryOptions } from "./factory-options";
|
||||
|
||||
type MessagingServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type MessageSenderInitOptions = MessagingServiceFactoryOptions;
|
||||
|
||||
export function messageSenderFactory(
|
||||
cache: { messagingService?: MessageSender } & CachedServices,
|
||||
opts: MessageSenderInitOptions,
|
||||
): Promise<MessageSender> {
|
||||
// NOTE: Name needs to match that of MainBackground property until we delete these.
|
||||
return factory(cache, "messagingService", opts, () => {
|
||||
throw new Error("Not implemented, not expected to be used.");
|
||||
});
|
||||
}
|
||||
@@ -1,19 +1,5 @@
|
||||
import { MessagingService as AbstractMessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
|
||||
import {
|
||||
CachedServices,
|
||||
factory,
|
||||
FactoryOptions,
|
||||
} from "../../background/service-factories/factory-options";
|
||||
import BrowserMessagingService from "../../services/browser-messaging.service";
|
||||
|
||||
type MessagingServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type MessagingServiceInitOptions = MessagingServiceFactoryOptions;
|
||||
|
||||
export function messagingServiceFactory(
|
||||
cache: { messagingService?: AbstractMessagingService } & CachedServices,
|
||||
opts: MessagingServiceInitOptions,
|
||||
): Promise<AbstractMessagingService> {
|
||||
return factory(cache, "messagingService", opts, () => new BrowserMessagingService());
|
||||
}
|
||||
// Export old messaging service stuff to minimize changes
|
||||
export {
|
||||
messageSenderFactory as messagingServiceFactory,
|
||||
MessageSenderInitOptions as MessagingServiceInitOptions,
|
||||
} from "./message-sender.factory";
|
||||
|
||||
37
apps/browser/src/platform/messaging/chrome-message.sender.ts
Normal file
37
apps/browser/src/platform/messaging/chrome-message.sender.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CommandDefinition, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||
import { getCommand } from "@bitwarden/common/platform/messaging/internal";
|
||||
|
||||
type ErrorHandler = (logger: LogService, command: string) => void;
|
||||
|
||||
const HANDLED_ERRORS: Record<string, ErrorHandler> = {
|
||||
"Could not establish connection. Receiving end does not exist.": (logger, command) =>
|
||||
logger.debug(`Receiving end didn't exist for command '${command}'`),
|
||||
|
||||
"The message port closed before a response was received.": (logger, command) =>
|
||||
logger.debug(`Port was closed for command '${command}'`),
|
||||
};
|
||||
|
||||
export class ChromeMessageSender implements MessageSender {
|
||||
constructor(private readonly logService: LogService) {}
|
||||
|
||||
send<T extends object>(
|
||||
commandDefinition: string | CommandDefinition<T>,
|
||||
payload: object | T = {},
|
||||
): void {
|
||||
const command = getCommand(commandDefinition);
|
||||
chrome.runtime.sendMessage(Object.assign(payload, { command: command }), () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
const errorHandler = HANDLED_ERRORS[chrome.runtime.lastError.message];
|
||||
if (errorHandler != null) {
|
||||
errorHandler(this.logService, command);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.warning(
|
||||
`Unhandled error while sending message with command '${command}': ${chrome.runtime.lastError.message}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
|
||||
export default class BrowserMessagingPrivateModeBackgroundService implements MessagingService {
|
||||
send(subscriber: string, arg: any = {}) {
|
||||
const message = Object.assign({}, { command: subscriber }, arg);
|
||||
(self as any).bitwardenPopupMainMessageListener(message);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
|
||||
export default class BrowserMessagingPrivateModePopupService implements MessagingService {
|
||||
send(subscriber: string, arg: any = {}) {
|
||||
const message = Object.assign({}, { command: subscriber }, arg);
|
||||
(self as any).bitwardenBackgroundMessageListener(message);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
|
||||
export default class BrowserMessagingService implements MessagingService {
|
||||
send(subscriber: string, arg: any = {}) {
|
||||
return BrowserApi.sendMessage(subscriber, arg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { map, share } from "rxjs";
|
||||
|
||||
import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal";
|
||||
|
||||
import { fromChromeEvent } from "../browser/from-chrome-event";
|
||||
|
||||
/**
|
||||
* Creates an observable that listens to messages through `chrome.runtime.onMessage`.
|
||||
* @returns An observable stream of messages.
|
||||
*/
|
||||
export const fromChromeRuntimeMessaging = () => {
|
||||
return fromChromeEvent(chrome.runtime.onMessage).pipe(
|
||||
map(([message, sender]) => {
|
||||
message ??= {};
|
||||
|
||||
// Force the sender onto the message as long as we won't overwrite anything
|
||||
if (!("webExtSender" in message)) {
|
||||
message.webExtSender = sender;
|
||||
}
|
||||
|
||||
return message;
|
||||
}),
|
||||
tagAsExternal,
|
||||
share(),
|
||||
);
|
||||
};
|
||||
@@ -1,17 +1,16 @@
|
||||
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
||||
import { filter, concatMap, Subject, takeUntil, firstValueFrom, map } from "rxjs";
|
||||
import { filter, concatMap, Subject, takeUntil, firstValueFrom, tap, map } from "rxjs";
|
||||
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { MessageListener } from "@bitwarden/common/platform/messaging";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { BrowserApi } from "../platform/browser/browser-api";
|
||||
import { ZonedMessageListenerService } from "../platform/browser/zoned-message-listener.service";
|
||||
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
|
||||
import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service";
|
||||
import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service";
|
||||
@@ -34,7 +33,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private broadcasterService: BroadcasterService,
|
||||
private authService: AuthService,
|
||||
private i18nService: I18nService,
|
||||
private router: Router,
|
||||
@@ -46,7 +44,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private ngZone: NgZone,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private dialogService: DialogService,
|
||||
private browserMessagingApi: ZonedMessageListenerService,
|
||||
private messageListener: MessageListener,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
@@ -78,77 +76,76 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
window.onkeypress = () => this.recordActivity();
|
||||
});
|
||||
|
||||
const bitwardenPopupMainMessageListener = (msg: any, sender: any) => {
|
||||
if (msg.command === "doneLoggingOut") {
|
||||
this.authService.logOut(async () => {
|
||||
if (msg.expired) {
|
||||
this.toastService.showToast({
|
||||
variant: "warning",
|
||||
title: this.i18nService.t("loggedOut"),
|
||||
message: this.i18nService.t("loginExpired"),
|
||||
this.messageListener.allMessages$
|
||||
.pipe(
|
||||
tap((msg: any) => {
|
||||
if (msg.command === "doneLoggingOut") {
|
||||
this.authService.logOut(async () => {
|
||||
if (msg.expired) {
|
||||
this.toastService.showToast({
|
||||
variant: "warning",
|
||||
title: this.i18nService.t("loggedOut"),
|
||||
message: this.i18nService.t("loginExpired"),
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["home"]);
|
||||
});
|
||||
this.changeDetectorRef.detectChanges();
|
||||
} else if (msg.command === "authBlocked") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["home"]);
|
||||
} else if (
|
||||
msg.command === "locked" &&
|
||||
(msg.userId == null || msg.userId == this.activeUserId)
|
||||
) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["lock"]);
|
||||
} else if (msg.command === "showDialog") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.showDialog(msg);
|
||||
} else if (msg.command === "showNativeMessagingFinterprintDialog") {
|
||||
// TODO: Should be refactored to live in another service.
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.showNativeMessagingFingerprintDialog(msg);
|
||||
} else if (msg.command === "showToast") {
|
||||
this.toastService._showToast(msg);
|
||||
} else if (msg.command === "reloadProcess") {
|
||||
const forceWindowReload =
|
||||
this.platformUtilsService.isSafari() ||
|
||||
this.platformUtilsService.isFirefox() ||
|
||||
this.platformUtilsService.isOpera();
|
||||
// Wait to make sure background has reloaded first.
|
||||
window.setTimeout(
|
||||
() => BrowserApi.reloadExtension(forceWindowReload ? window : null),
|
||||
2000,
|
||||
);
|
||||
} else if (msg.command === "reloadPopup") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/"]);
|
||||
} else if (msg.command === "convertAccountToKeyConnector") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/remove-password"]);
|
||||
} else if (msg.command === "switchAccountFinish") {
|
||||
// TODO: unset loading?
|
||||
// this.loading = false;
|
||||
} else if (msg.command == "update-temp-password") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/update-temp-password"]);
|
||||
}
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["home"]);
|
||||
});
|
||||
this.changeDetectorRef.detectChanges();
|
||||
} else if (msg.command === "authBlocked") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["home"]);
|
||||
} else if (
|
||||
msg.command === "locked" &&
|
||||
(msg.userId == null || msg.userId == this.activeUserId)
|
||||
) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["lock"]);
|
||||
} else if (msg.command === "showDialog") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.showDialog(msg);
|
||||
} else if (msg.command === "showNativeMessagingFinterprintDialog") {
|
||||
// TODO: Should be refactored to live in another service.
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.showNativeMessagingFingerprintDialog(msg);
|
||||
} else if (msg.command === "showToast") {
|
||||
this.toastService._showToast(msg);
|
||||
} else if (msg.command === "reloadProcess") {
|
||||
const forceWindowReload =
|
||||
this.platformUtilsService.isSafari() ||
|
||||
this.platformUtilsService.isFirefox() ||
|
||||
this.platformUtilsService.isOpera();
|
||||
// Wait to make sure background has reloaded first.
|
||||
window.setTimeout(
|
||||
() => BrowserApi.reloadExtension(forceWindowReload ? window : null),
|
||||
2000,
|
||||
);
|
||||
} else if (msg.command === "reloadPopup") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/"]);
|
||||
} else if (msg.command === "convertAccountToKeyConnector") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/remove-password"]);
|
||||
} else if (msg.command === "switchAccountFinish") {
|
||||
// TODO: unset loading?
|
||||
// this.loading = false;
|
||||
} else if (msg.command == "update-temp-password") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/update-temp-password"]);
|
||||
} else {
|
||||
msg.webExtSender = sender;
|
||||
this.broadcasterService.send(msg);
|
||||
}
|
||||
};
|
||||
|
||||
(self as any).bitwardenPopupMainMessageListener = bitwardenPopupMainMessageListener;
|
||||
this.browserMessagingApi.messageListener("app.component", bitwardenPopupMainMessageListener);
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// eslint-disable-next-line rxjs/no-async-subscribe
|
||||
this.router.events.pipe(takeUntil(this.destroy$)).subscribe(async (event) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { Subject, merge } from "rxjs";
|
||||
|
||||
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards";
|
||||
import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service";
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
OBSERVABLE_MEMORY_STORAGE,
|
||||
SYSTEM_THEME_OBSERVABLE,
|
||||
SafeInjectionToken,
|
||||
INTRAPROCESS_MESSAGING_SUBJECT,
|
||||
} from "@bitwarden/angular/services/injection-tokens";
|
||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||
import {
|
||||
@@ -54,7 +56,6 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import {
|
||||
@@ -63,6 +64,9 @@ import {
|
||||
ObservableStorageService,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.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 { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||
@@ -89,20 +93,23 @@ import AutofillService from "../../autofill/services/autofill.service";
|
||||
import MainBackground from "../../background/main.background";
|
||||
import { Account } from "../../models/account";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import { runInsideAngular } from "../../platform/browser/run-inside-angular.operator";
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import { ChromeMessageSender } from "../../platform/messaging/chrome-message.sender";
|
||||
/* eslint-enable no-restricted-imports */
|
||||
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
|
||||
import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service";
|
||||
import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service";
|
||||
import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service";
|
||||
import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service";
|
||||
import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service";
|
||||
import BrowserMessagingPrivateModePopupService from "../../platform/services/browser-messaging-private-mode-popup.service";
|
||||
import BrowserMessagingService from "../../platform/services/browser-messaging.service";
|
||||
import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service";
|
||||
import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service";
|
||||
import I18nService from "../../platform/services/i18n.service";
|
||||
import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service";
|
||||
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
|
||||
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
|
||||
import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging";
|
||||
import { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service";
|
||||
import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service";
|
||||
import { VaultBrowserStateService } from "../../vault/services/vault-browser-state.service";
|
||||
@@ -155,15 +162,6 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: UnauthGuardService,
|
||||
deps: [AuthServiceAbstraction, Router],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: MessagingService,
|
||||
useFactory: () => {
|
||||
return needsBackgroundInit && BrowserApi.isManifestVersion(2)
|
||||
? new BrowserMessagingPrivateModePopupService()
|
||||
: new BrowserMessagingService();
|
||||
},
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: TwoFactorService,
|
||||
useFactory: getBgService<TwoFactorService>("twoFactorService"),
|
||||
@@ -484,6 +482,65 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: BrowserSendStateService,
|
||||
deps: [StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: MessageListener,
|
||||
useFactory: (subject: Subject<Message<object>>, ngZone: NgZone) =>
|
||||
new MessageListener(
|
||||
merge(
|
||||
subject.asObservable(), // For messages in the same context
|
||||
fromChromeRuntimeMessaging().pipe(runInsideAngular(ngZone)), // For messages in the same context
|
||||
),
|
||||
),
|
||||
deps: [INTRAPROCESS_MESSAGING_SUBJECT, NgZone],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: MessageSender,
|
||||
useFactory: (subject: Subject<Message<object>>, logService: LogService) =>
|
||||
MessageSender.combine(
|
||||
new SubjectMessageSender(subject), // For sending messages in the same context
|
||||
new ChromeMessageSender(logService), // For sending messages to different contexts
|
||||
),
|
||||
deps: [INTRAPROCESS_MESSAGING_SUBJECT, LogService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: INTRAPROCESS_MESSAGING_SUBJECT,
|
||||
useFactory: () => {
|
||||
if (BrowserPopupUtils.backgroundInitializationRequired()) {
|
||||
// There is no persistent main background which means we have one in memory,
|
||||
// we need the same instance that our in memory background is utilizing.
|
||||
return getBgService("intraprocessMessagingSubject")();
|
||||
} else {
|
||||
return new Subject<Message<object>>();
|
||||
}
|
||||
},
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: MessageSender,
|
||||
useFactory: (subject: Subject<Message<object>>, logService: LogService) =>
|
||||
MessageSender.combine(
|
||||
new SubjectMessageSender(subject), // For sending messages in the same context
|
||||
new ChromeMessageSender(logService), // For sending messages to different contexts
|
||||
),
|
||||
deps: [INTRAPROCESS_MESSAGING_SUBJECT, LogService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: INTRAPROCESS_MESSAGING_SUBJECT,
|
||||
useFactory: () => {
|
||||
if (needsBackgroundInit) {
|
||||
// We will have created a popup within this context, in that case
|
||||
// we want to make sure we have the same subject as that context so we
|
||||
// can message with it.
|
||||
return getBgService("intraprocessMessagingSubject")();
|
||||
} else {
|
||||
// There isn't a locally created background so we will communicate with
|
||||
// the true background through chrome apis, in that case, we can just create
|
||||
// one for ourself.
|
||||
return new Subject<Message<object>>();
|
||||
}
|
||||
},
|
||||
deps: [],
|
||||
}),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -60,10 +60,10 @@ import {
|
||||
} from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums";
|
||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||
import { MessageSender } from "@bitwarden/common/platform/messaging";
|
||||
import { Account } from "@bitwarden/common/platform/models/domain/account";
|
||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/services/broadcaster.service";
|
||||
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
|
||||
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
|
||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||
@@ -75,7 +75,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";
|
||||
import { StateService } from "@bitwarden/common/platform/services/state.service";
|
||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||
import {
|
||||
@@ -155,7 +154,7 @@ global.DOMParser = new jsdom.JSDOM().window.DOMParser;
|
||||
const packageJson = require("../package.json");
|
||||
|
||||
export class Main {
|
||||
messagingService: NoopMessagingService;
|
||||
messagingService: MessageSender;
|
||||
storageService: LowdbStorageService;
|
||||
secureStorageService: NodeEnvSecureStorageService;
|
||||
memoryStorageService: MemoryStorageService;
|
||||
@@ -212,7 +211,6 @@ export class Main {
|
||||
organizationService: OrganizationService;
|
||||
providerService: ProviderService;
|
||||
twoFactorService: TwoFactorService;
|
||||
broadcasterService: BroadcasterService;
|
||||
folderApiService: FolderApiService;
|
||||
userVerificationApiService: UserVerificationApiService;
|
||||
organizationApiService: OrganizationApiServiceAbstraction;
|
||||
@@ -298,7 +296,7 @@ export class Main {
|
||||
stateEventRegistrarService,
|
||||
);
|
||||
|
||||
this.messagingService = new NoopMessagingService();
|
||||
this.messagingService = MessageSender.EMPTY;
|
||||
|
||||
this.accountService = new AccountServiceImplementation(
|
||||
this.messagingService,
|
||||
@@ -422,8 +420,6 @@ export class Main {
|
||||
|
||||
this.searchService = new SearchService(this.logService, this.i18nService, this.stateProvider);
|
||||
|
||||
this.broadcasterService = new BroadcasterService();
|
||||
|
||||
this.collectionService = new CollectionService(
|
||||
this.cryptoService,
|
||||
this.i18nService,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
apps/desktop/src/platform/utils/from-ipc-messaging.ts
Normal file
15
apps/desktop/src/platform/utils/from-ipc-messaging.ts
Normal 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());
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,8 @@ export class TrialBillingStepComponent implements OnInit {
|
||||
planDescription,
|
||||
});
|
||||
|
||||
this.messagingService.send("organizationCreated", organizationId);
|
||||
// TODO: No one actually listening to this?
|
||||
this.messagingService.send("organizationCreated", { organizationId });
|
||||
}
|
||||
|
||||
protected changedCountry() {
|
||||
|
||||
@@ -587,7 +587,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
||||
this.formPromise = doSubmit();
|
||||
const organizationId = await this.formPromise;
|
||||
this.onSuccess.emit({ organizationId: organizationId });
|
||||
this.messagingService.send("organizationCreated", organizationId);
|
||||
// TODO: No one actually listening to this message?
|
||||
this.messagingService.send("organizationCreated", { organizationId });
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
|
||||
@Injectable()
|
||||
export class BroadcasterMessagingService implements MessagingService {
|
||||
constructor(private broadcasterService: BroadcasterService) {}
|
||||
|
||||
send(subscriber: string, arg: any = {}) {
|
||||
const message = Object.assign({}, { command: subscriber }, arg);
|
||||
this.broadcasterService.send(message);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
@@ -51,7 +50,6 @@ import { WebStorageServiceProvider } from "../platform/web-storage-service.provi
|
||||
import { WindowStorageService } from "../platform/window-storage.service";
|
||||
import { CollectionAdminService } from "../vault/core/collection-admin.service";
|
||||
|
||||
import { BroadcasterMessagingService } from "./broadcaster-messaging.service";
|
||||
import { EventService } from "./event.service";
|
||||
import { InitService } from "./init.service";
|
||||
import { ModalService } from "./modal.service";
|
||||
@@ -117,11 +115,6 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: WebPlatformUtilsService,
|
||||
useAngularDecorators: true,
|
||||
}),
|
||||
safeProvider({
|
||||
provide: MessagingServiceAbstraction,
|
||||
useClass: BroadcasterMessagingService,
|
||||
useAngularDecorators: true,
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ModalServiceAbstraction,
|
||||
useClass: ModalService,
|
||||
|
||||
@@ -4,15 +4,15 @@ import { of } from "rxjs";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { MessageSender } from "@bitwarden/common/platform/messaging";
|
||||
import { BadgeModule, I18nMockService } from "@bitwarden/components";
|
||||
|
||||
import { PremiumBadgeComponent } from "./premium-badge.component";
|
||||
|
||||
class MockMessagingService implements MessagingService {
|
||||
send(subscriber: string, arg?: any) {
|
||||
class MockMessagingService implements MessageSender {
|
||||
send = () => {
|
||||
alert("Clicked on badge");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -31,7 +31,7 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessagingService,
|
||||
provide: MessageSender,
|
||||
useFactory: () => {
|
||||
return new MockMessagingService();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user