diff --git a/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts b/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts index af850c9a7bc..a6661ea263c 100644 --- a/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts +++ b/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts @@ -43,17 +43,13 @@ describe("AuthPopoutWindow", () => { singleActionKey: AuthPopoutType.unlockExtension, senderWindowId: 1, }); - expect(sendMessageDataSpy).toHaveBeenCalledWith(senderTab, "bgUnlockPopoutOpened", { - skipNotification: false, - }); + expect(sendMessageDataSpy).toHaveBeenCalledWith(senderTab, "bgUnlockPopoutOpened", {}); }); - it("sends an indication that the presenting the notification bar for unlocking the extension should be skipped", async () => { - await openUnlockPopout(senderTab, true); + it("sends the bgUnlockPopoutOpened message", async () => { + await openUnlockPopout(senderTab); - expect(sendMessageDataSpy).toHaveBeenCalledWith(senderTab, "bgUnlockPopoutOpened", { - skipNotification: true, - }); + expect(sendMessageDataSpy).toHaveBeenCalledWith(senderTab, "bgUnlockPopoutOpened", {}); }); it("closes any existing popup window types that are open to the unlock extension route", async () => { diff --git a/apps/browser/src/auth/popup/utils/auth-popout-window.ts b/apps/browser/src/auth/popup/utils/auth-popout-window.ts index 0611891b61e..15e1ceb3e7b 100644 --- a/apps/browser/src/auth/popup/utils/auth-popout-window.ts +++ b/apps/browser/src/auth/popup/utils/auth-popout-window.ts @@ -20,9 +20,8 @@ const extensionUnlockUrls = new Set([ * Opens a window that facilitates unlocking / logging into the extension. * * @param senderTab - Used to determine the windowId of the sender. - * @param skipNotification - Used to determine whether to show the unlock notification. */ -async function openUnlockPopout(senderTab: chrome.tabs.Tab, skipNotification = false) { +async function openUnlockPopout(senderTab: chrome.tabs.Tab) { const existingPopoutWindowTabs = await BrowserApi.tabsQuery({ windowType: "popup" }); existingPopoutWindowTabs.forEach((tab) => { if (extensionUnlockUrls.has(tab.url)) { @@ -36,7 +35,7 @@ async function openUnlockPopout(senderTab: chrome.tabs.Tab, skipNotification = f singleActionKey: AuthPopoutType.unlockExtension, senderWindowId: senderTab.windowId, }); - await BrowserApi.tabSendMessageData(senderTab, "bgUnlockPopoutOpened", { skipNotification }); + await BrowserApi.tabSendMessageData(senderTab, "bgUnlockPopoutOpened", {}); } /** diff --git a/apps/browser/src/autofill/background/abstractions/notification.background.ts b/apps/browser/src/autofill/background/abstractions/notification.background.ts index cc28ed0057e..52720b1f9f5 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -141,7 +141,6 @@ type NotificationBackgroundExtensionMessageHandlers = { sender, }: BackgroundOnMessageHandlerParams) => Promise; bgNeverSave: ({ sender }: BackgroundSenderParam) => Promise; - bgUnlockPopoutOpened: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; bgReopenUnlockPopout: ({ sender }: BackgroundSenderParam) => Promise; checkNotificationQueue: ({ sender }: BackgroundSenderParam) => Promise; collectPageDetailsResponse: ({ message }: BackgroundMessageParam) => Promise; diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 1c72b2af1d1..032baf2e32b 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -817,6 +817,7 @@ describe("NotificationBackground", () => { reprompt: CipherRepromptType.None, }); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); + taskService.tasksEnabled$.mockImplementation(() => of(false)); sendMockExtensionMessage(message, sender); await flushPromises(); @@ -865,7 +866,7 @@ describe("NotificationBackground", () => { reprompt: CipherRepromptType.Password, }); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); - + taskService.tasksEnabled$.mockImplementation(() => of(false)); sendMockExtensionMessage(message, sender); await flushPromises(); @@ -913,9 +914,6 @@ describe("NotificationBackground", () => { taskService.pendingTasks$.mockImplementation(() => of([mockSecurityTask, mockSecurityTask2]), ); - jest - .spyOn(notificationBackground as any, "getNotificationFlag") - .mockResolvedValueOnce(true); jest.spyOn(notificationBackground as any, "getOrgData").mockResolvedValueOnce([ { id: mockOrgId, @@ -1372,74 +1370,6 @@ describe("NotificationBackground", () => { }); }); - describe("bgUnlockPopoutOpened message handler", () => { - let pushUnlockVaultToQueueSpy: jest.SpyInstance; - - beforeEach(() => { - pushUnlockVaultToQueueSpy = jest.spyOn( - notificationBackground as any, - "pushUnlockVaultToQueue", - ); - }); - - it("skips pushing the unlock vault message to the queue if the message indicates that the notification should be skipped", async () => { - const tabMock = createChromeTabMock(); - const sender = mock({ tab: tabMock }); - const message: NotificationBackgroundExtensionMessage = { - command: "bgUnlockPopoutOpened", - data: { skipNotification: true }, - }; - - sendMockExtensionMessage(message, sender); - await flushPromises(); - - expect(pushUnlockVaultToQueueSpy).not.toHaveBeenCalled(); - }); - - it("skips pushing the unlock vault message to the queue if the auth status is not `Locked`", async () => { - const tabMock = createChromeTabMock(); - const sender = mock({ tab: tabMock }); - const message: NotificationBackgroundExtensionMessage = { - command: "bgUnlockPopoutOpened", - }; - activeAccountStatusMock$.next(AuthenticationStatus.LoggedOut); - - sendMockExtensionMessage(message, sender); - await flushPromises(); - - expect(pushUnlockVaultToQueueSpy).not.toHaveBeenCalled(); - }); - - it("skips pushing the unlock vault message to the queue if the notification queue already has an item", async () => { - const tabMock = createChromeTabMock(); - const sender = mock({ tab: tabMock }); - const message: NotificationBackgroundExtensionMessage = { - command: "bgUnlockPopoutOpened", - }; - activeAccountStatusMock$.next(AuthenticationStatus.Locked); - notificationBackground["notificationQueue"] = [mock()]; - - sendMockExtensionMessage(message, sender); - await flushPromises(); - - expect(pushUnlockVaultToQueueSpy).not.toHaveBeenCalled(); - }); - - it("sends an unlock vault message to the queue if the user has a locked vault", async () => { - const tabMock = createChromeTabMock({ url: "https://example.com" }); - const sender = mock({ tab: tabMock }); - const message: NotificationBackgroundExtensionMessage = { - command: "bgUnlockPopoutOpened", - }; - activeAccountStatusMock$.next(AuthenticationStatus.Locked); - - sendMockExtensionMessage(message, sender); - await flushPromises(); - - expect(pushUnlockVaultToQueueSpy).toHaveBeenCalledWith("example.com", sender.tab); - }); - }); - describe("checkNotificationQueue", () => { let doNotificationQueueCheckSpy: jest.SpyInstance; let getTabFromCurrentWindowSpy: jest.SpyInstance; diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index e9eea552f37..d44bf2f1507 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -22,7 +22,6 @@ import { import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; import { ProductTierType } from "@bitwarden/common/billing/enums/product-tier-type.enum"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; @@ -67,7 +66,6 @@ import { TemporaryNotificationChangeLoginService } from "../services/notificatio import { AddChangePasswordNotificationQueueMessage, AddLoginQueueMessage, - AddUnlockVaultQueueMessage, AddLoginMessageData, NotificationQueueMessageItem, LockedVaultPendingNotificationsData, @@ -116,12 +114,10 @@ export default class NotificationBackground { bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender), bgHandleReprompt: ({ message, sender }: any) => this.handleCipherUpdateRepromptResponse(message), - 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(), unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender), }; @@ -351,15 +347,6 @@ export default class NotificationBackground { return await firstValueFrom(this.configService.serverConfig$); } - /** - * Gets the current value of the notification refresh feature flag - * @returns Promise indicating if the feature is enabled - */ - async getNotificationFlag(): Promise { - const flagValue = await this.configService.getFeatureFlag(FeatureFlag.NotificationRefresh); - return flagValue; - } - /** * Gets the current authentication status of the user. * @returns Promise - The current authentication status of the user. @@ -465,11 +452,6 @@ export default class NotificationBackground { data: ModifyLoginCipherFormData, tab: chrome.tabs.Tab, ): Promise { - const flag = await this.getNotificationFlag(); - if (!flag) { - return false; - } - const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getOptionalUserId), ); @@ -683,34 +665,6 @@ export default class NotificationBackground { }); } - /** - * Sets up a notification to unlock the vault when the user - * attempts to autofill a cipher while the vault is locked. - * - * @param message - Extension message, determines if the notification should be skipped - * @param tab - The tab that the message was sent from - */ - private async unlockVault(message: NotificationBackgroundExtensionMessage, tab: chrome.tabs.Tab) { - const notificationRefreshFlagEnabled = await this.getNotificationFlag(); - if (message.data?.skipNotification) { - return; - } - - if (notificationRefreshFlagEnabled) { - return; - } - - const currentAuthStatus = await this.getAuthStatus(); - if (currentAuthStatus !== AuthenticationStatus.Locked || this.notificationQueue.length) { - return; - } - - const loginDomain = Utils.getDomain(tab.url); - if (loginDomain) { - await this.pushUnlockVaultToQueue(loginDomain, tab); - } - } - private async pushChangePasswordToQueue( cipherId: string, loginDomain: string, @@ -734,20 +688,6 @@ export default class NotificationBackground { await this.checkNotificationQueue(tab); } - private async pushUnlockVaultToQueue(loginDomain: string, tab: chrome.tabs.Tab) { - this.removeTabFromNotificationQueue(tab); - const launchTimestamp = new Date().getTime(); - const message: AddUnlockVaultQueueMessage = { - type: NotificationType.UnlockVault, - domain: loginDomain, - tab: tab, - launchTimestamp, - expires: new Date(launchTimestamp + 0.5 * 60000), // 30 seconds - wasVaultLocked: true, - }; - await this.sendNotificationQueueMessage(tab, message); - } - /** * Saves a cipher based on the message sent from the notification bar. If the vault * is locked, the message will be added to the notification queue and the unlock @@ -906,12 +846,11 @@ export default class NotificationBackground { } const cipher = await this.cipherService.encrypt(cipherView, userId); - const shouldGetTasks = await this.getNotificationFlag(); try { if (!cipherView.edit) { throw new Error("You do not have permission to edit this cipher."); } - const tasks = shouldGetTasks ? await this.getSecurityTasks(userId) : []; + const tasks = await this.getSecurityTasks(userId); const updatedCipherTask = tasks.find((task) => task.cipherId === cipherView?.id); const cipherHasTask = !!updatedCipherTask?.id; diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 6886e24a198..a2eb6eb7e90 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -2107,7 +2107,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { "addToLockedVaultPendingNotifications", retryMessage, ); - await this.openUnlockPopout(sender.tab, true); + await this.openUnlockPopout(sender.tab); } /** diff --git a/apps/browser/src/autofill/content/content-message-handler.spec.ts b/apps/browser/src/autofill/content/content-message-handler.spec.ts index 99d0d9031cf..fe023f344d6 100644 --- a/apps/browser/src/autofill/content/content-message-handler.spec.ts +++ b/apps/browser/src/autofill/content/content-message-handler.spec.ts @@ -100,10 +100,19 @@ describe("ContentMessageHandler", () => { }); it("forwards the message to the extension background if it is present in the forwardCommands list", () => { - sendMockExtensionMessage({ command: "bgUnlockPopoutOpened" }); + const forwardCommands = [ + "addToLockedVaultPendingNotifications", + "unlockCompleted", + "addedCipher", + ]; - expect(sendMessageSpy).toHaveBeenCalledTimes(1); - expect(sendMessageSpy).toHaveBeenCalledWith({ command: "bgUnlockPopoutOpened" }); + forwardCommands.forEach((command) => { + sendMockExtensionMessage({ command }); + + expect(sendMessageSpy).toHaveBeenCalledWith({ command }); + }); + + expect(sendMessageSpy).toHaveBeenCalledTimes(forwardCommands.length); }); }); diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 6e20a07f81f..9ae6fcedc8f 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -1,10 +1,8 @@ import { render } from "lit"; import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; -import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; 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 { CollectionView, I18n, OrgView } from "../content/components/common-types"; import { AtRiskNotification } from "../content/components/notification/at-risk-password/container"; @@ -12,8 +10,6 @@ import { NotificationConfirmationContainer } from "../content/components/notific import { NotificationContainer } from "../content/components/notification/container"; import { selectedFolder as selectedFolderSignal } from "../content/components/signals/selected-folder"; import { selectedVault as selectedVaultSignal } from "../content/components/signals/selected-vault"; -import { buildSvgDomElement } from "../utils"; -import { circleCheckIcon } from "../utils/svg-icons"; import { NotificationBarWindowMessageHandlers, @@ -23,34 +19,18 @@ import { NotificationTypes, } from "./abstractions/notification-bar"; -const logService = new ConsoleLogService(false); let notificationBarIframeInitData: NotificationBarIframeInitData = {}; let windowMessageOrigin: string; -let useComponentBar = false; const notificationBarWindowMessageHandlers: NotificationBarWindowMessageHandlers = { initNotificationBar: ({ message }) => initNotificationBar(message), - saveCipherAttemptCompleted: ({ message }) => - useComponentBar - ? handleSaveCipherConfirmation(message) - : handleSaveCipherAttemptCompletedMessage(message), + saveCipherAttemptCompleted: ({ message }) => handleSaveCipherConfirmation(message), }; globalThis.addEventListener("load", load); function load() { setupWindowMessageListener(); - sendPlatformMessage({ command: "notificationRefreshFlagValue" }, (flagValue) => { - useComponentBar = flagValue; - applyNotificationBarStyle(); - }); -} - -function applyNotificationBarStyle() { - if (!useComponentBar) { - // eslint-disable-next-line @typescript-eslint/no-require-imports - require("./bar.scss"); - } postMessageToParent({ command: "initNotificationBar" }); } @@ -99,25 +79,6 @@ function getI18n() { }; } -/** - * Attempts to locate an element by ID within a template’s content and casts it to the specified type. - * - * @param templateElement - The template whose content will be searched for the element. - * @param elementId - The ID of the element being searched for. - * @returns The typed element if found, otherwise log error. - * - */ -const findElementById = ( - templateElement: HTMLTemplateElement, - elementId: string, -): ElementType => { - const element = templateElement.content.getElementById(elementId); - if (!element) { - throw new Error(`Element with ID "${elementId}" not found in template.`); - } - return element as ElementType; -}; - /** * Returns the localized header message for the notification bar based on the notification type. * @@ -204,25 +165,6 @@ export function getNotificationTestId( }[notificationType]; } -/** - * Sets the text content of an element identified by ID within a template's content. - * - * @param template - The template whose content will be searched for the element. - * @param elementId - The ID of the element whose text content is to be set. - * @param text - The text content to set for the specified element. - * @returns void - * - * This function attempts to locate an element by its ID within the content of a given HTML template. - * If the element is found, it updates the element's text content with the provided text. - * If the element is not found, the function does nothing, ensuring that the operation is safe and does not throw errors. - */ -function setElementText(template: HTMLTemplateElement, elementId: string, text: string): void { - const element = template.content.getElementById(elementId); - if (element) { - element.textContent = text; - } -} - async function initNotificationBar(message: NotificationBarWindowMessage) { const { initData } = message; if (!initData) { @@ -232,189 +174,119 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { notificationBarIframeInitData = initData; const { isVaultLocked, - removeIndividualVault: personalVaultDisallowed, // renamed to avoid local method collision + removeIndividualVault: personalVaultDisallowed, theme, } = notificationBarIframeInitData; const i18n = getI18n(); const resolvedTheme = getResolvedTheme(theme ?? ThemeTypes.Light); - if (useComponentBar) { - const resolvedType = resolveNotificationType(notificationBarIframeInitData); - const headerMessage = getNotificationHeaderMessage(i18n, resolvedType); - const notificationTestId = getNotificationTestId(resolvedType); - appendHeaderMessageToTitle(headerMessage); + const resolvedType = resolveNotificationType(notificationBarIframeInitData); + const headerMessage = getNotificationHeaderMessage(i18n, resolvedType); + const notificationTestId = getNotificationTestId(resolvedType); + appendHeaderMessageToTitle(headerMessage); - document.body.innerHTML = ""; - // 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.body.innerHTML = ""; - if (isVaultLocked) { - const notificationConfig = { + if (isVaultLocked) { + const notificationConfig = { + ...notificationBarIframeInitData, + headerMessage, + type: resolvedType, + notificationTestId, + theme: resolvedTheme, + personalVaultIsAllowed: !personalVaultDisallowed, + handleCloseNotification, + handleEditOrUpdateAction, + i18n, + }; + + const handleSaveAction = () => { + sendSaveCipherMessage(true); + + render( + NotificationContainer({ + ...notificationConfig, + handleSaveAction: () => {}, + isLoading: true, + }), + document.body, + ); + }; + + const UnlockNotification = NotificationContainer({ ...notificationConfig, handleSaveAction }); + + return render(UnlockNotification, document.body); + } + + // Handle AtRiskPasswordNotification render + if (notificationBarIframeInitData.type === NotificationTypes.AtRiskPassword) { + return render( + AtRiskNotification({ + ...notificationBarIframeInitData, + type: notificationBarIframeInitData.type as NotificationType, + theme: resolvedTheme, + i18n, + notificationTestId, + params: initData.params, + handleCloseNotification, + }), + document.body, + ); + } + + // Default scenario: add or update password + const orgId = selectedVaultSignal.get(); + + await Promise.all([ + new Promise((resolve) => sendPlatformMessage({ command: "bgGetOrgData" }, resolve)), + new Promise((resolve) => + sendPlatformMessage({ command: "bgGetFolderData" }, resolve), + ), + new Promise((resolve) => + sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, resolve), + ), + new Promise((resolve) => + sendPlatformMessage({ command: "bgGetCollectionData", orgId }, resolve), + ), + ]).then(([organizations, folders, ciphers, collections]) => { + notificationBarIframeInitData = { + ...notificationBarIframeInitData, + organizations, + folders, + ciphers, + collections, + }; + + // @TODO use context to avoid prop drilling + return render( + NotificationContainer({ ...notificationBarIframeInitData, headerMessage, type: resolvedType, - notificationTestId, theme: resolvedTheme, + notificationTestId, personalVaultIsAllowed: !personalVaultDisallowed, handleCloseNotification, + handleSaveAction, handleEditOrUpdateAction, i18n, - }; + }), + document.body, + ); + }); - const handleSaveAction = () => { - sendSaveCipherMessage(true); - - render( - NotificationContainer({ - ...notificationConfig, - handleSaveAction: () => {}, - isLoading: true, - }), - document.body, - ); - }; - - const UnlockNotification = NotificationContainer({ ...notificationConfig, handleSaveAction }); - - return render(UnlockNotification, document.body); - } - - // Handle AtRiskPasswordNotification render - if (notificationBarIframeInitData.type === NotificationTypes.AtRiskPassword) { - return render( - AtRiskNotification({ - ...notificationBarIframeInitData, - type: notificationBarIframeInitData.type as NotificationType, - theme: resolvedTheme, - i18n, - notificationTestId, - params: initData.params, - handleCloseNotification, - }), - document.body, - ); - } - - // Default scenario: add or update password - const orgId = selectedVaultSignal.get(); - - await Promise.all([ - new Promise((resolve) => - sendPlatformMessage({ command: "bgGetOrgData" }, resolve), - ), - new Promise((resolve) => - sendPlatformMessage({ command: "bgGetFolderData" }, resolve), - ), - new Promise((resolve) => - sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, resolve), - ), - new Promise((resolve) => - sendPlatformMessage({ command: "bgGetCollectionData", orgId }, resolve), - ), - ]).then(([organizations, folders, ciphers, collections]) => { - notificationBarIframeInitData = { - ...notificationBarIframeInitData, - organizations, - folders, - ciphers, - collections, - }; - - // @TODO use context to avoid prop drilling - return render( - NotificationContainer({ - ...notificationBarIframeInitData, - headerMessage, - type: resolvedType, - theme: resolvedTheme, - notificationTestId, - personalVaultIsAllowed: !personalVaultDisallowed, - handleCloseNotification, - handleSaveAction, - handleEditOrUpdateAction, - i18n, - }), - document.body, - ); - }); - } else { - setNotificationBarTheme(); - - (document.getElementById("logo") as HTMLImageElement).src = isVaultLocked - ? chrome.runtime.getURL("images/icon38_locked.png") - : chrome.runtime.getURL("images/icon38.png"); - - setupLogoLink(i18n.appName); - - // i18n for "Add" template - const addTemplate = document.getElementById("template-add") as HTMLTemplateElement; - - const neverButton = findElementById(addTemplate, "never-save"); - neverButton.textContent = i18n.never; - - const selectFolder = findElementById(addTemplate, "select-folder"); - selectFolder.hidden = isVaultLocked || removeIndividualVault(); - selectFolder.setAttribute("aria-label", i18n.folder); - - const addButton = findElementById(addTemplate, "add-save"); - addButton.textContent = i18n.notificationAddSave; - - const addEditButton = findElementById(addTemplate, "add-edit"); - // If Remove Individual Vault policy applies, "Add" opens the edit tab, so we hide the Edit button - addEditButton.hidden = removeIndividualVault(); - addEditButton.textContent = i18n.notificationEdit; - - setElementText(addTemplate, "add-text", i18n.notificationAddDesc); - - // i18n for "Change" (update password) template - const changeTemplate = document.getElementById("template-change") as HTMLTemplateElement; - - const changeButton = findElementById(changeTemplate, "change-save"); - changeButton.textContent = i18n.notificationUpdate; - - const changeEditButton = findElementById(changeTemplate, "change-edit"); - changeEditButton.textContent = i18n.notificationEdit; - - setElementText(changeTemplate, "change-text", i18n.notificationChangeDesc); - - // i18n for "Unlock" (unlock extension) template - const unlockTemplate = document.getElementById("template-unlock") as HTMLTemplateElement; - - const unlockButton = findElementById(unlockTemplate, "unlock-vault"); - unlockButton.textContent = i18n.notificationUnlock; - - setElementText(unlockTemplate, "unlock-text", i18n.notificationUnlockDesc); - - // i18n for body content - const closeButton = document.getElementById("close-button"); - if (closeButton) { - closeButton.title = i18n.close; - } - - const notificationType = initData.type; - if (notificationType === "add") { - handleTypeAdd(); - } else if (notificationType === "change") { - handleTypeChange(); - } else if (notificationType === "unlock") { - handleTypeUnlock(); - } - - closeButton?.addEventListener("click", handleCloseNotification); - - globalThis.addEventListener("resize", adjustHeight); - adjustHeight(); - } function handleEditOrUpdateAction(e: Event) { const notificationType = initData?.type; e.preventDefault(); notificationType === "add" ? sendSaveCipherMessage(true) : sendSaveCipherMessage(false); } } + function handleCloseNotification(e: Event) { e.preventDefault(); sendPlatformMessage({ command: "bgCloseNotificationBar", + fadeOutNotification: true, }); } @@ -439,57 +311,6 @@ function handleSaveAction(e: Event) { } } -function handleTypeAdd() { - setContent(document.getElementById("template-add") as HTMLTemplateElement); - - const addButton = document.getElementById("add-save"); - addButton?.addEventListener("click", (e) => { - e.preventDefault(); - - // If Remove Individual Vault policy applies, "Add" opens the edit tab - sendSaveCipherMessage(removeIndividualVault(), getSelectedFolder()); - }); - - if (removeIndividualVault()) { - // Everything past this point is only required if user has an individual vault - return; - } - - const editButton = document.getElementById("add-edit"); - editButton?.addEventListener("click", (e) => { - e.preventDefault(); - - sendSaveCipherMessage(true, getSelectedFolder()); - }); - - const neverButton = document.getElementById("never-save"); - neverButton?.addEventListener("click", (e) => { - e.preventDefault(); - sendPlatformMessage({ - command: "bgNeverSave", - }); - }); - - loadFolderSelector(); -} - -function handleTypeChange() { - setContent(document.getElementById("template-change") as HTMLTemplateElement); - const changeButton = document.getElementById("change-save"); - changeButton?.addEventListener("click", (e) => { - e.preventDefault(); - - sendSaveCipherMessage(false); - }); - - const editButton = document.getElementById("change-edit"); - editButton?.addEventListener("click", (e) => { - e.preventDefault(); - - sendSaveCipherMessage(true); - }); -} - function sendSaveCipherMessage(edit: boolean, folder?: string) { sendPlatformMessage({ command: "bgSaveCipher", @@ -498,36 +319,6 @@ function sendSaveCipherMessage(edit: boolean, folder?: string) { }); } -function handleSaveCipherAttemptCompletedMessage(message: NotificationBarWindowMessage) { - const addSaveButtonContainers = document.querySelectorAll(".add-change-cipher-buttons"); - const notificationBarOuterWrapper = document.getElementById("notification-bar-outer-wrapper"); - if (message?.error) { - addSaveButtonContainers.forEach((element) => { - element.textContent = chrome.i18n.getMessage("saveCipherAttemptFailed"); - element.classList.add("error-message"); - notificationBarOuterWrapper?.classList.add("error-event"); - }); - - adjustHeight(); - logService.error(`Error encountered when saving credentials: ${message.error}`); - return; - } - const messageName = - notificationBarIframeInitData.type === "add" ? "passwordSaved" : "passwordUpdated"; - - addSaveButtonContainers.forEach((element) => { - element.textContent = chrome.i18n.getMessage(messageName); - element.prepend(buildSvgDomElement(circleCheckIcon)); - element.classList.add("success-message"); - notificationBarOuterWrapper?.classList.add("success-event"); - }); - adjustHeight(); - globalThis.setTimeout( - () => sendPlatformMessage({ command: "bgCloseNotificationBar", fadeOutNotification: true }), - 3000, - ); -} - function openAddEditVaultItemPopout( e: Event, options: { @@ -583,27 +374,6 @@ function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) { ); } -function handleTypeUnlock() { - setContent(document.getElementById("template-unlock") as HTMLTemplateElement); - - const unlockButton = document.getElementById("unlock-vault"); - unlockButton?.addEventListener("click", (e) => { - sendPlatformMessage({ - command: "bgReopenUnlockPopout", - }); - }); -} - -function setContent(template: HTMLTemplateElement) { - const content = document.getElementById("content"); - while (content?.firstChild) { - content?.removeChild(content.firstChild); - } - - const newElement = template.content.cloneNode(true) as HTMLElement; - content?.appendChild(newElement); -} - function sendPlatformMessage( msg: Record, responseCallback?: (response: any) => void, @@ -615,51 +385,10 @@ function sendPlatformMessage( }); } -function loadFolderSelector() { - const populateFolderData = (folderData: FolderView[]) => { - const select = document.getElementById("select-folder"); - if (!select) { - return; - } - - if (!folderData?.length) { - select.appendChild(new Option(chrome.i18n.getMessage("noFoldersFound"), undefined, true)); - select.setAttribute("disabled", "true"); - return; - } - - select.appendChild(new Option(chrome.i18n.getMessage("selectFolder"), undefined, true)); - folderData.forEach((folder: FolderView) => { - // Select "No Folder" (id=null) folder by default - select.appendChild(new Option(folder.name, folder.id || "", false)); - }); - }; - - sendPlatformMessage({ command: "bgGetFolderData" }, populateFolderData); -} - -function getSelectedFolder(): string { - return (document.getElementById("select-folder") as HTMLSelectElement).value; -} - function removeIndividualVault(): boolean { return Boolean(notificationBarIframeInitData?.removeIndividualVault); } -function adjustHeight() { - const body = document.querySelector("body"); - if (!body) { - return; - } - const data: AdjustNotificationBarMessageData = { - height: body.scrollHeight, - }; - sendPlatformMessage({ - command: "bgAdjustNotificationBar", - data, - }); -} - function setupWindowMessageListener() { globalThis.addEventListener("message", handleWindowMessage); } @@ -682,18 +411,6 @@ function handleWindowMessage(event: MessageEvent) { handler({ message }); } -function setupLogoLink(linkText: string) { - const logoLink = document.getElementById("logo-link") as HTMLAnchorElement; - logoLink.title = linkText; - const setWebVaultUrlLink = (webVaultURL: string) => { - const newVaultURL = webVaultURL && decodeURIComponent(webVaultURL); - if (newVaultURL && newVaultURL !== logoLink.href) { - logoLink.href = newVaultURL; - } - }; - sendPlatformMessage({ command: "getWebVaultUrlForNotification" }, setWebVaultUrlLink); -} - function getTheme(globalThis: any, theme: NotificationBarIframeInitData["theme"]) { if (theme === ThemeTypes.System) { return globalThis.matchMedia("(prefers-color-scheme: dark)").matches @@ -712,12 +429,6 @@ function getResolvedTheme(theme: Theme) { return resolvedTheme; } -function setNotificationBarTheme() { - const theme = getTheme(globalThis, notificationBarIframeInitData.theme); - - document.documentElement.classList.add(`theme_${theme}`); -} - function postMessageToParent(message: NotificationBarWindowMessage) { globalThis.parent.postMessage(message, windowMessageOrigin || "*"); } diff --git a/apps/browser/src/autofill/overlay/notifications/content/__snapshots__/overlay-notifications-content.service.spec.ts.snap b/apps/browser/src/autofill/overlay/notifications/content/__snapshots__/overlay-notifications-content.service.spec.ts.snap index 7bdde2560d0..e5bafe34b5f 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/__snapshots__/overlay-notifications-content.service.spec.ts.snap +++ b/apps/browser/src/autofill/overlay/notifications/content/__snapshots__/overlay-notifications-content.service.spec.ts.snap @@ -3,7 +3,7 @@ exports[`OverlayNotificationsContentService opening the notification bar creates the notification bar elements and appends them to the body 1`] = `