@muhkoo/connect 0.10.4-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/connect.d.ts CHANGED
@@ -4379,6 +4379,10 @@ declare class SpaceNamespace {
4379
4379
  private spaceCache?;
4380
4380
  /** Spaces built this session, by id — used to route offline send replays. */
4381
4381
  private readonly openSpaces;
4382
+ /** Unique id for THIS device/session — the P2P peer identity. A user's own
4383
+ * devices share a member id, so the mesh MUST distinguish them by this or a
4384
+ * device treats its siblings' signals as its own echo and never connects. */
4385
+ private readonly deviceId;
4382
4386
  constructor(deps: SpaceNamespaceDeps);
4383
4387
  /**
4384
4388
  * Create a new space: generate its keypair (public key = id), register
@@ -5392,15 +5396,20 @@ declare class PeerExchange implements PeerBlockSource {
5392
5396
  * to Space membership. The sender (`from`) is server-stamped and can't be
5393
5397
  * spoofed; the payload is opaque to the server.
5394
5398
  *
5395
- * Envelopes carry a `to` (a member id, or `"*"` for discovery) so the
5396
- * accelerator can optionally unicast peer-pair signaling instead of broadcasting
5397
- * (a small, backward-compatible `pub` enhancement); the client filters by `to`
5398
- * regardless.
5399
+ * Peers are identified by a unique **peer id** (per device/connection), carried
5400
+ * in the envelope's `from`/`to` — NOT by the server-stamped member id. This is
5401
+ * essential: one user's own devices share a member id, so the server's `from`
5402
+ * can't tell them apart (and would make each treat the others' signals as its
5403
+ * own echo). We therefore route entirely on the envelope peer ids and broadcast
5404
+ * to the whole Space, filtering client-side. The server `from` still guarantees
5405
+ * the sender is an authenticated Space member.
5399
5406
  */
5400
5407
  declare const P2P_SUBJECT = "__p2p__";
5401
5408
  type SignalKind = "hello" | "bye" | "offer" | "answer" | "ice";
5402
5409
  interface SignalEnvelope {
5403
- /** Target member id, or `"*"` to broadcast (discovery / departure). */
5410
+ /** Sender's unique PEER id (per device/connection). */
5411
+ from: string;
5412
+ /** Target peer id, or `"*"` to broadcast (discovery / departure). */
5404
5413
  to: string;
5405
5414
  kind: SignalKind;
5406
5415
  data?: unknown;
@@ -5424,7 +5433,9 @@ declare class SpaceSignaler {
5424
5433
  broadcast(kind: SignalKind, data?: unknown): void;
5425
5434
  /**
5426
5435
  * Subscribe to inbound signals addressed to us (or broadcast). Skips our own
5427
- * echoes. Returns an unsubscribe function.
5436
+ * echoes. Returns an unsubscribe function. Routing is entirely by the envelope
5437
+ * peer id (`env.from`/`env.to`), so a user's own devices — which share the
5438
+ * server-stamped `e.from` — are correctly distinguished.
5428
5439
  */
5429
5440
  onSignal(cb: (from: string, kind: SignalKind, data: unknown) => void): () => void;
5430
5441
  }
@@ -2976,7 +2976,7 @@ function base64UrlDecode(s) {
2976
2976
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
2977
2977
  * `package.json` `version`.
2978
2978
  */
2979
- const VERSION = "0.10.4-alpha.0";
2979
+ const VERSION = "0.10.5-alpha.0";
2980
2980
 
2981
2981
  /**
2982
2982
  * `HttpClient` — the one place the unified {@link Client} talks to the
@@ -8470,10 +8470,13 @@ class PeerExchange {
8470
8470
  * to Space membership. The sender (`from`) is server-stamped and can't be
8471
8471
  * spoofed; the payload is opaque to the server.
8472
8472
  *
8473
- * Envelopes carry a `to` (a member id, or `"*"` for discovery) so the
8474
- * accelerator can optionally unicast peer-pair signaling instead of broadcasting
8475
- * (a small, backward-compatible `pub` enhancement); the client filters by `to`
8476
- * regardless.
8473
+ * Peers are identified by a unique **peer id** (per device/connection), carried
8474
+ * in the envelope's `from`/`to` — NOT by the server-stamped member id. This is
8475
+ * essential: one user's own devices share a member id, so the server's `from`
8476
+ * can't tell them apart (and would make each treat the others' signals as its
8477
+ * own echo). We therefore route entirely on the envelope peer ids and broadcast
8478
+ * to the whole Space, filtering client-side. The server `from` still guarantees
8479
+ * the sender is an authenticated Space member.
8477
8480
  */
8478
8481
  const P2P_SUBJECT = "__p2p__";
8479
8482
  class SpaceSignaler {
@@ -8485,11 +8488,11 @@ class SpaceSignaler {
8485
8488
  }
8486
8489
  /** Send a signal to one peer (or `"*"`). */
8487
8490
  send(to, kind, data) {
8488
- const env = { to, kind, data };
8489
- // Ask the server to unicast for a directed target; broadcast for "*".
8490
- // `to` also stays in the envelope so a broadcast-only server still works
8491
- // (the client filters by it).
8492
- this.space.sendEphemeral(P2P_SUBJECT, env, to !== "*" ? to : undefined);
8491
+ const env = { from: this.myId, to, kind, data };
8492
+ // Always broadcast to the Space: the server can't unicast to a specific
8493
+ // DEVICE (a user's devices share a member id), so we route client-side on
8494
+ // the envelope peer id. Signaling frames are tiny.
8495
+ this.space.sendEphemeral(P2P_SUBJECT, env);
8493
8496
  }
8494
8497
  /** Announce/withdraw P2P availability to all Space members. */
8495
8498
  broadcast(kind, data) {
@@ -8497,18 +8500,22 @@ class SpaceSignaler {
8497
8500
  }
8498
8501
  /**
8499
8502
  * Subscribe to inbound signals addressed to us (or broadcast). Skips our own
8500
- * echoes. Returns an unsubscribe function.
8503
+ * echoes. Returns an unsubscribe function. Routing is entirely by the envelope
8504
+ * peer id (`env.from`/`env.to`), so a user's own devices — which share the
8505
+ * server-stamped `e.from` — are correctly distinguished.
8501
8506
  */
8502
8507
  onSignal(cb) {
8503
8508
  return this.space.onEphemeral((e) => {
8504
- if (e.subject !== P2P_SUBJECT || e.from === this.myId)
8509
+ if (e.subject !== P2P_SUBJECT)
8505
8510
  return;
8506
8511
  const env = e.data;
8507
- if (!env || typeof env.kind !== "string")
8512
+ if (!env || typeof env.kind !== "string" || typeof env.from !== "string")
8508
8513
  return;
8514
+ if (env.from === this.myId)
8515
+ return; // our own device's echo
8509
8516
  if (env.to !== this.myId && env.to !== "*")
8510
8517
  return;
8511
- cb(e.from, env.kind, env.data);
8518
+ cb(env.from, env.kind, env.data);
8512
8519
  });
8513
8520
  }
8514
8521
  }
@@ -9285,6 +9292,10 @@ class SpaceNamespace {
9285
9292
  spaceCache;
9286
9293
  /** Spaces built this session, by id — used to route offline send replays. */
9287
9294
  openSpaces = new Map();
9295
+ /** Unique id for THIS device/session — the P2P peer identity. A user's own
9296
+ * devices share a member id, so the mesh MUST distinguish them by this or a
9297
+ * device treats its siblings' signals as its own echo and never connects. */
9298
+ deviceId = `peer-${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)}`;
9288
9299
  constructor(deps) {
9289
9300
  this.deps = deps;
9290
9301
  if (deps.offline?.enabled) {
@@ -9483,7 +9494,7 @@ class SpaceNamespace {
9483
9494
  if (this.deps.p2p?.enabled && isP2pCapable()) {
9484
9495
  space.attachPeerNetwork(new PeerNetwork({
9485
9496
  space,
9486
- myId: memberId,
9497
+ myId: this.deviceId, // per-device peer id, NOT the shared member id
9487
9498
  workerFactory: this.deps.p2p.workerFactory,
9488
9499
  iceServers: this.deps.p2p.iceServers,
9489
9500
  maxPeers: this.deps.p2p.maxPeers,