1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 13:53:34 +00:00

PM-25075 [Remove - Step 1] Flagged logic from clients/server and clients notification-refresh feature flag (#16113)

* PM-25075 wip parking work

* remove flag from enums and add fade out

* fix tests

* remove flags from enum file after merge conflict re introduced

* remove dead code paths

* change naming back to bgUnlockPopoutOpened
This commit is contained in:
Daniel Riera
2025-09-15 14:55:36 -04:00
committed by GitHub
parent 4449cf45d8
commit 7ce8d06315
13 changed files with 159 additions and 616 deletions

View File

@@ -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 () => {

View File

@@ -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", {});
}
/**

View File

@@ -141,7 +141,6 @@ type NotificationBackgroundExtensionMessageHandlers = {
sender,
}: BackgroundOnMessageHandlerParams) => Promise<void>;
bgNeverSave: ({ sender }: BackgroundSenderParam) => Promise<void>;
bgUnlockPopoutOpened: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise<void>;
bgReopenUnlockPopout: ({ sender }: BackgroundSenderParam) => Promise<void>;
checkNotificationQueue: ({ sender }: BackgroundSenderParam) => Promise<void>;
collectPageDetailsResponse: ({ message }: BackgroundMessageParam) => Promise<void>;

View File

@@ -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<chrome.runtime.MessageSender>({ 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<chrome.runtime.MessageSender>({ 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<chrome.runtime.MessageSender>({ tab: tabMock });
const message: NotificationBackgroundExtensionMessage = {
command: "bgUnlockPopoutOpened",
};
activeAccountStatusMock$.next(AuthenticationStatus.Locked);
notificationBackground["notificationQueue"] = [mock<AddLoginQueueMessage>()];
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<chrome.runtime.MessageSender>({ 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;

View File

@@ -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<boolean> indicating if the feature is enabled
*/
async getNotificationFlag(): Promise<boolean> {
const flagValue = await this.configService.getFeatureFlag(FeatureFlag.NotificationRefresh);
return flagValue;
}
/**
* Gets the current authentication status of the user.
* @returns Promise<AuthenticationStatus> - The current authentication status of the user.
@@ -465,11 +452,6 @@ export default class NotificationBackground {
data: ModifyLoginCipherFormData,
tab: chrome.tabs.Tab,
): Promise<boolean> {
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;

View File

@@ -2107,7 +2107,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
"addToLockedVaultPendingNotifications",
retryMessage,
);
await this.openUnlockPopout(sender.tab, true);
await this.openUnlockPopout(sender.tab);
}
/**

View File

@@ -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);
});
});

View File

@@ -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 templates 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 = <ElementType extends HTMLElement>(
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,21 +174,18 @@ 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);
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());
if (isVaultLocked) {
const notificationConfig = {
@@ -299,9 +238,7 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
const orgId = selectedVaultSignal.get();
await Promise.all([
new Promise<OrgView[]>((resolve) =>
sendPlatformMessage({ command: "bgGetOrgData" }, resolve),
),
new Promise<OrgView[]>((resolve) => sendPlatformMessage({ command: "bgGetOrgData" }, resolve)),
new Promise<FolderView[]>((resolve) =>
sendPlatformMessage({ command: "bgGetFolderData" }, resolve),
),
@@ -337,84 +274,19 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
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<HTMLButtonElement>(addTemplate, "never-save");
neverButton.textContent = i18n.never;
const selectFolder = findElementById<HTMLSelectElement>(addTemplate, "select-folder");
selectFolder.hidden = isVaultLocked || removeIndividualVault();
selectFolder.setAttribute("aria-label", i18n.folder);
const addButton = findElementById<HTMLButtonElement>(addTemplate, "add-save");
addButton.textContent = i18n.notificationAddSave;
const addEditButton = findElementById<HTMLButtonElement>(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<HTMLSelectElement>(changeTemplate, "change-save");
changeButton.textContent = i18n.notificationUpdate;
const changeEditButton = findElementById<HTMLButtonElement>(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<HTMLButtonElement>(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<string, unknown>,
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 || "*");
}

View File

@@ -3,7 +3,7 @@
exports[`OverlayNotificationsContentService opening the notification bar creates the notification bar elements and appends them to the body 1`] = `
<div
id="bit-notification-bar"
style="height: 82px !important; width: 430px !important; max-width: calc(100% - 20px) !important; min-height: initial !important; top: 10px !important; right: 10px !important; padding: 0px !important; position: fixed !important; z-index: 2147483647 !important; visibility: visible !important; border-radius: 4px !important; background-color: transparent !important; overflow: hidden !important; transition: box-shadow 0.15s ease !important; transition-delay: 0.15s;"
style="height: 400px !important; width: 430px !important; max-width: calc(100% - 20px) !important; min-height: initial !important; top: 10px !important; right: 0px !important; padding: 0px !important; position: fixed !important; z-index: 2147483647 !important; visibility: visible !important; border-radius: 4px !important; background-color: transparent !important; overflow: hidden !important; transition: box-shadow 0.15s ease !important; transition-delay: 0.15s;"
>
<iframe
id="bit-notification-bar-iframe"

View File

@@ -1,11 +1,11 @@
import { mock, MockProxy } from "jest-mock-extended";
import AutofillInit from "../../../content/autofill-init";
import { NotificationType } from "../../../enums/notification-type.enum";
import { DomQueryService } from "../../../services/abstractions/dom-query.service";
import DomElementVisibilityService from "../../../services/dom-element-visibility.service";
import { flushPromises, sendMockExtensionMessage } from "../../../spec/testing-utils";
import * as utils from "../../../utils";
import { sendExtensionMessage } from "../../../utils";
import { NotificationTypeData } from "../abstractions/overlay-notifications-content.service";
import { OverlayNotificationsContentService } from "./overlay-notifications-content.service";
@@ -19,11 +19,7 @@ describe("OverlayNotificationsContentService", () => {
beforeEach(() => {
jest.useFakeTimers();
jest
.spyOn(utils, "sendExtensionMessage")
.mockImplementation((command: string) =>
Promise.resolve(command === "notificationRefreshFlagValue" ? false : true),
);
jest.spyOn(utils, "sendExtensionMessage").mockImplementation(jest.fn());
domQueryService = mock<DomQueryService>();
domElementVisibilityService = new DomElementVisibilityService();
overlayNotificationsContentService = new OverlayNotificationsContentService();
@@ -51,37 +47,6 @@ describe("OverlayNotificationsContentService", () => {
expect(bodyAppendChildSpy).not.toHaveBeenCalled();
});
it("applies correct styles when notificationRefreshFlag is true", async () => {
(sendExtensionMessage as jest.Mock).mockResolvedValue(true);
sendMockExtensionMessage({
command: "openNotificationBar",
data: {
type: "change",
typeData: mock<NotificationTypeData>(),
},
});
await flushPromises();
const barElement = overlayNotificationsContentService["notificationBarElement"]!;
expect(barElement.style.height).toBe("400px");
expect(barElement.style.right).toBe("0px");
});
it("applies correct styles when notificationRefreshFlag is false", async () => {
sendMockExtensionMessage({
command: "openNotificationBar",
data: {
type: "change",
typeData: mock<NotificationTypeData>(),
},
});
await flushPromises();
const barElement = overlayNotificationsContentService["notificationBarElement"]!;
expect(barElement.style.height).toBe("82px");
expect(barElement.style.right).toBe("10px");
});
it("closes the notification bar if the notification bar type has changed", async () => {
overlayNotificationsContentService["currentNotificationBarType"] = "add";
const closeNotificationBarSpy = jest.spyOn(
@@ -92,7 +57,7 @@ describe("OverlayNotificationsContentService", () => {
sendMockExtensionMessage({
command: "openNotificationBar",
data: {
type: "change",
type: NotificationType.ChangePassword,
typeData: mock<NotificationTypeData>(),
},
});
@@ -105,7 +70,7 @@ describe("OverlayNotificationsContentService", () => {
sendMockExtensionMessage({
command: "openNotificationBar",
data: {
type: "change",
type: NotificationType.ChangePassword,
typeData: mock<NotificationTypeData>(),
},
});
@@ -118,7 +83,7 @@ describe("OverlayNotificationsContentService", () => {
sendMockExtensionMessage({
command: "openNotificationBar",
data: {
type: "change",
type: NotificationType.ChangePassword,
typeData: mock<NotificationTypeData>({
launchTimestamp: Date.now(),
}),
@@ -135,7 +100,7 @@ describe("OverlayNotificationsContentService", () => {
sendMockExtensionMessage({
command: "openNotificationBar",
data: {
type: "change",
type: NotificationType.ChangePassword,
typeData: mock<NotificationTypeData>(),
},
});
@@ -154,7 +119,7 @@ describe("OverlayNotificationsContentService", () => {
sendMockExtensionMessage({
command: "openNotificationBar",
data: {
type: "change",
type: NotificationType.ChangePassword,
typeData: mock<NotificationTypeData>(),
},
});
@@ -211,8 +176,21 @@ describe("OverlayNotificationsContentService", () => {
).toBe("0");
jest.advanceTimersByTime(150);
});
expect(sendExtensionMessage).toHaveBeenCalledWith("bgRemoveTabFromNotificationQueue");
it("triggers a fadeout of the notification bar and removes from the notification queue", () => {
sendMockExtensionMessage({
command: "closeNotificationBar",
data: { fadeOutNotification: true, type: NotificationType.ChangePassword },
});
expect(
overlayNotificationsContentService["notificationBarIframeElement"]?.style.opacity,
).toBe("0");
jest.advanceTimersByTime(150);
expect(utils.sendExtensionMessage).toHaveBeenCalledWith("bgRemoveTabFromNotificationQueue");
});
it("closes the notification bar without a fadeout", () => {
@@ -232,7 +210,7 @@ describe("OverlayNotificationsContentService", () => {
sendMockExtensionMessage({
command: "openNotificationBar",
data: {
type: "change",
type: NotificationType.ChangePassword,
typeData: mock<NotificationTypeData>(),
},
});
@@ -256,7 +234,7 @@ describe("OverlayNotificationsContentService", () => {
sendMockExtensionMessage({
command: "openNotificationBar",
data: {
type: "change",
type: NotificationType.ChangePassword,
typeData: mock<NotificationTypeData>(),
},
});
@@ -286,7 +264,7 @@ describe("OverlayNotificationsContentService", () => {
sendMockExtensionMessage({
command: "openNotificationBar",
data: {
type: "change",
type: NotificationType.ChangePassword,
typeData: mock<NotificationTypeData>(),
},
});

View File

@@ -20,9 +20,7 @@ export class OverlayNotificationsContentService
private notificationBarElement: HTMLElement | null = null;
private notificationBarIframeElement: HTMLIFrameElement | null = null;
private currentNotificationBarType: NotificationType | null = null;
private notificationRefreshFlag: boolean = false;
private getNotificationBarStyles(): Partial<CSSStyleDeclaration> {
const styles: Partial<CSSStyleDeclaration> = {
private notificationBarContainerStyles: Partial<CSSStyleDeclaration> = {
height: "400px",
width: "430px",
maxWidth: "calc(100% - 20px)",
@@ -41,12 +39,6 @@ export class OverlayNotificationsContentService
transitionDelay: "0.15s",
};
if (!this.notificationRefreshFlag) {
styles.height = "82px";
styles.right = "10px";
}
return styles;
}
private notificationBarIframeElementStyles: Partial<CSSStyleDeclaration> = {
width: "100%",
height: "100%",
@@ -57,6 +49,7 @@ export class OverlayNotificationsContentService
borderRadius: "4px",
colorScheme: "auto",
};
private readonly extensionMessageHandlers: OverlayNotificationsExtensionMessageHandlers = {
openNotificationBar: ({ message }) => this.handleOpenNotificationBarMessage(message),
closeNotificationBar: ({ message }) => this.handleCloseNotificationBarMessage(message),
@@ -91,6 +84,7 @@ export class OverlayNotificationsContentService
if (this.currentNotificationBarType && type !== this.currentNotificationBarType) {
this.closeNotificationBar();
}
const initData = {
type: type as NotificationType,
isVaultLocked: typeData.isVaultLocked,
@@ -101,10 +95,6 @@ export class OverlayNotificationsContentService
params,
};
await sendExtensionMessage("notificationRefreshFlagValue").then((notificationRefreshFlag) => {
this.notificationRefreshFlag = !!notificationRefreshFlag;
});
if (globalThis.document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => this.openNotificationBar(initData));
return;
@@ -215,14 +205,8 @@ export class OverlayNotificationsContentService
{ transform: "translateX(0)", opacity: "1" },
true,
);
if (!this.notificationRefreshFlag) {
setElementStyles(
this.notificationBarElement,
{ boxShadow: "2px 4px 6px 0px #0000001A" },
true,
);
}
this.notificationBarIframeElement.removeEventListener(
this.notificationBarIframeElement?.removeEventListener(
EVENTS.LOAD,
this.handleNotificationBarIframeOnLoad,
);
@@ -236,7 +220,7 @@ export class OverlayNotificationsContentService
this.notificationBarElement = globalThis.document.createElement("div");
this.notificationBarElement.id = "bit-notification-bar";
setElementStyles(this.notificationBarElement, this.getNotificationBarStyles(), true);
setElementStyles(this.notificationBarElement, this.notificationBarContainerStyles, true);
this.notificationBarElement.appendChild(this.notificationBarIframeElement);
}

View File

@@ -83,8 +83,8 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
unsetMostRecentlyFocusedField: () => this.unsetMostRecentlyFocusedField(),
checkIsMostRecentlyFocusedFieldWithinViewport: () =>
this.checkIsMostRecentlyFocusedFieldWithinViewport(),
bgUnlockPopoutOpened: () => this.blurMostRecentlyFocusedField(true),
bgVaultItemRepromptPopoutOpened: () => this.blurMostRecentlyFocusedField(true),
bgUnlockPopoutOpened: () => this.blurMostRecentlyFocusedField(true),
redirectAutofillInlineMenuFocusOut: ({ message }) =>
this.redirectInlineMenuFocusOut(message?.data?.direction),
getSubFrameOffsets: ({ message }) => this.getSubFrameOffsets(message),

View File

@@ -18,7 +18,6 @@ export enum FeatureFlag {
PM14938_BrowserExtensionLoginApproval = "pm-14938-browser-extension-login-approvals",
/* Autofill */
NotificationRefresh = "notification-refresh",
MacOsNativeCredentialSync = "macos-native-credential-sync",
WindowsDesktopAutotype = "windows-desktop-autotype",
@@ -75,7 +74,6 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.CollectionVaultRefactor]: FALSE,
/* Autofill */
[FeatureFlag.NotificationRefresh]: FALSE,
[FeatureFlag.MacOsNativeCredentialSync]: FALSE,
[FeatureFlag.WindowsDesktopAutotype]: FALSE,