Example SIP Phone

 

To send and receive call audio for agents, we have used SIP.js to create a minimal softphone.
This softphone connects to a WebRTC server and automatically accepts any call that comes to it (after the user has allowed microphone access in their browser!)

The softphone has been embedded into its own tab, which has a lot of custom functionality for communicating with the web agent page – this functionality is not necessary for people who want to use the softphone without needing the WebAgent page open.

 

The provided documentation is split across three files. These are:

  • The SIP.js library. We use version 0.15.11. Newer versions are available but they require code changes to get them working.

  • The Softphone functionality. This file implements SIP.js to create a softphone that will register to an RTC server and accept any SIP invite.

  • A Harness Webpage. This page serves as an example of how the softphone script can be implemented. It is a barebones UI that hosts the softphone and provides updates when certain events are triggered.

 

How does it Work?

The script on our webpage creates an instance of our SipPhone. To do this, it needs to pass in valid credentials for the WebRTC server.

In addition to the credentials, we can also pass in many functions, that will be called when certain events occur. These functions are:

onRegistered: Called when the softphone has successfully registered with the WebRTC server. At this point, the softphone is ready to be called.

onInviteReceived: Called when a call invite comes into the softphone via the SIP connection. This invite will be automatically accepted by the softphone.

onSessionBegin: Called when the invite has been accepted, and the call begins.

onSessionEnd: Called when a BYE message is received, and the call is terminated.

 

For the password, you can use a JS library called crypto.js to encrypt the username.

This would be added to the top of the page       

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>

 

Then in our script further down, we can use it like this:

function encryptStringWithHmacSHA1(message, key) {

    const hmac = CryptoJS.HmacSHA1(message, key);

    return hmac.toString(CryptoJS.enc.Base64);

}

password = encryptStringWithHmacSHA1(username, "AUTH_SECRET");

 

example-sip-phone.js

SipPhone = function(settings){ // Allow the softphone to trigger events for the calling application var events = { onRegistered: settings.onRegistered || function(){}, onInviteReceived: settings.onInviteReceived || function(){}, onSessionBegin: settings.onSessionBegin || function(){}, onSessionEnd: settings.onSessionEnd || function(){} }; // Tell the browser that we need Mic access, but not camera access var sessionOptions = { sessionDescriptionHandlerOptions: { constraints: { audio: true, video: false } } }; // Used to play the audio to the user var remoteAudio = document.createElement("audio"); remoteAudio.autoplay = true; document.body.append(remoteAudio); // Ensure we can play sound through the audio element var playAudioInterval = setInterval(function () { if (remoteAudio.isPaused) remoteAudio.play(); else{ console.log("Audio is playing. Clearing interval."); clearInterval(playAudioInterval); } }, 1000); // Used to initiate SIP behaviours var UA = new SIP.UA(settings.credentials); // Once connected, and available to be called UA.on("registered", events.onRegistered) // When the websocket connects, and we can start making SIP requests UA.transport.on("connected", function () { console.log("The Transport has connected."); UA.register({ expires: 86400 // 1 Day }); }); UA.transport.on("transportError", function () { if (UA.transport.status == 3) { UAPromise.reject("An error has occured whilst connecting to the websocket server"); } }); // A call is being received UA.on("invite", function(session){ events.onInviteReceived(); session.on("accepted", function(){ var remoteAudio = document.createElement("audio"); document.body.append(remoteAudio); var pc = session.sessionDescriptionHandler.peerConnection; var remoteStream = new MediaStream(); pc.getReceivers().forEach(function (receiver) { var track = receiver.track; if (track) { remoteStream.addTrack(track); } }); var usMs = new MediaStream(); usMs.addTrack(pc.getSenders()[0].track); remoteAudio.srcObject = remoteStream; remoteAudio.play(); events.onSessionBegin(); }); // A call has been hung up on session.on("bye", events.onSessionEnd); session.on("failed", function(){ console.log("Failed to accept a call. This is usually due to the user rejecting Mic access."); }); // Automatically accept the call for the user session.accept(sessionOptions); }); };

 

example.html

<html> <head> <title>Example SIP Phone</title> <script src="./sip-0.15.11.js"></script> </head> <body> <p> Softphone Registered: <span id="registeredDate"></span><br/> Invite Received: <span id="invitedDate"></span><br/> Session Begin: <span id="sessionBeginDate"></span><br/> Session End: <span id="sessionEndDate"></span><br/> </p> <script src="./example-sip-phone.js"></script> <script> getUserDetails = function(){ const username = "{unixTimeStamp}:{customerID}--{userID}--{username}"; const password = encryptStringWithHmacSHA1(username, {authSecret}) return { "authorizationUser":username, "transportOptions": { "traceSip":true, "wsServers":[ { "wsUri":"{wsURL}", "sipUri":"sipURL", "weight":0, "isError":false, "scheme":"WSS" }]}, "sessionDescriptionHandlerFactoryOptions": { "peerConnectionConfiguration": { "iceServers":[ { "urls":"stun:stun.l.google.com:19302", "username":null, "credential":null }, { "urls":"{turnURL}", "username":username, "credential":password }]}}, "userAgentString":"OmniSenseSIPPhone", "rtcpMuxPolicy":"negotiate", "uri":"[CUSTOMER_ID--USER_ID--AGENTLOGIN]@{webrtcURL}", "password":password } } // Create an instance of the SipPhone and wire up events ExampleSipPhone = new SipPhone({ credentials: getUserDetails(), onRegistered: function(){ console.log("Softphone registered."); registeredDate.textContent = new Date().toISOString(); }, onInviteReceived: function(){ console.log("A SIP invite has been received."); invitedDate.textContent = new Date().toISOString(); }, onSessionBegin: function(){ console.log("The SIP session has started."); sessionBeginDate.textContent = new Date().toISOString(); }, onSessionEnd:function(){ console.log("The SIP session has ended."); sessionEndDate.textContent = new Date().toISOString(); } }); </script> </body> </html>