@leofcoin/peernet 2.0.0 → 3.0.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/docs/automatic-transport.md +37 -0
- package/exports/browser/{client-CAC7RVMf.js → client-C_pBoi1g.js} +252 -76
- package/exports/browser/{messages-Dw86z0zq.js → messages-BGHaihSi.js} +1 -1
- package/exports/browser/{peernet-B2jRhqGI.js → peernet-Db43ag2D.js} +2 -2
- package/exports/browser/peernet.js +1 -1
- package/exports/store.js +0 -3
- package/package.json +8 -4
- 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
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Peernet 3: automatic relay discovery
|
|
2
|
+
|
|
3
|
+
Peernet 3 adopts the published `@netpeer/swarm@0.10.0` transport. Normal nodes keep
|
|
4
|
+
the same startup API and configured discovery stars; no WebTransport URL, relay
|
|
5
|
+
secret, public certificate or extra relay process is required on ordinary peers.
|
|
6
|
+
Explicit `transport` restrictions/overrides remain supported.
|
|
7
|
+
|
|
8
|
+
Release verification builds both Node and browser bundles, keeps native transport
|
|
9
|
+
loaders out of browser resolution, cleans stale generated chunks, and tests a
|
|
10
|
+
packed artifact in a clean npm installation with registry-only dependencies.
|
|
11
|
+
|
|
12
|
+
This is a major release because the supported Node runtime moves from 19+ to
|
|
13
|
+
**24.15+**, matching the new native backend and Leofcoin launcher. The canonical
|
|
14
|
+
codec-first message format from peernet 2 is unchanged. This release does not
|
|
15
|
+
regenerate genesis, change allocations, replace identities, or delete stores.
|
|
16
|
+
|
|
17
|
+
## Operators
|
|
18
|
+
|
|
19
|
+
Upgrade discovery infrastructure and peer software together for the new fallback
|
|
20
|
+
handshakes. Public stars running swarm 0.10 can host their relay within the star
|
|
21
|
+
process and advertise it only after QUIC is listening. Existing DNS, trusted TLS
|
|
22
|
+
and UDP firewall configuration are infrastructure prerequisites, not client setup.
|
|
23
|
+
|
|
24
|
+
WebRTC remains the default direct transport. A ready relay is discovered from the
|
|
25
|
+
configured star and used on fallback, with tickets requested over that same star
|
|
26
|
+
connection. Circuit fallback also requires an actual reciprocal handshake before
|
|
27
|
+
reporting a peer connected; larger circuit payloads use shared PeerCore chunking.
|
|
28
|
+
|
|
29
|
+
Transport connectivity is not proof of chain synchronization or finality. Check
|
|
30
|
+
real block/state synchronization and finalized transactions after public rollout.
|
|
31
|
+
|
|
32
|
+
## Leofcoin release order
|
|
33
|
+
|
|
34
|
+
Publish peernet 3 only after review and build/tests. Then update chain's peernet
|
|
35
|
+
minimum to `^3.0.0` using the actual npm registry metadata and regenerate the
|
|
36
|
+
monorepo lockfile. Rebuild and verify a standalone launcher install before
|
|
37
|
+
publishing chain/launch-chain. Do not commit local tarball or Git overrides.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as LittlePubSub, d as deflate_1, i as inflate_1, c as createDebugger } from './peernet-
|
|
1
|
+
import { L as LittlePubSub, d as deflate_1, i as inflate_1, c as createDebugger } from './peernet-Db43ag2D.js';
|
|
2
2
|
import './identity-Cf8KKnho.js';
|
|
3
3
|
import 'crypto';
|
|
4
4
|
import './value-C3vAp-wb.js';
|
|
@@ -819,8 +819,61 @@ class WebRTCPeer {
|
|
|
819
819
|
}
|
|
820
820
|
}
|
|
821
821
|
|
|
822
|
-
|
|
822
|
+
// WebTransport streams carry bytes, not application message boundaries.
|
|
823
|
+
const MAX_FRAME_BYTES = 64 * 1024;
|
|
824
|
+
const encodeFrame = (payload) => {
|
|
825
|
+
if (!(payload instanceof Uint8Array) || !payload.length || payload.length > MAX_FRAME_BYTES) {
|
|
826
|
+
throw new Error('invalid transport frame length');
|
|
827
|
+
}
|
|
828
|
+
const frame = new Uint8Array(payload.length + 4);
|
|
829
|
+
new DataView(frame.buffer).setUint32(0, payload.length);
|
|
830
|
+
frame.set(payload, 4);
|
|
831
|
+
return frame;
|
|
832
|
+
};
|
|
833
|
+
class FrameReader {
|
|
834
|
+
constructor(reader) { this.reader = reader; this.chunk = new Uint8Array(); this.offset = 0; }
|
|
835
|
+
async exact(length, allowEnd = false) {
|
|
836
|
+
const result = new Uint8Array(length);
|
|
837
|
+
let written = 0;
|
|
838
|
+
while (written < length) {
|
|
839
|
+
if (this.offset === this.chunk.length) {
|
|
840
|
+
const { done, value } = await this.reader.read();
|
|
841
|
+
if (done) {
|
|
842
|
+
if (allowEnd && written === 0)
|
|
843
|
+
return null;
|
|
844
|
+
throw new Error('truncated transport frame');
|
|
845
|
+
}
|
|
846
|
+
this.chunk = value;
|
|
847
|
+
this.offset = 0;
|
|
848
|
+
if (!value?.length)
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
const count = Math.min(length - written, this.chunk.length - this.offset);
|
|
852
|
+
result.set(this.chunk.subarray(this.offset, this.offset + count), written);
|
|
853
|
+
this.offset += count;
|
|
854
|
+
written += count;
|
|
855
|
+
}
|
|
856
|
+
return result;
|
|
857
|
+
}
|
|
858
|
+
async read() {
|
|
859
|
+
const header = await this.exact(4, true);
|
|
860
|
+
if (!header)
|
|
861
|
+
return null;
|
|
862
|
+
const length = new DataView(header.buffer).getUint32(0);
|
|
863
|
+
if (!length || length > MAX_FRAME_BYTES)
|
|
864
|
+
throw new Error('invalid transport frame length');
|
|
865
|
+
return this.exact(length);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
const jsonBytes = (value) => new TextEncoder().encode(JSON.stringify(value));
|
|
869
|
+
const parseJson = (bytes) => JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
870
|
+
|
|
871
|
+
const getWebTransportCtor = async () => {
|
|
823
872
|
const candidate = globalThis;
|
|
873
|
+
if (!candidate.WebTransport && candidate.process?.versions?.node) {
|
|
874
|
+
const { WebTransport } = await import('@fails-components/webtransport');
|
|
875
|
+
return WebTransport;
|
|
876
|
+
}
|
|
824
877
|
if (!candidate.WebTransport) {
|
|
825
878
|
throw new Error('WebTransport is not available in this runtime');
|
|
826
879
|
}
|
|
@@ -854,7 +907,12 @@ class WebTransportPeer {
|
|
|
854
907
|
kind: 'webtransport-bidi',
|
|
855
908
|
isConnected: () => this.connected,
|
|
856
909
|
sendRaw: (payload) => {
|
|
857
|
-
void this.#sendRaw(payload)
|
|
910
|
+
void this.#sendRaw(payload).catch(error => {
|
|
911
|
+
if (!this.#destroyed) {
|
|
912
|
+
this.#emit('error', error);
|
|
913
|
+
this.destroy();
|
|
914
|
+
}
|
|
915
|
+
});
|
|
858
916
|
}
|
|
859
917
|
}, {
|
|
860
918
|
maxMessageSize: MAX_MESSAGE_SIZE,
|
|
@@ -865,7 +923,7 @@ class WebTransportPeer {
|
|
|
865
923
|
}
|
|
866
924
|
});
|
|
867
925
|
queueMicrotask(() => {
|
|
868
|
-
void this.#open(options
|
|
926
|
+
void this.#open(options);
|
|
869
927
|
});
|
|
870
928
|
}
|
|
871
929
|
#createMessageId() {
|
|
@@ -892,41 +950,55 @@ class WebTransportPeer {
|
|
|
892
950
|
for (const handler of listeners)
|
|
893
951
|
handler(...args);
|
|
894
952
|
}
|
|
895
|
-
async #open(
|
|
953
|
+
async #open(options) {
|
|
896
954
|
try {
|
|
897
|
-
const
|
|
898
|
-
|
|
955
|
+
const ticket = await options.getTicket();
|
|
956
|
+
if (this.#destroyed)
|
|
957
|
+
return;
|
|
958
|
+
const WebTransportCtor = await getWebTransportCtor();
|
|
959
|
+
const session = new WebTransportCtor(options.url);
|
|
899
960
|
this.#session = session;
|
|
961
|
+
void session.closed.then(() => this.destroy(), error => {
|
|
962
|
+
if (!this.#destroyed) {
|
|
963
|
+
this.#emit('error', error);
|
|
964
|
+
this.destroy();
|
|
965
|
+
}
|
|
966
|
+
});
|
|
900
967
|
await session.ready;
|
|
968
|
+
if (this.#destroyed)
|
|
969
|
+
return;
|
|
901
970
|
const stream = (await session.createBidirectionalStream());
|
|
971
|
+
if (this.#destroyed)
|
|
972
|
+
return;
|
|
902
973
|
this.#reader = stream.readable.getReader();
|
|
903
974
|
this.#writer = stream.writable.getWriter();
|
|
975
|
+
const frames = new FrameReader(this.#reader);
|
|
976
|
+
await this.#writer.write(encodeFrame(jsonBytes({ protocol: 'swarm-relay/1', ticket })));
|
|
977
|
+
const ready = parseJson(await frames.read());
|
|
978
|
+
if (ready.protocol !== 'swarm-relay/1' || ready.peerId !== this.peerId || String(ready.version) !== this.version) {
|
|
979
|
+
throw new Error('invalid WebTransport relay handshake');
|
|
980
|
+
}
|
|
981
|
+
if (this.#destroyed)
|
|
982
|
+
return;
|
|
904
983
|
this.#connected = true;
|
|
905
984
|
this.#emit('connect');
|
|
906
|
-
void this.#readLoop();
|
|
907
|
-
void session.closed.catch((error) => {
|
|
908
|
-
if (this.#destroyed)
|
|
909
|
-
return;
|
|
910
|
-
this.#emit('error', error instanceof Error ? error : new Error(String(error)));
|
|
911
|
-
});
|
|
985
|
+
void this.#readLoop(frames);
|
|
912
986
|
}
|
|
913
987
|
catch (error) {
|
|
914
988
|
this.#emit('error', error instanceof Error ? error : new Error(String(error)));
|
|
915
989
|
this.destroy();
|
|
916
990
|
}
|
|
917
991
|
}
|
|
918
|
-
async #readLoop() {
|
|
992
|
+
async #readLoop(frames) {
|
|
919
993
|
const reader = this.#reader;
|
|
920
994
|
if (!reader)
|
|
921
995
|
return;
|
|
922
996
|
try {
|
|
923
997
|
while (!this.#destroyed) {
|
|
924
998
|
// eslint-disable-next-line no-await-in-loop
|
|
925
|
-
const
|
|
926
|
-
if (done)
|
|
927
|
-
break;
|
|
999
|
+
const value = await frames.read();
|
|
928
1000
|
if (!value)
|
|
929
|
-
|
|
1001
|
+
break;
|
|
930
1002
|
this.bw.down += value.length;
|
|
931
1003
|
this.#emit('data', value);
|
|
932
1004
|
}
|
|
@@ -942,7 +1014,7 @@ class WebTransportPeer {
|
|
|
942
1014
|
async #sendRaw(payload) {
|
|
943
1015
|
if (!this.#writer)
|
|
944
1016
|
throw new Error('WebTransport writer is not ready');
|
|
945
|
-
await this.#writer.write(payload);
|
|
1017
|
+
await this.#writer.write(encodeFrame(payload));
|
|
946
1018
|
}
|
|
947
1019
|
on(event, handler) {
|
|
948
1020
|
const listeners = this.#listeners.get(event) ?? new Set();
|
|
@@ -972,13 +1044,13 @@ class WebTransportPeer {
|
|
|
972
1044
|
this.#destroyed = true;
|
|
973
1045
|
this.#connected = false;
|
|
974
1046
|
try {
|
|
975
|
-
void this.#reader?.cancel?.();
|
|
1047
|
+
void this.#reader?.cancel?.().catch(() => { });
|
|
976
1048
|
}
|
|
977
1049
|
catch (e) { }
|
|
978
1050
|
this.#reader?.releaseLock?.();
|
|
979
1051
|
this.#reader = null;
|
|
980
1052
|
try {
|
|
981
|
-
void this.#writer?.close?.();
|
|
1053
|
+
void this.#writer?.close?.().catch(() => { });
|
|
982
1054
|
}
|
|
983
1055
|
catch (e) { }
|
|
984
1056
|
this.#writer?.releaseLock?.();
|
|
@@ -1006,6 +1078,7 @@ class CircuitPeer {
|
|
|
1006
1078
|
#destroyed = false;
|
|
1007
1079
|
#connected = false;
|
|
1008
1080
|
#sendViaCircuit;
|
|
1081
|
+
#core;
|
|
1009
1082
|
initiator = true;
|
|
1010
1083
|
peerId;
|
|
1011
1084
|
channelName;
|
|
@@ -1019,12 +1092,40 @@ class CircuitPeer {
|
|
|
1019
1092
|
this.channelName = `circuit:${options.from}:${options.to}`;
|
|
1020
1093
|
this.version = String(options.version);
|
|
1021
1094
|
this.#sendViaCircuit = options.sendViaCircuit;
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1095
|
+
this.#core = new PeerCore({
|
|
1096
|
+
kind: 'circuit',
|
|
1097
|
+
isConnected: () => this.connected,
|
|
1098
|
+
sendRaw: data => {
|
|
1099
|
+
void this.#sendViaCircuit({ id: this.#createMessageId(), data: Array.from(data) }).catch(error => {
|
|
1100
|
+
if (this.#destroyed)
|
|
1101
|
+
return;
|
|
1102
|
+
this.#emit('error', error);
|
|
1103
|
+
this.destroy();
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
}, {
|
|
1107
|
+
// JSON byte arrays expand up to four characters per byte. Keep the whole
|
|
1108
|
+
// WebSocket frame below its default 64 KiB limit, including wrapper/ID.
|
|
1109
|
+
maxMessageSize: 14_000,
|
|
1110
|
+
compressionThreshold: 0.98,
|
|
1111
|
+
getPubSub,
|
|
1112
|
+
onPayloadSend: bytes => { this.bw.up += bytes; }
|
|
1113
|
+
});
|
|
1114
|
+
queueMicrotask(async () => {
|
|
1115
|
+
try {
|
|
1116
|
+
await options.handshake();
|
|
1117
|
+
if (this.#destroyed)
|
|
1118
|
+
return;
|
|
1119
|
+
this.#connected = true;
|
|
1120
|
+
this.#emit('connect');
|
|
1121
|
+
}
|
|
1122
|
+
catch (error) {
|
|
1123
|
+
if (this.#destroyed)
|
|
1124
|
+
return;
|
|
1125
|
+
this.#emit('error', error);
|
|
1126
|
+
this.destroy();
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1028
1129
|
}
|
|
1029
1130
|
#createMessageId() {
|
|
1030
1131
|
const randomUUID = globalThis.crypto?.randomUUID;
|
|
@@ -1054,43 +1155,21 @@ class CircuitPeer {
|
|
|
1054
1155
|
// Circuit transport does not use SDP/ICE signaling.
|
|
1055
1156
|
}
|
|
1056
1157
|
send(data, id = this.#createMessageId()) {
|
|
1158
|
+
if (new TextEncoder().encode(id).length > 1024) {
|
|
1159
|
+
this.#emit('error', new Error('circuit message id exceeds 1024 bytes'));
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1057
1162
|
if (this.#destroyed) {
|
|
1058
1163
|
this.#emit('error', new Error('circuit peer is destroyed'));
|
|
1059
1164
|
return;
|
|
1060
1165
|
}
|
|
1061
|
-
this.
|
|
1062
|
-
void this.#sendViaCircuit({ id, data: Array.from(data) }).catch((error) => {
|
|
1063
|
-
this.#emit('error', error instanceof Error ? error : new Error(String(error)));
|
|
1064
|
-
});
|
|
1166
|
+
this.#core.send(data, id);
|
|
1065
1167
|
}
|
|
1066
1168
|
request(data, id = this.#createMessageId()) {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
return Promise.reject(new Error('globalThis.pubsub is not available'));
|
|
1169
|
+
if (new TextEncoder().encode(id).length > 1024) {
|
|
1170
|
+
return Promise.reject(new Error('circuit message id exceeds 1024 bytes'));
|
|
1070
1171
|
}
|
|
1071
|
-
return
|
|
1072
|
-
let timeout;
|
|
1073
|
-
let settled = false;
|
|
1074
|
-
const finish = (fn) => {
|
|
1075
|
-
if (settled)
|
|
1076
|
-
return;
|
|
1077
|
-
settled = true;
|
|
1078
|
-
clearTimeout(timeout);
|
|
1079
|
-
try {
|
|
1080
|
-
pubsub.unsubscribe(id, onResponse);
|
|
1081
|
-
}
|
|
1082
|
-
catch (e) { }
|
|
1083
|
-
fn();
|
|
1084
|
-
};
|
|
1085
|
-
const onResponse = ({ data: response }) => {
|
|
1086
|
-
finish(() => resolve(response));
|
|
1087
|
-
};
|
|
1088
|
-
timeout = setTimeout(() => {
|
|
1089
|
-
finish(() => reject(new Error(`request for ${id} timed out`)));
|
|
1090
|
-
}, 30_000);
|
|
1091
|
-
pubsub.subscribe(id, onResponse);
|
|
1092
|
-
this.send(data, id);
|
|
1093
|
-
});
|
|
1172
|
+
return this.#core.request(data, id);
|
|
1094
1173
|
}
|
|
1095
1174
|
async getNetworkStats() {
|
|
1096
1175
|
return null;
|
|
@@ -1163,6 +1242,8 @@ class Client {
|
|
|
1163
1242
|
#fallbackCircuitMethod = 'swarm:fallback:send';
|
|
1164
1243
|
#testHooks;
|
|
1165
1244
|
#webTransportUrlTemplate;
|
|
1245
|
+
#discoverWebTransport = true;
|
|
1246
|
+
#relayUrls = new WeakMap();
|
|
1166
1247
|
#circuitHandlers = new Map();
|
|
1167
1248
|
#pendingCircuitRequests = new Map();
|
|
1168
1249
|
#circuitRequestTimeoutMs = 30_000;
|
|
@@ -1201,7 +1282,9 @@ class Client {
|
|
|
1201
1282
|
? options.transport.kinds
|
|
1202
1283
|
: options.transport?.kind
|
|
1203
1284
|
? [options.transport.kind]
|
|
1204
|
-
:
|
|
1285
|
+
: (options.transport?.webtransport?.urlTemplate
|
|
1286
|
+
? ['webrtc', 'webtransport']
|
|
1287
|
+
: ['webrtc']);
|
|
1205
1288
|
this.#supportedTransports = new Set(this.#normalizeTransportKinds(configuredKinds));
|
|
1206
1289
|
if (!this.#supportedTransports.size)
|
|
1207
1290
|
this.#supportedTransports.add('webrtc');
|
|
@@ -1212,6 +1295,11 @@ class Client {
|
|
|
1212
1295
|
? 'webrtc'
|
|
1213
1296
|
: 'webtransport';
|
|
1214
1297
|
this.#webTransportUrlTemplate = options.transport?.webtransport?.urlTemplate;
|
|
1298
|
+
this.#discoverWebTransport = !options.transport?.kind && !options.transport?.kinds ||
|
|
1299
|
+
!!options.transport?.kinds?.includes('webtransport') || options.transport?.kind === 'webtransport';
|
|
1300
|
+
if (this.#discoverWebTransport && (typeof globalThis.WebTransport === 'function' || globalThis.process?.versions?.node)) {
|
|
1301
|
+
this.#supportedTransports.add('webtransport');
|
|
1302
|
+
}
|
|
1215
1303
|
const wrtcBackend = options.transport?.webrtc?.backend ||
|
|
1216
1304
|
globalThis.process?.env?.NETPEER_SWARM_WRTC_BACKEND ||
|
|
1217
1305
|
'vandeurenglenn';
|
|
@@ -1229,6 +1317,9 @@ class Client {
|
|
|
1229
1317
|
if (options?.retry)
|
|
1230
1318
|
this.#retryOptions = { ...this.#retryOptions, ...options.retry };
|
|
1231
1319
|
this.#circuitHandlers.set(this.#fallbackCircuitMethod, (payload, context) => this.#handleFallbackCircuitPayload(payload, context));
|
|
1320
|
+
this.#circuitHandlers.set('swarm:fallback:hello', () => ({
|
|
1321
|
+
peerId: this.peerId, version: this.version, encoding: 'peer-core/1'
|
|
1322
|
+
}));
|
|
1232
1323
|
if (!this.#testHooks?.skipInit) {
|
|
1233
1324
|
this._init();
|
|
1234
1325
|
}
|
|
@@ -1367,6 +1458,30 @@ class Client {
|
|
|
1367
1458
|
const onServerData = (message) => {
|
|
1368
1459
|
globalThis.pubsub.publish('server:data', message);
|
|
1369
1460
|
};
|
|
1461
|
+
const onRelayReady = (message) => {
|
|
1462
|
+
if (!this.#discoverWebTransport || message?.protocol !== 'swarm-relay/1' || typeof message.url !== 'string')
|
|
1463
|
+
return;
|
|
1464
|
+
let url;
|
|
1465
|
+
try {
|
|
1466
|
+
url = new URL(message.url);
|
|
1467
|
+
}
|
|
1468
|
+
catch {
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.hash)
|
|
1472
|
+
return;
|
|
1473
|
+
if (this.#relayUrls.get(starConnection) === url.href)
|
|
1474
|
+
return;
|
|
1475
|
+
this.#relayUrls.set(starConnection, url.href);
|
|
1476
|
+
this.#supportedTransports.add('webtransport');
|
|
1477
|
+
// Update capabilities on this same authenticated star connection. The
|
|
1478
|
+
// server sends relay metadata before its peer snapshot, so fallback can
|
|
1479
|
+
// resolve a real endpoint without users configuring transport URLs.
|
|
1480
|
+
starConnection.send({ url: 'join', params: {
|
|
1481
|
+
version: this.version, peerId: this.peerId,
|
|
1482
|
+
transport: { kind: this.#preferredTransportKind, kinds: [...this.#supportedTransports] }
|
|
1483
|
+
} });
|
|
1484
|
+
};
|
|
1370
1485
|
starConnection.pubsub.subscribe('peer:joined', onPeerJoined);
|
|
1371
1486
|
starConnection.pubsub.subscribe('peer:left', onPeerLeft);
|
|
1372
1487
|
starConnection.pubsub.subscribe('star:joined', onStarJoined);
|
|
@@ -1374,6 +1489,7 @@ class Client {
|
|
|
1374
1489
|
starConnection.pubsub.subscribe('signal', onSignal);
|
|
1375
1490
|
starConnection.pubsub.subscribe('circuit', onCircuit);
|
|
1376
1491
|
starConnection.pubsub.subscribe('server:data', onServerData);
|
|
1492
|
+
starConnection.pubsub.subscribe('relay:ready', onRelayReady);
|
|
1377
1493
|
this.#starListeners[starId] = [
|
|
1378
1494
|
{ topic: 'peer:joined', handler: onPeerJoined },
|
|
1379
1495
|
{ topic: 'peer:left', handler: onPeerLeft },
|
|
@@ -1381,7 +1497,8 @@ class Client {
|
|
|
1381
1497
|
{ topic: 'star:left', handler: onStarLeft },
|
|
1382
1498
|
{ topic: 'signal', handler: onSignal },
|
|
1383
1499
|
{ topic: 'circuit', handler: onCircuit },
|
|
1384
|
-
{ topic: 'server:data', handler: onServerData }
|
|
1500
|
+
{ topic: 'server:data', handler: onServerData },
|
|
1501
|
+
{ topic: 'relay:ready', handler: onRelayReady }
|
|
1385
1502
|
];
|
|
1386
1503
|
}
|
|
1387
1504
|
#starJoined = (id) => {
|
|
@@ -1587,12 +1704,12 @@ class Client {
|
|
|
1587
1704
|
peer = this.#createRTCPeerConnection(peerId, attempt.star, attempt.version, attempt.initiator);
|
|
1588
1705
|
}
|
|
1589
1706
|
else if (transportKind === 'webtransport') {
|
|
1590
|
-
const url = this.#resolveWebTransportUrl(peerId);
|
|
1707
|
+
const url = this.#resolveWebTransportUrl(peerId, attempt.star);
|
|
1591
1708
|
if (!url) {
|
|
1592
1709
|
this.#advanceTransportAttempt(peerId);
|
|
1593
1710
|
return;
|
|
1594
1711
|
}
|
|
1595
|
-
peer = this.#createWebTransportConnection(peerId, url, attempt.version);
|
|
1712
|
+
peer = this.#createWebTransportConnection(peerId, url, attempt.version, attempt.star);
|
|
1596
1713
|
}
|
|
1597
1714
|
else {
|
|
1598
1715
|
peer = this.#createCircuitConnection(peerId, attempt.version);
|
|
@@ -1639,7 +1756,25 @@ class Client {
|
|
|
1639
1756
|
if (!peer) {
|
|
1640
1757
|
peer = this.#createCircuitConnection(context.from, this.version);
|
|
1641
1758
|
}
|
|
1642
|
-
|
|
1759
|
+
if (!peer)
|
|
1760
|
+
throw new Error('circuit peer unavailable');
|
|
1761
|
+
if (!peer.connected) {
|
|
1762
|
+
const waitingPeer = peer;
|
|
1763
|
+
await new Promise((resolve, reject) => {
|
|
1764
|
+
const finish = (error) => {
|
|
1765
|
+
clearTimeout(timer);
|
|
1766
|
+
waitingPeer.off('connect', onConnect);
|
|
1767
|
+
waitingPeer.off('close', onClose);
|
|
1768
|
+
error ? reject(error) : resolve();
|
|
1769
|
+
};
|
|
1770
|
+
const onConnect = () => finish();
|
|
1771
|
+
const onClose = () => finish(new Error('circuit peer closed during handshake'));
|
|
1772
|
+
const timer = setTimeout(() => finish(new Error('circuit handshake timed out')), this.#transportConnectTimeoutMs);
|
|
1773
|
+
waitingPeer.on('connect', onConnect);
|
|
1774
|
+
waitingPeer.on('close', onClose);
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
this.#peerData(peer, decoded.data);
|
|
1643
1778
|
return { ok: true };
|
|
1644
1779
|
}
|
|
1645
1780
|
#createCircuitId() {
|
|
@@ -1697,15 +1832,22 @@ class Client {
|
|
|
1697
1832
|
this.#pendingCircuitRequests.delete(id);
|
|
1698
1833
|
reject(new Error(`circuit request for ${id} timed out`));
|
|
1699
1834
|
}, timeoutMs);
|
|
1700
|
-
this.#pendingCircuitRequests.set(id, { resolve, reject, timeout });
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1835
|
+
this.#pendingCircuitRequests.set(id, { resolve, reject, timeout, peerId, starConnection });
|
|
1836
|
+
try {
|
|
1837
|
+
this.#sendCircuit(starConnection, {
|
|
1838
|
+
to: peerId,
|
|
1839
|
+
from: this.peerId,
|
|
1840
|
+
id,
|
|
1841
|
+
phase: 'request',
|
|
1842
|
+
method,
|
|
1843
|
+
data: this.#encodeCircuitData(data)
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
catch (error) {
|
|
1847
|
+
clearTimeout(timeout);
|
|
1848
|
+
this.#pendingCircuitRequests.delete(id);
|
|
1849
|
+
reject(error);
|
|
1850
|
+
}
|
|
1709
1851
|
});
|
|
1710
1852
|
}
|
|
1711
1853
|
#inComingCircuit = async ({ from, to, id, phase, method, data, error }, starConnection) => {
|
|
@@ -1715,6 +1857,10 @@ class Client {
|
|
|
1715
1857
|
const pending = this.#pendingCircuitRequests.get(id);
|
|
1716
1858
|
if (!pending)
|
|
1717
1859
|
return;
|
|
1860
|
+
if (pending.starConnection !== starConnection)
|
|
1861
|
+
return;
|
|
1862
|
+
if (from !== pending.peerId && !(from === 'server' && error))
|
|
1863
|
+
return;
|
|
1718
1864
|
clearTimeout(pending.timeout);
|
|
1719
1865
|
this.#pendingCircuitRequests.delete(id);
|
|
1720
1866
|
if (error) {
|
|
@@ -1788,17 +1934,16 @@ class Client {
|
|
|
1788
1934
|
connectWebTransport(peerId, url) {
|
|
1789
1935
|
this.#createWebTransportConnection(peerId, url, this.version);
|
|
1790
1936
|
}
|
|
1791
|
-
#resolveWebTransportUrl(peerId) {
|
|
1792
|
-
const urlTemplate = this.#webTransportUrlTemplate;
|
|
1937
|
+
#resolveWebTransportUrl(peerId, starConnection) {
|
|
1938
|
+
const urlTemplate = this.#webTransportUrlTemplate || (starConnection && this.#relayUrls.get(starConnection));
|
|
1793
1939
|
if (!urlTemplate) {
|
|
1794
|
-
console.warn('WebTransport requires transport.webtransport.urlTemplate in client options');
|
|
1795
1940
|
return null;
|
|
1796
1941
|
}
|
|
1797
1942
|
return urlTemplate
|
|
1798
1943
|
.replace('{peerId}', encodeURIComponent(peerId))
|
|
1799
1944
|
.replace('{selfId}', encodeURIComponent(this.peerId));
|
|
1800
1945
|
}
|
|
1801
|
-
#createWebTransportConnection(peerId, url, version) {
|
|
1946
|
+
#createWebTransportConnection(peerId, url, version, starConnection) {
|
|
1802
1947
|
if (this.#connections[peerId]) {
|
|
1803
1948
|
debug(`peer ${peerId} already connected`);
|
|
1804
1949
|
return null;
|
|
@@ -1823,7 +1968,27 @@ class Client {
|
|
|
1823
1968
|
from: this.peerId,
|
|
1824
1969
|
to: peerId,
|
|
1825
1970
|
version,
|
|
1826
|
-
url
|
|
1971
|
+
url,
|
|
1972
|
+
getTicket: async () => {
|
|
1973
|
+
const star = starConnection || this.#pickStarConnection();
|
|
1974
|
+
if (!star)
|
|
1975
|
+
throw new Error('no connected star available for WebTransport ticket');
|
|
1976
|
+
let timer;
|
|
1977
|
+
try {
|
|
1978
|
+
const response = await Promise.race([
|
|
1979
|
+
star.request({ url: 'webtransport-ticket', params: { from: this.peerId, to: peerId } }),
|
|
1980
|
+
new Promise((_, reject) => {
|
|
1981
|
+
timer = setTimeout(() => reject(new Error('WebTransport ticket request timed out')), this.#transportConnectTimeoutMs);
|
|
1982
|
+
})
|
|
1983
|
+
]);
|
|
1984
|
+
if (typeof response?.ticket !== 'string')
|
|
1985
|
+
throw new Error('star did not authorize WebTransport relay');
|
|
1986
|
+
return response.ticket;
|
|
1987
|
+
}
|
|
1988
|
+
finally {
|
|
1989
|
+
clearTimeout(timer);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1827
1992
|
});
|
|
1828
1993
|
peer.on('connect', () => this.#peerConnect(peer));
|
|
1829
1994
|
peer.on('close', () => this.#peerClose(peer));
|
|
@@ -1857,6 +2022,12 @@ class Client {
|
|
|
1857
2022
|
from: this.peerId,
|
|
1858
2023
|
to: peerId,
|
|
1859
2024
|
version,
|
|
2025
|
+
handshake: async () => {
|
|
2026
|
+
const response = await this.circuitRequest(peerId, 'swarm:fallback:hello', undefined, this.#transportConnectTimeoutMs);
|
|
2027
|
+
if (response?.peerId !== peerId || String(response?.version) !== String(version) || response.encoding !== 'peer-core/1') {
|
|
2028
|
+
throw new Error('invalid circuit handshake response');
|
|
2029
|
+
}
|
|
2030
|
+
},
|
|
1860
2031
|
sendViaCircuit: (payload) => this.circuitRequest(peerId, this.#fallbackCircuitMethod, payload)
|
|
1861
2032
|
});
|
|
1862
2033
|
peer.on('connect', () => this.#peerConnect(peer));
|
|
@@ -2173,6 +2344,11 @@ class Client {
|
|
|
2173
2344
|
peer.destroy();
|
|
2174
2345
|
};
|
|
2175
2346
|
async close() {
|
|
2347
|
+
for (const pending of this.#pendingCircuitRequests.values()) {
|
|
2348
|
+
clearTimeout(pending.timeout);
|
|
2349
|
+
pending.reject(new Error('client closed'));
|
|
2350
|
+
}
|
|
2351
|
+
this.#pendingCircuitRequests.clear();
|
|
2176
2352
|
for (const peerId of this.#peerTransportAttempts.keys()) {
|
|
2177
2353
|
this.#clearTransportAttempt(peerId);
|
|
2178
2354
|
}
|
|
@@ -8840,7 +8840,7 @@ class Peernet {
|
|
|
8840
8840
|
await getAddress();
|
|
8841
8841
|
this.storePrefix = options.storePrefix;
|
|
8842
8842
|
this.root = options.root;
|
|
8843
|
-
const { RequestMessage, ResponseMessage, PeerMessage, PeerMessageResponse, PeernetMessage, DHTMessage, DHTMessageResponse, DataMessage, DataMessageResponse, PsMessage, ChatMessage, PeernetFile } = await import(/* webpackChunkName: "messages" */ './messages-
|
|
8843
|
+
const { RequestMessage, ResponseMessage, PeerMessage, PeerMessageResponse, PeernetMessage, DHTMessage, DHTMessageResponse, DataMessage, DataMessageResponse, PsMessage, ChatMessage, PeernetFile } = await import(/* webpackChunkName: "messages" */ './messages-BGHaihSi.js');
|
|
8844
8844
|
/**
|
|
8845
8845
|
* proto Object containing protos
|
|
8846
8846
|
* @type {Object}
|
|
@@ -8887,7 +8887,7 @@ class Peernet {
|
|
|
8887
8887
|
if (this.#starting || this.#started)
|
|
8888
8888
|
return;
|
|
8889
8889
|
this.#starting = true;
|
|
8890
|
-
const importee = await import('./client-
|
|
8890
|
+
const importee = await import('./client-C_pBoi1g.js');
|
|
8891
8891
|
/**
|
|
8892
8892
|
* @access public
|
|
8893
8893
|
* @type {PeernetClient}
|
package/exports/store.js
CHANGED
|
@@ -102,9 +102,6 @@ class Store {
|
|
|
102
102
|
this.root = await init(root, homedir);
|
|
103
103
|
this.version = version;
|
|
104
104
|
this.db = new ClassicLevel(join(this.root, this.name), { valueEncoding: 'view' });
|
|
105
|
-
if (typeof this.db.open === 'function') {
|
|
106
|
-
await this.db.open();
|
|
107
|
-
}
|
|
108
105
|
}
|
|
109
106
|
toKeyPath(key) {
|
|
110
107
|
if (!key.isKeyPath())
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leofcoin/peernet",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -91,12 +91,17 @@
|
|
|
91
91
|
},
|
|
92
92
|
"type": "module",
|
|
93
93
|
"engines": {
|
|
94
|
-
"node": ">=
|
|
94
|
+
"node": ">=24.15.0"
|
|
95
95
|
},
|
|
96
|
+
"files": [
|
|
97
|
+
"exports",
|
|
98
|
+
"docs/automatic-transport.md"
|
|
99
|
+
],
|
|
96
100
|
"scripts": {
|
|
97
101
|
"build": "rollup -c",
|
|
98
102
|
"watch": "rollup -c -w",
|
|
99
103
|
"test": "node --test test/peernet.test.js test/file-transfer.test.js",
|
|
104
|
+
"test:package": "node test/standalone-install.js",
|
|
100
105
|
"server": "discovery-swarm-webrtc --port=4000",
|
|
101
106
|
"demo": "jsproject --serve ./ --port 6868"
|
|
102
107
|
},
|
|
@@ -112,7 +117,7 @@
|
|
|
112
117
|
"@leofcoin/multi-wallet": "^3.1.11",
|
|
113
118
|
"@leofcoin/storage": "^3.5.38",
|
|
114
119
|
"@mapbox/node-pre-gyp": "^2.0.3",
|
|
115
|
-
"@netpeer/swarm": "^0.
|
|
120
|
+
"@netpeer/swarm": "^0.10.0",
|
|
116
121
|
"@vandeurenglenn/base58": "^1.1.9",
|
|
117
122
|
"@vandeurenglenn/debug": "^1.4.0",
|
|
118
123
|
"@vandeurenglenn/little-pubsub": "^1.5.2",
|
|
@@ -129,7 +134,6 @@
|
|
|
129
134
|
"@rollup/plugin-wasm": "^6.2.2",
|
|
130
135
|
"@types/node": "^25.9.1",
|
|
131
136
|
"@types/qrcode": "^1.5.6",
|
|
132
|
-
"rimraf": "^5.0.10",
|
|
133
137
|
"rollup": "^4.60.4",
|
|
134
138
|
"tslib": "^2.8.1",
|
|
135
139
|
"typescript": "^6.0.2"
|