@evervault/react-native 2.0.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.
Files changed (177) hide show
  1. package/README.md +63 -0
  2. package/android/app/build/generated/source/codegen/java/com/facebook/fbreact/specs/NativeEvervaultSpec.java +60 -0
  3. package/android/app/build/generated/source/codegen/java/com/facebook/fbreact/specs/NativeRNCWebViewModuleSpec.java +42 -0
  4. package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/RNCWebViewManagerDelegate.java +294 -0
  5. package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/RNCWebViewManagerInterface.java +104 -0
  6. package/android/app/build/generated/source/codegen/jni/CMakeLists.txt +36 -0
  7. package/android/app/build/generated/source/codegen/jni/NativeEvervaultSpec-generated.cpp +62 -0
  8. package/android/app/build/generated/source/codegen/jni/NativeEvervaultSpec.h +31 -0
  9. package/android/app/build/generated/source/codegen/jni/RNCWebViewSpec-generated.cpp +38 -0
  10. package/android/app/build/generated/source/codegen/jni/RNCWebViewSpec.h +31 -0
  11. package/android/app/build/generated/source/codegen/jni/react/renderer/components/NativeEvervaultSpec/NativeEvervaultSpecJSI-generated.cpp +68 -0
  12. package/android/app/build/generated/source/codegen/jni/react/renderer/components/NativeEvervaultSpec/NativeEvervaultSpecJSI.h +112 -0
  13. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/ComponentDescriptors.cpp +22 -0
  14. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/ComponentDescriptors.h +24 -0
  15. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/EventEmitters.cpp +241 -0
  16. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/EventEmitters.h +263 -0
  17. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/Props.cpp +99 -0
  18. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/Props.h +488 -0
  19. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/RNCWebViewSpecJSI-generated.cpp +35 -0
  20. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/RNCWebViewSpecJSI.h +76 -0
  21. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/ShadowNodes.cpp +17 -0
  22. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/ShadowNodes.h +32 -0
  23. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/States.cpp +16 -0
  24. package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNCWebViewSpec/States.h +29 -0
  25. package/android/build.gradle +32 -0
  26. package/android/src/main/java/com/nativeevervault/EvervaultModule.kt +114 -0
  27. package/android/src/main/java/com/nativeevervault/EvervaultPackage.kt +29 -0
  28. package/build/Card/Cvc.d.ts +5 -0
  29. package/build/Card/Cvc.d.ts.map +1 -0
  30. package/build/Card/Cvc.test.d.ts +2 -0
  31. package/build/Card/Cvc.test.d.ts.map +1 -0
  32. package/build/Card/Expiry.d.ts +5 -0
  33. package/build/Card/Expiry.d.ts.map +1 -0
  34. package/build/Card/Holder.d.ts +5 -0
  35. package/build/Card/Holder.d.ts.map +1 -0
  36. package/build/Card/Number.d.ts +5 -0
  37. package/build/Card/Number.d.ts.map +1 -0
  38. package/build/Card/Number.test.d.ts +2 -0
  39. package/build/Card/Number.test.d.ts.map +1 -0
  40. package/build/Card/Root.d.ts +36 -0
  41. package/build/Card/Root.d.ts.map +1 -0
  42. package/build/Card/Root.test.d.ts +2 -0
  43. package/build/Card/Root.test.d.ts.map +1 -0
  44. package/build/Card/index.d.ts +23 -0
  45. package/build/Card/index.d.ts.map +1 -0
  46. package/build/Card/schema.d.ts +30 -0
  47. package/build/Card/schema.d.ts.map +1 -0
  48. package/build/Card/types.d.ts +31 -0
  49. package/build/Card/types.d.ts.map +1 -0
  50. package/build/Card/utils.d.ts +17 -0
  51. package/build/Card/utils.d.ts.map +1 -0
  52. package/build/Card/utils.test.d.ts +2 -0
  53. package/build/Card/utils.test.d.ts.map +1 -0
  54. package/build/EvervaultProvider.d.ts +7 -0
  55. package/build/EvervaultProvider.d.ts.map +1 -0
  56. package/build/EvervaultProvider.test.d.ts +2 -0
  57. package/build/EvervaultProvider.test.d.ts.map +1 -0
  58. package/build/Input.d.ts +14 -0
  59. package/build/Input.d.ts.map +1 -0
  60. package/build/Input.test.d.ts +2 -0
  61. package/build/Input.test.d.ts.map +1 -0
  62. package/build/ThreeDSecure/Frame.d.ts +6 -0
  63. package/build/ThreeDSecure/Frame.d.ts.map +1 -0
  64. package/build/ThreeDSecure/Frame.test.d.ts +2 -0
  65. package/build/ThreeDSecure/Frame.test.d.ts.map +1 -0
  66. package/build/ThreeDSecure/Root.d.ts +10 -0
  67. package/build/ThreeDSecure/Root.d.ts.map +1 -0
  68. package/build/ThreeDSecure/Root.test.d.ts +2 -0
  69. package/build/ThreeDSecure/Root.test.d.ts.map +1 -0
  70. package/build/ThreeDSecure/config.d.ts +3 -0
  71. package/build/ThreeDSecure/config.d.ts.map +1 -0
  72. package/build/ThreeDSecure/context.d.ts +3 -0
  73. package/build/ThreeDSecure/context.d.ts.map +1 -0
  74. package/build/ThreeDSecure/index.d.ts +10 -0
  75. package/build/ThreeDSecure/index.d.ts.map +1 -0
  76. package/build/ThreeDSecure/session.d.ts +6 -0
  77. package/build/ThreeDSecure/session.d.ts.map +1 -0
  78. package/build/ThreeDSecure/session.test.d.ts +2 -0
  79. package/build/ThreeDSecure/session.test.d.ts.map +1 -0
  80. package/build/ThreeDSecure/types.d.ts +57 -0
  81. package/build/ThreeDSecure/types.d.ts.map +1 -0
  82. package/build/ThreeDSecure/useThreeDSecure.d.ts +3 -0
  83. package/build/ThreeDSecure/useThreeDSecure.d.ts.map +1 -0
  84. package/build/ThreeDSecure/useThreeDSecure.test.d.ts +2 -0
  85. package/build/ThreeDSecure/useThreeDSecure.test.d.ts.map +1 -0
  86. package/build/__mocks__/NativeEvervault.d.ts +4 -0
  87. package/build/__mocks__/NativeEvervault.d.ts.map +1 -0
  88. package/build/__mocks__/react-native-webview.d.ts +3 -0
  89. package/build/__mocks__/react-native-webview.d.ts.map +1 -0
  90. package/build/context.d.ts +9 -0
  91. package/build/context.d.ts.map +1 -0
  92. package/build/generated/ios/FBReactNativeSpec/FBReactNativeSpec-generated.mm +2321 -0
  93. package/build/generated/ios/FBReactNativeSpec/FBReactNativeSpec.h +2761 -0
  94. package/build/generated/ios/FBReactNativeSpecJSI-generated.cpp +2923 -0
  95. package/build/generated/ios/FBReactNativeSpecJSI.h +7718 -0
  96. package/build/generated/ios/NativeEvervaultSpec/NativeEvervaultSpec-generated.mm +74 -0
  97. package/build/generated/ios/NativeEvervaultSpec/NativeEvervaultSpec.h +80 -0
  98. package/build/generated/ios/NativeEvervaultSpecJSI-generated.cpp +68 -0
  99. package/build/generated/ios/NativeEvervaultSpecJSI.h +112 -0
  100. package/build/generated/ios/RCTModulesConformingToProtocolsProvider.h +18 -0
  101. package/build/generated/ios/RCTModulesConformingToProtocolsProvider.mm +33 -0
  102. package/build/generated/ios/RNCWebViewSpec/RNCWebViewSpec-generated.mm +46 -0
  103. package/build/generated/ios/RNCWebViewSpec/RNCWebViewSpec.h +62 -0
  104. package/build/generated/ios/RNCWebViewSpecJSI-generated.cpp +35 -0
  105. package/build/generated/ios/RNCWebViewSpecJSI.h +76 -0
  106. package/build/generated/ios/react/renderer/components/RNCWebViewSpec/ComponentDescriptors.cpp +22 -0
  107. package/build/generated/ios/react/renderer/components/RNCWebViewSpec/ComponentDescriptors.h +24 -0
  108. package/build/generated/ios/react/renderer/components/RNCWebViewSpec/EventEmitters.cpp +241 -0
  109. package/build/generated/ios/react/renderer/components/RNCWebViewSpec/EventEmitters.h +263 -0
  110. package/build/generated/ios/react/renderer/components/RNCWebViewSpec/Props.cpp +99 -0
  111. package/build/generated/ios/react/renderer/components/RNCWebViewSpec/Props.h +488 -0
  112. package/build/generated/ios/react/renderer/components/RNCWebViewSpec/RCTComponentViewHelpers.h +218 -0
  113. package/build/generated/ios/react/renderer/components/RNCWebViewSpec/ShadowNodes.cpp +17 -0
  114. package/build/generated/ios/react/renderer/components/RNCWebViewSpec/ShadowNodes.h +32 -0
  115. package/build/generated/ios/react/renderer/components/RNCWebViewSpec/States.cpp +16 -0
  116. package/build/generated/ios/react/renderer/components/RNCWebViewSpec/States.h +29 -0
  117. package/build/index.cjs.js +7732 -0
  118. package/build/index.cjs.js.map +1 -0
  119. package/build/index.d.ts +9 -0
  120. package/build/index.d.ts.map +1 -0
  121. package/build/index.esm.js +7702 -0
  122. package/build/sdk.d.ts +9 -0
  123. package/build/sdk.d.ts.map +1 -0
  124. package/build/sdk.test.d.ts +2 -0
  125. package/build/sdk.test.d.ts.map +1 -0
  126. package/build/specs/NativeEvervault.d.ts +59 -0
  127. package/build/specs/NativeEvervault.d.ts.map +1 -0
  128. package/build/useEvervault.d.ts +2 -0
  129. package/build/useEvervault.d.ts.map +1 -0
  130. package/build/useEvervault.test.d.ts +2 -0
  131. package/build/useEvervault.test.d.ts.map +1 -0
  132. package/build/utils.d.ts +15 -0
  133. package/build/utils.d.ts.map +1 -0
  134. package/ios/NativeEvervault-Bridging-Header.h +2 -0
  135. package/ios/NativeEvervault.mm +38 -0
  136. package/ios/NativeEvervault.swift +62 -0
  137. package/native-evervault.podspec +20 -0
  138. package/package.json +85 -0
  139. package/src/Card/Cvc.test.tsx +41 -0
  140. package/src/Card/Cvc.tsx +51 -0
  141. package/src/Card/Expiry.tsx +26 -0
  142. package/src/Card/Holder.tsx +27 -0
  143. package/src/Card/Number.test.tsx +55 -0
  144. package/src/Card/Number.tsx +47 -0
  145. package/src/Card/Root.test.tsx +260 -0
  146. package/src/Card/Root.tsx +118 -0
  147. package/src/Card/index.ts +28 -0
  148. package/src/Card/schema.ts +51 -0
  149. package/src/Card/types.ts +50 -0
  150. package/src/Card/utils.test.ts +271 -0
  151. package/src/Card/utils.ts +127 -0
  152. package/src/EvervaultProvider.test.tsx +24 -0
  153. package/src/EvervaultProvider.tsx +43 -0
  154. package/src/Input.test.tsx +138 -0
  155. package/src/Input.tsx +136 -0
  156. package/src/ThreeDSecure/Frame.test.tsx +87 -0
  157. package/src/ThreeDSecure/Frame.tsx +50 -0
  158. package/src/ThreeDSecure/Root.test.tsx +67 -0
  159. package/src/ThreeDSecure/Root.tsx +23 -0
  160. package/src/ThreeDSecure/config.ts +3 -0
  161. package/src/ThreeDSecure/context.ts +6 -0
  162. package/src/ThreeDSecure/index.ts +17 -0
  163. package/src/ThreeDSecure/session.test.ts +329 -0
  164. package/src/ThreeDSecure/session.ts +132 -0
  165. package/src/ThreeDSecure/types.ts +67 -0
  166. package/src/ThreeDSecure/useThreeDSecure.test.tsx +133 -0
  167. package/src/ThreeDSecure/useThreeDSecure.ts +47 -0
  168. package/src/__mocks__/NativeEvervault.ts +13 -0
  169. package/src/__mocks__/react-native-webview.tsx +6 -0
  170. package/src/context.ts +14 -0
  171. package/src/index.ts +21 -0
  172. package/src/sdk.test.ts +122 -0
  173. package/src/sdk.ts +71 -0
  174. package/src/specs/NativeEvervault.ts +67 -0
  175. package/src/useEvervault.test.tsx +31 -0
  176. package/src/useEvervault.ts +14 -0
  177. package/src/utils.ts +41 -0
@@ -0,0 +1,271 @@
1
+ import { encryptedValue } from "../__mocks__/NativeEvervault";
2
+ import { EncryptFn } from "../context";
3
+ import {
4
+ areValuesComplete,
5
+ formatExpiry,
6
+ formatPayload,
7
+ isAcceptedBrand,
8
+ } from "./utils";
9
+
10
+ describe("formatPayload", () => {
11
+ const setValue = vi.fn();
12
+
13
+ const encrypt = vi.fn<EncryptFn>(
14
+ () => Promise.resolve(encryptedValue) as any
15
+ );
16
+
17
+ it("should format the payload", async () => {
18
+ const result = await formatPayload(
19
+ {
20
+ name: "John Doe",
21
+ number: "4242424242424242",
22
+ expiry: "1234",
23
+ cvc: "123",
24
+ },
25
+ {
26
+ encrypt,
27
+ form: {
28
+ setValue,
29
+ formState: {
30
+ errors: {},
31
+ },
32
+ } as any,
33
+ }
34
+ );
35
+
36
+ expect(result).toEqual({
37
+ card: {
38
+ name: "John Doe",
39
+ brand: "visa",
40
+ localBrands: [],
41
+ bin: "42424242",
42
+ lastFour: "4242",
43
+ expiry: {
44
+ month: "12",
45
+ year: "34",
46
+ },
47
+ number: encryptedValue,
48
+ cvc: encryptedValue,
49
+ },
50
+ isComplete: true,
51
+ isValid: true,
52
+ errors: {},
53
+ });
54
+ });
55
+
56
+ it("should slice the CVC to 3 characters if brand isn't American Express", async () => {
57
+ await formatPayload(
58
+ {
59
+ name: "John Doe",
60
+ number: "4242424242424242",
61
+ expiry: "1234",
62
+ cvc: "1234",
63
+ },
64
+ {
65
+ encrypt,
66
+ form: {
67
+ setValue,
68
+ formState: {
69
+ errors: {},
70
+ },
71
+ } as any,
72
+ }
73
+ );
74
+
75
+ expect(setValue).toHaveBeenCalledWith("cvc", "123");
76
+ });
77
+
78
+ it("should return isValid=false if any errors are present", async () => {
79
+ const result = await formatPayload(
80
+ {
81
+ name: "",
82
+ },
83
+ {
84
+ encrypt,
85
+ form: {
86
+ setValue,
87
+ formState: {
88
+ errors: {
89
+ name: {
90
+ message: "Required",
91
+ },
92
+ },
93
+ },
94
+ } as any,
95
+ }
96
+ );
97
+
98
+ expect(result).toEqual({
99
+ card: {
100
+ name: "",
101
+ brand: null,
102
+ localBrands: [],
103
+ bin: null,
104
+ lastFour: null,
105
+ expiry: null,
106
+ number: null,
107
+ cvc: null,
108
+ },
109
+ isComplete: false,
110
+ isValid: false,
111
+ errors: {
112
+ name: "Required",
113
+ },
114
+ });
115
+ });
116
+
117
+ it("should ignore values if they are not provided", async () => {
118
+ const result = await formatPayload(
119
+ {
120
+ name: "John Doe",
121
+ },
122
+ {
123
+ encrypt,
124
+ form: {
125
+ setValue,
126
+ formState: {
127
+ errors: {},
128
+ },
129
+ } as any,
130
+ }
131
+ );
132
+
133
+ expect(result).toEqual({
134
+ card: {
135
+ name: "John Doe",
136
+ brand: null,
137
+ localBrands: [],
138
+ bin: null,
139
+ lastFour: null,
140
+ expiry: null,
141
+ number: null,
142
+ cvc: null,
143
+ },
144
+ isComplete: true,
145
+ isValid: true,
146
+ errors: {},
147
+ });
148
+ });
149
+ });
150
+
151
+ describe("areValuesComplete", () => {
152
+ it("should return true if all values are complete and valid", () => {
153
+ const result = areValuesComplete({
154
+ name: "John Doe",
155
+ number: "4242424242424242",
156
+ expiry: "1234",
157
+ cvc: "123",
158
+ });
159
+ expect(result).toBe(true);
160
+ });
161
+
162
+ it("should return true if all _provided_ values are complete", () => {
163
+ const result = areValuesComplete({
164
+ name: "John Doe",
165
+ });
166
+ expect(result).toBe(true);
167
+
168
+ const result2 = areValuesComplete({
169
+ number: "4242424242424242",
170
+ expiry: "1234",
171
+ cvc: "123",
172
+ });
173
+ expect(result2).toBe(true);
174
+ });
175
+
176
+ it("should return false if any value is missing", () => {
177
+ const result = areValuesComplete({
178
+ name: "John Doe",
179
+ number: "",
180
+ expiry: "",
181
+ cvc: "",
182
+ });
183
+ expect(result).toBe(false);
184
+ });
185
+
186
+ it("should return false if any value is invalid", () => {
187
+ const result = areValuesComplete({
188
+ number: "1234567890",
189
+ });
190
+ expect(result).toBe(false);
191
+
192
+ const result2 = areValuesComplete({
193
+ expiry: "123",
194
+ });
195
+ expect(result2).toBe(false);
196
+
197
+ const result3 = areValuesComplete({
198
+ cvc: "1",
199
+ });
200
+ expect(result3).toBe(false);
201
+ });
202
+ });
203
+
204
+ describe("isAcceptedBrand", () => {
205
+ it("should return true if no accepted brands are provided", () => {
206
+ const undef = isAcceptedBrand(undefined, {
207
+ brand: "visa",
208
+ bin: "123456",
209
+ lastFour: "1234",
210
+ localBrands: [],
211
+ isValid: true,
212
+ });
213
+ expect(undef).toBe(true);
214
+
215
+ const emptyArr = isAcceptedBrand([], {
216
+ brand: "visa",
217
+ bin: "123456",
218
+ lastFour: "1234",
219
+ localBrands: [],
220
+ isValid: true,
221
+ });
222
+ expect(emptyArr).toBe(true);
223
+ });
224
+
225
+ it("should return true if the brand is accepted", () => {
226
+ const result = isAcceptedBrand(["visa"], {
227
+ brand: "visa",
228
+ bin: "123456",
229
+ lastFour: "1234",
230
+ localBrands: [],
231
+ isValid: true,
232
+ });
233
+ expect(result).toBe(true);
234
+ });
235
+
236
+ it("should return true if the local brand is accepted", () => {
237
+ const result = isAcceptedBrand(["hiper"], {
238
+ brand: "visa",
239
+ bin: "123456",
240
+ lastFour: "1234",
241
+ localBrands: ["hiper"],
242
+ isValid: true,
243
+ });
244
+ expect(result).toBe(true);
245
+ });
246
+
247
+ it("should return false if the brand is not accepted", () => {
248
+ const result = isAcceptedBrand(["visa"], {
249
+ brand: "mastercard",
250
+ bin: "123456",
251
+ lastFour: "1234",
252
+ localBrands: [],
253
+ isValid: true,
254
+ });
255
+ expect(result).toBe(false);
256
+ });
257
+ });
258
+
259
+ describe("formatExpiry", () => {
260
+ it("should return null if the expiry date is invalid", () => {
261
+ const expiry = "123";
262
+ const formatted = formatExpiry(expiry);
263
+ expect(formatted).toBeNull();
264
+ });
265
+
266
+ it("should format the expiry date", () => {
267
+ const expiry = "1234";
268
+ const formatted = formatExpiry(expiry);
269
+ expect(formatted).toEqual({ month: "12", year: "34" });
270
+ });
271
+ });
@@ -0,0 +1,127 @@
1
+ import {
2
+ validateNumber,
3
+ validateExpiry,
4
+ validateCVC,
5
+ CardNumberValidationResult,
6
+ } from "@evervault/card-validator";
7
+ import type { CardBrandName, CardPayload } from "./types";
8
+ import { type CardFormValues } from "./schema";
9
+ import { DeepPartial, UseFormReturn } from "react-hook-form";
10
+ import { type Encrypted, sdk } from "../sdk";
11
+
12
+ export interface FormatPayloadContext {
13
+ form: UseFormReturn<CardFormValues>;
14
+ encrypt<T>(data: T): Promise<Encrypted<T>>;
15
+ }
16
+
17
+ export async function formatPayload(
18
+ values: DeepPartial<CardFormValues>,
19
+ context: FormatPayloadContext
20
+ ): Promise<CardPayload> {
21
+ const number = values.number?.replace(/\s/g, "") || "";
22
+
23
+ const {
24
+ brand,
25
+ localBrands,
26
+ bin,
27
+ lastFour,
28
+ isValid: isNumberValid,
29
+ } = validateNumber(number);
30
+
31
+ if (
32
+ number.length > 0 &&
33
+ brand !== "american-express" &&
34
+ values.cvc?.length === 4
35
+ ) {
36
+ context.form.setValue("cvc", values.cvc?.slice(0, 3));
37
+ }
38
+
39
+ const { cvc, isValid: isCvcValid } = validateCVC(values.cvc ?? "", number);
40
+
41
+ const formErrors = context.form.formState.errors;
42
+ const isValid = !Object.keys(formErrors).length;
43
+ const isComplete = areValuesComplete(values);
44
+
45
+ const errors: Record<string, string> = {};
46
+ if (formErrors.name?.message) {
47
+ errors.name = formErrors.name.message;
48
+ }
49
+ if (formErrors.number?.message) {
50
+ errors.number = formErrors.number.message;
51
+ }
52
+ if (formErrors.expiry?.message) {
53
+ errors.expiry = formErrors.expiry.message;
54
+ }
55
+ if (formErrors.cvc?.message) {
56
+ errors.cvc = formErrors.cvc.message;
57
+ }
58
+
59
+ return {
60
+ card: {
61
+ name: values.name ?? null,
62
+ brand,
63
+ localBrands,
64
+ bin,
65
+ lastFour,
66
+ expiry: formatExpiry(values.expiry ?? ""),
67
+ number: isNumberValid ? await context.encrypt(number) : null,
68
+ cvc: isCvcValid ? await context.encrypt(cvc ?? "") : null,
69
+ },
70
+ isComplete,
71
+ isValid: isValid && isComplete,
72
+ errors,
73
+ };
74
+ }
75
+
76
+ export function areValuesComplete(values: DeepPartial<CardFormValues>) {
77
+ if ("name" in values && !values.name?.length) {
78
+ return false;
79
+ }
80
+
81
+ if ("number" in values && !validateNumber(values.number ?? "").isValid) {
82
+ return false;
83
+ }
84
+
85
+ if ("expiry" in values && !validateExpiry(values.expiry ?? "").isValid) {
86
+ return false;
87
+ }
88
+
89
+ if (
90
+ "cvc" in values &&
91
+ !validateCVC(values.cvc ?? "", values.number).isValid
92
+ ) {
93
+ return false;
94
+ }
95
+
96
+ return true;
97
+ }
98
+
99
+ export function isAcceptedBrand(
100
+ acceptedBrands: CardBrandName[] | undefined,
101
+ cardNumberValidationResult: CardNumberValidationResult
102
+ ): boolean {
103
+ if (!acceptedBrands?.length) return true;
104
+ const { brand, localBrands } = cardNumberValidationResult;
105
+
106
+ const acceptedBrandsSet = new Set(acceptedBrands);
107
+
108
+ const isBrandAccepted = brand !== null && acceptedBrandsSet.has(brand);
109
+ const isLocalBrandAccepted = localBrands.some((localBrand) =>
110
+ acceptedBrandsSet.has(localBrand)
111
+ );
112
+
113
+ return isBrandAccepted || isLocalBrandAccepted;
114
+ }
115
+
116
+ export function formatExpiry(expiry: string) {
117
+ const parsedExpiry = validateExpiry(expiry);
118
+
119
+ if (!parsedExpiry.isValid) {
120
+ return null;
121
+ }
122
+
123
+ return {
124
+ month: parsedExpiry.month!,
125
+ year: parsedExpiry.year!,
126
+ };
127
+ }
@@ -0,0 +1,24 @@
1
+ import { PropsWithChildren, useContext } from "react";
2
+ import { EvervaultProvider } from "./EvervaultProvider";
3
+ import { renderHook } from "@testing-library/react-native";
4
+ import { EvervaultContext } from "./context";
5
+ import { sdk } from "./sdk";
6
+
7
+ it("renders context", () => {
8
+ const wrapper = ({ children }: PropsWithChildren) => (
9
+ <EvervaultProvider teamId="team_123" appId="app_123">
10
+ {children}
11
+ </EvervaultProvider>
12
+ );
13
+
14
+ const initSpy = vi.spyOn(sdk, "initialize");
15
+ const { result } = renderHook(() => useContext(EvervaultContext), {
16
+ wrapper,
17
+ });
18
+
19
+ expect(initSpy).toHaveBeenCalledWith("team_123", "app_123");
20
+ expect(result.current).toBeDefined();
21
+ expect(result.current?.appId).toBe("app_123");
22
+ expect(result.current?.teamId).toBe("team_123");
23
+ expect(result.current?.encrypt).toStrictEqual(expect.any(Function));
24
+ });
@@ -0,0 +1,43 @@
1
+ import {
2
+ PropsWithChildren,
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useState,
7
+ } from "react";
8
+ import { type EvervaultContextValue, EvervaultContext } from "./context";
9
+ import { Encrypted, sdk } from "./sdk";
10
+
11
+ export interface EvervaultProviderProps extends PropsWithChildren {
12
+ teamId: string;
13
+ appId: string;
14
+ }
15
+
16
+ export function EvervaultProvider({
17
+ teamId,
18
+ appId,
19
+ children,
20
+ }: EvervaultProviderProps) {
21
+ const instanceId = useMemo(
22
+ () => sdk.initialize(teamId, appId),
23
+ [teamId, appId]
24
+ );
25
+
26
+ const encrypt = useCallback(
27
+ function <T>(data: T): Promise<Encrypted<T>> {
28
+ return sdk.encrypt(instanceId, data);
29
+ },
30
+ [instanceId]
31
+ );
32
+
33
+ const context = useMemo<EvervaultContextValue>(
34
+ () => ({ teamId, appId, encrypt }),
35
+ [teamId, appId, encrypt]
36
+ );
37
+
38
+ return (
39
+ <EvervaultContext.Provider value={context}>
40
+ {children}
41
+ </EvervaultContext.Provider>
42
+ );
43
+ }
@@ -0,0 +1,138 @@
1
+ import {
2
+ fireEvent,
3
+ render,
4
+ screen,
5
+ userEvent,
6
+ } from "@testing-library/react-native";
7
+ import { EvervaultInput, mask } from "./Input";
8
+ import { FormProvider, useForm, useFormContext } from "react-hook-form";
9
+ import { PropsWithChildren } from "react";
10
+ import { Text, View } from "react-native";
11
+
12
+ describe("mask", () => {
13
+ it("should convert a mask to an array of regex", () => {
14
+ expect(mask("999-999-9999")).toEqual([
15
+ /\d/,
16
+ /\d/,
17
+ /\d/,
18
+ "-",
19
+ /\d/,
20
+ /\d/,
21
+ /\d/,
22
+ "-",
23
+ /\d/,
24
+ /\d/,
25
+ /\d/,
26
+ /\d/,
27
+ ]);
28
+ });
29
+ });
30
+
31
+ describe("EvervaultInput", () => {
32
+ const methodMocks = {
33
+ setValue: vi.fn(),
34
+ };
35
+
36
+ function Form({ children }: PropsWithChildren) {
37
+ const methods = useForm();
38
+ const setValue = (...args: Parameters<typeof methods.setValue>) => {
39
+ methodMocks.setValue(...args);
40
+ methods.setValue(...args);
41
+ };
42
+ return (
43
+ <FormProvider {...methods} setValue={setValue}>
44
+ {children}
45
+ </FormProvider>
46
+ );
47
+ }
48
+
49
+ it("should render", async () => {
50
+ render(<EvervaultInput testID="phone" name="phone" />, {
51
+ wrapper: Form,
52
+ });
53
+
54
+ const input = screen.getByTestId("phone");
55
+ expect(input).toBeOnTheScreen();
56
+ expect(input).toHaveProp("id", "phone");
57
+ expect(input).toHaveProp("value", "");
58
+ expect(input).toHaveProp("editable", true);
59
+ });
60
+
61
+ it("uses the mask if provided", async () => {
62
+ render(
63
+ <EvervaultInput
64
+ testID="phone"
65
+ name="phone"
66
+ mask={mask("999-999-9999")}
67
+ />,
68
+ {
69
+ wrapper: Form,
70
+ }
71
+ );
72
+ const input = screen.getByTestId("phone");
73
+ const user = userEvent.setup();
74
+
75
+ expect(input).toHaveProp("value", "");
76
+ await user.type(input, "1234567890");
77
+ expect(input).toHaveProp("value", "123-456-7890");
78
+
79
+ expect(methodMocks.setValue).toHaveBeenCalledWith("phone", "1234567890", {
80
+ shouldDirty: true,
81
+ shouldValidate: false,
82
+ });
83
+ });
84
+
85
+ it("dirties the field when the user types", async () => {
86
+ render(<EvervaultInput testID="phone" name="phone" />, {
87
+ wrapper: Form,
88
+ });
89
+
90
+ const input = screen.getByTestId("phone");
91
+ const user = userEvent.setup();
92
+
93
+ await user.type(input, "1234567890");
94
+ expect(methodMocks.setValue).toHaveBeenCalledWith("phone", "1234567890", {
95
+ shouldDirty: true,
96
+ shouldValidate: false,
97
+ });
98
+ });
99
+
100
+ it("dirties and validates the field when the user types if touched", async () => {
101
+ render(<EvervaultInput testID="phone" name="phone" />, {
102
+ wrapper: Form,
103
+ });
104
+
105
+ const input = screen.getByTestId("phone");
106
+
107
+ // Blur the input to trigger touch
108
+ fireEvent(input, "blur");
109
+ expect(methodMocks.setValue).toHaveBeenCalledWith("phone", undefined, {
110
+ shouldDirty: true,
111
+ shouldTouch: true,
112
+ shouldValidate: true,
113
+ });
114
+
115
+ const user = userEvent.setup();
116
+ await user.type(input, "1234567890");
117
+ expect(input).toHaveProp("value", "1234567890");
118
+ expect(methodMocks.setValue).toHaveBeenCalledWith("phone", "1234567890", {
119
+ shouldDirty: true,
120
+ shouldValidate: true,
121
+ });
122
+ });
123
+
124
+ it("dirties, touches, and validates the field when blurred", async () => {
125
+ render(<EvervaultInput testID="phone" name="phone" />, {
126
+ wrapper: Form,
127
+ });
128
+
129
+ const input = screen.getByTestId("phone");
130
+
131
+ fireEvent(input, "blur");
132
+ expect(methodMocks.setValue).toHaveBeenCalledWith("phone", undefined, {
133
+ shouldDirty: true,
134
+ shouldTouch: true,
135
+ shouldValidate: true,
136
+ });
137
+ });
138
+ });