Subject: Built xcally-motion-dialpad from commit 141221b.|1.0.96
From: Andrea Bianco <[email-masked]>
Date: Sat, 28 Sep 2019 06:01:52 +0000 (+0200)

Built xcally-motion-dialpad from commit 141221b.|1.0.96
---

diff --git a/app.a6d55712.js b/app.a6d55712.js
new file mode 100644
index 0000000..0b239ff
--- /dev/null
+++ b/app.a6d55712.js
@@ -0,0 +1 @@
\ No newline at end of file
diff --git a/app.bb6e91b1.js b/app.bb6e91b1.js
deleted file mode 100644
index dc8d6c0..0000000
--- a/app.bb6e91b1.js
+++ /dev/null
@@ -1 +0,0 @@
\ No newline at end of file
diff --git a/index.html b/index.html
index a45c9c5..a4c0c87 100644
--- a/index.html
+++ b/index.html
@@ -1,21 +1,22 @@
 <!DOCTYPE html>
 <html ng-app="motion">
+
 <head>
   <meta charset="utf-8">
   <title>XCALLY Motion Dialpad</title>
 
-  <link rel="stylesheet" href="vendor.b5ccb327.css">
-
-  <link rel="stylesheet" href="vendor.b5ccb327.css">
+  <link rel="stylesheet" href="vendor.84f81d8f.css">
+  
 </head>
-<body ng-controller="IndexController as vm">
 
+<body>
   <md-content class="md-padding">
     <div layout="row">
       <div flex>
+        <br />
         <div>
           <span ng-if="!vm.id">Agent offline</span>
-          <span ng-if="vm.id">{{vm.fullname}}, {{vm.accountcode}}</span>
+          <span ng-if="vm.id">{{vm.fullname}} ({{vm.internal}})</span>
         </div>
       </div>
       <div flex="10" layout="row" layout-align="end center">
@@ -24,42 +25,96 @@
         </div>
       </div>
     </div>
+    <md-divider></md-divider>
+    <div layout="row" ng-if="vm.inCall">
+      <div flex>
+        <div>
+          <br />
+          <span>{{vm.timer}}</span>
+        </div>
+      </div>
+    </div>
+    <md-divider></md-divider>
     <md-input-container layout="row">
       <div flex>
-        <input ng-model="vm.phone" type="text" placeholder="Phone Number" ng-disabled="!vm.online">
+        <input ng-model="vm.phone" type="text" placeholder="Phone Number" ng-disabled="!vm.online || vm.inCall">
       </div>
-      <md-button class="md-icon-button" ng-click="vm.remove()" ng-disabled="!vm.online">
+      <md-button aria-label="canc" class="md-icon-button" ng-click="vm.remove()" ng-disabled="!vm.online">
         <i class="mdi mdi-backspace mdi-24px"></i>
       </md-button>
     </md-input-container>
     <div layout="row" layout-align="center center">
-      <md-button class="md-fab" aria-label="{{number}}" ng-repeat="number in [1,2,3]" ng-click="vm.compose(number)" md-colors="{background: 'grey'}" ng-disabled="!vm.online">{{number}}</md-button>
+      <md-button class="md-fab" aria-label="{{number}}" ng-repeat="number in [1,2,3]" ng-click="vm.compose(number)"
+        md-colors="{background: 'grey'}" ng-disabled="!vm.online">{{number}}</md-button>
     </div>
     <div layout="row" layout-align="center center">
-      <md-button class="md-fab" aria-label="{{number}}" ng-repeat="number in [4,5,6]" ng-click="vm.compose(number)" md-colors="{background: 'grey'}" ng-disabled="!vm.online">{{number}}</md-button>
+      <md-button class="md-fab" aria-label="{{number}}" ng-repeat="number in [4,5,6]" ng-click="vm.compose(number)"
+        md-colors="{background: 'grey'}" ng-disabled="!vm.online">{{number}}</md-button>
     </div>
     <div layout="row" layout-align="center center">
-      <md-button class="md-fab" aria-label="{{number}}" ng-repeat="number in [7,8,9]" ng-click="vm.compose(number)" md-colors="{background: 'grey'}" ng-disabled="!vm.online">{{number}}</md-button>
+      <md-button class="md-fab" aria-label="{{number}}" ng-repeat="number in [7,8,9]" ng-click="vm.compose(number)"
+        md-colors="{background: 'grey'}" ng-disabled="!vm.online">{{number}}</md-button>
     </div>
     <div layout="row" layout-align="center center">
-      <md-button class="md-fab md-primary" aria-label="*" ng-click="vm.compose('*')" md-colors="{background: 'grey'}" ng-disabled="!vm.online">*</md-button>
-      <md-button class="md-fab md-primary" aria-label="0" ng-click="vm.compose('0')" md-colors="{background: 'grey'}" ng-disabled="!vm.online">0</md-button>
-      <md-button class="md-fab md-primary" aria-label="#" ng-click="vm.compose('#')" md-colors="{background: 'grey'}" ng-disabled="!vm.online">#</md-button>
+      <md-button class="md-fab md-primary" aria-label="*" ng-click="vm.compose('*')" md-colors="{background: 'grey'}"
+        ng-disabled="!vm.online">*</md-button>
+      <md-button class="md-fab md-primary" aria-label="0" ng-click="vm.compose('0')" md-colors="{background: 'grey'}"
+        ng-disabled="!vm.online">0</md-button>
+      <md-button class="md-fab md-primary" aria-label="#" ng-click="vm.compose('#')" md-colors="{background: 'grey'}"
+        ng-disabled="!vm.online">#</md-button>
     </div>
     <div layout="row" layout-align="center center">
-      <md-button class="md-fab" md-colors="{background: vm.inCall ? 'red' : 'green'}" ng-click="vm.inCall ? vm.hangup() : vm.dial()" ng-disabled="!vm.online">
+      <md-button class="md-fab" aria-label="in call" md-colors="{background: vm.inCall ? 'red' : 'green'}"
+        ng-click="vm.inCall ? vm.hangup() : vm.dial()" ng-disabled="!vm.online">
         <i class="mdi mdi-phone mdi-24px"></i>
       </md-button>
     </div>
   </md-content>
+  <!-- <md-content class="md-padding">
+        <div layout="row" layout-align="center center">
+          <img src="./resources/default_loginlogo.png">
+    
+        </div>
+      </md-content> -->
 
-  <script src="vendor.c39ee70d.js"></script>
+  <script src="vendor.d76e66a8.js"></script>
 
   <script type="text/javascript">
     document.write("<base href='" + window.location.pathname + "' />");
+
+    function getQueryString() {
+                var queryStringKeyValue = window.parent.location.search.replace('?', '').split('&');
+                var qsJsonObject = {};
+                if (queryStringKeyValue != '') {
+                    for (i = 0; i < queryStringKeyValue.length; i++) {
+                        qsJsonObject[queryStringKeyValue[i].split('=')[0]] = queryStringKeyValue[i].split('=')[1];
+                    }
+                }
+                return qsJsonObject;
+            }
+    //
+    (function () {
+      //var dialpad = document.getElementById('dialpad');
+      var body = document.getElementsByTagName('body')[0];
+      var urlParams = new URLSearchParams(window.location.search);
+      console.log("TCL: window.location.search", window.location.search);
+      //console.log("TCL: urlParams", urlParams);
+      var mode = urlParams.get('mode');
+      var type = urlParams.get('type');
+      if (mode && mode.toLowerCase() === 'lightning') {
+        body.setAttribute('ng-controller', 'lightningController as vm');
+      } else if (type && type.toLowerCase() === 'desk') {
+        body.setAttribute('ng-controller', 'deskController as vm');
+      } else {
+        body.setAttribute('ng-controller', 'classicController as vm');
+      }
+    }
+
+    )();
   </script>
 
-  <script src="app.bb6e91b1.js"></script>
+  <script src="app.a6d55712.js"></script>
 
 </body>
-</html>
+
+</html>
\ No newline at end of file
diff --git a/js/controllers/classic.controller.js b/js/controllers/classic.controller.js
new file mode 100644
index 0000000..d9af647
--- /dev/null
+++ b/js/controllers/classic.controller.js
@@ -0,0 +1,575 @@
+'use strict';
+
+angular
+    .module('motion')
+    .controller('classicController', classicController);
+
+function classicController($scope, $window, $interval, $location, angularLoad, $http) {
+
+    var vm = this;
+
+    // Integration
+    var agentCallbacks = {};
+    var callsCallbacks = {};
+
+    // Socket object
+    var socket = null;
+
+    // Data
+    vm.timer = '';
+    vm.phone = '';
+    vm.inCall = false;
+
+    // task
+    var _task = {}; // global 
+    var _xml = {};
+
+    // Methods
+    vm.compose = compose;
+    vm.remove = remove;
+    vm.hangup = hangup;
+    vm.dial = dial;
+
+    var dialCount = 0;
+
+    //********************************************* */
+
+    var scriptTag = angular.element(document.createElement('base'));
+    scriptTag.href = window.location.pathname;
+
+    //*****************************************//
+    // LOADING
+    //********************************************* */
+
+    angularLoad
+        .loadScript(window.location.pathname.substring(0,window.location.pathname.lastIndexOf('/'))  + '/js/salesforce/interaction.js') //
+        //.loadScript(window.location.origin  + '/js/salesforce/interaction.js') //
+        .then(function () {
+
+            console.log('Salesforce Classic');
+            sforce.interaction.cti.getCallCenterSettings(getCallCenterSettingsCallback);
+            sforce.interaction.cti.enableClickToDial(enableClickToDialCallback);
+
+        }).catch(function (err) {
+            console.error(err);
+        });
+
+    function enableClickToDialCallback(response) {
+        if (response.result) { // boolean
+            sforce.interaction.cti.onClickToDial(onClickToDialListener);
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+    }
+
+    //*****************************************//
+    // getCallCenterSettingsCallback
+    //*****************************************//
+    function getCallCenterSettingsCallback(response) {
+
+        var _parsedXml = {};
+
+        if (response.result) {
+
+            _parsedXml = JSON.parse(response.result);
+            vm.host = _parsedXml['/Motion/Host'];
+            //_xml.host = _parsedXml['/Motion/Host'];
+
+            _xml.createTask = _parsedXml['/ManageTask/createTask'];
+            _xml.createTask = _xml.createTask ? _xml.createTask : 'false';
+
+            _xml.createOnAnswer = _parsedXml['/ManageTask/createOnAnswer'];
+            _xml.createOnAnswer = _xml.createOnAnswer ? _xml.createOnAnswer : 'false';
+
+            _xml.updateOnHangup = _parsedXml['/ManageTask/updateOnHangup'];
+            _xml.updateOnHangup = _xml.updateOnHangup ? _xml.updateOnHangup : 'false';
+
+            _xml.refreshOnHangup = _parsedXml['/ManageTask/refreshOnHangup'];
+            _xml.refreshOnHangup = _xml.refreshOnHangup ? _xml.refreshOnHangup : 'false';
+
+            _xml.defaultCreateStatus = _parsedXml['/ManageTask/defaultCreateStatus'];
+            _xml.defaultCreateStatus = _xml.defaultCreateStatus ? _xml.defaultCreateStatus : 'In Progress';
+
+            _xml.defaultUpdateStatus = _parsedXml['/ManageTask/defaultUpdateStatus'];
+            _xml.defaultUpdateStatus = _xml.defaultUpdateStatus ? _xml.defaultUpdateStatus : 'Completed';
+
+            _xml.uniqueid = _parsedXml['/FieldMapping/uniqueid'];
+            _xml.uniqueid = _xml.uniqueid ? _xml.uniqueid : null;
+            _xml.agent = _parsedXml['/FieldMapping/agent'];
+            _xml.agent = _xml.agent ? _xml.agent : null;
+            _xml.callerid = _parsedXml['/FieldMapping/callerid'];
+            _xml.callerid = _xml.callerid ? _xml.callerid : null;
+            _xml.date = _parsedXml['/FieldMapping/date'];
+            _xml.date = _xml.date ? _xml.date : null;
+            _xml.time = _parsedXml['/FieldMapping/time'];
+            _xml.time = _xml.time ? _xml.time : null;
+            _xml.starttime = _parsedXml['/FieldMapping/starttime'];
+            _xml.starttime = _xml.starttime ? _xml.starttime : null;
+            _xml.endtime = _parsedXml['/FieldMapping/endtime'];
+            _xml.endtime = _xml.endtime ? _xml.endtime : null;
+            _xml.answertime = _parsedXml['/FieldMapping/answertime'];
+            _xml.answertime = _xml.answertime ? _xml.answertime : null;
+            _xml.holdtime = _parsedXml['/FieldMapping/holdtime'];
+            _xml.holdtime = _xml.holdtime ? _xml.holdtime : null;
+            _xml.duration = _parsedXml['/FieldMapping/duration'];
+            _xml.duration = _xml.duration ? _xml.duration : null;
+            _xml.billsec = _parsedXml['/FieldMapping/billsec'];
+            _xml.billsec = _xml.billsec ? _xml.billsec : null;
+
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+    }
+
+    //*****************************************//
+    // onClickToDialListener
+    //*****************************************//
+    function onClickToDialListener(response) {
+
+        var result = JSON.parse(response.result);
+
+        if (dialCount < 1) {
+
+            if (vm.online && result.number) {
+
+                vm.phone = result.number;
+
+                _task.endUserPhone = angular.copy(result.number);
+                _task.endUserObjectType = result.object;
+                _task.endUserId = result.objectId;
+                _task.endUserName = result.objectName;
+
+                vm.dial();
+
+                dialCount++;
+            }
+        }
+    }
+
+    //*****************************************//
+    // display
+    //*****************************************//
+
+    function display(task) {
+        console.log('Display', task);
+        sforce.interaction.screenPop(task.recordId, true, screenPopCallback);
+    }
+
+    //****************************************************** */
+    // compose
+    //****************************************************** */
+    function compose(value) {
+        vm.phone = vm.phone.concat(value);
+    }
+
+    //****************************************************** */
+    // remove
+    //****************************************************** */
+    function remove() {
+        vm.phone = vm.phone.substring(0, vm.phone.length - 1);
+    }
+
+     //****************************************************** */
+    //  KEEPALIVE
+    //****************************************************** */
+    function keepalive() {
+
+        var uniqueid = Date.now();
+
+        $http.get('http://localhost:9888/api/agent')
+            .then(agentCallbacks[uniqueid] = function (result) {
+                if (result && result.status >= 200 && result.status < 300) {
+                    var message = result.data;
+                    vm.id = message.id;
+                    vm.name = message.name;
+                    vm.fullname = message.fullname;
+                    vm.internal = String(message.internal);
+                    vm.online = true;
+
+                    //****************************************************** */
+                    if (!socket) {
+                        if (vm.host && vm.id) {
+                            socket = io(vm.host, {
+                                query: {
+                                    id: vm.id
+                                },
+                                autoConnect: false,
+                                transports: ['websocket', 'polling']
+                            });
+
+                            // NOTE: RIVEDERE CON ANDREA
+                            socket.on('trigger:salesforce:display', display);
+
+                            //****************************************************** */
+                            // connessione socket OUTBOUND CHANNEL
+
+                            // socket.on to launch saveLog()
+
+                            socket.on('voice_outbound_channel:save', function (evt) {
+                                // evento
+                                if (evt.destexten && evt.destexten === String(_task.endUserPhone) &&
+                                    !evt.dialstatus) { // simple ring
+                                    if (_xml.createTask === 'true' && _xml.createOnAnswer === 'false') { // creazione task
+                                        saveLog(evt);
+                                    }
+                                }
+
+                                // evento
+                                if (evt.destexten && evt.destexten === String(_task.endUserPhone) &&
+                                    evt.dialstatus && evt.dialstatus === 'ANSWER') { // on answer
+                                    if (_xml.createTask === 'true' && _xml.createOnAnswer === 'true') { // creazione task
+                                        saveLog(evt);
+                                    }
+                                }
+                            });
+
+                            // socket.on to launch updateLog()
+
+                            socket.on('voice_outbound_channel:remove', function (evt) {
+
+                                vm.phone = '';
+
+                                if (evt.destexten && evt.destexten === String(_task.endUserPhone)) {
+                                    if (_xml.createTask === 'true' && _xml.updateOnHangup === 'true') { // 
+                                        updateLog(evt);
+                                    } else {
+                                        _task = {};
+                                    }
+                                }
+                            });
+                        }
+                    }
+                    //****************************************************** */
+                    if (socket && socket.disconnected) {
+                        socket.connect();
+                    }
+                } else {
+                    vm.online = false;
+                }
+            })
+            .catch(function(err){
+                console.error(err);
+            });
+    }
+
+//****************************************************** */
+    //  getCalls (checking status)
+    //****************************************************** */
+
+    function getCalls() {
+        var uniqueid = Date.now();
+
+        $http.get('http://localhost:9888/api/calls')
+        .then(
+            callsCallbacks[uniqueid] = function (result) {
+                if (result && result.status >= 200 && result.status < 300) {
+                    var message = result.data;
+                    if (_.isArray(message) && _.find(message, {
+                            stateId: 8
+                        })) {
+                        //******************************** */
+                        // activeDuration exists
+                        vm.timer = parseTime(message[0].callingNumber, message[0].activeDuration);
+                        //******************************** */
+                        vm.inCall = true;
+                    } else {
+                        // activeDuration undefined
+                        vm.inCall = false;
+                        vm.timer = '';
+                    }
+                } else {
+                    vm.inCall = false;
+                    vm.timer = '';
+                }
+            }
+
+        )
+        .catch(function(err){
+            console.error(err);
+        });
+
+        //postExtMessage('http://localhost:9888/api/calls', uniqueid);
+    }
+
+    function parseTime(cl_n, aD) {
+        var parsedTime = cl_n + ' ACTIVE  (';
+        parsedTime += aD.hours < 10 ? '0' : '';
+        parsedTime += aD.hours;
+        parsedTime += ':';
+        parsedTime += aD.minutes < 10 ? '0' : '';
+        parsedTime += aD.minutes;
+        parsedTime += ':';
+        parsedTime += aD.seconds < 10 ? '0' : '';
+        parsedTime += aD.seconds;
+        parsedTime += ')';
+
+        return parsedTime;
+    }
+
+    //******************************** */
+    //  DIAL
+    //******************************** */
+    function dial() {
+        $http.get('http://localhost:9888/api/originate/' + vm.phone);
+        $http.get('http://localhost:9888/api/answer');
+        getUser_Apex(); // logged user UserId
+    }
+
+    function getUser_Apex() {
+        sforce.interaction.runApex('UserInfo', 'getUserId', null, getUser_ApexCallback);
+    }
+
+    function getUser_ApexCallback(response) {
+        if (response.result) {
+            _task.userId = String(response.result);
+            //saveLog(); // ora gestito da evento socket.on in keepalive
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+    }
+
+    //******************************** */
+    //  SAVELOG
+    //******************************** */
+
+    function saveLog(evt) {
+
+        _task.activityDate = moment().utc(); //
+
+        // 00Q means a lead.. adding this to support logging on leads as well as contacts.
+
+        var endUserIdSubstr = _task.endUserId.substr(0, 3);
+        var description = '';
+        description += '*** Call Info ***\n';
+        description += 'Called Number: ' + _task.endUserPhone + '\n';
+        description += 'Member Name: ' + evt.membername + '\n';
+        description += 'UniqueId: ' + evt.uniqueid + '\n';
+        description += 'Date: ' + evt.starttime + '\n'; // creato alla chiamata
+        // if(evt.evt.answertime) {
+        //   description += 'Date: ' + evt.answertime + '\n'; // creato alla risposta
+        // }
+        description += '*** Time Info ***\n';
+        if (evt.answertime) {
+            description += 'Answer Time: ' + evt.answertime + '\n'; // creato alla risposta
+        }
+
+        var saveParams = 'Subject=' + 'Outbound Call [xCALLY Ticket] '  + _task.endUserPhone;
+        saveParams += '&Activitydate=' + _task.activityDate;
+        saveParams += '&Phone=' + _task.endUserPhone;
+        saveParams += '&Description=' + description;
+        saveParams += '&OwnerId=' + _task.userId; // different from ProfileId [ERROR: invalid field input type]
+        if (endUserIdSubstr === '003' || endUserIdSubstr === '00Q') {
+            saveParams += '&whoId=' + _task.endUserId;
+        } else {
+            saveParams += '&whatId=' + _task.endUserId;
+        }
+        saveParams += '&Priority=Normal';
+        //saveParams += '&Status=In Progress'; // or '&Status=Not Started';
+        saveParams += '&Status=' + _xml.defaultCreateStatus; // or '&Status=Not Started';
+        saveParams += '&Type=Call';
+        //saveParams += '&CallType=' + SP.calltype;  //should change this to reflect actual inbound or outbound
+        //
+
+        if (_xml.uniqueid && evt.uniqueid) {
+            saveParams += '&' + _xml.uniqueid + '=' + String(evt.uniqueid);
+        }
+        //   if(_xml.agent && evt.membername) {
+        //     saveParams += '&' + _xml.agent + '=' + evt.membername;
+        //   }
+        if (_xml.agent && vm.fullname) {
+            saveParams += '&' + _xml.agent + '=' + vm.fullname;
+        }
+        if (_xml.callerid && evt.calleridnum) {
+            saveParams += '&' + _xml.callerid + '=' + evt.calleridnum;
+        }
+        if (_xml.date && evt.starttime) {
+            var _date = String(evt.starttime).split(' ')[0];
+            saveParams += '&' + _xml.date + '=' + _date;
+        }
+        if (_xml.time && evt.starttime) {
+            var _time = String(evt.starttime).split(' ')[1];
+            saveParams += '&' + _xml.time + '=' + _time;
+        }
+        if (_xml.starttime && evt.starttime) {
+            saveParams += '&' + _xml.starttime + '=' + evt.starttime;
+        }
+        if (_xml.answertime && evt.answertime) {
+            saveParams += '&' + _xml.answertime + '=' + evt.answertime;
+        }
+
+        sforce.interaction.saveLog('Task', encodeURI(saveParams), saveLogCallback);
+
+        description = '';
+    }
+
+    function saveLogCallback(response) {
+
+        if (response.result) { // boolean
+            _task.Id = response.result; // differente dalla documentazione
+            dialCount = 0; // reset click count to 0 (new dial possible)
+            
+            //sforce.interaction.cti.enableClickToDial(enableClickToDialCallback);
+            
+            saveScreenPop();
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+    }
+
+    function saveScreenPop() {
+        sforce.interaction.screenPop('/' + _task.Id, true, screenPopCallback);
+    }
+
+    function screenPopCallback(response) {
+        if (response.result) { // boolean
+            console.log('API screenPop executed successfully!');
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+    }
+
+    //***************************************** */
+    // HANGUP
+    //***************************************** */
+
+    function hangup() {
+
+        vm.phone = '';
+        $http.get('http://localhost:9888/api/hangup');
+
+        // hangup comunicato alla phonebar
+        // phonebar comunica a asterisk* che invia evt relativo all'hangup
+        // intercettiamo hangup via socket - verifichiamo il numero della chiamata
+    }
+
+    //***************************************** */
+    // UPDATELOG
+    //***************************************** */
+
+    // keepalive lancia la funzione updateLog
+
+    function updateLog(evt) {
+
+        _task.activityDate = moment().utc(); // 
+
+        var description = '';
+        description += '*** Call Info ***\n';
+        description += 'Called Number: ' + _task.endUserPhone + '\n';
+        description += 'Member Name: ' + evt.membername + '\n';
+        description += 'UniqueId: ' + evt.uniqueid + '\n';
+        description += 'Date: ' + evt.endtime + '\n'; // creato alla chiusura
+        description += '*** Time Info ***\n';
+        if (evt.answertime) {
+            description += 'Answer Time: ' + evt.answertime + '\n';
+        }
+        description += 'End Time: ' + evt.endtime + '\n';
+        if (evt.holdtime) {
+            description += 'Hold Time: ' + evt.holdtime + '\n';
+        }
+        description += 'Duration: ' + evt.duration + '\n';
+        if (evt.billableseconds) {
+            description += 'Billable Seconds: ' + evt.billableseconds + '\n';
+        }
+
+        var updateParams = 'Id=' + _task.Id; // on UPDATE
+        updateParams += '&Activitydate=' + _task.activityDate;
+        //updateParams += '&Status=Completed'; // '&Status=In Progress';
+        updateParams += '&Status=' + _xml.defaultUpdateStatus;
+        updateParams += '&Description=' + description;
+        //
+        // if(_xml.uniqueid && evt.uniqueid) {
+        //   updateParams += '&' + _xml.uniqueid + '=' + String(evt.uniqueid);
+        // }
+        // if(_xml.agent && evt.membername) {
+        //   updateParams += '&' + _xml.agent + '=' + evt.membername;
+        // }
+        // if(_xml.agent && vm.fullname) {
+        //   updateParams += '&' + _xml.agent + '=' + vm.fullname;
+        // }
+        // if(_xml.callerid && evt.calleridnum) {
+        //   updateParams += '&' + _xml.callerid + '=' + evt.calleridnum;
+        // }
+        if (_xml.date && evt.endtime) {
+            var _date = String(evt.endtime).split(' ')[0];
+            updateParams += '&' + _xml.date + '=' + _date;
+        }
+        if (_xml.time && evt.endtime) {
+            var _time = String(evt.endtime).split(' ')[1];
+            updateParams += '&' + _xml.time + '=' + _time;
+        }
+        // if(_xml.starttime && evt.starttime) {
+        //   updateParams += '&' + _xml.starttime + '=' +evt.starttime;
+        // }
+        if (_xml.endtime && evt.endtime) {
+            updateParams += '&' + _xml.endtime + '=' + evt.endtime;
+        }
+        if (_xml.answertime && evt.answertime) {
+            updateParams += '&' + _xml.answertime + '=' + evt.answertime;
+        }
+        if (_xml.holdtime && evt.holdtime) {
+            updateParams += '&' + _xml.holdtime + '=' + evt.holdtime;
+        }
+        if (_xml.duration && evt.duration) {
+            updateParams += '&' + _xml.duration + '=' + String(evt.duration);
+        }
+        if (_xml.billsec && evt.billableseconds) {
+            updateParams += '&' + _xml.billsec + '=' + String(evt.billableseconds);
+        }
+
+        sforce.interaction.saveLog('Task', encodeURI(updateParams), updateLogCallback); // 'Task' is mandatory also on update
+
+        description = '';
+
+    }
+
+    //***************************************** */
+
+    function updateLogCallback(response) {
+
+        if (_xml.createTask === 'true' && _xml.updateOnHangup === 'true' && _xml.refreshOnHangup === 'true') { // 
+
+            if (response.result) { // boolean
+                _task = {}; // reset _task object
+                sforce.interaction.refreshPage(refreshViewCallback); // it refreshes the current active tab
+
+            } else {
+                console.error('Something went wrong! Errors:', response.errors);
+            }
+        } else {
+            // nessun refresh
+            // resetta _task = {}
+            _task = {}; 
+        }
+    }
+
+    function refreshViewCallback(response) {
+        if (response.result) { // boolean
+            console.log('API refreshView classic executed successfully! returnValue:', response.result);
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+    }
+
+    //***************************************** */
+    // receiveMessage
+    //***************************************** */
+    function receiveMessage(message) {
+        var data = message.data;
+
+        if (agentCallbacks[data.uniqueid]) {
+            agentCallbacks[data.uniqueid](data);
+            delete agentCallbacks[data.uniqueid];
+        } else if (callsCallbacks[data.uniqueid]) {
+            callsCallbacks[data.uniqueid](data);
+            delete callsCallbacks[data.uniqueid];
+        }
+
+        $scope.$apply();
+    }
+
+    $interval(keepalive, 2000);
+    $interval(getCalls, 1000);
+
+    $window.addEventListener('message', receiveMessage, false);
+
+}
\ No newline at end of file
diff --git a/js/controllers/desk.controller.js b/js/controllers/desk.controller.js
new file mode 100644
index 0000000..aecefdc
--- /dev/null
+++ b/js/controllers/desk.controller.js
@@ -0,0 +1,278 @@
+'use strict';
+
+angular
+    .module('motion')
+    .controller('deskController', deskController);
+
+function deskController($scope, $window, $interval, $location, angularLoad, $http) {
+
+    var vm = this;
+
+    // Integration
+    var version = null; // global
+    var uri = null;
+
+    var cti = null;
+    var agentCallbacks = {};
+    var callsCallbacks = {};
+
+    // Socket object
+    var socket = null;
+
+    // Data
+    vm.timer = '';
+    vm.phone = ''; // used inside DOM
+    vm.inCall = false; // used inside DOM
+
+    // task
+    var _task = {}; // global 
+    var _xml = {};
+
+    // Methods
+    vm.compose = compose;
+    vm.remove = remove;
+    vm.hangup = hangup;
+    vm.dial = dial;
+
+    var dialCount = 0;
+
+    //********************************************* */
+
+    var scriptTag = angular.element(document.createElement('base'));
+    scriptTag.href = window.location.pathname;
+
+    //*****************************************//
+    // LOADING
+    //********************************************* */
+
+    angularLoad
+    .loadScript(window.location.pathname.substring(0,window.location.pathname.lastIndexOf('/'))  +  'js/salesforce/interaction-desk.js') //
+        .then(function () {
+            vm.host = $location.search().host;
+            desk.ready(function (data) {
+                desk.interaction.cti.enableClickToDial();
+                desk.interaction.cti.onClickToDial(onClickToDialListener);
+            });
+        }).catch(function (err) {
+            console.error(err);
+        });
+
+    function onClickToDialListener(data) {
+
+        if (dialCount < 1) {
+
+            if (vm.online && data.number) {
+                vm.phone = data.number;
+
+                vm.dial();
+                dialCount++;
+            }
+        }
+    }
+
+    //*****************************************//
+    // display
+    //*****************************************//
+
+    function display(task) {
+        console.log('Display', task);
+        desk.interaction.screenPop(task.recordId.toString(), 'object=case');
+    }
+
+    //****************************************************** */
+    // compose
+    //****************************************************** */
+    function compose(value) {
+        vm.phone = vm.phone.concat(value);
+    }
+
+    //****************************************************** */
+    // remove
+    //****************************************************** */
+    function remove() {
+        vm.phone = vm.phone.substring(0, vm.phone.length - 1);
+    }
+
+    //****************************************************** */
+    // postExtMessage
+    //****************************************************** */
+    function postExtMessage(url, uniqueid) {
+        $window.parent.postMessage({
+            id: null,
+            tag: 'iframe',
+            className: null,
+            uniqueid: uniqueid,
+            options: {
+                url: url
+            }
+        }, "*");
+    }
+
+    //****************************************************** */
+    //  KEEPALIVE
+    //****************************************************** */
+    function keepalive() {
+
+        var uniqueid = Date.now();
+
+        agentCallbacks[uniqueid] = function (data) {
+            if (data.response && data.response.status >= 200 && data.response.status < 300) {
+                var message = JSON.parse(data.response.message);
+
+                vm.id = message.id;
+                vm.name = message.name;
+                vm.fullname = message.fullname;
+                vm.internal = String(message.internal); //  message.accountcode; 
+
+                vm.online = true;
+
+                //****************************************************** */
+                if (!socket) {
+                    if (vm.host && vm.id) {
+                        console.log('host', vm.host);
+                        console.log('id', vm.id);
+
+                        socket = io(vm.host, {
+                            query: {
+                                id: vm.id
+                            },
+                            autoConnect: false,
+                            transports: ['websocket', 'polling']
+                        });
+
+                        // NOTE: RIVEDERE CON ANDREA
+                        socket.on('trigger:desk:display', display);
+                    }
+                }
+                //****************************************************** */
+
+                if (socket && socket.disconnected) {
+                    socket.connect();
+                }
+            } else {
+                vm.online = false;
+            }
+        };
+
+        postExtMessage('http://localhost:9888/api/agent', uniqueid);
+
+        // NOTA: Perchè viene inviato un'iframe message e non viene fatta direttamente la HTTP Request?
+
+        // $http.get('http://localhost:9888/api/agent')
+        //   .then(function (res) {
+        //     console.log(res);
+        //   }, function (err) {
+        //     console.log(err);
+        //   });
+    }
+
+    //****************************************************** */
+    //  getCalls (checking status)
+    //****************************************************** */
+
+    function getCalls() {
+        var uniqueid = Date.now();
+
+        callsCallbacks[uniqueid] = function (data) {
+
+            if (data.response && data.response.status >= 200 && data.response.status < 300) {
+                var message = JSON.parse(data.response.message);
+
+                if (_.isArray(message) && _.find(message, {
+                        stateId: 8
+                    })) {
+                    //******************************** */
+                    // activeDuration exists
+
+                    vm.timer = parseTime(message[0].callingNumber, message[0].activeDuration);
+
+                    //******************************** */
+                    vm.inCall = true;
+
+                } else {
+                    // activeDuration undefined
+                    vm.inCall = false;
+                    vm.timer = '';
+                }
+            } else {
+                vm.inCall = false;
+                vm.timer = '';
+            }
+        };
+
+        postExtMessage('http://localhost:9888/api/calls', uniqueid);
+    }
+
+    function parseTime(cl_n, aD) {
+        var parsedTime = cl_n + ' ACTIVE  (';
+        parsedTime += aD.hours < 10 ? '0' : '';
+        parsedTime += aD.hours;
+        parsedTime += ':';
+        parsedTime += aD.minutes < 10 ? '0' : '';
+        parsedTime += aD.minutes;
+        parsedTime += ':';
+        parsedTime += aD.seconds < 10 ? '0' : '';
+        parsedTime += aD.seconds;
+        parsedTime += ')';
+
+        return parsedTime;
+    }
+
+    //******************************** */
+    //  
+
+    //******************************** */
+    function dial() {
+
+        var uniqueid = Date.now();
+
+        //postExtMessage('http://localhost:9888/api/originate/' + _task.endUserPhone, uniqueid);
+        postExtMessage('http://localhost:9888/api/originate/' + vm.phone, uniqueid);
+        postExtMessage('http://localhost:9888/api/answer', uniqueid);
+
+    }
+
+
+    //***************************************** */
+    // HANGUP
+    //***************************************** */
+
+    function hangup() {
+
+        vm.phone = '';
+        dialCount = 0;
+        
+        var uniqueid = Date.now();
+        postExtMessage('http://localhost:9888/api/hangup', uniqueid);
+
+        // hangup comunicato alla phonebar
+        // phonebar comunica a asterisk* che invia evt relativo all'hangup
+        // intercettiamo hangup via socket - verifichiamo il numero della chiamata
+    }
+
+    //***************************************** */
+    //***************************************** */
+
+    /**
+     * receiveMessage
+     */
+    function receiveMessage(message) {
+        var data = message.data;
+
+        if (agentCallbacks[data.uniqueid]) {
+            agentCallbacks[data.uniqueid](data);
+            delete agentCallbacks[data.uniqueid];
+        } else if (callsCallbacks[data.uniqueid]) {
+            callsCallbacks[data.uniqueid](data);
+            delete callsCallbacks[data.uniqueid];
+        }
+
+        $scope.$apply();
+    }
+
+    $interval(keepalive, 2000);
+    $interval(getCalls, 2000);
+
+    $window.addEventListener('message', receiveMessage, false);
+
+}
\ No newline at end of file
diff --git a/js/controllers/lightning.controller.js b/js/controllers/lightning.controller.js
new file mode 100644
index 0000000..9e8d895
--- /dev/null
+++ b/js/controllers/lightning.controller.js
@@ -0,0 +1,603 @@
+'use strict';
+
+angular
+    .module('motion')
+    .controller('lightningController', lightningController);
+
+function lightningController($scope, $window, $interval, $location, angularLoad, $http) {
+
+    var vm = this;
+    var agentCallbacks = {};
+    var callsCallbacks = {};
+    // Socket object
+    var socket = null;
+    // Data
+    vm.timer = '';
+    vm.phone = ''; // used inside DOM
+    vm.inCall = false; // used inside DOM
+    // task
+    var _task = {}; // global
+    var _xml = {};
+    // Methods
+    vm.compose = compose;
+    vm.remove = remove;
+    vm.hangup = hangup;
+    vm.dial = dial;
+    var dialCount = 0;
+
+    //********************************************* */
+    var scriptTag = angular.element(document.createElement('base'));
+    scriptTag.href = window.location.pathname;
+
+    //*****************************************//
+    // LOADING
+    //********************************************* */
+
+    angularLoad
+        .loadScript(window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + '/js/salesforce/lightning.js') //
+        .then(function () {
+            console.log('Salesforce Lightning');
+            sforce.opencti.getCallCenterSettings({
+                callback: getCallCenterSettingsCallback
+            });
+            sforce.opencti.enableClickToDial({
+                callback: enableClickToDialCallback
+            });
+        }).catch(function (err) {
+            console.error(err);
+        });
+
+    function enableClickToDialCallback(response) {
+        console.log('enableClickToDialCallback', response);
+        if (response.success) {
+            sforce.opencti.onClickToDial({
+                listener: onClickToDialListener_lightning
+            });
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+    }
+
+    //*****************************************//
+    // getCallCenterSettingsCallback
+    //*****************************************//
+    function getCallCenterSettingsCallback(response) {
+
+        if (response.success) {
+
+            vm.host = response.returnValue['/Motion/Host'];
+            //_xml.host = _parsedXml['/Motion/Host'];
+
+            _xml.createTask = response.returnValue['/ManageTask/createTask'];
+            _xml.createTask = _xml.createTask ? _xml.createTask : 'false';
+
+            _xml.createOnAnswer = response.returnValue['/ManageTask/createOnAnswer'];
+            _xml.createOnAnswer = _xml.createOnAnswer ? _xml.createOnAnswer : 'false';
+
+            _xml.updateOnHangup = response.returnValue['/ManageTask/updateOnHangup'];
+            _xml.updateOnHangup = _xml.updateOnHangup ? _xml.updateOnHangup : 'false';
+
+            _xml.refreshOnHangup = response.returnValue['/ManageTask/refreshOnHangup'];
+            _xml.refreshOnHangup = _xml.refreshOnHangup ? _xml.refreshOnHangup : 'false';
+
+            _xml.defaultCreateStatus = response.returnValue['/ManageTask/defaultCreateStatus'];
+            _xml.defaultCreateStatus = _xml.defaultCreateStatus ? _xml.defaultCreateStatus : 'In Progress';
+
+            _xml.defaultUpdateStatus = response.returnValue['/ManageTask/defaultUpdateStatus'];
+            _xml.defaultUpdateStatus = _xml.defaultUpdateStatus ? _xml.defaultUpdateStatus : 'Completed';
+
+            _xml.uniqueid = response.returnValue['/FieldMapping/uniqueid'];
+            _xml.uniqueid = _xml.uniqueid ? _xml.uniqueid : null;
+            _xml.agent = response.returnValue['/FieldMapping/agent'];
+            _xml.agent = _xml.agent ? _xml.agent : null;
+            _xml.callerid = response.returnValue['/FieldMapping/callerid'];
+            _xml.callerid = _xml.callerid ? _xml.callerid : null;
+            _xml.date = response.returnValue['/FieldMapping/date'];
+            _xml.date = _xml.date ? _xml.date : null;
+            _xml.time = response.returnValue['/FieldMapping/time'];
+            _xml.time = _xml.time ? _xml.time : null;
+            _xml.starttime = response.returnValue['/FieldMapping/starttime'];
+            _xml.starttime = _xml.starttime ? _xml.starttime : null;
+            _xml.endtime = response.returnValue['/FieldMapping/endtime'];
+            _xml.endtime = _xml.endtime ? _xml.endtime : null;
+            _xml.answertime = response.returnValue['/FieldMapping/answertime'];
+            _xml.answertime = _xml.answertime ? _xml.answertime : null;
+            _xml.holdtime = response.returnValue['/FieldMapping/holdtime'];
+            _xml.holdtime = _xml.holdtime ? _xml.holdtime : null;
+            _xml.duration = response.returnValue['/FieldMapping/duration'];
+            _xml.duration = _xml.duration ? _xml.duration : null;
+            _xml.billsec = response.returnValue['/FieldMapping/billsec'];
+            _xml.billsec = _xml.billsec ? _xml.billsec : null;
+
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+    }
+
+    //*****************************************//
+    // onClickToDialListener LIGHTNING
+    //*****************************************//
+    function onClickToDialListener_lightning(data) {
+
+        if (dialCount < 1) {
+            if (vm.online && data.number) {
+                vm.phone = data.number;
+
+                _task.endUserPhone = angular.copy(data.number);
+                // _task.endUserPhone is now indipendent from both vm.phone & data.phone
+                _task.endUserObjectType = data.objectType;
+                _task.endUserId = data.recordId;
+                _task.endUserName = data.recordName;
+                vm.dial();
+
+                dialCount++; // impedisce di creare eventi dial() dopo il primo click
+            }
+        }
+    }
+
+    //*****************************************//
+    // display
+    //*****************************************//
+
+    function display(task) {
+        sforce.opencti.screenPop({
+            type: sforce.opencti.SCREENPOP_TYPE.SOBJECT, //Review the arguments section.
+            params: {
+                recordId: task.recordId
+            }
+        });
+    }
+
+    //****************************************************** */
+    // compose
+    //****************************************************** */
+    function compose(value) {
+        vm.phone = vm.phone.concat(value);
+    }
+
+    //****************************************************** */
+    // remove
+    //****************************************************** */
+    function remove() {
+        vm.phone = vm.phone.substring(0, vm.phone.length - 1);
+    }
+
+    //****************************************************** */
+    //  KEEPALIVE
+    //****************************************************** */
+    function keepalive() {
+
+        var uniqueid = Date.now();
+
+        $http.get('http://localhost:9888/api/agent')
+            .then(agentCallbacks[uniqueid] = function (result) {
+                if (result && result.status >= 200 && result.status < 300) {
+                    var message = result.data;
+                    vm.id = message.id;
+                    vm.name = message.name;
+                    vm.fullname = message.fullname;
+                    vm.internal = String(message.internal);
+                    vm.online = true;
+
+                    //****************************************************** */
+                    if (!socket) {
+                        if (vm.host && vm.id) {
+                            socket = io(vm.host, {
+                                query: {
+                                    id: vm.id
+                                },
+                                autoConnect: false,
+                                transports: ['websocket', 'polling']
+                            });
+
+                            // NOTE: RIVEDERE CON ANDREA
+                            socket.on('trigger:salesforce:display', display);
+
+                            //****************************************************** */
+                            // connessione socket OUTBOUND CHANNEL
+
+                            // socket.on to launch saveLog()
+
+                            socket.on('voice_outbound_channel:save', function (evt) {
+                                // evento
+                                if (evt.destexten && evt.destexten === String(_task.endUserPhone) &&
+                                    !evt.dialstatus) { // simple ring
+                                    if (_xml.createTask === 'true' && _xml.createOnAnswer === 'false') { // creazione task
+                                        saveLog(evt);
+                                    }
+                                }
+
+                                // evento
+                                if (evt.destexten && evt.destexten === String(_task.endUserPhone) &&
+                                    evt.dialstatus && evt.dialstatus === 'ANSWER') { // on answer
+                                    if (_xml.createTask === 'true' && _xml.createOnAnswer === 'true') { // creazione task
+                                        saveLog(evt);
+                                    }
+                                }
+                            });
+
+                            // socket.on to launch updateLog()
+
+                            socket.on('voice_outbound_channel:remove', function (evt) {
+
+                                vm.phone = '';
+                                
+                                if (evt.destexten && evt.destexten === String(_task.endUserPhone)) {
+                                    // attende evento hangup prima di farlo 
+                                    // controlla che riguardi la nostra telefonata
+                                    if (_xml.createTask === 'true' && _xml.updateOnHangup === 'true') { // 
+                                        // esegue solo se updateOnHungup = true
+                                        updateLog(evt);
+                                    } else {
+                                        // si incarica di resettare _task = {}
+                                        _task = {};
+                                    }
+                                }
+                            });
+                        }
+                    }
+                    //****************************************************** */
+                    if (socket && socket.disconnected) {
+                        socket.connect();
+                    }
+                } else {
+                    vm.online = false;
+                }
+            })
+            .catch(function(err){
+                console.error(err);
+            });
+    }
+
+    //****************************************************** */
+    //  getCalls (checking status)
+    //****************************************************** */
+
+    function getCalls() {
+        var uniqueid = Date.now();
+
+        $http.get('http://localhost:9888/api/calls')
+        .then(
+            callsCallbacks[uniqueid] = function (result) {
+                if (result && result.status >= 200 && result.status < 300) {
+                    var message = result.data;
+                    if (_.isArray(message) && _.find(message, {
+                            stateId: 8
+                        })) {
+                        //******************************** */
+                        // activeDuration exists
+                        vm.timer = parseTime(message[0].callingNumber, message[0].activeDuration);
+                        //******************************** */
+                        vm.inCall = true;
+                    } else {
+                        // activeDuration undefined
+                        vm.inCall = false;
+                        vm.timer = '';
+                    }
+                } else {
+                    vm.inCall = false;
+                    vm.timer = '';
+                }
+            }
+        )
+        .catch(function(err){
+            console.error(err);
+        });
+    }
+
+    function parseTime(cl_n, aD) {
+        var parsedTime = cl_n + ' ACTIVE  (';
+        parsedTime += aD.hours < 10 ? '0' : '';
+        parsedTime += aD.hours;
+        parsedTime += ':';
+        parsedTime += aD.minutes < 10 ? '0' : '';
+        parsedTime += aD.minutes;
+        parsedTime += ':';
+        parsedTime += aD.seconds < 10 ? '0' : '';
+        parsedTime += aD.seconds;
+        parsedTime += ')';
+
+        return parsedTime;
+    }
+
+    //******************************** */
+    //  DIAL
+    //******************************** */
+    function dial() {
+
+        //var uniqueid = Date.now();
+
+        $http.get('http://localhost:9888/api/originate/' + vm.phone);
+
+        $http.get('http://localhost:9888/api/answer');
+
+        getUser_Apex(); // logged user UserId
+    }
+
+    function getUser_Apex() {
+        //Invokes API method
+        var objectParams = {
+            apexClass: 'UserInfo',
+            methodName: 'getUserId', 
+            callback: getUser_ApexCallback
+        };
+        sforce.opencti.runApex(objectParams);
+    }
+
+    function getUser_ApexCallback(response) {
+
+        if (response.success) {
+            _task.userId = String(response.returnValue.runApex);
+            //saveLog(); // ora gestito da evento socket.on in keepalive
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+    }
+
+    //******************************** */
+    //  SAVELOG
+    //******************************** */
+
+    function saveLog(evt) {
+
+        _task.activityDate = moment().utc(); //
+
+        // 00Q means a lead.. adding this to support logging on leads as well as contacts.
+
+        var endUserIdSubstr = _task.endUserId.substr(0, 3);
+        var description = '';
+        description += '*** Call Info ***\n';
+        description += 'Called Number: ' + _task.endUserPhone + '\n';
+        description += 'Member Name: ' + evt.membername + '\n';
+        description += 'UniqueId: ' + evt.uniqueid + '\n';
+        description += 'Date: ' + evt.starttime + '\n'; // creato alla chiamata
+        // if(evt.evt.answertime) {
+        //   description += 'Date: ' + evt.answertime + '\n'; // creato alla risposta
+        // }
+        description += '*** Time Info ***\n';
+        if (evt.answertime) {
+            description += 'Answer Time: ' + evt.answertime + '\n'; // creato alla risposta
+        }
+        var obj_saveLog = { //
+            //entityApiName: 'Task', //Optional, on CREATE
+            //Id: string, //Optional, on UPDATE
+            OwnerId: _task.userId, // different from ProfileId [ERROR: invalid field input type]
+            //WhoId: _task.endUserId,
+            ActivityDate: _task.activityDate, //
+            IsReminderSet: false,
+            IsRecurrence: false,
+            Subject: 'Outbound Call [xCALLY Ticket] ' + _task.endUserPhone,
+            Description: description,
+            Priority: 'Normal',
+            Type: 'Call',
+            //Status: 'In Progress' // or Status: 'Not Started'
+            Status: _xml.defaultCreateStatus
+        };
+
+        obj_saveLog.entityApiName = 'Task'; // CREATE
+        if (endUserIdSubstr === '003' || endUserIdSubstr === '00Q') {
+            obj_saveLog.WhoId = _task.endUserId;
+        } else {
+            obj_saveLog.WhatId = _task.endUserId;
+        }
+
+        if (_xml.uniqueid && evt.uniqueid) {
+            obj_saveLog[_xml.uniqueid] = String(evt.uniqueid);
+        }
+        // if (_xml.agent && evt.membername) {
+        //     obj_saveLog[_xml.agent] = evt.membername;
+        // }
+        if (_xml.agent && vm.fullname) {
+            obj_saveLog[_xml.agent] = vm.fullname;
+        }
+        if (_xml.callerid && evt.calleridnum) {
+            obj_saveLog[_xml.callerid] = evt.calleridnum;
+        }
+        if (_xml.date && evt.starttime) {
+            var date = String(evt.starttime).split(' ')[0];
+            obj_saveLog[_xml.date] = date;
+        }
+        if (_xml.time && evt.starttime) {
+            var time = String(evt.starttime).split(' ')[1];
+            obj_saveLog[_xml.time] = time;
+        }
+        if (_xml.starttime && evt.starttime) {
+            obj_saveLog[_xml.starttime] = evt.starttime;
+        }
+        if (_xml.answertime && evt.answertime) {
+            obj_saveLog[_xml.answertime] = evt.answertime;
+        }
+
+        sforce.opencti.saveLog({
+            value: JSON.parse(JSON.stringify(obj_saveLog)),
+            callback: saveLogCallback //Optional
+        });
+        description = '';
+    }
+
+    function saveLogCallback(response) {
+
+        if (response.success) { // boolean
+            _task.Id = response.returnValue.recordId; //
+            dialCount = 0; // reset click count to 0 (new dial possible)
+            
+            saveScreenPop();
+
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+    }
+
+    function saveScreenPop() {
+        sforce.opencti.screenPop({
+            type: sforce.opencti.SCREENPOP_TYPE.SOBJECT, //Review the arguments section.
+            params: {
+                recordId: _task.Id
+            }, //Depends on the SCREENPOP_TYPE. Review the arguments section.
+            callback: screenPopCallback
+        });
+
+    }
+
+    function screenPopCallback(response) {
+        if (response.success) {
+            console.log('API screenPop executed successfully!');
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+    }
+
+    //***************************************** */
+    // HANGUP
+    //***************************************** */
+
+    function hangup() {
+        vm.phone = '';
+        $http.get('http://localhost:9888/api/hangup');
+
+        // hangup comunicato alla phonebar
+        // phonebar comunica a asterisk* che invia evt relativo all'hangup
+        // intercettiamo hangup via socket - verifichiamo il numero della chiamata
+    }
+
+    //***************************************** */
+    // UPDATELOG
+    //***************************************** */
+
+    // keepalive lancia la funzione updateLog
+
+    function updateLog(evt) {
+        _task.activityDate = moment().utc(); //
+        var description = '';
+        description += '*** Call Info ***\n';
+        description += 'Called Number: ' + _task.endUserPhone + '\n';
+        description += 'Member Name: ' + evt.membername + '\n';
+        description += 'UniqueId: ' + evt.uniqueid + '\n';
+        description += 'Date: ' + evt.endtime + '\n'; // creato alla chiusura
+        description += '*** Time Info ***\n';
+        if (evt.answertime) {
+            description += 'Answer Time: ' + evt.answertime + '\n';
+        }
+        if (evt.endtime) {
+            description += 'End Time: ' + evt.endtime + '\n';
+        }
+        if (evt.holdtime) {
+            description += 'Hold Time: ' + evt.holdtime + '\n';
+        }
+        if (evt.duration) {
+            description += 'Duration: ' + evt.duration + '\n';
+        }
+        if (evt.billableseconds) {
+            description += 'Billable Seconds: ' + evt.billableseconds + '\n';
+        }
+        //var endUserIdSubstr = _task.endUserId.substr(0, 3);
+        var obj_updateLog = { //
+            Id: _task.Id, // on UPDATE
+            ActivityDate: _task.activityDate, // update
+            Status: _xml.defaultUpdateStatus,
+            Description: description
+        };
+
+        // if(_xml.uniqueid && evt.uniqueid) {
+        //   obj_updateLog[_xml.uniqueid] = String(evt.uniqueid);
+        // }
+        // if(_xml.agent && evt.membername) {
+        //   obj_updateLog[_xml.agent] = evt.membername;
+        // }
+        // if (_xml.agent && vm.fullname) {
+        //     obj_updateLog[_xml.agent] = vm.fullname;
+        // }
+        // if(_xml.callerid && evt.calleridnum) {
+        //   obj_updateLog[_xml.callerid] = evt.calleridnum;
+        // }
+        if (_xml.date && evt.endtime) {
+            var date = String(evt.endtime).split(' ')[0];
+            console.log("TCL: updateLog -> date", date);
+            obj_updateLog[_xml.date] = date;
+        }
+        if (_xml.time && evt.endtime) {
+            var time = String(evt.endtime).split(' ')[1];
+            console.log("TCL: updateLog -> time", time);
+            obj_updateLog[_xml.time] = time;
+        }
+        // if(_xml.starttime && evt.starttime) {
+        //   obj_updateLog[_xml.starttime] = evt.starttime;
+        // }
+        if (_xml.endtime && evt.endtime) {
+            obj_updateLog[_xml.endtime] = evt.endtime;
+        }
+        if (_xml.answertime && evt.answertime) {
+            obj_updateLog[_xml.answertime] = evt.answertime;
+        }
+        if (_xml.holdtime && evt.holdtime) {
+            obj_updateLog[_xml.holdtime] = String(evt.holdtime);
+        }
+        if (_xml.duration && evt.duration) {
+            obj_updateLog[_xml.duration] = String(evt.duration);
+        }
+        if (_xml.billsec && evt.billableseconds) {
+            obj_updateLog[_xml.billsec] = String(evt.billableseconds);
+        }
+
+        console.log(obj_updateLog);
+        sforce.opencti.saveLog({
+            value: JSON.parse(JSON.stringify(obj_updateLog)),
+            callback: updateLogCallback //Optional
+        });
+
+        description = '';
+    }
+
+    //***************************************** */
+
+    function updateLogCallback(response) {
+
+        if (_xml.createTask === 'true' && _xml.updateOnHangup === 'true' && _xml.refreshOnHangup === 'true') { //
+
+            if (response.success) {
+                _task = {}; 
+                sforce.opencti.refreshView({
+                    callback: refreshViewCallback
+                });
+            } else {
+                console.error('Something went wrong! Errors:', response.errors);
+            }
+        } else {
+            _task = {};
+        }
+    }
+
+    function refreshViewCallback(response) {
+        console.log('API refreshView classic executed successfully! returnValue:', response);
+
+        if (response.success) { // boolean
+            console.log('API refreshView lightning executed successfully!');
+        } else {
+            console.error('Something went wrong! Errors:', response.errors);
+        }
+
+    }
+
+    //***************************************** */
+    // RECEIVE MESSAGE
+    //***************************************** */
+
+    function receiveMessage(message) {
+        var data = message.data;
+
+        if (agentCallbacks[data.uniqueid]) {
+            agentCallbacks[data.uniqueid](data);
+            delete agentCallbacks[data.uniqueid];
+        } else if (callsCallbacks[data.uniqueid]) {
+            callsCallbacks[data.uniqueid](data);
+            delete callsCallbacks[data.uniqueid];
+        }
+
+        $scope.$apply();
+    }
+
+    $interval(keepalive, 2000);
+    $interval(getCalls, 1000);
+
+    $window.addEventListener('message', receiveMessage, false);
+}
\ No newline at end of file
diff --git a/js/salesforce/interaction.js b/js/salesforce/interaction.js
index b765d66..69bb9f6 100644
--- a/js/salesforce/interaction.js
+++ b/js/salesforce/interaction.js
@@ -1,610 +1,610 @@
 /*
-* Salesforce.com Interaction Toolkit 40.0
-* Copyright, 2015, salesforce.com, inc.
-* All Rights Reserved
-*/
+ * Copyright 2017 salesforce.com, inc.
+ * All Rights Reserved
+ * Company Confidential
+ */
 
 'use strict';
 
 window.sforce = window.sforce || {};
 
 sforce.interaction = (function() {
-  var apiVersion = 40;
-  var INTERACTION_API = 'interactionApi/';
-  var ENTITY_FEED_API = 'entityFeedApi/';
-  var frameOrigin = null;
-  var nonce = null;
-  var callbacks = {};
-  var GET_CALL_CENTER_SETTINGS = 'getCallCenterSettings';
-  var GET_SOFTPHONE_LAYOUT = 'getSoftphoneLayout';
-  var GET_PAGE_INFO = 'getPageInfo';
-  var SET_SOFTPHONE_HEIGHT = 'setSoftphoneHeight';
-  var SET_SOFTPHONE_WIDTH = 'setSoftphoneWidth';
-  var IS_IN_CONSOLE = 'isInConsole';
-  var SCREEN_POP = 'screenPop';
-  var SEARCH_AND_GET_SCREEN_POP_URL = 'searchAndGetScreenPopUrl';
-  var SEARCH_AND_SCREEN_POP = 'searchAndScreenPop';
-  var ENABLE_CLICK_TO_DIAL = 'enableClickToDial';
-  var DISABLE_CLICK_TO_DIAL = 'disableClickToDial';
-  var RUN_APEX_QUERY = 'runApex';
-  var SAVE_LOG = 'saveLog';
-  var SET_VISIBLE = 'setVisible';
-  var IS_VISIBLE = 'isVisible';
-  var AVAILABLE = 'available';
-  var CONNECT = 'connect';
-  var DISCONNECT = 'disconnect';
-  var REFRESH_OBJECT = 'refreshObject';
-  var RELOAD_FRAME = 'reloadFrame';
-  var REFRESH_PAGE = 'refreshPage';
-  var REFRESH_RELATED_LIST = 'refreshRelatedList';
-  var NOTIFY_INITIALIZATION_COMPLETE = 'notifyInitializationComplete';
-  var GET_DIRECTORY_NUMBERS = "getDirectoryNumbers";
-  var listeners = {onClickToDial:'onClickToDial', onFocus:'onFocus', onObjectUpdate:'onObjectUpdate'};
-  var methodId = 0;
-  var entityFeedApi = null;
-  var servedFromCanvas = false;
-
-  function isApiMessage(event, apiEndPoint) {
-    return event.data && event.data.indexOf(apiEndPoint) === 0;
-  }
-
-  function parseAuthParams(params) {
-    // initialize if sfdcIFrameOrigin and nonce are present
-    if (params['sfdcIFrameOrigin'] && params['nonce']) {
-      frameOrigin = params['sfdcIFrameOrigin'] ? params['sfdcIFrameOrigin'].toLowerCase():null;
-      nonce = params['nonce'];
-    } else {
-      // we may use canvas
-      if (typeof Sfdc !== "undefined" && Sfdc.canvas && Sfdc.canvas.client) {
-        var sr = Sfdc.canvas.client.signedrequest();
-        var parsedRequest;
-        if (sr) {
-          if (typeof sr === "string") {
-            parsedRequest = JSON.parse(sr);
-          } else {
-            // assume we're using OAuth
-            parsedRequest = sr;
-          }
-        }
-        if (parsedRequest) {
-          var environment;
-          if (parsedRequest.context) {
-            environment = parsedRequest.context.environment;
-          } else if (parsedRequest.payload) {
-            environment = parsedRequest.payload.environment;
-          }
-          if (environment && environment.parameters) {
-            frameOrigin = environment.parameters.sfdcIframeOrigin;
-            nonce = environment.parameters.nonce;
-            servedFromCanvas = environment.parameters.servedFromCanvas;
-          }
-        }
-      }
+    var apiVersion = 46;
+    var INTERACTION_API = 'interactionApi/';
+    var ENTITY_FEED_API = 'entityFeedApi/';
+    var frameOrigin = null;
+    var nonce = null;
+    var callbacks = {};
+    var GET_CALL_CENTER_SETTINGS = 'getCallCenterSettings';
+    var GET_SOFTPHONE_LAYOUT = 'getSoftphoneLayout';
+    var GET_PAGE_INFO = 'getPageInfo';
+    var SET_SOFTPHONE_HEIGHT = 'setSoftphoneHeight';
+    var SET_SOFTPHONE_WIDTH = 'setSoftphoneWidth';
+    var IS_IN_CONSOLE = 'isInConsole';
+    var SCREEN_POP = 'screenPop';
+    var SEARCH_AND_GET_SCREEN_POP_URL = 'searchAndGetScreenPopUrl';
+    var SEARCH_AND_SCREEN_POP = 'searchAndScreenPop';
+    var ENABLE_CLICK_TO_DIAL = 'enableClickToDial';
+    var DISABLE_CLICK_TO_DIAL = 'disableClickToDial';
+    var RUN_APEX_QUERY = 'runApex';
+    var SAVE_LOG = 'saveLog';
+    var SET_VISIBLE = 'setVisible';
+    var IS_VISIBLE = 'isVisible';
+    var AVAILABLE = 'available';
+    var CONNECT = 'connect';
+    var DISCONNECT = 'disconnect';
+    var REFRESH_OBJECT = 'refreshObject';
+    var RELOAD_FRAME = 'reloadFrame';
+    var REFRESH_PAGE = 'refreshPage';
+    var REFRESH_RELATED_LIST = 'refreshRelatedList';
+    var NOTIFY_INITIALIZATION_COMPLETE = 'notifyInitializationComplete';
+    var GET_DIRECTORY_NUMBERS = "getDirectoryNumbers";
+    var listeners = {onClickToDial:'onClickToDial', onFocus:'onFocus', onObjectUpdate:'onObjectUpdate'};
+    var methodId = 0;
+    var entityFeedApi = null;
+    var servedFromCanvas = false;
+
+    function isApiMessage(event, apiEndPoint) {
+        return event.data && event.data.indexOf(apiEndPoint) === 0;
     }
-  }
-
-  /**
-  * Process messages received from SFDC by executing callbacks, if any.
-  * The event object contains the following fields:
-  *      method: the API method that was called.
-  *      result: result returned from the call.
-  *      error: an error message if any errors were encountered.
-  */
-  function processPostMessage(event) {
-    var params;
-    try {
-      // Check if call is for entity feed
-      if (isApiMessage(event, ENTITY_FEED_API)) {
-        if (entityFeedApi && entityFeedApi.processPostMessage) {
-          params = entityFeedApi.processPostMessage(event);
-        }
-        if (!params) {
-          return;
-        }
-      } else if (isApiMessage(event, INTERACTION_API)) {
-        if (event.origin !== frameOrigin || !frameOrigin) {
-          // Only trust messages from the adapter frame
-          return;
-        }
-
-        var message = event.data.replace(INTERACTION_API, ''); // strip off API target
-        params = parseUrlQueryString(message);
 
-        // convert string true/false to boolean for methods that needs to return boolean values.
-        if (params && (params.result === 'true' || params.result === 'false')) {
-          params.result = params.result === 'true';
-        }
-      } else {
-        // return if postMessage is not targeted to interaction API
-        return;
-      }
-
-      // execute callbacks registered for the method called
-      for (var methodName in callbacks) {
-        if (callbacks.hasOwnProperty(methodName)) {
-          if (params.method === methodName) {
-            for (var i in callbacks[methodName]) {
-              callbacks[methodName][i](params);
-            }
-            if (!listeners[methodName]) {
-              delete callbacks[methodName];
+    function parseAuthParams(params) {
+        // initialize if sfdcIFrameOrigin and nonce are present
+        if (params['sfdcIFrameOrigin'] && params['nonce']) {
+            frameOrigin = params['sfdcIFrameOrigin'] ? params['sfdcIFrameOrigin'].toLowerCase():null;
+            nonce = params['nonce'];
+        } else {
+            // we may use canvas
+            if (typeof Sfdc !== "undefined" && Sfdc.canvas && Sfdc.canvas.client) {
+                var sr = Sfdc.canvas.client.signedrequest();
+                var parsedRequest;
+                if (sr) {
+                    if (typeof sr === "string") {
+                        parsedRequest = JSON.parse(sr);
+                    } else {
+                        // assume we're using OAuth
+                        parsedRequest = sr;
+                    }
+                }
+                if (parsedRequest) {
+                    var environment;
+                    if (parsedRequest.context) {
+                        environment = parsedRequest.context.environment;
+                    } else if (parsedRequest.payload) {
+                        environment = parsedRequest.payload.environment;
+                    }
+                    if (environment && environment.parameters) {
+                        frameOrigin = environment.parameters.sfdcIframeOrigin;
+                        nonce = environment.parameters.nonce;
+                        servedFromCanvas = environment.parameters.servedFromCanvas;
+                    }
+                }
             }
-          }
         }
-      }
-    } catch(e) {
-      consoleLog("Failed to process API response.");
     }
-  }
-
-  /**
-  * Makes an API call to SFDC domain.
-  */
-  function doPostMessage(params, callback) {
-    if (callback) {
-      params.method = registerCallback(params.method, callback);
-    }
-
-    // add nonce to params
-    params.nonce = nonce;
 
-    // add version
-    params.apiVersion = apiVersion;
+    /**
+     * Process messages received from SFDC by executing callbacks, if any.
+     * The event object contains the following fields:
+     *      method: the API method that was called.
+     *      result: result returned from the call.
+     *      error: an error message if any errors were encountered.
+     */
+     function processPostMessage(event) {
+        var params;
+        try {
+            // Check if call is for entity feed
+            if (isApiMessage(event, ENTITY_FEED_API)) {
+                if (entityFeedApi && entityFeedApi.processPostMessage) {
+                    params = entityFeedApi.processPostMessage(event);
+                }
+                if (!params) {
+                    return;
+                }
+            } else if (isApiMessage(event, INTERACTION_API)) {
+                if (event.origin !== frameOrigin || !frameOrigin) {
+                    // Only trust messages from the adapter frame
+                    return;
+                }
+
+                var message = event.data.replace(INTERACTION_API, ''); // strip off API target
+                params = parseUrlQueryString(message);
+
+                // convert string true/false to boolean for methods that needs to return boolean values.
+                if (params && (params.result === 'true' || params.result === 'false')) {
+                    params.result = params.result === 'true';
+                }
+            } else {
+                // return if postMessage is not targeted to interaction API
+                return;
+            }
 
-    if (frameOrigin) {
-      var targetWindow = servedFromCanvas ? window.top : window.parent;
-      targetWindow.postMessage(INTERACTION_API + buildQueryString(params), frameOrigin);
-    }
-  }
-
-  function registerCallback(method, callback) {
-    if (listeners[method]) {
-      if (callbacks[method]) {
-        callbacks[method].push(callback);
-      } else {
-        callbacks[method] = [callback];
-      }
-    } else {
-      // API methods that are not listeners needs an ID in case they are call multiple times in an async manner.
-      method += '_' + methodId;
-      callbacks[method] = [callback];
-      methodId++;
-    }
-    return method;
-  }
-
-  /**
-  * Utility method to create a query string object.
-  */
-  function parseUrlQueryString(queryString) {
-    var params = {};
-    if (typeof queryString !== 'string') {
-      return params;
+            // execute callbacks registered for the method called
+            for (var methodName in callbacks) {
+                if (callbacks.hasOwnProperty(methodName)) {
+                    if (params.method === methodName) {
+                        for (var i in callbacks[methodName]) {
+                            callbacks[methodName][i](params);
+                        }
+                        if (!listeners[methodName]) {
+                            delete callbacks[methodName];
+                        }
+                    }
+                }
+            }
+        } catch(e) {
+            //consoleLog("Failed to process API response.");
+        }
     }
 
-    if (queryString.charAt(0) === '?') {
-      queryString = queryString.slice(1);
-    }
+    /**
+     * Makes an API call to SFDC domain.
+     */
+    function doPostMessage(params, callback) {
+        if (callback) {
+            params.method = registerCallback(params.method, callback);
+        }
 
-    if (queryString.length === 0) {
-      return params;
-    }
+        // add nonce to params
+        params.nonce = nonce;
 
-    var pairs = queryString.split('&');
-    for (var i = 0; i < pairs.length; i++) {
-      var pair = pairs[i].split('=');
-      params[pair[0]] = !!pair[1] ? decodeURIComponent(pair[1]) : null;
-    }
+        // add version
+        params.apiVersion = apiVersion;
 
-    return params;
-  }
-
-  /**
-  * Utility method to build a query string from key/value object.
-  */
-  function buildQueryString(params) {
-    var qs = '';
-    for (var key in params) {
-      if (params.hasOwnProperty(key)) {
-        qs += key + '=' + encodeURIComponent(params[key]) + '&';
-      }
+        if (frameOrigin) {
+            var targetWindow = servedFromCanvas ? window.top : window.parent;
+            targetWindow.postMessage(INTERACTION_API + buildQueryString(params), frameOrigin);
+        }
     }
-    qs = qs.length > 0 ? qs.substr(0, qs.length-1) : qs;
-    return qs;
-  }
 
-  function consoleLog(message) {
-    if (window.console && console.log) {
-      console.log(message);
+    function registerCallback(method, callback) {
+        if (listeners[method]) {
+            if (callbacks[method]) {
+                callbacks[method].push(callback);
+            } else {
+                callbacks[method] = [callback];
+            }
+        } else {
+            // API methods that are not listeners needs an ID in case they are call multiple times in an async manner.
+            method += '_' + methodId;
+            callbacks[method] = [callback];
+            methodId++;
+        }
+        return method;
     }
-  }
 
-  function jsonStringify(object) {
-    if (typeof Sfdc !== "undefined" && Sfdc.JSON) {
-      return Sfdc.JSON.stringify(object);
-    } else {
-      return JSON.stringify(object);
-    }
-  }
+    /**
+     * Utility method to create a query string object.
+     */
+    function parseUrlQueryString(queryString) {
+        var params = {};
+        if (typeof queryString !== 'string') {
+            return params;
+        }
 
-  function jsonParse(json) {
-    if (typeof Sfdc !== "undefined" && Sfdc.JSON) {
-      return Sfdc.JSON.parse(json);
-    } else {
-      return JSON.parse(json);
-    }
-  }
+        if (queryString.charAt(0) === '?') {
+            queryString = queryString.slice(1);
+        }
 
-  /**
-  * Entity Feed API implementation.
-  */
-  function EntityFeedApi(params) {
-    var that = this;
-    var nonce = null;
-    var apiFrame;
-    var apiOrigin;
-    var readyQueue = [];
-    this.processPostMessage;
+        if (queryString.length === 0) {
+            return params;
+        }
 
-    function processApiResponse(event) {
-      return decodeMessage(event);
-    }
+        var pairs = queryString.split('&');
+        for (var i = 0; i < pairs.length; i++) {
+            var pair = pairs[i].split('=');
+            params[pair[0]] = !!pair[1] ? decodeURIComponent(pair[1]) : null;
+        }
 
-    function decodeMessage(event) {
-      if (isApiMessage(event, ENTITY_FEED_API)) {
-        // Decode message and check authenticity
-        var wrapper = jsonParse(event.data.substr(ENTITY_FEED_API.length));
+        return params;
+    }
 
-        // Check that message source is the API and that nonce is present unless it is an AVAILABLE broadcast
-        if (wrapper.message.fromApi && (wrapper.nonce === nonce || (!wrapper.nonce && wrapper.message.method === AVAILABLE))) {
-          return wrapper.message;
+    /**
+     * Utility method to build a query string from key/value object.
+     */
+    function buildQueryString(params) {
+        var qs = '';
+        for (var key in params) {
+            if (params.hasOwnProperty(key)) {
+                qs += key + '=' + encodeURIComponent(params[key]) + '&';
+            }
         }
-      }
-      return null;
+        qs = qs.length > 0 ? qs.substr(0, qs.length-1) : qs;
+        return qs;
     }
 
-    function doPostMessage(frames, targetOrigin, message, callback, connect) {
-      if (!nonce) {
-        consoleLog("API is not supported in this configuration.");
-        return;
-      }
-
-      // Register callback if any
-      if (callback) {
-        message.method = registerCallback(message.method, callback);
-      }
-
-      // Encode message
-      message.toApi = true;
-      message.version = apiVersion;
-      var messageContainer = {message: message, sourceFrameName: window.name};
-      if (connect) {
-        messageContainer.connect = true;
-      } else {
-        messageContainer.nonce = nonce;
-      }
-      var postData = ENTITY_FEED_API + jsonStringify(messageContainer);
-
-      for (var i = 0, len = frames.length; i < len; i++) {
-        frames[i].postMessage(postData, targetOrigin);
-      }
+    function consoleLog(message) {
+        if (window.console && console.log) {
+            console.log(message);
+        }
     }
 
-    this.callApi = function(message, callback) {
-      if (apiFrame) {
-        doPostMessage([apiFrame], apiOrigin, message, callback, false);
-      } else {
-        readyQueue.push(function() {
-          that.callApi(message, callback);
-        });
-      }
-    };
-
-    this.reloadFrame = function() {
-      if (apiFrame) {
-        if (params.frameName) {
-          that.callApi({method: RELOAD_FRAME, objectId: params.id, frameName: params.frameName});
+    function jsonStringify(object) {
+        if (typeof Sfdc !== "undefined" && Sfdc.JSON) {
+            return Sfdc.JSON.stringify(object);
         } else {
-          location.reload();
-        }
-      }
-    };
-
-    function initialize() {
-      nonce = params.entityFeedNonce;
-
-      that.processPostMessage = function(event) {
-        var message = decodeMessage(event);
-        if (message == null) {
-          return;
-        } else if (message.method === CONNECT && (!params.id || message.objectId === params.id)) { // Inline page frames do not have an id param
-          apiFrame = event.source;
-          apiOrigin = event.origin;
-          that.processPostMessage = processApiResponse;
-
-          for (var i = 0, len = readyQueue.length; i < len; i++) {
-            readyQueue[i]();
-          }
-          readyQueue = null;
-        } else if (message.method === AVAILABLE && message.objectId === params.id) {
-          // Detail page frame in console is notifying that API is available, try to connect
-          doPostMessage([event.source], '*', {method: CONNECT, objectId: params.id}, null, true);
-        }
-      };
-
-      var loadHandler = function() {
-        // Remove load handler
-        if (window.removeEventListener) {
-          window.removeEventListener("load", arguments.callee, false);
-        } else if (window.detachEvent) {
-          window.detachEvent("onload", arguments.callee);
+            return JSON.stringify(object);
         }
+    }
 
-        // Search for api connection point.
-        var frames = [];
-        // Connect to current frame if api is available
-        if (typeof entityFeedPage != "undefined") {
-          frames.push(window);
+    function jsonParse(json) {
+        if (typeof Sfdc !== "undefined" && Sfdc.JSON) {
+            return Sfdc.JSON.parse(json);
         } else {
-          // Attach to parent if VF custom publisher
-          frames.push(window.parent);
-
-          // Attach to siblings for console frames if this is not an inline VF page in the entity feed page
-          if (!params.isCEFP) {
-            for (var parentFrames = window.parent.frames, i = 0, len = parentFrames.length; i < len; i++) {
-              if (parentFrames[i] !== window.self) {
-                frames.push(parentFrames[i]);
-              }
-            }
-          }
-        }
-        // Call frames to connect
-        doPostMessage(frames, '*', {method: CONNECT, objectId: params.id}, null, true);
-      };
-
-      var unloadHandler = function() {
-        // Remove unload handler
-        if (window.removeEventListener) {
-          window.removeEventListener("unload", arguments.callee, false);
-        } else if (window.detachEvent) {
-          window.detachEvent("onunload", arguments.callee);
-        }
-        if (apiFrame) {
-          doPostMessage([apiFrame], apiOrigin, {method: DISCONNECT}, null, false);
-          apiFrame = null;
+            return JSON.parse(json);
         }
-      };
-
-      if (window.addEventListener) {
-        window.addEventListener("load", loadHandler, false);
-        window.addEventListener("unload", unloadHandler, false);
-      } else if (window.attachEvent) {
-        window.attachEvent("onload", loadHandler);
-        window.attachEvent("onunload", unloadHandler);
-      }
-    };
-    initialize();
-  }
-
-  return {
+    }
 
     /**
-    * Initializes API to listen for responses from SFDC.
+    * Entity Feed API implementation.
     */
-    initialize : function() {
-      // set sfdc frame origin and nonce needed to call API methods
-      var params = parseUrlQueryString(location.search);
-
-      parseAuthParams(params);
-
-      // initialize entity feed api
-      if (!entityFeedApi && params.entityFeedNonce && typeof window.postMessage !== "undefined") {
-        entityFeedApi = new EntityFeedApi(params);
-      }
-
-      if (frameOrigin || entityFeedApi) {
-        // attach postMessage event to handler
-        if (window.attachEvent) {
-          window.attachEvent('onmessage', processPostMessage);
-        } else {
-          window.addEventListener('message', processPostMessage, false);
+    function EntityFeedApi(params) {
+        var that = this;
+        var nonce = null;
+        var apiFrame;
+        var apiOrigin;
+        var readyQueue = [];
+        this.processPostMessage;
+
+        function processApiResponse(event) {
+            return decodeMessage(event);
         }
-      }
 
-    },
+        function decodeMessage(event) {
+            if (isApiMessage(event, ENTITY_FEED_API)) {
+                 // Decode message and check authenticity
+                 var wrapper = jsonParse(event.data.substr(ENTITY_FEED_API.length));
 
-    /**
-    * Returns true if is in console, false otherwise
-    */
-    isInConsole : function (callback) {
-      doPostMessage({method:IS_IN_CONSOLE}, callback);
-    },
+                 // Check that message source is the API and that nonce is present unless it is an AVAILABLE broadcast
+                 if (wrapper.message.fromApi && (wrapper.nonce === nonce || (!wrapper.nonce && wrapper.message.method === AVAILABLE))) {
+                     return wrapper.message;
+                 }
+            }
+            return null;
+        }
 
-    /**
-    * Screen pops to targetUrl and returns true if screen pop was successfully called, false otherwise.
-    * Parameter force must be a boolean. Set this value to true to force screen pop, i.e.: to force screen pop on an edit page.
-    */
-    screenPop : function (targetUrl, force, callback) {
-      doPostMessage({method:SCREEN_POP, targetUrl:targetUrl, force:!!force}, callback);
-    },
+        function doPostMessage(frames, targetOrigin, message, callback, connect) {
+            if (!nonce) {
+                consoleLog("API is not supported in this configuration.");
+                return;
+            }
 
-    searchAndGetScreenPopUrl : function (searchParams, queryParams, callType, callback) {
-      doPostMessage({method:SEARCH_AND_GET_SCREEN_POP_URL, searchParams:searchParams, queryParams:queryParams, callType:callType}, callback);
-    },
+            // Register callback if any
+            if (callback) {
+                message.method = registerCallback(message.method, callback);
+            }
 
-    searchAndScreenPop : function (searchParams, queryParams, callType, callback) {
-      doPostMessage({method:SEARCH_AND_SCREEN_POP, searchParams:searchParams, queryParams:queryParams, callType:callType}, callback);
-    },
+            // Encode message
+            message.toApi = true;
+            message.version = apiVersion;
+            var messageContainer = {message: message, sourceFrameName: window.name};
+            if (connect) {
+                messageContainer.connect = true;
+            } else {
+                messageContainer.nonce = nonce;
+            }
+            var postData = ENTITY_FEED_API + jsonStringify(messageContainer);
 
-    /**
-    * Returns the current page info parameters: page Url, object Id (if applicable), object Name (if applicable), object (if applicable) as a JSON String.
-    */
-    getPageInfo : function (callback) {
-      doPostMessage({method:GET_PAGE_INFO}, callback);
-    },
+            for (var i = 0, len = frames.length; i < len; i++) {
+                frames[i].postMessage(postData, targetOrigin);
+            }
+        }
 
-    /**
-    * Registers a callback to be fired when the page gets focused.
-    * When the callback is fired, it returns the current page info parameters: page Url, entity Id (if applicable), entity Name (if applicable) as a JSON String.
-    */
-    onFocus : function (callback) {
-      doPostMessage({method:listeners.onFocus}, callback);
-    },
+        this.callApi = function(message, callback) {
+            if (apiFrame) {
+                doPostMessage([apiFrame], apiOrigin, message, callback, false);
+            } else {
+                readyQueue.push(function() {
+                    that.callApi(message, callback);
+                });
+            }
+        };
+
+        this.reloadFrame = function() {
+            if (apiFrame) {
+                if (params.frameName) {
+                    that.callApi({method: RELOAD_FRAME, objectId: params.id, frameName: params.frameName});
+                } else {
+                    location.reload();
+                }
+            }
+        };       
+        
+        function initialize() {
+            nonce = params.entityFeedNonce;
+
+            that.processPostMessage = function(event) {
+                var message = decodeMessage(event);
+                if (message == null) {
+                    return;
+                } else if (message.method === CONNECT && (!params.id || message.objectId === params.id)) { // Inline page frames do not have an id param
+                    apiFrame = event.source;
+                    apiOrigin = event.origin;
+                    that.processPostMessage = processApiResponse;
+
+                    for (var i = 0, len = readyQueue.length; i < len; i++) {
+                        readyQueue[i]();
+                    }
+                    readyQueue = null;
+                } else if (message.method === AVAILABLE && message.objectId === params.id) {
+                    // Detail page frame in console is notifying that API is available, try to connect
+                    doPostMessage([event.source], '*', {method: CONNECT, objectId: params.id}, null, true);
+                }
+            };
+
+            var loadHandler = function() {
+                // Remove load handler
+                if (window.removeEventListener) {
+                    window.removeEventListener("load", arguments.callee, false);
+                } else if (window.detachEvent) {
+                    window.detachEvent("onload", arguments.callee);
+                }
+
+                // Search for api connection point.
+                var frames = [];
+                // Connect to current frame if api is available
+                if (typeof entityFeedPage != "undefined") {
+                    frames.push(window);
+                } else {
+                    // Attach to parent if VF custom publisher
+                    frames.push(window.parent);
+
+                    // Attach to siblings for console frames if this is not an inline VF page in the entity feed page
+                    if (!params.isCEFP) {
+                    for (var parentFrames = window.parent.frames, i = 0, len = parentFrames.length; i < len; i++) {
+                        if (parentFrames[i] !== window.self) {
+                            frames.push(parentFrames[i]);
+                        }
+                    }
+                }
+                }
+                // Call frames to connect
+                doPostMessage(frames, '*', {method: CONNECT, objectId: params.id}, null, true);
+            };
+
+            var unloadHandler = function() {
+                // Remove unload handler
+                if (window.removeEventListener) {
+                    window.removeEventListener("unload", arguments.callee, false);
+                } else if (window.detachEvent) {
+                    window.detachEvent("onunload", arguments.callee);
+                }
+                if (apiFrame) {
+                    doPostMessage([apiFrame], apiOrigin, {method: DISCONNECT}, null, false);
+                    apiFrame = null;
+                }
+            };
+            
+            if (window.addEventListener) {
+                window.addEventListener("load", loadHandler, false);
+                window.addEventListener("unload", unloadHandler, false);
+            } else if (window.attachEvent) {
+                window.attachEvent("onload", loadHandler);
+                window.attachEvent("onunload", unloadHandler);
+            }
+        };
+        initialize();
+    }
 
-    /**
-    * Save object to database and return true if object was saved successfully, false otherwise.
-    * objectName is the API name of an object
-    * saveParams is a query string representing a key-value pair of object fields to save.
-    * Example:
-    *      // to save a new record
-    *      sforce.interaction.saveLog('Account', 'Name=Acme&Phone=4152125555', callback);
-    *      // to update a new record
-    *      sforce.interaction.saveLog('Account', 'Id=001D000000J6qIX&Name=UpdatedAcmeName', callback);
-    */
-    saveLog : function(objectName, saveParams, callback) {
-      doPostMessage({method:SAVE_LOG, objectName:objectName, saveParams:encodeURIComponent(saveParams)}, callback);
-    },
+    return {
 
-    /**
-    * Runs an Apex method from a class with supplied parameters.
-    */
-    runApex : function(apexClass, methodName, methodParams, callback) {
-      doPostMessage({method:RUN_APEX_QUERY, apexClass:apexClass, methodName:methodName, methodParams:methodParams}, callback);
-    },
+        /**
+         * Initializes API to listen for responses from SFDC.
+         */
+        initialize : function() {
+            // set sfdc frame origin and nonce needed to call API methods
+            var params = parseUrlQueryString(location.search);
 
-    /**
-    * Returns true if widget was successfully shown or hidden, false otherwise.
-    * Parameter value must be a boolean.
-    * Parameter callback must be a function.
-    * If false is returned, an error message is also returned.
-    */
-    setVisible : function (value, callback) {
-      doPostMessage({method:SET_VISIBLE, value:value}, callback);
-    },
+            parseAuthParams(params);
 
-    /**
-    * Returns true if widget is visible, false otherwise.
-    */
-    isVisible : function (callback) {
-      doPostMessage({method:IS_VISIBLE}, callback);
-    },
+            // initialize entity feed api
+            if (!entityFeedApi && params.entityFeedNonce && typeof window.postMessage !== "undefined") {
+                entityFeedApi = new EntityFeedApi(params);
+            }
 
-    /**
-    * Returns true if page refresh is invoked, false otherwise.
-    */
-    refreshPage : function (callback) {
-      doPostMessage({method:REFRESH_PAGE}, callback);
-    },
+            if (frameOrigin || entityFeedApi) {
+                // attach postMessage event to handler
+                if (window.attachEvent) {
+                    window.attachEvent('onmessage', processPostMessage);
+                } else {
+                    window.addEventListener('message', processPostMessage, false);
+                }
+            }
 
-    /**
-    * Returns true if the related list with the given name is refreshed, false otherwise.
-    */
-    refreshRelatedList : function (listName, callback) {
-      doPostMessage({method:REFRESH_RELATED_LIST, listName:listName}, callback);
-    },
-
-    cti: {
-      /**
-      * Gets Call Center Settings.
-      */
-      getCallCenterSettings : function (callback) {
-        doPostMessage({method:GET_CALL_CENTER_SETTINGS}, callback);
-      },
-
-      /**
-      * Gets Softphone Layout.
-      */
-      getSoftphoneLayout : function (callback) {
-        doPostMessage({method:GET_SOFTPHONE_LAYOUT}, callback);
-      },
-
-      /**
-      * Sets softphone height. Height must be greater or equal than zero
-      */
-      setSoftphoneHeight : function (height, callback) {
-        doPostMessage({method:SET_SOFTPHONE_HEIGHT, height:height}, callback);
-      },
-
-      /**
-      * Sets softphone width. Width must be greater or equal than zero.
-      */
-      setSoftphoneWidth : function (width, callback) {
-        doPostMessage({method:SET_SOFTPHONE_WIDTH, width:width}, callback);
-      },
-
-      /**
-      * Enables click to dial.
-      */
-      enableClickToDial : function (callback) {
-        doPostMessage({method:ENABLE_CLICK_TO_DIAL}, callback);
-      },
-
-      /**
-      * Disables click to dial.
-      */
-      disableClickToDial : function (callback) {
-        doPostMessage({method:DISABLE_CLICK_TO_DIAL}, callback);
-      },
-
-      /**
-      * Registers callback to be fired when user clicks to dial.
-      */
-      onClickToDial : function (callback) {
-        doPostMessage({method:listeners.onClickToDial}, callback);
-      },
-
-      /**
-      * Notifies that the adapter url has been successfully loaded.
-      * Should be used if the standby url has been initialized.
-      */
-      notifyInitializationComplete: function() {
-        doPostMessage({method:NOTIFY_INITIALIZATION_COMPLETE});
-      },
-
-      /**
-      * Returns a list of phone numbers from a call center directory.
-      */
-      getDirectoryNumbers : function (isGlobal, callCenterName, callback, resultSetPage, resultSetPageSize) {
-        var params = {method:GET_DIRECTORY_NUMBERS, isGlobal: isGlobal};
-        if (callCenterName) {
-          params.callCenterName = callCenterName;
-        }
-        if (resultSetPage) {
-          params.resultSetPage = resultSetPage;
-        }
-        if (resultSetPageSize) {
-          params.resultSetPageSize = resultSetPageSize;
+        },
+
+        /**
+         * Returns true if is in console, false otherwise
+         */
+        isInConsole : function (callback) {
+             doPostMessage({method:IS_IN_CONSOLE}, callback);
+        },
+
+        /**
+         * Screen pops to targetUrl and returns true if screen pop was successfully called, false otherwise.
+         * Parameter force must be a boolean. Set this value to true to force screen pop, i.e.: to force screen pop on an edit page.
+         */
+        screenPop : function (targetUrl, force, callback) {
+            doPostMessage({method:SCREEN_POP, targetUrl:targetUrl, force:!!force}, callback);
+        },
+
+        searchAndGetScreenPopUrl : function (searchParams, queryParams, callType, callback) {
+            doPostMessage({method:SEARCH_AND_GET_SCREEN_POP_URL, searchParams:searchParams, queryParams:queryParams, callType:callType}, callback);
+        },
+
+        searchAndScreenPop : function (searchParams, queryParams, callType, callback) {
+            doPostMessage({method:SEARCH_AND_SCREEN_POP, searchParams:searchParams, queryParams:queryParams, callType:callType}, callback);
+        },
+
+        /**
+         * Returns the current page info parameters: page Url, object Id (if applicable), object Name (if applicable), object (if applicable) as a JSON String.
+         */
+        getPageInfo : function (callback) {
+            doPostMessage({method:GET_PAGE_INFO}, callback);
+        },
+
+        /**
+         * Registers a callback to be fired when the page gets focused.
+         * When the callback is fired, it returns the current page info parameters: page Url, entity Id (if applicable), entity Name (if applicable) as a JSON String.
+         */
+        onFocus : function (callback) {
+            doPostMessage({method:listeners.onFocus}, callback);
+        },
+
+        /**
+         * Save object to database and return true if object was saved successfully, false otherwise.
+         * objectName is the API name of an object
+         * saveParams is a query string representing a key-value pair of object fields to save.
+         * Example:
+         *      // to save a new record
+         *      sforce.interaction.saveLog('Account', 'Name=Acme&Phone=4152125555', callback);
+         *      // to update a new record
+         *      sforce.interaction.saveLog('Account', 'Id=001D000000J6qIX&Name=UpdatedAcmeName', callback);
+         */
+        saveLog : function(objectName, saveParams, callback) {
+            doPostMessage({method:SAVE_LOG, objectName:objectName, saveParams:encodeURIComponent(saveParams)}, callback);
+        },
+
+        /**
+         * Runs an Apex method from a class with supplied parameters.
+         */
+        runApex : function(apexClass, methodName, methodParams, callback) {
+            doPostMessage({method:RUN_APEX_QUERY, apexClass:apexClass, methodName:methodName, methodParams:methodParams}, callback);
+        },
+
+        /**
+         * Returns true if widget was successfully shown or hidden, false otherwise.
+         * Parameter value must be a boolean.
+         * Parameter callback must be a function.
+         * If false is returned, an error message is also returned.
+         */
+        setVisible : function (value, callback) {
+            doPostMessage({method:SET_VISIBLE, value:value}, callback);
+        },
+
+        /**
+         * Returns true if widget is visible, false otherwise.
+         */
+        isVisible : function (callback) {
+            doPostMessage({method:IS_VISIBLE}, callback);
+        },
+
+        /**
+         * Returns true if page refresh is invoked, false otherwise.
+         */
+        refreshPage : function (callback) {
+            doPostMessage({method:REFRESH_PAGE}, callback);
+        },
+
+        /**
+         * Returns true if the related list with the given name is refreshed, false otherwise.
+         */
+        refreshRelatedList : function (listName, callback) {
+            doPostMessage({method:REFRESH_RELATED_LIST, listName:listName}, callback);
+        },
+
+        cti: {
+            /**
+             * Gets Call Center Settings.
+             */
+            getCallCenterSettings : function (callback) {
+                doPostMessage({method:GET_CALL_CENTER_SETTINGS}, callback);
+            },
+
+            /**
+             * Gets Softphone Layout.
+             */
+            getSoftphoneLayout : function (callback) {
+                doPostMessage({method:GET_SOFTPHONE_LAYOUT}, callback);
+            },
+
+            /**
+             * Sets softphone height. Height must be greater or equal than zero
+             */
+            setSoftphoneHeight : function (height, callback) {
+                doPostMessage({method:SET_SOFTPHONE_HEIGHT, height:height}, callback);
+            },
+
+            /**
+             * Sets softphone width. Width must be greater or equal than zero.
+             */
+            setSoftphoneWidth : function (width, callback) {
+                doPostMessage({method:SET_SOFTPHONE_WIDTH, width:width}, callback);
+            },
+
+            /**
+             * Enables click to dial.
+             */
+            enableClickToDial : function (callback) {
+                doPostMessage({method:ENABLE_CLICK_TO_DIAL}, callback);
+            },
+
+            /**
+             * Disables click to dial.
+             */
+            disableClickToDial : function (callback) {
+                doPostMessage({method:DISABLE_CLICK_TO_DIAL}, callback);
+            },
+
+            /**
+             * Registers callback to be fired when user clicks to dial.
+             */
+            onClickToDial : function (callback) {
+                doPostMessage({method:listeners.onClickToDial}, callback);
+            },
+
+            /**
+             * Notifies that the adapter url has been successfully loaded.
+             * Should be used if the standby url has been initialized.
+             */
+            notifyInitializationComplete: function() {
+                doPostMessage({method:NOTIFY_INITIALIZATION_COMPLETE});
+            },
+
+            /**
+             * Returns a list of phone numbers from a call center directory.
+             */
+            getDirectoryNumbers : function (isGlobal, callCenterName, callback, resultSetPage, resultSetPageSize) {
+                var params = {method:GET_DIRECTORY_NUMBERS, isGlobal: isGlobal};
+                if (callCenterName) {
+                    params.callCenterName = callCenterName;
+                }
+                if (resultSetPage) {
+                    params.resultSetPage = resultSetPage;
+                }
+                if (resultSetPageSize) {
+                    params.resultSetPageSize = resultSetPageSize;
+                }
+                doPostMessage(params, callback);
+            }
+        },
+
+        /**
+         * Public API for Entity feed
+         */
+        entityFeed: {
+            /**
+             * Notifies that the object has been updated and its display need to be refreshed
+             */
+            refreshObject : function(objectId, refreshFields, refreshRelatedLists, refreshFeed, callback) {
+                entityFeedApi && entityFeedApi.callApi({method: REFRESH_OBJECT, objectId: objectId || params.id, refreshFields: refreshFields, refreshRelatedLists: refreshRelatedLists, refreshFeed: refreshFeed}, callback);
+            },
+
+            /**
+             * Registers a callback to be fired when the object has been updated.
+             */
+            onObjectUpdate : function(callback) {
+                entityFeedApi && entityFeedApi.callApi({method: listeners.onObjectUpdate}, callback);
+            },
+
+            /**
+             * Reloads the frame containing this page
+             */
+            reloadFrame : function() {
+                entityFeedApi && entityFeedApi.reloadFrame();
+            }
         }
-        doPostMessage(params, callback);
-      }
-    },
-
-    /**
-    * Public API for Entity feed
-    */
-    entityFeed: {
-      /**
-      * Notifies that the object has been updated and its display need to be refreshed
-      */
-      refreshObject : function(objectId, refreshFields, refreshRelatedLists, refreshFeed, callback) {
-        entityFeedApi && entityFeedApi.callApi({method: REFRESH_OBJECT, objectId: objectId || params.id, refreshFields: refreshFields, refreshRelatedLists: refreshRelatedLists, refreshFeed: refreshFeed}, callback);
-      },
-
-      /**
-      * Registers a callback to be fired when the object has been updated.
-      */
-      onObjectUpdate : function(callback) {
-        entityFeedApi && entityFeedApi.callApi({method: listeners.onObjectUpdate}, callback);
-      },
-
-      /**
-      * Reloads the frame containing this page
-      */
-      reloadFrame : function() {
-        entityFeedApi && entityFeedApi.reloadFrame();
-      }
-    }
-  };
+    };
 })();
 
-sforce.interaction.initialize();
+sforce.interaction.initialize();
\ No newline at end of file
diff --git a/js/salesforce/lightning.js b/js/salesforce/lightning.js
index 95858ef..c282052 100644
--- a/js/salesforce/lightning.js
+++ b/js/salesforce/lightning.js
@@ -1 +1 @@
\ No newline at end of file
diff --git a/vendor.84f81d8f.css b/vendor.84f81d8f.css
new file mode 100644
index 0000000..b1593ac
--- /dev/null
+++ b/vendor.84f81d8f.css
@@ -0,0 +1,6 @@
+/*!
+ * AngularJS Material Design
+ * https://github.com/angular/material
+ * @license MIT
+ * v1.1.19
\ No newline at end of file
diff --git a/vendor.b5ccb327.css b/vendor.b5ccb327.css
deleted file mode 100644
index a66a3c6..0000000
--- a/vendor.b5ccb327.css
+++ /dev/null
@@ -1,6 +0,0 @@
-/*!
- * AngularJS Material Design
- * https://github.com/angular/material
- * @license MIT
- * v1.1.4
\ No newline at end of file
diff --git a/vendor.c39ee70d.js b/vendor.c39ee70d.js
deleted file mode 100644
index cc75974..0000000
--- a/vendor.c39ee70d.js
+++ /dev/null
@@ -1 +0,0 @@
\ No newline at end of file
diff --git a/vendor.d76e66a8.js b/vendor.d76e66a8.js
new file mode 100644
index 0000000..148b3a3
--- /dev/null
+++ b/vendor.d76e66a8.js
@@ -0,0 +1 @@
\ No newline at end of file