@icedevml/react-native-host-card-emulation 0.6.0 → 0.6.1

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 CHANGED
@@ -233,6 +233,7 @@ for instance - your app may emulate an NDEF tag even when it's not launched on t
233
233
  ```
234
234
  3. Cleanup your event listener in `readerDeselected` -- this is obligatory because lack of proper cleanup will result in bugs and unexpected behavior.
235
235
  ```typescript
236
+ // inside processBackgroundHCE handler's switch
236
237
  case 'readerDeselected':
237
238
  // our application was deselected or the reader field was lost
238
239
  // perform a cleanup of any resources here
@@ -240,6 +241,7 @@ for instance - your app may emulate an NDEF tag even when it's not launched on t
240
241
  ```
241
242
  4. Implement the actual APDU handler that will process the C-APDU and generate a response.
242
243
  ```typescript
244
+ // inside processBackgroundHCE handler's switch
243
245
  case 'received':
244
246
  // decode incoming C-APDU to bytes
245
247
  const capdu = Buffer.from(event.arg!, 'hex');
@@ -20,7 +20,6 @@ import androidx.annotation.RequiresApi;
20
20
 
21
21
  import com.facebook.react.ReactApplication;
22
22
  import com.facebook.react.ReactHost;
23
- import com.facebook.react.ReactInstanceEventListener;
24
23
  import com.facebook.react.bridge.Arguments;
25
24
  import com.facebook.react.bridge.ReactContext;
26
25
  import com.facebook.react.bridge.UiThreadUtil;
@@ -28,12 +27,13 @@ import com.facebook.react.jstasks.HeadlessJsTaskConfig;
28
27
  import com.facebook.react.jstasks.HeadlessJsTaskContext;
29
28
  import com.facebook.react.jstasks.HeadlessJsTaskEventListener;
30
29
 
30
+ import java.util.HashMap;
31
31
  import java.util.List;
32
32
  import java.util.Locale;
33
33
  import java.util.UUID;
34
34
 
35
35
  @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
36
- public class HCEService extends HostApduService implements ReactInstanceEventListener, HCEServiceCallback {
36
+ public class HCEService extends HostApduService implements HCEServiceCallback {
37
37
  private static final String TAG = "HCEService";
38
38
 
39
39
  private boolean isForeground;
@@ -41,6 +41,13 @@ public class HCEService extends HostApduService implements ReactInstanceEventLis
41
41
  private RTNHCEAndroidModule hceModule;
42
42
  private byte[] pendingCAPDU;
43
43
  private volatile boolean needsResponse;
44
+ private final HashMap<String, Integer> taskSessionIdMap;
45
+
46
+ public HCEService() {
47
+ super();
48
+
49
+ taskSessionIdMap = new HashMap<>();
50
+ }
44
51
 
45
52
  private boolean isAppOnForeground(Context context) {
46
53
  /*
@@ -67,6 +74,7 @@ public class HCEService extends HostApduService implements ReactInstanceEventLis
67
74
  @Override
68
75
  public void onBackgroundHCEInit(String handle) {
69
76
  Log.d(TAG, "HCEService:onBackgroundHCEInit");
77
+ Log.d(TAG, "HCEService:onBackgroundHCEInit:" + handle + ":" + backgroundSessionUUID);
70
78
 
71
79
  if (handle == null && backgroundSessionUUID != null) {
72
80
  Log.d(TAG, "HCEService:onBackgroundHCEInit foreground call to background session");
@@ -83,6 +91,35 @@ public class HCEService extends HostApduService implements ReactInstanceEventLis
83
91
  }
84
92
  }
85
93
 
94
+ @Override
95
+ public void onBackgroundHCEFinish(String handle) {
96
+ Log.d(TAG, "HCEService:onBackgroundHCEFinish");
97
+ Integer taskId = taskSessionIdMap.get(handle);
98
+
99
+ if (taskId == null) {
100
+ Log.d(TAG, "HCEService:onBackgroundHCEFinish unable to resolve taskId");
101
+ throw new IllegalArgumentException();
102
+ }
103
+
104
+ ReactHost reactHost = ((ReactApplication) getApplication()).getReactHost();
105
+
106
+ if (reactHost == null) {
107
+ Log.d(TAG, "HCEService:onBackgroundHCEFinish getReactHost() returned null");
108
+ throw new IllegalArgumentException();
109
+ }
110
+
111
+ ReactContext reactContext = reactHost.getCurrentReactContext();
112
+
113
+ if (reactContext == null) {
114
+ Log.d(TAG, "HCEService:onBackgroundHCEFinish getCurrentReactContext() returned null");
115
+ throw new IllegalArgumentException();
116
+ }
117
+
118
+ HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(reactContext);
119
+ headlessJsTaskContext.finishTask(taskId);
120
+ taskSessionIdMap.remove(handle);
121
+ }
122
+
86
123
  @Override
87
124
  public void onRespondAPDU(String handle, String rapdu) {
88
125
  Log.d(TAG, "HCEService:onRespondAPDU");
@@ -110,13 +147,13 @@ public class HCEService extends HostApduService implements ReactInstanceEventLis
110
147
  String capdu = BinaryUtils.ByteArrayToHexString(command).toUpperCase(Locale.ROOT);
111
148
 
112
149
  if (isForeground) {
113
- if (hceModule._isHCERunning() && !hceModule.isHCEBrokenConnection()) {
150
+ if (hceModule._isHCERunning() && hceModule.isHCEActiveConnection()) {
114
151
  Log.d(TAG, "HCEService:processCommandApdu foreground sendEvent received");
115
152
  needsResponse = true;
116
153
  hceModule.sendEvent("received", capdu);
117
154
  } else {
118
155
  Log.d(TAG, "HCEService:processCommandApdu foreground respond 6999");
119
- return new byte[] { (byte) 0x69, (byte) 0x99 };
156
+ return new byte[]{(byte) 0x69, (byte) 0x99};
120
157
  }
121
158
  } else {
122
159
  if (hceModule != null && hceModule.isHCEBackgroundHandlerReady()) {
@@ -174,13 +211,29 @@ public class HCEService extends HostApduService implements ReactInstanceEventLis
174
211
  } else {
175
212
  ReactContext reactContext = reactHost.getCurrentReactContext();
176
213
  Log.d(TAG, "HCEService:onCreate background");
177
- this.backgroundSessionUUID = UUID.randomUUID().toString();
214
+ final String useSessID = UUID.randomUUID().toString();
215
+ backgroundSessionUUID = useSessID;
178
216
 
179
217
  if (reactContext == null) {
180
- reactHost.addReactInstanceEventListener(this);
218
+ reactHost.addReactInstanceEventListener(evReactContext -> {
219
+ HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(evReactContext);
220
+ headlessJsTaskContext.addTaskEventListener(new HeadlessJsTaskEventListener() {
221
+ @Override
222
+ public void onHeadlessJsTaskStart(int i) {
223
+ Log.d(TAG, "HCEService:HeadlessJsTaskEventListener:onHeadlessJsTaskStart: " + i);
224
+ }
225
+
226
+ @Override
227
+ public void onHeadlessJsTaskFinish(int i) {
228
+ Log.d(TAG, "HCEService:HeadlessJsTaskEventListener:onHeadlessJsTaskFinish: " + i);
229
+ }
230
+ });
231
+
232
+ setupRunJSTask(evReactContext, useSessID);
233
+ });
181
234
  reactHost.start();
182
235
  } else {
183
- setupRunJSTask(reactContext);
236
+ setupRunJSTask(reactContext, useSessID);
184
237
  }
185
238
  }
186
239
  }
@@ -191,37 +244,20 @@ public class HCEService extends HostApduService implements ReactInstanceEventLis
191
244
  needsResponse = false;
192
245
 
193
246
  if (isForeground) {
194
- if (this.hceModule != null && !this.hceModule.isHCEBrokenConnection()) {
247
+ if (this.hceModule != null && this.hceModule.isHCEActiveConnection()) {
195
248
  this.hceModule.sendEvent("readerDeselected", "");
196
249
  }
197
- } else {
198
- this.hceModule.sendBackgroundEvent(backgroundSessionUUID, "readerDeselected", "");
199
- }
250
+ } else if (backgroundSessionUUID != null) {
251
+ String prevBackgroundSessionUUID = backgroundSessionUUID;
252
+ backgroundSessionUUID = null;
200
253
 
201
- if (this.hceModule != null) {
202
- this.hceModule.setHCEService(null);
203
- }
204
- }
205
-
206
- @Override
207
- public void onReactContextInitialized(@NonNull ReactContext reactContext) {
208
- HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(reactContext);
209
- headlessJsTaskContext.addTaskEventListener(new HeadlessJsTaskEventListener() {
210
- @Override
211
- public void onHeadlessJsTaskStart(int i) {
212
- Log.d(TAG, "HCEService:HeadlessJsTaskEventListener:onHeadlessJsTaskStart: " + i);
213
- }
214
-
215
- @Override
216
- public void onHeadlessJsTaskFinish(int i) {
217
- Log.d(TAG, "HCEService:HeadlessJsTaskEventListener:onHeadlessJsTaskFinish: " + i);
254
+ if (this.hceModule != null) {
255
+ this.hceModule.sendBackgroundEvent(prevBackgroundSessionUUID, "readerDeselected", "");
218
256
  }
219
- });
220
-
221
- setupRunJSTask(reactContext);
257
+ }
222
258
  }
223
259
 
224
- public void setupRunJSTask(@NonNull ReactContext reactContext) {
260
+ public void setupRunJSTask(final @NonNull ReactContext reactContext, final String useSessUUID) {
225
261
  hceModule = (RTNHCEAndroidModule) reactContext.getNativeModule("NativeHCEModule");
226
262
 
227
263
  if (hceModule == null) {
@@ -230,21 +266,19 @@ public class HCEService extends HostApduService implements ReactInstanceEventLis
230
266
 
231
267
  hceModule.setHCEService(this);
232
268
 
233
- UiThreadUtil.runOnUiThread(new Runnable() {
234
- @Override
235
- public void run() {
236
- Log.d(TAG, "HCEService:setupRunJSTask:runOnUiThread startTask");
237
- Bundle argBundle = new Bundle();
238
- argBundle.putString("handle", backgroundSessionUUID);
239
- HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(reactContext);
240
- headlessJsTaskContext.startTask(
241
- new HeadlessJsTaskConfig(
242
- "handleBackgroundHCECall",
243
- Arguments.fromBundle(argBundle),
244
- 15000,
245
- false // not allowed in foreground
246
- ));
247
- }
269
+ UiThreadUtil.runOnUiThread(() -> {
270
+ Log.d(TAG, "HCEService:setupRunJSTask:runOnUiThread startTask");
271
+ Bundle argBundle = new Bundle();
272
+ argBundle.putString("handle", useSessUUID);
273
+ HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(reactContext);
274
+ int taskId = headlessJsTaskContext.startTask(
275
+ new HeadlessJsTaskConfig(
276
+ "handleBackgroundHCECall",
277
+ Arguments.fromBundle(argBundle),
278
+ 15000,
279
+ false // not allowed in foreground
280
+ ));
281
+ taskSessionIdMap.put(useSessUUID, taskId);
248
282
  });
249
283
  }
250
284
 
@@ -2,5 +2,6 @@ package com.itsecrnd.rtnhceandroid;
2
2
 
3
3
  public interface HCEServiceCallback {
4
4
  void onBackgroundHCEInit(String handle);
5
+ void onBackgroundHCEFinish(String handle);
5
6
  void onRespondAPDU(String handle, String rapdu);
6
7
  }
@@ -51,19 +51,29 @@ public class RTNHCEAndroidModule extends NativeHCEModuleSpec {
51
51
 
52
52
  void sendEvent(final String type, final String arg) {
53
53
  Log.d(TAG, "RTNHCEAndroidModule:sendEvent");
54
- WritableMap map = Arguments.createMap();
55
- map.putString("type", type);
56
- map.putString("arg", arg);
57
- emitOnEvent(map);
54
+
55
+ if (this.mEventEmitterCallback != null) {
56
+ WritableMap map = Arguments.createMap();
57
+ map.putString("type", type);
58
+ map.putString("arg", arg);
59
+ emitOnEvent(map);
60
+ } else {
61
+ Log.d(TAG, "RTNHCEAndroidModule:sendEvent missing event emitter, ignore");
62
+ }
58
63
  }
59
64
 
60
65
  void sendBackgroundEvent(final String audience, final String type, final String arg) {
61
66
  Log.d(TAG, "RTNHCEAndroidModule:sendBackgroundEvent");
62
- WritableMap map = Arguments.createMap();
63
- map.putString("audience", audience);
64
- map.putString("type", type);
65
- map.putString("arg", arg);
66
- emitOnBackgroundEvent(map);
67
+
68
+ if (this.mEventEmitterCallback != null) {
69
+ WritableMap map = Arguments.createMap();
70
+ map.putString("audience", audience);
71
+ map.putString("type", type);
72
+ map.putString("arg", arg);
73
+ emitOnBackgroundEvent(map);
74
+ } else {
75
+ Log.d(TAG, "RTNHCEAndroidModule:sendBackgroundEvent missing event emitter, ignore");
76
+ }
67
77
  }
68
78
 
69
79
  boolean _isHCERunning() {
@@ -78,8 +88,8 @@ public class RTNHCEAndroidModule extends NativeHCEModuleSpec {
78
88
  return this.hceBackgroundReady;
79
89
  }
80
90
 
81
- boolean isHCEBrokenConnection() {
82
- return this.hceBrokenConnection;
91
+ boolean isHCEActiveConnection() {
92
+ return !this.hceBrokenConnection;
83
93
  }
84
94
 
85
95
  void setHCEService(HCEServiceCallback serviceCallback) {
@@ -171,6 +181,19 @@ public class RTNHCEAndroidModule extends NativeHCEModuleSpec {
171
181
  return true;
172
182
  }
173
183
 
184
+ @Override
185
+ public boolean finishBackgroundHCE(String handle) {
186
+ Log.d(TAG, "RTNHCEAndroidModule:finishBackgroundHCE");
187
+
188
+ try {
189
+ this.serviceCb.onBackgroundHCEFinish(handle);
190
+ } catch (IllegalStateException e) {
191
+ return false;
192
+ }
193
+
194
+ return true;
195
+ }
196
+
174
197
  @Override
175
198
  public void startHCE(Promise promise) {
176
199
  Log.d(TAG, "RTNHCEAndroidModule:startHCE");
@@ -75,6 +75,7 @@ export interface Spec extends TurboModule {
75
75
  isSessionRunning(): boolean;
76
76
 
77
77
  beginBackgroundHCE(handle: string): boolean;
78
+ finishBackgroundHCE(handle: string): boolean;
78
79
 
79
80
  /**
80
81
  * iOS: Start the card emulation.
@@ -32,12 +32,14 @@ export const createBackgroundHCE = (handle: string) => {
32
32
  } finally {
33
33
  if (event.type === "readerDeselected") {
34
34
  subscription.remove();
35
+ NativeHCEModule.finishBackgroundHCE(handle);
35
36
  }
36
37
  }
37
38
  });
38
39
 
39
40
  if (!NativeHCEModule.beginBackgroundHCE(handle)) {
40
41
  subscription.remove();
42
+ NativeHCEModule.finishBackgroundHCE(handle);
41
43
  }
42
44
  }
43
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icedevml/react-native-host-card-emulation",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Host Card Emulation (HCE) React Native Turbo Module for iOS and Android platforms.",
5
5
  "react-native": "js/index",
6
6
  "source": "js/index",