@pagopa/io-react-native-iso18013 0.6.0 → 0.8.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/IoReactNativeIso18013.podspec +1 -1
- package/README.md +1 -1
- package/android/build.gradle +3 -2
- package/android/src/main/AndroidManifest.xml +17 -0
- package/android/src/main/AndroidManifestNew.xml +17 -0
- package/android/src/main/java/com/ioreactnativeiso18013/Enums.kt +42 -0
- package/android/src/main/java/com/ioreactnativeiso18013/IoNfcEngagementService.kt +17 -0
- package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeIso18013Module.kt +329 -137
- package/android/src/main/res/values/strings.xml +5 -0
- package/android/src/main/res/xml/nfc_engagement_apdu_service.xml +9 -0
- package/android/src/test/java/com/ioreactnativeiso18013/IoReactNativeIso18013Test.kt +11 -13
- package/ios/IoReactNativeIso18013.mm +8 -9
- package/ios/IoReactNativeIso18013.swift +264 -191
- package/lib/module/cbor/cbor/README.md +2 -1
- package/lib/module/cbor/cose/README.md +6 -4
- package/lib/module/iso18013/iso18013-5/README.md +161 -41
- package/lib/module/iso18013/iso18013-5/error.js +1 -3
- package/lib/module/iso18013/iso18013-5/error.js.map +1 -1
- package/lib/module/iso18013/iso18013-5/index.js +1 -1
- package/lib/module/iso18013/iso18013-5/index.js.map +1 -1
- package/lib/module/iso18013/iso18013-5/proximity.js +29 -19
- package/lib/module/iso18013/iso18013-5/proximity.js.map +1 -1
- package/lib/module/iso18013/iso18013-7/README.md +1 -1
- package/lib/typescript/src/iso18013/iso18013-5/error.d.ts +4 -4
- package/lib/typescript/src/iso18013/iso18013-5/error.d.ts.map +1 -1
- package/lib/typescript/src/iso18013/iso18013-5/index.d.ts +1 -1
- package/lib/typescript/src/iso18013/iso18013-5/index.d.ts.map +1 -1
- package/lib/typescript/src/iso18013/iso18013-5/proximity.d.ts +32 -13
- package/lib/typescript/src/iso18013/iso18013-5/proximity.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cbor/cbor/README.md +2 -1
- package/src/cbor/cose/README.md +6 -4
- package/src/iso18013/iso18013-5/README.md +161 -41
- package/src/iso18013/iso18013-5/error.ts +0 -2
- package/src/iso18013/iso18013-5/index.ts +3 -2
- package/src/iso18013/iso18013-5/proximity.ts +52 -23
- package/src/iso18013/iso18013-7/README.md +1 -1
|
@@ -3,20 +3,18 @@ import IOWalletProximity
|
|
|
3
3
|
import React
|
|
4
4
|
|
|
5
5
|
@objc(IoReactNativeIso18013)
|
|
6
|
-
class IoReactNativeIso18013: RCTEventEmitter {
|
|
6
|
+
class IoReactNativeIso18013: RCTEventEmitter, ISO18013Delegate {
|
|
7
7
|
|
|
8
8
|
@objc
|
|
9
9
|
override static func requiresMainQueueSetup() -> Bool {
|
|
10
10
|
return true
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
override init() {
|
|
14
|
-
super.init()
|
|
15
|
-
setupProximityHandler()
|
|
16
|
-
}
|
|
17
|
-
|
|
18
13
|
/**
|
|
19
14
|
Specifies supported events which will be emitted.
|
|
15
|
+
- `onQrCodeString`: Emitted when the QR Code payload is generated.
|
|
16
|
+
- `onNfcStarted`: Emitted when NFC starts successfully.
|
|
17
|
+
- `onNfcStopped`: Emitted when NFC stops successfully.
|
|
20
18
|
- `onDeviceConnecting`: Emitted when the device is connecting to the verifier app.
|
|
21
19
|
- `onDeviceConnected`: Emitted when the device is connected to the verifier app.
|
|
22
20
|
- `onDocumentRequestReceived`: Emitted when a document request is received from the verifier app. Carries a payload containing the request data.
|
|
@@ -24,7 +22,7 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
24
22
|
- `onError`: Emitted when an error occurs. Carries a payload containing the error data.
|
|
25
23
|
*/
|
|
26
24
|
override func supportedEvents() -> [String]! {
|
|
27
|
-
return ["onDeviceConnected", "onDeviceConnecting", "onDeviceDisconnected", "onDocumentRequestReceived", "onError", "unknown"]
|
|
25
|
+
return ["onQrCodeString", "onNfcStarted", "onNfcStopped", "onDeviceConnected", "onDeviceConnecting", "onDeviceDisconnected", "onDocumentRequestReceived", "onError", "unknown"]
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
/**
|
|
@@ -36,39 +34,146 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
36
34
|
typealias AcceptedFieldsDict = [String: [String: [String: Bool]]]
|
|
37
35
|
|
|
38
36
|
/**
|
|
39
|
-
|
|
37
|
+
ISO18013Delegate event handler
|
|
38
|
+
*/
|
|
39
|
+
func onEvent(event: ISO18013Event) {
|
|
40
|
+
var eventName: String
|
|
41
|
+
var eventBody: [String: Any] = [:]
|
|
42
|
+
|
|
43
|
+
switch(event) {
|
|
44
|
+
case .qrCode(let qrCode):
|
|
45
|
+
eventName = "onQrCodeString"
|
|
46
|
+
eventBody = ["data": qrCode]
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
case .bleConnecting:
|
|
50
|
+
eventName = "onDeviceConnecting"
|
|
51
|
+
break
|
|
52
|
+
|
|
53
|
+
case .bleConnected:
|
|
54
|
+
eventName = "onDeviceConnected"
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
case .nfcEngagementStarted:
|
|
58
|
+
eventName = "onDeviceConnecting"
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
case .nfcEngagementDone:
|
|
62
|
+
eventName = "onDeviceConnected"
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
case .dataTransferStarted(let args):
|
|
66
|
+
eventName = "onDocumentRequestReceived"
|
|
67
|
+
/**
|
|
68
|
+
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:
|
|
69
|
+
{
|
|
70
|
+
"org.iso.18013.5.1.mDL": {
|
|
71
|
+
"isAuthenticated": true,
|
|
72
|
+
"org.iso.18013.5.1": {
|
|
73
|
+
"hair_colour": true,
|
|
74
|
+
"given_name_national_character": true,
|
|
75
|
+
"family_name_national_character": true,
|
|
76
|
+
"given_name": true,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
*/
|
|
81
|
+
eventBody = [
|
|
82
|
+
// Here we either send the request or an empty string which signals that something went wrong.
|
|
83
|
+
"data": args.request.flatMap { deviceRequestToJson(request: $0) } ?? "",
|
|
84
|
+
"retrievalMethod": retrivalMethodToString(args.retrivalMethod)
|
|
85
|
+
]
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
case .dataTransferStopped:
|
|
89
|
+
eventName = "onDeviceDisconnected"
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
case .nfcStarted:
|
|
93
|
+
eventName = "onNfcStarted"
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
case .nfcStopped:
|
|
97
|
+
eventName = "onNfcStopped"
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
case .error(let error):
|
|
101
|
+
eventName = "onError"
|
|
102
|
+
eventBody = ["error": error.localizedDescription]
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
default:
|
|
106
|
+
eventName = "unknown"
|
|
107
|
+
eventBody = ["error": "Received an unknown event"]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
self.sendEvent(withName: eventName, body: eventBody)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
Starts proximity engagement with QR Code/NFC engagement and BLE/NFC retrieval.
|
|
40
115
|
Resolves to true or rejects with an error code defined in ``ModuleErrorCodes``.
|
|
41
116
|
- Parameters:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
117
|
+
- `certificates`: Two-dimensional array of base64 strings representing DER encoded X.509 certificate which are used to authenticate the verifier app
|
|
118
|
+
- `engagementModes`: Array of strings representing engamgnes modes (e.g., "QRCODE", "NFC")
|
|
119
|
+
- `retrievalMethods`: Array of strings representing data transfer modes (e.g., "BLE", "NFC")
|
|
120
|
+
- `resolve`: The promise to be resolved
|
|
121
|
+
- `reject`: The promise to be rejected
|
|
122
|
+
*/
|
|
123
|
+
@objc(startEngagement:withEngagementModes:withRetrievalMethods:withResolver:withRejecter:)
|
|
124
|
+
func startEngagement(
|
|
48
125
|
certificates: [Any],
|
|
126
|
+
engagementModes: [String],
|
|
127
|
+
retrievalMethods: [String],
|
|
49
128
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
50
129
|
reject: @escaping RCTPromiseRejectBlock
|
|
51
130
|
){
|
|
52
131
|
do {
|
|
53
132
|
let certsData = try parseCertificates(certificates)
|
|
54
|
-
try
|
|
133
|
+
let parsedEngagementModes = try parseEngagementMode(engagementModes)
|
|
134
|
+
let parsedRetrievalMethods = try parseDataTransferModes(retrievalMethods)
|
|
135
|
+
|
|
136
|
+
ISO18013.shared
|
|
137
|
+
.start(
|
|
138
|
+
certsData,
|
|
139
|
+
engagementModes: parsedEngagementModes,
|
|
140
|
+
retrivalMethods: parsedRetrievalMethods,
|
|
141
|
+
delegate: self,
|
|
142
|
+
isNfcLateEngagement: false
|
|
143
|
+
)
|
|
55
144
|
resolve(true)
|
|
56
145
|
} catch let proximityError as ProximityError {
|
|
57
|
-
reject(ModuleErrorCodes.
|
|
146
|
+
reject(ModuleErrorCodes.startError.rawValue, proximityError.description, proximityError)
|
|
58
147
|
} catch let parsingError as ParsingError{
|
|
59
|
-
reject(ModuleErrorCodes.
|
|
148
|
+
reject(ModuleErrorCodes.startError.rawValue, parsingError.description, parsingError)
|
|
60
149
|
} catch {
|
|
61
|
-
reject(ModuleErrorCodes.
|
|
150
|
+
reject(ModuleErrorCodes.startError.rawValue, error.localizedDescription, error)
|
|
62
151
|
}
|
|
63
152
|
}
|
|
64
153
|
|
|
154
|
+
/**
|
|
155
|
+
Closes connections, stops BLE, stops NFC and clears any resource.
|
|
156
|
+
Resolves to true after closing the connection or rejects with an error code defined in ``ModuleErrorCodes``.
|
|
157
|
+
- Parameters:
|
|
158
|
+
- resolve: The promise to be resolved.
|
|
159
|
+
- reject: The promise to be rejected.
|
|
160
|
+
*/
|
|
161
|
+
@objc(close:withRejecter:)
|
|
162
|
+
func close(
|
|
163
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
164
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
165
|
+
) {
|
|
166
|
+
ISO18013.shared.stop()
|
|
167
|
+
resolve(true)
|
|
168
|
+
}
|
|
169
|
+
|
|
65
170
|
/**
|
|
66
171
|
Utility function to parse an array coming from the React Native Bridge into an array of Data representing DER encoded X.509 certificates.
|
|
67
172
|
- Parameters:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
173
|
+
- certificates: Two-dimensional array of base64 strings representing DER encoded X.509 certificate
|
|
174
|
+
- Throws: `ParsingError` if the provided certificate is not a valid base64 encoded string.
|
|
175
|
+
- Returns: A two-dimensional array of Data containing DER encoded X.509 certificates.
|
|
176
|
+
*/
|
|
72
177
|
private func parseCertificates(_ certificates: [Any]) throws -> [[Data]] {
|
|
73
178
|
return try certificates.enumerated().compactMap { (chainIndex, item) in
|
|
74
179
|
guard let certStrings = item as? [String] else {
|
|
@@ -84,22 +189,42 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
84
189
|
}
|
|
85
190
|
|
|
86
191
|
/**
|
|
87
|
-
|
|
88
|
-
Resolves with a string containing the QR code or rejects with an error code defined in ``ModuleErrorCodes``.
|
|
192
|
+
Utility function to parse engagement mode strings into ISO18013EngagementMode enum values.
|
|
89
193
|
- Parameters:
|
|
90
|
-
|
|
91
|
-
|
|
194
|
+
- modes: Array of strings representing engamgnes modes (e.g., "QRCODE", "NFC")
|
|
195
|
+
- Throws: `ParsingError` if an invalid data transfer mode string is provided.
|
|
196
|
+
- Returns: An array of ISO18013EngagementMode values.
|
|
92
197
|
*/
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
198
|
+
private func parseEngagementMode(_ modes: [String]) throws -> [ISO18013EngagementMode] {
|
|
199
|
+
return try modes.map { modeString in
|
|
200
|
+
switch modeString.lowercased() {
|
|
201
|
+
case "qrcode":
|
|
202
|
+
return .qrCode
|
|
203
|
+
case "nfc":
|
|
204
|
+
return .nfc
|
|
205
|
+
default:
|
|
206
|
+
throw ParsingError.dataTransferModeNotValid("Invalid data engagement mode: '\(modeString)'. Expected 'qrcode' or 'nfc'.")
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
Utility function to parse data transfer mode strings into ISO18013DataTransferMode enum values.
|
|
213
|
+
- Parameters:
|
|
214
|
+
- modes: Array of strings representing data transfer modes (e.g., "BLE", "NFC")
|
|
215
|
+
- Throws: `ParsingError` if an invalid data transfer mode string is provided.
|
|
216
|
+
- Returns: An array of ISO18013DataTransferMode values.
|
|
217
|
+
*/
|
|
218
|
+
private func parseDataTransferModes(_ modes: [String]) throws -> [ISO18013DataTransferMode] {
|
|
219
|
+
return try modes.map { modeString in
|
|
220
|
+
switch modeString.lowercased() {
|
|
221
|
+
case "ble":
|
|
222
|
+
return .ble
|
|
223
|
+
case "nfc":
|
|
224
|
+
return .nfc
|
|
225
|
+
default:
|
|
226
|
+
throw ParsingError.dataTransferModeNotValid("Invalid data transfer mode: '\(modeString)'. Expected 'ble' or 'nfc'.")
|
|
227
|
+
}
|
|
103
228
|
}
|
|
104
229
|
}
|
|
105
230
|
|
|
@@ -108,15 +233,15 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
108
233
|
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.
|
|
109
234
|
The result can be fed to ``IOWalletProximity.generateResponse``.
|
|
110
235
|
- Parameters:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
236
|
+
- documents: An array containing documents. Each document is defined as a map containing:
|
|
237
|
+
- issuerSignedContent which is a base64 or base64url encoded string representing the credential;
|
|
238
|
+
- alias which is the alias of the key used to sign the credential;
|
|
239
|
+
- docType which is the document type.
|
|
115
240
|
- Throws: `ParsingError`:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
241
|
+
- If the provided documents array do not contain a dictionary;
|
|
242
|
+
- If the provided dictionary doesn't adhere to the structure we expect;
|
|
243
|
+
- If the issuerSignedContent is not a valid base64 or base64url encoded string;
|
|
244
|
+
- If the creation of a ProximityDdocument fails.
|
|
120
245
|
- Returns: An array of `ProximityDocument` containg the documents to be presented.
|
|
121
246
|
*/
|
|
122
247
|
private func parseDocuments(documents: [Any]) throws -> [ProximityDocument] {
|
|
@@ -146,7 +271,7 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
146
271
|
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.
|
|
147
272
|
The result can be fed to ``IOWalletProximity.generateResponse``.
|
|
148
273
|
- Parameters:
|
|
149
|
-
|
|
274
|
+
- 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
275
|
- Throws: `ParsingError` if a value doesn't has the ``AcceptedFieldsDict`` shape or the result dictionary is empty.
|
|
151
276
|
- Returns: An ``AcceptedFieldsDict`` containg the accepted fields to be presented.
|
|
152
277
|
*/
|
|
@@ -171,26 +296,26 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
171
296
|
It parses the untyped ``documents`` and ``acceptedFields`` parameters and feeds them to the ``IOWalletProximity.generateDeviceResponse`` function.
|
|
172
297
|
Resolves with a base64 encoded response or rejects with an error code defined in ``ModuleErrorCodes``.
|
|
173
298
|
- Parameters:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
299
|
+
- documents: An array containing documents. Each document is defined as a map containing:
|
|
300
|
+
- issuerSignedContent which is a base64 or base64url encoded string representing the credential;
|
|
301
|
+
- alias which is the alias of the key used to sign the credential;
|
|
302
|
+
- docType which is the document type.
|
|
303
|
+
- 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:
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
{
|
|
307
|
+
"org.iso.18013.5.1.mDL": {
|
|
308
|
+
"org.iso.18013.5.1": {
|
|
309
|
+
"hair_colour": true,
|
|
310
|
+
"given_name_national_character": true,
|
|
311
|
+
"family_name_national_character": true,
|
|
312
|
+
"given_name": true,
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
- resolve: The promise to be resolved.
|
|
318
|
+
- reject: The promise to be rejected.
|
|
194
319
|
*/
|
|
195
320
|
@objc(generateResponse:withAcceptedFields:withResolver:withRejecter:)
|
|
196
321
|
func generateResponse(
|
|
@@ -202,7 +327,7 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
202
327
|
do {
|
|
203
328
|
let parsedDocuments = try parseDocuments(documents: documents)
|
|
204
329
|
let items = try parseAcceptedFields(acceptedFields: acceptedFields)
|
|
205
|
-
let deviceResponse = try
|
|
330
|
+
let deviceResponse = try ISO18013.shared.generateDeviceResponse(items: items, documents: parsedDocuments, sessionTranscript: nil)
|
|
206
331
|
let strDeviceResponse = Data(deviceResponse).base64EncodedString()
|
|
207
332
|
resolve(strDeviceResponse)
|
|
208
333
|
}
|
|
@@ -221,9 +346,9 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
221
346
|
Currently there's not evidence of the verifier app responding to this request, thus we don't handle the response.
|
|
222
347
|
Resolves with a true boolean in case of success or rejects with an error code defined in ``ModuleErrorCodes``.
|
|
223
348
|
- Parameters:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
349
|
+
- response: A base64 encoded string containing the response generated by ``generateResponse``
|
|
350
|
+
- resolve: The promise to be resolved
|
|
351
|
+
- reject: The promise to be rejected
|
|
227
352
|
*/
|
|
228
353
|
@objc(sendResponse:withResolver:withRejecter:)
|
|
229
354
|
func sendResponse(
|
|
@@ -234,7 +359,7 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
234
359
|
do{
|
|
235
360
|
if let responseData = Data(base64Encoded: response) {
|
|
236
361
|
let decodedResponse = [UInt8](responseData)
|
|
237
|
-
try
|
|
362
|
+
try ISO18013.shared.dataPresentation(decodedResponse)
|
|
238
363
|
resolve(true)
|
|
239
364
|
}
|
|
240
365
|
}catch let error {
|
|
@@ -246,21 +371,21 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
246
371
|
Sends an error response during the presentation according to the SessionData status codes defined in table 20 of the ISO18013-5 standard.
|
|
247
372
|
Resolves to true or rejects with an error code defined in ``ModuleErrorCodes``.
|
|
248
373
|
- Parameters:
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
374
|
+
- code: The status error to be sent is an integer of type ``SessionDataStatus``:
|
|
375
|
+
```
|
|
376
|
+
10 -> Error: session encryption
|
|
377
|
+
11 -> Error: CBOR decoding
|
|
378
|
+
20 -> Session termination
|
|
379
|
+
```
|
|
380
|
+
- resolve: The promise to be resolved.
|
|
381
|
+
- reject: The promise to be rejected.
|
|
257
382
|
*/
|
|
258
383
|
@objc(sendErrorResponse:withResolver:withRejecter:)
|
|
259
384
|
func sendErrorResponse(code: UInt64, _ resolve: @escaping RCTPromiseResolveBlock,
|
|
260
|
-
|
|
385
|
+
reject: @escaping RCTPromiseRejectBlock){
|
|
261
386
|
do{
|
|
262
387
|
if let statusEnum = SessionDataStatus(rawValue: code) {
|
|
263
|
-
try
|
|
388
|
+
try ISO18013.shared.errorPresentation(statusEnum)
|
|
264
389
|
} else {
|
|
265
390
|
reject(ModuleErrorCodes.sendErrorResponseError.rawValue, "Invalid status code provided: \(code)", nil)
|
|
266
391
|
}
|
|
@@ -270,27 +395,10 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
270
395
|
}
|
|
271
396
|
}
|
|
272
397
|
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
Closes the bluetooth connection and clears any resource.
|
|
276
|
-
Resolves to true after closing the connection or rejects with an error code defined in ``ModuleErrorCodes``.
|
|
277
|
-
- Parameters:
|
|
278
|
-
- resolve: The promise to be resolved.
|
|
279
|
-
- reject: The promise to be rejected.
|
|
280
|
-
*/
|
|
281
|
-
@objc(close:withRejecter:)
|
|
282
|
-
func close(
|
|
283
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
284
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
285
|
-
) {
|
|
286
|
-
Proximity.shared.stop()
|
|
287
|
-
resolve(true)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
398
|
/**
|
|
291
399
|
Converts a device requested from the `onDocumentRequestReceived` callback into a serializable JSON.
|
|
292
400
|
- Parameters:
|
|
293
|
-
|
|
401
|
+
- 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
402
|
- Returns: A JSON string representing the device request or nil if an error occurs.
|
|
295
403
|
*/
|
|
296
404
|
private func deviceRequestToJson(request: [(docType: String, nameSpaces: [String: [String: Bool]], isAuthenticated: Bool)]?) -> String? {
|
|
@@ -320,86 +428,36 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
320
428
|
}
|
|
321
429
|
}
|
|
322
430
|
|
|
323
|
-
/**
|
|
324
|
-
Sets the proximity handler along with the possible dispatched events and their callbacks.
|
|
325
|
-
The events are then sent to React Native via `RCTEventEmitter`.
|
|
326
|
-
*/
|
|
327
|
-
private func setupProximityHandler() {
|
|
328
|
-
Proximity.shared.proximityHandler = { [weak self] event in
|
|
329
|
-
guard let self = self else { return }
|
|
330
|
-
var eventName: String
|
|
331
|
-
var eventBody: [String: Any] = [:]
|
|
332
|
-
|
|
333
|
-
switch event {
|
|
334
|
-
case .onDeviceConnecting:
|
|
335
|
-
eventName = "onDeviceConnecting"
|
|
336
|
-
case .onDeviceConnected:
|
|
337
|
-
eventName = "onDeviceConnected"
|
|
338
|
-
case .onDocumentRequestReceived(let request):
|
|
339
|
-
eventName = "onDocumentRequestReceived"
|
|
340
|
-
if let request = request {
|
|
341
|
-
/**
|
|
342
|
-
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:
|
|
343
|
-
{
|
|
344
|
-
"org.iso.18013.5.1.mDL": {
|
|
345
|
-
"isAuthenticated": true,
|
|
346
|
-
"org.iso.18013.5.1": {
|
|
347
|
-
"hair_colour": true,
|
|
348
|
-
"given_name_national_character": true,
|
|
349
|
-
"family_name_national_character": true,
|
|
350
|
-
"given_name": true,
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
*/
|
|
355
|
-
let jsonString = deviceRequestToJson(request: request)
|
|
356
|
-
// Here we either send the request or an empty string which signals that something went wrong.
|
|
357
|
-
eventBody = ["data": jsonString ?? ""]
|
|
358
|
-
}
|
|
359
|
-
case .onDeviceDisconnected:
|
|
360
|
-
eventName = "onDeviceDisconnected"
|
|
361
|
-
case .onError(let error):
|
|
362
|
-
eventName = "onError"
|
|
363
|
-
eventBody = ["error": error.localizedDescription]
|
|
364
|
-
default:
|
|
365
|
-
eventName = "unknown"
|
|
366
|
-
eventBody = ["error": "Received an unknown event"]
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
self.sendEvent(withName: eventName, body: eventBody)
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
431
|
/**
|
|
374
432
|
Generates a CBOR encoded device response for ISO 18013-7 mDL remote presentation using OID4VP.
|
|
375
433
|
Resolves with the base64 encoded device response or rejects with an error code defined in ``ModuleErrorCodes``.
|
|
376
434
|
- Parameters:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
435
|
+
- clientId: The client id extracted from OID4VP session.
|
|
436
|
+
- responseUri: The response URI extracted from OID4VP session.
|
|
437
|
+
- authorizationRequestNonce - The authorization request nonce extracted from OID4VP session.
|
|
438
|
+
- mdocGeneratedNonce - the mdoc generated nonce to be generated.
|
|
439
|
+
- documents: An array containing documents. Each document is defined as a map containing:
|
|
440
|
+
- issuerSignedContent which is a base64 or base64url encoded string representing the credential;
|
|
441
|
+
- alias which is the alias of the key used to sign the credential;
|
|
442
|
+
- docType which is the document type.
|
|
443
|
+
- 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:
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
{
|
|
447
|
+
"org.iso.18013.5.1.mDL": {
|
|
448
|
+
"org.iso.18013.5.1": {
|
|
449
|
+
"hair_colour": true,
|
|
450
|
+
"given_name_national_character": true,
|
|
451
|
+
"family_name_national_character": true,
|
|
452
|
+
"given_name": true,
|
|
453
|
+
},
|
|
454
|
+
{...}
|
|
455
|
+
},
|
|
456
|
+
{...}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
- resolve: The promise to be resolved.
|
|
460
|
+
- reject: The promise to be rejected.
|
|
403
461
|
*/
|
|
404
462
|
@objc(generateOID4VPDeviceResponse:withResponseUri:withAuthorizationRequestNonce:withMdocGeneratedNonce:withDocuments:withAcceptedFields:withResolver:withRejecter:)
|
|
405
463
|
func generateOID4VPDeviceResponse(
|
|
@@ -414,7 +472,7 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
414
472
|
) {
|
|
415
473
|
|
|
416
474
|
do {
|
|
417
|
-
let sessionTranscript =
|
|
475
|
+
let sessionTranscript = ISO18013.shared.generateOID4VPSessionTranscriptCBOR(
|
|
418
476
|
clientId: clientId,
|
|
419
477
|
responseUri: responseUri,
|
|
420
478
|
authorizationRequestNonce: authorizationRequestNonce,
|
|
@@ -423,7 +481,7 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
423
481
|
|
|
424
482
|
let documentsAsProximityDocument = try parseDocuments(documents: documents)
|
|
425
483
|
let items = try parseAcceptedFields(acceptedFields: acceptedFields)
|
|
426
|
-
let response = try
|
|
484
|
+
let response = try ISO18013.shared.generateDeviceResponse(items: items, documents: documentsAsProximityDocument, sessionTranscript: sessionTranscript)
|
|
427
485
|
resolve(Data(response).base64EncodedString())
|
|
428
486
|
} catch let parsingError as ParsingError{
|
|
429
487
|
reject(ModuleErrorCodes.generateOID4VPResponseError.rawValue, parsingError.description, parsingError)
|
|
@@ -434,34 +492,49 @@ class IoReactNativeIso18013: RCTEventEmitter {
|
|
|
434
492
|
reject(ModuleErrorCodes.generateOID4VPResponseError.rawValue, error.localizedDescription, error)
|
|
435
493
|
}
|
|
436
494
|
}
|
|
437
|
-
|
|
495
|
+
|
|
438
496
|
/**
|
|
439
497
|
Custom Error which is thrown when a parsing error occurs in our utility functions which converts data from the bridge to what
|
|
440
498
|
our underlying functions expect.
|
|
441
499
|
This is needed in order to provide a customized description which can include more debug information.
|
|
442
500
|
*/
|
|
443
501
|
enum ParsingError : Error, CustomStringConvertible {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
502
|
+
case documentsNotValid(String)
|
|
503
|
+
case certificatesNotValid(String)
|
|
504
|
+
case acceptedFieldsNotValid(String)
|
|
505
|
+
case dataTransferModeNotValid(String)
|
|
506
|
+
|
|
507
|
+
public var description: String {
|
|
508
|
+
switch(self) {
|
|
509
|
+
case .documentsNotValid(let message):
|
|
510
|
+
return message
|
|
511
|
+
case .certificatesNotValid(let message):
|
|
512
|
+
return message
|
|
513
|
+
case .acceptedFieldsNotValid(let message):
|
|
514
|
+
return message
|
|
515
|
+
case .dataTransferModeNotValid(let message):
|
|
516
|
+
return message
|
|
457
517
|
}
|
|
518
|
+
}
|
|
458
519
|
}
|
|
459
520
|
|
|
521
|
+
/**
|
|
522
|
+
Maps a retrival method enum value to its string representation.
|
|
523
|
+
*/
|
|
524
|
+
private func retrivalMethodToString(_ method: ISO18013DataTransferMode) -> String {
|
|
525
|
+
switch method {
|
|
526
|
+
case .ble:
|
|
527
|
+
return "ble"
|
|
528
|
+
case .nfc:
|
|
529
|
+
return "nfc"
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
460
533
|
// Errors which this module uses to reject a promise
|
|
461
534
|
private enum ModuleErrorCodes: String, CaseIterable {
|
|
462
535
|
// ISO18013-5 related errors
|
|
463
536
|
case startError = "START_ERROR"
|
|
464
|
-
case
|
|
537
|
+
case stopError = "STOP_ERROR"
|
|
465
538
|
case sendResponseError = "SEND_RESPONSE_ERROR"
|
|
466
539
|
case sendErrorResponseError = "SEND_ERROR_RESPONSE_ERROR"
|
|
467
540
|
case generateResponseError = "GENERATE_RESPONSE_ERROR"
|
|
@@ -42,8 +42,9 @@ This table contains the list of error codes that can be thrown by the `CBOR` mod
|
|
|
42
42
|
| DECODE_ERROR | Android/iOS | An error occurred while decoding the CBOR |
|
|
43
43
|
| DECODE_DOCUMENTS_ERROR | Android/iOS | An error occurred while decoding a CBOR mDOC |
|
|
44
44
|
| DECODE_ISSUER_SIGNED_ERROR | Android/iOS | An error occurred while decoding a CBOR Issuer Signed object |
|
|
45
|
+
| EUNSPECIFIED | Android | Default error when no other error is specified |
|
|
45
46
|
|
|
46
|
-
An error can be parsed using the `ModuleErrorSchema` with type `ModuleErrorCodes` exposed by the `
|
|
47
|
+
An error can be parsed using the `ModuleErrorSchema` with type `ModuleErrorCodes` exposed by the `CBOR` module. The error can be parsed as follows:
|
|
47
48
|
|
|
48
49
|
```typescript
|
|
49
50
|
import { CBOR } from '@pagopa/io-react-native-iso18013';
|
|
@@ -11,20 +11,21 @@ import { COSE } from '@pagopa/io-react-native-iso18013';
|
|
|
11
11
|
#### `sign`
|
|
12
12
|
|
|
13
13
|
Signs base64 encoded data using COSE (CBOR Object Signing and Encryption).
|
|
14
|
-
Returns a `Promise` which resolves to a `string` containing the COSE-Sign1 object in base64 encoding or rejects with
|
|
14
|
+
Returns a `Promise` which resolves to a `string` containing the COSE-Sign1 object in base64 encoding or rejects with a `ModuleError` in case of failures.
|
|
15
15
|
|
|
16
16
|
```typescript
|
|
17
17
|
try {
|
|
18
18
|
const coseSign1 = await COSE.sign('base64EncodedData', 'keyTag');
|
|
19
19
|
} catch (e) {
|
|
20
|
-
const
|
|
20
|
+
const parsedError = COSE.ModuleErrorSchema.parse(e);
|
|
21
|
+
console.log(parsedError.code, parsedError.message);
|
|
21
22
|
}
|
|
22
23
|
```
|
|
23
24
|
|
|
24
25
|
#### `verify`
|
|
25
26
|
|
|
26
27
|
Verifies a COSE-Sign1 object using the provided public key.
|
|
27
|
-
Returns a `Promise` which resolves to a `boolean` indicating if the signature is valid or rejects with
|
|
28
|
+
Returns a `Promise` which resolves to a `boolean` indicating if the signature is valid or rejects with a `ModuleError` in case of failures.
|
|
28
29
|
|
|
29
30
|
```typescript
|
|
30
31
|
// public key in JWK format
|
|
@@ -38,7 +39,8 @@ const publicKey = {
|
|
|
38
39
|
try {
|
|
39
40
|
const isValid = await COSE.verify('coseSign1Base64Data', publicKey);
|
|
40
41
|
} catch (e) {
|
|
41
|
-
const
|
|
42
|
+
const parsedError = COSE.ModuleErrorSchema.parse(e);
|
|
43
|
+
console.log(parsedError.code, parsedError.message);
|
|
42
44
|
}
|
|
43
45
|
```
|
|
44
46
|
|