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