@icedevml/react-native-host-card-emulation 0.2.0 → 0.2.2
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/LICENSE +21 -21
- package/README.md +102 -17
- package/RTNHCEModuleIOS.podspec +19 -19
- package/android/build.gradle +33 -33
- package/android/src/main/AndroidMainfest.xml +3 -3
- package/android/src/main/java/com/itsecrnd/rtnhceandroid/AESGCMUtil.java +89 -89
- package/android/src/main/java/com/itsecrnd/rtnhceandroid/BinaryUtils.java +65 -65
- package/android/src/main/java/com/itsecrnd/rtnhceandroid/HCEService.java +92 -92
- package/android/src/main/java/com/itsecrnd/rtnhceandroid/IntentKeys.java +6 -6
- package/android/src/main/java/com/itsecrnd/rtnhceandroid/RTNHCEAndroidModule.java +148 -148
- package/android/src/main/java/com/itsecrnd/rtnhceandroid/RTNHCEAndroidPackage.java +47 -47
- package/ios/RTNHCEModuleIOS-Bridging-Header.h +1 -1
- package/ios/RTNHCEModuleIOS.h +12 -12
- package/ios/RTNHCEModuleIOS.mm +125 -125
- package/ios/RTNHCEModuleIOSImpl.swift +190 -190
- package/js/NativeHCEModule.ts +33 -33
- package/package.json +1 -1
package/ios/RTNHCEModuleIOS.mm
CHANGED
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
#import "RTNHCEModuleIOS.h"
|
|
2
|
-
#import "RCTDefaultReactNativeFactoryDelegate.h"
|
|
3
|
-
#import "RTNHCEModuleIOS-Swift.h"
|
|
4
|
-
|
|
5
|
-
@implementation RTNHCEModuleIOS {
|
|
6
|
-
API_AVAILABLE(ios(17.4))
|
|
7
|
-
RTNHCEModuleIOSImpl *hcemoduleios;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
-(id) init {
|
|
11
|
-
self = [super init];
|
|
12
|
-
|
|
13
|
-
if (@available(iOS 17.4, *)) {
|
|
14
|
-
__weak RTNHCEModuleIOS *weakSelf = self;
|
|
15
|
-
|
|
16
|
-
hcemoduleios = [RTNHCEModuleIOSImpl new];
|
|
17
|
-
[hcemoduleios setEmitOnEventWithEmitOnEvent:^(NSString *type, NSString *arg){
|
|
18
|
-
[weakSelf emitOnEvent:@{@"type": type, @"arg": arg}];
|
|
19
|
-
}];
|
|
20
|
-
} else {
|
|
21
|
-
// ignore
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return(self);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
- (void)dealloc {
|
|
28
|
-
[hcemoduleios setEmitOnEventWithEmitOnEvent:^(NSString *type, NSString *arg){
|
|
29
|
-
// no-op
|
|
30
|
-
}];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
34
|
-
return std::make_shared<facebook::react::NativeHCEModuleSpecJSI>(params);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
- (nonnull NSNumber *)isPlatformSupported {
|
|
38
|
-
if (@available(iOS 17.4, *)) {
|
|
39
|
-
return @1;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return @0;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
- (void)acquireExclusiveNFC:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
46
|
-
if (@available(iOS 17.4, *)) {
|
|
47
|
-
return [hcemoduleios acquireExclusiveNFCWithResolve:resolve reject:reject];
|
|
48
|
-
} else {
|
|
49
|
-
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
- (nonnull NSNumber *)isExclusiveNFC {
|
|
54
|
-
if (@available(iOS 17.4, *)) {
|
|
55
|
-
return [hcemoduleios isExclusiveNFC] ? @1 : @0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return @0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
- (void)beginSession:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
62
|
-
if (@available(iOS 17.4, *)) {
|
|
63
|
-
return [hcemoduleios beginSessionWithResolve:resolve reject:reject];
|
|
64
|
-
} else {
|
|
65
|
-
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
- (void)setSessionAlertMessage:(nonnull NSString *)message {
|
|
70
|
-
if (@available(iOS 17.4, *)) {
|
|
71
|
-
return [hcemoduleios setSessionAlertMessageWithMessage:message];
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
- (void)invalidateSession {
|
|
76
|
-
if (@available(iOS 17.4, *)) {
|
|
77
|
-
[hcemoduleios invalidateSession];
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
- (nonnull NSNumber *)isSessionRunning {
|
|
82
|
-
if (@available(iOS 17.4, *)) {
|
|
83
|
-
return [hcemoduleios isSessionRunning] ? @1 : @0;
|
|
84
|
-
} else {
|
|
85
|
-
return @0;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
- (void)startHCE:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
90
|
-
if (@available(iOS 17.4, *)) {
|
|
91
|
-
return [hcemoduleios startHCEWithResolve:resolve reject:reject];
|
|
92
|
-
} else {
|
|
93
|
-
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
- (void)stopHCE:(nonnull NSString *)status resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
98
|
-
if (@available(iOS 17.4, *)) {
|
|
99
|
-
return [hcemoduleios stopHCEWithStatus:status resolve:resolve reject:reject];
|
|
100
|
-
} else {
|
|
101
|
-
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
- (void)respondAPDU:(nonnull NSString *)rapdu resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
106
|
-
if (@available(iOS 17.4, *)) {
|
|
107
|
-
return [hcemoduleios respondAPDUWithRapdu:rapdu resolve:resolve reject:reject];
|
|
108
|
-
} else {
|
|
109
|
-
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
- (void)isHCERunning:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
114
|
-
if (@available(iOS 17.4, *)) {
|
|
115
|
-
[hcemoduleios isHCERunningWithResolve:resolve reject:reject];
|
|
116
|
-
} else {
|
|
117
|
-
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
+ (NSString *)moduleName {
|
|
122
|
-
return @"NativeHCEModule";
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
@end
|
|
1
|
+
#import "RTNHCEModuleIOS.h"
|
|
2
|
+
#import "RCTDefaultReactNativeFactoryDelegate.h"
|
|
3
|
+
#import "RTNHCEModuleIOS-Swift.h"
|
|
4
|
+
|
|
5
|
+
@implementation RTNHCEModuleIOS {
|
|
6
|
+
API_AVAILABLE(ios(17.4))
|
|
7
|
+
RTNHCEModuleIOSImpl *hcemoduleios;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
-(id) init {
|
|
11
|
+
self = [super init];
|
|
12
|
+
|
|
13
|
+
if (@available(iOS 17.4, *)) {
|
|
14
|
+
__weak RTNHCEModuleIOS *weakSelf = self;
|
|
15
|
+
|
|
16
|
+
hcemoduleios = [RTNHCEModuleIOSImpl new];
|
|
17
|
+
[hcemoduleios setEmitOnEventWithEmitOnEvent:^(NSString *type, NSString *arg){
|
|
18
|
+
[weakSelf emitOnEvent:@{@"type": type, @"arg": arg}];
|
|
19
|
+
}];
|
|
20
|
+
} else {
|
|
21
|
+
// ignore
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return(self);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
- (void)dealloc {
|
|
28
|
+
[hcemoduleios setEmitOnEventWithEmitOnEvent:^(NSString *type, NSString *arg){
|
|
29
|
+
// no-op
|
|
30
|
+
}];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
34
|
+
return std::make_shared<facebook::react::NativeHCEModuleSpecJSI>(params);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
- (nonnull NSNumber *)isPlatformSupported {
|
|
38
|
+
if (@available(iOS 17.4, *)) {
|
|
39
|
+
return @1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return @0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
- (void)acquireExclusiveNFC:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
46
|
+
if (@available(iOS 17.4, *)) {
|
|
47
|
+
return [hcemoduleios acquireExclusiveNFCWithResolve:resolve reject:reject];
|
|
48
|
+
} else {
|
|
49
|
+
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
- (nonnull NSNumber *)isExclusiveNFC {
|
|
54
|
+
if (@available(iOS 17.4, *)) {
|
|
55
|
+
return [hcemoduleios isExclusiveNFC] ? @1 : @0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return @0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
- (void)beginSession:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
62
|
+
if (@available(iOS 17.4, *)) {
|
|
63
|
+
return [hcemoduleios beginSessionWithResolve:resolve reject:reject];
|
|
64
|
+
} else {
|
|
65
|
+
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
- (void)setSessionAlertMessage:(nonnull NSString *)message {
|
|
70
|
+
if (@available(iOS 17.4, *)) {
|
|
71
|
+
return [hcemoduleios setSessionAlertMessageWithMessage:message];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
- (void)invalidateSession {
|
|
76
|
+
if (@available(iOS 17.4, *)) {
|
|
77
|
+
[hcemoduleios invalidateSession];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
- (nonnull NSNumber *)isSessionRunning {
|
|
82
|
+
if (@available(iOS 17.4, *)) {
|
|
83
|
+
return [hcemoduleios isSessionRunning] ? @1 : @0;
|
|
84
|
+
} else {
|
|
85
|
+
return @0;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
- (void)startHCE:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
90
|
+
if (@available(iOS 17.4, *)) {
|
|
91
|
+
return [hcemoduleios startHCEWithResolve:resolve reject:reject];
|
|
92
|
+
} else {
|
|
93
|
+
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
- (void)stopHCE:(nonnull NSString *)status resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
98
|
+
if (@available(iOS 17.4, *)) {
|
|
99
|
+
return [hcemoduleios stopHCEWithStatus:status resolve:resolve reject:reject];
|
|
100
|
+
} else {
|
|
101
|
+
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
- (void)respondAPDU:(nonnull NSString *)rapdu resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
106
|
+
if (@available(iOS 17.4, *)) {
|
|
107
|
+
return [hcemoduleios respondAPDUWithRapdu:rapdu resolve:resolve reject:reject];
|
|
108
|
+
} else {
|
|
109
|
+
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
- (void)isHCERunning:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
114
|
+
if (@available(iOS 17.4, *)) {
|
|
115
|
+
[hcemoduleios isHCERunningWithResolve:resolve reject:reject];
|
|
116
|
+
} else {
|
|
117
|
+
reject(@"err_platform_unsupported", @"This iOS version does not support HCE.", nil);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
+ (NSString *)moduleName {
|
|
122
|
+
return @"NativeHCEModule";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@end
|
|
@@ -1,190 +1,190 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import CoreNFC
|
|
3
|
-
|
|
4
|
-
extension StringProtocol {
|
|
5
|
-
func asHexArray() -> [UInt8] {
|
|
6
|
-
var startIndex = self.startIndex
|
|
7
|
-
return stride(from: 0, to: count, by: 2).compactMap { _ in
|
|
8
|
-
let endIndex = index(startIndex, offsetBy: 2, limitedBy: self.endIndex) ?? self.endIndex
|
|
9
|
-
defer { startIndex = endIndex }
|
|
10
|
-
return UInt8(self[startIndex..<endIndex], radix: 16)
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
extension Data {
|
|
16
|
-
struct HexEncodingOptions: OptionSet {
|
|
17
|
-
let rawValue: Int
|
|
18
|
-
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
func hexEncodedString(options: HexEncodingOptions = []) -> String {
|
|
22
|
-
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
|
|
23
|
-
return map { String(format: format, $0) }.joined()
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
@available(iOS 17.4, *)
|
|
28
|
-
@objc public class RTNHCEModuleIOSImpl: NSObject {
|
|
29
|
-
var cardSession: CardSession? = nil
|
|
30
|
-
var cardSessionInvalidated: Bool = true
|
|
31
|
-
var presentmentIntent: NFCPresentmentIntentAssertion? = nil
|
|
32
|
-
var receivedCardAPDU: CardSession.APDU? = nil
|
|
33
|
-
|
|
34
|
-
var emitOnEvent: ((NSString, NSString?) -> ())? = nil
|
|
35
|
-
|
|
36
|
-
@objc public func setEmitOnEvent(emitOnEvent: @escaping (NSString, NSString?) -> ()) {
|
|
37
|
-
self.emitOnEvent = emitOnEvent
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
@objc public func acquireExclusiveNFC(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
41
|
-
Task.init {
|
|
42
|
-
if self.presentmentIntent != nil && self.presentmentIntent!.isValid {
|
|
43
|
-
reject("err_already_exists", "NFCPresentmentIntentAssertion already exists and is valid.", nil)
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
self.presentmentIntent = nil
|
|
48
|
-
|
|
49
|
-
do {
|
|
50
|
-
self.presentmentIntent = try await NFCPresentmentIntentAssertion.acquire()
|
|
51
|
-
} catch (let error) {
|
|
52
|
-
// TODO exact error details here and in other places as well
|
|
53
|
-
reject("err_create_presentment", "Failed to create NFCPresentmentIntentAssertion: \(error)", nil)
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
resolve(nil)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
@objc public func isExclusiveNFC() -> Bool {
|
|
62
|
-
return self.presentmentIntent != nil && self.presentmentIntent!.isValid
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
@objc public func beginSession(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
66
|
-
Task.init {
|
|
67
|
-
guard NFCReaderSession.readingAvailable else {
|
|
68
|
-
reject("err_nfc_reader_not_available", "Preflight check failed: NFCReaderSession.readingAvailable is false", nil)
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
guard CardSession.isSupported else {
|
|
73
|
-
reject("err_card_session_unsupported", "Preflight check failed: CardSession.isSupported is false", nil)
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
guard await CardSession.isEligible else {
|
|
78
|
-
reject("err_card_session_not_eligible", "Preflight check failed: CardSession.isEligible is false", nil)
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
guard self.cardSessionInvalidated else {
|
|
83
|
-
reject("err_card_session_exists", "CardSession already exists.", nil)
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
do {
|
|
88
|
-
self.cardSession = try await CardSession()
|
|
89
|
-
} catch (let error) {
|
|
90
|
-
reject("err_create_card_session", "Failed to create CardSession(): \(error)", nil)
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
resolve(nil)
|
|
95
|
-
|
|
96
|
-
do {
|
|
97
|
-
for try await event in cardSession!.eventStream {
|
|
98
|
-
switch event {
|
|
99
|
-
case .sessionStarted:
|
|
100
|
-
cardSession!.alertMessage = String(localized: "Communicating with card reader.")
|
|
101
|
-
self.emitOnEvent!("sessionStarted", "")
|
|
102
|
-
|
|
103
|
-
case .readerDetected:
|
|
104
|
-
self.emitOnEvent!("readerDetected", "")
|
|
105
|
-
|
|
106
|
-
case .readerDeselected:
|
|
107
|
-
self.emitOnEvent!("readerDeselected", "")
|
|
108
|
-
|
|
109
|
-
case .received(let cardAPDU):
|
|
110
|
-
self.receivedCardAPDU = cardAPDU
|
|
111
|
-
self.emitOnEvent!("received", NSString(string:cardAPDU.payload.hexEncodedString()))
|
|
112
|
-
|
|
113
|
-
case .sessionInvalidated(let reason):
|
|
114
|
-
self.cardSessionInvalidated = true
|
|
115
|
-
self.emitOnEvent!("sessionInvalidated", "\(reason)" as NSString)
|
|
116
|
-
self.cardSession = nil
|
|
117
|
-
|
|
118
|
-
@unknown default:
|
|
119
|
-
fatalError("Received unknown CardSession event.")
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
} catch (let err) {
|
|
123
|
-
self.emitOnEvent!("internalError", "\(err)" as NSString)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
@objc public func setSessionAlertMessage(message: NSString) {
|
|
129
|
-
self.cardSession?.alertMessage = String(message)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
@objc public func invalidateSession() {
|
|
133
|
-
self.cardSession?.invalidate()
|
|
134
|
-
self.cardSession = nil
|
|
135
|
-
self.cardSessionInvalidated = true
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@objc public func isSessionRunning() -> Bool {
|
|
139
|
-
return self.cardSession != nil && !self.cardSessionInvalidated
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
@objc public func isHCERunning(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
143
|
-
Task.init {
|
|
144
|
-
guard self.cardSession != nil else {
|
|
145
|
-
return resolve(false)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
let isRunning = await self.cardSession!.isEmulationInProgress
|
|
149
|
-
resolve(isRunning)
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
@objc public func respondAPDU(rapdu: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
154
|
-
Task.init {
|
|
155
|
-
guard let capdu = self.receivedCardAPDU else {
|
|
156
|
-
reject("err_no_apdu_received", "There is no received APDU to respond to.", nil)
|
|
157
|
-
return
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
do {
|
|
161
|
-
try await capdu.respond(response: Data.init(String(rapdu).asHexArray()))
|
|
162
|
-
} catch (let error) {
|
|
163
|
-
reject("err_apdu_respond", "Error trying to respond to APDU. \(error)", nil)
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
resolve(nil)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
@objc public func startHCE(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
172
|
-
Task.init {
|
|
173
|
-
try await self.cardSession?.startEmulation()
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
@objc public func stopHCE(status: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
178
|
-
Task.init {
|
|
179
|
-
let sStatus = String(status)
|
|
180
|
-
|
|
181
|
-
if (sStatus == "success") {
|
|
182
|
-
await self.cardSession?.stopEmulation(status: .success)
|
|
183
|
-
} else {
|
|
184
|
-
await self.cardSession?.stopEmulation(status: .failure)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
resolve(nil)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
1
|
+
import Foundation
|
|
2
|
+
import CoreNFC
|
|
3
|
+
|
|
4
|
+
extension StringProtocol {
|
|
5
|
+
func asHexArray() -> [UInt8] {
|
|
6
|
+
var startIndex = self.startIndex
|
|
7
|
+
return stride(from: 0, to: count, by: 2).compactMap { _ in
|
|
8
|
+
let endIndex = index(startIndex, offsetBy: 2, limitedBy: self.endIndex) ?? self.endIndex
|
|
9
|
+
defer { startIndex = endIndex }
|
|
10
|
+
return UInt8(self[startIndex..<endIndex], radix: 16)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
extension Data {
|
|
16
|
+
struct HexEncodingOptions: OptionSet {
|
|
17
|
+
let rawValue: Int
|
|
18
|
+
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func hexEncodedString(options: HexEncodingOptions = []) -> String {
|
|
22
|
+
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
|
|
23
|
+
return map { String(format: format, $0) }.joined()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@available(iOS 17.4, *)
|
|
28
|
+
@objc public class RTNHCEModuleIOSImpl: NSObject {
|
|
29
|
+
var cardSession: CardSession? = nil
|
|
30
|
+
var cardSessionInvalidated: Bool = true
|
|
31
|
+
var presentmentIntent: NFCPresentmentIntentAssertion? = nil
|
|
32
|
+
var receivedCardAPDU: CardSession.APDU? = nil
|
|
33
|
+
|
|
34
|
+
var emitOnEvent: ((NSString, NSString?) -> ())? = nil
|
|
35
|
+
|
|
36
|
+
@objc public func setEmitOnEvent(emitOnEvent: @escaping (NSString, NSString?) -> ()) {
|
|
37
|
+
self.emitOnEvent = emitOnEvent
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@objc public func acquireExclusiveNFC(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
41
|
+
Task.init {
|
|
42
|
+
if self.presentmentIntent != nil && self.presentmentIntent!.isValid {
|
|
43
|
+
reject("err_already_exists", "NFCPresentmentIntentAssertion already exists and is valid.", nil)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
self.presentmentIntent = nil
|
|
48
|
+
|
|
49
|
+
do {
|
|
50
|
+
self.presentmentIntent = try await NFCPresentmentIntentAssertion.acquire()
|
|
51
|
+
} catch (let error) {
|
|
52
|
+
// TODO exact error details here and in other places as well
|
|
53
|
+
reject("err_create_presentment", "Failed to create NFCPresentmentIntentAssertion: \(error)", nil)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
resolve(nil)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@objc public func isExclusiveNFC() -> Bool {
|
|
62
|
+
return self.presentmentIntent != nil && self.presentmentIntent!.isValid
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@objc public func beginSession(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
66
|
+
Task.init {
|
|
67
|
+
guard NFCReaderSession.readingAvailable else {
|
|
68
|
+
reject("err_nfc_reader_not_available", "Preflight check failed: NFCReaderSession.readingAvailable is false", nil)
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
guard CardSession.isSupported else {
|
|
73
|
+
reject("err_card_session_unsupported", "Preflight check failed: CardSession.isSupported is false", nil)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
guard await CardSession.isEligible else {
|
|
78
|
+
reject("err_card_session_not_eligible", "Preflight check failed: CardSession.isEligible is false", nil)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
guard self.cardSessionInvalidated else {
|
|
83
|
+
reject("err_card_session_exists", "CardSession already exists.", nil)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
do {
|
|
88
|
+
self.cardSession = try await CardSession()
|
|
89
|
+
} catch (let error) {
|
|
90
|
+
reject("err_create_card_session", "Failed to create CardSession(): \(error)", nil)
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
resolve(nil)
|
|
95
|
+
|
|
96
|
+
do {
|
|
97
|
+
for try await event in cardSession!.eventStream {
|
|
98
|
+
switch event {
|
|
99
|
+
case .sessionStarted:
|
|
100
|
+
cardSession!.alertMessage = String(localized: "Communicating with card reader.")
|
|
101
|
+
self.emitOnEvent!("sessionStarted", "")
|
|
102
|
+
|
|
103
|
+
case .readerDetected:
|
|
104
|
+
self.emitOnEvent!("readerDetected", "")
|
|
105
|
+
|
|
106
|
+
case .readerDeselected:
|
|
107
|
+
self.emitOnEvent!("readerDeselected", "")
|
|
108
|
+
|
|
109
|
+
case .received(let cardAPDU):
|
|
110
|
+
self.receivedCardAPDU = cardAPDU
|
|
111
|
+
self.emitOnEvent!("received", NSString(string:cardAPDU.payload.hexEncodedString()))
|
|
112
|
+
|
|
113
|
+
case .sessionInvalidated(let reason):
|
|
114
|
+
self.cardSessionInvalidated = true
|
|
115
|
+
self.emitOnEvent!("sessionInvalidated", "\(reason)" as NSString)
|
|
116
|
+
self.cardSession = nil
|
|
117
|
+
|
|
118
|
+
@unknown default:
|
|
119
|
+
fatalError("Received unknown CardSession event.")
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch (let err) {
|
|
123
|
+
self.emitOnEvent!("internalError", "\(err)" as NSString)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@objc public func setSessionAlertMessage(message: NSString) {
|
|
129
|
+
self.cardSession?.alertMessage = String(message)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@objc public func invalidateSession() {
|
|
133
|
+
self.cardSession?.invalidate()
|
|
134
|
+
self.cardSession = nil
|
|
135
|
+
self.cardSessionInvalidated = true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@objc public func isSessionRunning() -> Bool {
|
|
139
|
+
return self.cardSession != nil && !self.cardSessionInvalidated
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@objc public func isHCERunning(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
143
|
+
Task.init {
|
|
144
|
+
guard self.cardSession != nil else {
|
|
145
|
+
return resolve(false)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let isRunning = await self.cardSession!.isEmulationInProgress
|
|
149
|
+
resolve(isRunning)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@objc public func respondAPDU(rapdu: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
154
|
+
Task.init {
|
|
155
|
+
guard let capdu = self.receivedCardAPDU else {
|
|
156
|
+
reject("err_no_apdu_received", "There is no received APDU to respond to.", nil)
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
do {
|
|
161
|
+
try await capdu.respond(response: Data.init(String(rapdu).asHexArray()))
|
|
162
|
+
} catch (let error) {
|
|
163
|
+
reject("err_apdu_respond", "Error trying to respond to APDU. \(error)", nil)
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
resolve(nil)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@objc public func startHCE(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
172
|
+
Task.init {
|
|
173
|
+
try await self.cardSession?.startEmulation()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@objc public func stopHCE(status: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
178
|
+
Task.init {
|
|
179
|
+
let sStatus = String(status)
|
|
180
|
+
|
|
181
|
+
if (sStatus == "success") {
|
|
182
|
+
await self.cardSession?.stopEmulation(status: .success)
|
|
183
|
+
} else {
|
|
184
|
+
await self.cardSession?.stopEmulation(status: .failure)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
resolve(nil)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|