mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +00:00
Pm 18493 pass relevant cipher name into confirmation UI (#13570)
* PM-18276-wip * update typing * dynamically retrieve messages, resolve theme in function * five second timeout after save or update * adjust timeout to five seconds * negligible performance gain-revert * sacrifice contorl for to remove event listeners-revert * PM-18493 initial wip commit * fix types and story * edit tests to account for sendmessagewithdata * add tests and return id on new add/save * function name
This commit is contained in:
@@ -1070,6 +1070,24 @@
|
|||||||
"notificationAddSave": {
|
"notificationAddSave": {
|
||||||
"message": "Save"
|
"message": "Save"
|
||||||
},
|
},
|
||||||
|
"loginSaveSuccessDetails": {
|
||||||
|
"message": "$USERNAME$ saved to Bitwarden.",
|
||||||
|
"placeholders": {
|
||||||
|
"username": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Shown to user after login is saved."
|
||||||
|
},
|
||||||
|
"loginUpdatedSuccessDetails": {
|
||||||
|
"message": "$USERNAME$ updated in Bitwarden.",
|
||||||
|
"placeholders": {
|
||||||
|
"username": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Shown to user after login is updated."
|
||||||
|
},
|
||||||
"enableChangedPasswordNotification": {
|
"enableChangedPasswordNotification": {
|
||||||
"message": "Ask to update existing login"
|
"message": "Ask to update existing login"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ type NotificationBackgroundExtensionMessageHandlers = {
|
|||||||
bgChangedPassword: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise<void>;
|
bgChangedPassword: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise<void>;
|
||||||
bgRemoveTabFromNotificationQueue: ({ sender }: BackgroundSenderParam) => void;
|
bgRemoveTabFromNotificationQueue: ({ sender }: BackgroundSenderParam) => void;
|
||||||
bgSaveCipher: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
bgSaveCipher: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||||
|
bgOpenVault: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise<void>;
|
||||||
bgNeverSave: ({ sender }: BackgroundSenderParam) => Promise<void>;
|
bgNeverSave: ({ sender }: BackgroundSenderParam) => Promise<void>;
|
||||||
bgUnlockPopoutOpened: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise<void>;
|
bgUnlockPopoutOpened: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise<void>;
|
||||||
bgReopenUnlockPopout: ({ sender }: BackgroundSenderParam) => Promise<void>;
|
bgReopenUnlockPopout: ({ sender }: BackgroundSenderParam) => Promise<void>;
|
||||||
|
|||||||
@@ -812,7 +812,10 @@ describe("NotificationBackground", () => {
|
|||||||
newPassword: "newPassword",
|
newPassword: "newPassword",
|
||||||
});
|
});
|
||||||
notificationBackground["notificationQueue"] = [queueMessage];
|
notificationBackground["notificationQueue"] = [queueMessage];
|
||||||
const cipherView = mock<CipherView>();
|
const cipherView = mock<CipherView>({
|
||||||
|
id: "testId",
|
||||||
|
login: { username: "testUser" },
|
||||||
|
});
|
||||||
getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView);
|
getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView);
|
||||||
|
|
||||||
sendMockExtensionMessage(message, sender);
|
sendMockExtensionMessage(message, sender);
|
||||||
@@ -828,9 +831,14 @@ describe("NotificationBackground", () => {
|
|||||||
"testId",
|
"testId",
|
||||||
);
|
);
|
||||||
expect(updateWithServerSpy).toHaveBeenCalled();
|
expect(updateWithServerSpy).toHaveBeenCalled();
|
||||||
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
expect(tabSendMessageDataSpy).toHaveBeenCalledWith(
|
||||||
command: "saveCipherAttemptCompleted",
|
sender.tab,
|
||||||
});
|
"saveCipherAttemptCompleted",
|
||||||
|
{
|
||||||
|
username: cipherView.login.username,
|
||||||
|
cipherId: cipherView.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates the cipher password if the queue message was locked and an existing cipher has the same username as the message", async () => {
|
it("updates the cipher password if the queue message was locked and an existing cipher has the same username as the message", async () => {
|
||||||
@@ -976,11 +984,16 @@ describe("NotificationBackground", () => {
|
|||||||
});
|
});
|
||||||
notificationBackground["notificationQueue"] = [queueMessage];
|
notificationBackground["notificationQueue"] = [queueMessage];
|
||||||
const cipherView = mock<CipherView>({
|
const cipherView = mock<CipherView>({
|
||||||
|
id: "testId",
|
||||||
login: { username: "test", password: "password" },
|
login: { username: "test", password: "password" },
|
||||||
});
|
});
|
||||||
folderExistsSpy.mockResolvedValueOnce(false);
|
folderExistsSpy.mockResolvedValueOnce(false);
|
||||||
convertAddLoginQueueMessageToCipherViewSpy.mockReturnValueOnce(cipherView);
|
convertAddLoginQueueMessageToCipherViewSpy.mockReturnValueOnce(cipherView);
|
||||||
editItemSpy.mockResolvedValueOnce(undefined);
|
editItemSpy.mockResolvedValueOnce(undefined);
|
||||||
|
cipherEncryptSpy.mockResolvedValueOnce({
|
||||||
|
...cipherView,
|
||||||
|
id: "testId",
|
||||||
|
});
|
||||||
|
|
||||||
sendMockExtensionMessage(message, sender);
|
sendMockExtensionMessage(message, sender);
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
@@ -991,9 +1004,14 @@ describe("NotificationBackground", () => {
|
|||||||
);
|
);
|
||||||
expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView, "testId");
|
expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView, "testId");
|
||||||
expect(createWithServerSpy).toHaveBeenCalled();
|
expect(createWithServerSpy).toHaveBeenCalled();
|
||||||
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
expect(tabSendMessageDataSpy).toHaveBeenCalledWith(
|
||||||
command: "saveCipherAttemptCompleted",
|
sender.tab,
|
||||||
});
|
"saveCipherAttemptCompleted",
|
||||||
|
{
|
||||||
|
username: cipherView.login.username,
|
||||||
|
cipherId: cipherView.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { command: "addedCipher" });
|
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { command: "addedCipher" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export default class NotificationBackground {
|
|||||||
getWebVaultUrlForNotification: () => this.getWebVaultUrl(),
|
getWebVaultUrlForNotification: () => this.getWebVaultUrl(),
|
||||||
notificationRefreshFlagValue: () => this.getNotificationFlag(),
|
notificationRefreshFlagValue: () => this.getNotificationFlag(),
|
||||||
bgGetDecryptedCiphers: () => this.getNotificationCipherData(),
|
bgGetDecryptedCiphers: () => this.getNotificationCipherData(),
|
||||||
|
bgOpenVault: ({ message, sender }) => this.openVault(message, sender.tab),
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -594,7 +595,10 @@ export default class NotificationBackground {
|
|||||||
const cipher = await this.cipherService.encrypt(newCipher, activeUserId);
|
const cipher = await this.cipherService.encrypt(newCipher, activeUserId);
|
||||||
try {
|
try {
|
||||||
await this.cipherService.createWithServer(cipher);
|
await this.cipherService.createWithServer(cipher);
|
||||||
await BrowserApi.tabSendMessage(tab, { command: "saveCipherAttemptCompleted" });
|
await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", {
|
||||||
|
username: String(queueMessage?.username),
|
||||||
|
cipherId: String(cipher?.id),
|
||||||
|
});
|
||||||
await BrowserApi.tabSendMessage(tab, { command: "addedCipher" });
|
await BrowserApi.tabSendMessage(tab, { command: "addedCipher" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", {
|
await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", {
|
||||||
@@ -630,15 +634,16 @@ export default class NotificationBackground {
|
|||||||
await BrowserApi.tabSendMessage(tab, { command: "editedCipher" });
|
await BrowserApi.tabSendMessage(tab, { command: "editedCipher" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cipher = await this.cipherService.encrypt(cipherView, userId);
|
const cipher = await this.cipherService.encrypt(cipherView, userId);
|
||||||
try {
|
try {
|
||||||
// We've only updated the password, no need to broadcast editedCipher message
|
|
||||||
await this.cipherService.updateWithServer(cipher);
|
await this.cipherService.updateWithServer(cipher);
|
||||||
await BrowserApi.tabSendMessage(tab, { command: "saveCipherAttemptCompleted" });
|
await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", {
|
||||||
|
username: String(cipherView?.login?.username),
|
||||||
|
cipherId: String(cipherView?.id),
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", {
|
await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", {
|
||||||
error: String(error.message),
|
error: String(error?.message),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -663,6 +668,16 @@ export default class NotificationBackground {
|
|||||||
await this.openAddEditVaultItemPopout(senderTab, { cipherId: cipherView.id });
|
await this.openAddEditVaultItemPopout(senderTab, { cipherId: cipherView.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async openVault(
|
||||||
|
message: NotificationBackgroundExtensionMessage,
|
||||||
|
senderTab: chrome.tabs.Tab,
|
||||||
|
) {
|
||||||
|
if (!message.cipherId) {
|
||||||
|
await this.openAddEditVaultItemPopout(senderTab);
|
||||||
|
}
|
||||||
|
await this.openAddEditVaultItemPopout(senderTab, { cipherId: message.cipherId });
|
||||||
|
}
|
||||||
|
|
||||||
private async folderExists(folderId: string, userId: UserId) {
|
private async folderExists(folderId: string, userId: UserId) {
|
||||||
if (Utils.isNullOrWhitespace(folderId) || folderId === "null") {
|
if (Utils.isNullOrWhitespace(folderId) || folderId === "null") {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { NotificationConfirmationBody } from "../../notification/confirmation";
|
|||||||
type Args = {
|
type Args = {
|
||||||
buttonText: string;
|
buttonText: string;
|
||||||
confirmationMessage: string;
|
confirmationMessage: string;
|
||||||
handleClick: () => void;
|
handleOpenVault: () => void;
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
error: string;
|
error: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,18 +19,22 @@ import {
|
|||||||
export function NotificationConfirmationContainer({
|
export function NotificationConfirmationContainer({
|
||||||
error,
|
error,
|
||||||
handleCloseNotification,
|
handleCloseNotification,
|
||||||
|
handleOpenVault,
|
||||||
i18n,
|
i18n,
|
||||||
theme = ThemeTypes.Light,
|
theme = ThemeTypes.Light,
|
||||||
type,
|
type,
|
||||||
|
username,
|
||||||
}: NotificationBarIframeInitData & {
|
}: NotificationBarIframeInitData & {
|
||||||
handleCloseNotification: (e: Event) => void;
|
handleCloseNotification: (e: Event) => void;
|
||||||
|
handleOpenVault: (e: Event) => void;
|
||||||
} & {
|
} & {
|
||||||
error: string;
|
error: string;
|
||||||
i18n: { [key: string]: string };
|
i18n: { [key: string]: string };
|
||||||
type: NotificationType;
|
type: NotificationType;
|
||||||
|
username: string;
|
||||||
}) {
|
}) {
|
||||||
const headerMessage = getHeaderMessage(i18n, type, error);
|
const headerMessage = getHeaderMessage(i18n, type, error);
|
||||||
const confirmationMessage = getConfirmationMessage(i18n, type, error);
|
const confirmationMessage = getConfirmationMessage(i18n, username, type, error);
|
||||||
const buttonText = error ? i18n.newItem : i18n.view;
|
const buttonText = error ? i18n.newItem : i18n.view;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@@ -41,9 +45,10 @@ export function NotificationConfirmationContainer({
|
|||||||
theme,
|
theme,
|
||||||
})}
|
})}
|
||||||
${NotificationConfirmationBody({
|
${NotificationConfirmationBody({
|
||||||
error: error,
|
|
||||||
buttonText,
|
buttonText,
|
||||||
confirmationMessage,
|
confirmationMessage,
|
||||||
|
error: error,
|
||||||
|
handleOpenVault,
|
||||||
theme,
|
theme,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@@ -68,14 +73,21 @@ const notificationContainerStyles = (theme: Theme) => css`
|
|||||||
|
|
||||||
function getConfirmationMessage(
|
function getConfirmationMessage(
|
||||||
i18n: { [key: string]: string },
|
i18n: { [key: string]: string },
|
||||||
|
username: string,
|
||||||
type?: NotificationType,
|
type?: NotificationType,
|
||||||
error?: string,
|
error?: string,
|
||||||
) {
|
) {
|
||||||
|
const loginSaveSuccessDetails = chrome.i18n.getMessage("loginSaveSuccessDetails", [username]);
|
||||||
|
const loginUpdatedSuccessDetails = chrome.i18n.getMessage("loginUpdatedSuccessDetails", [
|
||||||
|
username,
|
||||||
|
]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return i18n.saveFailureDetails;
|
return i18n.saveFailureDetails;
|
||||||
}
|
}
|
||||||
return type === "add" ? i18n.loginSaveSuccessDetails : i18n.loginUpdateSuccessDetails;
|
return type === "add" ? loginSaveSuccessDetails : loginUpdatedSuccessDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHeaderMessage(
|
function getHeaderMessage(
|
||||||
i18n: { [key: string]: string },
|
i18n: { [key: string]: string },
|
||||||
type?: NotificationType,
|
type?: NotificationType,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import createEmotion from "@emotion/css/create-instance";
|
import createEmotion from "@emotion/css/create-instance";
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
|
|
||||||
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
import { themes } from "../constants/styles";
|
import { themes } from "../constants/styles";
|
||||||
import { PartyHorn, Warning } from "../icons";
|
import { PartyHorn, Warning } from "../icons";
|
||||||
@@ -18,12 +18,14 @@ export function NotificationConfirmationBody({
|
|||||||
buttonText,
|
buttonText,
|
||||||
error,
|
error,
|
||||||
confirmationMessage,
|
confirmationMessage,
|
||||||
theme = ThemeTypes.Light,
|
theme,
|
||||||
|
handleOpenVault,
|
||||||
}: {
|
}: {
|
||||||
error?: string;
|
error?: string;
|
||||||
buttonText: string;
|
buttonText: string;
|
||||||
confirmationMessage: string;
|
confirmationMessage: string;
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
|
handleOpenVault: (e: Event) => void;
|
||||||
}) {
|
}) {
|
||||||
const IconComponent = !error ? PartyHorn : Warning;
|
const IconComponent = !error ? PartyHorn : Warning;
|
||||||
return html`
|
return html`
|
||||||
@@ -31,7 +33,7 @@ export function NotificationConfirmationBody({
|
|||||||
<div class=${iconContainerStyles(error)}>${IconComponent({ theme })}</div>
|
<div class=${iconContainerStyles(error)}>${IconComponent({ theme })}</div>
|
||||||
${confirmationMessage && buttonText
|
${confirmationMessage && buttonText
|
||||||
? NotificationConfirmationMessage({
|
? NotificationConfirmationMessage({
|
||||||
handleClick: () => {},
|
handleClick: handleOpenVault,
|
||||||
confirmationMessage,
|
confirmationMessage,
|
||||||
theme,
|
theme,
|
||||||
buttonText,
|
buttonText,
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ async function loadNotificationBar() {
|
|||||||
{
|
{
|
||||||
command: "saveCipherAttemptCompleted",
|
command: "saveCipherAttemptCompleted",
|
||||||
error: msg.data?.error,
|
error: msg.data?.error,
|
||||||
|
username: msg.data?.username,
|
||||||
|
cipherId: msg.data?.cipherId,
|
||||||
},
|
},
|
||||||
"*",
|
"*",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ type NotificationBarIframeInitData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type NotificationBarWindowMessage = {
|
type NotificationBarWindowMessage = {
|
||||||
[key: string]: any;
|
|
||||||
command: string;
|
command: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
initData?: NotificationBarIframeInitData;
|
initData?: NotificationBarIframeInitData;
|
||||||
|
username?: string;
|
||||||
|
cipherId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NotificationBarWindowMessageHandlers = {
|
type NotificationBarWindowMessageHandlers = {
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ function getI18n() {
|
|||||||
close: chrome.i18n.getMessage("close"),
|
close: chrome.i18n.getMessage("close"),
|
||||||
never: chrome.i18n.getMessage("never"),
|
never: chrome.i18n.getMessage("never"),
|
||||||
folder: chrome.i18n.getMessage("folder"),
|
folder: chrome.i18n.getMessage("folder"),
|
||||||
|
loginSaveSuccessDetails: chrome.i18n.getMessage("loginSaveSuccessDetails"),
|
||||||
|
loginUpdateSuccessDetails: chrome.i18n.getMessage("loginUpdatedSuccessDetails"),
|
||||||
notificationAddSave: chrome.i18n.getMessage("notificationAddSave"),
|
notificationAddSave: chrome.i18n.getMessage("notificationAddSave"),
|
||||||
notificationAddDesc: chrome.i18n.getMessage("notificationAddDesc"),
|
notificationAddDesc: chrome.i18n.getMessage("notificationAddDesc"),
|
||||||
notificationEdit: chrome.i18n.getMessage("edit"),
|
notificationEdit: chrome.i18n.getMessage("edit"),
|
||||||
@@ -70,9 +72,7 @@ function getI18n() {
|
|||||||
saveLoginPrompt: "Save login?",
|
saveLoginPrompt: "Save login?",
|
||||||
updateLoginPrompt: "Update existing login?",
|
updateLoginPrompt: "Update existing login?",
|
||||||
loginSaveSuccess: "Login saved",
|
loginSaveSuccess: "Login saved",
|
||||||
loginSaveSuccessDetails: "Login saved to Bitwarden.",
|
|
||||||
loginUpdateSuccess: "Login updated",
|
loginUpdateSuccess: "Login updated",
|
||||||
loginUpdateSuccessDetails: "Login updated in Bitwarden.",
|
|
||||||
saveFailure: "Error saving",
|
saveFailure: "Error saving",
|
||||||
saveFailureDetails: "Oh no! We couldn't save this. Try entering the details as a New item",
|
saveFailureDetails: "Oh no! We couldn't save this. Try entering the details as a New item",
|
||||||
newItem: "New item",
|
newItem: "New item",
|
||||||
@@ -289,9 +289,17 @@ function handleSaveCipherAttemptCompletedMessage(message: NotificationBarWindowM
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openViewVaultItemPopout(e: Event, cipherId: string) {
|
||||||
|
e.preventDefault();
|
||||||
|
sendPlatformMessage({
|
||||||
|
command: "bgOpenVault",
|
||||||
|
cipherId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) {
|
function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) {
|
||||||
const { theme, type } = notificationBarIframeInitData;
|
const { theme, type } = notificationBarIframeInitData;
|
||||||
const { error } = message;
|
const { error, username, cipherId } = message;
|
||||||
const i18n = getI18n();
|
const i18n = getI18n();
|
||||||
const resolvedTheme = getResolvedTheme(theme);
|
const resolvedTheme = getResolvedTheme(theme);
|
||||||
|
|
||||||
@@ -305,6 +313,8 @@ function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) {
|
|||||||
handleCloseNotification,
|
handleCloseNotification,
|
||||||
i18n,
|
i18n,
|
||||||
error,
|
error,
|
||||||
|
username,
|
||||||
|
handleOpenVault: (e) => openViewVaultItemPopout(e, cipherId),
|
||||||
}),
|
}),
|
||||||
document.body,
|
document.body,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user