2021-03-11 05:43:45 +01:00
|
|
|
import $ from "jquery";
|
2021-02-28 01:11:47 +01:00
|
|
|
import _ from "lodash";
|
|
|
|
|
|
|
|
import * as channel from "./channel";
|
|
|
|
import * as echo from "./echo";
|
2021-02-28 21:33:40 +01:00
|
|
|
import * as message_events from "./message_events";
|
2021-02-28 01:11:47 +01:00
|
|
|
import * as message_store from "./message_store";
|
|
|
|
import * as reload from "./reload";
|
|
|
|
import * as reload_state from "./reload_state";
|
|
|
|
import * as sent_messages from "./sent_messages";
|
|
|
|
import * as server_events_dispatch from "./server_events_dispatch";
|
|
|
|
import * as ui_report from "./ui_report";
|
|
|
|
|
2018-11-30 00:48:13 +01:00
|
|
|
// Docs: https://zulip.readthedocs.io/en/latest/subsystems/events-system.html
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let waiting_on_homeview_load = true;
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let events_stored_while_loading = [];
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let get_events_xhr;
|
|
|
|
let get_events_timeout;
|
|
|
|
let get_events_failures = 0;
|
|
|
|
const get_events_params = {};
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2020-02-13 20:56:37 +01:00
|
|
|
// This field keeps track of whether we are attempting to
|
|
|
|
// force-reconnect to the events server due to suspecting we are
|
|
|
|
// offline. It is important for avoiding races with the presence
|
|
|
|
// system when coming back from unsuspend.
|
2021-02-28 01:11:47 +01:00
|
|
|
export let suspect_offline = false;
|
2020-02-13 20:56:37 +01:00
|
|
|
|
2014-01-30 20:29:00 +01:00
|
|
|
function get_events_success(events) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let messages = [];
|
|
|
|
const update_message_events = [];
|
|
|
|
const post_message_events = [];
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const clean_event = function clean_event(event) {
|
2014-01-31 06:24:15 +01:00
|
|
|
// Only log a whitelist of the event to remove private data
|
2020-07-15 01:29:15 +02:00
|
|
|
return _.pick(event, "id", "type", "op");
|
2014-01-31 06:24:15 +01:00
|
|
|
};
|
|
|
|
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
for (const event of events) {
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
2020-07-15 00:34:28 +02:00
|
|
|
get_events_params.last_event_id = Math.max(get_events_params.last_event_id, event.id);
|
2020-10-07 10:20:41 +02:00
|
|
|
} catch (error) {
|
|
|
|
blueslip.error(
|
|
|
|
"Failed to update last_event_id",
|
|
|
|
{event: clean_event(event)},
|
|
|
|
error.stack,
|
|
|
|
);
|
2014-01-31 06:24:15 +01:00
|
|
|
}
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2014-01-30 20:50:18 +01:00
|
|
|
if (waiting_on_homeview_load) {
|
|
|
|
events_stored_while_loading = events_stored_while_loading.concat(events);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (events_stored_while_loading.length > 0) {
|
|
|
|
events = events_stored_while_loading.concat(events);
|
|
|
|
events_stored_while_loading = [];
|
|
|
|
}
|
|
|
|
|
2017-05-31 22:04:19 +02:00
|
|
|
// Most events are dispatched via the code server_events_dispatch,
|
|
|
|
// called in the default case. The goal of this split is to avoid
|
|
|
|
// contributors needing to read or understand the complex and
|
|
|
|
// rarely modified logic for non-normal events.
|
2019-11-02 00:06:25 +01:00
|
|
|
const dispatch_event = function dispatch_event(event) {
|
2014-01-30 19:25:25 +01:00
|
|
|
switch (event.type) {
|
2020-07-15 02:14:03 +02:00
|
|
|
case "message": {
|
|
|
|
const msg = event.message;
|
|
|
|
msg.flags = event.flags;
|
|
|
|
if (event.local_message_id) {
|
|
|
|
msg.local_id = event.local_message_id;
|
|
|
|
sent_messages.report_event_received(event.local_message_id);
|
|
|
|
}
|
|
|
|
messages.push(msg);
|
|
|
|
break;
|
2017-07-14 19:30:23 +02:00
|
|
|
}
|
2016-08-23 03:53:05 +02:00
|
|
|
|
2020-07-15 02:14:03 +02:00
|
|
|
case "update_message":
|
|
|
|
update_message_events.push(event);
|
|
|
|
break;
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2020-07-15 02:14:03 +02:00
|
|
|
case "delete_message":
|
|
|
|
case "submessage":
|
|
|
|
case "update_message_flags":
|
|
|
|
post_message_events.push(event);
|
|
|
|
break;
|
Fix bugs related to batching message events.
This is general fix that makes sure that we
apply all message-modifying events after we
apply the events for the initial incoming
messages.
The particular scenario that was reported here
was when you would have two tabs for Zulip,
with one of them open and in a PM view, and
with the open tab being at the bottom of the
feed, such that incoming messages would be
immediately visible.
Now suppose the other person in that PM
conversation sent you a message.
The open tab would properly immediately
mark the message as read, and notify
the server. The problem was that the closed
tab would not process the main message event
until it "woke up", by which time the flag-update
event was bundled into the same event batch
as the main message event. We'd then process
the flag-update first, which essentially was
a noop, since the actual message wasn't in
the message store yet. The user would then
see unread counts increment in the closed tab,
while the open tab didn't increment. This
was confusing.
Now `server_events.js` processes the actual
message first and does the flag-update as part of a
`post_message_events` loop.
We include events for updating message flags,
deleting messages, and attaching submessages
to messages in the `post_message_events` array.
This bug was a bit difficult to simulate in a dev
environment, since you needed your "open" tab
to be in focus to simulate the race, but as
soon as you tab to another place to deliver
a message (whether from the browser or otherwise),
the open tab is no longer in focus.
I did this in the console of my "open"
tab to work around it:
unread_ops.process_visible = unread_ops.mark_current_list_as_read;
This problem was easy to reproduce, but it wasn't
entirely consistent. I often needed to send
several messages in succession to trigger event
batching and force the race condition. (This wasn't
precisely a "race", as events actually arrive in the
correct order; it was having them arrive in the same
batch that triggered the bug.)
2018-08-25 13:36:25 +02:00
|
|
|
|
2020-07-15 02:14:03 +02:00
|
|
|
default:
|
2020-09-24 07:50:36 +02:00
|
|
|
server_events_dispatch.dispatch_normal_event(event);
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
2014-01-31 06:24:15 +01:00
|
|
|
};
|
|
|
|
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
for (const event of events) {
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
|
|
|
dispatch_event(event);
|
2020-10-07 10:20:41 +02:00
|
|
|
} catch (error) {
|
2020-07-15 00:34:28 +02:00
|
|
|
blueslip.error(
|
2020-10-07 10:20:41 +02:00
|
|
|
"Failed to process an event\n" + blueslip.exception_msg(error),
|
2020-07-15 00:34:28 +02:00
|
|
|
{event: clean_event(event)},
|
2020-10-07 10:20:41 +02:00
|
|
|
error.stack,
|
2020-07-15 00:34:28 +02:00
|
|
|
);
|
2014-01-31 06:24:15 +01:00
|
|
|
}
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
|
|
|
if (messages.length !== 0) {
|
2017-10-12 00:49:25 +02:00
|
|
|
// Sort by ID, so that if we get multiple messages back from
|
|
|
|
// the server out-of-order, we'll still end up with our
|
|
|
|
// message lists in order.
|
2020-07-15 01:29:15 +02:00
|
|
|
messages = _.sortBy(messages, "id");
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
|
|
|
messages = echo.process_from_server(messages);
|
2019-02-21 00:27:30 +01:00
|
|
|
if (messages.length > 0) {
|
2021-01-22 22:29:08 +01:00
|
|
|
for (const message of messages) {
|
|
|
|
message_store.set_message_booleans(message);
|
|
|
|
}
|
2020-02-12 07:17:13 +01:00
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
const sent_by_this_client = messages.some((msg) =>
|
2020-07-02 02:16:03 +02:00
|
|
|
sent_messages.messages.has(msg.local_id),
|
2020-02-12 07:17:13 +01:00
|
|
|
);
|
|
|
|
// If some message in this batch of events was sent by this
|
|
|
|
// client, almost every time, this message will be the only one
|
|
|
|
// in messages, because multiple messages being returned by
|
|
|
|
// get_events usually only happens when a client is offline.
|
|
|
|
// But in any case, insert_new_messages handles multiple
|
|
|
|
// messages, only one of which was sent by this client,
|
|
|
|
// correctly.
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
|
2019-02-21 00:27:30 +01:00
|
|
|
message_events.insert_new_messages(messages, sent_by_this_client);
|
|
|
|
}
|
2020-10-07 10:20:41 +02:00
|
|
|
} catch (error) {
|
2020-07-15 00:34:28 +02:00
|
|
|
blueslip.error(
|
2020-10-07 10:20:41 +02:00
|
|
|
"Failed to insert new messages\n" + blueslip.exception_msg(error),
|
2020-07-15 00:34:28 +02:00
|
|
|
undefined,
|
2020-10-07 10:20:41 +02:00
|
|
|
error.stack,
|
2020-07-15 00:34:28 +02:00
|
|
|
);
|
2014-01-31 06:24:15 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
|
|
|
|
2018-06-06 18:50:09 +02:00
|
|
|
if (home_msg_list.selected_id() === -1 && !home_msg_list.empty()) {
|
2014-01-30 19:25:25 +01:00
|
|
|
home_msg_list.select_id(home_msg_list.first().id, {then_scroll: false});
|
|
|
|
}
|
|
|
|
|
2018-08-25 13:18:56 +02:00
|
|
|
if (update_message_events.length !== 0) {
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
2018-08-25 13:18:56 +02:00
|
|
|
message_events.update_messages(update_message_events);
|
2020-10-07 10:20:41 +02:00
|
|
|
} catch (error) {
|
2020-07-15 00:34:28 +02:00
|
|
|
blueslip.error(
|
2020-10-07 10:20:41 +02:00
|
|
|
"Failed to update messages\n" + blueslip.exception_msg(error),
|
2020-07-15 00:34:28 +02:00
|
|
|
undefined,
|
2020-10-07 10:20:41 +02:00
|
|
|
error.stack,
|
2020-07-15 00:34:28 +02:00
|
|
|
);
|
2014-01-31 06:24:15 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
Fix bugs related to batching message events.
This is general fix that makes sure that we
apply all message-modifying events after we
apply the events for the initial incoming
messages.
The particular scenario that was reported here
was when you would have two tabs for Zulip,
with one of them open and in a PM view, and
with the open tab being at the bottom of the
feed, such that incoming messages would be
immediately visible.
Now suppose the other person in that PM
conversation sent you a message.
The open tab would properly immediately
mark the message as read, and notify
the server. The problem was that the closed
tab would not process the main message event
until it "woke up", by which time the flag-update
event was bundled into the same event batch
as the main message event. We'd then process
the flag-update first, which essentially was
a noop, since the actual message wasn't in
the message store yet. The user would then
see unread counts increment in the closed tab,
while the open tab didn't increment. This
was confusing.
Now `server_events.js` processes the actual
message first and does the flag-update as part of a
`post_message_events` loop.
We include events for updating message flags,
deleting messages, and attaching submessages
to messages in the `post_message_events` array.
This bug was a bit difficult to simulate in a dev
environment, since you needed your "open" tab
to be in focus to simulate the race, but as
soon as you tab to another place to deliver
a message (whether from the browser or otherwise),
the open tab is no longer in focus.
I did this in the console of my "open"
tab to work around it:
unread_ops.process_visible = unread_ops.mark_current_list_as_read;
This problem was easy to reproduce, but it wasn't
entirely consistent. I often needed to send
several messages in succession to trigger event
batching and force the race condition. (This wasn't
precisely a "race", as events actually arrive in the
correct order; it was having them arrive in the same
batch that triggered the bug.)
2018-08-25 13:36:25 +02:00
|
|
|
|
|
|
|
// We do things like updating message flags and deleting messages last,
|
|
|
|
// to avoid ordering issues that are caused by batch handling of
|
|
|
|
// messages above.
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
for (const event of post_message_events) {
|
Fix bugs related to batching message events.
This is general fix that makes sure that we
apply all message-modifying events after we
apply the events for the initial incoming
messages.
The particular scenario that was reported here
was when you would have two tabs for Zulip,
with one of them open and in a PM view, and
with the open tab being at the bottom of the
feed, such that incoming messages would be
immediately visible.
Now suppose the other person in that PM
conversation sent you a message.
The open tab would properly immediately
mark the message as read, and notify
the server. The problem was that the closed
tab would not process the main message event
until it "woke up", by which time the flag-update
event was bundled into the same event batch
as the main message event. We'd then process
the flag-update first, which essentially was
a noop, since the actual message wasn't in
the message store yet. The user would then
see unread counts increment in the closed tab,
while the open tab didn't increment. This
was confusing.
Now `server_events.js` processes the actual
message first and does the flag-update as part of a
`post_message_events` loop.
We include events for updating message flags,
deleting messages, and attaching submessages
to messages in the `post_message_events` array.
This bug was a bit difficult to simulate in a dev
environment, since you needed your "open" tab
to be in focus to simulate the race, but as
soon as you tab to another place to deliver
a message (whether from the browser or otherwise),
the open tab is no longer in focus.
I did this in the console of my "open"
tab to work around it:
unread_ops.process_visible = unread_ops.mark_current_list_as_read;
This problem was easy to reproduce, but it wasn't
entirely consistent. I often needed to send
several messages in succession to trigger event
batching and force the race condition. (This wasn't
precisely a "race", as events actually arrive in the
correct order; it was having them arrive in the same
batch that triggered the bug.)
2018-08-25 13:36:25 +02:00
|
|
|
server_events_dispatch.dispatch_normal_event(event);
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
|
|
|
|
2018-11-29 21:31:08 +01:00
|
|
|
function show_ui_connection_error() {
|
|
|
|
ui_report.show_error($("#connection-error"));
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#connection-error").addClass("get-events-error");
|
2018-11-29 21:31:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function hide_ui_connection_error() {
|
|
|
|
ui_report.hide_error($("#connection-error"));
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#connection-error").removeClass("get-events-error");
|
2018-11-29 21:31:08 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 20:50:18 +01:00
|
|
|
function get_events(options) {
|
2020-07-16 22:40:18 +02:00
|
|
|
options = {dont_block: false, ...options};
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2018-08-04 15:40:25 +02:00
|
|
|
if (reload_state.is_in_progress()) {
|
2016-03-31 07:26:14 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-31 09:35:39 +02:00
|
|
|
// TODO: In the future, we may implement Tornado support for live
|
2020-10-04 08:47:36 +02:00
|
|
|
// update for web_public_visitor, but until then, there's nothing
|
2020-08-31 09:35:39 +02:00
|
|
|
// to do here.
|
2020-10-04 08:47:36 +02:00
|
|
|
if (page_params.is_web_public_visitor) {
|
2020-08-31 09:35:39 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-30 21:01:52 +01:00
|
|
|
get_events_params.dont_block = options.dont_block || get_events_failures > 0;
|
2017-05-04 02:39:54 +02:00
|
|
|
|
2020-02-13 20:56:37 +01:00
|
|
|
if (get_events_params.dont_block) {
|
|
|
|
// If we're requesting an immediate re-connect to the server,
|
|
|
|
// that means it's fairly likely that this client has been off
|
|
|
|
// the Internet and thus may have stale state (which is
|
|
|
|
// important for potential presence issues).
|
2021-02-28 01:11:47 +01:00
|
|
|
suspect_offline = true;
|
2020-02-13 20:56:37 +01:00
|
|
|
}
|
2014-01-30 21:01:52 +01:00
|
|
|
if (get_events_params.queue_id === undefined) {
|
2017-04-24 21:40:16 +02:00
|
|
|
get_events_params.queue_id = page_params.queue_id;
|
2014-01-30 21:01:52 +01:00
|
|
|
get_events_params.last_event_id = page_params.last_event_id;
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 20:29:00 +01:00
|
|
|
if (get_events_xhr !== undefined) {
|
|
|
|
get_events_xhr.abort();
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
2014-01-30 20:29:00 +01:00
|
|
|
if (get_events_timeout !== undefined) {
|
|
|
|
clearTimeout(get_events_timeout);
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
2017-11-03 16:53:21 +01:00
|
|
|
|
|
|
|
get_events_params.client_gravatar = true;
|
2020-02-02 17:29:05 +01:00
|
|
|
get_events_params.slim_presence = true;
|
2017-11-03 16:53:21 +01:00
|
|
|
|
2014-01-30 20:29:00 +01:00
|
|
|
get_events_timeout = undefined;
|
2016-10-19 02:46:21 +02:00
|
|
|
get_events_xhr = channel.get({
|
2020-07-15 01:29:15 +02:00
|
|
|
url: "/json/events",
|
2018-12-18 19:34:45 +01:00
|
|
|
data: get_events_params,
|
2014-01-30 19:25:25 +01:00
|
|
|
idempotent: true,
|
2018-12-18 19:34:45 +01:00
|
|
|
timeout: page_params.poll_timeout,
|
2020-07-20 22:18:43 +02:00
|
|
|
success(data) {
|
2021-02-28 01:11:47 +01:00
|
|
|
suspect_offline = false;
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
|
|
|
get_events_xhr = undefined;
|
|
|
|
get_events_failures = 0;
|
2018-11-29 21:31:08 +01:00
|
|
|
hide_ui_connection_error();
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2014-01-31 06:24:15 +01:00
|
|
|
get_events_success(data.events);
|
2020-10-07 10:20:41 +02:00
|
|
|
} catch (error) {
|
2020-07-15 00:34:28 +02:00
|
|
|
blueslip.error(
|
2020-10-07 10:20:41 +02:00
|
|
|
"Failed to handle get_events success\n" + blueslip.exception_msg(error),
|
2020-07-15 00:34:28 +02:00
|
|
|
undefined,
|
2020-10-07 10:20:41 +02:00
|
|
|
error.stack,
|
2020-07-15 00:34:28 +02:00
|
|
|
);
|
2014-01-31 06:24:15 +01:00
|
|
|
}
|
2014-01-30 20:29:00 +01:00
|
|
|
get_events_timeout = setTimeout(get_events, 0);
|
2014-01-30 19:25:25 +01:00
|
|
|
},
|
2020-07-20 22:18:43 +02:00
|
|
|
error(xhr, error_type) {
|
2014-01-31 06:24:15 +01:00
|
|
|
try {
|
|
|
|
get_events_xhr = undefined;
|
2017-03-23 05:13:23 +01:00
|
|
|
// If we're old enough that our message queue has been
|
|
|
|
// garbage collected, immediately reload.
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
xhr.status === 400 &&
|
|
|
|
JSON.parse(xhr.responseText).code === "BAD_EVENT_QUEUE_ID"
|
|
|
|
) {
|
2014-01-31 06:24:15 +01:00
|
|
|
page_params.event_queue_expired = true;
|
2020-07-15 00:34:28 +02:00
|
|
|
reload.initiate({
|
|
|
|
immediate: true,
|
|
|
|
save_pointer: false,
|
|
|
|
save_narrow: true,
|
|
|
|
save_compose: true,
|
|
|
|
});
|
2014-01-31 06:24:15 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
if (error_type === "abort") {
|
2014-01-31 06:24:15 +01:00
|
|
|
// Don't restart if we explicitly aborted
|
|
|
|
return;
|
2020-07-15 01:29:15 +02:00
|
|
|
} else if (error_type === "timeout") {
|
2014-01-31 06:24:15 +01:00
|
|
|
// Retry indefinitely on timeout.
|
|
|
|
get_events_failures = 0;
|
2018-11-29 21:31:08 +01:00
|
|
|
hide_ui_connection_error();
|
2014-01-31 06:24:15 +01:00
|
|
|
} else {
|
|
|
|
get_events_failures += 1;
|
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2014-01-31 06:24:15 +01:00
|
|
|
if (get_events_failures >= 5) {
|
2018-11-29 21:31:08 +01:00
|
|
|
show_ui_connection_error();
|
2014-01-31 06:24:15 +01:00
|
|
|
} else {
|
2018-11-29 21:31:08 +01:00
|
|
|
hide_ui_connection_error();
|
2014-01-31 06:24:15 +01:00
|
|
|
}
|
2020-10-07 10:20:41 +02:00
|
|
|
} catch (error) {
|
2020-07-15 00:34:28 +02:00
|
|
|
blueslip.error(
|
2020-10-07 10:20:41 +02:00
|
|
|
"Failed to handle get_events error\n" + blueslip.exception_msg(error),
|
2020-07-15 00:34:28 +02:00
|
|
|
undefined,
|
2020-10-07 10:20:41 +02:00
|
|
|
error.stack,
|
2020-07-15 00:34:28 +02:00
|
|
|
);
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const retry_sec = Math.min(90, Math.exp(get_events_failures / 2));
|
2018-06-04 21:13:07 +02:00
|
|
|
get_events_timeout = setTimeout(get_events, retry_sec * 1000);
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
2014-01-30 20:50:18 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2021-02-28 01:11:47 +01:00
|
|
|
export function assert_get_events_running(error_message) {
|
2014-01-30 20:29:00 +01:00
|
|
|
if (get_events_xhr === undefined && get_events_timeout === undefined) {
|
2021-02-28 01:11:47 +01:00
|
|
|
restart_get_events({dont_block: true});
|
2014-01-30 19:25:25 +01:00
|
|
|
blueslip.error(error_message);
|
|
|
|
}
|
2021-02-28 01:11:47 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2021-02-28 01:11:47 +01:00
|
|
|
export function restart_get_events(options) {
|
2014-01-30 20:50:18 +01:00
|
|
|
get_events(options);
|
2021-02-28 01:11:47 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2021-02-28 01:11:47 +01:00
|
|
|
export function force_get_events() {
|
2014-01-30 20:50:18 +01:00
|
|
|
get_events_timeout = setTimeout(get_events, 0);
|
2021-02-28 01:11:47 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2021-02-28 01:11:47 +01:00
|
|
|
export function home_view_loaded() {
|
2014-01-30 19:25:25 +01:00
|
|
|
waiting_on_homeview_load = false;
|
2014-01-30 20:50:18 +01:00
|
|
|
get_events_success([]);
|
2014-01-30 19:25:25 +01:00
|
|
|
$(document).trigger("home_view_loaded.zulip");
|
2021-02-28 01:11:47 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2021-03-04 19:12:27 +01:00
|
|
|
let watchdog_time = Date.now();
|
2021-02-28 01:11:47 +01:00
|
|
|
|
|
|
|
export function check_for_unsuspend() {
|
2021-03-04 19:12:27 +01:00
|
|
|
const new_time = Date.now();
|
2020-07-15 00:34:28 +02:00
|
|
|
if (new_time - watchdog_time > 20000) {
|
|
|
|
// 20 seconds.
|
2014-01-30 19:25:25 +01:00
|
|
|
// Defensively reset watchdog_time here in case there's an
|
|
|
|
// exception in one of the event handlers
|
|
|
|
watchdog_time = new_time;
|
|
|
|
// Our app's JS wasn't running, which probably means the machine was
|
|
|
|
// asleep.
|
2020-12-11 04:26:23 +01:00
|
|
|
$(document).trigger("unsuspend");
|
2014-01-30 19:25:25 +01:00
|
|
|
}
|
|
|
|
watchdog_time = new_time;
|
2021-02-28 01:11:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
setInterval(check_for_unsuspend, 5000);
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2021-02-28 01:11:47 +01:00
|
|
|
export function initialize() {
|
2020-07-15 01:29:15 +02:00
|
|
|
$(document).on("unsuspend", () => {
|
2014-01-30 20:29:00 +01:00
|
|
|
// Immediately poll for new events on unsuspend
|
|
|
|
blueslip.log("Restarting get_events due to unsuspend");
|
|
|
|
get_events_failures = 0;
|
2021-02-28 01:11:47 +01:00
|
|
|
restart_get_events({dont_block: true});
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
2014-01-30 20:50:18 +01:00
|
|
|
get_events();
|
2021-02-28 01:11:47 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2021-02-28 01:11:47 +01:00
|
|
|
export function cleanup_event_queue() {
|
2014-01-30 19:25:25 +01:00
|
|
|
// Submit a request to the server to cleanup our event queue
|
2020-09-27 06:49:16 +02:00
|
|
|
if (page_params.event_queue_expired === true || page_params.no_event_queue === true) {
|
2014-01-30 19:25:25 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-10-12 05:31:32 +02:00
|
|
|
blueslip.log("Cleaning up our event queue");
|
2016-03-31 08:01:52 +02:00
|
|
|
// Set expired because in a reload we may be called twice.
|
|
|
|
page_params.event_queue_expired = true;
|
2014-01-30 19:25:25 +01:00
|
|
|
channel.del({
|
2020-07-15 01:29:15 +02:00
|
|
|
url: "/json/events",
|
2018-12-18 19:34:45 +01:00
|
|
|
data: {queue_id: page_params.queue_id},
|
2020-02-14 00:26:34 +01:00
|
|
|
ignore_reload: true,
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
2021-02-28 01:11:47 +01:00
|
|
|
}
|
2014-01-30 19:25:25 +01:00
|
|
|
|
2020-07-02 01:45:54 +02:00
|
|
|
window.addEventListener("beforeunload", () => {
|
2021-02-28 01:11:47 +01:00
|
|
|
cleanup_event_queue();
|
2014-01-30 19:25:25 +01:00
|
|
|
});
|
|
|
|
|
2016-08-04 17:44:35 +02:00
|
|
|
// For unit testing
|
2021-02-28 01:11:47 +01:00
|
|
|
export const _get_events_success = get_events_success;
|