react-jssip-kit 0.6.8 → 0.7.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/index.cjs CHANGED
@@ -1,12 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  var JsSIP = require('jssip');
4
- var react = require('react');
4
+ var React = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
7
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
8
 
9
9
  var JsSIP__default = /*#__PURE__*/_interopDefault(JsSIP);
10
+ var React__default = /*#__PURE__*/_interopDefault(React);
10
11
 
11
12
  // src/jssip-lib/sip/debugger.ts
12
13
  var SipDebugger = class {
@@ -18,11 +19,8 @@ var SipDebugger = class {
18
19
  initFromSession(storage = safeSessionStorage()) {
19
20
  try {
20
21
  const saved = storage?.getItem(this.storageKey);
21
- if (saved === "true") {
22
- this.enable(this.defaultPattern, storage);
23
- } else if (saved) {
24
- storage?.removeItem?.(this.storageKey);
25
- }
22
+ if (saved)
23
+ this.enable(saved, storage);
26
24
  } catch {
27
25
  }
28
26
  }
@@ -32,9 +30,9 @@ var SipDebugger = class {
32
30
  JsSIP__default.default.debug.enable(pattern);
33
31
  this.logger = console;
34
32
  }
35
- storage?.setItem?.(this.storageKey, "true");
33
+ storage?.setItem?.(this.storageKey, pattern || this.defaultPattern);
36
34
  try {
37
- window.sipDebugBridge?.(true);
35
+ window.sipDebugBridge?.(pattern);
38
36
  } catch {
39
37
  }
40
38
  this.enabled = true;
@@ -180,8 +178,9 @@ var SipUserAgent = class {
180
178
  }
181
179
  }
182
180
  applyDebug(debug) {
183
- const enabled = debug === void 0 ? this.readSessionFlag() : !!debug;
184
- const pattern = typeof debug === "string" ? debug : "JsSIP:*";
181
+ const stored = debug === void 0 ? this.readSessionFlag() : debug;
182
+ const enabled = !!stored;
183
+ const pattern = typeof stored === "string" ? stored : "JsSIP:*";
185
184
  if (enabled) {
186
185
  JsSIP__default.default.debug.enable(pattern);
187
186
  const dbg = JsSIP__default.default.debug;
@@ -189,7 +188,7 @@ var SipUserAgent = class {
189
188
  dbg.setLogger(console);
190
189
  else if (dbg)
191
190
  dbg.logger = console;
192
- this.persistSessionFlag();
191
+ this.persistSessionFlag(typeof stored === "string" ? stored : void 0);
193
192
  } else {
194
193
  JsSIP__default.default.debug?.disable?.();
195
194
  this.clearSessionFlag();
@@ -202,15 +201,21 @@ var SipUserAgent = class {
202
201
  try {
203
202
  if (typeof window === "undefined")
204
203
  return false;
205
- return window.sessionStorage.getItem("sip-debug-enabled") === "true";
204
+ const value = window.sessionStorage.getItem("sip-debug-enabled");
205
+ if (!value)
206
+ return false;
207
+ return value;
206
208
  } catch {
207
209
  return false;
208
210
  }
209
211
  }
210
- persistSessionFlag() {
212
+ persistSessionFlag(pattern) {
211
213
  try {
212
214
  if (typeof window !== "undefined") {
213
- window.sessionStorage.setItem("sip-debug-enabled", "true");
215
+ window.sessionStorage.setItem(
216
+ "sip-debug-enabled",
217
+ pattern || "JsSIP:*"
218
+ );
214
219
  }
215
220
  } catch {
216
221
  }
@@ -675,6 +680,23 @@ var WebRTCSessionController = class {
675
680
  old.stop();
676
681
  return true;
677
682
  }
683
+ async replaceAudioTrack(nextAudioTrack) {
684
+ const pc = this.getPC();
685
+ if (!pc)
686
+ return false;
687
+ if (!this.mediaStream)
688
+ this.mediaStream = new MediaStream();
689
+ const old = this.mediaStream.getAudioTracks()[0];
690
+ this.mediaStream.addTrack(nextAudioTrack);
691
+ if (old)
692
+ this.mediaStream.removeTrack(old);
693
+ const sender = pc.getSenders?.().find((s) => s.track?.kind === "audio");
694
+ if (sender)
695
+ await sender.replaceTrack(nextAudioTrack);
696
+ if (old && old !== nextAudioTrack)
697
+ old.stop();
698
+ return true;
699
+ }
678
700
  };
679
701
 
680
702
  // src/jssip-lib/sip/sessionManager.ts
@@ -882,6 +904,12 @@ var SipClient = class extends EventTargetEmitter {
882
904
  this.sessionHandlers = /* @__PURE__ */ new Map();
883
905
  this.maxSessionCount = Infinity;
884
906
  this.sessionManager = new SessionManager();
907
+ this.micRecovery = /* @__PURE__ */ new Map();
908
+ this.micRecoveryEnabled = false;
909
+ this.micRecoveryDefaults = {
910
+ intervalMs: 2e3,
911
+ maxRetries: Infinity
912
+ };
885
913
  this.errorHandler = options.errorHandler ?? new SipErrorHandler({
886
914
  formatter: options.formatError,
887
915
  messages: options.errorMessages
@@ -915,11 +943,21 @@ var SipClient = class extends EventTargetEmitter {
915
943
  this.stateStore.setState({ sipStatus: SipStatus.Connecting });
916
944
  const {
917
945
  debug: cfgDebug,
946
+ enableMicRecovery,
947
+ micRecoveryIntervalMs,
948
+ micRecoveryMaxRetries,
918
949
  maxSessionCount,
919
950
  pendingMediaTtlMs,
920
951
  ...uaCfg
921
952
  } = config;
922
953
  this.maxSessionCount = typeof maxSessionCount === "number" ? maxSessionCount : Infinity;
954
+ this.micRecoveryEnabled = Boolean(enableMicRecovery);
955
+ if (typeof micRecoveryIntervalMs === "number") {
956
+ this.micRecoveryDefaults.intervalMs = micRecoveryIntervalMs;
957
+ }
958
+ if (typeof micRecoveryMaxRetries === "number") {
959
+ this.micRecoveryDefaults.maxRetries = micRecoveryMaxRetries;
960
+ }
923
961
  this.sessionManager.setPendingMediaTtl(pendingMediaTtlMs);
924
962
  const debug = cfgDebug ?? this.getPersistedDebug() ?? this.debugPattern;
925
963
  this.userAgent.start(uri, password, uaCfg, { debug });
@@ -927,6 +965,14 @@ var SipClient = class extends EventTargetEmitter {
927
965
  this.attachBeforeUnload();
928
966
  this.syncDebugInspector(debug);
929
967
  }
968
+ setMicrophoneProvider(fn) {
969
+ this.requestMicrophoneStream = fn;
970
+ if (fn && this.micRecoveryEnabled) {
971
+ this.sessionManager.getSessions().forEach(({ id }) => {
972
+ this.enableMicrophoneRecovery(id);
973
+ });
974
+ }
975
+ }
930
976
  registerUA() {
931
977
  this.userAgent.register();
932
978
  }
@@ -938,6 +984,21 @@ var SipClient = class extends EventTargetEmitter {
938
984
  this.stateStore.reset();
939
985
  }
940
986
  call(target, callOptions = {}) {
987
+ if (!callOptions.mediaStream && this.requestMicrophoneStream) {
988
+ void this.requestMicrophoneStream().then((stream) => {
989
+ if (!stream)
990
+ throw new Error("microphone stream unavailable");
991
+ this.call(target, { ...callOptions, mediaStream: stream });
992
+ }).catch((e) => {
993
+ const err = this.emitError(
994
+ e,
995
+ "MICROPHONE_FAILED",
996
+ "microphone failed"
997
+ );
998
+ this.stateStore.batchSet({ error: err.cause });
999
+ });
1000
+ return;
1001
+ }
941
1002
  try {
942
1003
  const opts = this.ensureMediaConstraints(callOptions);
943
1004
  if (opts.mediaStream)
@@ -1008,6 +1069,9 @@ var SipClient = class extends EventTargetEmitter {
1008
1069
  if (h)
1009
1070
  session.on(ev, h);
1010
1071
  });
1072
+ if (this.requestMicrophoneStream && this.micRecoveryEnabled) {
1073
+ this.enableMicrophoneRecovery(sessionId);
1074
+ }
1011
1075
  }
1012
1076
  detachSessionHandlers(sessionId, session) {
1013
1077
  const handlers = this.sessionHandlers.get(sessionId);
@@ -1033,11 +1097,14 @@ var SipClient = class extends EventTargetEmitter {
1033
1097
  cleanupSession(sessionId, session) {
1034
1098
  const targetSession = session ?? this.sessionManager.getSession(sessionId) ?? this.sessionManager.getRtc(sessionId)?.currentSession;
1035
1099
  this.detachSessionHandlers(sessionId, targetSession);
1100
+ this.disableMicrophoneRecovery(sessionId);
1036
1101
  this.sessionManager.cleanupSession(sessionId);
1037
1102
  removeSessionState(this.stateStore, sessionId);
1038
1103
  }
1039
1104
  cleanupAllSessions() {
1040
1105
  this.sessionManager.cleanupAllSessions();
1106
+ this.micRecovery.forEach((entry) => entry.stop());
1107
+ this.micRecovery.clear();
1041
1108
  this.sessionHandlers.clear();
1042
1109
  this.stateStore.setState({
1043
1110
  sessions: [],
@@ -1098,6 +1165,21 @@ var SipClient = class extends EventTargetEmitter {
1098
1165
  answerSession(sessionId, options = {}) {
1099
1166
  if (!sessionId || !this.sessionExists(sessionId))
1100
1167
  return false;
1168
+ if (!options.mediaStream && this.requestMicrophoneStream) {
1169
+ void this.requestMicrophoneStream().then((stream) => {
1170
+ if (!stream)
1171
+ throw new Error("microphone stream unavailable");
1172
+ this.answerSession(sessionId, { ...options, mediaStream: stream });
1173
+ }).catch((e) => {
1174
+ const err = this.emitError(
1175
+ e,
1176
+ "MICROPHONE_FAILED",
1177
+ "microphone failed"
1178
+ );
1179
+ this.stateStore.batchSet({ error: err.cause });
1180
+ });
1181
+ return true;
1182
+ }
1101
1183
  const opts = this.ensureMediaConstraints(options);
1102
1184
  return this.sessionManager.answer(sessionId, opts);
1103
1185
  }
@@ -1156,6 +1238,83 @@ var SipClient = class extends EventTargetEmitter {
1156
1238
  setSessionMedia(sessionId, stream) {
1157
1239
  this.sessionManager.setSessionMedia(sessionId, stream);
1158
1240
  }
1241
+ enableMicrophoneRecovery(sessionId, options = {}) {
1242
+ const resolved = this.resolveExistingSessionId(sessionId);
1243
+ if (!resolved)
1244
+ return () => {
1245
+ };
1246
+ if (!this.requestMicrophoneStream)
1247
+ return () => {
1248
+ };
1249
+ this.disableMicrophoneRecovery(resolved);
1250
+ const intervalMs = options.intervalMs ?? this.micRecoveryDefaults.intervalMs;
1251
+ const maxRetries = options.maxRetries ?? this.micRecoveryDefaults.maxRetries;
1252
+ let retries = 0;
1253
+ let stopped = false;
1254
+ const tick = async () => {
1255
+ if (stopped || retries >= maxRetries)
1256
+ return;
1257
+ const rtc = this.sessionManager.getRtc(resolved);
1258
+ const session2 = this.sessionManager.getSession(resolved);
1259
+ if (!rtc || !session2)
1260
+ return;
1261
+ const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
1262
+ if (sessionState?.muted)
1263
+ return;
1264
+ const stream = rtc.mediaStream;
1265
+ const track = stream?.getAudioTracks?.()[0];
1266
+ const sender = session2?.connection?.getSenders?.().find((s) => s.track?.kind === "audio");
1267
+ const trackLive = track?.readyState === "live";
1268
+ const senderLive = sender?.track?.readyState === "live";
1269
+ if (trackLive && senderLive)
1270
+ return;
1271
+ retries += 1;
1272
+ let nextStream;
1273
+ try {
1274
+ if (!this.requestMicrophoneStream)
1275
+ return;
1276
+ nextStream = await this.requestMicrophoneStream();
1277
+ } catch (err) {
1278
+ console.warn("[sip] mic recovery failed to get stream", err);
1279
+ return;
1280
+ }
1281
+ const nextTrack = nextStream.getAudioTracks()[0];
1282
+ if (!nextTrack)
1283
+ return;
1284
+ await rtc.replaceAudioTrack(nextTrack);
1285
+ this.sessionManager.setSessionMedia(resolved, nextStream);
1286
+ };
1287
+ const timer = setInterval(() => {
1288
+ void tick();
1289
+ }, intervalMs);
1290
+ void tick();
1291
+ const session = this.sessionManager.getSession(resolved);
1292
+ const pc = session?.connection;
1293
+ const onIceChange = () => {
1294
+ const state = pc?.iceConnectionState;
1295
+ if (state === "failed" || state === "disconnected")
1296
+ void tick();
1297
+ };
1298
+ pc?.addEventListener?.("iceconnectionstatechange", onIceChange);
1299
+ const stop = () => {
1300
+ stopped = true;
1301
+ clearInterval(timer);
1302
+ pc?.removeEventListener?.("iceconnectionstatechange", onIceChange);
1303
+ };
1304
+ this.micRecovery.set(resolved, { stop });
1305
+ return stop;
1306
+ }
1307
+ disableMicrophoneRecovery(sessionId) {
1308
+ const resolved = this.resolveExistingSessionId(sessionId);
1309
+ if (!resolved)
1310
+ return false;
1311
+ const entry = this.micRecovery.get(resolved);
1312
+ if (!entry)
1313
+ return false;
1314
+ entry.stop();
1315
+ this.micRecovery.delete(resolved);
1316
+ return true;
1317
+ }
1159
1318
  switchCameraSession(sessionId, track) {
1160
1319
  if (!this.sessionExists(sessionId))
1161
1320
  return false;
@@ -1247,8 +1406,6 @@ var SipClient = class extends EventTargetEmitter {
1247
1406
  const persisted = window.sessionStorage.getItem(SESSION_DEBUG_KEY);
1248
1407
  if (!persisted)
1249
1408
  return void 0;
1250
- if (persisted === "true")
1251
- return true;
1252
1409
  return persisted;
1253
1410
  } catch {
1254
1411
  return void 0;
@@ -1273,9 +1430,9 @@ function createSipEventManager(client) {
1273
1430
  }
1274
1431
  };
1275
1432
  }
1276
- var SipContext = react.createContext(null);
1433
+ var SipContext = React.createContext(null);
1277
1434
  function useSip() {
1278
- const ctx = react.useContext(SipContext);
1435
+ const ctx = React.useContext(SipContext);
1279
1436
  if (!ctx)
1280
1437
  throw new Error("Must be used within SipProvider");
1281
1438
  return ctx;
@@ -1284,16 +1441,16 @@ function useSip() {
1284
1441
  // src/hooks/useSipState.ts
1285
1442
  function useSipState() {
1286
1443
  const { client } = useSip();
1287
- const subscribe = react.useCallback(
1444
+ const subscribe = React.useCallback(
1288
1445
  (onStoreChange) => client.onChange(onStoreChange),
1289
1446
  [client]
1290
1447
  );
1291
- const getSnapshot = react.useCallback(() => client.state, [client]);
1292
- return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1448
+ const getSnapshot = React.useCallback(() => client.state, [client]);
1449
+ return React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1293
1450
  }
1294
1451
  function useSipActions() {
1295
1452
  const { client } = useSip();
1296
- return react.useMemo(
1453
+ return React.useMemo(
1297
1454
  () => ({
1298
1455
  call: (...args) => client.call(...args),
1299
1456
  answer: (...args) => client.answerSession(...args),
@@ -1306,6 +1463,8 @@ function useSipActions() {
1306
1463
  getSessionIds: () => client.getSessionIds(),
1307
1464
  getSessions: () => client.getSessions(),
1308
1465
  setSessionMedia: (...args) => client.setSessionMedia(...args),
1466
+ enableMicrophoneRecovery: (...args) => client.enableMicrophoneRecovery(...args),
1467
+ disableMicrophoneRecovery: (...args) => client.disableMicrophoneRecovery(...args),
1309
1468
  switchCamera: (...args) => client.switchCameraSession(...args),
1310
1469
  enableVideo: (...args) => client.enableVideoSession(...args),
1311
1470
  disableVideo: (...args) => client.disableVideoSession(...args)
@@ -1321,7 +1480,7 @@ function useSipSessions() {
1321
1480
  }
1322
1481
  function useSipEvent(event, handler) {
1323
1482
  const { sipEventManager } = useSip();
1324
- react.useEffect(() => {
1483
+ React.useEffect(() => {
1325
1484
  if (!handler)
1326
1485
  return;
1327
1486
  return sipEventManager.onUA(event, handler);
@@ -1329,7 +1488,7 @@ function useSipEvent(event, handler) {
1329
1488
  }
1330
1489
  function useSipSessionEvent(sessionId, event, handler) {
1331
1490
  const { sipEventManager } = useSip();
1332
- react.useEffect(() => {
1491
+ React.useEffect(() => {
1333
1492
  if (!handler)
1334
1493
  return;
1335
1494
  return sipEventManager.onSession(sessionId, event, handler);
@@ -1427,8 +1586,8 @@ function createCallPlayer(audioEl) {
1427
1586
  }
1428
1587
  function CallPlayer({ sessionId }) {
1429
1588
  const { client } = useSip();
1430
- const audioRef = react.useRef(null);
1431
- react.useEffect(() => {
1589
+ const audioRef = React.useRef(null);
1590
+ React.useEffect(() => {
1432
1591
  if (!audioRef.current)
1433
1592
  return;
1434
1593
  const player = createCallPlayer(audioRef.current);
@@ -1444,13 +1603,17 @@ function CallPlayer({ sessionId }) {
1444
1603
  function SipProvider({
1445
1604
  client,
1446
1605
  children,
1447
- sipEventManager
1606
+ sipEventManager,
1607
+ requestMicrophoneStream
1448
1608
  }) {
1449
- const manager = react.useMemo(
1609
+ const manager = React.useMemo(
1450
1610
  () => sipEventManager ?? createSipEventManager(client),
1451
1611
  [client, sipEventManager]
1452
1612
  );
1453
- const contextValue = react.useMemo(() => ({ client, sipEventManager: manager }), [client, manager]);
1613
+ React__default.default.useEffect(() => {
1614
+ client.setMicrophoneProvider(requestMicrophoneStream);
1615
+ }, [client, requestMicrophoneStream]);
1616
+ const contextValue = React.useMemo(() => ({ client, sipEventManager: manager }), [client, manager]);
1454
1617
  return /* @__PURE__ */ jsxRuntime.jsx(SipContext.Provider, { value: contextValue, children });
1455
1618
  }
1456
1619