@icedevml/react-native-host-card-emulation 0.3.0 → 0.4.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/README.md +63 -3
- package/android/src/main/java/com/itsecrnd/rtnhceandroid/HCEService.java +18 -15
- package/android/src/main/java/com/itsecrnd/rtnhceandroid/IntentKeys.java +7 -4
- package/android/src/main/java/com/itsecrnd/rtnhceandroid/RTNHCEAndroidModule.java +71 -50
- package/ios/RTNHCEModuleIOSImpl.swift +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ Install `@icedevml/react-native-host-card-emulation` package within your React N
|
|
|
67
67
|
+ <service
|
|
68
68
|
+ android:name="com.itsecrnd.rtnhceandroid.HCEService"
|
|
69
69
|
+ android:exported="true"
|
|
70
|
-
+ android:enabled="
|
|
70
|
+
+ android:enabled="true"
|
|
71
71
|
+ android:permission="android.permission.BIND_NFC_SERVICE" >
|
|
72
72
|
+ <intent-filter>
|
|
73
73
|
+ <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
|
|
@@ -98,5 +98,65 @@ Install `@icedevml/react-native-host-card-emulation` package within your React N
|
|
|
98
98
|
|
|
99
99
|
## API Specification & Demo App
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
This module provides a uniform low-level HCE API for both mobile platforms.
|
|
102
|
+
|
|
103
|
+
1. Subscribe to the event stream (there is a single global event stream for the entire module):
|
|
104
|
+
```typescript
|
|
105
|
+
NativeHCEModule?.onEvent(async (event: HCEModuleEvent) => {
|
|
106
|
+
switch (event.type) {
|
|
107
|
+
/* ... your handler here ... */
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
2. When user indicates that he wants to perform the HCE action, call the following function from the button's onClick routine:
|
|
112
|
+
```typescript
|
|
113
|
+
await NativeHCEModule?.beginSession();
|
|
114
|
+
```
|
|
115
|
+
This will emit `sessionStarted` event on both platforms. You can start the HCE straight from that event:
|
|
116
|
+
```typescript
|
|
117
|
+
// inside NativeHCEModule?.onEvent handler's switch
|
|
118
|
+
case 'sessionStarted':
|
|
119
|
+
NativeHCEModule?.setSessionAlertMessage('Tap towards the reader'); // only for iOS, no-op in Android
|
|
120
|
+
await NativeHCEModule?.startHCE();
|
|
121
|
+
break;
|
|
122
|
+
```
|
|
123
|
+
3. Calling `startHCE()` causes:
|
|
124
|
+
* iOS: Launch of the NFC scan system UI. The smartphone will start listening for C-APDUs from the readers right after that UI appears on screen. The `readerDetected` event will be emitted as soon as the NFC tag field presence is observed. The `readerDeselected` event will be emitted if the reader is physically disconnected or a non-matching AID is selected by the reader.
|
|
125
|
+
* Android: No user interface appears (you have to implement it on your own scanning UI, within your app). The `readerDetected` event will be emitted as soon as the first matching SELECT AID command is observed. The `readerDeselected` event will be emitted if the reader is physically disconnected or a non-matching AID is selected by the reader.
|
|
126
|
+
4. Optionally, you can handle `readerDetected` and `readerDeselected events to enhance user's experience.
|
|
127
|
+
```typescript
|
|
128
|
+
// inside NativeHCEModule?.onEvent handler's switch
|
|
129
|
+
case 'readerDetected':
|
|
130
|
+
NativeHCEModule?.setSessionAlertMessage('Reader detected'); // only for iOS, no-op in Android
|
|
131
|
+
break;
|
|
132
|
+
|
|
133
|
+
case 'readerDeselected':
|
|
134
|
+
NativeHCEModule?.setSessionAlertMessage('Lost reader'); // only for iOS, no-op in Android
|
|
135
|
+
break;
|
|
136
|
+
```
|
|
137
|
+
5. Handle CAPDU
|
|
138
|
+
```typescript
|
|
139
|
+
// inside NativeHCEModule?.onEvent handler's switch
|
|
140
|
+
case 'received':
|
|
141
|
+
NativeHCEModule?.setSessionAlertMessage('Keep holding the tag');
|
|
142
|
+
|
|
143
|
+
const capdu = Buffer.from(event.arg!, 'hex');
|
|
144
|
+
console.log('Received C-APDU, capdu.toString('hex'));
|
|
145
|
+
|
|
146
|
+
// let's say we always want to respond with [0x0A] + Status Word 0x9000
|
|
147
|
+
await NativeHCEModule?.respondAPDU(Buffer.from([0x0A, 0x90, 0x00], "hex"));
|
|
148
|
+
break;
|
|
149
|
+
```
|
|
150
|
+
6. (iOS) Optionally, if you need to utilize `NFCPresentmentIntentAssertion` then at any time you can call:
|
|
151
|
+
```typescript
|
|
152
|
+
NativeHCEModule?.acquireExclusiveNFC();
|
|
153
|
+
```
|
|
154
|
+
Which will throw an exception if the presentment intent assertion was already acquired and is still valid
|
|
155
|
+
(usually 15 seconds) or if the cooldown period for acquiring new assertion was not yet expired (usually also 15 seconds).
|
|
156
|
+
|
|
157
|
+
> [!NOTE]
|
|
158
|
+
> See [Demo App's Code (Example Library Usage)](https://github.com/icedevml/react-native-host-card-emulation/blob/master/packages/demo-hce-module-app/App.tsx) example for more insignt about the library's API.
|
|
159
|
+
|
|
160
|
+
> [!NOTE]
|
|
161
|
+
> Raw native module's API specification is available in [packages/native-hce-module/js/NativeHCEModule.ts](https://github.com/icedevml/react-native-host-card-emulation/blob/master/packages/native-hce-module/js/NativeHCEModule.ts).
|
|
162
|
+
> Check it out in order to understand what methods you can call against the module and what are the expected parameters/return values.
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
package com.itsecrnd.rtnhceandroid;
|
|
2
2
|
|
|
3
|
-
import static com.itsecrnd.rtnhceandroid.IntentKeys.
|
|
4
|
-
import static com.itsecrnd.rtnhceandroid.IntentKeys.
|
|
5
|
-
import static com.itsecrnd.rtnhceandroid.IntentKeys.
|
|
6
|
-
import static com.itsecrnd.rtnhceandroid.IntentKeys.
|
|
3
|
+
import static com.itsecrnd.rtnhceandroid.IntentKeys.ACTION_READER_DETECT;
|
|
4
|
+
import static com.itsecrnd.rtnhceandroid.IntentKeys.ACTION_READER_LOST;
|
|
5
|
+
import static com.itsecrnd.rtnhceandroid.IntentKeys.ACTION_RECEIVE_C_APDU;
|
|
6
|
+
import static com.itsecrnd.rtnhceandroid.IntentKeys.ACTION_SEND_R_APDU;
|
|
7
|
+
import static com.itsecrnd.rtnhceandroid.IntentKeys.KEY_AUTH;
|
|
8
|
+
import static com.itsecrnd.rtnhceandroid.IntentKeys.KEY_CAPDU;
|
|
9
|
+
import static com.itsecrnd.rtnhceandroid.IntentKeys.KEY_RAPDU;
|
|
7
10
|
|
|
8
11
|
import android.content.BroadcastReceiver;
|
|
9
12
|
import android.content.Context;
|
|
@@ -34,10 +37,10 @@ public class HCEService extends HostApduService {
|
|
|
34
37
|
String action = intent.getAction();
|
|
35
38
|
|
|
36
39
|
if (action != null) {
|
|
37
|
-
AESGCMUtil.decryptData(encSecretKey, intent.getStringExtra(
|
|
40
|
+
AESGCMUtil.decryptData(encSecretKey, intent.getStringExtra(KEY_AUTH));
|
|
38
41
|
|
|
39
|
-
if (action.equals(
|
|
40
|
-
String rapdu = AESGCMUtil.decryptData(encSecretKey, intent.getStringExtra(
|
|
42
|
+
if (action.equals(ACTION_SEND_R_APDU)) {
|
|
43
|
+
String rapdu = AESGCMUtil.decryptData(encSecretKey, intent.getStringExtra(KEY_RAPDU));
|
|
41
44
|
byte[] dec = BinaryUtils.HexStringToByteArray(rapdu.toUpperCase(Locale.ROOT));
|
|
42
45
|
sendResponseApdu(dec);
|
|
43
46
|
}
|
|
@@ -52,10 +55,10 @@ public class HCEService extends HostApduService {
|
|
|
52
55
|
public byte[] processCommandApdu(byte[] command, Bundle extras) {
|
|
53
56
|
String capdu = AESGCMUtil.encryptData(encSecretKey, BinaryUtils.ByteArrayToHexString(command));
|
|
54
57
|
|
|
55
|
-
Intent intent = new Intent(
|
|
58
|
+
Intent intent = new Intent(ACTION_RECEIVE_C_APDU);
|
|
56
59
|
intent.setPackage(getApplicationContext().getPackageName());
|
|
57
|
-
intent.putExtra(
|
|
58
|
-
intent.putExtra(
|
|
60
|
+
intent.putExtra(KEY_AUTH, AESGCMUtil.encryptData(encSecretKey, AESGCMUtil.randomString()));
|
|
61
|
+
intent.putExtra(KEY_CAPDU, capdu);
|
|
59
62
|
getApplicationContext().sendBroadcast(intent);
|
|
60
63
|
|
|
61
64
|
return null;
|
|
@@ -81,12 +84,12 @@ public class HCEService extends HostApduService {
|
|
|
81
84
|
* See a comment about registerReceiver() in RTNHCEAndroidModule.
|
|
82
85
|
*/
|
|
83
86
|
IntentFilter filter = new IntentFilter();
|
|
84
|
-
filter.addAction(
|
|
87
|
+
filter.addAction(ACTION_SEND_R_APDU);
|
|
85
88
|
getApplicationContext().registerReceiver(receiver, filter, RECEIVER_EXPORTED);
|
|
86
89
|
|
|
87
|
-
Intent intent = new Intent(
|
|
90
|
+
Intent intent = new Intent(ACTION_READER_DETECT);
|
|
88
91
|
intent.setPackage(getApplicationContext().getPackageName());
|
|
89
|
-
intent.putExtra(
|
|
92
|
+
intent.putExtra(KEY_AUTH, AESGCMUtil.encryptData(encSecretKey, AESGCMUtil.randomString()));
|
|
90
93
|
getApplicationContext().sendBroadcast(intent);
|
|
91
94
|
}
|
|
92
95
|
|
|
@@ -96,9 +99,9 @@ public class HCEService extends HostApduService {
|
|
|
96
99
|
|
|
97
100
|
getApplicationContext().unregisterReceiver(receiver);
|
|
98
101
|
|
|
99
|
-
Intent intent = new Intent(
|
|
102
|
+
Intent intent = new Intent(ACTION_READER_LOST);
|
|
100
103
|
intent.setPackage(getApplicationContext().getPackageName());
|
|
101
|
-
intent.putExtra(
|
|
104
|
+
intent.putExtra(KEY_AUTH, AESGCMUtil.encryptData(encSecretKey, AESGCMUtil.randomString()));
|
|
102
105
|
getApplicationContext().sendBroadcast(intent);
|
|
103
106
|
}
|
|
104
107
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
package com.itsecrnd.rtnhceandroid;
|
|
2
2
|
|
|
3
3
|
public class IntentKeys {
|
|
4
|
-
public static final String
|
|
5
|
-
public static final String
|
|
6
|
-
public static final String
|
|
7
|
-
public static final String
|
|
4
|
+
public static final String ACTION_RECEIVE_C_APDU = "com.itsecrnd.rtnhceandroid.ACTION_RECEIVE_C_APDU";
|
|
5
|
+
public static final String ACTION_SEND_R_APDU = "com.itsecrnd.rtnhceandroid.ACTION_SEND_R_APDU";
|
|
6
|
+
public static final String ACTION_READER_DETECT = "com.itsecrnd.rtnhceandroid.ACTION_READER_DETECT";
|
|
7
|
+
public static final String ACTION_READER_LOST = "com.itsecrnd.rtnhceandroid.ACTION_READER_LOST";
|
|
8
|
+
public static final String KEY_AUTH = "auth";
|
|
9
|
+
public static final String KEY_CAPDU = "capdu";
|
|
10
|
+
public static final String KEY_RAPDU = "rapdu";
|
|
8
11
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
package com.itsecrnd.rtnhceandroid;
|
|
2
2
|
|
|
3
3
|
import static android.content.Context.RECEIVER_EXPORTED;
|
|
4
|
-
import static com.itsecrnd.rtnhceandroid.IntentKeys.
|
|
5
|
-
import static com.itsecrnd.rtnhceandroid.IntentKeys.
|
|
6
|
-
import static com.itsecrnd.rtnhceandroid.IntentKeys.
|
|
7
|
-
import static com.itsecrnd.rtnhceandroid.IntentKeys.
|
|
4
|
+
import static com.itsecrnd.rtnhceandroid.IntentKeys.ACTION_READER_DETECT;
|
|
5
|
+
import static com.itsecrnd.rtnhceandroid.IntentKeys.ACTION_READER_LOST;
|
|
6
|
+
import static com.itsecrnd.rtnhceandroid.IntentKeys.ACTION_RECEIVE_C_APDU;
|
|
7
|
+
import static com.itsecrnd.rtnhceandroid.IntentKeys.ACTION_SEND_R_APDU;
|
|
8
8
|
|
|
9
9
|
import android.content.BroadcastReceiver;
|
|
10
10
|
import android.content.ComponentName;
|
|
@@ -30,20 +30,11 @@ public class RTNHCEAndroidModule extends NativeHCEModuleSpec {
|
|
|
30
30
|
public static final String TAG = "RTNHCEAndroidModule";
|
|
31
31
|
public static final String NAME = "NativeHCEModule";
|
|
32
32
|
|
|
33
|
+
private volatile boolean sessionRunning;
|
|
34
|
+
private volatile boolean hceRunning;
|
|
35
|
+
private volatile boolean hceBreakConnection;
|
|
36
|
+
private volatile boolean hceDeselected;
|
|
33
37
|
private final SecretKey encSecretKey;
|
|
34
|
-
private boolean sessionOpen;
|
|
35
|
-
|
|
36
|
-
private void toggleHceService(Boolean enabled) {
|
|
37
|
-
getReactApplicationContext()
|
|
38
|
-
.getPackageManager()
|
|
39
|
-
.setComponentEnabledSetting(
|
|
40
|
-
new ComponentName(getReactApplicationContext(), HCEService.class),
|
|
41
|
-
enabled
|
|
42
|
-
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
|
43
|
-
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
|
44
|
-
PackageManager.DONT_KILL_APP
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
38
|
|
|
48
39
|
private void sendEvent(final String type, final String arg) {
|
|
49
40
|
WritableMap map = Arguments.createMap();
|
|
@@ -61,13 +52,29 @@ public class RTNHCEAndroidModule extends NativeHCEModuleSpec {
|
|
|
61
52
|
if (action != null) {
|
|
62
53
|
AESGCMUtil.decryptData(encSecretKey, intent.getStringExtra("auth"));
|
|
63
54
|
|
|
64
|
-
if (action.equals(
|
|
55
|
+
if (action.equals(ACTION_RECEIVE_C_APDU)) {
|
|
65
56
|
String capdu = AESGCMUtil.decryptData(encSecretKey, intent.getStringExtra("capdu"));
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
|
|
58
|
+
if (!hceBreakConnection && hceRunning) {
|
|
59
|
+
sendEvent("received", capdu);
|
|
60
|
+
} else {
|
|
61
|
+
Intent rintent = new Intent(ACTION_SEND_R_APDU);
|
|
62
|
+
rintent.setPackage(getReactApplicationContext().getPackageName());
|
|
63
|
+
rintent.putExtra("auth", AESGCMUtil.encryptData(encSecretKey, AESGCMUtil.randomString()));
|
|
64
|
+
rintent.putExtra("rapdu", AESGCMUtil.encryptData(encSecretKey, "6999"));
|
|
65
|
+
getReactApplicationContext().getApplicationContext().sendBroadcast(rintent);
|
|
66
|
+
}
|
|
67
|
+
} else if (hceRunning && action.equals(ACTION_READER_DETECT)) {
|
|
68
|
+
hceDeselected = false;
|
|
68
69
|
sendEvent("readerDetected", "");
|
|
69
|
-
} else if (action.equals(
|
|
70
|
-
|
|
70
|
+
} else if (action.equals(ACTION_READER_LOST)) {
|
|
71
|
+
if (hceRunning && !hceBreakConnection && !hceDeselected) {
|
|
72
|
+
// only if we haven't send a fake disconnect event already
|
|
73
|
+
hceDeselected = true;
|
|
74
|
+
sendEvent("readerDeselected", "");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
hceBreakConnection = false;
|
|
71
78
|
}
|
|
72
79
|
}
|
|
73
80
|
} catch (Exception e) {
|
|
@@ -78,10 +85,11 @@ public class RTNHCEAndroidModule extends NativeHCEModuleSpec {
|
|
|
78
85
|
|
|
79
86
|
RTNHCEAndroidModule(ReactApplicationContext context) {
|
|
80
87
|
super(context);
|
|
81
|
-
|
|
82
|
-
this.sessionOpen = false;
|
|
83
|
-
|
|
84
88
|
String encKey;
|
|
89
|
+
this.sessionRunning = false;
|
|
90
|
+
this.hceRunning = false;
|
|
91
|
+
this.hceBreakConnection = false;
|
|
92
|
+
this.hceDeselected = true;
|
|
85
93
|
|
|
86
94
|
try {
|
|
87
95
|
encSecretKey = AESGCMUtil.generateKey();
|
|
@@ -98,9 +106,9 @@ public class RTNHCEAndroidModule extends NativeHCEModuleSpec {
|
|
|
98
106
|
.commit();
|
|
99
107
|
|
|
100
108
|
IntentFilter filter = new IntentFilter();
|
|
101
|
-
filter.addAction(
|
|
102
|
-
filter.addAction(
|
|
103
|
-
filter.addAction(
|
|
109
|
+
filter.addAction(ACTION_RECEIVE_C_APDU);
|
|
110
|
+
filter.addAction(ACTION_READER_DETECT);
|
|
111
|
+
filter.addAction(ACTION_READER_LOST);
|
|
104
112
|
|
|
105
113
|
/*
|
|
106
114
|
* FIXME It seems like those intents are not going through across this module and HostApduService.
|
|
@@ -115,6 +123,11 @@ public class RTNHCEAndroidModule extends NativeHCEModuleSpec {
|
|
|
115
123
|
getReactApplicationContext().getApplicationContext().registerReceiver(receiver, filter, RECEIVER_EXPORTED);
|
|
116
124
|
}
|
|
117
125
|
|
|
126
|
+
@Override
|
|
127
|
+
public void invalidate() {
|
|
128
|
+
getReactApplicationContext().getApplicationContext().unregisterReceiver(receiver);
|
|
129
|
+
}
|
|
130
|
+
|
|
118
131
|
@Override
|
|
119
132
|
@NonNull
|
|
120
133
|
public String getName() {
|
|
@@ -135,18 +148,18 @@ public class RTNHCEAndroidModule extends NativeHCEModuleSpec {
|
|
|
135
148
|
|
|
136
149
|
@Override
|
|
137
150
|
public boolean isExclusiveNFC() {
|
|
138
|
-
// unsupported on Android
|
|
151
|
+
// unsupported on Android, always false
|
|
139
152
|
return false;
|
|
140
153
|
}
|
|
141
154
|
|
|
142
155
|
@Override
|
|
143
156
|
public void beginSession(Promise promise) {
|
|
144
|
-
if (this.
|
|
145
|
-
|
|
146
|
-
} else {
|
|
147
|
-
this.sessionOpen = true;
|
|
148
|
-
promise.resolve(null);
|
|
157
|
+
if (!this.sessionRunning) {
|
|
158
|
+
this.sessionRunning = true;
|
|
149
159
|
sendEvent("sessionStarted", "");
|
|
160
|
+
promise.resolve(null);
|
|
161
|
+
} else {
|
|
162
|
+
promise.reject("err_card_session_exists", "Session already exists.");
|
|
150
163
|
}
|
|
151
164
|
}
|
|
152
165
|
|
|
@@ -157,37 +170,48 @@ public class RTNHCEAndroidModule extends NativeHCEModuleSpec {
|
|
|
157
170
|
|
|
158
171
|
@Override
|
|
159
172
|
public void invalidateSession() {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
173
|
+
if (this.sessionRunning) {
|
|
174
|
+
this.sessionRunning = false;
|
|
175
|
+
sendEvent("sessionInvalidated", "userInvalidated");
|
|
176
|
+
}
|
|
163
177
|
}
|
|
164
178
|
|
|
165
179
|
@Override
|
|
166
180
|
public boolean isSessionRunning() {
|
|
167
|
-
return this.
|
|
181
|
+
return this.sessionRunning;
|
|
168
182
|
}
|
|
169
183
|
|
|
170
184
|
@Override
|
|
171
185
|
public void startHCE(Promise promise) {
|
|
172
|
-
|
|
186
|
+
if (this.hceRunning) {
|
|
187
|
+
promise.reject("err_start_emulation", "Error trying to start emulation. Emulation already started.");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.hceRunning = true;
|
|
173
192
|
promise.resolve(null);
|
|
174
193
|
}
|
|
175
194
|
|
|
176
195
|
@Override
|
|
177
196
|
public void stopHCE(String status, Promise promise) {
|
|
178
|
-
|
|
197
|
+
if (this.hceRunning) {
|
|
198
|
+
this.hceRunning = false;
|
|
199
|
+
|
|
200
|
+
if (!this.hceDeselected) {
|
|
201
|
+
this.hceDeselected = true;
|
|
202
|
+
this.hceBreakConnection = true;
|
|
203
|
+
sendEvent("readerDeselected", "");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
179
207
|
promise.resolve(null);
|
|
180
208
|
}
|
|
181
209
|
|
|
182
210
|
@Override
|
|
183
211
|
public void respondAPDU(String rapdu, Promise promise) {
|
|
184
|
-
if (!this.sessionOpen) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
212
|
String encRapdu = AESGCMUtil.encryptData(encSecretKey, rapdu);
|
|
189
213
|
|
|
190
|
-
Intent intent = new Intent(
|
|
214
|
+
Intent intent = new Intent(ACTION_SEND_R_APDU);
|
|
191
215
|
intent.setPackage(getReactApplicationContext().getPackageName());
|
|
192
216
|
intent.putExtra("auth", AESGCMUtil.encryptData(encSecretKey, AESGCMUtil.randomString()));
|
|
193
217
|
intent.putExtra("rapdu", encRapdu);
|
|
@@ -198,10 +222,7 @@ public class RTNHCEAndroidModule extends NativeHCEModuleSpec {
|
|
|
198
222
|
|
|
199
223
|
@Override
|
|
200
224
|
public void isHCERunning(Promise promise) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
.getComponentEnabledSetting(new ComponentName(getReactApplicationContext(), HCEService.class));
|
|
204
|
-
|
|
205
|
-
promise.resolve(enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
|
|
225
|
+
// unsupported on Android, always true
|
|
226
|
+
promise.resolve(true);
|
|
206
227
|
}
|
|
207
228
|
}
|
|
@@ -170,7 +170,14 @@ extension Data {
|
|
|
170
170
|
|
|
171
171
|
@objc public func startHCE(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
172
172
|
Task.init {
|
|
173
|
-
|
|
173
|
+
do {
|
|
174
|
+
try await self.cardSession?.startEmulation()
|
|
175
|
+
} catch(let error) {
|
|
176
|
+
reject("err_start_emulation", "Error trying to start emulation. \(error)", nil)
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
resolve(nil)
|
|
174
181
|
}
|
|
175
182
|
}
|
|
176
183
|
|
package/package.json
CHANGED