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.d.ts
CHANGED
|
@@ -41,6 +41,18 @@ type SipConfiguration = Omit<UAConfiguration, "password" | "uri"> & {
|
|
|
41
41
|
* Enable JsSIP debug logging. If string, treated as debug pattern.
|
|
42
42
|
*/
|
|
43
43
|
debug?: boolean | string;
|
|
44
|
+
/**
|
|
45
|
+
* Enable automatic microphone recovery for sessions.
|
|
46
|
+
*/
|
|
47
|
+
enableMicRecovery?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Interval between recovery attempts in milliseconds.
|
|
50
|
+
*/
|
|
51
|
+
micRecoveryIntervalMs?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Maximum number of recovery attempts per session.
|
|
54
|
+
*/
|
|
55
|
+
micRecoveryMaxRetries?: number;
|
|
44
56
|
/**
|
|
45
57
|
* Maximum allowed concurrent sessions. Additional sessions are rejected.
|
|
46
58
|
*/
|
|
@@ -161,6 +173,10 @@ type SipClientOptions = {
|
|
|
161
173
|
errorHandler?: SipErrorHandler;
|
|
162
174
|
debug?: boolean | string;
|
|
163
175
|
};
|
|
176
|
+
type MicrophoneRecoveryOptions = {
|
|
177
|
+
intervalMs?: number;
|
|
178
|
+
maxRetries?: number;
|
|
179
|
+
};
|
|
164
180
|
declare class SipClient extends EventTargetEmitter<JsSIPEventMap> {
|
|
165
181
|
readonly userAgent: SipUserAgent;
|
|
166
182
|
readonly stateStore: SipStateStore;
|
|
@@ -172,11 +188,16 @@ declare class SipClient extends EventTargetEmitter<JsSIPEventMap> {
|
|
|
172
188
|
private maxSessionCount;
|
|
173
189
|
private sessionManager;
|
|
174
190
|
private lifecycle;
|
|
191
|
+
private micRecovery;
|
|
192
|
+
private requestMicrophoneStream?;
|
|
193
|
+
private micRecoveryEnabled;
|
|
194
|
+
private micRecoveryDefaults;
|
|
175
195
|
private unloadHandler?;
|
|
176
196
|
private stateLogOff?;
|
|
177
197
|
get state(): SipState;
|
|
178
198
|
constructor(options?: SipClientOptions);
|
|
179
199
|
connect(uri: string, password: string, config: SipConfiguration): void;
|
|
200
|
+
setMicrophoneProvider(fn?: () => Promise<MediaStream>): void;
|
|
180
201
|
registerUA(): void;
|
|
181
202
|
disconnect(): void;
|
|
182
203
|
call(target: string, callOptions?: CallOptions): void;
|
|
@@ -210,6 +231,8 @@ declare class SipClient extends EventTargetEmitter<JsSIPEventMap> {
|
|
|
210
231
|
sendDTMFSession(sessionId: string, tones: string | number, options?: DTMFOptions): boolean;
|
|
211
232
|
transferSession(sessionId: string, target: string, options?: ReferOptions): boolean;
|
|
212
233
|
setSessionMedia(sessionId: string, stream: MediaStream): void;
|
|
234
|
+
enableMicrophoneRecovery(sessionId: string, options?: MicrophoneRecoveryOptions): () => void;
|
|
235
|
+
disableMicrophoneRecovery(sessionId: string): boolean;
|
|
213
236
|
switchCameraSession(sessionId: string, track: MediaStreamTrack): false | Promise<boolean>;
|
|
214
237
|
enableVideoSession(sessionId: string): boolean;
|
|
215
238
|
disableVideoSession(sessionId: string): boolean;
|
|
@@ -252,6 +275,8 @@ declare function useSipActions(): {
|
|
|
252
275
|
session: jssip_src_RTCSession.RTCSession;
|
|
253
276
|
}[];
|
|
254
277
|
setSessionMedia: (sessionId: string, stream: MediaStream) => void;
|
|
278
|
+
enableMicrophoneRecovery: (sessionId: string, options?: MicrophoneRecoveryOptions | undefined) => () => void;
|
|
279
|
+
disableMicrophoneRecovery: (sessionId: string) => boolean;
|
|
255
280
|
switchCamera: (sessionId: string, track: MediaStreamTrack) => false | Promise<boolean>;
|
|
256
281
|
enableVideo: (sessionId: string) => boolean;
|
|
257
282
|
disableVideo: (sessionId: string) => boolean;
|
|
@@ -268,10 +293,11 @@ declare function CallPlayer({ sessionId }: {
|
|
|
268
293
|
sessionId?: string;
|
|
269
294
|
}): react_jsx_runtime.JSX.Element;
|
|
270
295
|
|
|
271
|
-
declare function SipProvider({ client, children, sipEventManager, }: {
|
|
296
|
+
declare function SipProvider({ client, children, sipEventManager, requestMicrophoneStream, }: {
|
|
272
297
|
sipEventManager?: SipEventManager;
|
|
273
298
|
client: SipClient;
|
|
274
299
|
children: react__default.ReactNode;
|
|
300
|
+
requestMicrophoneStream?: () => Promise<MediaStream>;
|
|
275
301
|
}): react_jsx_runtime.JSX.Element;
|
|
276
302
|
|
|
277
303
|
export { type CallDirection, type CallDirection as CallDirectionType, CallPlayer, CallStatus, CallStatus as CallStatusType, type JsSIPEventMap, type JsSIPEventName, type SessionEventName, type SessionEventPayload, type SipConfiguration, SipContext, type SipContextType, type SipEventHandlers, type SipEventManager, SipProvider, type SipSessionState, type SipState, SipStatus, SipStatus as SipStatusType, type UAEventName, type UAEventPayload, createSipClientInstance, createSipEventManager, useSip, useSipActions, useSipEvent, useSipSessionEvent, useSipSessions, useSipState };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import JsSIP from 'jssip';
|
|
2
2
|
export { WebSocketInterface } from 'jssip';
|
|
3
|
-
import { createContext, useContext, useCallback, useSyncExternalStore, useMemo, useEffect, useRef } from 'react';
|
|
3
|
+
import React, { createContext, useContext, useCallback, useSyncExternalStore, useMemo, useEffect, useRef } from 'react';
|
|
4
4
|
import { jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
// src/jssip-lib/sip/debugger.ts
|
|
@@ -674,6 +674,23 @@ var WebRTCSessionController = class {
|
|
|
674
674
|
old.stop();
|
|
675
675
|
return true;
|
|
676
676
|
}
|
|
677
|
+
async replaceAudioTrack(nextAudioTrack) {
|
|
678
|
+
const pc = this.getPC();
|
|
679
|
+
if (!pc)
|
|
680
|
+
return false;
|
|
681
|
+
if (!this.mediaStream)
|
|
682
|
+
this.mediaStream = new MediaStream();
|
|
683
|
+
const old = this.mediaStream.getAudioTracks()[0];
|
|
684
|
+
this.mediaStream.addTrack(nextAudioTrack);
|
|
685
|
+
if (old)
|
|
686
|
+
this.mediaStream.removeTrack(old);
|
|
687
|
+
const sender = pc.getSenders?.().find((s) => s.track?.kind === "audio");
|
|
688
|
+
if (sender)
|
|
689
|
+
await sender.replaceTrack(nextAudioTrack);
|
|
690
|
+
if (old && old !== nextAudioTrack)
|
|
691
|
+
old.stop();
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
677
694
|
};
|
|
678
695
|
|
|
679
696
|
// src/jssip-lib/sip/sessionManager.ts
|
|
@@ -881,6 +898,12 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
881
898
|
this.sessionHandlers = /* @__PURE__ */ new Map();
|
|
882
899
|
this.maxSessionCount = Infinity;
|
|
883
900
|
this.sessionManager = new SessionManager();
|
|
901
|
+
this.micRecovery = /* @__PURE__ */ new Map();
|
|
902
|
+
this.micRecoveryEnabled = false;
|
|
903
|
+
this.micRecoveryDefaults = {
|
|
904
|
+
intervalMs: 2e3,
|
|
905
|
+
maxRetries: Infinity
|
|
906
|
+
};
|
|
884
907
|
this.errorHandler = options.errorHandler ?? new SipErrorHandler({
|
|
885
908
|
formatter: options.formatError,
|
|
886
909
|
messages: options.errorMessages
|
|
@@ -914,11 +937,21 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
914
937
|
this.stateStore.setState({ sipStatus: SipStatus.Connecting });
|
|
915
938
|
const {
|
|
916
939
|
debug: cfgDebug,
|
|
940
|
+
enableMicRecovery,
|
|
941
|
+
micRecoveryIntervalMs,
|
|
942
|
+
micRecoveryMaxRetries,
|
|
917
943
|
maxSessionCount,
|
|
918
944
|
pendingMediaTtlMs,
|
|
919
945
|
...uaCfg
|
|
920
946
|
} = config;
|
|
921
947
|
this.maxSessionCount = typeof maxSessionCount === "number" ? maxSessionCount : Infinity;
|
|
948
|
+
this.micRecoveryEnabled = Boolean(enableMicRecovery);
|
|
949
|
+
if (typeof micRecoveryIntervalMs === "number") {
|
|
950
|
+
this.micRecoveryDefaults.intervalMs = micRecoveryIntervalMs;
|
|
951
|
+
}
|
|
952
|
+
if (typeof micRecoveryMaxRetries === "number") {
|
|
953
|
+
this.micRecoveryDefaults.maxRetries = micRecoveryMaxRetries;
|
|
954
|
+
}
|
|
922
955
|
this.sessionManager.setPendingMediaTtl(pendingMediaTtlMs);
|
|
923
956
|
const debug = cfgDebug ?? this.getPersistedDebug() ?? this.debugPattern;
|
|
924
957
|
this.userAgent.start(uri, password, uaCfg, { debug });
|
|
@@ -926,6 +959,14 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
926
959
|
this.attachBeforeUnload();
|
|
927
960
|
this.syncDebugInspector(debug);
|
|
928
961
|
}
|
|
962
|
+
setMicrophoneProvider(fn) {
|
|
963
|
+
this.requestMicrophoneStream = fn;
|
|
964
|
+
if (fn && this.micRecoveryEnabled) {
|
|
965
|
+
this.sessionManager.getSessions().forEach(({ id }) => {
|
|
966
|
+
this.enableMicrophoneRecovery(id);
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
}
|
|
929
970
|
registerUA() {
|
|
930
971
|
this.userAgent.register();
|
|
931
972
|
}
|
|
@@ -937,6 +978,21 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
937
978
|
this.stateStore.reset();
|
|
938
979
|
}
|
|
939
980
|
call(target, callOptions = {}) {
|
|
981
|
+
if (!callOptions.mediaStream && this.requestMicrophoneStream) {
|
|
982
|
+
void this.requestMicrophoneStream().then((stream) => {
|
|
983
|
+
if (!stream)
|
|
984
|
+
throw new Error("microphone stream unavailable");
|
|
985
|
+
this.call(target, { ...callOptions, mediaStream: stream });
|
|
986
|
+
}).catch((e) => {
|
|
987
|
+
const err = this.emitError(
|
|
988
|
+
e,
|
|
989
|
+
"MICROPHONE_FAILED",
|
|
990
|
+
"microphone failed"
|
|
991
|
+
);
|
|
992
|
+
this.stateStore.batchSet({ error: err.cause });
|
|
993
|
+
});
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
940
996
|
try {
|
|
941
997
|
const opts = this.ensureMediaConstraints(callOptions);
|
|
942
998
|
if (opts.mediaStream)
|
|
@@ -1007,6 +1063,9 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1007
1063
|
if (h)
|
|
1008
1064
|
session.on(ev, h);
|
|
1009
1065
|
});
|
|
1066
|
+
if (this.requestMicrophoneStream && this.micRecoveryEnabled) {
|
|
1067
|
+
this.enableMicrophoneRecovery(sessionId);
|
|
1068
|
+
}
|
|
1010
1069
|
}
|
|
1011
1070
|
detachSessionHandlers(sessionId, session) {
|
|
1012
1071
|
const handlers = this.sessionHandlers.get(sessionId);
|
|
@@ -1032,11 +1091,14 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1032
1091
|
cleanupSession(sessionId, session) {
|
|
1033
1092
|
const targetSession = session ?? this.sessionManager.getSession(sessionId) ?? this.sessionManager.getRtc(sessionId)?.currentSession;
|
|
1034
1093
|
this.detachSessionHandlers(sessionId, targetSession);
|
|
1094
|
+
this.disableMicrophoneRecovery(sessionId);
|
|
1035
1095
|
this.sessionManager.cleanupSession(sessionId);
|
|
1036
1096
|
removeSessionState(this.stateStore, sessionId);
|
|
1037
1097
|
}
|
|
1038
1098
|
cleanupAllSessions() {
|
|
1039
1099
|
this.sessionManager.cleanupAllSessions();
|
|
1100
|
+
this.micRecovery.forEach((entry) => entry.stop());
|
|
1101
|
+
this.micRecovery.clear();
|
|
1040
1102
|
this.sessionHandlers.clear();
|
|
1041
1103
|
this.stateStore.setState({
|
|
1042
1104
|
sessions: [],
|
|
@@ -1097,6 +1159,21 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1097
1159
|
answerSession(sessionId, options = {}) {
|
|
1098
1160
|
if (!sessionId || !this.sessionExists(sessionId))
|
|
1099
1161
|
return false;
|
|
1162
|
+
if (!options.mediaStream && this.requestMicrophoneStream) {
|
|
1163
|
+
void this.requestMicrophoneStream().then((stream) => {
|
|
1164
|
+
if (!stream)
|
|
1165
|
+
throw new Error("microphone stream unavailable");
|
|
1166
|
+
this.answerSession(sessionId, { ...options, mediaStream: stream });
|
|
1167
|
+
}).catch((e) => {
|
|
1168
|
+
const err = this.emitError(
|
|
1169
|
+
e,
|
|
1170
|
+
"MICROPHONE_FAILED",
|
|
1171
|
+
"microphone failed"
|
|
1172
|
+
);
|
|
1173
|
+
this.stateStore.batchSet({ error: err.cause });
|
|
1174
|
+
});
|
|
1175
|
+
return true;
|
|
1176
|
+
}
|
|
1100
1177
|
const opts = this.ensureMediaConstraints(options);
|
|
1101
1178
|
return this.sessionManager.answer(sessionId, opts);
|
|
1102
1179
|
}
|
|
@@ -1155,6 +1232,83 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1155
1232
|
setSessionMedia(sessionId, stream) {
|
|
1156
1233
|
this.sessionManager.setSessionMedia(sessionId, stream);
|
|
1157
1234
|
}
|
|
1235
|
+
enableMicrophoneRecovery(sessionId, options = {}) {
|
|
1236
|
+
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1237
|
+
if (!resolved)
|
|
1238
|
+
return () => {
|
|
1239
|
+
};
|
|
1240
|
+
if (!this.requestMicrophoneStream)
|
|
1241
|
+
return () => {
|
|
1242
|
+
};
|
|
1243
|
+
this.disableMicrophoneRecovery(resolved);
|
|
1244
|
+
const intervalMs = options.intervalMs ?? this.micRecoveryDefaults.intervalMs;
|
|
1245
|
+
const maxRetries = options.maxRetries ?? this.micRecoveryDefaults.maxRetries;
|
|
1246
|
+
let retries = 0;
|
|
1247
|
+
let stopped = false;
|
|
1248
|
+
const tick = async () => {
|
|
1249
|
+
if (stopped || retries >= maxRetries)
|
|
1250
|
+
return;
|
|
1251
|
+
const rtc = this.sessionManager.getRtc(resolved);
|
|
1252
|
+
const session2 = this.sessionManager.getSession(resolved);
|
|
1253
|
+
if (!rtc || !session2)
|
|
1254
|
+
return;
|
|
1255
|
+
const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
|
|
1256
|
+
if (sessionState?.muted)
|
|
1257
|
+
return;
|
|
1258
|
+
const stream = rtc.mediaStream;
|
|
1259
|
+
const track = stream?.getAudioTracks?.()[0];
|
|
1260
|
+
const sender = session2?.connection?.getSenders?.().find((s) => s.track?.kind === "audio");
|
|
1261
|
+
const trackLive = track?.readyState === "live";
|
|
1262
|
+
const senderLive = sender?.track?.readyState === "live";
|
|
1263
|
+
if (trackLive && senderLive)
|
|
1264
|
+
return;
|
|
1265
|
+
retries += 1;
|
|
1266
|
+
let nextStream;
|
|
1267
|
+
try {
|
|
1268
|
+
if (!this.requestMicrophoneStream)
|
|
1269
|
+
return;
|
|
1270
|
+
nextStream = await this.requestMicrophoneStream();
|
|
1271
|
+
} catch (err) {
|
|
1272
|
+
console.warn("[sip] mic recovery failed to get stream", err);
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
const nextTrack = nextStream.getAudioTracks()[0];
|
|
1276
|
+
if (!nextTrack)
|
|
1277
|
+
return;
|
|
1278
|
+
await rtc.replaceAudioTrack(nextTrack);
|
|
1279
|
+
this.sessionManager.setSessionMedia(resolved, nextStream);
|
|
1280
|
+
};
|
|
1281
|
+
const timer = setInterval(() => {
|
|
1282
|
+
void tick();
|
|
1283
|
+
}, intervalMs);
|
|
1284
|
+
void tick();
|
|
1285
|
+
const session = this.sessionManager.getSession(resolved);
|
|
1286
|
+
const pc = session?.connection;
|
|
1287
|
+
const onIceChange = () => {
|
|
1288
|
+
const state = pc?.iceConnectionState;
|
|
1289
|
+
if (state === "failed" || state === "disconnected")
|
|
1290
|
+
void tick();
|
|
1291
|
+
};
|
|
1292
|
+
pc?.addEventListener?.("iceconnectionstatechange", onIceChange);
|
|
1293
|
+
const stop = () => {
|
|
1294
|
+
stopped = true;
|
|
1295
|
+
clearInterval(timer);
|
|
1296
|
+
pc?.removeEventListener?.("iceconnectionstatechange", onIceChange);
|
|
1297
|
+
};
|
|
1298
|
+
this.micRecovery.set(resolved, { stop });
|
|
1299
|
+
return stop;
|
|
1300
|
+
}
|
|
1301
|
+
disableMicrophoneRecovery(sessionId) {
|
|
1302
|
+
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1303
|
+
if (!resolved)
|
|
1304
|
+
return false;
|
|
1305
|
+
const entry = this.micRecovery.get(resolved);
|
|
1306
|
+
if (!entry)
|
|
1307
|
+
return false;
|
|
1308
|
+
entry.stop();
|
|
1309
|
+
this.micRecovery.delete(resolved);
|
|
1310
|
+
return true;
|
|
1311
|
+
}
|
|
1158
1312
|
switchCameraSession(sessionId, track) {
|
|
1159
1313
|
if (!this.sessionExists(sessionId))
|
|
1160
1314
|
return false;
|
|
@@ -1246,8 +1400,6 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1246
1400
|
const persisted = window.sessionStorage.getItem(SESSION_DEBUG_KEY);
|
|
1247
1401
|
if (!persisted)
|
|
1248
1402
|
return void 0;
|
|
1249
|
-
if (persisted === "true")
|
|
1250
|
-
return true;
|
|
1251
1403
|
return persisted;
|
|
1252
1404
|
} catch {
|
|
1253
1405
|
return void 0;
|
|
@@ -1305,6 +1457,8 @@ function useSipActions() {
|
|
|
1305
1457
|
getSessionIds: () => client.getSessionIds(),
|
|
1306
1458
|
getSessions: () => client.getSessions(),
|
|
1307
1459
|
setSessionMedia: (...args) => client.setSessionMedia(...args),
|
|
1460
|
+
enableMicrophoneRecovery: (...args) => client.enableMicrophoneRecovery(...args),
|
|
1461
|
+
disableMicrophoneRecovery: (...args) => client.disableMicrophoneRecovery(...args),
|
|
1308
1462
|
switchCamera: (...args) => client.switchCameraSession(...args),
|
|
1309
1463
|
enableVideo: (...args) => client.enableVideoSession(...args),
|
|
1310
1464
|
disableVideo: (...args) => client.disableVideoSession(...args)
|
|
@@ -1443,12 +1597,16 @@ function CallPlayer({ sessionId }) {
|
|
|
1443
1597
|
function SipProvider({
|
|
1444
1598
|
client,
|
|
1445
1599
|
children,
|
|
1446
|
-
sipEventManager
|
|
1600
|
+
sipEventManager,
|
|
1601
|
+
requestMicrophoneStream
|
|
1447
1602
|
}) {
|
|
1448
1603
|
const manager = useMemo(
|
|
1449
1604
|
() => sipEventManager ?? createSipEventManager(client),
|
|
1450
1605
|
[client, sipEventManager]
|
|
1451
1606
|
);
|
|
1607
|
+
React.useEffect(() => {
|
|
1608
|
+
client.setMicrophoneProvider(requestMicrophoneStream);
|
|
1609
|
+
}, [client, requestMicrophoneStream]);
|
|
1452
1610
|
const contextValue = useMemo(() => ({ client, sipEventManager: manager }), [client, manager]);
|
|
1453
1611
|
return /* @__PURE__ */ jsx(SipContext.Provider, { value: contextValue, children });
|
|
1454
1612
|
}
|