diff --git a/.eslintrc.json b/.eslintrc.json index 6731885d51..22e3a80346 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -19,8 +19,6 @@ "MessageListData": false, "MessageListView": false, "Plotly": false, - "SockJS": false, - "Socket": false, "Sortable": false, "WinChan": false, "XDate": false, diff --git a/docs/THIRDPARTY b/docs/THIRDPARTY index bf23cbc40c..4d74968269 100644 --- a/docs/THIRDPARTY +++ b/docs/THIRDPARTY @@ -120,11 +120,6 @@ Files: static/third/marked/* Copyright: 2011-2013, Christopher Jeffrey License: Expat -Files: static/third/sockjs/sockjs-0.3.4.js -Copyright: 2011-2012 VMware, Inc. - 2012 Douglas Crockford -License: Expat and public-domain - Files: zerver/lib/bugdown/fenced_code.py Copyright: 2006-2008 Waylan Limberg License: BSD-3-Clause diff --git a/docs/development/setup-vagrant.md b/docs/development/setup-vagrant.md index 3e1fccd4c8..3496607374 100644 --- a/docs/development/setup-vagrant.md +++ b/docs/development/setup-vagrant.md @@ -839,7 +839,6 @@ warning fsevents@1.1.1: The platform "linux" is incompatible with this module. info "fsevents@1.1.1" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... [4/4] Building fresh packages... -$ browserify node_modules/sockjs-client/lib/entry.js --standalone SockJS > node_modules/sockjs-client/sockjs.js Done in 23.50s. ``` diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md index 95aa86408b..dfc6f8a34c 100644 --- a/docs/overview/architecture-overview.md +++ b/docs/overview/architecture-overview.md @@ -79,7 +79,7 @@ processes; see "Supervisor" below) and the nginx configuration (which explains which HTTP requests get sent to which app server). Tornado is an asynchronous server and is meant specifically to hold -open tens of thousands of long-lived (long-polling or websocket) +open tens of thousands of long-lived (long-polling) connections -- that is to say, routes that maintain a persistent connection from every running client. For this reason, it's responsible for event (message) delivery, but not much else. We try to @@ -94,9 +94,7 @@ use in most of our codebase using don't support that, and in any case, our architecture doesn't require Tornado to do that). The parts that are activated relatively rarely (e.g. when people type or -click on something) are processed by the Django application server. One -exception to this is that Zulip uses websockets through Tornado to -minimize latency on the code path for **sending** messages. +click on something) are processed by the Django application server. There is detailed documentation on the [real-time push and event queue system](../subsystems/events-system.md); most of @@ -137,7 +135,7 @@ from outside. compiles, minifies, and installs the static assets into the `prod-static/` tree form. In development, files are served directly from `/static/` in the git repository. -- Requests to `/json/events`, `/api/v1/events`, and `/sockjs` are +- Requests to `/json/events` and `/api/v1/events` are sent to the Tornado server. These are requests to the real-time push system, because the user's web browser sets up a long-lived TCP connection with Tornado to serve as [a channel for push diff --git a/docs/production/troubleshooting.md b/docs/production/troubleshooting.md index dce1113627..56d7436874 100644 --- a/docs/production/troubleshooting.md +++ b/docs/production/troubleshooting.md @@ -46,11 +46,6 @@ When everything is running as expected, you will see something like this: ``` process-fts-updates RUNNING pid 2194, uptime 1:13:11 zulip-django RUNNING pid 2192, uptime 1:13:11 -zulip-senders:zulip-events-message_sender-0 RUNNING pid 2209, uptime 1:13:11 -zulip-senders:zulip-events-message_sender-1 RUNNING pid 2210, uptime 1:13:11 -zulip-senders:zulip-events-message_sender-2 RUNNING pid 2211, uptime 1:13:11 -zulip-senders:zulip-events-message_sender-3 RUNNING pid 2212, uptime 1:13:11 -zulip-senders:zulip-events-message_sender-4 RUNNING pid 2208, uptime 1:13:11 zulip-tornado RUNNING pid 2193, uptime 1:13:11 zulip-workers:zulip-events-confirmation-emails RUNNING pid 2199, uptime 1:13:11 zulip-workers:zulip-events-digest_emails RUNNING pid 2205, uptime 1:13:11 diff --git a/docs/subsystems/queuing.md b/docs/subsystems/queuing.md index 16250387e1..76dd140c59 100644 --- a/docs/subsystems/queuing.md +++ b/docs/subsystems/queuing.md @@ -23,9 +23,6 @@ used for a variety of purposes: * Processing various errors, frontend tracebacks, and slow database queries in a batched fashion. -* Doing markdown rendering for messages delivered to the Tornado via - websockets. - Needless to say, the RabbitMQ-based queuing system is an important part of the overall Zulip architecture, since it's in critical code paths for everything from signing up for account, to rendering diff --git a/docs/subsystems/sending-messages.md b/docs/subsystems/sending-messages.md index 089571ea9b..21d3014864 100644 --- a/docs/subsystems/sending-messages.md +++ b/docs/subsystems/sending-messages.md @@ -68,8 +68,6 @@ number of purposes: plaintext/markdown raw content or the rendered HTML (e.g. the `apply_markdown` and `client_gravatar` features in our [events API docs](https://zulipchat.com/api/register-queue)). -* The webapp [uses websockets](#websockets) for client/server - interaction for sending messages. * Following our standard naming convention, input validation is done inside the `check_message` function, which is responsible for validating the user can send to the recipient, @@ -101,32 +99,6 @@ number of purposes: it makes when sending messages with large numbers of recipients, to ensure its performance. -### Websockets - -For the webapp only, we use WebSockets rather than standard HTTPS API -requests for triggering message sending. This is a design feature we -are very ambivalent about; it has some slight latency benefits, but is -also features extra complexity and some mostly-unmaintained -dependencies (e.g. `sockjs-tornado`). But in short, this system works -as follows: -* Requests are sent from the webapp to the backend via the `sockjs` -library (on the frontend) and `sockjs-tornado` (on the backend). This -ends up calling a handler in our Tornado codebase -(`zerver/tornado/socket.py`), which immediately puts the request into -the `message_sender` queue. -* The `message_sender` [queue processor](../subsystems/queuing.md) -reformats the request into a Django `HttpRequest` object with a fake -`SOCKET` HTTP method (which is why these requests appear as `SOCKET` -in our server logs), calls the Django `get_response` method on that -request, and returns the response to Tornado via the `tornado_return` -queue. -* Tornado then sends the result (success or error) back to the client -via the relevant WebSocket connection. -* `sockjs` automatically handles for us a fallback to longpolling in -the event that a WebSockets connection cannot be opened successfully -(which despite WebSockets being many years old is still a problem on -some networks today!). - ## Local echo An essential feature for a good chat is experience is local echo @@ -204,17 +176,15 @@ implementation was under 150 lines of code. This section just has a brief review of the sequence of steps all in one place: * User hits send in the compose box. -* Compose box validation runs; if passes, it locally echoes the - message and sends websocket message to Tornado -* Tornado converts websocket message to a `message_sender` queue item -* `message_sender` queue processor turns the queue item into a Django -`HttpRequest` and calls Django's main response handler -* The Django URL routes and middleware run, and eventually calls the +* Compose box validation runs; if it passes, the browser locally + echoes the message and then sends a request to the `POST /messages` + API endpoint. +* The Django URL routes and middleware run, and eventually call the `send_message_backend` view function in `zerver/views/messages.py`. - (Alternatively, for an API request to send a message via the HTTP - API, things start here). + (Alternatively, for an API request to send a message via Zulip's + REST API, things start here). * `send_message_backend` does some validation before triggering the -`check_message` + `do_send_messages` backend flow. + `check_message` + `do_send_messages` backend flow. * That backend flow saves the data to the database and triggers a `message` event in the `notify_tornado` queue (part of the events system). @@ -228,28 +198,11 @@ one place: locally echoed message with the final message it received back from the server (it indicates this to the sender by adding a display timestamp to the message). -* For an API client, the `send_message_backend` view function returns +* The `send_message_backend` view function returns a 200 `HTTP` response; the client receives that response and mostly does nothing with it other than update some logging details. (This may happen before or after the client receives the event notifying it about the new message via its event queue.) -* For a browser (websockets sender), the client receives the - equivalent of the HTTP response via a websockets message from - Tornado (which, in turn, got that via the `tornado_return` queue). - -## Error handling - -When there's an error trying to send a message, it's important to not -lose the text the user had composed. Zulip handles this with a few -approaches: - -* The data for a message in the process of being sent are stored in - browser local storage (see .e.g. `_save_localstorage_requests` in - `static/js/socket.js`), so that the client can retransmit as - appropriate, even if the browser reloads in the meantime. -* Additionally, Zulip renders UI for editing/retransmitting/resending - messages that had been locally echoed on top of those messages, in - red. ## Message editing diff --git a/frontend_tests/node_tests/compose_actions.js b/frontend_tests/node_tests/compose_actions.js index 126c869339..4a7d8e3936 100644 --- a/frontend_tests/node_tests/compose_actions.js +++ b/frontend_tests/node_tests/compose_actions.js @@ -6,9 +6,7 @@ set_global('document', { location: {}, // we need this to load compose.js }); -set_global('page_params', { - use_websockets: false, -}); +set_global('page_params', {}); set_global('$', global.make_zjquery()); diff --git a/frontend_tests/node_tests/transmit.js b/frontend_tests/node_tests/transmit.js index a8e0b9d303..924757c474 100644 --- a/frontend_tests/node_tests/transmit.js +++ b/frontend_tests/node_tests/transmit.js @@ -1,18 +1,11 @@ const noop = function () {}; set_global('$', global.make_zjquery()); -set_global('page_params', { - use_websockets: true, -}); - +set_global('page_params', {}); set_global('channel', {}); set_global('navigator', {}); set_global('reload', {}); set_global('reload_state', {}); -set_global('socket', {}); -set_global('Socket', function () { - return global.socket; -}); set_global('sent_messages', { start_tracking_message: noop, report_server_ack: noop, @@ -23,78 +16,6 @@ zrequire('people'); zrequire('util'); zrequire('transmit'); -function test_with_mock_socket(test_params) { - transmit.initialize(); - let socket_send_called; - const send_args = {}; - - global.socket.send = function (request, success, error) { - global.socket.send = undefined; - socket_send_called = true; - - // Save off args for check_send_args callback. - send_args.request = request; - send_args.success = success; - send_args.error = error; - }; - - // Run the actual code here. - test_params.run_code(); - - assert(socket_send_called); - test_params.check_send_args(send_args); -} - -run_test('transmit_message_sockets', () => { - page_params.use_websockets = true; - global.navigator.userAgent = 'unittest_transmit_message'; - - // Our request is mostly unimportant, except that the - // socket_user_agent field will be added. - const request = {foo: 'bar'}; - - let success_func_checked = false; - const success = function () { - success_func_checked = true; - }; - - // Our error function gets wrapped, so we set up a real - // function to test the wrapping mechanism. - let error_func_checked = false; - const error = function (error_msg) { - assert.equal(error_msg, 'Error sending message: simulated_error'); - error_func_checked = true; - }; - - test_with_mock_socket({ - run_code: function () { - transmit.send_message(request, success, error); - }, - check_send_args: function (send_args) { - // The real code patches new data on the request, rather - // than making a copy, so we test both that it didn't - // clone the object and that it did add a field. - assert.equal(send_args.request, request); - assert.deepEqual(send_args.request, { - foo: 'bar', - socket_user_agent: 'unittest_transmit_message', - }); - - send_args.success({}); - assert(success_func_checked); - - // Our error function does get wrapped, so we test by - // using socket.send's error callback, which should - // invoke our test error function via a wrapper - // function in the real code. - send_args.error('response', {msg: 'simulated_error'}); - assert(error_func_checked); - }, - }); -}); - -page_params.use_websockets = false; - run_test('transmit_message_ajax', () => { let success_func_called; diff --git a/mypy.ini b/mypy.ini index 0f1c96e44a..104a3f4410 100644 --- a/mypy.ini +++ b/mypy.ini @@ -125,8 +125,6 @@ strict_optional = False strict_optional = False [mypy-zerver.worker.queue_processors] strict_optional = False -[mypy-zerver.tornado.websocket_client] -strict_optional = False [mypy-zerver.views.registration] strict_optional = False diff --git a/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time b/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time index 23222821f6..ad35733b98 100755 --- a/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time +++ b/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time @@ -46,10 +46,6 @@ parser.add_argument('--munin', dest='munin', action='store_true') -parser.add_argument('--websocket', - dest='websocket', - action='store_true') - parser.add_argument('config', nargs='?', default=None) options = parser.parse_args() @@ -78,7 +74,6 @@ os.environ['DJANGO_SETTINGS_MODULE'] = "zproject.settings" django.setup() from zerver.models import get_system_bot -from zerver.tornado.websocket_client import WebsocketClient from django.conf import settings states = { @@ -93,10 +88,7 @@ def report(state, timestamp=None, msg=None): now = int(time.time()) if msg is None: msg = "send time was %s" % (timestamp,) - if options.websocket: - state_file_path = "/var/lib/nagios_state/check_send_receive_websockets_state" - else: - state_file_path = "/var/lib/nagios_state/check_send_receive_state" + state_file_path = "/var/lib/nagios_state/check_send_receive_state" with open(state_file_path + ".tmp", 'w') as f: f.write("%s|%s|%s|%s\n" % (now, states[state], state, msg)) os.rename(state_file_path + ".tmp", state_file_path) @@ -123,10 +115,6 @@ def get_zulips(): report("CRITICAL", msg="Got heartbeat waiting for Zulip, which means get_events is hanging") return [event['message'] for event in res['events']] -def send_message_via_websocket(websocket_client, recepient_email, content): - # type: (WebsocketClient, str, str) -> None - websocket_client.send_message('website', 'private', 'no topic', "", recepient_email, content) - if "staging" in options.site and settings.NAGIOS_STAGING_SEND_BOT is not None and \ settings.NAGIOS_STAGING_RECEIVE_BOT is not None: sender = get_system_bot(settings.NAGIOS_STAGING_SEND_BOT) @@ -161,19 +149,12 @@ except Exception: msg_to_send = str(random.getrandbits(64)) time_start = time.time() -if options.websocket: - client = WebsocketClient(host_url=options.site, sockjs_url='/sockjs/366/v8nw22qe/websocket', - run_on_start=send_message_via_websocket, sender_email=sender.email, - recepient_email=recipient.email, content=msg_to_send, - validate_ssl=not options.insecure) - client.run() -else: - send_zulip(zulip_sender, { - "type": 'private', - "content": msg_to_send, - "subject": "time to send", - "to": recipient.email, - }) +send_zulip(zulip_sender, { + "type": 'private', + "content": msg_to_send, + "subject": "time to send", + "to": recipient.email, +}) msg_content = [] # type: List[str] diff --git a/puppet/zulip/files/nginx/zulip-include-common/location-sockjs b/puppet/zulip/files/nginx/zulip-include-common/location-sockjs deleted file mode 100644 index ce2852d528..0000000000 --- a/puppet/zulip/files/nginx/zulip-include-common/location-sockjs +++ /dev/null @@ -1,7 +0,0 @@ -# Longpolling version needed for xhr streaming support -include /etc/nginx/zulip-include/proxy_longpolling; - -proxy_set_header Upgrade $http_upgrade; -# This should override the Connection setting in zulip-include/proxy -proxy_set_header Connection $connection_upgrade; -proxy_set_header X-Real-IP $remote_addr; diff --git a/puppet/zulip/files/nginx/zulip-include-frontend/app b/puppet/zulip/files/nginx/zulip-include-frontend/app index 1b56a46a82..938d5805bc 100644 --- a/puppet/zulip/files/nginx/zulip-include-frontend/app +++ b/puppet/zulip/files/nginx/zulip-include-frontend/app @@ -40,12 +40,6 @@ location /api/v1/events { } -# Send sockjs requests to Tornado -location /sockjs { - proxy_pass http://tornado; - include /etc/nginx/zulip-include/location-sockjs; -} - # Send everything else to Django via uWSGI location / { include uwsgi_params; diff --git a/puppet/zulip/manifests/app_frontend_base.pp b/puppet/zulip/manifests/app_frontend_base.pp index c0c292344c..901e55ec18 100644 --- a/puppet/zulip/manifests/app_frontend_base.pp +++ b/puppet/zulip/manifests/app_frontend_base.pp @@ -81,14 +81,10 @@ class zulip::app_frontend_base { $queues_multiprocess = $zulip::base::total_memory_mb > 3500 $queues = $zulip::base::normal_queues if $queues_multiprocess { - $message_sender_default_processes = 4 $uwsgi_default_processes = 6 } else { - $message_sender_default_processes = 2 $uwsgi_default_processes = 4 } - $message_sender_processes = zulipconf('application_server', 'message_sender_processes', - $message_sender_default_processes) file { "${zulip::common::supervisor_conf_dir}/zulip.conf": ensure => file, require => Package[supervisor], diff --git a/puppet/zulip/templates/nginx.conf.template.erb b/puppet/zulip/templates/nginx.conf.template.erb index 441b4c63d0..0bc5457b6c 100644 --- a/puppet/zulip/templates/nginx.conf.template.erb +++ b/puppet/zulip/templates/nginx.conf.template.erb @@ -42,12 +42,6 @@ http { text/plain; gzip_vary on; - # Select a Connection header for sockjs reverse-proxying - map $http_upgrade $connection_upgrade { - default upgrade; - '' close; - } - # https://wiki.mozilla.org/Security/Server_Side_TLS intermediate profile ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; diff --git a/puppet/zulip/templates/supervisor/zulip.conf.template.erb b/puppet/zulip/templates/supervisor/zulip.conf.template.erb index 3062d9f9d0..04a463e738 100644 --- a/puppet/zulip/templates/supervisor/zulip.conf.template.erb +++ b/puppet/zulip/templates/supervisor/zulip.conf.template.erb @@ -117,22 +117,6 @@ stdout_logfile_maxbytes=20MB ; max # logfile bytes b4 rotation (default 50MB) stdout_logfile_backups=3 ; # of stdout logfile backups (default 10) directory=/home/zulip/deployments/current/ -[program:zulip_events_message_sender] -command=/home/zulip/deployments/current/manage.py process_queue --queue_name=message_sender --worker_num=%(process_num)s -process_name=%(program_name)s-%(process_num)s -priority=350 ; the relative start priority (default 999) -autostart=true ; start at supervisord start (default: true) -autorestart=true ; whether/when to restart (default: unexpected) -stopsignal=TERM ; signal used to kill process (default TERM) -stopwaitsecs=30 ; max num secs to wait b4 SIGKILL (default 10) -user=zulip ; setuid to this UNIX account to run the program -redirect_stderr=true ; redirect proc stderr to stdout (default false) -stdout_logfile=/var/log/zulip/events_message_sender.log ; stdout log path, NONE for none; default AUTO -stdout_logfile_maxbytes=20MB ; max # logfile bytes b4 rotation (default 50MB) -stdout_logfile_backups=5 ; # of stdout logfile backups (default 10) -directory=/home/zulip/deployments/current/ -numprocs=<%= @message_sender_processes %> - ; The below sample group section shows all possible group values, ; create one or more 'real' group: sections to create "heterogeneous" ; process groups. @@ -145,9 +129,6 @@ programs=zulip_deliver_enqueued_emails, zulip_deliver_scheduled_messages, <% @qu programs=zulip_deliver_enqueued_emails, zulip_events, zulip_deliver_scheduled_messages <% end %> -[group:zulip-senders] -programs=zulip_events_message_sender - ; The [include] section can just contain the "files" setting. This ; setting can list multiple files (separated by whitespace or ; newlines). It can also contain wildcards. The filenames are diff --git a/puppet/zulip_ops/files/cron.d/check_send_receive_time b/puppet/zulip_ops/files/cron.d/check_send_receive_time index a4cff54afd..0a319f549a 100644 --- a/puppet/zulip_ops/files/cron.d/check_send_receive_time +++ b/puppet/zulip_ops/files/cron.d/check_send_receive_time @@ -4,4 +4,3 @@ USER=zulip STATE_FILE=/var/lib/nagios_state/check_send_receive_state * * * * * zulip /usr/lib/nagios/plugins/zulip_app_frontend/check_send_receive_time --nagios --site=https://$(/home/zulip/deployments/current/scripts/get-django-setting NAGIOS_BOT_HOST) >/dev/null -* * * * * zulip /usr/lib/nagios/plugins/zulip_app_frontend/check_send_receive_time --nagios --websocket --site=https://$(/home/zulip/deployments/current/scripts/get-django-setting NAGIOS_BOT_HOST) >/dev/null diff --git a/puppet/zulip_ops/files/nagios3/commands.cfg b/puppet/zulip_ops/files/nagios3/commands.cfg index 70db8587a7..a690a9986c 100644 --- a/puppet/zulip_ops/files/nagios3/commands.cfg +++ b/puppet/zulip_ops/files/nagios3/commands.cfg @@ -119,11 +119,6 @@ define command{ command_line /usr/lib/nagios/plugins/check_by_ssh -p $ARG1$ -l nagios -t 30 -i /var/lib/nagios/.ssh/id_rsa -H $HOSTADDRESS$ -C '/usr/lib/nagios/plugins/zulip_app_frontend/check_cron_file /var/lib/nagios_state/check_send_receive_state' } -define command{ - command_name check_send_receive_time_websockets - command_line /usr/lib/nagios/plugins/check_by_ssh -p $ARG1$ -l nagios -t 30 -i /var/lib/nagios/.ssh/id_rsa -H $HOSTADDRESS$ -C '/usr/lib/nagios/plugins/zulip_app_frontend/check_cron_file /var/lib/nagios_state/check_send_receive_websockets_state' -} - define command{ command_name check_rabbitmq_consumers command_line /usr/lib/nagios/plugins/check_by_ssh -p 22 -l nagios -t 30 -i /var/lib/nagios/.ssh/id_rsa -H $HOSTADDRESS$ -C '/usr/lib/nagios/plugins/zulip_app_frontend/check_rabbitmq_consumers $ARG1$' diff --git a/puppet/zulip_ops/files/nagios3/conf.d/services.cfg b/puppet/zulip_ops/files/nagios3/conf.d/services.cfg index 16f85c3bbb..4ba421fdca 100644 --- a/puppet/zulip_ops/files/nagios3/conf.d/services.cfg +++ b/puppet/zulip_ops/files/nagios3/conf.d/services.cfg @@ -143,15 +143,6 @@ define service { contact_groups page_admins } -define service { - use generic-service - service_description Check send receive time_websockets - check_command check_send_receive_time_websockets!22 - max_check_attempts 2 - hostgroup_name frontends - contact_groups page_admins -} - define service { use generic-service service_description Check analytics state @@ -392,17 +383,6 @@ define service { contact_groups page_admins } -define service { - use generic-service - service_description Check rabbitmq tornado_return consumers - check_command check_rabbitmq_consumers!tornado_return - # Workaround weird checks 40s after first error causing alerts - # from a single failure because cron hasn't run again yet - max_check_attempts 3 - hostgroup_name singletornado_frontends - contact_groups page_admins -} - define service { use generic-service service_description Check rabbitmq user_activity_interval consumers @@ -480,17 +460,6 @@ define service { contact_groups admins } -define service { - use generic-service - service_description Check rabbitmq message sender consumers - check_command check_rabbitmq_consumers!message_sender - # Workaround weird checks 40s after first error causing alerts - # from a single failure because cron hasn't run again yet - max_check_attempts 3 - hostgroup_name frontends - contact_groups page_admins -} - define service { use generic-service service_description Check rabbitmq missedmessage mobile notifications consumers diff --git a/puppet/zulip_ops/files/nginx/sites-available/loadbalancer b/puppet/zulip_ops/files/nginx/sites-available/loadbalancer index cfe7341574..e6a377f158 100644 --- a/puppet/zulip_ops/files/nginx/sites-available/loadbalancer +++ b/puppet/zulip_ops/files/nginx/sites-available/loadbalancer @@ -34,11 +34,6 @@ server { include /etc/nginx/zulip-include/proxy; } - location /sockjs { - proxy_pass https://staging; - include /etc/nginx/zulip-include/location-sockjs; - } - # We don't need /api/v1/events/internal, because that doesn't go through the loadbalancer. location ~ /json/events|/api/v1/events { proxy_pass https://staging; @@ -63,11 +58,6 @@ server { include /etc/nginx/zulip-include/proxy; } - location /sockjs { - proxy_pass https://prod; - include /etc/nginx/zulip-include/location-sockjs; - } - location ~ /json/events|/api/v1/events { proxy_pass https://prod; include /etc/nginx/zulip-include/proxy_longpolling; @@ -91,11 +81,6 @@ server { include /etc/nginx/zulip-include/proxy; } - location /sockjs { - proxy_pass https://prod; - include /etc/nginx/zulip-include/location-sockjs; - } - location ~ /json/events|/api/v1/events { proxy_pass https://prod; include /etc/nginx/zulip-include/proxy_longpolling; @@ -115,11 +100,6 @@ server { include /etc/nginx/zulip-include/proxy; } - location /sockjs { - proxy_pass https://prod; - include /etc/nginx/zulip-include/location-sockjs; - } - location ~ /json/events|/api/v1/events { proxy_pass https://prod; include /etc/nginx/zulip-include/proxy_longpolling; diff --git a/requirements/common.in b/requirements/common.in index 21c1baaaf5..c8f207ce25 100644 --- a/requirements/common.in +++ b/requirements/common.in @@ -95,9 +95,6 @@ redis # Needed for Python 2+3 compatibility six -# Needed for Tornado websockets support -sockjs-tornado - # Needed to parse source maps for error reporting sourcemap diff --git a/requirements/dev.txt b/requirements/dev.txt index d9e3cfa5ed..bc80d35ba6 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -762,8 +762,6 @@ social-auth-core==3.2.0 \ --hash=sha256:8320666548a532eb158968eda542bbe1863682357c432d8c4e28034a7f1e3b58 \ --hash=sha256:d81ed681e3c0722300b61a0792c5db5d21206793f95ca810f010c1cc931c8d89 \ # via social-auth-app-django -sockjs-tornado==1.0.6 \ - --hash=sha256:ec12b0c37723b0aac56610fb9b6aa68390720d0c9c2a10461df030c3a1d9af95 soupsieve==1.9.5 \ --hash=sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5 \ --hash=sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda \ diff --git a/requirements/prod.txt b/requirements/prod.txt index 199861a9e0..8a85f14395 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -510,8 +510,6 @@ social-auth-core==3.2.0 \ --hash=sha256:8320666548a532eb158968eda542bbe1863682357c432d8c4e28034a7f1e3b58 \ --hash=sha256:d81ed681e3c0722300b61a0792c5db5d21206793f95ca810f010c1cc931c8d89 \ # via social-auth-app-django -sockjs-tornado==1.0.6 \ - --hash=sha256:ec12b0c37723b0aac56610fb9b6aa68390720d0c9c2a10461df030c3a1d9af95 soupsieve==1.9.5 \ --hash=sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5 \ --hash=sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda \ diff --git a/scripts/lib/upgrade-zulip-stage-2 b/scripts/lib/upgrade-zulip-stage-2 index c28070b1bc..36a7e3cffa 100755 --- a/scripts/lib/upgrade-zulip-stage-2 +++ b/scripts/lib/upgrade-zulip-stage-2 @@ -163,7 +163,7 @@ if not args.skip_migrations: if os.path.exists("/etc/supervisor/conf.d/zulip_db.conf"): subprocess.check_call(["supervisorctl", "stop", "process-fts-updates"], preexec_fn=su_to_zulip) -core_server_services = ["zulip-django", "zulip-senders:*", +core_server_services = ["zulip-django", "zulip-tornado" if tornado_processes == 1 else "zulip-tornado:*"] worker_services = ["zulip-workers:*"] # Stop and start thumbor service only if thumbor is installed. diff --git a/scripts/nagios/check-rabbitmq-consumers b/scripts/nagios/check-rabbitmq-consumers index 092168350c..8fe25deff6 100755 --- a/scripts/nagios/check-rabbitmq-consumers +++ b/scripts/nagios/check-rabbitmq-consumers @@ -55,7 +55,6 @@ queues = { 'error_reports', 'feedback_messages', 'invites', - 'message_sender', 'missedmessage_emails', 'missedmessage_email_senders', 'email_senders', @@ -68,7 +67,6 @@ queues = { 'user_presence', # These queues may not be present if settings.TORNADO_PROCESSES > 1 'notify_tornado', - 'tornado_return', } for queue_name in queues: @@ -79,8 +77,6 @@ for line in output.split('\n'): parts = line.split('\t') if len(parts) >= 2: queue_name = parts[0] - if queue_name.startswith("tornado_return_"): - queue_name = "tornado_return" if queue_name.startswith("notify_tornado_"): queue_name = "notify_tornado" consumers[queue_name] += 1 @@ -92,7 +88,7 @@ for queue_name in consumers.keys(): state_file_tmp = state_file_path + "-tmp" target_count = options.min_count - if queue_name in ["tornado_return", "notify_tornado"]: + if queue_name == "notify_tornado": target_count = TORNADO_PROCESSES if consumers[queue_name] < target_count: diff --git a/scripts/restart-server b/scripts/restart-server index e1ef6c470e..862c27c78e 100755 --- a/scripts/restart-server +++ b/scripts/restart-server @@ -35,7 +35,7 @@ if args.fill_cache: logging.info("Filling memcached caches") subprocess.check_call(["./manage.py", "fill_memcached_caches"]) -core_server_services = ["zulip-django", "zulip-senders:*"] +core_server_services = ["zulip-django"] if os.path.exists("/etc/supervisor/conf.d/thumbor.conf"): core_server_services.append("zulip-thumbor") diff --git a/static/js/bundles/app.js b/static/js/bundles/app.js index 5f4f023e32..4c01f62280 100644 --- a/static/js/bundles/app.js +++ b/static/js/bundles/app.js @@ -7,7 +7,6 @@ import "../../third/jquery-filedrop/jquery.filedrop.js"; import "jquery-caret-plugin/src/jquery.caret.js"; import "../../third/jquery-idle/jquery.idle.js"; import "spectrum-colorpicker"; -import "../../third/sockjs/sockjs-0.3.4.js"; import "../../third/marked/lib/marked.js"; import "xdate/src/xdate.js"; import "jquery-validation/dist/jquery.validate.js"; @@ -84,7 +83,6 @@ import "../fenced_code.js"; import "../markdown.js"; import "../local_message.js"; import "../echo.js"; -import "../socket.js"; import "../sent_messages.js"; import "../compose_state.js"; import "../compose_actions.js"; diff --git a/static/js/global.d.ts b/static/js/global.d.ts index c67bd27c80..33d40c91cf 100644 --- a/static/js/global.d.ts +++ b/static/js/global.d.ts @@ -8,7 +8,6 @@ declare let Filter: any; declare let LightboxCanvas: any; declare let MessageListData: any; declare let MessageListView: any; -declare let Socket: any; declare let activity: any; declare let admin: any; declare let alert_words: any; diff --git a/static/js/setup.js b/static/js/setup.js index df3bc45afe..8214c5adfc 100644 --- a/static/js/setup.js +++ b/static/js/setup.js @@ -2,10 +2,7 @@ $(function () { if (util.is_mobile()) { - // if the client is mobile, disable websockets for message sending - // (it doesn't work on iOS for some reason). - page_params.use_websockets = false; - // Also disable the tutorial; it's ugly on mobile. + // Disable the tutorial; it's ugly on mobile. page_params.needs_tutorial = false; } @@ -54,5 +51,4 @@ $(function () { return $(this).is(sel) || $(this).closest(sel).length; }; } - transmit.initialize(); }); diff --git a/static/js/socket.js b/static/js/socket.js deleted file mode 100644 index 3f764b6080..0000000000 --- a/static/js/socket.js +++ /dev/null @@ -1,403 +0,0 @@ -const CLOSE_REASONS = { - none_given: {code: 4000, msg: "No reason provided"}, - no_heartbeat: {code: 4001, msg: "Missed too many heartbeats"}, - auth_fail: {code: 4002, msg: "Authentication failed"}, - ack_timeout: {code: 4003, msg: "ACK timeout"}, - cant_send: {code: 4004, msg: "User attempted to send while Socket was not ready"}, - unsuspend: {code: 4005, msg: "Got unsuspend event"}, -}; - -function Socket(url) { - this.url = url; - this._is_open = false; - this._is_authenticated = false; - this._is_reconnecting = false; - this._reconnect_initiation_time = null; - this._next_req_id_counter = 0; - this._connection_failures = 0; - this._reconnect_timeout_id = null; - this._heartbeat_timeout_id = null; - this._localstorage_requests_key = 'zulip_socket_requests'; - this._requests = this._localstorage_requests(); - - const that = this; - this._is_unloading = false; - $(window).on("unload", function () { - that._is_unloading = true; - }); - - $(document).on("unsuspend", function () { - that._try_to_reconnect({reason: 'unsuspend'}); - }); - - this._supported_protocols = [ - 'websocket', - 'xdr-streaming', - 'xhr-streaming', - 'xdr-polling', - 'xhr-polling', - 'jsonp-polling', - ]; - if (page_params.test_suite) { - this._supported_protocols = _.reject(this._supported_protocols, - function (x) { return x === 'xhr-streaming'; }); - // Don't create the SockJS on startup when running under the test suite. - // The first XHR request gets considered part of the page load and - // therefore the PhantomJS onLoadFinished handler doesn't get called - // until the SockJS XHR finishes, which happens at the heartbeat, 25 - // seconds later. The SockJS objects will be created on demand anyway. - } else { - this._create_sockjs_object(); - } -} - -Socket.prototype = { - _create_sockjs_object: function Socket__create_sockjs_object() { - this._sockjs = new SockJS(this.url, null, {protocols_whitelist: this._supported_protocols}); - this._setup_sockjs_callbacks(this._sockjs); - }, - - _make_request: function Socket__make_request(type) { - return {req_id: this._get_next_req_id(), - type: type, - state: 'pending'}; - }, - - // Note that by default messages are queued and retried across - // browser restarts if a restart takes place before a message - // is successfully transmitted. - // If that is the case, the success/error callbacks will not - // be automatically called. - send: function Socket__send(msg, success, error) { - const request = this._make_request('request'); - request.msg = msg; - request.success = success; - request.error = error; - this._save_request(request); - - if (!this._can_send()) { - this._try_to_reconnect({reason: 'cant_send'}); - return; - } - - this._do_send(request); - }, - - _get_next_req_id: function Socket__get_next_req_id() { - const req_id = page_params.queue_id + ':' + this._next_req_id_counter; - this._next_req_id_counter += 1; - return req_id; - }, - - _req_id_too_new: function Socket__req_id_too_new(req_id) { - const counter = req_id.split(':')[2]; - - return parseInt(counter, 10) >= this._next_req_id_counter; - }, - - _req_id_sorter: function Socket__req_id_sorter(req_id_a, req_id_b) { - // Sort in ascending order - const a_count = parseInt(req_id_a.split(':')[2], 10); - const b_count = parseInt(req_id_b.split(':')[2], 10); - - return a_count - b_count; - }, - - _do_send: function Socket__do_send(request) { - const that = this; - this._requests[request.req_id].ack_timeout_id = setTimeout(function () { - blueslip.info("Timeout on ACK for request " + request.req_id); - that._try_to_reconnect({reason: 'ack_timeout'}); - }, 2000); - - try { - this._update_request_state(request.req_id, 'sent'); - this._sockjs.send(JSON.stringify({req_id: request.req_id, - type: request.type, request: request.msg})); - } catch (e) { - this._update_request_state(request.req_id, 'pending'); - if (e instanceof Error && e.message === 'INVALID_STATE_ERR') { - // The connection was somehow closed. Our on-close handler will - // be called imminently and we'll retry this request upon reconnect. - return; - } else if (e instanceof Error && e.message.indexOf("NS_ERROR_NOT_CONNECTED") !== -1) { - // This is a rarely-occurring Firefox error. I'm not sure - // whether our on-close handler will be called, so let's just - // call close() explicitly. - this._sockjs.close(); - return; - } - throw e; - } - }, - - _can_send: function Socket__can_send() { - return this._is_open && this._is_authenticated; - }, - - _resend: function Socket__resend(req_id) { - const req_info = this._requests[req_id]; - if (req_info.ack_timeout_id !== null) { - clearTimeout(req_info.ack_timeout_id); - req_info.ack_timeout_id = null; - } - - if (req_info.type !== 'request') { - return; - } - - this._do_send(req_info); - }, - - _process_response: function Socket__process_response(req_id, response) { - const req_info = this._requests[req_id]; - if (req_info === undefined) { - if (this._req_id_too_new(req_id)) { - blueslip.error("Got a response for an unknown request", - {request_id: req_id, next_id: this._next_req_id_counter, - outstanding_ids: _.keys(this._requests)}); - } - // There is a small race where we might start reauthenticating - // before one of our requests has finished but then have the request - // finish and thus receive the finish notification both from the - // status inquiry and from the normal response. Therefore, we might - // be processing the response for a request where we already got the - // response from a status inquiry. In that case, don't process the - // response twice. - return; - } - - if (response.result === 'success' && req_info.success !== undefined) { - req_info.success(response); - } else if (req_info.error !== undefined) { - req_info.error('response', response); - } - this._remove_request(req_id); - }, - - _process_ack: function Socket__process_ack(req_id) { - const req_info = this._requests[req_id]; - if (req_info === undefined) { - blueslip.error("Got an ACK for an unknown request", - {request_id: req_id, next_id: this._next_req_id_counter, - outstanding_ids: _.keys(this._requests)}); - return; - } - - if (req_info.ack_timeout_id !== null) { - clearTimeout(req_info.ack_timeout_id); - req_info.ack_timeout_id = null; - } - }, - - _setup_sockjs_callbacks: function Socket__setup_sockjs_callbacks(sockjs) { - const that = this; - sockjs.onopen = function Socket__sockjs_onopen() { - blueslip.info("Socket connected [transport=" + sockjs.protocol + "]"); - if (that._reconnect_initiation_time !== null) { - // If this is a reconnect, network was probably - // recently interrupted, so we optimistically restart - // get_events - server_events.restart_get_events(); - } - that._is_open = true; - - // Notify listeners that we've finished the websocket handshake - $(document).trigger($.Event('websocket_postopen.zulip', {})); - - const request = that._make_request('auth'); - request.msg = {csrf_token: csrf_token, - queue_id: page_params.queue_id, - status_inquiries: _.keys(that._requests)}; - request.success = function (resp) { - that._is_authenticated = true; - that._is_reconnecting = false; - that._reconnect_initiation_time = null; - that._connection_failures = 0; - const resend_queue = []; - _.each(resp.status_inquiries, function (status, id) { - if (status.status === 'complete') { - that._process_response(id, status.response); - } else if (status.status === 'received') { - that._update_request_state(id, 'sent'); - } else if (status.status === 'not_received') { - resend_queue.push(id); - } - }); - resend_queue.sort(that._req_id_sorter); - _.each(resend_queue, function (id) { - that._resend(id); - }); - }; - request.error = function (type, resp) { - blueslip.info("Could not authenticate with server: " + resp.msg); - that._connection_failures += 1; - that._try_to_reconnect({reason: 'auth_fail', - wait_time: that._reconnect_wait_time()}); - }; - that._save_request(request); - that._do_send(request); - }; - - sockjs.onmessage = function Socket__sockjs_onmessage(event) { - if (event.data.type === 'ack') { - that._process_ack(event.data.req_id); - } else { - that._process_response(event.data.req_id, event.data.response); - } - }; - - sockjs.onheartbeat = function Socket__sockjs_onheartbeat() { - if (that._heartbeat_timeout_id !== null) { - clearTimeout(that._heartbeat_timeout_id); - that._heartbeat_timeout_id = null; - } - that._heartbeat_timeout_id = setTimeout(function () { - that._heartbeat_timeout_id = null; - blueslip.info("Missed too many hearbeats"); - that._try_to_reconnect({reason: 'no_heartbeat'}); - }, 60000); - }; - - sockjs.onclose = function Socket__sockjs_onclose(event) { - if (that._is_unloading) { - return; - } - // We've failed to handshake, but notify that the attempt finished - $(document).trigger($.Event('websocket_postopen.zulip', {})); - - blueslip.info("SockJS connection lost. Attempting to reconnect soon." - + " (" + event.code.toString() + ", " + event.reason + ")"); - that._connection_failures += 1; - that._is_reconnecting = false; - // We don't need to specify a reason because the Socket is already closed - that._try_to_reconnect({wait_time: that._reconnect_wait_time()}); - }; - }, - - _reconnect_wait_time: function Socket__reconnect_wait_time() { - if (this._connection_failures === 1) { - // We specify a non-zero timeout here so that we don't try to - // immediately reconnect when the page is refreshing - return 30; - } - return Math.min(90, Math.exp(this._connection_failures / 2)) * 1000; - }, - - _try_to_reconnect: function Socket__try_to_reconnect(opts) { - opts = _.extend({wait_time: 0, reason: 'none_given'}, opts); - const that = this; - - const now = new Date().getTime(); - if (this._is_reconnecting && now - this._reconnect_initiation_time < 1000) { - // Only try to reconnect once a second - return; - } - - if (this._reconnect_timeout_id !== null) { - clearTimeout(this._reconnect_timeout_id); - this._reconnect_timeout_id = null; - } - - if (this._heartbeat_timeout_id !== null) { - clearTimeout(that._heartbeat_timeout_id); - this._heartbeat_timeout_id = null; - } - - // Cancel any pending auth requests and any timeouts for ACKs - _.each(this._requests, function (val, key) { - if (val.ack_timeout_id !== null) { - clearTimeout(val.ack_timeout_id); - val.ack_timeout_id = null; - } - - if (val.type === 'auth') { - that._remove_request(key); - } - }); - - this._is_open = false; - this._is_authenticated = false; - this._is_reconnecting = true; - this._reconnect_initiation_time = now; - // This is a little weird because we're also called from the SockJS - // onclose handler. Fortunately, close() does nothing on an - // already-closed SockJS object. However, we do have to check that - // this._sockjs isn't undefined since it's not created immediately - // when running under the test suite. - if (this._sockjs !== undefined) { - const close_reason = CLOSE_REASONS[opts.reason]; - this._sockjs.close(close_reason.code, close_reason.msg); - } - - this._reconnect_timeout_id = setTimeout(function () { - that._reconnect_timeout_id = null; - blueslip.info("Attempting socket reconnect."); - that._create_sockjs_object(); - }, opts.wait_time); - }, - - _localstorage_requests: function Socket__localstorage_requests() { - if (!localstorage.supported()) { - return {}; - } - return JSON.parse(window.localStorage[this._localstorage_requests_key] || "{}"); - }, - - _save_localstorage_requests: function Socket__save_localstorage_requests() { - if (!localstorage.supported()) { - return; - } - - // Auth requests are always session-specific, so don't store them for later - const non_auth_reqs = {}; - _.each(this._requests, function (val, key) { - if (val.type !== 'auth') { - non_auth_reqs[key] = val; - } - }); - - try { - window.localStorage[this._localstorage_requests_key] = JSON.stringify(non_auth_reqs); - } catch (e) { - // We can't catch a specific exception type, because browsers return different types - // for out of space errors. See http://chrisberkhout.com/blog/localstorage-errors/ for - // more details. - blueslip.warn("Failed to save to local storage, caught exception when saving " + e); - } - }, - - _save_request: function Socket__save_request(request) { - this._requests[request.req_id] = request; - - if (!localstorage.supported()) { - return; - } - - this._save_localstorage_requests(); - }, - - _remove_request: function Socket__remove_request(req_id) { - delete this._requests[req_id]; - - if (!localstorage.supported()) { - return; - } - - this._save_localstorage_requests(); - - }, - - _update_request_state: function Socket__update_request_state(req_id, state) { - this._requests[req_id].state = state; - - if (!localstorage.supported()) { - return; - } - - this._save_localstorage_requests(); - }, -}; - -module.exports = Socket; -window.Socket = Socket; diff --git a/static/js/transmit.js b/static/js/transmit.js index e0a09405ea..50aa83aa5c 100644 --- a/static/js/transmit.js +++ b/static/js/transmit.js @@ -1,25 +1,3 @@ -let socket; -exports.initialize = function () { - // We initialize the socket inside a function so that this code - // runs after `csrf_token` is initialized in setup.js. - if (page_params.use_websockets) { - socket = new Socket("/sockjs"); - } - // For debugging. The socket will eventually move out of this file anyway. - exports._socket = socket; -}; - -function send_message_socket(request, success, error) { - request.socket_user_agent = navigator.userAgent; - socket.send(request, success, function (type, resp) { - let err_msg = "Error sending message"; - if (type === 'response') { - err_msg += ": " + resp.msg; - } - error(err_msg); - }); -} - function send_message_ajax(request, success, error) { channel.post({ url: '/json/messages', @@ -52,11 +30,7 @@ exports.send_message = function (request, on_success, error) { sent_messages.report_server_ack(request.local_id); } - if (page_params.use_websockets) { - send_message_socket(request, success, error); - } else { - send_message_ajax(request, success, error); - } + send_message_ajax(request, success, error); }; exports.reply_message = function (opts) { diff --git a/static/third/sockjs/sockjs-0.3.4.js b/static/third/sockjs/sockjs-0.3.4.js deleted file mode 100644 index c1acc52003..0000000000 --- a/static/third/sockjs/sockjs-0.3.4.js +++ /dev/null @@ -1,2384 +0,0 @@ -/** @preserve - Software from "SockJS" is Copyright (c) 2011-2012 VMware, Inc. and is provided - under the following license (the SockJS software has been modified): - -- - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -- -*/ - -// JSON2 by Douglas Crockford (minified). -var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c 1) { - this._listeners[eventType] = arr.slice(0, idx).concat( arr.slice(idx+1) ); - } else { - delete this._listeners[eventType]; - } - return; - } - return; -}; - -REventTarget.prototype.dispatchEvent = function (event) { - var t = event.type; - var args = Array.prototype.slice.call(arguments, 0); - if (this['on'+t]) { - this['on'+t].apply(this, args); - } - if (this._listeners && t in this._listeners) { - for(var i=0; i < this._listeners[t].length; i++) { - this._listeners[t][i].apply(this, args); - } - } -}; -// [*] End of lib/reventtarget.js - - -// [*] Including lib/simpleevent.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var SimpleEvent = function(type, obj) { - this.type = type; - if (typeof obj !== 'undefined') { - for(var k in obj) { - if (!obj.hasOwnProperty(k)) continue; - this[k] = obj[k]; - } - } -}; - -SimpleEvent.prototype.toString = function() { - var r = []; - for(var k in this) { - if (!this.hasOwnProperty(k)) continue; - var v = this[k]; - if (typeof v === 'function') v = '[function]'; - r.push(k + '=' + v); - } - return 'SimpleEvent(' + r.join(', ') + ')'; -}; -// [*] End of lib/simpleevent.js - - -// [*] Including lib/eventemitter.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var EventEmitter = function(events) { - var that = this; - that._events = events || []; - that._listeners = {}; -}; -EventEmitter.prototype.emit = function(type) { - var that = this; - that._verifyType(type); - if (that._nuked) return; - - var args = Array.prototype.slice.call(arguments, 1); - if (that['on'+type]) { - that['on'+type].apply(that, args); - } - if (type in that._listeners) { - for(var i = 0; i < that._listeners[type].length; i++) { - that._listeners[type][i].apply(that, args); - } - } -}; - -EventEmitter.prototype.on = function(type, callback) { - var that = this; - that._verifyType(type); - if (that._nuked) return; - - if (!(type in that._listeners)) { - that._listeners[type] = []; - } - that._listeners[type].push(callback); -}; - -EventEmitter.prototype._verifyType = function(type) { - var that = this; - if (utils.arrIndexOf(that._events, type) === -1) { - utils.log('Event ' + JSON.stringify(type) + - ' not listed ' + JSON.stringify(that._events) + - ' in ' + that); - } -}; - -EventEmitter.prototype.nuke = function() { - var that = this; - that._nuked = true; - for(var i=0; i= 3000 && code <= 4999); -}; - -// See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/ -// and RFC 2988. -utils.countRTO = function (rtt) { - var rto; - if (rtt > 100) { - rto = 3 * rtt; // rto > 300msec - } else { - rto = rtt + 200; // 200msec < rto <= 300msec - } - return rto; -} - -utils.log = function() { - if (_window.console && console.log && console.log.apply) { - console.log.apply(console, arguments); - } -}; - -utils.bind = function(fun, that) { - if (fun.bind) { - return fun.bind(that); - } else { - return function() { - return fun.apply(that, arguments); - }; - } -}; - -utils.flatUrl = function(url) { - return url.indexOf('?') === -1 && url.indexOf('#') === -1; -}; - -utils.amendUrl = function(url) { - var dl = _document.location; - if (!url) { - throw new Error('Wrong url for SockJS'); - } - if (!utils.flatUrl(url)) { - throw new Error('Only basic urls are supported in SockJS'); - } - - // '//abc' --> 'http://abc' - if (url.indexOf('//') === 0) { - url = dl.protocol + url; - } - // '/abc' --> 'http://localhost:80/abc' - if (url.indexOf('/') === 0) { - url = dl.protocol + '//' + dl.host + url; - } - // strip trailing slashes - url = url.replace(/[/]+$/,''); - return url; -}; - -// IE doesn't support [].indexOf. -utils.arrIndexOf = function(arr, obj){ - for(var i=0; i < arr.length; i++){ - if(arr[i] === obj){ - return i; - } - } - return -1; -}; - -utils.arrSkip = function(arr, obj) { - var idx = utils.arrIndexOf(arr, obj); - if (idx === -1) { - return arr.slice(); - } else { - var dst = arr.slice(0, idx); - return dst.concat(arr.slice(idx+1)); - } -}; - -// Via: https://gist.github.com/1133122/2121c601c5549155483f50be3da5305e83b8c5df -utils.isArray = Array.isArray || function(value) { - return {}.toString.call(value).indexOf('Array') >= 0 -}; - -utils.delay = function(t, fun) { - if(typeof t === 'function') { - fun = t; - t = 0; - } - return setTimeout(fun, t); -}; - - -// Chars worth escaping, as defined by Douglas Crockford: -// https://github.com/douglascrockford/JSON-js/blob/47a9882cddeb1e8529e07af9736218075372b8ac/json2.js#L196 -var json_escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - json_lookup = { -"\u0000":"\\u0000","\u0001":"\\u0001","\u0002":"\\u0002","\u0003":"\\u0003", -"\u0004":"\\u0004","\u0005":"\\u0005","\u0006":"\\u0006","\u0007":"\\u0007", -"\b":"\\b","\t":"\\t","\n":"\\n","\u000b":"\\u000b","\f":"\\f","\r":"\\r", -"\u000e":"\\u000e","\u000f":"\\u000f","\u0010":"\\u0010","\u0011":"\\u0011", -"\u0012":"\\u0012","\u0013":"\\u0013","\u0014":"\\u0014","\u0015":"\\u0015", -"\u0016":"\\u0016","\u0017":"\\u0017","\u0018":"\\u0018","\u0019":"\\u0019", -"\u001a":"\\u001a","\u001b":"\\u001b","\u001c":"\\u001c","\u001d":"\\u001d", -"\u001e":"\\u001e","\u001f":"\\u001f","\"":"\\\"","\\":"\\\\", -"\u007f":"\\u007f","\u0080":"\\u0080","\u0081":"\\u0081","\u0082":"\\u0082", -"\u0083":"\\u0083","\u0084":"\\u0084","\u0085":"\\u0085","\u0086":"\\u0086", -"\u0087":"\\u0087","\u0088":"\\u0088","\u0089":"\\u0089","\u008a":"\\u008a", -"\u008b":"\\u008b","\u008c":"\\u008c","\u008d":"\\u008d","\u008e":"\\u008e", -"\u008f":"\\u008f","\u0090":"\\u0090","\u0091":"\\u0091","\u0092":"\\u0092", -"\u0093":"\\u0093","\u0094":"\\u0094","\u0095":"\\u0095","\u0096":"\\u0096", -"\u0097":"\\u0097","\u0098":"\\u0098","\u0099":"\\u0099","\u009a":"\\u009a", -"\u009b":"\\u009b","\u009c":"\\u009c","\u009d":"\\u009d","\u009e":"\\u009e", -"\u009f":"\\u009f","\u00ad":"\\u00ad","\u0600":"\\u0600","\u0601":"\\u0601", -"\u0602":"\\u0602","\u0603":"\\u0603","\u0604":"\\u0604","\u070f":"\\u070f", -"\u17b4":"\\u17b4","\u17b5":"\\u17b5","\u200c":"\\u200c","\u200d":"\\u200d", -"\u200e":"\\u200e","\u200f":"\\u200f","\u2028":"\\u2028","\u2029":"\\u2029", -"\u202a":"\\u202a","\u202b":"\\u202b","\u202c":"\\u202c","\u202d":"\\u202d", -"\u202e":"\\u202e","\u202f":"\\u202f","\u2060":"\\u2060","\u2061":"\\u2061", -"\u2062":"\\u2062","\u2063":"\\u2063","\u2064":"\\u2064","\u2065":"\\u2065", -"\u2066":"\\u2066","\u2067":"\\u2067","\u2068":"\\u2068","\u2069":"\\u2069", -"\u206a":"\\u206a","\u206b":"\\u206b","\u206c":"\\u206c","\u206d":"\\u206d", -"\u206e":"\\u206e","\u206f":"\\u206f","\ufeff":"\\ufeff","\ufff0":"\\ufff0", -"\ufff1":"\\ufff1","\ufff2":"\\ufff2","\ufff3":"\\ufff3","\ufff4":"\\ufff4", -"\ufff5":"\\ufff5","\ufff6":"\\ufff6","\ufff7":"\\ufff7","\ufff8":"\\ufff8", -"\ufff9":"\\ufff9","\ufffa":"\\ufffa","\ufffb":"\\ufffb","\ufffc":"\\ufffc", -"\ufffd":"\\ufffd","\ufffe":"\\ufffe","\uffff":"\\uffff"}; - -// Some extra characters that Chrome gets wrong, and substitutes with -// something else on the wire. -var extra_escapable = /[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g, - extra_lookup; - -// JSON Quote string. Use native implementation when possible. -var JSONQuote = (JSON && JSON.stringify) || function(string) { - json_escapable.lastIndex = 0; - if (json_escapable.test(string)) { - string = string.replace(json_escapable, function(a) { - return json_lookup[a]; - }); - } - return '"' + string + '"'; -}; - -// This may be quite slow, so let's delay until user actually uses bad -// characters. -var unroll_lookup = function(escapable) { - var i; - var unrolled = {} - var c = [] - for(i=0; i<65536; i++) { - c.push( String.fromCharCode(i) ); - } - escapable.lastIndex = 0; - c.join('').replace(escapable, function (a) { - unrolled[ a ] = '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - return ''; - }); - escapable.lastIndex = 0; - return unrolled; -}; - -// Quote string, also taking care of unicode characters that browsers -// often break. Especially, take care of unicode surrogates: -// http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates -utils.quote = function(string) { - var quoted = JSONQuote(string); - - // In most cases this should be very fast and good enough. - extra_escapable.lastIndex = 0; - if(!extra_escapable.test(quoted)) { - return quoted; - } - - if(!extra_lookup) extra_lookup = unroll_lookup(extra_escapable); - - return quoted.replace(extra_escapable, function(a) { - return extra_lookup[a]; - }); -} - -var _all_protocols = ['websocket', - 'xdr-streaming', - 'xhr-streaming', - 'iframe-eventsource', - 'iframe-htmlfile', - 'xdr-polling', - 'xhr-polling', - 'iframe-xhr-polling', - 'jsonp-polling']; - -utils.probeProtocols = function() { - var probed = {}; - for(var i=0; i<_all_protocols.length; i++) { - var protocol = _all_protocols[i]; - // User can have a typo in protocol name. - probed[protocol] = SockJS[protocol] && - SockJS[protocol].enabled(); - } - return probed; -}; - -utils.detectProtocols = function(probed, protocols_whitelist, info) { - var pe = {}, - protocols = []; - if (!protocols_whitelist) protocols_whitelist = _all_protocols; - for(var i=0; i 0) { - maybe_push(protos); - } - } - } - - // 1. Websocket - if (info.websocket !== false) { - maybe_push(['websocket']); - } - - // 2. Streaming - if (pe['xhr-streaming'] && !info.null_origin) { - protocols.push('xhr-streaming'); - } else { - if (pe['xdr-streaming'] && !info.cookie_needed && !info.null_origin) { - protocols.push('xdr-streaming'); - } else { - maybe_push(['iframe-eventsource', - 'iframe-htmlfile']); - } - } - - // 3. Polling - if (pe['xhr-polling'] && !info.null_origin) { - protocols.push('xhr-polling'); - } else { - if (pe['xdr-polling'] && !info.cookie_needed && !info.null_origin) { - protocols.push('xdr-polling'); - } else { - maybe_push(['iframe-xhr-polling', - 'jsonp-polling']); - } - } - return protocols; -} -// [*] End of lib/utils.js - - -// [*] Including lib/dom.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -// May be used by htmlfile jsonp and transports. -var MPrefix = '_sockjs_global'; -utils.createHook = function() { - var window_id = 'a' + utils.random_string(8); - if (!(MPrefix in _window)) { - var map = {}; - _window[MPrefix] = function(window_id) { - if (!(window_id in map)) { - map[window_id] = { - id: window_id, - del: function() {delete map[window_id];} - }; - } - return map[window_id]; - } - } - return _window[MPrefix](window_id); -}; - - - -utils.attachMessage = function(listener) { - utils.attachEvent('message', listener); -}; -utils.attachEvent = function(event, listener) { - if (typeof _window.addEventListener !== 'undefined') { - _window.addEventListener(event, listener, false); - } else { - // IE quirks. - // According to: http://stevesouders.com/misc/test-postmessage.php - // the message gets delivered only to 'document', not 'window'. - _document.attachEvent("on" + event, listener); - // I get 'window' for ie8. - _window.attachEvent("on" + event, listener); - } -}; - -utils.detachMessage = function(listener) { - utils.detachEvent('message', listener); -}; -utils.detachEvent = function(event, listener) { - if (typeof _window.addEventListener !== 'undefined') { - _window.removeEventListener(event, listener, false); - } else { - _document.detachEvent("on" + event, listener); - _window.detachEvent("on" + event, listener); - } -}; - - -var on_unload = {}; -// Things registered after beforeunload are to be called immediately. -var after_unload = false; - -var trigger_unload_callbacks = function() { - for(var ref in on_unload) { - on_unload[ref](); - delete on_unload[ref]; - }; -}; - -var unload_triggered = function() { - if(after_unload) return; - after_unload = true; - trigger_unload_callbacks(); -}; - -// 'unload' alone is not reliable in opera within an iframe, but we -// can't use `beforeunload` as IE fires it on javascript: links. -utils.attachEvent('unload', unload_triggered); - -utils.unload_add = function(listener) { - var ref = utils.random_string(8); - on_unload[ref] = listener; - if (after_unload) { - utils.delay(trigger_unload_callbacks); - } - return ref; -}; -utils.unload_del = function(ref) { - if (ref in on_unload) - delete on_unload[ref]; -}; - - -utils.createIframe = function (iframe_url, error_callback) { - var iframe = _document.createElement('iframe'); - var tref, unload_ref; - var unattach = function() { - clearTimeout(tref); - // Explorer had problems with that. - try {iframe.onload = null;} catch (x) {} - iframe.onerror = null; - }; - var cleanup = function() { - if (iframe) { - unattach(); - // This timeout makes chrome fire onbeforeunload event - // within iframe. Without the timeout it goes straight to - // onunload. - setTimeout(function() { - if(iframe) { - iframe.parentNode.removeChild(iframe); - } - iframe = null; - }, 0); - utils.unload_del(unload_ref); - } - }; - var onerror = function(r) { - if (iframe) { - cleanup(); - error_callback(r); - } - }; - var post = function(msg, origin) { - try { - // When the iframe is not loaded, IE raises an exception - // on 'contentWindow'. - if (iframe && iframe.contentWindow) { - iframe.contentWindow.postMessage(msg, origin); - } - } catch (x) {}; - }; - - iframe.src = iframe_url; - iframe.style.display = 'none'; - iframe.style.position = 'absolute'; - iframe.onerror = function(){onerror('onerror');}; - iframe.onload = function() { - // `onload` is triggered before scripts on the iframe are - // executed. Give it few seconds to actually load stuff. - clearTimeout(tref); - tref = setTimeout(function(){onerror('onload timeout');}, 2000); - }; - _document.body.appendChild(iframe); - tref = setTimeout(function(){onerror('timeout');}, 15000); - unload_ref = utils.unload_add(cleanup); - return { - post: post, - cleanup: cleanup, - loaded: unattach - }; -}; - -utils.createHtmlfile = function (iframe_url, error_callback) { - var doc = new ActiveXObject('htmlfile'); - var tref, unload_ref; - var iframe; - var unattach = function() { - clearTimeout(tref); - }; - var cleanup = function() { - if (doc) { - unattach(); - utils.unload_del(unload_ref); - iframe.parentNode.removeChild(iframe); - iframe = doc = null; - CollectGarbage(); - } - }; - var onerror = function(r) { - if (doc) { - cleanup(); - error_callback(r); - } - }; - var post = function(msg, origin) { - try { - // When the iframe is not loaded, IE raises an exception - // on 'contentWindow'. - if (iframe && iframe.contentWindow) { - iframe.contentWindow.postMessage(msg, origin); - } - } catch (x) {}; - }; - - doc.open(); - doc.write('' + - 'document.domain="' + document.domain + '";' + - ''); - doc.close(); - doc.parentWindow[WPrefix] = _window[WPrefix]; - var c = doc.createElement('div'); - doc.body.appendChild(c); - iframe = doc.createElement('iframe'); - c.appendChild(iframe); - iframe.src = iframe_url; - tref = setTimeout(function(){onerror('timeout');}, 15000); - unload_ref = utils.unload_add(cleanup); - return { - post: post, - cleanup: cleanup, - loaded: unattach - }; -}; -// [*] End of lib/dom.js - - -// [*] Including lib/dom2.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var AbstractXHRObject = function(){}; -AbstractXHRObject.prototype = new EventEmitter(['chunk', 'finish']); - -AbstractXHRObject.prototype._start = function(method, url, payload, opts) { - var that = this; - - try { - that.xhr = new XMLHttpRequest(); - } catch(x) {}; - - if (!that.xhr) { - try { - that.xhr = new _window.ActiveXObject('Microsoft.XMLHTTP'); - } catch(x) {}; - } - if (_window.ActiveXObject || _window.XDomainRequest) { - // IE8 caches even POSTs - url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date); - } - - // Explorer tends to keep connection open, even after the - // tab gets closed: http://bugs.jquery.com/ticket/5280 - that.unload_ref = utils.unload_add(function(){that._cleanup(true);}); - try { - that.xhr.open(method, url, true); - } catch(e) { - // IE raises an exception on wrong port. - that.emit('finish', 0, ''); - that._cleanup(); - return; - }; - - if (!opts || !opts.no_credentials) { - // Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest : - // "This never affects same-site requests." - that.xhr.withCredentials = 'true'; - } - if (opts && opts.headers) { - for(var key in opts.headers) { - that.xhr.setRequestHeader(key, opts.headers[key]); - } - } - - that.xhr.onreadystatechange = function() { - if (that.xhr) { - var x = that.xhr; - switch (x.readyState) { - case 3: - // IE doesn't like peeking into responseText or status - // on Microsoft.XMLHTTP and readystate=3 - try { - var status = x.status; - var text = x.responseText; - } catch (x) {}; - // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450 - if (status === 1223) status = 204; - - // IE does return readystate == 3 for 404 answers. - if (text && text.length > 0) { - that.emit('chunk', status, text); - } - break; - case 4: - var status = x.status; - // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450 - if (status === 1223) status = 204; - - that.emit('finish', status, x.responseText); - that._cleanup(false); - break; - } - } - }; - that.xhr.send(payload); -}; - -AbstractXHRObject.prototype._cleanup = function(abort) { - var that = this; - if (!that.xhr) return; - utils.unload_del(that.unload_ref); - - // IE needs this field to be a function - that.xhr.onreadystatechange = function(){}; - - if (abort) { - try { - that.xhr.abort(); - } catch(x) {}; - } - that.unload_ref = that.xhr = null; -}; - -AbstractXHRObject.prototype.close = function() { - var that = this; - that.nuke(); - that._cleanup(true); -}; - -var XHRCorsObject = utils.XHRCorsObject = function() { - var that = this, args = arguments; - utils.delay(function(){that._start.apply(that, args);}); -}; -XHRCorsObject.prototype = new AbstractXHRObject(); - -var XHRLocalObject = utils.XHRLocalObject = function(method, url, payload) { - var that = this; - utils.delay(function(){ - that._start(method, url, payload, { - no_credentials: true - }); - }); -}; -XHRLocalObject.prototype = new AbstractXHRObject(); - - - -// References: -// http://ajaxian.com/archives/100-line-ajax-wrapper -// http://msdn.microsoft.com/en-us/library/cc288060(v=VS.85).aspx -var XDRObject = utils.XDRObject = function(method, url, payload) { - var that = this; - utils.delay(function(){that._start(method, url, payload);}); -}; -XDRObject.prototype = new EventEmitter(['chunk', 'finish']); -XDRObject.prototype._start = function(method, url, payload) { - var that = this; - var xdr = new XDomainRequest(); - // IE caches even POSTs - url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date); - - var onerror = xdr.ontimeout = xdr.onerror = function() { - that.emit('finish', 0, ''); - that._cleanup(false); - }; - xdr.onprogress = function() { - that.emit('chunk', 200, xdr.responseText); - }; - xdr.onload = function() { - that.emit('finish', 200, xdr.responseText); - that._cleanup(false); - }; - that.xdr = xdr; - that.unload_ref = utils.unload_add(function(){that._cleanup(true);}); - try { - // Fails with AccessDenied if port number is bogus - that.xdr.open(method, url); - that.xdr.send(payload); - } catch(x) { - onerror(); - } -}; - -XDRObject.prototype._cleanup = function(abort) { - var that = this; - if (!that.xdr) return; - utils.unload_del(that.unload_ref); - - that.xdr.ontimeout = that.xdr.onerror = that.xdr.onprogress = - that.xdr.onload = null; - if (abort) { - try { - that.xdr.abort(); - } catch(x) {}; - } - that.unload_ref = that.xdr = null; -}; - -XDRObject.prototype.close = function() { - var that = this; - that.nuke(); - that._cleanup(true); -}; - -// 1. Is natively via XHR -// 2. Is natively via XDR -// 3. Nope, but postMessage is there so it should work via the Iframe. -// 4. Nope, sorry. -utils.isXHRCorsCapable = function() { - if (_window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()) { - return 1; - } - // XDomainRequest doesn't work if page is served from file:// - if (_window.XDomainRequest && _document.domain) { - return 2; - } - if (IframeTransport.enabled()) { - return 3; - } - return 4; -}; -// [*] End of lib/dom2.js - - -// [*] Including lib/sockjs.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var SockJS = function(url, dep_protocols_whitelist, options) { - if (this === _window) { - // makes `new` optional - return new SockJS(url, dep_protocols_whitelist, options); - } - - var that = this, protocols_whitelist; - that._options = {devel: false, debug: false, protocols_whitelist: [], - info: undefined, rtt: undefined}; - if (options) { - utils.objectExtend(that._options, options); - } - that._base_url = utils.amendUrl(url); - that._server = that._options.server || utils.random_number_string(1000); - if (that._options.protocols_whitelist && - that._options.protocols_whitelist.length) { - protocols_whitelist = that._options.protocols_whitelist; - } else { - // Deprecated API - if (typeof dep_protocols_whitelist === 'string' && - dep_protocols_whitelist.length > 0) { - protocols_whitelist = [dep_protocols_whitelist]; - } else if (utils.isArray(dep_protocols_whitelist)) { - protocols_whitelist = dep_protocols_whitelist - } else { - protocols_whitelist = null; - } - if (protocols_whitelist) { - that._debug('Deprecated API: Use "protocols_whitelist" option ' + - 'instead of supplying protocol list as a second ' + - 'parameter to SockJS constructor.'); - } - } - that._protocols = []; - that.protocol = null; - that.readyState = SockJS.CONNECTING; - that._ir = createInfoReceiver(that._base_url); - that._ir.onfinish = function(info, rtt) { - that._ir = null; - if (info) { - if (that._options.info) { - // Override if user supplies the option - info = utils.objectExtend(info, that._options.info); - } - if (that._options.rtt) { - rtt = that._options.rtt; - } - that._applyInfo(info, rtt, protocols_whitelist); - that._didClose(); - } else { - that._didClose(1002, 'Can\'t connect to server', true); - } - }; -}; -// Inheritance -SockJS.prototype = new REventTarget(); - -SockJS.version = "0.3.4"; - -SockJS.CONNECTING = 0; -SockJS.OPEN = 1; -SockJS.CLOSING = 2; -SockJS.CLOSED = 3; - -SockJS.prototype._debug = function() { - if (this._options.debug) - utils.log.apply(utils, arguments); -}; - -SockJS.prototype._dispatchOpen = function() { - var that = this; - if (that.readyState === SockJS.CONNECTING) { - if (that._transport_tref) { - clearTimeout(that._transport_tref); - that._transport_tref = null; - } - that.readyState = SockJS.OPEN; - that.dispatchEvent(new SimpleEvent("open")); - } else { - // The server might have been restarted, and lost track of our - // connection. - that._didClose(1006, "Server lost session"); - } -}; - -SockJS.prototype._dispatchMessage = function(data) { - var that = this; - if (that.readyState !== SockJS.OPEN) - return; - that.dispatchEvent(new SimpleEvent("message", {data: data})); -}; - -SockJS.prototype._dispatchHeartbeat = function(data) { - var that = this; - if (that.readyState !== SockJS.OPEN) - return; - that.dispatchEvent(new SimpleEvent('heartbeat', {})); -}; - -SockJS.prototype._didClose = function(code, reason, force) { - var that = this; - if (that.readyState !== SockJS.CONNECTING && - that.readyState !== SockJS.OPEN && - that.readyState !== SockJS.CLOSING) - return; - if (that._ir) { - that._ir.nuke(); - that._ir = null; - } - - if (that._transport) { - that._transport.doCleanup(); - that._transport = null; - } - - var close_event = new SimpleEvent("close", { - code: code, - reason: reason, - wasClean: utils.userSetCode(code)}); - - if (!utils.userSetCode(code) && - that.readyState === SockJS.CONNECTING && !force) { - if (that._try_next_protocol(close_event)) { - return; - } - close_event = new SimpleEvent("close", {code: 2000, - reason: "All transports failed", - wasClean: false, - last_event: close_event}); - } - that.readyState = SockJS.CLOSED; - - utils.delay(function() { - that.dispatchEvent(close_event); - }); -}; - -SockJS.prototype._didMessage = function(data) { - var that = this; - var type = data.slice(0, 1); - switch(type) { - case 'o': - that._dispatchOpen(); - break; - case 'a': - var payload = JSON.parse(data.slice(1) || '[]'); - for(var i=0; i < payload.length; i++){ - that._dispatchMessage(payload[i]); - } - break; - case 'm': - var payload = JSON.parse(data.slice(1) || 'null'); - that._dispatchMessage(payload); - break; - case 'c': - var payload = JSON.parse(data.slice(1) || '[]'); - that._didClose(payload[0], payload[1]); - break; - case 'h': - that._dispatchHeartbeat(); - break; - } -}; - -SockJS.prototype._try_next_protocol = function(close_event) { - var that = this; - if (that.protocol) { - that._debug('Closed transport:', that.protocol, ''+close_event); - that.protocol = null; - } - if (that._transport_tref) { - clearTimeout(that._transport_tref); - that._transport_tref = null; - } - - while(1) { - var protocol = that.protocol = that._protocols.shift(); - if (!protocol) { - return false; - } - // Some protocols require access to `body`, what if were in - // the `head`? - if (SockJS[protocol] && - SockJS[protocol].need_body === true && - (!_document.body || - (typeof _document.readyState !== 'undefined' - && _document.readyState !== 'complete'))) { - that._protocols.unshift(protocol); - that.protocol = 'waiting-for-load'; - utils.attachEvent('load', function(){ - that._try_next_protocol(); - }); - return true; - } - - if (!SockJS[protocol] || - !SockJS[protocol].enabled(that._options)) { - that._debug('Skipping transport:', protocol); - } else { - var roundTrips = SockJS[protocol].roundTrips || 1; - var to = ((that._options.rto || 0) * roundTrips) || 5000; - that._transport_tref = utils.delay(to, function() { - if (that.readyState === SockJS.CONNECTING) { - // I can't understand how it is possible to run - // this timer, when the state is CLOSED, but - // apparently in IE everythin is possible. - that._didClose(2007, "Transport timeouted"); - } - }); - - var connid = utils.random_string(8); - var trans_url = that._base_url + '/' + that._server + '/' + connid; - that._debug('Opening transport:', protocol, ' url:'+trans_url, - ' RTO:'+that._options.rto); - that._transport = new SockJS[protocol](that, trans_url, - that._base_url); - return true; - } - } -}; - -SockJS.prototype.close = function(code, reason) { - var that = this; - if (code && !utils.userSetCode(code)) - throw new Error("INVALID_ACCESS_ERR"); - if(that.readyState !== SockJS.CONNECTING && - that.readyState !== SockJS.OPEN) { - return false; - } - that.readyState = SockJS.CLOSING; - that._didClose(code || 1000, reason || "Normal closure"); - return true; -}; - -SockJS.prototype.send = function(data) { - var that = this; - if (that.readyState === SockJS.CONNECTING) - throw new Error('INVALID_STATE_ERR'); - if (that.readyState === SockJS.OPEN) { - that._transport.doSend(utils.quote('' + data)); - } - return true; -}; - -SockJS.prototype._applyInfo = function(info, rtt, protocols_whitelist) { - var that = this; - that._options.info = info; - that._options.rtt = rtt; - that._options.rto = utils.countRTO(rtt); - that._options.info.null_origin = !_document.domain; - var probed = utils.probeProtocols(); - that._protocols = utils.detectProtocols(probed, protocols_whitelist, info); -}; -// [*] End of lib/sockjs.js - - -// [*] Including lib/trans-websocket.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var WebSocketTransport = SockJS.websocket = function(ri, trans_url) { - var that = this; - var url = trans_url + '/websocket'; - if (url.slice(0, 5) === 'https') { - url = 'wss' + url.slice(5); - } else { - url = 'ws' + url.slice(4); - } - that.ri = ri; - that.url = url; - var Constructor = _window.WebSocket || _window.MozWebSocket; - - // Zulip Addition: Notify that we're about to start the - // WebSocket handshake - $(document).trigger($.Event('websocket_preopen.zulip', {})); - - that.ws = new Constructor(that.url); - that.ws.onmessage = function(e) { - that.ri._didMessage(e.data); - }; - // Firefox has an interesting bug. If a websocket connection is - // created after onunload, it stays alive even when user - // navigates away from the page. In such situation let's lie - - // let's not open the ws connection at all. See: - // https://github.com/sockjs/sockjs-client/issues/28 - // https://bugzilla.mozilla.org/show_bug.cgi?id=696085 - that.unload_ref = utils.unload_add(function(){that.ws.close()}); - that.ws.onclose = function() { - that.ri._didMessage(utils.closeFrame(1006, "WebSocket connection broken")); - }; -}; - -WebSocketTransport.prototype.doSend = function(data) { - this.ws.send('[' + data + ']'); -}; - -WebSocketTransport.prototype.doCleanup = function() { - var that = this; - var ws = that.ws; - if (ws) { - ws.onmessage = ws.onclose = null; - ws.close(); - utils.unload_del(that.unload_ref); - that.unload_ref = that.ri = that.ws = null; - } -}; - -WebSocketTransport.enabled = function() { - return !!(_window.WebSocket || _window.MozWebSocket); -}; - -// In theory, ws should require 1 round trip. But in chrome, this is -// not very stable over SSL. Most likely a ws connection requires a -// separate SSL connection, in which case 2 round trips are an -// absolute minumum. -WebSocketTransport.roundTrips = 2; -// [*] End of lib/trans-websocket.js - - -// [*] Including lib/trans-sender.js -/* - * ***** BEGIN LICENSE BLOCK ***** - * Copyright (c) 2011-2012 VMware, Inc. - * - * For the license see COPYING. - * ***** END LICENSE BLOCK ***** - */ - -var BufferedSender = function() {}; -BufferedSender.prototype.send_constructor = function(sender) { - var that = this; - that.send_buffer = []; - that.sender = sender; -}; -BufferedSender.prototype.doSend = function(message) { - var that = this; - that.send_buffer.push(message); - if (!that.send_stop) { - that.send_schedule(); - } -}; - -// For polling transports in a situation when in the message callback, -// new message is being send. If the sending connection was started -// before receiving one, it is possible to saturate the network and -// timeout due to the lack of receiving socket. To avoid that we delay -// sending messages by some small time, in order to let receiving -// connection be started beforehand. This is only a halfmeasure and -// does not fix the big problem, but it does make the tests go more -// stable on slow networks. -BufferedSender.prototype.send_schedule_wait = function() { - var that = this; - var tref; - that.send_stop = function() { - that.send_stop = null; - clearTimeout(tref); - }; - tref = utils.delay(25, function() { - that.send_stop = null; - that.send_schedule(); - }); -}; - -BufferedSender.prototype.send_schedule = function() { - var that = this; - if (that.send_buffer.length > 0) { - var payload = '[' + that.send_buffer.join(',') + ']'; - that.send_stop = that.sender(that.trans_url, payload, function(success, abort_reason) { - that.send_stop = null; - if (success === false) { - that.ri._didClose(1006, 'Sending error ' + abort_reason); - } else { - that.send_schedule_wait(); - } - }); - that.send_buffer = []; - } -}; - -BufferedSender.prototype.send_destructor = function() { - var that = this; - if (that._send_stop) { - that._send_stop(); - } - that._send_stop = null; -}; - -var jsonPGenericSender = function(url, payload, callback) { - var that = this; - - if (!('_send_form' in that)) { - var form = that._send_form = _document.createElement('form'); - var area = that._send_area = _document.createElement('textarea'); - area.name = 'd'; - form.style.display = 'none'; - form.style.position = 'absolute'; - form.method = 'POST'; - form.enctype = 'application/x-www-form-urlencoded'; - form.acceptCharset = "UTF-8"; - form.appendChild(area); - _document.body.appendChild(form); - } - var form = that._send_form; - var area = that._send_area; - var id = 'a' + utils.random_string(8); - form.target = id; - form.action = url + '/jsonp_send?i=' + id; - - var iframe; - try { - // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) - iframe = _document.createElement('