[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:
parent
9a4279c8bb
commit
395ed3f5d4
|
@ -246,6 +246,22 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["**/*.ts"],
|
||||||
|
"excludedFiles": ["**/platform/**/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"patterns": [
|
||||||
|
"**/platform/**/internal", // General internal pattern
|
||||||
|
// All features that have been converted to barrel files
|
||||||
|
"**/platform/messaging/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { firstValueFrom } from "rxjs";
|
import { Subject, firstValueFrom, merge } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PinCryptoServiceAbstraction,
|
PinCryptoServiceAbstraction,
|
||||||
|
@ -82,7 +82,6 @@ import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/co
|
||||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.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 { 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 { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
AbstractMemoryStorageService,
|
||||||
|
@ -95,6 +94,9 @@ import {
|
||||||
DefaultBiometricStateService,
|
DefaultBiometricStateService,
|
||||||
} from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
} from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
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 { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
||||||
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.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 { BrowserApi } from "../platform/browser/browser-api";
|
||||||
import { flagEnabled } from "../platform/flags";
|
import { flagEnabled } from "../platform/flags";
|
||||||
import { UpdateBadge } from "../platform/listeners/update-badge";
|
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 { BrowserStateService as StateServiceAbstraction } from "../platform/services/abstractions/browser-state.service";
|
||||||
import { BrowserCryptoService } from "../platform/services/browser-crypto.service";
|
import { BrowserCryptoService } from "../platform/services/browser-crypto.service";
|
||||||
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
||||||
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
|
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
|
||||||
import BrowserMemoryStorageService from "../platform/services/browser-memory-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 { BrowserScriptInjectorService } from "../platform/services/browser-script-injector.service";
|
||||||
import { DefaultBrowserStateService } from "../platform/services/default-browser-state.service";
|
import { DefaultBrowserStateService } from "../platform/services/default-browser-state.service";
|
||||||
import I18nService from "../platform/services/i18n.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 { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
|
||||||
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
|
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
|
||||||
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
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 VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
|
||||||
import FilelessImporterBackground from "../tools/background/fileless-importer.background";
|
import FilelessImporterBackground from "../tools/background/fileless-importer.background";
|
||||||
import { Fido2Background as Fido2BackgroundAbstraction } from "../vault/fido2/background/abstractions/fido2.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";
|
import RuntimeBackground from "./runtime.background";
|
||||||
|
|
||||||
export default class MainBackground {
|
export default class MainBackground {
|
||||||
messagingService: MessagingServiceAbstraction;
|
messagingService: MessageSender;
|
||||||
storageService: BrowserLocalStorageService;
|
storageService: BrowserLocalStorageService;
|
||||||
secureStorageService: AbstractStorageService;
|
secureStorageService: AbstractStorageService;
|
||||||
memoryStorageService: AbstractMemoryStorageService;
|
memoryStorageService: AbstractMemoryStorageService;
|
||||||
|
@ -326,6 +330,8 @@ export default class MainBackground {
|
||||||
stateEventRunnerService: StateEventRunnerService;
|
stateEventRunnerService: StateEventRunnerService;
|
||||||
ssoLoginService: SsoLoginServiceAbstraction;
|
ssoLoginService: SsoLoginServiceAbstraction;
|
||||||
billingAccountProfileStateService: BillingAccountProfileStateService;
|
billingAccountProfileStateService: BillingAccountProfileStateService;
|
||||||
|
// eslint-disable-next-line rxjs/no-exposed-subjects -- Needed to give access to services module
|
||||||
|
intraprocessMessagingSubject: Subject<Message<object>>;
|
||||||
userKeyInitService: UserKeyInitService;
|
userKeyInitService: UserKeyInitService;
|
||||||
scriptInjectorService: BrowserScriptInjectorService;
|
scriptInjectorService: BrowserScriptInjectorService;
|
||||||
|
|
||||||
|
@ -369,15 +375,25 @@ export default class MainBackground {
|
||||||
const logoutCallback = async (expired: boolean, userId?: UserId) =>
|
const logoutCallback = async (expired: boolean, userId?: UserId) =>
|
||||||
await this.logout(expired, userId);
|
await this.logout(expired, userId);
|
||||||
|
|
||||||
this.messagingService =
|
|
||||||
this.isPrivateMode && BrowserApi.isManifestVersion(2)
|
|
||||||
? new BrowserMessagingPrivateModeBackgroundService()
|
|
||||||
: new BrowserMessagingService();
|
|
||||||
this.logService = new ConsoleLogService(false);
|
this.logService = new ConsoleLogService(false);
|
||||||
this.cryptoFunctionService = new WebCryptoFunctionService(self);
|
this.cryptoFunctionService = new WebCryptoFunctionService(self);
|
||||||
this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService);
|
this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService);
|
||||||
this.storageService = new BrowserLocalStorageService();
|
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) => {
|
const mv3MemoryStorageCreator = (partitionName: string) => {
|
||||||
// TODO: Consider using multithreaded encrypt service in popup only context
|
// TODO: Consider using multithreaded encrypt service in popup only context
|
||||||
return new LocalBackedSessionStorageService(
|
return new LocalBackedSessionStorageService(
|
||||||
|
@ -560,21 +576,6 @@ export default class MainBackground {
|
||||||
|
|
||||||
this.twoFactorService = new TwoFactorService(this.i18nService, this.platformUtilsService);
|
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.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
|
||||||
|
|
||||||
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
|
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
|
||||||
|
@ -605,7 +606,7 @@ export default class MainBackground {
|
||||||
|
|
||||||
this.authService = new AuthService(
|
this.authService = new AuthService(
|
||||||
this.accountService,
|
this.accountService,
|
||||||
backgroundMessagingService,
|
this.messagingService,
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.apiService,
|
this.apiService,
|
||||||
this.stateService,
|
this.stateService,
|
||||||
|
@ -626,7 +627,7 @@ export default class MainBackground {
|
||||||
this.tokenService,
|
this.tokenService,
|
||||||
this.appIdService,
|
this.appIdService,
|
||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
backgroundMessagingService,
|
this.messagingService,
|
||||||
this.logService,
|
this.logService,
|
||||||
this.keyConnectorService,
|
this.keyConnectorService,
|
||||||
this.environmentService,
|
this.environmentService,
|
||||||
|
@ -914,6 +915,7 @@ export default class MainBackground {
|
||||||
this.logService,
|
this.logService,
|
||||||
this.configService,
|
this.configService,
|
||||||
this.fido2Background,
|
this.fido2Background,
|
||||||
|
messageListener,
|
||||||
);
|
);
|
||||||
this.nativeMessagingBackground = new NativeMessagingBackground(
|
this.nativeMessagingBackground = new NativeMessagingBackground(
|
||||||
this.accountService,
|
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.
|
// 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
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.runtimeBackground.processMessage({ command: "unlocked" }, null);
|
this.runtimeBackground.processMessage({ command: "unlocked" });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom, mergeMap } from "rxjs";
|
||||||
|
|
||||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||||
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
|
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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
|
|
||||||
|
import { MessageListener } from "../../../../libs/common/src/platform/messaging";
|
||||||
import {
|
import {
|
||||||
closeUnlockPopout,
|
closeUnlockPopout,
|
||||||
openSsoAuthResultPopout,
|
openSsoAuthResultPopout,
|
||||||
|
@ -44,6 +45,7 @@ export default class RuntimeBackground {
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private fido2Background: Fido2Background,
|
private fido2Background: Fido2Background,
|
||||||
|
private messageListener: MessageListener,
|
||||||
) {
|
) {
|
||||||
// onInstalled listener must be wired up before anything else, so we do it in the ctor
|
// onInstalled listener must be wired up before anything else, so we do it in the ctor
|
||||||
chrome.runtime.onInstalled.addListener((details: any) => {
|
chrome.runtime.onInstalled.addListener((details: any) => {
|
||||||
|
@ -60,92 +62,47 @@ export default class RuntimeBackground {
|
||||||
const backgroundMessageListener = (
|
const backgroundMessageListener = (
|
||||||
msg: any,
|
msg: any,
|
||||||
sender: chrome.runtime.MessageSender,
|
sender: chrome.runtime.MessageSender,
|
||||||
sendResponse: any,
|
sendResponse: (response: any) => void,
|
||||||
) => {
|
) => {
|
||||||
const messagesWithResponse = ["biometricUnlock"];
|
const messagesWithResponse = ["biometricUnlock"];
|
||||||
|
|
||||||
if (messagesWithResponse.includes(msg.command)) {
|
if (messagesWithResponse.includes(msg.command)) {
|
||||||
this.processMessage(msg, sender).then(
|
this.processMessageWithSender(msg, sender).then(
|
||||||
(value) => sendResponse({ result: value }),
|
(value) => sendResponse({ result: value }),
|
||||||
(error) => sendResponse({ error: { ...error, message: error.message } }),
|
(error) => sendResponse({ error: { ...error, message: error.message } }),
|
||||||
);
|
);
|
||||||
return true;
|
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);
|
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) {
|
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":
|
case "triggerAutofillScriptInjection":
|
||||||
await this.autofillService.injectAutofillScripts(sender.tab, sender.frameId);
|
await this.autofillService.injectAutofillScripts(sender.tab, sender.frameId);
|
||||||
break;
|
break;
|
||||||
case "bgCollectPageDetails":
|
case "bgCollectPageDetails":
|
||||||
await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId);
|
await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId);
|
||||||
break;
|
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":
|
case "collectPageDetailsResponse":
|
||||||
switch (msg.sender) {
|
switch (msg.sender) {
|
||||||
case "autofiller":
|
case "autofiller":
|
||||||
|
@ -209,6 +166,72 @@ export default class RuntimeBackground {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
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": {
|
case "authResult": {
|
||||||
const env = await firstValueFrom(this.environmentService.environment$);
|
const env = await firstValueFrom(this.environmentService.environment$);
|
||||||
const vaultUrl = env.getWebVaultUrl();
|
const vaultUrl = env.getWebVaultUrl();
|
||||||
|
@ -265,9 +288,6 @@ export default class RuntimeBackground {
|
||||||
await this.main.clearClipboard(msg.clipboardValue, msg.timeoutMs);
|
await this.main.clearClipboard(msg.clipboardValue, msg.timeoutMs);
|
||||||
break;
|
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";
|
// Export old messaging service stuff to minimize changes
|
||||||
|
export {
|
||||||
import {
|
messageSenderFactory as messagingServiceFactory,
|
||||||
CachedServices,
|
MessageSenderInitOptions as MessagingServiceInitOptions,
|
||||||
factory,
|
} from "./message-sender.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());
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
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 { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components";
|
import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { BrowserApi } from "../platform/browser/browser-api";
|
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 { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
|
||||||
import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service";
|
import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service";
|
||||||
import { VaultBrowserStateService } from "../vault/services/vault-browser-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>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private broadcasterService: BroadcasterService,
|
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
@ -46,7 +44,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private browserMessagingApi: ZonedMessageListenerService,
|
private messageListener: MessageListener,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -78,77 +76,76 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||||
window.onkeypress = () => this.recordActivity();
|
window.onkeypress = () => this.recordActivity();
|
||||||
});
|
});
|
||||||
|
|
||||||
const bitwardenPopupMainMessageListener = (msg: any, sender: any) => {
|
this.messageListener.allMessages$
|
||||||
if (msg.command === "doneLoggingOut") {
|
.pipe(
|
||||||
this.authService.logOut(async () => {
|
tap((msg: any) => {
|
||||||
if (msg.expired) {
|
if (msg.command === "doneLoggingOut") {
|
||||||
this.toastService.showToast({
|
this.authService.logOut(async () => {
|
||||||
variant: "warning",
|
if (msg.expired) {
|
||||||
title: this.i18nService.t("loggedOut"),
|
this.toastService.showToast({
|
||||||
message: this.i18nService.t("loginExpired"),
|
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.
|
takeUntil(this.destroy$),
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
)
|
||||||
this.router.navigate(["home"]);
|
.subscribe();
|
||||||
});
|
|
||||||
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);
|
|
||||||
|
|
||||||
// eslint-disable-next-line rxjs/no-async-subscribe
|
// eslint-disable-next-line rxjs/no-async-subscribe
|
||||||
this.router.events.pipe(takeUntil(this.destroy$)).subscribe(async (event) => {
|
this.router.events.pipe(takeUntil(this.destroy$)).subscribe(async (event) => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core";
|
import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
import { Subject, merge } from "rxjs";
|
||||||
|
|
||||||
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards";
|
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards";
|
||||||
import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service";
|
import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service";
|
||||||
|
@ -11,6 +12,7 @@ import {
|
||||||
OBSERVABLE_MEMORY_STORAGE,
|
OBSERVABLE_MEMORY_STORAGE,
|
||||||
SYSTEM_THEME_OBSERVABLE,
|
SYSTEM_THEME_OBSERVABLE,
|
||||||
SafeInjectionToken,
|
SafeInjectionToken,
|
||||||
|
INTRAPROCESS_MESSAGING_SUBJECT,
|
||||||
} from "@bitwarden/angular/services/injection-tokens";
|
} from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||||
import {
|
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 { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import {
|
import {
|
||||||
|
@ -63,6 +64,9 @@ import {
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
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 { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||||
import { ContainerService } from "@bitwarden/common/platform/services/container.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 MainBackground from "../../background/main.background";
|
||||||
import { Account } from "../../models/account";
|
import { Account } from "../../models/account";
|
||||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
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 BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
|
||||||
import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service";
|
import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service";
|
||||||
import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service";
|
import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service";
|
||||||
import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service";
|
import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service";
|
||||||
import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service";
|
import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service";
|
||||||
import BrowserLocalStorageService from "../../platform/services/browser-local-storage.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 { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service";
|
||||||
import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service";
|
import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service";
|
||||||
import I18nService from "../../platform/services/i18n.service";
|
import I18nService from "../../platform/services/i18n.service";
|
||||||
import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service";
|
import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service";
|
||||||
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
|
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
|
||||||
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
|
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 { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service";
|
||||||
import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service";
|
import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service";
|
||||||
import { VaultBrowserStateService } from "../../vault/services/vault-browser-state.service";
|
import { VaultBrowserStateService } from "../../vault/services/vault-browser-state.service";
|
||||||
|
@ -155,15 +162,6 @@ const safeProviders: SafeProvider[] = [
|
||||||
useClass: UnauthGuardService,
|
useClass: UnauthGuardService,
|
||||||
deps: [AuthServiceAbstraction, Router],
|
deps: [AuthServiceAbstraction, Router],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
|
||||||
provide: MessagingService,
|
|
||||||
useFactory: () => {
|
|
||||||
return needsBackgroundInit && BrowserApi.isManifestVersion(2)
|
|
||||||
? new BrowserMessagingPrivateModePopupService()
|
|
||||||
: new BrowserMessagingService();
|
|
||||||
},
|
|
||||||
deps: [],
|
|
||||||
}),
|
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: TwoFactorService,
|
provide: TwoFactorService,
|
||||||
useFactory: getBgService<TwoFactorService>("twoFactorService"),
|
useFactory: getBgService<TwoFactorService>("twoFactorService"),
|
||||||
|
@ -484,6 +482,65 @@ const safeProviders: SafeProvider[] = [
|
||||||
useClass: BrowserSendStateService,
|
useClass: BrowserSendStateService,
|
||||||
deps: [StateProvider],
|
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({
|
@NgModule({
|
||||||
|
|
|
@ -60,10 +60,10 @@ import {
|
||||||
} from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
} from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||||
import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums";
|
import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
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 { Account } from "@bitwarden/common/platform/models/domain/account";
|
||||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
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 { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
|
||||||
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
|
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
|
||||||
import { ContainerService } from "@bitwarden/common/platform/services/container.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 { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
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 { StateService } from "@bitwarden/common/platform/services/state.service";
|
||||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
import {
|
import {
|
||||||
|
@ -155,7 +154,7 @@ global.DOMParser = new jsdom.JSDOM().window.DOMParser;
|
||||||
const packageJson = require("../package.json");
|
const packageJson = require("../package.json");
|
||||||
|
|
||||||
export class Main {
|
export class Main {
|
||||||
messagingService: NoopMessagingService;
|
messagingService: MessageSender;
|
||||||
storageService: LowdbStorageService;
|
storageService: LowdbStorageService;
|
||||||
secureStorageService: NodeEnvSecureStorageService;
|
secureStorageService: NodeEnvSecureStorageService;
|
||||||
memoryStorageService: MemoryStorageService;
|
memoryStorageService: MemoryStorageService;
|
||||||
|
@ -212,7 +211,6 @@ export class Main {
|
||||||
organizationService: OrganizationService;
|
organizationService: OrganizationService;
|
||||||
providerService: ProviderService;
|
providerService: ProviderService;
|
||||||
twoFactorService: TwoFactorService;
|
twoFactorService: TwoFactorService;
|
||||||
broadcasterService: BroadcasterService;
|
|
||||||
folderApiService: FolderApiService;
|
folderApiService: FolderApiService;
|
||||||
userVerificationApiService: UserVerificationApiService;
|
userVerificationApiService: UserVerificationApiService;
|
||||||
organizationApiService: OrganizationApiServiceAbstraction;
|
organizationApiService: OrganizationApiServiceAbstraction;
|
||||||
|
@ -298,7 +296,7 @@ export class Main {
|
||||||
stateEventRegistrarService,
|
stateEventRegistrarService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.messagingService = new NoopMessagingService();
|
this.messagingService = MessageSender.EMPTY;
|
||||||
|
|
||||||
this.accountService = new AccountServiceImplementation(
|
this.accountService = new AccountServiceImplementation(
|
||||||
this.messagingService,
|
this.messagingService,
|
||||||
|
@ -422,8 +420,6 @@ export class Main {
|
||||||
|
|
||||||
this.searchService = new SearchService(this.logService, this.i18nService, this.stateProvider);
|
this.searchService = new SearchService(this.logService, this.i18nService, this.stateProvider);
|
||||||
|
|
||||||
this.broadcasterService = new BroadcasterService();
|
|
||||||
|
|
||||||
this.collectionService = new CollectionService(
|
this.collectionService = new CollectionService(
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { APP_INITIALIZER, NgModule } from "@angular/core";
|
import { APP_INITIALIZER, NgModule } from "@angular/core";
|
||||||
|
import { Subject, merge } from "rxjs";
|
||||||
|
|
||||||
import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
||||||
import {
|
import {
|
||||||
|
@ -14,6 +15,7 @@ import {
|
||||||
SYSTEM_THEME_OBSERVABLE,
|
SYSTEM_THEME_OBSERVABLE,
|
||||||
SafeInjectionToken,
|
SafeInjectionToken,
|
||||||
STATE_FACTORY,
|
STATE_FACTORY,
|
||||||
|
INTRAPROCESS_MESSAGING_SUBJECT,
|
||||||
} from "@bitwarden/angular/services/injection-tokens";
|
} from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
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 { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.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 { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.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 { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service";
|
||||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
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 { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
|
@ -63,11 +67,12 @@ import {
|
||||||
ELECTRON_SUPPORTS_SECURE_STORAGE,
|
ELECTRON_SUPPORTS_SECURE_STORAGE,
|
||||||
ElectronPlatformUtilsService,
|
ElectronPlatformUtilsService,
|
||||||
} from "../../platform/services/electron-platform-utils.service";
|
} 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 { ElectronRendererSecureStorageService } from "../../platform/services/electron-renderer-secure-storage.service";
|
||||||
import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service";
|
import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service";
|
||||||
import { ElectronStateService } from "../../platform/services/electron-state.service";
|
import { ElectronStateService } from "../../platform/services/electron-state.service";
|
||||||
import { I18nRendererService } from "../../platform/services/i18n.renderer.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 { fromIpcSystemTheme } from "../../platform/utils/from-ipc-system-theme";
|
||||||
import { EncryptedMessageHandlerService } from "../../services/encrypted-message-handler.service";
|
import { EncryptedMessageHandlerService } from "../../services/encrypted-message-handler.service";
|
||||||
import { NativeMessageHandlerService } from "../../services/native-message-handler.service";
|
import { NativeMessageHandlerService } from "../../services/native-message-handler.service";
|
||||||
|
@ -138,9 +143,24 @@ const safeProviders: SafeProvider[] = [
|
||||||
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY, GlobalStateProvider],
|
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY, GlobalStateProvider],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: MessagingServiceAbstraction,
|
provide: MessageSender,
|
||||||
useClass: ElectronRendererMessagingService,
|
useFactory: (subject: Subject<Message<object>>) =>
|
||||||
deps: [BroadcasterServiceAbstraction],
|
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({
|
safeProvider({
|
||||||
provide: AbstractStorageService,
|
provide: AbstractStorageService,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
import { app } from "electron";
|
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 { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.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 { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { DefaultBiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
import { DefaultBiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
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 { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||||
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
||||||
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
|
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 { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
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 */
|
/* 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 { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
|
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
|
||||||
|
@ -59,7 +61,7 @@ export class Main {
|
||||||
storageService: ElectronStorageService;
|
storageService: ElectronStorageService;
|
||||||
memoryStorageService: MemoryStorageService;
|
memoryStorageService: MemoryStorageService;
|
||||||
memoryStorageForStateProviders: MemoryStorageServiceForStateProviders;
|
memoryStorageForStateProviders: MemoryStorageServiceForStateProviders;
|
||||||
messagingService: ElectronMainMessagingService;
|
messagingService: MessageSender;
|
||||||
stateService: StateService;
|
stateService: StateService;
|
||||||
environmentService: DefaultEnvironmentService;
|
environmentService: DefaultEnvironmentService;
|
||||||
mainCryptoFunctionService: MainCryptoFunctionService;
|
mainCryptoFunctionService: MainCryptoFunctionService;
|
||||||
|
@ -131,7 +133,7 @@ export class Main {
|
||||||
this.i18nService = new I18nMainService("en", "./locales/", globalStateProvider);
|
this.i18nService = new I18nMainService("en", "./locales/", globalStateProvider);
|
||||||
|
|
||||||
const accountService = new AccountServiceImplementation(
|
const accountService = new AccountServiceImplementation(
|
||||||
new NoopMessagingService(),
|
MessageSender.EMPTY,
|
||||||
this.logService,
|
this.logService,
|
||||||
globalStateProvider,
|
globalStateProvider,
|
||||||
);
|
);
|
||||||
|
@ -223,7 +225,13 @@ export class Main {
|
||||||
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain);
|
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain);
|
||||||
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.desktopSettingsService);
|
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);
|
this.messagingMain.onMessage(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { powerMonitor } from "electron";
|
import { powerMonitor } from "electron";
|
||||||
|
|
||||||
import { ElectronMainMessagingService } from "../services/electron-main-messaging.service";
|
import { MessageSender } from "@bitwarden/common/platform/messaging";
|
||||||
|
|
||||||
import { isSnapStore } from "../utils";
|
import { isSnapStore } from "../utils";
|
||||||
|
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
|
@ -10,7 +11,7 @@ const IdleCheckInterval = 30 * 1000; // 30 seconds
|
||||||
export class PowerMonitorMain {
|
export class PowerMonitorMain {
|
||||||
private idle = false;
|
private idle = false;
|
||||||
|
|
||||||
constructor(private messagingService: ElectronMainMessagingService) {}
|
constructor(private messagingService: MessageSender) {}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// ref: https://github.com/electron/electron/issues/13767
|
// ref: https://github.com/electron/electron/issues/13767
|
||||||
|
|
|
@ -124,12 +124,21 @@ export default {
|
||||||
|
|
||||||
sendMessage: (message: { command: string } & any) =>
|
sendMessage: (message: { command: string } & any) =>
|
||||||
ipcRenderer.send("messagingService", message),
|
ipcRenderer.send("messagingService", message),
|
||||||
onMessage: (callback: (message: { command: string } & any) => void) => {
|
onMessage: {
|
||||||
ipcRenderer.on("messagingService", (_event, message: any) => {
|
addListener: (callback: (message: { command: string } & any) => void) => {
|
||||||
if (message.command) {
|
ipcRenderer.addListener("messagingService", (_event, message: any) => {
|
||||||
callback(message);
|
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),
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 { 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 { 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 { SafeUrls } from "@bitwarden/common/platform/misc/safe-urls";
|
||||||
|
|
||||||
import { WindowMain } from "../main/window.main";
|
import { WindowMain } from "../main/window.main";
|
||||||
import { RendererMenuItem } from "../utils";
|
import { RendererMenuItem } from "../utils";
|
||||||
|
|
||||||
export class ElectronMainMessagingService implements MessagingService {
|
export class ElectronMainMessagingService implements MessageSender {
|
||||||
constructor(
|
constructor(private windowMain: WindowMain) {
|
||||||
private windowMain: WindowMain,
|
|
||||||
private onMessage: (message: any) => void,
|
|
||||||
) {
|
|
||||||
ipcMain.handle("appVersion", () => {
|
ipcMain.handle("appVersion", () => {
|
||||||
return app.getVersion();
|
return app.getVersion();
|
||||||
});
|
});
|
||||||
|
@ -88,9 +87,9 @@ export class ElectronMainMessagingService implements MessagingService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
send(subscriber: string, arg: any = {}) {
|
send<T extends object>(commandDefinition: CommandDefinition<T> | string, arg: T | object = {}) {
|
||||||
const message = Object.assign({}, { command: subscriber }, arg);
|
const command = getCommand(commandDefinition);
|
||||||
this.onMessage(message);
|
const message = Object.assign({}, { command: command }, arg);
|
||||||
if (this.windowMain.win != null) {
|
if (this.windowMain.win != null) {
|
||||||
this.windowMain.win.webContents.send("messagingService", message);
|
this.windowMain.win.webContents.send("messagingService", message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,8 @@ export class TrialBillingStepComponent implements OnInit {
|
||||||
planDescription,
|
planDescription,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.messagingService.send("organizationCreated", organizationId);
|
// TODO: No one actually listening to this?
|
||||||
|
this.messagingService.send("organizationCreated", { organizationId });
|
||||||
}
|
}
|
||||||
|
|
||||||
protected changedCountry() {
|
protected changedCountry() {
|
||||||
|
|
|
@ -587,7 +587,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
||||||
this.formPromise = doSubmit();
|
this.formPromise = doSubmit();
|
||||||
const organizationId = await this.formPromise;
|
const organizationId = await this.formPromise;
|
||||||
this.onSuccess.emit({ organizationId: organizationId });
|
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) {
|
} catch (e) {
|
||||||
this.logService.error(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 { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.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 { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.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 { WindowStorageService } from "../platform/window-storage.service";
|
||||||
import { CollectionAdminService } from "../vault/core/collection-admin.service";
|
import { CollectionAdminService } from "../vault/core/collection-admin.service";
|
||||||
|
|
||||||
import { BroadcasterMessagingService } from "./broadcaster-messaging.service";
|
|
||||||
import { EventService } from "./event.service";
|
import { EventService } from "./event.service";
|
||||||
import { InitService } from "./init.service";
|
import { InitService } from "./init.service";
|
||||||
import { ModalService } from "./modal.service";
|
import { ModalService } from "./modal.service";
|
||||||
|
@ -117,11 +115,6 @@ const safeProviders: SafeProvider[] = [
|
||||||
useClass: WebPlatformUtilsService,
|
useClass: WebPlatformUtilsService,
|
||||||
useAngularDecorators: true,
|
useAngularDecorators: true,
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
|
||||||
provide: MessagingServiceAbstraction,
|
|
||||||
useClass: BroadcasterMessagingService,
|
|
||||||
useAngularDecorators: true,
|
|
||||||
}),
|
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: ModalServiceAbstraction,
|
provide: ModalServiceAbstraction,
|
||||||
useClass: ModalService,
|
useClass: ModalService,
|
||||||
|
|
|
@ -4,15 +4,15 @@ import { of } from "rxjs";
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.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 { BadgeModule, I18nMockService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { PremiumBadgeComponent } from "./premium-badge.component";
|
import { PremiumBadgeComponent } from "./premium-badge.component";
|
||||||
|
|
||||||
class MockMessagingService implements MessagingService {
|
class MockMessagingService implements MessageSender {
|
||||||
send(subscriber: string, arg?: any) {
|
send = () => {
|
||||||
alert("Clicked on badge");
|
alert("Clicked on badge");
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -31,7 +31,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: MessagingService,
|
provide: MessageSender,
|
||||||
useFactory: () => {
|
useFactory: () => {
|
||||||
return new MockMessagingService();
|
return new MockMessagingService();
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { Injectable } from "@angular/core";
|
|
||||||
|
|
||||||
import { BroadcasterService as BaseBroadcasterService } from "@bitwarden/common/platform/services/broadcaster.service";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class BroadcasterService extends BaseBroadcasterService {}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { InjectionToken } from "@angular/core";
|
import { InjectionToken } from "@angular/core";
|
||||||
import { Observable } from "rxjs";
|
import { Observable, Subject } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
AbstractMemoryStorageService,
|
||||||
|
@ -8,6 +8,7 @@ import {
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||||
|
import { Message } from "@bitwarden/common/platform/messaging";
|
||||||
|
|
||||||
declare const tag: unique symbol;
|
declare const tag: unique symbol;
|
||||||
/**
|
/**
|
||||||
|
@ -49,3 +50,6 @@ export const LOG_MAC_FAILURES = new SafeInjectionToken<boolean>("LOG_MAC_FAILURE
|
||||||
export const SYSTEM_THEME_OBSERVABLE = new SafeInjectionToken<Observable<ThemeType>>(
|
export const SYSTEM_THEME_OBSERVABLE = new SafeInjectionToken<Observable<ThemeType>>(
|
||||||
"SYSTEM_THEME_OBSERVABLE",
|
"SYSTEM_THEME_OBSERVABLE",
|
||||||
);
|
);
|
||||||
|
export const INTRAPROCESS_MESSAGING_SUBJECT = new SafeInjectionToken<Subject<Message<object>>>(
|
||||||
|
"INTRAPROCESS_MESSAGING_SUBJECT",
|
||||||
|
);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ErrorHandler, LOCALE_ID, NgModule } from "@angular/core";
|
import { ErrorHandler, LOCALE_ID, NgModule } from "@angular/core";
|
||||||
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AuthRequestServiceAbstraction,
|
AuthRequestServiceAbstraction,
|
||||||
|
@ -116,7 +117,7 @@ import { BillingApiService } from "@bitwarden/common/billing/services/billing-ap
|
||||||
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
|
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
|
||||||
import { PaymentMethodWarningsService } from "@bitwarden/common/billing/services/payment-method-warnings.service";
|
import { PaymentMethodWarningsService } from "@bitwarden/common/billing/services/payment-method-warnings.service";
|
||||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
|
@ -137,6 +138,9 @@ import {
|
||||||
DefaultBiometricStateService,
|
DefaultBiometricStateService,
|
||||||
} from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
} from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
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 { devFlagEnabled, flagEnabled } from "@bitwarden/common/platform/misc/flags";
|
import { devFlagEnabled, flagEnabled } from "@bitwarden/common/platform/misc/flags";
|
||||||
import { Account } from "@bitwarden/common/platform/models/domain/account";
|
import { Account } from "@bitwarden/common/platform/models/domain/account";
|
||||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||||
|
@ -147,6 +151,7 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l
|
||||||
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
|
||||||
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
||||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
||||||
|
import { DefaultBroadcasterService } from "@bitwarden/common/platform/services/default-broadcaster.service";
|
||||||
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
|
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
|
||||||
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
|
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
|
||||||
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
|
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
|
||||||
|
@ -247,7 +252,6 @@ import {
|
||||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||||
import { UnauthGuard } from "../auth/guards/unauth.guard";
|
import { UnauthGuard } from "../auth/guards/unauth.guard";
|
||||||
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service";
|
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service";
|
||||||
import { BroadcasterService } from "../platform/services/broadcaster.service";
|
|
||||||
import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service";
|
import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service";
|
||||||
import { LoggingErrorHandler } from "../platform/services/logging-error-handler";
|
import { LoggingErrorHandler } from "../platform/services/logging-error-handler";
|
||||||
import { AngularThemingService } from "../platform/services/theming/angular-theming.service";
|
import { AngularThemingService } from "../platform/services/theming/angular-theming.service";
|
||||||
|
@ -270,6 +274,7 @@ import {
|
||||||
SYSTEM_LANGUAGE,
|
SYSTEM_LANGUAGE,
|
||||||
SYSTEM_THEME_OBSERVABLE,
|
SYSTEM_THEME_OBSERVABLE,
|
||||||
WINDOW,
|
WINDOW,
|
||||||
|
INTRAPROCESS_MESSAGING_SUBJECT,
|
||||||
} from "./injection-tokens";
|
} from "./injection-tokens";
|
||||||
import { ModalService } from "./modal.service";
|
import { ModalService } from "./modal.service";
|
||||||
|
|
||||||
|
@ -625,7 +630,11 @@ const safeProviders: SafeProvider[] = [
|
||||||
BillingAccountProfileStateService,
|
BillingAccountProfileStateService,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
safeProvider({ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService, deps: [] }),
|
safeProvider({
|
||||||
|
provide: BroadcasterService,
|
||||||
|
useClass: DefaultBroadcasterService,
|
||||||
|
deps: [MessageSender, MessageListener],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: VaultTimeoutSettingsServiceAbstraction,
|
provide: VaultTimeoutSettingsServiceAbstraction,
|
||||||
useClass: VaultTimeoutSettingsService,
|
useClass: VaultTimeoutSettingsService,
|
||||||
|
@ -1127,6 +1136,21 @@ const safeProviders: SafeProvider[] = [
|
||||||
useClass: LoggingErrorHandler,
|
useClass: LoggingErrorHandler,
|
||||||
deps: [],
|
deps: [],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: INTRAPROCESS_MESSAGING_SUBJECT,
|
||||||
|
useFactory: () => new Subject<Message<object>>(),
|
||||||
|
deps: [],
|
||||||
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: MessageListener,
|
||||||
|
useFactory: (subject: Subject<Message<object>>) => new MessageListener(subject.asObservable()),
|
||||||
|
deps: [INTRAPROCESS_MESSAGING_SUBJECT],
|
||||||
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: MessageSender,
|
||||||
|
useFactory: (subject: Subject<Message<object>>) => new SubjectMessageSender(subject),
|
||||||
|
deps: [INTRAPROCESS_MESSAGING_SUBJECT],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: ProviderApiServiceAbstraction,
|
provide: ProviderApiServiceAbstraction,
|
||||||
useClass: ProviderApiService,
|
useClass: ProviderApiService,
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export abstract class MessagingService {
|
// Export the new message sender as the legacy MessagingService to minimize changes in the initial PR,
|
||||||
abstract send(subscriber: string, arg?: any): void;
|
// team specific PR's will come after.
|
||||||
}
|
export { MessageSender as MessagingService } from "../messaging/message.sender";
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { Subject, firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { getCommand, isExternalMessage, tagAsExternal } from "./helpers";
|
||||||
|
import { Message, CommandDefinition } from "./types";
|
||||||
|
|
||||||
|
describe("helpers", () => {
|
||||||
|
describe("getCommand", () => {
|
||||||
|
it("can get the command from just a string", () => {
|
||||||
|
const command = getCommand("myCommand");
|
||||||
|
|
||||||
|
expect(command).toEqual("myCommand");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can get the command from a message definition", () => {
|
||||||
|
const commandDefinition = new CommandDefinition<object>("myCommand");
|
||||||
|
|
||||||
|
const command = getCommand(commandDefinition);
|
||||||
|
|
||||||
|
expect(command).toEqual("myCommand");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("tag integration", () => {
|
||||||
|
it("can tag and identify as tagged", async () => {
|
||||||
|
const messagesSubject = new Subject<Message<object>>();
|
||||||
|
|
||||||
|
const taggedMessages = messagesSubject.asObservable().pipe(tagAsExternal);
|
||||||
|
|
||||||
|
const firstValuePromise = firstValueFrom(taggedMessages);
|
||||||
|
|
||||||
|
messagesSubject.next({ command: "test" });
|
||||||
|
|
||||||
|
const result = await firstValuePromise;
|
||||||
|
|
||||||
|
expect(isExternalMessage(result)).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isExternalMessage", () => {
|
||||||
|
it.each([null, { command: "myCommand", test: "object" }, undefined] as Message<
|
||||||
|
Record<string, unknown>
|
||||||
|
>[])("returns false when value is %s", (value: Message<object>) => {
|
||||||
|
expect(isExternalMessage(value)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { MonoTypeOperatorFunction, map } from "rxjs";
|
||||||
|
|
||||||
|
import { Message, CommandDefinition } from "./types";
|
||||||
|
|
||||||
|
export const getCommand = (commandDefinition: CommandDefinition<object> | string) => {
|
||||||
|
if (typeof commandDefinition === "string") {
|
||||||
|
return commandDefinition;
|
||||||
|
} else {
|
||||||
|
return commandDefinition.command;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EXTERNAL_SOURCE_TAG = Symbol("externalSource");
|
||||||
|
|
||||||
|
export const isExternalMessage = (message: Message<object>) => {
|
||||||
|
return (message as Record<PropertyKey, unknown>)?.[EXTERNAL_SOURCE_TAG] === true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tagAsExternal: MonoTypeOperatorFunction<Message<object>> = map(
|
||||||
|
(message: Message<object>) => {
|
||||||
|
return Object.assign(message, { [EXTERNAL_SOURCE_TAG]: true });
|
||||||
|
},
|
||||||
|
);
|
|
@ -0,0 +1,4 @@
|
||||||
|
export { MessageListener } from "./message.listener";
|
||||||
|
export { MessageSender } from "./message.sender";
|
||||||
|
export { Message, CommandDefinition } from "./types";
|
||||||
|
export { isExternalMessage } from "./helpers";
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Built in implementations
|
||||||
|
export { SubjectMessageSender } from "./subject-message.sender";
|
||||||
|
|
||||||
|
// Helpers meant to be used only by other implementations
|
||||||
|
export { tagAsExternal, getCommand } from "./helpers";
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
|
import { subscribeTo } from "../../../spec/observable-tracker";
|
||||||
|
|
||||||
|
import { MessageListener } from "./message.listener";
|
||||||
|
import { Message, CommandDefinition } from "./types";
|
||||||
|
|
||||||
|
describe("MessageListener", () => {
|
||||||
|
const subject = new Subject<Message<{ test: number }>>();
|
||||||
|
const sut = new MessageListener(subject.asObservable());
|
||||||
|
|
||||||
|
const testCommandDefinition = new CommandDefinition<{ test: number }>("myCommand");
|
||||||
|
|
||||||
|
describe("allMessages$", () => {
|
||||||
|
it("runs on all nexts", async () => {
|
||||||
|
const tracker = subscribeTo(sut.allMessages$);
|
||||||
|
|
||||||
|
const pausePromise = tracker.pauseUntilReceived(2);
|
||||||
|
|
||||||
|
subject.next({ command: "command1", test: 1 });
|
||||||
|
subject.next({ command: "command2", test: 2 });
|
||||||
|
|
||||||
|
await pausePromise;
|
||||||
|
|
||||||
|
expect(tracker.emissions[0]).toEqual({ command: "command1", test: 1 });
|
||||||
|
expect(tracker.emissions[1]).toEqual({ command: "command2", test: 2 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("messages$", () => {
|
||||||
|
it("runs on only my commands", async () => {
|
||||||
|
const tracker = subscribeTo(sut.messages$(testCommandDefinition));
|
||||||
|
|
||||||
|
const pausePromise = tracker.pauseUntilReceived(2);
|
||||||
|
|
||||||
|
subject.next({ command: "notMyCommand", test: 1 });
|
||||||
|
subject.next({ command: "myCommand", test: 2 });
|
||||||
|
subject.next({ command: "myCommand", test: 3 });
|
||||||
|
subject.next({ command: "notMyCommand", test: 4 });
|
||||||
|
|
||||||
|
await pausePromise;
|
||||||
|
|
||||||
|
expect(tracker.emissions[0]).toEqual({ command: "myCommand", test: 2 });
|
||||||
|
expect(tracker.emissions[1]).toEqual({ command: "myCommand", test: 3 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { EMPTY, Observable, filter } from "rxjs";
|
||||||
|
|
||||||
|
import { Message, CommandDefinition } from "./types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that allows for listening to messages coming through the application,
|
||||||
|
* allows for listening of all messages or just the messages you care about.
|
||||||
|
*
|
||||||
|
* @note Consider NOT using messaging at all if you can. State Providers offer an observable stream of
|
||||||
|
* data that is persisted. This can serve messages that might have been used to notify of settings changes
|
||||||
|
* or vault data changes and those observables should be preferred over messaging.
|
||||||
|
*/
|
||||||
|
export class MessageListener {
|
||||||
|
constructor(private readonly messageStream: Observable<Message<object>>) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stream of all messages sent through the application. It does not contain type information for the
|
||||||
|
* other properties on the messages. You are encouraged to instead subscribe to an individual message
|
||||||
|
* through {@link messages$}.
|
||||||
|
*/
|
||||||
|
allMessages$ = this.messageStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an observable stream filtered to just the command given via the {@link CommandDefinition} and typed
|
||||||
|
* to the generic contained in the CommandDefinition. Be careful using this method unless all your messages are being
|
||||||
|
* sent through `MessageSender.send`, if that isn't the case you should have lower confidence in the message
|
||||||
|
* payload being the expected type.
|
||||||
|
*
|
||||||
|
* @param commandDefinition The CommandDefinition containing the information about the message type you care about.
|
||||||
|
*/
|
||||||
|
messages$<T extends object>(commandDefinition: CommandDefinition<T>): Observable<T> {
|
||||||
|
return this.allMessages$.pipe(
|
||||||
|
filter((msg) => msg?.command === commandDefinition.command),
|
||||||
|
) as Observable<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper property for returning a MessageListener that will never emit any messages and will immediately complete.
|
||||||
|
*/
|
||||||
|
static readonly EMPTY = new MessageListener(EMPTY);
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { CommandDefinition } from "./types";
|
||||||
|
|
||||||
|
class MultiMessageSender implements MessageSender {
|
||||||
|
constructor(private readonly innerMessageSenders: MessageSender[]) {}
|
||||||
|
|
||||||
|
send<T extends object>(
|
||||||
|
commandDefinition: string | CommandDefinition<T>,
|
||||||
|
payload: object | T = {},
|
||||||
|
): void {
|
||||||
|
for (const messageSender of this.innerMessageSenders) {
|
||||||
|
messageSender.send(commandDefinition, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class MessageSender {
|
||||||
|
/**
|
||||||
|
* A method for sending messages in a type safe manner. The passed in command definition
|
||||||
|
* will require you to provide a compatible type in the payload parameter.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const MY_COMMAND = new CommandDefinition<{ test: number }>("myCommand");
|
||||||
|
*
|
||||||
|
* this.messageSender.send(MY_COMMAND, { test: 14 });
|
||||||
|
*
|
||||||
|
* @param commandDefinition
|
||||||
|
* @param payload
|
||||||
|
*/
|
||||||
|
abstract send<T extends object>(commandDefinition: CommandDefinition<T>, payload: T): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A legacy method for sending messages in a non-type safe way.
|
||||||
|
*
|
||||||
|
* @remarks Consider defining a {@link CommandDefinition} and passing that in for the first parameter to
|
||||||
|
* get compilation errors when defining an incompatible payload.
|
||||||
|
*
|
||||||
|
* @param command The string based command of your message.
|
||||||
|
* @param payload Extra contextual information regarding the message. Be aware that this payload may
|
||||||
|
* be serialized and lose all prototype information.
|
||||||
|
*/
|
||||||
|
abstract send(command: string, payload?: object): void;
|
||||||
|
|
||||||
|
/** Implementation of the other two overloads, read their docs instead. */
|
||||||
|
abstract send<T extends object>(
|
||||||
|
commandDefinition: CommandDefinition<T> | string,
|
||||||
|
payload: T | object,
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper method for combine multiple {@link MessageSender}'s.
|
||||||
|
* @param messageSenders The message senders that should be combined.
|
||||||
|
* @returns A message sender that will relay all messages to the given message senders.
|
||||||
|
*/
|
||||||
|
static combine(...messageSenders: MessageSender[]) {
|
||||||
|
return new MultiMessageSender(messageSenders);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper property for creating a {@link MessageSender} that sends to nowhere.
|
||||||
|
*/
|
||||||
|
static readonly EMPTY: MessageSender = new MultiMessageSender([]);
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
|
import { subscribeTo } from "../../../spec/observable-tracker";
|
||||||
|
|
||||||
|
import { SubjectMessageSender } from "./internal";
|
||||||
|
import { MessageSender } from "./message.sender";
|
||||||
|
import { Message, CommandDefinition } from "./types";
|
||||||
|
|
||||||
|
describe("SubjectMessageSender", () => {
|
||||||
|
const subject = new Subject<Message<{ test: number }>>();
|
||||||
|
const subjectObservable = subject.asObservable();
|
||||||
|
|
||||||
|
const sut: MessageSender = new SubjectMessageSender(subject);
|
||||||
|
|
||||||
|
describe("send", () => {
|
||||||
|
it("will send message with command from message definition", async () => {
|
||||||
|
const commandDefinition = new CommandDefinition<{ test: number }>("myCommand");
|
||||||
|
|
||||||
|
const tracker = subscribeTo(subjectObservable);
|
||||||
|
const pausePromise = tracker.pauseUntilReceived(1);
|
||||||
|
|
||||||
|
sut.send(commandDefinition, { test: 1 });
|
||||||
|
|
||||||
|
await pausePromise;
|
||||||
|
|
||||||
|
expect(tracker.emissions[0]).toEqual({ command: "myCommand", test: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will send message with command from normal string", async () => {
|
||||||
|
const tracker = subscribeTo(subjectObservable);
|
||||||
|
const pausePromise = tracker.pauseUntilReceived(1);
|
||||||
|
|
||||||
|
sut.send("myCommand", { test: 1 });
|
||||||
|
|
||||||
|
await pausePromise;
|
||||||
|
|
||||||
|
expect(tracker.emissions[0]).toEqual({ command: "myCommand", test: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will send message with object even if payload not given", async () => {
|
||||||
|
const tracker = subscribeTo(subjectObservable);
|
||||||
|
const pausePromise = tracker.pauseUntilReceived(1);
|
||||||
|
|
||||||
|
sut.send("myCommand");
|
||||||
|
|
||||||
|
await pausePromise;
|
||||||
|
|
||||||
|
expect(tracker.emissions[0]).toEqual({ command: "myCommand" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([null, undefined])(
|
||||||
|
"will send message with object even if payload is null-ish (%s)",
|
||||||
|
async (payloadValue) => {
|
||||||
|
const tracker = subscribeTo(subjectObservable);
|
||||||
|
const pausePromise = tracker.pauseUntilReceived(1);
|
||||||
|
|
||||||
|
sut.send("myCommand", payloadValue);
|
||||||
|
|
||||||
|
await pausePromise;
|
||||||
|
|
||||||
|
expect(tracker.emissions[0]).toEqual({ command: "myCommand" });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
|
import { getCommand } from "./internal";
|
||||||
|
import { MessageSender } from "./message.sender";
|
||||||
|
import { Message, CommandDefinition } from "./types";
|
||||||
|
|
||||||
|
export class SubjectMessageSender implements MessageSender {
|
||||||
|
constructor(private readonly messagesSubject: Subject<Message<object>>) {}
|
||||||
|
|
||||||
|
send<T extends object>(
|
||||||
|
commandDefinition: string | CommandDefinition<T>,
|
||||||
|
payload: object | T = {},
|
||||||
|
): void {
|
||||||
|
const command = getCommand(commandDefinition);
|
||||||
|
this.messagesSubject.next(Object.assign(payload ?? {}, { command: command }));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
declare const tag: unique symbol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for defining information about a message, this is helpful
|
||||||
|
* alonside `MessageSender` and `MessageListener` for providing a type
|
||||||
|
* safe(-ish) way of sending and receiving messages.
|
||||||
|
*/
|
||||||
|
export class CommandDefinition<T extends object> {
|
||||||
|
[tag]: T;
|
||||||
|
constructor(readonly command: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Message<T extends object> = { command: string } & T;
|
|
@ -1,34 +0,0 @@
|
||||||
import {
|
|
||||||
BroadcasterService as BroadcasterServiceAbstraction,
|
|
||||||
MessageBase,
|
|
||||||
} from "../abstractions/broadcaster.service";
|
|
||||||
|
|
||||||
export class BroadcasterService implements BroadcasterServiceAbstraction {
|
|
||||||
subscribers: Map<string, (message: MessageBase) => void> = new Map<
|
|
||||||
string,
|
|
||||||
(message: MessageBase) => void
|
|
||||||
>();
|
|
||||||
|
|
||||||
send(message: MessageBase, id?: string) {
|
|
||||||
if (id != null) {
|
|
||||||
if (this.subscribers.has(id)) {
|
|
||||||
this.subscribers.get(id)(message);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.subscribers.forEach((value) => {
|
|
||||||
value(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(id: string, messageCallback: (message: MessageBase) => void) {
|
|
||||||
this.subscribers.set(id, messageCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribe(id: string) {
|
|
||||||
if (this.subscribers.has(id)) {
|
|
||||||
this.subscribers.delete(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Subscription } from "rxjs";
|
||||||
|
|
||||||
|
import { BroadcasterService, MessageBase } from "../abstractions/broadcaster.service";
|
||||||
|
import { MessageListener, MessageSender } from "../messaging";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary implementation that just delegates to the message sender and message listener
|
||||||
|
* and manages their subscriptions.
|
||||||
|
*/
|
||||||
|
export class DefaultBroadcasterService implements BroadcasterService {
|
||||||
|
subscriptions = new Map<string, Subscription>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly messageSender: MessageSender,
|
||||||
|
private readonly messageListener: MessageListener,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
send(message: MessageBase, id?: string) {
|
||||||
|
this.messageSender.send(message?.command, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(id: string, messageCallback: (message: MessageBase) => void) {
|
||||||
|
this.subscriptions.set(
|
||||||
|
id,
|
||||||
|
this.messageListener.allMessages$.subscribe((message) => {
|
||||||
|
messageCallback(message);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe(id: string) {
|
||||||
|
const subscription = this.subscriptions.get(id);
|
||||||
|
subscription?.unsubscribe();
|
||||||
|
this.subscriptions.delete(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
import { MessagingService } from "../abstractions/messaging.service";
|
|
||||||
|
|
||||||
export class NoopMessagingService implements MessagingService {
|
|
||||||
send(subscriber: string, arg: any = {}) {
|
|
||||||
// Do nothing...
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue