@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,439 @@
1
+ package com.ioreactnativeiso18013
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.Promise
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
7
+ import com.facebook.react.bridge.ReactMethod
8
+ import com.facebook.react.bridge.ReadableArray
9
+ import com.facebook.react.bridge.ReadableMap
10
+ import com.facebook.react.bridge.ReadableType
11
+ import com.facebook.react.bridge.WritableMap
12
+ import com.facebook.react.bridge.WritableNativeMap
13
+ import com.ioreactnativeiso18013.IoReactNativeCborModule.Companion.ERROR_USER_INFO_KEY
14
+ import it.pagopa.io.wallet.proximity.OpenID4VP
15
+ import it.pagopa.io.wallet.proximity.bluetooth.BleRetrievalMethod
16
+ import it.pagopa.io.wallet.proximity.qr_code.QrEngagement
17
+ import it.pagopa.io.wallet.proximity.qr_code.QrEngagementListener
18
+ import it.pagopa.io.wallet.proximity.request.DocRequested
19
+ import it.pagopa.io.wallet.proximity.response.ResponseGenerator
20
+ import it.pagopa.io.wallet.proximity.session_data.SessionDataStatus
21
+ import it.pagopa.io.wallet.proximity.wrapper.DeviceRetrievalHelperWrapper
22
+
23
+ class IoReactNativeIso18013Module(reactContext: ReactApplicationContext) :
24
+ ReactContextBaseJavaModule(reactContext) {
25
+
26
+ override fun getName(): String {
27
+ return NAME
28
+ }
29
+
30
+ private var qrEngagement: QrEngagement? = null
31
+ private var deviceRetrievalHelper: DeviceRetrievalHelperWrapper? = null
32
+
33
+ /**
34
+ * Starts the proximity flow by allocating the necessary resources and initializing the Bluetooth stack.
35
+ * Resolves to true or rejects if an error occurs.
36
+ * @param peripheralMode - Whether the device is in peripheral mode. Defaults to true
37
+ * @param centralClientMode - Whether the device is in central client mode. Defaults to false
38
+ * @param clearBleCache - Whether the BLE cache should be cleared. Defaults to true
39
+ * @param certificates - Array of base64 representing DER encoded X.509 certificate which are used to authenticate the verifier app
40
+ * @param promise - The promise which will be resolved in case of success or rejected in case of failure.
41
+ */
42
+ @ReactMethod
43
+ fun start(
44
+ peripheralMode: Boolean,
45
+ centralClientMode: Boolean,
46
+ clearBleCache: Boolean,
47
+ certificates: ReadableArray,
48
+ promise: Promise
49
+ ) {
50
+ try {
51
+ val retrievalMethod = BleRetrievalMethod(
52
+ peripheralServerMode = peripheralMode,
53
+ centralClientMode = centralClientMode,
54
+ clearBleCache = clearBleCache
55
+ )
56
+
57
+ val certificatesList = parseCertificates(certificates)
58
+ qrEngagement = QrEngagement.build(reactApplicationContext, listOf(retrievalMethod)).apply {
59
+ if (certificatesList.isNotEmpty()) {
60
+ withReaderTrustStore(certificatesList)
61
+ }
62
+ }
63
+ qrEngagement?.configure()
64
+ setupProximityHandler()
65
+ promise.resolve(true)
66
+ } catch (e: Exception) {
67
+ ModuleException.START_ERROR.reject(
68
+ promise,
69
+ Pair(ERROR_KEY, getExceptionMessageOrEmpty(e))
70
+ )
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Utility function to parse an array coming from the React Native Bridge into an ArrayList
76
+ * of ByteArray representing DER encoded X.509 certificates.
77
+ * @param certificates - Array of base64 strings representing DER encoded X.509 certificate
78
+ * @returns An ArrayList of ByteArray representing DER encoded X.509 certificates.
79
+ * @throws ClassCastException if the element in the array is not a string
80
+ */
81
+
82
+ private fun parseCertificates(certificates: ReadableArray): ArrayList<ByteArray> {
83
+ return ArrayList(
84
+ (0 until certificates.size()).mapNotNull { i ->
85
+ certificates.getString(i)?.let { cert ->
86
+ Base64Utils.decodeBase64(cert)
87
+ }
88
+ }
89
+ )
90
+ }
91
+
92
+ /**
93
+ * Creates a QR code to be scanned in order to initialize the presentation.
94
+ * Resolves with the QR code strings.
95
+ * @param promise - The promise which will be resolved in case of success or rejected in case of failure.
96
+ */
97
+ @ReactMethod
98
+ fun getQrCodeString(promise: Promise) {
99
+ try {
100
+ qrEngagement?.let {
101
+ val qrCodeString = it.getQrCodeString()
102
+ promise.resolve(qrCodeString)
103
+ } ?: run {
104
+ ModuleException.QR_ENGAGEMENT_NOT_DEFINED_ERROR.reject(promise)
105
+ }
106
+ } catch (e: Exception) {
107
+ ModuleException.GET_QR_CODE_ERROR.reject(
108
+ promise,
109
+ Pair(ERROR_KEY, getExceptionMessageOrEmpty(e))
110
+ )
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Closes the bluetooth connection and clears any resource.
116
+ * It resolves to true after closing the connection.
117
+ * @param promise - The promise which will be resolved in case of success or rejected in case of failure.
118
+ */
119
+ @ReactMethod
120
+ fun close(promise: Promise) {
121
+ try {
122
+ qrEngagement?.close()
123
+ deviceRetrievalHelper?.disconnect()
124
+ promise.resolve(true)
125
+ } catch (e: Exception) {
126
+ ModuleException.CLOSE_ERROR.reject(
127
+ promise,
128
+ Pair(ERROR_KEY, getExceptionMessageOrEmpty(e))
129
+ )
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Sends an error response during the presentation according to the SessionData status codes defined in table 20 of the ISO18013-5 standard.
135
+ * @param code - The status error to be sent is a long type but the bridge only maps double values. It is converted to a long.
136
+ * The accepted values are defined in ``SessionDataStatus`` as follows:
137
+ * 10 -> Error: session encryption
138
+ * 11 -> Error: CBOR decoding
139
+ * 20 -> Session termination
140
+ * @param promise - The promise which will be resolved in case of success or rejected in case of failure.
141
+ */
142
+ @ReactMethod
143
+ fun sendErrorResponse(code: Double, promise: Promise) {
144
+ try {
145
+ qrEngagement?.let { it ->
146
+ val sessionDataStatus = SessionDataStatus.entries.find { it.value == code.toLong() }
147
+ if (sessionDataStatus != null) {
148
+ it.sendErrorResponse(sessionDataStatus)
149
+ promise.resolve(true)
150
+ } else {
151
+ ModuleException.SEND_ERROR_RESPONSE_ERROR.reject(
152
+ promise,
153
+ Pair(ERROR_KEY, "Invalid status code")
154
+ )
155
+ }
156
+ } ?: run {
157
+ ModuleException.QR_ENGAGEMENT_NOT_DEFINED_ERROR.reject(promise)
158
+ }
159
+ } catch (e: Exception) {
160
+ ModuleException.SEND_ERROR_RESPONSE_ERROR.reject(
161
+ promise,
162
+ Pair(ERROR_KEY, getExceptionMessageOrEmpty(e))
163
+ )
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Generates a response which can later be sent with {sendResponse} with the provided
169
+ * CBOR documents and the requested attributes.
170
+ * @param documents - A {ReadableArray} containing documents. Each document is defined as a map containing:
171
+ * - issuerSignedContent which is a base64 or base64url encoded string representing the credential;
172
+ * - alias which is the alias of the key used to sign the credential;
173
+ * - docType which is the document type.
174
+ * @param fieldRequestedAndAccepted - The string containing the requested attributes. This is based on the request
175
+ * provided by the {onDocumentRequestReceived} callback.
176
+ * @param promise - The promise which will be resolved in case of success or rejected in case of failure.
177
+ */
178
+ @ReactMethod
179
+ fun generateResponse(
180
+ documents: ReadableArray,
181
+ fieldRequestedAndAccepted: ReadableMap,
182
+ promise: Promise
183
+ ) {
184
+ try {
185
+ deviceRetrievalHelper?.let { devHelper ->
186
+ // Get the DocRequested list and if it's empty then reject the promise and return
187
+ val docRequestedList = parseDocRequested(documents)
188
+
189
+ val sessionTranscript = devHelper.sessionTranscript()
190
+ val responseGenerator = ResponseGenerator(sessionTranscript)
191
+ responseGenerator.createResponse(docRequestedList,
192
+ fieldRequestedAndAccepted.toString(),
193
+ object : ResponseGenerator.Response {
194
+ override fun onResponseGenerated(response: ByteArray) {
195
+ promise.resolve(Base64Utils.encodeBase64(response))
196
+ }
197
+
198
+ override fun onError(message: String) {
199
+ ModuleException.GENERATE_RESPONSE_ERROR.reject(
200
+ promise,
201
+ Pair(ERROR_KEY, message)
202
+ )
203
+ }
204
+ })
205
+ } ?: run {
206
+ ModuleException.DRH_NOT_DEFINED.reject(promise)
207
+ }
208
+ } catch (e: Exception) {
209
+ ModuleException.GENERATE_RESPONSE_ERROR.reject(
210
+ promise,
211
+ Pair(ERROR_KEY, getExceptionMessageOrEmpty(e))
212
+ )
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Sends a response containing the documents and the fields which the user decided to present generated by {generateResponse}.
218
+ * It resolves to true after sending the response, otherwise it rejects if an error occurs while decoding the response.
219
+ * Currently there's not evidence of the verifier app responding to this request, thus we don't handle the response.
220
+ * @param response - A base64 encoded string containing the response generated by {generateResponse}
221
+ * @param promise - The promise which will be resolved in case of success or rejected in case of failure.
222
+ */
223
+ @ReactMethod
224
+ fun sendResponse(response: String, promise: Promise) {
225
+ try {
226
+ qrEngagement?.let { qrEng ->
227
+ val responseBytes = Base64Utils.decodeBase64(response)
228
+ qrEng.sendResponse(responseBytes)
229
+ promise.resolve(true)
230
+ }
231
+ ?: run {
232
+ ModuleException.QR_ENGAGEMENT_NOT_DEFINED_ERROR.reject(promise)
233
+ }
234
+ } catch (e: Exception) {
235
+ ModuleException.SEND_RESPONSE_ERROR.reject(
236
+ promise,
237
+ Pair(ERROR_KEY, getExceptionMessageOrEmpty(e))
238
+ )
239
+ }
240
+ }
241
+
242
+ @ReactMethod
243
+ fun generateOID4VPDeviceResponse(
244
+ clientId: String, responseUri: String, authorizationRequestNonce: String,
245
+ mdocGeneratedNonce: String, documents: ReadableArray,
246
+ fieldRequestedAndAccepted: String, promise: Promise
247
+ ) {
248
+ val sessionTranscript = try {
249
+ OpenID4VP(
250
+ clientId,
251
+ responseUri,
252
+ authorizationRequestNonce,
253
+ mdocGeneratedNonce
254
+ ).createSessionTranscript()
255
+ } catch (e: Exception) {
256
+ ModuleException.UNABLE_TO_GENERATE_TRANSCRIPT.reject(
257
+ promise,
258
+ Pair(ERROR_USER_INFO_KEY, e.message.orEmpty())
259
+ )
260
+ return
261
+ }
262
+
263
+ val documentsParsed = try {
264
+ parseDocRequested(documents)
265
+ } catch (e: Exception) {
266
+ ModuleException.INVALID_DOC_REQUESTED.reject(
267
+ promise,
268
+ Pair(ERROR_USER_INFO_KEY, e.message.orEmpty())
269
+ )
270
+ return
271
+ }
272
+
273
+ try {
274
+ val responseGenerator = ResponseGenerator(sessionTranscript)
275
+ responseGenerator.createResponse(
276
+ documentsParsed,
277
+ fieldRequestedAndAccepted,
278
+ object : ResponseGenerator.Response {
279
+ override fun onResponseGenerated(response: ByteArray) {
280
+ promise.resolve(Base64Utils.encodeBase64(response))
281
+ }
282
+
283
+ override fun onError(message: String) {
284
+ ModuleException.UNABLE_TO_GENERATE_RESPONSE.reject(
285
+ promise,
286
+ Pair(ERROR_USER_INFO_KEY, message)
287
+ )
288
+ }
289
+ }
290
+ )
291
+ } catch (e: Exception) {
292
+ ModuleException.UNABLE_TO_GENERATE_RESPONSE.reject(
293
+ promise,
294
+ Pair(ERROR_USER_INFO_KEY, e.message.orEmpty())
295
+ )
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Sets the proximity handler along with the possible dispatched events and their callbacks.
301
+ * The events are then sent to React Native via `RCTEventEmitter`.
302
+ * onDeviceConnecting: Emitted when the device is connecting to the verifier app.
303
+ * onDeviceConnected: Emitted when the device is connected to the verifier app.
304
+ * onDocumentRequestReceived: Emitted when a document request is received from the verifier app. Carries a payload containing the request data.
305
+ * onDeviceDisconnected: Emitted when the device is disconnected from the verifier app.
306
+ * onError: Emitted when an error occurs. Carries a payload containing the error data.
307
+ */
308
+ private fun setupProximityHandler() {
309
+ qrEngagement?.withListener(object : QrEngagementListener {
310
+ /**
311
+ * This event currently doesn't get called due to an issue with the underlying native library.
312
+ */
313
+ override fun onDeviceConnecting() {
314
+ sendEvent("onDeviceConnecting", "")
315
+ }
316
+
317
+ override fun onDeviceConnected(deviceRetrievalHelper: DeviceRetrievalHelperWrapper) {
318
+ this@IoReactNativeIso18013Module.deviceRetrievalHelper = deviceRetrievalHelper
319
+ sendEvent("onDeviceConnected", "")
320
+ }
321
+
322
+ override fun onError(error: Throwable) {
323
+ val data: WritableMap = Arguments.createMap()
324
+ data.putString("error", error.message ?: "")
325
+ sendEvent("onError", data)
326
+ }
327
+
328
+ override fun onDocumentRequestReceived(request: String?, sessionsTranscript: ByteArray) {
329
+ val data: WritableMap = Arguments.createMap()
330
+ data.putString("data", request)
331
+ sendEvent("onDocumentRequestReceived", data)
332
+ }
333
+
334
+ override fun onDeviceDisconnected(transportSpecificTermination: Boolean) {
335
+ sendEvent("onDeviceDisconnected", transportSpecificTermination.toString())
336
+ }
337
+ })
338
+ }
339
+
340
+ /**
341
+ * Utility function which extracts the document shape we expect to receive from the bridge
342
+ * in the one expected by {DocRequested}.
343
+ * @param documents - A {ReadableArray} containing documents. Each document is defined as a map containing:
344
+ * - issuerSignedContent which is a base64 or base64url encoded string representing the credential;
345
+ * - alias which is the alias of the key used to sign the credential;
346
+ * - docType which is the document type.
347
+ * @returns An array containing a {DocRequested} object for each document in {documents}
348
+ * @throws IllegalArgumentException if the provided document doesn't adhere to the expected format
349
+ */
350
+ private fun parseDocRequested(documents: ReadableArray): Array<DocRequested> {
351
+ return (0 until documents.size()).map { i ->
352
+ val entry = documents.getMap(i)
353
+ ?: throw IllegalArgumentException("Entry in ReadableArray is null")
354
+
355
+ val alias = entry.getString("alias")
356
+ val issuerSignedContentStr = entry.getString("issuerSignedContent")
357
+ val docType = entry.getString("docType")
358
+
359
+ if (
360
+ alias == null || entry.getType("alias") != ReadableType.String ||
361
+ issuerSignedContentStr == null || entry.getType("issuerSignedContent") != ReadableType.String ||
362
+ docType == null || entry.getType("docType") != ReadableType.String
363
+ ) throw IllegalArgumentException("Unable to decode the provided documents")
364
+
365
+ val issuerSignedContent = Base64Utils.decodeBase64AndBase64Url(issuerSignedContentStr)
366
+
367
+ DocRequested(
368
+ issuerSignedContent,
369
+ alias,
370
+ docType
371
+ )
372
+ }.toTypedArray()
373
+ }
374
+
375
+ /**
376
+ * Wrapper function to send an event via `RCTEventEmitter`
377
+ * @param eventName - The event name
378
+ * @param data - The data attached to eventName
379
+ */
380
+ private fun sendEvent(eventName: String, data: Any?) {
381
+ reactApplicationContext.getJSModule(com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
382
+ .emit(eventName, data)
383
+ }
384
+
385
+ @ReactMethod
386
+ fun addListener(eventName: String?) {
387
+ /* Keep: Required for RN built in Event Emitter Calls.
388
+ This fixes the warning: `new NativeEventEmitter()` was called with a non-null argument without the required `removeListeners` method
389
+ */
390
+ }
391
+
392
+ @ReactMethod
393
+ fun removeListeners(count: Int?) {
394
+ /*Keep: Required for RN built in Event Emitter Calls.
395
+ This fixes the warning: `new NativeEventEmitter()` was called with a non-null argument without the required `removeListeners` method
396
+ */
397
+ }
398
+
399
+ private enum class ModuleException(
400
+ val ex: Exception
401
+ ) {
402
+ /** ISO18013-5 related errors **/
403
+ DRH_NOT_DEFINED(Exception("DRH_NOT_DEFINED")),
404
+ QR_ENGAGEMENT_NOT_DEFINED_ERROR(Exception("QR_ENGAGEMENT_NOT_DEFINED_ERROR")),
405
+ START_ERROR(Exception("START_ERROR")),
406
+ GET_QR_CODE_ERROR(Exception("GET_QR_CODE_ERROR")),
407
+ GENERATE_RESPONSE_ERROR(Exception("GENERATE_RESPONSE_ERROR")),
408
+ SEND_RESPONSE_ERROR(Exception("SEND_RESPONSE_ERROR")),
409
+ SEND_ERROR_RESPONSE_ERROR(Exception("SEND_ERROR_RESPONSE_ERROR")),
410
+ CLOSE_ERROR(Exception("CLOSE_ERROR")),
411
+
412
+ /** ISO18013-7 related errors **/
413
+ UNABLE_TO_GENERATE_RESPONSE(Exception("UNABLE_TO_GENERATE_RESPONSE")),
414
+ UNABLE_TO_GENERATE_TRANSCRIPT(Exception("UNABLE_TO_GENERATE_TRANSCRIPT")),
415
+ INVALID_DOC_REQUESTED(Exception("INVALID_DOC_REQUESTED")),
416
+ GENERATE_OID4VP_DEVICE_RESPONSE_FAILED(Exception("GENERATE_OID4VP_DEVICE_RESPONSE_FAILED"));
417
+
418
+ fun reject(
419
+ promise: Promise, vararg args: Pair<String, String>
420
+ ) {
421
+ exMap(*args).let {
422
+ promise.reject(it.first, ex.message, it.second)
423
+ }
424
+ }
425
+
426
+ private fun exMap(vararg args: Pair<String, String>): Pair<String, WritableMap> {
427
+ val writableMap = WritableNativeMap()
428
+ args.forEach { writableMap.putString(it.first, it.second) }
429
+ return Pair(this.ex.message ?: "UNKNOWN", writableMap)
430
+ }
431
+ }
432
+
433
+ private fun getExceptionMessageOrEmpty(e: Exception): String = e.message ?: ""
434
+
435
+ companion object {
436
+ const val NAME = "IoReactNativeIso18013"
437
+ const val ERROR_KEY = "error"
438
+ }
439
+ }
@@ -0,0 +1,17 @@
1
+ package com.ioreactnativeiso18013
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+
9
+ class IoReactNativeIso18013Package : ReactPackage {
10
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
+ return listOf(IoReactNativeCborModule(reactContext), IoReactNativeIso18013Module(reactContext))
12
+ }
13
+
14
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15
+ return emptyList()
16
+ }
17
+ }
@@ -0,0 +1,47 @@
1
+ import Foundation
2
+
3
+ /**
4
+ * This class contains a set of utilities for encoding and decoding base64 and base64url strings.
5
+ */
6
+ class Base64Utils {
7
+ enum Base64DecodingError: Error {
8
+ case invalidBase64(String)
9
+ }
10
+
11
+ /**
12
+ Parses a base64 or base64url `String` into `Data` buffer.
13
+
14
+ - Parameters:
15
+ - documents: An array of any elements. In order to be added to the result array each element must be a dictionary with `issuerSignedContent`, `alias` and `docType` as keys and strings as values.
16
+
17
+ - Throws: `Base64DecodingError.invalidBase64` if `base` is not a valid base64 or base64url
18
+
19
+ - Returns: A `Data` buffer representing `base`.
20
+ */
21
+ static func decodeBase64OrBase64URL(base: String) throws -> Data {
22
+ if let data = dataFromBase64Url(base64url: base) {
23
+ return data
24
+ } else if let data = Data(base64Encoded: base) {
25
+ return data
26
+ } else {
27
+ throw Base64DecodingError.invalidBase64("Input is not valid Base64 or Base64URL")
28
+ }
29
+ }
30
+
31
+ /**
32
+ Parses a base64url `String` into `Data` buffer by converting it to base64 before feeding it into `Data`.
33
+ - Parameters:
34
+ - base64url: the base64url encoded `String` to be converted
35
+
36
+ - Returns: A `Data` buffer representing `base64url`
37
+ */
38
+ static private func dataFromBase64Url(base64url: String) -> Data? {
39
+ var base64 = base64url
40
+ .replacingOccurrences(of: "-", with: "+")
41
+ .replacingOccurrences(of: "_", with: "/")
42
+ if base64.count % 4 != 0 {
43
+ base64.append(String(repeating: "=", count: 4 - base64.count % 4))
44
+ }
45
+ return Data(base64Encoded: base64)
46
+ }
47
+ }
@@ -0,0 +1,32 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(IoReactNativeCbor, NSObject)
4
+
5
+ RCT_EXTERN_METHOD(decode: (NSString)cbor
6
+ resolver: (RCTPromiseResolveBlock)resolve
7
+ rejecter: (RCTPromiseRejectBlock)reject)
8
+
9
+ RCT_EXTERN_METHOD(decodeDocuments: (NSString)mdoc
10
+ resolver: (RCTPromiseResolveBlock)resolve
11
+ rejecter: (RCTPromiseRejectBlock)reject)
12
+
13
+ RCT_EXTERN_METHOD(decodeIssuerSigned: (NSString)issuerSigned
14
+ resolver: (RCTPromiseResolveBlock)resolve
15
+ rejecter: (RCTPromiseRejectBlock)reject)
16
+
17
+ RCT_EXTERN_METHOD(sign: (NSString)payload
18
+ keyTag: (NSString*)keyTag
19
+ resolver: (RCTPromiseResolveBlock)resolve
20
+ rejecter: (RCTPromiseRejectBlock)reject)
21
+
22
+ RCT_EXTERN_METHOD(verify: (NSString)sign1Data
23
+ jwk: (NSDictionary*)jwk
24
+ resolver: (RCTPromiseResolveBlock)resolve
25
+ rejecter: (RCTPromiseRejectBlock)reject)
26
+
27
+ + (BOOL)requiresMainQueueSetup
28
+ {
29
+ return NO;
30
+ }
31
+
32
+ @end