@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
|
@@ -0,0 +1,1293 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) 2026 DeRec Alliance. All rights reserved.
|
|
3
|
+
|
|
4
|
+
#include "StoreCallbacks.h"
|
|
5
|
+
|
|
6
|
+
#include <cstring>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <vector>
|
|
9
|
+
|
|
10
|
+
#include "Convert.h"
|
|
11
|
+
#include "Primitives.h"
|
|
12
|
+
|
|
13
|
+
using namespace facebook;
|
|
14
|
+
|
|
15
|
+
namespace derec {
|
|
16
|
+
|
|
17
|
+
namespace {
|
|
18
|
+
|
|
19
|
+
// Struct-layout guards. Every field is pointer-sized, so a struct's
|
|
20
|
+
// size is exactly its member count times `sizeof(void*)` on every ABI this
|
|
21
|
+
// binding targets; a mismatch means `derec_ffi.h` grew or shrank a field
|
|
22
|
+
// this file has not been updated to match.
|
|
23
|
+
|
|
24
|
+
static_assert(sizeof(ChannelStoreCallbacks) == 9 * sizeof(void*),
|
|
25
|
+
"ChannelStoreCallbacks layout changed; update the bindings");
|
|
26
|
+
static_assert(sizeof(SecretStoreCallbacks) == 5 * sizeof(void*),
|
|
27
|
+
"SecretStoreCallbacks layout changed; update the bindings");
|
|
28
|
+
static_assert(sizeof(ShareStoreCallbacks) == 8 * sizeof(void*),
|
|
29
|
+
"ShareStoreCallbacks layout changed; update the bindings");
|
|
30
|
+
static_assert(sizeof(UserSecretStoreCallbacks) == 5 * sizeof(void*),
|
|
31
|
+
"UserSecretStoreCallbacks layout changed; update the bindings");
|
|
32
|
+
static_assert(sizeof(StateStoreCallbacks) == 6 * sizeof(void*),
|
|
33
|
+
"StateStoreCallbacks layout changed; update the bindings");
|
|
34
|
+
static_assert(sizeof(TransportCallbacks) == 2 * sizeof(void*),
|
|
35
|
+
"TransportCallbacks layout changed; update the bindings");
|
|
36
|
+
|
|
37
|
+
/// Write `bytes` into a store callback's out-parameters. Returns `false`
|
|
38
|
+
/// only when the allocation itself failed; a genuinely empty `bytes` still
|
|
39
|
+
/// returns `true` with `*outPtr = nullptr, *outLen = 0` — that is the
|
|
40
|
+
/// documented "no payload" encoding, not an error. Callers MUST map a
|
|
41
|
+
/// `false` return to `kBackendFailure` rather than the success code they
|
|
42
|
+
/// were about to return: silently degrading an allocation failure into an
|
|
43
|
+
/// empty successful payload would make an out-of-memory condition
|
|
44
|
+
/// indistinguishable from a legitimate "not found", which can corrupt a
|
|
45
|
+
/// secret recovery exactly as a fabricated `1` would.
|
|
46
|
+
///
|
|
47
|
+
/// The buffer is allocated by this binding; Rust copies the contents and
|
|
48
|
+
/// returns the pointer through the same struct's `free_buffer`, so no
|
|
49
|
+
/// cross-allocator free can occur.
|
|
50
|
+
bool writeOut(const std::vector<uint8_t>& bytes, uint8_t** outPtr, size_t* outLen) {
|
|
51
|
+
if (bytes.empty()) {
|
|
52
|
+
*outPtr = nullptr;
|
|
53
|
+
*outLen = 0;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
OwnedBytes owned = allocBytes(bytes.size());
|
|
57
|
+
if (owned.ptr == nullptr) {
|
|
58
|
+
*outPtr = nullptr;
|
|
59
|
+
*outLen = 0;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
std::memcpy(owned.ptr, bytes.data(), bytes.size());
|
|
63
|
+
*outPtr = owned.ptr;
|
|
64
|
+
*outLen = owned.len;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// A decimal-string `jsi::Value`, matching every id parameter in
|
|
69
|
+
/// `src/types.ts` (`secretId`, `channelId`, `replicaId` are `string`, not
|
|
70
|
+
/// `bigint`, so real ids beyond `Number.MAX_SAFE_INTEGER` survive JS number
|
|
71
|
+
/// handling intact).
|
|
72
|
+
jsi::Value idVal(jsi::Runtime& rt, uint64_t id) {
|
|
73
|
+
return jsi::Value(rt, jsi::String::createFromUtf8(rt, std::to_string(id)));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Build a genuine `Uint8Array` (not a bare `ArrayBuffer`) — every store
|
|
77
|
+
/// interface types its byte payloads as `Uint8Array`, and application code
|
|
78
|
+
/// is entitled to index or slice it directly.
|
|
79
|
+
jsi::Value toUint8ArrayVal(jsi::Runtime& rt, const uint8_t* bytes, size_t len) {
|
|
80
|
+
jsi::Uint8Array arr(rt, len);
|
|
81
|
+
if (len > 0 && bytes != nullptr) {
|
|
82
|
+
jsi::ArrayBuffer buffer = arr.buffer(rt);
|
|
83
|
+
std::memcpy(buffer.data(rt), bytes, len);
|
|
84
|
+
}
|
|
85
|
+
return jsi::Value(rt, std::move(arr));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
jsi::Value toUint8ArrayVal(jsi::Runtime& rt, const std::vector<uint8_t>& bytes) {
|
|
89
|
+
return toUint8ArrayVal(rt, bytes.data(), bytes.size());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Minimal decoders for the wire's "bare JSON array/object of unsigned
|
|
93
|
+
// integers" convention (`versions_json`, `channel_ids_json`, the `bytes`
|
|
94
|
+
// field of a `ShareRecord` / `SecretValueRecord`). These are produced by
|
|
95
|
+
// `serde_json`'s default compact, whitespace-free output over a fixed,
|
|
96
|
+
// known shape, so a substring scan is sufficient and — critically — never
|
|
97
|
+
// routes a `u64` through a JS `number`, which cannot represent every value
|
|
98
|
+
// in that range exactly.
|
|
99
|
+
|
|
100
|
+
/// Parse a bare `[n, n, ...]` array of unsigned decimal integers.
|
|
101
|
+
std::vector<uint64_t> parseUnsignedJsonArray(const uint8_t* ptr, size_t len) {
|
|
102
|
+
std::vector<uint64_t> out;
|
|
103
|
+
size_t i = 0;
|
|
104
|
+
while (i < len && ptr[i] != '[') ++i;
|
|
105
|
+
if (i >= len) return out;
|
|
106
|
+
++i;
|
|
107
|
+
while (i < len && ptr[i] != ']') {
|
|
108
|
+
if (ptr[i] < '0' || ptr[i] > '9') {
|
|
109
|
+
++i;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
uint64_t value = 0;
|
|
113
|
+
while (i < len && ptr[i] >= '0' && ptr[i] <= '9') {
|
|
114
|
+
value = value * 10 + static_cast<uint64_t>(ptr[i] - '0');
|
|
115
|
+
++i;
|
|
116
|
+
}
|
|
117
|
+
out.push_back(value);
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Offset just past `"key":` in a flat, compact JSON object, or
|
|
123
|
+
/// `std::string::npos` if `key` is absent.
|
|
124
|
+
size_t fieldValueOffset(const std::string& json, const char* key) {
|
|
125
|
+
std::string needle = std::string("\"") + key + "\":";
|
|
126
|
+
size_t pos = json.find(needle);
|
|
127
|
+
return pos == std::string::npos ? std::string::npos : pos + needle.size();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
uint64_t parseUnsignedAt(const std::string& json, size_t pos) {
|
|
131
|
+
uint64_t value = 0;
|
|
132
|
+
while (pos < json.size() && json[pos] >= '0' && json[pos] <= '9') {
|
|
133
|
+
value = value * 10 + static_cast<uint64_t>(json[pos] - '0');
|
|
134
|
+
++pos;
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
std::vector<uint8_t> parseByteArrayAt(const std::string& json, size_t pos) {
|
|
140
|
+
std::vector<uint8_t> out;
|
|
141
|
+
while (pos < json.size() && json[pos] != '[') ++pos;
|
|
142
|
+
if (pos >= json.size()) return out;
|
|
143
|
+
++pos;
|
|
144
|
+
while (pos < json.size() && json[pos] != ']') {
|
|
145
|
+
if (json[pos] < '0' || json[pos] > '9') {
|
|
146
|
+
++pos;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
uint64_t value = 0;
|
|
150
|
+
while (pos < json.size() && json[pos] >= '0' && json[pos] <= '9') {
|
|
151
|
+
value = value * 10 + static_cast<uint64_t>(json[pos] - '0');
|
|
152
|
+
++pos;
|
|
153
|
+
}
|
|
154
|
+
out.push_back(static_cast<uint8_t>(value));
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// `{"kind":<n>,"bytes":[<n>,...]}` — the wire shape `SecretStore.save`
|
|
160
|
+
/// receives; `kind` is already carried as a separate FFI argument so only
|
|
161
|
+
/// `bytes` needs to be read back out.
|
|
162
|
+
std::vector<uint8_t> decodeSecretValueBytes(const uint8_t* ptr, size_t len) {
|
|
163
|
+
std::string json(reinterpret_cast<const char*>(ptr), len);
|
|
164
|
+
size_t pos = fieldValueOffset(json, "bytes");
|
|
165
|
+
if (pos == std::string::npos) return {};
|
|
166
|
+
return parseByteArrayAt(json, pos);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// `{"secret_id":"...","version":<n>,"bytes":[<n>,...]}` — the wire shape
|
|
170
|
+
/// `ShareStore.save` receives. `secret_id` duplicates the FFI's own
|
|
171
|
+
/// `secret_id` argument, so only `version` and `bytes` are decoded.
|
|
172
|
+
struct DecodedShare {
|
|
173
|
+
uint32_t version;
|
|
174
|
+
std::vector<uint8_t> bytes;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
DecodedShare decodeShareRecord(const uint8_t* ptr, size_t len) {
|
|
178
|
+
std::string json(reinterpret_cast<const char*>(ptr), len);
|
|
179
|
+
size_t versionPos = fieldValueOffset(json, "version");
|
|
180
|
+
size_t bytesPos = fieldValueOffset(json, "bytes");
|
|
181
|
+
DecodedShare out{};
|
|
182
|
+
out.version =
|
|
183
|
+
versionPos == std::string::npos ? 0 : static_cast<uint32_t>(parseUnsignedAt(json, versionPos));
|
|
184
|
+
out.bytes = bytesPos == std::string::npos ? std::vector<uint8_t>{} : parseByteArrayAt(json, bytesPos);
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/// True only for a non-empty run of ASCII decimal digits.
|
|
189
|
+
///
|
|
190
|
+
/// Every `u64` id crosses this boundary as a decimal string, and the wire
|
|
191
|
+
/// JSON built below splices those strings in directly — as a bare integer
|
|
192
|
+
/// literal for a channel id, and inside quotes for a secret id — rather than
|
|
193
|
+
/// routing them through a JS number, which loses precision above 2^53. That
|
|
194
|
+
/// splice is only sound if the string really is nothing but digits: any other
|
|
195
|
+
/// content would produce malformed JSON, or inject application-chosen
|
|
196
|
+
/// structure into the document Rust then deserialises. The store contract
|
|
197
|
+
/// already says these are `u64`-as-decimal-string, so anything else is a
|
|
198
|
+
/// malformed store return, not a value to interpret.
|
|
199
|
+
bool isDecimalDigits(const std::string& text) {
|
|
200
|
+
if (text.empty()) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
for (char c : text) {
|
|
204
|
+
if (c < '0' || c > '9') {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// Append a byte range as a bare JSON array of decimal integers.
|
|
212
|
+
void appendByteArrayJson(std::string& text, const uint8_t* bytes, size_t len) {
|
|
213
|
+
text += '[';
|
|
214
|
+
for (size_t i = 0; i < len; ++i) {
|
|
215
|
+
if (i != 0) text += ',';
|
|
216
|
+
text += std::to_string(bytes[i]);
|
|
217
|
+
}
|
|
218
|
+
text += ']';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
std::vector<uint8_t> secretValueRecordJson(uint32_t kind, const uint8_t* bytes, size_t len) {
|
|
222
|
+
std::string text = "{\"kind\":" + std::to_string(kind) + ",\"bytes\":";
|
|
223
|
+
appendByteArrayJson(text, bytes, len);
|
|
224
|
+
text += '}';
|
|
225
|
+
return std::vector<uint8_t>(text.begin(), text.end());
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
jsi::Array u32VectorToJsArray(jsi::Runtime& rt, const std::vector<uint32_t>& values) {
|
|
229
|
+
jsi::Array arr(rt, values.size());
|
|
230
|
+
for (size_t i = 0; i < values.size(); ++i) {
|
|
231
|
+
arr.setValueAtIndex(rt, i, jsi::Value(static_cast<int>(values[i])));
|
|
232
|
+
}
|
|
233
|
+
return arr;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
jsi::Array u64VectorToJsStringArray(jsi::Runtime& rt, const std::vector<uint64_t>& ids) {
|
|
237
|
+
jsi::Array arr(rt, ids.size());
|
|
238
|
+
for (size_t i = 0; i < ids.size(); ++i) {
|
|
239
|
+
arr.setValueAtIndex(rt, i, jsi::Value(rt, jsi::String::createFromUtf8(rt, std::to_string(ids[i]))));
|
|
240
|
+
}
|
|
241
|
+
return arr;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// `ResultConverter`s. Each encodes a settled JS value into the private
|
|
245
|
+
// `CallResult.bytes` payload the calling extern "C" function decodes once
|
|
246
|
+
// `callSync` returns — a channel this file controls end to end, so each
|
|
247
|
+
// converter is free to define its own encoding.
|
|
248
|
+
|
|
249
|
+
/// Load-family convention: `null`/`undefined` is "absent" (code `1`); any
|
|
250
|
+
/// other value is read as bytes and passed through unmodified (code `0`).
|
|
251
|
+
/// Valid whenever the wire's out-parameter bytes are exactly what the JS
|
|
252
|
+
/// store returns, with no re-encoding needed.
|
|
253
|
+
CallResult toCallResult(jsi::Runtime& rt, const jsi::Value& value) {
|
|
254
|
+
if (value.isNull() || value.isUndefined()) {
|
|
255
|
+
return CallResult{1, {}};
|
|
256
|
+
}
|
|
257
|
+
ByteView view = asBytes(rt, value);
|
|
258
|
+
return CallResult{0, std::vector<uint8_t>(view.ptr, view.ptr + view.len)};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/// For `save`/`remove`/`link`/`send`-family methods: the resolved value is
|
|
262
|
+
/// `undefined` (a `Promise<void>`) and carries no information. Success is
|
|
263
|
+
/// always code `0`; only a rejection (handled by `settleFromPromise`'s
|
|
264
|
+
/// `onErr`) becomes a backend failure. Never routes a value through the
|
|
265
|
+
/// load-family "null means absent" convention.
|
|
266
|
+
CallResult toVoidResult(jsi::Runtime&, const jsi::Value&) { return CallResult{0, {}}; }
|
|
267
|
+
|
|
268
|
+
/// Boolean-shaped outputs (`ChannelStore.remove`, `StateStore.remove`):
|
|
269
|
+
/// encode `true` as one byte, `false` as zero bytes. The calling function
|
|
270
|
+
/// derives `out_existed` / `out_removed` from `result.bytes.empty()`.
|
|
271
|
+
///
|
|
272
|
+
/// A non-boolean is a malformed store return, not a `false`: reporting it as
|
|
273
|
+
/// `false` would tell the protocol that a record it just asked to delete was
|
|
274
|
+
/// never there, which is a claim about storage this binding has no grounds to
|
|
275
|
+
/// make. It is a backend failure.
|
|
276
|
+
CallResult toBoolResult(jsi::Runtime&, const jsi::Value& value) {
|
|
277
|
+
if (!value.isBool()) {
|
|
278
|
+
return CallResult{kBackendFailure, {}};
|
|
279
|
+
}
|
|
280
|
+
return CallResult{0, value.asBool() ? std::vector<uint8_t>{1} : std::vector<uint8_t>{}};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/// `ShareStore.latestVersion(): Promise<number | null>`. Absent (`null`)
|
|
284
|
+
/// encodes as zero bytes; a version encodes as 4 little-endian bytes so the
|
|
285
|
+
/// calling function can rebuild `out_has_version` / `out_version`.
|
|
286
|
+
CallResult toVersionResult(jsi::Runtime&, const jsi::Value& value) {
|
|
287
|
+
if (value.isNull() || value.isUndefined()) {
|
|
288
|
+
return CallResult{0, {}};
|
|
289
|
+
}
|
|
290
|
+
auto version = static_cast<uint32_t>(value.asNumber());
|
|
291
|
+
std::vector<uint8_t> bytes(4);
|
|
292
|
+
bytes[0] = static_cast<uint8_t>(version & 0xFF);
|
|
293
|
+
bytes[1] = static_cast<uint8_t>((version >> 8) & 0xFF);
|
|
294
|
+
bytes[2] = static_cast<uint8_t>((version >> 16) & 0xFF);
|
|
295
|
+
bytes[3] = static_cast<uint8_t>((version >> 24) & 0xFF);
|
|
296
|
+
return CallResult{0, bytes};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// `ChannelStore.linkedChannels(): Promise<string[]>`. Rust decodes this as
|
|
300
|
+
/// a bare JSON array of `u64`; an empty JS array is written as literally
|
|
301
|
+
/// empty bytes so Rust's own "nothing linked" fallback (defaulting to the
|
|
302
|
+
/// channel itself) applies, matching what an absent record would produce.
|
|
303
|
+
///
|
|
304
|
+
/// A non-array return is a backend failure. Reporting it as the empty result
|
|
305
|
+
/// would trigger that same fallback and silently narrow the set of channels a
|
|
306
|
+
/// share is linked to, which is a protocol-visible outcome invented by the
|
|
307
|
+
/// binding rather than read from storage.
|
|
308
|
+
CallResult toLinkedChannelsResult(jsi::Runtime& rt, const jsi::Value& value) {
|
|
309
|
+
if (!value.isObject()) {
|
|
310
|
+
return CallResult{kBackendFailure, {}};
|
|
311
|
+
}
|
|
312
|
+
jsi::Array arr = value.asObject(rt).asArray(rt);
|
|
313
|
+
size_t n = arr.size(rt);
|
|
314
|
+
if (n == 0) {
|
|
315
|
+
return CallResult{0, {}};
|
|
316
|
+
}
|
|
317
|
+
std::string text = "[";
|
|
318
|
+
for (size_t i = 0; i < n; ++i) {
|
|
319
|
+
if (i != 0) text += ',';
|
|
320
|
+
// Each element is a decimal-digit channel id string — already a valid
|
|
321
|
+
// bare JSON integer literal, so splice it in verbatim rather than
|
|
322
|
+
// routing it through a JS number, which loses precision above 2^53.
|
|
323
|
+
std::string channelId = arr.getValueAtIndex(rt, i).asString(rt).utf8(rt);
|
|
324
|
+
if (!isDecimalDigits(channelId)) {
|
|
325
|
+
return CallResult{kBackendFailure, {}};
|
|
326
|
+
}
|
|
327
|
+
text += channelId;
|
|
328
|
+
}
|
|
329
|
+
text += ']';
|
|
330
|
+
return CallResult{0, std::vector<uint8_t>(text.begin(), text.end())};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/// `ShareStore.{load,loadMany,loadAll}(): Promise<Share[]>`. Encodes the
|
|
334
|
+
/// resolved array as the wire's `Vec<ShareRecord>` JSON.
|
|
335
|
+
///
|
|
336
|
+
/// A non-array return is a backend failure, never an empty list: "zero
|
|
337
|
+
/// shares, successfully" is exactly the report that turns an unreadable store
|
|
338
|
+
/// into a failed recovery with no error to trace it back to.
|
|
339
|
+
CallResult toShareListResult(jsi::Runtime& rt, const jsi::Value& value) {
|
|
340
|
+
if (!value.isObject()) {
|
|
341
|
+
return CallResult{kBackendFailure, {}};
|
|
342
|
+
}
|
|
343
|
+
jsi::Array arr = value.asObject(rt).asArray(rt);
|
|
344
|
+
size_t n = arr.size(rt);
|
|
345
|
+
std::string text = "[";
|
|
346
|
+
for (size_t i = 0; i < n; ++i) {
|
|
347
|
+
if (i != 0) text += ',';
|
|
348
|
+
jsi::Object entry = arr.getValueAtIndex(rt, i).asObject(rt);
|
|
349
|
+
std::string secretIdStr = entry.getProperty(rt, "secretId").asString(rt).utf8(rt);
|
|
350
|
+
if (!isDecimalDigits(secretIdStr)) {
|
|
351
|
+
return CallResult{kBackendFailure, {}};
|
|
352
|
+
}
|
|
353
|
+
auto version = static_cast<uint32_t>(entry.getProperty(rt, "version").asNumber());
|
|
354
|
+
ByteView bytes = asBytes(rt, entry.getProperty(rt, "bytes"));
|
|
355
|
+
text += "{\"secret_id\":\"" + secretIdStr + "\",\"version\":" + std::to_string(version) +
|
|
356
|
+
",\"bytes\":";
|
|
357
|
+
appendByteArrayJson(text, bytes.ptr, bytes.len);
|
|
358
|
+
text += '}';
|
|
359
|
+
}
|
|
360
|
+
text += ']';
|
|
361
|
+
return CallResult{0, std::vector<uint8_t>(text.begin(), text.end())};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/// `SecretStore.load(): Promise<Uint8Array | null | undefined>`. `kind` is
|
|
365
|
+
/// captured by value so the returned bytes can be re-wrapped as the wire's
|
|
366
|
+
/// `SecretValueRecord`, whose `kind` field the FFI struct's `load` does not
|
|
367
|
+
/// otherwise carry back out.
|
|
368
|
+
ResultConverter makeSecretLoadResult(uint32_t kind) {
|
|
369
|
+
return [kind](jsi::Runtime& rt, const jsi::Value& value) -> CallResult {
|
|
370
|
+
if (value.isNull() || value.isUndefined()) {
|
|
371
|
+
return CallResult{1, {}};
|
|
372
|
+
}
|
|
373
|
+
ByteView bytes = asBytes(rt, value);
|
|
374
|
+
return CallResult{0, secretValueRecordJson(kind, bytes.ptr, bytes.len)};
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/// `StateStore.loadAll(): Promise<Uint8Array[]>`. Each element is already
|
|
379
|
+
/// the exact item-JSON blob the wire expects; splice the raw bytes into an
|
|
380
|
+
/// array rather than re-parsing and re-serialising them.
|
|
381
|
+
///
|
|
382
|
+
/// A non-array return is a backend failure, never an empty list: an empty
|
|
383
|
+
/// list is a truthful claim that no state was persisted, and reporting it for
|
|
384
|
+
/// a store that simply did not answer correctly would let a restore silently
|
|
385
|
+
/// come up blank.
|
|
386
|
+
CallResult toStateLoadAllResult(jsi::Runtime& rt, const jsi::Value& value) {
|
|
387
|
+
if (!value.isObject()) {
|
|
388
|
+
return CallResult{kBackendFailure, {}};
|
|
389
|
+
}
|
|
390
|
+
jsi::Array arr = value.asObject(rt).asArray(rt);
|
|
391
|
+
size_t n = arr.size(rt);
|
|
392
|
+
std::string text = "[";
|
|
393
|
+
for (size_t i = 0; i < n; ++i) {
|
|
394
|
+
if (i != 0) text += ',';
|
|
395
|
+
ByteView bv = asBytes(rt, arr.getValueAtIndex(rt, i));
|
|
396
|
+
text.append(reinterpret_cast<const char*>(bv.ptr), bv.len);
|
|
397
|
+
}
|
|
398
|
+
text += ']';
|
|
399
|
+
return CallResult{0, std::vector<uint8_t>(text.begin(), text.end())};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// `UserSecrets` JSON <-> JS object conversion. Unlike the flat integer-only
|
|
403
|
+
// records above, `UserSecrets` carries application-controlled `name` /
|
|
404
|
+
// `description` strings, so building or reading its wire JSON goes through
|
|
405
|
+
// the runtime's own `JSON.parse` / `JSON.stringify` rather than a hand-rolled
|
|
406
|
+
// scanner — the only place in this file that needs real string escaping.
|
|
407
|
+
|
|
408
|
+
jsi::Value jsonParseUtf8(jsi::Runtime& rt, const uint8_t* bytes, size_t len) {
|
|
409
|
+
jsi::Object json = rt.global().getPropertyAsObject(rt, "JSON");
|
|
410
|
+
jsi::Function parse = json.getPropertyAsFunction(rt, "parse");
|
|
411
|
+
std::string text(reinterpret_cast<const char*>(bytes), len);
|
|
412
|
+
return parse.call(rt, jsi::Value(rt, jsi::String::createFromUtf8(rt, text)));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
std::vector<uint8_t> jsonStringifyToUtf8(jsi::Runtime& rt, const jsi::Value& value) {
|
|
416
|
+
jsi::Object json = rt.global().getPropertyAsObject(rt, "JSON");
|
|
417
|
+
jsi::Function stringify = json.getPropertyAsFunction(rt, "stringify");
|
|
418
|
+
jsi::Value result = stringify.call(rt, value);
|
|
419
|
+
std::string text = result.asString(rt).utf8(rt);
|
|
420
|
+
return std::vector<uint8_t>(text.begin(), text.end());
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
jsi::Array bytesToNumberArray(jsi::Runtime& rt, ByteView view) {
|
|
424
|
+
jsi::Array arr(rt, view.len);
|
|
425
|
+
for (size_t i = 0; i < view.len; ++i) {
|
|
426
|
+
arr.setValueAtIndex(rt, i, jsi::Value(static_cast<int>(view.ptr[i])));
|
|
427
|
+
}
|
|
428
|
+
return arr;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
std::vector<uint8_t> numberArrayToBytes(jsi::Runtime& rt, jsi::Array arr) {
|
|
432
|
+
size_t n = arr.size(rt);
|
|
433
|
+
std::vector<uint8_t> out;
|
|
434
|
+
out.reserve(n);
|
|
435
|
+
for (size_t i = 0; i < n; ++i) {
|
|
436
|
+
out.push_back(static_cast<uint8_t>(arr.getValueAtIndex(rt, i).asNumber()));
|
|
437
|
+
}
|
|
438
|
+
return out;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/// `UserSecretStore.loadLatest(): Promise<UserSecrets | null | undefined>`.
|
|
442
|
+
CallResult toUserSecretsResult(jsi::Runtime& rt, const jsi::Value& value) {
|
|
443
|
+
if (value.isNull() || value.isUndefined()) {
|
|
444
|
+
return CallResult{1, {}};
|
|
445
|
+
}
|
|
446
|
+
jsi::Object src = value.asObject(rt);
|
|
447
|
+
jsi::Object wire(rt);
|
|
448
|
+
wire.setProperty(rt, "version", jsi::Value(rt, src.getProperty(rt, "version")));
|
|
449
|
+
jsi::Array srcSecrets = src.getProperty(rt, "secrets").asObject(rt).asArray(rt);
|
|
450
|
+
size_t n = srcSecrets.size(rt);
|
|
451
|
+
jsi::Array wireSecrets(rt, n);
|
|
452
|
+
for (size_t i = 0; i < n; ++i) {
|
|
453
|
+
jsi::Object entry = srcSecrets.getValueAtIndex(rt, i).asObject(rt);
|
|
454
|
+
jsi::Object wireEntry(rt);
|
|
455
|
+
wireEntry.setProperty(rt, "id", jsi::Value(rt, bytesToNumberArray(rt, asBytes(rt, entry.getProperty(rt, "id")))));
|
|
456
|
+
wireEntry.setProperty(rt, "name", jsi::Value(rt, entry.getProperty(rt, "name")));
|
|
457
|
+
wireEntry.setProperty(
|
|
458
|
+
rt, "data", jsi::Value(rt, bytesToNumberArray(rt, asBytes(rt, entry.getProperty(rt, "data")))));
|
|
459
|
+
wireSecrets.setValueAtIndex(rt, i, jsi::Value(rt, wireEntry));
|
|
460
|
+
}
|
|
461
|
+
wire.setProperty(rt, "secrets", jsi::Value(rt, wireSecrets));
|
|
462
|
+
if (src.hasProperty(rt, "description")) {
|
|
463
|
+
jsi::Value description = src.getProperty(rt, "description");
|
|
464
|
+
if (!description.isUndefined()) {
|
|
465
|
+
wire.setProperty(rt, "description", description);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return CallResult{0, jsonStringifyToUtf8(rt, jsi::Value(rt, wire))};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/// Decode `UserSecretStore.saveLatest`'s incoming wire JSON into the JS
|
|
472
|
+
/// `UserSecrets` shape `src/types.ts` declares.
|
|
473
|
+
jsi::Value buildUserSecretsFromWire(jsi::Runtime& rt, const uint8_t* ptr, size_t len) {
|
|
474
|
+
jsi::Value parsed = jsonParseUtf8(rt, ptr, len);
|
|
475
|
+
jsi::Object src = parsed.asObject(rt);
|
|
476
|
+
jsi::Object out(rt);
|
|
477
|
+
out.setProperty(rt, "version", jsi::Value(rt, src.getProperty(rt, "version")));
|
|
478
|
+
jsi::Array srcSecrets = src.getProperty(rt, "secrets").asObject(rt).asArray(rt);
|
|
479
|
+
size_t n = srcSecrets.size(rt);
|
|
480
|
+
jsi::Array outSecrets(rt, n);
|
|
481
|
+
for (size_t i = 0; i < n; ++i) {
|
|
482
|
+
jsi::Object entry = srcSecrets.getValueAtIndex(rt, i).asObject(rt);
|
|
483
|
+
jsi::Object outEntry(rt);
|
|
484
|
+
std::vector<uint8_t> idBytes =
|
|
485
|
+
numberArrayToBytes(rt, entry.getProperty(rt, "id").asObject(rt).asArray(rt));
|
|
486
|
+
std::vector<uint8_t> dataBytes =
|
|
487
|
+
numberArrayToBytes(rt, entry.getProperty(rt, "data").asObject(rt).asArray(rt));
|
|
488
|
+
outEntry.setProperty(rt, "id", toUint8ArrayVal(rt, idBytes));
|
|
489
|
+
outEntry.setProperty(rt, "name", jsi::Value(rt, entry.getProperty(rt, "name")));
|
|
490
|
+
outEntry.setProperty(rt, "data", toUint8ArrayVal(rt, dataBytes));
|
|
491
|
+
outSecrets.setValueAtIndex(rt, i, jsi::Value(rt, outEntry));
|
|
492
|
+
}
|
|
493
|
+
out.setProperty(rt, "secrets", jsi::Value(rt, outSecrets));
|
|
494
|
+
if (src.hasProperty(rt, "description")) {
|
|
495
|
+
jsi::Value description = src.getProperty(rt, "description");
|
|
496
|
+
if (!description.isUndefined()) {
|
|
497
|
+
out.setProperty(rt, "description", description);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return jsi::Value(rt, out);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/// Releases a buffer this binding handed to Rust through an out-parameter.
|
|
504
|
+
/// Shared by every struct's `free_buffer` field — the signature is
|
|
505
|
+
/// identical across all six.
|
|
506
|
+
extern "C" void storeFreeBuffer(void* /*userData*/, uint8_t* ptr, size_t len) { freeBytes(ptr, len); }
|
|
507
|
+
|
|
508
|
+
/// `ChannelStore.load(secretId, channelId, replicaId) -> Uint8Array | null`
|
|
509
|
+
extern "C" int32_t channelStoreLoad(void* userData, uint64_t secretId, uint64_t channelId,
|
|
510
|
+
uint64_t replicaId, uint8_t** outPtr, size_t* outLen) {
|
|
511
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
512
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
513
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
514
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
515
|
+
auto keepAlive = self->shared_from_this();
|
|
516
|
+
CallResult result = keepAlive->bridge().callSync(
|
|
517
|
+
[keepAlive, secretId, channelId, replicaId](std::function<void(CallResult)> settle) {
|
|
518
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
519
|
+
auto store = keepAlive->channelStore();
|
|
520
|
+
auto method = store->getPropertyAsFunction(rt, "load");
|
|
521
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId),
|
|
522
|
+
idVal(rt, replicaId));
|
|
523
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toCallResult);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
if (result.code == 0) {
|
|
527
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
528
|
+
return kBackendFailure;
|
|
529
|
+
}
|
|
530
|
+
} else {
|
|
531
|
+
*outPtr = nullptr;
|
|
532
|
+
*outLen = 0;
|
|
533
|
+
}
|
|
534
|
+
return result.code;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/// `ChannelStore.save(secretId, channelId, replicaId, bytes) -> void`
|
|
538
|
+
extern "C" int32_t channelStoreSave(void* userData, uint64_t secretId, uint64_t channelId,
|
|
539
|
+
uint64_t replicaId, const uint8_t* bytes, size_t len) {
|
|
540
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
541
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
542
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
543
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
544
|
+
auto keepAlive = self->shared_from_this();
|
|
545
|
+
std::vector<uint8_t> payload(bytes, bytes + len);
|
|
546
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId, channelId, replicaId,
|
|
547
|
+
payload](std::function<void(CallResult)> settle) {
|
|
548
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
549
|
+
auto store = keepAlive->channelStore();
|
|
550
|
+
auto method = store->getPropertyAsFunction(rt, "save");
|
|
551
|
+
jsi::Value promise =
|
|
552
|
+
method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId), idVal(rt, replicaId),
|
|
553
|
+
toUint8ArrayVal(rt, payload));
|
|
554
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
555
|
+
});
|
|
556
|
+
return result.code;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/// `ChannelStore.remove(secretId, channelId, replicaId) -> boolean`
|
|
560
|
+
extern "C" int32_t channelStoreRemove(void* userData, uint64_t secretId, uint64_t channelId,
|
|
561
|
+
uint64_t replicaId, uint32_t* outExisted) {
|
|
562
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
563
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
564
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
565
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
566
|
+
auto keepAlive = self->shared_from_this();
|
|
567
|
+
CallResult result = keepAlive->bridge().callSync(
|
|
568
|
+
[keepAlive, secretId, channelId, replicaId](std::function<void(CallResult)> settle) {
|
|
569
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
570
|
+
auto store = keepAlive->channelStore();
|
|
571
|
+
auto method = store->getPropertyAsFunction(rt, "remove");
|
|
572
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId),
|
|
573
|
+
idVal(rt, replicaId));
|
|
574
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toBoolResult);
|
|
575
|
+
});
|
|
576
|
+
*outExisted = result.bytes.empty() ? 0 : 1;
|
|
577
|
+
return result.code;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/// `ChannelStore.listHelpers(secretId) -> Uint8Array | null`
|
|
581
|
+
extern "C" int32_t channelStoreListHelpers(void* userData, uint64_t secretId, uint8_t** outPtr,
|
|
582
|
+
size_t* outLen) {
|
|
583
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
584
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
585
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
586
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
587
|
+
auto keepAlive = self->shared_from_this();
|
|
588
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId](std::function<void(CallResult)> settle) {
|
|
589
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
590
|
+
auto store = keepAlive->channelStore();
|
|
591
|
+
auto method = store->getPropertyAsFunction(rt, "listHelpers");
|
|
592
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId));
|
|
593
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toCallResult);
|
|
594
|
+
});
|
|
595
|
+
if (result.code == 0) {
|
|
596
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
597
|
+
return kBackendFailure;
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
*outPtr = nullptr;
|
|
601
|
+
*outLen = 0;
|
|
602
|
+
}
|
|
603
|
+
return result.code;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/// `ChannelStore.listReplicas(secretId) -> Uint8Array | null`
|
|
607
|
+
extern "C" int32_t channelStoreListReplicas(void* userData, uint64_t secretId, uint8_t** outPtr,
|
|
608
|
+
size_t* outLen) {
|
|
609
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
610
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
611
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
612
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
613
|
+
auto keepAlive = self->shared_from_this();
|
|
614
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId](std::function<void(CallResult)> settle) {
|
|
615
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
616
|
+
auto store = keepAlive->channelStore();
|
|
617
|
+
auto method = store->getPropertyAsFunction(rt, "listReplicas");
|
|
618
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId));
|
|
619
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toCallResult);
|
|
620
|
+
});
|
|
621
|
+
if (result.code == 0) {
|
|
622
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
623
|
+
return kBackendFailure;
|
|
624
|
+
}
|
|
625
|
+
} else {
|
|
626
|
+
*outPtr = nullptr;
|
|
627
|
+
*outLen = 0;
|
|
628
|
+
}
|
|
629
|
+
return result.code;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/// `ChannelStore.linkChannel(secretId, channelId, linkedChannelId) -> void`
|
|
633
|
+
extern "C" int32_t channelStoreLinkChannel(void* userData, uint64_t secretId, uint64_t a, uint64_t b) {
|
|
634
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
635
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
636
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
637
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
638
|
+
auto keepAlive = self->shared_from_this();
|
|
639
|
+
CallResult result =
|
|
640
|
+
keepAlive->bridge().callSync([keepAlive, secretId, a, b](std::function<void(CallResult)> settle) {
|
|
641
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
642
|
+
auto store = keepAlive->channelStore();
|
|
643
|
+
auto method = store->getPropertyAsFunction(rt, "linkChannel");
|
|
644
|
+
jsi::Value promise =
|
|
645
|
+
method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, a), idVal(rt, b));
|
|
646
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
647
|
+
});
|
|
648
|
+
return result.code;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/// `ChannelStore.linkedChannels(secretId, channelId) -> string[]`
|
|
652
|
+
extern "C" int32_t channelStoreLinkedChannels(void* userData, uint64_t secretId, uint64_t channelId,
|
|
653
|
+
uint8_t** outPtr, size_t* outLen) {
|
|
654
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
655
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
656
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
657
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
658
|
+
auto keepAlive = self->shared_from_this();
|
|
659
|
+
CallResult result =
|
|
660
|
+
keepAlive->bridge().callSync([keepAlive, secretId, channelId](std::function<void(CallResult)> settle) {
|
|
661
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
662
|
+
auto store = keepAlive->channelStore();
|
|
663
|
+
auto method = store->getPropertyAsFunction(rt, "linkedChannels");
|
|
664
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId));
|
|
665
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toLinkedChannelsResult);
|
|
666
|
+
});
|
|
667
|
+
if (result.code == 0) {
|
|
668
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
669
|
+
return kBackendFailure;
|
|
670
|
+
}
|
|
671
|
+
} else {
|
|
672
|
+
*outPtr = nullptr;
|
|
673
|
+
*outLen = 0;
|
|
674
|
+
}
|
|
675
|
+
return result.code;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/// `SecretStore.load(secretId, channelId, kind) -> Uint8Array | null`
|
|
679
|
+
extern "C" int32_t secretStoreLoad(void* userData, uint64_t secretId, uint64_t channelId, uint32_t kind,
|
|
680
|
+
uint8_t** outPtr, size_t* outLen) {
|
|
681
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
682
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
683
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
684
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
685
|
+
auto keepAlive = self->shared_from_this();
|
|
686
|
+
CallResult result = keepAlive->bridge().callSync(
|
|
687
|
+
[keepAlive, secretId, channelId, kind](std::function<void(CallResult)> settle) {
|
|
688
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
689
|
+
auto store = keepAlive->secretStore();
|
|
690
|
+
auto method = store->getPropertyAsFunction(rt, "load");
|
|
691
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId),
|
|
692
|
+
jsi::Value(static_cast<int>(kind)));
|
|
693
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), makeSecretLoadResult(kind));
|
|
694
|
+
});
|
|
695
|
+
if (result.code == 0) {
|
|
696
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
697
|
+
return kBackendFailure;
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
*outPtr = nullptr;
|
|
701
|
+
*outLen = 0;
|
|
702
|
+
}
|
|
703
|
+
return result.code;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/// `SecretStore.save(secretId, channelId, kind, value) -> void`
|
|
707
|
+
extern "C" int32_t secretStoreSave(void* userData, uint64_t secretId, uint64_t channelId, uint32_t kind,
|
|
708
|
+
const uint8_t* bytes, size_t len) {
|
|
709
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
710
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
711
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
712
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
713
|
+
auto keepAlive = self->shared_from_this();
|
|
714
|
+
std::vector<uint8_t> payload = decodeSecretValueBytes(bytes, len);
|
|
715
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId, channelId, kind,
|
|
716
|
+
payload](std::function<void(CallResult)> settle) {
|
|
717
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
718
|
+
auto store = keepAlive->secretStore();
|
|
719
|
+
auto method = store->getPropertyAsFunction(rt, "save");
|
|
720
|
+
jsi::Value promise =
|
|
721
|
+
method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId),
|
|
722
|
+
jsi::Value(static_cast<int>(kind)), toUint8ArrayVal(rt, payload));
|
|
723
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
724
|
+
});
|
|
725
|
+
return result.code;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/// `SecretStore.remove(secretId, channelId, kind) -> void`
|
|
729
|
+
extern "C" int32_t secretStoreRemove(void* userData, uint64_t secretId, uint64_t channelId,
|
|
730
|
+
uint32_t kind) {
|
|
731
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
732
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
733
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
734
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
735
|
+
auto keepAlive = self->shared_from_this();
|
|
736
|
+
CallResult result = keepAlive->bridge().callSync(
|
|
737
|
+
[keepAlive, secretId, channelId, kind](std::function<void(CallResult)> settle) {
|
|
738
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
739
|
+
auto store = keepAlive->secretStore();
|
|
740
|
+
auto method = store->getPropertyAsFunction(rt, "remove");
|
|
741
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId),
|
|
742
|
+
jsi::Value(static_cast<int>(kind)));
|
|
743
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
744
|
+
});
|
|
745
|
+
return result.code;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/// `ShareStore.load(secretId, channelId, versions) -> Share[]`
|
|
749
|
+
extern "C" int32_t shareStoreLoad(void* userData, uint64_t secretId, uint64_t channelId,
|
|
750
|
+
const uint8_t* versionsJsonPtr, size_t versionsJsonLen,
|
|
751
|
+
uint8_t** outPtr, size_t* outLen) {
|
|
752
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
753
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
754
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
755
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
756
|
+
auto keepAlive = self->shared_from_this();
|
|
757
|
+
std::vector<uint64_t> raw = parseUnsignedJsonArray(versionsJsonPtr, versionsJsonLen);
|
|
758
|
+
std::vector<uint32_t> versions(raw.begin(), raw.end());
|
|
759
|
+
CallResult result = keepAlive->bridge().callSync(
|
|
760
|
+
[keepAlive, secretId, channelId, versions](std::function<void(CallResult)> settle) {
|
|
761
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
762
|
+
auto store = keepAlive->shareStore();
|
|
763
|
+
auto method = store->getPropertyAsFunction(rt, "load");
|
|
764
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId),
|
|
765
|
+
jsi::Value(rt, u32VectorToJsArray(rt, versions)));
|
|
766
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toShareListResult);
|
|
767
|
+
});
|
|
768
|
+
if (result.code == 0) {
|
|
769
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
770
|
+
return kBackendFailure;
|
|
771
|
+
}
|
|
772
|
+
} else {
|
|
773
|
+
*outPtr = nullptr;
|
|
774
|
+
*outLen = 0;
|
|
775
|
+
}
|
|
776
|
+
return result.code;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/// `ShareStore.loadMany(secretId, channelIds, versions) -> Share[]`
|
|
780
|
+
extern "C" int32_t shareStoreLoadMany(void* userData, uint64_t secretId, const uint8_t* channelIdsJsonPtr,
|
|
781
|
+
size_t channelIdsJsonLen, const uint8_t* versionsJsonPtr,
|
|
782
|
+
size_t versionsJsonLen, uint8_t** outPtr, size_t* outLen) {
|
|
783
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
784
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
785
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
786
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
787
|
+
auto keepAlive = self->shared_from_this();
|
|
788
|
+
std::vector<uint64_t> channelIds = parseUnsignedJsonArray(channelIdsJsonPtr, channelIdsJsonLen);
|
|
789
|
+
std::vector<uint64_t> rawVersions = parseUnsignedJsonArray(versionsJsonPtr, versionsJsonLen);
|
|
790
|
+
std::vector<uint32_t> versions(rawVersions.begin(), rawVersions.end());
|
|
791
|
+
CallResult result = keepAlive->bridge().callSync(
|
|
792
|
+
[keepAlive, secretId, channelIds, versions](std::function<void(CallResult)> settle) {
|
|
793
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
794
|
+
auto store = keepAlive->shareStore();
|
|
795
|
+
auto method = store->getPropertyAsFunction(rt, "loadMany");
|
|
796
|
+
jsi::Value promise =
|
|
797
|
+
method.callWithThis(rt, *store, idVal(rt, secretId),
|
|
798
|
+
jsi::Value(rt, u64VectorToJsStringArray(rt, channelIds)),
|
|
799
|
+
jsi::Value(rt, u32VectorToJsArray(rt, versions)));
|
|
800
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toShareListResult);
|
|
801
|
+
});
|
|
802
|
+
if (result.code == 0) {
|
|
803
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
804
|
+
return kBackendFailure;
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
*outPtr = nullptr;
|
|
808
|
+
*outLen = 0;
|
|
809
|
+
}
|
|
810
|
+
return result.code;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/// `ShareStore.loadAll(secretId, channelIds) -> Share[]`
|
|
814
|
+
extern "C" int32_t shareStoreLoadAll(void* userData, uint64_t secretId, const uint8_t* channelIdsJsonPtr,
|
|
815
|
+
size_t channelIdsJsonLen, uint8_t** outPtr, size_t* outLen) {
|
|
816
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
817
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
818
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
819
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
820
|
+
auto keepAlive = self->shared_from_this();
|
|
821
|
+
std::vector<uint64_t> channelIds = parseUnsignedJsonArray(channelIdsJsonPtr, channelIdsJsonLen);
|
|
822
|
+
CallResult result = keepAlive->bridge().callSync(
|
|
823
|
+
[keepAlive, secretId, channelIds](std::function<void(CallResult)> settle) {
|
|
824
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
825
|
+
auto store = keepAlive->shareStore();
|
|
826
|
+
auto method = store->getPropertyAsFunction(rt, "loadAll");
|
|
827
|
+
jsi::Value promise = method.callWithThis(
|
|
828
|
+
rt, *store, idVal(rt, secretId), jsi::Value(rt, u64VectorToJsStringArray(rt, channelIds)));
|
|
829
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toShareListResult);
|
|
830
|
+
});
|
|
831
|
+
if (result.code == 0) {
|
|
832
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
833
|
+
return kBackendFailure;
|
|
834
|
+
}
|
|
835
|
+
} else {
|
|
836
|
+
*outPtr = nullptr;
|
|
837
|
+
*outLen = 0;
|
|
838
|
+
}
|
|
839
|
+
return result.code;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/// `ShareStore.latestVersion(secretId) -> number | null`
|
|
843
|
+
extern "C" int32_t shareStoreLatestVersion(void* userData, uint64_t secretId, uint32_t* outHasVersion,
|
|
844
|
+
uint32_t* outVersion) {
|
|
845
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
846
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
847
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
848
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
849
|
+
auto keepAlive = self->shared_from_this();
|
|
850
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId](std::function<void(CallResult)> settle) {
|
|
851
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
852
|
+
auto store = keepAlive->shareStore();
|
|
853
|
+
auto method = store->getPropertyAsFunction(rt, "latestVersion");
|
|
854
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId));
|
|
855
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVersionResult);
|
|
856
|
+
});
|
|
857
|
+
if (result.bytes.size() == 4) {
|
|
858
|
+
*outHasVersion = 1;
|
|
859
|
+
*outVersion = static_cast<uint32_t>(result.bytes[0]) | (static_cast<uint32_t>(result.bytes[1]) << 8) |
|
|
860
|
+
(static_cast<uint32_t>(result.bytes[2]) << 16) |
|
|
861
|
+
(static_cast<uint32_t>(result.bytes[3]) << 24);
|
|
862
|
+
} else {
|
|
863
|
+
*outHasVersion = 0;
|
|
864
|
+
*outVersion = 0;
|
|
865
|
+
}
|
|
866
|
+
return result.code;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/// `ShareStore.save(secretId, channelId, share) -> void`
|
|
870
|
+
extern "C" int32_t shareStoreSave(void* userData, uint64_t secretId, uint64_t channelId,
|
|
871
|
+
const uint8_t* shareJsonPtr, size_t shareJsonLen) {
|
|
872
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
873
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
874
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
875
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
876
|
+
auto keepAlive = self->shared_from_this();
|
|
877
|
+
DecodedShare decoded = decodeShareRecord(shareJsonPtr, shareJsonLen);
|
|
878
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId, channelId,
|
|
879
|
+
decoded](std::function<void(CallResult)> settle) {
|
|
880
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
881
|
+
auto store = keepAlive->shareStore();
|
|
882
|
+
auto method = store->getPropertyAsFunction(rt, "save");
|
|
883
|
+
jsi::Object shareObj(rt);
|
|
884
|
+
shareObj.setProperty(rt, "secretId", idVal(rt, secretId));
|
|
885
|
+
shareObj.setProperty(rt, "version", jsi::Value(static_cast<int>(decoded.version)));
|
|
886
|
+
shareObj.setProperty(rt, "bytes", toUint8ArrayVal(rt, decoded.bytes));
|
|
887
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId),
|
|
888
|
+
jsi::Value(rt, shareObj));
|
|
889
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
890
|
+
});
|
|
891
|
+
return result.code;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/// `ShareStore.removeChannel(secretId, channelId) -> void`
|
|
895
|
+
extern "C" int32_t shareStoreRemoveChannel(void* userData, uint64_t secretId, uint64_t channelId) {
|
|
896
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
897
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
898
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
899
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
900
|
+
auto keepAlive = self->shared_from_this();
|
|
901
|
+
CallResult result =
|
|
902
|
+
keepAlive->bridge().callSync([keepAlive, secretId, channelId](std::function<void(CallResult)> settle) {
|
|
903
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
904
|
+
auto store = keepAlive->shareStore();
|
|
905
|
+
auto method = store->getPropertyAsFunction(rt, "removeChannel");
|
|
906
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId));
|
|
907
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
908
|
+
});
|
|
909
|
+
return result.code;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/// `UserSecretStore.loadLatest(secretId) -> UserSecrets | null`
|
|
913
|
+
extern "C" int32_t userSecretStoreLoadLatest(void* userData, uint64_t secretId, uint8_t** outPtr,
|
|
914
|
+
size_t* outLen) {
|
|
915
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
916
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
917
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
918
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
919
|
+
auto keepAlive = self->shared_from_this();
|
|
920
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId](std::function<void(CallResult)> settle) {
|
|
921
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
922
|
+
auto store = keepAlive->userSecretStore();
|
|
923
|
+
auto method = store->getPropertyAsFunction(rt, "loadLatest");
|
|
924
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId));
|
|
925
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toUserSecretsResult);
|
|
926
|
+
});
|
|
927
|
+
if (result.code == 0) {
|
|
928
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
929
|
+
return kBackendFailure;
|
|
930
|
+
}
|
|
931
|
+
} else {
|
|
932
|
+
*outPtr = nullptr;
|
|
933
|
+
*outLen = 0;
|
|
934
|
+
}
|
|
935
|
+
return result.code;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/// `UserSecretStore.saveLatest(secretId, value) -> void`
|
|
939
|
+
extern "C" int32_t userSecretStoreSaveLatest(void* userData, uint64_t secretId,
|
|
940
|
+
const uint8_t* valueJsonPtr, size_t valueJsonLen) {
|
|
941
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
942
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
943
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
944
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
945
|
+
auto keepAlive = self->shared_from_this();
|
|
946
|
+
std::vector<uint8_t> payload(valueJsonPtr, valueJsonPtr + valueJsonLen);
|
|
947
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId, payload](std::function<void(CallResult)> settle) {
|
|
948
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
949
|
+
auto store = keepAlive->userSecretStore();
|
|
950
|
+
auto method = store->getPropertyAsFunction(rt, "saveLatest");
|
|
951
|
+
jsi::Value userSecrets = buildUserSecretsFromWire(rt, payload.data(), payload.size());
|
|
952
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), std::move(userSecrets));
|
|
953
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
954
|
+
});
|
|
955
|
+
return result.code;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/// `UserSecretStore.remove(secretId) -> void`
|
|
959
|
+
extern "C" int32_t userSecretStoreRemove(void* userData, uint64_t secretId) {
|
|
960
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
961
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
962
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
963
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
964
|
+
auto keepAlive = self->shared_from_this();
|
|
965
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId](std::function<void(CallResult)> settle) {
|
|
966
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
967
|
+
auto store = keepAlive->userSecretStore();
|
|
968
|
+
auto method = store->getPropertyAsFunction(rt, "remove");
|
|
969
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId));
|
|
970
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
971
|
+
});
|
|
972
|
+
return result.code;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/// `StateStore.save(secretId, itemJson) -> void`
|
|
976
|
+
extern "C" int32_t stateStoreSave(void* userData, uint64_t secretId, const uint8_t* itemJsonPtr,
|
|
977
|
+
size_t itemJsonLen) {
|
|
978
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
979
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
980
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
981
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
982
|
+
auto keepAlive = self->shared_from_this();
|
|
983
|
+
std::vector<uint8_t> payload(itemJsonPtr, itemJsonPtr + itemJsonLen);
|
|
984
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId, payload](std::function<void(CallResult)> settle) {
|
|
985
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
986
|
+
auto store = keepAlive->stateStore();
|
|
987
|
+
auto method = store->getPropertyAsFunction(rt, "save");
|
|
988
|
+
jsi::Value promise =
|
|
989
|
+
method.callWithThis(rt, *store, idVal(rt, secretId), toUint8ArrayVal(rt, payload));
|
|
990
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
991
|
+
});
|
|
992
|
+
return result.code;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/// `StateStore.load(secretId, keyJson) -> Uint8Array | null`
|
|
996
|
+
extern "C" int32_t stateStoreLoad(void* userData, uint64_t secretId, const uint8_t* keyJsonPtr,
|
|
997
|
+
size_t keyJsonLen, uint8_t** outPtr, size_t* outLen) {
|
|
998
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
999
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
1000
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
1001
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
1002
|
+
auto keepAlive = self->shared_from_this();
|
|
1003
|
+
std::vector<uint8_t> key(keyJsonPtr, keyJsonPtr + keyJsonLen);
|
|
1004
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId, key](std::function<void(CallResult)> settle) {
|
|
1005
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
1006
|
+
auto store = keepAlive->stateStore();
|
|
1007
|
+
auto method = store->getPropertyAsFunction(rt, "load");
|
|
1008
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), toUint8ArrayVal(rt, key));
|
|
1009
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toCallResult);
|
|
1010
|
+
});
|
|
1011
|
+
if (result.code == 0) {
|
|
1012
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
1013
|
+
return kBackendFailure;
|
|
1014
|
+
}
|
|
1015
|
+
} else {
|
|
1016
|
+
*outPtr = nullptr;
|
|
1017
|
+
*outLen = 0;
|
|
1018
|
+
}
|
|
1019
|
+
return result.code;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/// `StateStore.remove(secretId, keyJson) -> boolean`
|
|
1023
|
+
extern "C" int32_t stateStoreRemove(void* userData, uint64_t secretId, const uint8_t* keyJsonPtr,
|
|
1024
|
+
size_t keyJsonLen, uint32_t* outRemoved) {
|
|
1025
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
1026
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
1027
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
1028
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
1029
|
+
auto keepAlive = self->shared_from_this();
|
|
1030
|
+
std::vector<uint8_t> key(keyJsonPtr, keyJsonPtr + keyJsonLen);
|
|
1031
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, secretId, key](std::function<void(CallResult)> settle) {
|
|
1032
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
1033
|
+
auto store = keepAlive->stateStore();
|
|
1034
|
+
auto method = store->getPropertyAsFunction(rt, "remove");
|
|
1035
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), toUint8ArrayVal(rt, key));
|
|
1036
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toBoolResult);
|
|
1037
|
+
});
|
|
1038
|
+
*outRemoved = result.bytes.empty() ? 0 : 1;
|
|
1039
|
+
return result.code;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/// `StateStore.loadAll(secretId, kind) -> Uint8Array[]`
|
|
1043
|
+
extern "C" int32_t stateStoreLoadAll(void* userData, uint64_t secretId, uint32_t kind, uint8_t** outPtr,
|
|
1044
|
+
size_t* outLen) {
|
|
1045
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
1046
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
1047
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
1048
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
1049
|
+
auto keepAlive = self->shared_from_this();
|
|
1050
|
+
CallResult result =
|
|
1051
|
+
keepAlive->bridge().callSync([keepAlive, secretId, kind](std::function<void(CallResult)> settle) {
|
|
1052
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
1053
|
+
auto store = keepAlive->stateStore();
|
|
1054
|
+
auto method = store->getPropertyAsFunction(rt, "loadAll");
|
|
1055
|
+
jsi::Value promise =
|
|
1056
|
+
method.callWithThis(rt, *store, idVal(rt, secretId), jsi::Value(static_cast<int>(kind)));
|
|
1057
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toStateLoadAllResult);
|
|
1058
|
+
});
|
|
1059
|
+
if (result.code == 0) {
|
|
1060
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
1061
|
+
return kBackendFailure;
|
|
1062
|
+
}
|
|
1063
|
+
} else {
|
|
1064
|
+
*outPtr = nullptr;
|
|
1065
|
+
*outLen = 0;
|
|
1066
|
+
}
|
|
1067
|
+
return result.code;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/// Maps the wire's `derec_proto::Protocol` discriminant to the string
|
|
1071
|
+
/// `Transport.send`'s endpoint carries. Only `HTTPS = 0` is defined today;
|
|
1072
|
+
/// the protocol is explicitly extensible, so an unrecognised discriminant
|
|
1073
|
+
/// is reported rather than guessed at.
|
|
1074
|
+
std::string protocolToString(int32_t protocol) { return protocol == 0 ? "https" : "unknown"; }
|
|
1075
|
+
|
|
1076
|
+
/// `Transport.send(endpoint, message) -> void`
|
|
1077
|
+
extern "C" int32_t transportSend(void* userData, const uint8_t* uriPtr, size_t uriLen, int32_t protocol,
|
|
1078
|
+
const uint8_t* bytes, size_t len) {
|
|
1079
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
1080
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
1081
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
1082
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
1083
|
+
auto keepAlive = self->shared_from_this();
|
|
1084
|
+
std::string uri(reinterpret_cast<const char*>(uriPtr), uriLen);
|
|
1085
|
+
std::vector<uint8_t> message(bytes, bytes + len);
|
|
1086
|
+
CallResult result = keepAlive->bridge().callSync([keepAlive, uri, protocol,
|
|
1087
|
+
message](std::function<void(CallResult)> settle) {
|
|
1088
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
1089
|
+
auto store = keepAlive->transportStore();
|
|
1090
|
+
auto method = store->getPropertyAsFunction(rt, "send");
|
|
1091
|
+
jsi::Object endpoint(rt);
|
|
1092
|
+
endpoint.setProperty(rt, "protocol",
|
|
1093
|
+
jsi::Value(rt, jsi::String::createFromUtf8(rt, protocolToString(protocol))));
|
|
1094
|
+
endpoint.setProperty(rt, "uri", jsi::Value(rt, jsi::String::createFromUtf8(rt, uri)));
|
|
1095
|
+
jsi::Value promise =
|
|
1096
|
+
method.callWithThis(rt, *store, jsi::Value(rt, endpoint), toUint8ArrayVal(rt, message));
|
|
1097
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
1098
|
+
});
|
|
1099
|
+
return result.code;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/// Throws a `jsi::JSError` naming `name` if `fn` is null. Guards against a
|
|
1103
|
+
/// callback struct silently shipping with an unset member — e.g. a future
|
|
1104
|
+
/// field added to `derec_ffi.h` that this file forgot to wire up.
|
|
1105
|
+
template <typename Fn>
|
|
1106
|
+
void requireField(jsi::Runtime& rt, Fn fn, const char* qualifiedName) {
|
|
1107
|
+
if (fn == nullptr) {
|
|
1108
|
+
throw jsi::JSError(rt, std::string("StoreBindings: ") + qualifiedName + " is not set");
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/// Reads and retains the JS object stored at `stores[name]`, throwing a
|
|
1113
|
+
/// `jsi::JSError` naming the missing or malformed store.
|
|
1114
|
+
std::shared_ptr<jsi::Object> requireStore(jsi::Runtime& rt, jsi::Object& stores, const char* name) {
|
|
1115
|
+
if (!stores.hasProperty(rt, name)) {
|
|
1116
|
+
throw jsi::JSError(rt, std::string("StoreBindings: missing store \"") + name + "\"");
|
|
1117
|
+
}
|
|
1118
|
+
jsi::Value value = stores.getProperty(rt, name);
|
|
1119
|
+
if (!value.isObject()) {
|
|
1120
|
+
throw jsi::JSError(rt, std::string("StoreBindings: store \"") + name + "\" is not an object");
|
|
1121
|
+
}
|
|
1122
|
+
return std::make_shared<jsi::Object>(value.asObject(rt));
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
} // namespace
|
|
1126
|
+
|
|
1127
|
+
StoreBindings::StoreBindings(jsi::Runtime& rt, JsCallbackBridge& bridge,
|
|
1128
|
+
std::shared_ptr<InstanceState> state)
|
|
1129
|
+
: rt_(rt), bridge_(bridge), instanceState_(std::move(state)) {}
|
|
1130
|
+
|
|
1131
|
+
void StoreBindings::settleFromPromise(jsi::Runtime& rt, jsi::Value value,
|
|
1132
|
+
std::function<void(CallResult)> settle,
|
|
1133
|
+
ResultConverter convert) {
|
|
1134
|
+
// A store may legitimately return a plain value; only route through
|
|
1135
|
+
// `then` when the result actually is a thenable.
|
|
1136
|
+
if (!value.isObject()) {
|
|
1137
|
+
settle(convert(rt, value));
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
auto object = value.asObject(rt);
|
|
1141
|
+
if (!object.hasProperty(rt, "then")) {
|
|
1142
|
+
settle(convert(rt, value));
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
auto shared = std::make_shared<std::function<void(CallResult)>>(std::move(settle));
|
|
1147
|
+
|
|
1148
|
+
// A converter can throw (e.g. `asBytes` on a non-buffer, `asNumber()` on
|
|
1149
|
+
// a non-number, `asString()` on a non-string — all reachable when a
|
|
1150
|
+
// store resolves the wrong type, and all checked forms: the `get*`
|
|
1151
|
+
// variants only `assert`, so under NDEBUG they would read the wrong union
|
|
1152
|
+
// member instead of throwing). Left uncaught, that exception would
|
|
1153
|
+
// escape into the promise machinery without ever calling `settle`,
|
|
1154
|
+
// stranding the blocked worker for the full `kStoreTimeout` instead of
|
|
1155
|
+
// failing fast. The synchronous, non-thenable branch above is already
|
|
1156
|
+
// covered by `JsCallbackBridge::callSync`'s own try/catch; this `then`
|
|
1157
|
+
// branch runs later, on its own turn of the microtask queue, so it needs
|
|
1158
|
+
// the same guarantee here.
|
|
1159
|
+
auto onOk = jsi::Function::createFromHostFunction(
|
|
1160
|
+
rt, jsi::PropNameID::forAscii(rt, "onOk"), 1,
|
|
1161
|
+
[shared, convert](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args,
|
|
1162
|
+
size_t count) -> jsi::Value {
|
|
1163
|
+
try {
|
|
1164
|
+
(*shared)(count > 0 ? convert(rt, args[0]) : convert(rt, jsi::Value::undefined()));
|
|
1165
|
+
} catch (...) {
|
|
1166
|
+
(*shared)(CallResult{kBackendFailure, {}});
|
|
1167
|
+
}
|
|
1168
|
+
return jsi::Value::undefined();
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
auto onErr = jsi::Function::createFromHostFunction(
|
|
1172
|
+
rt, jsi::PropNameID::forAscii(rt, "onErr"), 1,
|
|
1173
|
+
[shared](jsi::Runtime&, const jsi::Value&, const jsi::Value*, size_t) -> jsi::Value {
|
|
1174
|
+
try {
|
|
1175
|
+
(*shared)(CallResult{kBackendFailure, {}});
|
|
1176
|
+
} catch (...) {
|
|
1177
|
+
// `settle` on an already-resolved slot is a documented no-op, but
|
|
1178
|
+
// guard uniformly with `onOk` in case a future `settle`
|
|
1179
|
+
// implementation grows a fallible step.
|
|
1180
|
+
}
|
|
1181
|
+
return jsi::Value::undefined();
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
object.getPropertyAsFunction(rt, "then").callWithThis(rt, object, std::move(onOk), std::move(onErr));
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
std::shared_ptr<StoreBindings> StoreBindings::create(jsi::Runtime& rt, jsi::Object& stores,
|
|
1188
|
+
JsCallbackBridge& bridge,
|
|
1189
|
+
std::shared_ptr<InstanceState> state) {
|
|
1190
|
+
std::shared_ptr<StoreBindings> bindings(new StoreBindings(rt, bridge, std::move(state)));
|
|
1191
|
+
|
|
1192
|
+
bindings->channelStore_ = requireStore(rt, stores, "channelStore");
|
|
1193
|
+
bindings->secretStore_ = requireStore(rt, stores, "secretStore");
|
|
1194
|
+
bindings->shareStore_ = requireStore(rt, stores, "shareStore");
|
|
1195
|
+
bindings->userSecretStore_ = requireStore(rt, stores, "userSecretStore");
|
|
1196
|
+
bindings->stateStore_ = requireStore(rt, stores, "stateStore");
|
|
1197
|
+
bindings->transport_ = requireStore(rt, stores, "transport");
|
|
1198
|
+
|
|
1199
|
+
auto* self = bindings.get();
|
|
1200
|
+
|
|
1201
|
+
bindings->channelCallbacks_ = ChannelStoreCallbacks{
|
|
1202
|
+
self, // user_data
|
|
1203
|
+
channelStoreLoad, // load
|
|
1204
|
+
channelStoreSave, // save
|
|
1205
|
+
channelStoreRemove, // remove
|
|
1206
|
+
channelStoreListHelpers, // list_helpers
|
|
1207
|
+
channelStoreListReplicas, // list_replicas
|
|
1208
|
+
channelStoreLinkChannel, // link_channel
|
|
1209
|
+
channelStoreLinkedChannels, // linked_channels
|
|
1210
|
+
storeFreeBuffer, // free_buffer
|
|
1211
|
+
};
|
|
1212
|
+
bindings->secretCallbacks_ = SecretStoreCallbacks{
|
|
1213
|
+
self, // user_data
|
|
1214
|
+
secretStoreLoad, // load
|
|
1215
|
+
secretStoreSave, // save
|
|
1216
|
+
secretStoreRemove, // remove
|
|
1217
|
+
storeFreeBuffer, // free_buffer
|
|
1218
|
+
};
|
|
1219
|
+
bindings->shareCallbacks_ = ShareStoreCallbacks{
|
|
1220
|
+
self, // user_data
|
|
1221
|
+
shareStoreLoad, // load
|
|
1222
|
+
shareStoreLoadMany, // load_many
|
|
1223
|
+
shareStoreLoadAll, // load_all
|
|
1224
|
+
shareStoreLatestVersion, // latest_version
|
|
1225
|
+
shareStoreSave, // save
|
|
1226
|
+
shareStoreRemoveChannel, // remove_channel
|
|
1227
|
+
storeFreeBuffer, // free_buffer
|
|
1228
|
+
};
|
|
1229
|
+
bindings->userSecretCallbacks_ = UserSecretStoreCallbacks{
|
|
1230
|
+
self, // user_data
|
|
1231
|
+
userSecretStoreLoadLatest, // load_latest
|
|
1232
|
+
userSecretStoreSaveLatest, // save_latest
|
|
1233
|
+
userSecretStoreRemove, // remove
|
|
1234
|
+
storeFreeBuffer, // free_buffer
|
|
1235
|
+
};
|
|
1236
|
+
bindings->stateCallbacks_ = StateStoreCallbacks{
|
|
1237
|
+
self, // user_data
|
|
1238
|
+
stateStoreSave, // save
|
|
1239
|
+
stateStoreLoad, // load
|
|
1240
|
+
stateStoreRemove, // remove
|
|
1241
|
+
stateStoreLoadAll, // load_all
|
|
1242
|
+
storeFreeBuffer, // free_buffer
|
|
1243
|
+
};
|
|
1244
|
+
bindings->transportCallbacks_ = TransportCallbacks{
|
|
1245
|
+
self, // user_data
|
|
1246
|
+
transportSend, // send
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
requireField(rt, bindings->channelCallbacks_.user_data, "ChannelStoreCallbacks.user_data");
|
|
1250
|
+
requireField(rt, bindings->channelCallbacks_.load, "ChannelStoreCallbacks.load");
|
|
1251
|
+
requireField(rt, bindings->channelCallbacks_.save, "ChannelStoreCallbacks.save");
|
|
1252
|
+
requireField(rt, bindings->channelCallbacks_.remove, "ChannelStoreCallbacks.remove");
|
|
1253
|
+
requireField(rt, bindings->channelCallbacks_.list_helpers, "ChannelStoreCallbacks.list_helpers");
|
|
1254
|
+
requireField(rt, bindings->channelCallbacks_.list_replicas, "ChannelStoreCallbacks.list_replicas");
|
|
1255
|
+
requireField(rt, bindings->channelCallbacks_.link_channel, "ChannelStoreCallbacks.link_channel");
|
|
1256
|
+
requireField(rt, bindings->channelCallbacks_.linked_channels, "ChannelStoreCallbacks.linked_channels");
|
|
1257
|
+
requireField(rt, bindings->channelCallbacks_.free_buffer, "ChannelStoreCallbacks.free_buffer");
|
|
1258
|
+
|
|
1259
|
+
requireField(rt, bindings->secretCallbacks_.user_data, "SecretStoreCallbacks.user_data");
|
|
1260
|
+
requireField(rt, bindings->secretCallbacks_.load, "SecretStoreCallbacks.load");
|
|
1261
|
+
requireField(rt, bindings->secretCallbacks_.save, "SecretStoreCallbacks.save");
|
|
1262
|
+
requireField(rt, bindings->secretCallbacks_.remove, "SecretStoreCallbacks.remove");
|
|
1263
|
+
requireField(rt, bindings->secretCallbacks_.free_buffer, "SecretStoreCallbacks.free_buffer");
|
|
1264
|
+
|
|
1265
|
+
requireField(rt, bindings->shareCallbacks_.user_data, "ShareStoreCallbacks.user_data");
|
|
1266
|
+
requireField(rt, bindings->shareCallbacks_.load, "ShareStoreCallbacks.load");
|
|
1267
|
+
requireField(rt, bindings->shareCallbacks_.load_many, "ShareStoreCallbacks.load_many");
|
|
1268
|
+
requireField(rt, bindings->shareCallbacks_.load_all, "ShareStoreCallbacks.load_all");
|
|
1269
|
+
requireField(rt, bindings->shareCallbacks_.latest_version, "ShareStoreCallbacks.latest_version");
|
|
1270
|
+
requireField(rt, bindings->shareCallbacks_.save, "ShareStoreCallbacks.save");
|
|
1271
|
+
requireField(rt, bindings->shareCallbacks_.remove_channel, "ShareStoreCallbacks.remove_channel");
|
|
1272
|
+
requireField(rt, bindings->shareCallbacks_.free_buffer, "ShareStoreCallbacks.free_buffer");
|
|
1273
|
+
|
|
1274
|
+
requireField(rt, bindings->userSecretCallbacks_.user_data, "UserSecretStoreCallbacks.user_data");
|
|
1275
|
+
requireField(rt, bindings->userSecretCallbacks_.load_latest, "UserSecretStoreCallbacks.load_latest");
|
|
1276
|
+
requireField(rt, bindings->userSecretCallbacks_.save_latest, "UserSecretStoreCallbacks.save_latest");
|
|
1277
|
+
requireField(rt, bindings->userSecretCallbacks_.remove, "UserSecretStoreCallbacks.remove");
|
|
1278
|
+
requireField(rt, bindings->userSecretCallbacks_.free_buffer, "UserSecretStoreCallbacks.free_buffer");
|
|
1279
|
+
|
|
1280
|
+
requireField(rt, bindings->stateCallbacks_.user_data, "StateStoreCallbacks.user_data");
|
|
1281
|
+
requireField(rt, bindings->stateCallbacks_.save, "StateStoreCallbacks.save");
|
|
1282
|
+
requireField(rt, bindings->stateCallbacks_.load, "StateStoreCallbacks.load");
|
|
1283
|
+
requireField(rt, bindings->stateCallbacks_.remove, "StateStoreCallbacks.remove");
|
|
1284
|
+
requireField(rt, bindings->stateCallbacks_.load_all, "StateStoreCallbacks.load_all");
|
|
1285
|
+
requireField(rt, bindings->stateCallbacks_.free_buffer, "StateStoreCallbacks.free_buffer");
|
|
1286
|
+
|
|
1287
|
+
requireField(rt, bindings->transportCallbacks_.user_data, "TransportCallbacks.user_data");
|
|
1288
|
+
requireField(rt, bindings->transportCallbacks_.send, "TransportCallbacks.send");
|
|
1289
|
+
|
|
1290
|
+
return bindings;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
} // namespace derec
|