UNPKG

29.1 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports.RoomState = RoomState;
9
10var _events = require("events");
11
12var _roomMember = require("./room-member");
13
14var _logger = require("../logger");
15
16var utils = _interopRequireWildcard(require("../utils"));
17
18/*
19Copyright 2015, 2016 OpenMarket Ltd
20Copyright 2019 The Matrix.org Foundation C.I.C.
21
22Licensed under the Apache License, Version 2.0 (the "License");
23you may not use this file except in compliance with the License.
24You may obtain a copy of the License at
25
26 http://www.apache.org/licenses/LICENSE-2.0
27
28Unless required by applicable law or agreed to in writing, software
29distributed under the License is distributed on an "AS IS" BASIS,
30WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31See the License for the specific language governing permissions and
32limitations under the License.
33*/
34
35/**
36 * @module models/room-state
37 */
38// possible statuses for out-of-band member loading
39const OOB_STATUS_NOTSTARTED = 1;
40const OOB_STATUS_INPROGRESS = 2;
41const OOB_STATUS_FINISHED = 3;
42/**
43 * Construct room state.
44 *
45 * Room State represents the state of the room at a given point.
46 * It can be mutated by adding state events to it.
47 * There are two types of room member associated with a state event:
48 * normal member objects (accessed via getMember/getMembers) which mutate
49 * with the state to represent the current state of that room/user, eg.
50 * the object returned by getMember('@bob:example.com') will mutate to
51 * get a different display name if Bob later changes his display name
52 * in the room.
53 * There are also 'sentinel' members (accessed via getSentinelMember).
54 * These also represent the state of room members at the point in time
55 * represented by the RoomState object, but unlike objects from getMember,
56 * sentinel objects will always represent the room state as at the time
57 * getSentinelMember was called, so if Bob subsequently changes his display
58 * name, a room member object previously acquired with getSentinelMember
59 * will still have his old display name. Calling getSentinelMember again
60 * after the display name change will return a new RoomMember object
61 * with Bob's new display name.
62 *
63 * @constructor
64 * @param {?string} roomId Optional. The ID of the room which has this state.
65 * If none is specified it just tracks paginationTokens, useful for notifTimelineSet
66 * @param {?object} oobMemberFlags Optional. The state of loading out of bound members.
67 * As the timeline might get reset while they are loading, this state needs to be inherited
68 * and shared when the room state is cloned for the new timeline.
69 * This should only be passed from clone.
70 * @prop {Object.<string, RoomMember>} members The room member dictionary, keyed
71 * on the user's ID.
72 * @prop {Object.<string, Object.<string, MatrixEvent>>} events The state
73 * events dictionary, keyed on the event type and then the state_key value.
74 * @prop {string} paginationToken The pagination token for this state.
75 */
76
77function RoomState(roomId, oobMemberFlags = undefined) {
78 this.roomId = roomId;
79 this.members = {// userId: RoomMember
80 };
81 this.events = {// eventType: { stateKey: MatrixEvent }
82 };
83 this.paginationToken = null;
84 this._sentinels = {// userId: RoomMember
85 };
86
87 this._updateModifiedTime(); // stores fuzzy matches to a list of userIDs (applies utils.removeHiddenChars to keys)
88
89
90 this._displayNameToUserIds = {};
91 this._userIdsToDisplayNames = {};
92 this._tokenToInvite = {}; // 3pid invite state_key to m.room.member invite
93
94 this._joinedMemberCount = null; // cache of the number of joined members
95 // joined members count from summary api
96 // once set, we know the server supports the summary api
97 // and we should only trust that
98 // we could also only trust that before OOB members
99 // are loaded but doesn't seem worth the hassle atm
100
101 this._summaryJoinedMemberCount = null; // same for invited member count
102
103 this._invitedMemberCount = null;
104 this._summaryInvitedMemberCount = null;
105
106 if (!oobMemberFlags) {
107 oobMemberFlags = {
108 status: OOB_STATUS_NOTSTARTED
109 };
110 }
111
112 this._oobMemberFlags = oobMemberFlags;
113}
114
115utils.inherits(RoomState, _events.EventEmitter);
116/**
117 * Returns the number of joined members in this room
118 * This method caches the result.
119 * @return {integer} The number of members in this room whose membership is 'join'
120 */
121
122RoomState.prototype.getJoinedMemberCount = function () {
123 if (this._summaryJoinedMemberCount !== null) {
124 return this._summaryJoinedMemberCount;
125 }
126
127 if (this._joinedMemberCount === null) {
128 this._joinedMemberCount = this.getMembers().reduce((count, m) => {
129 return m.membership === 'join' ? count + 1 : count;
130 }, 0);
131 }
132
133 return this._joinedMemberCount;
134};
135/**
136 * Set the joined member count explicitly (like from summary part of the sync response)
137 * @param {number} count the amount of joined members
138 */
139
140
141RoomState.prototype.setJoinedMemberCount = function (count) {
142 this._summaryJoinedMemberCount = count;
143};
144/**
145 * Returns the number of invited members in this room
146 * @return {integer} The number of members in this room whose membership is 'invite'
147 */
148
149
150RoomState.prototype.getInvitedMemberCount = function () {
151 if (this._summaryInvitedMemberCount !== null) {
152 return this._summaryInvitedMemberCount;
153 }
154
155 if (this._invitedMemberCount === null) {
156 this._invitedMemberCount = this.getMembers().reduce((count, m) => {
157 return m.membership === 'invite' ? count + 1 : count;
158 }, 0);
159 }
160
161 return this._invitedMemberCount;
162};
163/**
164 * Set the amount of invited members in this room
165 * @param {number} count the amount of invited members
166 */
167
168
169RoomState.prototype.setInvitedMemberCount = function (count) {
170 this._summaryInvitedMemberCount = count;
171};
172/**
173 * Get all RoomMembers in this room.
174 * @return {Array<RoomMember>} A list of RoomMembers.
175 */
176
177
178RoomState.prototype.getMembers = function () {
179 return utils.values(this.members);
180};
181/**
182 * Get all RoomMembers in this room, excluding the user IDs provided.
183 * @param {Array<string>} excludedIds The user IDs to exclude.
184 * @return {Array<RoomMember>} A list of RoomMembers.
185 */
186
187
188RoomState.prototype.getMembersExcept = function (excludedIds) {
189 return utils.values(this.members).filter(m => !excludedIds.includes(m.userId));
190};
191/**
192 * Get a room member by their user ID.
193 * @param {string} userId The room member's user ID.
194 * @return {RoomMember} The member or null if they do not exist.
195 */
196
197
198RoomState.prototype.getMember = function (userId) {
199 return this.members[userId] || null;
200};
201/**
202 * Get a room member whose properties will not change with this room state. You
203 * typically want this if you want to attach a RoomMember to a MatrixEvent which
204 * may no longer be represented correctly by Room.currentState or Room.oldState.
205 * The term 'sentinel' refers to the fact that this RoomMember is an unchanging
206 * guardian for state at this particular point in time.
207 * @param {string} userId The room member's user ID.
208 * @return {RoomMember} The member or null if they do not exist.
209 */
210
211
212RoomState.prototype.getSentinelMember = function (userId) {
213 if (!userId) return null;
214 let sentinel = this._sentinels[userId];
215
216 if (sentinel === undefined) {
217 sentinel = new _roomMember.RoomMember(this.roomId, userId);
218 const member = this.members[userId];
219
220 if (member) {
221 sentinel.setMembershipEvent(member.events.member, this);
222 }
223
224 this._sentinels[userId] = sentinel;
225 }
226
227 return sentinel;
228};
229/**
230 * Get state events from the state of the room.
231 * @param {string} eventType The event type of the state event.
232 * @param {string} stateKey Optional. The state_key of the state event. If
233 * this is <code>undefined</code> then all matching state events will be
234 * returned.
235 * @return {MatrixEvent[]|MatrixEvent} A list of events if state_key was
236 * <code>undefined</code>, else a single event (or null if no match found).
237 */
238
239
240RoomState.prototype.getStateEvents = function (eventType, stateKey) {
241 if (!this.events[eventType]) {
242 // no match
243 return stateKey === undefined ? [] : null;
244 }
245
246 if (stateKey === undefined) {
247 // return all values
248 return utils.values(this.events[eventType]);
249 }
250
251 const event = this.events[eventType][stateKey];
252 return event ? event : null;
253};
254/**
255 * Creates a copy of this room state so that mutations to either won't affect the other.
256 * @return {RoomState} the copy of the room state
257 */
258
259
260RoomState.prototype.clone = function () {
261 const copy = new RoomState(this.roomId, this._oobMemberFlags); // Ugly hack: because setStateEvents will mark
262 // members as susperseding future out of bound members
263 // if loading is in progress (through _oobMemberFlags)
264 // since these are not new members, we're merely copying them
265 // set the status to not started
266 // after copying, we set back the status
267
268 const status = this._oobMemberFlags.status;
269 this._oobMemberFlags.status = OOB_STATUS_NOTSTARTED;
270 Object.values(this.events).forEach(eventsByStateKey => {
271 const eventsForType = Object.values(eventsByStateKey);
272 copy.setStateEvents(eventsForType);
273 }); // Ugly hack: see above
274
275 this._oobMemberFlags.status = status;
276
277 if (this._summaryInvitedMemberCount !== null) {
278 copy.setInvitedMemberCount(this.getInvitedMemberCount());
279 }
280
281 if (this._summaryJoinedMemberCount !== null) {
282 copy.setJoinedMemberCount(this.getJoinedMemberCount());
283 } // copy out of band flags if needed
284
285
286 if (this._oobMemberFlags.status == OOB_STATUS_FINISHED) {
287 // copy markOutOfBand flags
288 this.getMembers().forEach(member => {
289 if (member.isOutOfBand()) {
290 const copyMember = copy.getMember(member.userId);
291 copyMember.markOutOfBand();
292 }
293 });
294 }
295
296 return copy;
297};
298/**
299 * Add previously unknown state events.
300 * When lazy loading members while back-paginating,
301 * the relevant room state for the timeline chunk at the end
302 * of the chunk can be set with this method.
303 * @param {MatrixEvent[]} events state events to prepend
304 */
305
306
307RoomState.prototype.setUnknownStateEvents = function (events) {
308 const unknownStateEvents = events.filter(event => {
309 return this.events[event.getType()] === undefined || this.events[event.getType()][event.getStateKey()] === undefined;
310 });
311 this.setStateEvents(unknownStateEvents);
312};
313/**
314 * Add an array of one or more state MatrixEvents, overwriting
315 * any existing state with the same {type, stateKey} tuple. Will fire
316 * "RoomState.events" for every event added. May fire "RoomState.members"
317 * if there are <code>m.room.member</code> events.
318 * @param {MatrixEvent[]} stateEvents a list of state events for this room.
319 * @fires module:client~MatrixClient#event:"RoomState.members"
320 * @fires module:client~MatrixClient#event:"RoomState.newMember"
321 * @fires module:client~MatrixClient#event:"RoomState.events"
322 */
323
324
325RoomState.prototype.setStateEvents = function (stateEvents) {
326 const self = this;
327
328 this._updateModifiedTime(); // update the core event dict
329
330
331 utils.forEach(stateEvents, function (event) {
332 if (event.getRoomId() !== self.roomId) {
333 return;
334 }
335
336 if (!event.isState()) {
337 return;
338 }
339
340 self._setStateEvent(event);
341
342 if (event.getType() === "m.room.member") {
343 _updateDisplayNameCache(self, event.getStateKey(), event.getContent().displayname);
344
345 _updateThirdPartyTokenCache(self, event);
346 }
347
348 self.emit("RoomState.events", event, self);
349 }); // update higher level data structures. This needs to be done AFTER the
350 // core event dict as these structures may depend on other state events in
351 // the given array (e.g. disambiguating display names in one go to do both
352 // clashing names rather than progressively which only catches 1 of them).
353
354 utils.forEach(stateEvents, function (event) {
355 if (event.getRoomId() !== self.roomId) {
356 return;
357 }
358
359 if (!event.isState()) {
360 return;
361 }
362
363 if (event.getType() === "m.room.member") {
364 const userId = event.getStateKey(); // leave events apparently elide the displayname or avatar_url,
365 // so let's fake one up so that we don't leak user ids
366 // into the timeline
367
368 if (event.getContent().membership === "leave" || event.getContent().membership === "ban") {
369 event.getContent().avatar_url = event.getContent().avatar_url || event.getPrevContent().avatar_url;
370 event.getContent().displayname = event.getContent().displayname || event.getPrevContent().displayname;
371 }
372
373 const member = self._getOrCreateMember(userId, event);
374
375 member.setMembershipEvent(event, self);
376
377 self._updateMember(member);
378
379 self.emit("RoomState.members", event, self, member);
380 } else if (event.getType() === "m.room.power_levels") {
381 const members = utils.values(self.members);
382 utils.forEach(members, function (member) {
383 member.setPowerLevelEvent(event);
384 self.emit("RoomState.members", event, self, member);
385 }); // assume all our sentinels are now out-of-date
386
387 self._sentinels = {};
388 }
389 });
390};
391/**
392 * Looks up a member by the given userId, and if it doesn't exist,
393 * create it and emit the `RoomState.newMember` event.
394 * This method makes sure the member is added to the members dictionary
395 * before emitting, as this is done from setStateEvents and _setOutOfBandMember.
396 * @param {string} userId the id of the user to look up
397 * @param {MatrixEvent} event the membership event for the (new) member. Used to emit.
398 * @fires module:client~MatrixClient#event:"RoomState.newMember"
399 * @returns {RoomMember} the member, existing or newly created.
400 */
401
402
403RoomState.prototype._getOrCreateMember = function (userId, event) {
404 let member = this.members[userId];
405
406 if (!member) {
407 member = new _roomMember.RoomMember(this.roomId, userId); // add member to members before emitting any events,
408 // as event handlers often lookup the member
409
410 this.members[userId] = member;
411 this.emit("RoomState.newMember", event, this, member);
412 }
413
414 return member;
415};
416
417RoomState.prototype._setStateEvent = function (event) {
418 if (this.events[event.getType()] === undefined) {
419 this.events[event.getType()] = {};
420 }
421
422 this.events[event.getType()][event.getStateKey()] = event;
423};
424
425RoomState.prototype._updateMember = function (member) {
426 // this member may have a power level already, so set it.
427 const pwrLvlEvent = this.getStateEvents("m.room.power_levels", "");
428
429 if (pwrLvlEvent) {
430 member.setPowerLevelEvent(pwrLvlEvent);
431 } // blow away the sentinel which is now outdated
432
433
434 delete this._sentinels[member.userId];
435 this.members[member.userId] = member;
436 this._joinedMemberCount = null;
437 this._invitedMemberCount = null;
438};
439/**
440 * Get the out-of-band members loading state, whether loading is needed or not.
441 * Note that loading might be in progress and hence isn't needed.
442 * @return {bool} whether or not the members of this room need to be loaded
443 */
444
445
446RoomState.prototype.needsOutOfBandMembers = function () {
447 return this._oobMemberFlags.status === OOB_STATUS_NOTSTARTED;
448};
449/**
450 * Mark this room state as waiting for out-of-band members,
451 * ensuring it doesn't ask for them to be requested again
452 * through needsOutOfBandMembers
453 */
454
455
456RoomState.prototype.markOutOfBandMembersStarted = function () {
457 if (this._oobMemberFlags.status !== OOB_STATUS_NOTSTARTED) {
458 return;
459 }
460
461 this._oobMemberFlags.status = OOB_STATUS_INPROGRESS;
462};
463/**
464 * Mark this room state as having failed to fetch out-of-band members
465 */
466
467
468RoomState.prototype.markOutOfBandMembersFailed = function () {
469 if (this._oobMemberFlags.status !== OOB_STATUS_INPROGRESS) {
470 return;
471 }
472
473 this._oobMemberFlags.status = OOB_STATUS_NOTSTARTED;
474};
475/**
476 * Clears the loaded out-of-band members
477 */
478
479
480RoomState.prototype.clearOutOfBandMembers = function () {
481 let count = 0;
482 Object.keys(this.members).forEach(userId => {
483 const member = this.members[userId];
484
485 if (member.isOutOfBand()) {
486 ++count;
487 delete this.members[userId];
488 }
489 });
490
491 _logger.logger.log(`LL: RoomState removed ${count} members...`);
492
493 this._oobMemberFlags.status = OOB_STATUS_NOTSTARTED;
494};
495/**
496 * Sets the loaded out-of-band members.
497 * @param {MatrixEvent[]} stateEvents array of membership state events
498 */
499
500
501RoomState.prototype.setOutOfBandMembers = function (stateEvents) {
502 _logger.logger.log(`LL: RoomState about to set ${stateEvents.length} OOB members ...`);
503
504 if (this._oobMemberFlags.status !== OOB_STATUS_INPROGRESS) {
505 return;
506 }
507
508 _logger.logger.log(`LL: RoomState put in OOB_STATUS_FINISHED state ...`);
509
510 this._oobMemberFlags.status = OOB_STATUS_FINISHED;
511 stateEvents.forEach(e => this._setOutOfBandMember(e));
512};
513/**
514 * Sets a single out of band member, used by both setOutOfBandMembers and clone
515 * @param {MatrixEvent} stateEvent membership state event
516 */
517
518
519RoomState.prototype._setOutOfBandMember = function (stateEvent) {
520 if (stateEvent.getType() !== 'm.room.member') {
521 return;
522 }
523
524 const userId = stateEvent.getStateKey();
525 const existingMember = this.getMember(userId); // never replace members received as part of the sync
526
527 if (existingMember && !existingMember.isOutOfBand()) {
528 return;
529 }
530
531 const member = this._getOrCreateMember(userId, stateEvent);
532
533 member.setMembershipEvent(stateEvent, this); // needed to know which members need to be stored seperately
534 // as they are not part of the sync accumulator
535 // this is cleared by setMembershipEvent so when it's updated through /sync
536
537 member.markOutOfBand();
538
539 _updateDisplayNameCache(this, member.userId, member.name);
540
541 this._setStateEvent(stateEvent);
542
543 this._updateMember(member);
544
545 this.emit("RoomState.members", stateEvent, this, member);
546};
547/**
548 * Set the current typing event for this room.
549 * @param {MatrixEvent} event The typing event
550 */
551
552
553RoomState.prototype.setTypingEvent = function (event) {
554 utils.forEach(utils.values(this.members), function (member) {
555 member.setTypingEvent(event);
556 });
557};
558/**
559 * Get the m.room.member event which has the given third party invite token.
560 *
561 * @param {string} token The token
562 * @return {?MatrixEvent} The m.room.member event or null
563 */
564
565
566RoomState.prototype.getInviteForThreePidToken = function (token) {
567 return this._tokenToInvite[token] || null;
568};
569/**
570 * Update the last modified time to the current time.
571 */
572
573
574RoomState.prototype._updateModifiedTime = function () {
575 this._modified = Date.now();
576};
577/**
578 * Get the timestamp when this room state was last updated. This timestamp is
579 * updated when this object has received new state events.
580 * @return {number} The timestamp
581 */
582
583
584RoomState.prototype.getLastModifiedTime = function () {
585 return this._modified;
586};
587/**
588 * Get user IDs with the specified or similar display names.
589 * @param {string} displayName The display name to get user IDs from.
590 * @return {string[]} An array of user IDs or an empty array.
591 */
592
593
594RoomState.prototype.getUserIdsWithDisplayName = function (displayName) {
595 return this._displayNameToUserIds[utils.removeHiddenChars(displayName)] || [];
596};
597/**
598 * Returns true if userId is in room, event is not redacted and either sender of
599 * mxEvent or has power level sufficient to redact events other than their own.
600 * @param {MatrixEvent} mxEvent The event to test permission for
601 * @param {string} userId The user ID of the user to test permission for
602 * @return {boolean} true if the given used ID can redact given event
603 */
604
605
606RoomState.prototype.maySendRedactionForEvent = function (mxEvent, userId) {
607 const member = this.getMember(userId);
608 if (!member || member.membership === 'leave') return false;
609 if (mxEvent.status || mxEvent.isRedacted()) return false; // The user may have been the sender, but they can't redact their own message
610 // if redactions are blocked.
611
612 const canRedact = this.maySendEvent("m.room.redaction", userId);
613 if (mxEvent.getSender() === userId) return canRedact;
614 return this._hasSufficientPowerLevelFor('redact', member.powerLevel);
615};
616/**
617 * Returns true if the given power level is sufficient for action
618 * @param {string} action The type of power level to check
619 * @param {number} powerLevel The power level of the member
620 * @return {boolean} true if the given power level is sufficient
621 */
622
623
624RoomState.prototype._hasSufficientPowerLevelFor = function (action, powerLevel) {
625 const powerLevelsEvent = this.getStateEvents('m.room.power_levels', '');
626 let powerLevels = {};
627
628 if (powerLevelsEvent) {
629 powerLevels = powerLevelsEvent.getContent();
630 }
631
632 let requiredLevel = 50;
633
634 if (utils.isNumber(powerLevels[action])) {
635 requiredLevel = powerLevels[action];
636 }
637
638 return powerLevel >= requiredLevel;
639};
640/**
641 * Short-form for maySendEvent('m.room.message', userId)
642 * @param {string} userId The user ID of the user to test permission for
643 * @return {boolean} true if the given user ID should be permitted to send
644 * message events into the given room.
645 */
646
647
648RoomState.prototype.maySendMessage = function (userId) {
649 return this._maySendEventOfType('m.room.message', userId, false);
650};
651/**
652 * Returns true if the given user ID has permission to send a normal
653 * event of type `eventType` into this room.
654 * @param {string} eventType The type of event to test
655 * @param {string} userId The user ID of the user to test permission for
656 * @return {boolean} true if the given user ID should be permitted to send
657 * the given type of event into this room,
658 * according to the room's state.
659 */
660
661
662RoomState.prototype.maySendEvent = function (eventType, userId) {
663 return this._maySendEventOfType(eventType, userId, false);
664};
665/**
666 * Returns true if the given MatrixClient has permission to send a state
667 * event of type `stateEventType` into this room.
668 * @param {string} stateEventType The type of state events to test
669 * @param {MatrixClient} cli The client to test permission for
670 * @return {boolean} true if the given client should be permitted to send
671 * the given type of state event into this room,
672 * according to the room's state.
673 */
674
675
676RoomState.prototype.mayClientSendStateEvent = function (stateEventType, cli) {
677 if (cli.isGuest()) {
678 return false;
679 }
680
681 return this.maySendStateEvent(stateEventType, cli.credentials.userId);
682};
683/**
684 * Returns true if the given user ID has permission to send a state
685 * event of type `stateEventType` into this room.
686 * @param {string} stateEventType The type of state events to test
687 * @param {string} userId The user ID of the user to test permission for
688 * @return {boolean} true if the given user ID should be permitted to send
689 * the given type of state event into this room,
690 * according to the room's state.
691 */
692
693
694RoomState.prototype.maySendStateEvent = function (stateEventType, userId) {
695 return this._maySendEventOfType(stateEventType, userId, true);
696};
697/**
698 * Returns true if the given user ID has permission to send a normal or state
699 * event of type `eventType` into this room.
700 * @param {string} eventType The type of event to test
701 * @param {string} userId The user ID of the user to test permission for
702 * @param {boolean} state If true, tests if the user may send a state
703 event of this type. Otherwise tests whether
704 they may send a regular event.
705 * @return {boolean} true if the given user ID should be permitted to send
706 * the given type of event into this room,
707 * according to the room's state.
708 */
709
710
711RoomState.prototype._maySendEventOfType = function (eventType, userId, state) {
712 const power_levels_event = this.getStateEvents('m.room.power_levels', '');
713 let power_levels;
714 let events_levels = {};
715 let state_default = 0;
716 let events_default = 0;
717 let powerLevel = 0;
718
719 if (power_levels_event) {
720 power_levels = power_levels_event.getContent();
721 events_levels = power_levels.events || {};
722
723 if (Number.isFinite(power_levels.state_default)) {
724 state_default = power_levels.state_default;
725 } else {
726 state_default = 50;
727 }
728
729 const userPowerLevel = power_levels.users && power_levels.users[userId];
730
731 if (Number.isFinite(userPowerLevel)) {
732 powerLevel = userPowerLevel;
733 } else if (Number.isFinite(power_levels.users_default)) {
734 powerLevel = power_levels.users_default;
735 }
736
737 if (Number.isFinite(power_levels.events_default)) {
738 events_default = power_levels.events_default;
739 }
740 }
741
742 let required_level = state ? state_default : events_default;
743
744 if (Number.isFinite(events_levels[eventType])) {
745 required_level = events_levels[eventType];
746 }
747
748 return powerLevel >= required_level;
749};
750/**
751 * Returns true if the given user ID has permission to trigger notification
752 * of type `notifLevelKey`
753 * @param {string} notifLevelKey The level of notification to test (eg. 'room')
754 * @param {string} userId The user ID of the user to test permission for
755 * @return {boolean} true if the given user ID has permission to trigger a
756 * notification of this type.
757 */
758
759
760RoomState.prototype.mayTriggerNotifOfType = function (notifLevelKey, userId) {
761 const member = this.getMember(userId);
762
763 if (!member) {
764 return false;
765 }
766
767 const powerLevelsEvent = this.getStateEvents('m.room.power_levels', '');
768 let notifLevel = 50;
769
770 if (powerLevelsEvent && powerLevelsEvent.getContent() && powerLevelsEvent.getContent().notifications && utils.isNumber(powerLevelsEvent.getContent().notifications[notifLevelKey])) {
771 notifLevel = powerLevelsEvent.getContent().notifications[notifLevelKey];
772 }
773
774 return member.powerLevel >= notifLevel;
775};
776
777function _updateThirdPartyTokenCache(roomState, memberEvent) {
778 if (!memberEvent.getContent().third_party_invite) {
779 return;
780 }
781
782 const token = (memberEvent.getContent().third_party_invite.signed || {}).token;
783
784 if (!token) {
785 return;
786 }
787
788 const threePidInvite = roomState.getStateEvents("m.room.third_party_invite", token);
789
790 if (!threePidInvite) {
791 return;
792 }
793
794 roomState._tokenToInvite[token] = memberEvent;
795}
796
797function _updateDisplayNameCache(roomState, userId, displayName) {
798 const oldName = roomState._userIdsToDisplayNames[userId];
799 delete roomState._userIdsToDisplayNames[userId];
800
801 if (oldName) {
802 // Remove the old name from the cache.
803 // We clobber the user_id > name lookup but the name -> [user_id] lookup
804 // means we need to remove that user ID from that array rather than nuking
805 // the lot.
806 const strippedOldName = utils.removeHiddenChars(oldName);
807 const existingUserIds = roomState._displayNameToUserIds[strippedOldName];
808
809 if (existingUserIds) {
810 // remove this user ID from this array
811 const filteredUserIDs = existingUserIds.filter(id => id !== userId);
812 roomState._displayNameToUserIds[strippedOldName] = filteredUserIDs;
813 }
814 }
815
816 roomState._userIdsToDisplayNames[userId] = displayName;
817 const strippedDisplayname = displayName && utils.removeHiddenChars(displayName); // an empty stripped displayname (undefined/'') will be set to MXID in room-member.js
818
819 if (strippedDisplayname) {
820 if (!roomState._displayNameToUserIds[strippedDisplayname]) {
821 roomState._displayNameToUserIds[strippedDisplayname] = [];
822 }
823
824 roomState._displayNameToUserIds[strippedDisplayname].push(userId);
825 }
826}
827/**
828 * Fires whenever the event dictionary in room state is updated.
829 * @event module:client~MatrixClient#"RoomState.events"
830 * @param {MatrixEvent} event The matrix event which caused this event to fire.
831 * @param {RoomState} state The room state whose RoomState.events dictionary
832 * was updated.
833 * @example
834 * matrixClient.on("RoomState.events", function(event, state){
835 * var newStateEvent = event;
836 * });
837 */
838
839/**
840 * Fires whenever a member in the members dictionary is updated in any way.
841 * @event module:client~MatrixClient#"RoomState.members"
842 * @param {MatrixEvent} event The matrix event which caused this event to fire.
843 * @param {RoomState} state The room state whose RoomState.members dictionary
844 * was updated.
845 * @param {RoomMember} member The room member that was updated.
846 * @example
847 * matrixClient.on("RoomState.members", function(event, state, member){
848 * var newMembershipState = member.membership;
849 * });
850 */
851
852/**
853* Fires whenever a member is added to the members dictionary. The RoomMember
854* will not be fully populated yet (e.g. no membership state) but will already
855* be available in the members dictionary.
856* @event module:client~MatrixClient#"RoomState.newMember"
857* @param {MatrixEvent} event The matrix event which caused this event to fire.
858* @param {RoomState} state The room state whose RoomState.members dictionary
859* was updated with a new entry.
860* @param {RoomMember} member The room member that was added.
861* @example
862* matrixClient.on("RoomState.newMember", function(event, state, member){
863* // add event listeners on 'member'
864* });
865*/
\No newline at end of file