@derec-alliance/react-native 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/DeRec.podspec +25 -0
  2. package/LICENSE +201 -0
  3. package/README.md +222 -0
  4. package/android/CMakeLists.txt +38 -0
  5. package/android/build.gradle +79 -0
  6. package/android/src/main/AndroidManifest.xml +12 -0
  7. package/android/src/main/java/org/derec/DeRecModule.kt +56 -0
  8. package/android/src/main/java/org/derec/DeRecPackage.kt +24 -0
  9. package/android/src/main/jni/OnLoad.cpp +39 -0
  10. package/android/src/main/jniLibs/arm64-v8a/libderec_library.a +0 -0
  11. package/android/src/main/jniLibs/armeabi-v7a/libderec_library.a +0 -0
  12. package/android/src/main/jniLibs/x86_64/libderec_library.a +0 -0
  13. package/cpp/Convert.cpp +50 -0
  14. package/cpp/Convert.h +38 -0
  15. package/cpp/DeRecInstaller.cpp +186 -0
  16. package/cpp/DeRecInstaller.h +28 -0
  17. package/cpp/Invoker.h +20 -0
  18. package/cpp/JsCallbackBridge.cpp +144 -0
  19. package/cpp/JsCallbackBridge.h +148 -0
  20. package/cpp/Primitives.cpp +1193 -0
  21. package/cpp/Primitives.h +39 -0
  22. package/cpp/ProtocolHost.cpp +613 -0
  23. package/cpp/ProtocolHost.h +88 -0
  24. package/cpp/RuntimeInvoker.h +29 -0
  25. package/cpp/StoreCallbacks.cpp +1293 -0
  26. package/cpp/StoreCallbacks.h +111 -0
  27. package/cpp/WorkerThread.cpp +74 -0
  28. package/cpp/WorkerThread.h +54 -0
  29. package/cpp/derec_ffi.h +2145 -0
  30. package/ios/DeRec.h +23 -0
  31. package/ios/DeRec.mm +39 -0
  32. package/ios/DeRecFFI.xcframework/Info.plist +48 -0
  33. package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +2145 -0
  34. package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
  35. package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +2145 -0
  36. package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +0 -0
  37. package/lib/binary.d.ts +19 -0
  38. package/lib/binary.js +164 -0
  39. package/lib/codec.d.ts +16 -0
  40. package/lib/codec.js +80 -0
  41. package/lib/errors.d.ts +23 -0
  42. package/lib/errors.js +35 -0
  43. package/lib/index.d.ts +4 -0
  44. package/lib/index.js +27 -0
  45. package/lib/messages.d.ts +41 -0
  46. package/lib/messages.js +192 -0
  47. package/lib/native.d.ts +11 -0
  48. package/lib/native.js +40 -0
  49. package/lib/primitives.d.ts +137 -0
  50. package/lib/primitives.js +329 -0
  51. package/lib/protocol.d.ts +188 -0
  52. package/lib/protocol.js +514 -0
  53. package/lib/types.d.ts +1014 -0
  54. package/lib/types.js +63 -0
  55. package/package.json +55 -0
  56. package/react-native.config.js +19 -0
  57. package/src/binary.ts +182 -0
  58. package/src/codec.ts +79 -0
  59. package/src/errors.ts +45 -0
  60. package/src/index.ts +7 -0
  61. package/src/messages.ts +192 -0
  62. package/src/native.ts +52 -0
  63. package/src/primitives.ts +770 -0
  64. package/src/protocol.ts +621 -0
  65. package/src/types.ts +1040 -0
@@ -0,0 +1,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