@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.
- package/README.md +45 -0
- package/docs/automatic-transport.md +37 -0
- package/exports/browser/{client-CAC7RVMf.js → client-DKKzPSlb.js} +252 -76
- package/exports/browser/{messages-Dw86z0zq.js → messages-DCePyqKC.js} +1 -1
- package/exports/browser/{peernet-B2jRhqGI.js → peernet-DjddqiXc.js} +460 -18
- package/exports/browser/peernet.js +1 -1
- package/exports/peernet.js +445 -3
- package/exports/types/archive.d.ts +69 -0
- package/exports/types/peernet.d.ts +7 -1
- package/package.json +9 -5
- package/.esdoc.json +0 -10
- package/.gitattributes +0 -2
- package/.github/workflows/release.yml +0 -34
- package/.prettierrc +0 -7
- package/.travis.yml +0 -27
- package/BREAKING_CHANGES.md +0 -35
- package/deploy.js +0 -8
- package/exports/browser/browser-D-r0O9Qn.js +0 -53
- package/exports/browser/browser-DvU1xNFS.js +0 -147
- package/exports/browser/browser-_hiyXwPp.js +0 -147
- package/exports/browser/client-86Tnax66.js +0 -2224
- package/exports/browser/client-BFxJts6j.js +0 -2224
- package/exports/browser/client-BVhUamQG.js +0 -2200
- package/exports/browser/client-BzvWzzLv.js +0 -2224
- package/exports/browser/client-C9GBz2Tf.js +0 -2224
- package/exports/browser/client-CQzuu6At.js +0 -2224
- package/exports/browser/client-CVkFejdx.js +0 -2224
- package/exports/browser/client-Cj_UUO8W.js +0 -2224
- package/exports/browser/client-CrGaeBjj.js +0 -2224
- package/exports/browser/client-DDC8C510.js +0 -2224
- package/exports/browser/client-ERocgKET.js +0 -2224
- package/exports/browser/client-a8rws3cu.js +0 -2224
- package/exports/browser/identity-CYVPDmkf.js +0 -8402
- package/exports/browser/identity-ChyyZSve.js +0 -8397
- package/exports/browser/index-BD0Anx7_.js +0 -37
- package/exports/browser/index-Bxr5Iztg.js +0 -37
- package/exports/browser/messages-BFCmg6Vb.js +0 -212
- package/exports/browser/messages-BHIC2mQu.js +0 -212
- package/exports/browser/messages-CimzJ4wr.js +0 -210
- package/exports/browser/messages-Cjoa_LGO.js +0 -210
- package/exports/browser/messages-D7JxkkDW.js +0 -212
- package/exports/browser/messages-DH0tgNM9.js +0 -207
- package/exports/browser/messages-DVHcNvs_.js +0 -212
- package/exports/browser/messages-DqL_g2qw.js +0 -210
- package/exports/browser/messages-UKnuelZ7.js +0 -207
- package/exports/browser/messages-XgZZUMma.js +0 -212
- package/exports/browser/messages-kfFyctPL.js +0 -210
- package/exports/browser/messages-psROnfRi.js +0 -212
- package/exports/browser/peernet-B9VL4Xwe.js +0 -9522
- package/exports/browser/peernet-C-HNdrcd.js +0 -9528
- package/exports/browser/peernet-C4rOvyyU.js +0 -9661
- package/exports/browser/peernet-Ca1iM2tN.js +0 -9336
- package/exports/browser/peernet-CbI0-jFn.js +0 -9651
- package/exports/browser/peernet-CtHScDyh.js +0 -9524
- package/exports/browser/peernet-DfuxUK3u.js +0 -9525
- package/exports/browser/peernet-DiEcNDLD.js +0 -9655
- package/exports/browser/peernet-DtuRXOzP.js +0 -9653
- package/exports/browser/peernet-r_9IyzTe.js +0 -9336
- package/exports/browser/peernet-rcD5XL1y.js +0 -9590
- package/exports/browser/peernet-rw8HJGOz.js +0 -9597
- package/exports/messages-CHKVVTVE.js +0 -204
- package/exports/messages-DaUtFfBh.js +0 -207
- package/index.html +0 -19
- package/rollup.config.js +0 -143
- package/src/dht/dht.ts +0 -142
- package/src/discovery/peer-discovery.js +0 -75
- package/src/errors/errors.js +0 -12
- package/src/file-transfer.ts +0 -194
- package/src/handlers/data.js +0 -15
- package/src/handlers/message.js +0 -34
- package/src/identity.ts +0 -172
- package/src/messages/chat.ts +0 -13
- package/src/messages/data-response.ts +0 -13
- package/src/messages/data.ts +0 -17
- package/src/messages/dht-response.ts +0 -13
- package/src/messages/dht.ts +0 -21
- package/src/messages/file-link.ts +0 -17
- package/src/messages/file.ts +0 -17
- package/src/messages/peer-response.ts +0 -13
- package/src/messages/peer.ts +0 -13
- package/src/messages/peernet.ts +0 -13
- package/src/messages/ps.ts +0 -13
- package/src/messages/request.ts +0 -13
- package/src/messages/response.ts +0 -13
- package/src/messages.js +0 -12
- package/src/peer-info.js +0 -9
- package/src/peernet.ts +0 -1113
- package/src/prompts/password/browser.js +0 -1
- package/src/prompts/password/node.js +0 -6
- package/src/prompts/password.js +0 -1
- package/src/proto/chat-message.proto.js +0 -6
- package/src/proto/data-response.proto.js +0 -4
- package/src/proto/data.proto.js +0 -4
- package/src/proto/dht-response.proto.js +0 -4
- package/src/proto/dht.proto.js +0 -4
- package/src/proto/file-link.proto.js +0 -5
- package/src/proto/file.proto.js +0 -10
- package/src/proto/peer-response.proto.js +0 -3
- package/src/proto/peer.proto.js +0 -3
- package/src/proto/peernet.proto.js +0 -7
- package/src/proto/ps.proto.js +0 -4
- package/src/proto/request.proto.js +0 -4
- package/src/proto/response.proto.js +0 -3
- package/src/types.ts +0 -25
- package/src/utils/utils.js +0 -77
- package/test/all.js +0 -5
- package/test/client.js +0 -15
- package/test/codec.js +0 -56
- package/test/file-transfer.test.js +0 -81
- package/test/fresh-empty-store.js +0 -15
- package/test/hash.js +0 -14
- package/test/index.js +0 -6
- package/test/lastBlock.js +0 -7
- package/test/messages.js +0 -26
- package/test/peer-data-exchange.test.js +0 -52
- package/test/peer-file-exchange.test.js +0 -78
- package/test/peernet.js +0 -17
- package/test/peernet.test.js +0 -477
- package/test-output.log +0 -84
- package/test.js +0 -63
- package/test2.js +0 -13
- package/test3.js +0 -16
- package/test4.js +0 -7
- 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
|
-
|
|
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-
|
|
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-
|
|
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,
|
|
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 };
|