Files
zulip/frontend_tests/node_tests/transmit.js
Anders Kaseorg ea6934c26d dependencies: Remove WebSockets system for sending messages.
Zulip has had a small use of WebSockets (specifically, for the code
path of sending messages, via the webapp only) since ~2013.  We
originally added this use of WebSockets in the hope that the latency
benefits of doing so would allow us to avoid implementing a markdown
local echo; they were not.  Further, HTTP/2 may have eliminated the
latency difference we hoped to exploit by using WebSockets in any
case.

While we’d originally imagined using WebSockets for other endpoints,
there was never a good justification for moving more components to the
WebSockets system.

This WebSockets code path had a lot of downsides/complexity,
including:

* The messy hack involving constructing an emulated request object to
  hook into doing Django requests.
* The `message_senders` queue processor system, which increases RAM
  needs and must be provisioned independently from the rest of the
  server).
* A duplicate check_send_receive_time Nagios test specific to
  WebSockets.
* The requirement for users to have their firewalls/NATs allow
  WebSocket connections, and a setting to disable them for networks
  where WebSockets don’t work.
* Dependencies on the SockJS family of libraries, which has at times
  been poorly maintained, and periodically throws random JavaScript
  exceptions in our production environments without a deep enough
  traceback to effectively investigate.
* A total of about 1600 lines of our code related to the feature.
* Increased load on the Tornado system, especially around a Zulip
  server restart, and especially for large installations like
  zulipchat.com, resulting in extra delay before messages can be sent
  again.

As detailed in
https://github.com/zulip/zulip/pull/12862#issuecomment-536152397, it
appears that removing WebSockets moderately increases the time it
takes for the `send_message` API query to return from the server, but
does not significantly change the time between when a message is sent
and when it is received by clients.  We don’t understand the reason
for that change (suggesting the possibility of a measurement error),
and even if it is a real change, we consider that potential small
latency regression to be acceptable.

If we later want WebSockets, we’ll likely want to just use Django
Channels.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-14 22:34:00 -08:00

192 lines
4.4 KiB
JavaScript

const noop = function () {};
set_global('$', global.make_zjquery());
set_global('page_params', {});
set_global('channel', {});
set_global('navigator', {});
set_global('reload', {});
set_global('reload_state', {});
set_global('sent_messages', {
start_tracking_message: noop,
report_server_ack: noop,
});
set_global('blueslip', global.make_zblueslip());
zrequire('people');
zrequire('util');
zrequire('transmit');
run_test('transmit_message_ajax', () => {
let success_func_called;
const success = function () {
success_func_called = true;
};
const request = {foo: 'bar'};
channel.post = function (opts) {
assert.equal(opts.url, '/json/messages');
assert.equal(opts.data.foo, 'bar');
opts.success();
};
transmit.send_message(request, success);
assert(success_func_called);
channel.xhr_error_message = function (msg) {
assert.equal(msg, 'Error sending message');
return msg;
};
channel.post = function (opts) {
assert.equal(opts.url, '/json/messages');
assert.equal(opts.data.foo, 'bar');
const xhr = 'whatever';
opts.error(xhr, 'timeout');
};
let error_func_called;
const error = function (response) {
assert.equal(response, 'Error sending message');
error_func_called = true;
};
transmit.send_message(request, success, error);
assert(error_func_called);
});
run_test('transmit_message_ajax_reload_pending', () => {
const success = function () { throw 'unexpected success'; };
reload_state.is_pending = function () {
return true;
};
let reload_initiated;
reload.initiate = function (opts) {
reload_initiated = true;
assert.deepEqual(opts, {
immediate: true,
save_pointer: true,
save_narrow: true,
save_compose: true,
send_after_reload: true,
});
};
const request = {foo: 'bar'};
let error_func_called;
const error = function (response) {
assert.equal(response, 'Error sending message');
error_func_called = true;
};
error_func_called = false;
channel.post = function (opts) {
assert.equal(opts.url, '/json/messages');
assert.equal(opts.data.foo, 'bar');
const xhr = 'whatever';
opts.error(xhr, 'bad request');
};
transmit.send_message(request, success, error);
assert(!error_func_called);
assert(reload_initiated);
});
run_test('reply_message_stream', () => {
const stream_message = {
type: 'stream',
stream: 'social',
topic: 'lunch',
sender_full_name: 'Alice',
sender_id: 123,
};
const content = 'hello';
let send_message_args;
transmit.send_message = (args) => {
send_message_args = args;
};
page_params.user_id = 44;
page_params.queue_id = 66;
sent_messages.get_new_local_id = () => 99;
transmit.reply_message({
message: stream_message,
content: content,
});
assert.deepEqual(send_message_args, {
sender_id: 44,
queue_id: 66,
local_id: 99,
type: 'stream',
to: 'social',
content: '@**Alice** hello',
topic: 'lunch',
});
});
run_test('reply_message_private', () => {
const fred = {
user_id: 3,
email: 'fred@example.com',
full_name: 'Fred Frost',
};
people.add(fred);
people.is_my_user_id = () => false;
const pm_message = {
type: 'private',
display_recipient: [
{id: fred.user_id},
],
};
const content = 'hello';
let send_message_args;
transmit.send_message = (args) => {
send_message_args = args;
};
page_params.user_id = 155;
page_params.queue_id = 177;
sent_messages.get_new_local_id = () => 199;
transmit.reply_message({
message: pm_message,
content: content,
});
assert.deepEqual(send_message_args, {
sender_id: 155,
queue_id: 177,
local_id: 199,
type: 'private',
to: '["fred@example.com"]',
content: 'hello',
});
});
run_test('reply_message_errors', () => {
const bogus_message = {
type: 'bogus',
};
blueslip.set_test_data('error', 'unknown message type: bogus');
transmit.reply_message({
message: bogus_message,
});
blueslip.clear_test_data();
});