@decentnetwork/beagle 0.1.32 → 0.1.34

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.
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.31";
1
+ window.__DK_UI_VERSION="0.1.33";
2
2
  const ICON_PATHS = {
3
3
  // ---- tab bar (the four must feel like one set) ----
4
4
  users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
@@ -3858,10 +3858,44 @@ function useCallController(selfId, onCallLog) {
3858
3858
  React.useEffect(() => {
3859
3859
  activeRef.current = active;
3860
3860
  }, [active]);
3861
+ const claimTabId = React.useRef(Math.random().toString(36).slice(2));
3862
+ const claimBus = React.useRef(null);
3863
+ React.useEffect(() => {
3864
+ let ch = null;
3865
+ try {
3866
+ ch = new BroadcastChannel("dk-call-claim");
3867
+ } catch (e) {
3868
+ return void 0;
3869
+ }
3870
+ claimBus.current = ch;
3871
+ ch.onmessage = (ev) => {
3872
+ const m = ev.data || {};
3873
+ if (!m || m.tabId === claimTabId.current || m.type !== "claimed")
3874
+ return;
3875
+ const inc = incomingRef.current;
3876
+ if (inc && (inc.callId === m.callId || inc.peerId === m.peerId))
3877
+ setIncoming(null);
3878
+ };
3879
+ return () => {
3880
+ try {
3881
+ ch.close();
3882
+ } catch (e) {
3883
+ }
3884
+ claimBus.current = null;
3885
+ };
3886
+ }, []);
3887
+ const broadcastClaim = React.useCallback((inc, action) => {
3888
+ try {
3889
+ if (claimBus.current)
3890
+ claimBus.current.postMessage({ type: "claimed", tabId: claimTabId.current, callId: inc.callId, peerId: inc.peerId, action });
3891
+ } catch (e) {
3892
+ }
3893
+ }, []);
3861
3894
  const accept = React.useCallback(() => {
3862
3895
  const pending = incomingRef.current;
3863
3896
  if (!pending)
3864
3897
  return;
3898
+ broadcastClaim(pending, "accepted");
3865
3899
  setIncoming(null);
3866
3900
  setActive({ callId: pending.callId, peerId: pending.peerId, video: pending.video, direction: "incoming", state: "connecting" });
3867
3901
  waitEngine().then((eng) => {
@@ -3873,10 +3907,12 @@ function useCallController(selfId, onCallLog) {
3873
3907
  const reject = React.useCallback(() => {
3874
3908
  const eng = engineRef.current;
3875
3909
  const inc = incomingRef.current;
3910
+ if (inc)
3911
+ broadcastClaim(inc, "rejected");
3876
3912
  if (eng && inc)
3877
3913
  eng.reject(inc.callId);
3878
3914
  setIncoming(null);
3879
- }, []);
3915
+ }, [broadcastClaim]);
3880
3916
  const hangup = React.useCallback(() => {
3881
3917
  const eng = engineRef.current;
3882
3918
  const a = activeRef.current;
@@ -167,7 +167,10 @@ var PeerWebRTC = (() => {
167
167
  var DEFAULT_ICE_SERVERS = [
168
168
  { urls: "stun:stun.l.google.com:19302" }
169
169
  ];
170
- var _opts, _iceServers, _sessions, _setOutgoingVideoTrack, setOutgoingVideoTrack_fn, _renegotiate, renegotiate_fn, _handleSignal, handleSignal_fn, _onOffer, onOffer_fn, _answerRenegotiation, answerRenegotiation_fn, _onAnswer, onAnswer_fn, _onCandidate, onCandidate_fn, _onBye, onBye_fn, _onAuxiliary, onAuxiliary_fn, _createSession, createSession_fn, _wirePeerConnection, wirePeerConnection_fn, _ensureRemoteStream, ensureRemoteStream_fn, _attachLocalMedia, attachLocalMedia_fn, _flushLocalCandidates, flushLocalCandidates_fn, _drainRemoteCandidates, drainRemoteCandidates_fn, _send, send_fn, _sendByeSafe, sendByeSafe_fn, _cleanup, cleanup_fn, _setState, setState_fn, _normId, normId_fn, _toInfo, _newCallId, newCallId_fn, _log, log_fn;
170
+ var ICE_RESTART_MAX = 3;
171
+ var ICE_DISCONNECT_GRACE_MS = 4e3;
172
+ var ICE_FAILED_GRACE_MS = 15e3;
173
+ var _opts, _iceServers, _sessions, _setOutgoingVideoTrack, setOutgoingVideoTrack_fn, _renegotiate, renegotiate_fn, _restartIce, restartIce_fn, _handleSignal, handleSignal_fn, _onOffer, onOffer_fn, _answerRenegotiation, answerRenegotiation_fn, _onAnswer, onAnswer_fn, _onCandidate, onCandidate_fn, _onBye, onBye_fn, _onAuxiliary, onAuxiliary_fn, _createSession, createSession_fn, _wirePeerConnection, wirePeerConnection_fn, _ensureRemoteStream, ensureRemoteStream_fn, _attachLocalMedia, attachLocalMedia_fn, _flushLocalCandidates, flushLocalCandidates_fn, _drainRemoteCandidates, drainRemoteCandidates_fn, _send, send_fn, _sendByeSafe, sendByeSafe_fn, _cleanup, cleanup_fn, _setState, setState_fn, _normId, normId_fn, _toInfo, _newCallId, newCallId_fn, _log, log_fn;
171
174
  var CallEngine = class extends Emitter {
172
175
  constructor(opts) {
173
176
  var _a;
@@ -179,6 +182,13 @@ var PeerWebRTC = (() => {
179
182
  /** Create + send a fresh offer to renegotiate media (e.g. after adding a
180
183
  * screen-share track). The peer applies it via #onOffer's renegotiation path. */
181
184
  __privateAdd(this, _renegotiate);
185
+ /** Recover a dead transport with an ICE restart (RFC 8445 §12): a re-offer
186
+ * carrying fresh ufrag/pwd, sent over the SAME renegotiation path that
187
+ * screen-share re-offers use — any peer that answers those (web, iOS,
188
+ * Android) cooperates with no new protocol. Only the ORIGINAL CALLER
189
+ * initiates, so both ends noticing the outage can't glare offers at each
190
+ * other; the callee just answers. */
191
+ __privateAdd(this, _restartIce);
182
192
  // ---- internals -------------------------------------------------------
183
193
  __privateAdd(this, _handleSignal);
184
194
  __privateAdd(this, _onOffer);
@@ -440,6 +450,25 @@ var PeerWebRTC = (() => {
440
450
  __privateMethod(this, _log, log_fn).call(this, `renegotiate failed: ${err.message}`);
441
451
  }
442
452
  };
453
+ _restartIce = new WeakSet();
454
+ restartIce_fn = async function(session) {
455
+ if (session.closed)
456
+ return;
457
+ session.restartAttempts += 1;
458
+ __privateMethod(this, _log, log_fn).call(this, `ICE restart ${session.restartAttempts}/${ICE_RESTART_MAX} for call ${session.callId}`);
459
+ try {
460
+ const offer = await session.pc.createOffer({ iceRestart: true });
461
+ await session.pc.setLocalDescription(offer);
462
+ const options = [];
463
+ if (session.audio)
464
+ options.push("audio");
465
+ if (session.video || session.screenStream)
466
+ options.push("video");
467
+ await __privateMethod(this, _send, send_fn).call(this, session.peerId, { type: "offer", sdp: offer.sdp, options, callId: session.callId });
468
+ } catch (err) {
469
+ __privateMethod(this, _log, log_fn).call(this, `ICE restart failed: ${err.message}`);
470
+ }
471
+ };
443
472
  _handleSignal = new WeakSet();
444
473
  handleSignal_fn = function(peerId, signal) {
445
474
  switch (signal.type) {
@@ -578,6 +607,7 @@ var PeerWebRTC = (() => {
578
607
  hasReceivedSdp: false,
579
608
  localCandidateQueue: [],
580
609
  remoteCandidateQueue: [],
610
+ restartAttempts: 0,
581
611
  closed: false
582
612
  };
583
613
  __privateGet(this, _sessions).set(__privateMethod(this, _normId, normId_fn).call(this, callId), session);
@@ -616,14 +646,42 @@ var PeerWebRTC = (() => {
616
646
  switch (pc.iceConnectionState) {
617
647
  case "connected":
618
648
  case "completed":
649
+ if (session.restartTimer) {
650
+ clearTimeout(session.restartTimer);
651
+ session.restartTimer = void 0;
652
+ }
653
+ session.restartAttempts = 0;
619
654
  __privateMethod(this, _setState, setState_fn).call(this, session, "connected");
620
655
  break;
621
656
  case "disconnected":
622
657
  __privateMethod(this, _setState, setState_fn).call(this, session, "disconnected");
658
+ if (session.direction === "outgoing" && !session.restartTimer && session.restartAttempts < ICE_RESTART_MAX) {
659
+ session.restartTimer = setTimeout(() => {
660
+ session.restartTimer = void 0;
661
+ const st = pc.iceConnectionState;
662
+ if (!session.closed && (st === "disconnected" || st === "failed"))
663
+ void __privateMethod(this, _restartIce, restartIce_fn).call(this, session);
664
+ }, ICE_DISCONNECT_GRACE_MS);
665
+ }
623
666
  break;
624
667
  case "failed":
625
- __privateMethod(this, _setState, setState_fn).call(this, session, "failed");
626
- __privateMethod(this, _cleanup, cleanup_fn).call(this, session, "close", "failed");
668
+ if (session.restartAttempts < ICE_RESTART_MAX) {
669
+ __privateMethod(this, _setState, setState_fn).call(this, session, "disconnected");
670
+ if (session.direction === "outgoing")
671
+ void __privateMethod(this, _restartIce, restartIce_fn).call(this, session);
672
+ if (session.restartTimer)
673
+ clearTimeout(session.restartTimer);
674
+ session.restartTimer = setTimeout(() => {
675
+ session.restartTimer = void 0;
676
+ if (!session.closed && pc.iceConnectionState === "failed") {
677
+ __privateMethod(this, _setState, setState_fn).call(this, session, "failed");
678
+ __privateMethod(this, _cleanup, cleanup_fn).call(this, session, "close", "failed");
679
+ }
680
+ }, ICE_FAILED_GRACE_MS);
681
+ } else {
682
+ __privateMethod(this, _setState, setState_fn).call(this, session, "failed");
683
+ __privateMethod(this, _cleanup, cleanup_fn).call(this, session, "close", "failed");
684
+ }
627
685
  break;
628
686
  case "closed":
629
687
  if (!session.closed)
@@ -713,6 +771,10 @@ var PeerWebRTC = (() => {
713
771
  if (session.closed)
714
772
  return;
715
773
  session.closed = true;
774
+ if (session.restartTimer) {
775
+ clearTimeout(session.restartTimer);
776
+ session.restartTimer = void 0;
777
+ }
716
778
  try {
717
779
  session.pc.onicecandidate = null;
718
780
  session.pc.ontrack = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "Beagle — P2P chat, file transfer and calls for regular users, on the Decent Network. No admin privilege required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,7 +28,7 @@
28
28
  "@decentnetwork/chat-components": "^0.1.3",
29
29
  "@decentnetwork/lan": "^0.1.256",
30
30
  "@decentnetwork/peer": "^0.1.138",
31
- "@decentnetwork/peer-webrtc": "^0.2.11",
31
+ "@decentnetwork/peer-webrtc": "^0.2.12",
32
32
  "js-yaml": "^4.1.0",
33
33
  "yargs": "^17.7.2"
34
34
  },