react-jssip-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1377 @@
1
+ 'use strict';
2
+
3
+ var JsSIP = require('jssip');
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var JsSIP__default = /*#__PURE__*/_interopDefault(JsSIP);
10
+
11
+ // src/jssip-lib/sip/userAgent.ts
12
+ var SipUserAgent = class {
13
+ constructor() {
14
+ this._ua = null;
15
+ }
16
+ get ua() {
17
+ return this._ua;
18
+ }
19
+ get isStarted() {
20
+ return !!this._ua;
21
+ }
22
+ get isRegistered() {
23
+ return !!this._ua?.isRegistered();
24
+ }
25
+ start(uri, password, config, opts) {
26
+ this.stop();
27
+ const uaCfg = this.buildUAConfig(config, uri, password);
28
+ this.ensureValid(uaCfg);
29
+ this.applyDebug(opts?.debug);
30
+ const ua = this.createUA(uaCfg);
31
+ ua.start();
32
+ this._ua = ua;
33
+ return ua;
34
+ }
35
+ register() {
36
+ const ua = this.getUA();
37
+ if (!ua?.isRegistered())
38
+ ua?.register();
39
+ }
40
+ stop() {
41
+ const ua = this._ua;
42
+ if (!ua)
43
+ return;
44
+ try {
45
+ if (ua.isRegistered())
46
+ ua.unregister();
47
+ ua.stop();
48
+ } finally {
49
+ this._ua = null;
50
+ }
51
+ }
52
+ getUA() {
53
+ return this._ua;
54
+ }
55
+ setDebug(debug) {
56
+ this.applyDebug(debug);
57
+ }
58
+ buildUAConfig(config, uri, password) {
59
+ return { ...config, uri, password };
60
+ }
61
+ ensureValid(cfg) {
62
+ const sockets = cfg.sockets;
63
+ if (!cfg.uri || !cfg.password || !sockets || Array.isArray(sockets) && sockets.length === 0) {
64
+ throw new Error(
65
+ "Invalid SIP connect args: require uri, password, and at least one socket"
66
+ );
67
+ }
68
+ }
69
+ applyDebug(debug) {
70
+ const enabled = debug === void 0 ? this.readSessionFlag() : !!debug;
71
+ const pattern = typeof debug === "string" ? debug : "JsSIP:*";
72
+ if (enabled) {
73
+ JsSIP__default.default.debug.enable(pattern);
74
+ const dbg = JsSIP__default.default.debug;
75
+ if (dbg?.setLogger)
76
+ dbg.setLogger(console);
77
+ else if (dbg)
78
+ dbg.logger = console;
79
+ this.persistSessionFlag();
80
+ } else {
81
+ JsSIP__default.default.debug?.disable?.();
82
+ this.clearSessionFlag();
83
+ }
84
+ }
85
+ createUA(cfg) {
86
+ return new JsSIP__default.default.UA(cfg);
87
+ }
88
+ readSessionFlag() {
89
+ try {
90
+ if (typeof window === "undefined")
91
+ return false;
92
+ return window.sessionStorage.getItem("sip-debug-enabled") === "true";
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+ persistSessionFlag() {
98
+ try {
99
+ if (typeof window !== "undefined") {
100
+ window.sessionStorage.setItem("sip-debug-enabled", "true");
101
+ }
102
+ } catch {
103
+ }
104
+ }
105
+ clearSessionFlag() {
106
+ try {
107
+ if (typeof window !== "undefined") {
108
+ window.sessionStorage.removeItem("sip-debug-enabled");
109
+ }
110
+ } catch {
111
+ }
112
+ }
113
+ };
114
+
115
+ // src/jssip-lib/core/types.ts
116
+ var SipStatus = {
117
+ Disconnected: "disconnected",
118
+ Connecting: "connecting",
119
+ Connected: "connected",
120
+ Registered: "registered",
121
+ Unregistered: "unregistered",
122
+ RegistrationFailed: "registrationFailed"
123
+ };
124
+ var CallStatus = {
125
+ Idle: "idle",
126
+ Dialing: "dialing",
127
+ Ringing: "ringing",
128
+ Active: "active",
129
+ Hold: "hold"
130
+ };
131
+ var CallDirection = {
132
+ Incoming: "incoming",
133
+ Outgoing: "outgoing",
134
+ None: "none"
135
+ };
136
+ Object.values(SipStatus);
137
+ Object.values(CallStatus);
138
+ Object.values(CallDirection);
139
+
140
+ // src/jssip-lib/core/eventEmitter.ts
141
+ var EventTargetEmitter = class {
142
+ constructor() {
143
+ this.target = new EventTarget();
144
+ }
145
+ on(event, fn) {
146
+ const wrapper = (e) => fn(e.detail);
147
+ this.target.addEventListener(event, wrapper);
148
+ return () => this.target.removeEventListener(event, wrapper);
149
+ }
150
+ emit(event, payload) {
151
+ this.target.dispatchEvent(
152
+ new CustomEvent(event, { detail: payload })
153
+ );
154
+ }
155
+ };
156
+
157
+ // src/jssip-lib/core/sipErrorHandler.ts
158
+ var SipErrorHandler = class {
159
+ constructor(options = {}) {
160
+ this.formatter = options.formatter;
161
+ this.messages = options.messages;
162
+ }
163
+ format(input) {
164
+ const { code, raw, fallback } = input;
165
+ const mappedMessage = code && this.messages ? this.messages[code] : void 0;
166
+ if (this.formatter) {
167
+ const custom = this.formatter({
168
+ raw,
169
+ code,
170
+ fallback: mappedMessage ?? fallback
171
+ });
172
+ const message2 = custom?.message ?? custom?.cause ?? mappedMessage ?? fallback ?? this.readRawMessage(raw) ?? "unknown error";
173
+ return {
174
+ cause: custom?.cause ?? message2,
175
+ code: custom?.code ?? code,
176
+ raw: custom?.raw ?? raw,
177
+ message: message2
178
+ };
179
+ }
180
+ const message = mappedMessage ?? this.readRawMessage(raw) ?? fallback ?? "unknown error";
181
+ return {
182
+ cause: message,
183
+ code,
184
+ raw,
185
+ message
186
+ };
187
+ }
188
+ readRawMessage(raw) {
189
+ if (raw == null)
190
+ return void 0;
191
+ if (typeof raw === "string")
192
+ return raw;
193
+ if (typeof raw?.cause === "string")
194
+ return raw.cause;
195
+ if (typeof raw?.message === "string")
196
+ return raw.message;
197
+ return void 0;
198
+ }
199
+ };
200
+
201
+ // src/jssip-lib/core/sipState.ts
202
+ function getInitialSipState() {
203
+ return {
204
+ sipStatus: SipStatus.Disconnected,
205
+ error: null,
206
+ sessions: []
207
+ };
208
+ }
209
+ function shallowEqual(objA, objB) {
210
+ if (objA === objB)
211
+ return true;
212
+ if (!objA || !objB)
213
+ return false;
214
+ const keysA = Object.keys(objA);
215
+ const keysB = Object.keys(objB);
216
+ if (keysA.length !== keysB.length)
217
+ return false;
218
+ for (const key of keysA) {
219
+ if (objA[key] !== objB[key])
220
+ return false;
221
+ }
222
+ return true;
223
+ }
224
+
225
+ // src/jssip-lib/core/sipStateStore.ts
226
+ var SipStateStore = class {
227
+ constructor() {
228
+ this.state = getInitialSipState();
229
+ this.lastState = getInitialSipState();
230
+ this.listeners = /* @__PURE__ */ new Set();
231
+ this.pendingState = null;
232
+ this.updateScheduled = false;
233
+ }
234
+ getState() {
235
+ return this.state;
236
+ }
237
+ onChange(fn) {
238
+ this.listeners.add(fn);
239
+ fn(this.state);
240
+ return () => this.listeners.delete(fn);
241
+ }
242
+ subscribe(fn) {
243
+ return this.onChange(fn);
244
+ }
245
+ setState(partial) {
246
+ if (!partial || Object.keys(partial).length === 0)
247
+ return;
248
+ const next = { ...this.state, ...partial };
249
+ if (next.sessions === this.lastState.sessions && shallowEqual(this.lastState, next)) {
250
+ return;
251
+ }
252
+ this.state = next;
253
+ this.lastState = next;
254
+ this.emit();
255
+ }
256
+ batchSet(partial) {
257
+ this.pendingState = { ...this.pendingState, ...partial };
258
+ if (!this.updateScheduled) {
259
+ this.updateScheduled = true;
260
+ queueMicrotask(() => {
261
+ if (this.pendingState)
262
+ this.setState(this.pendingState);
263
+ this.pendingState = null;
264
+ this.updateScheduled = false;
265
+ });
266
+ }
267
+ }
268
+ reset(overrides = {}) {
269
+ this.setState({ ...getInitialSipState(), ...overrides });
270
+ }
271
+ emit() {
272
+ for (const fn of this.listeners)
273
+ fn(this.state);
274
+ }
275
+ };
276
+
277
+ // src/jssip-lib/sip/handlers/uaHandlers.ts
278
+ function createUAHandlers(deps) {
279
+ const { emitter, state, cleanupAllSessions, emitError, onNewRTCSession } = deps;
280
+ return {
281
+ connecting: (e) => {
282
+ emitter.emit("connecting", e);
283
+ state.batchSet({ sipStatus: SipStatus.Connecting });
284
+ },
285
+ connected: (e) => {
286
+ emitter.emit("connected", e);
287
+ state.batchSet({ sipStatus: SipStatus.Connected });
288
+ },
289
+ disconnected: (e) => {
290
+ emitter.emit("disconnected", e);
291
+ cleanupAllSessions();
292
+ state.reset();
293
+ },
294
+ registered: (e) => {
295
+ emitter.emit("registered", e);
296
+ state.batchSet({ sipStatus: SipStatus.Registered, error: null });
297
+ },
298
+ unregistered: (e) => {
299
+ emitter.emit("unregistered", e);
300
+ state.batchSet({ sipStatus: SipStatus.Unregistered });
301
+ },
302
+ registrationFailed: (e) => {
303
+ emitter.emit("registrationFailed", e);
304
+ cleanupAllSessions();
305
+ emitError(
306
+ {
307
+ raw: e,
308
+ cause: e?.cause,
309
+ statusCode: e?.response?.status_code,
310
+ statusText: e?.response?.reason_phrase
311
+ },
312
+ "REGISTRATION_FAILED",
313
+ "registration failed"
314
+ );
315
+ state.batchSet({
316
+ sipStatus: SipStatus.RegistrationFailed,
317
+ error: e?.cause || "registration failed"
318
+ });
319
+ },
320
+ newRTCSession: onNewRTCSession,
321
+ newMessage: (e) => emitter.emit("newMessage", e),
322
+ sipEvent: (e) => emitter.emit("sipEvent", e),
323
+ newOptions: (e) => emitter.emit("newOptions", e)
324
+ };
325
+ }
326
+
327
+ // src/jssip-lib/sip/handlers/sessionHandlers.ts
328
+ function createSessionHandlers(deps) {
329
+ const {
330
+ emitter,
331
+ state,
332
+ rtc,
333
+ detachSessionHandlers,
334
+ onSessionFailed,
335
+ sessionId
336
+ } = deps;
337
+ return {
338
+ progress: (e) => {
339
+ emitter.emit("progress", { sessionId, data: e });
340
+ state.batchSet({
341
+ sessions: state.getState().sessions.map(
342
+ (s) => s.id === sessionId ? { ...s, status: CallStatus.Ringing } : s
343
+ )
344
+ });
345
+ },
346
+ accepted: (e) => {
347
+ emitter.emit("accepted", { sessionId, data: e });
348
+ state.batchSet({
349
+ sessions: state.getState().sessions.map(
350
+ (s) => s.id === sessionId ? {
351
+ ...s,
352
+ status: CallStatus.Active,
353
+ acceptedAt: s.acceptedAt ?? Date.now()
354
+ } : s
355
+ )
356
+ });
357
+ },
358
+ confirmed: (e) => emitter.emit("confirmed", { sessionId, data: e }),
359
+ ended: (e) => {
360
+ emitter.emit("ended", { sessionId, data: e });
361
+ detachSessionHandlers();
362
+ rtc.cleanup();
363
+ const nextSessions = state.getState().sessions.filter((s) => s.id !== sessionId);
364
+ state.batchSet({
365
+ sessions: nextSessions
366
+ });
367
+ },
368
+ failed: (e) => {
369
+ emitter.emit("failed", { sessionId, data: e });
370
+ detachSessionHandlers();
371
+ rtc.cleanup();
372
+ const cause = e?.cause || "call failed";
373
+ onSessionFailed(cause, e);
374
+ const nextSessions = state.getState().sessions.filter((s) => s.id !== sessionId);
375
+ state.batchSet({
376
+ sessions: nextSessions
377
+ });
378
+ },
379
+ muted: () => {
380
+ emitter.emit("muted", { sessionId, data: void 0 });
381
+ state.batchSet({
382
+ sessions: state.getState().sessions.map(
383
+ (s) => s.id === sessionId ? { ...s, muted: true } : s
384
+ )
385
+ });
386
+ },
387
+ unmuted: () => {
388
+ emitter.emit("unmuted", { sessionId, data: void 0 });
389
+ state.batchSet({
390
+ sessions: state.getState().sessions.map(
391
+ (s) => s.id === sessionId ? { ...s, muted: false } : s
392
+ )
393
+ });
394
+ },
395
+ hold: () => {
396
+ emitter.emit("hold", { sessionId, data: void 0 });
397
+ state.batchSet({
398
+ sessions: state.getState().sessions.map(
399
+ (s) => s.id === sessionId ? { ...s, status: CallStatus.Hold } : s
400
+ )
401
+ });
402
+ },
403
+ unhold: () => {
404
+ emitter.emit("unhold", { sessionId, data: void 0 });
405
+ state.batchSet({
406
+ sessions: state.getState().sessions.map(
407
+ (s) => s.id === sessionId ? { ...s, status: CallStatus.Active } : s
408
+ )
409
+ });
410
+ },
411
+ reinvite: (e) => emitter.emit("reinvite", { sessionId, data: e }),
412
+ update: (e) => emitter.emit("update", { sessionId, data: e }),
413
+ sdp: (e) => emitter.emit("sdp", { sessionId, data: e }),
414
+ icecandidate: (e) => emitter.emit("icecandidate", { sessionId, data: e }),
415
+ refer: (e) => emitter.emit("refer", { sessionId, data: e }),
416
+ replaces: (e) => emitter.emit("replaces", { sessionId, data: e }),
417
+ newDTMF: (e) => emitter.emit("newDTMF", { sessionId, data: e }),
418
+ newInfo: (e) => emitter.emit("newInfo", { sessionId, data: e }),
419
+ getusermediafailed: (e) => {
420
+ emitter.emit("getusermediafailed", { sessionId, data: e });
421
+ detachSessionHandlers();
422
+ rtc.cleanup();
423
+ onSessionFailed("getUserMedia failed", e);
424
+ state.batchSet({
425
+ sessions: state.getState().sessions.filter((s) => s.id !== sessionId)
426
+ });
427
+ },
428
+ "peerconnection:createofferfailed": (e) => {
429
+ emitter.emit("peerconnection:createofferfailed", { sessionId, data: e });
430
+ detachSessionHandlers();
431
+ rtc.cleanup();
432
+ onSessionFailed("peer connection createOffer failed", e);
433
+ state.batchSet({
434
+ sessions: state.getState().sessions.filter((s) => s.id !== sessionId)
435
+ });
436
+ },
437
+ "peerconnection:createanswerfailed": (e) => {
438
+ emitter.emit("peerconnection:createanswerfailed", { sessionId, data: e });
439
+ detachSessionHandlers();
440
+ rtc.cleanup();
441
+ onSessionFailed("peer connection createAnswer failed", e);
442
+ state.batchSet({
443
+ sessions: state.getState().sessions.filter((s) => s.id !== sessionId)
444
+ });
445
+ },
446
+ "peerconnection:setlocaldescriptionfailed": (e) => {
447
+ emitter.emit("peerconnection:setlocaldescriptionfailed", { sessionId, data: e });
448
+ detachSessionHandlers();
449
+ rtc.cleanup();
450
+ onSessionFailed("peer connection setLocalDescription failed", e);
451
+ state.batchSet({
452
+ sessions: state.getState().sessions.filter((s) => s.id !== sessionId)
453
+ });
454
+ },
455
+ "peerconnection:setremotedescriptionfailed": (e) => {
456
+ emitter.emit("peerconnection:setremotedescriptionfailed", { sessionId, data: e });
457
+ detachSessionHandlers();
458
+ rtc.cleanup();
459
+ onSessionFailed("peer connection setRemoteDescription failed", e);
460
+ state.batchSet({
461
+ sessions: state.getState().sessions.filter((s) => s.id !== sessionId)
462
+ });
463
+ },
464
+ peerconnection: (e) => emitter.emit("peerconnection", { sessionId, data: e })
465
+ };
466
+ }
467
+
468
+ // src/jssip-lib/sip/sessionController.ts
469
+ var WebRTCSessionController = class {
470
+ constructor() {
471
+ this.currentSession = null;
472
+ this.mediaStream = null;
473
+ }
474
+ setSession(session) {
475
+ this.currentSession = session;
476
+ }
477
+ setMediaStream(stream) {
478
+ this.mediaStream = stream;
479
+ }
480
+ getPC() {
481
+ return this.currentSession?.connection ?? null;
482
+ }
483
+ cleanup(stopTracks = true) {
484
+ const pc = this.getPC();
485
+ if (pc && typeof pc.getSenders === "function") {
486
+ const isClosed = pc.connectionState === "closed" || pc.signalingState === "closed";
487
+ if (!isClosed) {
488
+ for (const s of pc.getSenders()) {
489
+ try {
490
+ s.replaceTrack(null);
491
+ } catch {
492
+ }
493
+ }
494
+ }
495
+ }
496
+ if (stopTracks && this.mediaStream) {
497
+ for (const t of this.mediaStream.getTracks())
498
+ t.stop();
499
+ }
500
+ this.mediaStream = null;
501
+ this.currentSession = null;
502
+ }
503
+ answer(options = {}) {
504
+ return this.currentSession ? (this.currentSession.answer(options), true) : false;
505
+ }
506
+ hangup(options) {
507
+ return this.currentSession ? (this.currentSession.terminate(
508
+ options ?? { status_code: 486, reason_phrase: "Busy Here" }
509
+ ), true) : false;
510
+ }
511
+ mute() {
512
+ this.mediaStream?.getAudioTracks().forEach((t) => t.enabled = false);
513
+ return this.currentSession ? (this.currentSession.mute({ audio: true }), true) : false;
514
+ }
515
+ unmute() {
516
+ this.mediaStream?.getAudioTracks().forEach((t) => t.enabled = true);
517
+ return this.currentSession ? (this.currentSession.unmute({ audio: true }), true) : false;
518
+ }
519
+ hold() {
520
+ return this.currentSession ? (this.currentSession.hold(), true) : false;
521
+ }
522
+ unhold() {
523
+ return this.currentSession ? (this.currentSession.unhold(), true) : false;
524
+ }
525
+ sendDTMF(tones, options) {
526
+ return this.currentSession ? (this.currentSession.sendDTMF(tones, options), true) : false;
527
+ }
528
+ transfer(target, options) {
529
+ return this.currentSession ? (this.currentSession.refer(target, options), true) : false;
530
+ }
531
+ attendedTransfer(otherSession) {
532
+ return this.currentSession ? (this.currentSession.refer(otherSession), true) : false;
533
+ }
534
+ enableVideo() {
535
+ this.mediaStream?.getVideoTracks().forEach((t) => t.enabled = true);
536
+ }
537
+ disableVideo() {
538
+ this.mediaStream?.getVideoTracks().forEach((t) => t.enabled = false);
539
+ }
540
+ async switchCamera(nextVideoTrack) {
541
+ const pc = this.getPC();
542
+ if (!pc)
543
+ return false;
544
+ if (!this.mediaStream)
545
+ this.mediaStream = new MediaStream();
546
+ const old = this.mediaStream.getVideoTracks()[0];
547
+ this.mediaStream.addTrack(nextVideoTrack);
548
+ if (old)
549
+ this.mediaStream.removeTrack(old);
550
+ const sender = pc.getSenders?.().find((s) => s.track?.kind === "video");
551
+ if (sender)
552
+ await sender.replaceTrack(nextVideoTrack);
553
+ if (old && old !== nextVideoTrack)
554
+ old.stop();
555
+ return true;
556
+ }
557
+ async startScreenShare(getDisplayMedia) {
558
+ const display = await getDisplayMedia();
559
+ const screen = display.getVideoTracks()[0];
560
+ return screen ? this.switchCamera(screen) : false;
561
+ }
562
+ };
563
+
564
+ // src/jssip-lib/sip/sessionManager.ts
565
+ var SessionManager = class {
566
+ constructor() {
567
+ this.entries = /* @__PURE__ */ new Map();
568
+ this.pendingMediaQueue = [];
569
+ this.pendingMediaTtlMs = 3e4;
570
+ }
571
+ setPendingMediaTtl(ms) {
572
+ if (typeof ms === "number" && ms > 0)
573
+ this.pendingMediaTtlMs = ms;
574
+ }
575
+ enqueueOutgoingMedia(stream) {
576
+ this.pendingMediaQueue.push({ stream, addedAt: Date.now() });
577
+ }
578
+ dequeueOutgoingMedia() {
579
+ const now = Date.now();
580
+ while (this.pendingMediaQueue.length) {
581
+ const next = this.pendingMediaQueue.shift();
582
+ if (!next)
583
+ break;
584
+ if (now - next.addedAt <= this.pendingMediaTtlMs) {
585
+ return next.stream;
586
+ } else {
587
+ next.stream.getTracks().forEach((t) => t.stop());
588
+ }
589
+ }
590
+ return null;
591
+ }
592
+ getOrCreateRtc(sessionId, session) {
593
+ let entry = this.entries.get(sessionId);
594
+ if (!entry) {
595
+ entry = { rtc: new WebRTCSessionController(), session: null, media: null };
596
+ this.entries.set(sessionId, entry);
597
+ }
598
+ if (session) {
599
+ entry.session = session;
600
+ entry.rtc.setSession(session);
601
+ }
602
+ if (entry.media)
603
+ entry.rtc.setMediaStream(entry.media);
604
+ return entry.rtc;
605
+ }
606
+ getRtc(sessionId) {
607
+ return this.entries.get(sessionId)?.rtc ?? null;
608
+ }
609
+ setSession(sessionId, session) {
610
+ const entry = this.entries.get(sessionId);
611
+ if (entry) {
612
+ entry.session = session;
613
+ entry.rtc.setSession(session);
614
+ } else {
615
+ this.entries.set(sessionId, {
616
+ rtc: new WebRTCSessionController(),
617
+ session,
618
+ media: null
619
+ });
620
+ }
621
+ }
622
+ setSessionMedia(sessionId, stream) {
623
+ const entry = this.entries.get(sessionId) ?? {
624
+ rtc: new WebRTCSessionController(),
625
+ session: null,
626
+ media: null
627
+ };
628
+ entry.media = stream;
629
+ entry.rtc.setMediaStream(stream);
630
+ this.entries.set(sessionId, entry);
631
+ }
632
+ getSession(sessionId) {
633
+ return this.entries.get(sessionId)?.session ?? null;
634
+ }
635
+ getSessionIds() {
636
+ return Array.from(this.entries.keys());
637
+ }
638
+ getSessions() {
639
+ return Array.from(this.entries.entries()).map(([id, entry]) => ({
640
+ id,
641
+ session: entry.session
642
+ }));
643
+ }
644
+ getActiveSessionId(activeStatuses = ["active"]) {
645
+ for (const [id, entry] of Array.from(this.entries.entries()).reverse()) {
646
+ const status = entry.session?.status;
647
+ if (status && activeStatuses.includes(String(status).toLowerCase())) {
648
+ return id;
649
+ }
650
+ }
651
+ return null;
652
+ }
653
+ cleanupSession(sessionId) {
654
+ const entry = this.entries.get(sessionId);
655
+ if (entry) {
656
+ entry.rtc.cleanup();
657
+ this.entries.delete(sessionId);
658
+ }
659
+ }
660
+ cleanupAllSessions() {
661
+ for (const [, entry] of this.entries.entries()) {
662
+ entry.rtc.cleanup();
663
+ }
664
+ this.entries.clear();
665
+ this.pendingMediaQueue = [];
666
+ }
667
+ answer(sessionId, options) {
668
+ const rtc = this.getRtc(sessionId);
669
+ return rtc ? rtc.answer(options) : false;
670
+ }
671
+ hangup(sessionId, options) {
672
+ const rtc = this.getRtc(sessionId);
673
+ return rtc ? rtc.hangup(options) : false;
674
+ }
675
+ mute(sessionId) {
676
+ const rtc = this.getRtc(sessionId);
677
+ return rtc ? rtc.mute() : false;
678
+ }
679
+ unmute(sessionId) {
680
+ const rtc = this.getRtc(sessionId);
681
+ return rtc ? rtc.unmute() : false;
682
+ }
683
+ hold(sessionId) {
684
+ const rtc = this.getRtc(sessionId);
685
+ return rtc ? rtc.hold() : false;
686
+ }
687
+ unhold(sessionId) {
688
+ const rtc = this.getRtc(sessionId);
689
+ return rtc ? rtc.unhold() : false;
690
+ }
691
+ sendDTMF(sessionId, tones, options) {
692
+ const rtc = this.getRtc(sessionId);
693
+ return rtc ? rtc.sendDTMF(tones, options) : false;
694
+ }
695
+ transfer(sessionId, target, options) {
696
+ const rtc = this.getRtc(sessionId);
697
+ return rtc ? rtc.transfer(target, options) : false;
698
+ }
699
+ attendedTransfer(sessionId, otherSession) {
700
+ const rtc = this.getRtc(sessionId);
701
+ return rtc ? rtc.attendedTransfer(otherSession) : false;
702
+ }
703
+ startScreenShare(sessionId, getDisplayMedia) {
704
+ const rtc = this.getRtc(sessionId);
705
+ return rtc ? rtc.startScreenShare(getDisplayMedia) : false;
706
+ }
707
+ };
708
+
709
+ // src/jssip-lib/sip/sessionState.ts
710
+ function holdOtherSessions(state, sessionId, holdFn, updateSession) {
711
+ const current = state.getState();
712
+ current.sessions.forEach((s) => {
713
+ if (s.id === sessionId)
714
+ return;
715
+ if (s.status === CallStatus.Active) {
716
+ holdFn(s.id);
717
+ updateSession(s.id, { status: CallStatus.Hold });
718
+ }
719
+ });
720
+ }
721
+ function upsertSessionState(state, sessionId, partial) {
722
+ const current = state.getState();
723
+ const existing = current.sessions.find((s) => s.id === sessionId);
724
+ const base = existing ?? {
725
+ id: sessionId,
726
+ status: CallStatus.Idle,
727
+ direction: CallDirection.None,
728
+ from: null,
729
+ to: null,
730
+ muted: false,
731
+ acceptedAt: null,
732
+ mediaKind: "audio",
733
+ remoteVideoEnabled: false
734
+ };
735
+ const nextSession = { ...base, ...partial };
736
+ const sessions = existing ? current.sessions.map((s) => s.id === sessionId ? nextSession : s) : [...current.sessions, nextSession];
737
+ state.setState({ sessions });
738
+ }
739
+ function removeSessionState(state, sessionId) {
740
+ const current = state.getState();
741
+ const sessions = current.sessions.filter((s) => s.id !== sessionId);
742
+ state.setState({
743
+ sessions,
744
+ error: null
745
+ });
746
+ }
747
+
748
+ // src/jssip-lib/sip/sessionLifecycle.ts
749
+ var SessionLifecycle = class {
750
+ constructor(deps) {
751
+ this.state = deps.state;
752
+ this.sessionManager = deps.sessionManager;
753
+ this.emit = deps.emit;
754
+ this.emitError = deps.emitError;
755
+ this.attachSessionHandlers = deps.attachSessionHandlers;
756
+ this.getMaxSessionCount = deps.getMaxSessionCount;
757
+ }
758
+ handleNewRTCSession(e) {
759
+ const session = e.session;
760
+ const sessionId = String(session?.id ?? crypto.randomUUID?.() ?? Date.now());
761
+ const currentSessions = this.state.getState().sessions;
762
+ if (currentSessions.length >= this.getMaxSessionCount()) {
763
+ try {
764
+ session.terminate?.({ status_code: 486, reason_phrase: "Busy Here" });
765
+ } catch {
766
+ }
767
+ if (e.originator === "remote") {
768
+ this.emit("missed", { sessionId, data: e });
769
+ }
770
+ this.emitError("max session count reached", "MAX_SESSIONS_REACHED", "max session count reached");
771
+ return;
772
+ }
773
+ const outgoingMedia = e.originator === "local" ? this.sessionManager.dequeueOutgoingMedia() : null;
774
+ if (outgoingMedia)
775
+ this.sessionManager.setSessionMedia(sessionId, outgoingMedia);
776
+ const rtc = this.sessionManager.getOrCreateRtc(sessionId, session);
777
+ if (outgoingMedia)
778
+ rtc.setMediaStream(outgoingMedia);
779
+ this.sessionManager.setSession(sessionId, session);
780
+ this.attachSessionHandlers(sessionId, session);
781
+ holdOtherSessions(
782
+ this.state,
783
+ sessionId,
784
+ (id) => {
785
+ const otherRtc = this.sessionManager.getRtc(id);
786
+ otherRtc?.hold();
787
+ },
788
+ (id, partial) => upsertSessionState(this.state, id, partial)
789
+ );
790
+ const sdpHasVideo = e.request?.body && e.request.body.toString().includes("m=video") || session?.connection?.getReceivers?.()?.some((r) => r.track?.kind === "video");
791
+ upsertSessionState(this.state, sessionId, {
792
+ direction: e.originator === "remote" ? CallDirection.Incoming : CallDirection.Outgoing,
793
+ from: e.originator === "remote" ? e.request.from.uri.user : null,
794
+ to: e.request.to.uri.user,
795
+ status: e.originator === "remote" ? CallStatus.Ringing : CallStatus.Dialing,
796
+ mediaKind: sdpHasVideo ? "video" : "audio",
797
+ remoteVideoEnabled: sdpHasVideo
798
+ });
799
+ this.emit("newRTCSession", { sessionId, data: e });
800
+ }
801
+ };
802
+
803
+ // src/jssip-lib/sip/client.ts
804
+ var SipClient = class extends EventTargetEmitter {
805
+ constructor(options = {}) {
806
+ super();
807
+ this.userAgent = new SipUserAgent();
808
+ this.stateStore = new SipStateStore();
809
+ this.sessionHandlers = /* @__PURE__ */ new Map();
810
+ this.maxSessionCount = Infinity;
811
+ this.sessionManager = new SessionManager();
812
+ this.errorHandler = options.errorHandler ?? new SipErrorHandler({
813
+ formatter: options.formatError,
814
+ messages: options.errorMessages
815
+ });
816
+ this.debugPattern = options.debug;
817
+ this.uaHandlers = createUAHandlers({
818
+ emitter: this,
819
+ state: this.stateStore,
820
+ cleanupAllSessions: () => this.cleanupAllSessions(),
821
+ emitError: (raw, code, fallback) => this.emitError(raw, code, fallback),
822
+ onNewRTCSession: (e) => this.onNewRTCSession(e)
823
+ });
824
+ this.uaHandlerKeys = Object.keys(this.uaHandlers);
825
+ this.lifecycle = new SessionLifecycle({
826
+ state: this.stateStore,
827
+ sessionManager: this.sessionManager,
828
+ emit: (event, payload) => this.emit(event, payload),
829
+ emitError: (raw, code, fallback) => this.emitError(raw, code, fallback),
830
+ attachSessionHandlers: (sessionId, session) => this.attachSessionHandlers(sessionId, session),
831
+ getMaxSessionCount: () => this.maxSessionCount
832
+ });
833
+ }
834
+ get state() {
835
+ return this.stateStore.getState();
836
+ }
837
+ connect(uri, password, config) {
838
+ this.disconnect();
839
+ this.stateStore.setState({ sipStatus: SipStatus.Connecting });
840
+ const { debug: cfgDebug, maxSessionCount, pendingMediaTtlMs, ...uaCfg } = config;
841
+ this.maxSessionCount = typeof maxSessionCount === "number" ? maxSessionCount : Infinity;
842
+ this.sessionManager.setPendingMediaTtl(pendingMediaTtlMs);
843
+ const debug = this.debugPattern ?? cfgDebug;
844
+ this.userAgent.start(uri, password, uaCfg, { debug });
845
+ this.attachUAHandlers();
846
+ }
847
+ registerUA() {
848
+ this.userAgent.register();
849
+ }
850
+ disconnect() {
851
+ this.detachUAHandlers();
852
+ this.userAgent.stop();
853
+ this.cleanupAllSessions();
854
+ this.stateStore.reset();
855
+ }
856
+ call(target, callOptions = {}) {
857
+ try {
858
+ if (callOptions.mediaStream)
859
+ this.sessionManager.enqueueOutgoingMedia(callOptions.mediaStream);
860
+ const ua = this.userAgent.getUA();
861
+ ua?.call(target, callOptions);
862
+ } catch (e) {
863
+ const err = this.emitError(e, "CALL_FAILED", "call failed");
864
+ this.cleanupAllSessions();
865
+ this.stateStore.batchSet({
866
+ error: err.cause
867
+ });
868
+ }
869
+ }
870
+ answer(options = {}) {
871
+ return this.answerSession(void 0, options);
872
+ }
873
+ hangup(options) {
874
+ return this.hangupSession(void 0, options);
875
+ }
876
+ mute() {
877
+ return this.muteSession();
878
+ }
879
+ unmute() {
880
+ return this.unmuteSession();
881
+ }
882
+ hold() {
883
+ return this.holdSession();
884
+ }
885
+ unhold() {
886
+ return this.unholdSession();
887
+ }
888
+ sendDTMF(tones, options) {
889
+ const sessionId = this.resolveSessionId();
890
+ return this.sendDTMFSession(tones, options, sessionId ?? void 0);
891
+ }
892
+ transfer(target, options) {
893
+ return this.transferSession(target, options);
894
+ }
895
+ attendedTransfer(otherSession) {
896
+ return this.attendedTransferSession(otherSession);
897
+ }
898
+ onChange(fn) {
899
+ return this.stateStore.onChange(fn);
900
+ }
901
+ attachUAHandlers() {
902
+ const ua = this.userAgent.ua;
903
+ if (!ua)
904
+ return;
905
+ this.detachUAHandlers();
906
+ this.uaHandlerKeys.forEach((ev) => {
907
+ const h = this.uaHandlers[ev];
908
+ if (h)
909
+ ua.on(ev, h);
910
+ });
911
+ }
912
+ setDebug(debug) {
913
+ this.debugPattern = debug;
914
+ this.userAgent.setDebug(debug);
915
+ }
916
+ attachSessionHandlers(sessionId, session) {
917
+ const handlers = this.createSessionHandlersFor(sessionId, session);
918
+ this.sessionHandlers.set(sessionId, handlers);
919
+ Object.keys(handlers).forEach((ev) => {
920
+ const h = handlers[ev];
921
+ if (h)
922
+ session.on(ev, h);
923
+ });
924
+ }
925
+ detachSessionHandlers(sessionId, session) {
926
+ const handlers = this.sessionHandlers.get(sessionId);
927
+ if (!handlers || !session)
928
+ return;
929
+ Object.keys(handlers).forEach((ev) => {
930
+ const h = handlers[ev];
931
+ if (h)
932
+ session.off(ev, h);
933
+ });
934
+ this.sessionHandlers.delete(sessionId);
935
+ }
936
+ detachUAHandlers() {
937
+ const ua = this.userAgent.ua;
938
+ if (!ua)
939
+ return;
940
+ this.uaHandlerKeys.forEach((ev) => {
941
+ const h = this.uaHandlers[ev];
942
+ if (h)
943
+ ua.off(ev, h);
944
+ });
945
+ }
946
+ cleanupSession(sessionId, session) {
947
+ const existingSession = this.sessionManager.getSession(sessionId);
948
+ this.emit("sessionCleanup", { sessionId, session: existingSession });
949
+ const targetSession = session ?? this.sessionManager.getSession(sessionId) ?? this.sessionManager.getRtc(sessionId)?.currentSession;
950
+ this.detachSessionHandlers(sessionId, targetSession);
951
+ this.sessionManager.cleanupSession(sessionId);
952
+ removeSessionState(this.stateStore, sessionId);
953
+ }
954
+ cleanupAllSessions() {
955
+ const ids = this.sessionManager.getSessionIds();
956
+ ids.forEach((id) => {
957
+ const s = this.sessionManager.getSession(id);
958
+ this.emit("sessionCleanup", { sessionId: id, session: s });
959
+ });
960
+ this.sessionManager.cleanupAllSessions();
961
+ this.sessionHandlers.clear();
962
+ this.stateStore.setState({
963
+ sessions: [],
964
+ error: null
965
+ });
966
+ }
967
+ createSessionHandlersFor(sessionId, session) {
968
+ const rtc = this.sessionManager.getOrCreateRtc(sessionId, session);
969
+ return createSessionHandlers({
970
+ emitter: this,
971
+ state: this.stateStore,
972
+ rtc,
973
+ detachSessionHandlers: () => this.cleanupSession(sessionId, session),
974
+ onSessionFailed: (err, event) => this.onSessionFailed(err, event),
975
+ sessionId
976
+ });
977
+ }
978
+ onNewRTCSession(e) {
979
+ this.lifecycle.handleNewRTCSession(e);
980
+ }
981
+ onSessionFailed(error, event) {
982
+ const rawCause = event?.cause ?? error;
983
+ const statusCode = event?.message?.status_code;
984
+ const statusText = event?.message?.reason_phrase;
985
+ const causeText = rawCause || (statusCode ? `${statusCode}${statusText ? " " + statusText : ""}` : "call failed");
986
+ this.emitError(
987
+ { raw: event, cause: rawCause, statusCode, statusText },
988
+ "SESSION_FAILED",
989
+ causeText
990
+ );
991
+ }
992
+ emitError(raw, code, fallback) {
993
+ const payload = this.errorHandler.format({ raw, code, fallback });
994
+ this.emit("error", payload);
995
+ return payload;
996
+ }
997
+ resolveSessionId(sessionId) {
998
+ if (sessionId)
999
+ return sessionId;
1000
+ const sessions = this.stateStore.getState().sessions;
1001
+ const active = sessions.find((s) => s.status === CallStatus.Active);
1002
+ return active?.id ?? sessions[0]?.id ?? null;
1003
+ }
1004
+ answerSession(sessionIdOrOptions, options) {
1005
+ const sessionId = typeof sessionIdOrOptions === "string" ? sessionIdOrOptions : this.resolveSessionId();
1006
+ const opts = typeof sessionIdOrOptions === "string" ? options ?? {} : sessionIdOrOptions ?? {};
1007
+ if (!sessionId)
1008
+ return false;
1009
+ holdOtherSessions(
1010
+ this.stateStore,
1011
+ sessionId,
1012
+ (id) => {
1013
+ const rtc = this.sessionManager.getRtc(id);
1014
+ rtc?.hold();
1015
+ },
1016
+ (id, partial) => upsertSessionState(this.stateStore, id, partial)
1017
+ );
1018
+ return this.sessionManager.answer(sessionId, opts);
1019
+ }
1020
+ hangupSession(sessionIdOrOptions, options) {
1021
+ const sessionId = typeof sessionIdOrOptions === "string" ? sessionIdOrOptions : this.resolveSessionId();
1022
+ const opts = typeof sessionIdOrOptions === "string" ? options : sessionIdOrOptions;
1023
+ if (!sessionId)
1024
+ return false;
1025
+ return this.sessionManager.hangup(sessionId, opts);
1026
+ }
1027
+ muteSession(sessionId) {
1028
+ const resolved = this.resolveSessionId(sessionId);
1029
+ if (!resolved)
1030
+ return false;
1031
+ const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
1032
+ if (sessionState?.muted)
1033
+ return true;
1034
+ this.sessionManager.mute(resolved);
1035
+ upsertSessionState(this.stateStore, resolved, { muted: true });
1036
+ return true;
1037
+ }
1038
+ unmuteSession(sessionId) {
1039
+ const resolved = this.resolveSessionId(sessionId);
1040
+ if (!resolved)
1041
+ return false;
1042
+ const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
1043
+ if (!sessionState?.muted)
1044
+ return true;
1045
+ this.sessionManager.unmute(resolved);
1046
+ upsertSessionState(this.stateStore, resolved, { muted: false });
1047
+ return true;
1048
+ }
1049
+ holdSession(sessionId) {
1050
+ const resolved = this.resolveSessionId(sessionId);
1051
+ if (!resolved)
1052
+ return false;
1053
+ const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
1054
+ if (sessionState?.status === CallStatus.Active) {
1055
+ this.sessionManager.hold(resolved);
1056
+ upsertSessionState(this.stateStore, resolved, { status: CallStatus.Hold });
1057
+ }
1058
+ return true;
1059
+ }
1060
+ unholdSession(sessionId) {
1061
+ const resolved = this.resolveSessionId(sessionId);
1062
+ if (!resolved)
1063
+ return false;
1064
+ const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
1065
+ if (sessionState?.status === CallStatus.Hold) {
1066
+ this.sessionManager.unhold(resolved);
1067
+ upsertSessionState(this.stateStore, resolved, {
1068
+ status: CallStatus.Active
1069
+ });
1070
+ }
1071
+ return true;
1072
+ }
1073
+ sendDTMFSession(tones, options, sessionId) {
1074
+ const resolved = this.resolveSessionId(sessionId);
1075
+ if (!resolved)
1076
+ return false;
1077
+ const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
1078
+ if (sessionState?.status === CallStatus.Active)
1079
+ this.sessionManager.sendDTMF(resolved, tones, options);
1080
+ return true;
1081
+ }
1082
+ transferSession(target, options, sessionId) {
1083
+ const resolved = this.resolveSessionId(sessionId);
1084
+ if (!resolved)
1085
+ return false;
1086
+ const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
1087
+ if (sessionState?.status === CallStatus.Active)
1088
+ this.sessionManager.transfer(resolved, target, options);
1089
+ return true;
1090
+ }
1091
+ attendedTransferSession(otherSession, sessionId) {
1092
+ const resolved = this.resolveSessionId(sessionId);
1093
+ if (!resolved)
1094
+ return false;
1095
+ const sessionState = this.stateStore.getState().sessions.find((s) => s.id === resolved);
1096
+ if (sessionState?.status === CallStatus.Active)
1097
+ this.sessionManager.attendedTransfer(resolved, otherSession);
1098
+ return true;
1099
+ }
1100
+ setSessionMedia(sessionId, stream) {
1101
+ this.sessionManager.setSessionMedia(sessionId, stream);
1102
+ }
1103
+ switchCameraSession(sessionId, track) {
1104
+ const rtc = this.sessionManager.getRtc(sessionId);
1105
+ return rtc ? rtc.switchCamera(track) : false;
1106
+ }
1107
+ startScreenShareSession(sessionId, getDisplayMedia) {
1108
+ return this.sessionManager.startScreenShare(sessionId, getDisplayMedia);
1109
+ }
1110
+ enableVideoSession(sessionId) {
1111
+ const rtc = this.sessionManager.getRtc(sessionId);
1112
+ rtc?.enableVideo();
1113
+ return !!rtc;
1114
+ }
1115
+ disableVideoSession(sessionId) {
1116
+ const rtc = this.sessionManager.getRtc(sessionId);
1117
+ rtc?.disableVideo();
1118
+ return !!rtc;
1119
+ }
1120
+ getSession(sessionId) {
1121
+ return this.sessionManager.getSession(sessionId);
1122
+ }
1123
+ getSessionIds() {
1124
+ return this.sessionManager.getSessionIds();
1125
+ }
1126
+ getSessions() {
1127
+ return this.sessionManager.getSessions();
1128
+ }
1129
+ };
1130
+ function createSipClientInstance(options) {
1131
+ return new SipClient(options);
1132
+ }
1133
+ function createSipEventManager(client) {
1134
+ return {
1135
+ on(event, handler) {
1136
+ return client.on(event, handler);
1137
+ },
1138
+ onSession(sessionId, event, handler) {
1139
+ return client.on(event, (payload) => {
1140
+ if (payload && "sessionId" in payload && payload.sessionId === sessionId) {
1141
+ handler(payload);
1142
+ }
1143
+ });
1144
+ },
1145
+ bind(handlers) {
1146
+ const offs = [];
1147
+ Object.keys(handlers).forEach((event) => {
1148
+ const handler = handlers[event];
1149
+ if (handler) {
1150
+ offs.push(client.on(event, handler));
1151
+ }
1152
+ });
1153
+ return () => offs.forEach((off) => off());
1154
+ },
1155
+ bindSession(sessionId, handlers) {
1156
+ const offs = [];
1157
+ Object.keys(handlers).forEach((event) => {
1158
+ const handler = handlers[event];
1159
+ if (handler) {
1160
+ offs.push(
1161
+ client.on(event, (payload) => {
1162
+ if (payload && "sessionId" in payload && payload.sessionId === sessionId) {
1163
+ handler(payload);
1164
+ }
1165
+ })
1166
+ );
1167
+ }
1168
+ });
1169
+ return () => offs.forEach((off) => off());
1170
+ }
1171
+ };
1172
+ }
1173
+ var SipContext = react.createContext(null);
1174
+ function useSip() {
1175
+ const ctx = react.useContext(SipContext);
1176
+ if (!ctx)
1177
+ throw new Error("Must be used within SipProvider");
1178
+ return ctx;
1179
+ }
1180
+
1181
+ // src/hooks/useSipState.ts
1182
+ function useSipState() {
1183
+ const { client } = useSip();
1184
+ const subscribe = react.useCallback(
1185
+ (onStoreChange) => client.onChange(onStoreChange),
1186
+ [client]
1187
+ );
1188
+ const getSnapshot = react.useCallback(() => client.state, [client]);
1189
+ return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1190
+ }
1191
+ function useSipActions() {
1192
+ const { client } = useSip();
1193
+ return react.useMemo(
1194
+ () => ({
1195
+ call: (...args) => client.call(...args),
1196
+ answer: (...args) => client.answerSession(...args),
1197
+ hangup: (...args) => client.hangupSession(...args),
1198
+ mute: (...args) => client.muteSession(...args),
1199
+ unmute: (...args) => client.unmuteSession(...args),
1200
+ hold: (...args) => client.holdSession(...args),
1201
+ unhold: (...args) => client.unholdSession(...args),
1202
+ sendDTMF: (...args) => client.sendDTMFSession(...args),
1203
+ transfer: (...args) => client.transferSession(...args),
1204
+ attendedTransfer: (...args) => client.attendedTransferSession(...args),
1205
+ getSession: (...args) => client.getSession(...args),
1206
+ getSessionIds: () => client.getSessionIds(),
1207
+ getSessions: () => client.getSessions(),
1208
+ setSessionMedia: (...args) => client.setSessionMedia(...args),
1209
+ switchCamera: (...args) => client.switchCameraSession(...args),
1210
+ startScreenShare: (...args) => client.startScreenShareSession(...args),
1211
+ enableVideo: (...args) => client.enableVideoSession(...args),
1212
+ disableVideo: (...args) => client.disableVideoSession(...args)
1213
+ }),
1214
+ [client]
1215
+ );
1216
+ }
1217
+
1218
+ // src/hooks/useSipSessions.ts
1219
+ function useSipSessions() {
1220
+ const { sessions } = useSipState();
1221
+ return { sessions };
1222
+ }
1223
+ function useSipEvent(event, handler) {
1224
+ const { sipEventManager } = useSip();
1225
+ react.useEffect(() => {
1226
+ if (!handler)
1227
+ return;
1228
+ return sipEventManager.on(event, handler);
1229
+ }, [event, handler, sipEventManager]);
1230
+ }
1231
+ function useSipSessionEvent(sessionId, event, handler) {
1232
+ const { sipEventManager } = useSip();
1233
+ react.useEffect(() => {
1234
+ if (!handler || !sessionId)
1235
+ return;
1236
+ return sipEventManager.onSession(sessionId, event, handler);
1237
+ }, [event, handler, sessionId, sipEventManager]);
1238
+ }
1239
+
1240
+ // src/jssip-lib/adapters/dom/createCallPlayer.ts
1241
+ function createCallPlayer(audioEl) {
1242
+ let cleanupTrackListener = null;
1243
+ let cleanupSessionPeerListener = null;
1244
+ let cleanupClientListeners = null;
1245
+ const dispose = (fn) => {
1246
+ if (fn)
1247
+ fn();
1248
+ return null;
1249
+ };
1250
+ function clearAudioStream(stream) {
1251
+ if (stream) {
1252
+ for (const t of stream.getTracks()) {
1253
+ t.stop();
1254
+ }
1255
+ }
1256
+ audioEl.srcObject = null;
1257
+ }
1258
+ const attachTracks = (pc) => {
1259
+ const onTrack = (e) => {
1260
+ if (e.track.kind !== "audio")
1261
+ return;
1262
+ const nextStream = e.streams?.[0] ?? new MediaStream([e.track]);
1263
+ const prev = audioEl.srcObject;
1264
+ if (prev && prev !== nextStream) {
1265
+ clearAudioStream(prev);
1266
+ }
1267
+ audioEl.srcObject = nextStream;
1268
+ audioEl.play?.().catch(() => {
1269
+ });
1270
+ };
1271
+ pc.addEventListener("track", onTrack);
1272
+ return () => pc.removeEventListener("track", onTrack);
1273
+ };
1274
+ const listenSessionPeerconnection = (session) => {
1275
+ const onPeer = (data) => {
1276
+ cleanupTrackListener = dispose(cleanupTrackListener);
1277
+ cleanupTrackListener = attachTracks(data.peerconnection);
1278
+ };
1279
+ session.on("peerconnection", onPeer);
1280
+ return () => session.off("peerconnection", onPeer);
1281
+ };
1282
+ function bindToSession(session) {
1283
+ if (session?.direction === "outgoing" && session.connection instanceof RTCPeerConnection) {
1284
+ cleanupTrackListener = dispose(cleanupTrackListener);
1285
+ cleanupTrackListener = attachTracks(session.connection);
1286
+ }
1287
+ cleanupSessionPeerListener = dispose(cleanupSessionPeerListener);
1288
+ cleanupSessionPeerListener = listenSessionPeerconnection(session);
1289
+ return () => {
1290
+ cleanupSessionPeerListener = dispose(cleanupSessionPeerListener);
1291
+ cleanupTrackListener = dispose(cleanupTrackListener);
1292
+ };
1293
+ }
1294
+ function bindToClient(client) {
1295
+ const offNew = client.on("newRTCSession", (payload) => {
1296
+ const e = payload?.data;
1297
+ cleanupSessionPeerListener = dispose(cleanupSessionPeerListener);
1298
+ cleanupTrackListener = dispose(cleanupTrackListener);
1299
+ if (!e?.session)
1300
+ return;
1301
+ cleanupSessionPeerListener = listenSessionPeerconnection(e.session);
1302
+ if (e.session.direction === "outgoing" && e.session.connection instanceof RTCPeerConnection) {
1303
+ cleanupTrackListener = attachTracks(e.session.connection);
1304
+ }
1305
+ });
1306
+ const offEnded = client.on("ended", () => detach());
1307
+ const offFailed = client.on("failed", () => detach());
1308
+ const offDisconnected = client.on("disconnected", () => detach());
1309
+ cleanupClientListeners = () => {
1310
+ offNew();
1311
+ offEnded();
1312
+ offFailed();
1313
+ offDisconnected();
1314
+ };
1315
+ return cleanupClientListeners;
1316
+ }
1317
+ function detach() {
1318
+ cleanupClientListeners = dispose(cleanupClientListeners);
1319
+ cleanupSessionPeerListener = dispose(cleanupSessionPeerListener);
1320
+ cleanupTrackListener = dispose(cleanupTrackListener);
1321
+ clearAudioStream(audioEl.srcObject);
1322
+ }
1323
+ return {
1324
+ bindToSession,
1325
+ bindToClient,
1326
+ detach
1327
+ };
1328
+ }
1329
+ function CallPlayer({ sessionId }) {
1330
+ const { client } = useSip();
1331
+ const audioRef = react.useRef(null);
1332
+ react.useEffect(() => {
1333
+ if (!audioRef.current)
1334
+ return;
1335
+ const player = createCallPlayer(audioRef.current);
1336
+ const session = sessionId ? client.getSession(sessionId) : null;
1337
+ const off = session ? player.bindToSession(session) : player.bindToClient(client);
1338
+ return () => {
1339
+ off?.();
1340
+ player.detach();
1341
+ };
1342
+ }, [client, sessionId]);
1343
+ return /* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, autoPlay: true, playsInline: true });
1344
+ }
1345
+ function SipProvider({
1346
+ client,
1347
+ children,
1348
+ sipEventManager
1349
+ }) {
1350
+ const manager = react.useMemo(
1351
+ () => sipEventManager ?? createSipEventManager(client),
1352
+ [client, sipEventManager]
1353
+ );
1354
+ const contextValue = react.useMemo(() => ({ client, sipEventManager: manager }), [client, manager]);
1355
+ return /* @__PURE__ */ jsxRuntime.jsx(SipContext.Provider, { value: contextValue, children });
1356
+ }
1357
+
1358
+ Object.defineProperty(exports, "WebSocketInterface", {
1359
+ enumerable: true,
1360
+ get: function () { return JsSIP.WebSocketInterface; }
1361
+ });
1362
+ exports.CallDirection = CallDirection;
1363
+ exports.CallPlayer = CallPlayer;
1364
+ exports.CallStatus = CallStatus;
1365
+ exports.SipContext = SipContext;
1366
+ exports.SipProvider = SipProvider;
1367
+ exports.SipStatus = SipStatus;
1368
+ exports.createSipClientInstance = createSipClientInstance;
1369
+ exports.createSipEventManager = createSipEventManager;
1370
+ exports.useSip = useSip;
1371
+ exports.useSipActions = useSipActions;
1372
+ exports.useSipEvent = useSipEvent;
1373
+ exports.useSipSessionEvent = useSipSessionEvent;
1374
+ exports.useSipSessions = useSipSessions;
1375
+ exports.useSipState = useSipState;
1376
+ //# sourceMappingURL=out.js.map
1377
+ //# sourceMappingURL=index.cjs.map