Implement the new sync state API
Also have retry schemes for the rest of the syncing ops (/events, /pushrules)
This commit is contained in:
+67
-22
@@ -151,7 +151,7 @@ function MatrixClient(opts) {
|
||||
setupCallEventHandler(this);
|
||||
this._supportsVoip = true;
|
||||
}
|
||||
|
||||
this._syncState = null;
|
||||
}
|
||||
utils.inherits(MatrixClient, EventEmitter);
|
||||
|
||||
@@ -179,6 +179,15 @@ MatrixClient.prototype.supportsVoip = function() {
|
||||
return this._supportsVoip;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current sync state.
|
||||
* @return {?string} the sync state, which may be null.
|
||||
* @see module:client~MatrixClient#event:"sync"
|
||||
*/
|
||||
MatrixClient.prototype.getSyncState = function() {
|
||||
return this._syncState;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is end-to-end crypto enabled for this client.
|
||||
* @return {boolean} True if end-to-end is enabled.
|
||||
@@ -1979,16 +1988,17 @@ function doInitialSync(client, historyLen, includeArchived, attempt) {
|
||||
}
|
||||
|
||||
client.clientRunning = true;
|
||||
client.emit("syncComplete");
|
||||
updateSyncState(client, "PREPARED");
|
||||
// assume success until we fail which may be 30+ secs
|
||||
updateSyncState(client, "SYNCING");
|
||||
_pollForEvents(client);
|
||||
}, function(err) {
|
||||
console.error("/initialSync error (%s attempts): %s", attempt, err);
|
||||
client.emit("syncError", err);
|
||||
updateSyncState(client, "ERROR", { error: err });
|
||||
attempt += 1;
|
||||
setTimeout(function() {
|
||||
doInitialSync(client, historyLen, includeArchived, attempt);
|
||||
}, Math.pow(2, Math.min(attempt, 7)) * 1000); // max 2^7 secs = 2.1 mins
|
||||
// TODO: Retries.
|
||||
}, retryTimeMsForAttempt(attempt));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2040,20 +2050,34 @@ MatrixClient.prototype.startClient = function(opts) {
|
||||
// periodically poll for turn servers if we support voip
|
||||
checkTurnServers(this);
|
||||
|
||||
var self = this;
|
||||
this.pushRules().done(function(result) {
|
||||
self.pushRules = result;
|
||||
doInitialSync(self, opts.initialSyncLimit, opts.includeArchivedRooms);
|
||||
}, function(err) {
|
||||
self.emit("syncError", err);
|
||||
});
|
||||
prepareForSync(this);
|
||||
};
|
||||
|
||||
function prepareForSync(client, attempt) {
|
||||
attempt = attempt || 1;
|
||||
client.pushRules().done(function(result) {
|
||||
client.pushRules = result;
|
||||
doInitialSync(
|
||||
client,
|
||||
client._config.initialSyncLimit,
|
||||
client._config.includeArchivedRooms
|
||||
);
|
||||
}, function(err) {
|
||||
updateSyncState(client, "ERROR", { error: err });
|
||||
attempt += 1;
|
||||
setTimeout(function() {
|
||||
prepareForSync(client, attempt);
|
||||
}, retryTimeMsForAttempt(attempt));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an internal method.
|
||||
* @param {MatrixClient} client
|
||||
* @param {Number} attempt The attempt number
|
||||
*/
|
||||
function _pollForEvents(client) {
|
||||
function _pollForEvents(client, attempt) {
|
||||
attempt = attempt || 1;
|
||||
var self = client;
|
||||
if (!client.clientRunning) {
|
||||
return;
|
||||
@@ -2075,6 +2099,11 @@ function _pollForEvents(client) {
|
||||
else {
|
||||
clearTimeout(timeoutObj);
|
||||
}
|
||||
|
||||
if (self._syncState !== "SYNCING") {
|
||||
updateSyncState(self, "SYNCING");
|
||||
}
|
||||
|
||||
try {
|
||||
var events = [];
|
||||
if (data) {
|
||||
@@ -2170,12 +2199,12 @@ function _pollForEvents(client) {
|
||||
else {
|
||||
clearTimeout(timeoutObj);
|
||||
}
|
||||
self.emit("syncError", err);
|
||||
// retry every few seconds
|
||||
// FIXME: this should be exponential backoff with an option to nudge
|
||||
|
||||
updateSyncState(self, "ERROR", { error: err });
|
||||
attempt += 1;
|
||||
setTimeout(function() {
|
||||
_pollForEvents(self);
|
||||
}, 2000);
|
||||
_pollForEvents(self, attempt);
|
||||
}, retryTimeMsForAttempt(attempt));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2455,6 +2484,12 @@ function setupCallEventHandler(client) {
|
||||
});
|
||||
}
|
||||
|
||||
function updateSyncState(client, newState, data) {
|
||||
var old = client._syncState;
|
||||
client._syncState = newState;
|
||||
client.emit("sync", client._syncState, old, data);
|
||||
}
|
||||
|
||||
function checkTurnServers(client) {
|
||||
if (!client._supportsVoip) {
|
||||
return;
|
||||
@@ -2511,6 +2546,12 @@ function createNewRoom(client, roomId) {
|
||||
return room;
|
||||
}
|
||||
|
||||
function retryTimeMsForAttempt(attempt) {
|
||||
// 2,4,8,16,32,64,128,128,128,... seconds
|
||||
// max 2^7 secs = 2.1 mins
|
||||
return Math.pow(2, Math.min(attempt, 7)) * 1000;
|
||||
}
|
||||
|
||||
function _reject(callback, defer, err) {
|
||||
if (callback) {
|
||||
callback(err);
|
||||
@@ -2604,18 +2645,20 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
||||
* ready for methods to be called on it. This will be immediately followed by
|
||||
* a state of SYNCING. <i>This is the equivalent of "syncComplete" in the
|
||||
* previous API.</i></li>
|
||||
* <li>SYNCING : The client is currently polling for new events from the server.</li>
|
||||
* <li>SYNCING : The client is currently polling for new events from the server.
|
||||
* The client may fire this before or after processing latest events from a sync.</li>
|
||||
* <li>ERROR : The client has had a problem syncing with the server. If this is
|
||||
* called <i>before</i> PREPARED then there was a problem performing the initial
|
||||
* sync. If this is called <i>after</i> PREPARED then there was a problem polling
|
||||
* the server for updates. <i>This is the equivalent of "syncError" in the previous
|
||||
* the server for updates. This may be called multiple times even if the state is
|
||||
* already ERROR. <i>This is the equivalent of "syncError" in the previous
|
||||
* API.</i></li>
|
||||
* </ul>
|
||||
* State transition diagram:
|
||||
* <pre>
|
||||
* +----->PREPARED -------> SYNCING <--+
|
||||
* | ^ | |
|
||||
* null ------+ | +----------------+ |
|
||||
* null ------+ | +---------------+ |
|
||||
* | | V |
|
||||
* +------->ERROR ---------------------+
|
||||
*
|
||||
@@ -2634,8 +2677,10 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
||||
* live update.
|
||||
* <li><code>ERROR -> SYNCING</code> : Occurs when the client has performed a
|
||||
* live update after having previously failed.
|
||||
* <li><code>ERROR -> ERROR</code> : Occurs when the client has failed to sync
|
||||
* for a second time or more.</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @event module:client~MatrixClient#"sync"
|
||||
* @param {string} state An enum representing the syncing state. One of "PREPARED",
|
||||
* "SYNCING", "ERROR".
|
||||
|
||||
@@ -82,7 +82,8 @@ describe("MatrixClient room timelines", function() {
|
||||
|
||||
it("should be added immediately after calling MatrixClient.sendEvent " +
|
||||
"with EventStatus.SENDING and the right event.sender", function(done) {
|
||||
client.on("syncComplete", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
|
||||
@@ -116,7 +117,8 @@ describe("MatrixClient room timelines", function() {
|
||||
];
|
||||
eventData.chunk[0].event_id = eventId;
|
||||
|
||||
client.on("syncComplete", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
client.sendTextMessage(roomId, "I am a fish", "txn1").done(
|
||||
function() {
|
||||
@@ -144,7 +146,8 @@ describe("MatrixClient room timelines", function() {
|
||||
];
|
||||
eventData.chunk[0].event_id = eventId;
|
||||
|
||||
client.on("syncComplete", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
var promise = client.sendTextMessage(roomId, "I am a fish", "txn1");
|
||||
httpBackend.flush("/events", 1).done(function() {
|
||||
@@ -180,7 +183,8 @@ describe("MatrixClient room timelines", function() {
|
||||
|
||||
it("should set Room.oldState.paginationToken to null at the start" +
|
||||
" of the timeline.", function(done) {
|
||||
client.on("syncComplete", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
|
||||
@@ -219,7 +223,8 @@ describe("MatrixClient room timelines", function() {
|
||||
})
|
||||
];
|
||||
|
||||
client.on("syncComplete", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
|
||||
@@ -249,7 +254,8 @@ describe("MatrixClient room timelines", function() {
|
||||
})
|
||||
];
|
||||
|
||||
client.on("syncComplete", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
|
||||
@@ -274,7 +280,8 @@ describe("MatrixClient room timelines", function() {
|
||||
})
|
||||
];
|
||||
|
||||
client.on("syncComplete", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
expect(room.oldState.paginationToken).toBeDefined();
|
||||
|
||||
@@ -297,7 +304,8 @@ describe("MatrixClient room timelines", function() {
|
||||
utils.mkMessage({user: userId, room: roomId}),
|
||||
utils.mkMessage({user: userId, room: roomId})
|
||||
];
|
||||
client.on("syncComplete", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
|
||||
var index = 0;
|
||||
@@ -331,7 +339,8 @@ describe("MatrixClient room timelines", function() {
|
||||
}),
|
||||
utils.mkMessage({user: userId, room: roomId})
|
||||
];
|
||||
client.on("syncComplete", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
httpBackend.flush("/events", 1).done(function() {
|
||||
var preNameEvent = room.timeline[room.timeline.length - 3];
|
||||
@@ -352,7 +361,8 @@ describe("MatrixClient room timelines", function() {
|
||||
}
|
||||
})
|
||||
];
|
||||
client.on("syncComplete", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
var nameEmitCount = 0;
|
||||
client.on("Room.name", function(rm) {
|
||||
@@ -392,7 +402,8 @@ describe("MatrixClient room timelines", function() {
|
||||
user: userC, room: roomId, mship: "invite", skey: userD
|
||||
})
|
||||
];
|
||||
client.on("syncComplete", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
httpBackend.flush("/events", 1).done(function() {
|
||||
expect(room.currentState.getMembers().length).toEqual(4);
|
||||
|
||||
Reference in New Issue
Block a user