0

I'm working on a Twilio app where a user will have a list of numbers in front of them at a public terminal. Some are SIP some are Traditional. A end user might push a button to call one individual on the list, have a conversation, and walk away--then another user would walk up and call a different individual.
I'm encountering an issue where Twilio.Device.destroy is not eliminating all devices in play, and the second user in the example would end up calling the target of the first user.
First the code, then the logs:
button hook:

$('.twilio-voice').click(function() {
    var toNum = this.value;
    console.log('To num in jq');
    console.log(toNum);
    makeVoiceCall(toNum);
});


makeVoiceCall Function:

var makeVoiceCall = function(toNum) {
    console.log('To Num Is:');
    console.log(toNum);
    if(localStorage.token == undefined) {
        console.log("In If");
        getVoiceToken(toNum);
    } else {
        console.log("In Else");
        console.log(localStorage.token);
        Twilio.Device.setup(localStorage.token, {debug: true});
        Twilio.Device.ready(function(device){
            console.log('IN READY TO NUM IS');
            console.log(toNum);
            Twilio.Device.connect({"To": toNum});
        });
        Twilio.Device.error(function(error) {
            console.log(error.message);
            if(error.message == 'JWT Token Expired') {
                getVoiceToken(toNum);
            };
        });
    }
};


There's an event listener which launches a modal on Twilio.Device.connect which creates a modal-button that when pressed AND a disconnect event listener which both call a hang-up function :

function hangup() {
    Twilio.Device.disconnectAll();
    Twilio.Device.destroy();
};


I've tried adding Twilio.Device.destroy(); at the beginning of makeVoiceCall, inside Twilio.Device.ready(); and at the top of the else statement and they seem to have no effect. Here are what the logs look like if I click a traditional number, and then a sip number. The traditional number is called twice. If the SIP number is called first, then it will be called if the traditional number is attempted afterwards. In this example there's already a token in localStorage when the page is loaded.

To num in jq
ts:1214 +15555555555
ts:1178 To Num Is:
ts:1179 +15555555555
ts:1190 In Else
ts:1191 
[STACKOVERFLOW EDIT: console logged token removed]
twilio.min.js:71 Device.sounds is deprecated and will be removed in the next 
breaking release. Please use the new functionality available on 
Device.audio.
m.defaultWarnHandler @ twilio.min.js:71
m.deprecated @ twilio.min.js:71
(anonymous) @ twilio.min.js:72
l @ twilio.min.js:71
a @ twilio.min.js:39
setup @ twilio.min.js:59
makeVoiceCall @ ts:1192
(anonymous) @ ts:1215
dispatch @ jquery-2.1.3.min.js:3
r.handle @ jquery-2.1.3.min.js:3
twilio.min.js:70 [Device] Setting up PStream
twilio.min.js:70 [WSTransport] Opening socket
twilio.min.js:70 [WSTransport] attempting to connect
twilio.min.js:70 [WSTransport] Socket opened
twilio.min.js:70 [PStream] Setting token and publishing listen
twilio.min.js:70 [Device] Stream is ready
ts:1194 IN READY TO NUM IS
ts:1195 +15555555555
twilio.min.js:70 [Twilio.PeerConnection] signalingState is "have-local-offer"
twilio.min.js:70 [Twilio.PeerConnection] signalingState is "stable"
twilio.min.js:70 [Twilio.PeerConnection] iceConnectionState is "checking"
xb @ jquery-2.1.3.min.js:3
get @ jquery-2.1.3.min.js:3
css @ jquery-2.1.3.min.js:3
get @ jquery-2.1.3.min.js:3
cur @ jquery-2.1.3.min.js:3
init @ jquery-2.1.3.min.js:3
Kb @ jquery-2.1.3.min.js:3
createTween @ jquery-2.1.3.min.js:3
Rb.* @ jquery-2.1.3.min.js:3
Ub @ jquery-2.1.3.min.js:3
Vb @ jquery-2.1.3.min.js:3
Xb @ jquery-2.1.3.min.js:3
g @ jquery-2.1.3.min.js:3
dequeue @ jquery-2.1.3.min.js:3
(anonymous) @ jquery-2.1.3.min.js:3
each @ jquery-2.1.3.min.js:2
each @ jquery-2.1.3.min.js:2
queue @ jquery-2.1.3.min.js:3
animate @ jquery-2.1.3.min.js:3
n.fn.(anonymous function) @ jquery-2.1.3.min.js:3
addModal @ ts:974
(anonymous) @ ts:1024
a.emit @ twilio.min.js:162
(anonymous) @ twilio.min.js:48
c @ twilio.min.js:165
a.emit @ twilio.min.js:163
mediaStream.onopen @ twilio.min.js:21
a.version.pc.onsignalingstatechange @ twilio.min.js:119
twilio.min.js:70 [Twilio.PeerConnection] iceConnectionState is "completed"
twilio.min.js:70 [Connection] Disconnecting...
twilio.min.js:70 [PStream] Closing PStream
twilio.min.js:70 [WSTransport] Closing socket
twilio.min.js:70 [Twilio.PeerConnection] iceConnectionState is "closed"
twilio.min.js:70 [Twilio.PeerConnection] signalingState is "closed"
twilio.min.js:158 WebSocket connection to 'wss://chunderw-vpc-gll.twilio.com/signal' failed: Close received after close
l._connect @ twilio.min.js:158
l.open @ twilio.min.js:157
a @ twilio.min.js:78
a @ twilio.min.js:75
a._setupStream @ twilio.min.js:54
a.register @ twilio.min.js:50
a @ twilio.min.js:46
setup @ twilio.min.js:59
makeVoiceCall @ ts:1192
(anonymous) @ ts:1215
dispatch @ jquery-2.1.3.min.js:3
r.handle @ jquery-2.1.3.min.js:3
twilio.min.js:70 [WSTransport] Socket received error: undefined
twilio.min.js:70 [WSTransport] Socket closed
twilio.min.js:70 [Device] Stream is offline
ts:1213 To num in jq
ts:1214 sip:test@sipexample.info
ts:1178 To Num Is:
ts:1179 sip:test@sipexample.info
ts:1190 In Else
ts:1191 [STACKOVERFLOW EDIT: token console.logged]
twilio.min.js:70 [Device] Found existing Device; using new token but ignoring options
twilio.min.js:70 [Device] Setting up PStream
twilio.min.js:70 [WSTransport] Opening socket
twilio.min.js:70 [WSTransport] attempting to connect
twilio.min.js:70 [WSTransport] Socket opened
twilio.min.js:70 [PStream] Setting token and publishing listen
twilio.min.js:70 [Device] Stream is ready
[STACKOVERFLOW EDIT: HERE IS WHERE TWO DEVICES ARE CALLED BY ONE Twilio.Device.setup()]
ts:1194 IN READY TO NUM IS
ts:1195 +15555555555
ts:1194 IN READY TO NUM IS
ts:1195 sip:test@sipexample.info
twilio.min.js:51 Uncaught Error: A Connection is already active
    at a.connect (twilio.min.js:51)
    at Function.connect (twilio.min.js:60)
    at a.<anonymous> (ts:1196)
    at a.emit (twilio.min.js:163)
    at a.<anonymous> (twilio.min.js:55)
    at a.emit (twilio.min.js:163)
    at l.transport.onmessage (twilio.min.js:78)
    at WebSocket.c.onmessage (twilio.min.js:160)
a.connect @ twilio.min.js:51
connect @ twilio.min.js:60
(anonymous) @ ts:1196
a.emit @ twilio.min.js:163
(anonymous) @ twilio.min.js:55
a.emit @ twilio.min.js:163
transport.onmessage @ twilio.min.js:78
c.onmessage @ twilio.min.js:160
twilio.min.js:70 [Twilio.PeerConnection] signalingState is "have-local-offer"
twilio.min.js:70 [Twilio.PeerConnection] signalingState is "stable"
twilio.min.js:70 [Twilio.PeerConnection] iceConnectionState is "checking"
twilio.min.js:70 [Twilio.PeerConnection] iceConnectionState is "completed"
twilio.min.js:70 [Connection] Disconnecting...
twilio.min.js:70 [PStream] Closing PStream
twilio.min.js:70 [WSTransport] Closing socket
twilio.min.js:70 [Twilio.PeerConnection] iceConnectionState is "closed"
twilio.min.js:70 [Twilio.PeerConnection] signalingState is "closed"
twilio.min.js:158 WebSocket connection to 'wss://chunderw-vpc-gll.twilio.com/signal' failed: Close received after close
l._connect @ twilio.min.js:158
l.open @ twilio.min.js:157
a @ twilio.min.js:78
a @ twilio.min.js:75
a._setupStream @ twilio.min.js:54
a.register @ twilio.min.js:50
setup @ twilio.min.js:59
makeVoiceCall @ ts:1192
(anonymous) @ ts:1215
dispatch @ jquery-2.1.3.min.js:3
r.handle @ jquery-2.1.3.min.js:3
twilio.min.js:70 [WSTransport] Socket received error: undefined
twilio.min.js:70 [WSTransport] Socket closed
twilio.min.js:70 [Device] Stream is offline

As you can see in the bottom quarter of the logs--marked by my addition: [STACKOVERFLOW EDIT: HERE IS WHERE TWO DEVICES ARE CALLED BY ONE Twilio.Device.setup()], both the SIP and the traditional number are called during singular Twilio.Device.setup call, even though the Device was destroyed through the hangup function (and I've tried other points to place Twilio.Device.destroy). Because of this, only the original number called is actually connected to on the second attempt.
At the moment, the only work-arounds I can think of are to create individual functions for every person on the list (which is the antithesis of DRY and adds a ton of work to maintenance or extension of this code) or to reload the page after a completed call (which my employer is not fond of).
If I could simply release / destroy the Twilio devices once the calls are complete, that would help me enormously. I have tried placing a Twilio.Device.disconnect(function() { Twilio.Device.disconnectAll(); Twilio.Device.destroy();}); underneath my Twilio.Device.connect call inside ready, with the same results. Any help on destroying these multiple devices (or reusing the first) would be invaluable.

K. Rhoda
  • 181
  • 1
  • 15

1 Answers1

2

Twilio developer evangelist here.

You do not have to destroy the Twilio.Device after each call. What is actually happening here is that you are registering multiple handlers that get called when the Device becomes ready.

I'd suggest re-architecting this somewhat. When the page loads, setup the Twilio Device. Then listen for offline events so that you know when to regenerate the token and re-connect to the service. Something like:

function makeVoiceCall(toNum) {
  Twilio.Device.connect({"To": toNum});
}

function setupDevice(token) {
  Twilio.Device.setup(token, { debug: true });
}

function getVoiceToken(callback) {
  $.post('/token', function(data) {
    localStorage.token = data.token;
    callback(token);
  })
}

function init() {
  if(localStorage.token == undefined) {
    getVoiceToken(function(token){ 
      setupDevice(token)
    });
  } else {
    setupDevice(localstorage.token)
  }

  // Ready will setup click handlers.
  Twilio.Device.ready(function() {
    $('.twilio-voice').click(function() {
      var toNum = this.value;
      makeVoiceCall(toNum);
    });
  });

  // offline will remove click handlers, get a new token and setup the device again
  Twilio.Device.offline(function() {
    // unregister click handlers for now
    $('.twilio-voice').off('click');
    getVoiceToken(function(token) {
      setupDevice(token);
    })
  })
}

window.addEventListener('load', init);

I made up a new version of getVoiceToken here so that it returns the token in a callback, which I think makes things more easy to read.

Let me know if this helps at all.

philnash
  • 70,667
  • 10
  • 60
  • 88
  • This helped a lot. I made some changes to the offline handler and how the init was called, but this got me there. Using setupDevice inside of the offline handler was a very good way to structure this. Thanks a ton! – K. Rhoda Aug 25 '17 at 17:17
  • Can i have multiple instances of call, twilio device connection at the same time? – N Nem Apr 17 '22 at 05:36
  • I believe that you can instantiate multiple [Twilio Device objects](https://www.twilio.com/docs/voice/sdks/javascript/twiliodevice) so you could do this. I recommend you try it yourself though. – philnash Apr 18 '22 at 01:25