react-jssip-kit 0.6.9 → 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 +175 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +162 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var JsSIP = require('jssip');
|
|
4
|
-
var
|
|
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 {
|
|
@@ -679,6 +680,23 @@ var WebRTCSessionController = class {
|
|
|
679
680
|
old.stop();
|
|
680
681
|
return true;
|
|
681
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
|
+
}
|
|
682
700
|
};
|
|
683
701
|
|
|
684
702
|
// src/jssip-lib/sip/sessionManager.ts
|
|
@@ -886,6 +904,12 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
886
904
|
this.sessionHandlers = /* @__PURE__ */ new Map();
|
|
887
905
|
this.maxSessionCount = Infinity;
|
|
888
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
|
+
};
|
|
889
913
|
this.errorHandler = options.errorHandler ?? new SipErrorHandler({
|
|
890
914
|
formatter: options.formatError,
|
|
891
915
|
messages: options.errorMessages
|
|
@@ -919,11 +943,21 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
919
943
|
this.stateStore.setState({ sipStatus: SipStatus.Connecting });
|
|
920
944
|
const {
|
|
921
945
|
debug: cfgDebug,
|
|
946
|
+
enableMicRecovery,
|
|
947
|
+
micRecoveryIntervalMs,
|
|
948
|
+
micRecoveryMaxRetries,
|
|
922
949
|
maxSessionCount,
|
|
923
950
|
pendingMediaTtlMs,
|
|
924
951
|
...uaCfg
|
|
925
952
|
} = config;
|
|
926
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
|
+
}
|
|
927
961
|
this.sessionManager.setPendingMediaTtl(pendingMediaTtlMs);
|
|
928
962
|
const debug = cfgDebug ?? this.getPersistedDebug() ?? this.debugPattern;
|
|
929
963
|
this.userAgent.start(uri, password, uaCfg, { debug });
|
|
@@ -931,6 +965,14 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
931
965
|
this.attachBeforeUnload();
|
|
932
966
|
this.syncDebugInspector(debug);
|
|
933
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
|
+
}
|
|
934
976
|
registerUA() {
|
|
935
977
|
this.userAgent.register();
|
|
936
978
|
}
|
|
@@ -942,6 +984,21 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
942
984
|
this.stateStore.reset();
|
|
943
985
|
}
|
|
944
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
|
+
}
|
|
945
1002
|
try {
|
|
946
1003
|
const opts = this.ensureMediaConstraints(callOptions);
|
|
947
1004
|
if (opts.mediaStream)
|
|
@@ -1012,6 +1069,9 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1012
1069
|
if (h)
|
|
1013
1070
|
session.on(ev, h);
|
|
1014
1071
|
});
|
|
1072
|
+
if (this.requestMicrophoneStream && this.micRecoveryEnabled) {
|
|
1073
|
+
this.enableMicrophoneRecovery(sessionId);
|
|
1074
|
+
}
|
|
1015
1075
|
}
|
|
1016
1076
|
detachSessionHandlers(sessionId, session) {
|
|
1017
1077
|
const handlers = this.sessionHandlers.get(sessionId);
|
|
@@ -1037,11 +1097,14 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1037
1097
|
cleanupSession(sessionId, session) {
|
|
1038
1098
|
const targetSession = session ?? this.sessionManager.getSession(sessionId) ?? this.sessionManager.getRtc(sessionId)?.currentSession;
|
|
1039
1099
|
this.detachSessionHandlers(sessionId, targetSession);
|
|
1100
|
+
this.disableMicrophoneRecovery(sessionId);
|
|
1040
1101
|
this.sessionManager.cleanupSession(sessionId);
|
|
1041
1102
|
removeSessionState(this.stateStore, sessionId);
|
|
1042
1103
|
}
|
|
1043
1104
|
cleanupAllSessions() {
|
|
1044
1105
|
this.sessionManager.cleanupAllSessions();
|
|
1106
|
+
this.micRecovery.forEach((entry) => entry.stop());
|
|
1107
|
+
this.micRecovery.clear();
|
|
1045
1108
|
this.sessionHandlers.clear();
|
|
1046
1109
|
this.stateStore.setState({
|
|
1047
1110
|
sessions: [],
|
|
@@ -1102,6 +1165,21 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1102
1165
|
answerSession(sessionId, options = {}) {
|
|
1103
1166
|
if (!sessionId || !this.sessionExists(sessionId))
|
|
1104
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
|
+
}
|
|
1105
1183
|
const opts = this.ensureMediaConstraints(options);
|
|
1106
1184
|
return this.sessionManager.answer(sessionId, opts);
|
|
1107
1185
|
}
|
|
@@ -1160,6 +1238,83 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1160
1238
|
setSessionMedia(sessionId, stream) {
|
|
1161
1239
|
this.sessionManager.setSessionMedia(sessionId, stream);
|
|
1162
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
|
+
}
|
|
1163
1318
|
switchCameraSession(sessionId, track) {
|
|
1164
1319
|
if (!this.sessionExists(sessionId))
|
|
1165
1320
|
return false;
|
|
@@ -1251,8 +1406,6 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1251
1406
|
const persisted = window.sessionStorage.getItem(SESSION_DEBUG_KEY);
|
|
1252
1407
|
if (!persisted)
|
|
1253
1408
|
return void 0;
|
|
1254
|
-
if (persisted === "true")
|
|
1255
|
-
return true;
|
|
1256
1409
|
return persisted;
|
|
1257
1410
|
} catch {
|
|
1258
1411
|
return void 0;
|
|
@@ -1277,9 +1430,9 @@ function createSipEventManager(client) {
|
|
|
1277
1430
|
}
|
|
1278
1431
|
};
|
|
1279
1432
|
}
|
|
1280
|
-
var SipContext =
|
|
1433
|
+
var SipContext = React.createContext(null);
|
|
1281
1434
|
function useSip() {
|
|
1282
|
-
const ctx =
|
|
1435
|
+
const ctx = React.useContext(SipContext);
|
|
1283
1436
|
if (!ctx)
|
|
1284
1437
|
throw new Error("Must be used within SipProvider");
|
|
1285
1438
|
return ctx;
|
|
@@ -1288,16 +1441,16 @@ function useSip() {
|
|
|
1288
1441
|
// src/hooks/useSipState.ts
|
|
1289
1442
|
function useSipState() {
|
|
1290
1443
|
const { client } = useSip();
|
|
1291
|
-
const subscribe =
|
|
1444
|
+
const subscribe = React.useCallback(
|
|
1292
1445
|
(onStoreChange) => client.onChange(onStoreChange),
|
|
1293
1446
|
[client]
|
|
1294
1447
|
);
|
|
1295
|
-
const getSnapshot =
|
|
1296
|
-
return
|
|
1448
|
+
const getSnapshot = React.useCallback(() => client.state, [client]);
|
|
1449
|
+
return React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
1297
1450
|
}
|
|
1298
1451
|
function useSipActions() {
|
|
1299
1452
|
const { client } = useSip();
|
|
1300
|
-
return
|
|
1453
|
+
return React.useMemo(
|
|
1301
1454
|
() => ({
|
|
1302
1455
|
call: (...args) => client.call(...args),
|
|
1303
1456
|
answer: (...args) => client.answerSession(...args),
|
|
@@ -1310,6 +1463,8 @@ function useSipActions() {
|
|
|
1310
1463
|
getSessionIds: () => client.getSessionIds(),
|
|
1311
1464
|
getSessions: () => client.getSessions(),
|
|
1312
1465
|
setSessionMedia: (...args) => client.setSessionMedia(...args),
|
|
1466
|
+
enableMicrophoneRecovery: (...args) => client.enableMicrophoneRecovery(...args),
|
|
1467
|
+
disableMicrophoneRecovery: (...args) => client.disableMicrophoneRecovery(...args),
|
|
1313
1468
|
switchCamera: (...args) => client.switchCameraSession(...args),
|
|
1314
1469
|
enableVideo: (...args) => client.enableVideoSession(...args),
|
|
1315
1470
|
disableVideo: (...args) => client.disableVideoSession(...args)
|
|
@@ -1325,7 +1480,7 @@ function useSipSessions() {
|
|
|
1325
1480
|
}
|
|
1326
1481
|
function useSipEvent(event, handler) {
|
|
1327
1482
|
const { sipEventManager } = useSip();
|
|
1328
|
-
|
|
1483
|
+
React.useEffect(() => {
|
|
1329
1484
|
if (!handler)
|
|
1330
1485
|
return;
|
|
1331
1486
|
return sipEventManager.onUA(event, handler);
|
|
@@ -1333,7 +1488,7 @@ function useSipEvent(event, handler) {
|
|
|
1333
1488
|
}
|
|
1334
1489
|
function useSipSessionEvent(sessionId, event, handler) {
|
|
1335
1490
|
const { sipEventManager } = useSip();
|
|
1336
|
-
|
|
1491
|
+
React.useEffect(() => {
|
|
1337
1492
|
if (!handler)
|
|
1338
1493
|
return;
|
|
1339
1494
|
return sipEventManager.onSession(sessionId, event, handler);
|
|
@@ -1431,8 +1586,8 @@ function createCallPlayer(audioEl) {
|
|
|
1431
1586
|
}
|
|
1432
1587
|
function CallPlayer({ sessionId }) {
|
|
1433
1588
|
const { client } = useSip();
|
|
1434
|
-
const audioRef =
|
|
1435
|
-
|
|
1589
|
+
const audioRef = React.useRef(null);
|
|
1590
|
+
React.useEffect(() => {
|
|
1436
1591
|
if (!audioRef.current)
|
|
1437
1592
|
return;
|
|
1438
1593
|
const player = createCallPlayer(audioRef.current);
|
|
@@ -1448,13 +1603,17 @@ function CallPlayer({ sessionId }) {
|
|
|
1448
1603
|
function SipProvider({
|
|
1449
1604
|
client,
|
|
1450
1605
|
children,
|
|
1451
|
-
sipEventManager
|
|
1606
|
+
sipEventManager,
|
|
1607
|
+
requestMicrophoneStream
|
|
1452
1608
|
}) {
|
|
1453
|
-
const manager =
|
|
1609
|
+
const manager = React.useMemo(
|
|
1454
1610
|
() => sipEventManager ?? createSipEventManager(client),
|
|
1455
1611
|
[client, sipEventManager]
|
|
1456
1612
|
);
|
|
1457
|
-
|
|
1613
|
+
React__default.default.useEffect(() => {
|
|
1614
|
+
client.setMicrophoneProvider(requestMicrophoneStream);
|
|
1615
|
+
}, [client, requestMicrophoneStream]);
|
|
1616
|
+
const contextValue = React.useMemo(() => ({ client, sipEventManager: manager }), [client, manager]);
|
|
1458
1617
|
return /* @__PURE__ */ jsxRuntime.jsx(SipContext.Provider, { value: contextValue, children });
|
|
1459
1618
|
}
|
|
1460
1619
|
|