diff --git a/apps/browser/src/background/idle.background.ts b/apps/browser/src/background/idle.background.ts index 90276eaea0a..da890566442 100644 --- a/apps/browser/src/background/idle.background.ts +++ b/apps/browser/src/background/idle.background.ts @@ -9,7 +9,7 @@ import { VaultTimeoutSettingsService, VaultTimeoutStringType, } from "@bitwarden/common/key-management/vault-timeout"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; const IdleInterval = 60 * 5; // 5 minutes @@ -20,7 +20,7 @@ export default class IdleBackground { constructor( private vaultTimeoutService: VaultTimeoutService, - private notificationsService: NotificationsService, + private notificationsService: ServerNotificationsService, private accountService: AccountService, private vaultTimeoutSettingsService: VaultTimeoutSettingsService, ) { diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 0d7fe740069..75bf509c468 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -111,6 +111,7 @@ import { ObservableStorageService, } from "@bitwarden/common/platform/abstractions/storage.service"; import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service"; +import { ActionsService } from "@bitwarden/common/platform/actions/actions-service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { IpcService } from "@bitwarden/common/platform/ipc"; import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; @@ -120,7 +121,7 @@ import { Lazy } from "@bitwarden/common/platform/misc/lazy"; import { Account } from "@bitwarden/common/platform/models/domain/account"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; // eslint-disable-next-line no-restricted-imports -- Needed for service creation import { DefaultNotificationsService, @@ -129,6 +130,8 @@ import { WebPushNotificationsApiService, WorkerWebPushConnectionService, } from "@bitwarden/common/platform/notifications/internal"; +import { SystemNotificationService } from "@bitwarden/common/platform/notifications/system-notification-service"; +import { UnsupportedSystemNotificationService } from "@bitwarden/common/platform/notifications/unsupported-system-notification.service"; import { AppIdService } from "@bitwarden/common/platform/services/app-id.service"; import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; @@ -268,6 +271,7 @@ import { InlineMenuFieldQualificationService } from "../autofill/services/inline import { SafariApp } from "../browser/safariApp"; import { BackgroundBrowserBiometricsService } from "../key-management/biometrics/background-browser-biometrics.service"; import VaultTimeoutService from "../key-management/vault-timeout/vault-timeout.service"; +import { BrowserActionsService } from "../platform/actions/browser-actions.service"; import { DefaultBadgeBrowserApi } from "../platform/badge/badge-browser-api"; import { BadgeService } from "../platform/badge/badge.service"; import { BrowserApi } from "../platform/browser/browser-api"; @@ -296,6 +300,7 @@ import { BackgroundMemoryStorageService } from "../platform/storage/background-m import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider"; import { OffscreenStorageService } from "../platform/storage/offscreen-storage.service"; import { SyncServiceListener } from "../platform/sync/sync-service.listener"; +import { ChromeExtensionSystemNotificationService } from "../platform/system-notifications/chrome-extension-system-notification.service"; import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging"; import { VaultFilterService } from "../vault/services/vault-filter.service"; @@ -341,7 +346,9 @@ export default class MainBackground { importService: ImportServiceAbstraction; exportService: VaultExportServiceAbstraction; searchService: SearchServiceAbstraction; - notificationsService: NotificationsService; + notificationsService: ServerNotificationsService; + systemNotificationService: SystemNotificationService; + actionsService: ActionsService; stateService: StateServiceAbstraction; userNotificationSettingsService: UserNotificationSettingsServiceAbstraction; autofillSettingsService: AutofillSettingsServiceAbstraction; @@ -1122,6 +1129,24 @@ export default class MainBackground { this.webPushConnectionService = new UnsupportedWebPushConnectionService(); } + this.actionsService = new BrowserActionsService(this.platformUtilsService); + + const userAgent = navigator.userAgent; + + const isChrome = + userAgent.includes("Chrome") && !userAgent.includes("Edge") && !userAgent.includes("OPR"); + const isSafari = userAgent.includes("Safari") && !userAgent.includes("Chrome"); + const isFirefox = userAgent.includes("Firefox"); + + if ((isChrome || isFirefox) && !isSafari) { + this.systemNotificationService = new ChromeExtensionSystemNotificationService( + this.logService, + this.platformUtilsService, + ); + } else { + this.systemNotificationService = new UnsupportedSystemNotificationService(); + } + this.notificationsService = new DefaultNotificationsService( this.logService, this.syncService, @@ -1690,6 +1715,11 @@ export default class MainBackground { ); } + /** + * Opens the popup. + * + * @deprecated Migrating to the browser actions service. + */ async openPopup() { const browserAction = BrowserApi.getBrowserAction(); @@ -1726,6 +1756,7 @@ export default class MainBackground { /** * Opens the popup to the given page * @default ExtensionPageUrls.Index + * @deprecated Migrating to the browser actions service. */ async openTheExtensionToPage(url: ExtensionPageUrls = ExtensionPageUrls.Index) { const isValidUrl = Object.values(ExtensionPageUrls).includes(url); diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 1e7a0140022..c8762e630aa 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -15,7 +15,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { MessageListener, isExternalMessage } from "@bitwarden/common/platform/messaging"; import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; import { CipherType } from "@bitwarden/common/vault/enums"; import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum"; import { BiometricsCommands } from "@bitwarden/key-management"; @@ -46,7 +46,7 @@ export default class RuntimeBackground { private main: MainBackground, private autofillService: AutofillService, private platformUtilsService: BrowserPlatformUtilsService, - private notificationsService: NotificationsService, + private notificationsService: ServerNotificationsService, private autofillSettingsService: AutofillSettingsServiceAbstraction, private processReloadSerivce: ProcessReloadServiceAbstraction, private environmentService: BrowserEnvironmentService, @@ -424,6 +424,12 @@ export default class RuntimeBackground { return await BrowserApi.tabsQuery({ url: `${urlObj.href}*` }); } + /** + * Opens the popup. + * + * Migrating to the browser actions service, not deprecating yet. + * @private + */ private async openPopup() { await this.main.openPopup(); } @@ -450,7 +456,7 @@ export default class RuntimeBackground { /** Sends a message to each tab that the popup was opened */ private announcePopupOpen() { const announceToAllTabs = async () => { - const isOpen = await this.platformUtilsService.isViewOpen(); + const isOpen = await this.platformUtilsService.isPopupOpen(); const tabs = await this.getBwTabs(); if (isOpen && tabs.length > 0) { diff --git a/apps/browser/src/platform/actions/browser-actions.service.ts b/apps/browser/src/platform/actions/browser-actions.service.ts new file mode 100644 index 00000000000..fcf5a2d9682 --- /dev/null +++ b/apps/browser/src/platform/actions/browser-actions.service.ts @@ -0,0 +1,26 @@ +import { DeviceType } from "@bitwarden/common/enums"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ActionsService } from "@bitwarden/common/platform/actions/actions-service"; + +import { BrowserApi } from "../browser/browser-api"; + +export class BrowserActionsService implements ActionsService { + constructor(private platformUtilsService: PlatformUtilsService) {} + + async openPopup(): Promise { + switch (this.platformUtilsService.getDevice()) { + case DeviceType.ChromeBrowser: + case DeviceType.ChromeExtension: { + const browserAction = BrowserApi.getBrowserAction(); + + if ("openPopup" in browserAction && typeof browserAction.openPopup === "function") { + await browserAction.openPopup(); + return; + } + break; + } + case DeviceType.SafariBrowser: + case DeviceType.SafariExtension: + } + } +} diff --git a/apps/browser/src/platform/notifications/foreground-notifications.service.ts b/apps/browser/src/platform/notifications/foreground-server-notifications.service.ts similarity index 87% rename from apps/browser/src/platform/notifications/foreground-notifications.service.ts rename to apps/browser/src/platform/notifications/foreground-server-notifications.service.ts index e7685d3fe50..02fd092339f 100644 --- a/apps/browser/src/platform/notifications/foreground-notifications.service.ts +++ b/apps/browser/src/platform/notifications/foreground-server-notifications.service.ts @@ -2,12 +2,12 @@ import { Observable, Subscription } from "rxjs"; import { NotificationResponse } from "@bitwarden/common/models/response/notification.response"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; import { UserId } from "@bitwarden/common/types/guid"; // Eventually if we want to support listening to notifications from browser foreground we // will only ever create a single SignalR connection, likely messaging to the background to reuse its connection. -export class ForegroundNotificationsService implements NotificationsService { +export class ForegroundServerNotificationsService implements ServerNotificationsService { notifications$: Observable; constructor(private readonly logService: LogService) { diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts index f75e9cc29a5..f9a4221b3ef 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts @@ -150,7 +150,7 @@ describe("Browser Utils Service", () => { callback(undefined); }); - const isViewOpen = await browserPlatformUtilsService.isViewOpen(); + const isViewOpen = await browserPlatformUtilsService.isPopupOpen(); expect(isViewOpen).toBe(false); }); @@ -160,7 +160,7 @@ describe("Browser Utils Service", () => { callback(message.command === "checkVaultPopupHeartbeat"); }); - const isViewOpen = await browserPlatformUtilsService.isViewOpen(); + const isViewOpen = await browserPlatformUtilsService.isPopupOpen(); expect(isViewOpen).toBe(true); }); @@ -173,7 +173,7 @@ describe("Browser Utils Service", () => { callback(undefined); }); - const isViewOpen = await browserPlatformUtilsService.isViewOpen(); + const isViewOpen = await browserPlatformUtilsService.isPopupOpen(); expect(isViewOpen).toBe(false); diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts index beac7616d8d..4c5869054b2 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts @@ -150,7 +150,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic * message to the popup and waiting for a response. If a response is received, * the view is open. */ - async isViewOpen(): Promise { + async isPopupOpen(): Promise { if (this.isSafari()) { // Query views on safari since chrome.runtime.sendMessage does not timeout and will hang. return BrowserApi.isPopupOpen(); diff --git a/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts b/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts deleted file mode 100644 index 0dfb41d2ed4..00000000000 --- a/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Observable, Subject } from "rxjs"; - -import { DeviceType } from "@bitwarden/common/enums"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { - ButtonLocation, - SystemNotificationClearInfo, - SystemNotificationCreateInfo, - SystemNotificationEvent, - SystemNotificationService, -} from "@bitwarden/common/platform/notifications/system-notification-service"; - -export class BrowserSystemNotificationService implements SystemNotificationService { - private systemNotificationClickedSubject = new Subject(); - notificationClicked$: Observable; - - constructor( - private logService: LogService, - private platformUtilsService: PlatformUtilsService, - ) { - this.notificationClicked$ = this.systemNotificationClickedSubject.asObservable(); - } - - async create(createInfo: SystemNotificationCreateInfo): Promise { - if (!this.isSupported()) { - this.logService.error("While trying to create, found that it is not supported"); - } - - chrome.notifications.create(createInfo.id, { - iconUrl: "https://avatars.githubusercontent.com/u/15990069?s=200", - message: createInfo.title, - type: "basic", - title: createInfo.title, - buttons: createInfo.buttons.map((value) => { - return { title: value.title }; - }), - }); - - // ESLint: Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi. addListener` instead (no-restricted-syntax) - // eslint-disable-next-line no-restricted-syntax - chrome.notifications.onButtonClicked.addListener( - (notificationId: string, buttonIndex: number) => { - this.systemNotificationClickedSubject.next({ - id: notificationId, - type: createInfo.type, - buttonIdentifier: buttonIndex, - }); - }, - ); - - // eslint-disable-next-line no-restricted-syntax - chrome.notifications.onClicked.addListener((notificationId: string) => { - this.systemNotificationClickedSubject.next({ - id: notificationId, - type: createInfo.type, - buttonIdentifier: ButtonLocation.NotificationButton, - }); - }); - return; - } - - clear(clearInfo: SystemNotificationClearInfo): undefined { - if (!this.isSupported()) { - this.logService.error("While trying to clear, found that it is not supported"); - } - - chrome.notifications.clear(clearInfo.id); - } - - isSupported(): boolean { - switch (this.platformUtilsService.getDevice()) { - case DeviceType.EdgeExtension: - case DeviceType.VivaldiExtension: - case DeviceType.OperaExtension: - case DeviceType.ChromeExtension: - return true; - default: - return false; - } - } -} diff --git a/apps/browser/src/platform/system-notifications/chrome-extension-system-notification.service.ts b/apps/browser/src/platform/system-notifications/chrome-extension-system-notification.service.ts new file mode 100644 index 00000000000..9c26e0d774d --- /dev/null +++ b/apps/browser/src/platform/system-notifications/chrome-extension-system-notification.service.ts @@ -0,0 +1,79 @@ +import { Observable, Subject } from "rxjs"; + +import { DeviceType } from "@bitwarden/common/enums"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { + ButtonLocation, + SystemNotificationClearInfo, + SystemNotificationCreateInfo, + SystemNotificationEvent, + SystemNotificationService, +} from "@bitwarden/common/platform/notifications/system-notification-service"; + +export class ChromeExtensionSystemNotificationService implements SystemNotificationService { + private systemNotificationClickedSubject = new Subject(); + notificationClicked$: Observable; + + constructor( + private logService: LogService, + private platformUtilsService: PlatformUtilsService, + ) { + this.notificationClicked$ = this.systemNotificationClickedSubject.asObservable(); + } + + async create(createInfo: SystemNotificationCreateInfo): Promise { + switch (this.platformUtilsService.getDevice()) { + case DeviceType.ChromeExtension: + chrome.notifications.create(createInfo.id, { + iconUrl: "https://avatars.githubusercontent.com/u/15990069?s=200", + message: createInfo.title, + type: "basic", + title: createInfo.title, + buttons: createInfo.buttons.map((value) => { + return { title: value.title }; + }), + }); + + // ESLint: Using addListener in the browser popup produces a memory leak in Safari, + // use `BrowserApi. addListener` instead (no-restricted-syntax) + // eslint-disable-next-line no-restricted-syntax + chrome.notifications.onButtonClicked.addListener( + (notificationId: string, buttonIndex: number) => { + this.systemNotificationClickedSubject.next({ + id: notificationId, + type: createInfo.type, + buttonIdentifier: buttonIndex, + }); + }, + ); + + // eslint-disable-next-line no-restricted-syntax + chrome.notifications.onClicked.addListener((notificationId: string) => { + this.systemNotificationClickedSubject.next({ + id: notificationId, + type: createInfo.type, + buttonIdentifier: ButtonLocation.NotificationButton, + }); + }); + + break; + } + } + + clear(clearInfo: SystemNotificationClearInfo): undefined { + chrome.notifications.clear(clearInfo.id); + } + + isSupported(): boolean { + switch (this.platformUtilsService.getDevice()) { + case DeviceType.EdgeExtension: + case DeviceType.VivaldiExtension: + case DeviceType.OperaExtension: + case DeviceType.ChromeExtension: + return true; + default: + return false; + } + } +} diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 54d09ab9d8c..76da465e4b8 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -79,7 +79,10 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { + PlatformUtilsService as PlatformUtilsServiceAbstraction, + PlatformUtilsService, +} from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; @@ -92,7 +95,8 @@ import { Message, MessageListener, MessageSender } from "@bitwarden/common/platf // eslint-disable-next-line no-restricted-imports -- Used for dependency injection import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; import { flagEnabled } from "@bitwarden/common/platform/misc/flags"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; +import { SystemNotificationService } from "@bitwarden/common/platform/notifications/system-notification-service"; import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; @@ -160,7 +164,7 @@ import { runInsideAngular } from "../../platform/browser/run-inside-angular.oper import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service"; import { ChromeMessageSender } from "../../platform/messaging/chrome-message.sender"; /* eslint-enable no-restricted-imports */ -import { ForegroundNotificationsService } from "../../platform/notifications/foreground-notifications.service"; +import { ForegroundServerNotificationsService } from "../../platform/notifications/foreground-server-notifications.service"; import { OffscreenDocumentService } from "../../platform/offscreen-document/abstractions/offscreen-document"; import { DefaultOffscreenDocumentService } from "../../platform/offscreen-document/offscreen-document.service"; import { PopupCompactModeService } from "../../platform/popup/layout/popup-compact-mode.service"; @@ -178,6 +182,7 @@ import { ForegroundTaskSchedulerService } from "../../platform/services/task-sch import { BrowserStorageServiceProvider } from "../../platform/storage/browser-storage-service.provider"; import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service"; import { ForegroundSyncService } from "../../platform/sync/foreground-sync.service"; +import { ChromeExtensionSystemNotificationService } from "../../platform/system-notifications/chrome-extension-system-notification.service"; import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging"; import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service"; import { Fido2UserVerificationService } from "../../vault/services/fido2-user-verification.service"; @@ -603,6 +608,11 @@ const safeProviders: SafeProvider[] = [ useClass: SsoUrlService, deps: [], }), + safeProvider({ + provide: SystemNotificationService, + useClass: ChromeExtensionSystemNotificationService, + deps: [LogService, PlatformUtilsServiceAbstraction], + }), safeProvider({ provide: LoginComponentService, useClass: ExtensionLoginComponentService, @@ -663,8 +673,8 @@ const safeProviders: SafeProvider[] = [ deps: [DialogService, ToastService, PlatformUtilsService, I18nServiceAbstraction], }), safeProvider({ - provide: NotificationsService, - useClass: ForegroundNotificationsService, + provide: ServerNotificationsService, + useClass: ForegroundServerNotificationsService, deps: [LogService], }), ]; diff --git a/apps/cli/src/platform/services/cli-platform-utils.service.ts b/apps/cli/src/platform/services/cli-platform-utils.service.ts index 7bed495bbf5..9e02b9dcca9 100644 --- a/apps/cli/src/platform/services/cli-platform-utils.service.ts +++ b/apps/cli/src/platform/services/cli-platform-utils.service.ts @@ -75,7 +75,7 @@ export class CliPlatformUtilsService implements PlatformUtilsService { return false; } - isViewOpen() { + isPopupOpen() { return Promise.resolve(false); } diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index b5c34cc95a3..da15641515b 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -60,7 +60,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { SystemService } from "@bitwarden/common/platform/abstractions/system.service"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; @@ -152,7 +152,7 @@ export class AppComponent implements OnInit, OnDestroy { private messagingService: MessagingService, private collectionService: CollectionService, private searchService: SearchService, - private notificationsService: NotificationsService, + private notificationsService: ServerNotificationsService, private platformUtilsService: PlatformUtilsService, private systemService: SystemService, private processReloadService: ProcessReloadServiceAbstraction, diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index e68491faaa3..def5dd1d590 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -15,7 +15,7 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/platform/sync"; @@ -38,7 +38,7 @@ export class InitService { private i18nService: I18nServiceAbstraction, private eventUploadService: EventUploadServiceAbstraction, private twoFactorService: TwoFactorServiceAbstraction, - private notificationsService: NotificationsService, + private notificationsService: ServerNotificationsService, private platformUtilsService: PlatformUtilsServiceAbstraction, private stateService: StateServiceAbstraction, private keyService: KeyServiceAbstraction, diff --git a/apps/desktop/src/platform/services/electron-platform-utils.service.ts b/apps/desktop/src/platform/services/electron-platform-utils.service.ts index 43b867b7a68..23fb29e932a 100644 --- a/apps/desktop/src/platform/services/electron-platform-utils.service.ts +++ b/apps/desktop/src/platform/services/electron-platform-utils.service.ts @@ -59,7 +59,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { return ipc.platform.isMacAppStore; } - isViewOpen(): Promise { + isPopupOpen(): Promise { return Promise.resolve(false); } diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index ada73dd0059..e19bddce338 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -21,7 +21,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -73,7 +73,7 @@ export class AppComponent implements OnDestroy, OnInit { private keyService: KeyService, private collectionService: CollectionService, private searchService: SearchService, - private notificationsService: NotificationsService, + private notificationsService: ServerNotificationsService, private stateService: StateService, private eventUploadService: EventUploadService, protected policyListService: PolicyListService, diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 39dc6e1edd4..6d0a6c25fa6 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -15,7 +15,7 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { IpcService } from "@bitwarden/common/platform/ipc"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; @@ -28,7 +28,7 @@ import { VersionService } from "../platform/version.service"; export class InitService { constructor( @Inject(WINDOW) private win: Window, - private notificationsService: NotificationsService, + private notificationsService: ServerNotificationsService, private vaultTimeoutService: DefaultVaultTimeoutService, private i18nService: I18nServiceAbstraction, private eventUploadService: EventUploadServiceAbstraction, diff --git a/apps/web/src/app/core/web-platform-utils.service.ts b/apps/web/src/app/core/web-platform-utils.service.ts index c3d1f5e3c1a..ada0b09aa17 100644 --- a/apps/web/src/app/core/web-platform-utils.service.ts +++ b/apps/web/src/app/core/web-platform-utils.service.ts @@ -98,7 +98,7 @@ export class WebPlatformUtilsService implements PlatformUtilsService { return false; } - isViewOpen(): Promise { + isPopupOpen(): Promise { return Promise.resolve(false); } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 391c20b30d6..bbd5292ee76 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -205,7 +205,7 @@ import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/inter import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; import { Account } from "@bitwarden/common/platform/models/domain/account"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; // eslint-disable-next-line no-restricted-imports -- Needed for service creation import { DefaultNotificationsService, @@ -945,7 +945,7 @@ const safeProviders: SafeProvider[] = [ deps: [], }), safeProvider({ - provide: NotificationsService, + provide: ServerNotificationsService, useClass: devFlagEnabled("noopNotifications") ? NoopNotificationsService : DefaultNotificationsService, @@ -1552,7 +1552,7 @@ const safeProviders: SafeProvider[] = [ ApiServiceAbstraction, OrganizationServiceAbstraction, AuthServiceAbstraction, - NotificationsService, + ServerNotificationsService, MessageListener, ], }), @@ -1562,7 +1562,7 @@ const safeProviders: SafeProvider[] = [ deps: [ StateProvider, ApiServiceAbstraction, - NotificationsService, + ServerNotificationsService, AuthServiceAbstraction, LogService, ], diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index b3509850ac0..ab10464bc80 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -1,5 +1,13 @@ import { CommonModule } from "@angular/common"; -import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { + Component, + ElementRef, + HostListener, + NgZone, + OnDestroy, + OnInit, + ViewChild, +} from "@angular/core"; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms"; import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { firstValueFrom, Subject, take, takeUntil } from "rxjs"; @@ -30,6 +38,10 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { + ButtonActions, + SystemNotificationService, +} from "@bitwarden/common/platform/notifications/system-notification-service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { UserId } from "@bitwarden/common/types/guid"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. @@ -127,10 +139,32 @@ export class LoginComponent implements OnInit, OnDestroy { private loginSuccessHandlerService: LoginSuccessHandlerService, private masterPasswordService: MasterPasswordServiceAbstraction, private configService: ConfigService, + private systemNotificationService: SystemNotificationService, ) { this.clientType = this.platformUtilsService.getClientType(); } + @HostListener("document:keydown.control.b", ["$event"]) + async onCtrlB(event: KeyboardEvent) { + if (process.env.ENV === "development") { + event.preventDefault(); + await this.systemNotificationService.create({ + id: Math.random() * 10000000 + "", + type: ButtonActions.AuthRequestNotification, + title: "Test", + body: "Body", + buttons: [], + }); + } + + // this.systemNotificationService.notificationClicked$.pipe(takeUntilDestroyed()).subscribe((value) => { + // if (value == ButtonActions.AuthRequestNotification) + // }); + // this.ngZone.onStable.pipe(take(1), takeUntil(this.destroy$)).subscribe(() => { + // this.masterPasswordInputRef?.nativeElement?.focus(); + // }); + } + async ngOnInit(): Promise { // Add popstate listener to listen for browser back button clicks window.addEventListener("popstate", this.handlePopState); diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts index 9963e7d24f8..cf783f73827 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts @@ -182,7 +182,7 @@ describe("VaultTimeoutService", () => { ), ); - platformUtilsService.isViewOpen.mockResolvedValue(globalSetups?.isViewOpen ?? false); + platformUtilsService.isPopupOpen.mockResolvedValue(globalSetups?.isViewOpen ?? false); vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$.mockImplementation((userId) => { return new BehaviorSubject(accounts[userId]?.timeoutAction); @@ -222,7 +222,7 @@ describe("VaultTimeoutService", () => { it.each([AuthenticationStatus.Locked, AuthenticationStatus.LoggedOut])( "should not try to log out or lock any user that has authStatus === %s.", async (authStatus) => { - platformUtilsService.isViewOpen.mockResolvedValue(false); + platformUtilsService.isPopupOpen.mockResolvedValue(false); setupAccounts({ 1: { authStatus: authStatus, diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts index b5ee6a1fc0f..bf79c8be395 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts @@ -82,7 +82,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { async checkVaultTimeout(): Promise { // Get whether or not the view is open a single time so it can be compared for each user - const isViewOpen = await this.platformUtilsService.isViewOpen(); + const isViewOpen = await this.platformUtilsService.isPopupOpen(); await firstValueFrom( combineLatest([ diff --git a/libs/common/src/platform/abstractions/platform-utils.service.ts b/libs/common/src/platform/abstractions/platform-utils.service.ts index 7586da5a564..4d3f032052b 100644 --- a/libs/common/src/platform/abstractions/platform-utils.service.ts +++ b/libs/common/src/platform/abstractions/platform-utils.service.ts @@ -22,7 +22,7 @@ export abstract class PlatformUtilsService { abstract isVivaldi(): boolean; abstract isSafari(): boolean; abstract isMacAppStore(): boolean; - abstract isViewOpen(): Promise; + abstract isPopupOpen(): Promise; abstract launchUri(uri: string, options?: any): void; abstract getApplicationVersion(): Promise; abstract getApplicationVersionNumber(): Promise; diff --git a/libs/common/src/platform/actions/actions-service.ts b/libs/common/src/platform/actions/actions-service.ts new file mode 100644 index 00000000000..46e1e87326f --- /dev/null +++ b/libs/common/src/platform/actions/actions-service.ts @@ -0,0 +1,3 @@ +export abstract class ActionsService { + abstract openPopup(): Promise; +} diff --git a/libs/common/src/platform/actions/index.ts b/libs/common/src/platform/actions/index.ts new file mode 100644 index 00000000000..4c3a6f2bf23 --- /dev/null +++ b/libs/common/src/platform/actions/index.ts @@ -0,0 +1 @@ +export { ActionsService } from "./actions-service"; diff --git a/libs/common/src/platform/actions/unsupported-actions.service.ts b/libs/common/src/platform/actions/unsupported-actions.service.ts new file mode 100644 index 00000000000..9dc05c3665a --- /dev/null +++ b/libs/common/src/platform/actions/unsupported-actions.service.ts @@ -0,0 +1,7 @@ +import { ActionsService } from "./actions-service"; + +export class UnsupportedActionsService implements ActionsService { + openPopup(): Promise { + return Promise.resolve(undefined); + } +} diff --git a/libs/common/src/platform/notifications/index.ts b/libs/common/src/platform/notifications/index.ts index b1b842f5152..4539c39fd41 100644 --- a/libs/common/src/platform/notifications/index.ts +++ b/libs/common/src/platform/notifications/index.ts @@ -1 +1 @@ -export { NotificationsService } from "./notifications.service"; +export { ServerNotificationsService } from "./server-notifications-service"; diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.ts index ff22173a26e..7f366318ded 100644 --- a/libs/common/src/platform/notifications/internal/default-notifications.service.ts +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.ts @@ -32,7 +32,7 @@ import { EnvironmentService } from "../../abstractions/environment.service"; import { LogService } from "../../abstractions/log.service"; import { MessagingService } from "../../abstractions/messaging.service"; import { supportSwitch } from "../../misc/support-status"; -import { NotificationsService as NotificationsServiceAbstraction } from "../notifications.service"; +import { ServerNotificationsService as NotificationsServiceAbstraction } from "../server-notifications-service"; import { ReceiveMessage, SignalRConnectionService } from "./signalr-connection.service"; import { WebPushConnectionService } from "./webpush-connection.service"; @@ -188,7 +188,6 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract case NotificationType.SyncCiphers: case NotificationType.SyncSettings: await this.syncService.fullSync(false); - break; case NotificationType.SyncOrganizations: // An organization update may not have bumped the user's account revision date, so force a sync @@ -214,11 +213,9 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract await this.syncService.syncDeleteSend(notification.payload as SyncSendNotification); break; case NotificationType.AuthRequest: - { - this.messagingService.send("openLoginApproval", { - notificationId: notification.payload.id, - }); - } + this.messagingService.send("openLoginApproval", { + notificationId: notification.payload.id, + }); break; case NotificationType.SyncOrganizationStatusChanged: await this.syncService.fullSync(true); diff --git a/libs/common/src/platform/notifications/internal/noop-notifications.service.ts b/libs/common/src/platform/notifications/internal/noop-notifications.service.ts index 9c2435fb1a7..513adf918bf 100644 --- a/libs/common/src/platform/notifications/internal/noop-notifications.service.ts +++ b/libs/common/src/platform/notifications/internal/noop-notifications.service.ts @@ -4,9 +4,9 @@ import { NotificationResponse } from "@bitwarden/common/models/response/notifica import { UserId } from "@bitwarden/common/types/guid"; import { LogService } from "../../abstractions/log.service"; -import { NotificationsService } from "../notifications.service"; +import { ServerNotificationsService } from "../server-notifications-service"; -export class NoopNotificationsService implements NotificationsService { +export class NoopNotificationsService implements ServerNotificationsService { notifications$: Observable = new Subject(); constructor(private logService: LogService) {} diff --git a/libs/common/src/platform/notifications/notifications.service.ts b/libs/common/src/platform/notifications/server-notifications-service.ts similarity index 96% rename from libs/common/src/platform/notifications/notifications.service.ts rename to libs/common/src/platform/notifications/server-notifications-service.ts index 9e72b4de493..7e7784de4ba 100644 --- a/libs/common/src/platform/notifications/notifications.service.ts +++ b/libs/common/src/platform/notifications/server-notifications-service.ts @@ -9,7 +9,7 @@ import type { DefaultNotificationsService } from "./internal"; /** * A service offering abilities to interact with push notifications from the server. */ -export abstract class NotificationsService { +export abstract class ServerNotificationsService { /** * @deprecated This method should not be consumed, an observable to listen to server * notifications will be available one day but it is not ready to be consumed generally. diff --git a/libs/common/src/platform/notifications/system-notification-service.ts b/libs/common/src/platform/notifications/system-notification-service.ts index 449c38b22c1..1cd05ff5ca8 100644 --- a/libs/common/src/platform/notifications/system-notification-service.ts +++ b/libs/common/src/platform/notifications/system-notification-service.ts @@ -9,11 +9,11 @@ export type ButtonActionsKeys = (typeof ButtonActions)[keyof typeof ButtonAction // This is currently tailored for chrome extension's api, if safari works // differently where clicking a notification button produces a different // identifier we need to reconcile that here. -export const ButtonLocation = { +export const ButtonLocation = Object.freeze({ FirstOptionalButton: 0, // this is the first optional button we can set SecondOptionalButton: 1, // this is the second optional button we can set NotificationButton: 2, // this is when you click the notification as a whole -}; +}); export type ButtonLocationKeys = (typeof ButtonLocation)[keyof typeof ButtonLocation]; diff --git a/libs/common/src/vault/notifications/services/default-end-user-notification.service.spec.ts b/libs/common/src/vault/notifications/services/default-end-user-notification.service.spec.ts index 89a78d6f7d2..3a5ba06508e 100644 --- a/libs/common/src/vault/notifications/services/default-end-user-notification.service.spec.ts +++ b/libs/common/src/vault/notifications/services/default-end-user-notification.service.spec.ts @@ -4,7 +4,7 @@ import { firstValueFrom, of } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; import { StateProvider } from "@bitwarden/common/platform/state"; import { NotificationId, UserId } from "@bitwarden/common/types/guid"; @@ -20,7 +20,7 @@ import { describe("End User Notification Center Service", () => { let fakeStateProvider: FakeStateProvider; let mockApiService: jest.Mocked; - let mockNotificationsService: jest.Mocked; + let mockNotificationsService: jest.Mocked; let mockAuthService: jest.Mocked; let mockLogService: jest.Mocked; let service: DefaultEndUserNotificationService; diff --git a/libs/common/src/vault/notifications/services/default-end-user-notification.service.ts b/libs/common/src/vault/notifications/services/default-end-user-notification.service.ts index 87f97b48c27..621a5951ed5 100644 --- a/libs/common/src/vault/notifications/services/default-end-user-notification.service.ts +++ b/libs/common/src/vault/notifications/services/default-end-user-notification.service.ts @@ -6,7 +6,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio import { NotificationType } from "@bitwarden/common/enums"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; import { StateProvider } from "@bitwarden/common/platform/state"; import { NotificationId, UserId } from "@bitwarden/common/types/guid"; import { @@ -36,7 +36,7 @@ export class DefaultEndUserNotificationService implements EndUserNotificationSer constructor( private stateProvider: StateProvider, private apiService: ApiService, - private notificationService: NotificationsService, + private notificationService: ServerNotificationsService, private authService: AuthService, private logService: LogService, ) {} diff --git a/libs/common/src/vault/tasks/services/default-task.service.spec.ts b/libs/common/src/vault/tasks/services/default-task.service.spec.ts index a1f9872266e..2322ab8f423 100644 --- a/libs/common/src/vault/tasks/services/default-task.service.spec.ts +++ b/libs/common/src/vault/tasks/services/default-task.service.spec.ts @@ -8,7 +8,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio import { NotificationType } from "@bitwarden/common/enums"; import { NotificationResponse } from "@bitwarden/common/models/response/notification.response"; import { Message, MessageListener } from "@bitwarden/common/platform/messaging"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; import { SecurityTaskId, UserId } from "@bitwarden/common/types/guid"; import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; @@ -39,7 +39,9 @@ describe("Default task service", () => { { send: mockApiSend } as unknown as ApiService, { organizations$: mockGetAllOrgs$ } as unknown as OrganizationService, { authStatuses$: mockAuthStatuses$.asObservable() } as unknown as AuthService, - { notifications$: mockNotifications$.asObservable() } as unknown as NotificationsService, + { + notifications$: mockNotifications$.asObservable(), + } as unknown as ServerNotificationsService, { allMessages$: mockMessages$.asObservable() } as unknown as MessageListener, ); }); diff --git a/libs/common/src/vault/tasks/services/default-task.service.ts b/libs/common/src/vault/tasks/services/default-task.service.ts index 35a7561d63d..487ab16b2a2 100644 --- a/libs/common/src/vault/tasks/services/default-task.service.ts +++ b/libs/common/src/vault/tasks/services/default-task.service.ts @@ -17,7 +17,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio import { NotificationType } from "@bitwarden/common/enums"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { MessageListener } from "@bitwarden/common/platform/messaging"; -import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; import { StateProvider } from "@bitwarden/common/platform/state"; import { SecurityTaskId, UserId } from "@bitwarden/common/types/guid"; import { @@ -42,7 +42,7 @@ export class DefaultTaskService implements TaskService { private apiService: ApiService, private organizationService: OrganizationService, private authService: AuthService, - private notificationService: NotificationsService, + private notificationService: ServerNotificationsService, private messageListener: MessageListener, ) {}