@fairfox/polly 0.69.0 → 0.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/src/client/index.js +17 -20
  2. package/dist/src/client/index.js.map +4 -4
  3. package/dist/src/mesh.js +462 -154
  4. package/dist/src/mesh.js.map +9 -7
  5. package/dist/src/peer.js +153 -9
  6. package/dist/src/peer.js.map +5 -4
  7. package/dist/src/polly-ui/markdown.js +3 -3
  8. package/dist/src/polly-ui/markdown.js.map +2 -2
  9. package/dist/src/shared/lib/mesh-client.d.ts +29 -0
  10. package/dist/src/shared/lib/mesh-diagnostics.d.ts +129 -0
  11. package/dist/src/shared/lib/mesh-network-adapter.d.ts +90 -3
  12. package/dist/src/shared/lib/revocation-summary.d.ts +54 -0
  13. package/dist/tools/test/src/e2e-mesh/console-allowlist.d.ts +31 -0
  14. package/dist/tools/test/src/e2e-mesh/index.d.ts +27 -0
  15. package/dist/tools/test/src/e2e-mesh/index.js +1183 -0
  16. package/dist/tools/test/src/e2e-mesh/index.js.map +22 -0
  17. package/dist/tools/test/src/e2e-mesh/keys.d.ts +55 -0
  18. package/dist/tools/test/src/e2e-mesh/launch-peer.d.ts +98 -0
  19. package/dist/tools/test/src/e2e-mesh/mesh-assertions.d.ts +53 -0
  20. package/dist/tools/test/src/e2e-mesh/serve-consumer.d.ts +32 -0
  21. package/dist/tools/test/src/e2e-mesh/wait-for-convergence.d.ts +38 -0
  22. package/dist/tools/test/src/e2e-mesh/with-relay.d.ts +53 -0
  23. package/dist/tools/test/src/visual/index.js +24 -24
  24. package/dist/tools/test/src/visual/index.js.map +2 -2
  25. package/dist/tools/verify/src/cli.js +361 -22
  26. package/dist/tools/verify/src/cli.js.map +6 -6
  27. package/dist/tools/verify/src/config.d.ts +26 -1
  28. package/dist/tools/verify/src/config.js +9 -1
  29. package/dist/tools/verify/src/config.js.map +4 -4
  30. package/dist/tools/verify/src/primitives/index.d.ts +30 -0
  31. package/dist/tools/visualize/src/cli.js +43 -1
  32. package/dist/tools/visualize/src/cli.js.map +3 -3
  33. package/package.json +11 -8
  34. package/LICENSE +0 -21
  35. package/README.md +0 -362
package/dist/src/peer.js CHANGED
@@ -650,6 +650,30 @@ import {
650
650
  NetworkAdapter
651
651
  } from "@automerge/automerge-repo/slim";
652
652
 
653
+ // src/shared/lib/mesh-diagnostics.ts
654
+ var listeners = new Set;
655
+ function emitMeshDiagnostic(diagnostic) {
656
+ const event = { ...diagnostic, timestamp: Date.now() };
657
+ for (const listener of listeners) {
658
+ try {
659
+ listener(event);
660
+ } catch {}
661
+ }
662
+ }
663
+ function subscribeToMeshDiagnostics(listener) {
664
+ listeners.add(listener);
665
+ return () => {
666
+ listeners.delete(listener);
667
+ };
668
+ }
669
+ function recordMeshDiagnostics() {
670
+ const captured = [];
671
+ const stop = subscribeToMeshDiagnostics((event) => {
672
+ captured.push(event);
673
+ });
674
+ return { events: captured, stop };
675
+ }
676
+
653
677
  // src/shared/lib/signing.ts
654
678
  import nacl2 from "tweetnacl";
655
679
  var PUBLIC_KEY_BYTES = 32;
@@ -735,11 +759,17 @@ function decodeSignedEnvelope(bytes) {
735
759
 
736
760
  // src/shared/lib/mesh-network-adapter.ts
737
761
  var DEFAULT_MESH_KEY_ID = "polly-mesh-default";
762
+ var MESH_CONTROL_TYPE = {
763
+ Sync: 0,
764
+ Revocation: 1,
765
+ RevocationSummary: 2
766
+ };
738
767
 
739
768
  class MeshNetworkAdapter extends NetworkAdapter {
740
769
  base;
741
770
  keyringSource;
742
771
  encryptionEnabled;
772
+ onControlMessage;
743
773
  get keyring() {
744
774
  return this.keyringSource();
745
775
  }
@@ -748,6 +778,7 @@ class MeshNetworkAdapter extends NetworkAdapter {
748
778
  this.base = options.base;
749
779
  this.keyringSource = options.keyringSource;
750
780
  this.encryptionEnabled = options.encryptionEnabled ?? true;
781
+ this.onControlMessage = options.onControlMessage;
751
782
  this.base.on("close", () => this.emit("close"));
752
783
  this.base.on("peer-candidate", (payload) => this.emit("peer-candidate", payload));
753
784
  this.base.on("peer-disconnected", (payload) => this.emit("peer-disconnected", payload));
@@ -781,16 +812,17 @@ class MeshNetworkAdapter extends NetworkAdapter {
781
812
  wrap(message) {
782
813
  const keyring = this.keyringSource();
783
814
  const serialised = serialiseMessage(message);
815
+ const tagged = prependControlTag(MESH_CONTROL_TYPE.Sync, serialised);
784
816
  let payloadToSign;
785
817
  if (this.encryptionEnabled) {
786
818
  const docKey = keyring.documentKeys.get(DEFAULT_MESH_KEY_ID);
787
819
  if (!docKey) {
788
820
  throw new Error(`MeshNetworkAdapter: missing document encryption key under id "${DEFAULT_MESH_KEY_ID}". Provision the key in the keyring before sending.`);
789
821
  }
790
- const encrypted = sealEnvelope(serialised, DEFAULT_MESH_KEY_ID, docKey);
822
+ const encrypted = sealEnvelope(tagged, DEFAULT_MESH_KEY_ID, docKey);
791
823
  payloadToSign = encodeEncryptedEnvelope(encrypted);
792
824
  } else {
793
- payloadToSign = serialised;
825
+ payloadToSign = tagged;
794
826
  }
795
827
  const signed = signEnvelope(payloadToSign, message.senderId, keyring.identity.secretKey);
796
828
  const signedBytes = encodeSignedEnvelope(signed);
@@ -811,44 +843,156 @@ class MeshNetworkAdapter extends NetworkAdapter {
811
843
  let signed;
812
844
  try {
813
845
  signed = decodeSignedEnvelope(message.data);
814
- } catch {
846
+ } catch (err) {
847
+ emitMeshDiagnostic({
848
+ kind: "drop:malformed-signed-envelope",
849
+ reason: err instanceof Error ? err.message : String(err)
850
+ });
815
851
  return;
816
852
  }
817
853
  const keyring = this.keyringSource();
818
854
  if (keyring.revokedPeers.has(signed.senderId)) {
855
+ emitMeshDiagnostic({
856
+ kind: "drop:revoked-peer",
857
+ senderId: signed.senderId
858
+ });
819
859
  return;
820
860
  }
821
861
  const senderKey = keyring.knownPeers.get(signed.senderId);
822
862
  if (!senderKey) {
863
+ emitMeshDiagnostic({
864
+ kind: "drop:unknown-peer",
865
+ senderId: signed.senderId
866
+ });
823
867
  return;
824
868
  }
825
869
  let verifiedPayload;
826
870
  try {
827
871
  verifiedPayload = openEnvelope2(signed, senderKey);
828
- } catch {
872
+ } catch (err) {
873
+ emitMeshDiagnostic({
874
+ kind: "drop:bad-signature",
875
+ senderId: signed.senderId,
876
+ reason: err instanceof Error ? err.message : String(err)
877
+ });
829
878
  return;
830
879
  }
831
880
  if (!this.encryptionEnabled) {
832
- return deserialiseMessage(verifiedPayload);
881
+ return this.dispatchTaggedPayload(verifiedPayload, signed.senderId);
833
882
  }
834
883
  let encrypted;
835
884
  try {
836
885
  encrypted = decodeEncryptedEnvelope(verifiedPayload);
837
- } catch {
886
+ } catch (err) {
887
+ emitMeshDiagnostic({
888
+ kind: "drop:malformed-encrypted-envelope",
889
+ senderId: signed.senderId,
890
+ reason: err instanceof Error ? err.message : String(err)
891
+ });
838
892
  return;
839
893
  }
840
894
  const docKey = keyring.documentKeys.get(encrypted.documentId);
841
895
  if (!docKey) {
896
+ emitMeshDiagnostic({
897
+ kind: "drop:missing-doc-key",
898
+ senderId: signed.senderId,
899
+ documentId: encrypted.documentId
900
+ });
842
901
  return;
843
902
  }
844
903
  let plaintext;
845
904
  try {
846
905
  plaintext = openEnvelope(encrypted, docKey);
847
- } catch {
906
+ } catch (err) {
907
+ emitMeshDiagnostic({
908
+ kind: "drop:bad-decryption",
909
+ senderId: signed.senderId,
910
+ documentId: encrypted.documentId,
911
+ reason: err instanceof Error ? err.message : String(err)
912
+ });
848
913
  return;
849
914
  }
850
- return deserialiseMessage(plaintext);
915
+ return this.dispatchTaggedPayload(plaintext, signed.senderId);
851
916
  }
917
+ dispatchTaggedPayload(payload, senderId) {
918
+ if (payload.byteLength < 1) {
919
+ emitMeshDiagnostic({ kind: "drop:empty-control-payload", senderId });
920
+ return;
921
+ }
922
+ const tag = payload[0];
923
+ const body = payload.subarray(1);
924
+ switch (tag) {
925
+ case MESH_CONTROL_TYPE.Sync:
926
+ return deserialiseMessage(body);
927
+ case MESH_CONTROL_TYPE.Revocation:
928
+ emitMeshDiagnostic({ kind: "ctrl:revocation-received", senderId });
929
+ this.invokeControlHandler(tag, body, senderId);
930
+ return;
931
+ case MESH_CONTROL_TYPE.RevocationSummary:
932
+ emitMeshDiagnostic({
933
+ kind: "ctrl:revocation-summary-received",
934
+ senderId
935
+ });
936
+ this.invokeControlHandler(tag, body, senderId);
937
+ return;
938
+ default:
939
+ emitMeshDiagnostic({
940
+ kind: "drop:unknown-control-type",
941
+ senderId,
942
+ tag
943
+ });
944
+ return;
945
+ }
946
+ }
947
+ invokeControlHandler(tag, body, senderId) {
948
+ if (!this.onControlMessage)
949
+ return;
950
+ try {
951
+ this.onControlMessage(tag, body, senderId);
952
+ } catch (err) {
953
+ emitMeshDiagnostic({
954
+ kind: "drop:control-handler-threw",
955
+ senderId,
956
+ tag,
957
+ reason: err instanceof Error ? err.message : String(err)
958
+ });
959
+ }
960
+ }
961
+ sendControlMessage(tag, body, targetPeerIds) {
962
+ if (targetPeerIds.length === 0)
963
+ return;
964
+ const keyring = this.keyringSource();
965
+ const tagged = prependControlTag(tag, body);
966
+ let payloadToSign;
967
+ if (this.encryptionEnabled) {
968
+ const docKey = keyring.documentKeys.get(DEFAULT_MESH_KEY_ID);
969
+ if (!docKey) {
970
+ throw new Error(`MeshNetworkAdapter.sendControlMessage: missing document encryption key under id "${DEFAULT_MESH_KEY_ID}".`);
971
+ }
972
+ const encrypted = sealEnvelope(tagged, DEFAULT_MESH_KEY_ID, docKey);
973
+ payloadToSign = encodeEncryptedEnvelope(encrypted);
974
+ } else {
975
+ payloadToSign = tagged;
976
+ }
977
+ const senderId = this.peerId ?? "";
978
+ const signed = signEnvelope(payloadToSign, senderId, keyring.identity.secretKey);
979
+ const signedBytes = encodeSignedEnvelope(signed);
980
+ for (const targetId of targetPeerIds) {
981
+ const outer = {
982
+ type: "sync",
983
+ senderId,
984
+ targetId,
985
+ data: signedBytes
986
+ };
987
+ this.base.send(outer);
988
+ }
989
+ }
990
+ }
991
+ function prependControlTag(tag, body) {
992
+ const out = new Uint8Array(body.byteLength + 1);
993
+ out[0] = tag;
994
+ out.set(body, 1);
995
+ return out;
852
996
  }
853
997
  function serialiseMessage(message) {
854
998
  const headerObj = {
@@ -1083,4 +1227,4 @@ export {
1083
1227
  $crdtCounter
1084
1228
  };
1085
1229
 
1086
- //# debugId=D4249D328F09D19464756E2164756E21
1230
+ //# debugId=C92D0D86B1D86EE064756E2164756E21