1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-14 23:45:37 +00:00

Allows strict typing on notification.background.ts.

This commit is contained in:
Miles Blackwood
2025-08-07 13:20:46 -04:00
parent 46046ca1fa
commit b97abee0f5
10 changed files with 95 additions and 84 deletions

View File

@@ -2,7 +2,7 @@ import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { CollectionView } from "../../content/components/common-types";
import { CollectionNotificationView } from "../../content/components/common-types";
import { NotificationQueueMessageTypes } from "../../enums/notification-queue-message-type.enum";
import AutofillPageDetails from "../../models/autofill-page-details";
@@ -98,7 +98,7 @@ type NotificationBackgroundExtensionMessageHandlers = {
bgGetCollectionData: ({
message,
sender,
}: BackgroundOnMessageHandlerParams) => Promise<CollectionView[]>;
}: BackgroundOnMessageHandlerParams) => Promise<CollectionNotificationView[]>;
bgCloseNotificationBar: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise<void>;
bgOpenAtRiskPasswords: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise<void>;
bgAdjustNotificationBar: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise<void>;

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { firstValueFrom, switchMap, map, of } from "rxjs";
import { CollectionService } from "@bitwarden/admin-console/common";
@@ -59,7 +57,7 @@ import {
OrganizationCategories,
NotificationCipherData,
} from "../content/components/cipher/types";
import { CollectionView } from "../content/components/common-types";
import { CollectionNotificationView } from "../content/components/common-types";
import { NotificationQueueMessageType } from "../enums/notification-queue-message-type.enum";
import { AutofillService } from "../services/abstractions/autofill.service";
import { TemporaryNotificationChangeLoginService } from "../services/notification-change-login-password.service";
@@ -106,18 +104,18 @@ export default class NotificationBackground {
bgGetFolderData: () => this.getFolderData(),
bgGetCollectionData: ({ message }) => this.getCollectionData(message),
bgGetOrgData: () => this.getOrgData(),
bgNeverSave: ({ sender }) => this.saveNever(sender.tab),
bgNeverSave: ({ sender }) => this.saveNever(sender.tab!),
bgOpenAddEditVaultItemPopout: ({ message, sender }) =>
this.openAddEditVaultItem(message, sender.tab),
bgOpenViewVaultItemPopout: ({ message, sender }) => this.viewItem(message, sender.tab),
this.openAddEditVaultItem(message, sender.tab!),
bgOpenViewVaultItemPopout: ({ message, sender }) => this.viewItem(message, sender.tab!),
bgRemoveTabFromNotificationQueue: ({ sender }) =>
this.removeTabFromNotificationQueue(sender.tab),
bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab),
this.removeTabFromNotificationQueue(sender.tab!),
bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab!),
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),
bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab!),
checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab!),
collectPageDetailsResponse: ({ message }) =>
this.handleCollectPageDetailsResponseMessage(message),
getWebVaultUrlForNotification: () => this.getWebVaultUrl(),
@@ -399,7 +397,7 @@ export default class NotificationBackground {
private async doNotificationQueueCheck(tab: chrome.tabs.Tab): Promise<void> {
const queueMessage = this.notificationQueue.find(
(message) => message.tab.id === tab.id && this.queueMessageIsFromTabOrigin(message, tab),
(message) => message.tab!.id === tab.id && this.queueMessageIsFromTabOrigin(message, tab),
);
if (queueMessage) {
await this.sendNotificationQueueMessage(tab, queueMessage);
@@ -672,7 +670,7 @@ export default class NotificationBackground {
}
const forms = this.autofillService.getFormsWithPasswordFields(message.details);
await BrowserApi.tabSendMessageData(message.tab, "notificationBarPageDetails", {
await BrowserApi.tabSendMessageData(message.tab!, "notificationBarPageDetails", {
details: message.details,
forms: forms,
});
@@ -757,7 +755,7 @@ export default class NotificationBackground {
sender: chrome.runtime.MessageSender,
) {
if ((await this.getAuthStatus()) < AuthenticationStatus.Unlocked) {
await BrowserApi.tabSendMessageData(sender.tab, "addToLockedVaultPendingNotifications", {
await BrowserApi.tabSendMessageData(sender.tab!, "addToLockedVaultPendingNotifications", {
commandToRetry: {
message: {
command: message.command,
@@ -768,18 +766,18 @@ export default class NotificationBackground {
},
target: "notification.background",
} as LockedVaultPendingNotificationsData);
await this.openUnlockPopout(sender.tab);
await this.openUnlockPopout(sender.tab!);
return;
}
await this.saveOrUpdateCredentials(sender.tab, message.edit, message.folder);
await this.saveOrUpdateCredentials(sender.tab!, message.edit, message.folder);
}
async handleCipherUpdateRepromptResponse(message: NotificationBackgroundExtensionMessage) {
if (message.success) {
await this.saveOrUpdateCredentials(message.tab, false, undefined, true);
await this.saveOrUpdateCredentials(message.tab!, false, undefined, true);
} else {
await BrowserApi.tabSendMessageData(message.tab, "saveCipherAttemptCompleted", {
await BrowserApi.tabSendMessageData(message.tab!, "saveCipherAttemptCompleted", {
error: "Password reprompt failed",
});
return;
@@ -871,7 +869,7 @@ export default class NotificationBackground {
await BrowserApi.tabSendMessage(tab, { command: "addedCipher" });
} catch (error) {
await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", {
error: error?.message && String(error.message),
error: (error as Error)?.message && String(error.message),
});
}
}
@@ -915,16 +913,16 @@ export default class NotificationBackground {
const updatedCipherTask = tasks.find((task) => task.cipherId === cipherView?.id);
const cipherHasTask = !!updatedCipherTask?.id;
let taskOrgName: string;
let orgName = undefined;
if (cipherHasTask && updatedCipherTask?.organizationId) {
const userOrgs = await this.getOrgData();
taskOrgName = userOrgs.find(({ id }) => id === updatedCipherTask.organizationId)?.name;
const userOrgs = await firstValueFrom(this.getOrgData());
orgName = userOrgs.find(({ id }) => id === updatedCipherTask.organizationId)?.name;
}
const taskData = cipherHasTask
? {
remainingTasksCount: tasks.length - 1,
orgName: taskOrgName,
orgName,
}
: undefined;
@@ -955,7 +953,7 @@ export default class NotificationBackground {
}
} catch (error) {
await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", {
error: error?.message && String(error.message),
error: (error as Error)?.message && String(error.message),
});
}
}
@@ -996,7 +994,9 @@ export default class NotificationBackground {
if (queueItem?.type === NotificationQueueMessageType.AddLogin) {
const cipherView = this.convertAddLoginQueueMessageToCipherView(queueItem);
cipherView.organizationId = organizationId;
cipherView.folderId = folder;
if (folder) {
cipherView.folderId = folder;
}
if (userId) {
await this.cipherService.setAddEditCipherInfo({ cipher: cipherView }, userId);
@@ -1017,7 +1017,7 @@ export default class NotificationBackground {
await Promise.all([
this.openViewVaultItemPopout(senderTab, {
cipherId: message.cipherId,
action: null,
action: "", // noop, TODO decide if action should be optional
}),
BrowserApi.tabSendMessageData(senderTab, "closeNotificationBar", {
fadeOutNotification: !!message.fadeOutNotification,
@@ -1073,6 +1073,10 @@ export default class NotificationBackground {
* @param tab - The tab that sent the neverSave message
*/
private async saveNever(tab: chrome.tabs.Tab) {
if (typeof tab.url !== "string") {
this.logService.warning("URL not present for tab.");
return;
}
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
const queueMessage = this.notificationQueue[i];
if (
@@ -1098,21 +1102,19 @@ export default class NotificationBackground {
* Returns the first value found from the folder service's folderViews$ observable.
*/
private async getFolderData() {
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(getOptionalUserId),
);
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
return await firstValueFrom(this.folderService.folderViews$(activeUserId));
}
private async getCollectionData(
message: NotificationBackgroundExtensionMessage,
): Promise<CollectionView[]> {
): Promise<CollectionNotificationView[]> {
const collections = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.collectionService.decryptedCollections$(userId)),
map((collections) =>
collections.reduce<CollectionView[]>((acc, collection) => {
collections.reduce<CollectionNotificationView[]>((acc, collection) => {
if (collection.organizationId === message?.orgId) {
acc.push({
id: collection.id,
@@ -1147,22 +1149,21 @@ export default class NotificationBackground {
/**
* Returns the first value found from the organization service organizations$ observable.
*/
private async getOrgData() {
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(getOptionalUserId),
private getOrgData() {
return this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.organizationService.organizations$(userId).pipe(
map((orgs) =>
orgs.map((org) => ({
id: org.id,
name: org.name,
productTierType: org.productTierType,
})),
),
),
),
);
const organizations = await firstValueFrom(
this.organizationService.organizations$(activeUserId),
);
return organizations.map((org) => {
const { id, name, productTierType } = org;
return {
id,
name,
productTierType,
};
});
}
/**
@@ -1180,7 +1181,7 @@ export default class NotificationBackground {
const messageData = message.data as LockedVaultPendingNotificationsData;
const retryCommand = messageData.commandToRetry.message.command as ExtensionCommandType;
if (this.allowedRetryCommands.has(retryCommand)) {
await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar");
await BrowserApi.tabSendMessageData(sender.tab!, "closeNotificationBar");
}
if (messageData.target !== "notification.background") {
@@ -1207,7 +1208,7 @@ export default class NotificationBackground {
message: NotificationBackgroundExtensionMessage,
sender: chrome.runtime.MessageSender,
) {
await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar", {
await BrowserApi.tabSendMessageData(sender.tab!, "closeNotificationBar", {
fadeOutNotification: !!message.fadeOutNotification,
});
}
@@ -1234,7 +1235,7 @@ export default class NotificationBackground {
await Promise.all([
this.messagingService.send(VaultMessages.OpenAtRiskPasswords),
BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar", {
BrowserApi.tabSendMessageData(sender.tab!, "closeNotificationBar", {
fadeOutNotification: !!message.fadeOutNotification,
}),
]);
@@ -1258,7 +1259,7 @@ export default class NotificationBackground {
message: NotificationBackgroundExtensionMessage,
sender: chrome.runtime.MessageSender,
) {
await BrowserApi.tabSendMessageData(sender.tab, "adjustNotificationBar", message.data);
await BrowserApi.tabSendMessageData(sender.tab!, "adjustNotificationBar", message.data);
}
/**
@@ -1282,10 +1283,12 @@ export default class NotificationBackground {
const cipherView = new CipherView();
cipherView.name = (Utils.getHostname(message.uri) || message.domain).replace(/^www\./, "");
cipherView.folderId = folderId;
cipherView.type = CipherType.Login;
cipherView.login = loginView;
cipherView.organizationId = null;
cipherView.organizationId = undefined;
if (folderId) {
cipherView.folderId = folderId;
}
return cipherView;
}
@@ -1298,15 +1301,15 @@ export default class NotificationBackground {
message: OverlayBackgroundExtensionMessage,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: any) => void,
) => {
): boolean | void => {
const handler: CallableFunction | undefined = this.extensionMessageHandlers[message?.command];
if (!handler) {
return null;
return;
}
const messageResponse = handler({ message, sender });
if (typeof messageResponse === "undefined") {
return null;
return;
}
Promise.resolve(messageResponse)
@@ -1324,7 +1327,12 @@ export default class NotificationBackground {
private queueMessageIsFromTabOrigin(
queueMessage: NotificationQueueMessageItem,
tab: chrome.tabs.Tab,
) {
): boolean | void {
if (typeof tab.url !== "string" || typeof queueMessage.tab.url !== "string") {
this.logService.warning("URL not present in tab or queue message tab.");
return;
}
const tabDomain = Utils.getDomain(tab.url);
return tabDomain === queueMessage.domain || tabDomain === Utils.getDomain(queueMessage.tab.url);
}

View File

@@ -1,7 +1,9 @@
import { TemplateResult } from "lit";
import { CollectionView } from "@bitwarden/admin-console/common";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { Theme } from "@bitwarden/common/platform/enums";
import { OrganizationId } from "@bitwarden/common/types/guid";
export type I18n = {
[key: string]: string;
@@ -27,13 +29,9 @@ export type FolderView = {
};
export type OrgView = {
id: string;
id: OrganizationId;
name: string;
productTierType?: ProductTierType;
};
export type CollectionView = {
id: string;
name: string;
organizationId: string;
};
export type CollectionNotificationView = Pick<CollectionView, "id" | "organizationId" | "name">;

View File

@@ -1,42 +1,45 @@
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
export const mockOrganizations = [
import { CollectionNotificationView, OrgView } from "../common-types";
export const mockOrganizations: OrgView[] = [
{
id: "unique-id0",
id: "unique-id0" as OrganizationId,
name: "Another personal vault",
},
{
id: "unique-id1",
id: "unique-id1" as OrganizationId,
name: "Acme, inc",
productTierType: ProductTierType.Teams,
},
{
id: "unique-id2",
id: "unique-id2" as OrganizationId,
name: "A Really Long Business Name That Just Kinda Goes On For A Really Long Time",
productTierType: ProductTierType.TeamsStarter,
},
{
id: "unique-id3",
id: "unique-id3" as OrganizationId,
name: "Family Vault",
productTierType: ProductTierType.Families,
},
{
id: "unique-id4",
id: "unique-id4" as OrganizationId,
name: "Family Vault Trial",
productTierType: ProductTierType.Free,
},
{
id: "unique-id5",
id: "unique-id5" as OrganizationId,
name: "Exciting Enterprises, LLC",
productTierType: ProductTierType.Enterprise,
},
];
export const mockCollections = [
export const mockCollections: CollectionNotificationView[] = [
{
id: "collection-id-01",
id: "collection-id-01" as CollectionId,
name: "A collection for stuff",
organizationId: mockOrganizations[0].id,
},

View File

@@ -3,7 +3,7 @@ import { html } from "lit";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { Theme } from "@bitwarden/common/platform/enums";
import { Option, OrgView, FolderView, I18n, CollectionView } from "../common-types";
import { Option, OrgView, FolderView, I18n, CollectionNotificationView } from "../common-types";
import { Business, Family, Folder, User, CollectionShared } from "../icons";
import { ButtonRow } from "../rows/button-row";
import { selectedCollection as selectedCollectionSignal } from "../signals/selected-collection";
@@ -28,7 +28,7 @@ function getVaultIconByProductTier(productTierType?: ProductTierType): Option["i
const defaultNoneSelectValue = "0";
export type NotificationButtonRowProps = {
collections?: CollectionView[];
collections?: CollectionNotificationView[];
folders?: FolderView[];
i18n: I18n;
organizations?: OrgView[];
@@ -116,7 +116,7 @@ export function NotificationButtonRow({
collections?.length &&
selectedCollectionSignal.get() === defaultNoneSelectValue
) {
selectedCollectionSignal?.set(collections[0].id);
selectedCollectionSignal?.set(collections[0].id!);
}
}

View File

@@ -9,7 +9,7 @@ import {
NotificationType,
} from "../../../notification/abstractions/notification-bar";
import { NotificationCipherData } from "../cipher/types";
import { CollectionView, FolderView, I18n, OrgView } from "../common-types";
import { CollectionNotificationView, FolderView, I18n, OrgView } from "../common-types";
import { themes, spacing } from "../constants/styles";
import { NotificationBody, componentClassPrefix as notificationBodyClassPrefix } from "./body";
@@ -25,7 +25,7 @@ export type NotificationContainerProps = NotificationBarIframeInitData & {
handleEditOrUpdateAction: (e: Event) => void;
} & {
ciphers?: NotificationCipherData[];
collections?: CollectionView[];
collections?: CollectionNotificationView[];
folders?: FolderView[];
headerMessage?: string;
i18n: I18n;

View File

@@ -7,13 +7,13 @@ import {
NotificationType,
NotificationTypes,
} from "../../../notification/abstractions/notification-bar";
import { OrgView, FolderView, I18n, CollectionView } from "../common-types";
import { OrgView, FolderView, I18n, CollectionNotificationView } from "../common-types";
import { spacing } from "../constants/styles";
import { NotificationButtonRow } from "./button-row";
export type NotificationFooterProps = {
collections?: CollectionView[];
collections?: CollectionNotificationView[];
folders?: FolderView[];
i18n: I18n;
isLoading?: boolean;

View File

@@ -1,3 +1,5 @@
import { signal } from "@lit-labs/signals";
export const selectedCollection = signal<string>("0");
import { CollectionId } from "@bitwarden/common/types/guid";
export const selectedCollection = signal<CollectionId>("0" as CollectionId);

View File

@@ -4,7 +4,7 @@ import { NotificationCipherData } from "../../../autofill/content/components/cip
import {
FolderView,
OrgView,
CollectionView,
CollectionNotificationView,
} from "../../../autofill/content/components/common-types";
const NotificationTypes = {
@@ -24,7 +24,7 @@ type NotificationTaskInfo = {
type NotificationBarIframeInitData = {
ciphers?: NotificationCipherData[];
folders?: FolderView[];
collections?: CollectionView[];
collections?: CollectionNotificationView[];
importType?: string;
isVaultLocked?: boolean;
launchTimestamp?: number;

View File

@@ -6,7 +6,7 @@ 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 { CollectionNotificationView, I18n, OrgView } from "../content/components/common-types";
import { AtRiskNotification } from "../content/components/notification/at-risk-password/container";
import { NotificationConfirmationContainer } from "../content/components/notification/confirmation/container";
import { NotificationContainer } from "../content/components/notification/container";
@@ -308,7 +308,7 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
new Promise<NotificationCipherData[]>((resolve) =>
sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, resolve),
),
new Promise<CollectionView[]>((resolve) =>
new Promise<CollectionNotificationView[]>((resolve) =>
sendPlatformMessage({ command: "bgGetCollectionData", orgId }, resolve),
),
]).then(([organizations, folders, ciphers, collections]) => {