[CP-30] Added creditCardNumber pipe for viewing saved card numbers properly (#590)

Co-authored-by: Hinton <oscar@oscarhinton.com>
This commit is contained in:
Anthony Garera 2022-05-02 10:52:53 -04:00 committed by GitHub
parent 2e2849b4de
commit 7e05089d73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 76 additions and 9 deletions

View File

@ -23,6 +23,7 @@ import { StopPropDirective } from "./directives/stop-prop.directive";
import { TrueFalseValueDirective } from "./directives/true-false-value.directive";
import { ColorPasswordCountPipe } from "./pipes/color-password-count.pipe";
import { ColorPasswordPipe } from "./pipes/color-password.pipe";
import { CreditCardNumberPipe } from "./pipes/credit-card-number.pipe";
import { EllipsisPipe } from "./pipes/ellipsis.pipe";
import { I18nPipe } from "./pipes/i18n.pipe";
import { SearchCiphersPipe } from "./pipes/search-ciphers.pipe";
@ -44,15 +45,19 @@ import { UserNamePipe } from "./pipes/user-name.pipe";
A11yInvalidDirective,
A11yTitleDirective,
ApiActionDirective,
AvatarComponent,
AutofocusDirective,
AvatarComponent,
BlurClickDirective,
BoxRowDirective,
CalloutComponent,
ColorPasswordCountPipe,
ColorPasswordPipe,
CreditCardNumberPipe,
EllipsisPipe,
ExportScopeCalloutComponent,
FallbackSrcDirective,
I18nPipe,
IconComponent,
InputStripSpacesDirective,
InputVerbatimDirective,
NotPremiumDirective,
@ -63,24 +68,25 @@ import { UserNamePipe } from "./pipes/user-name.pipe";
StopPropDirective,
TrueFalseValueDirective,
UserNamePipe,
CalloutComponent,
IconComponent,
ExportScopeCalloutComponent,
],
exports: [
A11yInvalidDirective,
A11yTitleDirective,
ApiActionDirective,
AvatarComponent,
AutofocusDirective,
AvatarComponent,
BitwardenToastModule,
BlurClickDirective,
BoxRowDirective,
CalloutComponent,
ColorPasswordCountPipe,
ColorPasswordPipe,
CreditCardNumberPipe,
EllipsisPipe,
ExportScopeCalloutComponent,
FallbackSrcDirective,
I18nPipe,
IconComponent,
InputStripSpacesDirective,
InputVerbatimDirective,
NotPremiumDirective,
@ -91,10 +97,7 @@ import { UserNamePipe } from "./pipes/user-name.pipe";
StopPropDirective,
TrueFalseValueDirective,
UserNamePipe,
CalloutComponent,
IconComponent,
ExportScopeCalloutComponent,
],
providers: [UserNamePipe, SearchPipe, I18nPipe, DatePipe],
providers: [CreditCardNumberPipe, DatePipe, I18nPipe, SearchPipe, UserNamePipe],
})
export class JslibModule {}

View File

@ -0,0 +1,64 @@
import { Pipe, PipeTransform } from "@angular/core";
interface CardRuleEntry {
cardLength: number;
blocks: number[];
}
// See https://baymard.com/checkout-usability/credit-card-patterns for
// all possible credit card spacing patterns. For now, we just handle
// the below.
const numberFormats: Record<string, CardRuleEntry[]> = {
Visa: [{ cardLength: 16, blocks: [4, 4, 4, 4] }],
Mastercard: [{ cardLength: 16, blocks: [4, 4, 4, 4] }],
Maestro: [
{ cardLength: 16, blocks: [4, 4, 4, 4] },
{ cardLength: 13, blocks: [4, 4, 5] },
{ cardLength: 15, blocks: [4, 6, 5] },
{ cardLength: 19, blocks: [4, 4, 4, 4, 3] },
],
Discover: [{ cardLength: 16, blocks: [4, 4, 4, 4] }],
"Diners Club": [{ cardLength: 14, blocks: [4, 6, 4] }],
JCB: [{ cardLength: 16, blocks: [4, 4, 4, 4] }],
UnionPay: [
{ cardLength: 16, blocks: [4, 4, 4, 4] },
{ cardLength: 19, blocks: [6, 13] },
],
Amex: [{ cardLength: 15, blocks: [4, 6, 5] }],
Other: [{ cardLength: 16, blocks: [4, 4, 4, 4] }],
};
@Pipe({ name: "creditCardNumber" })
export class CreditCardNumberPipe implements PipeTransform {
transform(creditCardNumber: string, brand: string): string {
let rules = numberFormats[brand];
if (rules == null) {
rules = numberFormats["Other"];
}
const cardLength = creditCardNumber.length;
let matchingRule = rules.find((r) => r.cardLength == cardLength);
if (matchingRule == null) {
matchingRule = rules[0];
}
const blocks = matchingRule.blocks;
let chunks: string[] = [];
let total = 0;
blocks.forEach((c) => {
chunks.push(creditCardNumber.slice(total, total + c));
total += c;
});
// Append the remaining part
if (cardLength > total) {
chunks.push(creditCardNumber.slice(total));
}
return chunks.join(" ");
}
}