diff --git a/lib/client.js b/lib/client.js index e9c77469a..65665e308 100644 --- a/lib/client.js +++ b/lib/client.js @@ -621,9 +621,10 @@ MatrixClient.prototype.joinRoom = function(roomIdOrAlias, opts, callback) { this._http.authedRequest(undefined, "POST", path, undefined, {}).then( function(res) { var roomId = res.room_id; - var room = createNewRoom(self, roomId); // XXX FIXME TODO + var syncApi = new SyncApi(self); + var room = syncApi.createRoom(roomId); if (opts.syncRoom) { - return _syncRoom(self, room); // XXX FIXME TODO + return syncApi.syncRoom(room); } return q(room); }, function(err) { @@ -2424,12 +2425,6 @@ function checkTurnServers(client) { }); } -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); @@ -2456,6 +2451,10 @@ function _PojoToMatrixEventMapper(client) { return mapper; } +MatrixClient.prototype.getEventMapper = function() { + return _PojoToMatrixEventMapper(this); +}; + // Identity Server Operations // ========================== @@ -2502,8 +2501,6 @@ MatrixClient.prototype.generateClientSecret = function() { module.exports.MatrixClient = MatrixClient; /** */ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED; -/** */ -module.exports.EventMapper = _PojoToMatrixEventMapper; // MatrixClient Event JSDocs diff --git a/lib/pushprocessor.js b/lib/pushprocessor.js index 8ea138afa..e7c71029b 100644 --- a/lib/pushprocessor.js +++ b/lib/pushprocessor.js @@ -195,7 +195,7 @@ function PushProcessor(client) { }; var matchingRuleForEventWithRulesets = function(ev, rulesets) { - if (!rulesets) { return null; } + if (!rulesets || !rulesets.device) { return null; } if (ev.user_id == client.credentials.userId) { return null; } var allDevNames = Object.keys(rulesets.device); diff --git a/lib/sync.js b/lib/sync.js index c34e7a242..bd0c214d8 100644 --- a/lib/sync.js +++ b/lib/sync.js @@ -8,14 +8,19 @@ * an alternative syncing API, we may want to have a proper syncing interface * for HTTP and WS at some point. */ - +var q = require("q"); var StubStore = require("./store/stub"); -var EventMapper = require("./client").EventMapper; var User = require("./models/user"); var Room = require("./models/room"); var utils = require("./utils"); var MatrixEvent = require("./models/event").MatrixEvent; +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 startSyncingRetryTimer(client, attempt, fn) { client._syncingRetry = {}; client._syncingRetry.fn = fn; @@ -79,6 +84,99 @@ function reEmit(reEmitEntity, emittableEntity, eventNames) { }); } +function _syncRoom(client, room) { + if (client._syncingRooms[room.roomId]) { + return client._syncingRooms[room.roomId]; + } + var defer = q.defer(); + client._syncingRooms[room.roomId] = defer.promise; + client.roomInitialSync(room.roomId, client._config.initialSyncLimit).done( + function(res) { + room.timeline = []; // blow away any previous messages. + _processRoomEvents(client, room, res.state, res.messages); + room.recalculate(client.credentials.userId); + client.store.storeRoom(room); + client.emit("Room", room); + defer.resolve(room); + client._syncingRooms[room.roomId] = undefined; + }, function(err) { + defer.reject(err); + client._syncingRooms[room.roomId] = undefined; + }); + return defer.promise; +} + +function _processRoomEvents(client, room, stateEventList, messageChunk) { + // "old" and "current" state are the same initially; they + // start diverging if the user paginates. + // We must deep copy otherwise membership changes in old state + // will leak through to current state! + var oldStateEvents = utils.map( + utils.deepCopy(stateEventList), client.getEventMapper() + ); + var stateEvents = utils.map(stateEventList, client.getEventMapper()); + room.oldState.setStateEvents(oldStateEvents); + room.currentState.setStateEvents(stateEvents); + + _resolveInvites(client, room); + + // add events to the timeline *after* setting the state + // events so messages use the right display names. Initial sync + // returns messages in chronological order, so we need to reverse + // it to get most recent -> oldest. We need it in that order in + // order to diverge old/current state correctly. + room.addEventsToTimeline( + utils.map( + messageChunk ? messageChunk.chunk : [], + client.getEventMapper() + ).reverse(), true + ); + if (messageChunk) { + room.oldState.paginationToken = messageChunk.start; + } +} + +function _resolveInvites(client, room) { + if (!room || !client._config.resolveInvitesToProfiles) { + return; + } + // For each invited room member we want to give them a displayname/avatar url + // if they have one (the m.room.member invites don't contain this). + room.getMembersWithMembership("invite").forEach(function(member) { + if (member._requestedProfileInfo) { + return; + } + member._requestedProfileInfo = true; + // try to get a cached copy first. + var user = client.getUser(member.userId); + var promise; + if (user) { + promise = q({ + avatar_url: user.avatarUrl, + displayname: user.displayName + }); + } + else { + promise = client.getProfileInfo(member.userId); + } + promise.done(function(info) { + // slightly naughty by doctoring the invite event but this means all + // the code paths remain the same between invite/join display name stuff + // which is a worthy trade-off for some minor pollution. + var inviteEvent = member.events.member; + if (inviteEvent.getContent().membership !== "invite") { + // between resolving and now they have since joined, so don't clobber + return; + } + inviteEvent.getContent().avatar_url = info.avatar_url; + inviteEvent.getContent().displayname = info.displayname; + member.setMembershipEvent(inviteEvent, room.currentState); // fire listeners + }, function(err) { + // OH WELL. + }); + }); +} + /** * Internal class - unstable. * Construct an entity which is able to sync with a homeserver. @@ -89,15 +187,23 @@ function SyncApi(client) { this.opts = {}; } +SyncApi.prototype.createRoom = function(roomId) { + return createNewRoom(this.client, roomId); +}; + +SyncApi.prototype.syncRoom = function(room) { + return _syncRoom(this.client, room); +}; + /** * @param {Object} opts * @param {Number} opts.historyLen * @param {Boolean} opts.includeArchived */ SyncApi.prototype.sync = function(opts) { - console.log("SyncApi.sync -> %s", opts); + console.log("SyncApi.sync"); this.opts = opts || {}; - return this._sync(); + return this._prepareForSync(); } @@ -119,7 +225,7 @@ SyncApi.prototype._prepareForSync = function(attempt) { }, function(err) { attempt += 1; startSyncingRetryTimer(client, attempt, function() { - prepareForSync(client, attempt); + self._prepareForSync(attempt); }); updateSyncState(client, "ERROR", { error: err }); }); @@ -147,7 +253,7 @@ SyncApi.prototype._sync = function(attempt) { // intercept the results and put them into our store if (!(client.store instanceof StubStore)) { utils.forEach( - utils.map(data.presence, EventMapper(client)), + utils.map(data.presence, client.getEventMapper()), function(e) { var user = createNewUser(client, e.getContent().user_id); user.setPresenceEvent(e); @@ -157,7 +263,7 @@ SyncApi.prototype._sync = function(attempt) { // group receipts by room ID. var receiptsByRoom = {}; data.receipts = data.receipts || []; - utils.forEach(data.receipts.map(EventMapper(client)), + utils.forEach(data.receipts.map(client.getEventMapper()), function(receiptEvent) { if (!receiptsByRoom[receiptEvent.getRoomId()]) { receiptsByRoom[receiptEvent.getRoomId()] = []; @@ -189,7 +295,7 @@ SyncApi.prototype._sync = function(attempt) { data.rooms[i].state.push(inviteEvent); } - _processRoomEvents( // XXX + _processRoomEvents( client, room, data.rooms[i].state, data.rooms[i].messages ); @@ -200,7 +306,7 @@ SyncApi.prototype._sync = function(attempt) { var privateUserData = data.rooms[i].account_data || []; var privateUserDataEvents = - utils.map(privateUserData, EventMapper(client)); + utils.map(privateUserData, client.getEventMapper()); for (j = 0; j < privateUserDataEvents.length; j++) { var event = privateUserDataEvents[j]; if (event.getType() === "m.tag") { @@ -253,7 +359,7 @@ SyncApi.prototype._sync = function(attempt) { console.error("/initialSync error (%s attempts): %s", attempt, err); attempt += 1; startSyncingRetryTimer(client, attempt, function() { - self._sync(opts, attempt); + self._sync(attempt); }); updateSyncState(client, "ERROR", { error: err }); }); @@ -305,13 +411,13 @@ SyncApi.prototype._pollForEvents = function(attempt) { } if (client._syncState !== "SYNCING") { - updateSyncState(self, "SYNCING"); + updateSyncState(client, "SYNCING"); } try { var events = []; if (data) { - events = utils.map(data.chunk, EventMapper(self)); + events = utils.map(data.chunk, client.getEventMapper()); } if (!(client.store instanceof StubStore)) { var roomIdsWithNewInvites = {}; @@ -337,7 +443,7 @@ SyncApi.prototype._pollForEvents = function(attempt) { usr.setPresenceEvent(events[i]); } else { - usr = createNewUser(self, events[i].getContent().user_id); + usr = createNewUser(client, events[i].getContent().user_id); usr.setPresenceEvent(events[i]); client.store.storeUser(usr); } @@ -350,7 +456,7 @@ SyncApi.prototype._pollForEvents = function(attempt) { var room = client.store.getRoom(roomId); var isBrandNewRoom = false; if (!room) { - room = createNewRoom(self, roomId); + room = createNewRoom(client, roomId); isBrandNewRoom = true; } @@ -375,12 +481,12 @@ SyncApi.prototype._pollForEvents = function(attempt) { if (!wasJoined && justJoined) { // we've just transitioned into a join state for this room, // so sync state. - _syncRoom(self, room); // XXX + _syncRoom(client, room); } }); Object.keys(roomIdsWithNewInvites).forEach(function(inviteRoomId) { - _resolveInvites(self, client.store.getRoom(inviteRoomId)); // XXX + _resolveInvites(client, client.store.getRoom(inviteRoomId)); }); } if (data) { @@ -394,7 +500,7 @@ SyncApi.prototype._pollForEvents = function(attempt) { console.error("Event stream error:"); console.error(e); } - client._pollForEvents(); + self._pollForEvents(); }, function(err) { console.error("/events error: %s", JSON.stringify(err)); if (discardResult) { @@ -405,10 +511,10 @@ SyncApi.prototype._pollForEvents = function(attempt) { } attempt += 1; - startSyncingRetryTimer(self, attempt, function() { - client._pollForEvents(attempt); + startSyncingRetryTimer(client, attempt, function() { + self._pollForEvents(attempt); }); - updateSyncState(self, "ERROR", { error: err }); + updateSyncState(client, "ERROR", { error: err }); }); }