react-native-persona 2.2.30 → 2.3.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/CHANGELOG.md CHANGED
@@ -7,53 +7,84 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## [v2.3.0] - 2023-02-16
11
+
12
+ ### Added
13
+
14
+ - Added optional support to receive data collected during inquiry flow on completion.
15
+
16
+ ### Changed
17
+
18
+ - Upgrade to iOS Inquiry SDK 2.6.2
19
+ - Upgrade to Android Inquiry SDK 2.3.6
20
+
10
21
  ## [v2.2.30] - 2023-01-25
11
22
 
23
+ ### Changed
24
+
12
25
  - Upgrade to iOS Inquiry SDK 2.5.11
13
26
  - Upgrade to Android Inquiry SDK 2.3.5
14
27
 
15
28
  ## [v2.2.29] - 2022-12-15
16
29
 
30
+ ### Changed
31
+
17
32
  - Upgrade to iOS Inquiry SDK 2.5.4
18
33
  - Upgrade to Android Inquiry SDK 2.3.0
19
34
 
20
35
  ## [v2.2.28] - 2022-11-30
21
36
 
37
+ ### Changed
38
+
22
39
  - Upgrade to iOS Inquiry SDK 2.5.3
23
40
  - Upgrade to Android Inquiry SDK 2.2.45
24
41
 
25
42
  ## [v2.2.27] - 2022-11-16
26
43
 
44
+ ### Changed
45
+
27
46
  - Upgrade to iOS Inquiry SDK 2.5.0
28
47
  - Upgrade to Android Inquiry SDK 2.2.43
29
48
 
30
49
  ## [v2.2.26] - 2022-10-20
31
50
 
51
+ ### Changed
52
+
32
53
  - Upgrade to iOS Inquiry SDK 2.4.3
33
54
  - Upgrade to Android Inquiry SDK 2.2.41
34
55
 
35
56
  ## [v2.2.25] - 2022-10-06
36
57
 
58
+ ### Changed
59
+
37
60
  - Upgrade to iOS Inquiry SDK 2.4.2
38
61
  - Upgrade to Android Inquiry SDK 2.2.40
39
62
 
40
63
  ## [v2.2.24] - 2022-09-22
41
64
 
65
+ ### Changed
66
+
42
67
  - Upgrade to iOS Inquiry SDK 2.4.0
43
68
  - Upgrade to Android Inquiry SDK 2.2.37
44
69
 
45
70
  ## [v2.2.23] - 2022-09-07
46
71
 
72
+ ### Changed
73
+
47
74
  - Upgrade to iOS Inquiry SDK 2.3.9
48
75
  - Upgrade to Android Inquiry SDK 2.2.36
49
76
 
50
77
  ## [v2.2.22] - 2022-08-25
51
78
 
79
+ ### Changed
80
+
52
81
  - Upgrade to iOS Inquiry SDK 2.3.6
53
82
  - Upgrade to Android Inquiry SDK 2.2.31
54
83
 
55
84
  ## [v2.2.21] - 2022-08-18
56
85
 
86
+ ### Fixed
87
+
57
88
  - Fixed a bug where the inquiry flow failed to present on iOS
58
89
 
59
90
  ## [v2.2.20] - 2022-08-15
@@ -22,7 +22,7 @@ Pod::Spec.new do |s|
22
22
  s.requires_arc = true
23
23
 
24
24
  s.dependency 'React-Core'
25
- s.dependency 'PersonaInquirySDK2', '2.5.11'
25
+ s.dependency 'PersonaInquirySDK2', '2.6.2'
26
26
 
27
27
  s.static_framework = true
28
28
  end
@@ -66,5 +66,5 @@ dependencies {
66
66
  //noinspection GradleDynamicVersion
67
67
  implementation 'com.facebook.react:react-native:+' // From node_modules
68
68
 
69
- implementation 'com.withpersona.sdk2:inquiry:2.3.5'
69
+ implementation 'com.withpersona.sdk2:inquiry:2.3.6'
70
70
  }
@@ -2,13 +2,17 @@ package com.withpersona.sdk2.reactnative;
2
2
 
3
3
  import android.app.Activity;
4
4
  import android.content.Intent;
5
+
5
6
  import androidx.annotation.Nullable;
7
+
6
8
  import com.facebook.react.bridge.ActivityEventListener;
7
9
  import com.facebook.react.bridge.Arguments;
8
10
  import com.facebook.react.bridge.ReactApplicationContext;
9
11
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
10
12
  import com.facebook.react.bridge.ReactMethod;
13
+ import com.facebook.react.bridge.ReadableArray;
11
14
  import com.facebook.react.bridge.ReadableMap;
15
+ import com.facebook.react.bridge.WritableArray;
12
16
  import com.facebook.react.bridge.WritableMap;
13
17
  import com.facebook.react.modules.core.DeviceEventManagerModule;
14
18
  import com.withpersona.sdk2.inquiry.Environment;
@@ -18,10 +22,18 @@ import com.withpersona.sdk2.inquiry.InquiryBuilder;
18
22
  import com.withpersona.sdk2.inquiry.InquiryField;
19
23
  import com.withpersona.sdk2.inquiry.InquiryResponse;
20
24
  import com.withpersona.sdk2.inquiry.InquiryTemplateBuilder;
25
+ import com.withpersona.sdk2.inquiry.types.collected_data.CollectedData;
26
+ import com.withpersona.sdk2.inquiry.types.collected_data.DocumentFile;
27
+ import com.withpersona.sdk2.inquiry.types.collected_data.GovernmentIdCapture;
28
+ import com.withpersona.sdk2.inquiry.types.collected_data.SelfieCapture;
29
+ import com.withpersona.sdk2.inquiry.types.collected_data.StepData;
30
+
31
+ import org.jetbrains.annotations.NotNull;
32
+
21
33
  import java.util.HashMap;
34
+ import java.util.List;
22
35
  import java.util.Map;
23
36
  import java.util.Objects;
24
- import org.jetbrains.annotations.NotNull;
25
37
 
26
38
  public class PersonaInquiryModule2 extends ReactContextBaseJavaModule
27
39
  implements ActivityEventListener {
@@ -35,6 +47,7 @@ public class PersonaInquiryModule2 extends ReactContextBaseJavaModule
35
47
  private static final String ENVIRONMENT = "environment";
36
48
  private static final String FIELDS = "fields";
37
49
  private static final String FIELD_ADDITIONAL_FIELDS = "additionalFields";
50
+ private static final String RETURN_COLLECTED_DATA = "returnCollectedData";
38
51
 
39
52
  private final ReactApplicationContext reactContext;
40
53
 
@@ -92,6 +105,8 @@ public class PersonaInquiryModule2 extends ReactContextBaseJavaModule
92
105
  }
93
106
  params.putMap("fields", fields);
94
107
 
108
+ params.putMap("collectedData", collectedDataToMap(complete.getCollectedData()));
109
+
95
110
  jsModule.emit("onComplete", params);
96
111
  } else if (response instanceof InquiryResponse.Cancel) {
97
112
  InquiryResponse.Cancel cancel = (InquiryResponse.Cancel) response;
@@ -109,6 +124,137 @@ public class PersonaInquiryModule2 extends ReactContextBaseJavaModule
109
124
  }
110
125
  }
111
126
 
127
+ @Nullable
128
+ private ReadableMap collectedDataToMap(@Nullable CollectedData collectedData) {
129
+ if (collectedData == null) {
130
+ return null;
131
+ }
132
+
133
+ WritableMap collectedDataMap = Arguments.createMap();
134
+
135
+ WritableArray stepDataMap = Arguments.createArray();
136
+ for (StepData stepDatum : collectedData.getStepData()) {
137
+ WritableMap stepDatumMap = Arguments.createMap();
138
+ stepDatumMap.putString("stepName", stepDatum.getStepName());
139
+
140
+ if (stepDatum instanceof StepData.DocumentStepData) {
141
+ WritableArray documentsArr = Arguments.createArray();
142
+
143
+ for (DocumentFile document : ((StepData.DocumentStepData) stepDatum).getDocuments()) {
144
+ WritableMap documentMap = Arguments.createMap();
145
+ documentMap.putString("absoluteFilePath", document.getData().getAbsolutePath());
146
+ documentsArr.pushMap(documentMap);
147
+ }
148
+
149
+ stepDatumMap.putArray("documents", documentsArr);
150
+ stepDatumMap.putString("type", "DocumentStepData");
151
+ } else if (stepDatum instanceof StepData.GovernmentIdStepData) {
152
+ WritableArray capturesArr = Arguments.createArray();
153
+
154
+ for (GovernmentIdCapture capture : ((StepData.GovernmentIdStepData) stepDatum).getCaptures()) {
155
+ WritableMap captureMap = Arguments.createMap();
156
+ captureMap.putString("idClass", capture.getIdClass());
157
+ captureMap.putString("captureMethod", capture.getCaptureMethod().name());
158
+ captureMap.putString("side", capture.getSide().name());
159
+
160
+ WritableArray framesArr = Arguments.createArray();
161
+ for (GovernmentIdCapture.Frame frame : capture.getFrames()) {
162
+ WritableMap frameMap = Arguments.createMap();
163
+ frameMap.putString("absoluteFilePath", frame.getData().getAbsolutePath());
164
+
165
+ framesArr.pushMap(frameMap);
166
+ }
167
+ captureMap.putArray("frames", framesArr);
168
+
169
+ capturesArr.pushMap(captureMap);
170
+ }
171
+
172
+ stepDatumMap.putArray("captures", capturesArr);
173
+ stepDatumMap.putString("type", "GovernmentIdStepData");
174
+ } else if (stepDatum instanceof StepData.SelfieStepData) {
175
+ StepData.SelfieStepData selfieStepData = (StepData.SelfieStepData) stepDatum;
176
+ ReadableMap centerCaptureMap = selfieCaptureToMap(selfieStepData.getCenterCapture());
177
+ ReadableMap leftCaptureMap = selfieCaptureToMap(selfieStepData.getLeftCapture());
178
+ ReadableMap rightCaptureMap = selfieCaptureToMap(selfieStepData.getRightCapture());
179
+
180
+ stepDatumMap.putMap("centerCapture", centerCaptureMap);
181
+ stepDatumMap.putMap("leftCapture", leftCaptureMap);
182
+ stepDatumMap.putMap("rightCapture", rightCaptureMap);
183
+ stepDatumMap.putString("type", "SelfieStepData");
184
+ } else if (stepDatum instanceof StepData.UiStepData) {
185
+ Map<String, Object> componentParams = ((StepData.UiStepData) stepDatum).getComponentParams();
186
+
187
+ stepDatumMap.putMap("componentParams", uiStepParamsMapToMap(componentParams));
188
+ stepDatumMap.putString("type", "UiStepData");
189
+ } else {
190
+ // do nothing
191
+ }
192
+
193
+ stepDataMap.pushMap(stepDatumMap);
194
+ }
195
+
196
+ collectedDataMap.putArray("stepData", stepDataMap);
197
+
198
+ return collectedDataMap;
199
+ }
200
+
201
+ private ReadableMap uiStepParamsMapToMap(Map<?, ?> map) {
202
+ WritableMap resultMap = Arguments.createMap();
203
+ for (Map.Entry<?, ?> param : map.entrySet()) {
204
+ Object key = param.getKey();
205
+ Object value = param.getValue();
206
+
207
+ if (!(key instanceof String)) {
208
+ continue; // All ui step params should have string keys.
209
+ }
210
+
211
+ String keyStr = (String) key;
212
+
213
+ if (value instanceof Map<?, ?>) {
214
+ resultMap.putMap(keyStr, uiStepParamsMapToMap((Map<?, ?>) value));
215
+ } else if (value instanceof List<?>) {
216
+ resultMap.putArray(keyStr, uiStepParamsArrToArr((List<?>) value));
217
+ } else if (value instanceof String) {
218
+ resultMap.putString(keyStr, (String) value);
219
+ } else if (value instanceof Boolean) {
220
+ resultMap.putBoolean(keyStr, (Boolean) value);
221
+ } else if (value instanceof Number) {
222
+ resultMap.putDouble(keyStr, (Double) value);
223
+ }
224
+ }
225
+
226
+ return resultMap;
227
+ }
228
+
229
+ private ReadableArray uiStepParamsArrToArr(List<?> arr) {
230
+ WritableArray resultArr = Arguments.createArray();
231
+
232
+ for (Object element : arr) {
233
+ if (element instanceof String) {
234
+ resultArr.pushString((String) element);
235
+ } else if (element instanceof Boolean) {
236
+ resultArr.pushBoolean((Boolean) element);
237
+ } else if (element instanceof Number) {
238
+ resultArr.pushDouble((Double) element);
239
+ }
240
+ }
241
+
242
+
243
+ return resultArr;
244
+ }
245
+
246
+ private ReadableMap selfieCaptureToMap(@Nullable SelfieCapture selfieCapture) {
247
+ if (selfieCapture == null) {
248
+ return null;
249
+ }
250
+
251
+ WritableMap captureMap = Arguments.createMap();
252
+ captureMap.putString("captureMethod", selfieCapture.getCaptureMethod().name());
253
+ captureMap.putString("absoluteFilePath", selfieCapture.getData().getAbsolutePath());
254
+ return captureMap;
255
+ }
256
+
257
+
112
258
  private static ReadableMap wrapField(String type, String value) {
113
259
  WritableMap map = Arguments.createMap();
114
260
  map.putString("type", type);
@@ -201,6 +347,11 @@ public class PersonaInquiryModule2 extends ReactContextBaseJavaModule
201
347
  builder = builder.fields(fieldsBuilder.build());
202
348
  }
203
349
 
350
+ Boolean returnCollectedData = options.hasKey(RETURN_COLLECTED_DATA) ? options.getBoolean(RETURN_COLLECTED_DATA) : null;
351
+ if (returnCollectedData != null) {
352
+ builder = builder.returnCollectedData(returnCollectedData);
353
+ }
354
+
204
355
  if (currentActivity != null) {
205
356
  builder.build().start(currentActivity, PERSONA_INQUIRY_REQUEST_CODE);
206
357
  }
@@ -11,6 +11,8 @@ import Persona2
11
11
  @objc(PersonaInquiry2)
12
12
  class PersonaInquiry2: RCTEventEmitter {
13
13
 
14
+ private var collectedData: InquiryData? = nil
15
+
14
16
  override func supportedEvents() -> [String] {
15
17
  ["onComplete", "onCanceled", "onError"]
16
18
  }
@@ -40,37 +42,39 @@ class PersonaInquiry2: RCTEventEmitter {
40
42
  let sessionToken = options["sessionToken"] as? String
41
43
  let themeObject = options["iosTheme"] as? [String: String]
42
44
  let fieldsObject = options["fields"] as? [String: [String: Any]]
43
-
44
- do {
45
- let config = try createConfig(
46
- templateId: templateId,
47
- templateVersion: templateVersion,
48
- inquiryId: inquiryId,
49
- sessionToken: sessionToken,
50
- referenceId: referenceId,
51
- accountId: accountId,
52
- environment: Environment.from(rawValue: environment),
53
- theme: themeFrom(themeObject),
54
- fields: fieldsFrom(fieldsObject)
55
- )
56
- DispatchQueue.main.async {
57
- guard let viewController = self.findTopViewController() else {
58
- self.sendEvent(
59
- withName: "onError",
60
- body: [
61
- "debugMessage": "Couldn't resolve a root view controller"
62
- ]
63
- )
64
- return
65
- }
66
-
67
- Inquiry(
68
- config: config,
69
- delegate: self
70
- ).start(from: viewController)
45
+ let returnCollectedData = options["returnCollectedData"] as? Bool ?? false
46
+
47
+ guard let config = InquiryConfiguration.build(
48
+ inquiryId: inquiryId,
49
+ sessionToken: sessionToken,
50
+ templateVersion: templateVersion,
51
+ templateId: templateId,
52
+ referenceId: referenceId,
53
+ accountId: accountId,
54
+ environment: Environment.from(rawValue: environment),
55
+ fields: fieldsFrom(fieldsObject),
56
+ theme: themeFrom(themeObject),
57
+ nfcAdapter: nil,
58
+ collectionDelegate: returnCollectedData ? self : nil
59
+ ) else {
60
+ print("An error occurred while starting the Inquiry, invalid config.")
61
+ return
62
+ }
63
+ DispatchQueue.main.async {
64
+ guard let viewController = self.findTopViewController() else {
65
+ self.sendEvent(
66
+ withName: "onError",
67
+ body: [
68
+ "debugMessage": "Couldn't resolve a root view controller"
69
+ ]
70
+ )
71
+ return
71
72
  }
72
- } catch {
73
- print("An error occurred while starting the Inquiry: \(error.localizedDescription)")
73
+
74
+ Inquiry(
75
+ config: config,
76
+ delegate: self
77
+ ).start(from: viewController)
74
78
  }
75
79
  }
76
80
 
@@ -85,7 +89,7 @@ class PersonaInquiry2: RCTEventEmitter {
85
89
  }
86
90
  }
87
91
 
88
- // MARK: - Inquiry Delegate Methods
92
+ // MARK: - InquiryDelegate
89
93
 
90
94
  extension PersonaInquiry2: InquiryDelegate {
91
95
  func inquiryComplete(inquiryId: String, status: String, fields: [String: InquiryField]) {
@@ -94,7 +98,8 @@ extension PersonaInquiry2: InquiryDelegate {
94
98
  body: [
95
99
  "inquiryId": inquiryId,
96
100
  "status": status,
97
- "fields": fieldsToDictionary(fields: fields)
101
+ "fields": fieldsToDictionary(fields: fields),
102
+ "collectedData": collectedData?.toDictionary()
98
103
  ]
99
104
  )
100
105
  }
@@ -120,100 +125,108 @@ extension PersonaInquiry2: InquiryDelegate {
120
125
  }
121
126
  }
122
127
 
123
- // MARK: - Helpers
128
+ // MARK: - InquiryCollectionDelegate
124
129
 
125
- extension PersonaInquiry2 {
130
+ extension PersonaInquiry2: InquiryCollectionDelegate {
126
131
 
127
- enum InvalidConfiguration: Error {
128
- case argumentError(String)
132
+ func collectionComplete(data: InquiryData) {
133
+ self.collectedData = data
129
134
  }
135
+ }
130
136
 
131
- /// Creates an InquiryConfiguration based on the passed in parameters
132
- private func createConfig(templateId: String?,
133
- templateVersion: String?,
134
- inquiryId: String? = nil,
135
- sessionToken: String? = nil,
136
- referenceId: String? = nil,
137
- accountId: String? = nil,
138
- environment: Environment? = .production,
139
- theme: InquiryTheme? = nil,
140
- fields: [String: InquiryField]? = nil) throws -> InquiryConfiguration {
141
-
142
- // If we have an inquiry ID there is only one path to take
143
- if let inquiryId = inquiryId {
144
- return InquiryConfiguration(
145
- inquiryId: inquiryId,
146
- sessionToken: sessionToken,
147
- theme: theme
148
- // fields: fields // TODO: Uncomment this once it's enabled
149
- )
150
- }
151
- // Use the template ID if we have it
152
- else if let templateId = templateId {
153
-
154
- // Use the account ID if we have it
155
- if let accountId = accountId {
156
- return InquiryConfiguration(
157
- templateId: templateId,
158
- accountId: accountId,
159
- environment: environment,
160
- fields: fields,
161
- theme: theme
162
- )
163
- }
164
- // Use the reference ID if we have it
165
- else if let referenceId = referenceId {
166
- return InquiryConfiguration(
167
- templateId: templateId,
168
- referenceId: referenceId,
169
- environment: environment,
170
- fields: fields,
171
- theme: theme
172
- )
173
- }
137
+ extension InquiryData {
138
+
139
+ func toDictionary() -> [String: Any?] {
140
+ var inquiryData = [[String: Any?]]()
141
+ for data in self.stepData {
142
+ var currentStepData = [String: Any?]()
143
+
144
+ switch data {
145
+ case .ui(let uiData):
146
+ currentStepData["type"] = "UiStepData"
147
+ currentStepData["stepName"] = uiData.name
148
+ currentStepData["componentParams"] = uiData.componentData.reduce(into: [[String: Any?]]()) { partial, element in
149
+ switch element {
150
+ case .int(let key, let value):
151
+ partial.append([key: value])
152
+ case .bool(let key, let value):
153
+ partial.append([key: value])
154
+ case .string(let key, let value):
155
+ partial.append([key: value])
156
+ case .strings(let key, let value):
157
+ partial.append([key: value])
158
+ case .double(let key, let value):
159
+ partial.append([key: value])
160
+ case .address(let key, let value):
161
+ partial.append([key: value])
162
+ default:
163
+ return
164
+ }
165
+ }
166
+ case .selfie(let selfieData):
167
+ currentStepData["type"] = "SelfieStepData"
168
+ currentStepData["stepName"] = selfieData.name
169
+ if let centerPhoto = selfieData.centerPhoto {
170
+ currentStepData["centerCapture"] = [
171
+ "captureMethod": centerPhoto.captureMethod,
172
+ "absoluteFilePath": centerPhoto.filePath
173
+ ]
174
+ }
174
175
 
175
- // Otherwise only use the template ID
176
- return InquiryConfiguration(
177
- templateId: templateId,
178
- environment: environment,
179
- fields: fields,
180
- theme: theme
181
- )
182
- }
183
- // Otherwise use the template version
184
- else if let templateVersion = templateVersion {
185
-
186
- // Use the account ID if we have it
187
- if let accountId = accountId {
188
- return InquiryConfiguration(
189
- templateVersion: templateVersion,
190
- accountId: accountId,
191
- environment: environment,
192
- fields: fields,
193
- theme: theme
194
- )
195
- }
196
- // Use the reference ID if we have it
197
- else if let referenceId = referenceId {
198
- return InquiryConfiguration(
199
- templateVersion: templateVersion,
200
- referenceId: referenceId,
201
- environment: environment,
202
- fields: fields,
203
- theme: theme
204
- )
205
- }
176
+ if let leftPhoto = selfieData.leftPhoto {
177
+ currentStepData["leftCapture"] = [
178
+ "captureMethod": leftPhoto.captureMethod,
179
+ "absoluteFilePath": leftPhoto.filePath
180
+ ]
181
+ }
206
182
 
207
- // Otherwise only use the template version
208
- return InquiryConfiguration(
209
- templateVersion: templateVersion,
210
- environment: environment,
211
- fields: fields,
212
- theme: theme
213
- )
183
+ if let rightPhoto = selfieData.rightPhoto {
184
+ currentStepData["rightCapture"] = [
185
+ "captureMethod": rightPhoto.captureMethod,
186
+ "absoluteFilePath": rightPhoto.filePath
187
+ ]
188
+ }
189
+
190
+ case .governmentId(let govIdData):
191
+ currentStepData["type"] = "GovernmentIdStepData"
192
+ currentStepData["stepName"] = govIdData.name
193
+
194
+ var captures = [[String: Any]]()
195
+ for file in govIdData.files {
196
+ captures.append([
197
+ "idClass": govIdData.idClass,
198
+ "captureMethod": file.captureMethod,
199
+ "side": file.page,
200
+ "frames": file.frames.map {
201
+ ["absoluteFilePath": $0.filePath]
202
+ }
203
+ ])
204
+ }
205
+ currentStepData["captures"] = captures
206
+ case .document(let docData):
207
+ currentStepData["type"] = "DocumentStepData"
208
+ currentStepData["stepName"] = docData.name
209
+
210
+ var documents = [[String: String]]()
211
+ for file in docData.files {
212
+ documents.append(["absoluteFilePath": file.filePath])
213
+ }
214
+ currentStepData["documents"] = documents
215
+ default:
216
+ continue
217
+ }
218
+ inquiryData.append(currentStepData)
214
219
  }
220
+ return ["stepData": inquiryData]
221
+ }
222
+ }
223
+
224
+ // MARK: - Helpers
225
+
226
+ extension PersonaInquiry2 {
215
227
 
216
- throw InvalidConfiguration.argumentError("Invalid arguments. Please refer to documentation or support for assistance.")
228
+ enum InvalidConfiguration: Error {
229
+ case argumentError(String)
217
230
  }
218
231
 
219
232
  /// Converts a dictionary into a theme