@pagopa/io-react-native-iso18013 0.3.0 → 0.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.
Files changed (110) hide show
  1. package/README.md +10 -5
  2. package/android/build.gradle +7 -1
  3. package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeCborModule.kt +69 -134
  4. package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeIso18013Module.kt +254 -183
  5. package/android/src/test/java/com/ioreactnativeiso18013/Base64UtilsTest.kt +66 -0
  6. package/android/src/test/java/com/ioreactnativeiso18013/IoReactNativeIso18013Test.kt +508 -0
  7. package/ios/Base64Utils.swift +10 -3
  8. package/ios/IoReactNativeCbor.mm +21 -21
  9. package/ios/IoReactNativeCbor.swift +116 -138
  10. package/ios/IoReactNativeIso18013.mm +10 -10
  11. package/ios/IoReactNativeIso18013.swift +172 -193
  12. package/lib/module/cbor/cbor/README.md +39 -59
  13. package/lib/module/cbor/cbor/decoder.js +13 -23
  14. package/lib/module/cbor/cbor/decoder.js.map +1 -1
  15. package/lib/module/cbor/cbor/error.js +15 -0
  16. package/lib/module/cbor/cbor/error.js.map +1 -0
  17. package/lib/module/cbor/cbor/index.js +1 -0
  18. package/lib/module/cbor/cbor/index.js.map +1 -1
  19. package/lib/module/cbor/cose/README.md +39 -9
  20. package/lib/module/cbor/cose/error.js +17 -0
  21. package/lib/module/cbor/cose/error.js.map +1 -0
  22. package/lib/module/cbor/cose/index.js +1 -0
  23. package/lib/module/cbor/cose/index.js.map +1 -1
  24. package/lib/module/cbor/cose/sign.js +4 -3
  25. package/lib/module/cbor/cose/sign.js.map +1 -1
  26. package/lib/module/iso18013/index.js +0 -1
  27. package/lib/module/iso18013/index.js.map +1 -1
  28. package/lib/module/iso18013/iso18013-5/README.md +120 -44
  29. package/lib/module/iso18013/iso18013-5/error.js +25 -0
  30. package/lib/module/iso18013/iso18013-5/error.js.map +1 -0
  31. package/lib/module/iso18013/iso18013-5/index.js +3 -2
  32. package/lib/module/iso18013/iso18013-5/index.js.map +1 -1
  33. package/lib/module/iso18013/iso18013-5/proximity.js +20 -15
  34. package/lib/module/iso18013/iso18013-5/proximity.js.map +1 -1
  35. package/lib/module/iso18013/iso18013-5/{schema.js → request.js} +1 -37
  36. package/lib/module/iso18013/iso18013-5/request.js.map +1 -0
  37. package/lib/module/iso18013/iso18013-7/README.md +66 -41
  38. package/lib/module/iso18013/iso18013-7/error.js +15 -0
  39. package/lib/module/iso18013/iso18013-7/error.js.map +1 -0
  40. package/lib/module/iso18013/iso18013-7/index.js +1 -0
  41. package/lib/module/iso18013/iso18013-7/index.js.map +1 -1
  42. package/lib/module/iso18013/iso18013-7/remote.js +11 -24
  43. package/lib/module/iso18013/iso18013-7/remote.js.map +1 -1
  44. package/lib/module/schema.js +46 -0
  45. package/lib/module/schema.js.map +1 -0
  46. package/lib/typescript/src/cbor/cbor/decoder.d.ts +12 -22
  47. package/lib/typescript/src/cbor/cbor/decoder.d.ts.map +1 -1
  48. package/lib/typescript/src/cbor/cbor/error.d.ts +68 -0
  49. package/lib/typescript/src/cbor/cbor/error.d.ts.map +1 -0
  50. package/lib/typescript/src/cbor/cbor/index.d.ts +1 -1
  51. package/lib/typescript/src/cbor/cbor/index.d.ts.map +1 -1
  52. package/lib/typescript/src/cbor/cose/error.d.ts +68 -0
  53. package/lib/typescript/src/cbor/cose/error.d.ts.map +1 -0
  54. package/lib/typescript/src/cbor/cose/index.d.ts +1 -1
  55. package/lib/typescript/src/cbor/cose/index.d.ts.map +1 -1
  56. package/lib/typescript/src/cbor/cose/sign.d.ts +4 -3
  57. package/lib/typescript/src/cbor/cose/sign.d.ts.map +1 -1
  58. package/lib/typescript/src/iso18013/index.d.ts +0 -1
  59. package/lib/typescript/src/iso18013/index.d.ts.map +1 -1
  60. package/lib/typescript/src/iso18013/iso18013-5/error.d.ts +73 -0
  61. package/lib/typescript/src/iso18013/iso18013-5/error.d.ts.map +1 -0
  62. package/lib/typescript/src/iso18013/iso18013-5/index.d.ts +4 -3
  63. package/lib/typescript/src/iso18013/iso18013-5/index.d.ts.map +1 -1
  64. package/lib/typescript/src/iso18013/iso18013-5/proximity.d.ts +21 -14
  65. package/lib/typescript/src/iso18013/iso18013-5/proximity.d.ts.map +1 -1
  66. package/lib/typescript/src/iso18013/iso18013-5/{schema.d.ts → request.d.ts} +1 -39
  67. package/lib/typescript/src/iso18013/iso18013-5/request.d.ts.map +1 -0
  68. package/lib/typescript/src/iso18013/iso18013-7/error.d.ts +68 -0
  69. package/lib/typescript/src/iso18013/iso18013-7/error.d.ts.map +1 -0
  70. package/lib/typescript/src/iso18013/iso18013-7/index.d.ts +3 -2
  71. package/lib/typescript/src/iso18013/iso18013-7/index.d.ts.map +1 -1
  72. package/lib/typescript/src/iso18013/iso18013-7/remote.d.ts +11 -25
  73. package/lib/typescript/src/iso18013/iso18013-7/remote.d.ts.map +1 -1
  74. package/lib/typescript/src/iso18013/types.d.ts +25 -0
  75. package/lib/typescript/src/iso18013/types.d.ts.map +1 -1
  76. package/lib/typescript/src/schema.d.ts +76 -0
  77. package/lib/typescript/src/schema.d.ts.map +1 -0
  78. package/package.json +1 -1
  79. package/src/cbor/cbor/README.md +39 -59
  80. package/src/cbor/cbor/decoder.ts +13 -23
  81. package/src/cbor/cbor/error.ts +23 -0
  82. package/src/cbor/cbor/index.ts +5 -1
  83. package/src/cbor/cose/README.md +39 -9
  84. package/src/cbor/cose/error.ts +23 -0
  85. package/src/cbor/cose/index.ts +5 -1
  86. package/src/cbor/cose/sign.ts +5 -4
  87. package/src/iso18013/index.ts +0 -7
  88. package/src/iso18013/iso18013-5/README.md +120 -44
  89. package/src/iso18013/iso18013-5/error.ts +35 -0
  90. package/src/iso18013/iso18013-5/index.ts +9 -8
  91. package/src/iso18013/iso18013-5/proximity.ts +21 -17
  92. package/src/iso18013/iso18013-5/{schema.ts → request.ts} +0 -42
  93. package/src/iso18013/iso18013-7/README.md +66 -41
  94. package/src/iso18013/iso18013-7/error.ts +21 -0
  95. package/src/iso18013/iso18013-7/index.ts +6 -5
  96. package/src/iso18013/iso18013-7/remote.ts +12 -34
  97. package/src/iso18013/types.ts +24 -0
  98. package/src/schema.ts +55 -0
  99. package/lib/module/cbor/cbor/failure.js +0 -2
  100. package/lib/module/cbor/cbor/failure.js.map +0 -1
  101. package/lib/module/cbor/cose/failure.js +0 -2
  102. package/lib/module/cbor/cose/failure.js.map +0 -1
  103. package/lib/module/iso18013/iso18013-5/schema.js.map +0 -1
  104. package/lib/typescript/src/cbor/cbor/failure.d.ts +0 -15
  105. package/lib/typescript/src/cbor/cbor/failure.d.ts.map +0 -1
  106. package/lib/typescript/src/cbor/cose/failure.d.ts +0 -15
  107. package/lib/typescript/src/cbor/cose/failure.d.ts.map +0 -1
  108. package/lib/typescript/src/iso18013/iso18013-5/schema.d.ts.map +0 -1
  109. package/src/cbor/cbor/failure.ts +0 -18
  110. package/src/cbor/cose/failure.ts +0 -20
@@ -5,8 +5,6 @@ import React
5
5
  @objc(IoReactNativeIso18013)
6
6
  class IoReactNativeIso18013: RCTEventEmitter {
7
7
 
8
- private typealias ME = ModuleException
9
-
10
8
  @objc
11
9
  override static func requiresMainQueueSetup() -> Bool {
12
10
  return true
@@ -39,8 +37,7 @@ class IoReactNativeIso18013: RCTEventEmitter {
39
37
 
40
38
  /**
41
39
  Starts the proximity flow by allocating the necessary resources and initializing the Bluetooth stack.
42
- Resolves to true or rejects if an error occurs.
43
-
40
+ Resolves to true or rejects with an error code defined in ``ModuleErrorCodes``.
44
41
  - Parameters:
45
42
  - certificates: Two-dimensional array of base64 strings representing DER encoded X.509 certificate which are used to authenticate the verifier app
46
43
  - resolve: The promise to be resolved
@@ -49,56 +46,60 @@ class IoReactNativeIso18013: RCTEventEmitter {
49
46
  @objc(start:withResolver:withRejecter:)
50
47
  func start(
51
48
  certificates: [Any],
52
- _ resolve: @escaping RCTPromiseResolveBlock,
49
+ resolve: @escaping RCTPromiseResolveBlock,
53
50
  reject: @escaping RCTPromiseRejectBlock
54
51
  ){
55
52
  do {
56
- let certsData = parseCertificates(certificates)
53
+ let certsData = try parseCertificates(certificates)
57
54
  try Proximity.shared.start(certsData)
58
55
  resolve(true)
59
- } catch let error {
60
- ME.startError.reject(reject: reject, ("error", error.localizedDescription))
56
+ } catch let proximityError as ProximityError {
57
+ reject(ModuleErrorCodes.generateResponseError.rawValue, proximityError.description, proximityError)
58
+ } catch let parsingError as ParsingError{
59
+ reject(ModuleErrorCodes.generateResponseError.rawValue, parsingError.description, parsingError)
60
+ } catch {
61
+ reject(ModuleErrorCodes.generateResponseError.rawValue, error.localizedDescription, error)
61
62
  }
62
63
  }
63
64
 
64
65
  /**
65
66
  Utility function to parse an array coming from the React Native Bridge into an array of Data representing DER encoded X.509 certificates.
66
-
67
67
  - Parameters:
68
- - certificates:Two-dimensional array of base64 strings representing DER encoded X.509 certificate
69
-
68
+ - certificates: Two-dimensional array of base64 strings representing DER encoded X.509 certificate
69
+ - Throws: `ParsingError` if the provided certificate is not a valid base64 encoded string.
70
70
  - Returns: A two-dimensional array of Data containing DER encoded X.509 certificates.
71
71
  */
72
- private func parseCertificates(_ certificates: Array<Any>) -> [[Data]] {
73
- return certificates.compactMap { item in
72
+ private func parseCertificates(_ certificates: [Any]) throws -> [[Data]] {
73
+ return try certificates.enumerated().compactMap { (chainIndex, item) in
74
74
  guard let certStrings = item as? [String] else {
75
- return nil
75
+ throw ParsingError.certificatesNotValid("Certificate chain at index \(chainIndex) is not an array of strings.")
76
76
  }
77
- let dataArray = certStrings.compactMap { certString in
78
- Data(base64Encoded: certString)
77
+ return try certStrings.enumerated().map { (certIndex, certString) in
78
+ guard let data = Data(base64Encoded: certString) else {
79
+ throw ParsingError.certificatesNotValid("Certificate at index \(certIndex) in the chain at index \(chainIndex) is not a valid base64 string.")
80
+ }
81
+ return data
79
82
  }
80
- return dataArray.isEmpty ? nil : dataArray
81
83
  }
82
84
  }
83
85
 
84
86
  /**
85
87
  Creates a QR code to be scanned in order to initialize the presentation.
86
- Resolves with the QR code strings.
87
-
88
+ Resolves with a string containing the QR code or rejects with an error code defined in ``ModuleErrorCodes``.
88
89
  - Parameters:
89
90
  - resolve: The promise to be resolved
90
91
  - reject: The promise to be rejected
91
- */
92
+ */
92
93
  @objc(getQrCodeString:withRejecter:)
93
94
  func getQrCodeString(
94
- _ resolve: @escaping RCTPromiseResolveBlock,
95
+ resolve: @escaping RCTPromiseResolveBlock,
95
96
  reject: @escaping RCTPromiseRejectBlock
96
97
  ) {
97
98
  do{
98
99
  let qrCodeString = try Proximity.shared.getQrCode()
99
100
  resolve(qrCodeString)
100
- } catch let error {
101
- ME.qrCodeError.reject(reject: reject, ("error", error.localizedDescription))
101
+ } catch {
102
+ reject(ModuleErrorCodes.getQrCodeError.rawValue, error.localizedDescription, error)
102
103
  }
103
104
  }
104
105
 
@@ -106,33 +107,35 @@ class IoReactNativeIso18013: RCTEventEmitter {
106
107
  Parses an array of documents from the React Native bridge which doesn't have any typing to an array of ``IOWalletProximity.ProximityDocument``.
107
108
  It checks if each element in the array has `issuerSignedContent`, `alias` and `docType` properties in order to build a ``IOWalletProximity.ProximityDocument``, then it appens it to the array.
108
109
  The result can be fed to ``IOWalletProximity.generateResponse``.
109
-
110
110
  - Parameters:
111
111
  - documents: An array containing documents. Each document is defined as a map containing:
112
112
  - issuerSignedContent which is a base64 or base64url encoded string representing the credential;
113
113
  - alias which is the alias of the key used to sign the credential;
114
114
  - docType which is the document type.
115
-
116
- - Throws: `ModuleException.invalidDocRequested.error()` and `ModuleException.invalidDocRequested.error()` iif an error occurs while parsing the document
117
-
118
- - Returns: An array of `ProximityDocument` containg the documents to be presented
115
+ - Throws: `ParsingError`:
116
+ - If the provided documents array do not contain a dictionary;
117
+ - If the provided dictionary doesn't adhere to the structure we expect;
118
+ - If the issuerSignedContent is not a valid base64 or base64url encoded string;
119
+ - If the creation of a ProximityDdocument fails.
120
+ - Returns: An array of `ProximityDocument` containg the documents to be presented.
119
121
  */
120
122
  private func parseDocuments(documents: [Any]) throws -> [ProximityDocument] {
121
- return try documents.compactMap{ (element) -> ProximityDocument in
122
- guard let dict : [String: Any] = element as? [String: Any] else { throw ModuleException.unableToDecode.error() }
123
+ return try documents.map{ (element) -> ProximityDocument in
124
+ guard let dict : [String: Any] = element as? [String: Any] else { throw ParsingError.documentsNotValid("The provided documents array element can't be parsed as a dictionary") }
123
125
  guard
124
126
  let issuerSignedContent = dict["issuerSignedContent"] as? String,
125
127
  let alias = dict["alias"] as? String,
126
128
  let docType = dict["docType"] as? String,
127
129
  let decodedIssuerSignedContent = try? Base64Utils.decodeBase64OrBase64URL(base: issuerSignedContent)
128
- else { throw ModuleException.invalidDocRequested.error() }
129
-
130
+ else {
131
+ throw ParsingError.documentsNotValid ("The provided issuerSignedContent is not a valid base64 or base64url or don't contain the required fields alas, doctype and issuerSignedContent")
132
+ }
130
133
  guard let document = ProximityDocument(
131
134
  docType: docType,
132
135
  issuerSigned: [UInt8](decodedIssuerSignedContent),
133
136
  deviceKeyTag: alias
134
137
  ) else {
135
- throw ModuleException.invalidDocRequested.error()
138
+ throw ParsingError.documentsNotValid("The parsing for the document with docType \(docType) failed")
136
139
  }
137
140
  return document
138
141
  }
@@ -142,72 +145,58 @@ class IoReactNativeIso18013: RCTEventEmitter {
142
145
  Parses a dictionary of accepted fields for the presentation from the React Native bridge which doesn't have any typing to a ``AcceptedFieldsDict`` dictionary.
143
146
  It checks if each element in the array is a dictionary where the key is a string, and the value is another dictionary. This nested dictionary has a string as its key and a boolean as its value, then it appens it to the array.
144
147
  The result can be fed to ``IOWalletProximity.generateResponse``.
145
-
146
148
  - Parameters:
147
- - acceptedFields: A dictionary of any elements. In order to be added to the result dictionary each element must be shaped as ``AcceptedFieldsDict`` thus as [String: [String: [String: Bool]]]
148
-
149
- - Throws: `NSError` if a value doesn't has the ``AcceptedFieldsDict`` shape or the result dictionary is empty
150
-
151
- - Returns: An ``AcceptedFieldsDict`` containg the accepted fields to be presented
152
-
149
+ - acceptedFields: A dictionary of any elements. In order to be added to the result dictionary each element must be shaped as ``AcceptedFieldsDict`` thus as [String: [String: [String: Bool]]].
150
+ - Throws: `ParsingError` if a value doesn't has the ``AcceptedFieldsDict`` shape or the result dictionary is empty.
151
+ - Returns: An ``AcceptedFieldsDict`` containg the accepted fields to be presented.
153
152
  */
154
- private func parseAcceptedFields(acceptedFields: [AnyHashable: Any]) throws -> AcceptedFieldsDict {
153
+ private func parseAcceptedFields(acceptedFields: [AnyHashable: Any]) throws(ParsingError) -> AcceptedFieldsDict {
155
154
  var result: AcceptedFieldsDict = [:]
156
155
 
157
156
  for (key, value) in acceptedFields {
158
157
  guard let keyString = key as? String else {
159
- throw NSError(domain: "ParseAcceptedFields", code: -1, userInfo: [NSLocalizedDescriptionKey: "Key must be a String."])
158
+ throw ParsingError.acceptedFieldsNotValid("The accepted fields keys must be a String")
160
159
  }
161
160
  guard let valueDict = value as? [String: [String: Bool]] else {
162
- throw NSError(domain: "ParseAcceptedFields", code: -1, userInfo: [NSLocalizedDescriptionKey: "Value must be of type [String: [String: Bool]]."])
161
+ throw ParsingError.acceptedFieldsNotValid("The accepted fields values must be of type [String: [String: Bool]]")
163
162
  }
164
163
  result[keyString] = valueDict
165
164
  }
166
165
 
167
- if(result.isEmpty){
168
- throw NSError(domain: "ParseAcceptedFields", code: -1, userInfo: [NSLocalizedDescriptionKey: "Error parsing documents"])
169
- }
170
-
171
166
  return result
172
167
  }
173
168
 
174
169
  /**
175
- Generates a response containing the documents and the fields which the user decided to present.
176
- It parses the untyped ``documents`` and ``acceptedFields`` parameters and feeds them to the ``IOWalletProximity.generateDeviceResponse`` function.
177
- It resolves the promise with the response as a base64 encoded string.
178
- It rejects the promise if an error occurs during the parameters parsing or while generating the device response.
179
-
180
- - Parameters:
181
- - documents: An array containing documents. Each document is defined as a map containing:
182
- - issuerSignedContent which is a base64 or base64url encoded string representing the credential;
183
- - alias which is the alias of the key used to sign the credential;
184
- - docType which is the document type.
185
- - issuerSignedContent which is a base64 or base64url encoded string representing the credential;
186
- - alias which is the alias of the key used to sign the credential;
187
- - docType which is the document type.
188
- - acceptedFields: A dictionary of elements, where each element must adhere to the structure of AcceptedFieldsDict—specifically, a [String: [String: [String: Bool]]]. The outermost key represents the credentia doctypel. The inner dictionary contains namespaces, and for each namespace, there is another dictionary mapping requested claims to a boolean value, which indicates whether the user is willing to present the corresponding claim. Example:
170
+ Generates a response containing the documents and the fields which the user decided to present.
171
+ It parses the untyped ``documents`` and ``acceptedFields`` parameters and feeds them to the ``IOWalletProximity.generateDeviceResponse`` function.
172
+ Resolves with a base64 encoded response or rejects with an error code defined in ``ModuleErrorCodes``.
173
+ - Parameters:
174
+ - documents: An array containing documents. Each document is defined as a map containing:
175
+ - issuerSignedContent which is a base64 or base64url encoded string representing the credential;
176
+ - alias which is the alias of the key used to sign the credential;
177
+ - docType which is the document type.
178
+ - acceptedFields: A dictionary of elements, where each element must adhere to the structure of AcceptedFieldsDict—specifically, a [String: [String: [String: Bool]]]. The outermost key represents the credential doctype. The inner dictionary contains namespaces, and for each namespace, there is another dictionary mapping requested claims to a boolean value, which indicates whether the user is willing to present the corresponding claim. Example:
189
179
 
190
180
 
191
- {
192
- "org.iso.18013.5.1.mDL": {
193
- "org.iso.18013.5.1": {
194
- "hair_colour": true,
195
- "given_name_national_character": true,
196
- "family_name_national_character": true,
197
- "given_name": true,
198
- }
181
+ {
182
+ "org.iso.18013.5.1.mDL": {
183
+ "org.iso.18013.5.1": {
184
+ "hair_colour": true,
185
+ "given_name_national_character": true,
186
+ "family_name_national_character": true,
187
+ "given_name": true,
199
188
  }
200
- }
201
-
202
- - resolve: The promise to be resolved
203
- - reject: The promise to be rejected
204
-
205
- */
189
+ }
190
+ }
191
+
192
+ - resolve: The promise to be resolved.
193
+ - reject: The promise to be rejected.
194
+ */
206
195
  @objc(generateResponse:withAcceptedFields:withResolver:withRejecter:)
207
196
  func generateResponse(
208
197
  documents: Array<Any>,
209
198
  acceptedFields: [AnyHashable: Any],
210
- _ resolve: @escaping RCTPromiseResolveBlock,
199
+ resolve: @escaping RCTPromiseResolveBlock,
211
200
  reject: @escaping RCTPromiseRejectBlock
212
201
  ){
213
202
  do {
@@ -216,16 +205,21 @@ class IoReactNativeIso18013: RCTEventEmitter {
216
205
  let deviceResponse = try Proximity.shared.generateDeviceResponse(items: items, documents: parsedDocuments, sessionTranscript: nil)
217
206
  let strDeviceResponse = Data(deviceResponse).base64EncodedString()
218
207
  resolve(strDeviceResponse)
219
- }catch{
220
- ME.generateDeviceResponseError.reject(reject: reject, ("error", error.localizedDescription))
208
+ }
209
+ catch let proximityError as ProximityError {
210
+ reject(ModuleErrorCodes.generateResponseError.rawValue, proximityError.description, proximityError)
211
+ }
212
+ catch let parsingError as ParsingError {
213
+ reject(ModuleErrorCodes.generateResponseError.rawValue, parsingError.description, parsingError)
214
+ } catch {
215
+ reject(ModuleErrorCodes.generateResponseError.rawValue, error.localizedDescription, error)
221
216
  }
222
217
  }
223
218
 
224
219
  /**
225
220
  Sends a response containing the documents and the fields which the user decided to present generated by ``generateResponse``.
226
- It resolves to true after sending the response, otherwise it rejects if an error occurs while decoding the response.
227
221
  Currently there's not evidence of the verifier app responding to this request, thus we don't handle the response.
228
-
222
+ Resolves with a true boolean in case of success or rejects with an error code defined in ``ModuleErrorCodes``.
229
223
  - Parameters:
230
224
  - response: A base64 encoded string containing the response generated by ``generateResponse``
231
225
  - resolve: The promise to be resolved
@@ -234,7 +228,7 @@ class IoReactNativeIso18013: RCTEventEmitter {
234
228
  @objc(sendResponse:withResolver:withRejecter:)
235
229
  func sendResponse(
236
230
  response: String,
237
- _ resolve: @escaping RCTPromiseResolveBlock,
231
+ resolve: @escaping RCTPromiseResolveBlock,
238
232
  reject: @escaping RCTPromiseRejectBlock
239
233
  ){
240
234
  do{
@@ -244,50 +238,49 @@ class IoReactNativeIso18013: RCTEventEmitter {
244
238
  resolve(true)
245
239
  }
246
240
  }catch let error {
247
- ME.sendResponseError.reject(reject: reject, ("error", error.localizedDescription))
241
+ reject(ModuleErrorCodes.sendResponseError.rawValue, error.localizedDescription, error)
248
242
  }
249
243
  }
250
244
 
251
245
  /**
252
246
  Sends an error response during the presentation according to the SessionData status codes defined in table 20 of the ISO18013-5 standard.
247
+ Resolves to true or rejects with an error code defined in ``ModuleErrorCodes``.
253
248
  - Parameters:
254
- - status: The status error to be sent is an integer of type ``SessionDataStatus``:
249
+ - code: The status error to be sent is an integer of type ``SessionDataStatus``:
255
250
  ```
256
251
  10 -> Error: session encryption
257
252
  11 -> Error: CBOR decoding
258
253
  20 -> Session termination
259
254
  ```
260
- - resolve: The promise to be resolved
261
- - reject: The promise to be rejected
255
+ - resolve: The promise to be resolved.
256
+ - reject: The promise to be rejected.
262
257
  */
263
258
  @objc(sendErrorResponse:withResolver:withRejecter:)
264
- func sendErrorResponse(status: UInt64, _ resolve: @escaping RCTPromiseResolveBlock,
265
- reject: @escaping RCTPromiseRejectBlock){
259
+ func sendErrorResponse(code: UInt64, _ resolve: @escaping RCTPromiseResolveBlock,
260
+ reject: @escaping RCTPromiseRejectBlock){
266
261
  do{
267
- if let statusEnum = SessionDataStatus(rawValue: status) {
262
+ if let statusEnum = SessionDataStatus(rawValue: code) {
268
263
  try Proximity.shared.errorPresentation(statusEnum)
269
264
  } else {
270
- ME.sendResponseError.reject(reject: reject, ("error", "Invalid status code"))
265
+ reject(ModuleErrorCodes.sendErrorResponseError.rawValue, "Invalid status code provided: \(code)", nil)
271
266
  }
272
267
  resolve(true)
273
268
  }catch let error{
274
- ME.sendResponseError.reject(reject: reject, ("error", error.localizedDescription))
269
+ reject(ModuleErrorCodes.sendErrorResponseError.rawValue, error.localizedDescription, error)
275
270
  }
276
271
  }
277
272
 
278
273
 
279
274
  /**
280
275
  Closes the bluetooth connection and clears any resource.
281
- It resolves to true after closing the connection.
282
-
276
+ Resolves to true after closing the connection or rejects with an error code defined in ``ModuleErrorCodes``.
283
277
  - Parameters:
284
- - resolve: The promise to be resolved
285
- - reject: The promise to be rejected
286
-
278
+ - resolve: The promise to be resolved.
279
+ - reject: The promise to be rejected.
287
280
  */
288
281
  @objc(close:withRejecter:)
289
282
  func close(
290
- _ resolve: @escaping RCTPromiseResolveBlock,
283
+ resolve: @escaping RCTPromiseResolveBlock,
291
284
  reject: @escaping RCTPromiseRejectBlock
292
285
  ) {
293
286
  Proximity.shared.stop()
@@ -295,13 +288,11 @@ class IoReactNativeIso18013: RCTEventEmitter {
295
288
  }
296
289
 
297
290
  /**
298
- Converts a device requested from the `onDocumentRequestReceived` callback into a serializable JSON.
299
-
300
- - Parameters:
301
- - request: The request returned from `onDocumentRequestReceived` which contains an array of tuples consists of a doctype, namespaces and the requested claims with a boolean value indicating wether or not the device which is making the request has an intent to retain the dataß
302
-
303
- - Returns: A JSON string representing the device request or nil if an error occurs
304
- */
291
+ Converts a device requested from the `onDocumentRequestReceived` callback into a serializable JSON.
292
+ - Parameters:
293
+ - request: The request returned from `onDocumentRequestReceived` which contains an array of tuples consists of a doctype, namespaces and the requested claims with a boolean value indicating wether or not the device which is making the request has an intent to retain the data.
294
+ - Returns: A JSON string representing the device request or nil if an error occurs.
295
+ */
305
296
  private func deviceRequestToJson(request: [(docType: String, nameSpaces: [String: [String: Bool]], isAuthenticated: Bool)]?) -> String? {
306
297
  var jsonRequest : [String: AnyHashable] = [:]
307
298
  request?.forEach({
@@ -379,115 +370,103 @@ class IoReactNativeIso18013: RCTEventEmitter {
379
370
  }
380
371
  }
381
372
 
382
- @objc func generateOID4VPDeviceResponse(
383
- _ clientId: String,
373
+ /**
374
+ Generates a CBOR encoded device response for ISO 18013-7 mDL remote presentation using OID4VP.
375
+ Resolves with the base64 encoded device response or rejects with an error code defined in ``ModuleErrorCodes``.
376
+ - Parameters:
377
+ - clientId: The client id extracted from OID4VP session.
378
+ - responseUri: The response URI extracted from OID4VP session.
379
+ - authorizationRequestNonce - The authorization request nonce extracted from OID4VP session.
380
+ - mdocGeneratedNonce - the mdoc generated nonce to be generated.
381
+ - documents: An array containing documents. Each document is defined as a map containing:
382
+ - issuerSignedContent which is a base64 or base64url encoded string representing the credential;
383
+ - alias which is the alias of the key used to sign the credential;
384
+ - docType which is the document type.
385
+ - acceptedFields: A dictionary of elements, where each element must adhere to the structure of AcceptedFieldsDict—specifically, a `[String: [String: [String: Bool]]]`. The outermost key represents the credential doctype. The inner dictionary contains namespaces, and for each namespace, there is another dictionary mapping requested claims to a boolean value, which indicates whether the user is willing to present the corresponding claim. Example:
386
+
387
+
388
+ {
389
+ "org.iso.18013.5.1.mDL": {
390
+ "org.iso.18013.5.1": {
391
+ "hair_colour": true,
392
+ "given_name_national_character": true,
393
+ "family_name_national_character": true,
394
+ "given_name": true,
395
+ },
396
+ {...}
397
+ },
398
+ {...}
399
+ }
400
+
401
+ - resolve: The promise to be resolved.
402
+ - reject: The promise to be rejected.
403
+ */
404
+ @objc(generateOID4VPDeviceResponse:withResponseUri:withAuthorizationRequestNonce:withMdocGeneratedNonce:withDocuments:withAcceptedFields:withResolver:withRejecter:)
405
+ func generateOID4VPDeviceResponse(
406
+ clientId: String,
384
407
  responseUri: String,
385
408
  authorizationRequestNonce: String,
386
409
  mdocGeneratedNonce: String,
387
410
  documents: [Any],
388
- fieldRequestedAndAccepted: String,
411
+ acceptedFields: [AnyHashable: Any],
389
412
  resolver resolve: RCTPromiseResolveBlock,
390
413
  rejecter reject: RCTPromiseRejectBlock
391
414
  ) {
392
415
 
393
416
  do {
394
417
  let sessionTranscript = Proximity.shared.generateOID4VPSessionTranscriptCBOR(
395
- clientId: clientId,
396
- responseUri: responseUri,
397
- authorizationRequestNonce: authorizationRequestNonce,
398
- mdocGeneratedNonce: mdocGeneratedNonce
418
+ clientId: clientId,
419
+ responseUri: responseUri,
420
+ authorizationRequestNonce: authorizationRequestNonce,
421
+ mdocGeneratedNonce: mdocGeneratedNonce
399
422
  )
400
-
401
- let documentsAsProximityDocument = try parseDocuments(documents: documents)
402
-
403
- let items = try JSONDecoder().decode([String : [String : [String : Bool]]].self, from: Data(fieldRequestedAndAccepted.utf8))
404
423
 
405
- do {
406
- let response = try Proximity.shared.generateDeviceResponse(items: items, documents: documentsAsProximityDocument, sessionTranscript: sessionTranscript)
407
- resolve(Data(response).base64EncodedString())
408
- } catch {
409
- ME.unableToGenerateResponse.reject(reject: reject)
410
- return
411
- }
412
- } catch let error as NSError where error.domain == ME.invalidDocRequested.rawValue {
413
- ME.invalidDocRequested.reject(reject: reject)
414
- } catch _ as DecodingError {
415
- ME.invalidItems.reject(reject: reject)
416
- } catch {
417
- ME.unexpected.reject(reject: reject)
424
+ let documentsAsProximityDocument = try parseDocuments(documents: documents)
425
+ let items = try parseAcceptedFields(acceptedFields: acceptedFields)
426
+ let response = try Proximity.shared.generateDeviceResponse(items: items, documents: documentsAsProximityDocument, sessionTranscript: sessionTranscript)
427
+ resolve(Data(response).base64EncodedString())
428
+ } catch let parsingError as ParsingError{
429
+ reject(ModuleErrorCodes.generateOID4VPResponseError.rawValue, parsingError.description, parsingError)
430
+ } catch let proximityError as ProximityError{
431
+ reject(ModuleErrorCodes.generateOID4VPResponseError.rawValue, proximityError.description, proximityError)
418
432
  }
419
- }
420
-
421
- private class DocRequested {
422
- var issuerSignedContent : [UInt8]
423
- var alias : String
424
- var docType : String
425
-
426
- public init(issuerSignedContent: [UInt8], alias: String, docType: String) {
427
- self.issuerSignedContent = issuerSignedContent
428
- self.alias = alias
429
- self.docType = docType
433
+ catch {
434
+ reject(ModuleErrorCodes.generateOID4VPResponseError.rawValue, error.localizedDescription, error)
430
435
  }
431
436
  }
432
-
437
+
433
438
  /**
434
- Wrapper for rejecting with an error.
435
- Add a new case in order to extend the possible errors.
439
+ Custom Error which is thrown when a parsing error occurs in our utility functions which converts data from the bridge to what
440
+ our underlying functions expect.
441
+ This is needed in order to provide a customized description which can include more debug information.
436
442
  */
437
- private enum ModuleException: String, CaseIterable {
443
+ enum ParsingError : Error, CustomStringConvertible {
444
+ case documentsNotValid(String)
445
+ case certificatesNotValid(String)
446
+ case acceptedFieldsNotValid(String)
447
+
448
+ public var description: String {
449
+ switch(self) {
450
+ case .documentsNotValid(let message):
451
+ return message
452
+ case .certificatesNotValid(let message):
453
+ return message
454
+ case .acceptedFieldsNotValid(let message):
455
+ return message
456
+ }
457
+ }
458
+ }
459
+
460
+ // Errors which this module uses to reject a promise
461
+ private enum ModuleErrorCodes: String, CaseIterable {
462
+ // ISO18013-5 related errors
438
463
  case startError = "START_ERROR"
439
- case qrCodeError = "QR_CODE_ERROR"
440
- case generateDeviceResponseError = "GENERATE_DEVICE_RESPONSE_ERROR"
464
+ case getQrCodeError = "GET_QR_CODE_ERROR"
441
465
  case sendResponseError = "SEND_RESPONSE_ERROR"
442
- case sendErrorResponse = "SEND_ERROR_RESPONSE_ERROR"
443
- case invalidDocRequested = "DOC_REQUESTED_PARSING_ERROR"
444
- case unableToGenerateResponse = "UNABLE_TO_GENERATE_RESPONSE_ERROR"
445
- case invalidItems = "REQUESTED_ITEMS_PARSING_ERROR"
446
- case unexpected = "UNEXPECTED_ERROR"
447
- case unableToDecode = "UNABLE_TO_DECODE"
466
+ case sendErrorResponseError = "SEND_ERROR_RESPONSE_ERROR"
467
+ case generateResponseError = "GENERATE_RESPONSE_ERROR"
448
468
 
449
- func error(
450
- userInfo: [String : Any]? = nil
451
- ) -> NSError {
452
- switch self {
453
- case .startError:
454
- return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
455
- case .qrCodeError:
456
- return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
457
- case .generateDeviceResponseError:
458
- return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
459
- case .sendResponseError:
460
- return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
461
- case .sendErrorResponse:
462
- return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
463
- case .invalidDocRequested:
464
- return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
465
- case .unableToGenerateResponse:
466
- return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
467
- case .invalidItems:
468
- return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
469
- case .unexpected:
470
- return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
471
- case .unableToDecode:
472
- return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
473
- }
474
- }
475
-
476
- /**
477
- Rejects the provided promise with the appropriate error message and additional data.
478
-
479
- - Parameters:
480
- - reject: the promise to be rejected.
481
- - moreUserInfo: additional key-value pairs of data to be passed along with the error.
482
-
483
- */
484
- func reject(
485
- reject: RCTPromiseRejectBlock,
486
- _ moreUserInfo: (String, Any)...
487
- ) {
488
- let userInfo = [String: Any](uniqueKeysWithValues: moreUserInfo)
489
- let error = error(userInfo: userInfo)
490
- reject("\(error.code)", error.domain, error)
491
- }
469
+ // ISO18013-7 related errors
470
+ case generateOID4VPResponseError = "GENERATE_OID4VP_RESPONSE_ERROR"
492
471
  }
493
472
  }