Select to Call for FusionPBX makes calling so easy!

I have just released my second Chrome Extension called FusionPBX SelectToCall.  The extension allows a user to easily dial a phone number by simply selecting it in their browser.  Of course, the user needs to first highlight the number and then select “Dial <phone number>” in the Chrome context menu.  I should also mention that the extension only works with FusionPBX.

The full source of the extension is available on GitHub https://github.com/gizmobin/FusionPBX-SelectToCall

Extension Overview

There are only a handful of files that the extension uses and each is pretty straightforward.  The approach to creating this extension was to break down the component pieces.  For this project they were:  (1) ability to set options,  (2) ability to create/show a context menu, (3) ability to dial or pass a phone number to FusionPBX.

In Action

Setting Options

The options were easy to setup and though I was able to use an options HTML layout from another (abandoned) project I did have to integrate the Chrome system for storing user selections.  What I did learn was that I could pass defaults into the storage call such that if values were not defined they had default values on one call.  The data is stored or at least passed as a JSON packet.

Here is a excerpt from the settings packet.  Notice that the domain would have the default “http://pbx”

var settings = {
   'domain'   : 'http://pbx',
   'username' : ''
};

Settings are passed to chrome.storage.local which then issues a callback on the supplied function.  At that point, the items variable is populated with either the data in storage or the default value supplied.  So on first run, the options domain would be populated with some helper text as a default.

chrome.storage.local.get(settings, function(items) {
   // populate the HTML element with stored/default data
   document.getElementById('domain').value = items.domain;
});

Storing the data is just as straightforward.  Here we get the value from the HTML form and populate it into a JSON associative array.  That array is passed to the chrome.storage.local and the callback function is called.  In this example we just alert the user that the data was stored.

var data = {};
  
// populate the item with the HTML form element value
data['domain'] = document.getElementById('domain').value;

chrome.storage.local.set(data, function() {
   alert('Options Saved.'); 
});

Creating Chrome Context Menu

Creating a Chrome context menu is very straightforward, however in my case I wanted the context menu to validate and also show the phone number that was going to be called.  The original version simply added a “Dial” menu item to the context menu which was prone to errors if the user did not select a proper phone number.  The current version validates that there are at least 10 digits before adding the choice to the context menu.

So to make this work I had to listen for the mousedown event, and then action that to build the context menu.  The following is not the complete code but shows the important pieces of how this is done.

document.addEventListener("mousedown", function(event){
   var selection = window.getSelection().toString();
  
   // Test #1:  did they actually select anything?
   if( selection ) {      
      var digits = selection.match(/\d/g);   // get only the digits
  
      // Test #2: can it be a phone number?
      // 123.4567 or 123.456.7890 or 1.123.456.7890 or 01.123.456.7890
      if( digits && (digits.length == 7 || (digits.length >= 10 && digits.length <= 12))) { 
         console.log("create_menu for "+selection);
  
         // send a message to build the context menu
         chrome.runtime.sendMessage({'action':'create_menu','selection':selection}, function(response) {
            console.log(response);
         });

Please note, that this example leaves out the code which removes any previous Chrome context menu.  The system for adding and removing is the same, and involves sending a message into the background to a script that is listening for such events.  As I understand it, at this point the above code is running in the context of the HTML page and does not have the necessary access to the browser elements like the context menu.  To add a context menu we need to communicate that to another part of the system which has the appropriate access.

background.js the Brain Script

Within the background.js script there is some code listening for messages.  That code is in charge of creating and removing Chrome context menus based on the requests it receives.   I again only included the relevant code for this discussion point; all working code is available on the GitHub link.

chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
  
   // request is the JSON packet representing the passed data
   if(request.action == 'create_menu') {
  
      // this is short form for: remove any existing context menu
      // and build a new one when done
      chrome.contextMenus.removeAll( function() {
         chrome.contextMenus.create({
            "title" : "Dial " + request.selection,  // menu text 
            "contexts" : ["selection"],    // when to show this menu
            "id" : "contextSelection",     // identification       
            "onclick" : onClickHandler     // callback function
         });
      });

      // we can send a response back to the caller; though unnecessary 
      sendResponse({'result':'created', 'message':request.action, 'selection':request.selection});
   }

The comments above highlight the important pieces.  One thing to note however is the callback method which is connected to our context menu.  That callback is the code that does the real work.  First though notice that the context of this menu is based on [“selection”] which means that the menu will only be seen when something on the web page is actually selected.   This is great because it means that we don’t have to pass around the phone number, we can request the selected text when we need it.

So a quick summary of where things are at this point.

  1. User right clicks on a page to see the context menu
  2. Our code validates that there is a selection and that it looks like a phone number
  3. A message is sent and the context menu is created with the selection
  4. The user can select the context menu option “Dial <phone number>” which will call onClickHandler

Our onClickHandler now needs to pass appropriate options and the phone number to the PBX.  With FusionPBX there is a call script (/app/click_to_call/click_to_call.php) which can be used, so we are going to push data into that script to pass everything to that system.  The script is secured behind the standard FusionPBX login screen so we will also need to pass the user’s login details to access the script.

In the code that follows the settings are pulled from chrome.storage.local and the appropriate FusionPBX GET request (URL) is built.  Then in order to login the username and password need to be posted to that URL.  This is done through the user of an XMLHttpRequest object.  This could also likely be done with a jQuery ajax call but that would create unnecessary overhead since the XMLHttpRequest implementation is quite straightforward.

function onClickHandler(info, tab) {
   // strip everything except numbers
   var phoneNum = info.selectionText.replace(/[^+\d]+/g,'');
 
   // get settings
   chrome.storage.local.get(settings, function(items) {
 
      // check if we have valid settings
      if( !items.username || !items.password ) {
         alert('Options have not been defined.');
      }
      else {
         // create the FusionPBX URL
         var url = items.domain + "/app/click_to_call/click_to_call.php?src_cid_name=WebDialer&auto_answer=false" +
            '&src_cid_number=' + phoneNum +
            '&dest_cid_name=' + items.dest_cid_name +
            '&dest_cid_number=' + items.dest_cid_number +
            '&src=' + items.src +
            '&dest=' + phoneNum +
            '&rec=' + items.rec +
            '&ringback=' + items.ringback;
  
            // do a background post
            var http = new XMLHttpRequest();
            http.open("POST", url, true);
            http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
 
            // not really necessary but helpful for debugging
            http.onreadystatechange = function() {
            if(http.readyState == 4 && http.status == 200) {
               // alert(http.responseText);
            }
         }

         // send the POST data which will perform the login 
         var login = 'username=' + items.username + '&password=' + items.password;
         http.send(login);
      }
   });
};

Finally the Manifest

The manifest file contains all the settings that are needed to tell the Chrome browser how the extension will integrate.   For most extensions one of the key settings is the permissions.

"permissions": [
   "storage",
   "contextMenus"
],

Another key setting is the background definition as we need the background script to watch for the ‘create context menu’ message.

"background": {
   "scripts": [
      "background.js"
   ]
},

And of course we need to tell the Chrome framework when to execute or make our content script available.  In the case of this extension a phone number could be on any page, so we note that in the settings.

"content_scripts" : [{
   "matches" : [
      "http://*/*",
      "https://*/*"
   ],
   "js" : [
      "content_script.js"
   ],
   "run_at" : "document_idle"
}],

There are other settings, but they have to do with visual components and extension details like version and title.  Review the complete code on GitHub to see all settings.

FusionPBX-SelectToCall source code on GitHub

FusionPBX SelectToCall extension on Chrome Store