react-jssip-kit 0.7.7 → 0.7.8

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.cjs CHANGED
@@ -837,6 +837,165 @@ var SessionManager = class {
837
837
  }
838
838
  };
839
839
 
840
+ // src/jssip-lib/sip/debugLogging.ts
841
+ var describePc = (pc) => ({
842
+ connectionState: pc?.connectionState,
843
+ signalingState: pc?.signalingState,
844
+ iceConnectionState: pc?.iceConnectionState
845
+ });
846
+ var SipDebugLogger = class {
847
+ constructor() {
848
+ this.enabled = false;
849
+ this.statsStops = /* @__PURE__ */ new Map();
850
+ }
851
+ setEnabled(enabled) {
852
+ this.enabled = enabled;
853
+ if (!enabled) {
854
+ this.statsStops.forEach((stop) => stop());
855
+ this.statsStops.clear();
856
+ }
857
+ }
858
+ isEnabled() {
859
+ return this.enabled;
860
+ }
861
+ logLocalAudioError(sessionId, message, pc, extra) {
862
+ if (!this.enabled)
863
+ return;
864
+ console.error(message, {
865
+ sessionId,
866
+ pc: describePc(pc),
867
+ ...extra
868
+ });
869
+ void this.logOutboundStats(sessionId, pc, message);
870
+ }
871
+ logRemoteAudioError(sessionId, message, pc, extra) {
872
+ if (!this.enabled)
873
+ return;
874
+ console.error(message, {
875
+ sessionId,
876
+ pc: describePc(pc),
877
+ ...extra
878
+ });
879
+ void this.logInboundStats(sessionId, pc, message);
880
+ }
881
+ logMicRecoveryDrop(payload) {
882
+ if (!this.enabled)
883
+ return;
884
+ console.error("[sip] microphone dropped", payload);
885
+ }
886
+ startCallStatsLogging(sessionId, session) {
887
+ if (!this.enabled || this.statsStops.has(sessionId))
888
+ return;
889
+ let pc = session?.connection ?? null;
890
+ const onPeer = (data) => {
891
+ pc = data.peerconnection;
892
+ };
893
+ session.on?.("peerconnection", onPeer);
894
+ const intervalMs = 3e3;
895
+ const logStats = async () => {
896
+ if (!this.enabled || !pc?.getStats)
897
+ return;
898
+ try {
899
+ const report = await pc.getStats();
900
+ const { outboundAudio, inboundAudio } = collectAudioStats(report);
901
+ console.info("[sip] call stats", {
902
+ sessionId,
903
+ pc: describePc(pc),
904
+ outboundAudio,
905
+ inboundAudio
906
+ });
907
+ } catch (err) {
908
+ console.error("[sip] call stats failed", { sessionId, error: err });
909
+ }
910
+ };
911
+ const timer = setInterval(() => {
912
+ void logStats();
913
+ }, intervalMs);
914
+ void logStats();
915
+ const stop = () => {
916
+ clearInterval(timer);
917
+ session.off?.("peerconnection", onPeer);
918
+ this.statsStops.delete(sessionId);
919
+ };
920
+ this.statsStops.set(sessionId, stop);
921
+ }
922
+ stopCallStatsLogging(sessionId) {
923
+ const stop = this.statsStops.get(sessionId);
924
+ if (stop)
925
+ stop();
926
+ }
927
+ async logOutboundStats(sessionId, pc, context) {
928
+ if (!pc?.getStats)
929
+ return;
930
+ try {
931
+ const report = await pc.getStats();
932
+ const { outboundAudio } = collectAudioStats(report);
933
+ if (outboundAudio.length) {
934
+ console.info("[sip] outgoing audio stats", {
935
+ sessionId,
936
+ context,
937
+ outboundAudio
938
+ });
939
+ }
940
+ } catch (err) {
941
+ console.error("[sip] outgoing audio stats failed", {
942
+ sessionId,
943
+ context,
944
+ error: err
945
+ });
946
+ }
947
+ }
948
+ async logInboundStats(sessionId, pc, context) {
949
+ if (!pc?.getStats)
950
+ return;
951
+ try {
952
+ const report = await pc.getStats();
953
+ const { inboundAudio } = collectAudioStats(report);
954
+ if (inboundAudio.length) {
955
+ console.error("[sip] incoming audio stats", {
956
+ sessionId,
957
+ context,
958
+ inboundAudio
959
+ });
960
+ }
961
+ } catch (err) {
962
+ console.error("[sip] incoming audio stats failed", {
963
+ sessionId,
964
+ context,
965
+ error: err
966
+ });
967
+ }
968
+ }
969
+ };
970
+ var sipDebugLogger = new SipDebugLogger();
971
+ function collectAudioStats(report) {
972
+ const outboundAudio = [];
973
+ const inboundAudio = [];
974
+ report.forEach((stat) => {
975
+ const kind = stat.kind ?? stat.mediaType;
976
+ if (stat.type === "outbound-rtp" && kind === "audio") {
977
+ outboundAudio.push({
978
+ id: stat.id,
979
+ packetsSent: stat.packetsSent,
980
+ bytesSent: stat.bytesSent,
981
+ jitter: stat.jitter,
982
+ roundTripTime: stat.roundTripTime
983
+ });
984
+ }
985
+ if (stat.type === "inbound-rtp" && kind === "audio") {
986
+ inboundAudio.push({
987
+ id: stat.id,
988
+ packetsReceived: stat.packetsReceived,
989
+ packetsLost: stat.packetsLost,
990
+ bytesReceived: stat.bytesReceived,
991
+ jitter: stat.jitter,
992
+ roundTripTime: stat.roundTripTime
993
+ });
994
+ }
995
+ });
996
+ return { outboundAudio, inboundAudio };
997
+ }
998
+
840
999
  // src/jssip-lib/sip/sessionLifecycle.ts
841
1000
  var SessionLifecycle = class {
842
1001
  constructor(deps) {
@@ -847,6 +1006,9 @@ var SessionLifecycle = class {
847
1006
  this.attachSessionHandlers = deps.attachSessionHandlers;
848
1007
  this.getMaxSessionCount = deps.getMaxSessionCount;
849
1008
  }
1009
+ setDebugEnabled(enabled) {
1010
+ sipDebugLogger.setEnabled(enabled);
1011
+ }
850
1012
  handleNewRTCSession(e) {
851
1013
  const session = e.session;
852
1014
  const sessionId = String(
@@ -875,71 +1037,12 @@ var SessionLifecycle = class {
875
1037
  const rtc = this.sessionManager.getOrCreateRtc(sessionId, session);
876
1038
  this.sessionManager.setSession(sessionId, session);
877
1039
  this.attachSessionHandlers(sessionId, session);
1040
+ this.attachCallStatsLogging(sessionId, session);
878
1041
  if (e.originator === "local" && !rtc.mediaStream) {
879
- const maxAttempts = 5;
880
- const retryDelayMs = 500;
881
- let attempts = 0;
882
- let retryScheduled = false;
883
- let retryTimer = null;
884
- let stopped = false;
885
- const tryBindFromPc = (pc) => {
886
- if (stopped || !pc || this.sessionManager.getRtc(sessionId)?.mediaStream) {
887
- return false;
888
- }
889
- const audioSender = pc?.getSenders?.()?.find((s) => s.track?.kind === "audio");
890
- const audioTrack = audioSender?.track;
891
- if (!audioTrack)
892
- return false;
893
- const outgoingStream = new MediaStream([audioTrack]);
894
- this.sessionManager.setSessionMedia(sessionId, outgoingStream);
895
- return true;
896
- };
897
- const scheduleRetry = (pc) => {
898
- if (stopped || retryScheduled || attempts >= maxAttempts) {
899
- session.off?.("peerconnection", onPeer);
900
- return;
901
- }
902
- retryScheduled = true;
903
- attempts += 1;
904
- retryTimer = setTimeout(() => {
905
- retryScheduled = false;
906
- retryTimer = null;
907
- if (tryBindFromPc(pc)) {
908
- session.off?.("peerconnection", onPeer);
909
- return;
910
- }
911
- scheduleRetry(pc);
912
- }, retryDelayMs);
913
- };
914
- const onPeer = (data) => {
915
- if (stopped)
916
- return;
917
- if (tryBindFromPc(data.peerconnection)) {
918
- session.off?.("peerconnection", onPeer);
919
- return;
920
- }
921
- scheduleRetry(data.peerconnection);
922
- };
923
- const stopRetry = () => {
924
- if (stopped)
925
- return;
926
- stopped = true;
927
- if (retryTimer) {
928
- clearTimeout(retryTimer);
929
- retryTimer = null;
930
- }
931
- session.off?.("peerconnection", onPeer);
932
- session.off?.("ended", stopRetry);
933
- session.off?.("failed", stopRetry);
934
- };
935
- const existingPc = session?.connection;
936
- if (!tryBindFromPc(existingPc)) {
937
- if (existingPc)
938
- scheduleRetry(existingPc);
939
- session.on?.("peerconnection", onPeer);
940
- }
941
- session.on?.("ended", stopRetry);
942
- session.on?.("failed", stopRetry);
1042
+ this.bindLocalOutgoingAudio(sessionId, session);
1043
+ }
1044
+ if (e.originator === "remote") {
1045
+ this.bindRemoteIncomingAudio(sessionId, session);
943
1046
  }
944
1047
  holdOtherSessions(this.state, sessionId, (id) => {
945
1048
  const otherRtc = this.sessionManager.getRtc(id);
@@ -956,6 +1059,414 @@ var SessionLifecycle = class {
956
1059
  });
957
1060
  this.emit("newRTCSession", e);
958
1061
  }
1062
+ bindLocalOutgoingAudio(sessionId, session) {
1063
+ const maxAttempts = 50;
1064
+ const retryDelayMs = 500;
1065
+ let attempts = 0;
1066
+ let retryScheduled = false;
1067
+ let retryTimer = null;
1068
+ let stopped = false;
1069
+ let exhausted = false;
1070
+ let exhaustedCheckUsed = false;
1071
+ let attachedPc = null;
1072
+ const logLocalAudioError = (message, pc, extra) => {
1073
+ sipDebugLogger.logLocalAudioError(sessionId, message, pc, extra);
1074
+ };
1075
+ const tryBindFromPc = (pc) => {
1076
+ if (stopped || !pc || this.sessionManager.getRtc(sessionId)?.mediaStream) {
1077
+ return false;
1078
+ }
1079
+ const audioSender = pc?.getSenders?.()?.find((s) => s.track?.kind === "audio");
1080
+ const audioTrack = audioSender?.track;
1081
+ if (!audioTrack) {
1082
+ logLocalAudioError(
1083
+ "[sip] outgoing audio bind failed: no audio track",
1084
+ pc
1085
+ );
1086
+ return false;
1087
+ }
1088
+ const outgoingStream = new MediaStream([audioTrack]);
1089
+ this.sessionManager.setSessionMedia(sessionId, outgoingStream);
1090
+ return true;
1091
+ };
1092
+ const onPcStateChange = () => {
1093
+ if (stopped)
1094
+ return;
1095
+ if (exhausted) {
1096
+ if (exhaustedCheckUsed)
1097
+ return;
1098
+ exhaustedCheckUsed = true;
1099
+ if (tryBindFromPc(attachedPc))
1100
+ stopRetry();
1101
+ return;
1102
+ }
1103
+ if (tryBindFromPc(attachedPc))
1104
+ stopRetry();
1105
+ };
1106
+ const attachPcListeners = (pc) => {
1107
+ if (!pc || pc === attachedPc)
1108
+ return;
1109
+ if (attachedPc) {
1110
+ attachedPc.removeEventListener?.(
1111
+ "signalingstatechange",
1112
+ onPcStateChange
1113
+ );
1114
+ attachedPc.removeEventListener?.(
1115
+ "connectionstatechange",
1116
+ onPcStateChange
1117
+ );
1118
+ attachedPc.removeEventListener?.(
1119
+ "iceconnectionstatechange",
1120
+ onPcStateChange
1121
+ );
1122
+ }
1123
+ attachedPc = pc;
1124
+ attachedPc.addEventListener?.("signalingstatechange", onPcStateChange);
1125
+ attachedPc.addEventListener?.("connectionstatechange", onPcStateChange);
1126
+ attachedPc.addEventListener?.("iceconnectionstatechange", onPcStateChange);
1127
+ };
1128
+ const clearRetryTimer = () => {
1129
+ if (!retryTimer)
1130
+ return;
1131
+ clearTimeout(retryTimer);
1132
+ retryTimer = null;
1133
+ };
1134
+ const stopRetry = () => {
1135
+ if (stopped)
1136
+ return;
1137
+ stopped = true;
1138
+ clearRetryTimer();
1139
+ if (attachedPc) {
1140
+ attachedPc.removeEventListener?.(
1141
+ "signalingstatechange",
1142
+ onPcStateChange
1143
+ );
1144
+ attachedPc.removeEventListener?.(
1145
+ "connectionstatechange",
1146
+ onPcStateChange
1147
+ );
1148
+ attachedPc.removeEventListener?.(
1149
+ "iceconnectionstatechange",
1150
+ onPcStateChange
1151
+ );
1152
+ attachedPc = null;
1153
+ }
1154
+ session.off?.("peerconnection", onPeer);
1155
+ session.off?.("confirmed", onConfirmed);
1156
+ session.off?.("ended", stopRetry);
1157
+ session.off?.("failed", stopRetry);
1158
+ };
1159
+ const scheduleRetry = (pc) => {
1160
+ if (stopped || retryScheduled || exhausted)
1161
+ return;
1162
+ if (attempts >= maxAttempts) {
1163
+ logLocalAudioError(
1164
+ "[sip] outgoing audio bind failed: max retries reached",
1165
+ pc,
1166
+ { attempts }
1167
+ );
1168
+ exhausted = true;
1169
+ clearRetryTimer();
1170
+ return;
1171
+ }
1172
+ if (!pc) {
1173
+ logLocalAudioError(
1174
+ "[sip] outgoing audio bind failed: missing peerconnection",
1175
+ pc
1176
+ );
1177
+ }
1178
+ retryScheduled = true;
1179
+ attempts += 1;
1180
+ retryTimer = setTimeout(() => {
1181
+ retryScheduled = false;
1182
+ retryTimer = null;
1183
+ if (tryBindFromPc(pc)) {
1184
+ stopRetry();
1185
+ return;
1186
+ }
1187
+ scheduleRetry(pc);
1188
+ }, retryDelayMs);
1189
+ };
1190
+ const onPeer = (data) => {
1191
+ if (stopped)
1192
+ return;
1193
+ attachPcListeners(data.peerconnection);
1194
+ if (exhausted) {
1195
+ if (exhaustedCheckUsed)
1196
+ return;
1197
+ exhaustedCheckUsed = true;
1198
+ if (tryBindFromPc(data.peerconnection))
1199
+ stopRetry();
1200
+ return;
1201
+ }
1202
+ if (tryBindFromPc(data.peerconnection)) {
1203
+ stopRetry();
1204
+ return;
1205
+ }
1206
+ scheduleRetry(data.peerconnection);
1207
+ };
1208
+ const onConfirmed = () => {
1209
+ if (stopped)
1210
+ return;
1211
+ const currentPc = session?.connection ?? attachedPc;
1212
+ if (exhausted) {
1213
+ if (exhaustedCheckUsed)
1214
+ return;
1215
+ exhaustedCheckUsed = true;
1216
+ if (tryBindFromPc(currentPc))
1217
+ stopRetry();
1218
+ return;
1219
+ }
1220
+ if (tryBindFromPc(currentPc)) {
1221
+ stopRetry();
1222
+ return;
1223
+ }
1224
+ scheduleRetry(currentPc);
1225
+ };
1226
+ const existingPc = session?.connection;
1227
+ if (!tryBindFromPc(existingPc)) {
1228
+ if (existingPc) {
1229
+ attachPcListeners(existingPc);
1230
+ scheduleRetry(existingPc);
1231
+ }
1232
+ session.on?.("peerconnection", onPeer);
1233
+ }
1234
+ session.on?.("confirmed", onConfirmed);
1235
+ session.on?.("ended", stopRetry);
1236
+ session.on?.("failed", stopRetry);
1237
+ }
1238
+ bindRemoteIncomingAudio(sessionId, session) {
1239
+ const maxAttempts = 50;
1240
+ const retryDelayMs = 500;
1241
+ let attempts = 0;
1242
+ let retryScheduled = false;
1243
+ let retryTimer = null;
1244
+ let stopped = false;
1245
+ let exhausted = false;
1246
+ let exhaustedCheckUsed = false;
1247
+ let attachedPc = null;
1248
+ let attachedTrack = null;
1249
+ const logRemoteAudioError = (message, pc, extra) => {
1250
+ sipDebugLogger.logRemoteAudioError(sessionId, message, pc, extra);
1251
+ };
1252
+ const logMissingReceiver = (pc, note) => {
1253
+ logRemoteAudioError(
1254
+ "[sip] incoming audio bind failed: no remote track",
1255
+ pc,
1256
+ { note }
1257
+ );
1258
+ };
1259
+ const getRemoteAudioTrack = (pc) => {
1260
+ const receiver = pc?.getReceivers?.()?.find((r) => r.track?.kind === "audio");
1261
+ return receiver?.track ?? null;
1262
+ };
1263
+ const attachTrackListeners = (track) => {
1264
+ if (!track || track === attachedTrack)
1265
+ return;
1266
+ if (attachedTrack) {
1267
+ attachedTrack.removeEventListener?.("ended", onRemoteEnded);
1268
+ attachedTrack.removeEventListener?.("mute", onRemoteMuted);
1269
+ }
1270
+ attachedTrack = track;
1271
+ attachedTrack.addEventListener?.("ended", onRemoteEnded);
1272
+ attachedTrack.addEventListener?.("mute", onRemoteMuted);
1273
+ };
1274
+ const checkRemoteTrack = (pc) => {
1275
+ if (stopped || !pc)
1276
+ return false;
1277
+ const track = getRemoteAudioTrack(pc);
1278
+ if (!track)
1279
+ return false;
1280
+ attachTrackListeners(track);
1281
+ if (track.readyState !== "live") {
1282
+ logRemoteAudioError("[sip] incoming audio track not live", pc, {
1283
+ trackState: track.readyState
1284
+ });
1285
+ }
1286
+ return true;
1287
+ };
1288
+ const onRemoteEnded = () => {
1289
+ logRemoteAudioError("[sip] incoming audio track ended", attachedPc);
1290
+ };
1291
+ const onRemoteMuted = () => {
1292
+ logRemoteAudioError("[sip] incoming audio track muted", attachedPc);
1293
+ };
1294
+ const onPcStateChange = () => {
1295
+ if (stopped)
1296
+ return;
1297
+ if (exhausted) {
1298
+ if (exhaustedCheckUsed)
1299
+ return;
1300
+ exhaustedCheckUsed = true;
1301
+ if (checkRemoteTrack(attachedPc))
1302
+ stopRetry();
1303
+ return;
1304
+ }
1305
+ if (checkRemoteTrack(attachedPc))
1306
+ stopRetry();
1307
+ };
1308
+ const attachPcListeners = (pc) => {
1309
+ if (!pc || pc === attachedPc)
1310
+ return;
1311
+ if (attachedPc) {
1312
+ attachedPc.removeEventListener?.(
1313
+ "signalingstatechange",
1314
+ onPcStateChange
1315
+ );
1316
+ attachedPc.removeEventListener?.(
1317
+ "connectionstatechange",
1318
+ onPcStateChange
1319
+ );
1320
+ attachedPc.removeEventListener?.(
1321
+ "iceconnectionstatechange",
1322
+ onPcStateChange
1323
+ );
1324
+ attachedPc.removeEventListener?.("track", onTrack);
1325
+ }
1326
+ attachedPc = pc;
1327
+ attachedPc.addEventListener?.("signalingstatechange", onPcStateChange);
1328
+ attachedPc.addEventListener?.("connectionstatechange", onPcStateChange);
1329
+ attachedPc.addEventListener?.("iceconnectionstatechange", onPcStateChange);
1330
+ attachedPc.addEventListener?.("track", onTrack);
1331
+ };
1332
+ const clearRetryTimer = () => {
1333
+ if (!retryTimer)
1334
+ return;
1335
+ clearTimeout(retryTimer);
1336
+ retryTimer = null;
1337
+ };
1338
+ const stopRetry = () => {
1339
+ if (stopped)
1340
+ return;
1341
+ stopped = true;
1342
+ clearRetryTimer();
1343
+ if (attachedPc) {
1344
+ attachedPc.removeEventListener?.(
1345
+ "signalingstatechange",
1346
+ onPcStateChange
1347
+ );
1348
+ attachedPc.removeEventListener?.(
1349
+ "connectionstatechange",
1350
+ onPcStateChange
1351
+ );
1352
+ attachedPc.removeEventListener?.(
1353
+ "iceconnectionstatechange",
1354
+ onPcStateChange
1355
+ );
1356
+ attachedPc.removeEventListener?.("track", onTrack);
1357
+ attachedPc = null;
1358
+ }
1359
+ if (attachedTrack) {
1360
+ attachedTrack.removeEventListener?.("ended", onRemoteEnded);
1361
+ attachedTrack.removeEventListener?.("mute", onRemoteMuted);
1362
+ attachedTrack = null;
1363
+ }
1364
+ session.off?.("peerconnection", onPeer);
1365
+ session.off?.("confirmed", onConfirmed);
1366
+ session.off?.("ended", stopRetry);
1367
+ session.off?.("failed", stopRetry);
1368
+ };
1369
+ const scheduleRetry = (pc) => {
1370
+ if (stopped || retryScheduled || exhausted)
1371
+ return;
1372
+ if (attempts >= maxAttempts) {
1373
+ logRemoteAudioError(
1374
+ "[sip] incoming audio bind failed: max retries reached",
1375
+ pc,
1376
+ { attempts }
1377
+ );
1378
+ exhausted = true;
1379
+ clearRetryTimer();
1380
+ return;
1381
+ }
1382
+ retryScheduled = true;
1383
+ attempts += 1;
1384
+ retryTimer = setTimeout(() => {
1385
+ retryScheduled = false;
1386
+ retryTimer = null;
1387
+ if (checkRemoteTrack(pc)) {
1388
+ stopRetry();
1389
+ return;
1390
+ }
1391
+ if (!pc)
1392
+ logMissingReceiver(pc, "missing peerconnection");
1393
+ scheduleRetry(pc);
1394
+ }, retryDelayMs);
1395
+ };
1396
+ const onTrack = () => {
1397
+ if (stopped)
1398
+ return;
1399
+ if (exhausted) {
1400
+ if (exhaustedCheckUsed)
1401
+ return;
1402
+ exhaustedCheckUsed = true;
1403
+ if (checkRemoteTrack(attachedPc))
1404
+ stopRetry();
1405
+ return;
1406
+ }
1407
+ if (checkRemoteTrack(attachedPc))
1408
+ stopRetry();
1409
+ };
1410
+ const onPeer = (data) => {
1411
+ if (stopped)
1412
+ return;
1413
+ attachPcListeners(data.peerconnection);
1414
+ if (exhausted) {
1415
+ if (exhaustedCheckUsed)
1416
+ return;
1417
+ exhaustedCheckUsed = true;
1418
+ if (checkRemoteTrack(data.peerconnection))
1419
+ stopRetry();
1420
+ return;
1421
+ }
1422
+ if (checkRemoteTrack(data.peerconnection)) {
1423
+ stopRetry();
1424
+ return;
1425
+ }
1426
+ scheduleRetry(data.peerconnection);
1427
+ };
1428
+ const onConfirmed = () => {
1429
+ if (stopped)
1430
+ return;
1431
+ const currentPc = session?.connection ?? attachedPc;
1432
+ if (exhausted) {
1433
+ if (exhaustedCheckUsed)
1434
+ return;
1435
+ exhaustedCheckUsed = true;
1436
+ if (checkRemoteTrack(currentPc))
1437
+ stopRetry();
1438
+ return;
1439
+ }
1440
+ if (checkRemoteTrack(currentPc)) {
1441
+ stopRetry();
1442
+ return;
1443
+ }
1444
+ logMissingReceiver(currentPc, "confirmed without remote track");
1445
+ scheduleRetry(currentPc);
1446
+ };
1447
+ const existingPc = session?.connection;
1448
+ if (!checkRemoteTrack(existingPc)) {
1449
+ if (existingPc) {
1450
+ attachPcListeners(existingPc);
1451
+ scheduleRetry(existingPc);
1452
+ }
1453
+ session.on?.("peerconnection", onPeer);
1454
+ }
1455
+ session.on?.("confirmed", onConfirmed);
1456
+ session.on?.("ended", stopRetry);
1457
+ session.on?.("failed", stopRetry);
1458
+ }
1459
+ attachCallStatsLogging(sessionId, session) {
1460
+ const onConfirmed = () => {
1461
+ sipDebugLogger.startCallStatsLogging(sessionId, session);
1462
+ };
1463
+ const onEnd = () => {
1464
+ sipDebugLogger.stopCallStatsLogging(sessionId);
1465
+ };
1466
+ session.on?.("confirmed", onConfirmed);
1467
+ session.on?.("ended", onEnd);
1468
+ session.on?.("failed", onEnd);
1469
+ }
959
1470
  };
960
1471
 
961
1472
  // src/jssip-lib/sip/micRecovery.ts
@@ -1016,15 +1527,11 @@ var MicRecoveryManager = class {
1016
1527
  const senderLive = sender?.track?.readyState === "live";
1017
1528
  if (trackLive && senderLive)
1018
1529
  return;
1019
- this.deps.emitError(
1020
- {
1021
- cause: "microphone dropped",
1022
- trackLive,
1023
- senderLive
1024
- },
1025
- "MICROPHONE_DROPPED",
1026
- "microphone dropped"
1027
- );
1530
+ sipDebugLogger.logMicRecoveryDrop({
1531
+ sessionId,
1532
+ trackLive,
1533
+ senderLive
1534
+ });
1028
1535
  retries += 1;
1029
1536
  if (trackLive && !senderLive && track) {
1030
1537
  await rtc.replaceAudioTrack(track);
@@ -1143,6 +1650,7 @@ var SipClient = class extends EventTargetEmitter {
1143
1650
  });
1144
1651
  const debug = cfgDebug ?? this.getPersistedDebug() ?? this.debugPattern;
1145
1652
  this.userAgent.start(uri, password, uaCfg, { debug });
1653
+ this.lifecycle.setDebugEnabled(Boolean(debug));
1146
1654
  this.attachUAHandlers();
1147
1655
  this.attachBeforeUnload();
1148
1656
  this.syncDebugInspector(debug);
@@ -1223,6 +1731,7 @@ var SipClient = class extends EventTargetEmitter {
1223
1731
  setDebug(debug) {
1224
1732
  this.debugPattern = debug;
1225
1733
  this.userAgent.setDebug(debug);
1734
+ this.lifecycle.setDebugEnabled(Boolean(debug));
1226
1735
  this.syncDebugInspector(debug);
1227
1736
  }
1228
1737
  attachSessionHandlers(sessionId, session) {
@@ -1436,14 +1945,17 @@ var SipClient = class extends EventTargetEmitter {
1436
1945
  syncDebugInspector(debug) {
1437
1946
  if (typeof window === "undefined")
1438
1947
  return;
1439
- this.toggleStateLogger(Boolean(debug));
1948
+ const persisted = this.getPersistedDebug();
1949
+ const effectiveDebug = debug ?? persisted ?? this.debugPattern;
1950
+ this.lifecycle.setDebugEnabled(Boolean(effectiveDebug));
1951
+ this.toggleStateLogger(Boolean(effectiveDebug));
1440
1952
  const win = window;
1441
1953
  const disabledInspector = () => {
1442
1954
  console.warn("SIP debug inspector disabled; enable debug to inspect.");
1443
1955
  return null;
1444
1956
  };
1445
- win.sipState = () => debug ? this.stateStore.getState() : disabledInspector();
1446
- win.sipSessions = () => debug ? this.getSessions() : disabledInspector();
1957
+ win.sipState = () => effectiveDebug ? this.stateStore.getState() : disabledInspector();
1958
+ win.sipSessions = () => effectiveDebug ? this.getSessions() : disabledInspector();
1447
1959
  }
1448
1960
  toggleStateLogger(enabled) {
1449
1961
  if (!enabled) {
@@ -1456,22 +1968,10 @@ var SipClient = class extends EventTargetEmitter {
1456
1968
  let prev = this.stateStore.getState();
1457
1969
  console.info("[sip][state]", { initial: true }, prev);
1458
1970
  this.stateLogOff = this.stateStore.onChange((next) => {
1459
- const changes = this.diffState(prev, next);
1460
- if (changes) {
1461
- console.info("[sip][state]", changes, next);
1462
- }
1971
+ console.info("[sip][state]", next);
1463
1972
  prev = next;
1464
1973
  });
1465
1974
  }
1466
- diffState(prev, next) {
1467
- const changed = {};
1468
- for (const key of Object.keys(next)) {
1469
- if (prev[key] !== next[key]) {
1470
- changed[key] = { from: prev[key], to: next[key] };
1471
- }
1472
- }
1473
- return Object.keys(changed).length ? changed : null;
1474
- }
1475
1975
  getPersistedDebug() {
1476
1976
  if (typeof window === "undefined")
1477
1977
  return void 0;