react-jssip-kit 0.7.8 → 0.7.9

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/index.d.cts CHANGED
@@ -57,6 +57,11 @@ type SipConfiguration = Omit<UAConfiguration, "password" | "uri"> & {
57
57
  * Maximum allowed concurrent sessions. Additional sessions are rejected.
58
58
  */
59
59
  maxSessionCount?: number;
60
+ /**
61
+ * Delay before calling event.ready() on ICE candidates (ms).
62
+ * When set, ICE gathering can be short-circuited for slow connections.
63
+ */
64
+ iceCandidateReadyDelayMs?: number;
60
65
  };
61
66
 
62
67
  type StartOpts = {
@@ -178,6 +183,7 @@ declare class SipClient extends EventTargetEmitter<JsSIPEventMap> {
178
183
  private readonly errorHandler;
179
184
  private debugPattern?;
180
185
  private maxSessionCount;
186
+ private iceCandidateReadyDelayMs?;
181
187
  private sessionManager;
182
188
  private lifecycle;
183
189
  private micRecovery;
package/dist/index.d.ts CHANGED
@@ -57,6 +57,11 @@ type SipConfiguration = Omit<UAConfiguration, "password" | "uri"> & {
57
57
  * Maximum allowed concurrent sessions. Additional sessions are rejected.
58
58
  */
59
59
  maxSessionCount?: number;
60
+ /**
61
+ * Delay before calling event.ready() on ICE candidates (ms).
62
+ * When set, ICE gathering can be short-circuited for slow connections.
63
+ */
64
+ iceCandidateReadyDelayMs?: number;
60
65
  };
61
66
 
62
67
  type StartOpts = {
@@ -178,6 +183,7 @@ declare class SipClient extends EventTargetEmitter<JsSIPEventMap> {
178
183
  private readonly errorHandler;
179
184
  private debugPattern?;
180
185
  private maxSessionCount;
186
+ private iceCandidateReadyDelayMs?;
181
187
  private sessionManager;
182
188
  private lifecycle;
183
189
  private micRecovery;
package/dist/index.js CHANGED
@@ -468,6 +468,175 @@ function removeSessionState(state, sessionId) {
468
468
  });
469
469
  }
470
470
 
471
+ // src/jssip-lib/sip/debugLogging.ts
472
+ var describePc = (pc) => ({
473
+ connectionState: pc?.connectionState,
474
+ signalingState: pc?.signalingState,
475
+ iceConnectionState: pc?.iceConnectionState
476
+ });
477
+ var SipDebugLogger = class {
478
+ constructor() {
479
+ this.enabled = false;
480
+ this.statsStops = /* @__PURE__ */ new Map();
481
+ }
482
+ setEnabled(enabled) {
483
+ this.enabled = enabled;
484
+ if (!enabled) {
485
+ this.statsStops.forEach((stop) => stop());
486
+ this.statsStops.clear();
487
+ }
488
+ }
489
+ isEnabled() {
490
+ return this.enabled;
491
+ }
492
+ logLocalAudioError(sessionId, message, pc, extra) {
493
+ if (!this.enabled)
494
+ return;
495
+ console.error(message, {
496
+ sessionId,
497
+ pc: describePc(pc),
498
+ ...extra
499
+ });
500
+ void this.logOutboundStats(sessionId, pc, message);
501
+ }
502
+ logRemoteAudioError(sessionId, message, pc, extra) {
503
+ if (!this.enabled)
504
+ return;
505
+ console.error(message, {
506
+ sessionId,
507
+ pc: describePc(pc),
508
+ ...extra
509
+ });
510
+ void this.logInboundStats(sessionId, pc, message);
511
+ }
512
+ logMicRecoveryDrop(payload) {
513
+ if (!this.enabled)
514
+ return;
515
+ console.error("[sip] microphone dropped", payload);
516
+ }
517
+ logIceReady(sessionId, payload) {
518
+ if (!this.enabled)
519
+ return;
520
+ console.info("[sip] ice ready", { sessionId, ...payload });
521
+ }
522
+ logIceReadyConfig(sessionId, delayMs) {
523
+ if (!this.enabled)
524
+ return;
525
+ console.info("[sip] ice ready config", { sessionId, delayMs });
526
+ }
527
+ startCallStatsLogging(sessionId, session) {
528
+ if (!this.enabled || this.statsStops.has(sessionId))
529
+ return;
530
+ let pc = session?.connection ?? null;
531
+ const onPeer = (data) => {
532
+ pc = data.peerconnection;
533
+ };
534
+ session.on?.("peerconnection", onPeer);
535
+ const intervalMs = 3e3;
536
+ const logStats = async () => {
537
+ if (!this.enabled || !pc?.getStats)
538
+ return;
539
+ try {
540
+ const report = await pc.getStats();
541
+ const { outboundAudio, inboundAudio } = collectAudioStats(report);
542
+ console.info("[sip] call stats", {
543
+ sessionId,
544
+ pc: describePc(pc),
545
+ outboundAudio,
546
+ inboundAudio
547
+ });
548
+ } catch (err) {
549
+ console.error("[sip] call stats failed", { sessionId, error: err });
550
+ }
551
+ };
552
+ const timer = setInterval(() => {
553
+ void logStats();
554
+ }, intervalMs);
555
+ void logStats();
556
+ const stop = () => {
557
+ clearInterval(timer);
558
+ session.off?.("peerconnection", onPeer);
559
+ this.statsStops.delete(sessionId);
560
+ };
561
+ this.statsStops.set(sessionId, stop);
562
+ }
563
+ stopCallStatsLogging(sessionId) {
564
+ const stop = this.statsStops.get(sessionId);
565
+ if (stop)
566
+ stop();
567
+ }
568
+ async logOutboundStats(sessionId, pc, context) {
569
+ if (!pc?.getStats)
570
+ return;
571
+ try {
572
+ const report = await pc.getStats();
573
+ const { outboundAudio } = collectAudioStats(report);
574
+ if (outboundAudio.length) {
575
+ console.info("[sip] outgoing audio stats", {
576
+ sessionId,
577
+ context,
578
+ outboundAudio
579
+ });
580
+ }
581
+ } catch (err) {
582
+ console.error("[sip] outgoing audio stats failed", {
583
+ sessionId,
584
+ context,
585
+ error: err
586
+ });
587
+ }
588
+ }
589
+ async logInboundStats(sessionId, pc, context) {
590
+ if (!pc?.getStats)
591
+ return;
592
+ try {
593
+ const report = await pc.getStats();
594
+ const { inboundAudio } = collectAudioStats(report);
595
+ if (inboundAudio.length) {
596
+ console.error("[sip] incoming audio stats", {
597
+ sessionId,
598
+ context,
599
+ inboundAudio
600
+ });
601
+ }
602
+ } catch (err) {
603
+ console.error("[sip] incoming audio stats failed", {
604
+ sessionId,
605
+ context,
606
+ error: err
607
+ });
608
+ }
609
+ }
610
+ };
611
+ var sipDebugLogger = new SipDebugLogger();
612
+ function collectAudioStats(report) {
613
+ const outboundAudio = [];
614
+ const inboundAudio = [];
615
+ report.forEach((stat) => {
616
+ const kind = stat.kind ?? stat.mediaType;
617
+ if (stat.type === "outbound-rtp" && kind === "audio") {
618
+ outboundAudio.push({
619
+ id: stat.id,
620
+ packetsSent: stat.packetsSent,
621
+ bytesSent: stat.bytesSent,
622
+ jitter: stat.jitter,
623
+ roundTripTime: stat.roundTripTime
624
+ });
625
+ }
626
+ if (stat.type === "inbound-rtp" && kind === "audio") {
627
+ inboundAudio.push({
628
+ id: stat.id,
629
+ packetsReceived: stat.packetsReceived,
630
+ packetsLost: stat.packetsLost,
631
+ bytesReceived: stat.bytesReceived,
632
+ jitter: stat.jitter,
633
+ roundTripTime: stat.roundTripTime
634
+ });
635
+ }
636
+ });
637
+ return { outboundAudio, inboundAudio };
638
+ }
639
+
471
640
  // src/jssip-lib/sip/handlers/sessionHandlers.ts
472
641
  function createSessionHandlers(deps) {
473
642
  const {
@@ -476,8 +645,20 @@ function createSessionHandlers(deps) {
476
645
  rtc,
477
646
  detachSessionHandlers,
478
647
  onSessionFailed,
479
- sessionId
648
+ sessionId,
649
+ iceCandidateReadyDelayMs
480
650
  } = deps;
651
+ let iceReadyCalled = false;
652
+ let iceReadyTimer = null;
653
+ const clearIceReadyTimer = () => {
654
+ if (!iceReadyTimer)
655
+ return;
656
+ clearTimeout(iceReadyTimer);
657
+ iceReadyTimer = null;
658
+ };
659
+ if (typeof iceCandidateReadyDelayMs === "number") {
660
+ sipDebugLogger.logIceReadyConfig(sessionId, iceCandidateReadyDelayMs);
661
+ }
481
662
  return {
482
663
  progress: (e) => {
483
664
  emitter.emit("progress", e);
@@ -500,6 +681,7 @@ function createSessionHandlers(deps) {
500
681
  },
501
682
  ended: (e) => {
502
683
  emitter.emit("ended", e);
684
+ clearIceReadyTimer();
503
685
  detachSessionHandlers();
504
686
  rtc.cleanup();
505
687
  const nextSessions = state.getState().sessions.filter((s) => s.id !== sessionId);
@@ -509,6 +691,7 @@ function createSessionHandlers(deps) {
509
691
  },
510
692
  failed: (e) => {
511
693
  emitter.emit("failed", e);
694
+ clearIceReadyTimer();
512
695
  detachSessionHandlers();
513
696
  rtc.cleanup();
514
697
  const cause = e?.cause || "call failed";
@@ -537,13 +720,55 @@ function createSessionHandlers(deps) {
537
720
  reinvite: (e) => emitter.emit("reinvite", e),
538
721
  update: (e) => emitter.emit("update", e),
539
722
  sdp: (e) => emitter.emit("sdp", e),
540
- icecandidate: (e) => emitter.emit("icecandidate", e),
723
+ icecandidate: (e) => {
724
+ const candidate = e?.candidate;
725
+ const ready = typeof e?.ready === "function" ? e.ready : null;
726
+ const delayMs = typeof iceCandidateReadyDelayMs === "number" ? iceCandidateReadyDelayMs : null;
727
+ if (!iceReadyCalled && ready && delayMs != null) {
728
+ if (candidate?.type === "srflx" && candidate?.relatedAddress != null && candidate?.relatedPort != null) {
729
+ iceReadyCalled = true;
730
+ if (iceReadyTimer) {
731
+ clearTimeout(iceReadyTimer);
732
+ iceReadyTimer = null;
733
+ }
734
+ sipDebugLogger.logIceReady(sessionId, {
735
+ source: "srflx",
736
+ delayMs,
737
+ candidateType: candidate?.type
738
+ });
739
+ ready();
740
+ } else if (!iceReadyTimer && delayMs > 0) {
741
+ iceReadyTimer = setTimeout(() => {
742
+ iceReadyTimer = null;
743
+ if (iceReadyCalled)
744
+ return;
745
+ iceReadyCalled = true;
746
+ sipDebugLogger.logIceReady(sessionId, {
747
+ source: "timer",
748
+ delayMs,
749
+ candidateType: candidate?.type
750
+ });
751
+ ready();
752
+ }, delayMs);
753
+ } else if (delayMs === 0) {
754
+ iceReadyCalled = true;
755
+ sipDebugLogger.logIceReady(sessionId, {
756
+ source: "immediate",
757
+ delayMs,
758
+ candidateType: candidate?.type
759
+ });
760
+ ready();
761
+ }
762
+ }
763
+ emitter.emit("icecandidate", e);
764
+ },
541
765
  refer: (e) => emitter.emit("refer", e),
542
766
  replaces: (e) => emitter.emit("replaces", e),
543
767
  newDTMF: (e) => emitter.emit("newDTMF", e),
544
768
  newInfo: (e) => emitter.emit("newInfo", e),
545
769
  getusermediafailed: (e) => {
546
770
  emitter.emit("getusermediafailed", e);
771
+ clearIceReadyTimer();
547
772
  detachSessionHandlers();
548
773
  rtc.cleanup();
549
774
  onSessionFailed("getUserMedia failed", e);
@@ -553,6 +778,7 @@ function createSessionHandlers(deps) {
553
778
  },
554
779
  "peerconnection:createofferfailed": (e) => {
555
780
  emitter.emit("peerconnection:createofferfailed", e);
781
+ clearIceReadyTimer();
556
782
  detachSessionHandlers();
557
783
  rtc.cleanup();
558
784
  onSessionFailed("peer connection createOffer failed", e);
@@ -562,6 +788,7 @@ function createSessionHandlers(deps) {
562
788
  },
563
789
  "peerconnection:createanswerfailed": (e) => {
564
790
  emitter.emit("peerconnection:createanswerfailed", e);
791
+ clearIceReadyTimer();
565
792
  detachSessionHandlers();
566
793
  rtc.cleanup();
567
794
  onSessionFailed("peer connection createAnswer failed", e);
@@ -571,6 +798,7 @@ function createSessionHandlers(deps) {
571
798
  },
572
799
  "peerconnection:setlocaldescriptionfailed": (e) => {
573
800
  emitter.emit("peerconnection:setlocaldescriptionfailed", e);
801
+ clearIceReadyTimer();
574
802
  detachSessionHandlers();
575
803
  rtc.cleanup();
576
804
  onSessionFailed("peer connection setLocalDescription failed", e);
@@ -580,6 +808,7 @@ function createSessionHandlers(deps) {
580
808
  },
581
809
  "peerconnection:setremotedescriptionfailed": (e) => {
582
810
  emitter.emit("peerconnection:setremotedescriptionfailed", e);
811
+ clearIceReadyTimer();
583
812
  detachSessionHandlers();
584
813
  rtc.cleanup();
585
814
  onSessionFailed("peer connection setRemoteDescription failed", e);
@@ -832,165 +1061,6 @@ var SessionManager = class {
832
1061
  }
833
1062
  };
834
1063
 
835
- // src/jssip-lib/sip/debugLogging.ts
836
- var describePc = (pc) => ({
837
- connectionState: pc?.connectionState,
838
- signalingState: pc?.signalingState,
839
- iceConnectionState: pc?.iceConnectionState
840
- });
841
- var SipDebugLogger = class {
842
- constructor() {
843
- this.enabled = false;
844
- this.statsStops = /* @__PURE__ */ new Map();
845
- }
846
- setEnabled(enabled) {
847
- this.enabled = enabled;
848
- if (!enabled) {
849
- this.statsStops.forEach((stop) => stop());
850
- this.statsStops.clear();
851
- }
852
- }
853
- isEnabled() {
854
- return this.enabled;
855
- }
856
- logLocalAudioError(sessionId, message, pc, extra) {
857
- if (!this.enabled)
858
- return;
859
- console.error(message, {
860
- sessionId,
861
- pc: describePc(pc),
862
- ...extra
863
- });
864
- void this.logOutboundStats(sessionId, pc, message);
865
- }
866
- logRemoteAudioError(sessionId, message, pc, extra) {
867
- if (!this.enabled)
868
- return;
869
- console.error(message, {
870
- sessionId,
871
- pc: describePc(pc),
872
- ...extra
873
- });
874
- void this.logInboundStats(sessionId, pc, message);
875
- }
876
- logMicRecoveryDrop(payload) {
877
- if (!this.enabled)
878
- return;
879
- console.error("[sip] microphone dropped", payload);
880
- }
881
- startCallStatsLogging(sessionId, session) {
882
- if (!this.enabled || this.statsStops.has(sessionId))
883
- return;
884
- let pc = session?.connection ?? null;
885
- const onPeer = (data) => {
886
- pc = data.peerconnection;
887
- };
888
- session.on?.("peerconnection", onPeer);
889
- const intervalMs = 3e3;
890
- const logStats = async () => {
891
- if (!this.enabled || !pc?.getStats)
892
- return;
893
- try {
894
- const report = await pc.getStats();
895
- const { outboundAudio, inboundAudio } = collectAudioStats(report);
896
- console.info("[sip] call stats", {
897
- sessionId,
898
- pc: describePc(pc),
899
- outboundAudio,
900
- inboundAudio
901
- });
902
- } catch (err) {
903
- console.error("[sip] call stats failed", { sessionId, error: err });
904
- }
905
- };
906
- const timer = setInterval(() => {
907
- void logStats();
908
- }, intervalMs);
909
- void logStats();
910
- const stop = () => {
911
- clearInterval(timer);
912
- session.off?.("peerconnection", onPeer);
913
- this.statsStops.delete(sessionId);
914
- };
915
- this.statsStops.set(sessionId, stop);
916
- }
917
- stopCallStatsLogging(sessionId) {
918
- const stop = this.statsStops.get(sessionId);
919
- if (stop)
920
- stop();
921
- }
922
- async logOutboundStats(sessionId, pc, context) {
923
- if (!pc?.getStats)
924
- return;
925
- try {
926
- const report = await pc.getStats();
927
- const { outboundAudio } = collectAudioStats(report);
928
- if (outboundAudio.length) {
929
- console.info("[sip] outgoing audio stats", {
930
- sessionId,
931
- context,
932
- outboundAudio
933
- });
934
- }
935
- } catch (err) {
936
- console.error("[sip] outgoing audio stats failed", {
937
- sessionId,
938
- context,
939
- error: err
940
- });
941
- }
942
- }
943
- async logInboundStats(sessionId, pc, context) {
944
- if (!pc?.getStats)
945
- return;
946
- try {
947
- const report = await pc.getStats();
948
- const { inboundAudio } = collectAudioStats(report);
949
- if (inboundAudio.length) {
950
- console.error("[sip] incoming audio stats", {
951
- sessionId,
952
- context,
953
- inboundAudio
954
- });
955
- }
956
- } catch (err) {
957
- console.error("[sip] incoming audio stats failed", {
958
- sessionId,
959
- context,
960
- error: err
961
- });
962
- }
963
- }
964
- };
965
- var sipDebugLogger = new SipDebugLogger();
966
- function collectAudioStats(report) {
967
- const outboundAudio = [];
968
- const inboundAudio = [];
969
- report.forEach((stat) => {
970
- const kind = stat.kind ?? stat.mediaType;
971
- if (stat.type === "outbound-rtp" && kind === "audio") {
972
- outboundAudio.push({
973
- id: stat.id,
974
- packetsSent: stat.packetsSent,
975
- bytesSent: stat.bytesSent,
976
- jitter: stat.jitter,
977
- roundTripTime: stat.roundTripTime
978
- });
979
- }
980
- if (stat.type === "inbound-rtp" && kind === "audio") {
981
- inboundAudio.push({
982
- id: stat.id,
983
- packetsReceived: stat.packetsReceived,
984
- packetsLost: stat.packetsLost,
985
- bytesReceived: stat.bytesReceived,
986
- jitter: stat.jitter,
987
- roundTripTime: stat.roundTripTime
988
- });
989
- }
990
- });
991
- return { outboundAudio, inboundAudio };
992
- }
993
-
994
1064
  // src/jssip-lib/sip/sessionLifecycle.ts
995
1065
  var SessionLifecycle = class {
996
1066
  constructor(deps) {
@@ -1635,9 +1705,11 @@ var SipClient = class extends EventTargetEmitter {
1635
1705
  micRecoveryIntervalMs,
1636
1706
  micRecoveryMaxRetries,
1637
1707
  maxSessionCount,
1708
+ iceCandidateReadyDelayMs,
1638
1709
  ...uaCfg
1639
1710
  } = config;
1640
1711
  this.maxSessionCount = typeof maxSessionCount === "number" ? maxSessionCount : Infinity;
1712
+ this.iceCandidateReadyDelayMs = typeof iceCandidateReadyDelayMs === "number" ? iceCandidateReadyDelayMs : void 0;
1641
1713
  this.micRecovery.configure({
1642
1714
  enabled: Boolean(enableMicRecovery),
1643
1715
  intervalMs: micRecoveryIntervalMs,
@@ -1785,6 +1857,7 @@ var SipClient = class extends EventTargetEmitter {
1785
1857
  emitError: (raw, code, fallback) => this.emitError(raw, code, fallback),
1786
1858
  onSessionFailed: (err, event) => this.onSessionFailed(err, event),
1787
1859
  enableMicrophoneRecovery: (confirmedSessionId) => this.micRecovery.enable(confirmedSessionId),
1860
+ iceCandidateReadyDelayMs: this.iceCandidateReadyDelayMs,
1788
1861
  sessionId
1789
1862
  });
1790
1863
  }