@procivis/react-native-one-core 1.33398.0 → 1.41710.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 (155) hide show
  1. package/README.md +8 -26
  2. package/android/build.gradle +27 -3
  3. package/android/build.gradle_opensource +121 -0
  4. package/android/libs/core-reactnative-compatible-2025.0305.1.aar +0 -0
  5. package/android/src/main/AndroidManifest.xml +5 -2
  6. package/android/src/main/java/ch/procivis/one/core/Deserialize.kt +5 -5
  7. package/android/src/main/java/ch/procivis/one/core/DeserializeSpecific.kt +3 -2
  8. package/android/src/main/java/ch/procivis/one/core/ProcivisOneCoreModule.kt +99 -12
  9. package/android/src/main/java/ch/procivis/one/core/ProcivisOneCorePackage.kt +3 -4
  10. package/android/src/main/java/ch/procivis/one/core/SerializeSpecific.kt +6 -0
  11. package/android/src/main/java/ch/procivis/one/core/ubiqu/PinPadRunner.kt +123 -0
  12. package/android/src/main/java/ch/procivis/one/core/ubiqu/PinPadViewManager.kt +23 -0
  13. package/android/src/main/java/ch/procivis/one/core/ubiqu/PinPadViewManager.kt_opensource +20 -0
  14. package/android/src/main/java/ch/procivis/one/core/ubiqu/PinPadWrapper.kt +396 -0
  15. package/android/src/main/java/ch/procivis/one/core/ubiqu/RegistrationManager.kt +59 -0
  16. package/android/src/main/java/ch/procivis/one/core/ubiqu/TokenManager.kt +48 -0
  17. package/android/src/main/java/ch/procivis/one/core/ubiqu/UbiquKeyStorage.kt +256 -0
  18. package/android/src/main/java/ch/procivis/one/core/ubiqu/UbiquKeyStorage.kt_opensource +51 -0
  19. package/android/src/main/java/ch/procivis/one/core/ubiqu/Util.kt +74 -0
  20. package/android/src/main/java/ch/procivis/one/core/ubiqu/dataformats/CreateIdentityRequest.kt +9 -0
  21. package/android/src/main/java/ch/procivis/one/core/ubiqu/dataformats/CreateIdentityResponse.kt +9 -0
  22. package/android/src/main/java/ch/procivis/one/core/ubiqu/dataformats/RegisterRSEResponse.kt +10 -0
  23. package/core-sdk.json +6 -6
  24. package/dist/index.d.ts +7 -0
  25. package/dist/index.js +10 -4
  26. package/dist/src/cache.d.ts +0 -4
  27. package/dist/src/cache.js +0 -5
  28. package/dist/src/config.d.ts +11 -3
  29. package/dist/src/core.d.ts +8 -4
  30. package/dist/src/core.js +67 -0
  31. package/dist/src/credential.d.ts +1 -0
  32. package/dist/src/credentialSchema.d.ts +5 -2
  33. package/dist/src/credentialSchema.js +2 -0
  34. package/dist/src/history.d.ts +19 -4
  35. package/dist/src/history.js +10 -1
  36. package/dist/src/organisation.d.ts +8 -0
  37. package/dist/src/organisation.js +1 -0
  38. package/dist/src/proof.d.ts +19 -4
  39. package/dist/src/proof.js +13 -5
  40. package/dist/src/trust.d.ts +1 -1
  41. package/dist/src/ubiqu.d.ts +49 -0
  42. package/dist/src/ubiqu.js +69 -0
  43. package/ios/Deserialize.swift +57 -19
  44. package/ios/EventEmitter.swift +79 -0
  45. package/ios/ProcivisOneCoreModule.m +30 -4
  46. package/ios/ProcivisOneCoreModule.swift +141 -14
  47. package/ios/ProcivisRNOneCore.xcodeproj/project.pbxproj +28 -26
  48. package/ios/RSEKeyStorage.swift +587 -0
  49. package/ios/RSEKeyStorage.swift_opensource +49 -0
  50. package/ios/RSEPinPad.swift +74 -0
  51. package/ios/RSEPinPad.swift_opensource +21 -0
  52. package/ios/RSEPinPadManager.m +19 -0
  53. package/ios/Serialize.swift +40 -20
  54. package/ios/UBQKit.xcframework/Info.plist +44 -0
  55. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Assets.car +0 -0
  56. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/AFCompatibilityMacros.h +37 -0
  57. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/AFHTTPSessionManager.h +304 -0
  58. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/AFNetworkReachabilityManager.h +216 -0
  59. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/AFNetworking.h +41 -0
  60. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/AFPlainTextRequestSerializer.h +16 -0
  61. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/AFSecurityPolicy.h +156 -0
  62. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/AFURLRequestSerialization.h +479 -0
  63. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/AFURLResponseSerialization.h +310 -0
  64. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/AFURLSessionManager.h +501 -0
  65. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/CardRequestProtocol.h +72 -0
  66. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/Constants.h +222 -0
  67. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/DeviceBindProtocol.h +38 -0
  68. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/DomainSecurityModule.h +43 -0
  69. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/ErrorDomains.h +68 -0
  70. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/FreeAssetProtocol.h +40 -0
  71. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/GDataXMLNode.h +214 -0
  72. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/NSDictionary+Utilities.h +23 -0
  73. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/NSFileManager+Utilities.h +20 -0
  74. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/NSString+Utilities.h +23 -0
  75. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/NSURL+Utilities.h +12 -0
  76. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/ProtocolBase.h +38 -0
  77. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/ResetPinProtocol.h +45 -0
  78. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/Settings.h +120 -0
  79. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/UBQCardRequest.h +58 -0
  80. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/UBQCardRequestBaseProcessor.h +37 -0
  81. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/UBQCardRequestBindProcessor.h +34 -0
  82. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/UBQCardRequestNullProcessor.h +28 -0
  83. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/UBQCardRequestResetPinProcessor.h +35 -0
  84. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/UBQCardRequestUnlockProcessor.h +31 -0
  85. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/UBQCardRequestUseProcessor.h +34 -0
  86. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/UBQKit-Swift.h +761 -0
  87. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/UBQKit.h +56 -0
  88. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Headers/UBQPacket.h +38 -0
  89. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Info.plist +0 -0
  90. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Modules/UBQKit.swiftmodule/arm64-apple-ios.abi.json +89006 -0
  91. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Modules/UBQKit.swiftmodule/arm64-apple-ios.private.swiftinterface +1099 -0
  92. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Modules/UBQKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  93. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Modules/UBQKit.swiftmodule/arm64-apple-ios.swiftinterface +1099 -0
  94. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/Modules/module.modulemap +11 -0
  95. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/UBQKit +0 -0
  96. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/_CodeSignature/CodeResources +597 -0
  97. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/certs +0 -0
  98. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/checksums +0 -0
  99. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/pinning_pub +0 -0
  100. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/static +0 -0
  101. package/ios/UBQKit.xcframework/ios-arm64/UBQKit.framework/stream +0 -0
  102. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Assets.car +0 -0
  103. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/AFCompatibilityMacros.h +37 -0
  104. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/AFHTTPSessionManager.h +304 -0
  105. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/AFNetworkReachabilityManager.h +216 -0
  106. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/AFNetworking.h +41 -0
  107. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/AFPlainTextRequestSerializer.h +16 -0
  108. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/AFSecurityPolicy.h +156 -0
  109. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/AFURLRequestSerialization.h +479 -0
  110. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/AFURLResponseSerialization.h +310 -0
  111. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/AFURLSessionManager.h +501 -0
  112. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/CardRequestProtocol.h +72 -0
  113. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/Constants.h +222 -0
  114. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/DeviceBindProtocol.h +38 -0
  115. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/DomainSecurityModule.h +43 -0
  116. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/ErrorDomains.h +68 -0
  117. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/FreeAssetProtocol.h +40 -0
  118. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/GDataXMLNode.h +214 -0
  119. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/NSDictionary+Utilities.h +23 -0
  120. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/NSFileManager+Utilities.h +20 -0
  121. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/NSString+Utilities.h +23 -0
  122. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/NSURL+Utilities.h +12 -0
  123. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/ProtocolBase.h +38 -0
  124. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/ResetPinProtocol.h +45 -0
  125. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/Settings.h +120 -0
  126. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/UBQCardRequest.h +58 -0
  127. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/UBQCardRequestBaseProcessor.h +37 -0
  128. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/UBQCardRequestBindProcessor.h +34 -0
  129. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/UBQCardRequestNullProcessor.h +28 -0
  130. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/UBQCardRequestResetPinProcessor.h +35 -0
  131. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/UBQCardRequestUnlockProcessor.h +31 -0
  132. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/UBQCardRequestUseProcessor.h +34 -0
  133. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/UBQKit-Swift.h +1522 -0
  134. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/UBQKit.h +56 -0
  135. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Headers/UBQPacket.h +38 -0
  136. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Info.plist +0 -0
  137. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Modules/UBQKit.swiftmodule/arm64-apple-ios-simulator.abi.json +89006 -0
  138. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Modules/UBQKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +1099 -0
  139. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Modules/UBQKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  140. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Modules/UBQKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +1099 -0
  141. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Modules/UBQKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +89006 -0
  142. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Modules/UBQKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +1099 -0
  143. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Modules/UBQKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  144. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Modules/UBQKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +1099 -0
  145. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/Modules/module.modulemap +11 -0
  146. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/UBQKit +0 -0
  147. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/_CodeSignature/CodeResources +652 -0
  148. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/certs +0 -0
  149. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/checksums +0 -0
  150. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/pinning_pub +0 -0
  151. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/static +0 -0
  152. package/ios/UBQKit.xcframework/ios-arm64_x86_64-simulator/UBQKit.framework/stream +0 -0
  153. package/ios/Util.swift +1 -0
  154. package/package.json +5 -4
  155. package/react-native-one-core.podspec +4 -5
package/README.md CHANGED
@@ -63,27 +63,6 @@ allprojects {
63
63
  }
64
64
  ```
65
65
 
66
- Add following to your app `android/app/build.gradle` to remove unsupported architectures:
67
-
68
- ```
69
- ...
70
- android {
71
- defaultConfig {
72
- ...
73
-
74
- ndk {
75
- abiFilters "armeabi-v7a", "arm64-v8a", "x86"
76
- }
77
- packagingOptions {
78
- exclude 'lib/mips/**'
79
- exclude 'lib/mips64/**'
80
- exclude 'lib/armeabi/**'
81
- exclude 'lib/x86_64/**'
82
- }
83
- }
84
- }
85
- ```
86
-
87
66
  ## Usage
88
67
 
89
68
  ```javascript
@@ -156,7 +135,7 @@ Support for the recently published [FIPS-204][fips] is planned for the near futu
156
135
 
157
136
  #### ISO mdoc
158
137
 
159
- - [ISO/IEC 18013-5:2021][iso] standard for mdoc credentials.
138
+ - [ISO/IEC 18013-5:2021][iso5] standard for mdoc credentials.
160
139
  - [COSE][cose] proofs
161
140
  - ECDSA [ES256][es2]
162
141
  - EdDSA [Ed25519][ed255]
@@ -176,7 +155,7 @@ Support for the recently published [FIPS-204][fips] is planned for the near futu
176
155
 
177
156
  - OpenID for Verifiable Credentials
178
157
  - [OID4VCI][vci]; ID-1
179
- - [OID4VP][vp]; ID-2
158
+ - OID4VP; [ID-2][vp20] (Draft 20) & [Draft 25][vp25]
180
159
  - [OID4VP over BLE][ble]; optimized version of Draft 00
181
160
  - OID4VP over MQTT; proprietary adaptation of "OID4VP over BLE" via MQTT channel
182
161
  - ISO/IEC 18013
@@ -216,6 +195,8 @@ Version 2.0](./LICENSE).
216
195
  [bbs2023]: https://www.w3.org/TR/vc-di-bbs/#bbs-2023
217
196
  [ble]: https://openid.net/specs/openid-4-verifiable-presentations-over-ble-1_0.html
218
197
  [canivc]: https://canivc.com/implementations/procivis-one-core/
198
+ [comp]: https://github.com/procivis/one-react-native-components
199
+ [core]: https://github.com/procivis/one-core
219
200
  [cose]: https://www.rfc-editor.org/rfc/rfc9052
220
201
  [crydi3]: https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium-01
221
202
  [docs]: https://docs.procivis.ch/
@@ -241,11 +222,12 @@ Version 2.0](./LICENSE).
241
222
  [rncore]: https://github.com/procivis/react-native-one-core
242
223
  [sdjwt]: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-12.html
243
224
  [sdjwtvc]: https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-05.html
244
- [sdkref]: https://docs.procivis.ch/sdk/overview
225
+ [sdkref]: https://docs.procivis.ch/sdk
245
226
  [sl]: https://www.w3.org/TR/vc-bitstring-status-list/
246
- [trial]: https://docs.procivis.ch/trial/intro
227
+ [trial]: https://docs.procivis.ch/trial
247
228
  [tsl]: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-03
248
229
  [vcdi]: https://www.w3.org/TR/vc-data-integrity/
249
230
  [vcdm]: https://www.w3.org/TR/vc-data-model-2.0/
250
231
  [vci]: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-ID1.html
251
- [vp]: https://openid.net/specs/openid-4-verifiable-presentations-1_0-ID2.html
232
+ [vp20]: https://openid.net/specs/openid-4-verifiable-presentations-1_0-20.html
233
+ [vp25]: https://openid.net/specs/openid-4-verifiable-presentations-1_0-25.html
@@ -24,8 +24,18 @@ class Checksum {
24
24
  }
25
25
  }
26
26
 
27
+ buildscript {
28
+ repositories {
29
+ gradlePluginPortal()
30
+ }
31
+ dependencies {
32
+ classpath 'org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin:1.9.0'
33
+ }
34
+ }
35
+
27
36
  apply plugin: 'com.android.library'
28
37
  apply plugin: 'kotlin-android'
38
+ apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
29
39
 
30
40
  def safeExtGet(prop, fallback) {
31
41
  rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
@@ -36,7 +46,7 @@ android {
36
46
  buildToolsVersion = "30.0.2"
37
47
 
38
48
  defaultConfig {
39
- minSdkVersion safeExtGet('minSdkVersion', 21)
49
+ minSdkVersion safeExtGet('minSdkVersion', 26)
40
50
  targetSdkVersion safeExtGet('targetSdkVersion', 33)
41
51
  versionCode 1
42
52
  versionName "1.0.0"
@@ -46,6 +56,9 @@ android {
46
56
  lintOptions {
47
57
  abortOnError false
48
58
  }
59
+ buildFeatures {
60
+ dataBinding true
61
+ }
49
62
  }
50
63
 
51
64
  repositories {
@@ -53,14 +66,25 @@ repositories {
53
66
  }
54
67
 
55
68
  dependencies {
56
- implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:${safeExtGet('kotlinVersion', '1.8.0')}")
69
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:${safeExtGet('kotlinVersion', '1.9.0')}")
57
70
  implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
58
71
  implementation 'com.facebook.react:react-native:+'
59
72
 
60
73
  releaseImplementation(name: 'onecore-release', ext: 'aar')
61
74
  debugImplementation(name: 'onecore-debug', ext: 'aar')
62
75
  implementation 'net.java.dev.jna:jna:5.13.0@aar'
63
- implementation("org.jetbrains.kotlin:kotlin-reflect:${safeExtGet('kotlinVersion', '1.8.0')}")
76
+ implementation("org.jetbrains.kotlin:kotlin-reflect:${safeExtGet('kotlinVersion', '1.9.0')}")
77
+
78
+ implementation(name: 'core-reactnative-compatible-2025.0305.1', ext: 'aar')
79
+ implementation("com.google.android.material:material:1.9.0")
80
+ implementation("com.squareup.retrofit2:retrofit:2.11.0")
81
+ implementation("com.squareup.retrofit2:converter-scalars:2.11.0")
82
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
83
+ implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
84
+ implementation("com.jakewharton.timber:timber:5.0.1")
85
+ implementation("org.apache.commons:commons-math3:3.6.1")
86
+ implementation 'androidx.appcompat:appcompat:1.6.1'
87
+ implementation 'androidx.biometric:biometric:1.1.0'
64
88
  }
65
89
 
66
90
  def downloadFile = { url, file ->
@@ -0,0 +1,121 @@
1
+ import java.security.MessageDigest
2
+ class Checksum {
3
+ static getChecksum(file, type) {
4
+ def digest = MessageDigest.getInstance(type)
5
+ def inputStream = file.newInputStream()
6
+ def buffer = new byte[16384]
7
+ def len
8
+
9
+ while((len=inputStream.read(buffer)) > 0) {
10
+ digest.update(buffer, 0, len)
11
+ }
12
+
13
+ def result = ""
14
+ for(byte b : digest.digest()) {
15
+ result += toHex(b)
16
+ }
17
+ return result
18
+ }
19
+ private static hexChr(int b) {
20
+ return Integer.toHexString(b & 0xF)
21
+ }
22
+ private static toHex(int b) {
23
+ return hexChr((b & 0xF0) >> 4) + hexChr(b & 0x0F)
24
+ }
25
+ }
26
+
27
+ buildscript {
28
+ repositories {
29
+ gradlePluginPortal()
30
+ }
31
+ dependencies {
32
+ classpath 'org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin:1.9.0'
33
+ }
34
+ }
35
+
36
+ apply plugin: 'com.android.library'
37
+ apply plugin: 'kotlin-android'
38
+ apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
39
+
40
+ def safeExtGet(prop, fallback) {
41
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
42
+ }
43
+
44
+ android {
45
+ compileSdkVersion safeExtGet('compileSdkVersion', 33)
46
+ buildToolsVersion = "30.0.2"
47
+
48
+ defaultConfig {
49
+ minSdkVersion safeExtGet('minSdkVersion', 26)
50
+ targetSdkVersion safeExtGet('targetSdkVersion', 33)
51
+ versionCode 1
52
+ versionName "1.0.0"
53
+
54
+ consumerProguardFiles 'proguard-rules.pro'
55
+ }
56
+ lintOptions {
57
+ abortOnError false
58
+ }
59
+ buildFeatures {
60
+ dataBinding true
61
+ }
62
+ }
63
+
64
+ repositories {
65
+ mavenCentral()
66
+ }
67
+
68
+ dependencies {
69
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:${safeExtGet('kotlinVersion', '1.9.0')}")
70
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
71
+ implementation 'com.facebook.react:react-native:+'
72
+
73
+ releaseImplementation(name: 'onecore-release', ext: 'aar')
74
+ debugImplementation(name: 'onecore-debug', ext: 'aar')
75
+ implementation 'net.java.dev.jna:jna:5.13.0@aar'
76
+ implementation("org.jetbrains.kotlin:kotlin-reflect:${safeExtGet('kotlinVersion', '1.9.0')}")
77
+ }
78
+
79
+ def downloadFile = { url, file ->
80
+ new URL(url).withInputStream { downloadStream ->
81
+ file.withOutputStream { fileOut ->
82
+ fileOut << downloadStream
83
+ }
84
+ }
85
+ }
86
+
87
+ def coreUrlAAR = { buildType ->
88
+ def name = "onecore-${buildType}"
89
+ def coreSdkJsonFile = file("$projectDir/../core-sdk.json")
90
+ def coreSdkJson = new groovy.json.JsonSlurper().parseText(coreSdkJsonFile.text)
91
+ def sdk = coreSdkJson.android[buildType]
92
+
93
+ def url = sdk.url
94
+ File file = new File("$projectDir/libs/${name}.aar")
95
+ file.parentFile.mkdirs()
96
+
97
+ def sha1Expected = sdk.sha1
98
+ if (file.exists()) {
99
+ if (Checksum.getChecksum(file, "SHA1") == sha1Expected) {
100
+ // all good
101
+ return
102
+ }
103
+ // The file is most likely just stale -> delete and download again
104
+ file.delete()
105
+ }
106
+
107
+ downloadFile(url, file)
108
+ def sha1sum = Checksum.getChecksum(file, "SHA1")
109
+ if (sha1sum != sha1Expected) {
110
+ // if the file hash still does not match then something is off
111
+ println "URL($name): $url"
112
+ println "sha1 expected: $sha1Expected"
113
+ println "sha1 actual: $sha1sum"
114
+ throw new Error("AAR file SHA1 mismatch: " + name)
115
+ }
116
+ }
117
+
118
+ afterEvaluate {
119
+ coreUrlAAR('debug')
120
+ coreUrlAAR('release')
121
+ }
@@ -1,2 +1,5 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ch.procivis.one.core">
2
- </manifest>
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
+ package="ch.procivis.one.core">
4
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
5
+ </manifest>
@@ -11,11 +11,11 @@ object Deserialize {
11
11
  return constructObjectWithMembers(T::class, data) as T
12
12
  }
13
13
 
14
- private fun extractArrayItem(kType: KType, data: ReadableArray, index: Int): Any {
14
+ private fun extractArrayItem(kType: KType, data: ReadableArray, index: Int): Any? {
15
15
  val kClass = kType.classifier as KClass<*>
16
16
 
17
17
  if (kClass.java.isEnum) {
18
- return constructEnumValue(kClass, data.getString(index))
18
+ return constructEnumValue(kClass, data.getString(index)!!)
19
19
  }
20
20
 
21
21
  return when (kClass) {
@@ -26,15 +26,15 @@ object Deserialize {
26
26
  List::class -> {
27
27
  val itemType = kType.arguments.first().type
28
28
  val values = data.getArray(index)
29
- return extractArrayItems(itemType!!, values)
29
+ return extractArrayItems(itemType!!, values!!)
30
30
  }
31
31
 
32
32
  Map::class -> {
33
33
  val values = data.getMap(index)
34
- return extractMapItems(kType, values)
34
+ return extractMapItems(kType, values!!)
35
35
  }
36
36
 
37
- else -> constructObjectWithMembers(kClass, data.getMap(index))
37
+ else -> constructObjectWithMembers(kClass, data.getMap(index)!!)
38
38
  }
39
39
  }
40
40
 
@@ -38,6 +38,7 @@ object DeserializeSpecific {
38
38
  "PROCIVIS_ONE_SCHEMA2024" -> CredentialSchemaTypeBindingEnum.ProcivisOneSchema2024
39
39
  "FALLBACK_SCHEMA2024" -> CredentialSchemaTypeBindingEnum.FallbackSchema2024
40
40
  "MDOC" -> CredentialSchemaTypeBindingEnum.Mdoc
41
+ "SD_JWT_VC" -> CredentialSchemaTypeBindingEnum.SdJwtVc
41
42
  else -> CredentialSchemaTypeBindingEnum.Other(credentialSchemaType)
42
43
  }
43
44
  }
@@ -45,7 +46,7 @@ object DeserializeSpecific {
45
46
  fun ids(ids: ReadableArray): List<String> {
46
47
  val result = mutableListOf<String>()
47
48
  for (n in 0 until ids.size()) {
48
- result.add(ids.getString(n))
49
+ result.add(ids.getString(n)!!)
49
50
  }
50
51
  return result
51
52
  }
@@ -94,7 +95,7 @@ object DeserializeSpecific {
94
95
  fun <T> enumList(entries: ReadableArray, fn: (String) -> T): List<T> {
95
96
  val result = mutableListOf<T>()
96
97
  for (n in 0 until entries.size()) {
97
- result.add(fn(entries.getString(n)))
98
+ result.add(fn(entries.getString(n)!!))
98
99
  }
99
100
  return result
100
101
  }
@@ -5,6 +5,7 @@ import ch.procivis.one.core.DeserializeSpecific.enumList
5
5
  import ch.procivis.one.core.Serialize.convertToRN
6
6
  import ch.procivis.one.core.Util.asyncCall
7
7
  import ch.procivis.one.core.Util.syncCall
8
+ import ch.procivis.one.core.ubiqu.UbiquKeyStorage
8
9
  import com.facebook.react.bridge.Promise
9
10
  import com.facebook.react.bridge.ReactApplicationContext
10
11
  import com.facebook.react.bridge.ReactContextBaseJavaModule
@@ -31,17 +32,30 @@ class ProcivisOneCoreModule(reactContext: ReactApplicationContext) :
31
32
  )
32
33
  }
33
34
 
35
+ private var ubiqu: UbiquKeyStorage? = null
36
+ private fun getUbiqu(): UbiquKeyStorage {
37
+ return ubiqu ?: throw BindingException.ErrorResponse(
38
+ ErrorResponseBindingDto(
39
+ "BR_0184",
40
+ "ubiqu not initialized",
41
+ null
42
+ )
43
+ )
44
+ }
45
+
34
46
  @ReactMethod
35
47
  fun initialize(configJson: String, promise: Promise) {
36
48
  syncCall(promise) {
37
49
  val dataDirPath = this.reactApplicationContext.filesDir.absolutePath
50
+ ubiqu = ubiqu ?: UbiquKeyStorage(this.reactApplicationContext)
38
51
  oneCore =
39
52
  initializeCore(
40
- configJson,
41
- dataDirPath,
42
- AndroidKeyStoreKeyStorage(this.reactApplicationContext),
43
- AndroidBLECentral(this.reactApplicationContext),
44
- AndroidBLEPeripheral(this.reactApplicationContext)
53
+ configJson = configJson,
54
+ dataDirPath = dataDirPath,
55
+ nativeSecureElement = AndroidKeyStoreKeyStorage(this.reactApplicationContext),
56
+ remoteSecureElement = ubiqu,
57
+ bleCentral = AndroidBLECentral(this.reactApplicationContext),
58
+ blePeripheral = AndroidBLEPeripheral(this.reactApplicationContext)
45
59
  )
46
60
  return@syncCall null
47
61
  }
@@ -103,9 +117,17 @@ class ProcivisOneCoreModule(reactContext: ReactApplicationContext) :
103
117
  }
104
118
 
105
119
  @ReactMethod
106
- fun createOrganisation(uuid: String?, promise: Promise) {
120
+ fun createOrganisation(request: ReadableMap, promise: Promise) {
107
121
  asyncCall(promise, scope) {
108
- return@asyncCall getCore().createOrganisation(uuid)
122
+ return@asyncCall getCore().createOrganisation(construct(request))
123
+ }
124
+ }
125
+
126
+ @ReactMethod
127
+ fun upsertOrganisation(request: ReadableMap, promise: Promise) {
128
+ asyncCall(promise, scope) {
129
+ getCore().upsertOrganisation(construct(request))
130
+ return@asyncCall null
109
131
  }
110
132
  }
111
133
 
@@ -290,9 +312,10 @@ class ProcivisOneCoreModule(reactContext: ReactApplicationContext) :
290
312
  }
291
313
 
292
314
  @ReactMethod
293
- fun retractProof(proofId: String, promise: Promise) {
315
+ fun deleteProof(proofId: String, promise: Promise) {
294
316
  asyncCall(promise, scope) {
295
- return@asyncCall getCore().retractProof(proofId)
317
+ getCore().deleteProof(proofId)
318
+ return@asyncCall null
296
319
  }
297
320
  }
298
321
 
@@ -345,13 +368,12 @@ class ProcivisOneCoreModule(reactContext: ReactApplicationContext) :
345
368
  @ReactMethod
346
369
  fun checkRevocation(
347
370
  credentialIds: ReadableArray,
348
- bypassCache: ReadableArray?,
371
+ forceRefresh: Boolean,
349
372
  promise: Promise
350
373
  ) {
351
374
  asyncCall(promise, scope) {
352
375
  val ids = DeserializeSpecific.ids(credentialIds)
353
- val bypass = bypassCache?.let { enumList(it, BypassCacheBindingDto::valueOf) }
354
- val results = getCore().checkRevocation(ids, bypass)
376
+ val results = getCore().checkRevocation(ids, forceRefresh)
355
377
  return@asyncCall convertToRN(results)
356
378
  }
357
379
  }
@@ -523,7 +545,72 @@ class ProcivisOneCoreModule(reactContext: ReactApplicationContext) :
523
545
  asyncCall(promise, scope) {
524
546
  getCore().uninitialize(deleteData)
525
547
  oneCore = null
548
+ if (deleteData) {
549
+ ubiqu?.reset()
550
+ }
551
+ return@asyncCall null
552
+ }
553
+ }
554
+
555
+ // ===== Ubiqu specific methods
556
+ @ReactMethod
557
+ fun changeRSEPin(promise: Promise) {
558
+ asyncCall(promise, scope) {
559
+ getUbiqu().changePin()
560
+ return@asyncCall null
561
+ }
562
+ }
563
+
564
+ @ReactMethod
565
+ fun areRSEBiometricsSupported(promise: Promise) {
566
+ asyncCall(promise, scope) {
567
+ return@asyncCall getUbiqu().getBiometricsSupported()
568
+ }
569
+ }
570
+
571
+ @ReactMethod
572
+ fun areRSEBiometricsEnabled(promise: Promise) {
573
+ asyncCall(promise, scope) {
574
+ return@asyncCall getUbiqu().getBiometricsEnabled()
575
+ }
576
+ }
577
+
578
+ @ReactMethod
579
+ fun setRSEBiometrics(enabled: Boolean, promise: Promise) {
580
+ asyncCall(promise, scope) {
581
+ if (enabled) {
582
+ getUbiqu().enableBiometrics(true)
583
+ } else {
584
+ getUbiqu().disableBiometrics()
585
+ }
586
+ return@asyncCall null
587
+ }
588
+ }
589
+
590
+ @ReactMethod
591
+ fun resetRSE(promise: Promise) {
592
+ asyncCall(promise, scope) {
593
+ ubiqu?.reset()
594
+ return@asyncCall null
595
+ }
596
+ }
597
+
598
+ @ReactMethod
599
+ fun resetRSEPinFlow(promise: Promise) {
600
+ asyncCall(promise, scope) {
601
+ ubiqu?.resetPinFlow()
526
602
  return@asyncCall null
527
603
  }
528
604
  }
605
+
606
+ // handling RN event emitter
607
+ @ReactMethod
608
+ fun addListener(eventName: String) {
609
+ UbiquKeyStorage.listenerCount++
610
+ }
611
+
612
+ @ReactMethod
613
+ fun removeListeners(count: Int) {
614
+ UbiquKeyStorage.listenerCount -= count
615
+ }
529
616
  }
@@ -1,19 +1,18 @@
1
1
  package ch.procivis.one.core
2
2
 
3
- import android.view.View
3
+ import ch.procivis.one.core.ubiqu.PinPadViewManager
4
4
  import com.facebook.react.ReactPackage
5
5
  import com.facebook.react.bridge.NativeModule
6
6
  import com.facebook.react.bridge.ReactApplicationContext
7
- import com.facebook.react.uimanager.ReactShadowNode
8
7
  import com.facebook.react.uimanager.ViewManager
9
8
 
10
9
  class ProcivisOneCorePackage : ReactPackage {
11
10
 
12
11
  override fun createViewManagers(
13
12
  reactContext: ReactApplicationContext
14
- ): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
13
+ ): MutableList<ViewManager<*, *>> = mutableListOf(PinPadViewManager(reactContext))
15
14
 
16
15
  override fun createNativeModules(
17
16
  reactContext: ReactApplicationContext
18
- ): MutableList<NativeModule> = listOf(ProcivisOneCoreModule(reactContext)).toMutableList()
17
+ ): MutableList<NativeModule> = mutableListOf(ProcivisOneCoreModule(reactContext))
19
18
  }
@@ -40,6 +40,9 @@ object SerializeSpecific {
40
40
  when (metadata) {
41
41
  is HistoryMetadataBinding.UnexportableEntities ->
42
42
  return convertToRN(metadata.value) as ReadableMap
43
+
44
+ is HistoryMetadataBinding.ErrorMetadata ->
45
+ return convertToRN(metadata.value) as ReadableMap
43
46
  }
44
47
  }
45
48
 
@@ -119,6 +122,9 @@ object SerializeSpecific {
119
122
  is CredentialSchemaTypeBindingEnum.Mdoc ->
120
123
  "MDOC"
121
124
 
125
+ is CredentialSchemaTypeBindingEnum.SdJwtVc ->
126
+ "SD_JWT_VC"
127
+
122
128
  is CredentialSchemaTypeBindingEnum.Other ->
123
129
  type.value
124
130
  }
@@ -0,0 +1,123 @@
1
+ package ch.procivis.one.core.ubiqu
2
+
3
+ import android.content.Intent
4
+ import ch.procivis.one.core.NativeKeyStorageException
5
+ import com.facebook.react.bridge.Arguments
6
+ import com.facebook.react.bridge.ReactContext
7
+ import com.facebook.react.bridge.WritableMap
8
+ import com.facebook.react.modules.core.DeviceEventManagerModule
9
+ import com.ubiqu.eid.sdk.requests.UbqRequestFlow
10
+
11
+ object PinPadRunner {
12
+ enum class RequestType {
13
+ SUBSCRIBE,
14
+ TRANSACTION,
15
+ ADD_BIOMETRICS,
16
+ CHANGE_PIN
17
+ }
18
+
19
+ enum class RNEvent {
20
+ RSE_SHOW_PIN,
21
+ RSE_HIDE_PIN,
22
+ RSE_DIGITS_ENTERED,
23
+ RSE_PIN_STAGE,
24
+ RSE_INCORRECT_PIN
25
+ }
26
+
27
+ enum class RNEventParam {
28
+ flowType,
29
+ digitsEntered,
30
+ stage,
31
+ attemptsLeft,
32
+ }
33
+
34
+ enum class RNEventStage {
35
+ CHECK_CURRENT_PIN,
36
+ NEW_FIRST_PIN,
37
+ NEW_SECOND_PIN,
38
+ }
39
+
40
+ data class Flow(
41
+ val flow: UbqRequestFlow,
42
+ val promise: Util.Promise<String?>,
43
+ val type: RequestType
44
+ )
45
+
46
+ @Volatile
47
+ private var activeFlow: Flow? = null
48
+
49
+ @Synchronized
50
+ fun getActiveFlow(): Flow {
51
+ return activeFlow ?: throw NativeKeyStorageException.Unknown("No active flow")
52
+ }
53
+
54
+ private fun sendRNEvent(context: ReactContext, eventName: String, params: WritableMap?) {
55
+ context
56
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
57
+ .emit(eventName, params)
58
+ }
59
+
60
+ // ==== called from UbiquKeyStorage
61
+ @Synchronized
62
+ fun startPinPadFlow(
63
+ context: ReactContext,
64
+ flow: UbqRequestFlow,
65
+ requestType: RequestType,
66
+ promise: Util.Promise<String?>
67
+ ) {
68
+ if (activeFlow != null) {
69
+ throw NativeKeyStorageException.Unknown("Another PinPad flow in progress")
70
+ }
71
+
72
+ activeFlow = Flow(flow, promise, requestType)
73
+
74
+ val params = Arguments.createMap()
75
+ params.putString(RNEventParam.flowType.name, requestType.name)
76
+
77
+ val initialStage = when (requestType) {
78
+ RequestType.SUBSCRIBE -> RNEventStage.NEW_FIRST_PIN
79
+ else -> RNEventStage.CHECK_CURRENT_PIN
80
+ }
81
+ params.putString(RNEventParam.stage.name, initialStage.name)
82
+ sendRNEvent(context, RNEvent.RSE_SHOW_PIN.name, params)
83
+ }
84
+
85
+ @Synchronized
86
+ fun cancelPinPadFlow() {
87
+ activeFlow?.promise?.fail(NativeKeyStorageException.Unknown("Canceled by user"))
88
+ activeFlow = null
89
+ }
90
+
91
+ // ==== called from PinPadWrapper
92
+ @Synchronized
93
+ fun finishPinPadFlowSuccess(context: ReactContext, data: String?) {
94
+ activeFlow?.promise?.succeed(data)
95
+ activeFlow = null
96
+ sendRNEvent(context, RNEvent.RSE_HIDE_PIN.name, null)
97
+ }
98
+
99
+ @Synchronized
100
+ fun finishPinPadFlowFailure(context: ReactContext, error: Throwable) {
101
+ activeFlow?.promise?.fail(error)
102
+ activeFlow = null
103
+ sendRNEvent(context, RNEvent.RSE_HIDE_PIN.name, null)
104
+ }
105
+
106
+ fun digitsEntered(context: ReactContext, numPins: Int) {
107
+ val params = Arguments.createMap()
108
+ params.putInt(RNEventParam.digitsEntered.name, numPins)
109
+ sendRNEvent(context, RNEvent.RSE_DIGITS_ENTERED.name, params)
110
+ }
111
+
112
+ fun pinStage(context: ReactContext, stage: RNEventStage) {
113
+ val params = Arguments.createMap()
114
+ params.putString(RNEventParam.stage.name, stage.name)
115
+ sendRNEvent(context, RNEvent.RSE_PIN_STAGE.name, params)
116
+ }
117
+
118
+ fun incorrectPin(context: ReactContext, attemptsLeft: Int) {
119
+ val params = Arguments.createMap()
120
+ params.putInt(RNEventParam.attemptsLeft.name, attemptsLeft)
121
+ sendRNEvent(context, RNEvent.RSE_INCORRECT_PIN.name, params)
122
+ }
123
+ }
@@ -0,0 +1,23 @@
1
+ package ch.procivis.one.core.ubiqu
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.uimanager.SimpleViewManager
5
+ import com.facebook.react.uimanager.ThemedReactContext
6
+ import com.ubiqu.eid.sdk.components.UbqInternalPad
7
+ import com.ubiqu.eid.sdk.components.UbqPinPad
8
+
9
+ class PinPadViewManager(private val callerContext: ReactApplicationContext) :
10
+ SimpleViewManager<UbqPinPad>() {
11
+ companion object {
12
+ const val REACT_CLASS = "RCTProcivisRSEPinPad"
13
+ }
14
+
15
+ override fun getName() = REACT_CLASS
16
+
17
+ override fun createViewInstance(context: ThemedReactContext): UbqPinPad {
18
+ val pinPad = UbqPinPad(context, extraButton = UbqInternalPad.ExtraButtonFunctionality.BIOMETRICS_AUTO.code)
19
+ val activeFlow = PinPadRunner.getActiveFlow()
20
+ PinPadWrapper(callerContext, pinPad, activeFlow.flow, activeFlow.type)
21
+ return pinPad
22
+ }
23
+ }