mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-25 12:15:18 +01:00
[PM-2753] Prompt to Save New Login Credentials Silently Drops Data on Network Error (#7730)
* [PM-673] Safari Notification Bar Does Not Show Folders * [PM-673] Refactoring Context Menu Implementations to Ensure Pages with No Logins Can Dismiss Notification Bar * [PM-673] Refactoring typing information for the LockedVaultPendingNotificationsItem typing * [PM-673] Refactoring typing information for notification background * [PM-673] Finishing out typing for potential extension messages to the notification bar; * [PM-673] Working through implementation details for the notification background * [PM-673] Fixing issues present with messaging re-implementation * [PM-673] Fixing issue with folders not populating within Safari notification bar * [PM-673] Fixing jest test issues present within implementation * [PM-673] Fixing issue present with webVaultUrl vulnerability * [PM-673] Fixing XSS Vulnerability within Notification Bar; * [PM-5670] Putting together a partial implementation for having messages appear on network error within the notification bar * [PM-673] Incorporating status update for when user has successfully saved credentials * [PM-673] Incorporating status update for when user has successfully saved credentials * [PM-5949] Refactor typing information for notification bar * [PM-5949] Fix jest tests for overlay background * [PM-5949] Removing unnused typing data * [PM-5949] Fixing lint error * [PM-5949] Adding jest tests for convertAddLoginQueueMessageToCipherView method * [PM-5949] Fixing jest test for overlay * [PM-5950] Fix Context Menu Update Race Condition and Refactor Implementation * [PM-5950] Adding jest test for cipherContextMenu.update method * [PM-5950] Adding documentation for method within MainContextMenuHandler * [PM-5950] Adding jest tests for the mainContextMenuHandler * [PM-673] Stripping unnecessary work for network drop issue * [PM-673] Stripping unnecessary work for network drop issue * [PM-2753] Prompt to Save New Login Credentials Silently Drops Data on Network Error * [PM-673] Stripping out work done for another ticket * [PM-5950] Removing unnecessary return value from MainContextMenuHandler.create method * [PM-673] Implementing unit test coverage for newly introduced logic * [PM-673] Implementing unit test coverage for newly introduced logic * [PM-673] Implementing unit test coverage for newly introduced logic * [PM-673] Implementing unit test coverage for newly introduced logic * [PM-2753] Implementing jest tests to validate logic changes * [PM-2753] Implementing jest tests to validate logic changes * [PM-2753] Implementing jest tests to validate logic changes * [PM-2753] Implementing jest tests to validate logic changes * [PM-2753] Incorporating addition of green and red borders when success or error events occur * [PM-5950] Fixing unawaited context menu promise * [PM-673] Merging changes in from main and fixing merge conflicts * [PM-2753] Merging work in from main and resolving merge conflicts * [PM-673] Fixing issue where updates to the added login were not triggering correctly * [PM-673] Merging changes in from main and fixing merge conflicts
This commit is contained in:
parent
43d1174a06
commit
3367339f9b
@ -2542,8 +2542,8 @@
|
|||||||
"description": "LastPass specific notification button text for cancelling a fileless import."
|
"description": "LastPass specific notification button text for cancelling a fileless import."
|
||||||
},
|
},
|
||||||
"startFilelessImport": {
|
"startFilelessImport": {
|
||||||
"message": "Import to Bitwarden",
|
"message": "Import to Bitwarden",
|
||||||
"description": "Notification button text for starting a fileless import."
|
"description": "Notification button text for starting a fileless import."
|
||||||
},
|
},
|
||||||
"importing": {
|
"importing": {
|
||||||
"message": "Importing...",
|
"message": "Importing...",
|
||||||
@ -2993,5 +2993,17 @@
|
|||||||
"makeDefault": {
|
"makeDefault": {
|
||||||
"message": "Make default",
|
"message": "Make default",
|
||||||
"description": "Button text for the setting that allows overriding the default browser autofill settings"
|
"description": "Button text for the setting that allows overriding the default browser autofill settings"
|
||||||
|
},
|
||||||
|
"saveCipherAttemptSuccess": {
|
||||||
|
"message": "Credentials saved successfully!",
|
||||||
|
"description": "Notification message for when saving credentials has succeeded."
|
||||||
|
},
|
||||||
|
"updateCipherAttemptSuccess": {
|
||||||
|
"message": "Credentials updated successfully!",
|
||||||
|
"description": "Notification message for when updating credentials has succeeded."
|
||||||
|
},
|
||||||
|
"saveCipherAttemptFailed": {
|
||||||
|
"message": "Error saving credentials. Check console for details.",
|
||||||
|
"description": "Notification message for when saving credentials has failed."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,8 @@ type NotificationBackgroundExtensionMessage = {
|
|||||||
notificationType?: string;
|
notificationType?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SaveOrUpdateCipherResult = undefined | { error: string };
|
||||||
|
|
||||||
type BackgroundMessageParam = { message: NotificationBackgroundExtensionMessage };
|
type BackgroundMessageParam = { message: NotificationBackgroundExtensionMessage };
|
||||||
type BackgroundSenderParam = { sender: chrome.runtime.MessageSender };
|
type BackgroundSenderParam = { sender: chrome.runtime.MessageSender };
|
||||||
type BackgroundOnMessageHandlerParams = BackgroundMessageParam & BackgroundSenderParam;
|
type BackgroundOnMessageHandlerParams = BackgroundMessageParam & BackgroundSenderParam;
|
||||||
@ -120,6 +122,7 @@ export {
|
|||||||
ChangePasswordMessageData,
|
ChangePasswordMessageData,
|
||||||
UnlockVaultMessageData,
|
UnlockVaultMessageData,
|
||||||
AddLoginMessageData,
|
AddLoginMessageData,
|
||||||
|
SaveOrUpdateCipherResult,
|
||||||
NotificationBackgroundExtensionMessage,
|
NotificationBackgroundExtensionMessage,
|
||||||
NotificationBackgroundExtensionMessageHandlers,
|
NotificationBackgroundExtensionMessageHandlers,
|
||||||
};
|
};
|
||||||
|
@ -20,6 +20,7 @@ import { createAutofillPageDetailsMock, createChromeTabMock } from "../spec/auto
|
|||||||
import { flushPromises, sendExtensionRuntimeMessage } from "../spec/testing-utils";
|
import { flushPromises, sendExtensionRuntimeMessage } from "../spec/testing-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AddChangePasswordQueueMessage,
|
||||||
AddLoginQueueMessage,
|
AddLoginQueueMessage,
|
||||||
AddUnlockVaultQueueMessage,
|
AddUnlockVaultQueueMessage,
|
||||||
LockedVaultPendingNotificationsData,
|
LockedVaultPendingNotificationsData,
|
||||||
@ -640,6 +641,424 @@ describe("NotificationBackground", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("bgSaveCipher message handler", () => {
|
||||||
|
let getAuthStatusSpy: jest.SpyInstance;
|
||||||
|
let tabSendMessageDataSpy: jest.SpyInstance;
|
||||||
|
let openUnlockPopoutSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getAuthStatusSpy = jest.spyOn(authService, "getAuthStatus");
|
||||||
|
tabSendMessageDataSpy = jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation();
|
||||||
|
openUnlockPopoutSpy = jest
|
||||||
|
.spyOn(notificationBackground as any, "openUnlockPopout")
|
||||||
|
.mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips saving the cipher and opens an unlock popout if the extension is not unlocked", async () => {
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab: { id: 1 } });
|
||||||
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
|
command: "bgSaveCipher",
|
||||||
|
edit: false,
|
||||||
|
folder: "folder-id",
|
||||||
|
};
|
||||||
|
getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Locked);
|
||||||
|
|
||||||
|
sendExtensionRuntimeMessage(message, sender);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(getAuthStatusSpy).toHaveBeenCalled();
|
||||||
|
expect(tabSendMessageDataSpy).toHaveBeenCalledWith(
|
||||||
|
sender.tab,
|
||||||
|
"addToLockedVaultPendingNotifications",
|
||||||
|
{
|
||||||
|
commandToRetry: { message, sender },
|
||||||
|
target: "notification.background",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(openUnlockPopoutSpy).toHaveBeenCalledWith(sender.tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("saveOrUpdateCredentials", () => {
|
||||||
|
let getDecryptedCipherByIdSpy: jest.SpyInstance;
|
||||||
|
let getAllDecryptedForUrlSpy: jest.SpyInstance;
|
||||||
|
let updatePasswordSpy: jest.SpyInstance;
|
||||||
|
let convertAddLoginQueueMessageToCipherViewSpy: jest.SpyInstance;
|
||||||
|
let tabSendMessageSpy: jest.SpyInstance;
|
||||||
|
let editItemSpy: jest.SpyInstance;
|
||||||
|
let setAddEditCipherInfoSpy: jest.SpyInstance;
|
||||||
|
let openAddEditVaultItemPopoutSpy: jest.SpyInstance;
|
||||||
|
let createWithServerSpy: jest.SpyInstance;
|
||||||
|
let updateWithServerSpy: jest.SpyInstance;
|
||||||
|
let folderExistsSpy: jest.SpyInstance;
|
||||||
|
let cipherEncryptSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Unlocked);
|
||||||
|
getDecryptedCipherByIdSpy = jest.spyOn(
|
||||||
|
notificationBackground as any,
|
||||||
|
"getDecryptedCipherById",
|
||||||
|
);
|
||||||
|
getAllDecryptedForUrlSpy = jest.spyOn(cipherService, "getAllDecryptedForUrl");
|
||||||
|
updatePasswordSpy = jest.spyOn(notificationBackground as any, "updatePassword");
|
||||||
|
convertAddLoginQueueMessageToCipherViewSpy = jest.spyOn(
|
||||||
|
notificationBackground as any,
|
||||||
|
"convertAddLoginQueueMessageToCipherView",
|
||||||
|
);
|
||||||
|
tabSendMessageSpy = jest.spyOn(BrowserApi, "tabSendMessage").mockImplementation();
|
||||||
|
editItemSpy = jest.spyOn(notificationBackground as any, "editItem");
|
||||||
|
setAddEditCipherInfoSpy = jest.spyOn(stateService, "setAddEditCipherInfo");
|
||||||
|
openAddEditVaultItemPopoutSpy = jest.spyOn(
|
||||||
|
notificationBackground as any,
|
||||||
|
"openAddEditVaultItemPopout",
|
||||||
|
);
|
||||||
|
createWithServerSpy = jest.spyOn(cipherService, "createWithServer");
|
||||||
|
updateWithServerSpy = jest.spyOn(cipherService, "updateWithServer");
|
||||||
|
folderExistsSpy = jest.spyOn(notificationBackground as any, "folderExists");
|
||||||
|
cipherEncryptSpy = jest.spyOn(cipherService, "encrypt");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips saving the cipher if the notification queue does not have a tab that is related to the sender", async () => {
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab: { id: 2 } });
|
||||||
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
|
command: "bgSaveCipher",
|
||||||
|
edit: false,
|
||||||
|
folder: "folder-id",
|
||||||
|
};
|
||||||
|
notificationBackground["notificationQueue"] = [
|
||||||
|
mock<AddLoginQueueMessage>({
|
||||||
|
tab: createChromeTabMock({ id: 1 }),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
sendExtensionRuntimeMessage(message, sender);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(updatePasswordSpy).not.toHaveBeenCalled();
|
||||||
|
expect(editItemSpy).not.toHaveBeenCalled();
|
||||||
|
expect(createWithServerSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips saving the cipher if the notification queue does not contain an AddLogin or ChangePassword type", async () => {
|
||||||
|
const tab = createChromeTabMock({ id: 1 });
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab });
|
||||||
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
|
command: "bgSaveCipher",
|
||||||
|
edit: false,
|
||||||
|
folder: "folder-id",
|
||||||
|
};
|
||||||
|
notificationBackground["notificationQueue"] = [
|
||||||
|
mock<AddUnlockVaultQueueMessage>({
|
||||||
|
tab,
|
||||||
|
type: NotificationQueueMessageType.UnlockVault,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
sendExtensionRuntimeMessage(message, sender);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(updatePasswordSpy).not.toHaveBeenCalled();
|
||||||
|
expect(editItemSpy).not.toHaveBeenCalled();
|
||||||
|
expect(createWithServerSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips saving the cipher if the notification queue message has a different domain than the passed tab", () => {
|
||||||
|
const tab = createChromeTabMock({ id: 1, url: "https://example.com" });
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab });
|
||||||
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
|
command: "bgSaveCipher",
|
||||||
|
edit: false,
|
||||||
|
folder: "folder-id",
|
||||||
|
};
|
||||||
|
notificationBackground["notificationQueue"] = [
|
||||||
|
mock<AddLoginQueueMessage>({
|
||||||
|
type: NotificationQueueMessageType.AddLogin,
|
||||||
|
tab,
|
||||||
|
domain: "another.com",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
sendExtensionRuntimeMessage(message, sender);
|
||||||
|
expect(updatePasswordSpy).not.toHaveBeenCalled();
|
||||||
|
expect(editItemSpy).not.toHaveBeenCalled();
|
||||||
|
expect(createWithServerSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates the password if the notification message type is for ChangePassword", async () => {
|
||||||
|
const tab = createChromeTabMock({ id: 1, url: "https://example.com" });
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab });
|
||||||
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
|
command: "bgSaveCipher",
|
||||||
|
edit: false,
|
||||||
|
folder: "folder-id",
|
||||||
|
};
|
||||||
|
const queueMessage = mock<AddChangePasswordQueueMessage>({
|
||||||
|
type: NotificationQueueMessageType.ChangePassword,
|
||||||
|
tab,
|
||||||
|
domain: "example.com",
|
||||||
|
newPassword: "newPassword",
|
||||||
|
});
|
||||||
|
notificationBackground["notificationQueue"] = [queueMessage];
|
||||||
|
const cipherView = mock<CipherView>();
|
||||||
|
getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView);
|
||||||
|
|
||||||
|
sendExtensionRuntimeMessage(message, sender);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(editItemSpy).not.toHaveBeenCalled();
|
||||||
|
expect(createWithServerSpy).not.toHaveBeenCalled();
|
||||||
|
expect(updatePasswordSpy).toHaveBeenCalledWith(
|
||||||
|
cipherView,
|
||||||
|
queueMessage.newPassword,
|
||||||
|
message.edit,
|
||||||
|
sender.tab,
|
||||||
|
);
|
||||||
|
expect(updateWithServerSpy).toHaveBeenCalled();
|
||||||
|
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||||
|
command: "saveCipherAttemptCompleted",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates the cipher password if the queue message was locked and an existing cipher has the same username as the message", async () => {
|
||||||
|
const tab = createChromeTabMock({ id: 1, url: "https://example.com" });
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab });
|
||||||
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
|
command: "bgSaveCipher",
|
||||||
|
edit: false,
|
||||||
|
folder: "folder-id",
|
||||||
|
};
|
||||||
|
const queueMessage = mock<AddLoginQueueMessage>({
|
||||||
|
type: NotificationQueueMessageType.AddLogin,
|
||||||
|
tab,
|
||||||
|
domain: "example.com",
|
||||||
|
username: "test",
|
||||||
|
password: "updated-password",
|
||||||
|
wasVaultLocked: true,
|
||||||
|
});
|
||||||
|
notificationBackground["notificationQueue"] = [queueMessage];
|
||||||
|
const cipherView = mock<CipherView>({
|
||||||
|
login: { username: "test", password: "old-password" },
|
||||||
|
});
|
||||||
|
getAllDecryptedForUrlSpy.mockResolvedValueOnce([cipherView]);
|
||||||
|
|
||||||
|
sendExtensionRuntimeMessage(message, sender);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(updatePasswordSpy).toHaveBeenCalledWith(
|
||||||
|
cipherView,
|
||||||
|
queueMessage.password,
|
||||||
|
message.edit,
|
||||||
|
sender.tab,
|
||||||
|
);
|
||||||
|
expect(editItemSpy).not.toHaveBeenCalled();
|
||||||
|
expect(createWithServerSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens an editItem window and closes the notification bar if the edit value is within the passed message when attempting to update an existing cipher", async () => {
|
||||||
|
const tab = createChromeTabMock({ id: 1, url: "https://example.com" });
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab });
|
||||||
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
|
command: "bgSaveCipher",
|
||||||
|
edit: true,
|
||||||
|
folder: "folder-id",
|
||||||
|
};
|
||||||
|
const queueMessage = mock<AddChangePasswordQueueMessage>({
|
||||||
|
type: NotificationQueueMessageType.ChangePassword,
|
||||||
|
tab,
|
||||||
|
domain: "example.com",
|
||||||
|
newPassword: "newPassword",
|
||||||
|
});
|
||||||
|
notificationBackground["notificationQueue"] = [queueMessage];
|
||||||
|
const cipherView = mock<CipherView>();
|
||||||
|
getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView);
|
||||||
|
setAddEditCipherInfoSpy.mockResolvedValue(undefined);
|
||||||
|
openAddEditVaultItemPopoutSpy.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
sendExtensionRuntimeMessage(message, sender);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(updatePasswordSpy).toHaveBeenCalledWith(
|
||||||
|
cipherView,
|
||||||
|
queueMessage.newPassword,
|
||||||
|
message.edit,
|
||||||
|
sender.tab,
|
||||||
|
);
|
||||||
|
expect(editItemSpy).toHaveBeenCalled();
|
||||||
|
expect(updateWithServerSpy).not.toHaveBeenCalled();
|
||||||
|
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||||
|
command: "closeNotificationBar",
|
||||||
|
});
|
||||||
|
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||||
|
command: "editedCipher",
|
||||||
|
});
|
||||||
|
expect(setAddEditCipherInfoSpy).toHaveBeenCalledWith({
|
||||||
|
cipher: cipherView,
|
||||||
|
collectionIds: cipherView.collectionIds,
|
||||||
|
});
|
||||||
|
expect(openAddEditVaultItemPopoutSpy).toHaveBeenCalledWith(sender.tab, {
|
||||||
|
cipherId: cipherView.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens an editItem window and closes the notification bar if the edit value is within the passed message when attempting to save the cipher", async () => {
|
||||||
|
const tab = createChromeTabMock({ id: 1, url: "https://example.com" });
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab });
|
||||||
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
|
command: "bgSaveCipher",
|
||||||
|
edit: true,
|
||||||
|
folder: "folder-id",
|
||||||
|
};
|
||||||
|
const queueMessage = mock<AddLoginQueueMessage>({
|
||||||
|
type: NotificationQueueMessageType.AddLogin,
|
||||||
|
tab,
|
||||||
|
domain: "example.com",
|
||||||
|
username: "test",
|
||||||
|
password: "password",
|
||||||
|
wasVaultLocked: false,
|
||||||
|
});
|
||||||
|
notificationBackground["notificationQueue"] = [queueMessage];
|
||||||
|
const cipherView = mock<CipherView>({
|
||||||
|
login: { username: "test", password: "password" },
|
||||||
|
});
|
||||||
|
folderExistsSpy.mockResolvedValueOnce(true);
|
||||||
|
convertAddLoginQueueMessageToCipherViewSpy.mockReturnValueOnce(cipherView);
|
||||||
|
editItemSpy.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
|
sendExtensionRuntimeMessage(message, sender);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(updatePasswordSpy).not.toHaveBeenCalled();
|
||||||
|
expect(convertAddLoginQueueMessageToCipherViewSpy).toHaveBeenCalledWith(
|
||||||
|
queueMessage,
|
||||||
|
message.folder,
|
||||||
|
);
|
||||||
|
expect(editItemSpy).toHaveBeenCalledWith(cipherView, sender.tab);
|
||||||
|
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||||
|
command: "closeNotificationBar",
|
||||||
|
});
|
||||||
|
expect(createWithServerSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("creates the cipher within the server and sends an `saveCipherAttemptCompleted` and `addedCipher` message to the sender tab", async () => {
|
||||||
|
const tab = createChromeTabMock({ id: 1, url: "https://example.com" });
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab });
|
||||||
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
|
command: "bgSaveCipher",
|
||||||
|
edit: false,
|
||||||
|
folder: "folder-id",
|
||||||
|
};
|
||||||
|
const queueMessage = mock<AddLoginQueueMessage>({
|
||||||
|
type: NotificationQueueMessageType.AddLogin,
|
||||||
|
tab,
|
||||||
|
domain: "example.com",
|
||||||
|
username: "test",
|
||||||
|
password: "password",
|
||||||
|
wasVaultLocked: false,
|
||||||
|
});
|
||||||
|
notificationBackground["notificationQueue"] = [queueMessage];
|
||||||
|
const cipherView = mock<CipherView>({
|
||||||
|
login: { username: "test", password: "password" },
|
||||||
|
});
|
||||||
|
folderExistsSpy.mockResolvedValueOnce(false);
|
||||||
|
convertAddLoginQueueMessageToCipherViewSpy.mockReturnValueOnce(cipherView);
|
||||||
|
editItemSpy.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
|
sendExtensionRuntimeMessage(message, sender);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(convertAddLoginQueueMessageToCipherViewSpy).toHaveBeenCalledWith(
|
||||||
|
queueMessage,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView);
|
||||||
|
expect(createWithServerSpy).toHaveBeenCalled();
|
||||||
|
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||||
|
command: "saveCipherAttemptCompleted",
|
||||||
|
});
|
||||||
|
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { command: "addedCipher" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends an error message within the `saveCipherAttemptCompleted` message if the cipher cannot be saved to the server", async () => {
|
||||||
|
const tab = createChromeTabMock({ id: 1, url: "https://example.com" });
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab });
|
||||||
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
|
command: "bgSaveCipher",
|
||||||
|
edit: false,
|
||||||
|
folder: "folder-id",
|
||||||
|
};
|
||||||
|
const queueMessage = mock<AddLoginQueueMessage>({
|
||||||
|
type: NotificationQueueMessageType.AddLogin,
|
||||||
|
tab,
|
||||||
|
domain: "example.com",
|
||||||
|
username: "test",
|
||||||
|
password: "password",
|
||||||
|
wasVaultLocked: false,
|
||||||
|
});
|
||||||
|
notificationBackground["notificationQueue"] = [queueMessage];
|
||||||
|
const cipherView = mock<CipherView>({
|
||||||
|
login: { username: "test", password: "password" },
|
||||||
|
});
|
||||||
|
folderExistsSpy.mockResolvedValueOnce(true);
|
||||||
|
convertAddLoginQueueMessageToCipherViewSpy.mockReturnValueOnce(cipherView);
|
||||||
|
editItemSpy.mockResolvedValueOnce(undefined);
|
||||||
|
const errorMessage = "fetch error";
|
||||||
|
createWithServerSpy.mockImplementation(() => {
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
sendExtensionRuntimeMessage(message, sender);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView);
|
||||||
|
expect(createWithServerSpy).toThrow(errorMessage);
|
||||||
|
expect(tabSendMessageSpy).not.toHaveBeenCalledWith(sender.tab, {
|
||||||
|
command: "addedCipher",
|
||||||
|
});
|
||||||
|
expect(tabSendMessageDataSpy).toHaveBeenCalledWith(
|
||||||
|
sender.tab,
|
||||||
|
"saveCipherAttemptCompleted",
|
||||||
|
{
|
||||||
|
error: errorMessage,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends an error message within the `saveCipherAttemptCompleted` message if the cipher cannot be updated within the server", async () => {
|
||||||
|
const tab = createChromeTabMock({ id: 1, url: "https://example.com" });
|
||||||
|
const sender = mock<chrome.runtime.MessageSender>({ tab });
|
||||||
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
|
command: "bgSaveCipher",
|
||||||
|
edit: false,
|
||||||
|
folder: "folder-id",
|
||||||
|
};
|
||||||
|
const queueMessage = mock<AddChangePasswordQueueMessage>({
|
||||||
|
type: NotificationQueueMessageType.ChangePassword,
|
||||||
|
tab,
|
||||||
|
domain: "example.com",
|
||||||
|
newPassword: "newPassword",
|
||||||
|
});
|
||||||
|
notificationBackground["notificationQueue"] = [queueMessage];
|
||||||
|
const cipherView = mock<CipherView>();
|
||||||
|
getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView);
|
||||||
|
const errorMessage = "fetch error";
|
||||||
|
updateWithServerSpy.mockImplementation(() => {
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
sendExtensionRuntimeMessage(message, sender);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(updateWithServerSpy).toThrow(errorMessage);
|
||||||
|
expect(tabSendMessageDataSpy).toHaveBeenCalledWith(
|
||||||
|
sender.tab,
|
||||||
|
"saveCipherAttemptCompleted",
|
||||||
|
{
|
||||||
|
error: errorMessage,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("bgNeverSave message handler", () => {
|
describe("bgNeverSave message handler", () => {
|
||||||
let tabSendMessageDataSpy: jest.SpyInstance;
|
let tabSendMessageDataSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ import { OverlayBackgroundExtensionMessage } from "./abstractions/overlay.backgr
|
|||||||
|
|
||||||
export default class NotificationBackground {
|
export default class NotificationBackground {
|
||||||
private openUnlockPopout = openUnlockPopout;
|
private openUnlockPopout = openUnlockPopout;
|
||||||
|
private openAddEditVaultItemPopout = openAddEditVaultItemPopout;
|
||||||
private notificationQueue: NotificationQueueMessageItem[] = [];
|
private notificationQueue: NotificationQueueMessageItem[] = [];
|
||||||
private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = {
|
private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = {
|
||||||
unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender),
|
unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender),
|
||||||
@ -431,6 +432,14 @@ export default class NotificationBackground {
|
|||||||
this.removeTabFromNotificationQueue(tab);
|
this.removeTabFromNotificationQueue(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* popout will be opened.
|
||||||
|
*
|
||||||
|
* @param message - The extension message
|
||||||
|
* @param sender - The contextual sender of the message
|
||||||
|
*/
|
||||||
private async handleSaveCipherMessage(
|
private async handleSaveCipherMessage(
|
||||||
message: NotificationBackgroundExtensionMessage,
|
message: NotificationBackgroundExtensionMessage,
|
||||||
sender: chrome.runtime.MessageSender,
|
sender: chrome.runtime.MessageSender,
|
||||||
@ -454,6 +463,14 @@ export default class NotificationBackground {
|
|||||||
await this.saveOrUpdateCredentials(sender.tab, message.edit, message.folder);
|
await this.saveOrUpdateCredentials(sender.tab, message.edit, message.folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves or updates credentials based on the message within the
|
||||||
|
* notification queue that is associated with the specified tab.
|
||||||
|
*
|
||||||
|
* @param tab - The tab to save or update credentials for
|
||||||
|
* @param edit - Identifies if the credentials should be edited or simply added
|
||||||
|
* @param folderId - The folder to add the cipher to
|
||||||
|
*/
|
||||||
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, edit: boolean, folderId?: string) {
|
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, edit: boolean, folderId?: string) {
|
||||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||||
const queueMessage = this.notificationQueue[i];
|
const queueMessage = this.notificationQueue[i];
|
||||||
@ -471,9 +488,6 @@ export default class NotificationBackground {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.notificationQueue.splice(i, 1);
|
this.notificationQueue.splice(i, 1);
|
||||||
BrowserApi.tabSendMessageData(tab, "closeNotificationBar").catch((error) =>
|
|
||||||
this.logService.error(error),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (queueMessage.type === NotificationQueueMessageType.ChangePassword) {
|
if (queueMessage.type === NotificationQueueMessageType.ChangePassword) {
|
||||||
const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId);
|
const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId);
|
||||||
@ -481,38 +495,52 @@ export default class NotificationBackground {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queueMessage.type === NotificationQueueMessageType.AddLogin) {
|
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
||||||
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
if (queueMessage.wasVaultLocked) {
|
||||||
if (queueMessage.wasVaultLocked) {
|
const allCiphers = await this.cipherService.getAllDecryptedForUrl(queueMessage.uri);
|
||||||
const allCiphers = await this.cipherService.getAllDecryptedForUrl(queueMessage.uri);
|
const existingCipher = allCiphers.find(
|
||||||
const existingCipher = allCiphers.find(
|
(c) =>
|
||||||
(c) =>
|
c.login.username != null && c.login.username.toLowerCase() === queueMessage.username,
|
||||||
c.login.username != null && c.login.username.toLowerCase() === queueMessage.username,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
if (existingCipher != null) {
|
if (existingCipher != null) {
|
||||||
await this.updatePassword(existingCipher, queueMessage.password, edit, tab);
|
await this.updatePassword(existingCipher, queueMessage.password, edit, tab);
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
folderId = (await this.folderExists(folderId)) ? folderId : null;
|
|
||||||
const newCipher = this.convertAddLoginQueueMessageToCipherView(queueMessage, folderId);
|
|
||||||
|
|
||||||
if (edit) {
|
|
||||||
await this.editItem(newCipher, tab);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const cipher = await this.cipherService.encrypt(newCipher);
|
folderId = (await this.folderExists(folderId)) ? folderId : null;
|
||||||
|
const newCipher = this.convertAddLoginQueueMessageToCipherView(queueMessage, folderId);
|
||||||
|
|
||||||
|
if (edit) {
|
||||||
|
await this.editItem(newCipher, tab);
|
||||||
|
await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipher = await this.cipherService.encrypt(newCipher);
|
||||||
|
try {
|
||||||
await this.cipherService.createWithServer(cipher);
|
await this.cipherService.createWithServer(cipher);
|
||||||
BrowserApi.tabSendMessageData(tab, "addedCipher").catch((error) =>
|
await BrowserApi.tabSendMessage(tab, { command: "saveCipherAttemptCompleted" });
|
||||||
this.logService.error(error),
|
await BrowserApi.tabSendMessage(tab, { command: "addedCipher" });
|
||||||
);
|
} catch (error) {
|
||||||
|
await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", {
|
||||||
|
error: String(error.message),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles updating an existing cipher's password. If the cipher
|
||||||
|
* is being edited, a popup will be opened to allow the user to
|
||||||
|
* edit the cipher.
|
||||||
|
*
|
||||||
|
* @param cipherView - The cipher to update
|
||||||
|
* @param newPassword - The new password to update the cipher with
|
||||||
|
* @param edit - Identifies if the cipher should be edited or simply updated
|
||||||
|
* @param tab - The tab that the message was sent from
|
||||||
|
*/
|
||||||
private async updatePassword(
|
private async updatePassword(
|
||||||
cipherView: CipherView,
|
cipherView: CipherView,
|
||||||
newPassword: string,
|
newPassword: string,
|
||||||
@ -523,23 +551,37 @@ export default class NotificationBackground {
|
|||||||
|
|
||||||
if (edit) {
|
if (edit) {
|
||||||
await this.editItem(cipherView, tab);
|
await this.editItem(cipherView, tab);
|
||||||
|
await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" });
|
||||||
await BrowserApi.tabSendMessage(tab, { command: "editedCipher" });
|
await BrowserApi.tabSendMessage(tab, { command: "editedCipher" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cipher = await this.cipherService.encrypt(cipherView);
|
const cipher = await this.cipherService.encrypt(cipherView);
|
||||||
await this.cipherService.updateWithServer(cipher);
|
try {
|
||||||
// We've only updated the password, no need to broadcast editedCipher message
|
// We've only updated the password, no need to broadcast editedCipher message
|
||||||
return;
|
await this.cipherService.updateWithServer(cipher);
|
||||||
|
await BrowserApi.tabSendMessage(tab, { command: "saveCipherAttemptCompleted" });
|
||||||
|
} catch (error) {
|
||||||
|
await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", {
|
||||||
|
error: String(error.message),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the add/edit cipher info in the state service
|
||||||
|
* and opens the add/edit vault item popout.
|
||||||
|
*
|
||||||
|
* @param cipherView - The cipher to edit
|
||||||
|
* @param senderTab - The tab that the message was sent from
|
||||||
|
*/
|
||||||
private async editItem(cipherView: CipherView, senderTab: chrome.tabs.Tab) {
|
private async editItem(cipherView: CipherView, senderTab: chrome.tabs.Tab) {
|
||||||
await this.stateService.setAddEditCipherInfo({
|
await this.stateService.setAddEditCipherInfo({
|
||||||
cipher: cipherView,
|
cipher: cipherView,
|
||||||
collectionIds: cipherView.collectionIds,
|
collectionIds: cipherView.collectionIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
await openAddEditVaultItemPopout(senderTab, { cipherId: cipherView.id });
|
await this.openAddEditVaultItemPopout(senderTab, { cipherId: cipherView.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async folderExists(folderId: string) {
|
private async folderExists(folderId: string) {
|
||||||
|
@ -194,6 +194,18 @@ async function loadNotificationBar() {
|
|||||||
watchForms(msg.data.forms);
|
watchForms(msg.data.forms);
|
||||||
sendResponse();
|
sendResponse();
|
||||||
return true;
|
return true;
|
||||||
|
} else if (msg.command === "saveCipherAttemptCompleted") {
|
||||||
|
if (!notificationBarIframe) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationBarIframe.contentWindow?.postMessage(
|
||||||
|
{
|
||||||
|
command: "saveCipherAttemptCompleted",
|
||||||
|
error: msg.data?.error,
|
||||||
|
},
|
||||||
|
"*",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// End Message Processing
|
// End Message Processing
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
import { SaveOrUpdateCipherResult } from "../../background/abstractions/notification.background";
|
||||||
|
|
||||||
|
type NotificationBarWindowMessage = {
|
||||||
|
[key: string]: any;
|
||||||
|
command: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NotificationBarWindowMessageHandlers = {
|
||||||
|
[key: string]: CallableFunction;
|
||||||
|
saveCipherAttemptCompleted: ({ message }: { message: SaveOrUpdateCipherResult }) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { NotificationBarWindowMessage, NotificationBarWindowMessageHandlers };
|
@ -6,7 +6,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="outer-wrapper">
|
<div id="notification-bar-outer-wrapper" class="outer-wrapper">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<a href="https://vault.bitwarden.com" target="_blank" id="logo-link" rel="noreferrer">
|
<a href="https://vault.bitwarden.com" target="_blank" id="logo-link" rel="noreferrer">
|
||||||
<img id="logo" alt="Bitwarden" />
|
<img id="logo" alt="Bitwarden" />
|
||||||
@ -33,7 +33,7 @@
|
|||||||
<template id="template-add">
|
<template id="template-add">
|
||||||
<div class="inner-wrapper">
|
<div class="inner-wrapper">
|
||||||
<div id="add-text"></div>
|
<div id="add-text"></div>
|
||||||
<div>
|
<div class="add-change-cipher-buttons">
|
||||||
<button type="button" id="never-save" class="link"></button>
|
<button type="button" id="never-save" class="link"></button>
|
||||||
<select id="select-folder"></select>
|
<select id="select-folder"></select>
|
||||||
<button type="button" id="add-edit" class="secondary"></button>
|
<button type="button" id="add-edit" class="secondary"></button>
|
||||||
@ -45,7 +45,7 @@
|
|||||||
<template id="template-change">
|
<template id="template-change">
|
||||||
<div class="inner-wrapper">
|
<div class="inner-wrapper">
|
||||||
<div id="change-text"></div>
|
<div id="change-text"></div>
|
||||||
<div>
|
<div class="add-change-cipher-buttons">
|
||||||
<button type="button" id="change-edit" class="secondary"></button>
|
<button type="button" id="change-edit" class="secondary"></button>
|
||||||
<button type="button" id="change-save" class="primary"></button>
|
<button type="button" id="change-save" class="primary"></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,6 +26,18 @@ body {
|
|||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
border-bottom-color: themed("primaryColor");
|
border-bottom-color: themed("primaryColor");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.success-event {
|
||||||
|
@include themify($themes) {
|
||||||
|
border-bottom-color: themed("successColor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error-event {
|
||||||
|
@include themify($themes) {
|
||||||
|
border-bottom-color: themed("errorColor");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner-wrapper {
|
.inner-wrapper {
|
||||||
|
@ -2,11 +2,23 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l
|
|||||||
import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||||
|
|
||||||
import { FilelessImportPort, FilelessImportType } from "../../tools/enums/fileless-import.enums";
|
import { FilelessImportPort, FilelessImportType } from "../../tools/enums/fileless-import.enums";
|
||||||
import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background";
|
import {
|
||||||
|
AdjustNotificationBarMessageData,
|
||||||
|
SaveOrUpdateCipherResult,
|
||||||
|
} from "../background/abstractions/notification.background";
|
||||||
|
|
||||||
|
import {
|
||||||
|
NotificationBarWindowMessageHandlers,
|
||||||
|
NotificationBarWindowMessage,
|
||||||
|
} from "./abstractions/notification-bar";
|
||||||
|
|
||||||
require("./bar.scss");
|
require("./bar.scss");
|
||||||
|
|
||||||
const logService = new ConsoleLogService(false);
|
const logService = new ConsoleLogService(false);
|
||||||
|
let windowMessageOrigin: string;
|
||||||
|
const notificationBarWindowMessageHandlers: NotificationBarWindowMessageHandlers = {
|
||||||
|
saveCipherAttemptCompleted: ({ message }) => handleSaveCipherAttemptCompletedMessage(message),
|
||||||
|
};
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
// delay 50ms so that we get proper body dimensions
|
// delay 50ms so that we get proper body dimensions
|
||||||
@ -14,6 +26,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
|
setupWindowMessageListener();
|
||||||
|
|
||||||
const theme = getQueryVariable("theme");
|
const theme = getQueryVariable("theme");
|
||||||
document.documentElement.classList.add("theme_" + theme);
|
document.documentElement.classList.add("theme_" + theme);
|
||||||
|
|
||||||
@ -205,6 +219,30 @@ function sendSaveCipherMessage(edit: boolean, folder?: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSaveCipherAttemptCompletedMessage(message: SaveOrUpdateCipherResult) {
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
|
||||||
|
logService.error(`Error encountered when saving credentials: ${message.error}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const messageName =
|
||||||
|
getQueryVariable("type") === "add" ? "saveCipherAttemptSuccess" : "updateCipherAttemptSuccess";
|
||||||
|
|
||||||
|
addSaveButtonContainers.forEach((element) => {
|
||||||
|
element.textContent = chrome.i18n.getMessage(messageName);
|
||||||
|
element.classList.add("success-message");
|
||||||
|
notificationBarOuterWrapper.classList.add("success-event");
|
||||||
|
});
|
||||||
|
setTimeout(() => sendPlatformMessage({ command: "bgCloseNotificationBar" }), 1250);
|
||||||
|
}
|
||||||
|
|
||||||
function handleTypeUnlock() {
|
function handleTypeUnlock() {
|
||||||
setContent(document.getElementById("template-unlock") as HTMLTemplateElement);
|
setContent(document.getElementById("template-unlock") as HTMLTemplateElement);
|
||||||
|
|
||||||
@ -248,17 +286,19 @@ function handleTypeFilelessImport() {
|
|||||||
|
|
||||||
port.disconnect();
|
port.disconnect();
|
||||||
|
|
||||||
|
const filelessImportButtons = document.getElementById("fileless-import-buttons");
|
||||||
|
const notificationBarOuterWrapper = document.getElementById("notification-bar-outer-wrapper");
|
||||||
|
|
||||||
if (msg.command === "filelessImportCompleted") {
|
if (msg.command === "filelessImportCompleted") {
|
||||||
document.getElementById("fileless-import-buttons").textContent = chrome.i18n.getMessage(
|
filelessImportButtons.textContent = chrome.i18n.getMessage("dataSuccessfullyImported");
|
||||||
"dataSuccessfullyImported",
|
filelessImportButtons.classList.add("success-message");
|
||||||
);
|
notificationBarOuterWrapper.classList.add("success-event");
|
||||||
document.getElementById("fileless-import-buttons").classList.add("success-message");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("fileless-import-buttons").textContent =
|
filelessImportButtons.textContent = chrome.i18n.getMessage("dataImportFailed");
|
||||||
chrome.i18n.getMessage("dataImportFailed");
|
filelessImportButtons.classList.add("error-message");
|
||||||
document.getElementById("fileless-import-buttons").classList.add("error-message");
|
notificationBarOuterWrapper.classList.add("error-event");
|
||||||
logService.error(`Error Encountered During Import: ${msg.importErrorMessage}`);
|
logService.error(`Error Encountered During Import: ${msg.importErrorMessage}`);
|
||||||
};
|
};
|
||||||
port.onMessage.addListener(handlePortMessage);
|
port.onMessage.addListener(handlePortMessage);
|
||||||
@ -321,3 +361,25 @@ function adjustHeight() {
|
|||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupWindowMessageListener() {
|
||||||
|
globalThis.addEventListener("message", handleWindowMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWindowMessage(event: MessageEvent) {
|
||||||
|
if (!windowMessageOrigin) {
|
||||||
|
windowMessageOrigin = event.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.origin !== windowMessageOrigin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = event.data as NotificationBarWindowMessage;
|
||||||
|
const handler = notificationBarWindowMessageHandlers[message.command];
|
||||||
|
if (!handler) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handler({ message });
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user