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:
@@ -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 () => {
|
||||
|
||||
@@ -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", {});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -2107,7 +2107,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
"addToLockedVaultPendingNotifications",
|
||||
retryMessage,
|
||||
);
|
||||
await this.openUnlockPopout(sender.tab, true);
|
||||
await this.openUnlockPopout(sender.tab);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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 = <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,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<OrgView[]>((resolve) => sendPlatformMessage({ command: "bgGetOrgData" }, resolve)),
|
||||
new Promise<FolderView[]>((resolve) =>
|
||||
sendPlatformMessage({ command: "bgGetFolderData" }, resolve),
|
||||
),
|
||||
new Promise<NotificationCipherData[]>((resolve) =>
|
||||
sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, resolve),
|
||||
),
|
||||
new Promise<CollectionView[]>((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<OrgView[]>((resolve) =>
|
||||
sendPlatformMessage({ command: "bgGetOrgData" }, resolve),
|
||||
),
|
||||
new Promise<FolderView[]>((resolve) =>
|
||||
sendPlatformMessage({ command: "bgGetFolderData" }, resolve),
|
||||
),
|
||||
new Promise<NotificationCipherData[]>((resolve) =>
|
||||
sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, resolve),
|
||||
),
|
||||
new Promise<CollectionView[]>((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<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 || "*");
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,33 +20,25 @@ 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> = {
|
||||
height: "400px",
|
||||
width: "430px",
|
||||
maxWidth: "calc(100% - 20px)",
|
||||
minHeight: "initial",
|
||||
top: "10px",
|
||||
right: "0px",
|
||||
padding: "0",
|
||||
position: "fixed",
|
||||
zIndex: "2147483647",
|
||||
visibility: "visible",
|
||||
borderRadius: "4px",
|
||||
border: "none",
|
||||
backgroundColor: "transparent",
|
||||
overflow: "hidden",
|
||||
transition: "box-shadow 0.15s ease",
|
||||
transitionDelay: "0.15s",
|
||||
};
|
||||
private notificationBarContainerStyles: Partial<CSSStyleDeclaration> = {
|
||||
height: "400px",
|
||||
width: "430px",
|
||||
maxWidth: "calc(100% - 20px)",
|
||||
minHeight: "initial",
|
||||
top: "10px",
|
||||
right: "0px",
|
||||
padding: "0",
|
||||
position: "fixed",
|
||||
zIndex: "2147483647",
|
||||
visibility: "visible",
|
||||
borderRadius: "4px",
|
||||
border: "none",
|
||||
backgroundColor: "transparent",
|
||||
overflow: "hidden",
|
||||
transition: "box-shadow 0.15s ease",
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user