diff --git a/lib/client.js b/lib/client.js
index 50bec8400..c847f0763 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -2110,6 +2110,36 @@ MatrixClient.prototype.search = function(opts, callback) {
};
+/**
+ * Populate the store with rooms the user has left.
+ * @return {module:client.Promise} Resolves: TODO - Resolved when the rooms have
+ * been added to the data store.
+ * @return {module:http-api.MatrixError} Rejects: with an error response.
+ */
+MatrixClient.prototype.syncLeftRooms = function() {
+ // Guard against multiple calls whilst ongoing and multiple calls post success
+ if (this._syncedLeftRooms) {
+ return q([]); // don't call syncRooms again if it succeeded.
+ }
+ if (this._syncLeftRoomsPromise) {
+ return this._syncLeftRoomsPromise; // return the ongoing request
+ }
+ var self = this;
+ var syncApi = new SyncApi(this);
+ this._syncLeftRoomsPromise = syncApi.syncLeftRooms();
+
+ // cleanup locks
+ this._syncLeftRoomsPromise.then(function(res) {
+ console.log("Marking success of sync left room request");
+ self._syncedLeftRooms = true; // flip the bit on success
+ }).finally(function() {
+ self._syncLeftRoomsPromise = null; // cleanup ongoing request state
+ });
+
+ return this._syncLeftRoomsPromise;
+};
+
+
/**
* Create a new filter.
* @param {Object} content The HTTP body for the request
diff --git a/lib/sync.js b/lib/sync.js
index d76c0f248..4d1cd71e9 100644
--- a/lib/sync.js
+++ b/lib/sync.js
@@ -21,12 +21,14 @@ var Filter = require("./filter");
// to determine the max time we're willing to wait.
var BUFFER_PERIOD_MS = 20 * 1000;
-function getFilterName(userId) {
+function getFilterName(userId, suffix) {
// scope this on the user ID because people may login on many accounts
// and they all need to be stored!
- return "FILTER_SYNC_" + userId;
+ return "FILTER_SYNC_" + userId + (suffix ? "_" + suffix : "");
}
+
+
/**
* Internal class - unstable.
* Construct an entity which is able to sync with a homeserver.
@@ -38,7 +40,6 @@ function SyncApi(client, opts) {
this.client = client;
opts = opts || {};
opts.initialSyncLimit = opts.initialSyncLimit || 8;
- opts.includeArchivedRooms = opts.includeArchivedRooms || false;
opts.resolveInvitesToProfiles = opts.resolveInvitesToProfiles || false;
opts.pollTimeout = opts.pollTimeout || (30 * 1000);
opts.pendingEventOrdering = opts.pendingEventOrdering || "chronological";
@@ -75,6 +76,69 @@ SyncApi.prototype.createRoom = function(roomId) {
return room;
};
+/**
+ * Sync rooms the user has left.
+ * @return {Promise} Resolved when they've been added to the store.
+ */
+SyncApi.prototype.syncLeftRooms = function() {
+ var client = this.client;
+ var self = this;
+
+ // grab a filter with limit=1 and include_leave=true
+ var filter = new Filter(this.client.credentials.userId);
+ filter.setTimelineLimit(1);
+ filter.setIncludeLeaveRooms(true);
+
+ var localTimeoutMs = this.opts.pollTimeout + BUFFER_PERIOD_MS;
+ var qps = {
+ timeout: 1 // don't want to block since this is a single isolated req
+ };
+
+ return this._getOrCreateFilter(
+ getFilterName(client.credentials.userId, "LEFT_ROOMS"), filter
+ ).then(function(filterId) {
+ qps.filter = filterId;
+ return client._http.authedRequestWithPrefix(
+ undefined, "GET", "/sync", qps, undefined, httpApi.PREFIX_V2_ALPHA,
+ localTimeoutMs
+ );
+ }).then(function(data) {
+ var leaveRooms = [];
+ if (data.rooms && data.rooms.leave) {
+ leaveRooms = self._mapSyncResponseToRoomArray(data.rooms.leave);
+ }
+ var rooms = [];
+ leaveRooms.forEach(function(leaveObj) {
+ var room = leaveObj.room;
+ rooms.push(room);
+ if (!leaveObj.isBrandNewRoom) {
+ // the intention behind syncLeftRooms is to add in rooms which were
+ // *omitted* from the initial /sync. Rooms the user were joined to
+ // but then left whilst the app is running will appear in this list
+ // and we do not want to bother with them since they will have the
+ // current state already (and may get dupe messages if we add
+ // yet more timeline events!), so skip them.
+ // NB: When we persist rooms to localStorage this will be more
+ // complicated...
+ return;
+ }
+ leaveObj.timeline = leaveObj.timeline || {};
+ var timelineEvents = self._mapSyncEventsFormat(leaveObj.timeline, room);
+ var stateEvents = self._mapSyncEventsFormat(leaveObj.state, room);
+ var paginationToken = (
+ leaveObj.timeline.limited ? leaveObj.timeline.prev_batch : null
+ );
+ self._processRoomEvents(
+ room, stateEvents, timelineEvents, paginationToken
+ );
+ room.recalculate(client.credentials.userId);
+ client.store.storeRoom(room);
+ client.emit("Room", room);
+ });
+ return rooms;
+ });
+};
+
/**
* Main entry point
*/
@@ -104,27 +168,14 @@ SyncApi.prototype.sync = function() {
attempt = attempt || 0;
attempt += 1;
-
- // Get or create filter
- var filterId = client.store.getFilterIdByName(
- getFilterName(client.credentials.userId)
- );
- if (filterId) {
- // super, just use that.
- console.log("Using existing filter ID %s", filterId);
- self._sync({ filterId: filterId });
- return;
- }
-
- // create a filter
var filter = new Filter(client.credentials.userId);
filter.setTimelineLimit(self.opts.initialSyncLimit);
- client.createFilter(filter.getDefinition()).done(function(filter) {
- client.store.setFilterIdByName(
- getFilterName(client.credentials.userId), filter.filterId
- );
- console.log("Created filter ", filter.filterId);
- self._sync({ filterId: filter.filterId }); // Now start the /sync loop
+
+ self._getOrCreateFilter(
+ getFilterName(client.credentials.userId), filter
+ ).done(function(filterId) {
+ console.log("Using existing filter ID %s", filterId);
+ self._sync({ filterId: filterId });
}, retryHandler(attempt, getFilter));
}
@@ -159,8 +210,6 @@ SyncApi.prototype._sync = function(syncOptions, attempt) {
var self = this;
attempt = attempt || 1;
- // TODO include archived rooms flag.
-
var qps = {
filter: syncOptions.filterId,
timeout: this.opts.pollTimeout,
@@ -313,9 +362,6 @@ SyncApi.prototype._sync = function(syncOptions, attempt) {
var room = leaveObj.room;
var timelineEvents = self._mapSyncEventsFormat(leaveObj.timeline, room);
room.addEvents(timelineEvents);
-
- // TODO: honour includeArchived opt
-
timelineEvents.forEach(function(e) { client.emit("event", e); });
});
}
@@ -345,6 +391,26 @@ SyncApi.prototype._sync = function(syncOptions, attempt) {
});
};
+/**
+ * @param {string} filterName
+ * @param {Filter} filter
+ * @return {Promise} Filter ID
+ */
+SyncApi.prototype._getOrCreateFilter = function(filterName, filter) {
+ var client = this.client;
+ var filterId = client.store.getFilterIdByName(filterName);
+ if (filterId) {
+ // super, just use that.
+ return q(filterId);
+ }
+
+ // create a filter
+ return client.createFilter(filter.getDefinition()).then(function(createdFilter) {
+ client.store.setFilterIdByName(filterName, createdFilter.filterId);
+ return createdFilter.filterId;
+ });
+};
+
/**
* @param {Object} obj
* @return {Object[]}