telephony 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +105 -0
  3. data/Rakefile +39 -0
  4. data/app/assets/fonts/telephony/zest-telephony.eot +0 -0
  5. data/app/assets/fonts/telephony/zest-telephony.svg +73 -0
  6. data/app/assets/fonts/telephony/zest-telephony.ttf +0 -0
  7. data/app/assets/fonts/telephony/zest-telephony.woff +0 -0
  8. data/app/assets/images/telephony/backspace.png +0 -0
  9. data/app/assets/images/telephony/icon-spinner.gif +0 -0
  10. data/app/assets/javascripts/telephony/application.js +12 -0
  11. data/app/assets/javascripts/telephony/collections/agents.js +4 -0
  12. data/app/assets/javascripts/telephony/config.js.erb +11 -0
  13. data/app/assets/javascripts/telephony/models/agent.js +103 -0
  14. data/app/assets/javascripts/telephony/models/conversation.js +226 -0
  15. data/app/assets/javascripts/telephony/models/device.js +30 -0
  16. data/app/assets/javascripts/telephony/models/transfer.js +65 -0
  17. data/app/assets/javascripts/telephony/namespace.js +6 -0
  18. data/app/assets/javascripts/telephony/push.js +80 -0
  19. data/app/assets/javascripts/telephony/vendor/backbone.js +1159 -0
  20. data/app/assets/javascripts/telephony/vendor/chosen.jquery.js +1018 -0
  21. data/app/assets/javascripts/telephony/vendor/jquery.form.js +1117 -0
  22. data/app/assets/javascripts/telephony/vendor/pusher.js +1304 -0
  23. data/app/assets/javascripts/telephony/vendor/test_dependencies.js +18 -0
  24. data/app/assets/javascripts/telephony/vendor/underscore.js +839 -0
  25. data/app/assets/javascripts/telephony/views/agents_view.js +69 -0
  26. data/app/assets/javascripts/telephony/views/application_view.js +78 -0
  27. data/app/assets/javascripts/telephony/views/call_queue_view.js +21 -0
  28. data/app/assets/javascripts/telephony/views/conversation_buttons_view.js +15 -0
  29. data/app/assets/javascripts/telephony/views/conversation_view.js +322 -0
  30. data/app/assets/javascripts/telephony/views/status_view.js +36 -0
  31. data/app/assets/javascripts/telephony/views/transfer_view.js +87 -0
  32. data/app/assets/javascripts/telephony/views/twilio_client_view.js +117 -0
  33. data/app/assets/javascripts/telephony/views/widget_view.js +55 -0
  34. data/app/assets/javascripts/telephony/widget.js +5 -0
  35. data/app/assets/javascripts/templates/telephony/agents_view.jst.ejs +8 -0
  36. data/app/assets/javascripts/templates/telephony/call_queue_view.jst.ejs +2 -0
  37. data/app/assets/javascripts/templates/telephony/conversation_buttons_view.jst.ejs +23 -0
  38. data/app/assets/javascripts/templates/telephony/conversation_view.jst.ejs +10 -0
  39. data/app/assets/javascripts/templates/telephony/status_view.jst.ejs +4 -0
  40. data/app/assets/javascripts/templates/telephony/transfer_view.jst.ejs +38 -0
  41. data/app/assets/javascripts/templates/telephony/twilio_client_view.jst.ejs +10 -0
  42. data/app/assets/stylesheets/telephony/font.css +43 -0
  43. data/app/assets/stylesheets/telephony/reset.css.scss +10 -0
  44. data/app/assets/stylesheets/telephony/widget.css.scss +512 -0
  45. data/app/controllers/telephony/agents_controller.rb +46 -0
  46. data/app/controllers/telephony/application_controller.rb +4 -0
  47. data/app/controllers/telephony/call_centers_controller.rb +9 -0
  48. data/app/controllers/telephony/conversations_controller.rb +74 -0
  49. data/app/controllers/telephony/inbound/conversation_queues_controller.rb +21 -0
  50. data/app/controllers/telephony/playable_listeners_controller.rb +43 -0
  51. data/app/controllers/telephony/providers/twilio/application_controller.rb +26 -0
  52. data/app/controllers/telephony/providers/twilio/calls_controller.rb +130 -0
  53. data/app/controllers/telephony/providers/twilio/inbound_calls_controller.rb +54 -0
  54. data/app/controllers/telephony/providers/twilio/musics_controller.rb +12 -0
  55. data/app/controllers/telephony/providers/twilio/voicemails_controller.rb +29 -0
  56. data/app/controllers/telephony/signals/agents/presences_controller.rb +64 -0
  57. data/app/controllers/telephony/transfers_controller.rb +18 -0
  58. data/app/controllers/telephony/twilio_client_controller.rb +37 -0
  59. data/app/controllers/telephony/voicemails_controller.rb +13 -0
  60. data/app/controllers/telephony/widget_controller.rb +10 -0
  61. data/app/helpers/telephony/application_helper.rb +4 -0
  62. data/app/helpers/telephony/calls_helper.rb +13 -0
  63. data/app/models/telephony/agent.rb +186 -0
  64. data/app/models/telephony/agent_state_machine.rb +54 -0
  65. data/app/models/telephony/base.rb +7 -0
  66. data/app/models/telephony/blacklisted_number.rb +5 -0
  67. data/app/models/telephony/call.rb +94 -0
  68. data/app/models/telephony/call_center.rb +26 -0
  69. data/app/models/telephony/call_state_machine.rb +98 -0
  70. data/app/models/telephony/conversation.rb +273 -0
  71. data/app/models/telephony/conversation_state_machine.rb +109 -0
  72. data/app/models/telephony/conversations_presenter.rb +83 -0
  73. data/app/models/telephony/events.rb +6 -0
  74. data/app/models/telephony/events/answer.rb +6 -0
  75. data/app/models/telephony/events/base.rb +118 -0
  76. data/app/models/telephony/events/busy.rb +6 -0
  77. data/app/models/telephony/events/call_answered.rb +22 -0
  78. data/app/models/telephony/events/call_fail.rb +6 -0
  79. data/app/models/telephony/events/complete_hold.rb +28 -0
  80. data/app/models/telephony/events/complete_one_step_transfer.rb +17 -0
  81. data/app/models/telephony/events/complete_resume.rb +28 -0
  82. data/app/models/telephony/events/complete_two_step_transfer.rb +6 -0
  83. data/app/models/telephony/events/conference.rb +6 -0
  84. data/app/models/telephony/events/connect.rb +22 -0
  85. data/app/models/telephony/events/customer_left_two_step_transfer.rb +6 -0
  86. data/app/models/telephony/events/dial_agent.rb +6 -0
  87. data/app/models/telephony/events/ended.rb +19 -0
  88. data/app/models/telephony/events/enqueue.rb +6 -0
  89. data/app/models/telephony/events/fail_one_step_transfer.rb +9 -0
  90. data/app/models/telephony/events/fail_two_step_transfer.rb +6 -0
  91. data/app/models/telephony/events/initialize_widget.rb +15 -0
  92. data/app/models/telephony/events/initiate_hold.rb +6 -0
  93. data/app/models/telephony/events/initiate_one_step_transfer.rb +6 -0
  94. data/app/models/telephony/events/initiate_resume.rb +6 -0
  95. data/app/models/telephony/events/initiate_two_step_transfer.rb +6 -0
  96. data/app/models/telephony/events/leave_two_step_transfer.rb +17 -0
  97. data/app/models/telephony/events/leave_voicemail.rb +22 -0
  98. data/app/models/telephony/events/no_answer.rb +6 -0
  99. data/app/models/telephony/events/play_closed_greeting.rb +6 -0
  100. data/app/models/telephony/events/play_message.rb +6 -0
  101. data/app/models/telephony/events/reject.rb +6 -0
  102. data/app/models/telephony/events/rona.rb +6 -0
  103. data/app/models/telephony/events/start.rb +17 -0
  104. data/app/models/telephony/events/straight_to_voicemail.rb +6 -0
  105. data/app/models/telephony/events/terminate.rb +6 -0
  106. data/app/models/telephony/events/transfer.rb +35 -0
  107. data/app/models/telephony/inbound_conversation_queue.rb +101 -0
  108. data/app/models/telephony/playable.rb +7 -0
  109. data/app/models/telephony/playable_listener.rb +42 -0
  110. data/app/models/telephony/pusher_event_publisher.rb +26 -0
  111. data/app/models/telephony/recording.rb +4 -0
  112. data/app/models/telephony/voicemail.rb +36 -0
  113. data/app/observers/telephony/agent_observer.rb +8 -0
  114. data/app/observers/telephony/call_observer.rb +15 -0
  115. data/app/observers/telephony/conversation_observer.rb +9 -0
  116. data/app/observers/telephony/event_observer.rb +9 -0
  117. data/app/views/layouts/telephony/application.html.erb +14 -0
  118. data/app/views/telephony/providers/twilio/calls/child_detached.builder +5 -0
  119. data/app/views/telephony/providers/twilio/calls/complete_hold.builder +6 -0
  120. data/app/views/telephony/providers/twilio/calls/connect.builder +13 -0
  121. data/app/views/telephony/providers/twilio/calls/dial.builder +14 -0
  122. data/app/views/telephony/providers/twilio/calls/done.builder +5 -0
  123. data/app/views/telephony/providers/twilio/calls/join_conference.builder +7 -0
  124. data/app/views/telephony/providers/twilio/calls/whisper_tone.builder +3 -0
  125. data/app/views/telephony/providers/twilio/inbound_calls/closed_hours.builder +7 -0
  126. data/app/views/telephony/providers/twilio/inbound_calls/create.builder +8 -0
  127. data/app/views/telephony/providers/twilio/inbound_calls/enqueue.builder +8 -0
  128. data/app/views/telephony/providers/twilio/inbound_calls/reject.builder +5 -0
  129. data/app/views/telephony/providers/twilio/inbound_calls/wait_music.builder +5 -0
  130. data/app/views/telephony/providers/twilio/musics/hold.builder +5 -0
  131. data/app/views/telephony/providers/twilio/voicemails/new.builder +6 -0
  132. data/app/views/telephony/twilio_client/index.html.erb +74 -0
  133. data/app/views/telephony/widget/index.erb +3 -0
  134. data/config/cucumber.yml +8 -0
  135. data/config/database.yml +57 -0
  136. data/config/environment.rb +67 -0
  137. data/config/initializers/pusher.rb +21 -0
  138. data/config/initializers/telephony.rb +44 -0
  139. data/config/routes.rb +94 -0
  140. data/config/wopr.yml.example +38 -0
  141. data/db/migrate/20130806213053_bootstrap_db.rb +105 -0
  142. data/db/migrate/20131009204026_create_telephony_blacklisted_numbers.rb +11 -0
  143. data/lib/agent_generator.rb +24 -0
  144. data/lib/tasks/cucumber.rake +65 -0
  145. data/lib/tasks/jasmine.rake +8 -0
  146. data/lib/tasks/telephony_tasks.rake +4 -0
  147. data/lib/telephony.rb +34 -0
  148. data/lib/telephony/concerns/controllers/twilio_request_verifier.rb +39 -0
  149. data/lib/telephony/conversation_data.rb +67 -0
  150. data/lib/telephony/engine.rb +19 -0
  151. data/lib/telephony/error.rb +8 -0
  152. data/lib/telephony/error/agent_on_a_call.rb +2 -0
  153. data/lib/telephony/error/base.rb +3 -0
  154. data/lib/telephony/error/connection.rb +2 -0
  155. data/lib/telephony/error/not_in_progress.rb +2 -0
  156. data/lib/telephony/error/queue_empty.rb +2 -0
  157. data/lib/telephony/helper.rb +11 -0
  158. data/lib/telephony/jobs/agent_offline.rb +28 -0
  159. data/lib/telephony/jobs/pusher_event.rb +21 -0
  160. data/lib/telephony/providers/twilio_provider.rb +162 -0
  161. data/lib/telephony/version.rb +3 -0
  162. data/spec/controllers/telephony/agents_controller_spec.rb +117 -0
  163. data/spec/controllers/telephony/call_centers_controller_spec.rb +25 -0
  164. data/spec/controllers/telephony/conversations_controller_spec.rb +229 -0
  165. data/spec/controllers/telephony/playable_listeners_controller_spec.rb +138 -0
  166. data/spec/controllers/telephony/providers/twilio/calls_controller_spec.rb +33 -0
  167. data/spec/controllers/telephony/providers/twilio/musics_controller_spec.rb +20 -0
  168. data/spec/controllers/telephony/signals/agents/presences_controller_spec.rb +154 -0
  169. data/spec/controllers/telephony/twilio_client_controller_spec.rb +58 -0
  170. data/spec/controllers/telephony/widget_controller_spec.rb +25 -0
  171. data/spec/dummy/Rakefile +7 -0
  172. data/spec/dummy/app/assets/javascripts/application.js +6 -0
  173. data/spec/dummy/app/assets/javascripts/lib/event_logger.js +82 -0
  174. data/spec/dummy/app/assets/javascripts/vendor/backbone.js +1159 -0
  175. data/spec/dummy/app/assets/javascripts/vendor/chosen.jquery.js +1018 -0
  176. data/spec/dummy/app/assets/javascripts/vendor/jquery.form.js +1117 -0
  177. data/spec/dummy/app/assets/javascripts/vendor/underscore.js +839 -0
  178. data/spec/dummy/app/assets/stylesheets/application.css +23 -0
  179. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  180. data/spec/dummy/app/controllers/widget_host_controller.rb +2 -0
  181. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  182. data/spec/dummy/app/views/layouts/application.html.erb +12 -0
  183. data/spec/dummy/app/views/widget_host/index.html.erb +53 -0
  184. data/spec/dummy/config.ru +4 -0
  185. data/spec/dummy/config/application.rb +61 -0
  186. data/spec/dummy/config/boot.rb +10 -0
  187. data/spec/dummy/config/call_centers.yml +35 -0
  188. data/spec/dummy/config/call_centers.yml.example +35 -0
  189. data/spec/dummy/config/database.yml +56 -0
  190. data/spec/dummy/config/environment.rb +5 -0
  191. data/spec/dummy/config/environments/cucumber.rb +39 -0
  192. data/spec/dummy/config/environments/development.rb +39 -0
  193. data/spec/dummy/config/environments/production.rb +67 -0
  194. data/spec/dummy/config/environments/test.rb +39 -0
  195. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  196. data/spec/dummy/config/initializers/inflections.rb +15 -0
  197. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  198. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  199. data/spec/dummy/config/initializers/session_store.rb +8 -0
  200. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  201. data/spec/dummy/config/locales/en.yml +5 -0
  202. data/spec/dummy/config/pusher.yml +14 -0
  203. data/spec/dummy/config/pusher.yml.example +14 -0
  204. data/spec/dummy/config/routes.rb +5 -0
  205. data/spec/dummy/config/sms_whitelist.json.example +4 -0
  206. data/spec/dummy/config/twilio.yml +57 -0
  207. data/spec/dummy/config/twilio.yml.example +57 -0
  208. data/spec/dummy/db/schema.rb +122 -0
  209. data/spec/dummy/db/seeds.rb +6 -0
  210. data/spec/dummy/log/production.log +156 -0
  211. data/spec/dummy/log/test.log +13812 -0
  212. data/spec/dummy/public/404.html +26 -0
  213. data/spec/dummy/public/422.html +26 -0
  214. data/spec/dummy/public/500.html +25 -0
  215. data/spec/dummy/public/assets/application-628309db7ce8c0abdd91f8f796881599.js +32 -0
  216. data/spec/dummy/public/assets/application-628309db7ce8c0abdd91f8f796881599.js.gz +0 -0
  217. data/spec/dummy/public/assets/application-6c551430f3bdd32ba57c9c41e5f91846.css +24 -0
  218. data/spec/dummy/public/assets/application-6c551430f3bdd32ba57c9c41e5f91846.css.gz +0 -0
  219. data/spec/dummy/public/assets/telephony/backspace-9f5b195e049ed90eb20ecdda6e264b0a.png +0 -0
  220. data/spec/dummy/public/assets/telephony/icon-spinner-ddbae473a70c5425810aeee31d4a8ad7.gif +0 -0
  221. data/spec/dummy/public/assets/telephony/zest-telephony-0e6b13673634da80de9ae09bcca253e3.eot +0 -0
  222. data/spec/dummy/public/assets/telephony/zest-telephony-37f394757ccb11b978f16d9fd32cb3b5.ttf +0 -0
  223. data/spec/dummy/public/assets/telephony/zest-telephony-e7efdbc60c0a1c8404951bc79c7fb3a8.woff +0 -0
  224. data/spec/dummy/public/assets/telephony/zest-telephony-f34287df626936908408f53a33db9e83.svg +73 -0
  225. data/spec/dummy/public/favicon.ico +0 -0
  226. data/spec/dummy/script/rails +6 -0
  227. data/spec/dummy/tmp/cache/assets/C98/020/sprockets%2Fa83f8254d688334ff179984e423f502f +0 -0
  228. data/spec/dummy/tmp/cache/assets/CA4/550/sprockets%2F407f9191fe573f2435c55c017cb0d022 +0 -0
  229. data/spec/dummy/tmp/cache/assets/CAA/6A0/sprockets%2F3265550e2d2c7f5a864ca85d255504c2 +0 -0
  230. data/spec/dummy/tmp/cache/assets/CB0/F60/sprockets%2F462f9efed31e6710732376ae06c27208 +0 -0
  231. data/spec/dummy/tmp/cache/assets/CB6/E20/sprockets%2Fa7092cd9f16917099d754f1380af13e1 +0 -0
  232. data/spec/dummy/tmp/cache/assets/CC7/9F0/sprockets%2F0517100815cd45c43fec1d8510fcb630 +0 -0
  233. data/spec/dummy/tmp/cache/assets/CCA/BF0/sprockets%2F16ea15e0b5d367c34b812612a4207e3e +0 -0
  234. data/spec/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  235. data/spec/dummy/tmp/cache/assets/CF1/960/sprockets%2F36ef26985821ec702c74dae4af789903 +0 -0
  236. data/spec/dummy/tmp/cache/assets/D07/F30/sprockets%2Fd12862f20df0c9ef7aa2b4216d247079 +0 -0
  237. data/spec/dummy/tmp/cache/assets/D09/BF0/sprockets%2F5c8595efaa8cc5235a09801c60e25a16 +0 -0
  238. data/spec/dummy/tmp/cache/assets/D0D/250/sprockets%2F5e759d66e374910dae5a51b221e6e3a4 +0 -0
  239. data/spec/dummy/tmp/cache/assets/D0D/E30/sprockets%2Fa282879e0788ba133f5bdc211cf134e8 +0 -0
  240. data/spec/dummy/tmp/cache/assets/D11/8E0/sprockets%2F2a193a8af29963f1113bef05788e02ff +0 -0
  241. data/spec/dummy/tmp/cache/assets/D14/270/sprockets%2F7f95a752910ed33d728532f8fabd340d +0 -0
  242. data/spec/dummy/tmp/cache/assets/D17/BE0/sprockets%2F58749d7aea4ca766c2520801f0f96b9b +0 -0
  243. data/spec/dummy/tmp/cache/assets/D18/270/sprockets%2Fbb2d88813b1795a97e9a7c14100eef77 +0 -0
  244. data/spec/dummy/tmp/cache/assets/D22/310/sprockets%2Fbae9148ce1229214dfbb0c425b0054d0 +0 -0
  245. data/spec/dummy/tmp/cache/assets/D30/EB0/sprockets%2F2c668b88beb4c9805d91ed4587f549e7 +0 -0
  246. data/spec/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  247. data/spec/dummy/tmp/cache/assets/D3C/690/sprockets%2F681ba175fdd2da8d590b7e624b6135a6 +0 -0
  248. data/spec/dummy/tmp/cache/assets/D44/BC0/sprockets%2F7214f66dd91fdd51f00c3882293f5eea +0 -0
  249. data/spec/dummy/tmp/cache/assets/D46/FA0/sprockets%2F4f9543cd9e68ba3141fd488afb13681b +0 -0
  250. data/spec/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  251. data/spec/dummy/tmp/cache/assets/D51/9A0/sprockets%2Fb6e4cbcf30f6f611671695f79ea846d5 +0 -0
  252. data/spec/dummy/tmp/cache/assets/D55/920/sprockets%2F0a02242aa4c4febcc796419aba103c69 +0 -0
  253. data/spec/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  254. data/spec/dummy/tmp/cache/assets/D5F/6F0/sprockets%2F155e7bc44f326371cce0ca8bc5b2414e +0 -0
  255. data/spec/dummy/tmp/cache/assets/D6B/250/sprockets%2Fe4eecce7a3371b11038c92f07cb28e67 +0 -0
  256. data/spec/dummy/tmp/cache/assets/D6B/E70/sprockets%2F103a456ae3692e3cca20b7696fdee8f0 +0 -0
  257. data/spec/dummy/tmp/cache/assets/D7F/AD0/sprockets%2Fb1c02d12303b1fe2f981771cbb0c4cbd +0 -0
  258. data/spec/dummy/tmp/cache/assets/D81/D00/sprockets%2F0c68b6d9dce98681a2a710d9fc3f7e39 +0 -0
  259. data/spec/dummy/tmp/cache/assets/D94/4D0/sprockets%2F4608a5bdf27ddf11bcaed1375911e9a2 +0 -0
  260. data/spec/dummy/tmp/cache/assets/D9A/580/sprockets%2F433005b19aaf7ed5cba9c7f8cd30b775 +0 -0
  261. data/spec/dummy/tmp/cache/assets/DA4/270/sprockets%2F860ab3bd30204e02c0a5d82bd4bbabe1 +0 -0
  262. data/spec/dummy/tmp/cache/assets/DA4/480/sprockets%2F53e39285bb57a2ddb6ff856b2eb491da +0 -0
  263. data/spec/dummy/tmp/cache/assets/DA6/3C0/sprockets%2F587c7e35f47442eadaa530acc5e9f5e6 +0 -0
  264. data/spec/dummy/tmp/cache/assets/DAB/DA0/sprockets%2Fb8fb5e8518ed3d38a7f6a8f18a9208aa +0 -0
  265. data/spec/dummy/tmp/cache/assets/DB2/700/sprockets%2Ffa85cb79b94fce1b8344d983ea863d2e +0 -0
  266. data/spec/dummy/tmp/cache/assets/DCC/4F0/sprockets%2Fbc0f3c61e16afb5a285ba8b39846ed8b +0 -0
  267. data/spec/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  268. data/spec/dummy/tmp/cache/assets/DDC/BE0/sprockets%2F2c0a4d4675a61b014aedbbabb3f071fc +0 -0
  269. data/spec/dummy/tmp/cache/assets/E04/640/sprockets%2F3c5ddf2bbefd9d70f1b506b519cf856e +0 -0
  270. data/spec/dummy/tmp/cache/assets/E04/7F0/sprockets%2F35f1b2ff729a5ec21f6dc5f7d6faa67b +0 -0
  271. data/spec/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  272. data/spec/dummy/tmp/cache/assets/E07/230/sprockets%2Febd4f2dd708879d593aff008ccb75acc +0 -0
  273. data/spec/dummy/tmp/cache/assets/E17/9D0/sprockets%2F8db2b79f6c4eabe7a9ebe7a7898ec066 +0 -0
  274. data/spec/dummy/tmp/cache/assets/E31/F10/sprockets%2Fcebacf56667da1cc2961b59fd922eafb +0 -0
  275. data/spec/factories/agent.rb +26 -0
  276. data/spec/factories/calls.rb +106 -0
  277. data/spec/factories/conversations.rb +171 -0
  278. data/spec/factories/events.rb +250 -0
  279. data/spec/factories/playable_listeners.rb +6 -0
  280. data/spec/factories/recordings.rb +6 -0
  281. data/spec/factories/voicemails.rb +8 -0
  282. data/spec/fixtures/vcr_cassettes/Authenticating_an_online_user.yml +90 -0
  283. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/creates_a_new_call.yml +42 -0
  284. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/creates_a_new_conversation.yml +42 -0
  285. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/returns_the_conversation_as_JSON.yml +42 -0
  286. data/spec/fixtures/vcr_cassettes/Pusher_channel_as_JSON.yml +90 -0
  287. data/spec/fixtures/vcr_cassettes/Pusher_to_publish_the_event.yml +32 -0
  288. data/spec/fixtures/vcr_cassettes/Reloading_the_widget_during_a_call/_create/during_a_call/after_a_failed_transfer/pushes_the_event_for_the_active_call.yml +1110 -0
  289. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/creates_a_new_call.yml +45 -0
  290. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/creates_a_new_conversation.yml +43 -0
  291. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/returns_the_conversation_as_JSON.yml +44 -0
  292. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/allows_the_phone_to_ring_for_60_seconds.yml +42 -0
  293. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/includes_a_status_change_callback_url.yml +42 -0
  294. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/places_a_call.yml +42 -0
  295. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_dial_into_conference/allows_the_phone_to_ring_for_15_seconds.yml +42 -0
  296. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_dial_into_conference/places_a_call_to_redirect_the_participant_to_a_conference.yml +42 -0
  297. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_hangup/by_default/hangs_up_the_call.yml +42 -0
  298. data/spec/fixtures/vcr_cassettes/buying_number_for_an_area_code.yml +42 -0
  299. data/spec/fixtures/vcr_cassettes/returns_success.yml +90 -0
  300. data/spec/javascripts/helpers/jasmine-jquery.js +546 -0
  301. data/spec/javascripts/helpers/mock-ajax.js +207 -0
  302. data/spec/javascripts/support/jasmine.yml +80 -0
  303. data/spec/javascripts/telephony/models/agent_spec.js +40 -0
  304. data/spec/javascripts/telephony/models/conversation_spec.js +515 -0
  305. data/spec/javascripts/telephony/models/device_spec.js +80 -0
  306. data/spec/javascripts/telephony/models/transfer_spec.js +22 -0
  307. data/spec/javascripts/telephony/push_spec.js +427 -0
  308. data/spec/javascripts/telephony/views/agents_view_spec.js +101 -0
  309. data/spec/javascripts/telephony/views/application_view_spec.js +74 -0
  310. data/spec/javascripts/telephony/views/conversation_view_spec.js +626 -0
  311. data/spec/javascripts/telephony/views/status_view_spec.js +30 -0
  312. data/spec/javascripts/telephony/views/transfer_view_spec.js +187 -0
  313. data/spec/javascripts/telephony/views/twilio_client_view_spec.js +77 -0
  314. data/spec/javascripts/telephony/views/widget_view_spec.js +20 -0
  315. data/spec/lib/telephony/concerns/controllers/twilio_request_verifier_spec.rb +77 -0
  316. data/spec/lib/telephony/conversation_data_spec.rb +342 -0
  317. data/spec/lib/telephony/helper_spec.rb +24 -0
  318. data/spec/lib/telephony/jobs/agent_offline_spec.rb +37 -0
  319. data/spec/lib/telephony/jobs/pusher_event_spec.rb +39 -0
  320. data/spec/lib/telephony/providers/twilio_provider_spec.rb +439 -0
  321. data/spec/lib/telephony_spec.rb +104 -0
  322. data/spec/models/telephony/agent_spec.rb +479 -0
  323. data/spec/models/telephony/agent_state_machine_spec.rb +131 -0
  324. data/spec/models/telephony/call_center_spec.rb +32 -0
  325. data/spec/models/telephony/call_spec.rb +597 -0
  326. data/spec/models/telephony/call_state_machine_spec.rb +472 -0
  327. data/spec/models/telephony/conversation_spec.rb +751 -0
  328. data/spec/models/telephony/conversation_state_machine_spec.rb +387 -0
  329. data/spec/models/telephony/conversations_presenter_spec.rb +40 -0
  330. data/spec/models/telephony/event_spec.rb +716 -0
  331. data/spec/models/telephony/inbound_conversation_queue_spec.rb +243 -0
  332. data/spec/models/telephony/playable_listener_spec.rb +43 -0
  333. data/spec/models/telephony/pusher_event_publisher_spec.rb +36 -0
  334. data/spec/models/telephony/recording_spec.rb +6 -0
  335. data/spec/models/telephony/voicemail_spec.rb +96 -0
  336. data/spec/observers/telephony/agent_observer_spec.rb +21 -0
  337. data/spec/observers/telephony/event_observer_spec.rb +19 -0
  338. data/spec/requests/create_conversation_spec.rb +39 -0
  339. data/spec/requests/dequeue_call_spec.rb +42 -0
  340. data/spec/requests/list_conversations_spec.rb +33 -0
  341. data/spec/requests/presences_spec.rb +51 -0
  342. data/spec/requests/providers/twilio/calls/child_answered_spec.rb +24 -0
  343. data/spec/requests/providers/twilio/calls/child_detached_spec.rb +249 -0
  344. data/spec/requests/providers/twilio/calls/dial_spec.rb +132 -0
  345. data/spec/requests/providers/twilio/calls/done_spec.rb +69 -0
  346. data/spec/requests/providers/twilio/calls/join_conference_spec.rb +56 -0
  347. data/spec/requests/providers/twilio/calls/leave_queue_spec.rb +44 -0
  348. data/spec/requests/providers/twilio/calls/parent_answered_spec.rb +59 -0
  349. data/spec/requests/providers/twilio/inbound/connect_dequeued_call_spec.rb +124 -0
  350. data/spec/requests/providers/twilio/inbound/enqueue_inbound_call_spec.rb +108 -0
  351. data/spec/requests/providers/twilio/inbound/enqueued_call_wait_music_spec.rb +13 -0
  352. data/spec/requests/providers/twilio/voicemails/leave_voicemail_spec.rb +78 -0
  353. data/spec/requests/search_conversations_spec.rb +101 -0
  354. data/spec/requests/transfer_spec.rb +65 -0
  355. data/spec/requests/voicemail_api_spec.rb +58 -0
  356. data/spec/spec_helper.rb +27 -0
  357. data/spec/support/factory_girl.rb +5 -0
  358. data/spec/support/matchers/be_complete_hold.rb +20 -0
  359. data/spec/support/matchers/be_whisper_tone.rb +35 -0
  360. data/spec/support/matchers/join_conference.rb +23 -0
  361. data/spec/support/nokogiri.rb +1 -0
  362. data/spec/support/pusher_helper.rb +24 -0
  363. data/spec/support/vcr.rb +12 -0
  364. data/spec/support/webmock.rb +1 -0
  365. metadata +692 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OTQ3MjJkODgzYzMxN2M2YzliMjk1NzU2ODdjNTNhMWI1YmRiYzVhNQ==
5
+ data.tar.gz: !binary |-
6
+ NGNmNDBhYWI4ODM5NzA0YjIxYjBhMjRhZTdiOTVmYTJlNTExMmY4NA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NzFlNzMwMjZhMDQ2NWYxZTk5MWM4NmQ2ZTdhNWJiYjBmZDYzMmIzNTM5OWVh
10
+ NjRmNTQ1YTBlNzQ2NGMyZWNmOGY5ZDBmMGIwMGRlZGJiYjhhYTY5MWM1ZGFi
11
+ NDhhNDJhY2E1NGQyNzVkNGJkN2IwZTJkNDVkOWY1NDE0ZTUxZDA=
12
+ data.tar.gz: !binary |-
13
+ YzQ4ZDE4MjY4ODdjMjNkODE5ZTEwODNlMTNiMzczMmJhOGVmZWE4NTkxMTc5
14
+ YTEyNzIwMTgzYWE5NjRiOWQxYzQ2OTI3ZTNmMjY1ZDI2ZWQxODhiYWE2ZmQ4
15
+ OTZjYmQwMjgwYjI5ZDdlYmU3Mjc0ZmM5N2NlMTg4MmY3YjdlNWI=
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ Telephony call-center software
2
+ =======
3
+ ZestPhone
4
+ ================
5
+
6
+ ## Who we are
7
+ We are Zestfinance (zestfinance.com) a financial services company that helps lenders with underwriting and loan servicing. In addition to building cutting-edge underwriting algorithms, we’ve also built a host of home-grown CRM tools and loan servicing products for our lending partners. We recently added call-center style phone controls into our existing CRM by leveraging Twilio’s cloud-based communication technology, which we call ZestPhone.
8
+
9
+ ## What is ZestPhone?
10
+ ZestPhone is a drop-in phone widget that can be simply added to any web application to give you full call center functionality.
11
+
12
+ ## Features:
13
+ - Inbound and outbound calling
14
+ - Call outbound to anywhere in the world*
15
+ - Set up your existing business phone number with Twilio or purchase one from Twilio to get started
16
+ - Have multiple business phone numbers in use for marketing attribution
17
+ - Cold and warm transfer
18
+ - Allow your agents transfer calls to each other either directly or with introduction
19
+ - Put a call on hold / resume a call
20
+ - Agent status
21
+ - Ability to respond to telephony events in your application
22
+ - Uses existing agent names/numbers/ids
23
+ - Embeds into your existing javascript-enabled application
24
+ - PSTN or VoIP (i.e. use a regular phone, SIP, or twilio client)
25
+ - Call recording
26
+ - All calls are recorded while the customer is on the line with an agent and are stored by Twilio (storage rates apply)
27
+ - Voicemail
28
+ - Customers can be transferred to a specific agent’s voicemail if the agent is busy with another customer or offline
29
+
30
+ ## Why use ZestPhone / Am I (or my company) a good candidate?
31
+ - **Quick integration** You want to quickly and easily add phone capabilities to your application
32
+ - **1:1 Customer Service Experience** You want to empower your call center agents to provide 1:1 support to customers with easy-to-use transfer and voicemail capabilities
33
+ - **Use any type of phone** You have call centers with different phone setups and want software to support them all (PSTN, SIP, VoIP)
34
+ - **Low cost per minute** You want to take advantage of twilio’s competitive usage pricing
35
+
36
+ ## Running ZestPhone server
37
+
38
+ ZestPhone is a ruby on rails application, backed by a MySql database.
39
+ It's only known to work for sure using ruby 1.9.3, but any 1.9.x ruby version is probably fine.
40
+
41
+ - You need a Twilio account of course.
42
+ - A Pusher account
43
+ - A database that is supported by rails, and that supports locking (only MySql is known to work)
44
+ - Your application needs to include backbone.js
45
+
46
+ The complete setup guide is here: https://github.com/ZestFinance/zestphone/blob/master/SETUP.md
47
+
48
+ ## Javascript events your app can respond to
49
+
50
+ All events have this basic data:
51
+
52
+ ```json
53
+ {
54
+ event_id:
55
+ conversation_id:
56
+ conversation_state:
57
+ call_id: # This will be empty for events that happen at the conversation level
58
+ number: # The customer's number
59
+ owner: # Is the agent receiving this event the conversation owner?
60
+ }
61
+ ```
62
+
63
+ - **telephony:Answer**:
64
+ Fired when the agent answers his phone
65
+ - **telephony:Busy**:
66
+ Fired to an agent whose phone was busy
67
+ - **telephony:CallFail**:
68
+ Sent to an agent when the agent leg fails
69
+ - **telephony:CompleteHold**:
70
+ Sent to all agents when the customer has been put on hold
71
+ - **telephony:CompleteOneStepTransfer**:
72
+ Sent to the agent who just answered a one-step transfer
73
+ - **telephony:CompleteResume**:
74
+ Sent to all agents when the customer is taken off of hold
75
+ - **telephony:CompleteTwoStepTransfer**:
76
+ Sent to all agents after the 2nd agent picks up in a two-step transfer. The data sent to agent1 will include agent2's name, extension, and csr_type. The data sent to agent2 will include that data for agent1.
77
+
78
+ - **telephony:Conference**
79
+ Sent to an agent who was just moved into a conference room. This can happen during hold or transfer actions, and is not very interesting on it's own.
80
+ - **telephony:Connect**
81
+ - **telephony:csrDidChangeStatus**
82
+ - **telephony:csrNotAvailable**
83
+ - **telephony:CustomerLeftTwoStepTransfer**
84
+ - **telephony:FailOneStepTransfer**
85
+ - **telephony:FailTwoStepTransfer**
86
+ - **telephony:InitiateOneStepTransfer**
87
+ - **telephony:InitiateTwoStepTransfer**
88
+ - **telephony:InitializeWidget**
89
+ - **telephony:LeaveTwoStepTransfer**
90
+ - **telephony:LeaveVoicemail**
91
+ - **telephony:NoAnswer**
92
+ - **telephony:QueueChange**
93
+ - **telephony:RemoveFromConference**
94
+ - **telephony:Start**
95
+ - **telephony:Terminate**
96
+ - **telephony:toggleCsrStatus**
97
+ - **telephony:WidgetReady**
98
+ - **telephony:conversationCreated**
99
+ - **transferInitiated**
100
+
101
+ ## Javascript events you can trigger from your app
102
+ - **telephony:ClickToCall**:
103
+ Sample usage: ```javascript
104
+ $(this).trigger("telephony:ClickToCall", to: $(event.currentTarget).text());
105
+ ```
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Telephony'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rspec/core/rake_task'
29
+
30
+ RSpec::Core::RakeTask.new(:spec)
31
+
32
+
33
+ require 'jasmine/headless'
34
+
35
+ Jasmine::Headless::Task.new do |t|
36
+ t.colors = true
37
+ end
38
+
39
+ task default: [:spec, 'jasmine:headless']
@@ -0,0 +1,73 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
3
+ <svg xmlns="http://www.w3.org/2000/svg">
4
+ <metadata>
5
+ Created by FontForge 20100429 at Sat Dec 22 19:34:50 2012
6
+ By root
7
+ Copyright (C) 2012 by original authors @ fontello.com
8
+ </metadata>
9
+ <defs>
10
+ <font id="zest-telephony" horiz-adv-x="852" >
11
+ <font-face
12
+ font-family="zest-telephony"
13
+ font-weight="500"
14
+ font-stretch="normal"
15
+ units-per-em="1000"
16
+ panose-1="2 0 6 3 0 0 0 0 0 0"
17
+ ascent="850"
18
+ descent="-150"
19
+ bbox="15 -150 1015 850"
20
+ underline-thickness="50"
21
+ underline-position="-100"
22
+ unicode-range="U+26A0-1F514"
23
+ />
24
+ <missing-glyph horiz-adv-x="364"
25
+ d="M33 0v666h265v-666h-265zM66 33h199v600h-199v-600z" />
26
+ <glyph glyph-name=".notdef" horiz-adv-x="364"
27
+ d="M33 0v666h265v-666h-265zM66 33h199v600h-199v-600z" />
28
+ <glyph glyph-name=".null" horiz-adv-x="0"
29
+ />
30
+ <glyph glyph-name="nonmarkingreturn" horiz-adv-x="333"
31
+ />
32
+ <glyph glyph-name="uni26A0" unicode="&#x26a0;" horiz-adv-x="944"
33
+ d="M77 -53q-25 0 -43 12t-18 35q0 21 11 38l395 684q22 37 49.5 37t49.5 -37l396 -684q12 -19 12 -38q0 -23 -18 -35t-43 -12h-791zM430 537q-18 0 -18 -17l6 -326q0 -7 5.5 -12.5t12.5 -5.5h72q7 0 12 5.5t5 12.5l8 326q0 7 -5.5 12t-12.5 5h-85zM415 33q0 -18 18 -18h79
34
+ q18 0 18 18v76q0 19 -18 19h-79q-18 0 -18 -19v-76z" />
35
+ <glyph glyph-name="uni2714" unicode="&#x2714;"
36
+ d="M16 350q0 171 120 291t290.5 120t290.5 -120t120 -291t-120 -291t-290.5 -120t-290.5 120t-120 291zM139 325q0 -11 7 -18l165 -164q18 -18 42 -18h28q24 0 42 18l284 283q17 18 0 35l-54 54q-8 8 -18.5 8t-17.5 -8l-233 -232q-7 -8 -17 -8t-17 8l-114 113q-7 8 -17 8
37
+ q-11 0 -19 -8l-54 -54q-7 -8 -7 -17z" />
38
+ <glyph glyph-name="uni2716" unicode="&#x2716;"
39
+ d="M16 350q0 171 120 291t290.5 120t290.5 -120t120 -291t-120 -291t-290.5 -120t-290.5 120t-120 291zM214 214q0 -5 5 -12l59 -60q13 -13 24 0l126 125l112 -114q5 -5 13 -5q6 0 11 5l58 58q12 12 0 24l-113 113l126 126q12 12 0 24l-60 59q-13 13 -24 0l-126 -125
40
+ l-113 114q-6 5 -12 5q-5 0 -13 -5l-56 -58q-13 -12 0 -24l112 -113l-124 -125q-5 -6 -5 -12z" />
41
+ <glyph glyph-name="uniE782" unicode="&#xe782;" horiz-adv-x="979"
42
+ d="M56 -45q-17 0 -28.5 11.5t-11.5 28.5v59q0 17 11.5 28t28.5 11h79q16 0 27.5 -11.5t11.5 -27.5v-59q0 -17 -11 -28.5t-28 -11.5h-79zM56 172q-17 0 -28.5 11.5t-11.5 28.5v59q0 17 11.5 28.5t28.5 11.5h79q17 0 28 -11.5t11 -28.5v-59q0 -17 -11 -28.5t-28 -11.5h-79z
43
+ M56 390q-17 0 -28.5 11t-11.5 28v59q0 17 11.5 28.5t28.5 11.5h79q17 0 28 -11.5t11 -28.5v-59q0 -17 -11 -28t-28 -11h-79zM56 607q-17 0 -28.5 11.5t-11.5 27.5v60q0 17 11.5 28t28.5 11h79q16 0 27.5 -11.5t11.5 -27.5v-60q0 -16 -11.5 -27.5t-27.5 -11.5h-79zM293 -45
44
+ q-17 0 -28.5 11.5t-11.5 28.5v59q0 17 11.5 28t28.5 11h632q15 0 26.5 -11.5t11.5 -27.5v-59q0 -17 -11 -28.5t-27 -11.5h-632zM293 172q-17 0 -28.5 11.5t-11.5 28.5v59q0 17 11.5 28.5t28.5 11.5h632q16 0 27 -11.5t11 -28.5v-59q0 -17 -11 -28.5t-27 -11.5h-632zM293 390
45
+ q-17 0 -28.5 11t-11.5 28v59q0 17 11.5 28.5t28.5 11.5h632q16 0 27 -11.5t11 -28.5v-59q0 -17 -11 -28t-27 -11h-632zM253 646v60q0 17 11.5 28t28.5 11h632q15 0 26.5 -11.5t11.5 -27.5v-60q0 -16 -11.5 -27.5t-26.5 -11.5h-632q-17 0 -28.5 11.5t-11.5 27.5z" />
46
+ <glyph glyph-name="uniE838" unicode="&#xe838;" horiz-adv-x="1030"
47
+ d="M335 586q-11 -5 -16 5l-91 157q-6 10 4 16l61 35q11 5 16 -5l91 -157q6 -10 -4 -16zM276 525l-35 -60q-5 -10 -16 -5l-158 92q-9 5 -4 15l35 61q5 10 16 4l158 -91q12 -6 4 -16zM220 381v-69q0 -11 -11 -11h-182q-12 0 -12 11v1v69q0 4 3.5 8t8.5 4h182q11 0 11 -12v-1z
48
+ M279 170q5 -11 -5 -16l-157 -91q-10 -6 -16 4l-35 61q-5 11 5 16l157 91q10 6 16 -4zM340 111l60 -35q10 -5 5 -16l-92 -158q-5 -9 -15 -4l-61 35q-10 5 -4 16l91 158q6 12 16 4zM484 55h69q11 0 11 -11v-182q0 -12 -11 -12h-1h-69q-5 0 -8.5 4t-3.5 8v182q0 11 12 11h1z
49
+ M695 114q11 5 16 -5l91 -157q6 -10 -4 -16l-61 -35q-11 -5 -16 5l-91 157q-6 10 4 16zM754 175l35 60q5 10 16 5l158 -92q9 -5 4 -15l-35 -61q-5 -10 -16 -4l-158 91q-12 6 -4 16zM810 319v69q0 11 11 11h182q12 0 12 -11v-1v-69q0 -4 -3.5 -8t-8.5 -4h-182q-11 0 -11 12v1z
50
+ M751 530q-5 11 5 16l157 91q10 6 16 -4l35 -61q5 -11 -5 -16l-157 -91q-10 -6 -16 4zM690 589l-60 35q-10 5 -5 16l92 158q5 9 15 4l61 -35q10 -5 4 -16l-91 -158q-6 -12 -16 -4zM546 645h-69q-11 0 -11 11v182q0 12 11 12h1h69q5 0 8.5 -4t3.5 -8v-182q0 -11 -12 -11h-1z
51
+ " />
52
+ <glyph glyph-name="uniF098" unicode="&#xf098;" horiz-adv-x="821"
53
+ d="M115 -45q-41 0 -70 29t-29 70v592q0 63 60 90q19 9 39 9h592q41 0 70 -29t29 -70v-592q0 -63 -62 -90q-20 -9 -37 -9h-592zM115 490q1 -13 3 -25t8 -27l8 -22l12 -24t10 -19q95 -189 258 -273q17 -8 82 -32q56 -22 99 -11q39 10 76 35q26 19 36 80q1 8 -2.5 14.5t-7 9
54
+ t-12 7t-11.5 6.5q-79 45 -90 53q-12 7 -20 7q-4 0 -9 -2.5t-10.5 -8t-8.5 -9.5l-9 -10q-5 -6 -6 -7q-2 -2 -8.5 -10.5t-10.5 -12t-12.5 -10t-13.5 -5.5l-15 5q-3 3 -9.5 6.5t-9.5 5.5q-110 61 -173 180q-8 8 -4 18t7 14t11.5 11t11.5 10q1 2 6 7.5t8 8.5t6.5 8.5t4.5 10.5
55
+ t1 11.5t-2 13.5q-35 84 -43 121q-2 7 -7 12.5t-15 7t-15.5 2t-20 0t-18.5 -0.5h-4q-57 -27 -76 -104q-6 -28 -5 -52z" />
56
+ <glyph glyph-name="uniF0E0" unicode="&#xf0e0;" horiz-adv-x="979"
57
+ d="M75 -45q-24 0 -41.5 17.5t-17.5 41.5v466q6 -7 21 -17l261 -199q82 -66 138 -82q27 -9 53 -9q73 0 149 57q32 23 89 68.5t84 65.5l142 107q6 4 10 9v-466q0 -24 -17 -41.5t-41 -17.5h-830zM17 692q0 20 18.5 36.5t39.5 16.5h830q21 0 39 -16.5t18 -36.5q0 -28 -32 -73
58
+ q-29 -38 -57 -61l-248 -187q-10 -7 -29.5 -23t-37 -28t-34 -20.5t-33.5 -8.5h-2q-28 0 -71 31q-12 8 -34 26l-30 23l-124 94l-124 93q-53 43 -80 99q-9 19 -9 35z" />
59
+ <glyph glyph-name="u1F464" unicode="&#x1f464;" horiz-adv-x="837"
60
+ d="M16 -24v199q0 39 33 87q36 49 71 60q24 7 86 14q13 2 46.5 6t47.5 6q-45 28 -73 76t-28 109q0 89 64 155q63 65 155 65q91 0 156 -65q64 -64 64 -155q0 -60 -28.5 -108.5t-72.5 -76.5l69 -8q52 -7 76 -11t31.5 -5.5t17.5 -7t22 -15.5q40 -37 59 -84q9 -24 9 -42v-199
61
+ q-7 -3 -20.5 -13t-18.5 -12q-8 -4 -11 -4h-705q-18 0 -27 11q-8 10 -23 18z" />
62
+ <glyph glyph-name="u1F4DE" unicode="&#x1f4de;"
63
+ d="M16 556q0 94 65 168q22 24 47 36q6 -1 16 0t12 1h15l21 -1q-1 -1 33 -7q7 -4 10.5 -12.5t7.5 -21.5t6 -17q7 -21 24 -68l23 -64q7 -21 7 -27q0 -30 -40 -65q-21 -18 -28 -26q-13 -12 -13 -23q0 -8 22 -48q45 -76 98 -129t129 -96q39 -23 49.5 -23t27.5 17q7 7 28.5 33.5
64
+ t33.5 38.5q17 16 30 16q3 0 27 -11q56 -31 93 -54q36 -22 55 -31q22 -11 22 -31q0 -32 -19 -81q-26 -59 -131 -84q-28 -7 -48 -7q-14 0 -29.5 2.5t-25.5 4.5t-27.5 7.5t-23 8t-25.5 10t-23 8.5q-174 65 -314 247q-83 106 -127 217q-28 67 -28 112z" />
65
+ <glyph glyph-name="u1F50D" unicode="&#x1f50d;"
66
+ d="M547 133q-90 -57 -189 -57q-140 0 -242 100q-100 100 -100 242q0 144 100 242q101 101 242 101q138 0 241 -101q101 -102 101 -242q0 -99 -57 -188l185 -184q9 -10 9 -25q0 -16 -32.5 -49t-49.5 -33q-15 0 -24 10zM153 418q0 -68 35 -116.5t91 -72.5q38 -16 79 -16
67
+ q67 0 115.5 35.5t73.5 89.5q16 40 16 80q0 84 -61 145q-59 61 -144 61q-67 0 -115.5 -36t-72.5 -91q-17 -40 -17 -79zM229 418q0 43 22.5 74t55.5 45q27 11 51 11q11 0 18 -8t7 -18.5t-7 -18t-18 -7.5q-33 0 -55.5 -22.5t-22.5 -55.5q0 -10 -7.5 -17.5t-17.5 -7.5
68
+ q-12 0 -19 7.5t-7 17.5z" />
69
+ <glyph glyph-name="u1F514" unicode="&#x1f514;" horiz-adv-x="936"
70
+ d="M106 36q-35 0 -61.5 24.5t-28.5 57.5q144 109 176 308q8 54 13 123q6 84 94 150q83 62 170 62q83 0 169 -62q93 -66 93 -148q15 -299 189 -433q-2 -34 -28 -58t-62 -24h-241q-10 -41 -42.5 -69t-78 -28t-78.5 28.5t-43 68.5h-241zM798 118q-128 142 -148 423
71
+ q-5 53 -67 98q-57 40 -114 40q-54 0 -114 -40q-64 -43 -68 -94q-13 -273 -149 -427h660zM382 63q0 -36 25.5 -61.5t61.5 -25.5q9 0 9 10.5t-9 10.5q-28 0 -47.5 19.5t-19.5 46.5q0 10 -9 10q-11 0 -11 -10z" />
72
+ </font>
73
+ </defs></svg>
@@ -0,0 +1,12 @@
1
+ //= require "./vendor/pusher"
2
+
3
+ //= require_tree "../templates/"
4
+ //= require "telephony/namespace"
5
+ //= require "telephony/config"
6
+ //= require "telephony/push"
7
+ //= require "telephony/models/device"
8
+ //= require "telephony/models/agent"
9
+ //= require "telephony/models/transfer"
10
+ //= require "telephony/models/conversation"
11
+ //= require "telephony/collections/agents"
12
+ //= require_tree "./views"
@@ -0,0 +1,4 @@
1
+ Zest.Telephony.Collections.Agents = Backbone.Collection.extend({
2
+ url: Zest.Telephony.Config.AGENT_PATH,
3
+ model: Zest.Telephony.Models.Agent
4
+ });
@@ -0,0 +1,11 @@
1
+ Zest.Telephony.Config = {
2
+ PUSHER_APP_KEY: "<%= PUSHER_CONFIG['app_key'] %>",
3
+ STYLESHEET_PATH: "<%= asset_path('telephony/widget.css') %>",
4
+ BASE_PATH: "/zestphone",
5
+ AGENT_PATH: "/zestphone/agents",
6
+ TWILIO_CLIENT_ENABLED: $('#telephony-widget').data('agent_phone_type') == 'twilio_client',
7
+ TWILIO_CLIENT_URL: "//static.twilio.com/libs/twiliojs/1.1/twilio.min.js",
8
+ TWILIO_CLIENT_TOKEN_PATH: "/zestphone/twilio_client/token",
9
+ CONVERSATION_PATH: "/zestphone/conversations"
10
+ }
11
+ Pusher.channel_auth_endpoint = "/zestphone/signals/agents/presences/authenticate";
@@ -0,0 +1,103 @@
1
+ Zest.Telephony.Models.Agent = Backbone.Model.extend({
2
+ urlRoot: Zest.Telephony.Config.AGENT_PATH,
3
+
4
+ defaults: {
5
+ "status": "loading...",
6
+ "in_transition": false
7
+ },
8
+
9
+ url: function() {
10
+ return this.urlRoot + "/" + this.get("csr_id");
11
+ },
12
+
13
+ statusUrl: function() {
14
+ return this.url() + "/status"
15
+ },
16
+
17
+ status: function() {
18
+ return this.get('status');
19
+ },
20
+
21
+ displayText: function() {
22
+ return this.get("csr_type") + " " + this.get("name") + " x" + this.get("phone_ext");
23
+ },
24
+
25
+ toggleAvailable: function() {
26
+ this.updateStatus("toggle_available");
27
+ },
28
+
29
+ updateStatus: function(eventName) {
30
+ var data = {};
31
+ data["event"] = eventName || "not_available";
32
+
33
+ this.set({in_transition: true});
34
+ $.ajax(this.statusUrl(), {data: data, type: 'PUT'})
35
+ .done($.proxy(this.done, this))
36
+ .fail($.proxy(this.fail, this))
37
+ .always($.proxy(this.always, this));
38
+ },
39
+
40
+ isValid: function(opts) {
41
+ opts = opts || {}
42
+ $.ajax(this.url(), {data: this.toJSON(), type: 'PUT'})
43
+ .done(opts['done_callback'])
44
+ .fail(opts['fail_callback']);
45
+ },
46
+
47
+ disabled: function() {
48
+ if (this.onACall() || this.offline() || this.inTransition()) {
49
+ return "disabled";
50
+ }
51
+ return "";
52
+ },
53
+
54
+ display: function() {
55
+ if (this.inTransition() || this.loading()) {
56
+ return "display";
57
+ }
58
+ return "";
59
+ },
60
+
61
+ inTransition: function() {
62
+ return this.get("in_transition");
63
+ },
64
+
65
+ onACall: function() {
66
+ return this.get("status") === "on_a_call";
67
+ },
68
+
69
+ offline: function() {
70
+ return this.get("status") === "offline";
71
+ },
72
+
73
+ available: function() {
74
+ return this.get("status") === "available";
75
+ },
76
+
77
+ notAvailable: function() {
78
+ return this.get("status") === "not_available";
79
+ },
80
+
81
+ loading: function() {
82
+ return this.get("status") === "loading...";
83
+ },
84
+
85
+ humanizedStatus: function() {
86
+ var humanizedStatus = this.get('status').replace(/_/g, ' ');
87
+ return humanizedStatus.substr(0,1).toUpperCase() + humanizedStatus.substr(1);
88
+ },
89
+
90
+ done: function(data) {
91
+ this.set(data, {silent: true});
92
+ },
93
+
94
+ always: function() {
95
+ this.set({in_transition: false});
96
+ },
97
+
98
+ fail: function(xhr, textStatus, errorThrown) {
99
+ if (typeof console === "object" && typeof console.log === "function") {
100
+ console.log("Unable to update the status: " + textStatus);
101
+ }
102
+ }
103
+ });
@@ -0,0 +1,226 @@
1
+ Zest.Telephony.Models.Conversation = Backbone.Model.extend({
2
+ urlRoot: Zest.Telephony.Config.CONVERSATION_PATH,
3
+
4
+ defaults: {
5
+ state: "disabled_by_default"
6
+ },
7
+
8
+ initialize: function() {
9
+ this.enabledHoldStates = ['in_progress', 'in_progress_two_step_transfer'];
10
+ this.disabledHoldStates = ['two_step_transferring', 'initiating_hold', 'agents_only'];
11
+ this.enabledResumeStates = ['in_progress_hold', 'in_progress_two_step_transfer_hold'];
12
+ this.disabledResumeStates = ['initiating_resume', 'initiating_two_step_transfer_resume', 'two_step_transferring_hold'];
13
+ },
14
+
15
+ create: function() {
16
+ var data = {
17
+ loan_id: this.get("loanId"),
18
+ from: this.get("from"),
19
+ from_id: this.get("fromId"),
20
+ from_type: 'csr',
21
+ to: this.get("to"),
22
+ to_id: this.get("toId"),
23
+ to_type: this.get("toType"),
24
+ owner: this.get("owner")
25
+ };
26
+
27
+ this.set({ state: "connecting" });
28
+ $.ajax(this.url(), { data: data, type: 'POST' })
29
+ .done($.proxy(this.createSuccess, this))
30
+ .fail($.proxy(this.createFail, this));
31
+ },
32
+
33
+ hold: function() {
34
+ var oldState = this.get('state');
35
+ var newState = "initiating_hold";
36
+
37
+ this.set({ state: newState });
38
+ $.ajax(this.url() + "/hold", {type: 'POST'})
39
+ .done($.proxy(this.holdResumeSuccess, this))
40
+ .fail($.proxy(this.holdResumeFail(oldState, newState), this));
41
+ },
42
+
43
+ resume: function() {
44
+ var oldState = this.get('state');
45
+ var newState = "initiating_resume";
46
+
47
+ this.set({ state: newState });
48
+ $.ajax(this.url() + "/resume", {type: 'POST'})
49
+ .done($.proxy(this.holdResumeSuccess, this))
50
+ .fail($.proxy(this.holdResumeFail(oldState, newState), this));
51
+ },
52
+
53
+ holdResumeSuccess: function(data) {
54
+ this.set(data);
55
+ },
56
+
57
+ holdResumeFail: function(oldState, newState) {
58
+ return function(xhr, textStatus, errorThrown) {
59
+ var body = JSON.parse(xhr.responseText);
60
+ var data = {
61
+ error: body.errors[0],
62
+ oldState: oldState,
63
+ newState: newState
64
+ };
65
+
66
+ $(document).trigger('holdResumeFail', data);
67
+ };
68
+ },
69
+
70
+ uiDisableCall: function() {
71
+ if (this.get('callingDisabled')) {
72
+ return 'disabled';
73
+ }
74
+
75
+ return this._enabledForStates(['not_initiated', 'terminated']);
76
+ },
77
+
78
+ uiShowCall: function() {
79
+ var displayableStates = ['not_initiated', 'connecting', 'terminated'];
80
+ return this._shownForStates(displayableStates);
81
+ },
82
+
83
+ uiDisableTransfer: function() {
84
+ var enabledStates = ['in_progress', 'in_progress_hold'];
85
+ return this._enabledForStates(enabledStates);
86
+ },
87
+
88
+ uiShowTransfer: function() {
89
+ if (this.get("isCancelable")) {
90
+ return "hidden";
91
+ }
92
+
93
+ var displayableStates = [
94
+ 'in_progress',
95
+ 'one_step_transferring',
96
+ 'two_step_transferring',
97
+ 'two_step_transferring_hold',
98
+ 'initiating_two_step_transfer_resume',
99
+ 'initiating_two_step_transfer_hold',
100
+ 'in_progress_two_step_transfer_hold',
101
+ 'in_progress_two_step_transfer',
102
+ 'initiating_hold',
103
+ 'in_progress_hold',
104
+ 'initiating_resume',
105
+ 'agents_only'
106
+ ];
107
+ return this._shownForStates(displayableStates);
108
+ },
109
+
110
+ uiShowCancelTransfer: function() {
111
+ return this.get("isCancelable") ? "" : "hidden";
112
+ },
113
+
114
+ uiShowResume: function() {
115
+ var displayableStates = this.enabledResumeStates.concat(this.disabledResumeStates);
116
+ return this._shownForStates(displayableStates);
117
+ },
118
+
119
+ uiDisableResume: function() {
120
+ return this._disabledIf(this._statesIn(this.disabledResumeStates) || !this.get("owner"));
121
+ },
122
+
123
+ uiShowHold: function() {
124
+ var displayableStates = this.enabledHoldStates.concat(this.disabledHoldStates);
125
+ return this._shownForStates(displayableStates);
126
+ },
127
+
128
+ uiDisableHold: function() {
129
+ return this._disabledIf(this._statesIn(this.disabledHoldStates) || !this.get("owner"));
130
+ },
131
+
132
+ uiShowCallSpinner: function() {
133
+ return this._displayedForStates(['connecting']);
134
+ },
135
+
136
+ uiShowHoldSpinner: function() {
137
+ return this._displayedForStates(['initiating_hold']);
138
+ },
139
+
140
+ uiShowResumeSpinner: function() {
141
+ return this._displayedForStates(['initiating_resume']);
142
+ },
143
+
144
+ state: function() {
145
+ return this.get("state");
146
+ },
147
+
148
+ createSuccess: function(data) {
149
+ $(document).trigger("telephony:conversationCreated", data);
150
+ this.set(data);
151
+ },
152
+
153
+ createFail: function(xhr, textStatus, errorThrown) {
154
+ var body = JSON.parse(xhr.responseText);
155
+ $(document).trigger('callFailed', body.errors[0]);
156
+ },
157
+
158
+
159
+ fail: function(xhr, textStatus, errorThrown) {
160
+ if (typeof console === "object" && typeof console.log === "function") {
161
+ console.log("Unable to complete the request: " + textStatus);
162
+ }
163
+ },
164
+
165
+ isValid: function() {
166
+ if (! this._isValidPhoneNumberFormat()) {
167
+ return false;
168
+ }
169
+ if (! this._isValidCallee()) {
170
+ return false;
171
+ }
172
+ return true;
173
+ },
174
+
175
+ _disabledIf: function(disable) {
176
+ return disable ? 'disabled' : '';
177
+ },
178
+
179
+ _statesIn: function(states) {
180
+ return _.contains(states, this.state());
181
+ },
182
+
183
+ _disabledForStates: function(states) {
184
+ return this._disabledIf(this._statesIn(states));
185
+ },
186
+
187
+ _enabledForStates: function(states) {
188
+ return _.contains(states, this.state()) ? '' : 'disabled';
189
+ },
190
+
191
+ _shownForStates: function(states) {
192
+ return _.contains(states, this.state()) ? '' : 'hidden';
193
+ },
194
+
195
+ _displayedForStates: function(states) {
196
+ return _.contains(states, this.state()) ? 'display' : '';
197
+ },
198
+
199
+ _isValidPhoneNumberFormat: function() {
200
+ var phoneNumber = this._normalizePhoneNumber(this.get('to'));
201
+ if (phoneNumber.length !== 10) {
202
+ this.errorMessage = 'Please enter a 10-digit phone number';
203
+ return false;
204
+ }
205
+ return true;
206
+ },
207
+
208
+ _isValidCallee: function() {
209
+ var from = this._normalizePhoneNumber(this.get('from'));
210
+ var to = this._normalizePhoneNumber(this.get('to'));
211
+ if (from === to) {
212
+ this.errorMessage = 'Please input a customer phone number';
213
+ return false;
214
+ }
215
+ return true;
216
+ },
217
+
218
+ _normalizePhoneNumber: function(phoneNumber) {
219
+ var number = phoneNumber || '';
220
+ var digits = number.toString().replace(/\D/g, '');
221
+ if (digits[0] === '1') {
222
+ digits = digits.slice(1);
223
+ }
224
+ return digits;
225
+ }
226
+ });