@derec-alliance/nodejs 0.0.1-alpha.9 → 0.0.2
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 +4 -5
- package/derec_library.d.ts +61 -7
- package/derec_library.js +178 -107
- package/derec_library_bg.wasm +0 -0
- package/derec_library_bg.wasm.d.ts +31 -28
- package/index.d.ts +375 -28
- package/index.js +2 -2
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -28,11 +28,76 @@ export interface SecretStore {
|
|
|
28
28
|
remove(secretId: string, channelId: string, kind: 0 | 1 | 2): Promise<void>;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Channel-record persistence.
|
|
33
|
+
*
|
|
34
|
+
* A record is addressed by `(channelId, replicaId)`. A `replicaId` of `"0"` —
|
|
35
|
+
* the value the protocol reserves as "absent" — addresses the helper channel
|
|
36
|
+
* at `channelId`.
|
|
37
|
+
*
|
|
38
|
+
* Any other value addresses that member of the replica group, and the member
|
|
39
|
+
* is keyed by **`replicaId` alone**. The accompanying `channelId` is context,
|
|
40
|
+
* not part of the key: a member moves between channels during an admission
|
|
41
|
+
* handover while remaining the same member, and a lookup that required both to
|
|
42
|
+
* match would miss it exactly when the move needs to be observed. Keep two
|
|
43
|
+
* maps — helpers by `channelId`, members by `replicaId` — not one keyed by the
|
|
44
|
+
* pair.
|
|
45
|
+
*
|
|
46
|
+
* `load`/`save` bytes are a JSON-encoded `ChannelRecord`: an externally
|
|
47
|
+
* tagged union carrying exactly one of `Helper` or `Replica`.
|
|
48
|
+
*
|
|
49
|
+
* `listHelpers` and `listReplicas` are **not** arrays of that union — they
|
|
50
|
+
* return a JSON array of the **inner** records with the tag stripped:
|
|
51
|
+
* `[{ channel_id, transport, ... }, ...]`, `HelperChannel` for the first and
|
|
52
|
+
* `ReplicaMember` for the second. Wrapping each element back in
|
|
53
|
+
* `{ "Helper": ... }` will not decode.
|
|
54
|
+
*
|
|
55
|
+
* Build that array by **splicing the stored bytes as text** — the payloads are
|
|
56
|
+
* opaque, so persist and re-emit them verbatim:
|
|
57
|
+
*
|
|
58
|
+
* ```js
|
|
59
|
+
* const inner = rows.map((r) => new TextDecoder().decode(r));
|
|
60
|
+
* return new TextEncoder().encode(`[${inner.join(",")}]`);
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* Do not `JSON.parse` and re-serialise. Every id in these records is a `u64`,
|
|
64
|
+
* and `JSON.parse` silently rounds anything above 2^53 — the corruption only
|
|
65
|
+
* appears once a real id happens to be large.
|
|
66
|
+
*/
|
|
31
67
|
export interface ChannelStore {
|
|
32
|
-
load(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
68
|
+
load(
|
|
69
|
+
secretId: string,
|
|
70
|
+
channelId: string,
|
|
71
|
+
replicaId: string,
|
|
72
|
+
): Promise<Uint8Array | null | undefined>;
|
|
73
|
+
save(
|
|
74
|
+
secretId: string,
|
|
75
|
+
channelId: string,
|
|
76
|
+
replicaId: string,
|
|
77
|
+
bytes: Uint8Array,
|
|
78
|
+
): Promise<void>;
|
|
79
|
+
remove(secretId: string, channelId: string, replicaId: string): Promise<boolean>;
|
|
80
|
+
/** JSON array of the helper channels stored under `secretId`. */
|
|
81
|
+
listHelpers(secretId: string): Promise<Uint8Array | null | undefined>;
|
|
82
|
+
/**
|
|
83
|
+
* JSON array of the replica-group members stored under `secretId`,
|
|
84
|
+
* including this device's own row.
|
|
85
|
+
*
|
|
86
|
+
* The order is significant in exactly one situation. A group has one member
|
|
87
|
+
* holding the `Source` role; when it is removed, the protocol promotes the
|
|
88
|
+
* first element of this array that is neither the departing member nor
|
|
89
|
+
* itself leaving. Ordering this array is therefore how an application
|
|
90
|
+
* chooses its succession policy. The choice is read once, on the single
|
|
91
|
+
* device running the removal, and is then published in the roster, so
|
|
92
|
+
* implementations on different devices need not agree on order. Nothing else
|
|
93
|
+
* consults it.
|
|
94
|
+
*
|
|
95
|
+
* Returning an arbitrary order is correct and simply delegates the choice to
|
|
96
|
+
* the storage — note that a SQL `SELECT` without `ORDER BY` and `Map`
|
|
97
|
+
* insertion order after arbitrary edits are both effectively arbitrary.
|
|
98
|
+
* Order explicitly to make succession predictable.
|
|
99
|
+
*/
|
|
100
|
+
listReplicas(secretId: string): Promise<Uint8Array | null | undefined>;
|
|
36
101
|
linkChannel(
|
|
37
102
|
secretId: string,
|
|
38
103
|
channelId: string,
|
|
@@ -90,7 +155,7 @@ export interface UserSecretStore {
|
|
|
90
155
|
* returns the exact blob it received, `remove` drops the row, and
|
|
91
156
|
* `loadAll` returns every blob whose `kind` matches the requested
|
|
92
157
|
* category (`0` = PendingVerification, `1` = PendingRecovery,
|
|
93
|
-
* `2` = PendingUnpair, `3` = SharingRound).
|
|
158
|
+
* `2` = PendingUnpair, `3` = SharingRound, `4` = PendingSyncCheck).
|
|
94
159
|
*
|
|
95
160
|
* Rows are keyed by `(secretId, StateKey)` — the `keyJson` buffer is
|
|
96
161
|
* a JSON object `{ kind, channel_id?, version? }` matching the `kind`
|
|
@@ -110,9 +175,27 @@ export interface StateStore {
|
|
|
110
175
|
keyJson: Uint8Array,
|
|
111
176
|
): Promise<Uint8Array | null | undefined>;
|
|
112
177
|
remove(secretId: string, keyJson: Uint8Array): Promise<boolean>;
|
|
113
|
-
loadAll(secretId: string, kind: 0 | 1 | 2 | 3): Promise<Uint8Array[]>;
|
|
178
|
+
loadAll(secretId: string, kind: 0 | 1 | 2 | 3 | 4): Promise<Uint8Array[]>;
|
|
114
179
|
}
|
|
115
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Outbound message delivery.
|
|
183
|
+
*
|
|
184
|
+
* This is a mailbox, not a request/response channel: every peer has an
|
|
185
|
+
* address, and a reply is posted to that address rather than returned from
|
|
186
|
+
* `process`. Where both sides are reachable services, a one-way push is all
|
|
187
|
+
* that is needed.
|
|
188
|
+
*
|
|
189
|
+
* A peer that cannot be addressed — a phone, a browser, anything behind NAT —
|
|
190
|
+
* breaks that silently: the reply is handed to `send`, goes nowhere, and
|
|
191
|
+
* nothing reports an error. Such a service must answer on the connection the
|
|
192
|
+
* request arrived on, by building the protocol per request with a `Transport`
|
|
193
|
+
* that collects into a buffer instead of sending, then returning the collected
|
|
194
|
+
* message whose trace id matches the inbound envelope's
|
|
195
|
+
* (`envelope_read_trace_id`). One call can emit several messages, so the rest
|
|
196
|
+
* of the buffer is genuine fan-out and still has to be delivered. See "Serving
|
|
197
|
+
* DeRec over request/response transports" in the Rust SDK README.
|
|
198
|
+
*/
|
|
116
199
|
export interface Transport {
|
|
117
200
|
send(endpoint: { protocol: string; uri: string }, message: Uint8Array): Promise<void>;
|
|
118
201
|
}
|
|
@@ -141,6 +224,14 @@ export enum SenderKind {
|
|
|
141
224
|
* already-KYC-authenticated institution). Applications MUST rate-limit
|
|
142
225
|
* inbound `PrePairRequest`s per channel and expire outstanding NoKeys
|
|
143
226
|
* contacts on a short timer.
|
|
227
|
+
*
|
|
228
|
+
* Because nothing binds the published keys to the contact, the channel is
|
|
229
|
+
* held `Pending` until `verifyFingerprint` succeeds on both sides: it is
|
|
230
|
+
* not a publish target, not a recovery source, and inbound messages on it
|
|
231
|
+
* are ignored. A man-in-the-middle on the plaintext `PrePair` leg leaves
|
|
232
|
+
* the two sides with different shared keys and so different fingerprints,
|
|
233
|
+
* which is what the comparison catches — the role `contact_binding_hash`
|
|
234
|
+
* plays for `HashedKeys`.
|
|
144
235
|
*/
|
|
145
236
|
export enum ContactMode {
|
|
146
237
|
InlineKeys = 0,
|
|
@@ -156,6 +247,15 @@ export enum FlowKind {
|
|
|
156
247
|
RecoverSecret = 4,
|
|
157
248
|
Unpair = 5,
|
|
158
249
|
UpdateChannelInfo = 6,
|
|
250
|
+
/** Ask the replica group whether this device is behind, and catch up if it
|
|
251
|
+
* is. Replica-only, and takes no parameters — the group and this device's
|
|
252
|
+
* own version both come from the stores. */
|
|
253
|
+
SyncCheck = 7,
|
|
254
|
+
/** Remove a member from the replica group. Replica-only. Naming this device
|
|
255
|
+
* is a voluntary departure; naming another is an eviction. Params:
|
|
256
|
+
* `{ replica_id: string; memo?: string }` — `replica_id` is a decimal
|
|
257
|
+
* string so ids above 2^53 survive JS number handling. */
|
|
258
|
+
RemoveReplica = 8,
|
|
159
259
|
}
|
|
160
260
|
|
|
161
261
|
export type UnpairAck = "required" | "not_required";
|
|
@@ -223,6 +323,45 @@ export interface UpdateChannelInfoParams {
|
|
|
223
323
|
transport_protocol?: { uri: string; protocol: number };
|
|
224
324
|
}
|
|
225
325
|
|
|
326
|
+
/**
|
|
327
|
+
* How long the protocol waits on each thing that can keep it waiting. Every
|
|
328
|
+
* field is optional; omit one to keep the library's default for it.
|
|
329
|
+
*/
|
|
330
|
+
export interface Timeouts {
|
|
331
|
+
/** Staleness boundary for inbound envelopes — the replay-defence window.
|
|
332
|
+
* Any message older than this is discarded on receipt, whatever the flow.
|
|
333
|
+
* Lowering it starts refusing legitimately old messages from slow
|
|
334
|
+
* transports or skewed clocks. Library default: 300. */
|
|
335
|
+
inbound_message_secs?: number;
|
|
336
|
+
/** How long a publishing round waits on a peer that has not answered.
|
|
337
|
+
* Bounds how long `SharingComplete` can be delayed by one unreachable
|
|
338
|
+
* peer. Library default: 60. */
|
|
339
|
+
sharing_round_secs?: number;
|
|
340
|
+
/** How long to wait for an unpair acknowledgement before dropping local
|
|
341
|
+
* channel state anyway. Library default: 60. */
|
|
342
|
+
unpair_ack_secs?: number;
|
|
343
|
+
/** Removal of channels still awaiting out-of-band fingerprint
|
|
344
|
+
* confirmation — every replica pairing, and every `NoKeys` pairing.
|
|
345
|
+
* Unlike the others this can be disabled, leaving the sweep to the
|
|
346
|
+
* application via `removeExpiredChannels`. The budget is a **human** one:
|
|
347
|
+
* someone comparing a fingerprint, possibly over the phone. Library
|
|
348
|
+
* default: `{ enabled: true, timeout_in_secs: 300 }`. */
|
|
349
|
+
expired_channels?: { enabled: boolean; timeout_in_secs: number };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** `SyncCheck` takes no parameters: the group and this device's own version
|
|
353
|
+
* are both read from the stores. The argument may be omitted entirely. */
|
|
354
|
+
export type SyncCheckParams = Record<string, never>;
|
|
355
|
+
|
|
356
|
+
export interface RemoveReplicaParams {
|
|
357
|
+
/** The member to remove, as a **decimal** `u64` string — the same form
|
|
358
|
+
* `ReplicaPaired.peer_replica_id` hands back. A value naming no current
|
|
359
|
+
* member is rejected; it is not silently ignored. */
|
|
360
|
+
replica_id: string;
|
|
361
|
+
|
|
362
|
+
memo?: string;
|
|
363
|
+
}
|
|
364
|
+
|
|
226
365
|
export type DeRecEvent =
|
|
227
366
|
| {
|
|
228
367
|
type: "PairingCompleted";
|
|
@@ -252,7 +391,62 @@ export type DeRecEvent =
|
|
|
252
391
|
| { type: "ShareStored"; channel_id: string; version: number }
|
|
253
392
|
| { type: "ShareConfirmed"; channel_id: string; version: number }
|
|
254
393
|
| { type: "ShareRejected"; channel_id: string; version: number; status: number; memo: string }
|
|
394
|
+
/** A publishing round finished — every targeted helper confirmed,
|
|
395
|
+
* rejected, or timed out.
|
|
396
|
+
*
|
|
397
|
+
* **A mixed round waits for the replica leg.** The counts here describe
|
|
398
|
+
* helpers only and are known the instant the helpers answer, but the
|
|
399
|
+
* event is withheld until every replica member has also acknowledged,
|
|
400
|
+
* refused, or timed out. One unreachable member therefore delays it by
|
|
401
|
+
* up to the configured timeout, which is easy to mistake for a hang.
|
|
402
|
+
* Nothing is lost — the round always terminates and a silent member is
|
|
403
|
+
* reported in `ReplicaSyncComplete.behind` rather than failing it.
|
|
404
|
+
*
|
|
405
|
+
* Drive per-helper progress from `ShareConfirmed` instead: those land as
|
|
406
|
+
* each helper answers, with no cross-population wait. A helpers-only
|
|
407
|
+
* round is unaffected. */
|
|
255
408
|
| { type: "SharingComplete"; version: number; confirmed_count: number; failed_count: number; threshold_met: boolean }
|
|
409
|
+
/** A group member refused a secret sync. Keyed by `replica_id`, not
|
|
410
|
+
* `channel_id`: every member answers on the one group channel. A
|
|
411
|
+
* `VERSION_CONFLICT` status means the round must be resolved and
|
|
412
|
+
* republished at a new version. */
|
|
413
|
+
| {
|
|
414
|
+
type: "ReplicaSyncRejected";
|
|
415
|
+
replica_id: string;
|
|
416
|
+
secret_id: string;
|
|
417
|
+
version: number;
|
|
418
|
+
status: number;
|
|
419
|
+
memo: string;
|
|
420
|
+
}
|
|
421
|
+
/** A secret sync could not be delivered to a member at all — distinct from
|
|
422
|
+
* `ReplicaSyncRejected`, which is the member answering "no". */
|
|
423
|
+
| { type: "ReplicaSyncFailed"; replica_id: string; version: number; reason: string }
|
|
424
|
+
/** A member left the group and its roster row was dropped. Fires on the
|
|
425
|
+
* members that remain. */
|
|
426
|
+
| { type: "ReplicaRemoved"; replica_id: string }
|
|
427
|
+
/** The group's source role moved to another member because the previous
|
|
428
|
+
* source is leaving. Fires on the device that chose the successor — which
|
|
429
|
+
* it does by the order its channel store returns members in — and on the
|
|
430
|
+
* successor itself when the roster promoting it arrives. */
|
|
431
|
+
| { type: "ReplicaSourceChanged"; replica_id: string }
|
|
432
|
+
/** This device left the group and dropped its whole `secret_id` partition —
|
|
433
|
+
* group channel, helper channels, shares, secrets and the snapshot. Fires
|
|
434
|
+
* only once it was told to leave *and* has since seen a roster excluding
|
|
435
|
+
* it; absence alone never destroys a copy of the secret. */
|
|
436
|
+
| { type: "SelfRemovedFromGroup"; version: number }
|
|
437
|
+
/** A replica catch-up finished. `fetched_from` is absent when this device
|
|
438
|
+
* was already current, in which case no hydration event follows. */
|
|
439
|
+
| {
|
|
440
|
+
type: "SyncCheckComplete";
|
|
441
|
+
local_version: number;
|
|
442
|
+
group_version: number;
|
|
443
|
+
fetched_from?: string;
|
|
444
|
+
}
|
|
445
|
+
/** The replica leg of a publishing round finished. Reported separately from
|
|
446
|
+
* `SharingComplete`: replicas are best-effort, so a member in `behind` does
|
|
447
|
+
* not fail the round. `behind` is the application's retry list — the
|
|
448
|
+
* library keeps no durable per-member sync state. */
|
|
449
|
+
| { type: "ReplicaSyncComplete"; version: number; synced: string[]; behind: string[] }
|
|
256
450
|
| { type: "ShareVerified"; channel_id: string; version: number }
|
|
257
451
|
| {
|
|
258
452
|
type: "SecretsDiscovered";
|
|
@@ -265,9 +459,8 @@ export type DeRecEvent =
|
|
|
265
459
|
/** Recovery completed — the typed `Secret` snapshot the owner
|
|
266
460
|
* originally protected. Mirrors `ReplicaSecretReceived.secret`:
|
|
267
461
|
* `secrets` is the user-facing `Vec<UserSecret>` the application
|
|
268
|
-
* fed to `start(FlowKind.ProtectSecret)`; `helpers
|
|
269
|
-
*
|
|
270
|
-
* distribution time. The library handles the two-stage
|
|
462
|
+
* fed to `start(FlowKind.ProtectSecret)`; `helpers` and `replicas`
|
|
463
|
+
* are the roster snapshot captured at distribution time. The library handles the two-stage
|
|
271
464
|
* `DeRecSecret` → `Secret` protobuf decode internally. */
|
|
272
465
|
| {
|
|
273
466
|
type: "SecretRecovered";
|
|
@@ -284,20 +477,23 @@ export type DeRecEvent =
|
|
|
284
477
|
data: Uint8Array;
|
|
285
478
|
}>;
|
|
286
479
|
/** Replica composite. Absent when this `secret_id` has no
|
|
287
|
-
* replica setup. Carries the
|
|
288
|
-
*
|
|
289
|
-
*
|
|
480
|
+
* replica setup. Carries the full member roster, the one channel
|
|
481
|
+
* they share, and the 32-byte group key. Required by `restore` to
|
|
482
|
+
* rebuild replica state without re-pairing. */
|
|
290
483
|
replicas?: {
|
|
291
|
-
|
|
292
|
-
|
|
484
|
+
/** The one channel every member is addressed on. */
|
|
485
|
+
channel_id: string;
|
|
486
|
+
/** Every member of the group, including the writer. Exactly one
|
|
487
|
+
* carries `role: "Source"` — that member is where the secret
|
|
488
|
+
* originated, which is why no separate owner field is needed. */
|
|
489
|
+
members: Array<{
|
|
490
|
+
replica_id: string;
|
|
293
491
|
transport_uri: string;
|
|
492
|
+
role: "Source" | "Destination";
|
|
294
493
|
communication_info: Record<string, string>;
|
|
295
|
-
replica_id: string;
|
|
296
|
-
sender_kind: number;
|
|
297
494
|
}>;
|
|
298
495
|
shared_key: Uint8Array;
|
|
299
496
|
};
|
|
300
|
-
owner_replica_id: string;
|
|
301
497
|
};
|
|
302
498
|
}
|
|
303
499
|
|
|
@@ -311,8 +507,10 @@ export type DeRecEvent =
|
|
|
311
507
|
| { type: "PrePairRejected"; channel_id: string; status: number; memo: string }
|
|
312
508
|
|
|
313
509
|
/** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
|
|
314
|
-
* `peer_replica_id` is the peer's
|
|
315
|
-
* `derec.replica_id` representation
|
|
510
|
+
* `peer_replica_id` is the peer's `u64` as a **decimal** string,
|
|
511
|
+
* matching the wire `derec.replica_id` representation and every other
|
|
512
|
+
* id across this boundary. Pass it back verbatim — `RemoveReplica`
|
|
513
|
+
* expects the same decimal form. The local side's role
|
|
316
514
|
* (`ReplicaSource` vs `ReplicaDestination`) is on the persisted
|
|
317
515
|
* channel record — replica pairings are unidirectional, so there is
|
|
318
516
|
* no separate "role in pair" field. */
|
|
@@ -325,7 +523,8 @@ export type DeRecEvent =
|
|
|
325
523
|
* `ReplicaDestination` channel. The library decoded the
|
|
326
524
|
* `ReplicaSecretPayload`; the app installs `secret.secrets` and
|
|
327
525
|
* optionally uses `shares` for recovery. `from_replica_id` and the
|
|
328
|
-
* `replica_id` fields inside `secret` are
|
|
526
|
+
* `replica_id` fields inside `secret` are `u64` as **decimal**
|
|
527
|
+
* strings. */
|
|
329
528
|
| {
|
|
330
529
|
type: "ReplicaSecretReceived";
|
|
331
530
|
channel_id: string;
|
|
@@ -347,16 +546,68 @@ export type DeRecEvent =
|
|
|
347
546
|
/** Replica composite. Absent when this `secret_id` has no
|
|
348
547
|
* replica setup. The same shape as `SecretRecovered.secret.replicas`. */
|
|
349
548
|
replicas?: {
|
|
350
|
-
|
|
351
|
-
|
|
549
|
+
/** The one channel every member is addressed on. */
|
|
550
|
+
channel_id: string;
|
|
551
|
+
/** Every member of the group, including the writer. Exactly one
|
|
552
|
+
* carries `role: "Source"` — that member is where the secret
|
|
553
|
+
* originated, which is why no separate owner field is needed. */
|
|
554
|
+
members: Array<{
|
|
555
|
+
replica_id: string;
|
|
352
556
|
transport_uri: string;
|
|
557
|
+
role: "Source" | "Destination";
|
|
353
558
|
communication_info: Record<string, string>;
|
|
559
|
+
}>;
|
|
560
|
+
shared_key: Uint8Array;
|
|
561
|
+
};
|
|
562
|
+
};
|
|
563
|
+
shares: Array<{
|
|
564
|
+
channel_id: string;
|
|
565
|
+
committed_share: Uint8Array;
|
|
566
|
+
}>;
|
|
567
|
+
}
|
|
568
|
+
/** The first sync for a `secret_id` this device had no snapshot for —
|
|
569
|
+
* the secret now exists here. Same payload as `ReplicaSecretReceived`,
|
|
570
|
+
* which reports a later version of a secret the device already held.
|
|
571
|
+
* Both are written to the stores by the library before the event is
|
|
572
|
+
* delivered; the distinct type is what tells an application the set of
|
|
573
|
+
* secrets on the device changed.
|
|
574
|
+
*
|
|
575
|
+
* This is not a recovery: recovery reconstructs a secret from helper
|
|
576
|
+
* shares and is driven by the application through `restore`. */
|
|
577
|
+
| {
|
|
578
|
+
type: "ReplicaSecretInstalled";
|
|
579
|
+
channel_id: string;
|
|
580
|
+
from_replica_id: string;
|
|
581
|
+
secret_id: string;
|
|
582
|
+
version: number;
|
|
583
|
+
secret: {
|
|
584
|
+
helpers: Array<{
|
|
585
|
+
channel_id: string;
|
|
586
|
+
transport_uri: string;
|
|
587
|
+
shared_key: Uint8Array;
|
|
588
|
+
communication_info: Record<string, string>;
|
|
589
|
+
}>;
|
|
590
|
+
secrets: Array<{
|
|
591
|
+
id: Uint8Array;
|
|
592
|
+
name: string;
|
|
593
|
+
data: Uint8Array;
|
|
594
|
+
}>;
|
|
595
|
+
/** Replica composite. Absent when this `secret_id` has no
|
|
596
|
+
* replica setup. The same shape as `SecretRecovered.secret.replicas`. */
|
|
597
|
+
replicas?: {
|
|
598
|
+
/** The one channel every member is addressed on. */
|
|
599
|
+
channel_id: string;
|
|
600
|
+
/** Every member of the group, including the writer. Exactly one
|
|
601
|
+
* carries `role: "Source"` — that member is where the secret
|
|
602
|
+
* originated, which is why no separate owner field is needed. */
|
|
603
|
+
members: Array<{
|
|
354
604
|
replica_id: string;
|
|
355
|
-
|
|
605
|
+
transport_uri: string;
|
|
606
|
+
role: "Source" | "Destination";
|
|
607
|
+
communication_info: Record<string, string>;
|
|
356
608
|
}>;
|
|
357
609
|
shared_key: Uint8Array;
|
|
358
610
|
};
|
|
359
|
-
owner_replica_id: string;
|
|
360
611
|
};
|
|
361
612
|
shares: Array<{
|
|
362
613
|
channel_id: string;
|
|
@@ -441,6 +692,7 @@ export type DeRecEvent =
|
|
|
441
692
|
/** An unpair request was dispatched to `channel_id`. Followed by an
|
|
442
693
|
* `Unpaired` event once the peer acknowledges (or in the same event
|
|
443
694
|
* vec, under `UnpairAck.NotRequired`). */
|
|
695
|
+
| { type: "UnpairFailed"; channel_id: string; error: string }
|
|
444
696
|
| { type: "UnpairStarted"; channel_id: string }
|
|
445
697
|
/** An update-channel-info request was dispatched to `channel_id`. */
|
|
446
698
|
| { type: "UpdateChannelInfoStarted"; channel_id: string }
|
|
@@ -463,6 +715,16 @@ export type DeRecEvent =
|
|
|
463
715
|
* oracle. Anyone who knows a HashedKeys contact's nonce can elicit a
|
|
464
716
|
* key-publish response. Keep off unless you control both ends of
|
|
465
717
|
* the transport.
|
|
718
|
+
* - `storeShare` — the helper's only admission-control point for
|
|
719
|
+
* inbound shares. The protocol enforces no size, quota or rate limit
|
|
720
|
+
* of its own, and `maxShareSize` is checked for range overlap at
|
|
721
|
+
* pairing time only, never against an actual share. While this is
|
|
722
|
+
* `false`, `ActionRequired` carries the decoded request, so the
|
|
723
|
+
* application can inspect the share and call `reject()` with
|
|
724
|
+
* `StatusEnum.SizeLimitExceeded`. Setting it `true` removes that
|
|
725
|
+
* opportunity entirely: every share from every paired Owner is stored
|
|
726
|
+
* unconditionally, at whatever size it arrives. Keep off in any
|
|
727
|
+
* deployment with per-user storage limits.
|
|
466
728
|
* - `unpair` — destructive. Accepting deletes the local channel
|
|
467
729
|
* record before any UI confirmation.
|
|
468
730
|
* - `updateChannelInfo` — silently overwrites the channel record with
|
|
@@ -510,8 +772,42 @@ export declare class DeRecProtocolBuilder {
|
|
|
510
772
|
withThreshold(threshold: number): DeRecProtocolBuilder;
|
|
511
773
|
/** Default: 3. */
|
|
512
774
|
withKeepVersionsCount(count: number): DeRecProtocolBuilder;
|
|
513
|
-
/**
|
|
514
|
-
|
|
775
|
+
/**
|
|
776
|
+
* Configure how long the protocol waits on each thing that can keep it
|
|
777
|
+
* waiting. Every field is optional and **absent means "keep the library
|
|
778
|
+
* default"**; not calling this at all leaves all four at their defaults.
|
|
779
|
+
*
|
|
780
|
+
* These were one setting until it became clear they answer different
|
|
781
|
+
* questions. `inbound_message_secs` is a **security** boundary — how stale
|
|
782
|
+
* a message may be and still be accepted — so it has to tolerate transport
|
|
783
|
+
* latency and clock skew. The other three are **liveness** budgets: how
|
|
784
|
+
* long to keep hoping a peer will answer.
|
|
785
|
+
*
|
|
786
|
+
* Values are forwarded verbatim; clamping, and the meaning of a disabled
|
|
787
|
+
* `expired_channels`, are library decisions rather than this binding's.
|
|
788
|
+
*/
|
|
789
|
+
withTimeouts(timeouts: Timeouts): DeRecProtocolBuilder;
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Accept plaintext `http://` transport endpoints. **Development only.**
|
|
793
|
+
* Default: `false`.
|
|
794
|
+
*
|
|
795
|
+
* With `false`, plaintext is accepted in exactly one situation: an endpoint
|
|
796
|
+
* this device configured for **itself** that names loopback (`localhost`,
|
|
797
|
+
* `127.0.0.1`, `::1`). A local dev server therefore needs no configuration
|
|
798
|
+
* at all.
|
|
799
|
+
*
|
|
800
|
+
* With `true`, plaintext is accepted for **any host on any path**,
|
|
801
|
+
* including endpoints a peer supplies. That is what makes the LAN case
|
|
802
|
+
* work — a phone talking to a laptop, where neither side is loopback — and
|
|
803
|
+
* why the name is blunt.
|
|
804
|
+
*
|
|
805
|
+
* This is a guardrail, not transport security. The SDK opens no sockets;
|
|
806
|
+
* delivery is your `Transport`. Nothing here stops an application sending
|
|
807
|
+
* plaintext — it governs which endpoints the protocol will record,
|
|
808
|
+
* propagate to peers, and reply to.
|
|
809
|
+
*/
|
|
810
|
+
withUnsafeHttp(allow: boolean): DeRecProtocolBuilder;
|
|
515
811
|
/** Default: empty. */
|
|
516
812
|
withCommunicationInfo(info: Record<string, string>): DeRecProtocolBuilder;
|
|
517
813
|
/** Default: false. */
|
|
@@ -593,6 +889,16 @@ export declare class DeRecProtocol {
|
|
|
593
889
|
start(flowKind: FlowKind.RecoverSecret, params: RecoverSecretParams): Promise<DeRecEvent[]>;
|
|
594
890
|
start(flowKind: FlowKind.Unpair, params: UnpairParams): Promise<DeRecEvent[]>;
|
|
595
891
|
start(flowKind: FlowKind.UpdateChannelInfo, params: UpdateChannelInfoParams): Promise<DeRecEvent[]>;
|
|
892
|
+
start(flowKind: FlowKind.SyncCheck, params?: SyncCheckParams): Promise<DeRecEvent[]>;
|
|
893
|
+
|
|
894
|
+
/** Announce a member's removal. This does **not** remove anything on its
|
|
895
|
+
* own and emits no `ReplicaRemoved`: it tells every member and flags the
|
|
896
|
+
* target locally. The removal completes only once the application
|
|
897
|
+
* publishes a roster omitting that member — an ordinary
|
|
898
|
+
* `start(FlowKind.ProtectSecret)` — at which point `ReplicaRemoved`
|
|
899
|
+
* fires. A group with no secret to publish therefore cannot complete a
|
|
900
|
+
* removal. */
|
|
901
|
+
start(flowKind: FlowKind.RemoveReplica, params: RemoveReplicaParams): Promise<DeRecEvent[]>;
|
|
596
902
|
|
|
597
903
|
/**
|
|
598
904
|
* Replace this node's local <c>communication_info</c> map. Does not
|
|
@@ -610,14 +916,31 @@ export declare class DeRecProtocol {
|
|
|
610
916
|
|
|
611
917
|
process(message: Uint8Array): Promise<DeRecEvent[]>;
|
|
612
918
|
|
|
919
|
+
/**
|
|
920
|
+
* Advance time-driven state without an inbound message.
|
|
921
|
+
*
|
|
922
|
+
* Timeouts are otherwise only evaluated by `process`, so a publish whose
|
|
923
|
+
* helpers all go quiet has nothing left to close it: the round stays open
|
|
924
|
+
* and no `SharingComplete` is ever emitted. Call this from a timer —
|
|
925
|
+
* `setInterval`, a service-worker alarm, a job runner — at an interval
|
|
926
|
+
* shorter than the configured timeout.
|
|
927
|
+
*
|
|
928
|
+
* Safe to call at any time; with nothing in flight it resolves to an empty
|
|
929
|
+
* array. It mutates the same round state an inbound response does, so it
|
|
930
|
+
* must be serialized against `process` for the same `secretId`.
|
|
931
|
+
*/
|
|
932
|
+
tick(): Promise<DeRecEvent[]>;
|
|
933
|
+
|
|
613
934
|
accept(actionBytes: Uint8Array): Promise<DeRecEvent[]>;
|
|
614
935
|
|
|
615
936
|
reject(actionBytes: Uint8Array, status: number, memo: string): Promise<void>;
|
|
616
937
|
|
|
617
938
|
/**
|
|
618
939
|
* Derive the human-readable fingerprint for a paired channel. Both sides
|
|
619
|
-
*
|
|
620
|
-
*
|
|
940
|
+
* compute the same value from the shared key — users compare them out of
|
|
941
|
+
* band before calling `verifyFingerprint`. Required for every replica
|
|
942
|
+
* pairing and every `NoKeys` pairing, which stay unusable until it
|
|
943
|
+
* succeeds.
|
|
621
944
|
*/
|
|
622
945
|
getFingerprint(channelId: bigint | number): Promise<string>;
|
|
623
946
|
|
|
@@ -627,6 +950,16 @@ export declare class DeRecProtocol {
|
|
|
627
950
|
* `true` on confirmation, `false` on mismatch.
|
|
628
951
|
*/
|
|
629
952
|
verifyFingerprint(channelId: bigint | number, fingerprint: string): Promise<boolean>;
|
|
953
|
+
/**
|
|
954
|
+
* Remove `Pending` channels older than `olderThanSecs`, along with their
|
|
955
|
+
* pairing keys. Resolves to the removed channel ids as decimal strings.
|
|
956
|
+
*
|
|
957
|
+
* Independent of the configured cleanup policy — this sweeps at the
|
|
958
|
+
* threshold given even when that policy is disabled. The age comparison
|
|
959
|
+
* is strict, so a channel created within the current second survives
|
|
960
|
+
* even `0`.
|
|
961
|
+
*/
|
|
962
|
+
removeExpiredChannels(olderThanSecs: number): Promise<string[]>;
|
|
630
963
|
|
|
631
964
|
/**
|
|
632
965
|
* Rebuild this protocol's `secret_id` namespace from a recovered
|
|
@@ -685,6 +1018,8 @@ export interface GetSecretIdsVersionsRequestMessage {
|
|
|
685
1018
|
/** Ephemeral endpoint where the requester wants the response routed.
|
|
686
1019
|
* Absent means "use the channel's stored peer endpoint". */
|
|
687
1020
|
reply_to?: TransportProtocol;
|
|
1021
|
+
/** Replica-group member that sent this; see `replicaId` semantics. */
|
|
1022
|
+
replica_id?: bigint;
|
|
688
1023
|
}
|
|
689
1024
|
|
|
690
1025
|
export interface VersionList {
|
|
@@ -701,6 +1036,8 @@ export interface GetSecretIdsVersionsResponseMessage {
|
|
|
701
1036
|
result?: DeRecResult;
|
|
702
1037
|
secret_list: VersionList[];
|
|
703
1038
|
timestamp?: Timestamp;
|
|
1039
|
+
/** Replica-group member that sent this; see `replicaId` semantics. */
|
|
1040
|
+
replica_id?: bigint;
|
|
704
1041
|
}
|
|
705
1042
|
|
|
706
1043
|
export interface VersionEntry {
|
|
@@ -795,6 +1132,8 @@ export interface GetShareRequestMessage {
|
|
|
795
1132
|
timestamp?: Timestamp;
|
|
796
1133
|
/** Ephemeral response endpoint; see `replyTo` semantics. */
|
|
797
1134
|
reply_to?: TransportProtocol;
|
|
1135
|
+
/** Replica-group member that sent this; see `replicaId` semantics. */
|
|
1136
|
+
replica_id?: bigint;
|
|
798
1137
|
}
|
|
799
1138
|
|
|
800
1139
|
export interface GetShareResponseMessage {
|
|
@@ -808,6 +1147,8 @@ export interface GetShareResponseMessage {
|
|
|
808
1147
|
secret_id: bigint;
|
|
809
1148
|
/** Echoed from the request for the same correlation reasons as `secret_id`. */
|
|
810
1149
|
version: number;
|
|
1150
|
+
/** Replica-group member that sent this; see `replicaId` semantics. */
|
|
1151
|
+
replica_id?: bigint;
|
|
811
1152
|
}
|
|
812
1153
|
|
|
813
1154
|
export interface SiblingHash {
|
|
@@ -833,6 +1174,8 @@ export interface StoreShareRequestMessage {
|
|
|
833
1174
|
secret_id: bigint;
|
|
834
1175
|
/** Ephemeral response endpoint; see `replyTo` semantics. */
|
|
835
1176
|
reply_to?: TransportProtocol;
|
|
1177
|
+
/** Replica-group member that sent this; see `replicaId` semantics. */
|
|
1178
|
+
replica_id?: bigint;
|
|
836
1179
|
}
|
|
837
1180
|
|
|
838
1181
|
export interface StoreShareResponseMessage {
|
|
@@ -840,6 +1183,8 @@ export interface StoreShareResponseMessage {
|
|
|
840
1183
|
version: number;
|
|
841
1184
|
timestamp?: Timestamp;
|
|
842
1185
|
secret_id: bigint;
|
|
1186
|
+
/** Replica-group member that sent this; see `replicaId` semantics. */
|
|
1187
|
+
replica_id?: bigint;
|
|
843
1188
|
}
|
|
844
1189
|
|
|
845
1190
|
export interface UnpairRequestMessage {
|
|
@@ -847,6 +1192,8 @@ export interface UnpairRequestMessage {
|
|
|
847
1192
|
timestamp?: Timestamp;
|
|
848
1193
|
/** Ephemeral response endpoint; see `replyTo` semantics. */
|
|
849
1194
|
reply_to?: TransportProtocol;
|
|
1195
|
+
/** Replica-group member that sent this; see `replicaId` semantics. */
|
|
1196
|
+
replica_id?: bigint;
|
|
850
1197
|
}
|
|
851
1198
|
|
|
852
1199
|
export interface UnpairResponseMessage {
|
package/index.js
CHANGED
|
@@ -8,9 +8,9 @@ const DeRecProtocolBuilder = wasm.DeRecProtocolBuilder;
|
|
|
8
8
|
|
|
9
9
|
const SenderKind = Object.freeze({ Owner: 0, Helper: 1, ReplicaSource: 3, ReplicaDestination: 4 });
|
|
10
10
|
|
|
11
|
-
const ContactMode = Object.freeze({ InlineKeys: 0, HashedKeys: 1 });
|
|
11
|
+
const ContactMode = Object.freeze({ InlineKeys: 0, HashedKeys: 1, NoKeys: 2 });
|
|
12
12
|
|
|
13
|
-
const FlowKind = Object.freeze({ Pairing: 0, Discovery: 1, ProtectSecret: 2, VerifyShares: 3, RecoverSecret: 4, Unpair: 5, UpdateChannelInfo: 6 });
|
|
13
|
+
const FlowKind = Object.freeze({ Pairing: 0, Discovery: 1, ProtectSecret: 2, VerifyShares: 3, RecoverSecret: 4, Unpair: 5, UpdateChannelInfo: 6, SyncCheck: 7, RemoveReplica: 8 });
|
|
14
14
|
|
|
15
15
|
const primitives = {
|
|
16
16
|
discovery: {
|
package/package.json
CHANGED