@derec-alliance/react-native 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/DeRec.podspec +25 -0
  2. package/LICENSE +201 -0
  3. package/README.md +222 -0
  4. package/android/CMakeLists.txt +38 -0
  5. package/android/build.gradle +79 -0
  6. package/android/src/main/AndroidManifest.xml +12 -0
  7. package/android/src/main/java/org/derec/DeRecModule.kt +56 -0
  8. package/android/src/main/java/org/derec/DeRecPackage.kt +24 -0
  9. package/android/src/main/jni/OnLoad.cpp +39 -0
  10. package/android/src/main/jniLibs/arm64-v8a/libderec_library.a +0 -0
  11. package/android/src/main/jniLibs/armeabi-v7a/libderec_library.a +0 -0
  12. package/android/src/main/jniLibs/x86_64/libderec_library.a +0 -0
  13. package/cpp/Convert.cpp +50 -0
  14. package/cpp/Convert.h +38 -0
  15. package/cpp/DeRecInstaller.cpp +186 -0
  16. package/cpp/DeRecInstaller.h +28 -0
  17. package/cpp/Invoker.h +20 -0
  18. package/cpp/JsCallbackBridge.cpp +144 -0
  19. package/cpp/JsCallbackBridge.h +148 -0
  20. package/cpp/Primitives.cpp +1193 -0
  21. package/cpp/Primitives.h +39 -0
  22. package/cpp/ProtocolHost.cpp +613 -0
  23. package/cpp/ProtocolHost.h +88 -0
  24. package/cpp/RuntimeInvoker.h +29 -0
  25. package/cpp/StoreCallbacks.cpp +1293 -0
  26. package/cpp/StoreCallbacks.h +111 -0
  27. package/cpp/WorkerThread.cpp +74 -0
  28. package/cpp/WorkerThread.h +54 -0
  29. package/cpp/derec_ffi.h +2145 -0
  30. package/ios/DeRec.h +23 -0
  31. package/ios/DeRec.mm +39 -0
  32. package/ios/DeRecFFI.xcframework/Info.plist +48 -0
  33. package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +2145 -0
  34. package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
  35. package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +2145 -0
  36. package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +0 -0
  37. package/lib/binary.d.ts +19 -0
  38. package/lib/binary.js +164 -0
  39. package/lib/codec.d.ts +16 -0
  40. package/lib/codec.js +80 -0
  41. package/lib/errors.d.ts +23 -0
  42. package/lib/errors.js +35 -0
  43. package/lib/index.d.ts +4 -0
  44. package/lib/index.js +27 -0
  45. package/lib/messages.d.ts +41 -0
  46. package/lib/messages.js +192 -0
  47. package/lib/native.d.ts +11 -0
  48. package/lib/native.js +40 -0
  49. package/lib/primitives.d.ts +137 -0
  50. package/lib/primitives.js +329 -0
  51. package/lib/protocol.d.ts +188 -0
  52. package/lib/protocol.js +514 -0
  53. package/lib/types.d.ts +1014 -0
  54. package/lib/types.js +63 -0
  55. package/package.json +55 -0
  56. package/react-native.config.js +19 -0
  57. package/src/binary.ts +182 -0
  58. package/src/codec.ts +79 -0
  59. package/src/errors.ts +45 -0
  60. package/src/index.ts +7 -0
  61. package/src/messages.ts +192 -0
  62. package/src/native.ts +52 -0
  63. package/src/primitives.ts +770 -0
  64. package/src/protocol.ts +621 -0
  65. package/src/types.ts +1040 -0
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) 2026 DeRec Alliance. All rights reserved.
3
+
4
+ #pragma once
5
+
6
+ #include <jsi/jsi.h>
7
+
8
+ extern "C" {
9
+ #include "derec_ffi.h"
10
+ }
11
+
12
+ namespace derec {
13
+
14
+ /// Convert a failed `DeRecError` into a JavaScript exception and throw it.
15
+ /// The thrown value carries the same field names the WASM bindings emit, so
16
+ /// application error handling ports between the JavaScript SDKs unchanged.
17
+ [[noreturn]] void throwDeRecError(facebook::jsi::Runtime& rt, const DeRecError& error);
18
+
19
+ /// Read an `ArrayBuffer` or typed-array argument as a byte range.
20
+ /// Returns `{nullptr, 0}` for `null`/`undefined`.
21
+ struct ByteView {
22
+ const uint8_t* ptr;
23
+ size_t len;
24
+ };
25
+ ByteView asBytes(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value);
26
+
27
+ /// Copy `bytes` into a fresh `ArrayBuffer`.
28
+ facebook::jsi::Value toArrayBuffer(facebook::jsi::Runtime& rt,
29
+ const uint8_t* bytes,
30
+ size_t len);
31
+
32
+ /// Read a `bigint` or `number` argument as a `uint64_t`.
33
+ uint64_t asU64(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value);
34
+
35
+ /// Install every stateless FFI primitive as a host function on `host`, named
36
+ /// exactly after its C symbol.
37
+ void installPrimitives(facebook::jsi::Runtime& rt, facebook::jsi::Object& host);
38
+
39
+ } // namespace derec
@@ -0,0 +1,613 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) 2026 DeRec Alliance. All rights reserved.
3
+
4
+ #include "ProtocolHost.h"
5
+
6
+ #include <cstring>
7
+ #include <string>
8
+ #include <utility>
9
+ #include <vector>
10
+
11
+ #include "Convert.h"
12
+ #include "DeRecInstaller.h"
13
+ #include "Primitives.h"
14
+
15
+ using namespace facebook;
16
+
17
+ namespace derec {
18
+
19
+ namespace {
20
+
21
+ /// Carries a Rust `DeRecError` out of a `runAsync` body running on the
22
+ /// worker thread, where no `jsi::Runtime` is available to build the
23
+ /// JavaScript exception `throwDeRecError` throws. Rebuilt into that exact
24
+ /// shape once execution hops back to the JavaScript thread, so a caller
25
+ /// never sees a second error shape depending on which thread noticed the
26
+ /// failure.
27
+ struct FfiFailure {
28
+ explicit FfiFailure(DeRecError e) : error(e) {}
29
+ DeRecError error;
30
+ };
31
+
32
+ constexpr const char* kReentrancyMessage =
33
+ "DeRec: protocol methods cannot be called from inside a store callback";
34
+
35
+ /// Keeps a JavaScript value alive across a hop to the worker thread and back.
36
+ ///
37
+ /// A bare `jsi::Value` cannot be used for that: its destructor calls into the
38
+ /// runtime, and the whole point of retaining one here is that it may outlive
39
+ /// the moment the runtime is torn down (React Native reload / bridge
40
+ /// invalidate). This wrapper releases the value only while the runtime is
41
+ /// known to be alive; once `runtimeInvalidated` is set it deliberately leaks
42
+ /// the handle instead. Leaking costs nothing real — the runtime owns the
43
+ /// referenced object and frees its entire heap as it shuts down — whereas
44
+ /// releasing would call into a runtime that is already gone.
45
+ ///
46
+ /// Note this keys off `runtimeInvalidated`, not `invalidated`: an ordinary
47
+ /// `free()` leaves the runtime perfectly alive, and a Promise settlement
48
+ /// still owed to a caller must run in that case.
49
+ class RetainedJsValue {
50
+ public:
51
+ RetainedJsValue(jsi::Runtime& rt,
52
+ const jsi::Value& value,
53
+ std::shared_ptr<InstanceState> state)
54
+ : value_(new jsi::Value(rt, value)), state_(std::move(state)) {}
55
+
56
+ RetainedJsValue(const RetainedJsValue&) = delete;
57
+ RetainedJsValue& operator=(const RetainedJsValue&) = delete;
58
+
59
+ ~RetainedJsValue() {
60
+ if (state_->runtimeInvalidated.load()) {
61
+ return; // intentionally not freed; see the class comment
62
+ }
63
+ delete value_;
64
+ }
65
+
66
+ const jsi::Value& get() const { return *value_; }
67
+
68
+ private:
69
+ jsi::Value* value_;
70
+ std::shared_ptr<InstanceState> state_;
71
+ };
72
+
73
+ void requireArgs(jsi::Runtime& rt, const char* name, size_t count, size_t required) {
74
+ if (count < required) {
75
+ throw jsi::JSError(rt, std::string(name) + " expects " +
76
+ std::to_string(required) + " arguments");
77
+ }
78
+ }
79
+
80
+ /// Copy a `convert` for the common case: the body's raw bytes are already
81
+ /// the exact wire payload the JavaScript side wants, verbatim.
82
+ jsi::Value bytesToArrayBuffer(jsi::Runtime& rt, std::vector<uint8_t>& bytes) {
83
+ return toArrayBuffer(rt, bytes.data(), bytes.size());
84
+ }
85
+
86
+ } // namespace
87
+
88
+ ProtocolHost::ProtocolHost(DeRecProtocolHandle* handle,
89
+ uint64_t secretId,
90
+ std::shared_ptr<Invoker> invoker,
91
+ std::shared_ptr<InstanceState> state,
92
+ std::shared_ptr<StoreBindings> stores)
93
+ : handle_(handle),
94
+ secretId_(secretId),
95
+ invoker_(std::move(invoker)),
96
+ state_(std::move(state)),
97
+ stores_(std::move(stores)),
98
+ worker_("derec-protocol-" + std::to_string(secretId)) {
99
+ registerInstance(state_);
100
+ }
101
+
102
+ ProtocolHost::~ProtocolHost() {
103
+ // Order matters. Invalidate first so any worker blocked in a store callback
104
+ // wakes and unwinds; drain so no task can still be inside a
105
+ // `derec_protocol_*` call; only then release the handle. Freeing earlier
106
+ // would let a live worker read freed memory, which the FFI's safety
107
+ // contract explicitly makes the host binding's responsibility to prevent.
108
+ teardown();
109
+ }
110
+
111
+ void ProtocolHost::teardown() {
112
+ state_->invalidate();
113
+ worker_.drainAndJoin();
114
+ derec_protocol_free(handle_);
115
+ handle_ = nullptr;
116
+ }
117
+
118
+ jsi::Value ProtocolHost::runAsync(
119
+ jsi::Runtime& rt,
120
+ std::function<std::vector<uint8_t>()> body,
121
+ std::function<jsi::Value(jsi::Runtime&, std::vector<uint8_t>&)> convert) {
122
+ // A protocol call issued from inside a store callback's own body would
123
+ // enqueue onto the very worker that is blocked waiting for that body's
124
+ // Promise — the worker cannot reach the new task until the store settles,
125
+ // and the store cannot settle if it is waiting on the new task.
126
+ //
127
+ // The gate is deliberately no wider than that. A call made from unrelated
128
+ // JavaScript-thread work that merely overlaps a parked worker is safe:
129
+ // the worker is serial, so the call simply runs after the current one
130
+ // finishes. Rejecting those too would fail ordinary concurrent calls
131
+ // nondeterministically, purely because a flow happened to be in a store
132
+ // callback at that instant.
133
+ if (isInsideStoreCallback()) {
134
+ throw jsi::JSError(rt, kReentrancyMessage);
135
+ }
136
+ if (handle_ == nullptr) {
137
+ throw jsi::JSError(rt, "DeRec: protocol instance has been freed");
138
+ }
139
+
140
+ // Everything captured past this point must survive `runAsync` returning
141
+ // and must not depend on this `ProtocolHost` outliving the call: the
142
+ // worker-thread step and the JavaScript-thread settlement both run later,
143
+ // potentially after `free()` or the destructor has already run. Only
144
+ // `invoker_` and `state_` (both `shared_ptr`) and `&rt` (the runtime, whose
145
+ // lifetime is independent of this object) cross that boundary; nothing here
146
+ // reaches back into `this`.
147
+ auto invoker = invoker_;
148
+ auto state = state_;
149
+ jsi::Runtime* runtime = &rt;
150
+
151
+ auto executor = jsi::Function::createFromHostFunction(
152
+ rt, jsi::PropNameID::forAscii(rt, "derecProtocolExecutor"), 2,
153
+ [&worker = worker_, invoker, state, runtime, body = std::move(body),
154
+ convert = std::move(convert)](jsi::Runtime& execRt, const jsi::Value&,
155
+ const jsi::Value* args,
156
+ size_t) mutable -> jsi::Value {
157
+ // Copy the executor's resolve/reject into handles that survive past
158
+ // this call: `Value`'s copy-with-runtime constructor bumps the
159
+ // underlying reference count, unlike a bare reference to `args`,
160
+ // which is only valid for this call. `RetainedJsValue` additionally
161
+ // makes dropping that reference safe on a torn-down runtime, whichever
162
+ // thread ends up dropping it.
163
+ auto resolve = std::make_shared<RetainedJsValue>(execRt, args[0], state);
164
+ auto reject = std::make_shared<RetainedJsValue>(execRt, args[1], state);
165
+
166
+ bool accepted =
167
+ worker.post([invoker, state, runtime, resolve, reject, body,
168
+ convert]() mutable {
169
+ std::vector<uint8_t> bytes;
170
+ bool failed = false;
171
+ bool unknownFailure = false;
172
+ DeRecError capturedError{};
173
+ try {
174
+ bytes = body();
175
+ } catch (const FfiFailure& failure) {
176
+ capturedError = failure.error;
177
+ failed = true;
178
+ } catch (...) {
179
+ // Anything other than `FfiFailure` — `std::bad_alloc` from
180
+ // `takeBuffer`, or any other exception a body's bookkeeping
181
+ // might throw — must still settle the Promise. Left
182
+ // uncaught here, it would propagate into
183
+ // `WorkerThread::loop`'s own catch-all (there specifically
184
+ // to stop `std::terminate`), which discards it silently:
185
+ // the Promise would then never settle and the caller would
186
+ // hang forever instead of seeing a failure.
187
+ unknownFailure = true;
188
+ }
189
+
190
+ if (state->runtimeInvalidated.load()) {
191
+ // The runtime is gone: there is nothing to settle into, and
192
+ // scheduling onto its invoker would only queue a lambda whose
193
+ // captures must never be released against it either. Returning
194
+ // here drops `resolve`/`reject` on this thread, which
195
+ // `RetainedJsValue` handles without touching the runtime.
196
+ return;
197
+ }
198
+
199
+ invoker->invokeAsync([state, runtime, resolve, reject, convert,
200
+ bytes = std::move(bytes), failed,
201
+ unknownFailure, capturedError]() mutable {
202
+ if (state->runtimeInvalidated.load()) {
203
+ // Queued before the runtime was torn down, reached only
204
+ // afterwards. Every JSI call below — including the ones the
205
+ // settlement itself makes — would run against a dead
206
+ // runtime.
207
+ return;
208
+ }
209
+ jsi::Runtime& jsRt = *runtime;
210
+ try {
211
+ if (failed) {
212
+ // Reuses the exact error shape every synchronous FFI
213
+ // wrapper throws, rather than inventing a second one for
214
+ // the asynchronous path.
215
+ throwDeRecError(jsRt, capturedError);
216
+ }
217
+ if (unknownFailure) {
218
+ throw jsi::JSError(jsRt, "DeRec: an unexpected native error occurred");
219
+ }
220
+ jsi::Value result = convert(jsRt, bytes);
221
+ resolve->get().asObject(jsRt).asFunction(jsRt).call(jsRt, result);
222
+ } catch (jsi::JSError& error) {
223
+ reject->get().asObject(jsRt).asFunction(jsRt).call(
224
+ jsRt, jsi::Value(jsRt, error.value()));
225
+ }
226
+ });
227
+ });
228
+
229
+ if (!accepted) {
230
+ // The worker has already been drained (teardown ran, concurrently
231
+ // with this call, on whatever thread finalized the previous
232
+ // `ProtocolHost`). Nothing will ever run the posted task, so
233
+ // settle the Promise immediately instead of leaving it pending
234
+ // forever. Rejects with a real `Error` (not a bare string) so
235
+ // `.catch(e => e.message)` behaves the same here as for every
236
+ // other rejection in this file.
237
+ jsi::JSError releasedError(execRt, "DeRec: protocol instance has been released");
238
+ reject->get()
239
+ .asObject(execRt)
240
+ .asFunction(execRt)
241
+ .call(execRt, jsi::Value(execRt, releasedError.value()));
242
+ }
243
+
244
+ return jsi::Value::undefined();
245
+ });
246
+
247
+ auto promiseCtor = rt.global().getPropertyAsFunction(rt, "Promise");
248
+ return promiseCtor.callAsConstructor(rt, executor);
249
+ }
250
+
251
+ namespace {
252
+
253
+ /// `secretId(): bigint` — no FFI call. The protocol handle carries no
254
+ /// accessor for it, so the value supplied at construction is the only
255
+ /// source of truth.
256
+ jsi::Value hostSecretId(jsi::Runtime& rt, uint64_t secretId) {
257
+ return jsi::Value(rt, jsi::BigInt::fromUint64(rt, secretId));
258
+ }
259
+
260
+ } // namespace
261
+
262
+ jsi::Value ProtocolHost::get(jsi::Runtime& rt, const jsi::PropNameID& name) {
263
+ std::string prop = name.utf8(rt);
264
+
265
+ if (prop == "secretId") {
266
+ return jsi::Function::createFromHostFunction(
267
+ rt, name, 0,
268
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value*,
269
+ size_t) -> jsi::Value { return hostSecretId(rt2, secretId_); });
270
+ }
271
+
272
+ // `setCommunicationInfo` and `setOwnTransport` return a Promise, unlike
273
+ // `@derec-alliance/nodejs` where both are synchronous. They must not run on
274
+ // the JavaScript thread: `derec_protocol_set_*` takes the same handle mutex
275
+ // that `derec_protocol_process`/`start`/`restore` hold for the whole
276
+ // duration of a flow, including across store callbacks that park the worker
277
+ // waiting on the JavaScript thread. Calling the setter synchronously would
278
+ // block the JavaScript thread on that mutex, so the callback's continuation
279
+ // could never run, so the worker could never resume and release it — broken
280
+ // only by the store timeout, which additionally corrupts the in-flight
281
+ // callback into a backend failure. The nodejs SDK is WASM and
282
+ // single-threaded, so the hazard cannot arise there. Queuing onto the
283
+ // serial worker instead orders the setter behind any in-flight flow, and
284
+ // returning a Promise keeps a caller that ignores the result — which is how
285
+ // nodejs code calls these — source-compatible, while letting a failure
286
+ // surface as a rejection rather than being swallowed.
287
+ if (prop == "setCommunicationInfo") {
288
+ return jsi::Function::createFromHostFunction(
289
+ rt, name, 1,
290
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
291
+ size_t count) -> jsi::Value {
292
+ requireArgs(rt2, "setCommunicationInfo", count, 1);
293
+ ByteView view = asBytes(rt2, args[0]);
294
+ std::vector<uint8_t> info(view.ptr, view.ptr + view.len);
295
+ DeRecProtocolHandle* handle = handle_;
296
+
297
+ auto body = [handle, info = std::move(info)]() -> std::vector<uint8_t> {
298
+ DeRecError error = derec_protocol_set_communication_info(
299
+ handle, info.data(), info.size());
300
+ if (error.code != DEREC_CODE_OK) {
301
+ throw FfiFailure(error);
302
+ }
303
+ return {};
304
+ };
305
+ auto convert = [](jsi::Runtime&, std::vector<uint8_t>&) {
306
+ return jsi::Value::undefined();
307
+ };
308
+ return runAsync(rt2, std::move(body), std::move(convert));
309
+ });
310
+ }
311
+
312
+ if (prop == "setOwnTransport") {
313
+ return jsi::Function::createFromHostFunction(
314
+ rt, name, 2,
315
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
316
+ size_t count) -> jsi::Value {
317
+ requireArgs(rt2, "setOwnTransport", count, 2);
318
+ std::string uri = args[0].asString(rt2).utf8(rt2);
319
+ auto protocol = static_cast<int32_t>(args[1].asNumber());
320
+ DeRecProtocolHandle* handle = handle_;
321
+
322
+ auto body = [handle, uri, protocol]() -> std::vector<uint8_t> {
323
+ DeRecError error = derec_protocol_set_own_transport(
324
+ handle, reinterpret_cast<const uint8_t*>(uri.data()), uri.size(),
325
+ protocol);
326
+ if (error.code != DEREC_CODE_OK) {
327
+ throw FfiFailure(error);
328
+ }
329
+ return {};
330
+ };
331
+ auto convert = [](jsi::Runtime&, std::vector<uint8_t>&) {
332
+ return jsi::Value::undefined();
333
+ };
334
+ return runAsync(rt2, std::move(body), std::move(convert));
335
+ });
336
+ }
337
+
338
+ if (prop == "createContact") {
339
+ return jsi::Function::createFromHostFunction(
340
+ rt, name, 5,
341
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
342
+ size_t count) -> jsi::Value {
343
+ requireArgs(rt2, "createContact", count, 5);
344
+ auto hasChannelId = static_cast<uint32_t>(args[0].asNumber());
345
+ uint64_t channelId = asU64(rt2, args[1]);
346
+ auto contactMode = static_cast<int32_t>(args[2].asNumber());
347
+ auto hasNonce = static_cast<uint32_t>(args[3].asNumber());
348
+ uint64_t nonce = asU64(rt2, args[4]);
349
+ DeRecProtocolHandle* handle = handle_;
350
+
351
+ auto body = [handle, hasChannelId, channelId, contactMode, hasNonce,
352
+ nonce]() -> std::vector<uint8_t> {
353
+ DeRecProtocolCreateContactResult result = derec_protocol_create_contact(
354
+ handle, hasChannelId, channelId, contactMode, hasNonce, nonce);
355
+ if (result.error.code != DEREC_CODE_OK) {
356
+ throw FfiFailure(result.error);
357
+ }
358
+ return takeBuffer(result.contact_wire_bytes);
359
+ };
360
+ return runAsync(rt2, std::move(body), bytesToArrayBuffer);
361
+ });
362
+ }
363
+
364
+ if (prop == "start") {
365
+ return jsi::Function::createFromHostFunction(
366
+ rt, name, 2,
367
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
368
+ size_t count) -> jsi::Value {
369
+ requireArgs(rt2, "start", count, 2);
370
+ auto flowKind = static_cast<uint32_t>(args[0].asNumber());
371
+ ByteView view = asBytes(rt2, args[1]);
372
+ std::vector<uint8_t> params(view.ptr, view.ptr + view.len);
373
+ DeRecProtocolHandle* handle = handle_;
374
+
375
+ auto body = [handle, flowKind,
376
+ params = std::move(params)]() -> std::vector<uint8_t> {
377
+ DeRecProtocolEventsResult result =
378
+ derec_protocol_start(handle, flowKind, params.data(), params.size());
379
+ if (result.error.code != DEREC_CODE_OK) {
380
+ throw FfiFailure(result.error);
381
+ }
382
+ return takeBuffer(result.events_json);
383
+ };
384
+ return runAsync(rt2, std::move(body), bytesToArrayBuffer);
385
+ });
386
+ }
387
+
388
+ if (prop == "process") {
389
+ return jsi::Function::createFromHostFunction(
390
+ rt, name, 1,
391
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
392
+ size_t count) -> jsi::Value {
393
+ requireArgs(rt2, "process", count, 1);
394
+ ByteView view = asBytes(rt2, args[0]);
395
+ std::vector<uint8_t> message(view.ptr, view.ptr + view.len);
396
+ DeRecProtocolHandle* handle = handle_;
397
+
398
+ auto body = [handle,
399
+ message = std::move(message)]() -> std::vector<uint8_t> {
400
+ DeRecProtocolEventsResult result =
401
+ derec_protocol_process(handle, message.data(), message.size());
402
+ if (result.error.code != DEREC_CODE_OK) {
403
+ throw FfiFailure(result.error);
404
+ }
405
+ return takeBuffer(result.events_json);
406
+ };
407
+ return runAsync(rt2, std::move(body), bytesToArrayBuffer);
408
+ });
409
+ }
410
+
411
+ if (prop == "tick") {
412
+ return jsi::Function::createFromHostFunction(
413
+ rt, name, 0,
414
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value*,
415
+ size_t) -> jsi::Value {
416
+ DeRecProtocolHandle* handle = handle_;
417
+
418
+ auto body = [handle]() -> std::vector<uint8_t> {
419
+ DeRecProtocolEventsResult result = derec_protocol_tick(handle);
420
+ if (result.error.code != DEREC_CODE_OK) {
421
+ throw FfiFailure(result.error);
422
+ }
423
+ return takeBuffer(result.events_json);
424
+ };
425
+ return runAsync(rt2, std::move(body), bytesToArrayBuffer);
426
+ });
427
+ }
428
+
429
+ if (prop == "accept") {
430
+ return jsi::Function::createFromHostFunction(
431
+ rt, name, 1,
432
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
433
+ size_t count) -> jsi::Value {
434
+ requireArgs(rt2, "accept", count, 1);
435
+ ByteView view = asBytes(rt2, args[0]);
436
+ std::vector<uint8_t> action(view.ptr, view.ptr + view.len);
437
+ DeRecProtocolHandle* handle = handle_;
438
+
439
+ auto body = [handle,
440
+ action = std::move(action)]() -> std::vector<uint8_t> {
441
+ DeRecProtocolEventsResult result =
442
+ derec_protocol_accept(handle, action.data(), action.size());
443
+ if (result.error.code != DEREC_CODE_OK) {
444
+ throw FfiFailure(result.error);
445
+ }
446
+ return takeBuffer(result.events_json);
447
+ };
448
+ return runAsync(rt2, std::move(body), bytesToArrayBuffer);
449
+ });
450
+ }
451
+
452
+ if (prop == "reject") {
453
+ return jsi::Function::createFromHostFunction(
454
+ rt, name, 3,
455
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
456
+ size_t count) -> jsi::Value {
457
+ requireArgs(rt2, "reject", count, 3);
458
+ ByteView view = asBytes(rt2, args[0]);
459
+ std::vector<uint8_t> action(view.ptr, view.ptr + view.len);
460
+ auto status = static_cast<int32_t>(args[1].asNumber());
461
+ std::string memo = args[2].asString(rt2).utf8(rt2);
462
+ DeRecProtocolHandle* handle = handle_;
463
+
464
+ auto body = [handle, action = std::move(action), status,
465
+ memo]() -> std::vector<uint8_t> {
466
+ DeRecError error = derec_protocol_reject(
467
+ handle, action.data(), action.size(), status,
468
+ reinterpret_cast<const uint8_t*>(memo.data()), memo.size());
469
+ if (error.code != DEREC_CODE_OK) {
470
+ throw FfiFailure(error);
471
+ }
472
+ return {};
473
+ };
474
+ auto convert = [](jsi::Runtime&, std::vector<uint8_t>&) {
475
+ return jsi::Value::undefined();
476
+ };
477
+ return runAsync(rt2, std::move(body), std::move(convert));
478
+ });
479
+ }
480
+
481
+ if (prop == "getFingerprint") {
482
+ return jsi::Function::createFromHostFunction(
483
+ rt, name, 1,
484
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
485
+ size_t count) -> jsi::Value {
486
+ requireArgs(rt2, "getFingerprint", count, 1);
487
+ uint64_t channelId = asU64(rt2, args[0]);
488
+ DeRecProtocolHandle* handle = handle_;
489
+
490
+ auto body = [handle, channelId]() -> std::vector<uint8_t> {
491
+ DeRecProtocolFingerprintResult result =
492
+ derec_protocol_get_fingerprint(handle, channelId);
493
+ if (result.error.code != DEREC_CODE_OK) {
494
+ throw FfiFailure(result.error);
495
+ }
496
+ std::string fingerprint = takeString(result.fingerprint);
497
+ return std::vector<uint8_t>(fingerprint.begin(), fingerprint.end());
498
+ };
499
+ auto convert = [](jsi::Runtime& rt3, std::vector<uint8_t>& bytes) {
500
+ return jsi::Value(
501
+ jsi::String::createFromUtf8(rt3, bytes.data(), bytes.size()));
502
+ };
503
+ return runAsync(rt2, std::move(body), std::move(convert));
504
+ });
505
+ }
506
+
507
+ if (prop == "verifyFingerprint") {
508
+ return jsi::Function::createFromHostFunction(
509
+ rt, name, 2,
510
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
511
+ size_t count) -> jsi::Value {
512
+ requireArgs(rt2, "verifyFingerprint", count, 2);
513
+ uint64_t channelId = asU64(rt2, args[0]);
514
+ std::string fingerprint = args[1].asString(rt2).utf8(rt2);
515
+ DeRecProtocolHandle* handle = handle_;
516
+
517
+ auto body = [handle, channelId, fingerprint]() -> std::vector<uint8_t> {
518
+ uint32_t matched = 0;
519
+ DeRecError error = derec_protocol_verify_fingerprint(
520
+ handle, channelId, fingerprint.c_str(), &matched);
521
+ if (error.code != DEREC_CODE_OK) {
522
+ throw FfiFailure(error);
523
+ }
524
+ return std::vector<uint8_t>{static_cast<uint8_t>(matched != 0 ? 1 : 0)};
525
+ };
526
+ auto convert = [](jsi::Runtime&, std::vector<uint8_t>& bytes) {
527
+ return jsi::Value(!bytes.empty() && bytes[0] != 0);
528
+ };
529
+ return runAsync(rt2, std::move(body), std::move(convert));
530
+ });
531
+ }
532
+
533
+ if (prop == "removeExpiredChannels") {
534
+ return jsi::Function::createFromHostFunction(
535
+ rt, name, 1,
536
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
537
+ size_t count) -> jsi::Value {
538
+ requireArgs(rt2, "removeExpiredChannels", count, 1);
539
+ uint64_t olderThanSecs = asU64(rt2, args[0]);
540
+ DeRecProtocolHandle* handle = handle_;
541
+
542
+ auto body = [handle, olderThanSecs]() -> std::vector<uint8_t> {
543
+ DeRecRemovedChannelsResult result =
544
+ derec_protocol_remove_expired_channels(handle, olderThanSecs);
545
+ if (result.error.code != DEREC_CODE_OK) {
546
+ throw FfiFailure(result.error);
547
+ }
548
+ return takeBuffer(result.channels);
549
+ };
550
+ return runAsync(rt2, std::move(body), bytesToArrayBuffer);
551
+ });
552
+ }
553
+
554
+ if (prop == "restore") {
555
+ return jsi::Function::createFromHostFunction(
556
+ rt, name, 1,
557
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value* args,
558
+ size_t count) -> jsi::Value {
559
+ requireArgs(rt2, "restore", count, 1);
560
+ ByteView view = asBytes(rt2, args[0]);
561
+ std::vector<uint8_t> params(view.ptr, view.ptr + view.len);
562
+ DeRecProtocolHandle* handle = handle_;
563
+
564
+ auto body = [handle,
565
+ params = std::move(params)]() -> std::vector<uint8_t> {
566
+ DeRecProtocolEventsResult result =
567
+ derec_protocol_restore(handle, params.data(), params.size());
568
+ if (result.error.code != DEREC_CODE_OK) {
569
+ throw FfiFailure(result.error);
570
+ }
571
+ return takeBuffer(result.events_json);
572
+ };
573
+ return runAsync(rt2, std::move(body), bytesToArrayBuffer);
574
+ });
575
+ }
576
+
577
+ if (prop == "free") {
578
+ return jsi::Function::createFromHostFunction(
579
+ rt, name, 0,
580
+ [this](jsi::Runtime& rt2, const jsi::Value&, const jsi::Value*,
581
+ size_t) -> jsi::Value {
582
+ // `teardown` drains the worker, which would deadlock if called
583
+ // from inside a store callback body that same worker is blocked
584
+ // waiting on — the same hazard `runAsync` guards against, with the
585
+ // same narrow scope.
586
+ if (isInsideStoreCallback()) {
587
+ throw jsi::JSError(rt2, kReentrancyMessage);
588
+ }
589
+ teardown();
590
+ return jsi::Value::undefined();
591
+ });
592
+ }
593
+
594
+ return jsi::Value::undefined();
595
+ }
596
+
597
+ std::vector<jsi::PropNameID> ProtocolHost::getPropertyNames(jsi::Runtime& rt) {
598
+ const char* names[] = {
599
+ "secretId", "setCommunicationInfo", "setOwnTransport",
600
+ "createContact", "start", "process",
601
+ "tick", "accept", "reject",
602
+ "getFingerprint", "verifyFingerprint", "removeExpiredChannels",
603
+ "restore", "free",
604
+ };
605
+ std::vector<jsi::PropNameID> result;
606
+ result.reserve(sizeof(names) / sizeof(names[0]));
607
+ for (const char* name : names) {
608
+ result.push_back(jsi::PropNameID::forAscii(rt, name));
609
+ }
610
+ return result;
611
+ }
612
+
613
+ } // namespace derec