@derec-alliance/react-native 0.0.5 → 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/src/protocol.ts CHANGED
@@ -11,6 +11,7 @@ import type {
11
11
  ContactMessage,
12
12
  ContactMode,
13
13
  DeRecEvent,
14
+ Endpoint,
14
15
  DiscoveryParams,
15
16
  PairingParams,
16
17
  ParameterRange,
@@ -20,6 +21,7 @@ import type {
20
21
  SecretStore,
21
22
  ShareStore,
22
23
  StateStore,
24
+ StatusEnum,
23
25
  ReplicaDiscoveryParams,
24
26
  Target,
25
27
  Timeouts,
@@ -81,25 +83,6 @@ function decodeEvents(buffer: ArrayBuffer): DeRecEvent[] {
81
83
  return reviveEventBytes(jsonFromBytes(buffer)) as DeRecEvent[];
82
84
  }
83
85
 
84
- /**
85
- * Maps a transport protocol name to the `derec_proto::Protocol` discriminant.
86
- * The shared enum fixture every other numeric mapping in this SDK is checked
87
- * against does not carry this enum (it
88
- * only covers the Rust-internal serde enums listed under its `"enums"` key);
89
- * the transport `Protocol` enum lives in `protobufs/transportprotocol.proto`
90
- * instead, where `HTTPS = 0` and `GRPC = 1` are defined.
91
- */
92
- export function protocolDiscriminant(protocol: string): number {
93
- switch (protocol.toLowerCase()) {
94
- case 'https':
95
- return 0;
96
- case 'grpc':
97
- return 1;
98
- default:
99
- throw new Error(`DeRec: unknown transport protocol "${protocol}"`);
100
- }
101
- }
102
-
103
86
  function encodeTarget(target: Target): unknown {
104
87
  if (target === null) {
105
88
  return null;
@@ -261,13 +244,8 @@ function buildStartParams(flowKind: FlowKind, params: unknown): Uint8Array {
261
244
  if (p.communication_info !== undefined) {
262
245
  out.communication_info = p.communication_info;
263
246
  }
264
- if (p.own_transports !== undefined && p.own_transports.length > 0) {
247
+ if (p.own_transports !== undefined) {
265
248
  out.own_transports = p.own_transports;
266
- // The first entry also fills the deprecated singular field so a peer
267
- // predating `supported_transports` still learns the new address.
268
- out.transport_protocol = p.own_transports[0];
269
- } else if (p.transport_protocol !== undefined) {
270
- out.transport_protocol = p.transport_protocol;
271
249
  }
272
250
  return jsonToBytes(out);
273
251
  }
@@ -302,9 +280,9 @@ function toBigInt(id: bigint | number): bigint {
302
280
  *
303
281
  * Every value the caller supplies is forwarded verbatim — defaults, clamping
304
282
  * and the meaning of a disabled cleanup policy are library decisions.
305
- * `threshold`, `keep_versions_count`, `auto_respond_on_failure`,
306
- * `unpair_ack`, `auto_reply_to` and `auto_accept` are omitted entirely from
307
- * the emitted config unless their setter was called: `ProtocolConfig` has a
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
308
286
  * `#[serde(default = "...")]` on each of them, reading the same constants
309
287
  * `DeRecProtocolBuilder::new` uses, so an absent key resolves to the
310
288
  * library's own default rather than one frozen into this shim.
@@ -313,7 +291,6 @@ export class DeRecProtocolBuilder {
313
291
  private readonly secretIdValue: bigint;
314
292
  private config: Record<string, unknown> = {};
315
293
  private stores: Record<string, unknown> = {};
316
- private communicationInfo: Record<string, string> | undefined;
317
294
 
318
295
  constructor(secretId: bigint | number) {
319
296
  this.secretIdValue = BigInt(secretId);
@@ -349,23 +326,10 @@ export class DeRecProtocolBuilder {
349
326
  return this;
350
327
  }
351
328
 
352
- /**
353
- * @deprecated Use {@link withOwnTransports}, which takes the whole
354
- * preference list — `withOwnTransports([endpoint])` is the direct
355
- * replacement. Removed at 0.0.5.
356
- */
357
- withOwnTransport(endpoint: { uri: string; protocol: string }): this {
358
- this.config.own_transport_uri = endpoint.uri;
359
- this.config.own_transport_protocol = protocolDiscriminant(endpoint.protocol);
360
- return this;
361
- }
362
-
363
329
  /**
364
330
  * Set every transport endpoint this application serves, in preference
365
- * order. Written to the config's `own_transports` array, which takes
366
- * precedence over `own_transport_uri` / `own_transport_protocol` on the
367
- * Rust side when non-empty — see `ProtocolConfig` in
368
- * `library/src/interop/ffi/protocol/handle/mod.rs`.
331
+ * order. Written to the config's `own_transports` array — see
332
+ * `ProtocolConfig` in `library/src/interop/ffi/protocol/handle/mod.rs`.
369
333
  *
370
334
  * The order given is forwarded verbatim: it is not sorted, deduplicated,
371
335
  * or reordered here. It is this application's own preference and
@@ -374,15 +338,11 @@ export class DeRecProtocolBuilder {
374
338
  * listing an endpoint this application does not serve makes pairing
375
339
  * succeed and replies vanish.
376
340
  *
377
- * Supersedes {@link withOwnTransport} for applications serving more
378
- * than one transport; the single-endpoint setter remains fully
379
- * supported.
341
+ * Required: `build()` with no own transport is refused by the library,
342
+ * since a node with no endpoint cannot be reached by any peer.
380
343
  */
381
- withOwnTransports(transports: { uri: string; protocol: string }[]): this {
382
- this.config.own_transports = transports.map((t) => ({
383
- uri: t.uri,
384
- protocol: protocolDiscriminant(t.protocol),
385
- }));
344
+ withOwnTransports(transports: Endpoint[]): this {
345
+ this.config.own_transports = transports.map(({ uri, protocol }) => ({ uri, protocol }));
386
346
  return this;
387
347
  }
388
348
 
@@ -392,12 +352,6 @@ export class DeRecProtocolBuilder {
392
352
  return this;
393
353
  }
394
354
 
395
- /** Default: 3. */
396
- withKeepVersionsCount(count: number): this {
397
- this.config.keep_versions_count = count;
398
- return this;
399
- }
400
-
401
355
  /**
402
356
  * Every field is optional; not calling this at all leaves all four at the
403
357
  * library's defaults — `timeouts` is genuinely absent from the JSON in
@@ -410,24 +364,9 @@ export class DeRecProtocolBuilder {
410
364
  return this;
411
365
  }
412
366
 
413
- /**
414
- * @deprecated Use {@link withUnsafeConnection}, which names both gated
415
- * schemes. Removed at 0.0.5. Default: false.
416
- */
417
- withUnsafeHttp(allow: boolean): this {
418
- this.config.unsafe_http = allow;
419
- return this;
420
- }
421
-
422
367
  /**
423
368
  * Accept plaintext `http://` and `grpc://` transport endpoints.
424
- * **Development only.** Default: false. Supersedes
425
- * {@link withUnsafeHttp}, which names only the HTTP scheme.
426
- *
427
- * Either flag alone is honored. Setting both to disagreeing values fails
428
- * construction with the error code `CONFLICTING_PLAINTEXT_OPT_IN` rather
429
- * than resolving silently, because precedence would hand the decision to
430
- * the flag being removed.
369
+ * **Development only.** Default: false.
431
370
  */
432
371
  withUnsafeConnection(allow: boolean): this {
433
372
  this.config.unsafe_connection = allow;
@@ -435,24 +374,11 @@ export class DeRecProtocolBuilder {
435
374
  }
436
375
 
437
376
  /**
438
- * Default: empty. `derec_protocol_new`'s own `communication_info` argument
439
- * is a pre-encoded `CommunicationInfo` protobuf message, and this SDK has
440
- * no encoder for that message — the C ABI exposes one only for
441
- * `ContactMessage`. Hand-rolling one here would be SDK-side protocol logic
442
- * with no test coverage — exactly what this codebase's "Rust decides, the
443
- * SDK only marshals" rule forbids. Instead,
444
- * `build()` constructs the protocol with an empty `communication_info`
445
- * (a legitimate, always-accepted value — see `decode_communication_info`
446
- * in `library/src/interop/ffi/protocol/handle/mod.rs`, which treats a zero-length
447
- * buffer as "no entries") and then calls the already-bound
448
- * {@link DeRecProtocol.setCommunicationInfo}, which takes the same map as
449
- * plain JSON (`derec_protocol_set_communication_info` in
450
- * `library/src/interop/ffi/protocol/handle/config.rs`). That setter is queued on
451
- * the protocol's serial worker before `build()` returns, so it is ordered
452
- * ahead of every call the caller can subsequently make.
377
+ * Default: empty. Written to the config's `communication_info` key, so the
378
+ * map is part of the protocol from construction.
453
379
  */
454
380
  withCommunicationInfo(info: Record<string, string>): this {
455
- this.communicationInfo = info;
381
+ this.config.communication_info = info;
456
382
  return this;
457
383
  }
458
384
 
@@ -464,13 +390,7 @@ export class DeRecProtocolBuilder {
464
390
 
465
391
  /** Default: "required". */
466
392
  withUnpairAck(ack: UnpairAck): this {
467
- if (ack === 'required') {
468
- this.config.unpair_ack = 0;
469
- } else if (ack === 'not_required') {
470
- this.config.unpair_ack = 1;
471
- } else {
472
- throw new Error(`DeRec: unknown unpair ack "${ack as string}"`);
473
- }
393
+ this.config.unpair_ack = ack;
474
394
  return this;
475
395
  }
476
396
 
@@ -480,17 +400,20 @@ export class DeRecProtocolBuilder {
480
400
  return this;
481
401
  }
482
402
 
483
- /** Default: empty policy (every flow off). */
403
+ /**
404
+ * Default: empty policy (every flow off). A flow left out of `policy` is
405
+ * left out of the config, and the library applies its own default to it.
406
+ */
484
407
  withAutoAccept(policy: AutoAcceptPolicy): this {
485
408
  this.config.auto_accept = {
486
- pairing: policy.pairing ?? false,
487
- pre_pair: policy.prePair ?? false,
488
- store_share: policy.storeShare ?? false,
489
- verify_share: policy.verifyShare ?? false,
490
- discovery: policy.discovery ?? false,
491
- get_share: policy.getShare ?? false,
492
- unpair: policy.unpair ?? false,
493
- update_channel_info: policy.updateChannelInfo ?? false,
409
+ pairing: policy.pairing,
410
+ pre_pair: policy.prePair,
411
+ store_share: policy.storeShare,
412
+ verify_share: policy.verifyShare,
413
+ discovery: policy.discovery,
414
+ get_share: policy.getShare,
415
+ unpair: policy.unpair,
416
+ update_channel_info: policy.updateChannelInfo,
494
417
  };
495
418
  return this;
496
419
  }
@@ -508,30 +431,26 @@ export class DeRecProtocolBuilder {
508
431
  * against the peer's range on inbound ones: a range that fails to
509
432
  * intersect rejects the pairing. Every bound is optional and defaults to
510
433
  * `0`, which the protocol reads as "no constraint on this dimension".
434
+ * Bounds cross to the library as decimal strings, so the full `i64` range
435
+ * survives; a bound left out is left out of the config, and the library
436
+ * applies its default.
511
437
  *
512
438
  * Default: unset — no constraints advertised, every peer range accepted.
513
439
  */
514
440
  withParameterRange(range: Partial<ParameterRange>): this {
515
- const n = (v: bigint | undefined) => Number(v ?? 0n);
516
441
  this.config.parameter_range = {
517
- min_share_size: n(range.min_share_size),
518
- max_share_size: n(range.max_share_size),
519
- min_time_between_verifications: n(range.min_time_between_verifications),
520
- max_time_between_verifications: n(range.max_time_between_verifications),
521
- min_time_between_share_updates: n(range.min_time_between_share_updates),
522
- max_time_between_share_updates: n(range.max_time_between_share_updates),
523
- min_unresponsive_deletion_timeout: n(
524
- range.min_unresponsive_deletion_timeout,
525
- ),
526
- max_unresponsive_deletion_timeout: n(
527
- range.max_unresponsive_deletion_timeout,
528
- ),
529
- min_unresponsive_deactivation_timeout: n(
530
- range.min_unresponsive_deactivation_timeout,
531
- ),
532
- max_unresponsive_deactivation_timeout: n(
533
- range.max_unresponsive_deactivation_timeout,
534
- ),
442
+ min_share_size: range.min_share_size?.toString(),
443
+ max_share_size: range.max_share_size?.toString(),
444
+ min_time_between_verifications: range.min_time_between_verifications?.toString(),
445
+ max_time_between_verifications: range.max_time_between_verifications?.toString(),
446
+ min_time_between_share_updates: range.min_time_between_share_updates?.toString(),
447
+ max_time_between_share_updates: range.max_time_between_share_updates?.toString(),
448
+ min_unresponsive_deletion_timeout: range.min_unresponsive_deletion_timeout?.toString(),
449
+ max_unresponsive_deletion_timeout: range.max_unresponsive_deletion_timeout?.toString(),
450
+ min_unresponsive_deactivation_timeout:
451
+ range.min_unresponsive_deactivation_timeout?.toString(),
452
+ max_unresponsive_deactivation_timeout:
453
+ range.max_unresponsive_deactivation_timeout?.toString(),
535
454
  };
536
455
  return this;
537
456
  }
@@ -540,8 +459,6 @@ export class DeRecProtocolBuilder {
540
459
  const config = {
541
460
  secret_id: this.secretIdValue.toString(),
542
461
  replica_id: null,
543
- own_transport_uri: '',
544
- own_transport_protocol: 0,
545
462
  ...this.config,
546
463
  };
547
464
  const host = (getNative() as unknown as NativeHost).protocol_new(
@@ -549,29 +466,15 @@ export class DeRecProtocolBuilder {
549
466
  new Uint8Array(0),
550
467
  this.stores,
551
468
  ) as NativeHost;
552
- const protocol = DeRecProtocol.fromHost(host);
553
- if (this.communicationInfo !== undefined) {
554
- // `setCommunicationInfo` is asynchronous, but `build()` stays
555
- // synchronous: the guarantee callers need is not that the map has been
556
- // applied by the time `build()` returns, it is that no flow can run
557
- // before it. Posting it here puts it on the protocol's serial worker
558
- // queue ahead of anything the caller has had the chance to start, so
559
- // every subsequent call observes it. Left unhandled deliberately — a
560
- // failure reaches the runtime's unhandled-rejection reporting rather
561
- // than being silently discarded, and `build()` has no synchronous
562
- // channel to raise it on.
563
- void protocol.setCommunicationInfo(this.communicationInfo);
564
- }
565
- return protocol;
469
+ return DeRecProtocol.fromHost(host);
566
470
  }
567
471
  }
568
472
 
569
473
  /**
570
474
  * Thin wrapper around the `ProtocolHost` JSI host object, mirroring
571
475
  * `@derec-alliance/nodejs`'s `DeRecProtocol` — same methods, and the same
572
- * sync/async split except for `setCommunicationInfo` and `setOwnTransport`,
573
- * which return a `Promise` here (see their own doc comments for why);
574
- * `secretId` is synchronous and everything else returns a `Promise`. Every method encodes its
476
+ * sync/async split: `secretId` is synchronous and everything else returns a
477
+ * `Promise`. Every method encodes its
575
478
  * arguments into the wire shape the host function expects and decodes its
576
479
  * result back into the typed value declared here; no protocol decision is
577
480
  * made in this class.
@@ -601,68 +504,39 @@ export class DeRecProtocol {
601
504
  }
602
505
 
603
506
  /**
604
- * Returns a `Promise`, where `@derec-alliance/nodejs` declares this
605
- * `void`. The native setter takes the same handle mutex a running flow
606
- * holds across its store callbacks, so calling it on the JavaScript thread
607
- * would block that thread until the flow released the mutex — which it
608
- * cannot do while it is waiting on the JavaScript thread. The binding
609
- * therefore queues the setter onto the same serial worker every flow runs
610
- * on, which orders it after any call already in flight. The nodejs SDK is
611
- * WASM and single-threaded, so it has no such hazard.
507
+ * Replaces this node's local communication info.
612
508
  *
613
- * Callers that ignore the result behave exactly as before, so this stays
614
- * source-compatible with nodejs-shaped code; awaiting it additionally
615
- * surfaces a failure that would otherwise be swallowed.
509
+ * The native setter takes the same handle mutex a running flow holds
510
+ * across its store callbacks, so calling it on the JavaScript thread would
511
+ * block that thread until the flow released the mutex — which it cannot do
512
+ * while it is waiting on the JavaScript thread. The binding therefore
513
+ * queues the setter onto the same serial worker every flow runs on, which
514
+ * orders it after any call already in flight. Awaiting the returned
515
+ * `Promise` surfaces a failure that would otherwise be swallowed.
616
516
  */
617
517
  setCommunicationInfo(info: Record<string, string>): Promise<void> {
618
518
  return this.host.setCommunicationInfo(jsonToBytes(info)) as Promise<void>;
619
519
  }
620
520
 
621
- /**
622
- * Replace this node's endpoint for one protocol, leaving the others
623
- * alone. A node serves at most one endpoint per protocol, so the
624
- * `(uri, protocol)` pair identifies the entry it replaces; an entry for a
625
- * protocol not yet served is appended, and a replaced one keeps its
626
- * position in the preference order.
627
- *
628
- * Returns a `Promise` for the same reason {@link setCommunicationInfo} does.
629
- *
630
- * @deprecated Use {@link setOwnTransports}, which takes the whole
631
- * preference list and is the only way to change which protocols this node
632
- * serves, or their order. Removed at 0.0.5.
633
- */
634
- setOwnTransport(uri: string, protocol: string): Promise<void> {
635
- return this.host.setOwnTransport(
636
- uri,
637
- protocolDiscriminant(protocol),
638
- ) as Promise<void>;
639
- }
640
-
641
521
  /**
642
522
  * Replaces every endpoint this node advertises, in preference order —
643
- * the runtime counterpart to `withOwnTransports`, and the only way to
644
- * change a multi-endpoint node's set ({@link setOwnTransport} collapses
645
- * it to the one endpoint it is given).
523
+ * the runtime counterpart to `withOwnTransports`.
646
524
  *
647
525
  * Every entry is validated before any is stored, so a malformed URI
648
526
  * leaves the previous set intact. An empty array is rejected.
649
527
  *
650
- * Returns a `Promise` for the same reason {@link setCommunicationInfo} does.
528
+ * Queued onto the serial worker for the same reason as
529
+ * {@link setCommunicationInfo}.
651
530
  */
652
- setOwnTransports(
653
- transports: { uri: string; protocol: string }[],
654
- ): Promise<void> {
655
- const wire = transports.map(({ uri, protocol }) => ({
656
- uri,
657
- protocol: protocolDiscriminant(protocol),
658
- }));
531
+ setOwnTransports(transports: Endpoint[]): Promise<void> {
532
+ const wire = transports.map(({ uri, protocol }) => ({ uri, protocol }));
659
533
  return this.host.setOwnTransports(jsonToBytes(wire)) as Promise<void>;
660
534
  }
661
535
 
662
536
  /**
663
537
  * Decodes the `contact_wire_bytes` `derec_protocol_create_contact`
664
538
  * produces into a typed `ContactMessage`, so an application can read the
665
- * contact's `nonce`, `transport_protocol` and mode without a protobuf
539
+ * contact's `nonce`, `supported_transports` and mode without a protobuf
666
540
  * codec of its own. Feed the returned object straight back into
667
541
  * `start(FlowKind.Pairing, { contact })`, which re-encodes it.
668
542
  */
@@ -699,7 +573,16 @@ export class DeRecProtocol {
699
573
  }
700
574
 
701
575
  async process(message: Uint8Array): Promise<DeRecEvent[]> {
702
- const buffer = (await this.host.process(message)) as ArrayBuffer;
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
+ }
703
586
  return decodeEvents(buffer);
704
587
  }
705
588
 
@@ -713,7 +596,7 @@ export class DeRecProtocol {
713
596
  return decodeEvents(buffer);
714
597
  }
715
598
 
716
- async reject(actionBytes: Uint8Array, status: number, memo: string): Promise<void> {
599
+ async reject(actionBytes: Uint8Array, status: StatusEnum, memo: string): Promise<void> {
717
600
  await this.host.reject(actionBytes, status, memo);
718
601
  }
719
602
 
@@ -721,6 +604,16 @@ export class DeRecProtocol {
721
604
  return (await this.host.getFingerprint(toBigInt(channelId))) as string;
722
605
  }
723
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
+ */
724
617
  async verifyFingerprint(channelId: bigint | number, fingerprint: string): Promise<boolean> {
725
618
  return (await this.host.verifyFingerprint(toBigInt(channelId), fingerprint)) as boolean;
726
619
  }
@@ -730,6 +623,24 @@ export class DeRecProtocol {
730
623
  return jsonFromBytes(buffer) as string[];
731
624
  }
732
625
 
626
+ /**
627
+ * Rebuild this protocol's `secret_id` namespace from a recovered `Secret`.
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.
632
+ * A helper or member whose `transports` is empty gets no channel: it is
633
+ * reported as a `PeerNotRestored` event in the returned array and the rest
634
+ * of the roster is restored.
635
+ *
636
+ * Rejects with a `DeRecError`; among its codes:
637
+ *
638
+ * | code | meaning |
639
+ * |--------------------|-------------------------------------------------------------|
640
+ * | `already_restored` | A user-secret snapshot already exists for this `secret_id`. |
641
+ * | `restore_conflict` | Channels live at ids restore is about to write. The error |
642
+ * | | carries `channel_ids: string[]` listing the collisions. |
643
+ */
733
644
  async restore(
734
645
  recoveredSecret: Extract<DeRecEvent, { type: 'SecretRecovered' }>['secret'],
735
646
  version: number,
@@ -743,4 +654,9 @@ export class DeRecProtocol {
743
654
  free(): void {
744
655
  this.host.free();
745
656
  }
657
+
658
+ /** Same as {@link DeRecProtocol.free}, for `using`. */
659
+ [Symbol.dispose](): void {
660
+ this.free();
661
+ }
746
662
  }