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

View File

@@ -2,6 +2,7 @@
// @ts-strict-ignore // @ts-strict-ignore
import { firstValueFrom } from "rxjs"; 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 { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -63,46 +64,48 @@ export default class NotificationBackground {
ExtensionCommand.AutofillIdentity, ExtensionCommand.AutofillIdentity,
]); ]);
private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = { private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = {
unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender), bgAddLogin: ({ message, sender }) => this.addLogin(message, sender),
bgGetFolderData: () => this.getFolderData(),
bgCloseNotificationBar: ({ message, sender }) =>
this.handleCloseNotificationBarMessage(message, sender),
bgAdjustNotificationBar: ({ message, sender }) => bgAdjustNotificationBar: ({ message, sender }) =>
this.handleAdjustNotificationBarMessage(message, sender), this.handleAdjustNotificationBarMessage(message, sender),
bgAddLogin: ({ message, sender }) => this.addLogin(message, sender),
bgChangedPassword: ({ message, sender }) => this.changedPassword(message, sender), bgChangedPassword: ({ message, sender }) => this.changedPassword(message, sender),
bgRemoveTabFromNotificationQueue: ({ sender }) => bgCloseNotificationBar: ({ message, sender }) =>
this.removeTabFromNotificationQueue(sender.tab), this.handleCloseNotificationBarMessage(message, sender),
bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender), bgGetActiveUserServerConfig: () => this.getActiveUserServerConfig(),
bgNeverSave: ({ sender }) => this.saveNever(sender.tab), bgGetDecryptedCiphers: () => this.getNotificationCipherData(),
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),
bgGetEnableChangedPasswordPrompt: () => this.getEnableChangedPasswordPrompt(), bgGetEnableChangedPasswordPrompt: () => this.getEnableChangedPasswordPrompt(),
bgGetEnableAddedLoginPrompt: () => this.getEnableAddedLoginPrompt(), bgGetEnableAddedLoginPrompt: () => this.getEnableAddedLoginPrompt(),
bgGetExcludedDomains: () => this.getExcludedDomains(), 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(), getWebVaultUrlForNotification: () => this.getWebVaultUrl(),
notificationRefreshFlagValue: () => this.getNotificationFlag(), notificationRefreshFlagValue: () => this.getNotificationFlag(),
bgGetDecryptedCiphers: () => this.getNotificationCipherData(), unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender),
bgOpenVault: ({ message, sender }) => this.openVault(message, sender.tab),
}; };
constructor( constructor(
private accountService: AccountService,
private authService: AuthService,
private autofillService: AutofillService, private autofillService: AutofillService,
private cipherService: CipherService, private cipherService: CipherService,
private authService: AuthService, private configService: ConfigService,
private policyService: PolicyService,
private folderService: FolderService,
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
private domainSettingsService: DomainSettingsService, private domainSettingsService: DomainSettingsService,
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private folderService: FolderService,
private logService: LogService, private logService: LogService,
private organizationService: OrganizationService,
private policyService: PolicyService,
private themeStateService: ThemeStateService, private themeStateService: ThemeStateService,
private configService: ConfigService, private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
private accountService: AccountService,
) {} ) {}
init() { 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 * Handles the unlockCompleted extension message. Will close the notification bar
* after an attempted autofill action, and retry the autofill action if the message * 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({ export function NotificationBody({
ciphers, ciphers = [],
notificationType, notificationType,
theme = ThemeTypes.Light, theme = ThemeTypes.Light,
handleEditOrUpdateAction, handleEditOrUpdateAction,
}: { }: {
ciphers: NotificationCipherData[]; ciphers?: NotificationCipherData[];
customClasses?: string[]; customClasses?: string[];
notificationType?: NotificationType; notificationType?: NotificationType;
theme: Theme; theme: Theme;

View File

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

View File

@@ -1,5 +1,8 @@
import { Theme } from "@bitwarden/common/platform/enums"; 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 = { const NotificationTypes = {
Add: "add", Add: "add",
Change: "change", Change: "change",
@@ -9,21 +12,24 @@ const NotificationTypes = {
type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes]; type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes];
type NotificationBarIframeInitData = { type NotificationBarIframeInitData = {
type?: string; // @TODO use `NotificationType`
isVaultLocked?: boolean;
theme?: Theme;
removeIndividualVault?: boolean;
importType?: string;
applyRedesign?: boolean; applyRedesign?: boolean;
ciphers?: NotificationCipherData[];
folders?: FolderView[];
importType?: string;
isVaultLocked?: boolean;
launchTimestamp?: number; launchTimestamp?: number;
organizations?: OrgView[];
removeIndividualVault?: boolean;
theme?: Theme;
type?: string; // @TODO use `NotificationType`
}; };
type NotificationBarWindowMessage = { type NotificationBarWindowMessage = {
cipherId?: string;
command: string; command: string;
error?: string; error?: string;
initData?: NotificationBarIframeInitData; initData?: NotificationBarIframeInitData;
username?: string; username?: string;
cipherId?: string;
}; };
type NotificationBarWindowMessageHandlers = { 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 type { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background"; 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 { NotificationConfirmationContainer } from "../content/components/notification/confirmation-container";
import { NotificationContainer } from "../content/components/notification/container"; import { NotificationContainer } from "../content/components/notification/container";
import { buildSvgDomElement } from "../utils"; 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; const { initData } = message;
if (!initData) { if (!initData) {
return; 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. // 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()); 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 // @TODO use context to avoid prop drilling
return render( return render(
NotificationContainer({ NotificationContainer({
@@ -142,7 +160,6 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
handleSaveAction, handleSaveAction,
handleEditOrUpdateAction, handleEditOrUpdateAction,
i18n, i18n,
ciphers: cipherData,
}), }),
document.body, document.body,
); );

View File

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