@muhkoo/connect 0.10.3-alpha.0 → 0.10.5-alpha.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/dist/browser/index.js +70 -19
- package/dist/browser/index.js.map +1 -1
- package/dist/connect.d.ts +24 -6
- package/dist/server/index.js +70 -19
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/browser/index.js
CHANGED
|
@@ -5057,7 +5057,7 @@ function base64UrlDecode(s) {
|
|
|
5057
5057
|
* expect (and isn't serving a stale cached bundle). Keep this in sync with
|
|
5058
5058
|
* `package.json` `version`.
|
|
5059
5059
|
*/
|
|
5060
|
-
const VERSION = "0.10.
|
|
5060
|
+
const VERSION = "0.10.5-alpha.0";
|
|
5061
5061
|
|
|
5062
5062
|
/**
|
|
5063
5063
|
* `HttpClient` — the one place the unified {@link Client} talks to the
|
|
@@ -8739,10 +8739,13 @@ class PeerExchange {
|
|
|
8739
8739
|
* to Space membership. The sender (`from`) is server-stamped and can't be
|
|
8740
8740
|
* spoofed; the payload is opaque to the server.
|
|
8741
8741
|
*
|
|
8742
|
-
*
|
|
8743
|
-
*
|
|
8744
|
-
*
|
|
8745
|
-
*
|
|
8742
|
+
* Peers are identified by a unique **peer id** (per device/connection), carried
|
|
8743
|
+
* in the envelope's `from`/`to` — NOT by the server-stamped member id. This is
|
|
8744
|
+
* essential: one user's own devices share a member id, so the server's `from`
|
|
8745
|
+
* can't tell them apart (and would make each treat the others' signals as its
|
|
8746
|
+
* own echo). We therefore route entirely on the envelope peer ids and broadcast
|
|
8747
|
+
* to the whole Space, filtering client-side. The server `from` still guarantees
|
|
8748
|
+
* the sender is an authenticated Space member.
|
|
8746
8749
|
*/
|
|
8747
8750
|
const P2P_SUBJECT = "__p2p__";
|
|
8748
8751
|
class SpaceSignaler {
|
|
@@ -8754,11 +8757,11 @@ class SpaceSignaler {
|
|
|
8754
8757
|
}
|
|
8755
8758
|
/** Send a signal to one peer (or `"*"`). */
|
|
8756
8759
|
send(to, kind, data) {
|
|
8757
|
-
const env = { to, kind, data };
|
|
8758
|
-
//
|
|
8759
|
-
//
|
|
8760
|
-
//
|
|
8761
|
-
this.space.sendEphemeral(P2P_SUBJECT, env
|
|
8760
|
+
const env = { from: this.myId, to, kind, data };
|
|
8761
|
+
// Always broadcast to the Space: the server can't unicast to a specific
|
|
8762
|
+
// DEVICE (a user's devices share a member id), so we route client-side on
|
|
8763
|
+
// the envelope peer id. Signaling frames are tiny.
|
|
8764
|
+
this.space.sendEphemeral(P2P_SUBJECT, env);
|
|
8762
8765
|
}
|
|
8763
8766
|
/** Announce/withdraw P2P availability to all Space members. */
|
|
8764
8767
|
broadcast(kind, data) {
|
|
@@ -8766,18 +8769,22 @@ class SpaceSignaler {
|
|
|
8766
8769
|
}
|
|
8767
8770
|
/**
|
|
8768
8771
|
* Subscribe to inbound signals addressed to us (or broadcast). Skips our own
|
|
8769
|
-
* echoes. Returns an unsubscribe function.
|
|
8772
|
+
* echoes. Returns an unsubscribe function. Routing is entirely by the envelope
|
|
8773
|
+
* peer id (`env.from`/`env.to`), so a user's own devices — which share the
|
|
8774
|
+
* server-stamped `e.from` — are correctly distinguished.
|
|
8770
8775
|
*/
|
|
8771
8776
|
onSignal(cb) {
|
|
8772
8777
|
return this.space.onEphemeral((e) => {
|
|
8773
|
-
if (e.subject !== P2P_SUBJECT
|
|
8778
|
+
if (e.subject !== P2P_SUBJECT)
|
|
8774
8779
|
return;
|
|
8775
8780
|
const env = e.data;
|
|
8776
|
-
if (!env || typeof env.kind !== "string")
|
|
8781
|
+
if (!env || typeof env.kind !== "string" || typeof env.from !== "string")
|
|
8777
8782
|
return;
|
|
8783
|
+
if (env.from === this.myId)
|
|
8784
|
+
return; // our own device's echo
|
|
8778
8785
|
if (env.to !== this.myId && env.to !== "*")
|
|
8779
8786
|
return;
|
|
8780
|
-
cb(
|
|
8787
|
+
cb(env.from, env.kind, env.data);
|
|
8781
8788
|
});
|
|
8782
8789
|
}
|
|
8783
8790
|
}
|
|
@@ -8812,14 +8819,20 @@ class WebRtcTransport {
|
|
|
8812
8819
|
offSignal;
|
|
8813
8820
|
iceServers;
|
|
8814
8821
|
maxPeers;
|
|
8822
|
+
debug;
|
|
8815
8823
|
nextMsgId = 1;
|
|
8816
8824
|
constructor(signaler, myId, opts = {}) {
|
|
8817
8825
|
this.signaler = signaler;
|
|
8818
8826
|
this.myId = myId;
|
|
8819
8827
|
this.iceServers = opts.iceServers ?? DEFAULT_ICE;
|
|
8820
8828
|
this.maxPeers = opts.maxPeers ?? 8;
|
|
8829
|
+
this.debug = opts.debug ?? false;
|
|
8821
8830
|
this.offSignal = this.signaler.onSignal((from, kind, data) => this.onSignal(from, kind, data));
|
|
8822
8831
|
}
|
|
8832
|
+
log(...a) {
|
|
8833
|
+
if (this.debug)
|
|
8834
|
+
console.info("[muhkoo:p2p]", ...a);
|
|
8835
|
+
}
|
|
8823
8836
|
peers() {
|
|
8824
8837
|
return [...this.conns].filter(([, c]) => c.dc?.readyState === "open").map(([id]) => id);
|
|
8825
8838
|
}
|
|
@@ -8870,19 +8883,42 @@ class WebRtcTransport {
|
|
|
8870
8883
|
// -- internals -----------------------------------------------------------
|
|
8871
8884
|
newPc(peerId) {
|
|
8872
8885
|
const pc = new RTCPeerConnection({ iceServers: this.iceServers });
|
|
8873
|
-
const conn = { pc, reasm: new Map() };
|
|
8886
|
+
const conn = { pc, reasm: new Map(), remoteSet: false, pendingIce: [] };
|
|
8874
8887
|
this.conns.set(peerId, conn);
|
|
8888
|
+
const short = String(peerId).slice(0, 8);
|
|
8875
8889
|
pc.onicecandidate = (e) => {
|
|
8876
|
-
if (e.candidate)
|
|
8890
|
+
if (e.candidate) {
|
|
8891
|
+
this.log(`${short} local candidate ${e.candidate.type ?? "?"} ${(e.candidate.address ?? e.candidate.candidate).toString().slice(0, 40)}`);
|
|
8877
8892
|
this.signaler.send(peerId, "ice", e.candidate.toJSON());
|
|
8893
|
+
}
|
|
8878
8894
|
};
|
|
8879
8895
|
pc.onconnectionstatechange = () => {
|
|
8896
|
+
this.log(`${short} connection=${pc.connectionState}`);
|
|
8880
8897
|
if (["failed", "closed", "disconnected"].includes(pc.connectionState))
|
|
8881
8898
|
this.drop(peerId);
|
|
8882
8899
|
};
|
|
8900
|
+
if (this.debug) {
|
|
8901
|
+
pc.oniceconnectionstatechange = () => this.log(`${short} ice=${pc.iceConnectionState}`);
|
|
8902
|
+
pc.onicegatheringstatechange = () => this.log(`${short} gathering=${pc.iceGatheringState}`);
|
|
8903
|
+
}
|
|
8883
8904
|
pc.ondatachannel = (e) => this.bindChannel(peerId, conn, e.channel);
|
|
8884
8905
|
return conn;
|
|
8885
8906
|
}
|
|
8907
|
+
/** Mark a peer's remote description as set and apply any ICE candidates that
|
|
8908
|
+
* arrived before it (dropping them, the old behavior, breaks LAN dials). */
|
|
8909
|
+
async markRemoteSet(conn) {
|
|
8910
|
+
conn.remoteSet = true;
|
|
8911
|
+
const pending = conn.pendingIce;
|
|
8912
|
+
conn.pendingIce = [];
|
|
8913
|
+
for (const cand of pending) {
|
|
8914
|
+
try {
|
|
8915
|
+
await conn.pc.addIceCandidate(cand);
|
|
8916
|
+
}
|
|
8917
|
+
catch { /* stale/duplicate */ }
|
|
8918
|
+
}
|
|
8919
|
+
if (pending.length)
|
|
8920
|
+
this.log(`flushed ${pending.length} queued ICE candidate(s)`);
|
|
8921
|
+
}
|
|
8886
8922
|
async initiate(peerId) {
|
|
8887
8923
|
const conn = this.newPc(peerId);
|
|
8888
8924
|
const dc = conn.pc.createDataChannel("blocks");
|
|
@@ -8904,22 +8940,32 @@ class WebRtcTransport {
|
|
|
8904
8940
|
return; // mesh cap
|
|
8905
8941
|
const conn = this.conns.get(from) ?? this.newPc(from);
|
|
8906
8942
|
await conn.pc.setRemoteDescription(data);
|
|
8943
|
+
await this.markRemoteSet(conn);
|
|
8907
8944
|
const answer = await conn.pc.createAnswer();
|
|
8908
8945
|
await conn.pc.setLocalDescription(answer);
|
|
8909
8946
|
this.signaler.send(from, "answer", answer);
|
|
8910
8947
|
}
|
|
8911
8948
|
else if (kind === "answer") {
|
|
8912
8949
|
const conn = this.conns.get(from);
|
|
8913
|
-
if (conn)
|
|
8950
|
+
if (conn) {
|
|
8914
8951
|
await conn.pc.setRemoteDescription(data);
|
|
8952
|
+
await this.markRemoteSet(conn);
|
|
8953
|
+
}
|
|
8915
8954
|
}
|
|
8916
8955
|
else if (kind === "ice") {
|
|
8917
8956
|
const conn = this.conns.get(from);
|
|
8918
8957
|
if (conn && data) {
|
|
8958
|
+
// Queue candidates that arrive before setRemoteDescription — adding
|
|
8959
|
+
// them early throws and (previously) dropped them, which killed the
|
|
8960
|
+
// connection on fast LANs where ICE beats the SDP answer.
|
|
8961
|
+
if (!conn.remoteSet) {
|
|
8962
|
+
conn.pendingIce.push(data);
|
|
8963
|
+
return;
|
|
8964
|
+
}
|
|
8919
8965
|
try {
|
|
8920
8966
|
await conn.pc.addIceCandidate(data);
|
|
8921
8967
|
}
|
|
8922
|
-
catch { /*
|
|
8968
|
+
catch { /* stale */ }
|
|
8923
8969
|
}
|
|
8924
8970
|
}
|
|
8925
8971
|
else if (kind === "bye") {
|
|
@@ -9388,6 +9434,7 @@ class PeerNetwork {
|
|
|
9388
9434
|
new WebRtcTransport(this.signaler, opts.myId, {
|
|
9389
9435
|
iceServers: opts.iceServers,
|
|
9390
9436
|
maxPeers: opts.maxPeers,
|
|
9437
|
+
debug: opts.debug,
|
|
9391
9438
|
});
|
|
9392
9439
|
this.host = buildEngineHost(opts);
|
|
9393
9440
|
// Wire transport ⇄ engine, demuxing block (engine) from gossip (app).
|
|
@@ -9514,6 +9561,10 @@ class SpaceNamespace {
|
|
|
9514
9561
|
spaceCache;
|
|
9515
9562
|
/** Spaces built this session, by id — used to route offline send replays. */
|
|
9516
9563
|
openSpaces = new Map();
|
|
9564
|
+
/** Unique id for THIS device/session — the P2P peer identity. A user's own
|
|
9565
|
+
* devices share a member id, so the mesh MUST distinguish them by this or a
|
|
9566
|
+
* device treats its siblings' signals as its own echo and never connects. */
|
|
9567
|
+
deviceId = `peer-${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)}`;
|
|
9517
9568
|
constructor(deps) {
|
|
9518
9569
|
this.deps = deps;
|
|
9519
9570
|
if (deps.offline?.enabled) {
|
|
@@ -9712,7 +9763,7 @@ class SpaceNamespace {
|
|
|
9712
9763
|
if (this.deps.p2p?.enabled && isP2pCapable()) {
|
|
9713
9764
|
space.attachPeerNetwork(new PeerNetwork({
|
|
9714
9765
|
space,
|
|
9715
|
-
myId:
|
|
9766
|
+
myId: this.deviceId, // per-device peer id, NOT the shared member id
|
|
9716
9767
|
workerFactory: this.deps.p2p.workerFactory,
|
|
9717
9768
|
iceServers: this.deps.p2p.iceServers,
|
|
9718
9769
|
maxPeers: this.deps.p2p.maxPeers,
|