From 50621218d4ec242c76ebaf35fb670b3f3c65efca Mon Sep 17 00:00:00 2001 From: Patrick Pimentel Date: Mon, 21 Jul 2025 12:41:23 -0400 Subject: [PATCH] feat(notification-processing): [PM-19877] System Notification Implementation - Giving up on safari. Getting the code ready for review now. --- .../browser/src/background/main.background.ts | 20 ++++++----- ...=> browser-system-notification.service.ts} | 31 +++++++++++++++-- .../src/popup/services/services.module.ts | 4 +-- .../internal/default-notifications.service.ts | 33 +++++++++++++++---- .../system-notifications-service.ts | 2 +- 5 files changed, 71 insertions(+), 19 deletions(-) rename apps/browser/src/platform/system-notifications/{chrome-extension-system-notification.service.ts => browser-system-notification.service.ts} (70%) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 9fd96a50a80..54361b2f739 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -303,7 +303,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 { BrowserSystemNotificationService } from "../platform/system-notifications/browser-system-notification.service"; import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging"; import { VaultFilterService } from "../vault/services/vault-filter.service"; @@ -1132,10 +1132,16 @@ export default class MainBackground { this.webPushConnectionService = new UnsupportedWebPushConnectionService(); } - this.logService.info(`The background service is registered as ${navigator.userAgent}`); + this.logService.info( + `The background service is registered as ${navigator.userAgent} with manifest version ${BrowserApi.manifestVersion}`, + ); this.actionsService = new BrowserActionsService(this.logService, this.platformUtilsService); + setTimeout(async () => { + await this.actionsService.openPopup(); + }, 1); + const userAgent = navigator.userAgent; const isChrome = @@ -1144,7 +1150,7 @@ export default class MainBackground { const isFirefox = userAgent.includes("Firefox"); if ((isChrome || isFirefox) && !isSafari) { - this.systemNotificationService = new ChromeExtensionSystemNotificationService( + this.systemNotificationService = new BrowserSystemNotificationService( this.logService, this.platformUtilsService, ); @@ -1153,17 +1159,15 @@ export default class MainBackground { } setTimeout(async () => { + this.logService.info("CREATING NOTIFICATION"); await this.systemNotificationService.create({ id: Math.random() * 100000 + "", type: ButtonActions.AuthRequestNotification, title: "Test Notification", body: "Body", - buttons: [ - { - title: "First Button", - }, - ], + buttons: [], }); + this.logService.info("DONE CREATING NOTIFICATION"); }, 1); this.notificationsService = new DefaultNotificationsService( diff --git a/apps/browser/src/platform/system-notifications/chrome-extension-system-notification.service.ts b/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts similarity index 70% rename from apps/browser/src/platform/system-notifications/chrome-extension-system-notification.service.ts rename to apps/browser/src/platform/system-notifications/browser-system-notification.service.ts index d29957cc484..66089fb0df8 100644 --- a/apps/browser/src/platform/system-notifications/chrome-extension-system-notification.service.ts +++ b/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts @@ -11,7 +11,7 @@ import { SystemNotificationsService, } from "@bitwarden/common/platform/notifications/system-notifications-service"; -export class ChromeExtensionSystemNotificationService implements SystemNotificationsService { +export class BrowserSystemNotificationService implements SystemNotificationsService { private systemNotificationClickedSubject = new Subject(); notificationClicked$: Observable; @@ -58,10 +58,37 @@ export class ChromeExtensionSystemNotificationService implements SystemNotificat }); break; + case DeviceType.FirefoxExtension: + this.logService.info("Creating firefox notification"); + + await browser.notifications.create(createInfo.id, { + iconUrl: "https://avatars.githubusercontent.com/u/15990069?s=200", + message: createInfo.title, + type: "basic", + title: createInfo.title, + }); + + browser.notifications.onButtonClicked.addListener( + (notificationId: string, buttonIndex: number) => { + this.systemNotificationClickedSubject.next({ + id: notificationId, + type: createInfo.type, + buttonIdentifier: buttonIndex, + }); + }, + ); + + browser.notifications.onClicked.addListener((notificationId: string) => { + this.systemNotificationClickedSubject.next({ + id: notificationId, + type: createInfo.type, + buttonIdentifier: ButtonLocation.NotificationButton, + }); + }); } } - clear(clearInfo: SystemNotificationClearInfo): undefined { + async clear(clearInfo: SystemNotificationClearInfo): Promise { chrome.notifications.clear(clearInfo.id); } diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 50aac1a6d2c..8e16bb7c8f2 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -184,7 +184,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 { BrowserSystemNotificationService } from "../../platform/system-notifications/browser-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"; @@ -617,7 +617,7 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: SystemNotificationsService, - useClass: ChromeExtensionSystemNotificationService, + useClass: BrowserSystemNotificationService, deps: [LogService, PlatformUtilsServiceAbstraction], }), safeProvider({ 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 0d6b343b0e2..23d0e4aa5db 100644 --- a/libs/common/src/platform/notifications/internal/default-notifications.service.ts +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.ts @@ -17,6 +17,7 @@ import { LogoutReason } from "@bitwarden/auth/common"; import { ActionsService } from "@bitwarden/common/platform/actions"; import { ButtonActions, + ButtonLocation, SystemNotificationEvent, SystemNotificationsService, } from "@bitwarden/common/platform/notifications/system-notifications-service"; @@ -66,12 +67,12 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract ) { this.systemNotificationService.notificationClicked$ .pipe( - map(async (value: SystemNotificationEvent) => { - switch (value.type) { - case ButtonActions.AuthRequestNotification: - await this.actionService.openPopup(); - } - }), + filter( + (event: SystemNotificationEvent) => event.type === ButtonActions.AuthRequestNotification, + ), + mergeMap((event: SystemNotificationEvent) => + this.handleAuthRequestNotificationClicked(event), + ), ) .subscribe(); @@ -92,6 +93,26 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract ); } + /** + * TODO: Requests are doubling, figure out if a subscription is duplicating or something. + * @param event + * @private + */ + private async handleAuthRequestNotificationClicked( + event: SystemNotificationEvent, + ): Promise { + // This is the approval event. WE WILL NOT BE USING 0 or 1! + if (event.buttonIdentifier === ButtonLocation.FirstOptionalButton) { + this.logService.info("Approve the request"); + } else if (event.buttonIdentifier === ButtonLocation.SecondOptionalButton) { + // This is the deny event. + this.logService.info("Deny the request"); + } else if (event.buttonIdentifier === ButtonLocation.NotificationButton) { + this.logService.info("Main button clicked, open popup"); + await this.actionService.openPopup(); + } + } + /** * Retrieves a stream of push notifications for the given user. * @param userId The user id of the user to get the push notifications for. diff --git a/libs/common/src/platform/notifications/system-notifications-service.ts b/libs/common/src/platform/notifications/system-notifications-service.ts index 9d252214dc0..6c259996af6 100644 --- a/libs/common/src/platform/notifications/system-notifications-service.ts +++ b/libs/common/src/platform/notifications/system-notifications-service.ts @@ -42,7 +42,7 @@ export type SystemNotificationEvent = { export abstract class SystemNotificationsService { abstract notificationClicked$: Observable; abstract create(createInfo: SystemNotificationCreateInfo): Promise; - abstract clear(clearInfo: SystemNotificationClearInfo): undefined; + abstract clear(clearInfo: SystemNotificationClearInfo): Promise; /** * Used to know if a given platform supports notifications.