mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[CL-761] Enable strict template typechecking (#17334)
* enable strict template typechecking * add callout component to module * fixing popup action types * fixing cipher item copy types * fix archive cipher type * fixing trash list items types * fix remaining trash list item type errors * use CipherViewLike as correct type * change popup back directive to attribute selector * allow undefined in popupBackAction handler * Remove undefined from type * fix error with firefox commercial build --------- Co-authored-by: Vicki League <vleague@bitwarden.com>
This commit is contained in:
@@ -92,7 +92,7 @@ import "../platform/popup/locales";
|
||||
TabsV2Component,
|
||||
RemovePasswordComponent,
|
||||
],
|
||||
exports: [],
|
||||
exports: [CalloutModule],
|
||||
providers: [CurrencyPipe, DatePipe],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { IconButtonModule, ItemModule, MenuModule } from "@bitwarden/components";
|
||||
import { CopyableCipherFields } from "@bitwarden/sdk-internal";
|
||||
import { CopyAction, CopyCipherFieldDirective } from "@bitwarden/vault";
|
||||
import { CopyFieldAction, CopyCipherFieldDirective } from "@bitwarden/vault";
|
||||
|
||||
import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service";
|
||||
|
||||
@@ -18,7 +18,7 @@ type CipherItem = {
|
||||
/** Translation key for the respective value */
|
||||
key: string;
|
||||
/** Property key on `CipherView` to retrieve the copy value */
|
||||
field: CopyAction;
|
||||
field: CopyFieldAction;
|
||||
};
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
@@ -48,7 +48,7 @@ export class ItemCopyActionsComponent {
|
||||
* singleCopyableLogin uses appCopyField instead of appCopyClick. This allows for the TOTP
|
||||
* code to be copied correctly. See #14167
|
||||
*/
|
||||
get singleCopyableLogin() {
|
||||
get singleCopyableLogin(): CipherItem | null {
|
||||
const loginItems: CipherItem[] = [
|
||||
{ key: "copyUsername", field: "username" },
|
||||
{ key: "copyPassword", field: "password" },
|
||||
@@ -62,7 +62,7 @@ export class ItemCopyActionsComponent {
|
||||
) {
|
||||
return {
|
||||
key: this.i18nService.t("copyUsername"),
|
||||
field: "username",
|
||||
field: "username" as const,
|
||||
};
|
||||
}
|
||||
return this.findSingleCopyableItem(loginItems);
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
<app-vault-icon [cipher]="cipher"></app-vault-icon>
|
||||
</div>
|
||||
<span data-testid="item-name">{{ cipher.name }}</span>
|
||||
@if (cipher.hasAttachments) {
|
||||
@if (CipherViewLikeUtils.hasAttachments(cipher)) {
|
||||
<i class="bwi bwi-paperclip bwi-sm" [appA11yTitle]="'attachments' | i18n"></i>
|
||||
}
|
||||
<span slot="secondary">{{ cipher.subTitle }}</span>
|
||||
<span slot="secondary">{{ CipherViewLikeUtils.subtitle(cipher) }}</span>
|
||||
</button>
|
||||
<bit-item-action slot="end">
|
||||
<button
|
||||
|
||||
@@ -11,7 +11,10 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import {
|
||||
CipherViewLike,
|
||||
CipherViewLikeUtils,
|
||||
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import {
|
||||
DialogService,
|
||||
IconButtonModule,
|
||||
@@ -71,12 +74,14 @@ export class ArchiveComponent {
|
||||
switchMap((userId) => this.cipherArchiveService.archivedCiphers$(userId)),
|
||||
);
|
||||
|
||||
protected CipherViewLikeUtils = CipherViewLikeUtils;
|
||||
|
||||
protected loading$ = this.archivedCiphers$.pipe(
|
||||
map(() => false),
|
||||
startWith(true),
|
||||
);
|
||||
|
||||
async view(cipher: CipherView) {
|
||||
async view(cipher: CipherViewLike) {
|
||||
if (!(await this.canInteract(cipher))) {
|
||||
return;
|
||||
}
|
||||
@@ -86,7 +91,7 @@ export class ArchiveComponent {
|
||||
});
|
||||
}
|
||||
|
||||
async edit(cipher: CipherView) {
|
||||
async edit(cipher: CipherViewLike) {
|
||||
if (!(await this.canInteract(cipher))) {
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +101,7 @@ export class ArchiveComponent {
|
||||
});
|
||||
}
|
||||
|
||||
async delete(cipher: CipherView) {
|
||||
async delete(cipher: CipherViewLike) {
|
||||
if (!(await this.canInteract(cipher))) {
|
||||
return;
|
||||
}
|
||||
@@ -113,7 +118,7 @@ export class ArchiveComponent {
|
||||
const activeUserId = await firstValueFrom(this.userId$);
|
||||
|
||||
try {
|
||||
await this.cipherService.softDeleteWithServer(cipher.id, activeUserId);
|
||||
await this.cipherService.softDeleteWithServer(cipher.id as string, activeUserId);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
return;
|
||||
@@ -125,13 +130,16 @@ export class ArchiveComponent {
|
||||
});
|
||||
}
|
||||
|
||||
async unarchive(cipher: CipherView) {
|
||||
async unarchive(cipher: CipherViewLike) {
|
||||
if (!(await this.canInteract(cipher))) {
|
||||
return;
|
||||
}
|
||||
const activeUserId = await firstValueFrom(this.userId$);
|
||||
|
||||
await this.cipherArchiveService.unarchiveWithServer(cipher.id as CipherId, activeUserId);
|
||||
await this.cipherArchiveService.unarchiveWithServer(
|
||||
cipher.id as unknown as CipherId,
|
||||
activeUserId,
|
||||
);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
@@ -139,12 +147,12 @@ export class ArchiveComponent {
|
||||
});
|
||||
}
|
||||
|
||||
async clone(cipher: CipherView) {
|
||||
async clone(cipher: CipherViewLike) {
|
||||
if (!(await this.canInteract(cipher))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cipher.login?.hasFido2Credentials) {
|
||||
if (CipherViewLikeUtils.hasFido2Credentials(cipher)) {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "passkeyNotCopied" },
|
||||
content: { key: "passkeyNotCopiedAlert" },
|
||||
@@ -171,8 +179,8 @@ export class ArchiveComponent {
|
||||
* @param cipher
|
||||
* @private
|
||||
*/
|
||||
private canInteract(cipher: CipherView) {
|
||||
if (cipher.decryptionFailure) {
|
||||
private canInteract(cipher: CipherViewLike) {
|
||||
if (CipherViewLikeUtils.decryptionFailure(cipher)) {
|
||||
DecryptionFailureDialogComponent.open(this.dialogService, {
|
||||
cipherIds: [cipher.id as CipherId],
|
||||
});
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
[appA11yTitle]="orgIconTooltip(cipher)"
|
||||
></i>
|
||||
<i
|
||||
*ngIf="cipher.hasAttachments"
|
||||
*ngIf="hasAttachments(cipher)"
|
||||
class="bwi bwi-paperclip bwi-sm"
|
||||
[appA11yTitle]="'attachments' | i18n"
|
||||
></i>
|
||||
<span slot="secondary">{{ cipher.subTitle }}</span>
|
||||
<span slot="secondary">{{ getSubtitle(cipher) }}</span>
|
||||
</button>
|
||||
<ng-container slot="end" *ngIf="cipher.permissions.restore">
|
||||
<bit-item-action>
|
||||
@@ -45,7 +45,7 @@
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="restore(cipher)"
|
||||
*ngIf="!cipher.decryptionFailure"
|
||||
*ngIf="!hasDecryptionFailure(cipher)"
|
||||
>
|
||||
{{ "restore" | i18n }}
|
||||
</button>
|
||||
|
||||
@@ -12,7 +12,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import {
|
||||
DialogService,
|
||||
IconButtonModule,
|
||||
@@ -85,10 +84,40 @@ export class TrashListItemsContainerComponent {
|
||||
return collections[0]?.name;
|
||||
}
|
||||
|
||||
async restore(cipher: CipherView) {
|
||||
/**
|
||||
* Check if a cipher has attachments. CipherView has a hasAttachments getter,
|
||||
* while CipherListView has an attachments count property.
|
||||
*/
|
||||
hasAttachments(cipher: PopupCipherViewLike): boolean {
|
||||
if ("hasAttachments" in cipher) {
|
||||
return cipher.hasAttachments;
|
||||
}
|
||||
return cipher.attachments > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subtitle for a cipher. CipherView has a subTitle getter,
|
||||
* while CipherListView has a subtitle property.
|
||||
*/
|
||||
getSubtitle(cipher: PopupCipherViewLike): string | undefined {
|
||||
if ("subTitle" in cipher) {
|
||||
return cipher.subTitle;
|
||||
}
|
||||
return cipher.subtitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a cipher has a decryption failure. CipherView has this property,
|
||||
* while CipherListView does not.
|
||||
*/
|
||||
hasDecryptionFailure(cipher: PopupCipherViewLike): boolean {
|
||||
return "decryptionFailure" in cipher && cipher.decryptionFailure;
|
||||
}
|
||||
|
||||
async restore(cipher: PopupCipherViewLike) {
|
||||
try {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
await this.cipherService.restoreWithServer(cipher.id, activeUserId);
|
||||
await this.cipherService.restoreWithServer(cipher.id as string, activeUserId);
|
||||
|
||||
await this.router.navigate(["/trash"]);
|
||||
this.toastService.showToast({
|
||||
@@ -101,7 +130,7 @@ export class TrashListItemsContainerComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async delete(cipher: CipherView) {
|
||||
async delete(cipher: PopupCipherViewLike) {
|
||||
const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher);
|
||||
|
||||
if (!repromptPassed) {
|
||||
@@ -120,7 +149,7 @@ export class TrashListItemsContainerComponent {
|
||||
|
||||
try {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
await this.cipherService.deleteWithServer(cipher.id, activeUserId);
|
||||
await this.cipherService.deleteWithServer(cipher.id as string, activeUserId);
|
||||
|
||||
await this.router.navigate(["/trash"]);
|
||||
this.toastService.showToast({
|
||||
@@ -133,8 +162,9 @@ export class TrashListItemsContainerComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async onViewCipher(cipher: CipherView) {
|
||||
if (cipher.decryptionFailure) {
|
||||
async onViewCipher(cipher: PopupCipherViewLike) {
|
||||
// CipherListView doesn't have decryptionFailure, so we use optional chaining
|
||||
if ("decryptionFailure" in cipher && cipher.decryptionFailure) {
|
||||
DecryptionFailureDialogComponent.open(this.dialogService, {
|
||||
cipherIds: [cipher.id as CipherId],
|
||||
});
|
||||
@@ -147,7 +177,7 @@ export class TrashListItemsContainerComponent {
|
||||
}
|
||||
|
||||
await this.router.navigate(["/view-cipher"], {
|
||||
queryParams: { cipherId: cipher.id, type: cipher.type },
|
||||
queryParams: { cipherId: cipher.id as string, type: cipher.type },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base",
|
||||
"angularCompilerOptions": {
|
||||
"strictTemplates": true
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"../../libs/common/src/autofill/constants",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Directive, Input, OnDestroy, TemplateRef, ViewContainerRef } from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
|
||||
/**
|
||||
* Only shows the element if the user can delete the cipher.
|
||||
@@ -15,7 +15,7 @@ export class CanDeleteCipherDirective implements OnDestroy {
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@Input("appCanDeleteCipher") set cipher(cipher: CipherView) {
|
||||
@Input("appCanDeleteCipher") set cipher(cipher: CipherViewLike) {
|
||||
this.viewContainer.clear();
|
||||
|
||||
this.cipherAuthorizationService
|
||||
|
||||
@@ -36,7 +36,7 @@ export class CopyCipherFieldDirective implements OnChanges {
|
||||
alias: "appCopyField",
|
||||
required: true,
|
||||
})
|
||||
action!: Exclude<CopyAction, "hiddenField">;
|
||||
action!: CopyAction;
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
|
||||
@@ -3,7 +3,11 @@ export {
|
||||
AtRiskPasswordCalloutData,
|
||||
} from "./services/at-risk-password-callout.service";
|
||||
export { PasswordRepromptService } from "./services/password-reprompt.service";
|
||||
export { CopyCipherFieldService, CopyAction } from "./services/copy-cipher-field.service";
|
||||
export {
|
||||
CopyCipherFieldService,
|
||||
CopyAction,
|
||||
CopyFieldAction,
|
||||
} from "./services/copy-cipher-field.service";
|
||||
export { CopyCipherFieldDirective } from "./components/copy-cipher-field.directive";
|
||||
export { OrgIconDirective } from "./components/org-icon.directive";
|
||||
export { CanDeleteCipherDirective } from "./components/can-delete-cipher.directive";
|
||||
|
||||
@@ -35,6 +35,12 @@ export type CopyAction =
|
||||
| "publicKey"
|
||||
| "keyFingerprint";
|
||||
|
||||
/**
|
||||
* Copy actions that can be used with the appCopyField directive.
|
||||
* Excludes "hiddenField" which requires special handling.
|
||||
*/
|
||||
export type CopyFieldAction = Exclude<CopyAction, "hiddenField">;
|
||||
|
||||
type CopyActionInfo = {
|
||||
/**
|
||||
* The i18n key for the type of field being copied. Will be used to display a toast message.
|
||||
|
||||
Reference in New Issue
Block a user