@pagopa/io-react-native-iso18013 0.1.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 (113) hide show
  1. package/IoReactNativeIso18013.podspec +29 -0
  2. package/LICENSE +20 -0
  3. package/README.md +30 -0
  4. package/android/build.gradle +83 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/AndroidManifestNew.xml +2 -0
  8. package/android/src/main/java/com/ioreactnativeiso18013/Base64Utils.kt +56 -0
  9. package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeCborModule.kt +209 -0
  10. package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeIso18013Module.kt +439 -0
  11. package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeIso18013Package.kt +17 -0
  12. package/ios/Base64Utils.swift +47 -0
  13. package/ios/IoReactNativeCbor.mm +32 -0
  14. package/ios/IoReactNativeCbor.swift +188 -0
  15. package/ios/IoReactNativeIso18013-Bridging-Header.h +2 -0
  16. package/ios/IoReactNativeIso18013.mm +43 -0
  17. package/ios/IoReactNativeIso18013.swift +491 -0
  18. package/lib/module/cbor/cbor/README.md +95 -0
  19. package/lib/module/cbor/cbor/decoder.js +51 -0
  20. package/lib/module/cbor/cbor/decoder.js.map +1 -0
  21. package/lib/module/cbor/cbor/failure.js +2 -0
  22. package/lib/module/cbor/cbor/failure.js.map +1 -0
  23. package/lib/module/cbor/cbor/index.js +5 -0
  24. package/lib/module/cbor/cbor/index.js.map +1 -0
  25. package/lib/module/cbor/cbor/schema.js +117 -0
  26. package/lib/module/cbor/cbor/schema.js.map +1 -0
  27. package/lib/module/cbor/cbor/schema.utils.js +16 -0
  28. package/lib/module/cbor/cbor/schema.utils.js.map +1 -0
  29. package/lib/module/cbor/cose/README.md +53 -0
  30. package/lib/module/cbor/cose/failure.js +2 -0
  31. package/lib/module/cbor/cose/failure.js.map +1 -0
  32. package/lib/module/cbor/cose/index.js +4 -0
  33. package/lib/module/cbor/cose/index.js.map +1 -0
  34. package/lib/module/cbor/cose/sign.js +23 -0
  35. package/lib/module/cbor/cose/sign.js.map +1 -0
  36. package/lib/module/cbor/index.js +13 -0
  37. package/lib/module/cbor/index.js.map +1 -0
  38. package/lib/module/index.js +8 -0
  39. package/lib/module/index.js.map +1 -0
  40. package/lib/module/iso18013/index.js +14 -0
  41. package/lib/module/iso18013/index.js.map +1 -0
  42. package/lib/module/iso18013/iso18013-5/README.md +234 -0
  43. package/lib/module/iso18013/iso18013-5/index.js +5 -0
  44. package/lib/module/iso18013/iso18013-5/index.js.map +1 -0
  45. package/lib/module/iso18013/iso18013-5/proximity.js +112 -0
  46. package/lib/module/iso18013/iso18013-5/proximity.js.map +1 -0
  47. package/lib/module/iso18013/iso18013-5/schema.js +79 -0
  48. package/lib/module/iso18013/iso18013-5/schema.js.map +1 -0
  49. package/lib/module/iso18013/iso18013-7/README.md +59 -0
  50. package/lib/module/iso18013/iso18013-7/index.js +4 -0
  51. package/lib/module/iso18013/iso18013-7/index.js.map +1 -0
  52. package/lib/module/iso18013/iso18013-7/remote.js +31 -0
  53. package/lib/module/iso18013/iso18013-7/remote.js.map +1 -0
  54. package/lib/module/iso18013/types.js +2 -0
  55. package/lib/module/iso18013/types.js.map +1 -0
  56. package/lib/module/package.json +1 -0
  57. package/lib/typescript/package.json +1 -0
  58. package/lib/typescript/src/cbor/cbor/decoder.d.ts +35 -0
  59. package/lib/typescript/src/cbor/cbor/decoder.d.ts.map +1 -0
  60. package/lib/typescript/src/cbor/cbor/failure.d.ts +15 -0
  61. package/lib/typescript/src/cbor/cbor/failure.d.ts.map +1 -0
  62. package/lib/typescript/src/cbor/cbor/index.d.ts +4 -0
  63. package/lib/typescript/src/cbor/cbor/index.d.ts.map +1 -0
  64. package/lib/typescript/src/cbor/cbor/schema.d.ts +2420 -0
  65. package/lib/typescript/src/cbor/cbor/schema.d.ts.map +1 -0
  66. package/lib/typescript/src/cbor/cbor/schema.utils.d.ts +8 -0
  67. package/lib/typescript/src/cbor/cbor/schema.utils.d.ts.map +1 -0
  68. package/lib/typescript/src/cbor/cose/failure.d.ts +15 -0
  69. package/lib/typescript/src/cbor/cose/failure.d.ts.map +1 -0
  70. package/lib/typescript/src/cbor/cose/index.d.ts +3 -0
  71. package/lib/typescript/src/cbor/cose/index.d.ts.map +1 -0
  72. package/lib/typescript/src/cbor/cose/sign.d.ts +19 -0
  73. package/lib/typescript/src/cbor/cose/sign.d.ts.map +1 -0
  74. package/lib/typescript/src/cbor/index.d.ts +2 -0
  75. package/lib/typescript/src/cbor/index.d.ts.map +1 -0
  76. package/lib/typescript/src/index.d.ts +6 -0
  77. package/lib/typescript/src/index.d.ts.map +1 -0
  78. package/lib/typescript/src/iso18013/index.d.ts +3 -0
  79. package/lib/typescript/src/iso18013/index.d.ts.map +1 -0
  80. package/lib/typescript/src/iso18013/iso18013-5/index.d.ts +4 -0
  81. package/lib/typescript/src/iso18013/iso18013-5/index.d.ts.map +1 -0
  82. package/lib/typescript/src/iso18013/iso18013-5/proximity.d.ts +88 -0
  83. package/lib/typescript/src/iso18013/iso18013-5/proximity.d.ts.map +1 -0
  84. package/lib/typescript/src/iso18013/iso18013-5/schema.d.ts +85 -0
  85. package/lib/typescript/src/iso18013/iso18013-5/schema.d.ts.map +1 -0
  86. package/lib/typescript/src/iso18013/iso18013-7/index.d.ts +3 -0
  87. package/lib/typescript/src/iso18013/iso18013-7/index.d.ts.map +1 -0
  88. package/lib/typescript/src/iso18013/iso18013-7/remote.d.ts +29 -0
  89. package/lib/typescript/src/iso18013/iso18013-7/remote.d.ts.map +1 -0
  90. package/lib/typescript/src/iso18013/types.d.ts +16 -0
  91. package/lib/typescript/src/iso18013/types.d.ts.map +1 -0
  92. package/package.json +163 -0
  93. package/src/cbor/cbor/README.md +95 -0
  94. package/src/cbor/cbor/decoder.ts +56 -0
  95. package/src/cbor/cbor/failure.ts +18 -0
  96. package/src/cbor/cbor/index.ts +9 -0
  97. package/src/cbor/cbor/schema.ts +150 -0
  98. package/src/cbor/cbor/schema.utils.ts +13 -0
  99. package/src/cbor/cose/README.md +53 -0
  100. package/src/cbor/cose/failure.ts +20 -0
  101. package/src/cbor/cose/index.ts +2 -0
  102. package/src/cbor/cose/sign.ts +25 -0
  103. package/src/cbor/index.ts +18 -0
  104. package/src/index.tsx +5 -0
  105. package/src/iso18013/index.ts +25 -0
  106. package/src/iso18013/iso18013-5/README.md +234 -0
  107. package/src/iso18013/iso18013-5/index.ts +23 -0
  108. package/src/iso18013/iso18013-5/proximity.ts +134 -0
  109. package/src/iso18013/iso18013-5/schema.ts +87 -0
  110. package/src/iso18013/iso18013-7/README.md +59 -0
  111. package/src/iso18013/iso18013-7/index.ts +7 -0
  112. package/src/iso18013/iso18013-7/remote.ts +54 -0
  113. package/src/iso18013/types.ts +16 -0
@@ -0,0 +1,491 @@
1
+ import Foundation
2
+ import IOWalletProximity
3
+ import React
4
+
5
+ @objc(IoReactNativeIso18013)
6
+ class IoReactNativeProximity: RCTEventEmitter {
7
+
8
+ private typealias ME = ModuleException
9
+
10
+ @objc
11
+ override static func requiresMainQueueSetup() -> Bool {
12
+ return true
13
+ }
14
+
15
+ override init() {
16
+ super.init()
17
+ setupProximityHandler()
18
+ }
19
+
20
+ /**
21
+ Specifies supported events which will be emitted.
22
+ - `onDeviceConnecting`: Emitted when the device is connecting to the verifier app.
23
+ - `onDeviceConnected`: Emitted when the device is connected to the verifier app.
24
+ - `onDocumentRequestReceived`: Emitted when a document request is received from the verifier app. Carries a payload containing the request data.
25
+ - `onDeviceDisconnected`: Emitted when the device is disconnected from the verifier app.
26
+ - `onError`: Emitted when an error occurs. Carries a payload containing the error data.
27
+ */
28
+ override func supportedEvents() -> [String]! {
29
+ return ["onDeviceConnected", "onDeviceConnecting", "onDeviceDisconnected", "onDocumentRequestReceived", "onError", "unknown"]
30
+ }
31
+
32
+ /**
33
+ Type alias for the accepted fields during the presentation. These are the fields which the user accepted to share.
34
+ It can be fed to the ``IOWalletProximity.generateResponse`` function.
35
+ An example might be:
36
+ `["org.iso.18013.5.1.mDL": ["org.iso.18013.5.1": ["hair_colour": true, "given_name_national_character": true, "family_name_national_character": true, "given_name": true]]]`
37
+ */
38
+ typealias AcceptedFieldsDict = [String: [String: [String: Bool]]]
39
+
40
+ /**
41
+ 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
+
44
+ - Parameters:
45
+ - certificates: Array of base64 representing DER encoded X.509 certificate which are used to authenticate the verifier app
46
+ - resolve: The promise to be resolved
47
+ - reject: The promise to be rejected
48
+ */
49
+ @objc(start:withResolver:withRejecter:)
50
+ func start(
51
+ certificates: Array<Any>,
52
+ _ resolve: @escaping RCTPromiseResolveBlock,
53
+ reject: @escaping RCTPromiseRejectBlock
54
+ ){
55
+ do {
56
+ let certsData = parseCertificates(certificates)
57
+ try Proximity.shared.start(certsData.isEmpty ? nil : certsData)
58
+ resolve(true)
59
+ } catch let error {
60
+ ME.startError.reject(reject: reject, ("error", error.localizedDescription))
61
+ }
62
+ }
63
+
64
+ /**
65
+ 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
+ - Parameters:
68
+ - certificates:Array of base64 strings representing DER encoded X.509 certificate
69
+
70
+ - Returns: An array of Data containing DER ecnoded X.509 certificates.
71
+ */
72
+ private func parseCertificates(_ certificates: [Any]) -> [Data] {
73
+ return certificates.compactMap { item in
74
+ guard let certString = item as? String,
75
+ let data = Data(base64Encoded: certString) else {
76
+ return nil
77
+ }
78
+ return data
79
+ }
80
+ }
81
+
82
+ /**
83
+ Creates a QR code to be scanned in order to initialize the presentation.
84
+ Resolves with the QR code strings.
85
+
86
+ - Parameters:
87
+ - resolve: The promise to be resolved
88
+ - reject: The promise to be rejected
89
+ */
90
+ @objc(getQrCodeString:withRejecter:)
91
+ func getQrCodeString(
92
+ _ resolve: @escaping RCTPromiseResolveBlock,
93
+ reject: @escaping RCTPromiseRejectBlock
94
+ ) {
95
+ do{
96
+ let qrCodeString = try Proximity.shared.getQrCode()
97
+ resolve(qrCodeString)
98
+ } catch let error {
99
+ ME.qrCodeError.reject(reject: reject, ("error", error.localizedDescription))
100
+ }
101
+ }
102
+
103
+ /**
104
+ Parses an array of documents from the React Native bridge which doesn't have any typing to an array of ``IOWalletProximity.ProximityDocument``.
105
+ 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.
106
+ The result can be fed to ``IOWalletProximity.generateResponse``.
107
+
108
+ - Parameters:
109
+ - documents: An array containing documents. Each document is defined as a map containing:
110
+ - issuerSignedContent which is a base64 or base64url encoded string representing the credential;
111
+ - alias which is the alias of the key used to sign the credential;
112
+ - docType which is the document type.
113
+
114
+ - Throws: `ModuleException.invalidDocRequested.error()` and `ModuleException.invalidDocRequested.error()` iif an error occurs while parsing the document
115
+
116
+ - Returns: An array of `ProximityDocument` containg the documents to be presented
117
+ */
118
+ private func parseDocuments(documents: [Any]) throws -> [ProximityDocument] {
119
+ return try documents.compactMap{ (element) -> ProximityDocument in
120
+ guard let dict : [String: Any] = element as? [String: Any] else { throw ModuleException.unableToDecode.error() }
121
+ guard
122
+ let issuerSignedContent = dict["issuerSignedContent"] as? String,
123
+ let alias = dict["alias"] as? String,
124
+ let docType = dict["docType"] as? String,
125
+ let decodedIssuerSignedContent = try? Base64Utils.decodeBase64OrBase64URL(base: issuerSignedContent)
126
+ else { throw ModuleException.invalidDocRequested.error() }
127
+
128
+ guard let document = ProximityDocument(
129
+ docType: docType,
130
+ issuerSigned: [UInt8](decodedIssuerSignedContent),
131
+ deviceKeyTag: alias
132
+ ) else {
133
+ throw ModuleException.invalidDocRequested.error()
134
+ }
135
+ return document
136
+ }
137
+ }
138
+
139
+ /**
140
+ Parses a dictionary of accepted fields for the presentation from the React Native bridge which doesn't have any typing to a ``AcceptedFieldsDict`` dictionary.
141
+ 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.
142
+ The result can be fed to ``IOWalletProximity.generateResponse``.
143
+
144
+ - Parameters:
145
+ - 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]]]
146
+
147
+ - Throws: `NSError` if a value doesn't has the ``AcceptedFieldsDict`` shape or the result dictionary is empty
148
+
149
+ - Returns: An ``AcceptedFieldsDict`` containg the accepted fields to be presented
150
+
151
+ */
152
+ private func parseAcceptedFields(acceptedFields: [AnyHashable: Any]) throws -> AcceptedFieldsDict {
153
+ var result: AcceptedFieldsDict = [:]
154
+
155
+ for (key, value) in acceptedFields {
156
+ guard let keyString = key as? String else {
157
+ throw NSError(domain: "ParseAcceptedFields", code: -1, userInfo: [NSLocalizedDescriptionKey: "Key must be a String."])
158
+ }
159
+ guard let valueDict = value as? [String: [String: Bool]] else {
160
+ throw NSError(domain: "ParseAcceptedFields", code: -1, userInfo: [NSLocalizedDescriptionKey: "Value must be of type [String: [String: Bool]]."])
161
+ }
162
+ result[keyString] = valueDict
163
+ }
164
+
165
+ if(result.isEmpty){
166
+ throw NSError(domain: "ParseAcceptedFields", code: -1, userInfo: [NSLocalizedDescriptionKey: "Error parsing documents"])
167
+ }
168
+
169
+ return result
170
+ }
171
+
172
+ /**
173
+ Generates a response containing the documents and the fields which the user decided to present.
174
+ It parses the untyped ``documents`` and ``acceptedFields`` parameters and feeds them to the ``IOWalletProximity.generateDeviceResponse`` function.
175
+ It resolves the promise with the response as a base64 encoded string.
176
+ It rejects the promise if an error occurs during the parameters parsing or while generating the device response.
177
+
178
+ - Parameters:
179
+ - documents: An array containing documents. Each document is defined as a map containing:
180
+ - issuerSignedContent which is a base64 or base64url encoded string representing the credential;
181
+ - alias which is the alias of the key used to sign the credential;
182
+ - docType which is the document type.
183
+ - issuerSignedContent which is a base64 or base64url encoded string representing the credential;
184
+ - alias which is the alias of the key used to sign the credential;
185
+ - docType which is the document type.
186
+ - 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:
187
+
188
+
189
+ {
190
+ "org.iso.18013.5.1.mDL": {
191
+ "org.iso.18013.5.1": {
192
+ "hair_colour": true,
193
+ "given_name_national_character": true,
194
+ "family_name_national_character": true,
195
+ "given_name": true,
196
+ }
197
+ }
198
+ }
199
+
200
+ - resolve: The promise to be resolved
201
+ - reject: The promise to be rejected
202
+
203
+ */
204
+ @objc(generateResponse:withAcceptedFields:withResolver:withRejecter:)
205
+ func generateResponse(
206
+ documents: Array<Any>,
207
+ acceptedFields: [AnyHashable: Any],
208
+ _ resolve: @escaping RCTPromiseResolveBlock,
209
+ reject: @escaping RCTPromiseRejectBlock
210
+ ){
211
+ do {
212
+ let parsedDocuments = try parseDocuments(documents: documents)
213
+ let items = try parseAcceptedFields(acceptedFields: acceptedFields)
214
+ let deviceResponse = try Proximity.shared.generateDeviceResponse(items: items, documents: parsedDocuments, sessionTranscript: nil)
215
+ let strDeviceResponse = Data(deviceResponse).base64EncodedString()
216
+ resolve(strDeviceResponse)
217
+ }catch{
218
+ ME.generateDeviceResponseError.reject(reject: reject, ("error", error.localizedDescription))
219
+ }
220
+ }
221
+
222
+ /**
223
+ Sends a response containing the documents and the fields which the user decided to present generated by ``generateResponse``.
224
+ It resolves to true after sending the response, otherwise it rejects if an error occurs while decoding the response.
225
+ Currently there's not evidence of the verifier app responding to this request, thus we don't handle the response.
226
+
227
+ - Parameters:
228
+ - response: A base64 encoded string containing the response generated by ``generateResponse``
229
+ - resolve: The promise to be resolved
230
+ - reject: The promise to be rejected
231
+ */
232
+ @objc(sendResponse:withResolver:withRejecter:)
233
+ func sendResponse(
234
+ response: String,
235
+ _ resolve: @escaping RCTPromiseResolveBlock,
236
+ reject: @escaping RCTPromiseRejectBlock
237
+ ){
238
+ do{
239
+ if let responseData = Data(base64Encoded: response) {
240
+ let decodedResponse = [UInt8](responseData)
241
+ try Proximity.shared.dataPresentation(decodedResponse)
242
+ resolve(true)
243
+ }
244
+ }catch let error {
245
+ ME.sendResponseError.reject(reject: reject, ("error", error.localizedDescription))
246
+ }
247
+ }
248
+
249
+ /**
250
+ Sends an error response during the presentation according to the SessionData status codes defined in table 20 of the ISO18013-5 standard.
251
+ - Parameters:
252
+ - status: The status error to be sent is an integer of type ``SessionDataStatus``:
253
+ ```
254
+ 10 -> Error: session encryption
255
+ 11 -> Error: CBOR decoding
256
+ 20 -> Session termination
257
+ ```
258
+ - resolve: The promise to be resolved
259
+ - reject: The promise to be rejected
260
+ */
261
+ @objc(sendErrorResponse:withResolver:withRejecter:)
262
+ func sendErrorResponse(status: UInt64, _ resolve: @escaping RCTPromiseResolveBlock,
263
+ reject: @escaping RCTPromiseRejectBlock){
264
+ do{
265
+ if let statusEnum = SessionDataStatus(rawValue: status) {
266
+ try Proximity.shared.errorPresentation(statusEnum)
267
+ } else {
268
+ ME.sendResponseError.reject(reject: reject, ("error", "Invalid status code"))
269
+ }
270
+ resolve(true)
271
+ }catch let error{
272
+ ME.sendResponseError.reject(reject: reject, ("error", error.localizedDescription))
273
+ }
274
+ }
275
+
276
+
277
+ /**
278
+ Closes the bluetooth connection and clears any resource.
279
+ It resolves to true after closing the connection.
280
+
281
+ - Parameters:
282
+ - resolve: The promise to be resolved
283
+ - reject: The promise to be rejected
284
+
285
+ */
286
+ @objc(close:withRejecter:)
287
+ func close(
288
+ _ resolve: @escaping RCTPromiseResolveBlock,
289
+ reject: @escaping RCTPromiseRejectBlock
290
+ ) {
291
+ Proximity.shared.stop()
292
+ resolve(true)
293
+ }
294
+
295
+ /**
296
+ Converts a device requested from the `onDocumentRequestReceived` callback into a serializable JSON.
297
+
298
+ - Parameters:
299
+ - 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ß
300
+
301
+ - Returns: A JSON string representing the device request or nil if an error occurs
302
+ */
303
+ private func deviceRequestToJson(request: [(docType: String, nameSpaces: [String: [String: Bool]], isAuthenticated: Bool)]?) -> String? {
304
+ var jsonRequest : [String: AnyHashable] = [:]
305
+ request?.forEach({
306
+ item in
307
+ var subReq: [String: AnyHashable] = [:]
308
+ item.nameSpaces.keys.forEach({
309
+ nameSpace in
310
+ subReq[nameSpace] = item.nameSpaces[nameSpace]
311
+ })
312
+
313
+ subReq["isAuthenticated"] = item.isAuthenticated
314
+
315
+ jsonRequest[item.docType] = subReq
316
+ })
317
+
318
+ let json: [String: AnyHashable] = [
319
+ "request": jsonRequest
320
+ ]
321
+
322
+ if let jsonData = try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted),
323
+ let jsonString = String(data: jsonData, encoding: .utf8) {
324
+ return jsonString
325
+ } else {
326
+ return nil
327
+ }
328
+ }
329
+
330
+ /**
331
+ Sets the proximity handler along with the possible dispatched events and their callbacks.
332
+ The events are then sent to React Native via `RCTEventEmitter`.
333
+ */
334
+ private func setupProximityHandler() {
335
+ Proximity.shared.proximityHandler = { [weak self] event in
336
+ guard let self = self else { return }
337
+ var eventName: String
338
+ var eventBody: [String: Any] = [:]
339
+
340
+ switch event {
341
+ case .onDeviceConnecting:
342
+ eventName = "onDeviceConnecting"
343
+ case .onDeviceConnected:
344
+ eventName = "onDeviceConnected"
345
+ case .onDocumentRequestReceived(let request):
346
+ eventName = "onDocumentRequestReceived"
347
+ if let request = request {
348
+ /**
349
+ The outermost key represents the credential doctype, the inner key represents the namespace and the innermost key represents the requested fields with a boolean value. Example:
350
+ {
351
+ "org.iso.18013.5.1.mDL": {
352
+ "isAuthenticated": true,
353
+ "org.iso.18013.5.1": {
354
+ "hair_colour": true,
355
+ "given_name_national_character": true,
356
+ "family_name_national_character": true,
357
+ "given_name": true,
358
+ }
359
+ }
360
+ }
361
+ */
362
+ let jsonString = deviceRequestToJson(request: request)
363
+ // Here we either send the request or an empty string which signals that something went wrong.
364
+ eventBody = ["data": jsonString ?? ""]
365
+ }
366
+ case .onDeviceDisconnected:
367
+ eventName = "onDeviceDisconnected"
368
+ case .onError(let error):
369
+ eventName = "onError"
370
+ eventBody = ["error": error.localizedDescription]
371
+ default:
372
+ eventName = "unknown"
373
+ eventBody = ["error": "Received an unknown event"]
374
+ }
375
+
376
+ self.sendEvent(withName: eventName, body: eventBody)
377
+ }
378
+ }
379
+
380
+ @objc func generateOID4VPDeviceResponse(
381
+ _ clientId: String,
382
+ responseUri: String,
383
+ authorizationRequestNonce: String,
384
+ mdocGeneratedNonce: String,
385
+ documents: [Any],
386
+ fieldRequestedAndAccepted: String,
387
+ resolver resolve: RCTPromiseResolveBlock,
388
+ rejecter reject: RCTPromiseRejectBlock
389
+ ) {
390
+
391
+ do {
392
+ let sessionTranscript = Proximity.shared.generateOID4VPSessionTranscriptCBOR(
393
+ clientId: clientId,
394
+ responseUri: responseUri,
395
+ authorizationRequestNonce: authorizationRequestNonce,
396
+ mdocGeneratedNonce: mdocGeneratedNonce
397
+ )
398
+
399
+ let documentsAsProximityDocument = try parseDocuments(documents: documents)
400
+
401
+ let items = try JSONDecoder().decode([String : [String : [String : Bool]]].self, from: Data(fieldRequestedAndAccepted.utf8))
402
+
403
+ do {
404
+ let response = try Proximity.shared.generateDeviceResponse(items: items, documents: documentsAsProximityDocument, sessionTranscript: sessionTranscript)
405
+ resolve(Data(response).base64EncodedString())
406
+ } catch {
407
+ ME.unableToGenerateResponse.reject(reject: reject)
408
+ return
409
+ }
410
+ } catch let error as NSError where error.domain == ME.invalidDocRequested.rawValue {
411
+ ME.invalidDocRequested.reject(reject: reject)
412
+ } catch _ as DecodingError {
413
+ ME.invalidItems.reject(reject: reject)
414
+ } catch {
415
+ ME.unexpected.reject(reject: reject)
416
+ }
417
+ }
418
+
419
+ private class DocRequested {
420
+ var issuerSignedContent : [UInt8]
421
+ var alias : String
422
+ var docType : String
423
+
424
+ public init(issuerSignedContent: [UInt8], alias: String, docType: String) {
425
+ self.issuerSignedContent = issuerSignedContent
426
+ self.alias = alias
427
+ self.docType = docType
428
+ }
429
+ }
430
+
431
+ /**
432
+ Wrapper for rejecting with an error.
433
+ Add a new case in order to extend the possible errors.
434
+ */
435
+ private enum ModuleException: String, CaseIterable {
436
+ case startError = "START_ERROR"
437
+ case qrCodeError = "QR_CODE_ERROR"
438
+ case generateDeviceResponseError = "GENERATE_DEVICE_RESPONSE_ERROR"
439
+ case sendResponseError = "SEND_RESPONSE_ERROR"
440
+ case sendErrorResponse = "SEND_ERROR_RESPONSE_ERROR"
441
+ case invalidDocRequested = "DOC_REQUESTED_PARSING_ERROR"
442
+ case unableToGenerateResponse = "UNABLE_TO_GENERATE_RESPONSE_ERROR"
443
+ case invalidItems = "REQUESTED_ITEMS_PARSING_ERROR"
444
+ case unexpected = "UNEXPECTED_ERROR"
445
+ case unableToDecode = "UNABLE_TO_DECODE"
446
+
447
+ func error(
448
+ userInfo: [String : Any]? = nil
449
+ ) -> NSError {
450
+ switch self {
451
+ case .startError:
452
+ return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
453
+ case .qrCodeError:
454
+ return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
455
+ case .generateDeviceResponseError:
456
+ return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
457
+ case .sendResponseError:
458
+ return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
459
+ case .sendErrorResponse:
460
+ return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
461
+ case .invalidDocRequested:
462
+ return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
463
+ case .unableToGenerateResponse:
464
+ return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
465
+ case .invalidItems:
466
+ return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
467
+ case .unexpected:
468
+ return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
469
+ case .unableToDecode:
470
+ return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
471
+ }
472
+ }
473
+
474
+ /**
475
+ Rejects the provided promise with the appropriate error message and additional data.
476
+
477
+ - Parameters:
478
+ - reject: the promise to be rejected.
479
+ - moreUserInfo: additional key-value pairs of data to be passed along with the error.
480
+
481
+ */
482
+ func reject(
483
+ reject: RCTPromiseRejectBlock,
484
+ _ moreUserInfo: (String, Any)...
485
+ ) {
486
+ let userInfo = [String: Any](uniqueKeysWithValues: moreUserInfo)
487
+ let error = error(userInfo: userInfo)
488
+ reject("\(error.code)", error.domain, error)
489
+ }
490
+ }
491
+ }
@@ -0,0 +1,95 @@
1
+ ## CBOR
2
+
3
+ This module provides methods to decode CBOR data into readable objects.
4
+
5
+ ```typescript
6
+ import { CBOR } from '@pagopa/io-react-native-cbor';
7
+ ```
8
+
9
+ ### Methods
10
+
11
+ #### `decode`
12
+
13
+ This method allows to decode CBOR data into readable JSON objects.
14
+ Returns a `Promise` which resolves to a JSON object, or rejects with an instance of `CborFailure` in case of failure.
15
+
16
+ **Note**: this method does not decode nested CBOR objects and therefore complex objects needs additional manual decoding
17
+
18
+ ```typescript
19
+ try {
20
+ const decoded = await CBOR.decode('...');
21
+ } catch (e) {
22
+ const { message, userInfo } = e as CborFailure;
23
+ }
24
+ ```
25
+
26
+ #### `decodeDocuments`
27
+
28
+ This metod allows the decoding of CBOR data which contains MDOC objects.
29
+ Returns a promise wich resolves to a [Documents](#documents) object, or rejects with an instance of `CborFailure` in case of failure.
30
+
31
+ ```typescript
32
+ try {
33
+ const decoded = await CBOR.decodeDocuments('...');
34
+ } catch (e) {
35
+ const { message, userInfo } = e as CborFailure;
36
+ }
37
+ ```
38
+
39
+ ### Types
40
+
41
+ #### `Documents`
42
+
43
+ ```typescript
44
+ type Documents = {
45
+ status?: number;
46
+ version?: string;
47
+ documents?: Array<MDOC>;
48
+ };
49
+ ```
50
+
51
+ #### `MDOC`
52
+
53
+ ```typescript
54
+ type MDOC = {
55
+ docType?: DocumentType;
56
+ issuerSigned?: IssuerSigned;
57
+ };
58
+ ```
59
+
60
+ #### `IssuerSigned`
61
+
62
+ ```typescript
63
+ type IssuerSigned = {
64
+ nameSpaces?: Record<string, Array<DocumentValue>>;
65
+ issuerAuth?: string;
66
+ };
67
+ ```
68
+
69
+ #### `DocumentValue`
70
+
71
+ ```typescript
72
+ type DocumentValue = {
73
+ digestID?: number;
74
+ random?: string;
75
+ elementIdentifier?: string;
76
+ elementValue?: string;
77
+ };
78
+ ```
79
+
80
+ #### `DocumentType`
81
+
82
+ ```typescript
83
+ enum DocumentTypeEnum {
84
+ MDL = 'org.iso.18013.5.1.mDL',
85
+ EU_PID = 'eu.europa.ec.eudi.pid.1',
86
+ }
87
+ ```
88
+
89
+ ### Error Codes
90
+
91
+ | Type | Platform | Description |
92
+ | ----------------- | ----------- | --------------------------------------------- |
93
+ | INVALID_ENCODING | Android/iOS | Provided payload has incorrect encoding |
94
+ | UNABLE_TO_DECODE | Android/iOS | The data does not contain a valid CBOR object |
95
+ | UNKNOWN_EXCEPTION | Android/iOS | Unexpected failure |
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ import { IoReactNativeCbor } from "../index.js";
4
+ import { DocumentsFromString, IssuerSignedFromString } from "./schema.js";
5
+ import { coerceToJSON } from "./schema.utils.js";
6
+
7
+ /**
8
+ * Decode base64 encoded CBOR data to JSON object.
9
+ *
10
+ * If it is not possibile to decode the provided data, the promise will be rejected with
11
+ * an instance of {@link CborFailure}.
12
+ *
13
+ * **NOTE**: this method does not handle nested CBOR data, which will need additional
14
+ * parsing.
15
+ *
16
+ * @param data - The base64 or base64url encoded CBOR data
17
+ * @returns The decoded data as JSON object
18
+ */
19
+ export const decode = async data => {
20
+ const jsonString = await IoReactNativeCbor.decode(data);
21
+ return await coerceToJSON.parseAsync(jsonString);
22
+ };
23
+
24
+ /**
25
+ * Decode base64 or base64url encoded CBOR data to mDOC object
26
+ *
27
+ * If it is not possibile to decode the provided data, the promise will be rejected with
28
+ * an instance of {@link CborFailure}.
29
+ *
30
+ * @param data - The base64 encoded MDOC data
31
+ * @returns The decoded data as mDOC object
32
+ */
33
+ export const decodeDocuments = async data => {
34
+ const documentsString = await IoReactNativeCbor.decodeDocuments(data);
35
+ return await DocumentsFromString.parseAsync(documentsString);
36
+ };
37
+
38
+ /**
39
+ * Extract and decode the {@link IssuerSigned} with the {@link IssuerAuth} decoded from base64 encoded CBOR
40
+ *
41
+ * If it is not possibile to decode the provided data, the promise will be rejected with
42
+ * an instance of {@link CborFailure}.
43
+ *
44
+ * @param issuerSigned - The base64 or base64url encoded MDOC data
45
+ * @returns The decoded {@link IssuerSigned} contained in the mDOC object
46
+ */
47
+ export const decodeIssuerSigned = async issuerSigned => {
48
+ const decodedIssuerSignedString = await IoReactNativeCbor.decodeIssuerSigned(issuerSigned);
49
+ return await IssuerSignedFromString.parseAsync(decodedIssuerSignedString);
50
+ };
51
+ //# sourceMappingURL=decoder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["IoReactNativeCbor","DocumentsFromString","IssuerSignedFromString","coerceToJSON","decode","data","jsonString","parseAsync","decodeDocuments","documentsString","decodeIssuerSigned","issuerSigned","decodedIssuerSignedString"],"sourceRoot":"../../../../src","sources":["cbor/cbor/decoder.ts"],"mappings":";;AAAA,SAASA,iBAAiB,QAAQ,aAAI;AACtC,SAEEC,mBAAmB,EAEnBC,sBAAsB,QACjB,aAAU;AACjB,SAASC,YAAY,QAAQ,mBAAgB;;AAE7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,MAAM,GAAG,MAAOC,IAAY,IAAmB;EAC1D,MAAMC,UAAU,GAAG,MAAMN,iBAAiB,CAACI,MAAM,CAACC,IAAI,CAAC;EACvD,OAAO,MAAMF,YAAY,CAACI,UAAU,CAACD,UAAU,CAAC;AAClD,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAME,eAAe,GAAG,MAAOH,IAAY,IAAyB;EACzE,MAAMI,eAAe,GAAG,MAAMT,iBAAiB,CAACQ,eAAe,CAACH,IAAI,CAAC;EACrE,OAAO,MAAMJ,mBAAmB,CAACM,UAAU,CAACE,eAAe,CAAC;AAC9D,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,kBAAkB,GAAG,MAChCC,YAAoB,IACM;EAC1B,MAAMC,yBAAyB,GAC7B,MAAMZ,iBAAiB,CAACU,kBAAkB,CAACC,YAAY,CAAC;EAC1D,OAAO,MAAMT,sBAAsB,CAACK,UAAU,CAACK,yBAAyB,CAAC;AAC3E,CAAC","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=failure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../../../src","sources":["cbor/cbor/failure.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ export { decode, decodeDocuments, decodeIssuerSigned } from "./decoder.js";
4
+ export { DocumentValue, Documents, DocumentsFromString, IssuerSigned, MDOC } from "./schema.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["decode","decodeDocuments","decodeIssuerSigned","DocumentValue","Documents","DocumentsFromString","IssuerSigned","MDOC"],"sourceRoot":"../../../../src","sources":["cbor/cbor/index.ts"],"mappings":";;AAAA,SAASA,MAAM,EAAEC,eAAe,EAAEC,kBAAkB,QAAQ,cAAW;AACvE,SACEC,aAAa,EACbC,SAAS,EACTC,mBAAmB,EACnBC,YAAY,EACZC,IAAI,QACC,aAAU","ignoreList":[]}