1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 03:33:30 +00:00

feat(notification-processing): [PM-19877] System Notification Implementation - Trying to have the notification present to prompt the browser extension to popup

This commit is contained in:
Patrick Pimentel
2025-07-17 18:01:59 -04:00
parent bcbca86320
commit b269eaed0b
11 changed files with 92 additions and 40 deletions

View File

@@ -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",

View File

@@ -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,
);

View File

@@ -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<void> {
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<void> {
return Promise.resolve(undefined);
}
}

View File

@@ -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<SystemNotificationEvent>();
notificationClicked$: Observable<SystemNotificationEvent>;

View File

@@ -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],
}),

View File

@@ -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,
],
}),

View File

@@ -3,13 +3,4 @@ export abstract class ActionsService {
* Opens the popup.
*/
abstract openPopup(): Promise<void>;
/**
* Opens the popup and navigates to a url.
*
* Stubbed for now.
*
* @param url
*/
abstract openPopupToUrl(url: string): Promise<void>;
}

View File

@@ -4,8 +4,4 @@ export class UnsupportedActionsService implements ActionsService {
openPopup(): Promise<void> {
throw new Error("Open Popup unsupported.");
}
openPopupToUrl(url: string): Promise<void> {
throw new Error("Open Popup to Url unsupported.");
}
}

View File

@@ -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,
});

View File

@@ -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<SystemNotificationEvent>;
abstract create(createInfo: SystemNotificationCreateInfo): Promise<undefined>;
abstract clear(clearInfo: SystemNotificationClearInfo): undefined;

View File

@@ -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<SystemNotificationEvent>();
notificationClicked$ = throwError(() => new Error("Notification clicked is not supported."));