diff --git a/lib/client.js b/lib/client.js index ad06e39ee..df0f88ec5 100644 --- a/lib/client.js +++ b/lib/client.js @@ -742,6 +742,43 @@ MatrixClient.prototype.deleteRoomTag = function(roomId, tagName, callback) { ); }; +/** + * @param {string} eventType event type to be set + * @param {object} content event content + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixClient.prototype.setAccountData = function(eventType, content, callback) { + var path = utils.encodeUri("/user/$userId/account_data/$type", { + $userId: this.credentials.userId, + $type: eventType, + }); + return this._http.authedRequestWithPrefix( + callback, "PUT", path, undefined, content, httpApi.PREFIX_V2_ALPHA + ); +}; + +/** + * @param {string} roomId + * @param {string} eventType event type to be set + * @param {object} content event content + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixClient.prototype.setRoomAccountData = function(roomId, eventType, + content, callback) { + var path = utils.encodeUri("/user/$userId/rooms/$roomId/account_data/$type", { + $userId: this.credentials.userId, + $roomId: roomId, + $type: eventType, + }); + return this._http.authedRequestWithPrefix( + callback, "PUT", path, undefined, content, httpApi.PREFIX_V2_ALPHA + ); +}; + /** * Set a user's power level. * @param {string} roomId @@ -1810,7 +1847,7 @@ MatrixClient.prototype.setPresence = function(presence, callback) { * @return {module:http-api.MatrixError} Rejects: with an error response. */ MatrixClient.prototype.publicRooms = function(callback) { - return this._http.request(callback, "GET", "/publicRooms"); + return this._http.authedRequest(callback, "GET", "/publicRooms"); }; /** diff --git a/lib/models/room.js b/lib/models/room.js index 03fd00722..52f4d2241 100644 --- a/lib/models/room.js +++ b/lib/models/room.js @@ -62,6 +62,8 @@ function synthesizeReceipt(userId, event, receiptType) { * this room, with the oldest event at index 0. * @prop {object} tags Dict of room tags; the keys are the tag name and the values * are any metadata associated with the tag - e.g. { "fav" : { order: 1 } } + * @prop {object} accountData Dict of per-room account_data events; the keys are the + * event type and the values are the events. * @prop {RoomState} oldState The state of the room at the time of the oldest * event in the timeline. * @prop {RoomState} currentState The state of the room at the time of the @@ -88,6 +90,9 @@ function Room(roomId, opts) { // $tagName: { $metadata: $value }, // $tagName: { $metadata: $value }, }; + this.accountData = { + // $eventType: $event + }; this.oldState = new RoomState(roomId); this.currentState = new RoomState(roomId); this.summary = null; @@ -335,9 +340,8 @@ Room.prototype.addEvents = function(events, duplicateStrategy) { else if (events[i].getType() === "m.receipt") { this.addReceipt(events[i]); } - else if (events[i].getType() === "m.tag") { - this.addTags(events[i]); - } + // N.B. account_data is added directly by /sync to avoid + // having to maintain an event.isAccountData() here else { if (duplicateStrategy) { // is there a duplicate? @@ -602,6 +606,30 @@ Room.prototype.addTags = function(event) { this.emit("Room.tags", event, this); }; +/** + * Update the account_data events for this room, overwriting events of the same type. + * @param {Array} events an array of account_data events to add + */ +Room.prototype.addAccountData = function(events) { + for (var i = 0; i < events.length; i++) { + var event = events[i]; + if (event.getType() === "m.tag") { + this.addTags(event); + } + this.accountData[event.getType()] = event; + this.emit("Room.accountData", event, this); + } +}; + +/** + * Access account_data event of given event type for this room + * @param {string} type the type of account_data event to be accessed + * @return {?MatrixEvent} the account_data event in question + */ +Room.prototype.getAccountData = function(type) { + return this.accountData[type]; +}; + function setEventMetadata(event, stateContext, toStartOfTimeline) { // set sender and target properties event.sender = stateContext.getSentinelMember( @@ -752,3 +780,16 @@ module.exports = Room; * if (newTags["favourite"]) showStar(room); * }); */ + +/** + * Fires whenever a room's account_data is updated. + * @event module:client~MatrixClient#"Room.accountData" + * @param {event} event The account_data event + * @param {Room} room The room whose account_data was updated. + * @example + * matrixClient.on("Room.accountData", function(event, room){ + * if (event.getType() === "m.room.colorscheme") { + * applyColorScheme(event.getContents()); + * } + * }); + */ diff --git a/lib/sync.js b/lib/sync.js index ec8a9089e..2e160756e 100644 --- a/lib/sync.js +++ b/lib/sync.js @@ -139,7 +139,8 @@ SyncApi.prototype.syncLeftRooms = function() { return; } leaveObj.timeline = leaveObj.timeline || {}; - var timelineEvents = self._mapSyncEventsFormat(leaveObj.timeline, room); + var timelineEvents = + self._mapSyncEventsFormat(leaveObj.timeline, room); var stateEvents = self._mapSyncEventsFormat(leaveObj.state, room); var paginationToken = ( leaveObj.timeline.limited ? leaveObj.timeline.prev_batch : null @@ -420,7 +421,8 @@ SyncApi.prototype._sync = function(syncOptions, attempt) { // Handle invites inviteRooms.forEach(function(inviteObj) { var room = inviteObj.room; - var stateEvents = self._mapSyncEventsFormat(inviteObj.invite_state, room); + var stateEvents = + self._mapSyncEventsFormat(inviteObj.invite_state, room); self._processRoomEvents(room, stateEvents); if (inviteObj.isBrandNewRoom) { room.recalculate(client.credentials.userId); @@ -458,8 +460,15 @@ SyncApi.prototype._sync = function(syncOptions, attempt) { self._processRoomEvents( room, stateEvents, timelineEvents, paginationToken ); + + // XXX: should we be adding ephemeralEvents to the timeline? + // It feels like that for symmetry with room.addAccountData() + // there should be a room.addEphemeralEvents() or similar. room.addEvents(ephemeralEvents); - room.addEvents(accountDataEvents); + + // we deliberately don't add accountData to the timeline + room.addAccountData(accountDataEvents); + room.recalculate(client.credentials.userId); if (joinObj.isBrandNewRoom) { client.store.storeRoom(room); @@ -475,7 +484,8 @@ SyncApi.prototype._sync = function(syncOptions, attempt) { leaveRooms.forEach(function(leaveObj) { // Do the bear minimum to register rejected invites / you leaving rooms var room = leaveObj.room; - var timelineEvents = self._mapSyncEventsFormat(leaveObj.timeline, room); + var timelineEvents = + self._mapSyncEventsFormat(leaveObj.timeline, room); room.addEvents(timelineEvents); timelineEvents.forEach(function(e) { client.emit("event", e); }); });