react-jssip-kit 0.7.8 → 1.0.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/CHANGELOG.md +17 -0
- package/README.md +91 -17
- package/dist/index.cjs +1270 -1190
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -144
- package/dist/index.d.ts +121 -144
- package/dist/index.js +1266 -1190
- package/dist/index.js.map +1 -1
- package/package.json +5 -6
package/dist/index.cjs
CHANGED
|
@@ -8,7 +8,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
8
8
|
|
|
9
9
|
var JsSIP__default = /*#__PURE__*/_interopDefault(JsSIP);
|
|
10
10
|
|
|
11
|
-
// src/
|
|
11
|
+
// src/core/modules/debug/sip-debugger.ts
|
|
12
12
|
var SipDebugger = class {
|
|
13
13
|
constructor(storageKey = "sip-debug-enabled", defaultPattern = "JsSIP:*") {
|
|
14
14
|
this.enabled = false;
|
|
@@ -27,7 +27,6 @@ var SipDebugger = class {
|
|
|
27
27
|
try {
|
|
28
28
|
if (typeof JsSIP__default.default?.debug?.enable === "function") {
|
|
29
29
|
JsSIP__default.default.debug.enable(pattern);
|
|
30
|
-
this.logger = console;
|
|
31
30
|
}
|
|
32
31
|
storage?.setItem?.(this.storageKey, pattern || this.defaultPattern);
|
|
33
32
|
try {
|
|
@@ -119,6 +118,25 @@ if (typeof window !== "undefined") {
|
|
|
119
118
|
sipDebugger.attachToWindow();
|
|
120
119
|
sipDebugger.initFromSession();
|
|
121
120
|
}
|
|
121
|
+
|
|
122
|
+
// src/core/contracts/state.ts
|
|
123
|
+
var SipStatus = {
|
|
124
|
+
Disconnected: "disconnected",
|
|
125
|
+
Connecting: "connecting",
|
|
126
|
+
Connected: "connected",
|
|
127
|
+
Registered: "registered",
|
|
128
|
+
Unregistered: "unregistered",
|
|
129
|
+
RegistrationFailed: "registrationFailed"
|
|
130
|
+
};
|
|
131
|
+
var CallStatus = {
|
|
132
|
+
Idle: "idle",
|
|
133
|
+
Dialing: "dialing",
|
|
134
|
+
Ringing: "ringing",
|
|
135
|
+
Active: "active",
|
|
136
|
+
Hold: "hold"
|
|
137
|
+
};
|
|
138
|
+
Object.values(SipStatus);
|
|
139
|
+
Object.values(CallStatus);
|
|
122
140
|
var SipUserAgent = class {
|
|
123
141
|
constructor() {
|
|
124
142
|
this._ua = null;
|
|
@@ -229,26 +247,7 @@ var SipUserAgent = class {
|
|
|
229
247
|
}
|
|
230
248
|
};
|
|
231
249
|
|
|
232
|
-
// src/
|
|
233
|
-
var SipStatus = {
|
|
234
|
-
Disconnected: "disconnected",
|
|
235
|
-
Connecting: "connecting",
|
|
236
|
-
Connected: "connected",
|
|
237
|
-
Registered: "registered",
|
|
238
|
-
Unregistered: "unregistered",
|
|
239
|
-
RegistrationFailed: "registrationFailed"
|
|
240
|
-
};
|
|
241
|
-
var CallStatus = {
|
|
242
|
-
Idle: "idle",
|
|
243
|
-
Dialing: "dialing",
|
|
244
|
-
Ringing: "ringing",
|
|
245
|
-
Active: "active",
|
|
246
|
-
Hold: "hold"
|
|
247
|
-
};
|
|
248
|
-
Object.values(SipStatus);
|
|
249
|
-
Object.values(CallStatus);
|
|
250
|
-
|
|
251
|
-
// src/jssip-lib/core/eventEmitter.ts
|
|
250
|
+
// src/core/modules/event/event-target.emitter.ts
|
|
252
251
|
var EventTargetEmitter = class {
|
|
253
252
|
constructor() {
|
|
254
253
|
this.target = new EventTarget();
|
|
@@ -265,56 +264,14 @@ var EventTargetEmitter = class {
|
|
|
265
264
|
}
|
|
266
265
|
};
|
|
267
266
|
|
|
268
|
-
// src/
|
|
269
|
-
var SipErrorHandler = class {
|
|
270
|
-
constructor(options = {}) {
|
|
271
|
-
this.formatter = options.formatter;
|
|
272
|
-
this.messages = options.messages;
|
|
273
|
-
}
|
|
274
|
-
format(input) {
|
|
275
|
-
const { code, raw, fallback } = input;
|
|
276
|
-
const mappedMessage = code && this.messages ? this.messages[code] : void 0;
|
|
277
|
-
if (this.formatter) {
|
|
278
|
-
const custom = this.formatter({
|
|
279
|
-
raw,
|
|
280
|
-
code,
|
|
281
|
-
fallback: mappedMessage ?? fallback
|
|
282
|
-
});
|
|
283
|
-
const message2 = custom?.message ?? custom?.cause ?? mappedMessage ?? fallback ?? this.readRawMessage(raw) ?? "unknown error";
|
|
284
|
-
return {
|
|
285
|
-
cause: custom?.cause ?? message2,
|
|
286
|
-
code: custom?.code ?? code,
|
|
287
|
-
raw: custom?.raw ?? raw,
|
|
288
|
-
message: message2
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
const message = mappedMessage ?? this.readRawMessage(raw) ?? fallback ?? "unknown error";
|
|
292
|
-
return {
|
|
293
|
-
cause: message,
|
|
294
|
-
code,
|
|
295
|
-
raw,
|
|
296
|
-
message
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
readRawMessage(raw) {
|
|
300
|
-
if (raw == null)
|
|
301
|
-
return void 0;
|
|
302
|
-
if (typeof raw === "string")
|
|
303
|
-
return raw;
|
|
304
|
-
if (typeof raw?.cause === "string")
|
|
305
|
-
return raw.cause;
|
|
306
|
-
if (typeof raw?.message === "string")
|
|
307
|
-
return raw.message;
|
|
308
|
-
return void 0;
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
// src/jssip-lib/core/sipState.ts
|
|
267
|
+
// src/core/modules/state/sip.state.ts
|
|
313
268
|
function getInitialSipState() {
|
|
314
269
|
return {
|
|
315
270
|
sipStatus: SipStatus.Disconnected,
|
|
316
271
|
error: null,
|
|
317
|
-
sessions: []
|
|
272
|
+
sessions: [],
|
|
273
|
+
sessionsById: {},
|
|
274
|
+
sessionIds: []
|
|
318
275
|
};
|
|
319
276
|
}
|
|
320
277
|
function shallowEqual(objA, objB) {
|
|
@@ -333,7 +290,7 @@ function shallowEqual(objA, objB) {
|
|
|
333
290
|
return true;
|
|
334
291
|
}
|
|
335
292
|
|
|
336
|
-
// src/
|
|
293
|
+
// src/core/modules/state/sip.state.store.ts
|
|
337
294
|
var SipStateStore = class {
|
|
338
295
|
constructor() {
|
|
339
296
|
this.state = getInitialSipState();
|
|
@@ -385,468 +342,90 @@ var SipStateStore = class {
|
|
|
385
342
|
}
|
|
386
343
|
};
|
|
387
344
|
|
|
388
|
-
// src/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
connected: (e) => {
|
|
397
|
-
emitter.emit("connected", e);
|
|
398
|
-
state.batchSet({ sipStatus: SipStatus.Connected });
|
|
399
|
-
},
|
|
400
|
-
disconnected: (e) => {
|
|
401
|
-
emitter.emit("disconnected", e);
|
|
402
|
-
cleanupAllSessions();
|
|
403
|
-
state.reset();
|
|
404
|
-
},
|
|
405
|
-
registered: (e) => {
|
|
406
|
-
emitter.emit("registered", e);
|
|
407
|
-
state.batchSet({ sipStatus: SipStatus.Registered, error: null });
|
|
408
|
-
},
|
|
409
|
-
unregistered: (e) => {
|
|
410
|
-
emitter.emit("unregistered", e);
|
|
411
|
-
state.batchSet({ sipStatus: SipStatus.Unregistered });
|
|
412
|
-
},
|
|
413
|
-
registrationFailed: (e) => {
|
|
414
|
-
emitter.emit("registrationFailed", e);
|
|
415
|
-
cleanupAllSessions();
|
|
416
|
-
emitError(
|
|
417
|
-
{
|
|
418
|
-
raw: e,
|
|
419
|
-
cause: e?.cause,
|
|
420
|
-
statusCode: e?.response?.status_code,
|
|
421
|
-
statusText: e?.response?.reason_phrase
|
|
422
|
-
},
|
|
423
|
-
"REGISTRATION_FAILED",
|
|
424
|
-
"registration failed"
|
|
425
|
-
);
|
|
426
|
-
state.batchSet({
|
|
427
|
-
sipStatus: SipStatus.RegistrationFailed,
|
|
428
|
-
error: e?.cause || "registration failed"
|
|
429
|
-
});
|
|
430
|
-
},
|
|
431
|
-
newRTCSession: onNewRTCSession,
|
|
432
|
-
newMessage: (e) => emitter.emit("newMessage", e),
|
|
433
|
-
sipEvent: (e) => emitter.emit("sipEvent", e),
|
|
434
|
-
newOptions: (e) => emitter.emit("newOptions", e)
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// src/jssip-lib/sip/sessionState.ts
|
|
439
|
-
function holdOtherSessions(state, sessionId, holdFn) {
|
|
440
|
-
const current = state.getState();
|
|
441
|
-
current.sessions.forEach((s) => {
|
|
442
|
-
if (s.id === sessionId)
|
|
345
|
+
// src/core/modules/debug/sip-debug.runtime.ts
|
|
346
|
+
var SESSION_DEBUG_KEY = "sip-debug-enabled";
|
|
347
|
+
var SipDebugRuntime = class {
|
|
348
|
+
constructor(deps) {
|
|
349
|
+
this.deps = deps;
|
|
350
|
+
}
|
|
351
|
+
attachBridge(setDebug) {
|
|
352
|
+
if (typeof window === "undefined")
|
|
443
353
|
return;
|
|
444
|
-
|
|
445
|
-
|
|
354
|
+
window.sipDebugBridge = (debug) => setDebug(debug ?? true);
|
|
355
|
+
}
|
|
356
|
+
getPersistedDebug() {
|
|
357
|
+
if (typeof window === "undefined")
|
|
358
|
+
return void 0;
|
|
359
|
+
try {
|
|
360
|
+
const persisted = window.sessionStorage.getItem(SESSION_DEBUG_KEY);
|
|
361
|
+
if (!persisted)
|
|
362
|
+
return void 0;
|
|
363
|
+
return persisted;
|
|
364
|
+
} catch {
|
|
365
|
+
return void 0;
|
|
446
366
|
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
367
|
+
}
|
|
368
|
+
syncInspector(effectiveDebug) {
|
|
369
|
+
if (typeof window === "undefined")
|
|
370
|
+
return;
|
|
371
|
+
const enabled = Boolean(effectiveDebug);
|
|
372
|
+
this.deps.setDebugEnabled(enabled);
|
|
373
|
+
this.toggleStateLogger(enabled);
|
|
374
|
+
const win = window;
|
|
375
|
+
const disabledInspector = () => {
|
|
376
|
+
console.warn("SIP debug inspector disabled; enable debug to inspect.");
|
|
377
|
+
return null;
|
|
378
|
+
};
|
|
379
|
+
win.sipState = () => enabled ? this.deps.getState() : disabledInspector();
|
|
380
|
+
win.sipSessions = () => enabled ? this.deps.getSessions() : disabledInspector();
|
|
381
|
+
}
|
|
382
|
+
cleanup() {
|
|
383
|
+
this.toggleStateLogger(false);
|
|
384
|
+
}
|
|
385
|
+
toggleStateLogger(enabled) {
|
|
386
|
+
if (!enabled) {
|
|
387
|
+
this.stateLogOff?.();
|
|
388
|
+
this.stateLogOff = void 0;
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (this.stateLogOff)
|
|
392
|
+
return;
|
|
393
|
+
let prev = this.deps.getState();
|
|
394
|
+
console.info("[sip][state]", { initial: true }, prev);
|
|
395
|
+
this.stateLogOff = this.deps.onChange((next) => {
|
|
396
|
+
console.info("[sip][state]", next);
|
|
397
|
+
prev = next;
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
};
|
|
475
401
|
|
|
476
|
-
// src/
|
|
477
|
-
function
|
|
478
|
-
const {
|
|
479
|
-
emitter,
|
|
480
|
-
state,
|
|
481
|
-
rtc,
|
|
482
|
-
detachSessionHandlers,
|
|
483
|
-
onSessionFailed,
|
|
484
|
-
sessionId
|
|
485
|
-
} = deps;
|
|
402
|
+
// src/core/modules/event/sip-event-manager.adapter.ts
|
|
403
|
+
function createSipEventManager(client) {
|
|
486
404
|
return {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
},
|
|
490
|
-
accepted: (e) => {
|
|
491
|
-
emitter.emit("accepted", e);
|
|
492
|
-
state.batchSet({
|
|
493
|
-
sessions: state.getState().sessions.map(
|
|
494
|
-
(s) => s.id === sessionId ? {
|
|
495
|
-
...s,
|
|
496
|
-
status: CallStatus.Active,
|
|
497
|
-
acceptedAt: s.acceptedAt ?? Date.now()
|
|
498
|
-
} : s
|
|
499
|
-
)
|
|
500
|
-
});
|
|
501
|
-
},
|
|
502
|
-
confirmed: (e) => {
|
|
503
|
-
emitter.emit("confirmed", e);
|
|
504
|
-
deps.enableMicrophoneRecovery?.(sessionId);
|
|
505
|
-
},
|
|
506
|
-
ended: (e) => {
|
|
507
|
-
emitter.emit("ended", e);
|
|
508
|
-
detachSessionHandlers();
|
|
509
|
-
rtc.cleanup();
|
|
510
|
-
const nextSessions = state.getState().sessions.filter((s) => s.id !== sessionId);
|
|
511
|
-
state.batchSet({
|
|
512
|
-
sessions: nextSessions
|
|
513
|
-
});
|
|
514
|
-
},
|
|
515
|
-
failed: (e) => {
|
|
516
|
-
emitter.emit("failed", e);
|
|
517
|
-
detachSessionHandlers();
|
|
518
|
-
rtc.cleanup();
|
|
519
|
-
const cause = e?.cause || "call failed";
|
|
520
|
-
onSessionFailed(cause, e);
|
|
521
|
-
const nextSessions = state.getState().sessions.filter((s) => s.id !== sessionId);
|
|
522
|
-
state.batchSet({
|
|
523
|
-
sessions: nextSessions
|
|
524
|
-
});
|
|
525
|
-
},
|
|
526
|
-
muted: () => {
|
|
527
|
-
emitter.emit("muted", void 0);
|
|
528
|
-
upsertSessionState(state, sessionId, { muted: true });
|
|
529
|
-
},
|
|
530
|
-
unmuted: () => {
|
|
531
|
-
emitter.emit("unmuted", void 0);
|
|
532
|
-
upsertSessionState(state, sessionId, { muted: false });
|
|
533
|
-
},
|
|
534
|
-
hold: () => {
|
|
535
|
-
emitter.emit("hold", void 0);
|
|
536
|
-
upsertSessionState(state, sessionId, { status: CallStatus.Hold });
|
|
537
|
-
},
|
|
538
|
-
unhold: () => {
|
|
539
|
-
emitter.emit("unhold", void 0);
|
|
540
|
-
upsertSessionState(state, sessionId, { status: CallStatus.Active });
|
|
541
|
-
},
|
|
542
|
-
reinvite: (e) => emitter.emit("reinvite", e),
|
|
543
|
-
update: (e) => emitter.emit("update", e),
|
|
544
|
-
sdp: (e) => emitter.emit("sdp", e),
|
|
545
|
-
icecandidate: (e) => emitter.emit("icecandidate", e),
|
|
546
|
-
refer: (e) => emitter.emit("refer", e),
|
|
547
|
-
replaces: (e) => emitter.emit("replaces", e),
|
|
548
|
-
newDTMF: (e) => emitter.emit("newDTMF", e),
|
|
549
|
-
newInfo: (e) => emitter.emit("newInfo", e),
|
|
550
|
-
getusermediafailed: (e) => {
|
|
551
|
-
emitter.emit("getusermediafailed", e);
|
|
552
|
-
detachSessionHandlers();
|
|
553
|
-
rtc.cleanup();
|
|
554
|
-
onSessionFailed("getUserMedia failed", e);
|
|
555
|
-
state.batchSet({
|
|
556
|
-
sessions: state.getState().sessions.filter((s) => s.id !== sessionId)
|
|
557
|
-
});
|
|
558
|
-
},
|
|
559
|
-
"peerconnection:createofferfailed": (e) => {
|
|
560
|
-
emitter.emit("peerconnection:createofferfailed", e);
|
|
561
|
-
detachSessionHandlers();
|
|
562
|
-
rtc.cleanup();
|
|
563
|
-
onSessionFailed("peer connection createOffer failed", e);
|
|
564
|
-
state.batchSet({
|
|
565
|
-
sessions: state.getState().sessions.filter((s) => s.id !== sessionId)
|
|
566
|
-
});
|
|
567
|
-
},
|
|
568
|
-
"peerconnection:createanswerfailed": (e) => {
|
|
569
|
-
emitter.emit("peerconnection:createanswerfailed", e);
|
|
570
|
-
detachSessionHandlers();
|
|
571
|
-
rtc.cleanup();
|
|
572
|
-
onSessionFailed("peer connection createAnswer failed", e);
|
|
573
|
-
state.batchSet({
|
|
574
|
-
sessions: state.getState().sessions.filter((s) => s.id !== sessionId)
|
|
575
|
-
});
|
|
576
|
-
},
|
|
577
|
-
"peerconnection:setlocaldescriptionfailed": (e) => {
|
|
578
|
-
emitter.emit("peerconnection:setlocaldescriptionfailed", e);
|
|
579
|
-
detachSessionHandlers();
|
|
580
|
-
rtc.cleanup();
|
|
581
|
-
onSessionFailed("peer connection setLocalDescription failed", e);
|
|
582
|
-
state.batchSet({
|
|
583
|
-
sessions: state.getState().sessions.filter((s) => s.id !== sessionId)
|
|
584
|
-
});
|
|
585
|
-
},
|
|
586
|
-
"peerconnection:setremotedescriptionfailed": (e) => {
|
|
587
|
-
emitter.emit("peerconnection:setremotedescriptionfailed", e);
|
|
588
|
-
detachSessionHandlers();
|
|
589
|
-
rtc.cleanup();
|
|
590
|
-
onSessionFailed("peer connection setRemoteDescription failed", e);
|
|
591
|
-
state.batchSet({
|
|
592
|
-
sessions: state.getState().sessions.filter((s) => s.id !== sessionId)
|
|
593
|
-
});
|
|
405
|
+
onUA(event, handler) {
|
|
406
|
+
return client.on(event, handler);
|
|
594
407
|
},
|
|
595
|
-
|
|
408
|
+
onSession(sessionId, event, handler) {
|
|
409
|
+
const session = client.getSession(sessionId);
|
|
410
|
+
if (!session)
|
|
411
|
+
return () => {
|
|
412
|
+
};
|
|
413
|
+
session.on(event, handler);
|
|
414
|
+
return () => session.off(event, handler);
|
|
415
|
+
}
|
|
596
416
|
};
|
|
597
417
|
}
|
|
598
418
|
|
|
599
|
-
// src/
|
|
600
|
-
var
|
|
419
|
+
// src/core/modules/debug/sip-debug.logger.ts
|
|
420
|
+
var describePc = (pc) => ({
|
|
421
|
+
connectionState: pc?.connectionState,
|
|
422
|
+
signalingState: pc?.signalingState,
|
|
423
|
+
iceConnectionState: pc?.iceConnectionState
|
|
424
|
+
});
|
|
425
|
+
var SipDebugLogger = class {
|
|
601
426
|
constructor() {
|
|
602
|
-
this.
|
|
603
|
-
this.
|
|
604
|
-
}
|
|
605
|
-
setSession(session) {
|
|
606
|
-
this.currentSession = session;
|
|
607
|
-
}
|
|
608
|
-
setMediaStream(stream) {
|
|
609
|
-
this.mediaStream = stream;
|
|
610
|
-
}
|
|
611
|
-
getPC() {
|
|
612
|
-
return this.currentSession?.connection ?? null;
|
|
613
|
-
}
|
|
614
|
-
cleanup(stopTracks = true) {
|
|
615
|
-
const pc = this.getPC();
|
|
616
|
-
const isClosed = pc?.connectionState === "closed" || pc?.signalingState === "closed";
|
|
617
|
-
if (pc && typeof pc.getSenders === "function") {
|
|
618
|
-
if (!isClosed) {
|
|
619
|
-
for (const s of pc.getSenders()) {
|
|
620
|
-
try {
|
|
621
|
-
s.replaceTrack(null);
|
|
622
|
-
} catch {
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
if (stopTracks && this.mediaStream) {
|
|
628
|
-
const senderTracks = pc && !isClosed ? new Set(
|
|
629
|
-
pc.getSenders().map((s) => s.track).filter((t) => Boolean(t))
|
|
630
|
-
) : null;
|
|
631
|
-
for (const t of this.mediaStream.getTracks()) {
|
|
632
|
-
if (senderTracks?.has(t))
|
|
633
|
-
continue;
|
|
634
|
-
t.stop();
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
this.mediaStream = null;
|
|
638
|
-
this.currentSession = null;
|
|
639
|
-
}
|
|
640
|
-
answer(options = {}) {
|
|
641
|
-
return this.currentSession ? (this.currentSession.answer(options), true) : false;
|
|
642
|
-
}
|
|
643
|
-
hangup(options) {
|
|
644
|
-
return this.currentSession ? (this.currentSession.terminate(
|
|
645
|
-
options ?? { status_code: 486, reason_phrase: "Busy Here" }
|
|
646
|
-
), true) : false;
|
|
647
|
-
}
|
|
648
|
-
mute() {
|
|
649
|
-
this.mediaStream?.getAudioTracks().forEach((t) => t.enabled = false);
|
|
650
|
-
return this.currentSession ? (this.currentSession.mute({ audio: true }), true) : false;
|
|
651
|
-
}
|
|
652
|
-
unmute() {
|
|
653
|
-
this.mediaStream?.getAudioTracks().forEach((t) => t.enabled = true);
|
|
654
|
-
return this.currentSession ? (this.currentSession.unmute({ audio: true }), true) : false;
|
|
655
|
-
}
|
|
656
|
-
hold() {
|
|
657
|
-
return this.currentSession ? (this.currentSession.hold(), true) : false;
|
|
658
|
-
}
|
|
659
|
-
unhold() {
|
|
660
|
-
return this.currentSession ? (this.currentSession.unhold(), true) : false;
|
|
661
|
-
}
|
|
662
|
-
sendDTMF(tones, options) {
|
|
663
|
-
return this.currentSession ? (this.currentSession.sendDTMF(tones, options), true) : false;
|
|
664
|
-
}
|
|
665
|
-
transfer(target, options) {
|
|
666
|
-
return this.currentSession ? (this.currentSession.refer(target, options), true) : false;
|
|
667
|
-
}
|
|
668
|
-
enableVideo() {
|
|
669
|
-
this.mediaStream?.getVideoTracks().forEach((t) => t.enabled = true);
|
|
670
|
-
}
|
|
671
|
-
disableVideo() {
|
|
672
|
-
this.mediaStream?.getVideoTracks().forEach((t) => t.enabled = false);
|
|
673
|
-
}
|
|
674
|
-
async switchCamera(nextVideoTrack) {
|
|
675
|
-
const pc = this.getPC();
|
|
676
|
-
if (!pc)
|
|
677
|
-
return false;
|
|
678
|
-
if (!this.mediaStream)
|
|
679
|
-
this.mediaStream = new MediaStream();
|
|
680
|
-
const old = this.mediaStream.getVideoTracks()[0];
|
|
681
|
-
this.mediaStream.addTrack(nextVideoTrack);
|
|
682
|
-
if (old)
|
|
683
|
-
this.mediaStream.removeTrack(old);
|
|
684
|
-
const sender = pc.getSenders?.().find((s) => s.track?.kind === "video");
|
|
685
|
-
if (sender)
|
|
686
|
-
await sender.replaceTrack(nextVideoTrack);
|
|
687
|
-
if (old && old !== nextVideoTrack)
|
|
688
|
-
old.stop();
|
|
689
|
-
return true;
|
|
690
|
-
}
|
|
691
|
-
async replaceAudioTrack(nextAudioTrack) {
|
|
692
|
-
const pc = this.getPC();
|
|
693
|
-
if (!pc)
|
|
694
|
-
return false;
|
|
695
|
-
if (!this.mediaStream)
|
|
696
|
-
this.mediaStream = new MediaStream();
|
|
697
|
-
const old = this.mediaStream.getAudioTracks()[0];
|
|
698
|
-
this.mediaStream.addTrack(nextAudioTrack);
|
|
699
|
-
if (old)
|
|
700
|
-
this.mediaStream.removeTrack(old);
|
|
701
|
-
const sender = pc.getSenders?.().find((s) => s.track?.kind === "audio");
|
|
702
|
-
if (sender)
|
|
703
|
-
await sender.replaceTrack(nextAudioTrack);
|
|
704
|
-
if (old && old !== nextAudioTrack)
|
|
705
|
-
old.stop();
|
|
706
|
-
return true;
|
|
707
|
-
}
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
// src/jssip-lib/sip/sessionManager.ts
|
|
711
|
-
var SessionManager = class {
|
|
712
|
-
constructor() {
|
|
713
|
-
this.entries = /* @__PURE__ */ new Map();
|
|
714
|
-
}
|
|
715
|
-
stopMediaStream(stream) {
|
|
716
|
-
if (!stream)
|
|
717
|
-
return;
|
|
718
|
-
for (const t of stream.getTracks()) {
|
|
719
|
-
if (t.readyState !== "ended")
|
|
720
|
-
t.stop();
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
getOrCreateRtc(sessionId, session) {
|
|
724
|
-
let entry = this.entries.get(sessionId);
|
|
725
|
-
if (!entry) {
|
|
726
|
-
entry = {
|
|
727
|
-
rtc: new WebRTCSessionController(),
|
|
728
|
-
session: null,
|
|
729
|
-
media: null
|
|
730
|
-
};
|
|
731
|
-
this.entries.set(sessionId, entry);
|
|
732
|
-
}
|
|
733
|
-
if (session) {
|
|
734
|
-
entry.session = session;
|
|
735
|
-
entry.rtc.setSession(session);
|
|
736
|
-
}
|
|
737
|
-
if (entry.media)
|
|
738
|
-
entry.rtc.setMediaStream(entry.media);
|
|
739
|
-
return entry.rtc;
|
|
740
|
-
}
|
|
741
|
-
getRtc(sessionId) {
|
|
742
|
-
return this.entries.get(sessionId)?.rtc ?? null;
|
|
743
|
-
}
|
|
744
|
-
setSession(sessionId, session) {
|
|
745
|
-
const entry = this.entries.get(sessionId);
|
|
746
|
-
if (entry) {
|
|
747
|
-
entry.session = session;
|
|
748
|
-
entry.rtc.setSession(session);
|
|
749
|
-
} else {
|
|
750
|
-
this.entries.set(sessionId, {
|
|
751
|
-
rtc: new WebRTCSessionController(),
|
|
752
|
-
session,
|
|
753
|
-
media: null
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
setSessionMedia(sessionId, stream) {
|
|
758
|
-
const entry = this.entries.get(sessionId) ?? {
|
|
759
|
-
rtc: new WebRTCSessionController(),
|
|
760
|
-
session: null,
|
|
761
|
-
media: null
|
|
762
|
-
};
|
|
763
|
-
if (entry.media && entry.media !== stream) {
|
|
764
|
-
this.stopMediaStream(entry.media);
|
|
765
|
-
}
|
|
766
|
-
entry.media = stream;
|
|
767
|
-
entry.rtc.setMediaStream(stream);
|
|
768
|
-
this.entries.set(sessionId, entry);
|
|
769
|
-
}
|
|
770
|
-
getSession(sessionId) {
|
|
771
|
-
return this.entries.get(sessionId)?.session ?? null;
|
|
772
|
-
}
|
|
773
|
-
getSessionIds() {
|
|
774
|
-
return Array.from(this.entries.keys());
|
|
775
|
-
}
|
|
776
|
-
getSessions() {
|
|
777
|
-
return Array.from(this.entries.entries()).map(([id, entry]) => ({
|
|
778
|
-
id,
|
|
779
|
-
session: entry.session
|
|
780
|
-
}));
|
|
781
|
-
}
|
|
782
|
-
getActiveSessionId(activeStatuses = ["active"]) {
|
|
783
|
-
for (const [id, entry] of Array.from(this.entries.entries()).reverse()) {
|
|
784
|
-
const status = entry.session?.status;
|
|
785
|
-
if (status && activeStatuses.includes(String(status).toLowerCase())) {
|
|
786
|
-
return id;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
return null;
|
|
790
|
-
}
|
|
791
|
-
cleanupSession(sessionId) {
|
|
792
|
-
const entry = this.entries.get(sessionId);
|
|
793
|
-
if (entry) {
|
|
794
|
-
entry.rtc.cleanup();
|
|
795
|
-
this.stopMediaStream(entry.media);
|
|
796
|
-
this.entries.delete(sessionId);
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
cleanupAllSessions() {
|
|
800
|
-
for (const [, entry] of this.entries.entries()) {
|
|
801
|
-
entry.rtc.cleanup();
|
|
802
|
-
this.stopMediaStream(entry.media);
|
|
803
|
-
}
|
|
804
|
-
this.entries.clear();
|
|
805
|
-
}
|
|
806
|
-
answer(sessionId, options) {
|
|
807
|
-
const rtc = this.getRtc(sessionId);
|
|
808
|
-
return rtc ? rtc.answer(options) : false;
|
|
809
|
-
}
|
|
810
|
-
hangup(sessionId, options) {
|
|
811
|
-
const rtc = this.getRtc(sessionId);
|
|
812
|
-
return rtc ? rtc.hangup(options) : false;
|
|
813
|
-
}
|
|
814
|
-
mute(sessionId) {
|
|
815
|
-
const rtc = this.getRtc(sessionId);
|
|
816
|
-
return rtc ? rtc.mute() : false;
|
|
817
|
-
}
|
|
818
|
-
unmute(sessionId) {
|
|
819
|
-
const rtc = this.getRtc(sessionId);
|
|
820
|
-
return rtc ? rtc.unmute() : false;
|
|
821
|
-
}
|
|
822
|
-
hold(sessionId) {
|
|
823
|
-
const rtc = this.getRtc(sessionId);
|
|
824
|
-
return rtc ? rtc.hold() : false;
|
|
825
|
-
}
|
|
826
|
-
unhold(sessionId) {
|
|
827
|
-
const rtc = this.getRtc(sessionId);
|
|
828
|
-
return rtc ? rtc.unhold() : false;
|
|
829
|
-
}
|
|
830
|
-
sendDTMF(sessionId, tones, options) {
|
|
831
|
-
const rtc = this.getRtc(sessionId);
|
|
832
|
-
return rtc ? rtc.sendDTMF(tones, options) : false;
|
|
833
|
-
}
|
|
834
|
-
transfer(sessionId, target, options) {
|
|
835
|
-
const rtc = this.getRtc(sessionId);
|
|
836
|
-
return rtc ? rtc.transfer(target, options) : false;
|
|
837
|
-
}
|
|
838
|
-
};
|
|
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();
|
|
427
|
+
this.enabled = false;
|
|
428
|
+
this.statsStops = /* @__PURE__ */ new Map();
|
|
850
429
|
}
|
|
851
430
|
setEnabled(enabled) {
|
|
852
431
|
this.enabled = enabled;
|
|
@@ -883,6 +462,16 @@ var SipDebugLogger = class {
|
|
|
883
462
|
return;
|
|
884
463
|
console.error("[sip] microphone dropped", payload);
|
|
885
464
|
}
|
|
465
|
+
logIceReady(sessionId, payload) {
|
|
466
|
+
if (!this.enabled)
|
|
467
|
+
return;
|
|
468
|
+
console.info("[sip] ice ready", { sessionId, ...payload });
|
|
469
|
+
}
|
|
470
|
+
logIceReadyConfig(sessionId, delayMs) {
|
|
471
|
+
if (!this.enabled)
|
|
472
|
+
return;
|
|
473
|
+
console.info("[sip] ice ready config", { sessionId, delayMs });
|
|
474
|
+
}
|
|
886
475
|
startCallStatsLogging(sessionId, session) {
|
|
887
476
|
if (!this.enabled || this.statsStops.has(sessionId))
|
|
888
477
|
return;
|
|
@@ -996,43 +585,582 @@ function collectAudioStats(report) {
|
|
|
996
585
|
return { outboundAudio, inboundAudio };
|
|
997
586
|
}
|
|
998
587
|
|
|
999
|
-
// src/
|
|
1000
|
-
var
|
|
588
|
+
// src/core/modules/media/mic-recovery.manager.ts
|
|
589
|
+
var MicRecoveryManager = class {
|
|
1001
590
|
constructor(deps) {
|
|
1002
|
-
this.
|
|
1003
|
-
this.
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
this.
|
|
591
|
+
this.enabled = false;
|
|
592
|
+
this.defaults = {
|
|
593
|
+
intervalMs: 2e3,
|
|
594
|
+
maxRetries: Infinity
|
|
595
|
+
};
|
|
596
|
+
this.active = /* @__PURE__ */ new Map();
|
|
597
|
+
this.syncedSenderTrackId = /* @__PURE__ */ new Map();
|
|
598
|
+
this.deps = deps;
|
|
1008
599
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
600
|
+
configure(config) {
|
|
601
|
+
if (typeof config.enabled === "boolean") {
|
|
602
|
+
this.enabled = config.enabled;
|
|
603
|
+
}
|
|
604
|
+
if (typeof config.intervalMs === "number") {
|
|
605
|
+
this.defaults.intervalMs = config.intervalMs;
|
|
606
|
+
}
|
|
607
|
+
if (typeof config.maxRetries === "number") {
|
|
608
|
+
this.defaults.maxRetries = config.maxRetries;
|
|
609
|
+
}
|
|
1011
610
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
);
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
611
|
+
enable(sessionId, options = {}) {
|
|
612
|
+
if (!this.enabled)
|
|
613
|
+
return () => {
|
|
614
|
+
};
|
|
615
|
+
this.disable(sessionId);
|
|
616
|
+
const intervalMs = options.intervalMs ?? this.defaults.intervalMs;
|
|
617
|
+
const maxRetries = options.maxRetries ?? this.defaults.maxRetries;
|
|
618
|
+
let retries = 0;
|
|
619
|
+
let stopped = false;
|
|
620
|
+
const startedAt = Date.now();
|
|
621
|
+
const warmupMs = Math.max(intervalMs * 2, 2e3);
|
|
622
|
+
const tick = async () => {
|
|
623
|
+
if (stopped || retries >= maxRetries)
|
|
624
|
+
return;
|
|
625
|
+
const rtc = this.deps.getRtc(sessionId);
|
|
626
|
+
const session2 = this.deps.getSession(sessionId);
|
|
627
|
+
if (!rtc || !session2)
|
|
628
|
+
return;
|
|
629
|
+
const sessionState = this.deps.getSessionState(sessionId);
|
|
630
|
+
if (sessionState?.muted)
|
|
631
|
+
return;
|
|
632
|
+
const stream = rtc.mediaStream;
|
|
633
|
+
const track = stream?.getAudioTracks?.()[0];
|
|
634
|
+
const pc2 = session2?.connection;
|
|
635
|
+
const sender = pc2?.getSenders?.()?.find((s) => s.track?.kind === "audio");
|
|
636
|
+
if (!track && !sender)
|
|
637
|
+
return;
|
|
638
|
+
if (!track && sender?.track?.readyState === "live") {
|
|
639
|
+
const nextId = sender.track.id;
|
|
640
|
+
const prevId = this.syncedSenderTrackId.get(sessionId);
|
|
641
|
+
if (prevId === nextId)
|
|
642
|
+
return;
|
|
643
|
+
this.syncedSenderTrackId.set(sessionId, nextId);
|
|
644
|
+
this.deps.setSessionMedia(sessionId, new MediaStream([sender.track]));
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (Date.now() - startedAt < warmupMs)
|
|
648
|
+
return;
|
|
649
|
+
if (pc2?.connectionState === "new" || pc2?.connectionState === "connecting" || pc2?.iceConnectionState === "new" || pc2?.iceConnectionState === "checking") {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const trackLive = track?.readyState === "live";
|
|
653
|
+
const senderLive = sender?.track?.readyState === "live";
|
|
654
|
+
if (trackLive && senderLive)
|
|
655
|
+
return;
|
|
656
|
+
sipDebugLogger.logMicRecoveryDrop({
|
|
657
|
+
sessionId,
|
|
658
|
+
trackLive,
|
|
659
|
+
senderLive
|
|
660
|
+
});
|
|
661
|
+
retries += 1;
|
|
662
|
+
if (trackLive && !senderLive && track) {
|
|
663
|
+
await rtc.replaceAudioTrack(track);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
const timer = setInterval(() => {
|
|
668
|
+
void tick();
|
|
669
|
+
}, intervalMs);
|
|
670
|
+
void tick();
|
|
671
|
+
const session = this.deps.getSession(sessionId);
|
|
672
|
+
const pc = session?.connection;
|
|
673
|
+
const onIceChange = () => {
|
|
674
|
+
const state = pc?.iceConnectionState;
|
|
675
|
+
if (state === "failed" || state === "disconnected")
|
|
676
|
+
void tick();
|
|
677
|
+
};
|
|
678
|
+
pc?.addEventListener?.("iceconnectionstatechange", onIceChange);
|
|
679
|
+
const stop = () => {
|
|
680
|
+
stopped = true;
|
|
681
|
+
clearInterval(timer);
|
|
682
|
+
pc?.removeEventListener?.("iceconnectionstatechange", onIceChange);
|
|
683
|
+
};
|
|
684
|
+
this.active.set(sessionId, { stop });
|
|
685
|
+
return stop;
|
|
686
|
+
}
|
|
687
|
+
disable(sessionId) {
|
|
688
|
+
const entry = this.active.get(sessionId);
|
|
689
|
+
if (!entry)
|
|
690
|
+
return false;
|
|
691
|
+
entry.stop();
|
|
692
|
+
this.active.delete(sessionId);
|
|
693
|
+
this.syncedSenderTrackId.delete(sessionId);
|
|
694
|
+
return true;
|
|
695
|
+
}
|
|
696
|
+
cleanupAll() {
|
|
697
|
+
this.active.forEach((entry) => entry.stop());
|
|
698
|
+
this.active.clear();
|
|
699
|
+
this.syncedSenderTrackId.clear();
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// src/core/modules/runtime/browser-unload.runtime.ts
|
|
704
|
+
var BrowserUnloadRuntime = class {
|
|
705
|
+
attach(onBeforeUnload) {
|
|
706
|
+
if (typeof window === "undefined" || this.handler)
|
|
707
|
+
return;
|
|
708
|
+
this.handler = () => onBeforeUnload();
|
|
709
|
+
window.addEventListener("beforeunload", this.handler);
|
|
710
|
+
}
|
|
711
|
+
detach() {
|
|
712
|
+
if (typeof window === "undefined" || !this.handler)
|
|
713
|
+
return;
|
|
714
|
+
window.removeEventListener("beforeunload", this.handler);
|
|
715
|
+
this.handler = void 0;
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
// src/core/modules/media/webrtc-session.controller.ts
|
|
720
|
+
var WebRTCSessionController = class {
|
|
721
|
+
constructor() {
|
|
722
|
+
this.currentSession = null;
|
|
723
|
+
this.mediaStream = null;
|
|
724
|
+
}
|
|
725
|
+
setSession(session) {
|
|
726
|
+
this.currentSession = session;
|
|
727
|
+
}
|
|
728
|
+
setMediaStream(stream) {
|
|
729
|
+
this.mediaStream = stream;
|
|
730
|
+
}
|
|
731
|
+
getPC() {
|
|
732
|
+
return this.currentSession?.connection ?? null;
|
|
733
|
+
}
|
|
734
|
+
cleanup(stopTracks = true) {
|
|
735
|
+
const pc = this.getPC();
|
|
736
|
+
const isClosed = pc?.connectionState === "closed" || pc?.signalingState === "closed";
|
|
737
|
+
if (pc && typeof pc.getSenders === "function") {
|
|
738
|
+
if (!isClosed) {
|
|
739
|
+
for (const sender of pc.getSenders()) {
|
|
740
|
+
try {
|
|
741
|
+
sender.replaceTrack(null);
|
|
742
|
+
} catch {
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (stopTracks && this.mediaStream) {
|
|
748
|
+
const senderTracks = pc && !isClosed ? new Set(
|
|
749
|
+
pc.getSenders().map((sender) => sender.track).filter((track) => Boolean(track))
|
|
750
|
+
) : null;
|
|
751
|
+
for (const track of this.mediaStream.getTracks()) {
|
|
752
|
+
if (senderTracks?.has(track))
|
|
753
|
+
continue;
|
|
754
|
+
track.stop();
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
this.mediaStream = null;
|
|
758
|
+
this.currentSession = null;
|
|
759
|
+
}
|
|
760
|
+
answer(options = {}) {
|
|
761
|
+
return this.currentSession ? (this.currentSession.answer(options), true) : false;
|
|
762
|
+
}
|
|
763
|
+
hangup(options) {
|
|
764
|
+
return this.currentSession ? (this.currentSession.terminate(
|
|
765
|
+
options ?? { status_code: 486, reason_phrase: "Busy Here" }
|
|
766
|
+
), true) : false;
|
|
767
|
+
}
|
|
768
|
+
mute() {
|
|
769
|
+
this.mediaStream?.getAudioTracks().forEach((track) => track.enabled = false);
|
|
770
|
+
return this.currentSession ? (this.currentSession.mute({ audio: true }), true) : false;
|
|
771
|
+
}
|
|
772
|
+
unmute() {
|
|
773
|
+
this.mediaStream?.getAudioTracks().forEach((track) => track.enabled = true);
|
|
774
|
+
return this.currentSession ? (this.currentSession.unmute({ audio: true }), true) : false;
|
|
775
|
+
}
|
|
776
|
+
hold() {
|
|
777
|
+
return this.currentSession ? (this.currentSession.hold(), true) : false;
|
|
778
|
+
}
|
|
779
|
+
unhold() {
|
|
780
|
+
return this.currentSession ? (this.currentSession.unhold(), true) : false;
|
|
781
|
+
}
|
|
782
|
+
sendDTMF(tones, options) {
|
|
783
|
+
return this.currentSession ? (this.currentSession.sendDTMF(tones, options), true) : false;
|
|
784
|
+
}
|
|
785
|
+
transfer(target, options) {
|
|
786
|
+
return this.currentSession ? (this.currentSession.refer(target, options), true) : false;
|
|
787
|
+
}
|
|
788
|
+
async replaceAudioTrack(nextAudioTrack) {
|
|
789
|
+
const pc = this.getPC();
|
|
790
|
+
if (!pc)
|
|
791
|
+
return false;
|
|
792
|
+
if (!this.mediaStream)
|
|
793
|
+
this.mediaStream = new MediaStream();
|
|
794
|
+
const old = this.mediaStream.getAudioTracks()[0];
|
|
795
|
+
this.mediaStream.addTrack(nextAudioTrack);
|
|
796
|
+
if (old)
|
|
797
|
+
this.mediaStream.removeTrack(old);
|
|
798
|
+
const sender = pc.getSenders?.().find((entry) => entry.track?.kind === "audio");
|
|
799
|
+
if (sender)
|
|
800
|
+
await sender.replaceTrack(nextAudioTrack);
|
|
801
|
+
if (old && old !== nextAudioTrack)
|
|
802
|
+
old.stop();
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
// src/core/modules/session/session.manager.ts
|
|
808
|
+
var SessionManager = class {
|
|
809
|
+
constructor() {
|
|
810
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
811
|
+
}
|
|
812
|
+
stopMediaStream(stream) {
|
|
813
|
+
if (!stream)
|
|
814
|
+
return;
|
|
815
|
+
for (const track of stream.getTracks()) {
|
|
816
|
+
if (track.readyState !== "ended")
|
|
817
|
+
track.stop();
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
getOrCreateRtc(sessionId, session) {
|
|
821
|
+
let entry = this.entries.get(sessionId);
|
|
822
|
+
if (!entry) {
|
|
823
|
+
entry = {
|
|
824
|
+
rtc: new WebRTCSessionController(),
|
|
825
|
+
session: null,
|
|
826
|
+
media: null
|
|
827
|
+
};
|
|
828
|
+
this.entries.set(sessionId, entry);
|
|
829
|
+
}
|
|
830
|
+
if (session) {
|
|
831
|
+
entry.session = session;
|
|
832
|
+
entry.rtc.setSession(session);
|
|
833
|
+
}
|
|
834
|
+
if (entry.media)
|
|
835
|
+
entry.rtc.setMediaStream(entry.media);
|
|
836
|
+
return entry.rtc;
|
|
837
|
+
}
|
|
838
|
+
getRtc(sessionId) {
|
|
839
|
+
return this.entries.get(sessionId)?.rtc ?? null;
|
|
840
|
+
}
|
|
841
|
+
setSession(sessionId, session) {
|
|
842
|
+
const entry = this.entries.get(sessionId);
|
|
843
|
+
if (entry) {
|
|
844
|
+
entry.session = session;
|
|
845
|
+
entry.rtc.setSession(session);
|
|
846
|
+
} else {
|
|
847
|
+
this.entries.set(sessionId, {
|
|
848
|
+
rtc: new WebRTCSessionController(),
|
|
849
|
+
session,
|
|
850
|
+
media: null
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
setSessionMedia(sessionId, stream) {
|
|
855
|
+
const entry = this.entries.get(sessionId) ?? {
|
|
856
|
+
rtc: new WebRTCSessionController(),
|
|
857
|
+
session: null,
|
|
858
|
+
media: null
|
|
859
|
+
};
|
|
860
|
+
if (entry.media && entry.media !== stream) {
|
|
861
|
+
this.stopMediaStream(entry.media);
|
|
862
|
+
}
|
|
863
|
+
entry.media = stream;
|
|
864
|
+
entry.rtc.setMediaStream(stream);
|
|
865
|
+
this.entries.set(sessionId, entry);
|
|
866
|
+
}
|
|
867
|
+
getSession(sessionId) {
|
|
868
|
+
return this.entries.get(sessionId)?.session ?? null;
|
|
869
|
+
}
|
|
870
|
+
getSessionIds() {
|
|
871
|
+
return Array.from(this.entries.keys());
|
|
872
|
+
}
|
|
873
|
+
getSessions() {
|
|
874
|
+
return Array.from(this.entries.entries()).map(([id, entry]) => ({
|
|
875
|
+
id,
|
|
876
|
+
session: entry.session
|
|
877
|
+
}));
|
|
878
|
+
}
|
|
879
|
+
cleanupSession(sessionId) {
|
|
880
|
+
const entry = this.entries.get(sessionId);
|
|
881
|
+
if (entry) {
|
|
882
|
+
entry.rtc.cleanup();
|
|
883
|
+
this.stopMediaStream(entry.media);
|
|
884
|
+
this.entries.delete(sessionId);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
cleanupAllSessions() {
|
|
888
|
+
for (const [, entry] of this.entries.entries()) {
|
|
889
|
+
entry.rtc.cleanup();
|
|
890
|
+
this.stopMediaStream(entry.media);
|
|
891
|
+
}
|
|
892
|
+
this.entries.clear();
|
|
893
|
+
}
|
|
894
|
+
answer(sessionId, options) {
|
|
895
|
+
const rtc = this.getRtc(sessionId);
|
|
896
|
+
return rtc ? rtc.answer(options) : false;
|
|
897
|
+
}
|
|
898
|
+
hangup(sessionId, options) {
|
|
899
|
+
const rtc = this.getRtc(sessionId);
|
|
900
|
+
return rtc ? rtc.hangup(options) : false;
|
|
901
|
+
}
|
|
902
|
+
mute(sessionId) {
|
|
903
|
+
const rtc = this.getRtc(sessionId);
|
|
904
|
+
return rtc ? rtc.mute() : false;
|
|
905
|
+
}
|
|
906
|
+
unmute(sessionId) {
|
|
907
|
+
const rtc = this.getRtc(sessionId);
|
|
908
|
+
return rtc ? rtc.unmute() : false;
|
|
909
|
+
}
|
|
910
|
+
hold(sessionId) {
|
|
911
|
+
const rtc = this.getRtc(sessionId);
|
|
912
|
+
return rtc ? rtc.hold() : false;
|
|
913
|
+
}
|
|
914
|
+
unhold(sessionId) {
|
|
915
|
+
const rtc = this.getRtc(sessionId);
|
|
916
|
+
return rtc ? rtc.unhold() : false;
|
|
917
|
+
}
|
|
918
|
+
sendDTMF(sessionId, tones, options) {
|
|
919
|
+
const rtc = this.getRtc(sessionId);
|
|
920
|
+
return rtc ? rtc.sendDTMF(tones, options) : false;
|
|
921
|
+
}
|
|
922
|
+
transfer(sessionId, target, options) {
|
|
923
|
+
const rtc = this.getRtc(sessionId);
|
|
924
|
+
return rtc ? rtc.transfer(target, options) : false;
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
// src/core/modules/session/session.state.projector.ts
|
|
929
|
+
function toSessionMaps(sessions) {
|
|
930
|
+
const sessionsById = {};
|
|
931
|
+
const sessionIds = [];
|
|
932
|
+
for (const session of sessions) {
|
|
933
|
+
sessionsById[session.id] = session;
|
|
934
|
+
sessionIds.push(session.id);
|
|
935
|
+
}
|
|
936
|
+
return { sessionsById, sessionIds };
|
|
937
|
+
}
|
|
938
|
+
function holdOtherSessions(state, sessionId, holdFn) {
|
|
939
|
+
const current = state.getState();
|
|
940
|
+
current.sessionIds.forEach((id) => {
|
|
941
|
+
if (id === sessionId)
|
|
942
|
+
return;
|
|
943
|
+
const session = current.sessionsById[id];
|
|
944
|
+
if (session?.status === CallStatus.Active) {
|
|
945
|
+
holdFn(id);
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
function upsertSessionState(state, sessionId, partial) {
|
|
950
|
+
const current = state.getState();
|
|
951
|
+
const existing = current.sessionsById[sessionId];
|
|
952
|
+
const base = existing ?? {
|
|
953
|
+
id: sessionId,
|
|
954
|
+
status: CallStatus.Idle,
|
|
955
|
+
direction: null,
|
|
956
|
+
from: null,
|
|
957
|
+
to: null,
|
|
958
|
+
muted: false,
|
|
959
|
+
acceptedAt: null
|
|
960
|
+
};
|
|
961
|
+
const nextSession = { ...base, ...partial };
|
|
962
|
+
const sessions = current.sessionIds.map(
|
|
963
|
+
(id) => id === sessionId ? nextSession : current.sessionsById[id]
|
|
964
|
+
);
|
|
965
|
+
if (!existing) {
|
|
966
|
+
sessions.push(nextSession);
|
|
967
|
+
}
|
|
968
|
+
const { sessionsById, sessionIds } = toSessionMaps(sessions);
|
|
969
|
+
state.setState({ sessions, sessionsById, sessionIds });
|
|
970
|
+
}
|
|
971
|
+
function removeSessionState(state, sessionId) {
|
|
972
|
+
const current = state.getState();
|
|
973
|
+
const sessions = current.sessionIds.filter((id) => id !== sessionId).map((id) => current.sessionsById[id]);
|
|
974
|
+
const { sessionsById, sessionIds } = toSessionMaps(sessions);
|
|
975
|
+
state.setState({
|
|
976
|
+
sessions,
|
|
977
|
+
sessionsById,
|
|
978
|
+
sessionIds,
|
|
979
|
+
error: null
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/core/modules/session/session.handlers.ts
|
|
984
|
+
function createSessionHandlers(deps) {
|
|
985
|
+
const {
|
|
986
|
+
emitter,
|
|
987
|
+
state,
|
|
988
|
+
rtc,
|
|
989
|
+
detachSessionHandlers,
|
|
990
|
+
sessionId,
|
|
991
|
+
iceCandidateReadyDelayMs
|
|
992
|
+
} = deps;
|
|
993
|
+
let iceReadyCalled = false;
|
|
994
|
+
let iceReadyTimer = null;
|
|
995
|
+
const clearIceReadyTimer = () => {
|
|
996
|
+
if (!iceReadyTimer)
|
|
997
|
+
return;
|
|
998
|
+
clearTimeout(iceReadyTimer);
|
|
999
|
+
iceReadyTimer = null;
|
|
1000
|
+
};
|
|
1001
|
+
if (typeof iceCandidateReadyDelayMs === "number") {
|
|
1002
|
+
sipDebugLogger.logIceReadyConfig(sessionId, iceCandidateReadyDelayMs);
|
|
1003
|
+
}
|
|
1004
|
+
return {
|
|
1005
|
+
progress: (e) => {
|
|
1006
|
+
emitter.emit("progress", e);
|
|
1007
|
+
},
|
|
1008
|
+
accepted: (e) => {
|
|
1009
|
+
emitter.emit("accepted", e);
|
|
1010
|
+
const existing = state.getState().sessionsById[sessionId];
|
|
1011
|
+
upsertSessionState(state, sessionId, {
|
|
1012
|
+
status: CallStatus.Active,
|
|
1013
|
+
acceptedAt: existing?.acceptedAt ?? Date.now()
|
|
1014
|
+
});
|
|
1015
|
+
},
|
|
1016
|
+
confirmed: (e) => {
|
|
1017
|
+
emitter.emit("confirmed", e);
|
|
1018
|
+
deps.enableMicrophoneRecovery?.(sessionId);
|
|
1019
|
+
},
|
|
1020
|
+
ended: (e) => {
|
|
1021
|
+
emitter.emit("ended", e);
|
|
1022
|
+
clearIceReadyTimer();
|
|
1023
|
+
detachSessionHandlers();
|
|
1024
|
+
rtc.cleanup();
|
|
1025
|
+
removeSessionState(state, sessionId);
|
|
1026
|
+
},
|
|
1027
|
+
failed: (e) => {
|
|
1028
|
+
emitter.emit("failed", e);
|
|
1029
|
+
clearIceReadyTimer();
|
|
1030
|
+
detachSessionHandlers();
|
|
1031
|
+
rtc.cleanup();
|
|
1032
|
+
removeSessionState(state, sessionId);
|
|
1033
|
+
},
|
|
1034
|
+
muted: (e) => {
|
|
1035
|
+
emitter.emit("muted", e);
|
|
1036
|
+
upsertSessionState(state, sessionId, { muted: true });
|
|
1037
|
+
},
|
|
1038
|
+
unmuted: (e) => {
|
|
1039
|
+
emitter.emit("unmuted", e);
|
|
1040
|
+
upsertSessionState(state, sessionId, { muted: false });
|
|
1041
|
+
},
|
|
1042
|
+
hold: (e) => {
|
|
1043
|
+
emitter.emit("hold", e);
|
|
1044
|
+
upsertSessionState(state, sessionId, { status: CallStatus.Hold });
|
|
1045
|
+
},
|
|
1046
|
+
unhold: (e) => {
|
|
1047
|
+
emitter.emit("unhold", e);
|
|
1048
|
+
upsertSessionState(state, sessionId, { status: CallStatus.Active });
|
|
1049
|
+
},
|
|
1050
|
+
reinvite: (e) => emitter.emit("reinvite", e),
|
|
1051
|
+
update: (e) => emitter.emit("update", e),
|
|
1052
|
+
sdp: (e) => emitter.emit("sdp", e),
|
|
1053
|
+
icecandidate: (e) => {
|
|
1054
|
+
const candidate = e?.candidate;
|
|
1055
|
+
const ready = typeof e?.ready === "function" ? e.ready : null;
|
|
1056
|
+
const delayMs = typeof iceCandidateReadyDelayMs === "number" ? iceCandidateReadyDelayMs : null;
|
|
1057
|
+
if (!iceReadyCalled && ready && delayMs != null) {
|
|
1058
|
+
if (candidate?.type === "srflx" && candidate?.relatedAddress != null && candidate?.relatedPort != null) {
|
|
1059
|
+
iceReadyCalled = true;
|
|
1060
|
+
if (iceReadyTimer) {
|
|
1061
|
+
clearTimeout(iceReadyTimer);
|
|
1062
|
+
iceReadyTimer = null;
|
|
1063
|
+
}
|
|
1064
|
+
sipDebugLogger.logIceReady(sessionId, {
|
|
1065
|
+
source: "srflx",
|
|
1066
|
+
delayMs,
|
|
1067
|
+
candidateType: candidate?.type
|
|
1068
|
+
});
|
|
1069
|
+
ready();
|
|
1070
|
+
} else if (!iceReadyTimer && delayMs > 0) {
|
|
1071
|
+
iceReadyTimer = setTimeout(() => {
|
|
1072
|
+
iceReadyTimer = null;
|
|
1073
|
+
if (iceReadyCalled)
|
|
1074
|
+
return;
|
|
1075
|
+
iceReadyCalled = true;
|
|
1076
|
+
sipDebugLogger.logIceReady(sessionId, {
|
|
1077
|
+
source: "timer",
|
|
1078
|
+
delayMs,
|
|
1079
|
+
candidateType: candidate?.type
|
|
1080
|
+
});
|
|
1081
|
+
ready();
|
|
1082
|
+
}, delayMs);
|
|
1083
|
+
} else if (delayMs === 0) {
|
|
1084
|
+
iceReadyCalled = true;
|
|
1085
|
+
sipDebugLogger.logIceReady(sessionId, {
|
|
1086
|
+
source: "immediate",
|
|
1087
|
+
delayMs,
|
|
1088
|
+
candidateType: candidate?.type
|
|
1089
|
+
});
|
|
1090
|
+
ready();
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
emitter.emit("icecandidate", e);
|
|
1094
|
+
},
|
|
1095
|
+
refer: (e) => emitter.emit("refer", e),
|
|
1096
|
+
replaces: (e) => emitter.emit("replaces", e),
|
|
1097
|
+
newDTMF: (e) => emitter.emit("newDTMF", e),
|
|
1098
|
+
newInfo: (e) => emitter.emit("newInfo", e),
|
|
1099
|
+
getusermediafailed: (e) => {
|
|
1100
|
+
emitter.emit("getusermediafailed", e);
|
|
1101
|
+
clearIceReadyTimer();
|
|
1102
|
+
detachSessionHandlers();
|
|
1103
|
+
rtc.cleanup();
|
|
1104
|
+
removeSessionState(state, sessionId);
|
|
1105
|
+
},
|
|
1106
|
+
"peerconnection:createofferfailed": (e) => {
|
|
1107
|
+
emitter.emit("peerconnection:createofferfailed", e);
|
|
1108
|
+
clearIceReadyTimer();
|
|
1109
|
+
detachSessionHandlers();
|
|
1110
|
+
rtc.cleanup();
|
|
1111
|
+
removeSessionState(state, sessionId);
|
|
1112
|
+
},
|
|
1113
|
+
"peerconnection:createanswerfailed": (e) => {
|
|
1114
|
+
emitter.emit("peerconnection:createanswerfailed", e);
|
|
1115
|
+
clearIceReadyTimer();
|
|
1116
|
+
detachSessionHandlers();
|
|
1117
|
+
rtc.cleanup();
|
|
1118
|
+
removeSessionState(state, sessionId);
|
|
1119
|
+
},
|
|
1120
|
+
"peerconnection:setlocaldescriptionfailed": (e) => {
|
|
1121
|
+
emitter.emit("peerconnection:setlocaldescriptionfailed", e);
|
|
1122
|
+
clearIceReadyTimer();
|
|
1123
|
+
detachSessionHandlers();
|
|
1124
|
+
rtc.cleanup();
|
|
1125
|
+
removeSessionState(state, sessionId);
|
|
1126
|
+
},
|
|
1127
|
+
"peerconnection:setremotedescriptionfailed": (e) => {
|
|
1128
|
+
emitter.emit("peerconnection:setremotedescriptionfailed", e);
|
|
1129
|
+
clearIceReadyTimer();
|
|
1130
|
+
detachSessionHandlers();
|
|
1131
|
+
rtc.cleanup();
|
|
1132
|
+
removeSessionState(state, sessionId);
|
|
1133
|
+
},
|
|
1134
|
+
peerconnection: (e) => emitter.emit("peerconnection", e)
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// src/core/modules/session/session.lifecycle.ts
|
|
1139
|
+
var SessionLifecycle = class {
|
|
1140
|
+
constructor(deps) {
|
|
1141
|
+
this.state = deps.state;
|
|
1142
|
+
this.sessionManager = deps.sessionManager;
|
|
1143
|
+
this.emit = deps.emit;
|
|
1144
|
+
this.attachSessionHandlers = deps.attachSessionHandlers;
|
|
1145
|
+
this.getMaxSessionCount = deps.getMaxSessionCount;
|
|
1146
|
+
}
|
|
1147
|
+
setDebugEnabled(enabled) {
|
|
1148
|
+
sipDebugLogger.setEnabled(enabled);
|
|
1149
|
+
}
|
|
1150
|
+
handleNewRTCSession(e) {
|
|
1151
|
+
const session = e.session;
|
|
1152
|
+
const sessionId = String(
|
|
1153
|
+
session?.id ?? crypto.randomUUID?.() ?? Date.now()
|
|
1154
|
+
);
|
|
1155
|
+
const currentSessions = this.state.getState().sessions;
|
|
1156
|
+
if (currentSessions.length >= this.getMaxSessionCount()) {
|
|
1157
|
+
try {
|
|
1158
|
+
session.terminate?.({
|
|
1021
1159
|
status_code: 486,
|
|
1022
1160
|
reason_phrase: "Busy Here"
|
|
1023
1161
|
});
|
|
1024
1162
|
} catch {
|
|
1025
1163
|
}
|
|
1026
|
-
if (e.originator === "remote") {
|
|
1027
|
-
this.emit("missed", e);
|
|
1028
|
-
} else {
|
|
1029
|
-
this.emitError(
|
|
1030
|
-
"max session count reached",
|
|
1031
|
-
"MAX_SESSIONS_REACHED",
|
|
1032
|
-
"max session count reached"
|
|
1033
|
-
);
|
|
1034
|
-
}
|
|
1035
|
-
return;
|
|
1036
1164
|
}
|
|
1037
1165
|
const rtc = this.sessionManager.getOrCreateRtc(sessionId, session);
|
|
1038
1166
|
this.sessionManager.setSession(sessionId, session);
|
|
@@ -1048,14 +1176,11 @@ var SessionLifecycle = class {
|
|
|
1048
1176
|
const otherRtc = this.sessionManager.getRtc(id);
|
|
1049
1177
|
otherRtc?.hold();
|
|
1050
1178
|
});
|
|
1051
|
-
const sdpHasVideo = e.request?.body && e.request.body.toString().includes("m=video") || session?.connection?.getReceivers?.()?.some((r) => r.track?.kind === "video");
|
|
1052
1179
|
upsertSessionState(this.state, sessionId, {
|
|
1053
1180
|
direction: e.originator,
|
|
1054
1181
|
from: e.originator === "remote" ? e.request.from.uri.user : null,
|
|
1055
1182
|
to: e.request.to.uri.user,
|
|
1056
|
-
status: e.originator === "remote" ? CallStatus.Ringing : CallStatus.Dialing
|
|
1057
|
-
mediaKind: sdpHasVideo ? "video" : "audio",
|
|
1058
|
-
remoteVideoEnabled: sdpHasVideo
|
|
1183
|
+
status: e.originator === "remote" ? CallStatus.Ringing : CallStatus.Dialing
|
|
1059
1184
|
});
|
|
1060
1185
|
this.emit("newRTCSession", e);
|
|
1061
1186
|
}
|
|
@@ -1123,7 +1248,10 @@ var SessionLifecycle = class {
|
|
|
1123
1248
|
attachedPc = pc;
|
|
1124
1249
|
attachedPc.addEventListener?.("signalingstatechange", onPcStateChange);
|
|
1125
1250
|
attachedPc.addEventListener?.("connectionstatechange", onPcStateChange);
|
|
1126
|
-
attachedPc.addEventListener?.(
|
|
1251
|
+
attachedPc.addEventListener?.(
|
|
1252
|
+
"iceconnectionstatechange",
|
|
1253
|
+
onPcStateChange
|
|
1254
|
+
);
|
|
1127
1255
|
};
|
|
1128
1256
|
const clearRetryTimer = () => {
|
|
1129
1257
|
if (!retryTimer)
|
|
@@ -1131,7 +1259,7 @@ var SessionLifecycle = class {
|
|
|
1131
1259
|
clearTimeout(retryTimer);
|
|
1132
1260
|
retryTimer = null;
|
|
1133
1261
|
};
|
|
1134
|
-
const stopRetry = () => {
|
|
1262
|
+
const stopRetry = (opts = {}) => {
|
|
1135
1263
|
if (stopped)
|
|
1136
1264
|
return;
|
|
1137
1265
|
stopped = true;
|
|
@@ -1232,8 +1360,8 @@ var SessionLifecycle = class {
|
|
|
1232
1360
|
session.on?.("peerconnection", onPeer);
|
|
1233
1361
|
}
|
|
1234
1362
|
session.on?.("confirmed", onConfirmed);
|
|
1235
|
-
session.on?.("ended", stopRetry);
|
|
1236
|
-
session.on?.("failed", stopRetry);
|
|
1363
|
+
session.on?.("ended", (e) => stopRetry());
|
|
1364
|
+
session.on?.("failed", (e) => stopRetry());
|
|
1237
1365
|
}
|
|
1238
1366
|
bindRemoteIncomingAudio(sessionId, session) {
|
|
1239
1367
|
const maxAttempts = 50;
|
|
@@ -1299,11 +1427,11 @@ var SessionLifecycle = class {
|
|
|
1299
1427
|
return;
|
|
1300
1428
|
exhaustedCheckUsed = true;
|
|
1301
1429
|
if (checkRemoteTrack(attachedPc))
|
|
1302
|
-
stopRetry();
|
|
1430
|
+
stopRetry({ keepTrack: true });
|
|
1303
1431
|
return;
|
|
1304
1432
|
}
|
|
1305
1433
|
if (checkRemoteTrack(attachedPc))
|
|
1306
|
-
stopRetry();
|
|
1434
|
+
stopRetry({ keepTrack: true });
|
|
1307
1435
|
};
|
|
1308
1436
|
const attachPcListeners = (pc) => {
|
|
1309
1437
|
if (!pc || pc === attachedPc)
|
|
@@ -1326,7 +1454,10 @@ var SessionLifecycle = class {
|
|
|
1326
1454
|
attachedPc = pc;
|
|
1327
1455
|
attachedPc.addEventListener?.("signalingstatechange", onPcStateChange);
|
|
1328
1456
|
attachedPc.addEventListener?.("connectionstatechange", onPcStateChange);
|
|
1329
|
-
attachedPc.addEventListener?.(
|
|
1457
|
+
attachedPc.addEventListener?.(
|
|
1458
|
+
"iceconnectionstatechange",
|
|
1459
|
+
onPcStateChange
|
|
1460
|
+
);
|
|
1330
1461
|
attachedPc.addEventListener?.("track", onTrack);
|
|
1331
1462
|
};
|
|
1332
1463
|
const clearRetryTimer = () => {
|
|
@@ -1335,7 +1466,7 @@ var SessionLifecycle = class {
|
|
|
1335
1466
|
clearTimeout(retryTimer);
|
|
1336
1467
|
retryTimer = null;
|
|
1337
1468
|
};
|
|
1338
|
-
const stopRetry = () => {
|
|
1469
|
+
const stopRetry = (opts = {}) => {
|
|
1339
1470
|
if (stopped)
|
|
1340
1471
|
return;
|
|
1341
1472
|
stopped = true;
|
|
@@ -1356,7 +1487,7 @@ var SessionLifecycle = class {
|
|
|
1356
1487
|
attachedPc.removeEventListener?.("track", onTrack);
|
|
1357
1488
|
attachedPc = null;
|
|
1358
1489
|
}
|
|
1359
|
-
if (attachedTrack) {
|
|
1490
|
+
if (attachedTrack && !opts.keepTrack) {
|
|
1360
1491
|
attachedTrack.removeEventListener?.("ended", onRemoteEnded);
|
|
1361
1492
|
attachedTrack.removeEventListener?.("mute", onRemoteMuted);
|
|
1362
1493
|
attachedTrack = null;
|
|
@@ -1385,7 +1516,7 @@ var SessionLifecycle = class {
|
|
|
1385
1516
|
retryScheduled = false;
|
|
1386
1517
|
retryTimer = null;
|
|
1387
1518
|
if (checkRemoteTrack(pc)) {
|
|
1388
|
-
stopRetry();
|
|
1519
|
+
stopRetry({ keepTrack: true });
|
|
1389
1520
|
return;
|
|
1390
1521
|
}
|
|
1391
1522
|
if (!pc)
|
|
@@ -1401,11 +1532,11 @@ var SessionLifecycle = class {
|
|
|
1401
1532
|
return;
|
|
1402
1533
|
exhaustedCheckUsed = true;
|
|
1403
1534
|
if (checkRemoteTrack(attachedPc))
|
|
1404
|
-
stopRetry();
|
|
1535
|
+
stopRetry({ keepTrack: true });
|
|
1405
1536
|
return;
|
|
1406
1537
|
}
|
|
1407
1538
|
if (checkRemoteTrack(attachedPc))
|
|
1408
|
-
stopRetry();
|
|
1539
|
+
stopRetry({ keepTrack: true });
|
|
1409
1540
|
};
|
|
1410
1541
|
const onPeer = (data) => {
|
|
1411
1542
|
if (stopped)
|
|
@@ -1416,11 +1547,11 @@ var SessionLifecycle = class {
|
|
|
1416
1547
|
return;
|
|
1417
1548
|
exhaustedCheckUsed = true;
|
|
1418
1549
|
if (checkRemoteTrack(data.peerconnection))
|
|
1419
|
-
stopRetry();
|
|
1550
|
+
stopRetry({ keepTrack: true });
|
|
1420
1551
|
return;
|
|
1421
1552
|
}
|
|
1422
1553
|
if (checkRemoteTrack(data.peerconnection)) {
|
|
1423
|
-
stopRetry();
|
|
1554
|
+
stopRetry({ keepTrack: true });
|
|
1424
1555
|
return;
|
|
1425
1556
|
}
|
|
1426
1557
|
scheduleRetry(data.peerconnection);
|
|
@@ -1428,205 +1559,341 @@ var SessionLifecycle = class {
|
|
|
1428
1559
|
const onConfirmed = () => {
|
|
1429
1560
|
if (stopped)
|
|
1430
1561
|
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
|
-
}
|
|
1470
|
-
};
|
|
1471
|
-
|
|
1472
|
-
// src/jssip-lib/sip/micRecovery.ts
|
|
1473
|
-
var MicRecoveryManager = class {
|
|
1474
|
-
constructor(deps) {
|
|
1475
|
-
this.enabled = false;
|
|
1476
|
-
this.defaults = {
|
|
1477
|
-
intervalMs: 2e3,
|
|
1478
|
-
maxRetries: Infinity
|
|
1479
|
-
};
|
|
1480
|
-
this.active = /* @__PURE__ */ new Map();
|
|
1481
|
-
this.deps = deps;
|
|
1482
|
-
}
|
|
1483
|
-
configure(config) {
|
|
1484
|
-
if (typeof config.enabled === "boolean") {
|
|
1485
|
-
this.enabled = config.enabled;
|
|
1486
|
-
}
|
|
1487
|
-
if (typeof config.intervalMs === "number") {
|
|
1488
|
-
this.defaults.intervalMs = config.intervalMs;
|
|
1489
|
-
}
|
|
1490
|
-
if (typeof config.maxRetries === "number") {
|
|
1491
|
-
this.defaults.maxRetries = config.maxRetries;
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
enable(sessionId, options = {}) {
|
|
1495
|
-
if (!this.enabled)
|
|
1496
|
-
return () => {
|
|
1497
|
-
};
|
|
1498
|
-
this.disable(sessionId);
|
|
1499
|
-
const intervalMs = options.intervalMs ?? this.defaults.intervalMs;
|
|
1500
|
-
const maxRetries = options.maxRetries ?? this.defaults.maxRetries;
|
|
1501
|
-
let retries = 0;
|
|
1502
|
-
let stopped = false;
|
|
1503
|
-
const startedAt = Date.now();
|
|
1504
|
-
const warmupMs = Math.max(intervalMs * 2, 2e3);
|
|
1505
|
-
const tick = async () => {
|
|
1506
|
-
if (stopped || retries >= maxRetries)
|
|
1507
|
-
return;
|
|
1508
|
-
const rtc = this.deps.getRtc(sessionId);
|
|
1509
|
-
const session2 = this.deps.getSession(sessionId);
|
|
1510
|
-
if (!rtc || !session2)
|
|
1511
|
-
return;
|
|
1512
|
-
const sessionState = this.deps.getSessionState(sessionId);
|
|
1513
|
-
if (sessionState?.muted)
|
|
1514
|
-
return;
|
|
1515
|
-
const stream = rtc.mediaStream;
|
|
1516
|
-
const track = stream?.getAudioTracks?.()[0];
|
|
1517
|
-
const pc2 = session2?.connection;
|
|
1518
|
-
const sender = pc2?.getSenders?.()?.find((s) => s.track?.kind === "audio");
|
|
1519
|
-
if (!track && !sender)
|
|
1520
|
-
return;
|
|
1521
|
-
if (Date.now() - startedAt < warmupMs)
|
|
1522
|
-
return;
|
|
1523
|
-
if (pc2?.connectionState === "new" || pc2?.connectionState === "connecting" || pc2?.iceConnectionState === "new" || pc2?.iceConnectionState === "checking") {
|
|
1524
|
-
return;
|
|
1525
|
-
}
|
|
1526
|
-
const trackLive = track?.readyState === "live";
|
|
1527
|
-
const senderLive = sender?.track?.readyState === "live";
|
|
1528
|
-
if (trackLive && senderLive)
|
|
1529
|
-
return;
|
|
1530
|
-
sipDebugLogger.logMicRecoveryDrop({
|
|
1531
|
-
sessionId,
|
|
1532
|
-
trackLive,
|
|
1533
|
-
senderLive
|
|
1534
|
-
});
|
|
1535
|
-
retries += 1;
|
|
1536
|
-
if (trackLive && !senderLive && track) {
|
|
1537
|
-
await rtc.replaceAudioTrack(track);
|
|
1538
|
-
return;
|
|
1539
|
-
}
|
|
1540
|
-
let nextStream;
|
|
1541
|
-
try {
|
|
1542
|
-
const deviceId = track?.getSettings?.().deviceId ?? sender?.track?.getSettings?.().deviceId;
|
|
1543
|
-
nextStream = await this.deps.requestMicrophoneStream(deviceId);
|
|
1544
|
-
} catch (err) {
|
|
1545
|
-
console.warn("[sip] mic recovery failed to get stream", err);
|
|
1562
|
+
const currentPc = session?.connection ?? attachedPc;
|
|
1563
|
+
if (exhausted) {
|
|
1564
|
+
if (exhaustedCheckUsed)
|
|
1565
|
+
return;
|
|
1566
|
+
exhaustedCheckUsed = true;
|
|
1567
|
+
if (checkRemoteTrack(currentPc))
|
|
1568
|
+
stopRetry({ keepTrack: true });
|
|
1546
1569
|
return;
|
|
1547
1570
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1571
|
+
if (checkRemoteTrack(currentPc)) {
|
|
1572
|
+
stopRetry({ keepTrack: true });
|
|
1550
1573
|
return;
|
|
1551
|
-
|
|
1552
|
-
|
|
1574
|
+
}
|
|
1575
|
+
logMissingReceiver(currentPc, "confirmed without remote track");
|
|
1576
|
+
scheduleRetry(currentPc);
|
|
1553
1577
|
};
|
|
1554
|
-
const
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1578
|
+
const existingPc = session?.connection;
|
|
1579
|
+
if (!checkRemoteTrack(existingPc)) {
|
|
1580
|
+
if (existingPc) {
|
|
1581
|
+
attachPcListeners(existingPc);
|
|
1582
|
+
scheduleRetry(existingPc);
|
|
1583
|
+
}
|
|
1584
|
+
session.on?.("peerconnection", onPeer);
|
|
1585
|
+
}
|
|
1586
|
+
session.on?.("confirmed", onConfirmed);
|
|
1587
|
+
session.on?.("ended", () => stopRetry());
|
|
1588
|
+
session.on?.("failed", () => stopRetry());
|
|
1589
|
+
}
|
|
1590
|
+
attachCallStatsLogging(sessionId, session) {
|
|
1591
|
+
const onConfirmed = () => {
|
|
1592
|
+
sipDebugLogger.startCallStatsLogging(sessionId, session);
|
|
1564
1593
|
};
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
stopped = true;
|
|
1568
|
-
clearInterval(timer);
|
|
1569
|
-
pc?.removeEventListener?.("iceconnectionstatechange", onIceChange);
|
|
1594
|
+
const onEnd = () => {
|
|
1595
|
+
sipDebugLogger.stopCallStatsLogging(sessionId);
|
|
1570
1596
|
};
|
|
1571
|
-
|
|
1572
|
-
|
|
1597
|
+
session.on?.("confirmed", onConfirmed);
|
|
1598
|
+
session.on?.("ended", onEnd);
|
|
1599
|
+
session.on?.("failed", onEnd);
|
|
1573
1600
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
// src/core/modules/session/session.module.ts
|
|
1604
|
+
var SessionModule = class {
|
|
1605
|
+
constructor(deps) {
|
|
1606
|
+
this.deps = deps;
|
|
1607
|
+
this.sessionHandlers = /* @__PURE__ */ new Map();
|
|
1608
|
+
this.lifecycle = new SessionLifecycle({
|
|
1609
|
+
state: deps.state,
|
|
1610
|
+
sessionManager: deps.sessionManager,
|
|
1611
|
+
emit: (event, payload) => deps.emitter.emit(event, payload),
|
|
1612
|
+
attachSessionHandlers: (sessionId, session) => this.attachSessionHandlers(sessionId, session),
|
|
1613
|
+
getMaxSessionCount: deps.getMaxSessionCount
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
setDebugEnabled(enabled) {
|
|
1617
|
+
this.lifecycle.setDebugEnabled(enabled);
|
|
1618
|
+
}
|
|
1619
|
+
handleNewRTCSession(e) {
|
|
1620
|
+
this.lifecycle.handleNewRTCSession(e);
|
|
1621
|
+
}
|
|
1622
|
+
setSessionMedia(sessionId, stream) {
|
|
1623
|
+
this.deps.sessionManager.setSessionMedia(sessionId, stream);
|
|
1624
|
+
}
|
|
1625
|
+
setSession(sessionId, session) {
|
|
1626
|
+
this.deps.sessionManager.setSession(sessionId, session);
|
|
1627
|
+
}
|
|
1628
|
+
answerSession(sessionId, options = {}) {
|
|
1629
|
+
if (!sessionId || !this.sessionExists(sessionId))
|
|
1577
1630
|
return false;
|
|
1578
|
-
|
|
1579
|
-
|
|
1631
|
+
return this.deps.sessionManager.answer(sessionId, options);
|
|
1632
|
+
}
|
|
1633
|
+
hangupSession(sessionId, options) {
|
|
1634
|
+
if (!sessionId || !this.sessionExists(sessionId))
|
|
1635
|
+
return false;
|
|
1636
|
+
return this.deps.sessionManager.hangup(sessionId, options);
|
|
1637
|
+
}
|
|
1638
|
+
hangupAll(options) {
|
|
1639
|
+
const ids = this.getSessionIds();
|
|
1640
|
+
ids.forEach((id) => this.hangupSession(id, options));
|
|
1641
|
+
return ids.length > 0;
|
|
1642
|
+
}
|
|
1643
|
+
toggleMuteSession(sessionId) {
|
|
1644
|
+
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1645
|
+
if (!resolved)
|
|
1646
|
+
return false;
|
|
1647
|
+
const sessionState = this.deps.state.getState().sessionsById[resolved];
|
|
1648
|
+
const muted = sessionState?.muted ?? false;
|
|
1649
|
+
if (muted) {
|
|
1650
|
+
this.deps.sessionManager.unmute(resolved);
|
|
1651
|
+
return true;
|
|
1652
|
+
}
|
|
1653
|
+
this.deps.sessionManager.mute(resolved);
|
|
1580
1654
|
return true;
|
|
1581
1655
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1656
|
+
toggleHoldSession(sessionId) {
|
|
1657
|
+
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1658
|
+
if (!resolved)
|
|
1659
|
+
return false;
|
|
1660
|
+
const sessionState = this.deps.state.getState().sessionsById[resolved];
|
|
1661
|
+
const isOnHold = sessionState?.status === CallStatus.Hold;
|
|
1662
|
+
if (isOnHold) {
|
|
1663
|
+
this.deps.sessionManager.unhold(resolved);
|
|
1664
|
+
return true;
|
|
1665
|
+
}
|
|
1666
|
+
if (sessionState?.status === CallStatus.Active) {
|
|
1667
|
+
this.deps.sessionManager.hold(resolved);
|
|
1668
|
+
return true;
|
|
1669
|
+
}
|
|
1670
|
+
return true;
|
|
1671
|
+
}
|
|
1672
|
+
sendDTMFSession(sessionId, tones, options) {
|
|
1673
|
+
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1674
|
+
if (!resolved)
|
|
1675
|
+
return false;
|
|
1676
|
+
const sessionState = this.deps.state.getState().sessionsById[resolved];
|
|
1677
|
+
if (sessionState?.status === CallStatus.Active) {
|
|
1678
|
+
this.deps.sessionManager.sendDTMF(resolved, tones, options);
|
|
1679
|
+
}
|
|
1680
|
+
return true;
|
|
1681
|
+
}
|
|
1682
|
+
transferSession(sessionId, target, options) {
|
|
1683
|
+
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1684
|
+
if (!resolved)
|
|
1685
|
+
return false;
|
|
1686
|
+
const sessionState = this.deps.state.getState().sessionsById[resolved];
|
|
1687
|
+
if (sessionState?.status === CallStatus.Active) {
|
|
1688
|
+
this.deps.sessionManager.transfer(resolved, target, options);
|
|
1689
|
+
}
|
|
1690
|
+
return true;
|
|
1691
|
+
}
|
|
1692
|
+
getSession(sessionId) {
|
|
1693
|
+
return this.deps.sessionManager.getSession(sessionId);
|
|
1694
|
+
}
|
|
1695
|
+
getSessionIds() {
|
|
1696
|
+
return this.deps.sessionManager.getSessionIds();
|
|
1697
|
+
}
|
|
1698
|
+
getSessions() {
|
|
1699
|
+
return this.deps.sessionManager.getSessions();
|
|
1700
|
+
}
|
|
1701
|
+
cleanupAllSessions() {
|
|
1702
|
+
this.deps.sessionManager.cleanupAllSessions();
|
|
1703
|
+
this.deps.micRecovery.cleanupAll();
|
|
1704
|
+
this.sessionHandlers.clear();
|
|
1705
|
+
this.deps.state.setState({
|
|
1706
|
+
sessions: [],
|
|
1707
|
+
sessionsById: {},
|
|
1708
|
+
sessionIds: [],
|
|
1709
|
+
error: null
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
attachSessionHandlers(sessionId, session) {
|
|
1713
|
+
const handlers = this.createSessionHandlersFor(sessionId, session);
|
|
1714
|
+
this.sessionHandlers.set(sessionId, handlers);
|
|
1715
|
+
Object.keys(handlers).forEach((ev) => {
|
|
1716
|
+
const h = handlers[ev];
|
|
1717
|
+
if (h)
|
|
1718
|
+
session.on(ev, h);
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
detachSessionHandlers(sessionId, session) {
|
|
1722
|
+
const handlers = this.sessionHandlers.get(sessionId);
|
|
1723
|
+
if (!handlers || !session)
|
|
1724
|
+
return;
|
|
1725
|
+
Object.keys(handlers).forEach((ev) => {
|
|
1726
|
+
const h = handlers[ev];
|
|
1727
|
+
if (h)
|
|
1728
|
+
session.off(ev, h);
|
|
1729
|
+
});
|
|
1730
|
+
this.sessionHandlers.delete(sessionId);
|
|
1731
|
+
}
|
|
1732
|
+
cleanupSession(sessionId, session) {
|
|
1733
|
+
const targetSession = session ?? this.deps.sessionManager.getSession(sessionId) ?? this.deps.sessionManager.getRtc(sessionId)?.currentSession;
|
|
1734
|
+
this.detachSessionHandlers(sessionId, targetSession);
|
|
1735
|
+
this.deps.micRecovery.disable(sessionId);
|
|
1736
|
+
this.deps.sessionManager.cleanupSession(sessionId);
|
|
1737
|
+
removeSessionState(this.deps.state, sessionId);
|
|
1738
|
+
}
|
|
1739
|
+
createSessionHandlersFor(sessionId, session) {
|
|
1740
|
+
const rtc = this.deps.sessionManager.getOrCreateRtc(sessionId, session);
|
|
1741
|
+
return createSessionHandlers({
|
|
1742
|
+
emitter: this.deps.emitter,
|
|
1743
|
+
state: this.deps.state,
|
|
1744
|
+
rtc,
|
|
1745
|
+
detachSessionHandlers: () => this.cleanupSession(sessionId, session),
|
|
1746
|
+
enableMicrophoneRecovery: (confirmedSessionId) => this.deps.micRecovery.enable(confirmedSessionId),
|
|
1747
|
+
iceCandidateReadyDelayMs: this.deps.getIceCandidateReadyDelayMs(),
|
|
1748
|
+
sessionId
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
resolveSessionId(sessionId) {
|
|
1752
|
+
if (sessionId)
|
|
1753
|
+
return sessionId;
|
|
1754
|
+
const state = this.deps.state.getState();
|
|
1755
|
+
const activeId = state.sessionIds.find(
|
|
1756
|
+
(id) => state.sessionsById[id]?.status === CallStatus.Active
|
|
1757
|
+
);
|
|
1758
|
+
return activeId ?? state.sessionIds[0] ?? null;
|
|
1759
|
+
}
|
|
1760
|
+
sessionExists(sessionId) {
|
|
1761
|
+
return !!this.deps.sessionManager.getSession(sessionId) || !!this.deps.sessionManager.getRtc(sessionId);
|
|
1762
|
+
}
|
|
1763
|
+
resolveExistingSessionId(sessionId) {
|
|
1764
|
+
const id = this.resolveSessionId(sessionId);
|
|
1765
|
+
if (!id)
|
|
1766
|
+
return null;
|
|
1767
|
+
return this.sessionExists(id) ? id : null;
|
|
1585
1768
|
}
|
|
1586
1769
|
};
|
|
1587
1770
|
|
|
1588
|
-
// src/
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1771
|
+
// src/core/modules/ua/ua.handlers.ts
|
|
1772
|
+
function createUAHandlers(deps) {
|
|
1773
|
+
const { emitter, state, cleanupAllSessions, onNewRTCSession } = deps;
|
|
1774
|
+
return {
|
|
1775
|
+
connecting: (e) => {
|
|
1776
|
+
emitter.emit("connecting", e);
|
|
1777
|
+
state.batchSet({ sipStatus: SipStatus.Connecting });
|
|
1778
|
+
},
|
|
1779
|
+
connected: (e) => {
|
|
1780
|
+
emitter.emit("connected", e);
|
|
1781
|
+
state.batchSet({ sipStatus: SipStatus.Connected });
|
|
1782
|
+
},
|
|
1783
|
+
disconnected: (e) => {
|
|
1784
|
+
emitter.emit("disconnected", e);
|
|
1785
|
+
cleanupAllSessions();
|
|
1786
|
+
state.reset();
|
|
1787
|
+
},
|
|
1788
|
+
registered: (e) => {
|
|
1789
|
+
emitter.emit("registered", e);
|
|
1790
|
+
state.batchSet({ sipStatus: SipStatus.Registered, error: null });
|
|
1791
|
+
},
|
|
1792
|
+
unregistered: (e) => {
|
|
1793
|
+
emitter.emit("unregistered", e);
|
|
1794
|
+
state.batchSet({ sipStatus: SipStatus.Unregistered });
|
|
1795
|
+
},
|
|
1796
|
+
registrationFailed: (e) => {
|
|
1797
|
+
emitter.emit("registrationFailed", e);
|
|
1798
|
+
cleanupAllSessions();
|
|
1799
|
+
state.batchSet({
|
|
1800
|
+
sipStatus: SipStatus.RegistrationFailed,
|
|
1801
|
+
error: e?.cause || "registration failed"
|
|
1802
|
+
});
|
|
1803
|
+
},
|
|
1804
|
+
newRTCSession: onNewRTCSession,
|
|
1805
|
+
newMessage: (e) => emitter.emit("newMessage", e),
|
|
1806
|
+
sipEvent: (e) => emitter.emit("sipEvent", e),
|
|
1807
|
+
newOptions: (e) => emitter.emit("newOptions", e)
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// src/core/modules/ua/ua.module.ts
|
|
1812
|
+
var UaModule = class {
|
|
1813
|
+
constructor(deps) {
|
|
1814
|
+
this.userAgent = deps.userAgent;
|
|
1815
|
+
this.uaHandlers = deps.createHandlers();
|
|
1816
|
+
this.uaHandlerKeys = Object.keys(this.uaHandlers);
|
|
1817
|
+
}
|
|
1818
|
+
start(uri, password, config, debug) {
|
|
1819
|
+
this.userAgent.start(uri, password, config, { debug });
|
|
1820
|
+
this.attachHandlers();
|
|
1821
|
+
}
|
|
1822
|
+
stop() {
|
|
1823
|
+
this.detachHandlers();
|
|
1824
|
+
this.userAgent.stop();
|
|
1825
|
+
}
|
|
1826
|
+
register() {
|
|
1827
|
+
this.userAgent.register();
|
|
1828
|
+
}
|
|
1829
|
+
setDebug(debug) {
|
|
1830
|
+
this.userAgent.setDebug(debug);
|
|
1831
|
+
}
|
|
1832
|
+
attachHandlers() {
|
|
1833
|
+
const ua = this.userAgent.ua;
|
|
1834
|
+
if (!ua)
|
|
1835
|
+
return;
|
|
1836
|
+
this.detachHandlers();
|
|
1837
|
+
this.uaHandlerKeys.forEach((event) => {
|
|
1838
|
+
const handler = this.uaHandlers[event];
|
|
1839
|
+
if (handler)
|
|
1840
|
+
ua.on(event, handler);
|
|
1601
1841
|
});
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1842
|
+
}
|
|
1843
|
+
detachHandlers() {
|
|
1844
|
+
const ua = this.userAgent.ua;
|
|
1845
|
+
if (!ua)
|
|
1846
|
+
return;
|
|
1847
|
+
this.uaHandlerKeys.forEach((event) => {
|
|
1848
|
+
const handler = this.uaHandlers[event];
|
|
1849
|
+
if (handler)
|
|
1850
|
+
ua.off(event, handler);
|
|
1609
1851
|
});
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
|
|
1855
|
+
// src/core/client/sip.client.ts
|
|
1856
|
+
var SipClient = class extends EventTargetEmitter {
|
|
1857
|
+
constructor(options = {}) {
|
|
1858
|
+
super();
|
|
1859
|
+
this.userAgent = new SipUserAgent();
|
|
1860
|
+
this.stateStore = new SipStateStore();
|
|
1861
|
+
this.maxSessionCount = Infinity;
|
|
1862
|
+
this.sessionManager = new SessionManager();
|
|
1863
|
+
this.unloadRuntime = new BrowserUnloadRuntime();
|
|
1864
|
+
this.debugPattern = options.debug;
|
|
1865
|
+
this.uaModule = new UaModule({
|
|
1866
|
+
userAgent: this.userAgent,
|
|
1867
|
+
createHandlers: () => createUAHandlers({
|
|
1868
|
+
emitter: this,
|
|
1869
|
+
state: this.stateStore,
|
|
1870
|
+
cleanupAllSessions: () => this.cleanupAllSessions(),
|
|
1871
|
+
onNewRTCSession: (e) => this.onNewRTCSession(e)
|
|
1872
|
+
})
|
|
1618
1873
|
});
|
|
1619
1874
|
this.micRecovery = new MicRecoveryManager({
|
|
1620
1875
|
getRtc: (sessionId) => this.sessionManager.getRtc(sessionId),
|
|
1621
1876
|
getSession: (sessionId) => this.sessionManager.getSession(sessionId),
|
|
1622
|
-
getSessionState: (sessionId) => this.stateStore.getState().
|
|
1623
|
-
setSessionMedia: (sessionId, stream) => this.sessionManager.setSessionMedia(sessionId, stream)
|
|
1624
|
-
emitError: (raw, code, fallback) => this.emitError(raw, code, fallback),
|
|
1625
|
-
requestMicrophoneStream: (deviceId) => this.requestMicrophoneStreamInternal(deviceId)
|
|
1877
|
+
getSessionState: (sessionId) => this.stateStore.getState().sessionsById[sessionId],
|
|
1878
|
+
setSessionMedia: (sessionId, stream) => this.sessionManager.setSessionMedia(sessionId, stream)
|
|
1626
1879
|
});
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1880
|
+
this.sessionModule = new SessionModule({
|
|
1881
|
+
state: this.stateStore,
|
|
1882
|
+
emitter: this,
|
|
1883
|
+
sessionManager: this.sessionManager,
|
|
1884
|
+
micRecovery: this.micRecovery,
|
|
1885
|
+
getMaxSessionCount: () => this.maxSessionCount,
|
|
1886
|
+
getIceCandidateReadyDelayMs: () => this.iceCandidateReadyDelayMs
|
|
1887
|
+
});
|
|
1888
|
+
this.debugRuntime = new SipDebugRuntime({
|
|
1889
|
+
getState: () => this.stateStore.getState(),
|
|
1890
|
+
onChange: (listener) => this.stateStore.onChange(listener),
|
|
1891
|
+
getSessions: () => this.getSessions(),
|
|
1892
|
+
setDebugEnabled: (enabled) => this.sessionModule.setDebugEnabled(enabled)
|
|
1893
|
+
});
|
|
1894
|
+
this.debugRuntime.attachBridge(
|
|
1895
|
+
(debug) => this.setDebug(debug)
|
|
1896
|
+
);
|
|
1630
1897
|
}
|
|
1631
1898
|
get state() {
|
|
1632
1899
|
return this.stateStore.getState();
|
|
@@ -1640,388 +1907,184 @@ var SipClient = class extends EventTargetEmitter {
|
|
|
1640
1907
|
micRecoveryIntervalMs,
|
|
1641
1908
|
micRecoveryMaxRetries,
|
|
1642
1909
|
maxSessionCount,
|
|
1910
|
+
iceCandidateReadyDelayMs,
|
|
1643
1911
|
...uaCfg
|
|
1644
1912
|
} = config;
|
|
1645
1913
|
this.maxSessionCount = typeof maxSessionCount === "number" ? maxSessionCount : Infinity;
|
|
1914
|
+
this.iceCandidateReadyDelayMs = typeof iceCandidateReadyDelayMs === "number" ? iceCandidateReadyDelayMs : void 0;
|
|
1646
1915
|
this.micRecovery.configure({
|
|
1647
1916
|
enabled: Boolean(enableMicRecovery),
|
|
1648
1917
|
intervalMs: micRecoveryIntervalMs,
|
|
1649
1918
|
maxRetries: micRecoveryMaxRetries
|
|
1650
1919
|
});
|
|
1651
|
-
const debug = cfgDebug ?? this.getPersistedDebug() ?? this.debugPattern;
|
|
1652
|
-
this.
|
|
1653
|
-
this.
|
|
1654
|
-
this.
|
|
1655
|
-
|
|
1656
|
-
|
|
1920
|
+
const debug = cfgDebug ?? this.debugRuntime.getPersistedDebug() ?? this.debugPattern;
|
|
1921
|
+
this.uaModule.start(uri, password, uaCfg, debug);
|
|
1922
|
+
this.sessionModule.setDebugEnabled(Boolean(debug));
|
|
1923
|
+
this.unloadRuntime.attach(() => {
|
|
1924
|
+
this.hangupAll();
|
|
1925
|
+
this.disconnect();
|
|
1926
|
+
});
|
|
1927
|
+
this.debugRuntime.syncInspector(debug);
|
|
1657
1928
|
}
|
|
1658
1929
|
registerUA() {
|
|
1659
|
-
this.
|
|
1930
|
+
this.uaModule.register();
|
|
1660
1931
|
}
|
|
1661
1932
|
disconnect() {
|
|
1662
|
-
this.
|
|
1663
|
-
this.
|
|
1664
|
-
this.userAgent.stop();
|
|
1933
|
+
this.unloadRuntime.detach();
|
|
1934
|
+
this.uaModule.stop();
|
|
1665
1935
|
this.cleanupAllSessions();
|
|
1666
1936
|
this.stateStore.reset();
|
|
1937
|
+
this.debugRuntime.cleanup();
|
|
1667
1938
|
}
|
|
1668
1939
|
call(target, callOptions = {}) {
|
|
1669
1940
|
try {
|
|
1670
|
-
const opts = this.ensureMediaConstraints(callOptions);
|
|
1671
1941
|
const ua = this.userAgent.getUA();
|
|
1672
|
-
const session = ua?.call(target,
|
|
1673
|
-
if (session &&
|
|
1942
|
+
const session = ua?.call(target, callOptions);
|
|
1943
|
+
if (session && callOptions.mediaStream) {
|
|
1674
1944
|
const sessionId = String(session?.id ?? "");
|
|
1675
1945
|
if (sessionId) {
|
|
1676
|
-
this.
|
|
1677
|
-
this.
|
|
1946
|
+
this.sessionModule.setSessionMedia(sessionId, callOptions.mediaStream);
|
|
1947
|
+
this.sessionModule.setSession(sessionId, session);
|
|
1678
1948
|
}
|
|
1679
1949
|
}
|
|
1680
1950
|
} catch (e) {
|
|
1681
|
-
|
|
1951
|
+
console.error(e);
|
|
1682
1952
|
this.cleanupAllSessions();
|
|
1683
|
-
this.stateStore.batchSet({
|
|
1684
|
-
error: err.cause
|
|
1685
|
-
});
|
|
1686
1953
|
}
|
|
1687
1954
|
}
|
|
1688
|
-
answer(sessionId, options = {}) {
|
|
1689
|
-
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1690
|
-
if (!resolved)
|
|
1691
|
-
return false;
|
|
1692
|
-
return this.answerSession(resolved, options);
|
|
1693
|
-
}
|
|
1694
|
-
hangup(sessionId, options) {
|
|
1695
|
-
const resolved = this.resolveExistingSessionId(sessionId);
|
|
1696
|
-
if (!resolved)
|
|
1697
|
-
return false;
|
|
1698
|
-
return this.hangupSession(resolved, options);
|
|
1699
|
-
}
|
|
1700
1955
|
hangupAll(options) {
|
|
1701
1956
|
const ids = this.getSessionIds();
|
|
1702
1957
|
ids.forEach((id) => this.hangupSession(id, options));
|
|
1703
1958
|
return ids.length > 0;
|
|
1704
1959
|
}
|
|
1705
|
-
toggleMute(sessionId) {
|
|
1706
|
-
return this.toggleMuteSession(sessionId);
|
|
1707
|
-
}
|
|
1708
|
-
toggleHold(sessionId) {
|
|
1709
|
-
return this.toggleHoldSession(sessionId);
|
|
1710
|
-
}
|
|
1711
|
-
sendDTMF(sessionId, tones, options) {
|
|
1712
|
-
return this.sendDTMFSession(sessionId, tones, options);
|
|
1713
|
-
}
|
|
1714
|
-
transfer(sessionId, target, options) {
|
|
1715
|
-
return this.transferSession(sessionId, target, options);
|
|
1716
|
-
}
|
|
1717
1960
|
onChange(fn) {
|
|
1718
1961
|
return this.stateStore.onChange(fn);
|
|
1719
1962
|
}
|
|
1720
|
-
attachUAHandlers() {
|
|
1721
|
-
const ua = this.userAgent.ua;
|
|
1722
|
-
if (!ua)
|
|
1723
|
-
return;
|
|
1724
|
-
this.detachUAHandlers();
|
|
1725
|
-
this.uaHandlerKeys.forEach((ev) => {
|
|
1726
|
-
const h = this.uaHandlers[ev];
|
|
1727
|
-
if (h)
|
|
1728
|
-
ua.on(ev, h);
|
|
1729
|
-
});
|
|
1730
|
-
}
|
|
1731
1963
|
setDebug(debug) {
|
|
1732
1964
|
this.debugPattern = debug;
|
|
1733
|
-
this.
|
|
1734
|
-
this.
|
|
1735
|
-
this.
|
|
1736
|
-
|
|
1737
|
-
attachSessionHandlers(sessionId, session) {
|
|
1738
|
-
const handlers = this.createSessionHandlersFor(sessionId, session);
|
|
1739
|
-
this.sessionHandlers.set(sessionId, handlers);
|
|
1740
|
-
Object.keys(handlers).forEach((ev) => {
|
|
1741
|
-
const h = handlers[ev];
|
|
1742
|
-
if (h)
|
|
1743
|
-
session.on(ev, h);
|
|
1744
|
-
});
|
|
1745
|
-
}
|
|
1746
|
-
detachSessionHandlers(sessionId, session) {
|
|
1747
|
-
const handlers = this.sessionHandlers.get(sessionId);
|
|
1748
|
-
if (!handlers || !session)
|
|
1749
|
-
return;
|
|
1750
|
-
Object.keys(handlers).forEach((ev) => {
|
|
1751
|
-
const h = handlers[ev];
|
|
1752
|
-
if (h)
|
|
1753
|
-
session.off(ev, h);
|
|
1754
|
-
});
|
|
1755
|
-
this.sessionHandlers.delete(sessionId);
|
|
1756
|
-
}
|
|
1757
|
-
detachUAHandlers() {
|
|
1758
|
-
const ua = this.userAgent.ua;
|
|
1759
|
-
if (!ua)
|
|
1760
|
-
return;
|
|
1761
|
-
this.uaHandlerKeys.forEach((ev) => {
|
|
1762
|
-
const h = this.uaHandlers[ev];
|
|
1763
|
-
if (h)
|
|
1764
|
-
ua.off(ev, h);
|
|
1765
|
-
});
|
|
1766
|
-
}
|
|
1767
|
-
cleanupSession(sessionId, session) {
|
|
1768
|
-
const targetSession = session ?? this.sessionManager.getSession(sessionId) ?? this.sessionManager.getRtc(sessionId)?.currentSession;
|
|
1769
|
-
this.detachSessionHandlers(sessionId, targetSession);
|
|
1770
|
-
this.micRecovery.disable(sessionId);
|
|
1771
|
-
this.sessionManager.cleanupSession(sessionId);
|
|
1772
|
-
removeSessionState(this.stateStore, sessionId);
|
|
1965
|
+
this.uaModule.setDebug(debug);
|
|
1966
|
+
this.sessionModule.setDebugEnabled(Boolean(debug));
|
|
1967
|
+
const effectiveDebug = debug ?? this.debugRuntime.getPersistedDebug() ?? this.debugPattern;
|
|
1968
|
+
this.debugRuntime.syncInspector(effectiveDebug);
|
|
1773
1969
|
}
|
|
1774
1970
|
cleanupAllSessions() {
|
|
1775
|
-
this.
|
|
1776
|
-
this.micRecovery.cleanupAll();
|
|
1777
|
-
this.sessionHandlers.clear();
|
|
1778
|
-
this.stateStore.setState({
|
|
1779
|
-
sessions: [],
|
|
1780
|
-
error: null
|
|
1781
|
-
});
|
|
1782
|
-
}
|
|
1783
|
-
createSessionHandlersFor(sessionId, session) {
|
|
1784
|
-
const rtc = this.sessionManager.getOrCreateRtc(sessionId, session);
|
|
1785
|
-
return createSessionHandlers({
|
|
1786
|
-
emitter: this,
|
|
1787
|
-
state: this.stateStore,
|
|
1788
|
-
rtc,
|
|
1789
|
-
detachSessionHandlers: () => this.cleanupSession(sessionId, session),
|
|
1790
|
-
emitError: (raw, code, fallback) => this.emitError(raw, code, fallback),
|
|
1791
|
-
onSessionFailed: (err, event) => this.onSessionFailed(err, event),
|
|
1792
|
-
enableMicrophoneRecovery: (confirmedSessionId) => this.micRecovery.enable(confirmedSessionId),
|
|
1793
|
-
sessionId
|
|
1794
|
-
});
|
|
1971
|
+
this.sessionModule.cleanupAllSessions();
|
|
1795
1972
|
}
|
|
1796
1973
|
onNewRTCSession(e) {
|
|
1797
|
-
this.
|
|
1798
|
-
}
|
|
1799
|
-
onSessionFailed(error, event) {
|
|
1800
|
-
const rawCause = event?.cause ?? error;
|
|
1801
|
-
const statusCode = event?.message?.status_code;
|
|
1802
|
-
const statusText = event?.message?.reason_phrase;
|
|
1803
|
-
const causeText = rawCause || (statusCode ? `${statusCode}${statusText ? " " + statusText : ""}` : "call failed");
|
|
1804
|
-
this.emitError(
|
|
1805
|
-
{ raw: event, cause: rawCause, statusCode, statusText },
|
|
1806
|
-
"SESSION_FAILED",
|
|
1807
|
-
causeText
|
|
1808
|
-
);
|
|
1809
|
-
}
|
|
1810
|
-
emitError(raw, code, fallback) {
|
|
1811
|
-
const payload = this.errorHandler.format({ raw, code, fallback });
|
|
1812
|
-
this.emit("error", payload);
|
|
1813
|
-
return payload;
|
|
1814
|
-
}
|
|
1815
|
-
resolveSessionId(sessionId) {
|
|
1816
|
-
if (sessionId)
|
|
1817
|
-
return sessionId;
|
|
1818
|
-
const sessions = this.stateStore.getState().sessions;
|
|
1819
|
-
const active = sessions.find((s) => s.status === CallStatus.Active);
|
|
1820
|
-
return active?.id ?? sessions[0]?.id ?? null;
|
|
1821
|
-
}
|
|
1822
|
-
sessionExists(sessionId) {
|
|
1823
|
-
return !!this.sessionManager.getSession(sessionId) || !!this.sessionManager.getRtc(sessionId);
|
|
1824
|
-
}
|
|
1825
|
-
resolveExistingSessionId(sessionId) {
|
|
1826
|
-
const id = this.resolveSessionId(sessionId);
|
|
1827
|
-
if (!id)
|
|
1828
|
-
return null;
|
|
1829
|
-
return this.sessionExists(id) ? id : null;
|
|
1830
|
-
}
|
|
1831
|
-
ensureMediaConstraints(opts) {
|
|
1832
|
-
if (opts.mediaStream || opts.mediaConstraints)
|
|
1833
|
-
return opts;
|
|
1834
|
-
return { ...opts, mediaConstraints: { audio: true, video: false } };
|
|
1974
|
+
this.sessionModule.handleNewRTCSession(e);
|
|
1835
1975
|
}
|
|
1836
1976
|
answerSession(sessionId, options = {}) {
|
|
1837
|
-
if (
|
|
1838
|
-
|
|
1839
|
-
const opts = this.ensureMediaConstraints(options);
|
|
1840
|
-
if (opts.mediaStream) {
|
|
1841
|
-
this.sessionManager.setSessionMedia(sessionId, opts.mediaStream);
|
|
1977
|
+
if (options.mediaStream) {
|
|
1978
|
+
this.sessionModule.setSessionMedia(sessionId, options.mediaStream);
|
|
1842
1979
|
}
|
|
1843
|
-
return this.
|
|
1980
|
+
return this.sessionModule.answerSession(sessionId, options);
|
|
1844
1981
|
}
|
|
1845
1982
|
hangupSession(sessionId, options) {
|
|
1846
|
-
|
|
1847
|
-
return false;
|
|
1848
|
-
return this.sessionManager.hangup(sessionId, options);
|
|
1983
|
+
return this.sessionModule.hangupSession(sessionId, options);
|
|
1849
1984
|
}
|
|
1850
1985
|
toggleMuteSession(sessionId) {
|
|
1851
|
-
|
|
1852
|
-
if (!resolved)
|
|
1853
|
-
return false;
|
|
1854
|
-
const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
|
|
1855
|
-
const muted = sessionState?.muted ?? false;
|
|
1856
|
-
if (muted) {
|
|
1857
|
-
this.sessionManager.unmute(resolved);
|
|
1858
|
-
return true;
|
|
1859
|
-
}
|
|
1860
|
-
this.sessionManager.mute(resolved);
|
|
1861
|
-
return true;
|
|
1986
|
+
return this.sessionModule.toggleMuteSession(sessionId);
|
|
1862
1987
|
}
|
|
1863
1988
|
toggleHoldSession(sessionId) {
|
|
1864
|
-
|
|
1865
|
-
if (!resolved)
|
|
1866
|
-
return false;
|
|
1867
|
-
const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
|
|
1868
|
-
const isOnHold = sessionState?.status === CallStatus.Hold;
|
|
1869
|
-
if (isOnHold) {
|
|
1870
|
-
this.sessionManager.unhold(resolved);
|
|
1871
|
-
return true;
|
|
1872
|
-
}
|
|
1873
|
-
if (sessionState?.status === CallStatus.Active) {
|
|
1874
|
-
this.sessionManager.hold(resolved);
|
|
1875
|
-
return true;
|
|
1876
|
-
}
|
|
1877
|
-
return true;
|
|
1989
|
+
return this.sessionModule.toggleHoldSession(sessionId);
|
|
1878
1990
|
}
|
|
1879
1991
|
sendDTMFSession(sessionId, tones, options) {
|
|
1880
|
-
|
|
1881
|
-
if (!resolved)
|
|
1882
|
-
return false;
|
|
1883
|
-
const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
|
|
1884
|
-
if (sessionState?.status === CallStatus.Active)
|
|
1885
|
-
this.sessionManager.sendDTMF(resolved, tones, options);
|
|
1886
|
-
return true;
|
|
1992
|
+
return this.sessionModule.sendDTMFSession(sessionId, tones, options);
|
|
1887
1993
|
}
|
|
1888
1994
|
transferSession(sessionId, target, options) {
|
|
1889
|
-
|
|
1890
|
-
if (!resolved)
|
|
1891
|
-
return false;
|
|
1892
|
-
const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
|
|
1893
|
-
if (sessionState?.status === CallStatus.Active)
|
|
1894
|
-
this.sessionManager.transfer(resolved, target, options);
|
|
1895
|
-
return true;
|
|
1995
|
+
return this.sessionModule.transferSession(sessionId, target, options);
|
|
1896
1996
|
}
|
|
1897
1997
|
setSessionMedia(sessionId, stream) {
|
|
1898
|
-
this.
|
|
1899
|
-
}
|
|
1900
|
-
switchCameraSession(sessionId, track) {
|
|
1901
|
-
if (!this.sessionExists(sessionId))
|
|
1902
|
-
return false;
|
|
1903
|
-
const rtc = this.sessionManager.getRtc(sessionId);
|
|
1904
|
-
return rtc ? rtc.switchCamera(track) : false;
|
|
1905
|
-
}
|
|
1906
|
-
enableVideoSession(sessionId) {
|
|
1907
|
-
if (!this.sessionExists(sessionId))
|
|
1908
|
-
return false;
|
|
1909
|
-
const rtc = this.sessionManager.getRtc(sessionId);
|
|
1910
|
-
rtc?.enableVideo();
|
|
1911
|
-
return !!rtc;
|
|
1912
|
-
}
|
|
1913
|
-
disableVideoSession(sessionId) {
|
|
1914
|
-
if (!this.sessionExists(sessionId))
|
|
1915
|
-
return false;
|
|
1916
|
-
const rtc = this.sessionManager.getRtc(sessionId);
|
|
1917
|
-
rtc?.disableVideo();
|
|
1918
|
-
return !!rtc;
|
|
1998
|
+
this.sessionModule.setSessionMedia(sessionId, stream);
|
|
1919
1999
|
}
|
|
1920
2000
|
getSession(sessionId) {
|
|
1921
|
-
return this.
|
|
2001
|
+
return this.sessionModule.getSession(sessionId);
|
|
1922
2002
|
}
|
|
1923
2003
|
getSessionIds() {
|
|
1924
|
-
return this.
|
|
2004
|
+
return this.sessionModule.getSessionIds();
|
|
1925
2005
|
}
|
|
1926
2006
|
getSessions() {
|
|
1927
|
-
return this.
|
|
1928
|
-
}
|
|
1929
|
-
attachBeforeUnload() {
|
|
1930
|
-
if (typeof window === "undefined" || this.unloadHandler)
|
|
1931
|
-
return;
|
|
1932
|
-
const handler = () => {
|
|
1933
|
-
this.hangupAll();
|
|
1934
|
-
this.disconnect();
|
|
1935
|
-
};
|
|
1936
|
-
window.addEventListener("beforeunload", handler);
|
|
1937
|
-
this.unloadHandler = handler;
|
|
1938
|
-
}
|
|
1939
|
-
detachBeforeUnload() {
|
|
1940
|
-
if (typeof window === "undefined" || !this.unloadHandler)
|
|
1941
|
-
return;
|
|
1942
|
-
window.removeEventListener("beforeunload", this.unloadHandler);
|
|
1943
|
-
this.unloadHandler = void 0;
|
|
1944
|
-
}
|
|
1945
|
-
syncDebugInspector(debug) {
|
|
1946
|
-
if (typeof window === "undefined")
|
|
1947
|
-
return;
|
|
1948
|
-
const persisted = this.getPersistedDebug();
|
|
1949
|
-
const effectiveDebug = debug ?? persisted ?? this.debugPattern;
|
|
1950
|
-
this.lifecycle.setDebugEnabled(Boolean(effectiveDebug));
|
|
1951
|
-
this.toggleStateLogger(Boolean(effectiveDebug));
|
|
1952
|
-
const win = window;
|
|
1953
|
-
const disabledInspector = () => {
|
|
1954
|
-
console.warn("SIP debug inspector disabled; enable debug to inspect.");
|
|
1955
|
-
return null;
|
|
1956
|
-
};
|
|
1957
|
-
win.sipState = () => effectiveDebug ? this.stateStore.getState() : disabledInspector();
|
|
1958
|
-
win.sipSessions = () => effectiveDebug ? this.getSessions() : disabledInspector();
|
|
1959
|
-
}
|
|
1960
|
-
toggleStateLogger(enabled) {
|
|
1961
|
-
if (!enabled) {
|
|
1962
|
-
this.stateLogOff?.();
|
|
1963
|
-
this.stateLogOff = void 0;
|
|
1964
|
-
return;
|
|
1965
|
-
}
|
|
1966
|
-
if (this.stateLogOff)
|
|
1967
|
-
return;
|
|
1968
|
-
let prev = this.stateStore.getState();
|
|
1969
|
-
console.info("[sip][state]", { initial: true }, prev);
|
|
1970
|
-
this.stateLogOff = this.stateStore.onChange((next) => {
|
|
1971
|
-
console.info("[sip][state]", next);
|
|
1972
|
-
prev = next;
|
|
1973
|
-
});
|
|
1974
|
-
}
|
|
1975
|
-
getPersistedDebug() {
|
|
1976
|
-
if (typeof window === "undefined")
|
|
1977
|
-
return void 0;
|
|
1978
|
-
try {
|
|
1979
|
-
const persisted = window.sessionStorage.getItem(SESSION_DEBUG_KEY);
|
|
1980
|
-
if (!persisted)
|
|
1981
|
-
return void 0;
|
|
1982
|
-
return persisted;
|
|
1983
|
-
} catch {
|
|
1984
|
-
return void 0;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
async requestMicrophoneStreamInternal(deviceId) {
|
|
1988
|
-
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
1989
|
-
throw new Error("getUserMedia not available");
|
|
1990
|
-
}
|
|
1991
|
-
const audio = deviceId && deviceId !== "default" ? { deviceId: { exact: deviceId } } : true;
|
|
1992
|
-
try {
|
|
1993
|
-
return await navigator.mediaDevices.getUserMedia({ audio });
|
|
1994
|
-
} catch (err) {
|
|
1995
|
-
const cause = err?.name || "getUserMedia failed";
|
|
1996
|
-
this.emitError(
|
|
1997
|
-
{ raw: err, cause },
|
|
1998
|
-
"MICROPHONE_UNAVAILABLE",
|
|
1999
|
-
"microphone unavailable"
|
|
2000
|
-
);
|
|
2001
|
-
throw err;
|
|
2002
|
-
}
|
|
2007
|
+
return this.sessionModule.getSessions();
|
|
2003
2008
|
}
|
|
2004
2009
|
};
|
|
2005
2010
|
function createSipClientInstance(options) {
|
|
2006
2011
|
return new SipClient(options);
|
|
2007
2012
|
}
|
|
2008
|
-
|
|
2013
|
+
|
|
2014
|
+
// src/core/modules/media/media.module.ts
|
|
2015
|
+
function createMediaModule(deps) {
|
|
2016
|
+
const { client, eventManager } = deps;
|
|
2009
2017
|
return {
|
|
2010
|
-
|
|
2011
|
-
return client.
|
|
2018
|
+
getSession(sessionId) {
|
|
2019
|
+
return client.getSession(sessionId);
|
|
2012
2020
|
},
|
|
2013
|
-
|
|
2021
|
+
observePeerConnection(sessionId, onPeerConnection) {
|
|
2014
2022
|
const session = client.getSession(sessionId);
|
|
2015
|
-
if (!session)
|
|
2023
|
+
if (!session) {
|
|
2024
|
+
onPeerConnection(null);
|
|
2016
2025
|
return () => {
|
|
2017
2026
|
};
|
|
2018
|
-
|
|
2019
|
-
|
|
2027
|
+
}
|
|
2028
|
+
const initialPc = session.connection ?? null;
|
|
2029
|
+
onPeerConnection(initialPc);
|
|
2030
|
+
return eventManager.onSession(sessionId, "peerconnection", (payload) => {
|
|
2031
|
+
const pc = payload?.peerconnection ?? null;
|
|
2032
|
+
onPeerConnection(pc);
|
|
2033
|
+
});
|
|
2034
|
+
},
|
|
2035
|
+
buildRemoteStream(peerConnection) {
|
|
2036
|
+
if (!peerConnection || typeof peerConnection.getReceivers !== "function") {
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
2039
|
+
const tracks = peerConnection.getReceivers().map((receiver) => receiver.track).filter((track) => Boolean(track));
|
|
2040
|
+
if (tracks.length === 0)
|
|
2041
|
+
return null;
|
|
2042
|
+
return new MediaStream(tracks);
|
|
2020
2043
|
}
|
|
2021
2044
|
};
|
|
2022
2045
|
}
|
|
2046
|
+
|
|
2047
|
+
// src/core/kernel/createSipKernel.ts
|
|
2048
|
+
function createSipKernel() {
|
|
2049
|
+
const client = createSipClientInstance();
|
|
2050
|
+
const eventManager = createSipEventManager(client);
|
|
2051
|
+
const media = createMediaModule({ client, eventManager });
|
|
2052
|
+
return {
|
|
2053
|
+
client,
|
|
2054
|
+
store: {
|
|
2055
|
+
getState: () => client.state,
|
|
2056
|
+
subscribe: (onStoreChange) => client.onChange(onStoreChange)
|
|
2057
|
+
},
|
|
2058
|
+
commands: {
|
|
2059
|
+
connect: (uri, password, config) => client.connect(uri, password, config),
|
|
2060
|
+
disconnect: () => client.disconnect(),
|
|
2061
|
+
register: () => client.registerUA(),
|
|
2062
|
+
setDebug: (debug) => client.setDebug(debug),
|
|
2063
|
+
call: (target, options) => client.call(target, options),
|
|
2064
|
+
answer: (sessionId, options) => client.answerSession(sessionId, options),
|
|
2065
|
+
hangup: (sessionId, options) => client.hangupSession(sessionId, options),
|
|
2066
|
+
hangupAll: (options) => client.hangupAll(options),
|
|
2067
|
+
toggleMute: (sessionId) => client.toggleMuteSession(sessionId),
|
|
2068
|
+
toggleHold: (sessionId) => client.toggleHoldSession(sessionId),
|
|
2069
|
+
sendDTMF: (sessionId, tones, options) => client.sendDTMFSession(sessionId, tones, options),
|
|
2070
|
+
transfer: (sessionId, target, options) => client.transferSession(sessionId, target, options),
|
|
2071
|
+
getSession: (sessionId) => client.getSession(sessionId),
|
|
2072
|
+
getSessionIds: () => client.getSessionIds(),
|
|
2073
|
+
getSessions: () => client.getSessions(),
|
|
2074
|
+
setSessionMedia: (sessionId, stream) => client.setSessionMedia(sessionId, stream)
|
|
2075
|
+
},
|
|
2076
|
+
events: {
|
|
2077
|
+
onUA: (event, handler) => eventManager.onUA(event, handler),
|
|
2078
|
+
onSession: (sessionId, event, handler) => eventManager.onSession(sessionId, event, handler)
|
|
2079
|
+
},
|
|
2080
|
+
eventManager,
|
|
2081
|
+
media
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2023
2084
|
var SipContext = react.createContext(null);
|
|
2024
|
-
|
|
2085
|
+
|
|
2086
|
+
// src/hooks/useSip.ts
|
|
2087
|
+
function useSipKernel() {
|
|
2025
2088
|
const ctx = react.useContext(SipContext);
|
|
2026
2089
|
if (!ctx)
|
|
2027
2090
|
throw new Error("Must be used within SipProvider");
|
|
@@ -2030,177 +2093,190 @@ function useSip() {
|
|
|
2030
2093
|
|
|
2031
2094
|
// src/hooks/useSipState.ts
|
|
2032
2095
|
function useSipState() {
|
|
2033
|
-
const {
|
|
2034
|
-
|
|
2035
|
-
(onStoreChange) => client.onChange(onStoreChange),
|
|
2036
|
-
[client]
|
|
2037
|
-
);
|
|
2038
|
-
const getSnapshot = react.useCallback(() => client.state, [client]);
|
|
2039
|
-
return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
2096
|
+
const { store } = useSipKernel();
|
|
2097
|
+
return react.useSyncExternalStore(store.subscribe, store.getState, store.getState);
|
|
2040
2098
|
}
|
|
2041
2099
|
function useSipActions() {
|
|
2042
|
-
const {
|
|
2100
|
+
const { commands } = useSipKernel();
|
|
2043
2101
|
return react.useMemo(
|
|
2044
2102
|
() => ({
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2103
|
+
connect: commands.connect,
|
|
2104
|
+
disconnect: commands.disconnect,
|
|
2105
|
+
register: commands.register,
|
|
2106
|
+
setDebug: commands.setDebug,
|
|
2107
|
+
call: commands.call,
|
|
2108
|
+
answer: commands.answer,
|
|
2109
|
+
hangup: commands.hangup,
|
|
2110
|
+
hangupAll: commands.hangupAll,
|
|
2111
|
+
toggleMute: commands.toggleMute,
|
|
2112
|
+
toggleHold: commands.toggleHold,
|
|
2113
|
+
sendDTMF: commands.sendDTMF,
|
|
2114
|
+
transfer: commands.transfer,
|
|
2115
|
+
getSession: commands.getSession,
|
|
2116
|
+
getSessionIds: commands.getSessionIds,
|
|
2117
|
+
getSessions: commands.getSessions,
|
|
2118
|
+
setSessionMedia: commands.setSessionMedia
|
|
2059
2119
|
}),
|
|
2060
|
-
[
|
|
2120
|
+
[commands]
|
|
2061
2121
|
);
|
|
2062
2122
|
}
|
|
2123
|
+
function useSipSelector(selector, equalityFn = Object.is) {
|
|
2124
|
+
const { store } = useSipKernel();
|
|
2125
|
+
const selectorRef = react.useRef(selector);
|
|
2126
|
+
const equalityFnRef = react.useRef(equalityFn);
|
|
2127
|
+
const selectedRef = react.useRef(void 0);
|
|
2128
|
+
const hasSelectedRef = react.useRef(false);
|
|
2129
|
+
selectorRef.current = selector;
|
|
2130
|
+
equalityFnRef.current = equalityFn;
|
|
2131
|
+
const getSelection = () => {
|
|
2132
|
+
const nextSelected = selectorRef.current(store.getState());
|
|
2133
|
+
if (hasSelectedRef.current && equalityFnRef.current(selectedRef.current, nextSelected)) {
|
|
2134
|
+
return selectedRef.current;
|
|
2135
|
+
}
|
|
2136
|
+
hasSelectedRef.current = true;
|
|
2137
|
+
selectedRef.current = nextSelected;
|
|
2138
|
+
return nextSelected;
|
|
2139
|
+
};
|
|
2140
|
+
return react.useSyncExternalStore(store.subscribe, getSelection, getSelection);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
// src/hooks/useActiveSipSession.ts
|
|
2144
|
+
function useActiveSipSession() {
|
|
2145
|
+
return useSipSelector((state) => {
|
|
2146
|
+
const activeId = state.sessionIds.find(
|
|
2147
|
+
(id) => state.sessionsById[id]?.status === CallStatus.Active
|
|
2148
|
+
);
|
|
2149
|
+
return activeId ? state.sessionsById[activeId] : null;
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2063
2152
|
|
|
2064
|
-
// src/hooks/
|
|
2153
|
+
// src/hooks/useSipSession.ts
|
|
2154
|
+
function useSipSession(sessionId) {
|
|
2155
|
+
return useSipSelector((state) => {
|
|
2156
|
+
if (!sessionId)
|
|
2157
|
+
return null;
|
|
2158
|
+
return state.sessionsById[sessionId] ?? null;
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2065
2161
|
function useSipSessions() {
|
|
2066
|
-
const
|
|
2067
|
-
return { sessions };
|
|
2162
|
+
const sessions = useSipSelector((state) => state.sessions);
|
|
2163
|
+
return react.useMemo(() => ({ sessions }), [sessions]);
|
|
2068
2164
|
}
|
|
2069
2165
|
function useSipEvent(event, handler) {
|
|
2070
|
-
const {
|
|
2166
|
+
const { events } = useSipKernel();
|
|
2071
2167
|
react.useEffect(() => {
|
|
2072
2168
|
if (!handler)
|
|
2073
2169
|
return;
|
|
2074
|
-
return
|
|
2075
|
-
}, [event, handler,
|
|
2170
|
+
return events.onUA(event, handler);
|
|
2171
|
+
}, [event, handler, events]);
|
|
2076
2172
|
}
|
|
2077
2173
|
function useSipSessionEvent(sessionId, event, handler) {
|
|
2078
|
-
const {
|
|
2174
|
+
const { events } = useSipKernel();
|
|
2079
2175
|
react.useEffect(() => {
|
|
2080
2176
|
if (!handler)
|
|
2081
2177
|
return;
|
|
2082
|
-
return
|
|
2083
|
-
}, [event, handler, sessionId,
|
|
2178
|
+
return events.onSession(sessionId, event, handler);
|
|
2179
|
+
}, [event, handler, sessionId, events]);
|
|
2084
2180
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2181
|
+
function useSessionMedia(sessionId) {
|
|
2182
|
+
const { media } = useSipKernel();
|
|
2183
|
+
const { sessionIds, sessionsById } = useSipSelector(
|
|
2184
|
+
(state) => ({
|
|
2185
|
+
sessionIds: state.sessionIds,
|
|
2186
|
+
sessionsById: state.sessionsById
|
|
2187
|
+
}),
|
|
2188
|
+
(prev, next) => prev.sessionIds === next.sessionIds && prev.sessionsById === next.sessionsById
|
|
2189
|
+
);
|
|
2190
|
+
const [peerConnection, setPeerConnection] = react.useState(
|
|
2191
|
+
null
|
|
2192
|
+
);
|
|
2193
|
+
const [remoteStream, setRemoteStream] = react.useState(null);
|
|
2194
|
+
const resolvedSessionId = react.useMemo(() => {
|
|
2195
|
+
if (sessionId)
|
|
2196
|
+
return sessionId;
|
|
2197
|
+
const activeId = sessionIds.find(
|
|
2198
|
+
(id) => sessionsById[id]?.status === CallStatus.Active
|
|
2199
|
+
);
|
|
2200
|
+
return activeId ?? sessionIds[0];
|
|
2201
|
+
}, [sessionId, sessionIds, sessionsById]);
|
|
2202
|
+
const session = react.useMemo(
|
|
2203
|
+
() => resolvedSessionId ? media.getSession(resolvedSessionId) : null,
|
|
2204
|
+
[media, resolvedSessionId]
|
|
2205
|
+
);
|
|
2206
|
+
const sessionState = react.useMemo(() => {
|
|
2207
|
+
if (!resolvedSessionId)
|
|
2208
|
+
return null;
|
|
2209
|
+
return sessionsById[resolvedSessionId] ?? null;
|
|
2210
|
+
}, [sessionsById, resolvedSessionId]);
|
|
2211
|
+
react.useEffect(() => {
|
|
2212
|
+
if (!resolvedSessionId) {
|
|
2213
|
+
setPeerConnection(null);
|
|
2214
|
+
setRemoteStream(null);
|
|
2215
|
+
return;
|
|
2101
2216
|
}
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
}
|
|
2113
|
-
audioEl.srcObject = nextStream;
|
|
2114
|
-
audioEl.play?.().catch(() => {
|
|
2115
|
-
});
|
|
2116
|
-
};
|
|
2117
|
-
pc.addEventListener("track", onTrack);
|
|
2118
|
-
return () => pc.removeEventListener("track", onTrack);
|
|
2119
|
-
};
|
|
2120
|
-
const listenSessionPeerconnection = (session) => {
|
|
2121
|
-
const onPeer = (data) => {
|
|
2122
|
-
cleanupTrackListener = dispose(cleanupTrackListener);
|
|
2123
|
-
cleanupTrackListener = attachTracks(data.peerconnection);
|
|
2124
|
-
};
|
|
2125
|
-
session.on("peerconnection", onPeer);
|
|
2126
|
-
return () => session.off("peerconnection", onPeer);
|
|
2127
|
-
};
|
|
2128
|
-
function bindToSession(session) {
|
|
2129
|
-
clearAudioStream(audioEl.srcObject);
|
|
2130
|
-
if (session?.direction === "outgoing" && session.connection instanceof RTCPeerConnection) {
|
|
2131
|
-
cleanupTrackListener = dispose(cleanupTrackListener);
|
|
2132
|
-
cleanupTrackListener = attachTracks(session.connection);
|
|
2217
|
+
const off = media.observePeerConnection(resolvedSessionId, (pc) => {
|
|
2218
|
+
setPeerConnection(pc);
|
|
2219
|
+
setRemoteStream(media.buildRemoteStream(pc));
|
|
2220
|
+
});
|
|
2221
|
+
return off;
|
|
2222
|
+
}, [media, resolvedSessionId]);
|
|
2223
|
+
react.useEffect(() => {
|
|
2224
|
+
if (!peerConnection) {
|
|
2225
|
+
setRemoteStream(null);
|
|
2226
|
+
return;
|
|
2133
2227
|
}
|
|
2134
|
-
|
|
2135
|
-
|
|
2228
|
+
const update = () => {
|
|
2229
|
+
setRemoteStream(media.buildRemoteStream(peerConnection));
|
|
2230
|
+
};
|
|
2231
|
+
peerConnection.addEventListener("track", update);
|
|
2232
|
+
peerConnection.addEventListener("connectionstatechange", update);
|
|
2233
|
+
peerConnection.addEventListener("iceconnectionstatechange", update);
|
|
2234
|
+
update();
|
|
2136
2235
|
return () => {
|
|
2137
|
-
|
|
2138
|
-
|
|
2236
|
+
peerConnection.removeEventListener("track", update);
|
|
2237
|
+
peerConnection.removeEventListener("connectionstatechange", update);
|
|
2238
|
+
peerConnection.removeEventListener("iceconnectionstatechange", update);
|
|
2139
2239
|
};
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
if (e.session.direction === "outgoing" && e.session.connection instanceof RTCPeerConnection) {
|
|
2151
|
-
cleanupTrackListener = attachTracks(e.session.connection);
|
|
2152
|
-
}
|
|
2153
|
-
});
|
|
2154
|
-
const offEnded = client.on("ended", () => detach());
|
|
2155
|
-
const offFailed = client.on("failed", () => detach());
|
|
2156
|
-
const offDisconnected = client.on("disconnected", () => detach());
|
|
2157
|
-
cleanupClientListeners = () => {
|
|
2158
|
-
offNew();
|
|
2159
|
-
offEnded();
|
|
2160
|
-
offFailed();
|
|
2161
|
-
offDisconnected();
|
|
2240
|
+
}, [media, peerConnection]);
|
|
2241
|
+
const tracks = remoteStream?.getTracks() ?? [];
|
|
2242
|
+
const audioTracks = tracks.filter((track) => track.kind === "audio");
|
|
2243
|
+
if (!sessionState) {
|
|
2244
|
+
return {
|
|
2245
|
+
sessionId: resolvedSessionId ?? "",
|
|
2246
|
+
session,
|
|
2247
|
+
peerConnection,
|
|
2248
|
+
remoteStream,
|
|
2249
|
+
audioTracks
|
|
2162
2250
|
};
|
|
2163
|
-
return cleanupClientListeners;
|
|
2164
|
-
}
|
|
2165
|
-
function detach() {
|
|
2166
|
-
cleanupClientListeners = dispose(cleanupClientListeners);
|
|
2167
|
-
cleanupSessionPeerListener = dispose(cleanupSessionPeerListener);
|
|
2168
|
-
cleanupTrackListener = dispose(cleanupTrackListener);
|
|
2169
|
-
clearAudioStream(audioEl.srcObject);
|
|
2170
2251
|
}
|
|
2171
2252
|
return {
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2253
|
+
sessionId: sessionState.id,
|
|
2254
|
+
session,
|
|
2255
|
+
peerConnection,
|
|
2256
|
+
remoteStream,
|
|
2257
|
+
audioTracks
|
|
2175
2258
|
};
|
|
2176
2259
|
}
|
|
2177
2260
|
function CallPlayer({ sessionId }) {
|
|
2178
|
-
const {
|
|
2261
|
+
const { remoteStream } = useSessionMedia(sessionId);
|
|
2179
2262
|
const audioRef = react.useRef(null);
|
|
2180
2263
|
react.useEffect(() => {
|
|
2181
2264
|
if (!audioRef.current)
|
|
2182
2265
|
return;
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2266
|
+
audioRef.current.srcObject = remoteStream;
|
|
2267
|
+
audioRef.current.play?.().catch(() => {
|
|
2268
|
+
});
|
|
2186
2269
|
return () => {
|
|
2187
|
-
|
|
2188
|
-
|
|
2270
|
+
if (audioRef.current) {
|
|
2271
|
+
audioRef.current.srcObject = null;
|
|
2272
|
+
}
|
|
2189
2273
|
};
|
|
2190
|
-
}, [
|
|
2274
|
+
}, [remoteStream]);
|
|
2191
2275
|
return /* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, autoPlay: true, playsInline: true });
|
|
2192
2276
|
}
|
|
2193
|
-
function SipProvider({
|
|
2194
|
-
|
|
2195
|
-
children
|
|
2196
|
-
sipEventManager
|
|
2197
|
-
}) {
|
|
2198
|
-
const manager = react.useMemo(
|
|
2199
|
-
() => sipEventManager ?? createSipEventManager(client),
|
|
2200
|
-
[client, sipEventManager]
|
|
2201
|
-
);
|
|
2202
|
-
const contextValue = react.useMemo(() => ({ client, sipEventManager: manager }), [client, manager]);
|
|
2203
|
-
return /* @__PURE__ */ jsxRuntime.jsx(SipContext.Provider, { value: contextValue, children });
|
|
2277
|
+
function SipProvider(props) {
|
|
2278
|
+
const contextValue = react.useMemo(() => props.kernel, [props.kernel]);
|
|
2279
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SipContext.Provider, { value: contextValue, children: props.children });
|
|
2204
2280
|
}
|
|
2205
2281
|
|
|
2206
2282
|
Object.defineProperty(exports, "WebSocketInterface", {
|
|
@@ -2209,14 +2285,18 @@ Object.defineProperty(exports, "WebSocketInterface", {
|
|
|
2209
2285
|
});
|
|
2210
2286
|
exports.CallPlayer = CallPlayer;
|
|
2211
2287
|
exports.CallStatus = CallStatus;
|
|
2212
|
-
exports.SipContext = SipContext;
|
|
2213
2288
|
exports.SipProvider = SipProvider;
|
|
2214
2289
|
exports.SipStatus = SipStatus;
|
|
2215
2290
|
exports.createSipClientInstance = createSipClientInstance;
|
|
2216
2291
|
exports.createSipEventManager = createSipEventManager;
|
|
2217
|
-
exports.
|
|
2292
|
+
exports.createSipKernel = createSipKernel;
|
|
2293
|
+
exports.useActiveSipSession = useActiveSipSession;
|
|
2294
|
+
exports.useSessionMedia = useSessionMedia;
|
|
2218
2295
|
exports.useSipActions = useSipActions;
|
|
2219
2296
|
exports.useSipEvent = useSipEvent;
|
|
2297
|
+
exports.useSipKernel = useSipKernel;
|
|
2298
|
+
exports.useSipSelector = useSipSelector;
|
|
2299
|
+
exports.useSipSession = useSipSession;
|
|
2220
2300
|
exports.useSipSessionEvent = useSipSessionEvent;
|
|
2221
2301
|
exports.useSipSessions = useSipSessions;
|
|
2222
2302
|
exports.useSipState = useSipState;
|