Online shopping is great but can be quite complex; you never know if you are about to make a horrible decision. What if we could have little helpers all along the shopping process that could answer our questions in real time before we click on that “purchase” button?

The beauty of WebRTC is that we can integrate its real-time communication functionalities everywhere on the web and extremely easily.

Today, we’ll look at how we can build a viable “personal shopper” experience compatible with any shopping website by linking the powers of WebRTC and Chrome Extensions, along with Voxbone’s WebRTC-to-SIP SDK.

Over the past few years, Chrome extensions have become extremely easy to build as they work with Javascript, CSS, and HTML pages – very interoperable with WebRTC itself!

Too long to read? Download the source code.

What you will need beforehand:

  • A Voxbone account enabled for WebRTC use (with your Voxbone WebRTC service credentials)
  • A Voxbone test number, also enabled for WebRTC use and linked to a working SIP URI.
  • A Chrome browser (with latest OS version)
  • 15 minutes of your time!

What will we build?

The goal of this tutorial is to build a simple chrome extension/widget that launches a call to the “Personal Shopper Central” (your SIP URI) and sends information such as the IP address of the shopper and the URL of the page he or she is looking at.

soso-shopping-1

So let’s get right to it!

Chrome extensions are very easy to build, all we need really is a manifesto.json file and either a JS or HTML file that will hold the app logic/design.

1. The Manifesto

Create a manifesto.json file and copy the following code in it.

{
  "manifest_version": 2,

  "name": "SOS Shopping",
  "description": "Get in touch with an agent that can help you make proper shopping decisions!",
  "version": "1.0",
  "icons": {
      "128": "icon.png"
    },
  "browser_action": {
   "default_icon": "icon.png",
   "default_title": "SOS Shopping"
  },
  "background": {
  "scripts": ["./js/popup.js"],
  "persistent": false
  },
  "permissions": [
   "activeTab"
   ]
}

You can choose whichever name and description you prefer and import your own icon to the mix. The “browser_action” is the actual button of the chrome extension that sits in the bookmark bar that will “do something” when clicked – in this case, run the popup.js script.

The “permissions” allow you to gain more control over the tabs (such as reordering, deleting, or flashing them). In this case, we just need to get some control over the active tab our shopping customers are looking at.

2.The Script Launcher

The popup.js file (that you have to create) will be run whenever the “SOS Shopping” button is clicked. We will use this file to host the different scripts and css styles we want to inject to the active tab.

Here’s what it looks like:

document.addEventListener('DOMContentLoaded', function() {
  // Called when the user clicks on the browser action.
  chrome.browserAction.onClicked.addListener(function(tab) {
  chrome.tabs.executeScript({
    // Loads dependency scripts
    file: './js/scriptloader.js'
  });
  chrome.tabs.executeScript({
    // Launches authentication and WebRTC call
    file: './js/authentication.js'
  });
  //Modal
   chrome.tabs.executeScript({
    file: './js/modal.js'
  });
  // Modal Style
  chrome.tabs.insertCSS({
    file: './css/style.css'
  });
});
}, false);

The scriptloader.js, authentication.js, modal.js, and style.css (all of which we will create later on) are injected to the active tab as soon as the browserAction (the extension button) is clicked.

3.Injecting the dependencies

The scriptloader.js is where we will inject the necessary dependencies in the active tab. The dependencies include: the VoxboneJS and JSSIP dependencies, the IP detection script, and a few other scripts necessary for the WebRTC authentication mechanism to run (CryptoJS). Below is the content of the scriptloader.js file:

//Inject JQuery Dependency
var jquery = document.createElement('script');
jquery.src = 'https://code.jquery.com/jquery-1.11.1.min.js';
jquery.type = 'text/javascript';
jquery.async = true;
document.getElementsByTagName('head')[0].appendChild(jquery);

//Inject CryptoJS Dependency
var crypto1 = document.createElement('script');
crypto1.src = 'https://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/hmac-sha1.js';
crypto1.type = 'text/javascript';
crypto1.async = true;
document.getElementsByTagName('head')[0].appendChild(crypto1);

//Inject CryptoJS Dependency
setTimeout(function() {
var crypto2 = document.createElement('script');
crypto2.src = 'https://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/enc-base64-min.js';
crypto2.type = 'text/javascript';
crypto2.async = true;
document.getElementsByTagName('head')[0].appendChild(crypto2);

}, 100);

//Inject JSSIP Dependency
var jssip = document.createElement('script');
jssip.src = 'https://webrtc.voxbone.com/js/jssip-0.3.0.js';
jssip.type = 'text/javascript';
jssip.async = true;
document.getElementsByTagName('head')[0].appendChild(jssip);

//Inject VoxboneJS dependency
var voxbone = document.createElement('script');
voxbone.src = 'https://webrtc.voxbone.com/js/voxbone-0.0.2.js';
voxbone.type = 'text/javascript';
voxbone.async = true;
document.getElementsByTagName('head')[0].appendChild(voxbone);

//Get user IP address to be used in voxbone.WebRTC.configuration.uri
var ip = document.createElement('script');
ip.src = 'https://l2.io/ip.js?var=userip';
ip.type = 'text/javascript';
ip.async = true;
document.getElementsByTagName('head')[0].appendChild(ip);

You can already start testing the application to see that those scripts are injected to the webpage you are looking at (in the console).

The appendChild line appends the script the the end of the head tag of the webpage.

document.getElementsByTagName('head')[0].appendChild(jssip);

To test the extension, open chrome://extensions/, clicked on “Load unpacked extension” and select your application. Then go to any webpage and open the console. You can now click on the chrome extension button that was added to the browser bar and see those scripts add themselves to the rest of the webpage’s code. Slick, isn’t it?

4.Creating the modal

The modal will be the little window that will appear under the button when we click it, it’s the UI of our application that will provide feedback on the state of the call for the user.

Screen Shot 2015-10-29 at 7.21.56 PM

To do this, we will now create the modal.js file which will contain the following code.

//Create dialog box
var modal = document.createElement('div');
modal.id = "modal-one";
modal.setAttribute("class", "modal");

var modal_dialog = document.createElement('div');
modal_dialog.setAttribute("class", "modal-dialog");

var modal_header = document.createElement('div');
modal_header.setAttribute("class", "modal-header");

//Create title and append to header
var modal_header_title = document.createElement('h2');
var modal_header_title_node = document.createTextNode("SOS Shopping");
modal_header_title.appendChild(modal_header_title_node);
modal_header.appendChild(modal_header_title);

//Create close button and append to header
var modal_close = document.createElement('button');
modal_close.setAttribute('class', 'btn-close');
modal_close.setAttribute("onclick", "voxbone.WebRTC.hangup()");
var modal_close_node = document.createTextNode('x');
modal_close.appendChild(modal_close_node);
modal_header.appendChild(modal_close);

//Create body and call status message
var modal_body = document.createElement('div');
modal_body.setAttribute("class", "modal-body");
var modal_body_status = document.createElement('p');
modal_body_status.id = "status_message";
var modal_body_status_node = document.createTextNode("connecting...");
modal_body_status.appendChild(modal_body_status_node);
modal_body.appendChild(modal_body_status);

//Create hangup button and append to footer
var modal_footer = document.createElement('div');
modal_footer.setAttribute("class", "modal-footer");
var hangup = document.createElement('button');
hangup.setAttribute("class", "btn");
hangup.setAttribute("onclick", "voxbone.WebRTC.hangup()");
var hangup_node = document.createTextNode("Hang up");
hangup.appendChild(hangup_node);
modal_footer.appendChild(hangup);

//Append elements to modal window
modal_dialog.appendChild(modal_header);
modal_dialog.appendChild(modal_body);
modal_dialog.appendChild(modal_footer);
modal.appendChild(modal_dialog);

//Append modal to body
document.getElementsByTagName('body')[0].appendChild(modal);

The HTML we want to create will look like this:

<div id="modal-one" class="modal">
<div class="modal-dialog">
<div class="modal-header">
<h2>SOS Shopping</h2>
<button class="btn-close" onclick="voxbone.WebRTC.hangup()">x</button>
</div>
<div class="modal-body">
<p id="status_message">connecting...</p>
</div>
<div class="modal-footer">
<button class="btn" onclick="voxbone.WebRTC.hangup()">Hang up</button>
</div>
</div>
</div>

but we want to inject it using JavaScript so we use the upper versions which essentially creates a few elements and we append them to one another to construct the bigger element.

Take a close look at the lines that include ‘voxbone.WebRTC.hangup(), like:

modal_close.setAttribute("onclick", "voxbone.WebRTC.hangup()");

This will be important when we launch the calls and we will want to hang up!

5.WebRTC Calling

This were the magic happens! We will now implement the programming logic that will enable the calls to be placed to the agent and pass some relevant information. This is where your Voxbone phone number (linked to a SIP URI), and your WebRTC credentials become important.

Create the authentication.js file and add your credentials like so:

//Credentials -- TODO: add your own
var username = "your_username";
var secret = "your_secret";

//Phone number/SIP Endpoint to call -- TODO: add your own
var number = "32000000";

No we will inject the script necessary for the Voxbone WebRTC authentication to take place and minify it!

Below is what the original code looks like:

var userip; //takes advantage of the IP detector script

/**
 ** Used for hashing the secret key and username
 **/
var cleanHmacDigest = function (hmac) {
    while ((hmac.length % 4 != 0)) {
        hmac += '=';
    }
    hmac = hmac.replace('/ /g', '+');
    return hmac;
};

/**
 ** Used for hashing the secret key and username into voxrtc_config
 ** then used in init()
 **/
var createKey = function (user) {
  self.username = username;
  self.secret = secret;
  var sha1 = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA1, self.secret);
  self.expires = Math.round(Date.now()/1000) + 100;
  var text = self.expires + ':' + self.username;
  sha1.update(text);
  hmac = sha1.finalize();
  self.key = cleanHmacDigest( hmac.toString(CryptoJS.enc.Base64) );
  var data = {};
  data.key = self.key;
  data.expires = self.expires;
  data.username = self.username;
  return data;
};
var voxrtc_config = createKey();
var eventHandlers = {
        'progress':   function(e){ document.getElementById("status_message").innerHTML="Calling.."},
        'failed':     function(e){ document.getElementById("status_message").innerHTML="Failed to Connect: " + e.data.cause;},
        'started':    function(e){ document.getElementById("status_message").innerHTML="In call"; },
        'ended':      function(e){ document.getElementById("status_message").innerHTML="Call ended"; }
    };
function init(){
        voxbone.WebRTC.authServerURL = "http://webrtc.voxbone.com/rest/authentication/createToken";
        voxbone.WebRTC.useSecureSocket = false;
        voxbone.WebRTC.customEventHandler = eventHandlers;
        voxbone.WebRTC.configuration.uri = userip + "@voxbone.com"; //using the IP address generated and used as caller ID
        voxbone.WebRTC.context= document.URL; //URL sent over the call in context header.
        voxbone.WebRTC.configuration.display_name = "user"; //can also be dynamically generated
        voxbone.WebRTC.init(voxrtc_config);
    };

Now that we have the WebRTC code, we can minify it to inject it in the script when the browserAction is clicked. Below is the actual authentication.js file that also includes script to launch the code

//Credentials -- TODO: add your own
var username = "your_username";
var secret = "your_secret";

//Phone number/SIP Endpoint to call -- TODO: add your own
var number = "32000000";

//Authentication mechanism
setTimeout(function(){
var script = document.createElement("script");
script.type = "text/javascript";

/**
 ** The Voxbone WebRTC init() script is injected below.
 ** voxbone.WebRTC.configuration.uri is replaced with IP address of calling browser generated in script loader
 ** voxbone.WebRTC.configuration.display_name is replaced with "user" but can be replaced dynamically
 ** voxbone.WebRTC.context is dynamically replaced with the URL visited using document.URL
 **/
script.text = "var userip;function init(){voxbone.WebRTC.authServerURL='http://webrtc.voxbone.com/rest/authentication/createToken',voxbone.WebRTC.useSecureSocket=!1,voxbone.WebRTC.customEventHandler=eventHandlers,voxbone.WebRTC.configuration.uri=userip+'@voxbone.com',voxbone.WebRTC.configuration.display_name='user',voxbone.WebRTC.context= document.URL,voxbone.WebRTC.init(voxrtc_config)}var cleanHmacDigest=function(e){for(;e.length%4!=0;)e+='=';return e=e.replace('/ /g','+')},createKey=function(e){self.username='"+ username +"',self.secret='"+ secret +"';var n=CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA1,self.secret);self.expires=Math.round(Date.now()/1e3)+100;var t=self.expires+':'+self.username;n.update(t),hmac=n.finalize(),self.key=cleanHmacDigest(hmac.toString(CryptoJS.enc.Base64));var a={};return a.key=self.key,a.expires=self.expires,a.username=self.username,a},voxrtc_config=createKey(),eventHandlers={progress:function(e){document.getElementById('status_message').innerHTML='Calling..'},failed:function(e){document.getElementById('status_message').innerHTML='Failed to Connect: '+e.data.cause},started:function(e){document.getElementById('status_message').innerHTML='In call'},ended:function(e){document.getElementById('status_message').innerHTML='Call ended'}};init();";
document.getElementsByTagName('head')[0].appendChild(script);
}, 1000);

/**
 ** Launch call using the "number" input by you!
 ** setTimeout is used to incur delay so authentication happens before calling
 **/
setTimeout(function(){
var caller = document.createElement('script');
caller.type = 'text/javascript';
caller.text = "voxbone.WebRTC.call('"+ number +"')";
caller.async = true;
document.getElementsByTagName('head')[0].appendChild(caller);
}, 2000);

soso-shopping-1

Has it been 15 minutes already?! I’m glad I was able to call my personal shopper that told me this purchase would have been a horrible mistake! Super skinny ripped jeans don’t suit me AT ALL. Full source code available on Github.