@padosoft/react-native-ecr17 0.0.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/Ecr17.podspec +39 -0
- package/README.md +348 -0
- package/android/CMakeLists.txt +41 -0
- package/android/build.gradle +149 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +9 -0
- package/android/src/main/java/com/margelo/nitro/ecr17/HybridEcr17Transport.kt +233 -0
- package/android/src/main/java/com/padosoft/ecr17/Ecr17Package.kt +30 -0
- package/cpp/Ecr17.cpp +1 -0
- package/cpp/Ecr17.hpp +2 -0
- package/cpp/Ecr17Client/HybridEcr17Client.cpp +598 -0
- package/cpp/Ecr17Client/HybridEcr17Client.hpp +85 -0
- package/cpp/Ecr17Protocol/Ecr17Protocol.cpp +277 -0
- package/cpp/Ecr17Protocol/Ecr17Protocol.hpp +103 -0
- package/cpp/Ecr17Response/Ecr17Response.cpp +155 -0
- package/cpp/Ecr17Response/Ecr17Response.hpp +113 -0
- package/cpp/Lcr/Lcr.cpp +42 -0
- package/cpp/Lcr/Lcr.hpp +22 -0
- package/cpp/PacketCodec/PacketCodec.cpp +146 -0
- package/cpp/PacketCodec/PacketCodec.hpp +48 -0
- package/cpp/Session/Ecr17Session.cpp +260 -0
- package/cpp/Session/Ecr17Session.hpp +97 -0
- package/cpp/Session/RetryPolicy.hpp +23 -0
- package/cpp/Transport/FakeTransport.hpp +95 -0
- package/cpp/Transport/NativeTransportAdapter.cpp +42 -0
- package/cpp/Transport/NativeTransportAdapter.hpp +32 -0
- package/cpp/Transport/Transport.hpp +31 -0
- package/cpp/tests/CMakeLists.txt +55 -0
- package/cpp/tests/PosixTcpTransport.hpp +105 -0
- package/cpp/tests/stubs/LrcMode.hpp +25 -0
- package/cpp/tests/test_flows.cpp +148 -0
- package/cpp/tests/test_integration_terminal.cpp +72 -0
- package/cpp/tests/test_lrc.cpp +66 -0
- package/cpp/tests/test_packet_codec.cpp +164 -0
- package/cpp/tests/test_protocol.cpp +102 -0
- package/cpp/tests/test_protocol_commands.cpp +190 -0
- package/cpp/tests/test_response.cpp +164 -0
- package/cpp/tests/test_retry_policy.cpp +28 -0
- package/cpp/tests/test_session.cpp +262 -0
- package/ios/Bridge.h +1 -0
- package/ios/HybridEcr17Transport.swift +103 -0
- package/nitro.json +30 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/Ecr17+autolinking.cmake +82 -0
- package/nitrogen/generated/android/Ecr17+autolinking.gradle +27 -0
- package/nitrogen/generated/android/Ecr17OnLoad.cpp +68 -0
- package/nitrogen/generated/android/Ecr17OnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__shared_ptr_ArrayBuffer_.hpp +77 -0
- package/nitrogen/generated/android/c++/JHybridEcr17TransportSpec.cpp +93 -0
- package/nitrogen/generated/android/c++/JHybridEcr17TransportSpec.hpp +68 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/Ecr17OnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/Func_void.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/Func_void_std__shared_ptr_ArrayBuffer_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/HybridEcr17TransportSpec.kt +86 -0
- package/nitrogen/generated/ios/Ecr17+autolinking.rb +62 -0
- package/nitrogen/generated/ios/Ecr17-Swift-Cxx-Bridge.cpp +57 -0
- package/nitrogen/generated/ios/Ecr17-Swift-Cxx-Bridge.hpp +154 -0
- package/nitrogen/generated/ios/Ecr17-Swift-Cxx-Umbrella.hpp +47 -0
- package/nitrogen/generated/ios/Ecr17Autolinking.mm +43 -0
- package/nitrogen/generated/ios/Ecr17Autolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridEcr17TransportSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridEcr17TransportSpecSwift.hpp +119 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_ArrayBuffer_.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridEcr17TransportSpec.swift +60 -0
- package/nitrogen/generated/ios/swift/HybridEcr17TransportSpec_cxx.swift +211 -0
- package/nitrogen/generated/shared/c++/CardType.hpp +84 -0
- package/nitrogen/generated/shared/c++/CardVerificationRequest.hpp +97 -0
- package/nitrogen/generated/shared/c++/CardVerificationResult.hpp +136 -0
- package/nitrogen/generated/shared/c++/CloseSessionResult.hpp +106 -0
- package/nitrogen/generated/shared/c++/ConnectionState.hpp +80 -0
- package/nitrogen/generated/shared/c++/CurrencyExchange.hpp +100 -0
- package/nitrogen/generated/shared/c++/Ecr17Config.hpp +138 -0
- package/nitrogen/generated/shared/c++/HybridEcr17ClientSpec.cpp +42 -0
- package/nitrogen/generated/shared/c++/HybridEcr17ClientSpec.hpp +138 -0
- package/nitrogen/generated/shared/c++/HybridEcr17TransportSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridEcr17TransportSpec.hpp +70 -0
- package/nitrogen/generated/shared/c++/IncrementalAuthRequest.hpp +96 -0
- package/nitrogen/generated/shared/c++/LrcMode.hpp +84 -0
- package/nitrogen/generated/shared/c++/PaymentCardType.hpp +84 -0
- package/nitrogen/generated/shared/c++/PaymentRequest.hpp +109 -0
- package/nitrogen/generated/shared/c++/PaymentResult.hpp +139 -0
- package/nitrogen/generated/shared/c++/PosStatusResponse.hpp +96 -0
- package/nitrogen/generated/shared/c++/PreAuthClosureRequest.hpp +96 -0
- package/nitrogen/generated/shared/c++/PreAuthRequest.hpp +109 -0
- package/nitrogen/generated/shared/c++/PreAuthResult.hpp +144 -0
- package/nitrogen/generated/shared/c++/ProgressEvent.hpp +83 -0
- package/nitrogen/generated/shared/c++/ReceiptLine.hpp +83 -0
- package/nitrogen/generated/shared/c++/ReversalRequest.hpp +88 -0
- package/nitrogen/generated/shared/c++/ReversalResult.hpp +132 -0
- package/nitrogen/generated/shared/c++/TokenizationRequest.hpp +89 -0
- package/nitrogen/generated/shared/c++/TokenizationService.hpp +76 -0
- package/nitrogen/generated/shared/c++/TotalsResult.hpp +93 -0
- package/nitrogen/generated/shared/c++/TransactionEntryMode.hpp +92 -0
- package/nitrogen/generated/shared/c++/TransactionOutcome.hpp +88 -0
- package/nitrogen/generated/shared/c++/VasResult.hpp +96 -0
- package/package.json +102 -0
- package/react-native.config.js +18 -0
- package/src/index.ts +4 -0
- package/src/specs/client.nitro.ts +102 -0
- package/src/specs/transport.nitro.ts +25 -0
- package/src/types/client.ts +196 -0
- package/src/utils/client.ts +10 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// Orchestration tests for Ecr17Session driven by the in-memory FakeTransport:
|
|
2
|
+
// ACK/NAK handshake, retransmission, timeouts, LRC-failure NAK, and progress /
|
|
3
|
+
// receipt forwarding. Timeouts are tiny so the suite stays fast.
|
|
4
|
+
|
|
5
|
+
#include <gtest/gtest.h>
|
|
6
|
+
|
|
7
|
+
#include <cstdint>
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
#include "PacketCodec/PacketCodec.hpp"
|
|
12
|
+
#include "Session/Ecr17Session.hpp"
|
|
13
|
+
#include "Transport/FakeTransport.hpp"
|
|
14
|
+
|
|
15
|
+
using namespace margelo::nitro::ecr17;
|
|
16
|
+
|
|
17
|
+
namespace {
|
|
18
|
+
|
|
19
|
+
SessionConfig fastConfig() {
|
|
20
|
+
SessionConfig c;
|
|
21
|
+
c.lrcMode = LrcMode::STD;
|
|
22
|
+
c.ackTimeoutMs = 40;
|
|
23
|
+
c.responseTimeoutMs = 40;
|
|
24
|
+
c.retryCount = 2;
|
|
25
|
+
c.retryDelayMs = 1;
|
|
26
|
+
return c;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void append(std::vector<uint8_t>& a, const std::vector<uint8_t>& b) {
|
|
30
|
+
a.insert(a.end(), b.begin(), b.end());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
std::vector<uint8_t> progressFrame(const std::string& msg20) {
|
|
34
|
+
std::vector<uint8_t> f{0x01}; // SOH
|
|
35
|
+
f.insert(f.end(), msg20.begin(), msg20.end());
|
|
36
|
+
f.push_back(0x04); // EOT
|
|
37
|
+
return f;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const std::string kResultPayload = "123456780E0000DATA"; // code 'E' at pos 10 -> result
|
|
41
|
+
const std::string kReceiptPayload = "123456780SLINE 1"; // code 'S' at pos 10 -> receipt
|
|
42
|
+
|
|
43
|
+
} // namespace
|
|
44
|
+
|
|
45
|
+
TEST(Session, HappyPathReturnsResultAndAcks) {
|
|
46
|
+
FakeTransport t;
|
|
47
|
+
PacketCodec codec(LrcMode::STD);
|
|
48
|
+
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
49
|
+
append(response, codec.encodeApplication(kResultPayload));
|
|
50
|
+
t.enqueueResponse(response);
|
|
51
|
+
|
|
52
|
+
Ecr17Session session(t, fastConfig());
|
|
53
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
54
|
+
EXPECT_EQ(result.type, PacketType::APPLICATION);
|
|
55
|
+
EXPECT_TRUE(result.validLrc);
|
|
56
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
57
|
+
EXPECT_EQ(t.applicationRequestCount(), 1u);
|
|
58
|
+
|
|
59
|
+
// The session must have ACKed the received result frame.
|
|
60
|
+
bool sentAck = false;
|
|
61
|
+
for (const auto& f : t.sentFrames()) {
|
|
62
|
+
if (!f.empty() && f.front() == PacketCodec::ACK) sentAck = true;
|
|
63
|
+
}
|
|
64
|
+
EXPECT_TRUE(sentAck);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
TEST(Session, NakTriggersRetransmitThenSucceeds) {
|
|
68
|
+
FakeTransport t;
|
|
69
|
+
PacketCodec codec(LrcMode::STD);
|
|
70
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::NAK)); // reply to attempt 1
|
|
71
|
+
std::vector<uint8_t> ok = codec.encodeControl(PacketCodec::ACK);
|
|
72
|
+
append(ok, codec.encodeApplication(kResultPayload));
|
|
73
|
+
t.enqueueResponse(ok); // reply to retransmit
|
|
74
|
+
|
|
75
|
+
Ecr17Session session(t, fastConfig());
|
|
76
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
77
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
78
|
+
EXPECT_EQ(t.applicationRequestCount(), 2u); // initial + 1 retransmit
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
TEST(Session, AckTimeoutExhaustsRetriesThenThrows) {
|
|
82
|
+
FakeTransport t; // no responses queued
|
|
83
|
+
Ecr17Session session(t, fastConfig());
|
|
84
|
+
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
85
|
+
// initial + retryCount retransmissions
|
|
86
|
+
EXPECT_EQ(t.applicationRequestCount(), 3u);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
TEST(Session, BadLrcResponseSendsNak) {
|
|
90
|
+
FakeTransport t;
|
|
91
|
+
PacketCodec codec(LrcMode::STD);
|
|
92
|
+
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
93
|
+
auto bad = codec.encodeApplication(kResultPayload);
|
|
94
|
+
bad.back() ^= 0xFF; // corrupt LRC
|
|
95
|
+
append(response, bad);
|
|
96
|
+
t.enqueueResponse(response);
|
|
97
|
+
|
|
98
|
+
Ecr17Session session(t, fastConfig());
|
|
99
|
+
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error); // no valid result
|
|
100
|
+
|
|
101
|
+
bool sentNak = false;
|
|
102
|
+
for (const auto& f : t.sentFrames()) {
|
|
103
|
+
if (!f.empty() && f.front() == PacketCodec::NAK) sentNak = true;
|
|
104
|
+
}
|
|
105
|
+
EXPECT_TRUE(sentNak);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
TEST(Session, ProgressMessagesForwarded) {
|
|
109
|
+
FakeTransport t;
|
|
110
|
+
PacketCodec codec(LrcMode::STD);
|
|
111
|
+
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
112
|
+
append(response, progressFrame("ATTENDERE PREGO "));
|
|
113
|
+
append(response, codec.encodeApplication(kResultPayload));
|
|
114
|
+
t.enqueueResponse(response);
|
|
115
|
+
|
|
116
|
+
std::vector<std::string> progress;
|
|
117
|
+
Ecr17Session session(t, fastConfig());
|
|
118
|
+
session.setOnProgress([&](const std::string& m) { progress.push_back(m); });
|
|
119
|
+
|
|
120
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
121
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
122
|
+
ASSERT_EQ(progress.size(), 1u);
|
|
123
|
+
EXPECT_EQ(progress[0], "ATTENDERE PREGO ");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
TEST(Session, ReceiptLinesForwardedBeforeResult) {
|
|
127
|
+
FakeTransport t;
|
|
128
|
+
PacketCodec codec(LrcMode::STD);
|
|
129
|
+
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
130
|
+
append(response, codec.encodeApplication(kReceiptPayload));
|
|
131
|
+
append(response, codec.encodeApplication(kResultPayload));
|
|
132
|
+
t.enqueueResponse(response);
|
|
133
|
+
|
|
134
|
+
std::vector<std::string> receipts;
|
|
135
|
+
Ecr17Session session(t, fastConfig());
|
|
136
|
+
session.setOnReceiptLine([&](const std::string& l) { receipts.push_back(l); });
|
|
137
|
+
|
|
138
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
139
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
140
|
+
ASSERT_EQ(receipts.size(), 1u);
|
|
141
|
+
EXPECT_EQ(receipts[0], kReceiptPayload);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Regression: some terminals send the application RESULT before (or instead of)
|
|
145
|
+
// the physical ACK. The handshake must not discard that frame — losing the
|
|
146
|
+
// result of a completed financial transaction is the worst failure mode.
|
|
147
|
+
TEST(Session, ResultBeforeAckIsNotLost) {
|
|
148
|
+
FakeTransport t;
|
|
149
|
+
PacketCodec codec(LrcMode::STD);
|
|
150
|
+
t.enqueueResponse(codec.encodeApplication(kResultPayload)); // result, no leading ACK
|
|
151
|
+
|
|
152
|
+
Ecr17Session session(t, fastConfig());
|
|
153
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
154
|
+
EXPECT_EQ(result.type, PacketType::APPLICATION);
|
|
155
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
156
|
+
EXPECT_EQ(t.applicationRequestCount(), 1u); // no spurious retransmit
|
|
157
|
+
|
|
158
|
+
// The accepted result must still be ACKed back to the terminal.
|
|
159
|
+
bool sentAck = false;
|
|
160
|
+
for (const auto& f : t.sentFrames()) {
|
|
161
|
+
if (!f.empty() && f.front() == PacketCodec::ACK) sentAck = true;
|
|
162
|
+
}
|
|
163
|
+
EXPECT_TRUE(sentAck);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
TEST(Session, ResponseTimeoutAfterAckThrows) {
|
|
167
|
+
FakeTransport t;
|
|
168
|
+
PacketCodec codec(LrcMode::STD);
|
|
169
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK)); // ACK only, no result
|
|
170
|
+
|
|
171
|
+
Ecr17Session session(t, fastConfig());
|
|
172
|
+
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
TEST(Session, DisconnectDuringExchangeThrows) {
|
|
176
|
+
FakeTransport t;
|
|
177
|
+
t.disconnectOnNextRequest();
|
|
178
|
+
Ecr17Session session(t, fastConfig());
|
|
179
|
+
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Scenario: the connection drops mid-exchange, then the client reconnects and
|
|
183
|
+
// retries (the auto-reconnect path for safe commands). The session must recover
|
|
184
|
+
// — it must NOT stay stuck in the disconnected state after a drop.
|
|
185
|
+
TEST(Session, RecoversAndSucceedsAfterReconnect) {
|
|
186
|
+
FakeTransport t;
|
|
187
|
+
t.disconnectOnNextRequest();
|
|
188
|
+
Ecr17Session session(t, fastConfig());
|
|
189
|
+
|
|
190
|
+
// First attempt drops.
|
|
191
|
+
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
192
|
+
|
|
193
|
+
// Client reconnects the transport and the next transaction must work.
|
|
194
|
+
t.rearm();
|
|
195
|
+
PacketCodec codec(LrcMode::STD);
|
|
196
|
+
std::vector<uint8_t> ok = codec.encodeControl(PacketCodec::ACK);
|
|
197
|
+
append(ok, codec.encodeApplication(kResultPayload));
|
|
198
|
+
t.enqueueResponse(ok);
|
|
199
|
+
|
|
200
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
201
|
+
EXPECT_EQ(result.type, PacketType::APPLICATION);
|
|
202
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
TEST(Session, SendAckOnlyReturnsOnAck) {
|
|
206
|
+
FakeTransport t;
|
|
207
|
+
PacketCodec codec(LrcMode::STD);
|
|
208
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK)); // ACK, no app result
|
|
209
|
+
Ecr17Session session(t, fastConfig());
|
|
210
|
+
EXPECT_NO_THROW(session.sendAckOnly("123456780E1"));
|
|
211
|
+
EXPECT_EQ(t.applicationRequestCount(), 1u);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
TEST(Session, SendAckOnlyRetransmitsOnNak) {
|
|
215
|
+
FakeTransport t;
|
|
216
|
+
PacketCodec codec(LrcMode::STD);
|
|
217
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::NAK));
|
|
218
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK));
|
|
219
|
+
Ecr17Session session(t, fastConfig());
|
|
220
|
+
EXPECT_NO_THROW(session.sendAckOnly("123456780E0"));
|
|
221
|
+
EXPECT_EQ(t.applicationRequestCount(), 2u);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
TEST(Session, SendAckOnlyTimesOut) {
|
|
225
|
+
FakeTransport t; // no ACK
|
|
226
|
+
Ecr17Session session(t, fastConfig());
|
|
227
|
+
EXPECT_THROW(session.sendAckOnly("123456780E1"), std::runtime_error);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
TEST(Session, ExchangeWithAdditionalDataSendsTwoRequests) {
|
|
231
|
+
FakeTransport t;
|
|
232
|
+
PacketCodec codec(LrcMode::STD);
|
|
233
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK)); // ACK for the main 'P'
|
|
234
|
+
std::vector<uint8_t> ok = codec.encodeControl(PacketCodec::ACK);
|
|
235
|
+
append(ok, codec.encodeApplication(kResultPayload));
|
|
236
|
+
t.enqueueResponse(ok); // ACK for 'U' + the result
|
|
237
|
+
|
|
238
|
+
Ecr17Session session(t, fastConfig());
|
|
239
|
+
DecodedPacket result = session.exchangeWithAdditionalData("123456780P...", "123456780U...");
|
|
240
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
241
|
+
EXPECT_EQ(t.applicationRequestCount(), 2u); // P + U
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
TEST(Session, ReceiptDrainForwardsReceiptsAfterResult) {
|
|
245
|
+
FakeTransport t;
|
|
246
|
+
PacketCodec codec(LrcMode::STD);
|
|
247
|
+
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
248
|
+
append(response, codec.encodeApplication(kResultPayload)); // result first
|
|
249
|
+
append(response, codec.encodeApplication(kReceiptPayload)); // then a receipt line
|
|
250
|
+
t.enqueueResponse(response);
|
|
251
|
+
|
|
252
|
+
SessionConfig cfg = fastConfig();
|
|
253
|
+
cfg.receiptDrainMs = 30; // drain receipts that follow the result
|
|
254
|
+
std::vector<std::string> receipts;
|
|
255
|
+
Ecr17Session session(t, cfg);
|
|
256
|
+
session.setOnReceiptLine([&](const std::string& l) { receipts.push_back(l); });
|
|
257
|
+
|
|
258
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
259
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
260
|
+
ASSERT_EQ(receipts.size(), 1u);
|
|
261
|
+
EXPECT_EQ(receipts[0], kReceiptPayload);
|
|
262
|
+
}
|
package/ios/Bridge.h
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#pragma once
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Network
|
|
3
|
+
import NitroModules
|
|
4
|
+
|
|
5
|
+
/// iOS (Swift) implementation of the ECR17 LAN transport, using Network.framework.
|
|
6
|
+
///
|
|
7
|
+
/// NOTE: there is no iOS CI runner in this project yet, so this file is
|
|
8
|
+
/// best-effort and must be verified by an actual iOS build (the ArrayBuffer <->
|
|
9
|
+
/// Data bridging in particular). The Android (Kotlin) transport is the
|
|
10
|
+
/// CI-verified reference implementation; this mirrors its behaviour.
|
|
11
|
+
final class HybridEcr17Transport: HybridEcr17TransportSpec {
|
|
12
|
+
private var connection: NWConnection?
|
|
13
|
+
private let queue = DispatchQueue(label: "com.ecr17.transport")
|
|
14
|
+
private var onData: ((ArrayBuffer) -> Void)?
|
|
15
|
+
private var onDisconnect: (() -> Void)?
|
|
16
|
+
|
|
17
|
+
func connect(host: String, port: Double, timeoutMs: Double) throws -> Promise<Void> {
|
|
18
|
+
let promise = Promise<Void>()
|
|
19
|
+
let endpointPort =
|
|
20
|
+
NWEndpoint.Port(rawValue: UInt16(port)) ?? NWEndpoint.Port(integerLiteral: 10000)
|
|
21
|
+
let conn = NWConnection(host: NWEndpoint.Host(host), port: endpointPort, using: .tcp)
|
|
22
|
+
connection = conn
|
|
23
|
+
|
|
24
|
+
var settled = false
|
|
25
|
+
conn.stateUpdateHandler = { [weak self] state in
|
|
26
|
+
switch state {
|
|
27
|
+
case .ready:
|
|
28
|
+
if !settled {
|
|
29
|
+
settled = true
|
|
30
|
+
promise.resolve(withResult: ())
|
|
31
|
+
}
|
|
32
|
+
self?.receiveLoop(conn)
|
|
33
|
+
case .failed(let error):
|
|
34
|
+
if !settled {
|
|
35
|
+
settled = true
|
|
36
|
+
promise.reject(withError: error)
|
|
37
|
+
}
|
|
38
|
+
self?.onDisconnect?()
|
|
39
|
+
case .cancelled:
|
|
40
|
+
self?.onDisconnect?()
|
|
41
|
+
default:
|
|
42
|
+
break
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
conn.start(queue: queue)
|
|
46
|
+
return promise
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private func receiveLoop(_ conn: NWConnection) {
|
|
50
|
+
conn.receive(minimumIncompleteLength: 1, maximumLength: 4096) {
|
|
51
|
+
[weak self] data, _, isComplete, error in
|
|
52
|
+
guard let self = self else { return }
|
|
53
|
+
if let data = data, !data.isEmpty {
|
|
54
|
+
self.onData?(HybridEcr17Transport.toArrayBuffer(data))
|
|
55
|
+
}
|
|
56
|
+
if error == nil && !isComplete {
|
|
57
|
+
self.receiveLoop(conn)
|
|
58
|
+
} else {
|
|
59
|
+
self.onDisconnect?()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func disconnect() throws {
|
|
65
|
+
connection?.cancel()
|
|
66
|
+
connection = nil
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func isConnected() throws -> Bool {
|
|
70
|
+
// Used by the C++ client as a PRE-SEND liveness check so a command starts on
|
|
71
|
+
// a verified-live socket (the Android transport probes the socket here). With
|
|
72
|
+
// Network.framework the connection transitions to .failed/.cancelled when the
|
|
73
|
+
// peer closes, so `.ready` is the closest equivalent. NOTE: state updates are
|
|
74
|
+
// delivered asynchronously on `queue`, so a peer close observed between
|
|
75
|
+
// transactions may take a moment to flip the state — a small residual race
|
|
76
|
+
// not present on Android. Best-effort (no iOS CI); verify on a real build.
|
|
77
|
+
return connection?.state == .ready
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func send(bytes: ArrayBuffer) throws {
|
|
81
|
+
let data = HybridEcr17Transport.toData(bytes)
|
|
82
|
+
connection?.send(content: data, completion: .contentProcessed { _ in })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func setOnData(callback: @escaping (_ bytes: ArrayBuffer) -> Void) throws {
|
|
86
|
+
onData = callback
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func setOnDisconnect(callback: @escaping () -> Void) throws {
|
|
90
|
+
onDisconnect = callback
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// MARK: - ArrayBuffer <-> Data (verify on a real iOS build)
|
|
94
|
+
private static func toData(_ buffer: ArrayBuffer) -> Data {
|
|
95
|
+
return Data(bytes: buffer.data, count: buffer.size)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private static func toArrayBuffer(_ data: Data) -> ArrayBuffer {
|
|
99
|
+
let buffer = ArrayBuffer.allocate(size: data.count)
|
|
100
|
+
data.copyBytes(to: buffer.data, count: data.count)
|
|
101
|
+
return buffer
|
|
102
|
+
}
|
|
103
|
+
}
|
package/nitro.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://nitro.margelo.com/nitro.schema.json",
|
|
3
|
+
"cxxNamespace": ["ecr17"],
|
|
4
|
+
"ios": {
|
|
5
|
+
"iosModuleName": "Ecr17"
|
|
6
|
+
},
|
|
7
|
+
"android": {
|
|
8
|
+
"androidNamespace": ["ecr17"],
|
|
9
|
+
"androidCxxLibName": "Ecr17"
|
|
10
|
+
},
|
|
11
|
+
"autolinking": {
|
|
12
|
+
"Ecr17Client": {
|
|
13
|
+
"all": {
|
|
14
|
+
"language": "c++",
|
|
15
|
+
"implementationClassName": "HybridEcr17Client"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"Ecr17Transport": {
|
|
19
|
+
"ios": {
|
|
20
|
+
"language": "swift",
|
|
21
|
+
"implementationClassName": "HybridEcr17Transport"
|
|
22
|
+
},
|
|
23
|
+
"android": {
|
|
24
|
+
"language": "kotlin",
|
|
25
|
+
"implementationClassName": "HybridEcr17Transport"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"ignorePaths": ["**/node_modules"]
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
** linguist-generated=true
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Ecr17+autolinking.cmake
|
|
3
|
+
# This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
# https://github.com/mrousavy/nitro
|
|
5
|
+
# Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
# This is a CMake file that adds all files generated by Nitrogen
|
|
9
|
+
# to the current CMake project.
|
|
10
|
+
#
|
|
11
|
+
# To use it, add this to your CMakeLists.txt:
|
|
12
|
+
# ```cmake
|
|
13
|
+
# include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/Ecr17+autolinking.cmake)
|
|
14
|
+
# ```
|
|
15
|
+
|
|
16
|
+
# Define a flag to check if we are building properly
|
|
17
|
+
add_definitions(-DBUILDING_ECR17_WITH_GENERATED_CMAKE_PROJECT)
|
|
18
|
+
|
|
19
|
+
# Enable Raw Props parsing in react-native (for Nitro Views)
|
|
20
|
+
add_definitions(-DRN_SERIALIZABLE_STATE)
|
|
21
|
+
|
|
22
|
+
# Add all headers that were generated by Nitrogen
|
|
23
|
+
include_directories(
|
|
24
|
+
"../nitrogen/generated/shared/c++"
|
|
25
|
+
"../nitrogen/generated/android/c++"
|
|
26
|
+
"../nitrogen/generated/android/"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Add all .cpp sources that were generated by Nitrogen
|
|
30
|
+
target_sources(
|
|
31
|
+
# CMake project name (Android C++ library name)
|
|
32
|
+
Ecr17 PRIVATE
|
|
33
|
+
# Autolinking Setup
|
|
34
|
+
../nitrogen/generated/android/Ecr17OnLoad.cpp
|
|
35
|
+
# Shared Nitrogen C++ sources
|
|
36
|
+
../nitrogen/generated/shared/c++/HybridEcr17ClientSpec.cpp
|
|
37
|
+
../nitrogen/generated/shared/c++/HybridEcr17TransportSpec.cpp
|
|
38
|
+
# Android-specific Nitrogen C++ sources
|
|
39
|
+
../nitrogen/generated/android/c++/JHybridEcr17TransportSpec.cpp
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake
|
|
43
|
+
# Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake
|
|
44
|
+
target_compile_definitions(
|
|
45
|
+
Ecr17 PRIVATE
|
|
46
|
+
-DFOLLY_NO_CONFIG=1
|
|
47
|
+
-DFOLLY_HAVE_CLOCK_GETTIME=1
|
|
48
|
+
-DFOLLY_USE_LIBCPP=1
|
|
49
|
+
-DFOLLY_CFG_NO_COROUTINES=1
|
|
50
|
+
-DFOLLY_MOBILE=1
|
|
51
|
+
-DFOLLY_HAVE_RECVMMSG=1
|
|
52
|
+
-DFOLLY_HAVE_PTHREAD=1
|
|
53
|
+
# Once we target android-23 above, we can comment
|
|
54
|
+
# the following line. NDK uses GNU style stderror_r() after API 23.
|
|
55
|
+
-DFOLLY_HAVE_XSI_STRERROR_R=1
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Add all libraries required by the generated specs
|
|
59
|
+
find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++
|
|
60
|
+
find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule)
|
|
61
|
+
find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library
|
|
62
|
+
|
|
63
|
+
# Link all libraries together
|
|
64
|
+
target_link_libraries(
|
|
65
|
+
Ecr17
|
|
66
|
+
fbjni::fbjni # <-- Facebook C++ JNI helpers
|
|
67
|
+
ReactAndroid::jsi # <-- RN: JSI
|
|
68
|
+
react-native-nitro-modules::NitroModules # <-- NitroModules Core :)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Link react-native (different prefab between RN 0.75 and RN 0.76)
|
|
72
|
+
if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
|
|
73
|
+
target_link_libraries(
|
|
74
|
+
Ecr17
|
|
75
|
+
ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab
|
|
76
|
+
)
|
|
77
|
+
else()
|
|
78
|
+
target_link_libraries(
|
|
79
|
+
Ecr17
|
|
80
|
+
ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core
|
|
81
|
+
)
|
|
82
|
+
endif()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// Ecr17+autolinking.gradle
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
/// This is a Gradle file that adds all files generated by Nitrogen
|
|
9
|
+
/// to the current Gradle project.
|
|
10
|
+
///
|
|
11
|
+
/// To use it, add this to your build.gradle:
|
|
12
|
+
/// ```gradle
|
|
13
|
+
/// apply from: '../nitrogen/generated/android/Ecr17+autolinking.gradle'
|
|
14
|
+
/// ```
|
|
15
|
+
|
|
16
|
+
logger.warn("[NitroModules] 🔥 Ecr17 is boosted by nitro!")
|
|
17
|
+
|
|
18
|
+
android {
|
|
19
|
+
sourceSets {
|
|
20
|
+
main {
|
|
21
|
+
java.srcDirs += [
|
|
22
|
+
// Nitrogen files
|
|
23
|
+
"${project.projectDir}/../nitrogen/generated/android/kotlin"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// Ecr17OnLoad.cpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#ifndef BUILDING_ECR17_WITH_GENERATED_CMAKE_PROJECT
|
|
9
|
+
#error Ecr17OnLoad.cpp is not being built with the autogenerated CMakeLists.txt project. Is a different CMakeLists.txt building this?
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
#include "Ecr17OnLoad.hpp"
|
|
13
|
+
|
|
14
|
+
#include <jni.h>
|
|
15
|
+
#include <fbjni/fbjni.h>
|
|
16
|
+
#include <NitroModules/HybridObjectRegistry.hpp>
|
|
17
|
+
|
|
18
|
+
#include "JHybridEcr17TransportSpec.hpp"
|
|
19
|
+
#include "JFunc_void_std__shared_ptr_ArrayBuffer_.hpp"
|
|
20
|
+
#include "JFunc_void.hpp"
|
|
21
|
+
#include "HybridEcr17Client.hpp"
|
|
22
|
+
#include <NitroModules/DefaultConstructableObject.hpp>
|
|
23
|
+
|
|
24
|
+
namespace margelo::nitro::ecr17 {
|
|
25
|
+
|
|
26
|
+
int initialize(JavaVM* vm) {
|
|
27
|
+
return facebook::jni::initialize(vm, []() {
|
|
28
|
+
::margelo::nitro::ecr17::registerAllNatives();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
struct JHybridEcr17TransportSpecImpl: public jni::JavaClass<JHybridEcr17TransportSpecImpl, JHybridEcr17TransportSpec::JavaPart> {
|
|
33
|
+
static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/ecr17/HybridEcr17Transport;";
|
|
34
|
+
static std::shared_ptr<JHybridEcr17TransportSpec> create() {
|
|
35
|
+
static const auto constructorFn = javaClassStatic()->getConstructor<JHybridEcr17TransportSpecImpl::javaobject()>();
|
|
36
|
+
jni::local_ref<JHybridEcr17TransportSpec::JavaPart> javaPart = javaClassStatic()->newObject(constructorFn);
|
|
37
|
+
return javaPart->getJHybridEcr17TransportSpec();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
void registerAllNatives() {
|
|
42
|
+
using namespace margelo::nitro;
|
|
43
|
+
using namespace margelo::nitro::ecr17;
|
|
44
|
+
|
|
45
|
+
// Register native JNI methods
|
|
46
|
+
margelo::nitro::ecr17::JHybridEcr17TransportSpec::CxxPart::registerNatives();
|
|
47
|
+
margelo::nitro::ecr17::JFunc_void_std__shared_ptr_ArrayBuffer__cxx::registerNatives();
|
|
48
|
+
margelo::nitro::ecr17::JFunc_void_cxx::registerNatives();
|
|
49
|
+
|
|
50
|
+
// Register Nitro Hybrid Objects
|
|
51
|
+
HybridObjectRegistry::registerHybridObjectConstructor(
|
|
52
|
+
"Ecr17Client",
|
|
53
|
+
[]() -> std::shared_ptr<HybridObject> {
|
|
54
|
+
static_assert(std::is_default_constructible_v<HybridEcr17Client>,
|
|
55
|
+
"The HybridObject \"HybridEcr17Client\" is not default-constructible! "
|
|
56
|
+
"Create a public constructor that takes zero arguments to be able to autolink this HybridObject.");
|
|
57
|
+
return std::make_shared<HybridEcr17Client>();
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
HybridObjectRegistry::registerHybridObjectConstructor(
|
|
61
|
+
"Ecr17Transport",
|
|
62
|
+
[]() -> std::shared_ptr<HybridObject> {
|
|
63
|
+
return JHybridEcr17TransportSpecImpl::create();
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
} // namespace margelo::nitro::ecr17
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// Ecr17OnLoad.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#include <jni.h>
|
|
9
|
+
#include <functional>
|
|
10
|
+
#include <NitroModules/NitroDefines.hpp>
|
|
11
|
+
|
|
12
|
+
namespace margelo::nitro::ecr17 {
|
|
13
|
+
|
|
14
|
+
[[deprecated("Use registerNatives() instead.")]]
|
|
15
|
+
int initialize(JavaVM* vm);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register the native (C++) part of Ecr17, and autolinks all Hybrid Objects.
|
|
19
|
+
* Call this in your `JNI_OnLoad` function (probably inside `cpp-adapter.cpp`),
|
|
20
|
+
* inside a `facebook::jni::initialize(vm, ...)` call.
|
|
21
|
+
* Example:
|
|
22
|
+
* ```cpp (cpp-adapter.cpp)
|
|
23
|
+
* JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
|
|
24
|
+
* return facebook::jni::initialize(vm, []() {
|
|
25
|
+
* // register all Ecr17 HybridObjects
|
|
26
|
+
* margelo::nitro::ecr17::registerNatives();
|
|
27
|
+
* // any other custom registrations go here.
|
|
28
|
+
* });
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
void registerAllNatives();
|
|
33
|
+
|
|
34
|
+
} // namespace margelo::nitro::ecr17
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// JFunc_void.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <fbjni/fbjni.h>
|
|
11
|
+
#include <functional>
|
|
12
|
+
|
|
13
|
+
#include <functional>
|
|
14
|
+
#include <NitroModules/JNICallable.hpp>
|
|
15
|
+
|
|
16
|
+
namespace margelo::nitro::ecr17 {
|
|
17
|
+
|
|
18
|
+
using namespace facebook;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Represents the Java/Kotlin callback `() -> Unit`.
|
|
22
|
+
* This can be passed around between C++ and Java/Kotlin.
|
|
23
|
+
*/
|
|
24
|
+
struct JFunc_void: public jni::JavaClass<JFunc_void> {
|
|
25
|
+
public:
|
|
26
|
+
static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/ecr17/Func_void;";
|
|
27
|
+
|
|
28
|
+
public:
|
|
29
|
+
/**
|
|
30
|
+
* Invokes the function this `JFunc_void` instance holds through JNI.
|
|
31
|
+
*/
|
|
32
|
+
void invoke() const {
|
|
33
|
+
static const auto method = javaClassStatic()->getMethod<void()>("invoke");
|
|
34
|
+
method(self());
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* An implementation of Func_void that is backed by a C++ implementation (using `std::function<...>`)
|
|
40
|
+
*/
|
|
41
|
+
class JFunc_void_cxx final: public jni::HybridClass<JFunc_void_cxx, JFunc_void> {
|
|
42
|
+
public:
|
|
43
|
+
static jni::local_ref<JFunc_void::javaobject> fromCpp(const std::function<void()>& func) {
|
|
44
|
+
return JFunc_void_cxx::newObjectCxxArgs(func);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public:
|
|
48
|
+
/**
|
|
49
|
+
* Invokes the C++ `std::function<...>` this `JFunc_void_cxx` instance holds.
|
|
50
|
+
*/
|
|
51
|
+
void invoke_cxx() {
|
|
52
|
+
_func();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public:
|
|
56
|
+
[[nodiscard]]
|
|
57
|
+
inline const std::function<void()>& getFunction() const {
|
|
58
|
+
return _func;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public:
|
|
62
|
+
static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/ecr17/Func_void_cxx;";
|
|
63
|
+
static void registerNatives() {
|
|
64
|
+
registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_cxx::invoke_cxx)});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private:
|
|
68
|
+
explicit JFunc_void_cxx(const std::function<void()>& func): _func(func) { }
|
|
69
|
+
|
|
70
|
+
private:
|
|
71
|
+
friend HybridBase;
|
|
72
|
+
std::function<void()> _func;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
} // namespace margelo::nitro::ecr17
|