diff --git a/apps/browser/src/autofill/background/abstractions/notification.background.ts b/apps/browser/src/autofill/background/abstractions/notification.background.ts index f2152b44862..e161aa3079d 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -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; + }: BackgroundOnMessageHandlerParams) => Promise; bgCloseNotificationBar: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; bgOpenAtRiskPasswords: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; bgAdjustNotificationBar: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 3f6e93d8454..08a6945aaae 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -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 { 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 { + ): Promise { const collections = await firstValueFrom( this.accountService.activeAccount$.pipe( getUserId, switchMap((userId) => this.collectionService.decryptedCollections$(userId)), map((collections) => - collections.reduce((acc, collection) => { + collections.reduce((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); } diff --git a/apps/browser/src/autofill/content/components/common-types.ts b/apps/browser/src/autofill/content/components/common-types.ts index 5967f6205a9..2971ec27a39 100644 --- a/apps/browser/src/autofill/content/components/common-types.ts +++ b/apps/browser/src/autofill/content/components/common-types.ts @@ -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; diff --git a/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts b/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts index 3451029a01a..dbf335e0e26 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts @@ -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, }, diff --git a/apps/browser/src/autofill/content/components/notification/button-row.ts b/apps/browser/src/autofill/content/components/notification/button-row.ts index 04b79c1951a..535e5db3685 100644 --- a/apps/browser/src/autofill/content/components/notification/button-row.ts +++ b/apps/browser/src/autofill/content/components/notification/button-row.ts @@ -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!); } } diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts index 0c70e0da63c..66511e9d2f2 100644 --- a/apps/browser/src/autofill/content/components/notification/container.ts +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -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; diff --git a/apps/browser/src/autofill/content/components/notification/footer.ts b/apps/browser/src/autofill/content/components/notification/footer.ts index d37547a6fae..1b7e234291f 100644 --- a/apps/browser/src/autofill/content/components/notification/footer.ts +++ b/apps/browser/src/autofill/content/components/notification/footer.ts @@ -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; diff --git a/apps/browser/src/autofill/content/components/signals/selected-collection.ts b/apps/browser/src/autofill/content/components/signals/selected-collection.ts index 7e6a8d69e3a..fd1558f39ab 100644 --- a/apps/browser/src/autofill/content/components/signals/selected-collection.ts +++ b/apps/browser/src/autofill/content/components/signals/selected-collection.ts @@ -1,3 +1,5 @@ import { signal } from "@lit-labs/signals"; -export const selectedCollection = signal("0"); +import { CollectionId } from "@bitwarden/common/types/guid"; + +export const selectedCollection = signal("0" as CollectionId); diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index 934aa4a2571..830d470746f 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -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; diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 6e20a07f81f..5ce78a2701e 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -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((resolve) => sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, resolve), ), - new Promise((resolve) => + new Promise((resolve) => sendPlatformMessage({ command: "bgGetCollectionData", orgId }, resolve), ), ]).then(([organizations, folders, ciphers, collections]) => {