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
@@ -0,0 +1,64 @@
1
+ module Telephony
2
+ module Signals
3
+ module Agents
4
+ class PresencesController < ApplicationController
5
+ protect_from_forgery :except => [:authenticate, :create]
6
+ before_filter :verify_pusher_request_signature, :except => :authenticate
7
+ after_filter :publish_events
8
+
9
+ def authenticate
10
+ @agent = Agent.find_by_csr_id params[:csr_id]
11
+
12
+ @agent.verify_status!
13
+
14
+ @agent.with_lock do
15
+ if @agent.offline?
16
+ if params[:csr_default_status] == 'not_available'
17
+ @agent.not_available
18
+ else
19
+ @agent.available
20
+ end
21
+ end
22
+ end
23
+
24
+ response = Pusher[params[:channel_name]]
25
+ .authenticate(params[:socket_id], user_id: @agent.id)
26
+ render json: response
27
+ end
28
+
29
+ def create
30
+ timestamp = params["presence"]["time_ms"].to_i
31
+ params[:presence][:events].each do |data|
32
+ @agent = Agent.where(id: data[:user_id]).first
33
+ if @agent
34
+ @agent.with_lock do
35
+ @agent.process_presence_event(data[:name], timestamp)
36
+ end
37
+ end
38
+ end
39
+ head :ok
40
+ end
41
+
42
+ private
43
+
44
+ def verify_pusher_request_signature
45
+ req = Pusher::WebHook.new request
46
+ unless req.valid?
47
+ render :text => "Bad Pusher signature", :status => :unauthorized and return
48
+ end
49
+ end
50
+
51
+ def publish_events
52
+ return if @agent.nil?
53
+
54
+ event = Events::Base.find_last_for_agent(@agent)
55
+ event.republish_only_for @agent
56
+
57
+ @agent.publish_status_change unless @agent.offline?
58
+
59
+ PusherEventPublisher.queue_change Conversation.queue_size, event.id, @agent
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,18 @@
1
+ require_dependency 'telephony/application_controller'
2
+
3
+ module Telephony
4
+ class TransfersController < ApplicationController
5
+ def create
6
+ Conversation.find_with_lock params[:conversation_id] do |conversation|
7
+ if conversation.transfer! params[:transfer_id], params[:transfer_type] == 'one_step'
8
+ render json: {}
9
+ else
10
+ errors = ['Transfer failed. Please try again in a few seconds.'] +
11
+ conversation.errors.full_messages
12
+
13
+ render json: { errors: errors }, status: :unprocessable_entity
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ require_dependency "telephony/application_controller"
2
+
3
+ module Telephony
4
+ class TwilioClientController < ApplicationController
5
+ before_filter :validate_params, only: :token
6
+
7
+ def index
8
+ @token = generate_token_for("agent#{params['csr-id']}")
9
+ render layout: false
10
+ end
11
+
12
+ def token
13
+ token = generate_token_for("agent#{params['csr_id']}")
14
+ render json: { token: token }
15
+ end
16
+
17
+ private
18
+
19
+ def validate_params
20
+ if params['csr_id'].blank?
21
+ render json: { errors: ['Invalid csr id'] },
22
+ status: :bad_request
23
+ end
24
+ end
25
+
26
+ def generate_token_for client_name
27
+ account_sid = TWILIO_CONFIG[:account_sid]
28
+ auth_token = TWILIO_CONFIG[:auth_token]
29
+
30
+ capability = Twilio::Util::Capability.new account_sid, auth_token
31
+ # TODO: Get the correct value
32
+ capability.allow_client_outgoing "APabe7650f654fc34655fc81ae71caa3ff"
33
+ capability.allow_client_incoming client_name
34
+ capability.generate
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ require_dependency 'telephony/application_controller'
2
+
3
+ module Telephony
4
+ class VoicemailsController < ApplicationController
5
+ def index
6
+ @voicemails = Voicemail.most_recent.filter params.slice(:page, :csr_id)
7
+ render json: {
8
+ items: @voicemails,
9
+ total_count: @voicemails.total_count
10
+ }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ require_dependency "telephony/application_controller"
2
+
3
+ module Telephony
4
+ class WidgetController < ApplicationController
5
+
6
+ def index
7
+ render layout: false, content_type: 'application/json'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ module Telephony
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,13 @@
1
+ module Telephony
2
+ module CallsHelper
3
+ def twiml_word_for call
4
+ if call.agent && call.agent.uses_twilio_client?
5
+ :Client
6
+ elsif call.agent && call.agent.uses_sip?
7
+ :Sip
8
+ else
9
+ :Number
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,186 @@
1
+ module Telephony
2
+ class Agent < Base
3
+ include AgentStateMachine
4
+
5
+ OFFLINE = "offline"
6
+ AVAILABLE = "available"
7
+ NOT_AVAILABLE = "not_available"
8
+ ON_A_CALL = "on_a_call"
9
+
10
+ STATUS_SORT_ORDER = {
11
+ AVAILABLE => 0,
12
+ ON_A_CALL => 1,
13
+ NOT_AVAILABLE => 2,
14
+ OFFLINE => 3
15
+ }
16
+
17
+ module PhoneType
18
+ PHONE = "phone"
19
+ TWILIO_CLIENT = "twilio_client"
20
+ SIP = "sip"
21
+
22
+ ALL = [PHONE, TWILIO_CLIENT, SIP]
23
+ end
24
+
25
+ PhoneType::ALL.each do |type|
26
+ define_method "uses_#{type}?".to_sym do
27
+ phone_type == type
28
+ end
29
+ end
30
+
31
+ has_many :calls
32
+
33
+ attr_accessible :csr_id,
34
+ :status,
35
+ :generate_caller_id,
36
+ :name,
37
+ :phone_ext,
38
+ :phone_number,
39
+ :phone_type,
40
+ :csr_type,
41
+ :sip_number,
42
+ :call_center_name,
43
+ :transferable_agents
44
+
45
+ serialize :transferable_agents, Array
46
+
47
+ validates :csr_id, presence: true
48
+ validates :phone_type, inclusion: { :in => PhoneType::ALL }
49
+ validates :sip_number, presence: true, :if => :uses_sip?
50
+ validates :phone_number, presence: true, :if => :uses_phone?
51
+
52
+ def self.sort_by_status agents
53
+ agents.sort_by { |agent| STATUS_SORT_ORDER[agent.status] }
54
+ end
55
+
56
+ def self.all_transferable_for_csr_id csr_id
57
+ agent = find_by_csr_id csr_id
58
+
59
+ transferables = select([:id, :csr_id, :csr_type, :status, :name, :phone_ext, :phone_number])
60
+
61
+ if agent && agent.transferable_agents.count > 0
62
+ transferables = transferables.where(csr_id: agent.transferable_agents)
63
+ end
64
+
65
+ sort_by_status(transferables)
66
+ end
67
+
68
+ def self.update_or_create_by_widget_data data
69
+ agent = find_or_create_by_csr_id data[:csr_id]
70
+ agent.update_attributes({
71
+ csr_type: data[:csr_type],
72
+ name: data[:csr_name],
73
+ generate_caller_id: data[:csr_generate_caller_id],
74
+ phone_number: data[:csr_phone_number],
75
+ phone_ext: data[:csr_phone_ext],
76
+ sip_number: data[:csr_sip_number],
77
+ call_center_name: data[:csr_call_center_name],
78
+ phone_type: data[:csr_phone_type].present? ? data[:csr_phone_type] : PhoneType::PHONE,
79
+ transferable_agents: data[:csr_transferable_agents].present? ? JSON.parse(data[:csr_transferable_agents]) : []
80
+ })
81
+ agent
82
+ end
83
+
84
+ def self.find_with_lock agent_id
85
+ transaction do
86
+ yield find agent_id, lock: true
87
+ end
88
+ end
89
+
90
+ def transferrable?
91
+ available?
92
+ end
93
+
94
+ def process_presence_event event, timestamp
95
+ if event == 'member_added'
96
+ update_status_by_event :came_online, timestamp
97
+ else
98
+ job = Telephony::Jobs::AgentOffline.new id, timestamp
99
+ if Telephony::DELAYED_JOB.respond_to?(:enqueue)
100
+ Telephony::DELAYED_JOB.enqueue job, run_at: 15.minutes.from_now
101
+ else
102
+ job.update_status
103
+ end
104
+ end
105
+ end
106
+
107
+ def update_status_by_event presence_event, timestamp
108
+ return if timestamp_of_last_presence_event > timestamp
109
+
110
+ self.send presence_event.to_sym
111
+ self.update_attribute :timestamp_of_last_presence_event, timestamp
112
+ end
113
+
114
+ def terminate_active_call
115
+ active_call.terminate! if active_call
116
+ end
117
+
118
+ def active_call
119
+ calls.where("state <> 'terminated'").last
120
+ end
121
+
122
+ def active_conversation_id
123
+ return nil unless on_a_call?
124
+ call = active_call
125
+ call.conversation_id if call
126
+ end
127
+
128
+ def verify_status!
129
+ return unless on_a_call?
130
+ call = active_call
131
+
132
+ if call.nil?
133
+ with_lock { call_ended! if (active_call.nil? and on_a_call?) }
134
+ elsif call.created_at < 5.minutes.ago && Telephony.provider.call_ended?(call.sid)
135
+ Conversation.find_with_lock(call.conversation_id) do
136
+ call.reload.terminate
137
+ end
138
+ end
139
+ end
140
+
141
+ def publish_status_change
142
+ PusherEventPublisher.publish channel: "csrs-#{csr_id}",
143
+ name: 'statusChange',
144
+ data: {
145
+ status: status,
146
+ timestamp: Integer(Time.now.to_f * 1000)
147
+ }
148
+ end
149
+
150
+ def number with_protocol = false
151
+ case phone_type
152
+ when PhoneType::SIP
153
+ sip_string with_protocol
154
+ when PhoneType::TWILIO_CLIENT
155
+ client_string with_protocol
156
+ else
157
+ phone_number
158
+ end
159
+ end
160
+
161
+ def encode_with coder
162
+ coder['attributes'] = {
163
+ 'id' => attributes['id'],
164
+ 'csr_id' => attributes['csr_id']
165
+ }
166
+ end
167
+
168
+ private
169
+
170
+ def client_string with_protocol = false
171
+ "#{'client:' if with_protocol}agent#{csr_id}"
172
+ end
173
+
174
+ def sip_string with_protocol = false
175
+ "#{'sip:' if with_protocol}#{sip_address}"
176
+ end
177
+
178
+ def sip_address
179
+ "#{sip_number}@#{call_center.host}"
180
+ end
181
+
182
+ def call_center
183
+ CallCenter.find_by_name call_center_name
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,54 @@
1
+ require 'state_machine'
2
+
3
+ module Telephony
4
+ module AgentStateMachine
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ state_machine :status, :initial => :offline do
9
+ event :available do
10
+ transition any => :available
11
+ end
12
+
13
+ event :came_online do
14
+ transition :offline => :available
15
+ end
16
+
17
+ event :offline do
18
+ transition all - [:offline] => :offline
19
+ end
20
+
21
+ event :on_a_call do
22
+ transition :available => :on_a_call
23
+ transition :not_available => :on_a_call
24
+ end
25
+
26
+ event :not_available do
27
+ transition [:offline, :available] => :not_available
28
+ end
29
+
30
+ event :call_ended do
31
+ transition :on_a_call => :not_available
32
+ end
33
+
34
+ event :toggle_available do
35
+ transition :available => :not_available
36
+ transition :not_available => :available
37
+ end
38
+
39
+ after_transition do |agent, transition|
40
+ unless transition.loopback?
41
+ ActiveSupport::Notifications
42
+ .instrument("telephony.agent_status_change",
43
+ {
44
+ agent_id: agent.id,
45
+ csr_id: agent.csr_id,
46
+ status: transition.to,
47
+ timestamp: Time.now.to_i
48
+ })
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,7 @@
1
+ module Telephony
2
+ class Base < ActiveRecord::Base
3
+ establish_connection "telephony_#{Rails.env}"
4
+
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Telephony
2
+ class BlacklistedNumber < ActiveRecord::Base
3
+ attr_accessible :number
4
+ end
5
+ end
@@ -0,0 +1,94 @@
1
+ module Telephony
2
+ class Call < Base
3
+ include NumberHelper
4
+ include CallStateMachine
5
+
6
+ belongs_to :conversation
7
+ has_many :recordings
8
+ has_one :voicemail
9
+ has_many :events, class_name: 'Events::Base'
10
+ belongs_to :agent
11
+
12
+ attr_accessible :sid,
13
+ :number,
14
+ :state,
15
+ :participant_id,
16
+ :participant_type,
17
+ :connected_at,
18
+ :terminated_at,
19
+ :agent
20
+
21
+ def number with_protocol = false
22
+ if agent
23
+ agent.number with_protocol
24
+ else
25
+ customer_number
26
+ end
27
+ end
28
+
29
+ def sanitized_number
30
+ normalize_number(number)
31
+ end
32
+
33
+ def make!
34
+ provider_call = Telephony.provider.call id, number(true), caller_id
35
+ update_attribute :sid, provider_call.sid
36
+ end
37
+
38
+ def dial_into_conference!
39
+ provider_call = Telephony.provider.dial_into_conference id, number(true), caller_id
40
+ update_attribute :sid, provider_call.sid
41
+ end
42
+
43
+ def caller_id
44
+ conversation.caller_id
45
+ end
46
+
47
+ def whitelisted_number?
48
+ agent || number != Telephony.provider.uncallable_number
49
+ end
50
+
51
+ def record!(options)
52
+ if options.include?(:RecordingUrl) && options.include?(:RecordingDuration)
53
+ recordings.create url: options[:RecordingUrl],
54
+ duration: options[:RecordingDuration],
55
+ start_time: options[:RecordingDuration].to_i.seconds.ago
56
+ end
57
+ end
58
+
59
+ def recorded?
60
+ agent.nil?
61
+ end
62
+
63
+ def redirect_to_dial
64
+ Telephony.provider.redirect_to_dial id, sid
65
+ end
66
+
67
+ def redirect_to_inbound_connect csr_id
68
+ Telephony.provider.redirect_to_inbound_connect csr_id, sid
69
+ end
70
+
71
+ def redirect_to_conference
72
+ Telephony.provider.redirect_to_conference id, sid
73
+ end
74
+
75
+ def redirect_to_hold
76
+ Telephony.provider.redirect_to_hold id, sid
77
+ end
78
+
79
+ def agent?
80
+ agent.present?
81
+ end
82
+
83
+ private
84
+
85
+ def number_without_whitelisting
86
+ self[:number]
87
+ end
88
+
89
+ def customer_number
90
+ number_without_whitelisting &&
91
+ Telephony.with_whitelisting(number_without_whitelisting)
92
+ end
93
+ end
94
+ end