@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.
Files changed (107) hide show
  1. package/Ecr17.podspec +39 -0
  2. package/README.md +348 -0
  3. package/android/CMakeLists.txt +41 -0
  4. package/android/build.gradle +149 -0
  5. package/android/fix-prefab.gradle +51 -0
  6. package/android/gradle.properties +5 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/cpp/cpp-adapter.cpp +9 -0
  9. package/android/src/main/java/com/margelo/nitro/ecr17/HybridEcr17Transport.kt +233 -0
  10. package/android/src/main/java/com/padosoft/ecr17/Ecr17Package.kt +30 -0
  11. package/cpp/Ecr17.cpp +1 -0
  12. package/cpp/Ecr17.hpp +2 -0
  13. package/cpp/Ecr17Client/HybridEcr17Client.cpp +598 -0
  14. package/cpp/Ecr17Client/HybridEcr17Client.hpp +85 -0
  15. package/cpp/Ecr17Protocol/Ecr17Protocol.cpp +277 -0
  16. package/cpp/Ecr17Protocol/Ecr17Protocol.hpp +103 -0
  17. package/cpp/Ecr17Response/Ecr17Response.cpp +155 -0
  18. package/cpp/Ecr17Response/Ecr17Response.hpp +113 -0
  19. package/cpp/Lcr/Lcr.cpp +42 -0
  20. package/cpp/Lcr/Lcr.hpp +22 -0
  21. package/cpp/PacketCodec/PacketCodec.cpp +146 -0
  22. package/cpp/PacketCodec/PacketCodec.hpp +48 -0
  23. package/cpp/Session/Ecr17Session.cpp +260 -0
  24. package/cpp/Session/Ecr17Session.hpp +97 -0
  25. package/cpp/Session/RetryPolicy.hpp +23 -0
  26. package/cpp/Transport/FakeTransport.hpp +95 -0
  27. package/cpp/Transport/NativeTransportAdapter.cpp +42 -0
  28. package/cpp/Transport/NativeTransportAdapter.hpp +32 -0
  29. package/cpp/Transport/Transport.hpp +31 -0
  30. package/cpp/tests/CMakeLists.txt +55 -0
  31. package/cpp/tests/PosixTcpTransport.hpp +105 -0
  32. package/cpp/tests/stubs/LrcMode.hpp +25 -0
  33. package/cpp/tests/test_flows.cpp +148 -0
  34. package/cpp/tests/test_integration_terminal.cpp +72 -0
  35. package/cpp/tests/test_lrc.cpp +66 -0
  36. package/cpp/tests/test_packet_codec.cpp +164 -0
  37. package/cpp/tests/test_protocol.cpp +102 -0
  38. package/cpp/tests/test_protocol_commands.cpp +190 -0
  39. package/cpp/tests/test_response.cpp +164 -0
  40. package/cpp/tests/test_retry_policy.cpp +28 -0
  41. package/cpp/tests/test_session.cpp +262 -0
  42. package/ios/Bridge.h +1 -0
  43. package/ios/HybridEcr17Transport.swift +103 -0
  44. package/nitro.json +30 -0
  45. package/nitrogen/generated/.gitattributes +1 -0
  46. package/nitrogen/generated/android/Ecr17+autolinking.cmake +82 -0
  47. package/nitrogen/generated/android/Ecr17+autolinking.gradle +27 -0
  48. package/nitrogen/generated/android/Ecr17OnLoad.cpp +68 -0
  49. package/nitrogen/generated/android/Ecr17OnLoad.hpp +34 -0
  50. package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  51. package/nitrogen/generated/android/c++/JFunc_void_std__shared_ptr_ArrayBuffer_.hpp +77 -0
  52. package/nitrogen/generated/android/c++/JHybridEcr17TransportSpec.cpp +93 -0
  53. package/nitrogen/generated/android/c++/JHybridEcr17TransportSpec.hpp +68 -0
  54. package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/Ecr17OnLoad.kt +35 -0
  55. package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/Func_void.kt +80 -0
  56. package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/Func_void_std__shared_ptr_ArrayBuffer_.kt +80 -0
  57. package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/HybridEcr17TransportSpec.kt +86 -0
  58. package/nitrogen/generated/ios/Ecr17+autolinking.rb +62 -0
  59. package/nitrogen/generated/ios/Ecr17-Swift-Cxx-Bridge.cpp +57 -0
  60. package/nitrogen/generated/ios/Ecr17-Swift-Cxx-Bridge.hpp +154 -0
  61. package/nitrogen/generated/ios/Ecr17-Swift-Cxx-Umbrella.hpp +47 -0
  62. package/nitrogen/generated/ios/Ecr17Autolinking.mm +43 -0
  63. package/nitrogen/generated/ios/Ecr17Autolinking.swift +26 -0
  64. package/nitrogen/generated/ios/c++/HybridEcr17TransportSpecSwift.cpp +11 -0
  65. package/nitrogen/generated/ios/c++/HybridEcr17TransportSpecSwift.hpp +119 -0
  66. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  67. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  68. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_ArrayBuffer_.swift +46 -0
  69. package/nitrogen/generated/ios/swift/HybridEcr17TransportSpec.swift +60 -0
  70. package/nitrogen/generated/ios/swift/HybridEcr17TransportSpec_cxx.swift +211 -0
  71. package/nitrogen/generated/shared/c++/CardType.hpp +84 -0
  72. package/nitrogen/generated/shared/c++/CardVerificationRequest.hpp +97 -0
  73. package/nitrogen/generated/shared/c++/CardVerificationResult.hpp +136 -0
  74. package/nitrogen/generated/shared/c++/CloseSessionResult.hpp +106 -0
  75. package/nitrogen/generated/shared/c++/ConnectionState.hpp +80 -0
  76. package/nitrogen/generated/shared/c++/CurrencyExchange.hpp +100 -0
  77. package/nitrogen/generated/shared/c++/Ecr17Config.hpp +138 -0
  78. package/nitrogen/generated/shared/c++/HybridEcr17ClientSpec.cpp +42 -0
  79. package/nitrogen/generated/shared/c++/HybridEcr17ClientSpec.hpp +138 -0
  80. package/nitrogen/generated/shared/c++/HybridEcr17TransportSpec.cpp +26 -0
  81. package/nitrogen/generated/shared/c++/HybridEcr17TransportSpec.hpp +70 -0
  82. package/nitrogen/generated/shared/c++/IncrementalAuthRequest.hpp +96 -0
  83. package/nitrogen/generated/shared/c++/LrcMode.hpp +84 -0
  84. package/nitrogen/generated/shared/c++/PaymentCardType.hpp +84 -0
  85. package/nitrogen/generated/shared/c++/PaymentRequest.hpp +109 -0
  86. package/nitrogen/generated/shared/c++/PaymentResult.hpp +139 -0
  87. package/nitrogen/generated/shared/c++/PosStatusResponse.hpp +96 -0
  88. package/nitrogen/generated/shared/c++/PreAuthClosureRequest.hpp +96 -0
  89. package/nitrogen/generated/shared/c++/PreAuthRequest.hpp +109 -0
  90. package/nitrogen/generated/shared/c++/PreAuthResult.hpp +144 -0
  91. package/nitrogen/generated/shared/c++/ProgressEvent.hpp +83 -0
  92. package/nitrogen/generated/shared/c++/ReceiptLine.hpp +83 -0
  93. package/nitrogen/generated/shared/c++/ReversalRequest.hpp +88 -0
  94. package/nitrogen/generated/shared/c++/ReversalResult.hpp +132 -0
  95. package/nitrogen/generated/shared/c++/TokenizationRequest.hpp +89 -0
  96. package/nitrogen/generated/shared/c++/TokenizationService.hpp +76 -0
  97. package/nitrogen/generated/shared/c++/TotalsResult.hpp +93 -0
  98. package/nitrogen/generated/shared/c++/TransactionEntryMode.hpp +92 -0
  99. package/nitrogen/generated/shared/c++/TransactionOutcome.hpp +88 -0
  100. package/nitrogen/generated/shared/c++/VasResult.hpp +96 -0
  101. package/package.json +102 -0
  102. package/react-native.config.js +18 -0
  103. package/src/index.ts +4 -0
  104. package/src/specs/client.nitro.ts +102 -0
  105. package/src/specs/transport.nitro.ts +25 -0
  106. package/src/types/client.ts +196 -0
  107. 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