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 - Initial changeset

This commit is contained in:
Patrick Pimentel
2025-07-16 15:01:24 -04:00
parent 70b65a20dd
commit ca43e923ce
34 changed files with 255 additions and 141 deletions

View File

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

View File

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

View File

@@ -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) {

View File

@@ -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<void> {
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:
}
}
}

View File

@@ -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<readonly [NotificationResponse, UserId]>;
constructor(private readonly logService: LogService) {

View File

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

View File

@@ -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<boolean> {
async isPopupOpen(): Promise<boolean> {
if (this.isSafari()) {
// Query views on safari since chrome.runtime.sendMessage does not timeout and will hang.
return BrowserApi.isPopupOpen();

View File

@@ -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<SystemNotificationEvent>();
notificationClicked$: Observable<SystemNotificationEvent>;
constructor(
private logService: LogService,
private platformUtilsService: PlatformUtilsService,
) {
this.notificationClicked$ = this.systemNotificationClickedSubject.asObservable();
}
async create(createInfo: SystemNotificationCreateInfo): Promise<undefined> {
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;
}
}
}

View File

@@ -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<SystemNotificationEvent>();
notificationClicked$: Observable<SystemNotificationEvent>;
constructor(
private logService: LogService,
private platformUtilsService: PlatformUtilsService,
) {
this.notificationClicked$ = this.systemNotificationClickedSubject.asObservable();
}
async create(createInfo: SystemNotificationCreateInfo): Promise<undefined> {
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;
}
}
}

View File

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

View File

@@ -75,7 +75,7 @@ export class CliPlatformUtilsService implements PlatformUtilsService {
return false;
}
isViewOpen() {
isPopupOpen() {
return Promise.resolve(false);
}

View File

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

View File

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

View File

@@ -59,7 +59,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
return ipc.platform.isMacAppStore;
}
isViewOpen(): Promise<boolean> {
isPopupOpen(): Promise<boolean> {
return Promise.resolve(false);
}

View File

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

View File

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

View File

@@ -98,7 +98,7 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
return false;
}
isViewOpen(): Promise<boolean> {
isPopupOpen(): Promise<boolean> {
return Promise.resolve(false);
}

View File

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

View File

@@ -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<void> {
// Add popstate listener to listen for browser back button clicks
window.addEventListener("popstate", this.handlePopState);

View File

@@ -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<VaultTimeoutAction>(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,

View File

@@ -82,7 +82,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
async checkVaultTimeout(): Promise<void> {
// 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([

View File

@@ -22,7 +22,7 @@ export abstract class PlatformUtilsService {
abstract isVivaldi(): boolean;
abstract isSafari(): boolean;
abstract isMacAppStore(): boolean;
abstract isViewOpen(): Promise<boolean>;
abstract isPopupOpen(): Promise<boolean>;
abstract launchUri(uri: string, options?: any): void;
abstract getApplicationVersion(): Promise<string>;
abstract getApplicationVersionNumber(): Promise<string>;

View File

@@ -0,0 +1,3 @@
export abstract class ActionsService {
abstract openPopup(): Promise<void>;
}

View File

@@ -0,0 +1 @@
export { ActionsService } from "./actions-service";

View File

@@ -0,0 +1,7 @@
import { ActionsService } from "./actions-service";
export class UnsupportedActionsService implements ActionsService {
openPopup(): Promise<void> {
return Promise.resolve(undefined);
}
}

View File

@@ -1 +1 @@
export { NotificationsService } from "./notifications.service";
export { ServerNotificationsService } from "./server-notifications-service";

View File

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

View File

@@ -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<readonly [NotificationResponse, UserId]> = new Subject();
constructor(private logService: LogService) {}

View File

@@ -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.

View File

@@ -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];

View File

@@ -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<ApiService>;
let mockNotificationsService: jest.Mocked<NotificationsService>;
let mockNotificationsService: jest.Mocked<ServerNotificationsService>;
let mockAuthService: jest.Mocked<AuthService>;
let mockLogService: jest.Mocked<LogService>;
let service: DefaultEndUserNotificationService;

View File

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

View File

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

View File

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