mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +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
@@ -7,7 +7,7 @@ import {
|
||||
VaultTimeoutSettingsService,
|
||||
VaultTimeoutStringType,
|
||||
} from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
||||
import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications";
|
||||
|
||||
const IdleInterval = 60 * 5; // 5 minutes
|
||||
|
||||
@@ -18,7 +18,7 @@ export default class IdleBackground {
|
||||
|
||||
constructor(
|
||||
private vaultTimeoutService: VaultTimeoutService,
|
||||
private notificationsService: NotificationsService,
|
||||
private serverNotificationsService: ServerNotificationsService,
|
||||
private accountService: AccountService,
|
||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||
) {
|
||||
@@ -32,9 +32,9 @@ export default class IdleBackground {
|
||||
|
||||
const idleHandler = (newState: string) => {
|
||||
if (newState === "active") {
|
||||
this.notificationsService.reconnectFromActivity();
|
||||
this.serverNotificationsService.reconnectFromActivity();
|
||||
} else {
|
||||
this.notificationsService.disconnectFromInactivity();
|
||||
this.serverNotificationsService.disconnectFromInactivity();
|
||||
}
|
||||
};
|
||||
if (this.idle.onStateChanged && this.idle.setDetectionInterval) {
|
||||
|
||||
@@ -111,21 +111,22 @@ 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 { IpcService } from "@bitwarden/common/platform/ipc";
|
||||
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||
// eslint-disable-next-line no-restricted-imports -- Used for dependency creation
|
||||
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
||||
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
||||
import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications";
|
||||
// eslint-disable-next-line no-restricted-imports -- Needed for service creation
|
||||
import {
|
||||
DefaultNotificationsService,
|
||||
DefaultServerNotificationsService,
|
||||
SignalRConnectionService,
|
||||
UnsupportedWebPushConnectionService,
|
||||
WebPushNotificationsApiService,
|
||||
WorkerWebPushConnectionService,
|
||||
} from "@bitwarden/common/platform/notifications/internal";
|
||||
} from "@bitwarden/common/platform/server-notifications/internal";
|
||||
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";
|
||||
@@ -164,6 +165,8 @@ import { WindowStorageService } from "@bitwarden/common/platform/storage/window-
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
// eslint-disable-next-line no-restricted-imports -- Needed for service creation
|
||||
import { DefaultSyncService } from "@bitwarden/common/platform/sync/internal";
|
||||
import { SystemNotificationsService } from "@bitwarden/common/platform/system-notifications/";
|
||||
import { UnsupportedSystemNotificationsService } from "@bitwarden/common/platform/system-notifications/unsupported-system-notifications.service";
|
||||
import { DefaultThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||
import { AuditService } from "@bitwarden/common/services/audit.service";
|
||||
@@ -264,6 +267,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";
|
||||
@@ -292,6 +296,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 { BrowserSystemNotificationService } from "../platform/system-notifications/browser-system-notification.service";
|
||||
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
|
||||
import { VaultFilterService } from "../vault/services/vault-filter.service";
|
||||
|
||||
@@ -337,7 +342,9 @@ export default class MainBackground {
|
||||
importService: ImportServiceAbstraction;
|
||||
exportService: VaultExportServiceAbstraction;
|
||||
searchService: SearchServiceAbstraction;
|
||||
notificationsService: NotificationsService;
|
||||
serverNotificationsService: ServerNotificationsService;
|
||||
systemNotificationService: SystemNotificationsService;
|
||||
actionsService: ActionsService;
|
||||
stateService: StateServiceAbstraction;
|
||||
userNotificationSettingsService: UserNotificationSettingsServiceAbstraction;
|
||||
autofillSettingsService: AutofillSettingsServiceAbstraction;
|
||||
@@ -439,7 +446,6 @@ export default class MainBackground {
|
||||
private webRequestBackground: WebRequestBackground;
|
||||
|
||||
private syncTimeout: any;
|
||||
private isSafari: boolean;
|
||||
private nativeMessagingBackground: NativeMessagingBackground;
|
||||
|
||||
private popupViewCacheBackgroundService: PopupViewCacheBackgroundService;
|
||||
@@ -1109,7 +1115,18 @@ export default class MainBackground {
|
||||
this.webPushConnectionService = new UnsupportedWebPushConnectionService();
|
||||
}
|
||||
|
||||
this.notificationsService = new DefaultNotificationsService(
|
||||
this.actionsService = new BrowserActionsService(this.logService, this.platformUtilsService);
|
||||
|
||||
if ("notifications" in chrome) {
|
||||
this.systemNotificationService = new BrowserSystemNotificationService(
|
||||
this.logService,
|
||||
this.platformUtilsService,
|
||||
);
|
||||
} else {
|
||||
this.systemNotificationService = new UnsupportedSystemNotificationsService();
|
||||
}
|
||||
|
||||
this.serverNotificationsService = new DefaultServerNotificationsService(
|
||||
this.logService,
|
||||
this.syncService,
|
||||
this.appIdService,
|
||||
@@ -1163,9 +1180,6 @@ export default class MainBackground {
|
||||
this.logService,
|
||||
);
|
||||
|
||||
// Other fields
|
||||
this.isSafari = this.platformUtilsService.isSafari();
|
||||
|
||||
// Background
|
||||
|
||||
this.fido2Background = new Fido2Background(
|
||||
@@ -1183,7 +1197,6 @@ export default class MainBackground {
|
||||
this,
|
||||
this.autofillService,
|
||||
this.platformUtilsService as BrowserPlatformUtilsService,
|
||||
this.notificationsService,
|
||||
this.autofillSettingsService,
|
||||
this.processReloadService,
|
||||
this.environmentService,
|
||||
@@ -1222,7 +1235,7 @@ export default class MainBackground {
|
||||
this.apiService,
|
||||
this.organizationService,
|
||||
this.authService,
|
||||
this.notificationsService,
|
||||
this.serverNotificationsService,
|
||||
messageListener,
|
||||
);
|
||||
|
||||
@@ -1296,7 +1309,7 @@ export default class MainBackground {
|
||||
|
||||
this.idleBackground = new IdleBackground(
|
||||
this.vaultTimeoutService,
|
||||
this.notificationsService,
|
||||
this.serverNotificationsService,
|
||||
this.accountService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
);
|
||||
@@ -1354,7 +1367,7 @@ export default class MainBackground {
|
||||
this.endUserNotificationService = new DefaultEndUserNotificationService(
|
||||
this.stateProvider,
|
||||
this.apiService,
|
||||
this.notificationsService,
|
||||
this.serverNotificationsService,
|
||||
this.authService,
|
||||
this.logService,
|
||||
);
|
||||
@@ -1433,7 +1446,7 @@ export default class MainBackground {
|
||||
setTimeout(async () => {
|
||||
await this.fullSync(false);
|
||||
this.backgroundSyncService.init();
|
||||
this.notificationsService.startListening();
|
||||
this.serverNotificationsService.startListening();
|
||||
|
||||
this.taskService.listenForTaskNotifications();
|
||||
this.endUserNotificationService.listenForEndUserNotifications();
|
||||
@@ -1656,6 +1669,11 @@ export default class MainBackground {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the popup.
|
||||
*
|
||||
* @deprecated Migrating to the browser actions service.
|
||||
*/
|
||||
async openPopup() {
|
||||
const browserAction = BrowserApi.getBrowserAction();
|
||||
|
||||
@@ -1664,7 +1682,7 @@ export default class MainBackground {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isSafari) {
|
||||
if (this.platformUtilsService.isSafari()) {
|
||||
await SafariApp.sendMessageToApp("showPopover", null, true);
|
||||
}
|
||||
}
|
||||
@@ -1691,7 +1709,9 @@ 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);
|
||||
|
||||
@@ -15,7 +15,6 @@ 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 { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum";
|
||||
import { BiometricsCommands } from "@bitwarden/key-management";
|
||||
@@ -46,7 +45,6 @@ export default class RuntimeBackground {
|
||||
private main: MainBackground,
|
||||
private autofillService: AutofillService,
|
||||
private platformUtilsService: BrowserPlatformUtilsService,
|
||||
private notificationsService: NotificationsService,
|
||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||
private processReloadService: ProcessReloadServiceAbstraction,
|
||||
private environmentService: BrowserEnvironmentService,
|
||||
@@ -424,6 +422,11 @@ export default class RuntimeBackground {
|
||||
return await BrowserApi.tabsQuery({ url: `${urlObj.href}*` });
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the popup.
|
||||
*
|
||||
* @deprecated Migrating to the browser actions service.
|
||||
*/
|
||||
private async openPopup() {
|
||||
await this.main.openPopup();
|
||||
}
|
||||
@@ -450,7 +453,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) {
|
||||
|
||||
48
apps/browser/src/platform/actions/browser-actions.service.ts
Normal file
48
apps/browser/src/platform/actions/browser-actions.service.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
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 logService: LogService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
) {}
|
||||
|
||||
async openPopup(): Promise<void> {
|
||||
const deviceType = this.platformUtilsService.getDevice();
|
||||
|
||||
try {
|
||||
switch (deviceType) {
|
||||
case DeviceType.FirefoxExtension:
|
||||
case DeviceType.ChromeExtension: {
|
||||
const browserAction = BrowserApi.getBrowserAction();
|
||||
|
||||
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.SafariExtension:
|
||||
await SafariApp.sendMessageToApp("showPopover", null, true);
|
||||
return;
|
||||
default:
|
||||
this.logService.warning(
|
||||
`Tried to open the popup from an unsupported device type: ${deviceType}`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(
|
||||
`Failed to open the popup on ${DeviceType[deviceType]} with manifest ${BrowserApi.manifestVersion} and error: ${e}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { BrowserApi } from "./browser-api";
|
||||
@@ -26,13 +24,13 @@ export function fromChromeEvent<T extends unknown[]>(
|
||||
event: chrome.events.Event<(...args: T) => void>,
|
||||
): Observable<T> {
|
||||
return new Observable<T>((subscriber) => {
|
||||
const handler = (...args: T) => {
|
||||
const handler = (...args: readonly unknown[]) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
subscriber.error(chrome.runtime.lastError);
|
||||
return;
|
||||
}
|
||||
|
||||
subscriber.next(args);
|
||||
subscriber.next(args as T);
|
||||
};
|
||||
|
||||
BrowserApi.addListener(event, handler);
|
||||
|
||||
@@ -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/server-notifications";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
// Eventually if we want to support listening to notifications from browser foreground we
|
||||
// Eventually if we want to support listening to server 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) {
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { map, merge, Observable } from "rxjs";
|
||||
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
ButtonLocation,
|
||||
SystemNotificationClearInfo,
|
||||
SystemNotificationCreateInfo,
|
||||
SystemNotificationEvent,
|
||||
SystemNotificationsService,
|
||||
} from "@bitwarden/common/platform/system-notifications/system-notifications.service";
|
||||
|
||||
import { fromChromeEvent } from "../browser/from-chrome-event";
|
||||
|
||||
export class BrowserSystemNotificationService implements SystemNotificationsService {
|
||||
notificationClicked$: Observable<SystemNotificationEvent>;
|
||||
|
||||
constructor(
|
||||
private logService: LogService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
) {
|
||||
this.notificationClicked$ = merge(
|
||||
fromChromeEvent(chrome.notifications.onButtonClicked).pipe(
|
||||
map(([notificationId, buttonIndex]) => ({
|
||||
id: notificationId,
|
||||
buttonIdentifier: buttonIndex,
|
||||
})),
|
||||
),
|
||||
fromChromeEvent(chrome.notifications.onClicked).pipe(
|
||||
map(([notificationId]: [string]) => ({
|
||||
id: notificationId,
|
||||
buttonIdentifier: ButtonLocation.NotificationButton,
|
||||
})),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async create(createInfo: SystemNotificationCreateInfo): Promise<string> {
|
||||
return new Promise<string>((resolve) => {
|
||||
chrome.notifications.create(
|
||||
{
|
||||
iconUrl: chrome.runtime.getURL("images/icon128.png"),
|
||||
message: createInfo.body,
|
||||
type: "basic",
|
||||
title: createInfo.title,
|
||||
buttons: createInfo.buttons.map((value) => ({ title: value.title })),
|
||||
},
|
||||
(notificationId) => resolve(notificationId),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async clear(clearInfo: SystemNotificationClearInfo): Promise<undefined> {
|
||||
chrome.notifications.clear(clearInfo.id);
|
||||
}
|
||||
|
||||
isSupported(): boolean {
|
||||
return "notifications" in chrome;
|
||||
}
|
||||
}
|
||||
@@ -92,12 +92,13 @@ import {
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { ActionsService } from "@bitwarden/common/platform/actions";
|
||||
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||
// 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 { TaskSchedulerService } from "@bitwarden/common/platform/scheduling";
|
||||
import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications";
|
||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||
import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory";
|
||||
@@ -113,6 +114,7 @@ import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/imp
|
||||
import { PrimarySecondaryStorageService } from "@bitwarden/common/platform/storage/primary-secondary-storage.service";
|
||||
import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { SystemNotificationsService } from "@bitwarden/common/platform/system-notifications/system-notifications.service";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@@ -160,13 +162,14 @@ import { InlineMenuFieldQualificationService } from "../../autofill/services/inl
|
||||
import { ForegroundBrowserBiometricsService } from "../../key-management/biometrics/foreground-browser-biometrics";
|
||||
import { ExtensionLockComponentService } from "../../key-management/lock/services/extension-lock-component.service";
|
||||
import { ForegroundVaultTimeoutService } from "../../key-management/vault-timeout/foreground-vault-timeout.service";
|
||||
import { BrowserActionsService } from "../../platform/actions/browser-actions.service";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import { runInsideAngular } from "../../platform/browser/run-inside-angular.operator";
|
||||
/* eslint-disable no-restricted-imports */
|
||||
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";
|
||||
@@ -184,6 +187,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 { BrowserSystemNotificationService } from "../../platform/system-notifications/browser-system-notification.service";
|
||||
import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging";
|
||||
import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service";
|
||||
import { Fido2UserVerificationService } from "../../vault/services/fido2-user-verification.service";
|
||||
@@ -253,6 +257,11 @@ const safeProviders: SafeProvider[] = [
|
||||
},
|
||||
deps: [GlobalStateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ActionsService,
|
||||
useClass: BrowserActionsService,
|
||||
deps: [LogService, PlatformUtilsService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: KeyService,
|
||||
useFactory: (
|
||||
@@ -609,6 +618,11 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: SsoUrlService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SystemNotificationsService,
|
||||
useClass: BrowserSystemNotificationService,
|
||||
deps: [LogService, PlatformUtilsService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: LoginComponentService,
|
||||
useClass: ExtensionLoginComponentService,
|
||||
@@ -674,8 +688,8 @@ const safeProviders: SafeProvider[] = [
|
||||
deps: [KeyService, MasterPasswordApiService, InternalMasterPasswordServiceAbstraction, WINDOW],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: NotificationsService,
|
||||
useClass: ForegroundNotificationsService,
|
||||
provide: ServerNotificationsService,
|
||||
useClass: ForegroundServerNotificationsService,
|
||||
deps: [LogService],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
@@ -75,7 +75,7 @@ export class CliPlatformUtilsService implements PlatformUtilsService {
|
||||
return false;
|
||||
}
|
||||
|
||||
isViewOpen() {
|
||||
isPopupOpen() {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,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/server-notifications";
|
||||
import { StateEventRunnerService } from "@bitwarden/common/platform/state";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
@@ -155,7 +155,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,
|
||||
|
||||
@@ -13,7 +13,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/server-notifications";
|
||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
||||
@@ -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,
|
||||
|
||||
@@ -59,7 +59,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
|
||||
return ipc.platform.isMacAppStore;
|
||||
}
|
||||
|
||||
isViewOpen(): Promise<boolean> {
|
||||
isPopupOpen(): Promise<boolean> {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,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/server-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";
|
||||
@@ -76,7 +76,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private keyService: KeyService,
|
||||
private collectionService: CollectionService,
|
||||
private searchService: SearchService,
|
||||
private notificationsService: NotificationsService,
|
||||
private serverNotificationsService: ServerNotificationsService,
|
||||
private stateService: StateService,
|
||||
private eventUploadService: EventUploadService,
|
||||
protected policyListService: PolicyListService,
|
||||
@@ -88,14 +88,14 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private accountService: AccountService,
|
||||
private processReloadService: ProcessReloadServiceAbstraction,
|
||||
private deviceTrustToastService: DeviceTrustToastService,
|
||||
private readonly destoryRef: DestroyRef,
|
||||
private readonly destroy: DestroyRef,
|
||||
private readonly documentLangSetter: DocumentLangSetter,
|
||||
private readonly tokenService: TokenService,
|
||||
) {
|
||||
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||
|
||||
const langSubscription = this.documentLangSetter.start();
|
||||
this.destoryRef.onDestroy(() => langSubscription.unsubscribe());
|
||||
this.destroy.onDestroy(() => langSubscription.unsubscribe());
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -347,9 +347,9 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
|
||||
private idleStateChanged() {
|
||||
if (this.isIdle) {
|
||||
this.notificationsService.disconnectFromInactivity();
|
||||
this.serverNotificationsService.disconnectFromInactivity();
|
||||
} else {
|
||||
this.notificationsService.reconnectFromActivity();
|
||||
this.serverNotificationsService.reconnectFromActivity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ import { IpcService } from "@bitwarden/common/platform/ipc";
|
||||
import {
|
||||
UnsupportedWebPushConnectionService,
|
||||
WebPushConnectionService,
|
||||
} from "@bitwarden/common/platform/notifications/internal";
|
||||
} from "@bitwarden/common/platform/server-notifications/internal";
|
||||
import { AppIdService as DefaultAppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||
|
||||
@@ -12,7 +12,7 @@ import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vau
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||
import { IpcService } from "@bitwarden/common/platform/ipc";
|
||||
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
||||
import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications";
|
||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
||||
@@ -26,7 +26,7 @@ import { VersionService } from "../platform/version.service";
|
||||
export class InitService {
|
||||
constructor(
|
||||
@Inject(WINDOW) private win: Window,
|
||||
private notificationsService: NotificationsService,
|
||||
private serverNotificationsService: ServerNotificationsService,
|
||||
private vaultTimeoutService: DefaultVaultTimeoutService,
|
||||
private i18nService: I18nServiceAbstraction,
|
||||
private eventUploadService: EventUploadServiceAbstraction,
|
||||
@@ -56,7 +56,7 @@ export class InitService {
|
||||
await this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(activeAccount.id);
|
||||
}
|
||||
|
||||
this.notificationsService.startListening();
|
||||
this.serverNotificationsService.startListening();
|
||||
await this.vaultTimeoutService.init(true);
|
||||
await this.i18nService.init();
|
||||
(this.eventUploadService as EventUploadService).init(true);
|
||||
|
||||
@@ -98,7 +98,7 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
|
||||
return false;
|
||||
}
|
||||
|
||||
isViewOpen(): Promise<boolean> {
|
||||
isPopupOpen(): Promise<boolean> {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SupportStatus } from "@bitwarden/common/platform/misc/support-status";
|
||||
import {
|
||||
WebPushConnector,
|
||||
WorkerWebPushConnectionService,
|
||||
} from "@bitwarden/common/platform/notifications/internal";
|
||||
} from "@bitwarden/common/platform/server-notifications/internal";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
export class PermissionsWebPushConnectionService extends WorkerWebPushConnectionService {
|
||||
|
||||
@@ -565,7 +565,7 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
this.refreshing = false;
|
||||
|
||||
// Explicitly mark for check to ensure the view is updated
|
||||
// Some sources are not always emitted within the Angular zone (e.g. ciphers updated via WS notifications)
|
||||
// Some sources are not always emitted within the Angular zone (e.g. ciphers updated via WS server notifications)
|
||||
this.changeDetectorRef.markForCheck();
|
||||
},
|
||||
);
|
||||
|
||||
@@ -196,24 +196,26 @@ import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.serv
|
||||
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { ActionsService } from "@bitwarden/common/platform/actions";
|
||||
import { UnsupportedActionsService } from "@bitwarden/common/platform/actions/unsupported-actions.service";
|
||||
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||
// eslint-disable-next-line no-restricted-imports -- Used for dependency injection
|
||||
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
||||
import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags";
|
||||
import { NotificationsService } from "@bitwarden/common/platform/notifications";
|
||||
// eslint-disable-next-line no-restricted-imports -- Needed for service creation
|
||||
import {
|
||||
DefaultNotificationsService,
|
||||
NoopNotificationsService,
|
||||
SignalRConnectionService,
|
||||
UnsupportedWebPushConnectionService,
|
||||
WebPushConnectionService,
|
||||
WebPushNotificationsApiService,
|
||||
} from "@bitwarden/common/platform/notifications/internal";
|
||||
import {
|
||||
DefaultTaskSchedulerService,
|
||||
TaskSchedulerService,
|
||||
} from "@bitwarden/common/platform/scheduling";
|
||||
import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications";
|
||||
// eslint-disable-next-line no-restricted-imports -- Needed for service creation
|
||||
import {
|
||||
DefaultServerNotificationsService,
|
||||
NoopServerNotificationsService,
|
||||
SignalRConnectionService,
|
||||
UnsupportedWebPushConnectionService,
|
||||
WebPushConnectionService,
|
||||
WebPushNotificationsApiService,
|
||||
} from "@bitwarden/common/platform/server-notifications/internal";
|
||||
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";
|
||||
@@ -919,10 +921,15 @@ const safeProviders: SafeProvider[] = [
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: NotificationsService,
|
||||
provide: ActionsService,
|
||||
useClass: UnsupportedActionsService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ServerNotificationsService,
|
||||
useClass: devFlagEnabled("noopNotifications")
|
||||
? NoopNotificationsService
|
||||
: DefaultNotificationsService,
|
||||
? NoopServerNotificationsService
|
||||
: DefaultServerNotificationsService,
|
||||
deps: [
|
||||
LogService,
|
||||
SyncService,
|
||||
@@ -1513,7 +1520,7 @@ const safeProviders: SafeProvider[] = [
|
||||
ApiServiceAbstraction,
|
||||
OrganizationServiceAbstraction,
|
||||
AuthServiceAbstraction,
|
||||
NotificationsService,
|
||||
ServerNotificationsService,
|
||||
MessageListener,
|
||||
],
|
||||
}),
|
||||
@@ -1523,7 +1530,7 @@ const safeProviders: SafeProvider[] = [
|
||||
deps: [
|
||||
StateProvider,
|
||||
ApiServiceAbstraction,
|
||||
NotificationsService,
|
||||
ServerNotificationsService,
|
||||
AuthServiceAbstraction,
|
||||
LogService,
|
||||
],
|
||||
|
||||
@@ -109,9 +109,9 @@ export abstract class AuthRequestServiceAbstraction {
|
||||
): Promise<{ masterKey: MasterKey; masterKeyHash: string }>;
|
||||
|
||||
/**
|
||||
* Handles incoming auth request push notifications.
|
||||
* Handles incoming auth request push server notifications.
|
||||
* @param notification push notification.
|
||||
* @remark We should only be receiving approved push notifications to prevent enumeration.
|
||||
* @remark We should only be receiving approved push server notifications to prevent enumeration.
|
||||
*/
|
||||
abstract sendAuthRequestPushNotification(notification: AuthRequestPushNotification): void;
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export class DefaultAuthRequestApiService implements AuthRequestApiServiceAbstra
|
||||
try {
|
||||
// Submit the current device identifier in the header as well as in the POST body.
|
||||
// The value in the header will be used to build the request context and ensure that the resulting
|
||||
// notifications have the current device as a source.
|
||||
// server notifications have the current device as a source.
|
||||
const response = await this.apiService.send(
|
||||
"POST",
|
||||
"/auth-requests/",
|
||||
|
||||
@@ -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$
|
||||
|
||||
@@ -625,7 +625,7 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
// Vault can be de-synced since notifications get ignored while locked. Need to check whether sync is required using the sync service.
|
||||
// Vault can be de-synced since server notifications get ignored while locked. Need to check whether sync is required using the sync service.
|
||||
const startSync = new Date().getTime();
|
||||
// TODO: This should probably not be blocking
|
||||
await this.syncService.fullSync(false);
|
||||
|
||||
Reference in New Issue
Block a user