1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 06:43:35 +00:00

PM-19291 Pass relevant folder and vault data to drop down component within notification footer (#13901)

* PM-19291
- Pass relevant data into dropdown component
- Clean up some files
- Pass all data into notificationBarIframeInitData using promise all

* fix tests
This commit is contained in:
Daniel Riera
2025-03-20 09:54:56 -04:00
committed by GitHub
parent 57c15a26eb
commit 45d5b171b8
7 changed files with 110 additions and 53 deletions

View File

@@ -1,6 +1,7 @@
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
@@ -59,6 +60,7 @@ describe("NotificationBackground", () => {
const themeStateService = mock<ThemeStateService>();
const configService = mock<ConfigService>();
const accountService = mock<AccountService>();
const organizationService = mock<OrganizationService>();
const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({
id: "testId" as UserId,
@@ -73,18 +75,19 @@ describe("NotificationBackground", () => {
authService.activeAccountStatus$ = activeAccountStatusMock$;
accountService.activeAccount$ = activeAccountSubject;
notificationBackground = new NotificationBackground(
accountService,
authService,
autofillService,
cipherService,
authService,
policyService,
folderService,
userNotificationSettingsService,
configService,
domainSettingsService,
environmentService,
folderService,
logService,
organizationService,
policyService,
themeStateService,
configService,
accountService,
userNotificationSettingsService,
);
});

View File

@@ -2,6 +2,7 @@
// @ts-strict-ignore
import { firstValueFrom } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -63,46 +64,48 @@ export default class NotificationBackground {
ExtensionCommand.AutofillIdentity,
]);
private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = {
unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender),
bgGetFolderData: () => this.getFolderData(),
bgCloseNotificationBar: ({ message, sender }) =>
this.handleCloseNotificationBarMessage(message, sender),
bgAddLogin: ({ message, sender }) => this.addLogin(message, sender),
bgAdjustNotificationBar: ({ message, sender }) =>
this.handleAdjustNotificationBarMessage(message, sender),
bgAddLogin: ({ message, sender }) => this.addLogin(message, sender),
bgChangedPassword: ({ message, sender }) => this.changedPassword(message, sender),
bgRemoveTabFromNotificationQueue: ({ sender }) =>
this.removeTabFromNotificationQueue(sender.tab),
bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender),
bgNeverSave: ({ sender }) => this.saveNever(sender.tab),
collectPageDetailsResponse: ({ message }) =>
this.handleCollectPageDetailsResponseMessage(message),
bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab),
checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab),
bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab),
bgCloseNotificationBar: ({ message, sender }) =>
this.handleCloseNotificationBarMessage(message, sender),
bgGetActiveUserServerConfig: () => this.getActiveUserServerConfig(),
bgGetDecryptedCiphers: () => this.getNotificationCipherData(),
bgGetEnableChangedPasswordPrompt: () => this.getEnableChangedPasswordPrompt(),
bgGetEnableAddedLoginPrompt: () => this.getEnableAddedLoginPrompt(),
bgGetExcludedDomains: () => this.getExcludedDomains(),
bgGetActiveUserServerConfig: () => this.getActiveUserServerConfig(),
bgGetFolderData: () => this.getFolderData(),
bgGetOrgData: () => this.getOrgData(),
bgNeverSave: ({ sender }) => this.saveNever(sender.tab),
bgOpenVault: ({ message, sender }) => this.openVault(message, sender.tab),
bgRemoveTabFromNotificationQueue: ({ sender }) =>
this.removeTabFromNotificationQueue(sender.tab),
bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab),
bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender),
bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab),
checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab),
collectPageDetailsResponse: ({ message }) =>
this.handleCollectPageDetailsResponseMessage(message),
getWebVaultUrlForNotification: () => this.getWebVaultUrl(),
notificationRefreshFlagValue: () => this.getNotificationFlag(),
bgGetDecryptedCiphers: () => this.getNotificationCipherData(),
bgOpenVault: ({ message, sender }) => this.openVault(message, sender.tab),
unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender),
};
constructor(
private accountService: AccountService,
private authService: AuthService,
private autofillService: AutofillService,
private cipherService: CipherService,
private authService: AuthService,
private policyService: PolicyService,
private folderService: FolderService,
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
private configService: ConfigService,
private domainSettingsService: DomainSettingsService,
private environmentService: EnvironmentService,
private folderService: FolderService,
private logService: LogService,
private organizationService: OrganizationService,
private policyService: PolicyService,
private themeStateService: ThemeStateService,
private configService: ConfigService,
private accountService: AccountService,
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
) {}
init() {
@@ -744,6 +747,26 @@ export default class NotificationBackground {
);
}
/**
* Returns the first value found from the organization service organizations$ observable.
*/
private async getOrgData() {
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(getOptionalUserId),
);
const organizations = await firstValueFrom(
this.organizationService.organizations$(activeUserId),
);
return organizations.map((org) => {
const { id, name, productTierType } = org;
return {
id,
name,
productTierType,
};
});
}
/**
* Handles the unlockCompleted extension message. Will close the notification bar
* after an attempted autofill action, and retry the autofill action if the message

View File

@@ -16,12 +16,12 @@ const { css } = createEmotion({
});
export function NotificationBody({
ciphers,
ciphers = [],
notificationType,
theme = ThemeTypes.Light,
handleEditOrUpdateAction,
}: {
ciphers: NotificationCipherData[];
ciphers?: NotificationCipherData[];
customClasses?: string[];
notificationType?: NotificationType;
theme: Theme;

View File

@@ -9,6 +9,7 @@ import {
NotificationType,
} from "../../../notification/abstractions/notification-bar";
import { NotificationCipherData } from "../cipher/types";
import { FolderView, OrgView } from "../common-types";
import { themes, spacing } from "../constants/styles";
import { NotificationBody, componentClassPrefix as notificationBodyClassPrefix } from "./body";
@@ -20,20 +21,24 @@ import {
export function NotificationContainer({
handleCloseNotification,
handleEditOrUpdateAction,
handleSaveAction,
ciphers,
folders,
i18n,
organizations,
theme = ThemeTypes.Light,
type,
ciphers,
handleSaveAction,
handleEditOrUpdateAction,
}: NotificationBarIframeInitData & {
handleCloseNotification: (e: Event) => void;
handleSaveAction: (e: Event) => void;
handleEditOrUpdateAction: (e: Event) => void;
} & {
ciphers?: NotificationCipherData[];
folders?: FolderView[];
i18n: { [key: string]: string };
organizations?: OrgView[];
type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type`
ciphers: NotificationCipherData[];
}) {
const headerMessage = getHeaderMessage(i18n, type);
const showBody = true;
@@ -42,8 +47,8 @@ export function NotificationContainer({
<div class=${notificationContainerStyles(theme)}>
${NotificationHeader({
handleCloseNotification,
standalone: showBody,
message: headerMessage,
standalone: showBody,
theme,
})}
${showBody
@@ -56,9 +61,11 @@ export function NotificationContainer({
: null}
${NotificationFooter({
handleSaveAction,
theme,
notificationType: type,
folders,
i18n,
notificationType: type,
organizations,
theme,
})}
</div>
`;

View File

@@ -1,5 +1,8 @@
import { Theme } from "@bitwarden/common/platform/enums";
import { NotificationCipherData } from "../../../autofill/content/components/cipher/types";
import { FolderView, OrgView } from "../../../autofill/content/components/common-types";
const NotificationTypes = {
Add: "add",
Change: "change",
@@ -9,21 +12,24 @@ const NotificationTypes = {
type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes];
type NotificationBarIframeInitData = {
type?: string; // @TODO use `NotificationType`
isVaultLocked?: boolean;
theme?: Theme;
removeIndividualVault?: boolean;
importType?: string;
applyRedesign?: boolean;
ciphers?: NotificationCipherData[];
folders?: FolderView[];
importType?: string;
isVaultLocked?: boolean;
launchTimestamp?: number;
organizations?: OrgView[];
removeIndividualVault?: boolean;
theme?: Theme;
type?: string; // @TODO use `NotificationType`
};
type NotificationBarWindowMessage = {
cipherId?: string;
command: string;
error?: string;
initData?: NotificationBarIframeInitData;
username?: string;
cipherId?: string;
};
type NotificationBarWindowMessageHandlers = {

View File

@@ -5,6 +5,8 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l
import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background";
import { NotificationCipherData } from "../content/components/cipher/types";
import { OrgView } from "../content/components/common-types";
import { NotificationConfirmationContainer } from "../content/components/notification/confirmation-container";
import { NotificationContainer } from "../content/components/notification/container";
import { buildSvgDomElement } from "../utils";
@@ -115,7 +117,7 @@ function setElementText(template: HTMLTemplateElement, elementId: string, text:
}
}
function initNotificationBar(message: NotificationBarWindowMessage) {
async function initNotificationBar(message: NotificationBarWindowMessage) {
const { initData } = message;
if (!initData) {
return;
@@ -131,7 +133,23 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
// Current implementations utilize a require for scss files which creates the need to remove the node.
document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove());
sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, (cipherData) => {
await Promise.all([
new Promise<OrgView[]>((resolve) =>
sendPlatformMessage({ command: "bgGetOrgData" }, resolve),
),
new Promise<FolderView[]>((resolve) =>
sendPlatformMessage({ command: "bgGetFolderData" }, resolve),
),
new Promise<NotificationCipherData[]>((resolve) =>
sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, resolve),
),
]).then(([organizations, folders, ciphers]) => {
notificationBarIframeInitData = {
...notificationBarIframeInitData,
folders,
ciphers,
organizations,
};
// @TODO use context to avoid prop drilling
return render(
NotificationContainer({
@@ -142,7 +160,6 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
handleSaveAction,
handleEditOrUpdateAction,
i18n,
ciphers: cipherData,
}),
document.body,
);

View File

@@ -1173,18 +1173,19 @@ export default class MainBackground {
() => this.generatePasswordToClipboard(),
);
this.notificationBackground = new NotificationBackground(
this.accountService,
this.authService,
this.autofillService,
this.cipherService,
this.authService,
this.policyService,
this.folderService,
this.userNotificationSettingsService,
this.configService,
this.domainSettingsService,
this.environmentService,
this.folderService,
this.logService,
this.organizationService,
this.policyService,
this.themeStateService,
this.configService,
this.accountService,
this.userNotificationSettingsService,
);
this.overlayNotificationsBackground = new OverlayNotificationsBackground(