You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-18 05:42:00 +03:00
269 lines
9.7 KiB
JavaScript
269 lines
9.7 KiB
JavaScript
"use strict";
|
|
/**
|
|
* @module models/room-state
|
|
*/
|
|
var EventEmitter = require("events").EventEmitter;
|
|
|
|
var utils = require("../utils");
|
|
var RoomMember = require("./room-member");
|
|
|
|
/**
|
|
* Construct room state.
|
|
* @constructor
|
|
* @param {string} roomId Required. The ID of the room which has this state.
|
|
* @prop {Object.<string, RoomMember>} members The room member dictionary, keyed
|
|
* on the user's ID.
|
|
* @prop {Object.<string, Object.<string, MatrixEvent>>} events The state
|
|
* events dictionary, keyed on the event type and then the state_key value.
|
|
* @prop {string} paginationToken The pagination token for this state.
|
|
*/
|
|
function RoomState(roomId) {
|
|
this.roomId = roomId;
|
|
this.members = {
|
|
// userId: RoomMember
|
|
};
|
|
this.events = {
|
|
// eventType: { stateKey: MatrixEvent }
|
|
};
|
|
this.paginationToken = null;
|
|
|
|
this._sentinels = {
|
|
// userId: RoomMember
|
|
};
|
|
this._updateModifiedTime();
|
|
this._displayNameToUserIds = {};
|
|
this._userIdsToDisplayNames = {};
|
|
}
|
|
utils.inherits(RoomState, EventEmitter);
|
|
|
|
/**
|
|
* Get all RoomMembers in this room.
|
|
* @return {Array<RoomMember>} A list of RoomMembers.
|
|
*/
|
|
RoomState.prototype.getMembers = function() {
|
|
return utils.values(this.members);
|
|
};
|
|
|
|
/**
|
|
* Get a room member by their user ID.
|
|
* @param {string} userId The room member's user ID.
|
|
* @return {RoomMember} The member or null if they do not exist.
|
|
*/
|
|
RoomState.prototype.getMember = function(userId) {
|
|
return this.members[userId] || null;
|
|
};
|
|
|
|
/**
|
|
* Get a room member whose properties will not change with this room state. You
|
|
* typically want this if you want to attach a RoomMember to a MatrixEvent which
|
|
* may no longer be represented correctly by Room.currentState or Room.oldState.
|
|
* The term 'sentinel' refers to the fact that this RoomMember is an unchanging
|
|
* guardian for state at this particular point in time.
|
|
* @param {string} userId The room member's user ID.
|
|
* @return {RoomMember} The member or null if they do not exist.
|
|
*/
|
|
RoomState.prototype.getSentinelMember = function(userId) {
|
|
return this._sentinels[userId] || null;
|
|
};
|
|
|
|
/**
|
|
* Get state events from the state of the room.
|
|
* @param {string} eventType The event type of the state event.
|
|
* @param {string} stateKey Optional. The state_key of the state event. If
|
|
* this is <code>undefined</code> then all matching state events will be
|
|
* returned.
|
|
* @return {MatrixEvent[]|MatrixEvent} A list of events if state_key was
|
|
* <code>undefined</code>, else a single event (or null if no match found).
|
|
*/
|
|
RoomState.prototype.getStateEvents = function(eventType, stateKey) {
|
|
if (!this.events[eventType]) {
|
|
// no match
|
|
return stateKey === undefined ? [] : null;
|
|
}
|
|
if (stateKey === undefined) { // return all values
|
|
return utils.values(this.events[eventType]);
|
|
}
|
|
var event = this.events[eventType][stateKey];
|
|
return event ? event : null;
|
|
};
|
|
|
|
/**
|
|
* Add an array of one or more state MatrixEvents, overwriting
|
|
* any existing state with the same {type, stateKey} tuple. Will fire
|
|
* "RoomState.events" for every event added. May fire "RoomState.members"
|
|
* if there are <code>m.room.member</code> events.
|
|
* @param {MatrixEvent[]} stateEvents a list of state events for this room.
|
|
* @fires module:client~MatrixClient#event:"RoomState.members"
|
|
* @fires module:client~MatrixClient#event:"RoomState.newMember"
|
|
* @fires module:client~MatrixClient#event:"RoomState.events"
|
|
*/
|
|
RoomState.prototype.setStateEvents = function(stateEvents) {
|
|
var self = this;
|
|
this._updateModifiedTime();
|
|
|
|
// update the core event dict
|
|
utils.forEach(stateEvents, function(event) {
|
|
if (event.getRoomId() !== self.roomId) { return; }
|
|
if (!event.isState()) { return; }
|
|
|
|
if (self.events[event.getType()] === undefined) {
|
|
self.events[event.getType()] = {};
|
|
}
|
|
self.events[event.getType()][event.getStateKey()] = event;
|
|
if (event.getType() === "m.room.member") {
|
|
_updateDisplayNameCache(
|
|
self, event.getStateKey(), event.getContent().displayname
|
|
);
|
|
}
|
|
self.emit("RoomState.events", event, self);
|
|
});
|
|
|
|
// update higher level data structures. This needs to be done AFTER the
|
|
// core event dict as these structures may depend on other state events in
|
|
// the given array (e.g. disambiguating display names in one go to do both
|
|
// clashing names rather than progressively which only catches 1 of them).
|
|
utils.forEach(stateEvents, function(event) {
|
|
if (event.getRoomId() !== self.roomId) { return; }
|
|
if (!event.isState()) { return; }
|
|
|
|
if (event.getType() === "m.room.member") {
|
|
var userId = event.getStateKey();
|
|
var member = self.members[userId];
|
|
if (!member) {
|
|
member = new RoomMember(event.getRoomId(), userId);
|
|
self.emit("RoomState.newMember", event, self, member);
|
|
}
|
|
// Add a new sentinel for this change. We apply the same
|
|
// operations to both sentinel and member rather than deep copying
|
|
// so we don't make assumptions about the properties of RoomMember
|
|
// (e.g. and manage to break it because deep copying doesn't do
|
|
// everything).
|
|
var sentinel = new RoomMember(event.getRoomId(), userId);
|
|
utils.forEach([member, sentinel], function(roomMember) {
|
|
roomMember.setMembershipEvent(event, self);
|
|
// this member may have a power level already, so set it.
|
|
var pwrLvlEvent = self.getStateEvents("m.room.power_levels", "");
|
|
if (pwrLvlEvent) {
|
|
roomMember.setPowerLevelEvent(pwrLvlEvent);
|
|
}
|
|
});
|
|
|
|
self._sentinels[userId] = sentinel;
|
|
self.members[userId] = member;
|
|
self.emit("RoomState.members", event, self, member);
|
|
}
|
|
else if (event.getType() === "m.room.power_levels") {
|
|
var members = utils.values(self.members);
|
|
utils.forEach(members, function(member) {
|
|
member.setPowerLevelEvent(event);
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Set the current typing event for this room.
|
|
* @param {MatrixEvent} event The typing event
|
|
*/
|
|
RoomState.prototype.setTypingEvent = function(event) {
|
|
utils.forEach(utils.values(this.members), function(member) {
|
|
member.setTypingEvent(event);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Update the last modified time to the current time.
|
|
*/
|
|
RoomState.prototype._updateModifiedTime = function() {
|
|
this._modified = Date.now();
|
|
};
|
|
|
|
/**
|
|
* Get the timestamp when this room state was last updated. This timestamp is
|
|
* updated when this object has received new state events.
|
|
* @return {number} The timestamp
|
|
*/
|
|
RoomState.prototype.getLastModifiedTime = function() {
|
|
return this._modified;
|
|
};
|
|
|
|
/**
|
|
* Get user IDs with the specified display name.
|
|
* @param {string} displayName The display name to get user IDs from.
|
|
* @return {string[]} An array of user IDs or an empty array.
|
|
*/
|
|
RoomState.prototype.getUserIdsWithDisplayName = function(displayName) {
|
|
return this._displayNameToUserIds[displayName] || [];
|
|
};
|
|
|
|
/**
|
|
* The RoomState class.
|
|
*/
|
|
module.exports = RoomState;
|
|
|
|
|
|
function _updateDisplayNameCache(roomState, userId, displayName) {
|
|
var oldName = roomState._userIdsToDisplayNames[userId];
|
|
delete roomState._userIdsToDisplayNames[userId];
|
|
if (oldName) {
|
|
// Remove the old name from the cache.
|
|
// We clobber the user_id > name lookup but the name -> [user_id] lookup
|
|
// means we need to remove that user ID from that array rather than nuking
|
|
// the lot.
|
|
var existingUserIds = roomState._displayNameToUserIds[oldName] || [];
|
|
for (var i = 0; i < existingUserIds.length; i++) {
|
|
if (existingUserIds[i] === userId) {
|
|
// remove this user ID from this array
|
|
existingUserIds.splice(i, 1);
|
|
i--;
|
|
}
|
|
}
|
|
roomState._displayNameToUserIds[oldName] = existingUserIds;
|
|
}
|
|
|
|
roomState._userIdsToDisplayNames[userId] = displayName;
|
|
if (!roomState._displayNameToUserIds[displayName]) {
|
|
roomState._displayNameToUserIds[displayName] = [];
|
|
}
|
|
roomState._displayNameToUserIds[displayName].push(userId);
|
|
}
|
|
|
|
/**
|
|
* Fires whenever the event dictionary in room state is updated.
|
|
* @event module:client~MatrixClient#"RoomState.events"
|
|
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
|
* @param {RoomState} state The room state whose RoomState.events dictionary
|
|
* was updated.
|
|
* @example
|
|
* matrixClient.on("RoomState.events", function(event, state){
|
|
* var newStateEvent = event;
|
|
* });
|
|
*/
|
|
|
|
/**
|
|
* Fires whenever a member in the members dictionary is updated in any way.
|
|
* @event module:client~MatrixClient#"RoomState.members"
|
|
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
|
* @param {RoomState} state The room state whose RoomState.members dictionary
|
|
* was updated.
|
|
* @param {RoomMember} member The room member that was updated.
|
|
* @example
|
|
* matrixClient.on("RoomState.members", function(event, state, member){
|
|
* var newMembershipState = member.membership;
|
|
* });
|
|
*/
|
|
|
|
/**
|
|
* Fires whenever a member is added to the members dictionary. The RoomMember
|
|
* will not be fully populated yet (e.g. no membership state).
|
|
* @event module:client~MatrixClient#"RoomState.newMember"
|
|
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
|
* @param {RoomState} state The room state whose RoomState.members dictionary
|
|
* was updated with a new entry.
|
|
* @param {RoomMember} member The room member that was added.
|
|
* @example
|
|
* matrixClient.on("RoomState.newMember", function(event, state, member){
|
|
* // add event listeners on 'member'
|
|
* });
|
|
*/
|