diff --git a/CHANGELOG.md b/CHANGELOG.md index c3aeac4..4513f05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable GA release changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v2.2.11 (released@ 07-06-2024) + +**Bug Fixes** +* Enhanced call handling functionality to support multiple executions of the call() method. + + ## v2.2.10 (released@ 22-05-2024) **Bug Fixes** diff --git a/lib/managers/incomingCall.ts b/lib/managers/incomingCall.ts index 7831472..8a49b31 100644 --- a/lib/managers/incomingCall.ts +++ b/lib/managers/incomingCall.ts @@ -124,15 +124,21 @@ const onProgress = (incomingCall: CallSession) => (): void => { 4, callerUri.indexOf('@'), )}@${DOMAIN}`; - Plivo.log.debug(`${LOGCAT.CALL} | Emitting onIncomingCall`); - cs.emit( - 'onIncomingCall', - callerId, - incomingCall.extraHeaders, - incomingCall.getCallInfo("local"), - callerName, - ); - cs.noiseSuppresion.setLocalMediaStream(); + const emitIncomingCall = () => { + Plivo.log.debug(`${LOGCAT.CALL} | Emitting onIncomingCall`); + cs.emit( + 'onIncomingCall', + callerId, + incomingCall.extraHeaders, + incomingCall.getCallInfo("local"), + callerName, + ); + }; + cs.noiseSuppresion.setLocalMediaStream().then(() => { + emitIncomingCall(); + }).catch(() => { + emitIncomingCall(); + }); addCloseProtectionListeners.call(cs); Plivo.log.debug(`${LOGCAT.CALL} | Incoming Call Extra Headers : ${JSON.stringify(incomingCall.extraHeaders)}`); }; @@ -542,21 +548,21 @@ export const answerIncomingCall = function ( handleOtherInvites(curIncomingCall, actionOnOtherIncomingCalls); cs.owaLastDetect.isOneWay = false; try { + cs._currentSession = curIncomingCall; + cs.incomingInvites.delete(curIncomingCall.callUUID as string); + if (curIncomingCall === cs.lastIncomingCall) { + cs.lastIncomingCall = null; + if (cs.incomingInvites.size) { + cs.lastIncomingCall = cs.incomingInvites.values().next().value; + } + } + cs.loggerUtil.setSipCallID(cs._currentSession.sipCallID ?? ""); + cs.callSession = cs._currentSession.session; + cs.callUUID = cs._currentSession.callUUID; + Plivo.log.debug(`${LOGCAT.CALL} | CallUUID is ${cs.callUUID}`); + cs.callDirection = cs._currentSession.direction; getAnswerOptions().then((options) => { curIncomingCall.session.answer(options); - cs._currentSession = curIncomingCall; - cs.incomingInvites.delete(curIncomingCall.callUUID as string); - if (curIncomingCall === cs.lastIncomingCall) { - cs.lastIncomingCall = null; - if (cs.incomingInvites.size) { - cs.lastIncomingCall = cs.incomingInvites.values().next().value; - } - } - cs.loggerUtil.setSipCallID(cs._currentSession.sipCallID ?? ""); - cs.callSession = cs._currentSession.session; - cs.callUUID = cs._currentSession.callUUID; - Plivo.log.debug(`${LOGCAT.CALL} | CallUUID is ${cs.callUUID}`); - cs.callDirection = cs._currentSession.direction; }); } catch (err) { Plivo.log.error(`${LOGCAT.CALL} | error in answering incoming call : `, err.message); diff --git a/lib/managers/outgoingCall.ts b/lib/managers/outgoingCall.ts index a3df323..0950315 100644 --- a/lib/managers/outgoingCall.ts +++ b/lib/managers/outgoingCall.ts @@ -463,71 +463,67 @@ const getCleanedHeaders = (extraHeaders: ExtraHeaders = {}): string[] => { * They should start with 'X-PH' * @returns Outgoing call answer options */ -const getOptions = function (extraHeaders: ExtraHeaders): Promise { - return new Promise((resolve) => { - const opts: SessionAnswerOptions = {}; - opts.sessionTimersExpires = SESSION_TIMERS_EXPIRES; - opts.pcConfig = { - iceServers: [{ urls: STUN_SERVERS }], - }; - opts.mediaConstraints = { - audio: cs.options.audioConstraints || true, - video: false, - }; - // opts.rtcConstraints = null; - opts.extraHeaders = getCleanedHeaders(extraHeaders); - cs.noiseSuppresion.startNoiseSuppression().then((mediaStream) => { - opts.mediaStream = mediaStream != null ? mediaStream : (window as any).localStream; - // eslint-disable-next-line @typescript-eslint/dot-notation - opts['eventHandlers'] = { - sending: onSending, - sdp: onSDP, - progress: OnProgress, - accepted: onAccepted, - confirmed: onConfirmed, - noCall: onEnded, - newDTMF, - newInfo, - icecandidate: (event: SessionIceCandidateEvent) => cs._currentSession - && cs._currentSession.onIceCandidate(cs, event), - icetimeout: (sec: number) => cs._currentSession - && cs._currentSession.onIceTimeout(cs, sec), - failed: onFailed, - ended: onEnded, - getusermediafailed: (err) => cs._currentSession - && cs._currentSession.onGetUserMediaFailed(cs, err), - 'peerconnection:createofferfailed': (err) => cs._currentSession - && cs._currentSession.handlePeerConnectionFailures( - cs, - 'createofferfailed', - cs.callStats ? cs.callStats.webRTCFunctions.createOffer : null, - err, - ), - 'peerconnection:createanswerfailed': (err) => cs._currentSession - && cs._currentSession.handlePeerConnectionFailures( - cs, - 'createanswerfailed', - cs.callStats ? cs.callStats.webRTCFunctions.createAnswer : null, - err, - ), - 'peerconnection:setlocaldescriptionfailed': (err) => cs._currentSession - && cs._currentSession.handlePeerConnectionFailures( - cs, - 'setlocaldescriptionfailed', - cs.callStats ? cs.callStats.webRTCFunctions.setLocalDescription : null, - err, - ), - 'peerconnection:setremotedescriptionfailed': (err) => cs._currentSession - && cs._currentSession.handlePeerConnectionFailures( - cs, - 'setremotedescriptionfailed', - cs.callStats ? cs.callStats.webRTCFunctions.setRemoteDescription : null, - err, - ), - }; - resolve(opts); - }); - }); +const getOptions = function (extraHeaders: ExtraHeaders, mediaStream: MediaStream | null): any { + const opts: SessionAnswerOptions = {}; + opts.sessionTimersExpires = SESSION_TIMERS_EXPIRES; + opts.pcConfig = { + iceServers: [{ urls: STUN_SERVERS }], + }; + opts.mediaConstraints = { + audio: cs.options.audioConstraints || true, + video: false, + }; + // opts.rtcConstraints = null; + opts.extraHeaders = getCleanedHeaders(extraHeaders); + opts.mediaStream = mediaStream != null ? mediaStream : (window as any).localStream; + // eslint-disable-next-line @typescript-eslint/dot-notation + opts['eventHandlers'] = { + sending: onSending, + sdp: onSDP, + progress: OnProgress, + accepted: onAccepted, + confirmed: onConfirmed, + noCall: onEnded, + newDTMF, + newInfo, + icecandidate: (event: SessionIceCandidateEvent) => cs._currentSession + && cs._currentSession.onIceCandidate(cs, event), + icetimeout: (sec: number) => cs._currentSession + && cs._currentSession.onIceTimeout(cs, sec), + failed: onFailed, + ended: onEnded, + getusermediafailed: (err) => cs._currentSession + && cs._currentSession.onGetUserMediaFailed(cs, err), + 'peerconnection:createofferfailed': (err) => cs._currentSession + && cs._currentSession.handlePeerConnectionFailures( + cs, + 'createofferfailed', + cs.callStats ? cs.callStats.webRTCFunctions.createOffer : null, + err, + ), + 'peerconnection:createanswerfailed': (err) => cs._currentSession + && cs._currentSession.handlePeerConnectionFailures( + cs, + 'createanswerfailed', + cs.callStats ? cs.callStats.webRTCFunctions.createAnswer : null, + err, + ), + 'peerconnection:setlocaldescriptionfailed': (err) => cs._currentSession + && cs._currentSession.handlePeerConnectionFailures( + cs, + 'setlocaldescriptionfailed', + cs.callStats ? cs.callStats.webRTCFunctions.setLocalDescription : null, + err, + ), + 'peerconnection:setremotedescriptionfailed': (err) => cs._currentSession + && cs._currentSession.handlePeerConnectionFailures( + cs, + 'setremotedescriptionfailed', + cs.callStats ? cs.callStats.webRTCFunctions.setRemoteDescription : null, + err, + ), + }; + return opts; }; const makingCall = (opts: SessionAnswerOptions, destinationUri: string): void => { @@ -581,17 +577,15 @@ export const makeCall = ( if (phoneNumber) { phoneNumberStr = removeSpaces(String(phoneNumber)); } - if (!validateSession(phoneNumberStr)) return false; - const destinationUri = getValidPhoneNumber(phoneNumberStr); - (() => { - getOptions(extraHeaders) - .then((data) => { - makingCall(data, destinationUri); - }) - .catch((error) => { - Plivo.log.error("Error:", error); - }); - })(); + // eslint-disable-next-line consistent-return + cs.noiseSuppresion.startNoiseSuppression().then((mediaStream) => { + const options = getOptions(extraHeaders, mediaStream); + if (!validateSession(phoneNumberStr)) { + return false; + } + const destinationUri = getValidPhoneNumber(phoneNumberStr); + makingCall(options, destinationUri); + }); return true; }; diff --git a/package.json b/package.json index 01f3d4f..6c3e86b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "plivo-browser-sdk", "title": "plivo-browser-sdk", - "version": "2.2.10", + "version": "2.2.11", "description": "This library allows you to connect with plivo's voice enviroment from browser", "main": "./dist/plivobrowsersdk.js", "types": "index.d.ts", diff --git a/test/integration/incomingcall.js b/test/integration/incomingcall.js index ed906c9..4689b34 100644 --- a/test/integration/incomingcall.js +++ b/test/integration/incomingcall.js @@ -164,6 +164,33 @@ describe('plivoWebSdk', function () { // #8 // eslint-disable-next-line no-undef + it('incoming call answered multiple times should be answered', (done) => { + if (bail) { + done(new Error('bailing')); + } + Client2.call(primary_user, { + 'X-Ph-Random': true, + }); + const answerCall = () => { + const response1 = Client1.answer(); + const response2 = Client1.answer(); + const response3 = Client1.answer(); + const response4 = Client1.answer(); + waitUntilIncoming(events.onCallAnswered, () => { + if (response1 && !response2 && !response3 && !response4) { + done(); + } + }, 1000); + }; + waitUntilIncoming(events.onIncomingCall, answerCall, 500); + bailTimer = setTimeout(() => { + bail = true; + done(new Error('incoming call hangup failed')); + }, TIMEOUT); + }); + + // #9 + // eslint-disable-next-line no-undef it('inbound call should be ended without answer', (done) => { // terminate any ongoing calls Client2.hangup(); diff --git a/test/integration/outgoingcall.js b/test/integration/outgoingcall.js index 34864c3..eec760c 100644 --- a/test/integration/outgoingcall.js +++ b/test/integration/outgoingcall.js @@ -137,7 +137,6 @@ describe("plivoWebSdk", function () { }, TIMEOUT); }); - // // #16 // // eslint-disable-next-line no-undef // it("outbound call should ring", (done) => { @@ -282,16 +281,18 @@ describe("plivoWebSdk", function () { } Client1.hangup(); - let s = secondary_user.replace(/\s/g, ""); - s = s.split('').join(' '); - if (Client1.isLoggedIn) { - Client1.call(s, {}); - } else { - Client1.on("onLogin", () => { + waitUntilOutgoingCall(events.onCallTerminated, () => { + let s = secondary_user.replace(/\s/g, ""); + s = s.split('').join(' '); + if (Client1.isLoggedIn) { Client1.call(s, {}); - }); - } - waitUntilOutgoingCall(events.onCalling, done, 500); + } else { + Client1.on("onLogin", () => { + Client1.call(s, {}); + }); + } + waitUntilOutgoingCall(events.onCalling, done, 1000); + }, 1000); bailTimer = setTimeout(() => { bail = true; done(new Error("Outgoing call failed")); @@ -305,14 +306,88 @@ describe("plivoWebSdk", function () { } Client1.hangup(); - if (Client1.isLoggedIn) { - Client1.call(919728082876, {}); - } else { - Client1.on("onLogin", () => { + waitUntilOutgoingCall(events.onCallTerminated, () => { + if (Client1.isLoggedIn) { Client1.call(919728082876, {}); - }); + } else { + Client1.on("onLogin", () => { + Client1.call(919728082876, {}); + }); + } + waitUntilOutgoingCall(events.onCalling, done, 1000); + }, 1000); + bailTimer = setTimeout(() => { + bail = true; + done(new Error("Outgoing call failed")); + }, TIMEOUT); + }); + + // // eslint-disable-next-line no-undef + it("multiple outbound calls to the same user should ring", (done) => { + console.log('calling call method multiple times should only allow first call to go through'); + if (bail) { + done(new Error("Bailing")); + return; } - waitUntilOutgoingCall(events.onCalling, done, 500); + + Client1.hangup(); + Client1.call(secondary_user, { + 'X-PH-plivoHeaders': '1', + }); + Client1.call(secondary_user, { + 'X-PH-plivoHeaders': '2', + }); + Client1.call(secondary_user, { + 'X-PH-plivoHeaders': '3', + }); + Client1.call(secondary_user, { + 'X-PH-plivoHeaders': '4', + }); + Client1.call(secondary_user, { + 'X-PH-plivoHeaders': '5', + }); + waitUntilOutgoingCall(events.onCalling, () => { + if (Client1._currentSession.extraHeaders && Client1._currentSession.extraHeaders['X-PH-plivoHeaders'] === '1' && Client1._currentSession.dest === secondary_user) { + done(); + } + }, 1000); + bailTimer = setTimeout(() => { + bail = true; + done(new Error("Outgoing call failed")); + }, TIMEOUT); + }); + + it("multiple outbound calls to the different user should ring", (done) => { + console.log('calling call method multiple times should only allow first call'); + if (bail) { + done(new Error("Bailing")); + return; + } + + Client1.hangup(); + waitUntilOutgoingCall(events.onCallTerminated, () => { + Client1.call('user1', { + 'X-PH-plivoHeaders': '1', + }); + Client1.call('user2', { + 'X-PH-plivoHeaders': '2', + }); + Client1.call('user3', { + 'X-PH-plivoHeaders': '3', + }); + Client1.call('user4', { + 'X-PH-plivoHeaders': '4', + }); + Client1.call('user5', { + 'X-PH-plivoHeaders': '5', + }); + waitUntilOutgoingCall(events.onCalling, () => { + console.log('extraheaders are ', JSON.stringify(Client1._currentSession.extraHeaders)); + if (Client1._currentSession.extraHeaders && Client1._currentSession.extraHeaders['X-PH-plivoHeaders'] === '1' && Client1._currentSession.dest === 'user1') { + done(); + } + }, 200); + }, 1000); bailTimer = setTimeout(() => { bail = true; done(new Error("Outgoing call failed"));