diff --git a/examples/node/app.js b/examples/node/app.js index a7533be6f..ed512b563 100644 --- a/examples/node/app.js +++ b/examples/node/app.js @@ -28,7 +28,9 @@ rl.on('line', function(line) { viewingRoom = roomList[roomIndex]; if (viewingRoom.getMember(myUserId).membership === "invite") { // join the room first - matrixClient.joinRoom(viewingRoom.roomId).done(function() { + matrixClient.joinRoom(viewingRoom.roomId).done(function(room) { + roomList = matrixClient.getRooms(); + viewingRoom = room; printMessages(); }, function(err) { console.log("Error: %s", err); @@ -69,6 +71,13 @@ matrixClient.on("syncComplete", function() { printHelp(); }); +matrixClient.on("Room", function() { + roomList = matrixClient.getRooms(); + if (!viewingRoom) { + printRoomList(); + } +}); + // print incoming messages. matrixClient.on("Room.timeline", function(event, room, toStartOfTimeline) { if (toStartOfTimeline) { diff --git a/lib/client.js b/lib/client.js index 956be29a3..08e53cc40 100644 --- a/lib/client.js +++ b/lib/client.js @@ -121,12 +121,26 @@ MatrixClient.prototype.createRoom = function(options, callback) { * Join a room. * @param {string} roomIdOrAlias The room ID or room alias to join. * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: {room_id: {string}} + * @return {module:client.Promise} Resolves: Room object. * @return {module:http-api.MatrixError} Rejects: with an error response. */ MatrixClient.prototype.joinRoom = function(roomIdOrAlias, callback) { var path = utils.encodeUri("/join/$roomid", { $roomid: roomIdOrAlias}); - return this._http.authedRequest(callback, "POST", path, undefined, {}); + var defer = q.defer(); + var self = this; + this._http.authedRequest(undefined, "POST", path, undefined, {}).then( + function(res) { + var roomId = res.room_id; + var room = createNewRoom(self, roomId); + return _syncRoom(self, room); + }, function(err) { + _reject(callback, defer, err); + }).done(function(room) { + _resolve(callback, defer, room); + }, function(err) { + _reject(callback, defer, err); + }); + return defer.promise; }; /** @@ -915,10 +929,6 @@ MatrixClient.prototype.isLoggedIn = function() { }; - - - - // Higher level APIs // ================= @@ -959,10 +969,8 @@ MatrixClient.prototype.startClient = function(historyLen) { var i, j; // intercept the results and put them into our store if (self.store) { - var eventMapper = function(event) { - return new MatrixEvent(event); - }; - utils.forEach(utils.map(data.presence, eventMapper), function(e) { + utils.forEach(utils.map(data.presence, _PojoToMatrixEventMapper), + function(e) { var user = createNewUser(self, e.getContent().user_id); user.setPresenceEvent(e); self.store.storeUser(user); @@ -986,27 +994,8 @@ MatrixClient.prototype.startClient = function(historyLen) { }); } - // "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(data.rooms[i].state), eventMapper - ); - var stateEvents = utils.map(data.rooms[i].state, eventMapper); - room.oldState.setStateEvents(oldStateEvents); - room.currentState.setStateEvents(stateEvents); - - // 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( - data.rooms[i].messages ? data.rooms[i].messages.chunk : [], - eventMapper - ).reverse(), true + _processRoomEvents( + room, data.rooms[i].state, data.rooms[i].messages ); // cache the name/summary/etc prior to storage since we don't @@ -1087,23 +1076,38 @@ function _pollForEvents(client) { } // add events to room var roomIds = utils.keys(roomIdToEvents); - for (i = 0; i < roomIds.length; i++) { - var room = self.store.getRoom(roomIds[i]); - var shouldStoreRoom = false; + utils.forEach(roomIds, function(roomId) { + var room = self.store.getRoom(roomId); + var isBrandNewRoom = false; if (!room) { - room = createNewRoom(self, roomIds[i]); - shouldStoreRoom = true; + room = createNewRoom(self, roomId); + isBrandNewRoom = true; } - room.addEvents(roomIdToEvents[roomIds[i]], "replace"); + + //var wasJoined = room.hasMembershipState( + // self.credentials.userId, "join" + //); + + room.addEvents(roomIdToEvents[roomId], "replace"); room.recalculate(self.credentials.userId); - if (shouldStoreRoom) { + // store the Room for things like invite events so developers + // can update the UI + if (isBrandNewRoom) { self.store.storeRoom(room); - // TODO: Should be doing roomInitialSync here to pull in - // all state before emitting this(!) self.emit("Room", room); } - } + /* TODO invite->join trigger roominitialsync + var justJoined = room.hasMembershipState( + self.credentials.userId, "join" + ); + + if (!wasJoined && justJoined) { + // we've just transitioned into a join state for this room, + // so sync state. + _syncRoom(self, room); + } */ + }); } if (data) { self.fromToken = data.end; @@ -1122,6 +1126,45 @@ function _pollForEvents(client) { }); } +function _syncRoom(client, room) { + var defer = q.defer(); + client.roomInitialSync(room.roomId, 8).done(function(res) { + _processRoomEvents(room, res.state, res.messages); + room.recalculate(client.credentials.userId); + client.store.storeRoom(room); + client.emit("Room", room); + defer.resolve(room); + }, function(err) { + defer.reject(err); + }); + return defer.promise; +} + +function _processRoomEvents(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), _PojoToMatrixEventMapper + ); + var stateEvents = utils.map(stateEventList, _PojoToMatrixEventMapper); + room.oldState.setStateEvents(oldStateEvents); + room.currentState.setStateEvents(stateEvents); + + // 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 : [], + _PojoToMatrixEventMapper + ).reverse(), true + ); +} + /** * High level helper method to stop the client from polling and allow a * clean shutdown. @@ -1178,6 +1221,24 @@ function createNewRoom(client, roomId) { return room; } +function _reject(callback, defer, err) { + if (callback) { + callback(err); + } + defer.reject(err); +} + +function _resolve(callback, defer, res) { + if (callback) { + callback(null, res); + } + defer.resolve(res); +} + +function _PojoToMatrixEventMapper(plainOldJsObject) { + return new MatrixEvent(plainOldJsObject); +} + /** */ module.exports.MatrixClient = MatrixClient; @@ -1215,7 +1276,8 @@ module.exports.MatrixClient = MatrixClient; */ /** - * Fires whenever a new Room is added. This event is experimental and + * Fires whenever a new Room is added. This will fire when you are invited to a + * room, as well as when you join a room. This event is experimental and * may change. * @event module:client~MatrixClient#"Room" * @param {Room} room The newly created, fully populated room. diff --git a/lib/models/room.js b/lib/models/room.js index 24550ad76..64afa001c 100644 --- a/lib/models/room.js +++ b/lib/models/room.js @@ -55,6 +55,18 @@ utils.inherits(Room, EventEmitter); }); }; + /** + * Check if the given user_id has the given membership state. + * @param {string} userId The user ID to check. + * @param {string} membership The membership e.g. 'join' + * @return {boolean} True if this user_id has the given membership state. + */ + Room.prototype.hasMembershipState = function(userId, membership) { + return utils.filter(this.currentState.getMembers(), function(m) { + return m.membership === membership && m.userId === userId; + }).length > 0; + }; + /** * Add some events to this room's timeline. Will fire "Room.timeline" for * each event added. diff --git a/lib/scheduler.js b/lib/scheduler.js index 899c556b9..8f7d4adcc 100644 --- a/lib/scheduler.js +++ b/lib/scheduler.js @@ -4,7 +4,6 @@ * of requests. * @module scheduler */ -var EventStatus = require("./models/event").EventStatus; var utils = require("./utils"); var q = require("q");