@leofcoin/peernet 2.0.0 → 3.1.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 (124) hide show
  1. package/README.md +45 -0
  2. package/docs/automatic-transport.md +37 -0
  3. package/exports/browser/{client-CAC7RVMf.js → client-DKKzPSlb.js} +252 -76
  4. package/exports/browser/{messages-Dw86z0zq.js → messages-DCePyqKC.js} +1 -1
  5. package/exports/browser/{peernet-B2jRhqGI.js → peernet-DjddqiXc.js} +460 -18
  6. package/exports/browser/peernet.js +1 -1
  7. package/exports/peernet.js +445 -3
  8. package/exports/types/archive.d.ts +69 -0
  9. package/exports/types/peernet.d.ts +7 -1
  10. package/package.json +9 -5
  11. package/.esdoc.json +0 -10
  12. package/.gitattributes +0 -2
  13. package/.github/workflows/release.yml +0 -34
  14. package/.prettierrc +0 -7
  15. package/.travis.yml +0 -27
  16. package/BREAKING_CHANGES.md +0 -35
  17. package/deploy.js +0 -8
  18. package/exports/browser/browser-D-r0O9Qn.js +0 -53
  19. package/exports/browser/browser-DvU1xNFS.js +0 -147
  20. package/exports/browser/browser-_hiyXwPp.js +0 -147
  21. package/exports/browser/client-86Tnax66.js +0 -2224
  22. package/exports/browser/client-BFxJts6j.js +0 -2224
  23. package/exports/browser/client-BVhUamQG.js +0 -2200
  24. package/exports/browser/client-BzvWzzLv.js +0 -2224
  25. package/exports/browser/client-C9GBz2Tf.js +0 -2224
  26. package/exports/browser/client-CQzuu6At.js +0 -2224
  27. package/exports/browser/client-CVkFejdx.js +0 -2224
  28. package/exports/browser/client-Cj_UUO8W.js +0 -2224
  29. package/exports/browser/client-CrGaeBjj.js +0 -2224
  30. package/exports/browser/client-DDC8C510.js +0 -2224
  31. package/exports/browser/client-ERocgKET.js +0 -2224
  32. package/exports/browser/client-a8rws3cu.js +0 -2224
  33. package/exports/browser/identity-CYVPDmkf.js +0 -8402
  34. package/exports/browser/identity-ChyyZSve.js +0 -8397
  35. package/exports/browser/index-BD0Anx7_.js +0 -37
  36. package/exports/browser/index-Bxr5Iztg.js +0 -37
  37. package/exports/browser/messages-BFCmg6Vb.js +0 -212
  38. package/exports/browser/messages-BHIC2mQu.js +0 -212
  39. package/exports/browser/messages-CimzJ4wr.js +0 -210
  40. package/exports/browser/messages-Cjoa_LGO.js +0 -210
  41. package/exports/browser/messages-D7JxkkDW.js +0 -212
  42. package/exports/browser/messages-DH0tgNM9.js +0 -207
  43. package/exports/browser/messages-DVHcNvs_.js +0 -212
  44. package/exports/browser/messages-DqL_g2qw.js +0 -210
  45. package/exports/browser/messages-UKnuelZ7.js +0 -207
  46. package/exports/browser/messages-XgZZUMma.js +0 -212
  47. package/exports/browser/messages-kfFyctPL.js +0 -210
  48. package/exports/browser/messages-psROnfRi.js +0 -212
  49. package/exports/browser/peernet-B9VL4Xwe.js +0 -9522
  50. package/exports/browser/peernet-C-HNdrcd.js +0 -9528
  51. package/exports/browser/peernet-C4rOvyyU.js +0 -9661
  52. package/exports/browser/peernet-Ca1iM2tN.js +0 -9336
  53. package/exports/browser/peernet-CbI0-jFn.js +0 -9651
  54. package/exports/browser/peernet-CtHScDyh.js +0 -9524
  55. package/exports/browser/peernet-DfuxUK3u.js +0 -9525
  56. package/exports/browser/peernet-DiEcNDLD.js +0 -9655
  57. package/exports/browser/peernet-DtuRXOzP.js +0 -9653
  58. package/exports/browser/peernet-r_9IyzTe.js +0 -9336
  59. package/exports/browser/peernet-rcD5XL1y.js +0 -9590
  60. package/exports/browser/peernet-rw8HJGOz.js +0 -9597
  61. package/exports/messages-CHKVVTVE.js +0 -204
  62. package/exports/messages-DaUtFfBh.js +0 -207
  63. package/index.html +0 -19
  64. package/rollup.config.js +0 -143
  65. package/src/dht/dht.ts +0 -142
  66. package/src/discovery/peer-discovery.js +0 -75
  67. package/src/errors/errors.js +0 -12
  68. package/src/file-transfer.ts +0 -194
  69. package/src/handlers/data.js +0 -15
  70. package/src/handlers/message.js +0 -34
  71. package/src/identity.ts +0 -172
  72. package/src/messages/chat.ts +0 -13
  73. package/src/messages/data-response.ts +0 -13
  74. package/src/messages/data.ts +0 -17
  75. package/src/messages/dht-response.ts +0 -13
  76. package/src/messages/dht.ts +0 -21
  77. package/src/messages/file-link.ts +0 -17
  78. package/src/messages/file.ts +0 -17
  79. package/src/messages/peer-response.ts +0 -13
  80. package/src/messages/peer.ts +0 -13
  81. package/src/messages/peernet.ts +0 -13
  82. package/src/messages/ps.ts +0 -13
  83. package/src/messages/request.ts +0 -13
  84. package/src/messages/response.ts +0 -13
  85. package/src/messages.js +0 -12
  86. package/src/peer-info.js +0 -9
  87. package/src/peernet.ts +0 -1113
  88. package/src/prompts/password/browser.js +0 -1
  89. package/src/prompts/password/node.js +0 -6
  90. package/src/prompts/password.js +0 -1
  91. package/src/proto/chat-message.proto.js +0 -6
  92. package/src/proto/data-response.proto.js +0 -4
  93. package/src/proto/data.proto.js +0 -4
  94. package/src/proto/dht-response.proto.js +0 -4
  95. package/src/proto/dht.proto.js +0 -4
  96. package/src/proto/file-link.proto.js +0 -5
  97. package/src/proto/file.proto.js +0 -10
  98. package/src/proto/peer-response.proto.js +0 -3
  99. package/src/proto/peer.proto.js +0 -3
  100. package/src/proto/peernet.proto.js +0 -7
  101. package/src/proto/ps.proto.js +0 -4
  102. package/src/proto/request.proto.js +0 -4
  103. package/src/proto/response.proto.js +0 -3
  104. package/src/types.ts +0 -25
  105. package/src/utils/utils.js +0 -77
  106. package/test/all.js +0 -5
  107. package/test/client.js +0 -15
  108. package/test/codec.js +0 -56
  109. package/test/file-transfer.test.js +0 -81
  110. package/test/fresh-empty-store.js +0 -15
  111. package/test/hash.js +0 -14
  112. package/test/index.js +0 -6
  113. package/test/lastBlock.js +0 -7
  114. package/test/messages.js +0 -26
  115. package/test/peer-data-exchange.test.js +0 -52
  116. package/test/peer-file-exchange.test.js +0 -78
  117. package/test/peernet.js +0 -17
  118. package/test/peernet.test.js +0 -477
  119. package/test-output.log +0 -84
  120. package/test.js +0 -63
  121. package/test2.js +0 -13
  122. package/test3.js +0 -16
  123. package/test4.js +0 -7
  124. package/tsconfig.json +0 -13
@@ -7090,23 +7090,23 @@ const getTokens = (proto) => {
7090
7090
  return tokens;
7091
7091
  };
7092
7092
 
7093
- const encoder = new TextEncoder();
7093
+ const encoder$1 = new TextEncoder();
7094
7094
  const toType = (data) => {
7095
7095
  if (data instanceof Uint8Array)
7096
7096
  return data;
7097
7097
  if (data instanceof ArrayBuffer)
7098
7098
  return new Uint8Array(data);
7099
7099
  if (typeof data === 'bigint')
7100
- return encoder.encode(data.toString());
7100
+ return encoder$1.encode(data.toString());
7101
7101
  if (typeof data === 'string')
7102
- return encoder.encode(data);
7102
+ return encoder$1.encode(data);
7103
7103
  if (typeof data === 'object')
7104
- return encoder.encode(JSON.stringify(data, jsonStringifyBigInt$1));
7104
+ return encoder$1.encode(JSON.stringify(data, jsonStringifyBigInt$1));
7105
7105
  if (typeof data === 'number' || typeof data === 'boolean')
7106
- return encoder.encode(data.toString());
7106
+ return encoder$1.encode(data.toString());
7107
7107
  throw new Error(`unsuported type ${typeof data || data}`);
7108
7108
  };
7109
- const encode = (proto, input, compress) => {
7109
+ const encode$1 = (proto, input, compress) => {
7110
7110
  const tokens = getTokens(proto);
7111
7111
  const set = [];
7112
7112
  for (let i = 0; i < tokens.length; i++) {
@@ -7163,8 +7163,8 @@ var index$1 = {
7163
7163
  toString: toString$1};
7164
7164
 
7165
7165
  const { toString } = index$1;
7166
- const decoder = new TextDecoder();
7167
- const decode = (proto, uint8Array, compressed) => {
7166
+ const decoder$1 = new TextDecoder();
7167
+ const decode$1 = (proto, uint8Array, compressed) => {
7168
7168
  if (compressed)
7169
7169
  uint8Array = pako.inflate(uint8Array);
7170
7170
  let deconcated = index$4(uint8Array);
@@ -7179,13 +7179,13 @@ const decode = (proto, uint8Array, compressed) => {
7179
7179
  else if (token.type === 'string')
7180
7180
  output[token.key] = toString(deconcated[i]);
7181
7181
  else if (token.type === 'boolean')
7182
- output[token.key] = decoder.decode(deconcated[i]) === 'true';
7182
+ output[token.key] = decoder$1.decode(deconcated[i]) === 'true';
7183
7183
  else if (token.type === 'number')
7184
- output[token.key] = Number(decoder.decode(deconcated[i]));
7184
+ output[token.key] = Number(decoder$1.decode(deconcated[i]));
7185
7185
  else if (token.type === 'bigint')
7186
- output[token.key] = BigInt(decoder.decode(deconcated[i]));
7186
+ output[token.key] = BigInt(decoder$1.decode(deconcated[i]));
7187
7187
  else if (token.type === 'object' || token.type === 'array')
7188
- output[token.key] = JSON.parse(decoder.decode(deconcated[i]), jsonParseBigInt$1);
7188
+ output[token.key] = JSON.parse(decoder$1.decode(deconcated[i]), jsonParseBigInt$1);
7189
7189
  if (token.optional) {
7190
7190
  if (!output[token.key] || output[token.key].length === 0)
7191
7191
  delete output[token.key];
@@ -7196,7 +7196,7 @@ const decode = (proto, uint8Array, compressed) => {
7196
7196
  return output;
7197
7197
  };
7198
7198
 
7199
- var index = { encode, decode };
7199
+ var index = { encode: encode$1, decode: decode$1 };
7200
7200
 
7201
7201
  const jsonStringifyBigInt = (key, value) => {
7202
7202
  if (typeof value === 'bigint')
@@ -8662,6 +8662,439 @@ class FileTransfer {
8662
8662
  }
8663
8663
  }
8664
8664
 
8665
+ const VERSION = 1;
8666
+ const MAX_PACKET_BYTES = 64_000;
8667
+ const MAX_ENVELOPE_BYTES = 32_000;
8668
+ const MAX_ARCHIVES = 500;
8669
+ const MAX_OUTBOX = 500;
8670
+ const MAX_AGE = 30 * 24 * 60 * 60 * 1000;
8671
+ const MAX_CLOCK_SKEW = 10 * 60 * 1000;
8672
+ const MAX_HOPS = 8;
8673
+ const encoder = new TextEncoder();
8674
+ const decoder = new TextDecoder();
8675
+ function isUnsubscribe(value) {
8676
+ return typeof value === 'function';
8677
+ }
8678
+ function encode(value) {
8679
+ return encoder.encode(JSON.stringify(value));
8680
+ }
8681
+ function decode(payload) {
8682
+ if (!(payload instanceof Uint8Array))
8683
+ throw new TypeError('Archive packets must be encoded bytes.');
8684
+ if (payload.byteLength > MAX_PACKET_BYTES)
8685
+ throw new RangeError('Archive packet exceeds the size limit.');
8686
+ return JSON.parse(decoder.decode(payload));
8687
+ }
8688
+ function randomId() {
8689
+ if (typeof crypto.randomUUID === 'function')
8690
+ return crypto.randomUUID();
8691
+ return Array.from(crypto.getRandomValues(new Uint8Array(16)), (byte) => byte.toString(16).padStart(2, '0')).join('');
8692
+ }
8693
+ function keyPrefix(network) {
8694
+ return `peernet:archive:v1:${encodeURIComponent(network)}:`;
8695
+ }
8696
+ function recordKey(network, kind, id) {
8697
+ return `${keyPrefix(network)}${kind}:${encodeURIComponent(id)}`;
8698
+ }
8699
+ function stateKey(network) {
8700
+ return `${keyPrefix(network)}state`;
8701
+ }
8702
+ async function storeGet(store, key) {
8703
+ if (store.has && !(await store.has(key)))
8704
+ return undefined;
8705
+ try {
8706
+ return await store.get(key);
8707
+ }
8708
+ catch (error) {
8709
+ if (error?.code === 'LEVEL_NOT_FOUND' ||
8710
+ error?.name === 'NotFoundError')
8711
+ return undefined;
8712
+ throw error;
8713
+ }
8714
+ }
8715
+ function validateEnvelope(envelope, network) {
8716
+ const envelopeNetwork = envelope?.network ||
8717
+ envelope?.senderCertificate?.network ||
8718
+ envelope?.recipientCertificate?.network;
8719
+ if (!envelope ||
8720
+ typeof envelope !== 'object' ||
8721
+ envelopeNetwork !== network ||
8722
+ typeof envelope.id !== 'string' ||
8723
+ !envelope.id ||
8724
+ typeof envelope.recipientAccount !== 'string' ||
8725
+ typeof envelope.recipientDeviceId !== 'string' ||
8726
+ typeof envelope.ciphertext !== 'string' ||
8727
+ !envelope.ciphertext ||
8728
+ typeof envelope.signature !== 'string' ||
8729
+ !Number.isFinite(envelope.timestamp) ||
8730
+ envelope.timestamp < Date.now() - MAX_AGE ||
8731
+ envelope.timestamp > Date.now() + MAX_CLOCK_SKEW)
8732
+ throw new Error('Only a valid, network-scoped encrypted message envelope can be archived.');
8733
+ if ('text' in envelope || 'plaintext' in envelope || 'message' in envelope)
8734
+ throw new Error('Plaintext message fields cannot be archived.');
8735
+ if (encode(envelope).byteLength > MAX_ENVELOPE_BYTES)
8736
+ throw new RangeError('Encrypted message envelope exceeds the archive size limit.');
8737
+ }
8738
+ function validatePacket(value, network) {
8739
+ if (!value || typeof value !== 'object' || Array.isArray(value))
8740
+ return;
8741
+ const packet = value;
8742
+ if (packet.version !== VERSION ||
8743
+ packet.network !== network ||
8744
+ typeof packet.id !== 'string' ||
8745
+ !packet.id ||
8746
+ typeof packet.origin !== 'string' ||
8747
+ !packet.origin ||
8748
+ !Number.isFinite(packet.timestamp) ||
8749
+ packet.timestamp < Date.now() - MAX_AGE ||
8750
+ packet.timestamp > Date.now() + MAX_CLOCK_SKEW ||
8751
+ !Number.isInteger(packet.hopsRemaining) ||
8752
+ packet.hopsRemaining < 0 ||
8753
+ packet.hopsRemaining > MAX_HOPS ||
8754
+ !['direct', 'inbox-request', 'archive-delivery', 'delivery-receipt'].includes(packet.type) ||
8755
+ !packet.payload ||
8756
+ typeof packet.payload !== 'object' ||
8757
+ Array.isArray(packet.payload))
8758
+ return;
8759
+ return packet;
8760
+ }
8761
+ class PeernetArchiveMode {
8762
+ #peernet;
8763
+ #store;
8764
+ #network;
8765
+ #topic;
8766
+ #verifyEnvelope;
8767
+ #verifyInboxRequest;
8768
+ #verifyReceipt;
8769
+ #handlers = new Set();
8770
+ #seen = new Map();
8771
+ #archives = new Set();
8772
+ #outbox = new Set();
8773
+ #enabled = false;
8774
+ #started = false;
8775
+ #unsubscribe;
8776
+ #retryTimer;
8777
+ #account = '';
8778
+ #deviceId = '';
8779
+ constructor(options) {
8780
+ if (!options?.peernet?.network || typeof options.peernet.publish !== 'function' ||
8781
+ typeof options.peernet.subscribe !== 'function')
8782
+ throw new Error('A Peernet instance is required for archive mode.');
8783
+ const store = options.store || globalThis.messageStore;
8784
+ if (!store || typeof store.get !== 'function' || typeof store.put !== 'function' ||
8785
+ typeof store.delete !== 'function' || typeof store.keys !== 'function')
8786
+ throw new Error('A persistent Peernet message store is required for archive mode.');
8787
+ if (typeof options.verifyEnvelope !== 'function' ||
8788
+ typeof options.verifyInboxRequest !== 'function' ||
8789
+ typeof options.verifyReceipt !== 'function')
8790
+ throw new Error('Archive mode requires envelope, inbox-request and receipt verification functions.');
8791
+ this.#peernet = options.peernet;
8792
+ this.#store = store;
8793
+ this.#network = options.peernet.network;
8794
+ this.#topic = `peernet:archive:v1:${this.#network}`;
8795
+ this.#verifyEnvelope = options.verifyEnvelope;
8796
+ this.#verifyInboxRequest = options.verifyInboxRequest;
8797
+ this.#verifyReceipt = options.verifyReceipt;
8798
+ }
8799
+ get network() {
8800
+ return this.#network;
8801
+ }
8802
+ get enabled() {
8803
+ return this.#enabled;
8804
+ }
8805
+ get archiveCount() {
8806
+ return this.#archives.size;
8807
+ }
8808
+ get pendingCount() {
8809
+ return this.#outbox.size;
8810
+ }
8811
+ onMessage(handler) {
8812
+ if (typeof handler !== 'function')
8813
+ throw new TypeError('An archive message handler is required.');
8814
+ this.#handlers.add(handler);
8815
+ return () => this.#handlers.delete(handler);
8816
+ }
8817
+ async start({ account, deviceId }) {
8818
+ if (this.#started)
8819
+ return;
8820
+ if (!account || !deviceId)
8821
+ throw new Error('A local account and chat device ID are required.');
8822
+ this.#account = account;
8823
+ this.#deviceId = deviceId;
8824
+ await this.#load();
8825
+ const receiver = (payload) => {
8826
+ this.#receive(payload).catch((error) => console.error('Peernet archive packet failed:', error));
8827
+ };
8828
+ const unsubscribe = await this.#peernet.subscribe(this.#topic, receiver);
8829
+ this.#unsubscribe = isUnsubscribe(unsubscribe)
8830
+ ? unsubscribe
8831
+ : typeof globalThis.globalSub?.unsubscribe === 'function'
8832
+ ? () => globalThis.globalSub.unsubscribe(this.#topic, receiver)
8833
+ : undefined;
8834
+ this.#started = true;
8835
+ this.#retryTimer = setInterval(() => {
8836
+ this.#retryOutbox().catch((error) => console.error('Peernet archive retry failed:', error));
8837
+ }, 30_000);
8838
+ try {
8839
+ for (const id of this.#outbox) {
8840
+ const bytes = await storeGet(this.#store, recordKey(this.#network, 'outbox', id));
8841
+ if (bytes)
8842
+ await this.#publish('direct', { envelope: JSON.parse(decoder.decode(bytes)) });
8843
+ }
8844
+ }
8845
+ catch (error) {
8846
+ this.stop();
8847
+ throw error;
8848
+ }
8849
+ }
8850
+ stop() {
8851
+ clearInterval(this.#retryTimer);
8852
+ this.#retryTimer = undefined;
8853
+ this.#unsubscribe?.();
8854
+ this.#unsubscribe = undefined;
8855
+ this.#started = false;
8856
+ this.#account = '';
8857
+ this.#deviceId = '';
8858
+ }
8859
+ async setEnabled(enabled) {
8860
+ if (typeof enabled !== 'boolean')
8861
+ throw new TypeError('Archive mode must be enabled or disabled explicitly.');
8862
+ if (!this.#started)
8863
+ await this.#load();
8864
+ this.#enabled = enabled;
8865
+ await this.#store.put(stateKey(this.#network), encode({ version: VERSION, enabled }));
8866
+ if (!enabled) {
8867
+ for (const id of this.#archives)
8868
+ await this.#store.delete(recordKey(this.#network, 'archive', id));
8869
+ this.#archives.clear();
8870
+ }
8871
+ }
8872
+ async send(envelope) {
8873
+ validateEnvelope(envelope, this.#network);
8874
+ if (!(await this.#verifyEnvelope(envelope)))
8875
+ throw new Error('Encrypted message signature verification failed.');
8876
+ if (this.#outbox.has(envelope.id))
8877
+ throw new Error('This encrypted message is already pending.');
8878
+ await this.#store.put(recordKey(this.#network, 'outbox', envelope.id), encode(envelope));
8879
+ this.#outbox.add(envelope.id);
8880
+ if (this.#outbox.size > MAX_OUTBOX)
8881
+ await this.#dropOldest('outbox');
8882
+ await this.#publish('direct', { envelope });
8883
+ }
8884
+ async requestInbox(request) {
8885
+ if (request.network !== this.#network ||
8886
+ request.account !== this.#account ||
8887
+ request.deviceId !== this.#deviceId ||
8888
+ !(await this.#validInboxRequest(request)))
8889
+ throw new Error('A valid signed inbox request for this Peernet network and device is required.');
8890
+ await this.#publish('inbox-request', { request });
8891
+ }
8892
+ async acknowledge(receipt) {
8893
+ if (receipt.network !== this.#network ||
8894
+ receipt.recipientAccount !== this.#account ||
8895
+ receipt.recipientDeviceId !== this.#deviceId ||
8896
+ typeof receipt.signature !== 'string' ||
8897
+ !Number.isFinite(receipt.timestamp) ||
8898
+ Math.abs(Date.now() - receipt.timestamp) > MAX_CLOCK_SKEW)
8899
+ throw new Error('Delivery receipt does not belong to this Peernet network and device.');
8900
+ await this.#publish('delivery-receipt', { receipt });
8901
+ }
8902
+ async #load() {
8903
+ const state = await storeGet(this.#store, stateKey(this.#network));
8904
+ if (state) {
8905
+ const saved = JSON.parse(decoder.decode(state));
8906
+ if (saved?.version !== VERSION || typeof saved.enabled !== 'boolean')
8907
+ throw new Error('Saved Peernet archive settings are invalid.');
8908
+ this.#enabled = saved.enabled;
8909
+ }
8910
+ const prefix = keyPrefix(this.#network);
8911
+ const keys = await this.#store.keys();
8912
+ for (const keyInput of keys) {
8913
+ const key = keyInput.toString().replace(/^\/+/, '');
8914
+ if (!key.startsWith(prefix))
8915
+ continue;
8916
+ const archiveMarker = `${prefix}archive:`;
8917
+ const outboxMarker = `${prefix}outbox:`;
8918
+ if (key.startsWith(archiveMarker))
8919
+ this.#archives.add(decodeURIComponent(key.slice(archiveMarker.length)));
8920
+ if (key.startsWith(outboxMarker))
8921
+ this.#outbox.add(decodeURIComponent(key.slice(outboxMarker.length)));
8922
+ }
8923
+ await this.#prune();
8924
+ }
8925
+ async #publish(type, payload, hopsRemaining = MAX_HOPS) {
8926
+ if (!this.#started)
8927
+ throw new Error('Peernet archive mode is not started.');
8928
+ const packet = {
8929
+ version: VERSION,
8930
+ id: randomId(),
8931
+ network: this.#network,
8932
+ origin: this.#peernet.id,
8933
+ timestamp: Date.now(),
8934
+ hopsRemaining,
8935
+ type,
8936
+ payload
8937
+ };
8938
+ const bytes = encode(packet);
8939
+ if (bytes.byteLength > MAX_PACKET_BYTES)
8940
+ throw new RangeError('Peernet archive packet exceeds its size limit.');
8941
+ this.#seen.set(packet.id, packet.timestamp);
8942
+ await this.#peernet.publish(this.#topic, bytes);
8943
+ }
8944
+ async #receive(payload) {
8945
+ const packet = validatePacket(decode(payload), this.#network);
8946
+ if (!packet || packet.origin === this.#peernet.id || this.#wasSeen(packet.id))
8947
+ return;
8948
+ this.#markSeen(packet.id);
8949
+ let forward = false;
8950
+ switch (packet.type) {
8951
+ case 'direct':
8952
+ case 'archive-delivery': {
8953
+ const envelope = packet.payload.envelope;
8954
+ try {
8955
+ validateEnvelope(envelope, this.#network);
8956
+ }
8957
+ catch {
8958
+ break;
8959
+ }
8960
+ if (!(await this.#verifyEnvelope(envelope)))
8961
+ break;
8962
+ forward = true;
8963
+ if (packet.type === 'direct' && this.#enabled)
8964
+ await this.#saveArchive(envelope);
8965
+ if (envelope.recipientAccount === this.#account && envelope.recipientDeviceId === this.#deviceId) {
8966
+ for (const handler of this.#handlers)
8967
+ handler({ type: 'message', envelope });
8968
+ }
8969
+ break;
8970
+ }
8971
+ case 'inbox-request': {
8972
+ const request = packet.payload.request;
8973
+ if (!(await this.#validInboxRequest(request)))
8974
+ break;
8975
+ forward = true;
8976
+ if (this.#enabled && !(request.account === this.#account && request.deviceId === this.#deviceId))
8977
+ await this.#deliverTo(request);
8978
+ break;
8979
+ }
8980
+ case 'delivery-receipt': {
8981
+ const receipt = packet.payload.receipt;
8982
+ forward = await this.#receiveReceipt(receipt);
8983
+ break;
8984
+ }
8985
+ }
8986
+ if (forward && packet.hopsRemaining > 0) {
8987
+ await this.#peernet.publish(this.#topic, encode({ ...packet, hopsRemaining: packet.hopsRemaining - 1 }));
8988
+ }
8989
+ }
8990
+ async #validInboxRequest(request) {
8991
+ return Boolean(request &&
8992
+ request.network === this.#network &&
8993
+ typeof request.account === 'string' &&
8994
+ typeof request.deviceId === 'string' &&
8995
+ Number.isFinite(request.timestamp) &&
8996
+ Math.abs(Date.now() - request.timestamp) <= MAX_CLOCK_SKEW &&
8997
+ typeof request.signature === 'string' &&
8998
+ await this.#verifyInboxRequest(request));
8999
+ }
9000
+ async #saveArchive(envelope) {
9001
+ if (this.#archives.has(envelope.id))
9002
+ return;
9003
+ await this.#store.put(recordKey(this.#network, 'archive', envelope.id), encode({
9004
+ envelope,
9005
+ storedAt: Date.now()
9006
+ }));
9007
+ this.#archives.add(envelope.id);
9008
+ if (this.#archives.size > MAX_ARCHIVES)
9009
+ await this.#dropOldest('archive');
9010
+ }
9011
+ async #deliverTo(request) {
9012
+ for (const id of this.#archives) {
9013
+ const bytes = await storeGet(this.#store, recordKey(this.#network, 'archive', id));
9014
+ if (!bytes) {
9015
+ this.#archives.delete(id);
9016
+ continue;
9017
+ }
9018
+ const record = JSON.parse(decoder.decode(bytes));
9019
+ const envelope = record?.envelope;
9020
+ if (envelope?.recipientAccount === request.account && envelope.recipientDeviceId === request.deviceId)
9021
+ await this.#publish('archive-delivery', { envelope });
9022
+ }
9023
+ }
9024
+ async #receiveReceipt(receipt) {
9025
+ if (!receipt || receipt.network !== this.#network ||
9026
+ typeof receipt.messageId !== 'string' ||
9027
+ !Number.isFinite(receipt.timestamp) ||
9028
+ Math.abs(Date.now() - receipt.timestamp) > MAX_CLOCK_SKEW)
9029
+ return false;
9030
+ const keys = ['archive', 'outbox'];
9031
+ let verified = false;
9032
+ for (const kind of keys) {
9033
+ const key = recordKey(this.#network, kind, receipt.messageId);
9034
+ const bytes = await storeGet(this.#store, key);
9035
+ if (!bytes)
9036
+ continue;
9037
+ const stored = JSON.parse(decoder.decode(bytes));
9038
+ const envelope = kind === 'archive' ? stored.envelope : stored;
9039
+ if (!(await this.#verifyReceipt(receipt, envelope)))
9040
+ continue;
9041
+ await this.#store.delete(key);
9042
+ (kind === 'archive' ? this.#archives : this.#outbox).delete(receipt.messageId);
9043
+ verified = true;
9044
+ }
9045
+ return verified;
9046
+ }
9047
+ async #retryOutbox() {
9048
+ if (!this.#started)
9049
+ return;
9050
+ await this.#prune();
9051
+ for (const id of this.#outbox) {
9052
+ const bytes = await storeGet(this.#store, recordKey(this.#network, 'outbox', id));
9053
+ if (bytes)
9054
+ await this.#publish('direct', { envelope: JSON.parse(decoder.decode(bytes)) });
9055
+ }
9056
+ }
9057
+ async #prune() {
9058
+ const cutoff = Date.now() - MAX_AGE;
9059
+ for (const [kind, ids] of [['archive', this.#archives], ['outbox', this.#outbox]]) {
9060
+ for (const id of ids) {
9061
+ const key = recordKey(this.#network, kind, id);
9062
+ const bytes = await storeGet(this.#store, key);
9063
+ if (!bytes) {
9064
+ ids.delete(id);
9065
+ continue;
9066
+ }
9067
+ const saved = JSON.parse(decoder.decode(bytes));
9068
+ const timestamp = kind === 'archive' ? saved.storedAt : saved.timestamp;
9069
+ if (!Number.isFinite(timestamp) || timestamp < cutoff) {
9070
+ await this.#store.delete(key);
9071
+ ids.delete(id);
9072
+ }
9073
+ }
9074
+ }
9075
+ }
9076
+ async #dropOldest(kind) {
9077
+ const ids = kind === 'archive' ? this.#archives : this.#outbox;
9078
+ const id = ids.values().next().value;
9079
+ if (!id)
9080
+ return;
9081
+ await this.#store.delete(recordKey(this.#network, kind, id));
9082
+ ids.delete(id);
9083
+ }
9084
+ #wasSeen(id) {
9085
+ const timestamp = this.#seen.get(id);
9086
+ return timestamp !== undefined && Date.now() - timestamp < MAX_AGE;
9087
+ }
9088
+ #markSeen(id) {
9089
+ this.#seen.set(id, Date.now());
9090
+ if (this.#seen.size > 4_000) {
9091
+ const oldestId = this.#seen.keys().next().value;
9092
+ if (oldestId !== undefined)
9093
+ this.#seen.delete(oldestId);
9094
+ }
9095
+ }
9096
+ }
9097
+
8665
9098
  const DEFAULT_CHUNK_SIZE = 256 * 1024;
8666
9099
  const DEFAULT_BLOCK_CHUNK_SIZE = 256 * 1024;
8667
9100
  const DEFAULT_BLOCK_CHUNK_THRESHOLD = 1024 * 1024;
@@ -8688,7 +9121,7 @@ class Peernet {
8688
9121
  root;
8689
9122
  identity;
8690
9123
  stores = [];
8691
- peerId;
9124
+ routingId;
8692
9125
  /**
8693
9126
  * @type {Object}
8694
9127
  * @property {Object} peer Instance of Peer
@@ -8723,6 +9156,7 @@ class Peernet {
8723
9156
  * @param {String} options.root - path to root directory
8724
9157
  * @param {String} options.version - path to root directory
8725
9158
  * @param {String} options.storePrefix - prefix for datatores (lfc)
9159
+ * @param {String} options.routingId - optional transport identity, separate from the wallet identity
8726
9160
  * @param {Object} options.transport - transport selection and fallback options passed to @netpeer/swarm
8727
9161
  *
8728
9162
  * @return {Promise} instance of Peernet
@@ -8735,6 +9169,9 @@ class Peernet {
8735
9169
  * @property {String} network - current network
8736
9170
  */
8737
9171
  this.network = options.network || 'leofcoin';
9172
+ if (options.routingId !== undefined && (typeof options.routingId !== 'string' || !options.routingId.trim()))
9173
+ throw new TypeError('routingId must be a non-empty string');
9174
+ this.routingId = options.routingId?.trim();
8738
9175
  this.autoStart = options.autoStart === undefined ? true : options.autoStart;
8739
9176
  this.stars = options.stars;
8740
9177
  this.transport = options.transport;
@@ -8766,6 +9203,9 @@ class Peernet {
8766
9203
  return this._init(options, password);
8767
9204
  }
8768
9205
  get id() {
9206
+ return this.routingId || this.identity.id;
9207
+ }
9208
+ get peerId() {
8769
9209
  return this.identity.id;
8770
9210
  }
8771
9211
  get selectedAccount() {
@@ -8787,6 +9227,9 @@ class Peernet {
8787
9227
  addCodec(codec) {
8788
9228
  return utils.addCodec(codec);
8789
9229
  }
9230
+ createArchiveMode(options) {
9231
+ return new PeernetArchiveMode({ ...options, peernet: this });
9232
+ }
8790
9233
  async addStore(name, prefix, root, isPrivate = true) {
8791
9234
  if (name === 'block' ||
8792
9235
  name === 'transaction' ||
@@ -8840,7 +9283,7 @@ class Peernet {
8840
9283
  await getAddress();
8841
9284
  this.storePrefix = options.storePrefix;
8842
9285
  this.root = options.root;
8843
- const { RequestMessage, ResponseMessage, PeerMessage, PeerMessageResponse, PeernetMessage, DHTMessage, DHTMessageResponse, DataMessage, DataMessageResponse, PsMessage, ChatMessage, PeernetFile } = await import(/* webpackChunkName: "messages" */ './messages-Dw86z0zq.js');
9286
+ const { RequestMessage, ResponseMessage, PeerMessage, PeerMessageResponse, PeernetMessage, DHTMessage, DHTMessageResponse, DataMessage, DataMessageResponse, PsMessage, ChatMessage, PeernetFile } = await import(/* webpackChunkName: "messages" */ './messages-DCePyqKC.js');
8844
9287
  /**
8845
9288
  * proto Object containing protos
8846
9289
  * @type {Object}
@@ -8873,7 +9316,6 @@ class Peernet {
8873
9316
  this.identity = new Identity(this.network);
8874
9317
  await this.identity.load(password, options.freshIdentity === true);
8875
9318
  this._peerHandler = new PeerDiscovery(this.id);
8876
- this.peerId = this.id;
8877
9319
  /**
8878
9320
  * converts data -> message -> proto
8879
9321
  * @see DataHandler
@@ -8887,7 +9329,7 @@ class Peernet {
8887
9329
  if (this.#starting || this.#started)
8888
9330
  return;
8889
9331
  this.#starting = true;
8890
- const importee = await import('./client-CAC7RVMf.js');
9332
+ const importee = await import('./client-DKKzPSlb.js');
8891
9333
  /**
8892
9334
  * @access public
8893
9335
  * @type {PeernetClient}
@@ -9681,4 +10123,4 @@ class Peernet {
9681
10123
  }
9682
10124
  globalThis.Peernet = Peernet;
9683
10125
 
9684
- export { FormatInterface as F, LittlePubSub as L, Peernet as P, FileTransfer as a, createDebugger as c, deflate_1 as d, inflate_1 as i };
10126
+ export { FormatInterface as F, LittlePubSub as L, PeernetArchiveMode as P, FileTransfer as a, Peernet as b, createDebugger as c, deflate_1 as d, inflate_1 as i };
@@ -1,4 +1,4 @@
1
- export { a as FileTransfer, P as default } from './peernet-B2jRhqGI.js';
1
+ export { a as FileTransfer, P as PeernetArchiveMode, b as default } from './peernet-DjddqiXc.js';
2
2
  import './identity-Cf8KKnho.js';
3
3
  import './value-C3vAp-wb.js';
4
4
  import 'crypto';