From b269eaed0b3a76ba5c37b9da27e76584bc5fae38 Mon Sep 17 00:00:00 2001 From: Patrick Pimentel Date: Thu, 17 Jul 2025 18:01:59 -0400 Subject: [PATCH] feat(notification-processing): [PM-19877] System Notification Implementation - Trying to have the notification present to prompt the browser extension to popup --- apps/browser/package.json | 14 +++++++++ .../browser/src/background/main.background.ts | 30 +++++++++++++++--- .../actions/browser-actions.service.ts | 31 +++++++++++++------ ...e-extension-system-notification.service.ts | 6 ++-- .../src/popup/services/services.module.ts | 6 ++-- .../src/services/jslib-services.module.ts | 2 ++ .../src/platform/actions/actions-service.ts | 9 ------ .../actions/unsupported-actions.service.ts | 4 --- .../internal/default-notifications.service.ts | 20 ++++++++++-- ...ice.ts => system-notifications-service.ts} | 4 +-- ...supported-system-notifications.service.ts} | 6 ++-- 11 files changed, 92 insertions(+), 40 deletions(-) rename libs/common/src/platform/notifications/{system-notification-service.ts => system-notifications-service.ts} (94%) rename libs/common/src/platform/notifications/{unsupported-system-notification.service.ts => unsupported-system-notifications.service.ts} (79%) diff --git a/apps/browser/package.json b/apps/browser/package.json index 70dd0d7a241..c0e8058a1bc 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -19,6 +19,11 @@ "build:prod:firefox": "cross-env NODE_ENV=production npm run build:firefox", "build:prod:opera": "cross-env NODE_ENV=production npm run build:opera", "build:prod:safari": "cross-env NODE_ENV=production npm run build:safari", + "build:nonprod:chrome": "cross-env npm run build:chrome", + "build:nonprod:edge": "cross-env npm run build:edge", + "build:nonprod:firefox": "cross-env npm run build:firefox", + "build:nonprod:opera": "cross-env npm run build:opera", + "build:nonprod:safari": "cross-env npm run build:safari", "dist:chrome": "npm run build:prod:chrome && mkdir -p dist && ./scripts/compress.sh dist-chrome.zip", "dist:edge": "npm run build:prod:edge && mkdir -p dist && ./scripts/compress.sh dist-edge.zip", "dist:firefox": "npm run build:prod:firefox && mkdir -p dist && ./scripts/compress.sh dist-firefox.zip", @@ -27,6 +32,15 @@ "dist:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:firefox", "dist:opera:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:opera", "dist:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:safari", + "dist:chrome:nonprod": "npm run build:nonprod:chrome && mkdir -p dist && ./scripts/compress.sh dist-chrome.zip", + "dist:edge:nonprod": "npm run build:nonprod:edge && mkdir -p dist && ./scripts/compress.sh dist-edge.zip", + "dist:firefox:nonprod": "npm run build:nonprod:firefox && mkdir -p dist && ./scripts/compress.sh dist-firefox.zip", + "dist:opera:nonprod": "npm run build:nonprod:opera && mkdir -p dist && ./scripts/compress.sh dist-opera.zip", + "dist:safari:nonprod": "npm run build:nonprod:safari && ./scripts/package-safari.ps1", + "dist:edge:mv3:nonprod": "cross-env MANIFEST_VERSION=3 npm run dist:edge:nonprod", + "dist:firefox:mv3:nonprod": "cross-env MANIFEST_VERSION=3 npm run dist:firefox:nonprod", + "dist:opera:mv3:nonprod": "cross-env MANIFEST_VERSION=3 npm run dist:opera:nonprod", + "dist:safari:mv3:nonprod": "cross-env MANIFEST_VERSION=3 npm run dist:safari:nonprod", "test": "jest", "test:watch": "jest --watch", "test:watch:all": "jest --watchAll", diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index b913c422f23..9fd96a50a80 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -130,8 +130,11 @@ 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 { + ButtonActions, + SystemNotificationsService, +} from "@bitwarden/common/platform/notifications/system-notifications-service"; +import { UnsupportedSystemNotificationsService } from "@bitwarden/common/platform/notifications/unsupported-system-notifications.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"; @@ -347,7 +350,7 @@ export default class MainBackground { exportService: VaultExportServiceAbstraction; searchService: SearchServiceAbstraction; notificationsService: ServerNotificationsService; - systemNotificationService: SystemNotificationService; + systemNotificationService: SystemNotificationsService; actionsService: ActionsService; stateService: StateServiceAbstraction; userNotificationSettingsService: UserNotificationSettingsServiceAbstraction; @@ -1129,7 +1132,9 @@ export default class MainBackground { this.webPushConnectionService = new UnsupportedWebPushConnectionService(); } - this.actionsService = new BrowserActionsService(this.platformUtilsService); + this.logService.info(`The background service is registered as ${navigator.userAgent}`); + + this.actionsService = new BrowserActionsService(this.logService, this.platformUtilsService); const userAgent = navigator.userAgent; @@ -1144,9 +1149,23 @@ export default class MainBackground { this.platformUtilsService, ); } else { - this.systemNotificationService = new UnsupportedSystemNotificationService(); + this.systemNotificationService = new UnsupportedSystemNotificationsService(); } + setTimeout(async () => { + await this.systemNotificationService.create({ + id: Math.random() * 100000 + "", + type: ButtonActions.AuthRequestNotification, + title: "Test Notification", + body: "Body", + buttons: [ + { + title: "First Button", + }, + ], + }); + }, 1); + this.notificationsService = new DefaultNotificationsService( this.logService, this.syncService, @@ -1158,6 +1177,7 @@ export default class MainBackground { new SignalRConnectionService(this.apiService, this.logService), this.authService, this.webPushConnectionService, + this.systemNotificationService, this.actionsService, ); diff --git a/apps/browser/src/platform/actions/browser-actions.service.ts b/apps/browser/src/platform/actions/browser-actions.service.ts index 20e037b30f9..402b588ed6d 100644 --- a/apps/browser/src/platform/actions/browser-actions.service.ts +++ b/apps/browser/src/platform/actions/browser-actions.service.ts @@ -1,31 +1,44 @@ 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 { LogService } from "@bitwarden/logging"; +import { SafariApp } from "../../browser/safariApp"; import { BrowserApi } from "../browser/browser-api"; export class BrowserActionsService implements ActionsService { - constructor(private platformUtilsService: PlatformUtilsService) {} + constructor( + private logService: LogService, + private platformUtilsService: PlatformUtilsService, + ) {} async openPopup(): Promise { - switch (this.platformUtilsService.getDevice()) { - case DeviceType.ChromeBrowser: + const deviceType = this.platformUtilsService.getDevice(); + + switch (deviceType) { + case DeviceType.FirefoxExtension: case DeviceType.ChromeExtension: { const browserAction = BrowserApi.getBrowserAction(); + // We might get back mv2 or mv3 browserAction, only mv3 supports the openPopup function, + // so check for that function existing. if ("openPopup" in browserAction && typeof browserAction.openPopup === "function") { await browserAction.openPopup(); return; + } else { + this.logService.warning( + `No openPopup function found on browser actions. On browser: ${deviceType} and manifest version: ${BrowserApi.manifestVersion}`, + ); } break; } - case DeviceType.SafariBrowser: case DeviceType.SafariExtension: - break; + await SafariApp.sendMessageToApp("showPopover", null, true); + return; + default: + this.logService.warning( + `Tried to open the popup from an unsupported device type: ${deviceType}`, + ); } } - - openPopupToUrl(url: string): Promise { - return Promise.resolve(undefined); - } } 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 index 9c26e0d774d..d29957cc484 100644 --- 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 @@ -8,10 +8,10 @@ import { SystemNotificationClearInfo, SystemNotificationCreateInfo, SystemNotificationEvent, - SystemNotificationService, -} from "@bitwarden/common/platform/notifications/system-notification-service"; + SystemNotificationsService, +} from "@bitwarden/common/platform/notifications/system-notifications-service"; -export class ChromeExtensionSystemNotificationService implements SystemNotificationService { +export class ChromeExtensionSystemNotificationService implements SystemNotificationsService { private systemNotificationClickedSubject = new Subject(); notificationClicked$: Observable; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 9056856b7f1..50aac1a6d2c 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -97,7 +97,7 @@ import { Message, MessageListener, MessageSender } from "@bitwarden/common/platf import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; import { flagEnabled } from "@bitwarden/common/platform/misc/flags"; import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; -import { SystemNotificationService } from "@bitwarden/common/platform/notifications/system-notification-service"; +import { SystemNotificationsService } from "@bitwarden/common/platform/notifications/system-notifications-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"; @@ -257,7 +257,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: ActionsService, useClass: BrowserActionsService, - deps: [PlatformUtilsServiceAbstraction], + deps: [LogService, PlatformUtilsServiceAbstraction], }), safeProvider({ provide: KeyService, @@ -616,7 +616,7 @@ const safeProviders: SafeProvider[] = [ deps: [], }), safeProvider({ - provide: SystemNotificationService, + provide: SystemNotificationsService, useClass: ChromeExtensionSystemNotificationService, deps: [LogService, PlatformUtilsServiceAbstraction], }), diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index fd1f3e83ad8..a6db0bb5fb2 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -217,6 +217,7 @@ import { WebPushConnectionService, WebPushNotificationsApiService, } from "@bitwarden/common/platform/notifications/internal"; +import { SystemNotificationsService } from "@bitwarden/common/platform/notifications/system-notifications-service"; import { DefaultTaskSchedulerService, TaskSchedulerService, @@ -967,6 +968,7 @@ const safeProviders: SafeProvider[] = [ SignalRConnectionService, AuthServiceAbstraction, WebPushConnectionService, + SystemNotificationsService, ActionsService, ], }), diff --git a/libs/common/src/platform/actions/actions-service.ts b/libs/common/src/platform/actions/actions-service.ts index a1317975088..ba72e057caa 100644 --- a/libs/common/src/platform/actions/actions-service.ts +++ b/libs/common/src/platform/actions/actions-service.ts @@ -3,13 +3,4 @@ export abstract class ActionsService { * Opens the popup. */ abstract openPopup(): Promise; - - /** - * Opens the popup and navigates to a url. - * - * Stubbed for now. - * - * @param url - */ - abstract openPopupToUrl(url: string): Promise; } diff --git a/libs/common/src/platform/actions/unsupported-actions.service.ts b/libs/common/src/platform/actions/unsupported-actions.service.ts index aab25f56324..15a8fa39261 100644 --- a/libs/common/src/platform/actions/unsupported-actions.service.ts +++ b/libs/common/src/platform/actions/unsupported-actions.service.ts @@ -4,8 +4,4 @@ export class UnsupportedActionsService implements ActionsService { openPopup(): Promise { throw new Error("Open Popup unsupported."); } - - openPopupToUrl(url: string): Promise { - throw new Error("Open Popup to Url unsupported."); - } } 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 618f9bb6ae7..0d6b343b0e2 100644 --- a/libs/common/src/platform/notifications/internal/default-notifications.service.ts +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.ts @@ -15,6 +15,11 @@ import { // eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { ActionsService } from "@bitwarden/common/platform/actions"; +import { + ButtonActions, + SystemNotificationEvent, + SystemNotificationsService, +} from "@bitwarden/common/platform/notifications/system-notifications-service"; import { AccountService } from "../../../auth/abstractions/account.service"; import { AuthService } from "../../../auth/abstractions/auth.service"; @@ -56,8 +61,20 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract private readonly signalRConnectionService: SignalRConnectionService, private readonly authService: AuthService, private readonly webPushConnectionService: WebPushConnectionService, - private readonly actionsService: ActionsService, + private readonly systemNotificationService: SystemNotificationsService, + private readonly actionService: ActionsService, ) { + this.systemNotificationService.notificationClicked$ + .pipe( + map(async (value: SystemNotificationEvent) => { + switch (value.type) { + case ButtonActions.AuthRequestNotification: + await this.actionService.openPopup(); + } + }), + ) + .subscribe(); + this.notifications$ = this.accountService.activeAccount$.pipe( map((account) => account?.id), distinctUntilChanged(), @@ -215,7 +232,6 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract await this.syncService.syncDeleteSend(notification.payload as SyncSendNotification); break; case NotificationType.AuthRequest: - await this.actionsService.openPopup(); this.messagingService.send("openLoginApproval", { notificationId: notification.payload.id, }); diff --git a/libs/common/src/platform/notifications/system-notification-service.ts b/libs/common/src/platform/notifications/system-notifications-service.ts similarity index 94% rename from libs/common/src/platform/notifications/system-notification-service.ts rename to libs/common/src/platform/notifications/system-notifications-service.ts index 1cd05ff5ca8..9d252214dc0 100644 --- a/libs/common/src/platform/notifications/system-notification-service.ts +++ b/libs/common/src/platform/notifications/system-notifications-service.ts @@ -35,11 +35,11 @@ export type SystemNotificationClearInfo = { export type SystemNotificationEvent = { id: string; - type: string; + type: ButtonActionsKeys; buttonIdentifier: number; }; -export abstract class SystemNotificationService { +export abstract class SystemNotificationsService { abstract notificationClicked$: Observable; abstract create(createInfo: SystemNotificationCreateInfo): Promise; abstract clear(clearInfo: SystemNotificationClearInfo): undefined; diff --git a/libs/common/src/platform/notifications/unsupported-system-notification.service.ts b/libs/common/src/platform/notifications/unsupported-system-notifications.service.ts similarity index 79% rename from libs/common/src/platform/notifications/unsupported-system-notification.service.ts rename to libs/common/src/platform/notifications/unsupported-system-notifications.service.ts index f03da717138..3877043c4ba 100644 --- a/libs/common/src/platform/notifications/unsupported-system-notification.service.ts +++ b/libs/common/src/platform/notifications/unsupported-system-notifications.service.ts @@ -4,10 +4,10 @@ import { SystemNotificationClearInfo, SystemNotificationCreateInfo, SystemNotificationEvent, - SystemNotificationService, -} from "./system-notification-service"; + SystemNotificationsService, +} from "./system-notifications-service"; -export class UnsupportedSystemNotificationService implements SystemNotificationService { +export class UnsupportedSystemNotificationsService implements SystemNotificationsService { private systemNotificationClickedSubject = new Subject(); notificationClicked$ = throwError(() => new Error("Notification clicked is not supported."));