From 97ed66d41bd18cef485fefa450c5386935475599 Mon Sep 17 00:00:00 2001 From: trogdoro Date: Fri, 27 Sep 2013 17:33:36 -0700 Subject: [PATCH] Moved phone call out of browser, made screenpop come from websocket, began updating after refresh. Made view fit in salesforce page. --- client-acd.rb | 583 +++++++++++++++++++----------------- public/css/dialer.css | 19 +- public/scripts/softphone.js | 560 +++++++++++++++++----------------- views/index.erb | 16 +- 4 files changed, 597 insertions(+), 581 deletions(-) diff --git a/client-acd.rb b/client-acd.rb index 2d9002a..44d69f7 100644 --- a/client-acd.rb +++ b/client-acd.rb @@ -8,16 +8,13 @@ require 'pp' - - config_file 'config_file.yml' set :server, 'thin' set :sockets, [] - -disable :protection +disable :protection ############ CONFIG ########################### @@ -57,7 +54,7 @@ queueid = q.sid puts "found #{queueid} for #{q.friendly_name}" end -end +end unless queueid #didn't find queue, create it @@ -70,86 +67,72 @@ queue1 = @account.queues.get(queueid) -#puts "queue wait time: #{queue.average_wait_time}" -userlist = Hash.new #all users, in memory -calls = Hash.new # tracked calls, in memory +userlist = {} # All users, in memory +calls = {} # Tracked calls, in memory - -activeusers = 0 +# activeusers = 0 # Calculated from userlist $sum = 0 #Starting ACD processing thread #todo - add exception handling for threads -Thread.new do +Thread.new do while true do - sleep 1 - $sum += 1 - - #print out users - puts "printing user list.." - userlist.each do |key, value| - puts "#{key} = #{value}" - activeusers += 1 if value.first == "Ready" + sleep 1 + $sum += 1 + + #print out users + puts "printing user list.." + userlist.each do |name, hash| + string = hash.map{|k, v| "#{k.inspect}=>#{v.is_a?(SinatraWebsocket::Connection) ? '' : v.inspect}"}.join(", ") + puts " - #{name}: #{string}" end - topmember = 0 - callerinqueue = false - qsize = 0 - @members = queue1.members - @members.list.each do |m| - qsize +=1 - puts "Sid: #{m.call_sid}" - puts "Date Enqueue: #{m.date_enqueued}" - puts "Wait_Time: #{m.wait_time} " - puts "Position: #{m.position}" - if topmember == 0 - topmember = m - callerinqueue = true - end + first = 0 + callerinqueue = false + qsize = 0 + @members = queue1.members + @members.list.each do |m| + qsize +=1 + puts "Sid: #{m.call_sid}" + puts "Date Enqueue: #{m.date_enqueued}" + puts "Wait_Time: #{m.wait_time} " + puts "Position: #{m.position}" + if first == 0 + first = m + callerinqueue = true + end + end - end - + puts "qsize = #{qsize}" + + if callerinqueue #only check for route if there is a queue member + bestclient = getlongestidle(userlist) + if bestclient == "NoReadyAgents" + #nobody to take the call... should redirect to a queue here + puts "No ready agents.. keeq waiting...." + else + puts "Found best client! #{bestclient}" + first.dequeue(dqueueurl) + #get clients phone number, if any + end + end + # Send updated counts to each client... + readycount = userlist.select{|key, hash| hash[:status] == "Ready"}.length - puts "qsize = #{qsize}" + settings.sockets.each{|s| + msg = {:queuesize => qsize, :readyagents => readycount}.to_json + puts "sending #{msg}" + s.send(msg) + } - #get ready users (need an object!) - readyusers = userlist.clone - readyusers.keep_if {|key, value| - value[0] == "Ready" - } - - readycount = readyusers.count.to_i.to_s || 0 - - - if callerinqueue #only check for route if there is a queue member - bestclient = getlongestidle(userlist) - if bestclient == "NoReadyAgents" - #nobody to take the call... should redirect to a queue here - puts "No ready agents.. keeq waiting...." - else - puts "Found best client! #{bestclient}" - topmember.dequeue(dqueueurl) - #get clients phone number, if any - end - end - - - - settings.sockets.each{|s| - #msg = '{"queuesize": ' + qsize + ', "readyagents": ' + readycount + '}' - msg = { :queuesize => qsize, :readyagents => readycount}.to_json - #msg.to_json - puts "sending #{msg}" - s.send(msg) - } - + # TODO: guard against thread death - maybe wrap in rescue? - #puts "average queue wait time: #{queue1.average_wait_time}" - #puts "queue depth = #{queue.depth}" - puts "run = #{$sum} #{Time.now}" + #puts "average queue wait time: #{queue1.average_wait_time}" + #puts "queue depth = #{queue.depth}" + puts "run = #{$sum} #{Time.now}" end end @@ -162,11 +145,11 @@ end capability = Twilio::Util::Capability.new account_sid, auth_token # Create an application sid at twilio.com/user/account/apps and use it here - capability.allow_client_outgoing app_id + capability.allow_client_outgoing app_id capability.allow_client_incoming client_name token = capability.generate return token -end +end get '/' do #for hmtl client @@ -177,285 +160,312 @@ erb :index, :locals => {} end - -get '/websocket' do + +get '/websocket' do request.websocket do |ws| ws.onopen do - puts ws.object_id + puts ws.object_id querystring = ws.request["query"] - #querry should be something like wsclient=coppenheimerATvccsystemsDOTcom - - clientname = querystring.split(/\=/)[1] - - if userlist.has_key?(clientname) - currentclientcount = userlist[clientname][2] || 0 - newclientcount = currentclientcount + 1 - else + # query should be something like wsclient=coppenheimerATvccsystemsDOTcom + + clientname = params[:clientname] + + user = userlist[clientname] + + if user + user[:status] = " " + user[:activity] = Time.now.to_f + user[:count] ||= 0; user[:count] += 1 + user[:socket] = ws + # Phone number stays the same + else #user didn't exist, create them - newclientcount = 1 - end - userlist[clientname] = [" ", Time.now.to_f,newclientcount ] + userlist[clientname] = {:status=>" ", :activity=>Time.now.to_f, :count=>1, :socket=>ws} + end + + # Store websocket here redundantly temporarily + settings.sockets << ws - + + number = userlist[clientname][:phone] + + # Each time we open the socket, give it the field values to repaint + msg = {:do=>'paint', :agent_number_entry=>number}.to_json + + ws.send msg + end + ws.onmessage do |msg| puts "got websocket message" + # Broadcasts out to all - not doing this, right? EM.next_tick { settings.sockets.each{|s| s.send(msg) } } end + ws.onclose do - warn("wetbsocket closed") - querystring = ws.request["query"] - clientname = querystring.split(/\=/)[1] - - settings.sockets.delete(ws) - - currentclientcount = userlist[clientname][2] - newclientcount = currentclientcount - 1 - userlist[clientname][2] = newclientcount - - #if not more clients are registered, set to not ready - if newclientcount < 1 - userlist[clientname][0] = "LOGGEDOUT" - userlist[clientname][1] = Time.now - end + + begin + querystring = ws.request["query"] + + clientname = params[:clientname] + + settings.sockets.delete(ws) + + user = userlist[clientname] + user[:count] -= 1 + + # if not more clients are registered, set to not ready + if user[:count] < 1 + user[:status] = "LOGGEDOUT" + user[:activity] = Time.now + end #remove client count + rescue + puts $!, $@ + end + end end - -end +end -#for incoming voice calls.. not for client to client routing (move that elsewhere) +# +# For incoming voice calls, from twilio... +# Not for client to client routing (move that elsewhere). +# post '/voice' do - puts "params = #{params}" - - number = params[:PhoneNumber] - sid = params[:CallSid] - queue_name = params[:queue_name] - requestor_name = params[:requestor_name] - message = params[:message] - - - - callerid = params[:Caller] - #if special parameter requesting_party is passed, make it the caller id - if params[:requesting_party] - callerid = params[:requesting_party] - elsif params[:Direction] == "outbound-api" #special case when call queued from a outbound leg - callerid = params[:To] - end + puts "params = #{params}" - #capture call data - if calls[sid] - puts "found sid #{sid} = #{calls[sid]}" - else - puts "creating sid #{sid}" - calls[sid] = {} - calls[sid][:queue_name] = queue_name - calls[sid][:requestor_name] = requestor_name - calls[sid][:message] = message - end - - - bestclient = getlongestidle(userlist) - if bestclient == "NoReadyAgents" - #nobody to take the call... should redirect to a queue here - dialqueue = qname - else - puts "Found best client! #{bestclient}" - client_name = bestclient - #get clients phone number, if any - end + number = params[:PhoneNumber] + sid = params[:CallSid] + queue_name = params[:queue_name] + requestor_name = params[:requestor_name] + message = params[:message] + + callerid = params[:Caller] + #if special parameter requesting_party is passed, make it the caller id + if params[:requesting_party] + callerid = params[:requesting_party] + elsif params[:Direction] == "outbound-api" #special case when call queued from a outbound leg + callerid = params[:To] + end + + #capture call data + if calls[sid] + puts "found sid #{sid} = #{calls[sid]}" + else + puts "creating sid #{sid}" + calls[sid] = {} + calls[sid][:queue_name] = queue_name + calls[sid][:requestor_name] = requestor_name + calls[sid][:message] = message + end - #if no client is choosen, route to queue + bestclient = getlongestidle(userlist) + if bestclient == "NoReadyAgents" + #nobody to take the call... should redirect to a queue here + dialqueue = qname + else + puts "Found best client! #{bestclient[0]}" + client_name = bestclient[0] + #get clients phone number, if any + end - response = Twilio::TwiML::Response.new do |r| + #if no client is choosen, route to queue - if dialqueue #no agents avalible - r.Say("Please wait for the next availible agent ") - r.Enqueue(dialqueue) - #r.Redirect('/wait') - else #send to best agent - r.Dial(:timeout=>"10", :action=>"/handleDialCallStatus", :callerId => callerid) do |d| - puts "dialing client #{client_name}" - calls[sid][:agent] = client_name - calls[sid][:status] = "Ringing" - d.Client client_name - - end - end + + + response = Twilio::TwiML::Response.new do |r| + + if dialqueue #no agents avalible + r.Say("Please wait for the next availible agent ") + r.Enqueue(dialqueue) + #r.Redirect('/wait') + else #send to best agent + r.Dial(:timeout=>"10", :action=>"/handleDialCallStatus", :callerId => callerid) do |d| + + calls[sid][:agent] = client_name + calls[sid][:status] = "Ringing" + + puts "dialing client #{client_name}" + + # Send websocket message to client to do screen pop... + + # Why socket going away! + + socket = bestclient[1][:socket] + + msg = {:do=>"screenpop", :From=>params[:From]}.to_json + + puts "sending #{msg}" + socket.send(msg) + + # Respond to twilio to call the agent's number... + + d.Number bestclient[1][:phone] + + end end - puts "response text = #{response.text}" - response.text + end + puts "response text = #{response.text}" + response.text end +# Twillio calls this when hung up + post '/handleDialCallStatus' do puts "HANDLEDIALCALLSTATUS params = #{params}" #todo - log this info? #rules - if you dialed a client, and the response is "no-answer", set client to not ready. - # + # sid = params[:CallSid] - response = Twilio::TwiML::Response.new do |r| + response = Twilio::TwiML::Response.new do |r| - #consider logging all of this? - if params['DialCallStatus'] == "no-answer" - #if a call got here when ringing a client, they didn't answer. set values - calls[sid][:status] = "Missed" - agent = calls[sid][:agent] + #consider logging all of this? + if params['DialCallStatus'] == "no-answer" + #if a call got here when ringing a client, they didn't answer. set values + calls[sid][:status] = "Missed" + agent = calls[sid][:agent] - puts calls # {"CAcb90adcb68b6e51b96d8216d105ff645"=>{:client=>"defaultclient", :status=>"Ringing", "status"=>"Missed"}} - # now, since this client missed a call, set him to paused, and send a websocket message? - userlist[agent][0] = "Missed" - puts "user list = #{userlist}" + puts calls # {"CAcb90adcb68b6e51b96d8216d105ff645"=>{:client=>"defaultclient", :status=>"Ringing", "status"=>"Missed"}} + # now, since this client missed a call, set him to paused, and send a websocket message? + userlist[agent][:status] = "Missed" + puts "user list = #{userlist}" - r.Redirect('/voice') - else - r.Hangup - end + r.Redirect('/voice') + else + r.Hangup + end end puts "response.text = #{response.text}" response.text - end - - post '/dial' do - number = params[:PhoneNumber] - client_name = params[:client] - if client_name.nil? - client_name = default_client - end - response = Twilio::TwiML::Response.new do |r| - # outboudn dialing (from client) must have a :callerId - - r.Dial :callerId => caller_id do |d| - # Test to see if the PhoneNumber is a number, or a Client ID. In - # this case, we detect a Client ID by the presence of non-numbers - # in the PhoneNumber parameter. - puts "for callerid: #{caller_id}" - if /^[\d\+\-\(\) ]+$/.match(number) - d.Number(CGI::escapeHTML number) - puts "matched number!" - else - d.Client client_name - puts "matched cliennt" - end - end - end - puts response.text - response.text + number = params[:PhoneNumber] + client_name = params[:client] + if client_name.nil? + client_name = default_client + end + response = Twilio::TwiML::Response.new do |r| + # outboudn dialing (from client) must have a :callerId + + r.Dial :callerId => caller_id do |d| + # Test to see if the PhoneNumber is a number, or a Client ID. In + # this case, we detect a Client ID by the presence of non-numbers + # in the PhoneNumber parameter. + puts "for callerid: #{caller_id}" + if /^[\d\+\-\(\) ]+$/.match(number) + d.Number(CGI::escapeHTML number) + puts "matched number!" + else + d.Client client_name + puts "matched cliennt" + end + end + end + puts response.text + response.text end ### ACD stuff - for tracking agent state get '/track' do - activeusers = 0 - from = params[:from] - status = params[:status] - currentclientcount = 0 + from = params[:from] + status = params[:status] + agent_number = params[:agent_number] + currentclientcount = 0 + #check if this guy is already registered as a client from another webpage + if userlist.has_key?(from) + currentclientcount = userlist[from][:count] + end - #check if this guy is already registered as a client from another webpage - if userlist.has_key?(from) - currentclientcount = userlist[from][2] - end + #update the userlist{} status.. this is now his status + puts "For client #{from} retrieved currentclientcount = #{currentclientcount} and setting status to #{status}" - #update the userlist{} status.. this is now his status - puts "For client #{from} retrieved currentclientcount = #{currentclientcount} and setting status to #{status}" + # Pass socket along (must already by there) + socket = userlist[from][:socket] - userlist[from] = [status, Time.now.to_f, currentclientcount ] + userlist[from] = {:status=>status, :activity=>Time.now.to_f, :count=>currentclientcount, :socket=>socket, :phone=>agent_number} - - activeusers = 0 - userlist.each do |key, value| - puts "#{key} = #{value}" - activeusers += 1 if value.first == "Ready" - end - - usercount = userlist.length + userlist.each do |name, hash| + string = hash.map{|k, v| "#{k.inspect}=>#{v.is_a?(SinatraWebsocket::Connection) ? '' : v.inspect}"}.join(", ") + puts "::: #{name} = #{string}" + end + + readycount = userlist.select{|key, hash| hash[:status] == "Ready"} + + p "Number of users #{userlist.length}, number of readyusers = #{readycount}, currentclientcount = #{currentclientcount}" - p "Number of users #{usercount}, number of readyusers = #{activeusers}, currentclientcount = #{currentclientcount}" - end get '/status' do - #returns status for a particular client - from = params[:from] - p "from #{from}" - #grab the first element in the status array for this user ie, [\"Ready\", 1376194403.9692101]" - - if userlist.has_key?(from) - status = userlist[from].first - p "status = #{status}" - else - status ="no status" - end - return status + #returns status for a particular client + from = params[:from] + p "from #{from}" + #grab the first element in the status array for this user ie, [\"Ready\", 1376194403.9692101]" + + if userlist.has_key?(from) + status = userlist[from][:status] + p "status = #{status}" + else + status ="no status" + end + return status end -def getlongestidle (userlist) - #gets all "Ready" agents, sorts by longest idle - - readyusers = userlist.clone #don't - - readyusers.keep_if {|key, value| - value[0] == "Ready" - } +def getlongestidle(userlist) - if readyusers.count < 1 - return "NoReadyAgents" - end + readyusers = userlist.select{|key, hash| hash[:status] == "Ready"} - sorted = readyusers.sort_by { |x| - x[1] - #sorts by idle time, {"sam" => ["Ready", 124444]} - } + if readyusers.count < 1 + return "NoReadyAgents" + end - longestidleagent = sorted.first[0] #first element of first array is name of user - return longestidleagent + sorted = readyusers.sort_by{|name, hash| + hash["activity"] + } + sorted.last # Lowest time of last activity is longest idle end - -get '/calldata' do - #sid will be a client call, need to get parent for attached data - sid = params[:CallSid] - - @client = Twilio::REST::Client.new(account_sid, auth_token) - @call = @client.account.calls.get(sid) +get '/calldata' do + #sid will be a client call, need to get parent for attached data + sid = params[:CallSid] + @client = Twilio::REST::Client.new(account_sid, auth_token) + @call = @client.account.calls.get(sid) - parentsid = @call.parent_call_sid - puts "parent sid for #{sid} = #{parentsid}" + parentsid = @call.parent_call_sid + puts "parent sid for #{sid} = #{parentsid}" - calldata = calls[@call.parent_call_sid] + calldata = calls[@call.parent_call_sid] - #puts "calls sid = #{calls[sid]}" - + #puts "calls sid = #{calls[sid]}" - if calls[parentsid] - msg = { :agentname => calldata[:agent], :agentstatus => calldata[:status], :queue_name => calldata[:queue_name], :requestor_name => calldata[:requestor_name], :message => calldata[:message]}.to_json - else - msg = "NoSID" - end + if calls[parentsid] + msg = { :agentname => calldata[:agent], :agentstatus => calldata[:status], :queue_name => calldata[:queue_name], :requestor_name => calldata[:requestor_name], :message => calldata[:message]}.to_json + else + msg = "NoSID" + end - return msg + return msg -end +end ## requests from mobile application to initiate PSTN callback post '/mobile-call-request' do @@ -466,17 +476,17 @@ def getlongestidle (userlist) requestor_name = params[:name] message = params[:message] - -url = request.base_url -unless request.base_url.include? 'localhost' - url = url.sub('http', 'https') -end -puts "mobile call request url = #{url}" + + url = request.base_url + unless request.base_url.include? 'localhost' + url = url.sub('http', 'https') + end + puts "mobile call request url = #{url}" @client = Twilio::REST::Client.new(account_sid, auth_token) # outbound PSTN call to requesting party. They will be call screened before being connected. @client.account.calls.create(:from => caller_id, :to => requesting_party, :url => URI.escape("#{url}/connect-mobile-call-to-agent?queue_name=#{queue_name}&requestor_name=#{requestor_name}&requesting_party=#{requesting_party}&message=#{message}")) - + return "" @@ -508,4 +518,21 @@ def getlongestidle (userlist) end +get '/clicktodial' do + params[:to] + + @client = Twilio::REST::Client.new(account_sid, auth_token) + + url = request.base_url + @client.account.calls.create(:from=>params[:to], :to=>params[:from], :url => URI.escape("#{url}/clicktodialcallback")) +end + +get '/clicktodialcallback' do + r.Dial(:timeout=>"10", :action=>"/handleDialCallStatus", :callerId => callerid) do |d| + d.Number bestclient[1][:number] + end + +end + + diff --git a/public/css/dialer.css b/public/css/dialer.css index 277cc60..9a90762 100644 --- a/public/css/dialer.css +++ b/public/css/dialer.css @@ -4,7 +4,7 @@ body { background-color: white; } div#softphone { - width: 220px; + width: 182px; padding: 7px; } .button-style { @@ -32,6 +32,7 @@ div#agent-status-controls button:hover { div#agent-status { text-align: center; padding: 20px 0 20px 0; + margin: 0 0 6px 0; -webkit-border-radius: 7px; -moz-border-radius: 7px; -ms-border-radius: 7px; @@ -55,15 +56,15 @@ div#agent-status.on-call { div#agent-status p { color: #FFF; font-weight: 300; - font-size: 1.0em; + font-size: 0.8em; } div.divider { border-bottom: 1px dotted #ccc; margin: 8px 0 8px 0; } -div#number-entry input { +div#number-entry input, div#agent-number-entry input { border: 1px solid #ccc; - width: 208px; + width: 171px; padding: 5px; margin: 0px; } @@ -76,10 +77,10 @@ div#dialer { color: #3B3B3B; cursor: pointer; display: inline-block; - height: 20px; - padding-top: 6px; - margin-bottom: 4px; - width: 68px; + height: 17px; + padding-top: 5px; + margin-bottom: 5px; + width: 54px; background-image: linear-gradient(bottom, #f0f0f0 20%, #f5f5f5 72%); background-image: -o-linear-gradient(bottom, #f0f0f0 20%, #f5f5f5 72%); background-image: -moz-linear-gradient(bottom, #f0f0f0 20%, #f5f5f5 72%); @@ -105,7 +106,7 @@ div#action-buttons { } div#action-buttons button { height: 100%; - width: 69px; + width: 180px; margin-left: 1px; display: inline-block; background-color: #fff; diff --git a/public/scripts/softphone.js b/public/scripts/softphone.js index d546084..c843986 100644 --- a/public/scripts/softphone.js +++ b/public/scripts/softphone.js @@ -1,374 +1,362 @@ // Page loaded $(function() { - // ** Application container ** // - window.SP = {} - - // Global state - SP.state = {}; - SP.state.callNumber = null; - SP.username = "default_client"; - - - SP.functions = {}; - - - SP.functions.getSFDCUserInfo = function () { - - var callback = function (response) { - if (response.result) { - console.log("result = " + response.result); - var useresult = response.result; - useresult = useresult.replace("@", "AT"); - useresult = useresult.replace(".", "DOT"); - SP.username = useresult; - - } else { - console.log("error = " + response.error); - - } - - //TODO: need a way to get here when not inside Salesforce - this is only called in the runApex callback. - $.get("/token", {"client":SP.username}, function (token) { - //alert("got token=" + token); - Twilio.Device.setup(token, {debug: true}); - }); - - SP.functions.startWebSocket(); - }; - - //how can we tell if sforce works before calling this? - sforce.interaction.runApex('UserInfo', 'getUserName', '' ,callback); - - } + // ** Application container ** // + window.SP = {} + // Global state + SP.state = {}; + SP.state.callNumber = null; + SP.username = "default_client"; - //1. run sfdc code - - - // ** UI Widgets ** // + SP.functions = {}; - // Hook up numpad to input field - $("div.number").bind('click',function(){ - $("#number-entry > input").val($("#number-entry > input").val()+$(this).attr('Value')); - }); - // Hide caller info - SP.functions.hideCallData = function() { - $("#call-data").hide(); - } - SP.functions.hideCallData(); - - // Show caller info - SP.functions.showCallData = function(callData) { - $("#call-data > ul").hide(); - $(".caller-name").text(callData.callerName); - $(".caller-number").text(callData.callerNumber); - $(".caller-queue").text(callData.callerQueue); - $(".caller-message").text(callData.callerMessage); - - if (callData.callerName) { - $("#call-data > ul.name").show(); - } + SP.functions.getSFDCUserInfo = function () { - if (callData.callerNumber) { - $("#call-data > ul.phone_number").show(); - } + var callback = function (response) { + if (response.result) { + console.log("result = " + response.result); + var useresult = response.result; + useresult = useresult.replace("@", "AT"); + useresult = useresult.replace(".", "DOT"); + SP.username = useresult; - if (callData.callerQueue) { - $("#call-data > ul.queue").show(); + } else { + console.log("error = " + response.error); } - if (callData.callerMessage) { - $("#call-data > ul.message").show(); - } + SP.functions.startWebSocket(); + }; + + //how can we tell if sforce works before calling this? + sforce.interaction.runApex('UserInfo', 'getUserName', '' ,callback); + + } + - $("#call-data").slideDown(400); + //1. run sfdc code + + + + // ** UI Widgets ** // + + // Hook up numpad to input field + $("div.number").bind('click',function(){ + $("#number-entry > input").val($("#number-entry > input").val()+$(this).attr('Value')); + }); + + // Hide caller info + SP.functions.hideCallData = function() { + $("#call-data").hide(); + } + SP.functions.hideCallData(); + + // Show caller info + SP.functions.showCallData = function(callData) { + $("#call-data > ul").hide(); + $(".caller-name").text(callData.callerName); + $(".caller-number").text(callData.callerNumber); + $(".caller-queue").text(callData.callerQueue); + $(".caller-message").text(callData.callerMessage); + + if (callData.callerName) { + $("#call-data > ul.name").show(); } - // Attach answer button to an incoming connection object - SP.functions.attachAnswerButton = function(conn) { - $("#action-buttons > button.answer").click(function() { - conn.accept(); - }).removeClass('inactive').addClass("active"); + if (callData.callerNumber) { + $("#call-data > ul.phone_number").show(); } - SP.functions.detachAnswerButton = function() { - $("#action-buttons > button.answer").unbind().removeClass('active').addClass("inactive"); + if (callData.callerQueue) { + $("#call-data > ul.queue").show(); } - SP.functions.updateAgentStatusText = function(statusCategory, statusText) { + if (callData.callerMessage) { + $("#call-data > ul.message").show(); + } - if (statusCategory == "ready") { - $("#agent-status").removeClass(); - $("#agent-status").addClass("ready"); - } + $("#call-data").slideDown(400); + } - if (statusCategory == "notReady") { - $("#agent-status").removeClass(); - $("#agent-status").addClass("not-ready"); - } + // Attach answer button to an incoming connection object + SP.functions.attachAnswerButton = function(conn) { + $("#action-buttons > button.answer").click(function() { + conn.accept(); + }).removeClass('inactive').addClass("active"); + } - if (statusCategory == "onCall") { - $("#agent-status").removeClass(); - $("#agent-status").addClass("on-call"); - } + SP.functions.detachAnswerButton = function() { + $("#action-buttons > button.answer").unbind().removeClass('active').addClass("inactive"); + } + + SP.functions.updateAgentStatusText = function(statusCategory, statusText) { + + if (statusCategory == "ready") { + $("#agent-status").removeClass(); + $("#agent-status").addClass("ready"); + } - $("#agent-status > p").text(statusText); + if (statusCategory == "notReady") { + $("#agent-status").removeClass(); + $("#agent-status").addClass("not-ready"); } - // Call button will make an outbound call (click to dial) to the number entered - $("#action-buttons > button.call").click( function( ) { - params = {"PhoneNumber": $("#number-entry > input").val()}; - Twilio.Device.connect(params); - }); + if (statusCategory == "onCall") { + $("#agent-status").removeClass(); + $("#agent-status").addClass("on-call"); + } - // Hang up button will hang up any active calls - $("#action-buttons > button.hangup").click( function( ) { - Twilio.Device.disconnectAll(); - }); + $("#agent-status > p").text(statusText); + } - // Wire the ready / not ready buttons up to the server-side status change functions - $("#agent-status-controls > button.ready").click( function( ) { - SP.functions.ready(); - }); + // Call button will make an outbound call (click to dial) to the number entered + $("#action-buttons > button.call").click( function( ) { + alert("TODO: Call new /clicktodial action"); + // params = {"PhoneNumber": $("#number-entry > input").val()}; + // Twilio.Device.connect(params); + }); - $("#agent-status-controls > button.not-ready").click( function( ) { - SP.functions.notReady(); - }); + // Wire the ready / not ready buttons up to the server-side status change functions + $("#agent-status-controls > button.ready").click( function( ) { + SP.functions.ready(); + }); - $("#agent-status-controls > button.userinfo").click( function( ) { - SP.functions.getSFDCUserInfo(); - }); + $("#agent-status-controls > button.not-ready").click( function( ) { + SP.functions.notReady(); + }); + $("#agent-status-controls > button.userinfo").click( function( ) { + SP.functions.getSFDCUserInfo(); + }); - // ** Twilio Client Stuff ** // - // get username, generate token, set up device with token. callbacks bitch. - SP.functions.getSFDCUserInfo(); - + // ** Twilio Client Stuff ** // - + // get username, generate token, set up device with token. callbacks bitch. + SP.functions.getSFDCUserInfo(); - Twilio.Device.ready(function (device) { - sforce.interaction.cti.enableClickToDial(); - sforce.interaction.cti.onClickToDial(startCall); - SP.functions.ready(); - }); - Twilio.Device.offline(function (device) { - //make a new status call.. something like.. disconnected instead of notReady ? - sforce.interaction.cti.disableClickToDial(); - SP.functions.notReady(); - SP.functions.hideCallData(); - }); + sforce.interaction.cti.onClickToDial(startCall); - /* Report any errors on the screen */ - Twilio.Device.error(function (error) { - SP.functions.updateAgentStatusText("ready", error.message); - SP.functions.hideCallData(); - }); + // No longer need twilio stuff? - /* Log a message when a call disconnects. */ - Twilio.Device.disconnect(function (conn) { - SP.functions.updateAgentStatusText("ready", "Call ended"); - - sforce.interaction.getPageInfo(saveLog); - - SP.state.callNumber = null; - - // deactivate answer button - SP.functions.detachAnswerButton(); - - // return to waiting state - SP.functions.hideCallData(); - SP.functions.ready(); - }); + // Twilio.Device.ready(function (device) { + // sforce.interaction.cti.enableClickToDial(); + // sforce.interaction.cti.onClickToDial(startCall); + // SP.functions.ready(); + // }); - Twilio.Device.connect(function (conn) { + // Twilio.Device.offline(function (device) { + // //make a new status call.. something like.. disconnected instead of notReady ? + // sforce.interaction.cti.disableClickToDial(); + // SP.functions.notReady(); + // SP.functions.hideCallData(); + // }); - console.dir(conn); - var status = ""; - var callNum = null; - if (conn.parameters.From) { - callNum = conn.parameters.From; - status = "Call From: " + callNum; - } else { - status = "Outbound call"; + // /* Report any errors on the screen */ + // Twilio.Device.error(function (error) { + // SP.functions.updateAgentStatusText("ready", error.message); + // SP.functions.hideCallData(); + // }); - } + // /* Log a message when a call disconnects. */ + // Twilio.Device.disconnect(function (conn) { + // SP.functions.updateAgentStatusText("ready", "Call ended"); + // sforce.interaction.getPageInfo(saveLog); - SP.functions.updateAgentStatusText("onCall", status); - SP.functions.detachAnswerButton(); + // SP.state.callNumber = null; - //send status info - $.get("/track", { "from":SP.username, "status":"OnCall" }, function(data) { - - }); + // // deactivate answer button + // SP.functions.detachAnswerButton(); - }); + // // return to waiting state + // SP.functions.hideCallData(); + // SP.functions.ready(); + // }); - /* Listen for incoming connections */ - Twilio.Device.incoming(function (conn) { + // Twilio.Device.connect(function (conn) { - // Update agent status - sforce.interaction.setVisible(true); //pop up CTI console - SP.functions.updateAgentStatusText("ready", ("Call from: " + conn.parameters.From)) - // Enable answer button and attach to incoming call - SP.functions.attachAnswerButton(conn); + // console.dir(conn); + // var status = ""; + // var callNum = null; + // if (conn.parameters.From) { + // callNum = conn.parameters.From; + // status = "Call From: " + callNum; + // } else { + // status = "Outbound call"; - var inboundnum = cleanInboundTwilioNumber(conn.parameters.From); - var sid = conn.parameters.CallSid - var result = ""; + // } - $.get("/calldata", { "CallSid":sid}, function(data) { - result = JSON.parse(data); - result.caller + // SP.functions.updateAgentStatusText("onCall", status); + // SP.functions.detachAnswerButton(); - callData = {} - callData.callerName = result.requestor_name; - callData.callerNumber = conn.parameters.From; - callData.callerQueue = result.queue_name; - callData.callerMessage = result.message; - SP.functions.showCallData(callData); - var name = result.requestor_name || ""; + // //send status info + // $.get("/track", { "from":SP.username, "status":"OnCall" }, function(data) { - sforce.interaction.searchAndScreenPop(inboundnum, 'con10=' + inboundnum + '&name_firstcon2=' + name,'inbound'); + // }); - }); + // }); - - + SP.functions.screenPop = function(options) { - }); + // Update agent status + sforce.interaction.setVisible(true); //pop up CTI console - Twilio.Device.cancel(function(conn) { - console.log(conn.parameters.From); // who canceled the call - SP.functions.detachAnswerButton(); - SP.functions.hideCallData(); - SP.functions.notReady(); - }); + var from = options["From"]; + + SP.functions.updateAgentStatusText("ready", ("Call from: " + from)) + + var inboundnum = cleanInboundTwilioNumber(from); + var result = ""; + + var name = ''; + + sforce.interaction.searchAndScreenPop(inboundnum, 'con10=' + inboundnum + '&name_firstcon2=' + name,'inbound'); + + } + + + SP.functions.startWebSocket = function() { + // ** Agent Presence Stuff ** // + console.log(".startWebSocket..."); + var wsaddress = 'wss://' + window.location.host + "/websocket?clientname=" + SP.username + // Temp > hit localhost + // var wsaddress = 'ws://' + window.location.host + "/websocket?clientname=" + SP.username + var ws = new WebSocket(wsaddress); + ws.onopen = function() { console.log('websocket opened'); }; + ws.onclose = function() { console.log('websocket closed'); } - SP.functions.startWebSocket = function() { - // ** Agent Presence Stuff ** // - console.log(".startWebSocket..."); - var wsaddress = 'wss://' + window.location.host + "/websocket?clientname=" + SP.username + // Socket message from server, so screen pop or update status... - var ws = new WebSocket(wsaddress); - ws.onopen = function() { console.log('websocket opened'); }; - ws.onclose = function() { console.log('websocket closed'); } - ws.onmessage = function(m) { - console.log('websocket message: ' + m.data); - - var result = JSON.parse(m.data); + ws.onmessage = function(m) { + console.log('message from server: ' + m.data); + var result = JSON.parse(m.data); + + if(result['do'] == 'screenpop'){ // If call, do screen pop + SP.functions.screenPop(result); + }else if(result['do'] == 'paint'){ // If status message, just update + SP.functions.paint(result); + }else{ // If status message, just update $("#team-status > .queues-status").text("Call Queue: " + result.queuesize); - $("#team-status > .agents-status").text("Ready Agents: " + result.readyagents); - }; + $("#team-status > .agents-status").text("Ready Agents: " + result.readyagents); + } - } + }; + } - // Set server-side status to ready / not-ready - SP.functions.notReady = function() { - $.get("/track", { "from":SP.username, "status":"NotReady" }, function(data) { - SP.functions.updateStatus(); - }); - } - SP.functions.ready = function() { - $.get("/track", { "from":SP.username, "status":"Ready" }, function(data) { - SP.functions.updateStatus(); - }); + // Set server-side status to ready / not-ready + SP.functions.notReady = function() { + $.get("/track", { "from":SP.username, "status":"NotReady" }, function(data) { + SP.functions.updateStatus(); + }); + } + + SP.functions.ready = function() { + // Show alert if they haven't entered a phone number + var agent_number = $("#agent-number-entry input").val(); + if(agent_number == "") { + alert("Enter your number in the 'your number' field and click 'Ready'."); + return false; } + $.get("/track", { "agent_number":agent_number, "from":SP.username, "status":"Ready" }, function(data) { + SP.functions.updateStatus(); + }); + } - // Check the status on the server and update the agent status dialog accordingly - SP.functions.updateStatus = function() { - $.get("/status", { "from":SP.username}, function(data) { - if (data == "NotReady") { - SP.functions.updateAgentStatusText("notReady", "Not Ready") - } + // Check the status on the server and update the agent status dialog accordingly + SP.functions.updateStatus = function() { + $.get("/status", { "from":SP.username}, function(data) { - if (data == "Ready") { - SP.functions.updateAgentStatusText("ready", "Ready") - } - }); + if (data == "NotReady") { + SP.functions.updateAgentStatusText("notReady", "Not Ready") + } - } + if (data == "Ready") { + SP.functions.updateAgentStatusText("ready", "Ready") + } + }); + + } - /******** GENERAL FUNCTIONS for SFDC ***********************/ - function cleanInboundTwilioNumber(number) { - //twilio inabound calls are passed with +1 (number). SFDC only stores - return number.replace('+1',''); - } + SP.functions.paint = function(result) { + $("#agent-number-entry input").val(result['agent_number_entry']); + } + + /******** GENERAL FUNCTIONS for SFDC ***********************/ + + function cleanInboundTwilioNumber(number) { + //twilio inabound calls are passed with +1 (number). SFDC only stores + return number.replace('+1',''); + } + + function cleanFormatting(number) { + //changes a SFDC formatted US number, which would be 415-555-1212 + return number.replace(' ','').replace('-','').replace('(','').replace(')','').replace('+',''); + } + + function startCall(response) { + var result = JSON.parse(response.result); + var cleanednumber = cleanFormatting(result.number); + var agent_number = $("#agent-number-entry input").val(); + + SP.functions.updateAgentStatusText("ready", ("Calling: " + cleanednumber)) + + $.get("/clicktodial", {"to":cleanednumber, "from":agent_number}, function (token) { + Twilio.Device.setup(token, {debug: true}); + }); + } + + function saveLog(response) { - function cleanFormatting(number) { - //changes a SFDC formatted US number, which would be 415-555-1212 - return number.replace(' ','').replace('-','').replace('(','').replace(')','').replace('+',''); - } + console.log("saving log result, response:"); + var result = JSON.parse(response.result); + console.log(response.result); - function startCall(response) { - - //called onClick2dial - sforce.interaction.setVisible(true); //pop up CTI console - var result = JSON.parse(response.result); - var cleanednumber = cleanFormatting(result.number); + var timeStamp = new Date().toString(); + timeStamp = timeStamp.substring(0, timeStamp.lastIndexOf(':') + 3); + var currentDate = new Date(); + var currentDay = currentDate.getDate(); + var currentMonth = currentDate.getMonth()+1; + var currentYear = currentDate.getFullYear(); + var dueDate = currentYear + '-' + currentMonth + '-' + currentDay; + var saveParams = 'Subject=' + 'Call on ' + timeStamp; + saveParams += '&Status=completed'; + saveParams += '&CallType=' + 'Inbound'; + saveParams += '&Activitydate=' + dueDate; + saveParams += '&CallObject=' + currentDate.getTime(); + saveParams += '&Phone=' + SP.state.callNumber; //we need to get this from.. somewhere + saveParams += '&Description=' + "test description"; - //alert("cleanednumber = " + cleanednumber); - params = {"PhoneNumber": cleanednumber}; - Twilio.Device.connect(params); + console.log("About to parse result.."); + var result = JSON.parse(response.result); + if(result.objectId.substr(0,3) == '003') { + saveParams += '&whoId=' + result.objectId; + } else { + saveParams += '&whatId=' + result.objectId; } - function saveLog(response) { - - console.log("saving log result, response:"); - var result = JSON.parse(response.result); - - console.log(response.result); - - var timeStamp = new Date().toString(); - timeStamp = timeStamp.substring(0, timeStamp.lastIndexOf(':') + 3); - var currentDate = new Date(); - var currentDay = currentDate.getDate(); - var currentMonth = currentDate.getMonth()+1; - var currentYear = currentDate.getFullYear(); - var dueDate = currentYear + '-' + currentMonth + '-' + currentDay; - var saveParams = 'Subject=' + 'Call on ' + timeStamp; - - saveParams += '&Status=completed'; - saveParams += '&CallType=' + 'Inbound'; - saveParams += '&Activitydate=' + dueDate; - saveParams += '&CallObject=' + currentDate.getTime(); - saveParams += '&Phone=' + SP.state.callNumber; //we need to get this from.. somewhere - saveParams += '&Description=' + "test description"; - - console.log("About to parse result.."); - - var result = JSON.parse(response.result); - if(result.objectId.substr(0,3) == '003') { - saveParams += '&whoId=' + result.objectId; - } else { - saveParams += '&whatId=' + result.objectId; - } - - console.log("save params = " + saveParams); - sforce.interaction.saveLog('Task', saveParams); + console.log("save params = " + saveParams); + sforce.interaction.saveLog('Task', saveParams); } @@ -376,4 +364,4 @@ $(function() { -}); \ No newline at end of file +}); diff --git a/views/index.erb b/views/index.erb index e38e259..ab52bf4 100644 --- a/views/index.erb +++ b/views/index.erb @@ -3,12 +3,12 @@ Salesforce Dialer - + + - @@ -24,6 +24,10 @@

+
+ +
+
@@ -51,9 +55,7 @@
- - -
+
@@ -65,9 +67,7 @@
    Message:
-
- -
+
Ready agents: ?
Call queue: ?