@evervault/react-native 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/Card/Cvc.d.ts +9 -2
- package/build/Card/Cvc.d.ts.map +1 -1
- package/build/Card/Number.d.ts +9 -2
- package/build/Card/Number.d.ts.map +1 -1
- package/build/Card/index.d.ts +2 -2
- package/build/Input.d.ts +1 -0
- package/build/Input.d.ts.map +1 -1
- package/build/ThreeDSecure/event.d.ts +11 -0
- package/build/ThreeDSecure/event.d.ts.map +1 -0
- package/build/ThreeDSecure/session.d.ts +4 -4
- package/build/ThreeDSecure/session.d.ts.map +1 -1
- package/build/ThreeDSecure/types.d.ts +15 -3
- package/build/ThreeDSecure/types.d.ts.map +1 -1
- package/build/ThreeDSecure/useThreeDSecure.d.ts +4 -1
- package/build/ThreeDSecure/useThreeDSecure.d.ts.map +1 -1
- package/build/index.cjs.js +149 -110
- package/build/index.cjs.js.map +1 -1
- package/build/index.esm.js +149 -110
- package/package.json +1 -1
- package/src/Card/Cvc.tsx +10 -3
- package/src/Card/Number.test.tsx +24 -3
- package/src/Card/Number.tsx +11 -4
- package/src/Input.test.tsx +84 -0
- package/src/Input.tsx +73 -47
- package/src/ThreeDSecure/event.ts +19 -0
- package/src/ThreeDSecure/session.test.ts +219 -24
- package/src/ThreeDSecure/session.ts +76 -24
- package/src/ThreeDSecure/types.ts +17 -4
- package/src/ThreeDSecure/useThreeDSecure.test.tsx +112 -1
- package/src/ThreeDSecure/useThreeDSecure.ts +23 -6
package/build/index.esm.js
CHANGED
|
@@ -588,50 +588,6 @@ function useController(props) {
|
|
|
588
588
|
}), [field, formState, fieldState]);
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
-
/**
|
|
592
|
-
* Component based on `useController` hook to work with controlled component.
|
|
593
|
-
*
|
|
594
|
-
* @remarks
|
|
595
|
-
* [API](https://react-hook-form.com/docs/usecontroller/controller) • [Demo](https://codesandbox.io/s/react-hook-form-v6-controller-ts-jwyzw) • [Video](https://www.youtube.com/watch?v=N2UNk_UCVyA)
|
|
596
|
-
*
|
|
597
|
-
* @param props - the path name to the form field value, and validation rules.
|
|
598
|
-
*
|
|
599
|
-
* @returns provide field handler functions, field and form state.
|
|
600
|
-
*
|
|
601
|
-
* @example
|
|
602
|
-
* ```tsx
|
|
603
|
-
* function App() {
|
|
604
|
-
* const { control } = useForm<FormValues>({
|
|
605
|
-
* defaultValues: {
|
|
606
|
-
* test: ""
|
|
607
|
-
* }
|
|
608
|
-
* });
|
|
609
|
-
*
|
|
610
|
-
* return (
|
|
611
|
-
* <form>
|
|
612
|
-
* <Controller
|
|
613
|
-
* control={control}
|
|
614
|
-
* name="test"
|
|
615
|
-
* render={({ field: { onChange, onBlur, value, ref }, formState, fieldState }) => (
|
|
616
|
-
* <>
|
|
617
|
-
* <input
|
|
618
|
-
* onChange={onChange} // send value to hook form
|
|
619
|
-
* onBlur={onBlur} // notify when input is touched
|
|
620
|
-
* value={value} // return updated value
|
|
621
|
-
* ref={ref} // set ref for focus management
|
|
622
|
-
* />
|
|
623
|
-
* <p>{formState.isSubmitted ? "submitted" : ""}</p>
|
|
624
|
-
* <p>{fieldState.isTouched ? "touched" : ""}</p>
|
|
625
|
-
* </>
|
|
626
|
-
* )}
|
|
627
|
-
* />
|
|
628
|
-
* </form>
|
|
629
|
-
* );
|
|
630
|
-
* }
|
|
631
|
-
* ```
|
|
632
|
-
*/
|
|
633
|
-
const Controller = (props) => props.render(useController(props));
|
|
634
|
-
|
|
635
591
|
var appendErrors = (name, validateAllFieldCriteria, errors, type, message) => validateAllFieldCriteria
|
|
636
592
|
? {
|
|
637
593
|
...errors[name],
|
|
@@ -7408,44 +7364,69 @@ function useForwardedInputRef(ref) {
|
|
|
7408
7364
|
return inputRef;
|
|
7409
7365
|
}
|
|
7410
7366
|
function mask(format) {
|
|
7411
|
-
|
|
7367
|
+
const maskArray = [];
|
|
7368
|
+
let isObfuscated = false;
|
|
7369
|
+
format.split("").forEach((char) => {
|
|
7370
|
+
if (char === "[") {
|
|
7371
|
+
isObfuscated = true;
|
|
7372
|
+
return;
|
|
7373
|
+
}
|
|
7374
|
+
else if (char === "]") {
|
|
7375
|
+
isObfuscated = false;
|
|
7376
|
+
return;
|
|
7377
|
+
}
|
|
7378
|
+
let value = char;
|
|
7412
7379
|
if (char === "9") {
|
|
7413
|
-
|
|
7380
|
+
value = isObfuscated ? [/\d/] : /\d/;
|
|
7414
7381
|
}
|
|
7415
|
-
|
|
7382
|
+
maskArray.push(value);
|
|
7416
7383
|
});
|
|
7384
|
+
return maskArray;
|
|
7417
7385
|
}
|
|
7418
|
-
const EvervaultInput = forwardRef(function EvervaultInput({ name, mask, ...props }, ref) {
|
|
7386
|
+
const EvervaultInput = forwardRef(function EvervaultInput({ name, mask, obfuscateValue, ...props }, ref) {
|
|
7419
7387
|
const { validationMode } = useContext(EvervaultInputContext);
|
|
7420
7388
|
const inputRef = useForwardedInputRef(ref);
|
|
7421
7389
|
const methods = useFormContext();
|
|
7422
|
-
|
|
7390
|
+
const { field, fieldState } = useController({
|
|
7391
|
+
control: methods.control,
|
|
7392
|
+
name,
|
|
7393
|
+
shouldUnregister: true,
|
|
7394
|
+
});
|
|
7395
|
+
const obfuscationCharacter = useMemo(() => {
|
|
7396
|
+
if (typeof obfuscateValue === "string") {
|
|
7397
|
+
return obfuscateValue;
|
|
7398
|
+
}
|
|
7399
|
+
else {
|
|
7400
|
+
return "•";
|
|
7401
|
+
}
|
|
7402
|
+
}, [obfuscateValue]);
|
|
7403
|
+
return (jsx(MaskInput
|
|
7404
|
+
// Overridable props
|
|
7405
|
+
, {
|
|
7423
7406
|
// Overridable props
|
|
7424
|
-
,
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
// Remove unwanted props
|
|
7448
|
-
defaultValue: undefined, onChange: undefined })) }));
|
|
7407
|
+
id: field.name, ...props,
|
|
7408
|
+
// Strict props
|
|
7409
|
+
ref: mergeRefs(inputRef, field.ref), editable: !field.disabled && (props.editable ?? true), onBlur: (evt) => {
|
|
7410
|
+
const shouldValidate = validationMode === "onBlur" ||
|
|
7411
|
+
validationMode === "onTouched" ||
|
|
7412
|
+
validationMode === "all";
|
|
7413
|
+
methods.setValue(field.name, field.value, {
|
|
7414
|
+
shouldDirty: true,
|
|
7415
|
+
shouldTouch: true,
|
|
7416
|
+
shouldValidate,
|
|
7417
|
+
});
|
|
7418
|
+
props.onBlur?.(evt);
|
|
7419
|
+
}, mask: mask, maskAutoComplete: !!mask, obfuscationCharacter: obfuscationCharacter, showObfuscatedValue: !!obfuscateValue, value: field.value, onChangeText: (masked, unmasked) => {
|
|
7420
|
+
const shouldValidate = (validationMode === "onTouched" && fieldState.isTouched) ||
|
|
7421
|
+
((validationMode === "onChange" || validationMode === "all") &&
|
|
7422
|
+
(!!fieldState.error || fieldState.isTouched));
|
|
7423
|
+
methods.setValue(field.name, unmasked, {
|
|
7424
|
+
shouldDirty: true,
|
|
7425
|
+
shouldValidate,
|
|
7426
|
+
});
|
|
7427
|
+
},
|
|
7428
|
+
// Remove unwanted props
|
|
7429
|
+
defaultValue: undefined, onChange: undefined }));
|
|
7449
7430
|
});
|
|
7450
7431
|
|
|
7451
7432
|
const DEFAULT_ACCEPTED_BRANDS = [];
|
|
@@ -7518,9 +7499,9 @@ const CardExpiry = forwardRef(function CardExpiry(props, ref) {
|
|
|
7518
7499
|
return (jsx(EvervaultInput, { placeholder: "MM / YY", ...props, ref: ref, name: "expiry", mask: CARD_EXPIRY_MASK, inputMode: "numeric", autoComplete: "cc-exp", keyboardType: "number-pad" }));
|
|
7519
7500
|
});
|
|
7520
7501
|
|
|
7521
|
-
const DEFAULT_CARD_CVC_MASK = mask("999");
|
|
7502
|
+
const DEFAULT_CARD_CVC_MASK = mask("[999]");
|
|
7522
7503
|
const CARD_CVC_MASKS = {
|
|
7523
|
-
"american-express": mask("9999"),
|
|
7504
|
+
"american-express": mask("[9999]"),
|
|
7524
7505
|
};
|
|
7525
7506
|
const CardCvc = forwardRef(function CardCvc(props, ref) {
|
|
7526
7507
|
const methods = useFormContext();
|
|
@@ -7538,10 +7519,10 @@ const CardCvc = forwardRef(function CardCvc(props, ref) {
|
|
|
7538
7519
|
return (jsx(EvervaultInput, { placeholder: "CVC", ...props, ref: ref, name: "cvc", mask: mask, inputMode: "numeric", autoComplete: "cc-csc", keyboardType: "number-pad" }));
|
|
7539
7520
|
});
|
|
7540
7521
|
|
|
7541
|
-
const DEFAULT_CARD_NUMBER_MASK = mask("9999
|
|
7522
|
+
const DEFAULT_CARD_NUMBER_MASK = mask("9999 99[99 9999 9999]");
|
|
7542
7523
|
const CARD_NUMBER_MASKS = {
|
|
7543
|
-
unionpay: mask("9999
|
|
7544
|
-
"american-express": mask("9999
|
|
7524
|
+
unionpay: mask("9999 99[99 9999 9999 999]"),
|
|
7525
|
+
"american-express": mask("9999 99[9999 99999]"),
|
|
7545
7526
|
};
|
|
7546
7527
|
const CardNumber = forwardRef(function CardNumber(props, ref) {
|
|
7547
7528
|
const mask = useCallback((text) => {
|
|
@@ -7602,6 +7583,23 @@ const defaultStyles = StyleSheet.create({
|
|
|
7602
7583
|
},
|
|
7603
7584
|
});
|
|
7604
7585
|
|
|
7586
|
+
class ThreeDSecureEvent {
|
|
7587
|
+
type;
|
|
7588
|
+
session;
|
|
7589
|
+
_defaultPrevented;
|
|
7590
|
+
constructor(type, session, _defaultPrevented = false) {
|
|
7591
|
+
this.type = type;
|
|
7592
|
+
this.session = session;
|
|
7593
|
+
this._defaultPrevented = _defaultPrevented;
|
|
7594
|
+
}
|
|
7595
|
+
preventDefault() {
|
|
7596
|
+
this._defaultPrevented = true;
|
|
7597
|
+
}
|
|
7598
|
+
get defaultPrevented() {
|
|
7599
|
+
return this._defaultPrevented;
|
|
7600
|
+
}
|
|
7601
|
+
}
|
|
7602
|
+
|
|
7605
7603
|
function stopPolling(intervalRef, setIsVisible) {
|
|
7606
7604
|
setIsVisible(false);
|
|
7607
7605
|
if (intervalRef.current) {
|
|
@@ -7609,55 +7607,91 @@ function stopPolling(intervalRef, setIsVisible) {
|
|
|
7609
7607
|
intervalRef.current = null;
|
|
7610
7608
|
}
|
|
7611
7609
|
}
|
|
7612
|
-
async function startSession(session,
|
|
7610
|
+
async function startSession(session, options, intervalRef, setIsVisible) {
|
|
7613
7611
|
try {
|
|
7614
7612
|
const sessionState = await session.get();
|
|
7613
|
+
function fail() {
|
|
7614
|
+
stopPolling(intervalRef, setIsVisible);
|
|
7615
|
+
options?.onFailure?.(new Error("3DS session failed"));
|
|
7616
|
+
}
|
|
7615
7617
|
switch (sessionState.status) {
|
|
7616
|
-
case "success":
|
|
7618
|
+
case "success": {
|
|
7617
7619
|
stopPolling(intervalRef, setIsVisible);
|
|
7618
|
-
|
|
7620
|
+
options?.onSuccess?.();
|
|
7619
7621
|
break;
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7622
|
+
}
|
|
7623
|
+
case "failure": {
|
|
7624
|
+
fail();
|
|
7623
7625
|
break;
|
|
7624
|
-
|
|
7626
|
+
}
|
|
7627
|
+
case "action-required": {
|
|
7628
|
+
const failOnChallenge = typeof options?.failOnChallenge === "function"
|
|
7629
|
+
? await options.failOnChallenge()
|
|
7630
|
+
: options?.failOnChallenge ?? false;
|
|
7631
|
+
if (failOnChallenge) {
|
|
7632
|
+
fail();
|
|
7633
|
+
break;
|
|
7634
|
+
}
|
|
7635
|
+
const event = new ThreeDSecureEvent("requestChallenge", session);
|
|
7636
|
+
options?.onRequestChallenge?.(event);
|
|
7637
|
+
if (event.defaultPrevented) {
|
|
7638
|
+
fail();
|
|
7639
|
+
break;
|
|
7640
|
+
}
|
|
7625
7641
|
setIsVisible(true);
|
|
7626
|
-
pollSession(session,
|
|
7627
|
-
|
|
7628
|
-
default:
|
|
7629
|
-
break;
|
|
7642
|
+
pollSession(session, options, intervalRef, setIsVisible);
|
|
7643
|
+
}
|
|
7630
7644
|
}
|
|
7631
7645
|
}
|
|
7632
7646
|
catch (error) {
|
|
7633
7647
|
console.error("Error checking session state", error);
|
|
7634
|
-
|
|
7648
|
+
options?.onError?.(new Error("Failed to check 3DS session state"));
|
|
7635
7649
|
}
|
|
7636
7650
|
}
|
|
7637
|
-
function pollSession(session,
|
|
7651
|
+
function pollSession(session, options, intervalRef, setIsVisible, interval = 3000) {
|
|
7652
|
+
function fail() {
|
|
7653
|
+
stopPolling(intervalRef, setIsVisible);
|
|
7654
|
+
options?.onFailure?.(new Error("3DS session failed"));
|
|
7655
|
+
}
|
|
7638
7656
|
intervalRef.current = setInterval(async () => {
|
|
7639
7657
|
try {
|
|
7640
7658
|
const pollResponse = await session.get();
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7659
|
+
switch (pollResponse.status) {
|
|
7660
|
+
case "success": {
|
|
7661
|
+
stopPolling(intervalRef, setIsVisible);
|
|
7662
|
+
options?.onSuccess?.();
|
|
7663
|
+
break;
|
|
7664
|
+
}
|
|
7665
|
+
case "failure": {
|
|
7666
|
+
fail();
|
|
7667
|
+
break;
|
|
7668
|
+
}
|
|
7669
|
+
case "action-required": {
|
|
7670
|
+
const failOnChallenge = typeof options?.failOnChallenge === "function"
|
|
7671
|
+
? await options.failOnChallenge()
|
|
7672
|
+
: options?.failOnChallenge ?? false;
|
|
7673
|
+
if (failOnChallenge) {
|
|
7674
|
+
fail();
|
|
7675
|
+
break;
|
|
7676
|
+
}
|
|
7677
|
+
const event = new ThreeDSecureEvent("requestChallenge", session);
|
|
7678
|
+
options?.onRequestChallenge?.(event);
|
|
7679
|
+
if (event.defaultPrevented) {
|
|
7680
|
+
fail();
|
|
7681
|
+
break;
|
|
7682
|
+
}
|
|
7683
|
+
setIsVisible(true);
|
|
7684
|
+
}
|
|
7651
7685
|
}
|
|
7652
7686
|
}
|
|
7653
7687
|
catch (error) {
|
|
7654
7688
|
stopPolling(intervalRef, setIsVisible);
|
|
7655
7689
|
console.error("Error polling session", error);
|
|
7656
|
-
|
|
7690
|
+
options?.onError?.(new Error("Error polling 3DS session"));
|
|
7657
7691
|
}
|
|
7658
7692
|
}, interval);
|
|
7659
7693
|
}
|
|
7660
|
-
function threeDSecureSession({ sessionId, appId,
|
|
7694
|
+
function threeDSecureSession({ sessionId, appId, options, intervalRef, setIsVisible, }) {
|
|
7661
7695
|
async function get() {
|
|
7662
7696
|
try {
|
|
7663
7697
|
const response = await fetch(`https://${EV_API_DOMAIN}/frontend/3ds/browser-sessions/${sessionId}`, {
|
|
@@ -7683,7 +7717,7 @@ function threeDSecureSession({ sessionId, appId, callbacks, intervalRef, setIsVi
|
|
|
7683
7717
|
},
|
|
7684
7718
|
body: JSON.stringify({ outcome: "cancelled" }),
|
|
7685
7719
|
});
|
|
7686
|
-
|
|
7720
|
+
options?.onFailure?.(new Error("3DS session cancelled by user"));
|
|
7687
7721
|
stopPolling(intervalRef, setIsVisible);
|
|
7688
7722
|
}
|
|
7689
7723
|
catch (error) {
|
|
@@ -7698,22 +7732,27 @@ function threeDSecureSession({ sessionId, appId, callbacks, intervalRef, setIsVi
|
|
|
7698
7732
|
};
|
|
7699
7733
|
}
|
|
7700
7734
|
|
|
7701
|
-
function useThreeDSecure() {
|
|
7735
|
+
function useThreeDSecure(options) {
|
|
7702
7736
|
const { appId } = useEvervault();
|
|
7703
7737
|
const intervalRef = useRef(null);
|
|
7704
7738
|
const [session, setSession] = useState(null);
|
|
7705
7739
|
const [isVisible, setIsVisible] = useState(false);
|
|
7706
|
-
const
|
|
7740
|
+
const failOnChallenge = options?.failOnChallenge ?? false;
|
|
7741
|
+
const start = useCallback((sessionId, options) => {
|
|
7742
|
+
const startOptions = {
|
|
7743
|
+
...options,
|
|
7744
|
+
failOnChallenge: options?.failOnChallenge ?? failOnChallenge,
|
|
7745
|
+
};
|
|
7707
7746
|
const session = threeDSecureSession({
|
|
7708
7747
|
sessionId,
|
|
7709
7748
|
appId,
|
|
7710
|
-
|
|
7749
|
+
options: startOptions,
|
|
7711
7750
|
intervalRef,
|
|
7712
7751
|
setIsVisible,
|
|
7713
7752
|
});
|
|
7714
7753
|
setSession(session);
|
|
7715
|
-
startSession(session,
|
|
7716
|
-
}, [appId]);
|
|
7754
|
+
startSession(session, startOptions, intervalRef, setIsVisible);
|
|
7755
|
+
}, [appId, failOnChallenge]);
|
|
7717
7756
|
const cancel = useCallback(async () => {
|
|
7718
7757
|
if (session) {
|
|
7719
7758
|
await session.cancel();
|
package/package.json
CHANGED
package/src/Card/Cvc.tsx
CHANGED
|
@@ -6,13 +6,20 @@ import { validateNumber } from "@evervault/card-validator";
|
|
|
6
6
|
import { useFormContext } from "react-hook-form";
|
|
7
7
|
import { CardBrandName } from "./types";
|
|
8
8
|
|
|
9
|
-
const DEFAULT_CARD_CVC_MASK = mask("999");
|
|
9
|
+
const DEFAULT_CARD_CVC_MASK = mask("[999]");
|
|
10
10
|
|
|
11
11
|
const CARD_CVC_MASKS: Partial<Record<CardBrandName, Mask>> = {
|
|
12
|
-
"american-express": mask("9999"),
|
|
12
|
+
"american-express": mask("[9999]"),
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export
|
|
15
|
+
export interface CardCvcProps extends BaseEvervaultInputProps {
|
|
16
|
+
/**
|
|
17
|
+
* Whether to obfuscate the entire CVC value.
|
|
18
|
+
*
|
|
19
|
+
* If a string is provided, it will be used to obfuscate the value.
|
|
20
|
+
*/
|
|
21
|
+
obfuscateValue?: boolean | string;
|
|
22
|
+
}
|
|
16
23
|
|
|
17
24
|
export type CardCvc = EvervaultInput;
|
|
18
25
|
|
package/src/Card/Number.test.tsx
CHANGED
|
@@ -13,7 +13,7 @@ function wrapper({ children }: PropsWithChildren) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
it("uses 16 digits for mask by default", async () => {
|
|
16
|
-
const { getByTestId } = render(
|
|
16
|
+
const { rerender, getByTestId } = render(
|
|
17
17
|
<Card>
|
|
18
18
|
<CardNumber testID="number" />
|
|
19
19
|
</Card>,
|
|
@@ -24,10 +24,17 @@ it("uses 16 digits for mask by default", async () => {
|
|
|
24
24
|
const user = userEvent.setup();
|
|
25
25
|
await user.type(number, "4242424242424242");
|
|
26
26
|
expect(number).toHaveProp("value", "4242 4242 4242 4242");
|
|
27
|
+
|
|
28
|
+
rerender(
|
|
29
|
+
<Card>
|
|
30
|
+
<CardNumber testID="number" obfuscateValue />
|
|
31
|
+
</Card>
|
|
32
|
+
);
|
|
33
|
+
expect(number).toHaveProp("value", "4242 42•• •••• ••••");
|
|
27
34
|
});
|
|
28
35
|
|
|
29
36
|
it("uses 19 digits for mask for unionpay", async () => {
|
|
30
|
-
const { getByTestId } = render(
|
|
37
|
+
const { rerender, getByTestId } = render(
|
|
31
38
|
<Card>
|
|
32
39
|
<CardNumber testID="number" />
|
|
33
40
|
</Card>,
|
|
@@ -38,10 +45,17 @@ it("uses 19 digits for mask for unionpay", async () => {
|
|
|
38
45
|
const user = userEvent.setup();
|
|
39
46
|
await user.type(number, "6205500000000000004");
|
|
40
47
|
expect(number).toHaveProp("value", "6205 5000 0000 0000 004");
|
|
48
|
+
|
|
49
|
+
rerender(
|
|
50
|
+
<Card>
|
|
51
|
+
<CardNumber testID="number" obfuscateValue />
|
|
52
|
+
</Card>
|
|
53
|
+
);
|
|
54
|
+
expect(number).toHaveProp("value", "6205 50•• •••• •••• •••");
|
|
41
55
|
});
|
|
42
56
|
|
|
43
57
|
it("uses 15 digits for mask for american express", async () => {
|
|
44
|
-
const { getByTestId } = render(
|
|
58
|
+
const { rerender, getByTestId } = render(
|
|
45
59
|
<Card>
|
|
46
60
|
<CardNumber testID="number" />
|
|
47
61
|
</Card>,
|
|
@@ -52,4 +66,11 @@ it("uses 15 digits for mask for american express", async () => {
|
|
|
52
66
|
const user = userEvent.setup();
|
|
53
67
|
await user.type(number, "371449635398431");
|
|
54
68
|
expect(number).toHaveProp("value", "3714 496353 98431");
|
|
69
|
+
|
|
70
|
+
rerender(
|
|
71
|
+
<Card>
|
|
72
|
+
<CardNumber testID="number" obfuscateValue />
|
|
73
|
+
</Card>
|
|
74
|
+
);
|
|
75
|
+
expect(number).toHaveProp("value", "3714 49•••• •••••");
|
|
55
76
|
});
|
package/src/Card/Number.tsx
CHANGED
|
@@ -5,14 +5,21 @@ import { MaskArray } from "react-native-mask-input";
|
|
|
5
5
|
import { validateNumber } from "@evervault/card-validator";
|
|
6
6
|
import { CardBrandName } from "./types";
|
|
7
7
|
|
|
8
|
-
const DEFAULT_CARD_NUMBER_MASK = mask("9999
|
|
8
|
+
const DEFAULT_CARD_NUMBER_MASK = mask("9999 99[99 9999 9999]");
|
|
9
9
|
|
|
10
10
|
const CARD_NUMBER_MASKS: Partial<Record<CardBrandName, MaskArray>> = {
|
|
11
|
-
unionpay: mask("9999
|
|
12
|
-
"american-express": mask("9999
|
|
11
|
+
unionpay: mask("9999 99[99 9999 9999 999]"),
|
|
12
|
+
"american-express": mask("9999 99[9999 99999]"),
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export
|
|
15
|
+
export interface CardNumberProps extends BaseEvervaultInputProps {
|
|
16
|
+
/**
|
|
17
|
+
* Whether to obfuscate the card number value (excluding the last 4 digits).
|
|
18
|
+
*
|
|
19
|
+
* If a string is provided, it will be used to obfuscate the value.
|
|
20
|
+
*/
|
|
21
|
+
obfuscateValue?: boolean | string;
|
|
22
|
+
}
|
|
16
23
|
|
|
17
24
|
export type CardNumber = EvervaultInput;
|
|
18
25
|
|
package/src/Input.test.tsx
CHANGED
|
@@ -27,6 +27,25 @@ describe("mask", () => {
|
|
|
27
27
|
/\d/,
|
|
28
28
|
]);
|
|
29
29
|
});
|
|
30
|
+
|
|
31
|
+
it("should account for obfuscation", () => {
|
|
32
|
+
expect(mask("[9999 9999] 9999")).toEqual([
|
|
33
|
+
[/\d/],
|
|
34
|
+
[/\d/],
|
|
35
|
+
[/\d/],
|
|
36
|
+
[/\d/],
|
|
37
|
+
" ",
|
|
38
|
+
[/\d/],
|
|
39
|
+
[/\d/],
|
|
40
|
+
[/\d/],
|
|
41
|
+
[/\d/],
|
|
42
|
+
" ",
|
|
43
|
+
/\d/,
|
|
44
|
+
/\d/,
|
|
45
|
+
/\d/,
|
|
46
|
+
/\d/,
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
30
49
|
});
|
|
31
50
|
|
|
32
51
|
describe("EvervaultInput", () => {
|
|
@@ -333,4 +352,69 @@ describe("EvervaultInput", () => {
|
|
|
333
352
|
{ shouldDirty: true, shouldValidate: true }
|
|
334
353
|
);
|
|
335
354
|
});
|
|
355
|
+
|
|
356
|
+
it("should obfuscate the value when obfuscateValue=true", async () => {
|
|
357
|
+
const phoneMask = mask("[(999) 999]-9999");
|
|
358
|
+
const { rerender } = render(
|
|
359
|
+
<EvervaultInput testID="phone" mask={phoneMask} name="phone" />,
|
|
360
|
+
{
|
|
361
|
+
wrapper: Form,
|
|
362
|
+
}
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const input = screen.getByTestId("phone");
|
|
366
|
+
const user = userEvent.setup();
|
|
367
|
+
|
|
368
|
+
await user.type(input, "1234567890");
|
|
369
|
+
expect(input).toHaveProp("value", "(123) 456-7890");
|
|
370
|
+
|
|
371
|
+
rerender(
|
|
372
|
+
<EvervaultInput
|
|
373
|
+
testID="phone"
|
|
374
|
+
mask={phoneMask}
|
|
375
|
+
name="phone"
|
|
376
|
+
obfuscateValue
|
|
377
|
+
/>
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
await user.type(input, "1234567890");
|
|
381
|
+
expect(input).toHaveProp("value", "(•••) •••-7890");
|
|
382
|
+
|
|
383
|
+
rerender(
|
|
384
|
+
<EvervaultInput
|
|
385
|
+
testID="phone"
|
|
386
|
+
mask={phoneMask}
|
|
387
|
+
name="phone"
|
|
388
|
+
obfuscateValue="#"
|
|
389
|
+
/>
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
await user.type(input, "1234567890");
|
|
393
|
+
expect(input).toHaveProp("value", "(###) ###-7890");
|
|
394
|
+
|
|
395
|
+
rerender(
|
|
396
|
+
<EvervaultInput
|
|
397
|
+
testID="phone"
|
|
398
|
+
mask={phoneMask}
|
|
399
|
+
name="phone"
|
|
400
|
+
obfuscateValue="🤔"
|
|
401
|
+
/>
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
await user.type(input, "1234567890");
|
|
405
|
+
expect(input).toHaveProp("value", "(🤔🤔🤔) 🤔🤔🤔-7890");
|
|
406
|
+
|
|
407
|
+
const unobfuscatedMask = mask("(999) 999-9999");
|
|
408
|
+
rerender(
|
|
409
|
+
<EvervaultInput
|
|
410
|
+
testID="phone"
|
|
411
|
+
mask={unobfuscatedMask}
|
|
412
|
+
name="phone"
|
|
413
|
+
obfuscateValue
|
|
414
|
+
/>
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
await user.type(input, "1234567890");
|
|
418
|
+
expect(input).toHaveProp("value", "(123) 456-7890");
|
|
419
|
+
});
|
|
336
420
|
});
|