@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.
- package/DeRec.podspec +25 -0
- package/LICENSE +201 -0
- package/README.md +222 -0
- package/android/CMakeLists.txt +38 -0
- package/android/build.gradle +79 -0
- package/android/src/main/AndroidManifest.xml +12 -0
- package/android/src/main/java/org/derec/DeRecModule.kt +56 -0
- package/android/src/main/java/org/derec/DeRecPackage.kt +24 -0
- package/android/src/main/jni/OnLoad.cpp +39 -0
- package/android/src/main/jniLibs/arm64-v8a/libderec_library.a +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libderec_library.a +0 -0
- package/android/src/main/jniLibs/x86_64/libderec_library.a +0 -0
- package/cpp/Convert.cpp +50 -0
- package/cpp/Convert.h +38 -0
- package/cpp/DeRecInstaller.cpp +186 -0
- package/cpp/DeRecInstaller.h +28 -0
- package/cpp/Invoker.h +20 -0
- package/cpp/JsCallbackBridge.cpp +144 -0
- package/cpp/JsCallbackBridge.h +148 -0
- package/cpp/Primitives.cpp +1193 -0
- package/cpp/Primitives.h +39 -0
- package/cpp/ProtocolHost.cpp +613 -0
- package/cpp/ProtocolHost.h +88 -0
- package/cpp/RuntimeInvoker.h +29 -0
- package/cpp/StoreCallbacks.cpp +1293 -0
- package/cpp/StoreCallbacks.h +111 -0
- package/cpp/WorkerThread.cpp +74 -0
- package/cpp/WorkerThread.h +54 -0
- package/cpp/derec_ffi.h +2145 -0
- package/ios/DeRec.h +23 -0
- package/ios/DeRec.mm +39 -0
- package/ios/DeRecFFI.xcframework/Info.plist +48 -0
- package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +2145 -0
- package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +2145 -0
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +0 -0
- package/lib/binary.d.ts +19 -0
- package/lib/binary.js +164 -0
- package/lib/codec.d.ts +16 -0
- package/lib/codec.js +80 -0
- package/lib/errors.d.ts +23 -0
- package/lib/errors.js +35 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +27 -0
- package/lib/messages.d.ts +41 -0
- package/lib/messages.js +192 -0
- package/lib/native.d.ts +11 -0
- package/lib/native.js +40 -0
- package/lib/primitives.d.ts +137 -0
- package/lib/primitives.js +329 -0
- package/lib/protocol.d.ts +188 -0
- package/lib/protocol.js +514 -0
- package/lib/types.d.ts +1014 -0
- package/lib/types.js +63 -0
- package/package.json +55 -0
- package/react-native.config.js +19 -0
- package/src/binary.ts +182 -0
- package/src/codec.ts +79 -0
- package/src/errors.ts +45 -0
- package/src/index.ts +7 -0
- package/src/messages.ts +192 -0
- package/src/native.ts +52 -0
- package/src/primitives.ts +770 -0
- package/src/protocol.ts +621 -0
- package/src/types.ts +1040 -0
package/cpp/Primitives.h
ADDED
|
@@ -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
|