diff --git a/src/client.js b/src/client.js
index 410634b3c..7ddb3a81e 100644
--- a/src/client.js
+++ b/src/client.js
@@ -3132,6 +3132,9 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
* will become the 'since' token for the next call to /sync. Only present if
* state=PREPARED or state=SYNCING.
*
+ * @param {boolean} data.catchingUp True if we are working our way through a
+ * backlog of events after connecting. Only present if state=SYNCING.
+ *
* @example
* matrixClient.on("sync", function(state, prevState, data) {
* switch (state) {
diff --git a/src/sync.js b/src/sync.js
index 99e0a7528..2b3b91f2d 100644
--- a/src/sync.js
+++ b/src/sync.js
@@ -73,6 +73,7 @@ function SyncApi(client, opts) {
this._peekRoomId = null;
this._currentSyncRequest = null;
this._syncState = null;
+ this._catchingUp = false;
this._running = false;
this._keepAliveTimer = null;
this._connectionReturnedDefer = null;
@@ -483,9 +484,30 @@ SyncApi.prototype._sync = function(syncOptions) {
const syncToken = client.store.getSyncToken();
+ let pollTimeout = this.opts.pollTimeout;
+
+ if (this.getSyncState() !== 'SYNCING' || this._catchingUp) {
+ // unless we are happily syncing already, we want the server to return
+ // as quickly as possible, even if there are no events queued. This
+ // serves two purposes:
+ //
+ // * When the connection dies, we want to know asap when it comes back,
+ // so that we can hide the error from the user. (We don't want to
+ // have to wait for an event or a timeout).
+ //
+ // * We want to know if the server has any to_device messages queued up
+ // for us. We do that by calling it with a zero timeout until it
+ // doesn't give us any more to_device messages.
+ this._catchingUp = true;
+ pollTimeout = 0;
+ }
+
+ // normal timeout= plus buffer time
+ const clientSideTimeoutMs = pollTimeout + BUFFER_PERIOD_MS;
+
const qps = {
filter: filterId,
- timeout: this.opts.pollTimeout,
+ timeout: pollTimeout,
};
if (syncToken) {
@@ -497,17 +519,6 @@ SyncApi.prototype._sync = function(syncOptions) {
qps._cacheBuster = Date.now();
}
- if (this.getSyncState() == 'ERROR' || this.getSyncState() == 'RECONNECTING') {
- // we think the connection is dead. If it comes back up, we won't know
- // about it till /sync returns. If the timeout= is high, this could
- // be a long time. Set it to 0 when doing retries so we don't have to wait
- // for an event or a timeout before emiting the SYNCING event.
- qps.timeout = 0;
- }
-
- // normal timeout= plus buffer time
- const clientSideTimeoutMs = this.opts.pollTimeout + BUFFER_PERIOD_MS;
-
debuglog('Starting sync since=' + syncToken);
this._currentSyncRequest = client._http.authedRequest(
undefined, "GET", "/sync", qps, undefined, clientSideTimeoutMs,
@@ -533,6 +544,7 @@ SyncApi.prototype._sync = function(syncOptions) {
const syncEventData = {
oldSyncToken: syncToken,
nextSyncToken: data.next_batch,
+ catchingUp: self._catchingUp,
};
if (!syncOptions.hasSyncedBefore) {
@@ -656,7 +668,9 @@ SyncApi.prototype._processSyncResponse = function(syncToken, data) {
}
// handle to-device events
- if (data.to_device && utils.isArray(data.to_device.events)) {
+ if (data.to_device && utils.isArray(data.to_device.events) &&
+ data.to_device.events.length > 0
+ ) {
data.to_device.events
.map(client.getEventMapper())
.forEach(
@@ -677,6 +691,9 @@ SyncApi.prototype._processSyncResponse = function(syncToken, data) {
client.emit("toDeviceEvent", toDeviceEvent);
},
);
+ } else {
+ // no more to-device events: we can stop polling with a short timeout.
+ this._catchingUp = false;
}
// the returned json structure is a bit crap, so make it into a