mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 08:43:33 +00:00
Feat PM-19877 System Notification Processing (#15611)
* feat(notification-processing): [PM-19877] System Notification Implementation - Minor changes to popup logic and removed content in login component. * docs(notification-processing): [PM-19877] System Notification Implementation - Added more docs. * docs(notification-processing): [PM-19877] System Notification Implementation - Added markdown document. * fix(notification-processing): [PM-19877] System Notification Implementation - Updated condition for if notification is supported. * fix(notification-processing): [PM-19877] System Notification Implementation - Updated services module with correct platform utils service.
This commit is contained in:
committed by
GitHub
parent
bcd73a9c00
commit
719a43d050
@@ -5,11 +5,11 @@
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum PushTechnology {
|
||||
/**
|
||||
* Indicates that we should use SignalR over web sockets to receive push notifications from the server.
|
||||
* Indicates that we should use SignalR over web sockets to receive push server notifications from the server.
|
||||
*/
|
||||
SignalR = 0,
|
||||
/**
|
||||
* Indicatates that we should use WebPush to receive push notifications from the server.
|
||||
* Indicates that we should use WebPush to receive push server notifications from the server.
|
||||
*/
|
||||
WebPush = 1,
|
||||
}
|
||||
|
||||
@@ -185,7 +185,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);
|
||||
@@ -225,7 +225,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,
|
||||
|
||||
@@ -84,7 +84,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([
|
||||
|
||||
@@ -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>;
|
||||
|
||||
40
libs/common/src/platform/actions/README.md
Normal file
40
libs/common/src/platform/actions/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Platform Actions API
|
||||
|
||||
## ActionsService.openPopup()
|
||||
|
||||
This document outlines the current behavior of `ActionsService.openPopup()` across different browsers, specifically in two contexts:
|
||||
|
||||
- **Window Context**: When the call is triggered from an active browser window (e.g., from a tab's script).
|
||||
- **Background Service Worker Context**: When the call is made from a background context, such as a service worker.
|
||||
|
||||
The `openPopup()` method has limitations in some environments due to browser-specific restrictions or bugs. Below is a compatibility chart detailing the observed behavior.
|
||||
|
||||
---
|
||||
|
||||
## Compatibility Table
|
||||
|
||||
| Browser | Window Context | Background Service Worker Context |
|
||||
| ------- | ------------------- | --------------------------------- |
|
||||
| Safari | ✅ Works | ❌ Fails |
|
||||
| Firefox | ❌ Fails | ❌ Fails |
|
||||
| Chrome | ✅ Works | ✅ Works |
|
||||
| Edge | 🟡 Untested | 🟡 Untested |
|
||||
| Vivaldi | ⚠️ Ambiguous (Bug?) | ⚠️ Ambiguous (Bug?) |
|
||||
| Opera | ✅ Works | ❌ Fails silently |
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **Safari**: Only works when `openPopup()` is triggered from a window context. Attempts from background service workers fail.
|
||||
- **Firefox**: Does not appear to support `openPopup()` in either context.
|
||||
- **Chrome**: Fully functional in both contexts, but only on Mac. Windows it does not work in.
|
||||
- **Edge**: Behavior has not been tested.
|
||||
- **Vivaldi**: `openPopup()` results in an error that _might_ be related to running in a background context, but the cause is currently unclear.
|
||||
- **Opera**: Works from window context. Background calls fail silently with no error message.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
When implementing `ActionsService.openPopup()`, prefer triggering it from a window context whenever possible to maximize cross-browser compatibility. Full background service worker support is only reliable in **Chrome**.
|
||||
6
libs/common/src/platform/actions/actions-service.ts
Normal file
6
libs/common/src/platform/actions/actions-service.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export abstract class ActionsService {
|
||||
/**
|
||||
* Opens the popup if it is supported.
|
||||
*/
|
||||
abstract openPopup(): Promise<void>;
|
||||
}
|
||||
1
libs/common/src/platform/actions/index.ts
Normal file
1
libs/common/src/platform/actions/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { ActionsService } from "./actions-service";
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ActionsService } from "./actions-service";
|
||||
|
||||
export class UnsupportedActionsService implements ActionsService {
|
||||
openPopup(): Promise<void> {
|
||||
throw new Error("Open Popup unsupported.");
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { NotificationsService } from "./notifications.service";
|
||||
1
libs/common/src/platform/server-notifications/index.ts
Normal file
1
libs/common/src/platform/server-notifications/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { ServerNotificationsService } from "./server-notifications.service";
|
||||
@@ -21,9 +21,9 @@ import { SupportStatus } from "../../misc/support-status";
|
||||
import { SyncService } from "../../sync";
|
||||
|
||||
import {
|
||||
DefaultNotificationsService,
|
||||
DefaultServerNotificationsService,
|
||||
DISABLED_NOTIFICATIONS_URL,
|
||||
} from "./default-notifications.service";
|
||||
} from "./default-server-notifications.service";
|
||||
import { SignalRConnectionService, SignalRNotification } from "./signalr-connection.service";
|
||||
import { WebPushConnectionService, WebPushConnector } from "./webpush-connection.service";
|
||||
import { WorkerWebPushConnectionService } from "./worker-webpush-connection.service";
|
||||
@@ -52,7 +52,7 @@ describe("NotificationsService", () => {
|
||||
notificationsUrl: string,
|
||||
) => Subject<SignalRNotification>;
|
||||
|
||||
let sut: DefaultNotificationsService;
|
||||
let sut: DefaultServerNotificationsService;
|
||||
|
||||
beforeEach(() => {
|
||||
syncService = mock<SyncService>();
|
||||
@@ -93,7 +93,7 @@ describe("NotificationsService", () => {
|
||||
() => new Subject<SignalRNotification>(),
|
||||
);
|
||||
|
||||
sut = new DefaultNotificationsService(
|
||||
sut = new DefaultServerNotificationsService(
|
||||
mock<LogService>(),
|
||||
syncService,
|
||||
appIdService,
|
||||
@@ -134,7 +134,7 @@ describe("NotificationsService", () => {
|
||||
expect(actualNotification.type).toBe(expectedType);
|
||||
};
|
||||
|
||||
it("emits notifications through WebPush when supported", async () => {
|
||||
it("emits server notifications through WebPush when supported", async () => {
|
||||
const notificationsPromise = firstValueFrom(sut.notifications$.pipe(bufferCount(2)));
|
||||
|
||||
emitActiveUser(mockUser1);
|
||||
@@ -227,7 +227,7 @@ describe("NotificationsService", () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
// Temporarily rolling back notifications being connected while locked
|
||||
// Temporarily rolling back server notifications being connected while locked
|
||||
// { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Unlocked },
|
||||
// { initialStatus: AuthenticationStatus.Unlocked, updatedStatus: AuthenticationStatus.Locked },
|
||||
// { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Locked },
|
||||
@@ -256,7 +256,7 @@ describe("NotificationsService", () => {
|
||||
);
|
||||
|
||||
it.each([
|
||||
// Temporarily disabling notifications connecting while in a locked state
|
||||
// Temporarily disabling server notifications connecting while in a locked state
|
||||
// AuthenticationStatus.Locked,
|
||||
AuthenticationStatus.Unlocked,
|
||||
])(
|
||||
@@ -282,7 +282,7 @@ describe("NotificationsService", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("does not connect to any notification stream when notifications are disabled through special url", () => {
|
||||
it("does not connect to any notification stream when server notifications are disabled through special url", () => {
|
||||
const subscription = sut.notifications$.subscribe();
|
||||
emitActiveUser(mockUser1);
|
||||
emitNotificationUrl(DISABLED_NOTIFICATIONS_URL);
|
||||
@@ -32,14 +32,14 @@ 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 } from "../server-notifications.service";
|
||||
|
||||
import { ReceiveMessage, SignalRConnectionService } from "./signalr-connection.service";
|
||||
import { WebPushConnectionService } from "./webpush-connection.service";
|
||||
|
||||
export const DISABLED_NOTIFICATIONS_URL = "http://-";
|
||||
|
||||
export class DefaultNotificationsService implements NotificationsServiceAbstraction {
|
||||
export class DefaultServerNotificationsService implements ServerNotificationsService {
|
||||
notifications$: Observable<readonly [NotificationResponse, UserId]>;
|
||||
|
||||
private activitySubject = new BehaviorSubject<"active" | "inactive">("active");
|
||||
@@ -61,7 +61,7 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract
|
||||
distinctUntilChanged(),
|
||||
switchMap((activeAccountId) => {
|
||||
if (activeAccountId == null) {
|
||||
// We don't emit notifications for inactive accounts currently
|
||||
// We don't emit server-notifications for inactive accounts currently
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
@@ -74,8 +74,8 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a stream of push notifications for the given user.
|
||||
* @param userId The user id of the user to get the push notifications for.
|
||||
* Retrieves a stream of push server notifications for the given user.
|
||||
* @param userId The user id of the user to get the push server notifications for.
|
||||
*/
|
||||
private userNotifications$(userId: UserId) {
|
||||
return this.environmentService.environment$.pipe(
|
||||
@@ -109,7 +109,7 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract
|
||||
}),
|
||||
supportSwitch({
|
||||
supported: (service) => {
|
||||
this.logService.info("Using WebPush for notifications");
|
||||
this.logService.info("Using WebPush for server notifications");
|
||||
return service.notifications$.pipe(
|
||||
catchError((err: unknown) => {
|
||||
this.logService.warning("Issue with web push, falling back to SignalR", err);
|
||||
@@ -118,7 +118,7 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract
|
||||
);
|
||||
},
|
||||
notSupported: () => {
|
||||
this.logService.info("Using SignalR for notifications");
|
||||
this.logService.info("Using SignalR for server notifications");
|
||||
return this.connectSignalR$(userId, notificationsUrl);
|
||||
},
|
||||
}),
|
||||
@@ -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,11 @@ 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,
|
||||
});
|
||||
}
|
||||
// create notification
|
||||
|
||||
this.messagingService.send("openLoginApproval", {
|
||||
notificationId: notification.payload.id,
|
||||
});
|
||||
break;
|
||||
case NotificationType.SyncOrganizationStatusChanged:
|
||||
await this.syncService.fullSync(true);
|
||||
@@ -237,7 +236,8 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract
|
||||
mergeMap(async ([notification, userId]) => this.processNotification(notification, userId)),
|
||||
)
|
||||
.subscribe({
|
||||
error: (e: unknown) => this.logService.warning("Error in notifications$ observable", e),
|
||||
error: (e: unknown) =>
|
||||
this.logService.warning("Error in server notifications$ observable", e),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export * from "./worker-webpush-connection.service";
|
||||
export * from "./signalr-connection.service";
|
||||
export * from "./default-notifications.service";
|
||||
export * from "./noop-notifications.service";
|
||||
export * from "./default-server-notifications.service";
|
||||
export * from "./noop-server-notifications.service";
|
||||
export * from "./unsupported-webpush-connection.service";
|
||||
export * from "./webpush-connection.service";
|
||||
export * from "./websocket-webpush-connection.service";
|
||||
@@ -4,16 +4,16 @@ 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 NoopServerNotificationsService implements ServerNotificationsService {
|
||||
notifications$: Observable<readonly [NotificationResponse, UserId]> = new Subject();
|
||||
|
||||
constructor(private logService: LogService) {}
|
||||
|
||||
startListening(): Subscription {
|
||||
this.logService.info(
|
||||
"Initializing no-op notification service, no push notifications will be received",
|
||||
"Initializing no-op notification service, no push server notifications will be received",
|
||||
);
|
||||
return Subscription.EMPTY;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export class WebPushNotificationsApiService {
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Posts a device-user association to the server and ensures it's installed for push notifications
|
||||
* Posts a device-user association to the server and ensures it's installed for push server notifications
|
||||
*/
|
||||
async putSubscription(pushSubscription: PushSubscriptionJSON): Promise<void> {
|
||||
const request = WebPushRequest.from(pushSubscription);
|
||||
@@ -40,7 +40,7 @@ interface PushEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation for connecting to web push based notifications running in a Worker.
|
||||
* An implementation for connecting to web push based server notifications running in a Worker.
|
||||
*/
|
||||
export class WorkerWebPushConnectionService implements WebPushConnectionService {
|
||||
private pushEvent = new Subject<PushEvent>();
|
||||
@@ -75,7 +75,7 @@ export class WorkerWebPushConnectionService implements WebPushConnectionService
|
||||
}
|
||||
|
||||
supportStatus$(userId: UserId): Observable<SupportStatus<WebPushConnector>> {
|
||||
// Check the server config to see if it supports sending WebPush notifications
|
||||
// Check the server config to see if it supports sending WebPush server notifications
|
||||
// FIXME: get config of server for the specified userId, once ConfigService supports it
|
||||
return this.configService.serverConfig$.pipe(
|
||||
map((config) =>
|
||||
@@ -4,20 +4,20 @@ import { NotificationResponse } from "@bitwarden/common/models/response/notifica
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Needed to link to API
|
||||
import type { DefaultNotificationsService } from "./internal";
|
||||
import type { DefaultServerNotificationsService } 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.
|
||||
* Please add code reacting to notifications in {@link DefaultNotificationsService.processNotification}
|
||||
* Please add code reacting to server notifications in {@link DefaultServerNotificationsService.processNotification}
|
||||
*/
|
||||
abstract notifications$: Observable<readonly [NotificationResponse, UserId]>;
|
||||
/**
|
||||
* Starts automatic listening and processing of notifications, should only be called once per application,
|
||||
* Starts automatic listening and processing of server notifications, should only be called once per application,
|
||||
* or you will risk notifications being processed multiple times.
|
||||
*/
|
||||
abstract startListening(): Subscription;
|
||||
@@ -230,7 +230,7 @@ export abstract class CoreSyncService implements SyncService {
|
||||
}),
|
||||
),
|
||||
);
|
||||
// Process only notifications for currently active user when user is not logged out
|
||||
// Process only server notifications for currently active user when user is not logged out
|
||||
// TODO: once send service allows data manipulation of non-active users, this should process any received notification
|
||||
if (activeUserId === notification.userId && status !== AuthenticationStatus.LoggedOut) {
|
||||
try {
|
||||
|
||||
1
libs/common/src/platform/system-notifications/index.ts
Normal file
1
libs/common/src/platform/system-notifications/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { SystemNotificationsService } from "./system-notifications.service";
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
// 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 = 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];
|
||||
|
||||
export type SystemNotificationsButton = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
export type SystemNotificationCreateInfo = {
|
||||
id?: string;
|
||||
title: string;
|
||||
body: string;
|
||||
buttons: SystemNotificationsButton[];
|
||||
};
|
||||
|
||||
export type SystemNotificationClearInfo = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type SystemNotificationEvent = {
|
||||
id: string;
|
||||
buttonIdentifier: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* A service responsible for displaying operating system level server notifications.
|
||||
*/
|
||||
export abstract class SystemNotificationsService {
|
||||
abstract notificationClicked$: Observable<SystemNotificationEvent>;
|
||||
|
||||
/**
|
||||
* Creates a notification.
|
||||
* @param createInfo
|
||||
* @returns If a notification is successfully created it will respond back with an
|
||||
* id that refers to a notification.
|
||||
*/
|
||||
abstract create(createInfo: SystemNotificationCreateInfo): Promise<string>;
|
||||
|
||||
/**
|
||||
* Clears a notification.
|
||||
* @param clearInfo Any info needed required to clear a notification.
|
||||
*/
|
||||
abstract clear(clearInfo: SystemNotificationClearInfo): Promise<void>;
|
||||
|
||||
/**
|
||||
* Used to know if a given platform supports server notifications.
|
||||
*/
|
||||
abstract isSupported(): boolean;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { throwError } from "rxjs";
|
||||
|
||||
import {
|
||||
SystemNotificationClearInfo,
|
||||
SystemNotificationCreateInfo,
|
||||
SystemNotificationsService,
|
||||
} from "./system-notifications.service";
|
||||
|
||||
export class UnsupportedSystemNotificationsService implements SystemNotificationsService {
|
||||
notificationClicked$ = throwError(() => new Error("Notification clicked is not supported."));
|
||||
|
||||
async create(createInfo: SystemNotificationCreateInfo): Promise<string> {
|
||||
throw new Error("Create OS Notification unsupported.");
|
||||
}
|
||||
|
||||
clear(clearInfo: SystemNotificationClearInfo): Promise<undefined> {
|
||||
throw new Error("Clear OS Notification unsupported.");
|
||||
}
|
||||
|
||||
isSupported(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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/server-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;
|
||||
@@ -48,7 +48,7 @@ describe("End User Notification Center Service", () => {
|
||||
});
|
||||
|
||||
describe("notifications$", () => {
|
||||
it("should return notifications from state when not null", async () => {
|
||||
it("should return server notifications from state when not null", async () => {
|
||||
fakeStateProvider.singleUser.mockFor("user-id" as UserId, NOTIFICATIONS, [
|
||||
{
|
||||
id: "notification-id" as NotificationId,
|
||||
@@ -62,7 +62,7 @@ describe("End User Notification Center Service", () => {
|
||||
expect(mockLogService.warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return notifications API when state is null", async () => {
|
||||
it("should return server notifications API when state is null", async () => {
|
||||
mockApiService.send.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
@@ -86,7 +86,7 @@ describe("End User Notification Center Service", () => {
|
||||
expect(mockLogService.warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should log a warning if there are more notifications available", async () => {
|
||||
it("should log a warning if there are more server notifications available", async () => {
|
||||
mockApiService.send.mockResolvedValue({
|
||||
data: [
|
||||
...new Array(DEFAULT_NOTIFICATION_PAGE_SIZE + 1).fill({ id: "notification-id" }),
|
||||
@@ -120,7 +120,7 @@ describe("End User Notification Center Service", () => {
|
||||
});
|
||||
|
||||
describe("unreadNotifications$", () => {
|
||||
it("should return unread notifications from state when read value is null", async () => {
|
||||
it("should return unread server notifications from state when read value is null", async () => {
|
||||
fakeStateProvider.singleUser.mockFor("user-id" as UserId, NOTIFICATIONS, [
|
||||
{
|
||||
id: "notification-id" as NotificationId,
|
||||
@@ -136,7 +136,7 @@ describe("End User Notification Center Service", () => {
|
||||
});
|
||||
|
||||
describe("getNotifications", () => {
|
||||
it("should call getNotifications returning notifications from API", async () => {
|
||||
it("should call getNotifications returning server notifications from API", async () => {
|
||||
mockApiService.send.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
@@ -156,7 +156,7 @@ describe("End User Notification Center Service", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should update local state when notifications are updated", async () => {
|
||||
it("should update local state when server notifications are updated", async () => {
|
||||
mockApiService.send.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
|
||||
@@ -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/server-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,
|
||||
) {}
|
||||
|
||||
@@ -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/server-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,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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/server-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,
|
||||
) {}
|
||||
|
||||
@@ -171,7 +171,7 @@ export class DefaultTaskService implements TaskService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a subscription for pending security task notifications or completed syncs for unlocked users.
|
||||
* Creates a subscription for pending security task server notifications or completed syncs for unlocked users.
|
||||
*/
|
||||
listenForTaskNotifications(): Subscription {
|
||||
return this.authService.authStatuses$
|
||||
|
||||
Reference in New Issue
Block a user