mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +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,
|
TabsV2Component,
|
||||||
RemovePasswordComponent,
|
RemovePasswordComponent,
|
||||||
],
|
],
|
||||||
exports: [],
|
exports: [CalloutModule],
|
||||||
providers: [CurrencyPipe, DatePipe],
|
providers: [CurrencyPipe, DatePipe],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||||
import { IconButtonModule, ItemModule, MenuModule } from "@bitwarden/components";
|
import { IconButtonModule, ItemModule, MenuModule } from "@bitwarden/components";
|
||||||
import { CopyableCipherFields } from "@bitwarden/sdk-internal";
|
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";
|
import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service";
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ type CipherItem = {
|
|||||||
/** Translation key for the respective value */
|
/** Translation key for the respective value */
|
||||||
key: string;
|
key: string;
|
||||||
/** Property key on `CipherView` to retrieve the copy value */
|
/** Property key on `CipherView` to retrieve the copy value */
|
||||||
field: CopyAction;
|
field: CopyFieldAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
// 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
|
* singleCopyableLogin uses appCopyField instead of appCopyClick. This allows for the TOTP
|
||||||
* code to be copied correctly. See #14167
|
* code to be copied correctly. See #14167
|
||||||
*/
|
*/
|
||||||
get singleCopyableLogin() {
|
get singleCopyableLogin(): CipherItem | null {
|
||||||
const loginItems: CipherItem[] = [
|
const loginItems: CipherItem[] = [
|
||||||
{ key: "copyUsername", field: "username" },
|
{ key: "copyUsername", field: "username" },
|
||||||
{ key: "copyPassword", field: "password" },
|
{ key: "copyPassword", field: "password" },
|
||||||
@@ -62,7 +62,7 @@ export class ItemCopyActionsComponent {
|
|||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
key: this.i18nService.t("copyUsername"),
|
key: this.i18nService.t("copyUsername"),
|
||||||
field: "username",
|
field: "username" as const,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return this.findSingleCopyableItem(loginItems);
|
return this.findSingleCopyableItem(loginItems);
|
||||||
|
|||||||
@@ -27,10 +27,10 @@
|
|||||||
<app-vault-icon [cipher]="cipher"></app-vault-icon>
|
<app-vault-icon [cipher]="cipher"></app-vault-icon>
|
||||||
</div>
|
</div>
|
||||||
<span data-testid="item-name">{{ cipher.name }}</span>
|
<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>
|
<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>
|
</button>
|
||||||
<bit-item-action slot="end">
|
<bit-item-action slot="end">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
|||||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.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 {
|
import {
|
||||||
DialogService,
|
DialogService,
|
||||||
IconButtonModule,
|
IconButtonModule,
|
||||||
@@ -71,12 +74,14 @@ export class ArchiveComponent {
|
|||||||
switchMap((userId) => this.cipherArchiveService.archivedCiphers$(userId)),
|
switchMap((userId) => this.cipherArchiveService.archivedCiphers$(userId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
protected CipherViewLikeUtils = CipherViewLikeUtils;
|
||||||
|
|
||||||
protected loading$ = this.archivedCiphers$.pipe(
|
protected loading$ = this.archivedCiphers$.pipe(
|
||||||
map(() => false),
|
map(() => false),
|
||||||
startWith(true),
|
startWith(true),
|
||||||
);
|
);
|
||||||
|
|
||||||
async view(cipher: CipherView) {
|
async view(cipher: CipherViewLike) {
|
||||||
if (!(await this.canInteract(cipher))) {
|
if (!(await this.canInteract(cipher))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -86,7 +91,7 @@ export class ArchiveComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async edit(cipher: CipherView) {
|
async edit(cipher: CipherViewLike) {
|
||||||
if (!(await this.canInteract(cipher))) {
|
if (!(await this.canInteract(cipher))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -96,7 +101,7 @@ export class ArchiveComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(cipher: CipherView) {
|
async delete(cipher: CipherViewLike) {
|
||||||
if (!(await this.canInteract(cipher))) {
|
if (!(await this.canInteract(cipher))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -113,7 +118,7 @@ export class ArchiveComponent {
|
|||||||
const activeUserId = await firstValueFrom(this.userId$);
|
const activeUserId = await firstValueFrom(this.userId$);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.cipherService.softDeleteWithServer(cipher.id, activeUserId);
|
await this.cipherService.softDeleteWithServer(cipher.id as string, activeUserId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
return;
|
return;
|
||||||
@@ -125,13 +130,16 @@ export class ArchiveComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async unarchive(cipher: CipherView) {
|
async unarchive(cipher: CipherViewLike) {
|
||||||
if (!(await this.canInteract(cipher))) {
|
if (!(await this.canInteract(cipher))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const activeUserId = await firstValueFrom(this.userId$);
|
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({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
@@ -139,12 +147,12 @@ export class ArchiveComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async clone(cipher: CipherView) {
|
async clone(cipher: CipherViewLike) {
|
||||||
if (!(await this.canInteract(cipher))) {
|
if (!(await this.canInteract(cipher))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cipher.login?.hasFido2Credentials) {
|
if (CipherViewLikeUtils.hasFido2Credentials(cipher)) {
|
||||||
const confirmed = await this.dialogService.openSimpleDialog({
|
const confirmed = await this.dialogService.openSimpleDialog({
|
||||||
title: { key: "passkeyNotCopied" },
|
title: { key: "passkeyNotCopied" },
|
||||||
content: { key: "passkeyNotCopiedAlert" },
|
content: { key: "passkeyNotCopiedAlert" },
|
||||||
@@ -171,8 +179,8 @@ export class ArchiveComponent {
|
|||||||
* @param cipher
|
* @param cipher
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private canInteract(cipher: CipherView) {
|
private canInteract(cipher: CipherViewLike) {
|
||||||
if (cipher.decryptionFailure) {
|
if (CipherViewLikeUtils.decryptionFailure(cipher)) {
|
||||||
DecryptionFailureDialogComponent.open(this.dialogService, {
|
DecryptionFailureDialogComponent.open(this.dialogService, {
|
||||||
cipherIds: [cipher.id as CipherId],
|
cipherIds: [cipher.id as CipherId],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,11 +25,11 @@
|
|||||||
[appA11yTitle]="orgIconTooltip(cipher)"
|
[appA11yTitle]="orgIconTooltip(cipher)"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
*ngIf="cipher.hasAttachments"
|
*ngIf="hasAttachments(cipher)"
|
||||||
class="bwi bwi-paperclip bwi-sm"
|
class="bwi bwi-paperclip bwi-sm"
|
||||||
[appA11yTitle]="'attachments' | i18n"
|
[appA11yTitle]="'attachments' | i18n"
|
||||||
></i>
|
></i>
|
||||||
<span slot="secondary">{{ cipher.subTitle }}</span>
|
<span slot="secondary">{{ getSubtitle(cipher) }}</span>
|
||||||
</button>
|
</button>
|
||||||
<ng-container slot="end" *ngIf="cipher.permissions.restore">
|
<ng-container slot="end" *ngIf="cipher.permissions.restore">
|
||||||
<bit-item-action>
|
<bit-item-action>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
bitMenuItem
|
bitMenuItem
|
||||||
(click)="restore(cipher)"
|
(click)="restore(cipher)"
|
||||||
*ngIf="!cipher.decryptionFailure"
|
*ngIf="!hasDecryptionFailure(cipher)"
|
||||||
>
|
>
|
||||||
{{ "restore" | i18n }}
|
{{ "restore" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
|||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { CipherId } from "@bitwarden/common/types/guid";
|
import { CipherId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
|
||||||
import {
|
import {
|
||||||
DialogService,
|
DialogService,
|
||||||
IconButtonModule,
|
IconButtonModule,
|
||||||
@@ -85,10 +84,40 @@ export class TrashListItemsContainerComponent {
|
|||||||
return collections[0]?.name;
|
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 {
|
try {
|
||||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
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"]);
|
await this.router.navigate(["/trash"]);
|
||||||
this.toastService.showToast({
|
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);
|
const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher);
|
||||||
|
|
||||||
if (!repromptPassed) {
|
if (!repromptPassed) {
|
||||||
@@ -120,7 +149,7 @@ export class TrashListItemsContainerComponent {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
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"]);
|
await this.router.navigate(["/trash"]);
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
@@ -133,8 +162,9 @@ export class TrashListItemsContainerComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onViewCipher(cipher: CipherView) {
|
async onViewCipher(cipher: PopupCipherViewLike) {
|
||||||
if (cipher.decryptionFailure) {
|
// CipherListView doesn't have decryptionFailure, so we use optional chaining
|
||||||
|
if ("decryptionFailure" in cipher && cipher.decryptionFailure) {
|
||||||
DecryptionFailureDialogComponent.open(this.dialogService, {
|
DecryptionFailureDialogComponent.open(this.dialogService, {
|
||||||
cipherIds: [cipher.id as CipherId],
|
cipherIds: [cipher.id as CipherId],
|
||||||
});
|
});
|
||||||
@@ -147,7 +177,7 @@ export class TrashListItemsContainerComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.router.navigate(["/view-cipher"], {
|
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",
|
"extends": "../../tsconfig.base",
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictTemplates": true
|
||||||
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src",
|
"src",
|
||||||
"../../libs/common/src/autofill/constants",
|
"../../libs/common/src/autofill/constants",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Directive, Input, OnDestroy, TemplateRef, ViewContainerRef } from "@angular/core";
|
import { Directive, Input, OnDestroy, TemplateRef, ViewContainerRef } from "@angular/core";
|
||||||
import { Subject, takeUntil } from "rxjs";
|
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 { 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.
|
* 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
|
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||||
// eslint-disable-next-line @angular-eslint/prefer-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.viewContainer.clear();
|
||||||
|
|
||||||
this.cipherAuthorizationService
|
this.cipherAuthorizationService
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class CopyCipherFieldDirective implements OnChanges {
|
|||||||
alias: "appCopyField",
|
alias: "appCopyField",
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
action!: Exclude<CopyAction, "hiddenField">;
|
action!: CopyAction;
|
||||||
|
|
||||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ export {
|
|||||||
AtRiskPasswordCalloutData,
|
AtRiskPasswordCalloutData,
|
||||||
} from "./services/at-risk-password-callout.service";
|
} from "./services/at-risk-password-callout.service";
|
||||||
export { PasswordRepromptService } from "./services/password-reprompt.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 { CopyCipherFieldDirective } from "./components/copy-cipher-field.directive";
|
||||||
export { OrgIconDirective } from "./components/org-icon.directive";
|
export { OrgIconDirective } from "./components/org-icon.directive";
|
||||||
export { CanDeleteCipherDirective } from "./components/can-delete-cipher.directive";
|
export { CanDeleteCipherDirective } from "./components/can-delete-cipher.directive";
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ export type CopyAction =
|
|||||||
| "publicKey"
|
| "publicKey"
|
||||||
| "keyFingerprint";
|
| "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 = {
|
type CopyActionInfo = {
|
||||||
/**
|
/**
|
||||||
* The i18n key for the type of field being copied. Will be used to display a toast message.
|
* 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