@derec-alliance/react-native 0.0.6 → 0.0.7
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/README.md +24 -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/Primitives.cpp +16 -0
- package/cpp/Primitives.h +17 -0
- package/cpp/ProtocolHost.cpp +18 -5
- package/cpp/StoreCallbacks.cpp +88 -3
- package/cpp/derec_ffi.h +46 -16
- package/ios/DeRecFFI.xcframework/Info.plist +5 -5
- package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +46 -16
- package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +46 -16
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +2 -2
- package/lib/errors.d.ts +9 -0
- package/lib/protocol.d.ts +16 -5
- package/lib/protocol.js +27 -9
- package/lib/types.d.ts +93 -4
- package/package.json +1 -1
- package/src/errors.ts +10 -0
- package/src/protocol.ts +26 -10
- package/src/types.ts +84 -4
package/README.md
CHANGED
|
@@ -125,6 +125,27 @@ longer needed (safe to call more than once).
|
|
|
125
125
|
|
|
126
126
|
---
|
|
127
127
|
|
|
128
|
+
## When `process()` fails
|
|
129
|
+
|
|
130
|
+
`process()` settles expired deadlines (sharing-round and unpair timeouts)
|
|
131
|
+
before it handles the message, and those are never reported again. So when
|
|
132
|
+
the message then fails, the thrown `DeRecError` carries them: `events` holds
|
|
133
|
+
every event produced before the failure, and `channel_id` names the channel
|
|
134
|
+
the message came from (absent when the bytes were not a decodable envelope).
|
|
135
|
+
Handle the events as you would a successful call's, then the error:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
try {
|
|
139
|
+
handle(await protocol.process(bytes));
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const failure = error as DeRecError;
|
|
142
|
+
handle(failure.events ?? []);
|
|
143
|
+
report(failure.channel_id, failure);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
128
149
|
## Store interfaces
|
|
129
150
|
|
|
130
151
|
The application layer owns all persistence; the library never writes to disk
|
|
@@ -134,6 +155,9 @@ declared in `src/types.ts`:
|
|
|
134
155
|
- **`ChannelStore`** — pairing state: helper channels and replica-group
|
|
135
156
|
membership, keyed by `(channelId, replicaId)`.
|
|
136
157
|
- **`ShareStore`** — versioned VSS shares this device holds as a helper.
|
|
158
|
+
On an owner, `keepList` chooses which versions helpers keep; returning
|
|
159
|
+
`null` sends no keepList, so helpers keep every version; see
|
|
160
|
+
[the owner's keep list](https://github.com/derecalliance/lib-derec/tree/main/library#share-store-the-owners-keep-list).
|
|
137
161
|
- **`SecretStore`** — small per-channel key material (shared keys, etc.),
|
|
138
162
|
distinct from share bytes.
|
|
139
163
|
- **`UserSecretStore`** — the most recent user-facing secret snapshot per
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/cpp/Primitives.cpp
CHANGED
|
@@ -20,6 +20,13 @@ void throwDeRecError(jsi::Runtime& rt, const DeRecError& error) {
|
|
|
20
20
|
void throwDeRecError(jsi::Runtime& rt,
|
|
21
21
|
const DeRecError& error,
|
|
22
22
|
const std::vector<uint8_t>& conflictingChannelIdsJson) {
|
|
23
|
+
throwDeRecError(rt, error, conflictingChannelIdsJson, {});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
void throwDeRecError(jsi::Runtime& rt,
|
|
27
|
+
const DeRecError& error,
|
|
28
|
+
const std::vector<uint8_t>& conflictingChannelIdsJson,
|
|
29
|
+
const ProcessFailureDetails& process) {
|
|
23
30
|
auto names = errorName(error.category, error.code);
|
|
24
31
|
// `message` and `peer_memo` are Rust-owned strings; copy before releasing.
|
|
25
32
|
std::string message =
|
|
@@ -53,6 +60,15 @@ void throwDeRecError(jsi::Runtime& rt,
|
|
|
53
60
|
jsi::Value::createFromJsonUtf8(rt, conflictingChannelIdsJson.data(),
|
|
54
61
|
conflictingChannelIdsJson.size()));
|
|
55
62
|
}
|
|
63
|
+
if (process.hasChannelId) {
|
|
64
|
+
payload.setProperty(rt, "channel_id",
|
|
65
|
+
jsi::String::createFromUtf8(rt, std::to_string(process.channelId)));
|
|
66
|
+
}
|
|
67
|
+
if (!process.eventsJson.empty()) {
|
|
68
|
+
payload.setProperty(rt, "events",
|
|
69
|
+
jsi::Value::createFromJsonUtf8(rt, process.eventsJson.data(),
|
|
70
|
+
process.eventsJson.size()));
|
|
71
|
+
}
|
|
56
72
|
throw jsi::JSError(rt, jsi::Value(rt, payload));
|
|
57
73
|
}
|
|
58
74
|
|
package/cpp/Primitives.h
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
#include <jsi/jsi.h>
|
|
7
7
|
|
|
8
|
+
#include <cstdint>
|
|
8
9
|
#include <vector>
|
|
9
10
|
|
|
10
11
|
extern "C" {
|
|
@@ -25,6 +26,22 @@ namespace derec {
|
|
|
25
26
|
const DeRecError& error,
|
|
26
27
|
const std::vector<uint8_t>& conflictingChannelIdsJson);
|
|
27
28
|
|
|
29
|
+
/// What a failed `derec_protocol_process` adds to its error: the events it
|
|
30
|
+
/// produced before failing, and the channel the failing message came from.
|
|
31
|
+
struct ProcessFailureDetails {
|
|
32
|
+
std::vector<uint8_t> eventsJson;
|
|
33
|
+
bool hasChannelId = false;
|
|
34
|
+
uint64_t channelId = 0;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/// As above, additionally attaching `channel_id` (a decimal string) when
|
|
38
|
+
/// `process.hasChannelId` is set, and `events` (the JSON array verbatim)
|
|
39
|
+
/// when `process.eventsJson` is non-empty. Only `process` produces these.
|
|
40
|
+
[[noreturn]] void throwDeRecError(facebook::jsi::Runtime& rt,
|
|
41
|
+
const DeRecError& error,
|
|
42
|
+
const std::vector<uint8_t>& conflictingChannelIdsJson,
|
|
43
|
+
const ProcessFailureDetails& process);
|
|
44
|
+
|
|
28
45
|
/// Read an `ArrayBuffer` or typed-array argument as a byte range.
|
|
29
46
|
/// Returns `{nullptr, 0}` for `null`/`undefined`.
|
|
30
47
|
struct ByteView {
|
package/cpp/ProtocolHost.cpp
CHANGED
|
@@ -28,10 +28,14 @@ struct FfiFailure {
|
|
|
28
28
|
explicit FfiFailure(DeRecError e) : error(e) {}
|
|
29
29
|
FfiFailure(DeRecError e, std::vector<uint8_t> channelIdsJson)
|
|
30
30
|
: error(e), conflictingChannelIdsJson(std::move(channelIdsJson)) {}
|
|
31
|
+
FfiFailure(DeRecError e, ProcessFailureDetails details)
|
|
32
|
+
: error(e), process(std::move(details)) {}
|
|
31
33
|
DeRecError error;
|
|
32
34
|
/// `restore`'s JSON array of colliding channel ids, empty for every other
|
|
33
35
|
/// failure. Carried as raw bytes: parsing needs the runtime.
|
|
34
36
|
std::vector<uint8_t> conflictingChannelIdsJson;
|
|
37
|
+
/// `process`'s events and failing channel, empty for every other failure.
|
|
38
|
+
ProcessFailureDetails process;
|
|
35
39
|
};
|
|
36
40
|
|
|
37
41
|
constexpr const char* kReentrancyMessage =
|
|
@@ -176,11 +180,13 @@ jsi::Value ProtocolHost::runAsync(
|
|
|
176
180
|
bool unknownFailure = false;
|
|
177
181
|
DeRecError capturedError{};
|
|
178
182
|
std::vector<uint8_t> capturedChannelIdsJson;
|
|
183
|
+
ProcessFailureDetails capturedProcess;
|
|
179
184
|
try {
|
|
180
185
|
bytes = body();
|
|
181
186
|
} catch (FfiFailure& failure) {
|
|
182
187
|
capturedError = failure.error;
|
|
183
188
|
capturedChannelIdsJson = std::move(failure.conflictingChannelIdsJson);
|
|
189
|
+
capturedProcess = std::move(failure.process);
|
|
184
190
|
failed = true;
|
|
185
191
|
} catch (...) {
|
|
186
192
|
// Anything other than `FfiFailure` — `std::bad_alloc` from
|
|
@@ -207,7 +213,9 @@ jsi::Value ProtocolHost::runAsync(
|
|
|
207
213
|
bytes = std::move(bytes), failed,
|
|
208
214
|
unknownFailure, capturedError,
|
|
209
215
|
capturedChannelIdsJson =
|
|
210
|
-
std::move(capturedChannelIdsJson)
|
|
216
|
+
std::move(capturedChannelIdsJson),
|
|
217
|
+
capturedProcess =
|
|
218
|
+
std::move(capturedProcess)]() mutable {
|
|
211
219
|
if (state->runtimeInvalidated.load()) {
|
|
212
220
|
// Queued before the runtime was torn down, reached only
|
|
213
221
|
// afterwards. Every JSI call below — including the ones the
|
|
@@ -221,7 +229,8 @@ jsi::Value ProtocolHost::runAsync(
|
|
|
221
229
|
// Reuses the exact error shape every synchronous FFI
|
|
222
230
|
// wrapper throws, rather than inventing a second one for
|
|
223
231
|
// the asynchronous path.
|
|
224
|
-
throwDeRecError(jsRt, capturedError, capturedChannelIdsJson
|
|
232
|
+
throwDeRecError(jsRt, capturedError, capturedChannelIdsJson,
|
|
233
|
+
capturedProcess);
|
|
225
234
|
}
|
|
226
235
|
if (unknownFailure) {
|
|
227
236
|
throw jsi::JSError(jsRt, "DeRec: an unexpected native error occurred");
|
|
@@ -407,12 +416,16 @@ jsi::Value ProtocolHost::get(jsi::Runtime& rt, const jsi::PropNameID& name) {
|
|
|
407
416
|
|
|
408
417
|
auto body = [handle,
|
|
409
418
|
message = std::move(message)]() -> std::vector<uint8_t> {
|
|
410
|
-
|
|
419
|
+
DeRecProtocolProcessResult result =
|
|
411
420
|
derec_protocol_process(handle, message.data(), message.size());
|
|
421
|
+
std::vector<uint8_t> events = takeBuffer(result.events_json);
|
|
412
422
|
if (result.error.code != DEREC_CODE_OK) {
|
|
413
|
-
throw FfiFailure(result.error
|
|
423
|
+
throw FfiFailure(result.error,
|
|
424
|
+
ProcessFailureDetails{std::move(events),
|
|
425
|
+
result.has_channel_id,
|
|
426
|
+
result.channel_id});
|
|
414
427
|
}
|
|
415
|
-
return
|
|
428
|
+
return events;
|
|
416
429
|
};
|
|
417
430
|
return runAsync(rt2, std::move(body), bytesToArrayBuffer);
|
|
418
431
|
});
|
package/cpp/StoreCallbacks.cpp
CHANGED
|
@@ -27,7 +27,7 @@ static_assert(sizeof(ChannelStoreCallbacks) == 9 * sizeof(void*),
|
|
|
27
27
|
"ChannelStoreCallbacks layout changed; update the bindings");
|
|
28
28
|
static_assert(sizeof(SecretStoreCallbacks) == 6 * sizeof(void*),
|
|
29
29
|
"SecretStoreCallbacks layout changed; update the bindings");
|
|
30
|
-
static_assert(sizeof(ShareStoreCallbacks) ==
|
|
30
|
+
static_assert(sizeof(ShareStoreCallbacks) == 10 * sizeof(void*),
|
|
31
31
|
"ShareStoreCallbacks layout changed; update the bindings");
|
|
32
32
|
static_assert(sizeof(UserSecretStoreCallbacks) == 5 * sizeof(void*),
|
|
33
33
|
"UserSecretStoreCallbacks layout changed; update the bindings");
|
|
@@ -320,6 +320,40 @@ CallResult toStateLoadAllResult(jsi::Runtime& rt, const jsi::Value& value) {
|
|
|
320
320
|
return CallResult{0, std::vector<uint8_t>(text.begin(), text.end())};
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
/// `ShareStore.keepList(): Promise<number[] | null | undefined>`. Encodes the
|
|
324
|
+
/// resolved value as the wire's `Option<Vec<u32>>` JSON: `null` for
|
|
325
|
+
/// `null`/`undefined`, otherwise the array of versions.
|
|
326
|
+
///
|
|
327
|
+
/// Anything else, or an entry that is not an integer in `0..=4294967295`, is
|
|
328
|
+
/// a backend failure: reading it as `null` would silently send an empty
|
|
329
|
+
/// keepList, so helpers would keep every version instead of the list the
|
|
330
|
+
/// application meant to send.
|
|
331
|
+
CallResult toKeepListResult(jsi::Runtime& rt, const jsi::Value& value) {
|
|
332
|
+
if (value.isNull() || value.isUndefined()) {
|
|
333
|
+
return CallResult{0, {'n', 'u', 'l', 'l'}};
|
|
334
|
+
}
|
|
335
|
+
if (!value.isObject() || !value.asObject(rt).isArray(rt)) {
|
|
336
|
+
return CallResult{kBackendFailure, {}};
|
|
337
|
+
}
|
|
338
|
+
jsi::Array arr = value.asObject(rt).asArray(rt);
|
|
339
|
+
size_t n = arr.size(rt);
|
|
340
|
+
std::string text = "[";
|
|
341
|
+
for (size_t i = 0; i < n; ++i) {
|
|
342
|
+
jsi::Value entry = arr.getValueAtIndex(rt, i);
|
|
343
|
+
if (!entry.isNumber()) {
|
|
344
|
+
return CallResult{kBackendFailure, {}};
|
|
345
|
+
}
|
|
346
|
+
double version = entry.asNumber();
|
|
347
|
+
if (!(version >= 0 && version <= 4294967295.0) || version != static_cast<double>(static_cast<uint32_t>(version))) {
|
|
348
|
+
return CallResult{kBackendFailure, {}};
|
|
349
|
+
}
|
|
350
|
+
if (i != 0) text += ',';
|
|
351
|
+
text += std::to_string(static_cast<uint32_t>(version));
|
|
352
|
+
}
|
|
353
|
+
text += ']';
|
|
354
|
+
return CallResult{0, std::vector<uint8_t>(text.begin(), text.end())};
|
|
355
|
+
}
|
|
356
|
+
|
|
323
357
|
/// `UserSecretStore.loadLatest(): Promise<UserSecrets | null | undefined>`.
|
|
324
358
|
CallResult toUserSecretsResult(jsi::Runtime& rt, const jsi::Value& value) {
|
|
325
359
|
if (value.isNull() || value.isUndefined()) {
|
|
@@ -802,6 +836,53 @@ extern "C" int32_t shareStoreRemoveChannel(void* userData, uint64_t secretId, ui
|
|
|
802
836
|
return result.code;
|
|
803
837
|
}
|
|
804
838
|
|
|
839
|
+
/// `ShareStore.removeVersions(secretId, channelId, versions) -> void`
|
|
840
|
+
extern "C" int32_t shareStoreRemoveVersions(void* userData, uint64_t secretId, uint64_t channelId,
|
|
841
|
+
const uint8_t* versionsJsonPtr, size_t versionsJsonLen) {
|
|
842
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
843
|
+
// Kept alive past this call so the lambda handed to `callSync` — which the
|
|
844
|
+
// JavaScript CallInvoker's queue may still be holding after this object's
|
|
845
|
+
// owner has released its own reference — has somewhere safe to run.
|
|
846
|
+
auto keepAlive = self->shared_from_this();
|
|
847
|
+
std::vector<uint64_t> raw = parseUnsignedJsonArray(versionsJsonPtr, versionsJsonLen);
|
|
848
|
+
std::vector<uint32_t> versions(raw.begin(), raw.end());
|
|
849
|
+
CallResult result = keepAlive->bridge().callSync(
|
|
850
|
+
[keepAlive, secretId, channelId, versions](std::function<void(CallResult)> settle) {
|
|
851
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
852
|
+
auto store = keepAlive->shareStore();
|
|
853
|
+
auto method = store->getPropertyAsFunction(rt, "removeVersions");
|
|
854
|
+
jsi::Value promise = method.callWithThis(rt, *store, idVal(rt, secretId), idVal(rt, channelId),
|
|
855
|
+
jsi::Value(rt, u32VectorToJsArray(rt, versions)));
|
|
856
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toVoidResult);
|
|
857
|
+
});
|
|
858
|
+
return result.code;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/// `ShareStore.keepList(secretId, version) -> number[] | null`
|
|
862
|
+
extern "C" int32_t shareStoreKeepList(void* userData, uint64_t secretId, uint32_t version, uint8_t** outPtr,
|
|
863
|
+
size_t* outLen) {
|
|
864
|
+
auto* self = static_cast<StoreBindings*>(userData);
|
|
865
|
+
auto keepAlive = self->shared_from_this();
|
|
866
|
+
CallResult result =
|
|
867
|
+
keepAlive->bridge().callSync([keepAlive, secretId, version](std::function<void(CallResult)> settle) {
|
|
868
|
+
jsi::Runtime& rt = keepAlive->runtime();
|
|
869
|
+
auto store = keepAlive->shareStore();
|
|
870
|
+
auto method = store->getPropertyAsFunction(rt, "keepList");
|
|
871
|
+
jsi::Value promise =
|
|
872
|
+
method.callWithThis(rt, *store, idVal(rt, secretId), jsi::Value(static_cast<double>(version)));
|
|
873
|
+
keepAlive->settleFromPromise(rt, std::move(promise), std::move(settle), toKeepListResult);
|
|
874
|
+
});
|
|
875
|
+
if (result.code == 0) {
|
|
876
|
+
if (!writeOut(result.bytes, outPtr, outLen)) {
|
|
877
|
+
return kBackendFailure;
|
|
878
|
+
}
|
|
879
|
+
} else {
|
|
880
|
+
*outPtr = nullptr;
|
|
881
|
+
*outLen = 0;
|
|
882
|
+
}
|
|
883
|
+
return result.code;
|
|
884
|
+
}
|
|
885
|
+
|
|
805
886
|
/// `UserSecretStore.loadLatest(secretId) -> UserSecrets | null`
|
|
806
887
|
extern "C" int32_t userSecretStoreLoadLatest(void* userData, uint64_t secretId, uint8_t** outPtr,
|
|
807
888
|
size_t* outLen) {
|
|
@@ -1123,8 +1204,10 @@ std::shared_ptr<StoreBindings> StoreBindings::create(jsi::Runtime& rt, jsi::Obje
|
|
|
1123
1204
|
shareStoreLoadAll, // load_all
|
|
1124
1205
|
shareStoreLatestVersion, // latest_version
|
|
1125
1206
|
shareStoreSave, // save
|
|
1126
|
-
shareStoreRemoveChannel,
|
|
1127
|
-
|
|
1207
|
+
shareStoreRemoveChannel, // remove_channel
|
|
1208
|
+
shareStoreRemoveVersions, // remove_versions
|
|
1209
|
+
shareStoreKeepList, // keep_list
|
|
1210
|
+
storeFreeBuffer, // free_buffer
|
|
1128
1211
|
};
|
|
1129
1212
|
bindings->userSecretCallbacks_ = UserSecretStoreCallbacks{
|
|
1130
1213
|
self, // user_data
|
|
@@ -1170,6 +1253,8 @@ std::shared_ptr<StoreBindings> StoreBindings::create(jsi::Runtime& rt, jsi::Obje
|
|
|
1170
1253
|
requireField(rt, bindings->shareCallbacks_.latest_version, "ShareStoreCallbacks.latest_version");
|
|
1171
1254
|
requireField(rt, bindings->shareCallbacks_.save, "ShareStoreCallbacks.save");
|
|
1172
1255
|
requireField(rt, bindings->shareCallbacks_.remove_channel, "ShareStoreCallbacks.remove_channel");
|
|
1256
|
+
requireField(rt, bindings->shareCallbacks_.remove_versions, "ShareStoreCallbacks.remove_versions");
|
|
1257
|
+
requireField(rt, bindings->shareCallbacks_.keep_list, "ShareStoreCallbacks.keep_list");
|
|
1173
1258
|
requireField(rt, bindings->shareCallbacks_.free_buffer, "ShareStoreCallbacks.free_buffer");
|
|
1174
1259
|
|
|
1175
1260
|
requireField(rt, bindings->userSecretCallbacks_.user_data, "UserSecretStoreCallbacks.user_data");
|
package/cpp/derec_ffi.h
CHANGED
|
@@ -65,13 +65,6 @@
|
|
|
65
65
|
*/
|
|
66
66
|
#define DEFAULT_THRESHOLD 3
|
|
67
67
|
|
|
68
|
-
/**
|
|
69
|
-
* Number of recent share versions each helper retains, absent an explicit
|
|
70
|
-
* [`DeRecProtocolBuilder::with_keep_versions_count`](crate::protocol::DeRecProtocolBuilder::with_keep_versions_count) call. Sole definition
|
|
71
|
-
* of the value; see [`DEFAULT_THRESHOLD`].
|
|
72
|
-
*/
|
|
73
|
-
#define DEFAULT_KEEP_VERSIONS_COUNT 3
|
|
74
|
-
|
|
75
68
|
/**
|
|
76
69
|
* Maximum accepted transport URI length, in bytes.
|
|
77
70
|
*
|
|
@@ -844,6 +837,11 @@ typedef struct SecretStoreCallbacks {
|
|
|
844
837
|
* arrays (`channel_ids[]`, `versions[]`) cross the FFI as JSON
|
|
845
838
|
* strings, matching the `Vec<u8>` ↔ JSON-array convention used for
|
|
846
839
|
* every other wire-format buffer in this module.
|
|
840
|
+
*
|
|
841
|
+
* `keep_list` returns the JSON encoding of the application's answer:
|
|
842
|
+
* `null` for no list (the owner sends an empty `keepList` and helpers keep
|
|
843
|
+
* every version) or an array of version numbers. An empty buffer or return code `1` also
|
|
844
|
+
* means `null`.
|
|
847
845
|
*/
|
|
848
846
|
typedef struct ShareStoreCallbacks {
|
|
849
847
|
void *user_data;
|
|
@@ -878,6 +876,16 @@ typedef struct ShareStoreCallbacks {
|
|
|
878
876
|
const uint8_t *share_json_ptr,
|
|
879
877
|
size_t share_json_len);
|
|
880
878
|
int32_t (*remove_channel)(void *user_data, uint64_t secret_id, uint64_t channel_id);
|
|
879
|
+
int32_t (*remove_versions)(void *user_data,
|
|
880
|
+
uint64_t secret_id,
|
|
881
|
+
uint64_t channel_id,
|
|
882
|
+
const uint8_t *versions_json_ptr,
|
|
883
|
+
size_t versions_json_len);
|
|
884
|
+
int32_t (*keep_list)(void *user_data,
|
|
885
|
+
uint64_t secret_id,
|
|
886
|
+
uint32_t version,
|
|
887
|
+
uint8_t **out_ptr,
|
|
888
|
+
size_t *out_len);
|
|
881
889
|
void (*free_buffer)(void *user_data, uint8_t *ptr, size_t len);
|
|
882
890
|
} ShareStoreCallbacks;
|
|
883
891
|
|
|
@@ -1001,6 +1009,30 @@ typedef struct DeRecProtocolEventsResult {
|
|
|
1001
1009
|
struct DeRecBuffer events_json;
|
|
1002
1010
|
} DeRecProtocolEventsResult;
|
|
1003
1011
|
|
|
1012
|
+
/**
|
|
1013
|
+
* Result of [`derec_protocol_process`].
|
|
1014
|
+
*
|
|
1015
|
+
* A failed call still carries the events produced before the failure in
|
|
1016
|
+
* `events_json`, and names the channel the failing message came from.
|
|
1017
|
+
*/
|
|
1018
|
+
typedef struct DeRecProtocolProcessResult {
|
|
1019
|
+
struct DeRecError error;
|
|
1020
|
+
/**
|
|
1021
|
+
* UTF-8 JSON array of events, as on [`DeRecProtocolEventsResult`].
|
|
1022
|
+
* Filled on success and on failure.
|
|
1023
|
+
*/
|
|
1024
|
+
struct DeRecBuffer events_json;
|
|
1025
|
+
/**
|
|
1026
|
+
* Whether `channel_id` is set. False on success and when the bytes
|
|
1027
|
+
* were not a decodable envelope.
|
|
1028
|
+
*/
|
|
1029
|
+
bool has_channel_id;
|
|
1030
|
+
/**
|
|
1031
|
+
* The channel the failing message came from.
|
|
1032
|
+
*/
|
|
1033
|
+
uint64_t channel_id;
|
|
1034
|
+
} DeRecProtocolProcessResult;
|
|
1035
|
+
|
|
1004
1036
|
/**
|
|
1005
1037
|
* Result of [`derec_protocol_restore`].
|
|
1006
1038
|
*
|
|
@@ -1685,7 +1717,6 @@ struct ProcessPrePairResponseMessageResult process_pre_pair_response_message(con
|
|
|
1685
1717
|
* "secret_id": "12345678901234567890",
|
|
1686
1718
|
* "own_transports": [{ "uri": "https://example.com/derec", "protocol": "https" }],
|
|
1687
1719
|
* "threshold": 3,
|
|
1688
|
-
* "keep_versions_count": 3,
|
|
1689
1720
|
* "auto_respond_on_failure": false,
|
|
1690
1721
|
* "unpair_ack": 0,
|
|
1691
1722
|
* "auto_reply_to": false,
|
|
@@ -1730,10 +1761,8 @@ struct ProcessPrePairResponseMessageResult process_pre_pair_response_message(con
|
|
|
1730
1761
|
* two entries of the same protocol are rejected. Required: an absent or
|
|
1731
1762
|
* empty list is refused, since a node with no endpoint cannot be reached
|
|
1732
1763
|
* by any peer.
|
|
1733
|
-
* - `threshold
|
|
1734
|
-
*
|
|
1735
|
-
* [`crate::protocol::DEFAULT_KEEP_VERSIONS_COUNT`]. A `threshold` below
|
|
1736
|
-
* `2` is rejected with `DEREC_CODE_INVALID_INPUT`.
|
|
1764
|
+
* - `threshold`: omitted means [`crate::protocol::DEFAULT_THRESHOLD`]. A
|
|
1765
|
+
* `threshold` below `2` is rejected with `DEREC_CODE_INVALID_INPUT`.
|
|
1737
1766
|
* - `unpair_ack`: `0` / `"required"` = Required, `1` / `"not_required"` =
|
|
1738
1767
|
* NotRequired; omitted means Required.
|
|
1739
1768
|
* Any other value is rejected with `DEREC_CODE_FFI_INVALID_ENUM`.
|
|
@@ -1880,7 +1909,8 @@ struct DeRecProtocolEventsResult derec_protocol_start(struct DeRecProtocolHandle
|
|
|
1880
1909
|
|
|
1881
1910
|
/**
|
|
1882
1911
|
* Process an inbound `DeRecMessage` envelope. See
|
|
1883
|
-
* [`crate::protocol::DeRecProtocol::process`].
|
|
1912
|
+
* [`crate::protocol::DeRecProtocol::process`]. On failure the result still
|
|
1913
|
+
* carries the events produced before it and the failing channel.
|
|
1884
1914
|
*
|
|
1885
1915
|
* # Safety
|
|
1886
1916
|
*
|
|
@@ -1888,9 +1918,9 @@ struct DeRecProtocolEventsResult derec_protocol_start(struct DeRecProtocolHandle
|
|
|
1888
1918
|
* [`super::derec_protocol_new`]. `message_ptr`/`message_len` must
|
|
1889
1919
|
* describe a readable byte range.
|
|
1890
1920
|
*/
|
|
1891
|
-
struct
|
|
1892
|
-
|
|
1893
|
-
|
|
1921
|
+
struct DeRecProtocolProcessResult derec_protocol_process(struct DeRecProtocolHandle *handle,
|
|
1922
|
+
const uint8_t *message_ptr,
|
|
1923
|
+
size_t message_len);
|
|
1894
1924
|
|
|
1895
1925
|
/**
|
|
1896
1926
|
* Advance time-driven state without an inbound message. See
|
|
@@ -10,15 +10,18 @@
|
|
|
10
10
|
<key>HeadersPath</key>
|
|
11
11
|
<string>Headers</string>
|
|
12
12
|
<key>LibraryIdentifier</key>
|
|
13
|
-
<string>ios-
|
|
13
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
14
14
|
<key>LibraryPath</key>
|
|
15
15
|
<string>libderec_library.a</string>
|
|
16
16
|
<key>SupportedArchitectures</key>
|
|
17
17
|
<array>
|
|
18
18
|
<string>arm64</string>
|
|
19
|
+
<string>x86_64</string>
|
|
19
20
|
</array>
|
|
20
21
|
<key>SupportedPlatform</key>
|
|
21
22
|
<string>ios</string>
|
|
23
|
+
<key>SupportedPlatformVariant</key>
|
|
24
|
+
<string>simulator</string>
|
|
22
25
|
</dict>
|
|
23
26
|
<dict>
|
|
24
27
|
<key>BinaryPath</key>
|
|
@@ -26,18 +29,15 @@
|
|
|
26
29
|
<key>HeadersPath</key>
|
|
27
30
|
<string>Headers</string>
|
|
28
31
|
<key>LibraryIdentifier</key>
|
|
29
|
-
<string>ios-
|
|
32
|
+
<string>ios-arm64</string>
|
|
30
33
|
<key>LibraryPath</key>
|
|
31
34
|
<string>libderec_library.a</string>
|
|
32
35
|
<key>SupportedArchitectures</key>
|
|
33
36
|
<array>
|
|
34
37
|
<string>arm64</string>
|
|
35
|
-
<string>x86_64</string>
|
|
36
38
|
</array>
|
|
37
39
|
<key>SupportedPlatform</key>
|
|
38
40
|
<string>ios</string>
|
|
39
|
-
<key>SupportedPlatformVariant</key>
|
|
40
|
-
<string>simulator</string>
|
|
41
41
|
</dict>
|
|
42
42
|
</array>
|
|
43
43
|
<key>CFBundlePackageType</key>
|
|
@@ -65,13 +65,6 @@
|
|
|
65
65
|
*/
|
|
66
66
|
#define DEFAULT_THRESHOLD 3
|
|
67
67
|
|
|
68
|
-
/**
|
|
69
|
-
* Number of recent share versions each helper retains, absent an explicit
|
|
70
|
-
* [`DeRecProtocolBuilder::with_keep_versions_count`](crate::protocol::DeRecProtocolBuilder::with_keep_versions_count) call. Sole definition
|
|
71
|
-
* of the value; see [`DEFAULT_THRESHOLD`].
|
|
72
|
-
*/
|
|
73
|
-
#define DEFAULT_KEEP_VERSIONS_COUNT 3
|
|
74
|
-
|
|
75
68
|
/**
|
|
76
69
|
* Maximum accepted transport URI length, in bytes.
|
|
77
70
|
*
|
|
@@ -844,6 +837,11 @@ typedef struct SecretStoreCallbacks {
|
|
|
844
837
|
* arrays (`channel_ids[]`, `versions[]`) cross the FFI as JSON
|
|
845
838
|
* strings, matching the `Vec<u8>` ↔ JSON-array convention used for
|
|
846
839
|
* every other wire-format buffer in this module.
|
|
840
|
+
*
|
|
841
|
+
* `keep_list` returns the JSON encoding of the application's answer:
|
|
842
|
+
* `null` for no list (the owner sends an empty `keepList` and helpers keep
|
|
843
|
+
* every version) or an array of version numbers. An empty buffer or return code `1` also
|
|
844
|
+
* means `null`.
|
|
847
845
|
*/
|
|
848
846
|
typedef struct ShareStoreCallbacks {
|
|
849
847
|
void *user_data;
|
|
@@ -878,6 +876,16 @@ typedef struct ShareStoreCallbacks {
|
|
|
878
876
|
const uint8_t *share_json_ptr,
|
|
879
877
|
size_t share_json_len);
|
|
880
878
|
int32_t (*remove_channel)(void *user_data, uint64_t secret_id, uint64_t channel_id);
|
|
879
|
+
int32_t (*remove_versions)(void *user_data,
|
|
880
|
+
uint64_t secret_id,
|
|
881
|
+
uint64_t channel_id,
|
|
882
|
+
const uint8_t *versions_json_ptr,
|
|
883
|
+
size_t versions_json_len);
|
|
884
|
+
int32_t (*keep_list)(void *user_data,
|
|
885
|
+
uint64_t secret_id,
|
|
886
|
+
uint32_t version,
|
|
887
|
+
uint8_t **out_ptr,
|
|
888
|
+
size_t *out_len);
|
|
881
889
|
void (*free_buffer)(void *user_data, uint8_t *ptr, size_t len);
|
|
882
890
|
} ShareStoreCallbacks;
|
|
883
891
|
|
|
@@ -1001,6 +1009,30 @@ typedef struct DeRecProtocolEventsResult {
|
|
|
1001
1009
|
struct DeRecBuffer events_json;
|
|
1002
1010
|
} DeRecProtocolEventsResult;
|
|
1003
1011
|
|
|
1012
|
+
/**
|
|
1013
|
+
* Result of [`derec_protocol_process`].
|
|
1014
|
+
*
|
|
1015
|
+
* A failed call still carries the events produced before the failure in
|
|
1016
|
+
* `events_json`, and names the channel the failing message came from.
|
|
1017
|
+
*/
|
|
1018
|
+
typedef struct DeRecProtocolProcessResult {
|
|
1019
|
+
struct DeRecError error;
|
|
1020
|
+
/**
|
|
1021
|
+
* UTF-8 JSON array of events, as on [`DeRecProtocolEventsResult`].
|
|
1022
|
+
* Filled on success and on failure.
|
|
1023
|
+
*/
|
|
1024
|
+
struct DeRecBuffer events_json;
|
|
1025
|
+
/**
|
|
1026
|
+
* Whether `channel_id` is set. False on success and when the bytes
|
|
1027
|
+
* were not a decodable envelope.
|
|
1028
|
+
*/
|
|
1029
|
+
bool has_channel_id;
|
|
1030
|
+
/**
|
|
1031
|
+
* The channel the failing message came from.
|
|
1032
|
+
*/
|
|
1033
|
+
uint64_t channel_id;
|
|
1034
|
+
} DeRecProtocolProcessResult;
|
|
1035
|
+
|
|
1004
1036
|
/**
|
|
1005
1037
|
* Result of [`derec_protocol_restore`].
|
|
1006
1038
|
*
|
|
@@ -1685,7 +1717,6 @@ struct ProcessPrePairResponseMessageResult process_pre_pair_response_message(con
|
|
|
1685
1717
|
* "secret_id": "12345678901234567890",
|
|
1686
1718
|
* "own_transports": [{ "uri": "https://example.com/derec", "protocol": "https" }],
|
|
1687
1719
|
* "threshold": 3,
|
|
1688
|
-
* "keep_versions_count": 3,
|
|
1689
1720
|
* "auto_respond_on_failure": false,
|
|
1690
1721
|
* "unpair_ack": 0,
|
|
1691
1722
|
* "auto_reply_to": false,
|
|
@@ -1730,10 +1761,8 @@ struct ProcessPrePairResponseMessageResult process_pre_pair_response_message(con
|
|
|
1730
1761
|
* two entries of the same protocol are rejected. Required: an absent or
|
|
1731
1762
|
* empty list is refused, since a node with no endpoint cannot be reached
|
|
1732
1763
|
* by any peer.
|
|
1733
|
-
* - `threshold
|
|
1734
|
-
*
|
|
1735
|
-
* [`crate::protocol::DEFAULT_KEEP_VERSIONS_COUNT`]. A `threshold` below
|
|
1736
|
-
* `2` is rejected with `DEREC_CODE_INVALID_INPUT`.
|
|
1764
|
+
* - `threshold`: omitted means [`crate::protocol::DEFAULT_THRESHOLD`]. A
|
|
1765
|
+
* `threshold` below `2` is rejected with `DEREC_CODE_INVALID_INPUT`.
|
|
1737
1766
|
* - `unpair_ack`: `0` / `"required"` = Required, `1` / `"not_required"` =
|
|
1738
1767
|
* NotRequired; omitted means Required.
|
|
1739
1768
|
* Any other value is rejected with `DEREC_CODE_FFI_INVALID_ENUM`.
|
|
@@ -1880,7 +1909,8 @@ struct DeRecProtocolEventsResult derec_protocol_start(struct DeRecProtocolHandle
|
|
|
1880
1909
|
|
|
1881
1910
|
/**
|
|
1882
1911
|
* Process an inbound `DeRecMessage` envelope. See
|
|
1883
|
-
* [`crate::protocol::DeRecProtocol::process`].
|
|
1912
|
+
* [`crate::protocol::DeRecProtocol::process`]. On failure the result still
|
|
1913
|
+
* carries the events produced before it and the failing channel.
|
|
1884
1914
|
*
|
|
1885
1915
|
* # Safety
|
|
1886
1916
|
*
|
|
@@ -1888,9 +1918,9 @@ struct DeRecProtocolEventsResult derec_protocol_start(struct DeRecProtocolHandle
|
|
|
1888
1918
|
* [`super::derec_protocol_new`]. `message_ptr`/`message_len` must
|
|
1889
1919
|
* describe a readable byte range.
|
|
1890
1920
|
*/
|
|
1891
|
-
struct
|
|
1892
|
-
|
|
1893
|
-
|
|
1921
|
+
struct DeRecProtocolProcessResult derec_protocol_process(struct DeRecProtocolHandle *handle,
|
|
1922
|
+
const uint8_t *message_ptr,
|
|
1923
|
+
size_t message_len);
|
|
1894
1924
|
|
|
1895
1925
|
/**
|
|
1896
1926
|
* Advance time-driven state without an inbound message. See
|
|
Binary file
|
|
@@ -65,13 +65,6 @@
|
|
|
65
65
|
*/
|
|
66
66
|
#define DEFAULT_THRESHOLD 3
|
|
67
67
|
|
|
68
|
-
/**
|
|
69
|
-
* Number of recent share versions each helper retains, absent an explicit
|
|
70
|
-
* [`DeRecProtocolBuilder::with_keep_versions_count`](crate::protocol::DeRecProtocolBuilder::with_keep_versions_count) call. Sole definition
|
|
71
|
-
* of the value; see [`DEFAULT_THRESHOLD`].
|
|
72
|
-
*/
|
|
73
|
-
#define DEFAULT_KEEP_VERSIONS_COUNT 3
|
|
74
|
-
|
|
75
68
|
/**
|
|
76
69
|
* Maximum accepted transport URI length, in bytes.
|
|
77
70
|
*
|
|
@@ -844,6 +837,11 @@ typedef struct SecretStoreCallbacks {
|
|
|
844
837
|
* arrays (`channel_ids[]`, `versions[]`) cross the FFI as JSON
|
|
845
838
|
* strings, matching the `Vec<u8>` ↔ JSON-array convention used for
|
|
846
839
|
* every other wire-format buffer in this module.
|
|
840
|
+
*
|
|
841
|
+
* `keep_list` returns the JSON encoding of the application's answer:
|
|
842
|
+
* `null` for no list (the owner sends an empty `keepList` and helpers keep
|
|
843
|
+
* every version) or an array of version numbers. An empty buffer or return code `1` also
|
|
844
|
+
* means `null`.
|
|
847
845
|
*/
|
|
848
846
|
typedef struct ShareStoreCallbacks {
|
|
849
847
|
void *user_data;
|
|
@@ -878,6 +876,16 @@ typedef struct ShareStoreCallbacks {
|
|
|
878
876
|
const uint8_t *share_json_ptr,
|
|
879
877
|
size_t share_json_len);
|
|
880
878
|
int32_t (*remove_channel)(void *user_data, uint64_t secret_id, uint64_t channel_id);
|
|
879
|
+
int32_t (*remove_versions)(void *user_data,
|
|
880
|
+
uint64_t secret_id,
|
|
881
|
+
uint64_t channel_id,
|
|
882
|
+
const uint8_t *versions_json_ptr,
|
|
883
|
+
size_t versions_json_len);
|
|
884
|
+
int32_t (*keep_list)(void *user_data,
|
|
885
|
+
uint64_t secret_id,
|
|
886
|
+
uint32_t version,
|
|
887
|
+
uint8_t **out_ptr,
|
|
888
|
+
size_t *out_len);
|
|
881
889
|
void (*free_buffer)(void *user_data, uint8_t *ptr, size_t len);
|
|
882
890
|
} ShareStoreCallbacks;
|
|
883
891
|
|
|
@@ -1001,6 +1009,30 @@ typedef struct DeRecProtocolEventsResult {
|
|
|
1001
1009
|
struct DeRecBuffer events_json;
|
|
1002
1010
|
} DeRecProtocolEventsResult;
|
|
1003
1011
|
|
|
1012
|
+
/**
|
|
1013
|
+
* Result of [`derec_protocol_process`].
|
|
1014
|
+
*
|
|
1015
|
+
* A failed call still carries the events produced before the failure in
|
|
1016
|
+
* `events_json`, and names the channel the failing message came from.
|
|
1017
|
+
*/
|
|
1018
|
+
typedef struct DeRecProtocolProcessResult {
|
|
1019
|
+
struct DeRecError error;
|
|
1020
|
+
/**
|
|
1021
|
+
* UTF-8 JSON array of events, as on [`DeRecProtocolEventsResult`].
|
|
1022
|
+
* Filled on success and on failure.
|
|
1023
|
+
*/
|
|
1024
|
+
struct DeRecBuffer events_json;
|
|
1025
|
+
/**
|
|
1026
|
+
* Whether `channel_id` is set. False on success and when the bytes
|
|
1027
|
+
* were not a decodable envelope.
|
|
1028
|
+
*/
|
|
1029
|
+
bool has_channel_id;
|
|
1030
|
+
/**
|
|
1031
|
+
* The channel the failing message came from.
|
|
1032
|
+
*/
|
|
1033
|
+
uint64_t channel_id;
|
|
1034
|
+
} DeRecProtocolProcessResult;
|
|
1035
|
+
|
|
1004
1036
|
/**
|
|
1005
1037
|
* Result of [`derec_protocol_restore`].
|
|
1006
1038
|
*
|
|
@@ -1685,7 +1717,6 @@ struct ProcessPrePairResponseMessageResult process_pre_pair_response_message(con
|
|
|
1685
1717
|
* "secret_id": "12345678901234567890",
|
|
1686
1718
|
* "own_transports": [{ "uri": "https://example.com/derec", "protocol": "https" }],
|
|
1687
1719
|
* "threshold": 3,
|
|
1688
|
-
* "keep_versions_count": 3,
|
|
1689
1720
|
* "auto_respond_on_failure": false,
|
|
1690
1721
|
* "unpair_ack": 0,
|
|
1691
1722
|
* "auto_reply_to": false,
|
|
@@ -1730,10 +1761,8 @@ struct ProcessPrePairResponseMessageResult process_pre_pair_response_message(con
|
|
|
1730
1761
|
* two entries of the same protocol are rejected. Required: an absent or
|
|
1731
1762
|
* empty list is refused, since a node with no endpoint cannot be reached
|
|
1732
1763
|
* by any peer.
|
|
1733
|
-
* - `threshold
|
|
1734
|
-
*
|
|
1735
|
-
* [`crate::protocol::DEFAULT_KEEP_VERSIONS_COUNT`]. A `threshold` below
|
|
1736
|
-
* `2` is rejected with `DEREC_CODE_INVALID_INPUT`.
|
|
1764
|
+
* - `threshold`: omitted means [`crate::protocol::DEFAULT_THRESHOLD`]. A
|
|
1765
|
+
* `threshold` below `2` is rejected with `DEREC_CODE_INVALID_INPUT`.
|
|
1737
1766
|
* - `unpair_ack`: `0` / `"required"` = Required, `1` / `"not_required"` =
|
|
1738
1767
|
* NotRequired; omitted means Required.
|
|
1739
1768
|
* Any other value is rejected with `DEREC_CODE_FFI_INVALID_ENUM`.
|
|
@@ -1880,7 +1909,8 @@ struct DeRecProtocolEventsResult derec_protocol_start(struct DeRecProtocolHandle
|
|
|
1880
1909
|
|
|
1881
1910
|
/**
|
|
1882
1911
|
* Process an inbound `DeRecMessage` envelope. See
|
|
1883
|
-
* [`crate::protocol::DeRecProtocol::process`].
|
|
1912
|
+
* [`crate::protocol::DeRecProtocol::process`]. On failure the result still
|
|
1913
|
+
* carries the events produced before it and the failing channel.
|
|
1884
1914
|
*
|
|
1885
1915
|
* # Safety
|
|
1886
1916
|
*
|
|
@@ -1888,9 +1918,9 @@ struct DeRecProtocolEventsResult derec_protocol_start(struct DeRecProtocolHandle
|
|
|
1888
1918
|
* [`super::derec_protocol_new`]. `message_ptr`/`message_len` must
|
|
1889
1919
|
* describe a readable byte range.
|
|
1890
1920
|
*/
|
|
1891
|
-
struct
|
|
1892
|
-
|
|
1893
|
-
|
|
1921
|
+
struct DeRecProtocolProcessResult derec_protocol_process(struct DeRecProtocolHandle *handle,
|
|
1922
|
+
const uint8_t *message_ptr,
|
|
1923
|
+
size_t message_len);
|
|
1894
1924
|
|
|
1895
1925
|
/**
|
|
1896
1926
|
* Advance time-driven state without an inbound message. See
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 38454248 bytes
|
|
4
|
+
sha256: 8e4fa480cdb006907de3fbd3c13011ee89816dc78b4c1c22b6a730297f29521a
|
package/lib/errors.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DeRecEvent } from './types';
|
|
1
2
|
/**
|
|
2
3
|
* Every category name `derec_error_category_name` can return, and no others.
|
|
3
4
|
*
|
|
@@ -27,4 +28,12 @@ export interface DeRecError {
|
|
|
27
28
|
* Clear exactly these and retry `restore`.
|
|
28
29
|
*/
|
|
29
30
|
channel_ids?: string[];
|
|
31
|
+
/** The channel the failing inbound message arrived on, when `process()` could tell. */
|
|
32
|
+
channel_id?: string;
|
|
33
|
+
/**
|
|
34
|
+
* On a failed `process()`: the events it produced before failing, such as
|
|
35
|
+
* sharing-round and unpair timeouts. They are not reported again, so
|
|
36
|
+
* handle them as you would a successful call's events.
|
|
37
|
+
*/
|
|
38
|
+
events?: DeRecEvent[];
|
|
30
39
|
}
|
package/lib/protocol.d.ts
CHANGED
|
@@ -38,9 +38,9 @@ export declare function buildRestoreParams(recoveredSecret: Extract<DeRecEvent,
|
|
|
38
38
|
*
|
|
39
39
|
* Every value the caller supplies is forwarded verbatim — defaults, clamping
|
|
40
40
|
* and the meaning of a disabled cleanup policy are library decisions.
|
|
41
|
-
* `threshold`, `
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* `threshold`, `auto_respond_on_failure`, `unpair_ack`, `auto_reply_to`
|
|
42
|
+
* and `auto_accept` are omitted entirely from the emitted config unless
|
|
43
|
+
* their setter was called: `ProtocolConfig` has a
|
|
44
44
|
* `#[serde(default = "...")]` on each of them, reading the same constants
|
|
45
45
|
* `DeRecProtocolBuilder::new` uses, so an absent key resolves to the
|
|
46
46
|
* library's own default rather than one frozen into this shim.
|
|
@@ -74,8 +74,6 @@ export declare class DeRecProtocolBuilder {
|
|
|
74
74
|
withOwnTransports(transports: Endpoint[]): this;
|
|
75
75
|
/** Default: 3. */
|
|
76
76
|
withThreshold(threshold: number): this;
|
|
77
|
-
/** Default: 3. */
|
|
78
|
-
withKeepVersionsCount(count: number): this;
|
|
79
77
|
/**
|
|
80
78
|
* Every field is optional; not calling this at all leaves all four at the
|
|
81
79
|
* library's defaults — `timeouts` is genuinely absent from the JSON in
|
|
@@ -191,11 +189,24 @@ export declare class DeRecProtocol {
|
|
|
191
189
|
accept(actionBytes: Uint8Array): Promise<DeRecEvent[]>;
|
|
192
190
|
reject(actionBytes: Uint8Array, status: StatusEnum, memo: string): Promise<void>;
|
|
193
191
|
getFingerprint(channelId: bigint | number): Promise<string>;
|
|
192
|
+
/**
|
|
193
|
+
* Verify `fingerprint` against the channel's locally-derived one. On
|
|
194
|
+
* match, the channel transitions from `Pending` to `Paired`. Returns
|
|
195
|
+
* `true` on confirmation, `false` on mismatch.
|
|
196
|
+
*
|
|
197
|
+
* On a replica destination, confirming is also the decision to adopt the
|
|
198
|
+
* group's vault: the source's publish is then installed as it arrives,
|
|
199
|
+
* with no further prompt. Ask the user before calling this; to decline,
|
|
200
|
+
* never confirm.
|
|
201
|
+
*/
|
|
194
202
|
verifyFingerprint(channelId: bigint | number, fingerprint: string): Promise<boolean>;
|
|
195
203
|
removeExpiredChannels(olderThanSecs: number): Promise<string[]>;
|
|
196
204
|
/**
|
|
197
205
|
* Rebuild this protocol's `secret_id` namespace from a recovered `Secret`.
|
|
198
206
|
* Pass the `secret` carried by the `SecretRecovered` event verbatim.
|
|
207
|
+
* Recovery and restore are separate steps on purpose: `SecretRecovered`
|
|
208
|
+
* writes nothing. Show the user what was recovered, or ask them, before
|
|
209
|
+
* calling `restore`, which commits it to this device.
|
|
199
210
|
* A helper or member whose `transports` is empty gets no channel: it is
|
|
200
211
|
* reported as a `PeerNotRestored` event in the returned array and the rest
|
|
201
212
|
* of the roster is restored.
|
package/lib/protocol.js
CHANGED
|
@@ -234,9 +234,9 @@ function toBigInt(id) {
|
|
|
234
234
|
*
|
|
235
235
|
* Every value the caller supplies is forwarded verbatim — defaults, clamping
|
|
236
236
|
* and the meaning of a disabled cleanup policy are library decisions.
|
|
237
|
-
* `threshold`, `
|
|
238
|
-
*
|
|
239
|
-
*
|
|
237
|
+
* `threshold`, `auto_respond_on_failure`, `unpair_ack`, `auto_reply_to`
|
|
238
|
+
* and `auto_accept` are omitted entirely from the emitted config unless
|
|
239
|
+
* their setter was called: `ProtocolConfig` has a
|
|
240
240
|
* `#[serde(default = "...")]` on each of them, reading the same constants
|
|
241
241
|
* `DeRecProtocolBuilder::new` uses, so an absent key resolves to the
|
|
242
242
|
* library's own default rather than one frozen into this shim.
|
|
@@ -295,11 +295,6 @@ class DeRecProtocolBuilder {
|
|
|
295
295
|
this.config.threshold = threshold;
|
|
296
296
|
return this;
|
|
297
297
|
}
|
|
298
|
-
/** Default: 3. */
|
|
299
|
-
withKeepVersionsCount(count) {
|
|
300
|
-
this.config.keep_versions_count = count;
|
|
301
|
-
return this;
|
|
302
|
-
}
|
|
303
298
|
/**
|
|
304
299
|
* Every field is optional; not calling this at all leaves all four at the
|
|
305
300
|
* library's defaults — `timeouts` is genuinely absent from the JSON in
|
|
@@ -478,7 +473,17 @@ class DeRecProtocol {
|
|
|
478
473
|
return decodeEvents(buffer);
|
|
479
474
|
}
|
|
480
475
|
async process(message) {
|
|
481
|
-
|
|
476
|
+
let buffer;
|
|
477
|
+
try {
|
|
478
|
+
buffer = (await this.host.process(message));
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
const failure = error;
|
|
482
|
+
if (Array.isArray(failure.events)) {
|
|
483
|
+
failure.events = reviveEventBytes(failure.events);
|
|
484
|
+
}
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
482
487
|
return decodeEvents(buffer);
|
|
483
488
|
}
|
|
484
489
|
async tick() {
|
|
@@ -495,6 +500,16 @@ class DeRecProtocol {
|
|
|
495
500
|
async getFingerprint(channelId) {
|
|
496
501
|
return (await this.host.getFingerprint(toBigInt(channelId)));
|
|
497
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Verify `fingerprint` against the channel's locally-derived one. On
|
|
505
|
+
* match, the channel transitions from `Pending` to `Paired`. Returns
|
|
506
|
+
* `true` on confirmation, `false` on mismatch.
|
|
507
|
+
*
|
|
508
|
+
* On a replica destination, confirming is also the decision to adopt the
|
|
509
|
+
* group's vault: the source's publish is then installed as it arrives,
|
|
510
|
+
* with no further prompt. Ask the user before calling this; to decline,
|
|
511
|
+
* never confirm.
|
|
512
|
+
*/
|
|
498
513
|
async verifyFingerprint(channelId, fingerprint) {
|
|
499
514
|
return (await this.host.verifyFingerprint(toBigInt(channelId), fingerprint));
|
|
500
515
|
}
|
|
@@ -505,6 +520,9 @@ class DeRecProtocol {
|
|
|
505
520
|
/**
|
|
506
521
|
* Rebuild this protocol's `secret_id` namespace from a recovered `Secret`.
|
|
507
522
|
* Pass the `secret` carried by the `SecretRecovered` event verbatim.
|
|
523
|
+
* Recovery and restore are separate steps on purpose: `SecretRecovered`
|
|
524
|
+
* writes nothing. Show the user what was recovered, or ask them, before
|
|
525
|
+
* calling `restore`, which commits it to this device.
|
|
508
526
|
* A helper or member whose `transports` is empty gets no channel: it is
|
|
509
527
|
* reported as a `PeerNotRestored` event in the returned array and the rest
|
|
510
528
|
* of the roster is restored.
|
package/lib/types.d.ts
CHANGED
|
@@ -191,6 +191,35 @@ export interface ShareStore {
|
|
|
191
191
|
save(secretId: string, channelId: string, share: Share): Promise<void>;
|
|
192
192
|
latestVersion(secretId: string): Promise<number | null>;
|
|
193
193
|
removeChannel(secretId: string, channelId: string): Promise<void>;
|
|
194
|
+
/**
|
|
195
|
+
* Drop the shares stored under `(secretId, channelId)` at each of
|
|
196
|
+
* `versions`. Idempotent: a version that is not stored is skipped, and
|
|
197
|
+
* an empty array is a no-op.
|
|
198
|
+
*
|
|
199
|
+
* A helper calls this to apply `StoreShareRequestMessage.keepList`, the
|
|
200
|
+
* complete set of versions the owner wants retained: every stored
|
|
201
|
+
* version outside it is removed once the incoming share is persisted.
|
|
202
|
+
* Shares under other channels or partitions must be left untouched.
|
|
203
|
+
*/
|
|
204
|
+
removeVersions(secretId: string, channelId: string, versions: number[]): Promise<void>;
|
|
205
|
+
/**
|
|
206
|
+
* Owner only: the versions every helper keeps after the owner distributes
|
|
207
|
+
* `version`. Asked once per sharing round, before anything is sent,
|
|
208
|
+
* including the rounds the library starts itself; the answer becomes
|
|
209
|
+
* `keepList` for every helper.
|
|
210
|
+
*
|
|
211
|
+
* Return `null` or `undefined` to send no `keepList` (it goes out empty):
|
|
212
|
+
* helpers then keep every version they hold. An app that wants to cap how
|
|
213
|
+
* many versions helpers retain returns that cap here. A returned list is
|
|
214
|
+
* used as is, plus `version`, which the library always adds. Helpers
|
|
215
|
+
* delete every version that is not listed, so list every version that
|
|
216
|
+
* could still become the latest: those that committed (for example, whose
|
|
217
|
+
* `SharingComplete` reported `threshold_met`) and those whose round is
|
|
218
|
+
* still open. Leave out only versions whose round failed or that the user
|
|
219
|
+
* rolled back; a list that leaves out too much can make the secret
|
|
220
|
+
* unrecoverable.
|
|
221
|
+
*/
|
|
222
|
+
keepList(secretId: string, version: number): Promise<number[] | null | undefined>;
|
|
194
223
|
}
|
|
195
224
|
export interface UserSecretEntry {
|
|
196
225
|
id: Uint8Array;
|
|
@@ -519,6 +548,15 @@ export interface Timeouts {
|
|
|
519
548
|
/** `ReplicaDiscovery` takes no parameters: the group and this device's own version
|
|
520
549
|
* are both read from the stores. The argument may be omitted entirely. */
|
|
521
550
|
export type ReplicaDiscoveryParams = Record<string, never>;
|
|
551
|
+
/** Any member may remove any member, the source included: a lost or stolen
|
|
552
|
+
* source must be removable by the devices that remain, and the library
|
|
553
|
+
* checks no role. Ask the user before starting this flow, above all when it
|
|
554
|
+
* names the source. Removing the source promotes the first remaining member
|
|
555
|
+
* in the order the channel store's `listReplicas` returns. The removed
|
|
556
|
+
* member is not asked and gets no event when told to leave: when a roster
|
|
557
|
+
* excluding it arrives it drops its whole `secret_id` partition and emits
|
|
558
|
+
* `SelfRemovedFromGroup`. The secret survives on the remaining members and
|
|
559
|
+
* the helpers. */
|
|
522
560
|
export interface UnpairReplicaParams {
|
|
523
561
|
/** The member to remove, as a **decimal** `u64` string — the same form
|
|
524
562
|
* `ReplicaPaired.peer_replica_id` hands back. A value naming no current
|
|
@@ -601,8 +639,11 @@ export type DeRecEvent = {
|
|
|
601
639
|
}
|
|
602
640
|
/** A group member refused a secret sync. Keyed by `replica_id`, not
|
|
603
641
|
* `channel_id`: every member answers on the one group channel. A
|
|
604
|
-
* `VERSION_CONFLICT` status means
|
|
605
|
-
*
|
|
642
|
+
* `VERSION_CONFLICT` status means another member holds a different copy
|
|
643
|
+
* of this version: do not publish from this device again until the
|
|
644
|
+
* conflict is resolved. Run `start(FlowKind.ReplicaDiscovery)` to receive
|
|
645
|
+
* the group's copy as `ReplicaVersionConflict`, merge, and publish the
|
|
646
|
+
* result once with `start(FlowKind.ProtectSecret)`. */
|
|
606
647
|
| {
|
|
607
648
|
type: "ReplicaSyncRejected";
|
|
608
649
|
replica_id: string;
|
|
@@ -636,7 +677,8 @@ export type DeRecEvent = {
|
|
|
636
677
|
/** This device left the group and dropped its whole `secret_id` partition —
|
|
637
678
|
* group channel, helper channels, shares, secrets and the snapshot. Fires
|
|
638
679
|
* only once it was told to leave *and* has since seen a roster excluding
|
|
639
|
-
* it; absence alone never destroys a copy of the secret.
|
|
680
|
+
* it; absence alone never destroys a copy of the secret. The teardown is
|
|
681
|
+
* automatic: this device is not asked first and gets no earlier event. */
|
|
640
682
|
| {
|
|
641
683
|
type: "SelfRemovedFromGroup";
|
|
642
684
|
version: number;
|
|
@@ -662,6 +704,16 @@ export type DeRecEvent = {
|
|
|
662
704
|
type: "ShareVerified";
|
|
663
705
|
channel_id: string;
|
|
664
706
|
version: number;
|
|
707
|
+
}
|
|
708
|
+
/** A helper refused a verification challenge: its response carried a
|
|
709
|
+
* non-OK `status` instead of a proof. The challenge is spent; a new
|
|
710
|
+
* `VerifyShares` round challenges the helper again. */
|
|
711
|
+
| {
|
|
712
|
+
type: "ShareVerifyRejected";
|
|
713
|
+
channel_id: string;
|
|
714
|
+
version: number;
|
|
715
|
+
status: StatusEnum;
|
|
716
|
+
memo: string;
|
|
665
717
|
} | {
|
|
666
718
|
type: "SecretsDiscovered";
|
|
667
719
|
channel_id: string;
|
|
@@ -682,6 +734,33 @@ export type DeRecEvent = {
|
|
|
682
734
|
shares_received: number;
|
|
683
735
|
error: string;
|
|
684
736
|
}
|
|
737
|
+
/** A helper refused a recovery share request: its response carried a
|
|
738
|
+
* non-OK `status` (e.g. `UNKNOWN_SHARE_VERSION`) instead of a share. The
|
|
739
|
+
* refusal is not collected — it does not count towards `shares_received`
|
|
740
|
+
* and the recovery stays open for the other helpers' shares — but it does
|
|
741
|
+
* answer that helper's `RecoverSecretStarted`. */
|
|
742
|
+
| {
|
|
743
|
+
type: "RecoveryShareRefused";
|
|
744
|
+
channel_id: string;
|
|
745
|
+
version: number;
|
|
746
|
+
status: StatusEnum;
|
|
747
|
+
memo: string;
|
|
748
|
+
}
|
|
749
|
+
/** A helper answered with a share that cannot be part of the secret;
|
|
750
|
+
* `reason` says how it failed. `Malformed` and `InvalidProof` are judged
|
|
751
|
+
* on arrival; `Inconsistent` (valid on its own but disagreeing with the
|
|
752
|
+
* shares the secret was rebuilt from) is reported alongside
|
|
753
|
+
* `SecretRecovered`, once per helper. The share is set aside — it does
|
|
754
|
+
* not count towards `shares_received` and never blocks the recovery. An
|
|
755
|
+
* honest helper never sends one, so the app may treat it as a sign of a
|
|
756
|
+
* damaged or compromised helper, e.g. offer to unpair it. It also
|
|
757
|
+
* answers that helper's `RecoverSecretStarted`. */
|
|
758
|
+
| {
|
|
759
|
+
type: "RecoveryShareCorrupted";
|
|
760
|
+
channel_id: string;
|
|
761
|
+
version: number;
|
|
762
|
+
reason: CorruptionReason;
|
|
763
|
+
}
|
|
685
764
|
/** Recovery completed — the typed `Secret` snapshot the owner
|
|
686
765
|
* originally protected. Mirrors `ReplicaSecretReceived.secret`:
|
|
687
766
|
* `secrets` is the user-facing `Vec<UserSecret>` the application
|
|
@@ -866,7 +945,10 @@ export type DeRecEvent = {
|
|
|
866
945
|
* `ReplicaSyncRejected`. Both copies are complete states — the held one
|
|
867
946
|
* is in the local stores, the incoming one is `secret`. Resolve by
|
|
868
947
|
* publishing the chosen state with `start(FlowKind.ProtectSecret)`; the
|
|
869
|
-
* next version supersedes both on every member and helper.
|
|
948
|
+
* next version supersedes both on every member and helper. Until then,
|
|
949
|
+
* do not publish from this device: any further `ProtectSecret` is a
|
|
950
|
+
* higher version that every other member applies over its own copy,
|
|
951
|
+
* losing the change it never merged.
|
|
870
952
|
*
|
|
871
953
|
* `held_author_replica_id` / `incoming_author_replica_id` are the
|
|
872
954
|
* decimal `replica_id` of each copy's publisher, or `null` when that
|
|
@@ -1177,6 +1259,13 @@ export type IgnoreReason = "PendingVerification" | "Expired";
|
|
|
1177
1259
|
/** Why a `PeerNotRestored` event left a roster entry without a channel.
|
|
1178
1260
|
* Matches the Rust `NotRestoredReason` discriminants one-for-one. */
|
|
1179
1261
|
export type NotRestoredReason = "NoTransports";
|
|
1262
|
+
/** Why a `RecoveryShareCorrupted` event set a helper's share aside.
|
|
1263
|
+
* Matches the Rust `CorruptionReason` discriminants one-for-one:
|
|
1264
|
+
* `Malformed` — no decodable share for the requested secret and version;
|
|
1265
|
+
* `InvalidProof` — the share fails its own Merkle proof;
|
|
1266
|
+
* `Inconsistent` — valid on its own, but its commitment root or ciphertext
|
|
1267
|
+
* disagrees with the shares the secret was rebuilt from. */
|
|
1268
|
+
export type CorruptionReason = "Malformed" | "InvalidProof" | "Inconsistent";
|
|
1180
1269
|
/**
|
|
1181
1270
|
* The label vocabulary for `ActionRequired.action_kind` and
|
|
1182
1271
|
* `AutoAccepted.action_kind` — one value per pending-action kind the
|
package/package.json
CHANGED
package/src/errors.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) 2026 DeRec Alliance. All rights reserved.
|
|
3
3
|
|
|
4
|
+
import type { DeRecEvent } from './types';
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Every category name `derec_error_category_name` can return, and no others.
|
|
6
8
|
*
|
|
@@ -49,4 +51,12 @@ export interface DeRecError {
|
|
|
49
51
|
* Clear exactly these and retry `restore`.
|
|
50
52
|
*/
|
|
51
53
|
channel_ids?: string[];
|
|
54
|
+
/** The channel the failing inbound message arrived on, when `process()` could tell. */
|
|
55
|
+
channel_id?: string;
|
|
56
|
+
/**
|
|
57
|
+
* On a failed `process()`: the events it produced before failing, such as
|
|
58
|
+
* sharing-round and unpair timeouts. They are not reported again, so
|
|
59
|
+
* handle them as you would a successful call's events.
|
|
60
|
+
*/
|
|
61
|
+
events?: DeRecEvent[];
|
|
52
62
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -280,9 +280,9 @@ function toBigInt(id: bigint | number): bigint {
|
|
|
280
280
|
*
|
|
281
281
|
* Every value the caller supplies is forwarded verbatim — defaults, clamping
|
|
282
282
|
* and the meaning of a disabled cleanup policy are library decisions.
|
|
283
|
-
* `threshold`, `
|
|
284
|
-
*
|
|
285
|
-
*
|
|
283
|
+
* `threshold`, `auto_respond_on_failure`, `unpair_ack`, `auto_reply_to`
|
|
284
|
+
* and `auto_accept` are omitted entirely from the emitted config unless
|
|
285
|
+
* their setter was called: `ProtocolConfig` has a
|
|
286
286
|
* `#[serde(default = "...")]` on each of them, reading the same constants
|
|
287
287
|
* `DeRecProtocolBuilder::new` uses, so an absent key resolves to the
|
|
288
288
|
* library's own default rather than one frozen into this shim.
|
|
@@ -352,12 +352,6 @@ export class DeRecProtocolBuilder {
|
|
|
352
352
|
return this;
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
-
/** Default: 3. */
|
|
356
|
-
withKeepVersionsCount(count: number): this {
|
|
357
|
-
this.config.keep_versions_count = count;
|
|
358
|
-
return this;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
355
|
/**
|
|
362
356
|
* Every field is optional; not calling this at all leaves all four at the
|
|
363
357
|
* library's defaults — `timeouts` is genuinely absent from the JSON in
|
|
@@ -579,7 +573,16 @@ export class DeRecProtocol {
|
|
|
579
573
|
}
|
|
580
574
|
|
|
581
575
|
async process(message: Uint8Array): Promise<DeRecEvent[]> {
|
|
582
|
-
|
|
576
|
+
let buffer: ArrayBuffer;
|
|
577
|
+
try {
|
|
578
|
+
buffer = (await this.host.process(message)) as ArrayBuffer;
|
|
579
|
+
} catch (error) {
|
|
580
|
+
const failure = error as { events?: unknown };
|
|
581
|
+
if (Array.isArray(failure.events)) {
|
|
582
|
+
failure.events = reviveEventBytes(failure.events);
|
|
583
|
+
}
|
|
584
|
+
throw error;
|
|
585
|
+
}
|
|
583
586
|
return decodeEvents(buffer);
|
|
584
587
|
}
|
|
585
588
|
|
|
@@ -601,6 +604,16 @@ export class DeRecProtocol {
|
|
|
601
604
|
return (await this.host.getFingerprint(toBigInt(channelId))) as string;
|
|
602
605
|
}
|
|
603
606
|
|
|
607
|
+
/**
|
|
608
|
+
* Verify `fingerprint` against the channel's locally-derived one. On
|
|
609
|
+
* match, the channel transitions from `Pending` to `Paired`. Returns
|
|
610
|
+
* `true` on confirmation, `false` on mismatch.
|
|
611
|
+
*
|
|
612
|
+
* On a replica destination, confirming is also the decision to adopt the
|
|
613
|
+
* group's vault: the source's publish is then installed as it arrives,
|
|
614
|
+
* with no further prompt. Ask the user before calling this; to decline,
|
|
615
|
+
* never confirm.
|
|
616
|
+
*/
|
|
604
617
|
async verifyFingerprint(channelId: bigint | number, fingerprint: string): Promise<boolean> {
|
|
605
618
|
return (await this.host.verifyFingerprint(toBigInt(channelId), fingerprint)) as boolean;
|
|
606
619
|
}
|
|
@@ -613,6 +626,9 @@ export class DeRecProtocol {
|
|
|
613
626
|
/**
|
|
614
627
|
* Rebuild this protocol's `secret_id` namespace from a recovered `Secret`.
|
|
615
628
|
* Pass the `secret` carried by the `SecretRecovered` event verbatim.
|
|
629
|
+
* Recovery and restore are separate steps on purpose: `SecretRecovered`
|
|
630
|
+
* writes nothing. Show the user what was recovered, or ask them, before
|
|
631
|
+
* calling `restore`, which commits it to this device.
|
|
616
632
|
* A helper or member whose `transports` is empty gets no channel: it is
|
|
617
633
|
* reported as a `PeerNotRestored` event in the returned array and the rest
|
|
618
634
|
* of the roster is restored.
|
package/src/types.ts
CHANGED
|
@@ -261,6 +261,42 @@ export interface ShareStore {
|
|
|
261
261
|
save(secretId: string, channelId: string, share: Share): Promise<void>;
|
|
262
262
|
latestVersion(secretId: string): Promise<number | null>;
|
|
263
263
|
removeChannel(secretId: string, channelId: string): Promise<void>;
|
|
264
|
+
/**
|
|
265
|
+
* Drop the shares stored under `(secretId, channelId)` at each of
|
|
266
|
+
* `versions`. Idempotent: a version that is not stored is skipped, and
|
|
267
|
+
* an empty array is a no-op.
|
|
268
|
+
*
|
|
269
|
+
* A helper calls this to apply `StoreShareRequestMessage.keepList`, the
|
|
270
|
+
* complete set of versions the owner wants retained: every stored
|
|
271
|
+
* version outside it is removed once the incoming share is persisted.
|
|
272
|
+
* Shares under other channels or partitions must be left untouched.
|
|
273
|
+
*/
|
|
274
|
+
removeVersions(
|
|
275
|
+
secretId: string,
|
|
276
|
+
channelId: string,
|
|
277
|
+
versions: number[],
|
|
278
|
+
): Promise<void>;
|
|
279
|
+
/**
|
|
280
|
+
* Owner only: the versions every helper keeps after the owner distributes
|
|
281
|
+
* `version`. Asked once per sharing round, before anything is sent,
|
|
282
|
+
* including the rounds the library starts itself; the answer becomes
|
|
283
|
+
* `keepList` for every helper.
|
|
284
|
+
*
|
|
285
|
+
* Return `null` or `undefined` to send no `keepList` (it goes out empty):
|
|
286
|
+
* helpers then keep every version they hold. An app that wants to cap how
|
|
287
|
+
* many versions helpers retain returns that cap here. A returned list is
|
|
288
|
+
* used as is, plus `version`, which the library always adds. Helpers
|
|
289
|
+
* delete every version that is not listed, so list every version that
|
|
290
|
+
* could still become the latest: those that committed (for example, whose
|
|
291
|
+
* `SharingComplete` reported `threshold_met`) and those whose round is
|
|
292
|
+
* still open. Leave out only versions whose round failed or that the user
|
|
293
|
+
* rolled back; a list that leaves out too much can make the secret
|
|
294
|
+
* unrecoverable.
|
|
295
|
+
*/
|
|
296
|
+
keepList(
|
|
297
|
+
secretId: string,
|
|
298
|
+
version: number,
|
|
299
|
+
): Promise<number[] | null | undefined>;
|
|
264
300
|
}
|
|
265
301
|
|
|
266
302
|
export interface UserSecretEntry {
|
|
@@ -653,6 +689,15 @@ export interface Timeouts {
|
|
|
653
689
|
* are both read from the stores. The argument may be omitted entirely. */
|
|
654
690
|
export type ReplicaDiscoveryParams = Record<string, never>;
|
|
655
691
|
|
|
692
|
+
/** Any member may remove any member, the source included: a lost or stolen
|
|
693
|
+
* source must be removable by the devices that remain, and the library
|
|
694
|
+
* checks no role. Ask the user before starting this flow, above all when it
|
|
695
|
+
* names the source. Removing the source promotes the first remaining member
|
|
696
|
+
* in the order the channel store's `listReplicas` returns. The removed
|
|
697
|
+
* member is not asked and gets no event when told to leave: when a roster
|
|
698
|
+
* excluding it arrives it drops its whole `secret_id` partition and emits
|
|
699
|
+
* `SelfRemovedFromGroup`. The secret survives on the remaining members and
|
|
700
|
+
* the helpers. */
|
|
656
701
|
export interface UnpairReplicaParams {
|
|
657
702
|
/** The member to remove, as a **decimal** `u64` string — the same form
|
|
658
703
|
* `ReplicaPaired.peer_replica_id` hands back. A value naming no current
|
|
@@ -724,8 +769,11 @@ export type DeRecEvent =
|
|
|
724
769
|
| { type: "SharingComplete"; version: number; confirmed_count: number; failed_count: number; threshold_met: boolean }
|
|
725
770
|
/** A group member refused a secret sync. Keyed by `replica_id`, not
|
|
726
771
|
* `channel_id`: every member answers on the one group channel. A
|
|
727
|
-
* `VERSION_CONFLICT` status means
|
|
728
|
-
*
|
|
772
|
+
* `VERSION_CONFLICT` status means another member holds a different copy
|
|
773
|
+
* of this version: do not publish from this device again until the
|
|
774
|
+
* conflict is resolved. Run `start(FlowKind.ReplicaDiscovery)` to receive
|
|
775
|
+
* the group's copy as `ReplicaVersionConflict`, merge, and publish the
|
|
776
|
+
* result once with `start(FlowKind.ProtectSecret)`. */
|
|
729
777
|
| {
|
|
730
778
|
type: "ReplicaSyncRejected";
|
|
731
779
|
replica_id: string;
|
|
@@ -748,7 +796,8 @@ export type DeRecEvent =
|
|
|
748
796
|
/** This device left the group and dropped its whole `secret_id` partition —
|
|
749
797
|
* group channel, helper channels, shares, secrets and the snapshot. Fires
|
|
750
798
|
* only once it was told to leave *and* has since seen a roster excluding
|
|
751
|
-
* it; absence alone never destroys a copy of the secret.
|
|
799
|
+
* it; absence alone never destroys a copy of the secret. The teardown is
|
|
800
|
+
* automatic: this device is not asked first and gets no earlier event. */
|
|
752
801
|
| { type: "SelfRemovedFromGroup"; version: number }
|
|
753
802
|
/** A replica catch-up finished. `fetched_from` is absent when this device
|
|
754
803
|
* was already current, in which case no hydration event follows. */
|
|
@@ -764,6 +813,10 @@ export type DeRecEvent =
|
|
|
764
813
|
* library keeps no durable per-member sync state. */
|
|
765
814
|
| { type: "ReplicaSyncComplete"; version: number; synced: string[]; behind: string[] }
|
|
766
815
|
| { type: "ShareVerified"; channel_id: string; version: number }
|
|
816
|
+
/** A helper refused a verification challenge: its response carried a
|
|
817
|
+
* non-OK `status` instead of a proof. The challenge is spent; a new
|
|
818
|
+
* `VerifyShares` round challenges the helper again. */
|
|
819
|
+
| { type: "ShareVerifyRejected"; channel_id: string; version: number; status: StatusEnum; memo: string }
|
|
767
820
|
| {
|
|
768
821
|
type: "SecretsDiscovered";
|
|
769
822
|
channel_id: string;
|
|
@@ -772,6 +825,22 @@ export type DeRecEvent =
|
|
|
772
825
|
}
|
|
773
826
|
| { type: "RecoveryShareReceived"; channel_id: string; shares_received: number }
|
|
774
827
|
| { type: "RecoveryShareError"; channel_id: string; shares_received: number; error: string }
|
|
828
|
+
/** A helper refused a recovery share request: its response carried a
|
|
829
|
+
* non-OK `status` (e.g. `UNKNOWN_SHARE_VERSION`) instead of a share. The
|
|
830
|
+
* refusal is not collected — it does not count towards `shares_received`
|
|
831
|
+
* and the recovery stays open for the other helpers' shares — but it does
|
|
832
|
+
* answer that helper's `RecoverSecretStarted`. */
|
|
833
|
+
| { type: "RecoveryShareRefused"; channel_id: string; version: number; status: StatusEnum; memo: string }
|
|
834
|
+
/** A helper answered with a share that cannot be part of the secret;
|
|
835
|
+
* `reason` says how it failed. `Malformed` and `InvalidProof` are judged
|
|
836
|
+
* on arrival; `Inconsistent` (valid on its own but disagreeing with the
|
|
837
|
+
* shares the secret was rebuilt from) is reported alongside
|
|
838
|
+
* `SecretRecovered`, once per helper. The share is set aside — it does
|
|
839
|
+
* not count towards `shares_received` and never blocks the recovery. An
|
|
840
|
+
* honest helper never sends one, so the app may treat it as a sign of a
|
|
841
|
+
* damaged or compromised helper, e.g. offer to unpair it. It also
|
|
842
|
+
* answers that helper's `RecoverSecretStarted`. */
|
|
843
|
+
| { type: "RecoveryShareCorrupted"; channel_id: string; version: number; reason: CorruptionReason }
|
|
775
844
|
/** Recovery completed — the typed `Secret` snapshot the owner
|
|
776
845
|
* originally protected. Mirrors `ReplicaSecretReceived.secret`:
|
|
777
846
|
* `secrets` is the user-facing `Vec<UserSecret>` the application
|
|
@@ -949,7 +1018,10 @@ export type DeRecEvent =
|
|
|
949
1018
|
* `ReplicaSyncRejected`. Both copies are complete states — the held one
|
|
950
1019
|
* is in the local stores, the incoming one is `secret`. Resolve by
|
|
951
1020
|
* publishing the chosen state with `start(FlowKind.ProtectSecret)`; the
|
|
952
|
-
* next version supersedes both on every member and helper.
|
|
1021
|
+
* next version supersedes both on every member and helper. Until then,
|
|
1022
|
+
* do not publish from this device: any further `ProtectSecret` is a
|
|
1023
|
+
* higher version that every other member applies over its own copy,
|
|
1024
|
+
* losing the change it never merged.
|
|
953
1025
|
*
|
|
954
1026
|
* `held_author_replica_id` / `incoming_author_replica_id` are the
|
|
955
1027
|
* decimal `replica_id` of each copy's publisher, or `null` when that
|
|
@@ -1232,6 +1304,14 @@ export type IgnoreReason = "PendingVerification" | "Expired";
|
|
|
1232
1304
|
* Matches the Rust `NotRestoredReason` discriminants one-for-one. */
|
|
1233
1305
|
export type NotRestoredReason = "NoTransports";
|
|
1234
1306
|
|
|
1307
|
+
/** Why a `RecoveryShareCorrupted` event set a helper's share aside.
|
|
1308
|
+
* Matches the Rust `CorruptionReason` discriminants one-for-one:
|
|
1309
|
+
* `Malformed` — no decodable share for the requested secret and version;
|
|
1310
|
+
* `InvalidProof` — the share fails its own Merkle proof;
|
|
1311
|
+
* `Inconsistent` — valid on its own, but its commitment root or ciphertext
|
|
1312
|
+
* disagrees with the shares the secret was rebuilt from. */
|
|
1313
|
+
export type CorruptionReason = "Malformed" | "InvalidProof" | "Inconsistent";
|
|
1314
|
+
|
|
1235
1315
|
/**
|
|
1236
1316
|
* The label vocabulary for `ActionRequired.action_kind` and
|
|
1237
1317
|
* `AutoAccepted.action_kind` — one value per pending-action kind the
|