telephony 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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,1304 @@
1
+ /*!
2
+ * Pusher JavaScript Library v1.12.5
3
+ * http://pusherapp.com/
4
+ *
5
+ * Copyright 2011, Pusher
6
+ * Released under the MIT licence.
7
+ */
8
+
9
+ ;(function() {
10
+ if (Function.prototype.scopedTo === undefined) {
11
+ Function.prototype.scopedTo = function(context, args) {
12
+ var f = this;
13
+ return function() {
14
+ return f.apply(context, Array.prototype.slice.call(args || [])
15
+ .concat(Array.prototype.slice.call(arguments)));
16
+ };
17
+ };
18
+ }
19
+
20
+ var Pusher = function(app_key, options) {
21
+ this.options = options || {};
22
+ this.key = app_key;
23
+ this.channels = new Pusher.Channels();
24
+ this.global_emitter = new Pusher.EventsDispatcher()
25
+
26
+ var self = this;
27
+
28
+ this.checkAppKey();
29
+
30
+ this.connection = new Pusher.Connection(this.key, this.options);
31
+
32
+ // Setup / teardown connection
33
+ this.connection
34
+ .bind('connected', function() {
35
+ self.subscribeAll();
36
+ })
37
+ .bind('message', function(params) {
38
+ var internal = (params.event.indexOf('pusher_internal:') === 0);
39
+ if (params.channel) {
40
+ var channel;
41
+ if (channel = self.channel(params.channel)) {
42
+ channel.emit(params.event, params.data);
43
+ }
44
+ }
45
+ // Emit globaly [deprecated]
46
+ if (!internal) self.global_emitter.emit(params.event, params.data);
47
+ })
48
+ .bind('disconnected', function() {
49
+ self.channels.disconnect();
50
+ })
51
+ .bind('error', function(err) {
52
+ Pusher.warn('Error', err);
53
+ });
54
+
55
+ Pusher.instances.push(this);
56
+
57
+ if (Pusher.isReady) self.connect();
58
+ };
59
+ Pusher.instances = [];
60
+ Pusher.prototype = {
61
+ channel: function(name) {
62
+ return this.channels.find(name);
63
+ },
64
+
65
+ connect: function() {
66
+ this.connection.connect();
67
+ },
68
+
69
+ disconnect: function() {
70
+ this.connection.disconnect();
71
+ },
72
+
73
+ bind: function(event_name, callback) {
74
+ this.global_emitter.bind(event_name, callback);
75
+ return this;
76
+ },
77
+
78
+ bind_all: function(callback) {
79
+ this.global_emitter.bind_all(callback);
80
+ return this;
81
+ },
82
+
83
+ subscribeAll: function() {
84
+ var channel;
85
+ for (channelName in this.channels.channels) {
86
+ if (this.channels.channels.hasOwnProperty(channelName)) {
87
+ this.subscribe(channelName);
88
+ }
89
+ }
90
+ },
91
+
92
+ subscribe: function(channel_name) {
93
+ var self = this;
94
+ var channel = this.channels.add(channel_name, this);
95
+
96
+ if (this.connection.state === 'connected') {
97
+ channel.authorize(this.connection.socket_id, this.options, function(err, data) {
98
+ if (err) {
99
+ channel.emit('pusher:subscription_error', data);
100
+ } else {
101
+ self.send_event('pusher:subscribe', {
102
+ channel: channel_name,
103
+ auth: data.auth,
104
+ channel_data: data.channel_data
105
+ });
106
+ }
107
+ });
108
+ }
109
+ return channel;
110
+ },
111
+
112
+ unsubscribe: function(channel_name) {
113
+ this.channels.remove(channel_name);
114
+ if (this.connection.state === 'connected') {
115
+ this.send_event('pusher:unsubscribe', {
116
+ channel: channel_name
117
+ });
118
+ }
119
+ },
120
+
121
+ send_event: function(event_name, data, channel) {
122
+ return this.connection.send_event(event_name, data, channel);
123
+ },
124
+
125
+ checkAppKey: function() {
126
+ if(this.key === null || this.key === undefined) {
127
+ Pusher.warn('Warning', 'You must pass your app key when you instantiate Pusher.');
128
+ }
129
+ }
130
+ };
131
+
132
+ Pusher.Util = {
133
+ extend: function extend(target, extensions) {
134
+ for (var property in extensions) {
135
+ if (extensions[property] && extensions[property].constructor &&
136
+ extensions[property].constructor === Object) {
137
+ target[property] = extend(target[property] || {}, extensions[property]);
138
+ } else {
139
+ target[property] = extensions[property];
140
+ }
141
+ }
142
+ return target;
143
+ },
144
+
145
+ stringify: function stringify() {
146
+ var m = ["Pusher"]
147
+ for (var i = 0; i < arguments.length; i++){
148
+ if (typeof arguments[i] === "string") {
149
+ m.push(arguments[i])
150
+ } else {
151
+ if (window['JSON'] == undefined) {
152
+ m.push(arguments[i].toString());
153
+ } else {
154
+ m.push(JSON.stringify(arguments[i]))
155
+ }
156
+ }
157
+ };
158
+ return m.join(" : ")
159
+ },
160
+
161
+ arrayIndexOf: function(array, item) { // MSIE doesn't have array.indexOf
162
+ var nativeIndexOf = Array.prototype.indexOf;
163
+ if (array == null) return -1;
164
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
165
+ for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
166
+ return -1;
167
+ }
168
+ };
169
+
170
+ // To receive log output provide a Pusher.log function, for example
171
+ // Pusher.log = function(m){console.log(m)}
172
+ Pusher.debug = function() {
173
+ if (!Pusher.log) return
174
+ Pusher.log(Pusher.Util.stringify.apply(this, arguments))
175
+ }
176
+ Pusher.warn = function() {
177
+ if (window.console && window.console.warn) {
178
+ window.console.warn(Pusher.Util.stringify.apply(this, arguments));
179
+ } else {
180
+ if (!Pusher.log) return
181
+ Pusher.log(Pusher.Util.stringify.apply(this, arguments));
182
+ }
183
+ };
184
+
185
+ // Pusher defaults
186
+ Pusher.VERSION = '1.12.5';
187
+ // WS connection parameters
188
+ Pusher.host = 'ws.pusherapp.com';
189
+ Pusher.ws_port = 80;
190
+ Pusher.wss_port = 443;
191
+ // SockJS fallback parameters
192
+ Pusher.sockjs_host = 'sockjs.pusher.com';
193
+ Pusher.sockjs_http_port = 80
194
+ Pusher.sockjs_https_port = 443
195
+ Pusher.sockjs_path = "/pusher"
196
+ // Other settings
197
+ Pusher.channel_auth_endpoint = '/pusher/auth';
198
+ Pusher.cdn_http = 'http://js.pusher.com/'
199
+ Pusher.cdn_https = 'https://d3dy5gmtp8yhk7.cloudfront.net/'
200
+ Pusher.dependency_suffix = '';
201
+ Pusher.channel_auth_transport = 'ajax';
202
+ Pusher.activity_timeout = 120000;
203
+ Pusher.pong_timeout = 30000;
204
+
205
+ Pusher.isReady = false;
206
+ Pusher.ready = function() {
207
+ Pusher.isReady = true;
208
+ for (var i = 0, l = Pusher.instances.length; i < l; i++) {
209
+ Pusher.instances[i].connect();
210
+ }
211
+ };
212
+
213
+ this.Pusher = Pusher;
214
+ }).call(this);
215
+
216
+ ;(function() {
217
+ /* Abstract event binding
218
+ Example:
219
+
220
+ var MyEventEmitter = function(){};
221
+ MyEventEmitter.prototype = new Pusher.EventsDispatcher;
222
+
223
+ var emitter = new MyEventEmitter();
224
+
225
+ // Bind to single event
226
+ emitter.bind('foo_event', function(data){ alert(data)} );
227
+
228
+ // Bind to all
229
+ emitter.bind_all(function(eventName, data){ alert(data) });
230
+
231
+ --------------------------------------------------------*/
232
+
233
+ function CallbackRegistry() {
234
+ this._callbacks = {};
235
+ };
236
+
237
+ CallbackRegistry.prototype.get = function(eventName) {
238
+ return this._callbacks[this._prefix(eventName)];
239
+ };
240
+
241
+ CallbackRegistry.prototype.add = function(eventName, callback) {
242
+ var prefixedEventName = this._prefix(eventName);
243
+ this._callbacks[prefixedEventName] = this._callbacks[prefixedEventName] || [];
244
+ this._callbacks[prefixedEventName].push(callback);
245
+ };
246
+
247
+ CallbackRegistry.prototype.remove = function(eventName, callback) {
248
+ if(this.get(eventName)) {
249
+ var index = Pusher.Util.arrayIndexOf(this.get(eventName), callback);
250
+ this._callbacks[this._prefix(eventName)].splice(index, 1);
251
+ }
252
+ };
253
+
254
+ CallbackRegistry.prototype._prefix = function(eventName) {
255
+ return "_" + eventName;
256
+ };
257
+
258
+
259
+ function EventsDispatcher(failThrough) {
260
+ this.callbacks = new CallbackRegistry();
261
+ this.global_callbacks = [];
262
+ // Run this function when dispatching an event when no callbacks defined
263
+ this.failThrough = failThrough;
264
+ }
265
+
266
+ EventsDispatcher.prototype.bind = function(eventName, callback) {
267
+ this.callbacks.add(eventName, callback);
268
+ return this;// chainable
269
+ };
270
+
271
+ EventsDispatcher.prototype.unbind = function(eventName, callback) {
272
+ this.callbacks.remove(eventName, callback);
273
+ return this;
274
+ };
275
+
276
+ EventsDispatcher.prototype.emit = function(eventName, data) {
277
+ // Global callbacks
278
+ for (var i = 0; i < this.global_callbacks.length; i++) {
279
+ this.global_callbacks[i](eventName, data);
280
+ }
281
+
282
+ // Event callbacks
283
+ var callbacks = this.callbacks.get(eventName);
284
+ if (callbacks) {
285
+ for (var i = 0; i < callbacks.length; i++) {
286
+ callbacks[i](data);
287
+ }
288
+ } else if (this.failThrough) {
289
+ this.failThrough(eventName, data)
290
+ }
291
+
292
+ return this;
293
+ };
294
+
295
+ EventsDispatcher.prototype.bind_all = function(callback) {
296
+ this.global_callbacks.push(callback);
297
+ return this;
298
+ };
299
+
300
+ this.Pusher.EventsDispatcher = EventsDispatcher;
301
+ }).call(this);
302
+
303
+ ;(function() {
304
+ var Pusher = this.Pusher;
305
+
306
+ /*-----------------------------------------------
307
+ Helpers:
308
+ -----------------------------------------------*/
309
+
310
+ function capitalize(str) {
311
+ return str.substr(0, 1).toUpperCase() + str.substr(1);
312
+ }
313
+
314
+
315
+ function safeCall(method, obj, data) {
316
+ if (obj[method] !== undefined) {
317
+ obj[method](data);
318
+ }
319
+ }
320
+
321
+ /*-----------------------------------------------
322
+ The State Machine
323
+ -----------------------------------------------*/
324
+ function Machine(initialState, transitions, stateActions) {
325
+ Pusher.EventsDispatcher.call(this);
326
+
327
+ this.state = undefined;
328
+ this.errors = [];
329
+
330
+ // functions for each state
331
+ this.stateActions = stateActions;
332
+
333
+ // set up the transitions
334
+ this.transitions = transitions;
335
+
336
+ this.transition(initialState);
337
+ };
338
+
339
+ Machine.prototype.transition = function(nextState, data) {
340
+ var prevState = this.state;
341
+ var stateCallbacks = this.stateActions;
342
+
343
+ if (prevState && (Pusher.Util.arrayIndexOf(this.transitions[prevState], nextState) == -1)) {
344
+ this.emit('invalid_transition_attempt', {
345
+ oldState: prevState,
346
+ newState: nextState
347
+ });
348
+
349
+ throw new Error('Invalid transition [' + prevState + ' to ' + nextState + ']');
350
+ }
351
+
352
+ // exit
353
+ safeCall(prevState + 'Exit', stateCallbacks, data);
354
+
355
+ // tween
356
+ safeCall(prevState + 'To' + capitalize(nextState), stateCallbacks, data);
357
+
358
+ // pre
359
+ safeCall(nextState + 'Pre', stateCallbacks, data);
360
+
361
+ // change state:
362
+ this.state = nextState;
363
+
364
+ // handy to bind to
365
+ this.emit('state_change', {
366
+ oldState: prevState,
367
+ newState: nextState
368
+ });
369
+
370
+ // Post:
371
+ safeCall(nextState + 'Post', stateCallbacks, data);
372
+ };
373
+
374
+ Machine.prototype.is = function(state) {
375
+ return this.state === state;
376
+ };
377
+
378
+ Machine.prototype.isNot = function(state) {
379
+ return this.state !== state;
380
+ };
381
+
382
+ Pusher.Util.extend(Machine.prototype, Pusher.EventsDispatcher.prototype);
383
+
384
+ this.Pusher.Machine = Machine;
385
+ }).call(this);
386
+
387
+ ;(function() {
388
+ /*
389
+ A little bauble to interface with window.navigator.onLine,
390
+ window.ononline and window.onoffline. Easier to mock.
391
+ */
392
+
393
+ var NetInfo = function() {
394
+ var self = this;
395
+ Pusher.EventsDispatcher.call(this);
396
+ // This is okay, as IE doesn't support this stuff anyway.
397
+ if (window.addEventListener !== undefined) {
398
+ window.addEventListener("online", function() {
399
+ self.emit('online', null);
400
+ }, false);
401
+ window.addEventListener("offline", function() {
402
+ self.emit('offline', null);
403
+ }, false);
404
+ }
405
+ };
406
+
407
+ // Offline means definitely offline (no connection to router).
408
+ // Inverse does NOT mean definitely online (only currently supported in Safari
409
+ // and even there only means the device has a connection to the router).
410
+ NetInfo.prototype.isOnLine = function() {
411
+ if (window.navigator.onLine === undefined) {
412
+ return true;
413
+ } else {
414
+ return window.navigator.onLine;
415
+ }
416
+ };
417
+
418
+ Pusher.Util.extend(NetInfo.prototype, Pusher.EventsDispatcher.prototype);
419
+
420
+ this.Pusher.NetInfo = NetInfo;
421
+ }).call(this);
422
+
423
+ ;(function() {
424
+ var Pusher = this.Pusher;
425
+
426
+ var machineTransitions = {
427
+ 'initialized': ['waiting', 'failed'],
428
+ 'waiting': ['connecting', 'permanentlyClosed'],
429
+ 'connecting': ['open', 'permanentlyClosing', 'impermanentlyClosing', 'waiting'],
430
+ 'open': ['connected', 'permanentlyClosing', 'impermanentlyClosing', 'waiting'],
431
+ 'connected': ['permanentlyClosing', 'waiting'],
432
+ 'impermanentlyClosing': ['waiting', 'permanentlyClosing'],
433
+ 'permanentlyClosing': ['permanentlyClosed'],
434
+ 'permanentlyClosed': ['waiting', 'failed'],
435
+ 'failed': ['permanentlyClosed']
436
+ };
437
+
438
+
439
+ var OPEN_TIMEOUT_INCREMENT = 2000;
440
+ var CONNECTED_TIMEOUT_INCREMENT = 2000;
441
+
442
+ var MAX_OPEN_TIMEOUT = 10000;
443
+ var MAX_CONNECTED_TIMEOUT = 10000;
444
+
445
+ function resetConnectionParameters(connection) {
446
+ connection.connectionWait = 0;
447
+
448
+ if (Pusher.TransportType === 'native') {
449
+ connection.openTimeout = 4000;
450
+ } else if (Pusher.TransportType === 'flash') {
451
+ connection.openTimeout = 7000;
452
+ } else { // SockJS
453
+ connection.openTimeout = 6000;
454
+ }
455
+ connection.connectedTimeout = 2000;
456
+ connection.connectionSecure = connection.compulsorySecure;
457
+ connection.failedAttempts = 0;
458
+ }
459
+
460
+ function Connection(key, options) {
461
+ var self = this;
462
+
463
+ Pusher.EventsDispatcher.call(this);
464
+
465
+ this.ping = true
466
+ this.options = Pusher.Util.extend({encrypted: false}, options);
467
+
468
+ this.netInfo = new Pusher.NetInfo();
469
+
470
+ this.netInfo.bind('online', function(){
471
+ if (self._machine.is('waiting')) {
472
+ self._machine.transition('connecting');
473
+ updateState('connecting');
474
+ }
475
+ });
476
+
477
+ this.netInfo.bind('offline', function() {
478
+ if (self._machine.is('connected')) {
479
+ // These are for Chrome 15, which ends up
480
+ // having two sockets hanging around.
481
+ self.socket.onclose = undefined;
482
+ self.socket.onmessage = undefined;
483
+ self.socket.onerror = undefined;
484
+ self.socket.onopen = undefined;
485
+
486
+ self.socket.close();
487
+ self.socket = undefined;
488
+ self._machine.transition('waiting');
489
+ }
490
+ });
491
+
492
+ // define the state machine that runs the connection
493
+ this._machine = new Pusher.Machine('initialized', machineTransitions, {
494
+ initializedPre: function() {
495
+ self.compulsorySecure = self.options.encrypted;
496
+
497
+ self.key = key;
498
+ self.socket = null;
499
+ self.socket_id = null;
500
+
501
+ self.state = 'initialized';
502
+ },
503
+
504
+ waitingPre: function() {
505
+ if (self.netInfo.isOnLine()) {
506
+ if (self.failedAttempts < 2) {
507
+ updateState('connecting');
508
+ } else {
509
+ updateState('unavailable');
510
+ // Delay 10s between connection attempts on entering unavailable
511
+ self.connectionWait = 10000;
512
+ }
513
+
514
+ if (self.connectionWait > 0) {
515
+ self.emit('connecting_in', connectionDelay());
516
+ }
517
+
518
+ self._waitingTimer = setTimeout(function() {
519
+ // Even when unavailable we try connecting (not changing state)
520
+ self._machine.transition('connecting');
521
+ }, connectionDelay());
522
+ } else {
523
+ updateState('unavailable');
524
+ }
525
+ },
526
+
527
+ waitingExit: function() {
528
+ clearTimeout(self._waitingTimer);
529
+ },
530
+
531
+ connectingPre: function() {
532
+ // Case that a user manages to get to the connecting
533
+ // state even when offline.
534
+ if (self.netInfo.isOnLine() === false) {
535
+ self._machine.transition('waiting');
536
+ updateState('unavailable');
537
+
538
+ return;
539
+ }
540
+
541
+ var path = connectPath(self.key);
542
+ if (Pusher.TransportType === 'sockjs') {
543
+ Pusher.debug('Connecting to sockjs', Pusher.sockjs);
544
+ var url = buildSockJSURL(self.connectionSecure);
545
+
546
+ self.ping = false
547
+ self.socket = new SockJS(url);
548
+ self.socket.onopen = function() {
549
+ // SockJS does not yet support custom paths and query params
550
+ self.socket.send(JSON.stringify({path: path}));
551
+ self._machine.transition('open');
552
+ }
553
+ } else {
554
+ var url = connectBaseURL(self.connectionSecure) + path;
555
+ Pusher.debug('Connecting', url);
556
+ self.socket = new Pusher.Transport(url);
557
+ self.socket.onopen = function() {
558
+ self._machine.transition('open');
559
+ }
560
+ }
561
+
562
+ self.socket.onclose = transitionToWaiting;
563
+ self.socket.onerror = ws_onError;
564
+
565
+ // allow time to get ws_onOpen, otherwise close socket and try again
566
+ self._connectingTimer = setTimeout(TransitionToImpermanentlyClosing, self.openTimeout);
567
+ },
568
+
569
+ connectingExit: function() {
570
+ clearTimeout(self._connectingTimer);
571
+ self.socket.onopen = undefined; // unbind to avoid open events that are no longer relevant
572
+ },
573
+
574
+ connectingToWaiting: function() {
575
+ updateConnectionParameters();
576
+
577
+ // FUTURE: update only ssl
578
+ },
579
+
580
+ connectingToImpermanentlyClosing: function() {
581
+ updateConnectionParameters();
582
+
583
+ // FUTURE: update only timeout
584
+ },
585
+
586
+ openPre: function() {
587
+ self.socket.onmessage = ws_onMessageOpen;
588
+ self.socket.onerror = ws_onError;
589
+ self.socket.onclose = transitionToWaiting;
590
+
591
+ // allow time to get connected-to-Pusher message, otherwise close socket, try again
592
+ self._openTimer = setTimeout(TransitionToImpermanentlyClosing, self.connectedTimeout);
593
+ },
594
+
595
+ openExit: function() {
596
+ clearTimeout(self._openTimer);
597
+ self.socket.onmessage = undefined; // unbind to avoid messages that are no longer relevant
598
+ },
599
+
600
+ openToWaiting: function() {
601
+ updateConnectionParameters();
602
+ },
603
+
604
+ openToImpermanentlyClosing: function() {
605
+ updateConnectionParameters();
606
+ },
607
+
608
+ connectedPre: function(socket_id) {
609
+ self.socket_id = socket_id;
610
+
611
+ self.socket.onmessage = ws_onMessageConnected;
612
+ self.socket.onerror = ws_onError;
613
+ self.socket.onclose = transitionToWaiting;
614
+
615
+ resetConnectionParameters(self);
616
+ self.connectedAt = new Date().getTime();
617
+
618
+ resetActivityCheck();
619
+ },
620
+
621
+ connectedPost: function() {
622
+ updateState('connected');
623
+ },
624
+
625
+ connectedExit: function() {
626
+ stopActivityCheck();
627
+ updateState('disconnected');
628
+ },
629
+
630
+ impermanentlyClosingPost: function() {
631
+ if (self.socket) {
632
+ self.socket.onclose = transitionToWaiting;
633
+ self.socket.close();
634
+ }
635
+ },
636
+
637
+ permanentlyClosingPost: function() {
638
+ if (self.socket) {
639
+ self.socket.onclose = function() {
640
+ resetConnectionParameters(self);
641
+ self._machine.transition('permanentlyClosed');
642
+ };
643
+
644
+ self.socket.close();
645
+ } else {
646
+ resetConnectionParameters(self);
647
+ self._machine.transition('permanentlyClosed');
648
+ }
649
+ },
650
+
651
+ failedPre: function() {
652
+ updateState('failed');
653
+ Pusher.debug('WebSockets are not available in this browser.');
654
+ },
655
+
656
+ permanentlyClosedPost: function() {
657
+ updateState('disconnected');
658
+ }
659
+ });
660
+
661
+ /*-----------------------------------------------
662
+ -----------------------------------------------*/
663
+
664
+ function updateConnectionParameters() {
665
+ if (self.openTimeout < MAX_OPEN_TIMEOUT) {
666
+ self.openTimeout += OPEN_TIMEOUT_INCREMENT;
667
+ }
668
+
669
+ if (self.connectedTimeout < MAX_CONNECTED_TIMEOUT) {
670
+ self.connectedTimeout += CONNECTED_TIMEOUT_INCREMENT;
671
+ }
672
+
673
+ // Toggle between ws & wss
674
+ if (self.compulsorySecure !== true) {
675
+ self.connectionSecure = !self.connectionSecure;
676
+ }
677
+
678
+ self.failedAttempts++;
679
+ }
680
+
681
+ function connectBaseURL(isSecure) {
682
+ // Always connect with SSL if the current page served over https
683
+ var ssl = (isSecure || document.location.protocol === 'https:');
684
+ var port = ssl ? Pusher.wss_port : Pusher.ws_port;
685
+ var scheme = ssl ? 'wss://' : 'ws://';
686
+
687
+ return scheme + Pusher.host + ':' + port;
688
+ }
689
+
690
+ function connectPath(key) {
691
+ var flash = (Pusher.TransportType === "flash") ? "true" : "false";
692
+ var path = '/app/' + key + '?protocol=5&client=js'
693
+ + '&version=' + Pusher.VERSION
694
+ + '&flash=' + flash;
695
+ return path;
696
+ }
697
+
698
+ function buildSockJSURL(isSecure) {
699
+ var ssl = (isSecure || document.location.protocol === 'https:');
700
+ var port = ssl ? Pusher.sockjs_https_port : Pusher.sockjs_http_port;
701
+ var scheme = ssl ? 'https://' : 'http://';
702
+
703
+ return scheme + Pusher.sockjs_host + ':' + port + Pusher.sockjs_path;
704
+ }
705
+
706
+ // callback for close and retry. Used on timeouts.
707
+ function TransitionToImpermanentlyClosing() {
708
+ self._machine.transition('impermanentlyClosing');
709
+ }
710
+
711
+ function resetActivityCheck() {
712
+ if (self._activityTimer) { clearTimeout(self._activityTimer); }
713
+ // Send ping after inactivity
714
+ if (self.ping) {
715
+ self._activityTimer = setTimeout(function() {
716
+ self.send_event('pusher:ping', {})
717
+ // Wait for pong response
718
+ self._activityTimer = setTimeout(function() {
719
+ self.socket.close();
720
+ }, (self.options.pong_timeout || Pusher.pong_timeout))
721
+ }, (self.options.activity_timeout || Pusher.activity_timeout))
722
+ }
723
+ }
724
+
725
+ function stopActivityCheck() {
726
+ if (self._activityTimer) { clearTimeout(self._activityTimer); }
727
+ }
728
+
729
+ // Returns the delay before the next connection attempt should be made
730
+ //
731
+ // This function guards against attempting to connect more frequently than
732
+ // once every second
733
+ //
734
+ function connectionDelay() {
735
+ var delay = self.connectionWait;
736
+ if (delay === 0) {
737
+ if (self.connectedAt) {
738
+ var t = 1000;
739
+ var connectedFor = new Date().getTime() - self.connectedAt;
740
+ if (connectedFor < t) {
741
+ delay = t - connectedFor;
742
+ }
743
+ }
744
+ }
745
+ return delay;
746
+ }
747
+
748
+ /*-----------------------------------------------
749
+ WebSocket Callbacks
750
+ -----------------------------------------------*/
751
+
752
+ function handleCloseCode(code, message) {
753
+ // first inform the end-developer of this error
754
+ self.emit('error', {type: 'PusherError', data: {code: code, message: message}});
755
+
756
+ if (code === 4000) {
757
+ // SSL only app
758
+ self.compulsorySecure = true;
759
+ self.connectionSecure = true;
760
+ self.options.encrypted = true;
761
+
762
+ TransitionToImpermanentlyClosing();
763
+ } else if (code < 4100) {
764
+ // Permentently close connection
765
+ self._machine.transition('permanentlyClosing')
766
+ } else if (code < 4200) {
767
+ // Backoff before reconnecting
768
+ self.connectionWait = 1000;
769
+ self._machine.transition('waiting')
770
+ } else if (code < 4300) {
771
+ // Reconnect immediately
772
+ TransitionToImpermanentlyClosing();
773
+ } else {
774
+ // Unknown error
775
+ self._machine.transition('permanentlyClosing')
776
+ }
777
+ }
778
+
779
+ function ws_onMessageOpen(event) {
780
+ var params = parseWebSocketEvent(event);
781
+ if (params !== undefined) {
782
+ if (params.event === 'pusher:connection_established') {
783
+ self._machine.transition('connected', params.data.socket_id);
784
+ } else if (params.event === 'pusher:error') {
785
+ handleCloseCode(params.data.code, params.data.message)
786
+ }
787
+ }
788
+ }
789
+
790
+ function ws_onMessageConnected(event) {
791
+ resetActivityCheck();
792
+
793
+ var params = parseWebSocketEvent(event);
794
+ if (params !== undefined) {
795
+ Pusher.debug('Event recd', params);
796
+
797
+ switch (params.event) {
798
+ case 'pusher:error':
799
+ self.emit('error', {type: 'PusherError', data: params.data});
800
+ break;
801
+ case 'pusher:ping':
802
+ self.send_event('pusher:pong', {})
803
+ break;
804
+ }
805
+
806
+ self.emit('message', params);
807
+ }
808
+ }
809
+
810
+
811
+ /**
812
+ * Parses an event from the WebSocket to get
813
+ * the JSON payload that we require
814
+ *
815
+ * @param {MessageEvent} event The event from the WebSocket.onmessage handler.
816
+ **/
817
+ function parseWebSocketEvent(event) {
818
+ try {
819
+ var params = JSON.parse(event.data);
820
+
821
+ if (typeof params.data === 'string') {
822
+ try {
823
+ params.data = JSON.parse(params.data);
824
+ } catch (e) {
825
+ if (!(e instanceof SyntaxError)) {
826
+ throw e;
827
+ }
828
+ }
829
+ }
830
+
831
+ return params;
832
+ } catch (e) {
833
+ self.emit('error', {type: 'MessageParseError', error: e, data: event.data});
834
+ }
835
+ }
836
+
837
+ function transitionToWaiting() {
838
+ self._machine.transition('waiting');
839
+ }
840
+
841
+ function ws_onError(error) {
842
+ // just emit error to user - socket will already be closed by browser
843
+ self.emit('error', { type: 'WebSocketError', error: error });
844
+ }
845
+
846
+ // Updates the public state information exposed by connection
847
+ //
848
+ // This is distinct from the internal state information used by _machine
849
+ // to manage the connection
850
+ //
851
+ function updateState(newState, data) {
852
+ var prevState = self.state;
853
+ self.state = newState;
854
+
855
+ // Only emit when the state changes
856
+ if (prevState !== newState) {
857
+ Pusher.debug('State changed', prevState + ' -> ' + newState);
858
+ self.emit('state_change', {previous: prevState, current: newState});
859
+ self.emit(newState, data);
860
+ }
861
+ }
862
+ };
863
+
864
+ Connection.prototype.connect = function() {
865
+ // no WebSockets
866
+ if (!this._machine.is('failed') && !Pusher.Transport) {
867
+ this._machine.transition('failed');
868
+ }
869
+ // initial open of connection
870
+ else if(this._machine.is('initialized')) {
871
+ resetConnectionParameters(this);
872
+ this._machine.transition('waiting');
873
+ }
874
+ // user skipping connection wait
875
+ else if (this._machine.is('waiting') && this.netInfo.isOnLine() === true) {
876
+ this._machine.transition('connecting');
877
+ }
878
+ // user re-opening connection after closing it
879
+ else if(this._machine.is("permanentlyClosed")) {
880
+ resetConnectionParameters(this);
881
+ this._machine.transition('waiting');
882
+ }
883
+ };
884
+
885
+ Connection.prototype.send = function(data) {
886
+ if (this._machine.is('connected')) {
887
+ // Workaround for MobileSafari bug (see https://gist.github.com/2052006)
888
+ var self = this;
889
+ setTimeout(function() {
890
+ self.socket.send(data);
891
+ }, 0);
892
+ return true;
893
+ } else {
894
+ return false;
895
+ }
896
+ };
897
+
898
+ Connection.prototype.send_event = function(event_name, data, channel) {
899
+ var payload = {
900
+ event: event_name,
901
+ data: data
902
+ };
903
+ if (channel) payload['channel'] = channel;
904
+
905
+ Pusher.debug('Event sent', payload);
906
+ return this.send(JSON.stringify(payload));
907
+ }
908
+
909
+ Connection.prototype.disconnect = function() {
910
+ if (this._machine.is('permanentlyClosed')) return;
911
+
912
+ if (this._machine.is('waiting') || this._machine.is('failed')) {
913
+ this._machine.transition('permanentlyClosed');
914
+ } else {
915
+ this._machine.transition('permanentlyClosing');
916
+ }
917
+ };
918
+
919
+ Pusher.Util.extend(Connection.prototype, Pusher.EventsDispatcher.prototype);
920
+ this.Pusher.Connection = Connection;
921
+ }).call(this);
922
+
923
+ ;(function() {
924
+ Pusher.Channels = function() {
925
+ this.channels = {};
926
+ };
927
+
928
+ Pusher.Channels.prototype = {
929
+ add: function(channel_name, pusher) {
930
+ var existing_channel = this.find(channel_name);
931
+ if (!existing_channel) {
932
+ var channel = Pusher.Channel.factory(channel_name, pusher);
933
+ this.channels[channel_name] = channel;
934
+ return channel;
935
+ } else {
936
+ return existing_channel;
937
+ }
938
+ },
939
+
940
+ find: function(channel_name) {
941
+ return this.channels[channel_name];
942
+ },
943
+
944
+ remove: function(channel_name) {
945
+ delete this.channels[channel_name];
946
+ },
947
+
948
+ disconnect: function () {
949
+ for(var channel_name in this.channels){
950
+ this.channels[channel_name].disconnect()
951
+ }
952
+ }
953
+ };
954
+
955
+ Pusher.Channel = function(channel_name, pusher) {
956
+ var self = this;
957
+ Pusher.EventsDispatcher.call(this, function(event_name, event_data) {
958
+ Pusher.debug('No callbacks on ' + channel_name + ' for ' + event_name);
959
+ });
960
+
961
+ this.pusher = pusher;
962
+ this.name = channel_name;
963
+ this.subscribed = false;
964
+
965
+ this.bind('pusher_internal:subscription_succeeded', function(data) {
966
+ self.onSubscriptionSucceeded(data);
967
+ });
968
+ };
969
+
970
+ Pusher.Channel.prototype = {
971
+ // inheritable constructor
972
+ init: function() {},
973
+ disconnect: function() {
974
+ this.subscribed = false;
975
+ this.emit("pusher_internal:disconnected");
976
+ },
977
+
978
+ onSubscriptionSucceeded: function(data) {
979
+ this.subscribed = true;
980
+ this.emit('pusher:subscription_succeeded');
981
+ },
982
+
983
+ authorize: function(socketId, options, callback){
984
+ return callback(false, {}); // normal channels don't require auth
985
+ },
986
+
987
+ trigger: function(event, data) {
988
+ return this.pusher.send_event(event, data, this.name);
989
+ }
990
+ };
991
+
992
+ Pusher.Util.extend(Pusher.Channel.prototype, Pusher.EventsDispatcher.prototype);
993
+
994
+ Pusher.Channel.PrivateChannel = {
995
+ authorize: function(socketId, options, callback){
996
+ var self = this;
997
+ var authorizer = new Pusher.Channel.Authorizer(this, Pusher.channel_auth_transport, options);
998
+ return authorizer.authorize(socketId, function(err, authData) {
999
+ if(!err) {
1000
+ self.emit('pusher_internal:authorized', authData);
1001
+ }
1002
+
1003
+ callback(err, authData);
1004
+ });
1005
+ }
1006
+ };
1007
+
1008
+ Pusher.Channel.PresenceChannel = {
1009
+ init: function(){
1010
+ this.members = new Members(this); // leeches off channel events
1011
+ },
1012
+
1013
+ onSubscriptionSucceeded: function(data) {
1014
+ this.subscribed = true;
1015
+ // We override this because we want the Members obj to be responsible for
1016
+ // emitting the pusher:subscription_succeeded. It will do this after it has done its work.
1017
+ }
1018
+ };
1019
+
1020
+ var Members = function(channel) {
1021
+ var self = this;
1022
+
1023
+ var reset = function() {
1024
+ this._members_map = {};
1025
+ this.count = 0;
1026
+ this.me = null;
1027
+ };
1028
+ reset.call(this);
1029
+
1030
+ channel.bind('pusher_internal:authorized', function(authorizedData) {
1031
+ var channelData = JSON.parse(authorizedData.channel_data);
1032
+ channel.bind("pusher_internal:subscription_succeeded", function(subscriptionData) {
1033
+ self._members_map = subscriptionData.presence.hash;
1034
+ self.count = subscriptionData.presence.count;
1035
+ self.me = self.get(channelData.user_id);
1036
+ channel.emit('pusher:subscription_succeeded', self);
1037
+ });
1038
+ });
1039
+
1040
+ channel.bind('pusher_internal:member_added', function(data) {
1041
+ if(self.get(data.user_id) === null) { // only incr if user_id does not already exist
1042
+ self.count++;
1043
+ }
1044
+
1045
+ self._members_map[data.user_id] = data.user_info;
1046
+ channel.emit('pusher:member_added', self.get(data.user_id));
1047
+ });
1048
+
1049
+ channel.bind('pusher_internal:member_removed', function(data) {
1050
+ var member = self.get(data.user_id);
1051
+ if(member) {
1052
+ delete self._members_map[data.user_id];
1053
+ self.count--;
1054
+ channel.emit('pusher:member_removed', member);
1055
+ }
1056
+ });
1057
+
1058
+ channel.bind('pusher_internal:disconnected', function() {
1059
+ reset.call(self);
1060
+ });
1061
+ };
1062
+
1063
+ Members.prototype = {
1064
+ each: function(callback) {
1065
+ for(var i in this._members_map) {
1066
+ callback(this.get(i));
1067
+ }
1068
+ },
1069
+
1070
+ get: function(user_id) {
1071
+ if (this._members_map.hasOwnProperty(user_id)) { // have heard of this user user_id
1072
+ return {
1073
+ id: user_id,
1074
+ info: this._members_map[user_id]
1075
+ }
1076
+ } else { // have never heard of this user
1077
+ return null;
1078
+ }
1079
+ }
1080
+ };
1081
+
1082
+ Pusher.Channel.factory = function(channel_name, pusher){
1083
+ var channel = new Pusher.Channel(channel_name, pusher);
1084
+ if (channel_name.indexOf('private-') === 0) {
1085
+ Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
1086
+ } else if (channel_name.indexOf('presence-') === 0) {
1087
+ Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
1088
+ Pusher.Util.extend(channel, Pusher.Channel.PresenceChannel);
1089
+ };
1090
+ channel.init();
1091
+ return channel;
1092
+ };
1093
+ }).call(this);
1094
+ ;(function() {
1095
+ Pusher.Channel.Authorizer = function(channel, type, options) {
1096
+ this.channel = channel;
1097
+ this.type = type;
1098
+
1099
+ this.authOptions = (options || {}).auth || {};
1100
+ };
1101
+
1102
+ Pusher.Channel.Authorizer.prototype = {
1103
+ composeQuery: function(socketId) {
1104
+ var query = '&socket_id=' + encodeURIComponent(socketId)
1105
+ + '&channel_name=' + encodeURIComponent(this.channel.name);
1106
+
1107
+ for(var i in this.authOptions.params) {
1108
+ query += "&" + encodeURIComponent(i) + "=" + encodeURIComponent(this.authOptions.params[i]);
1109
+ }
1110
+
1111
+ return query;
1112
+ },
1113
+
1114
+ authorize: function(socketId, callback) {
1115
+ return Pusher.authorizers[this.type].call(this, socketId, callback);
1116
+ }
1117
+ };
1118
+
1119
+
1120
+ Pusher.auth_callbacks = {};
1121
+ Pusher.authorizers = {
1122
+ ajax: function(socketId, callback){
1123
+ var self = this, xhr;
1124
+
1125
+ if (Pusher.XHR) {
1126
+ xhr = new Pusher.XHR();
1127
+ } else {
1128
+ xhr = (window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"));
1129
+ }
1130
+
1131
+ xhr.open("POST", Pusher.channel_auth_endpoint, true);
1132
+
1133
+ // add request headers
1134
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
1135
+ for(var headerName in this.authOptions.headers) {
1136
+ xhr.setRequestHeader(headerName, this.authOptions.headers[headerName]);
1137
+ }
1138
+
1139
+ xhr.onreadystatechange = function() {
1140
+ if (xhr.readyState == 4) {
1141
+ if (xhr.status == 200) {
1142
+ var data, parsed = false;
1143
+
1144
+ try {
1145
+ data = JSON.parse(xhr.responseText);
1146
+ parsed = true;
1147
+ } catch (e) {
1148
+ callback(true, 'JSON returned from webapp was invalid, yet status code was 200. Data was: ' + xhr.responseText);
1149
+ }
1150
+
1151
+ if (parsed) { // prevents double execution.
1152
+ callback(false, data);
1153
+ }
1154
+ } else {
1155
+ Pusher.warn("Couldn't get auth info from your webapp", xhr.status);
1156
+ callback(true, xhr.status);
1157
+ }
1158
+ }
1159
+ };
1160
+
1161
+ xhr.send(this.composeQuery(socketId));
1162
+ return xhr;
1163
+ },
1164
+
1165
+ jsonp: function(socketId, callback){
1166
+ if(this.authOptions.headers !== undefined) {
1167
+ Pusher.warn("Warn", "To send headers with the auth request, you must use AJAX, rather than JSONP.");
1168
+ }
1169
+
1170
+ var script = document.createElement("script");
1171
+ // Hacked wrapper.
1172
+ Pusher.auth_callbacks[this.channel.name] = function(data) {
1173
+ callback(false, data);
1174
+ };
1175
+
1176
+ var callback_name = "Pusher.auth_callbacks['" + this.channel.name + "']";
1177
+ script.src = Pusher.channel_auth_endpoint
1178
+ + '?callback='
1179
+ + encodeURIComponent(callback_name)
1180
+ + this.composeQuery(socketId);
1181
+
1182
+ var head = document.getElementsByTagName("head")[0] || document.documentElement;
1183
+ head.insertBefore( script, head.firstChild );
1184
+ }
1185
+ };
1186
+ }).call(this);
1187
+ // _require(dependencies, callback) takes an array of dependency urls and a
1188
+ // callback to call when all the dependecies have finished loading
1189
+ var _require = (function() {
1190
+ function handleScriptLoaded(elem, callback) {
1191
+ if (document.addEventListener) {
1192
+ elem.addEventListener('load', callback, false);
1193
+ } else {
1194
+ elem.attachEvent('onreadystatechange', function () {
1195
+ if (elem.readyState == 'loaded' || elem.readyState == 'complete') {
1196
+ callback();
1197
+ }
1198
+ });
1199
+ }
1200
+ }
1201
+
1202
+ function addScript(src, callback) {
1203
+ var head = document.getElementsByTagName('head')[0];
1204
+ var script = document.createElement('script');
1205
+ script.setAttribute('src', src);
1206
+ script.setAttribute("type","text/javascript");
1207
+ script.setAttribute('async', true);
1208
+
1209
+ handleScriptLoaded(script, function() {
1210
+ callback();
1211
+ });
1212
+
1213
+ head.appendChild(script);
1214
+ }
1215
+
1216
+ return function(deps, callback) {
1217
+ var deps_loaded = 0;
1218
+ for (var i = 0; i < deps.length; i++) {
1219
+ addScript(deps[i], function() {
1220
+ if (deps.length == ++deps_loaded) {
1221
+ // This setTimeout is a workaround for an Opera issue
1222
+ setTimeout(callback, 0);
1223
+ }
1224
+ });
1225
+ }
1226
+ }
1227
+ })();
1228
+
1229
+ ;(function() {
1230
+ // Support Firefox versions which prefix WebSocket
1231
+ if (!window['WebSocket'] && window['MozWebSocket']) {
1232
+ window['WebSocket'] = window['MozWebSocket']
1233
+ }
1234
+
1235
+ if (window['WebSocket']) {
1236
+ Pusher.Transport = window['WebSocket'];
1237
+ Pusher.TransportType = 'native';
1238
+ }
1239
+
1240
+ var cdn = (document.location.protocol == 'http:') ? Pusher.cdn_http : Pusher.cdn_https;
1241
+ var root = cdn + Pusher.VERSION;
1242
+ var deps = [];
1243
+
1244
+ if (!window['JSON']) {
1245
+ deps.push(root + '/json2' + Pusher.dependency_suffix + '.js');
1246
+ }
1247
+ if (!window['WebSocket']) {
1248
+ // Try to use web-socket-js (flash WebSocket emulation)
1249
+ window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = true;
1250
+ window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;
1251
+ deps.push(root + '/flashfallback' + Pusher.dependency_suffix + '.js');
1252
+ }
1253
+
1254
+ var initialize = function() {
1255
+ if (window['WebSocket']) {
1256
+ // Initialize function in the case that we have native WebSocket support
1257
+ return function() {
1258
+ Pusher.ready();
1259
+ }
1260
+ } else {
1261
+ // Initialize function for fallback case
1262
+ return function() {
1263
+ if (window['WebSocket']) {
1264
+ // window['WebSocket'] is a flash emulation of WebSocket
1265
+ Pusher.Transport = window['WebSocket'];
1266
+ Pusher.TransportType = 'flash';
1267
+
1268
+ window.WEB_SOCKET_SWF_LOCATION = root + "/WebSocketMain.swf";
1269
+ WebSocket.__addTask(function() {
1270
+ Pusher.ready();
1271
+ })
1272
+ WebSocket.__initialize();
1273
+ } else {
1274
+ // web-socket-js cannot initialize (most likely flash not installed)
1275
+ sockjsPath = root + '/sockjs' + Pusher.dependency_suffix + '.js';
1276
+ _require([sockjsPath], function() {
1277
+ Pusher.Transport = SockJS;
1278
+ Pusher.TransportType = 'sockjs';
1279
+ Pusher.ready();
1280
+ })
1281
+ }
1282
+ }
1283
+ }
1284
+ }();
1285
+
1286
+ // Allows calling a function when the document body is available
1287
+ var ondocumentbody = function(callback) {
1288
+ var load_body = function() {
1289
+ document.body ? callback() : setTimeout(load_body, 0);
1290
+ }
1291
+ load_body();
1292
+ };
1293
+
1294
+ var initializeOnDocumentBody = function() {
1295
+ ondocumentbody(initialize);
1296
+ }
1297
+
1298
+ if (deps.length > 0) {
1299
+ _require(deps, initializeOnDocumentBody);
1300
+ } else {
1301
+ initializeOnDocumentBody();
1302
+ }
1303
+ })();
1304
+