UNPKG

13.3 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports.RoomMember = RoomMember;
9
10var _events = require("events");
11
12var _contentRepo = require("../content-repo");
13
14var utils = _interopRequireWildcard(require("../utils"));
15
16/*
17Copyright 2015, 2016 OpenMarket Ltd
18Copyright 2019 The Matrix.org Foundation C.I.C.
19
20Licensed under the Apache License, Version 2.0 (the "License");
21you may not use this file except in compliance with the License.
22You may obtain a copy of the License at
23
24 http://www.apache.org/licenses/LICENSE-2.0
25
26Unless required by applicable law or agreed to in writing, software
27distributed under the License is distributed on an "AS IS" BASIS,
28WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29See the License for the specific language governing permissions and
30limitations under the License.
31*/
32
33/**
34 * @module models/room-member
35 */
36
37/**
38 * Construct a new room member.
39 *
40 * @constructor
41 * @alias module:models/room-member
42 *
43 * @param {string} roomId The room ID of the member.
44 * @param {string} userId The user ID of the member.
45 * @prop {string} roomId The room ID for this member.
46 * @prop {string} userId The user ID of this member.
47 * @prop {boolean} typing True if the room member is currently typing.
48 * @prop {string} name The human-readable name for this room member. This will be
49 * disambiguated with a suffix of " (@user_id:matrix.org)" if another member shares the
50 * same displayname.
51 * @prop {string} rawDisplayName The ambiguous displayname of this room member.
52 * @prop {Number} powerLevel The power level for this room member.
53 * @prop {Number} powerLevelNorm The normalised power level (0-100) for this
54 * room member.
55 * @prop {User} user The User object for this room member, if one exists.
56 * @prop {string} membership The membership state for this room member e.g. 'join'.
57 * @prop {Object} events The events describing this RoomMember.
58 * @prop {MatrixEvent} events.member The m.room.member event for this RoomMember.
59 */
60function RoomMember(roomId, userId) {
61 this.roomId = roomId;
62 this.userId = userId;
63 this.typing = false;
64 this.name = userId;
65 this.rawDisplayName = userId;
66 this.powerLevel = 0;
67 this.powerLevelNorm = 0;
68 this.user = null;
69 this.membership = null;
70 this.events = {
71 member: null
72 };
73 this._isOutOfBand = false;
74
75 this._updateModifiedTime();
76}
77
78utils.inherits(RoomMember, _events.EventEmitter);
79/**
80 * Mark the member as coming from a channel that is not sync
81 */
82
83RoomMember.prototype.markOutOfBand = function () {
84 this._isOutOfBand = true;
85};
86/**
87 * @return {bool} does the member come from a channel that is not sync?
88 * This is used to store the member seperately
89 * from the sync state so it available across browser sessions.
90 */
91
92
93RoomMember.prototype.isOutOfBand = function () {
94 return this._isOutOfBand;
95};
96/**
97 * Update this room member's membership event. May fire "RoomMember.name" if
98 * this event updates this member's name.
99 * @param {MatrixEvent} event The <code>m.room.member</code> event
100 * @param {RoomState} roomState Optional. The room state to take into account
101 * when calculating (e.g. for disambiguating users with the same name).
102 * @fires module:client~MatrixClient#event:"RoomMember.name"
103 * @fires module:client~MatrixClient#event:"RoomMember.membership"
104 */
105
106
107RoomMember.prototype.setMembershipEvent = function (event, roomState) {
108 if (event.getType() !== "m.room.member") {
109 return;
110 }
111
112 this._isOutOfBand = false;
113 this.events.member = event;
114 const oldMembership = this.membership;
115 this.membership = event.getDirectionalContent().membership;
116 const oldName = this.name;
117 this.name = calculateDisplayName(this.userId, event.getDirectionalContent().displayname, roomState);
118 this.rawDisplayName = event.getDirectionalContent().displayname || this.userId;
119
120 if (oldMembership !== this.membership) {
121 this._updateModifiedTime();
122
123 this.emit("RoomMember.membership", event, this, oldMembership);
124 }
125
126 if (oldName !== this.name) {
127 this._updateModifiedTime();
128
129 this.emit("RoomMember.name", event, this, oldName);
130 }
131};
132/**
133 * Update this room member's power level event. May fire
134 * "RoomMember.powerLevel" if this event updates this member's power levels.
135 * @param {MatrixEvent} powerLevelEvent The <code>m.room.power_levels</code>
136 * event
137 * @fires module:client~MatrixClient#event:"RoomMember.powerLevel"
138 */
139
140
141RoomMember.prototype.setPowerLevelEvent = function (powerLevelEvent) {
142 if (powerLevelEvent.getType() !== "m.room.power_levels") {
143 return;
144 }
145
146 const evContent = powerLevelEvent.getDirectionalContent();
147 let maxLevel = evContent.users_default || 0;
148 utils.forEach(utils.values(evContent.users), function (lvl) {
149 maxLevel = Math.max(maxLevel, lvl);
150 });
151 const oldPowerLevel = this.powerLevel;
152 const oldPowerLevelNorm = this.powerLevelNorm;
153
154 if (evContent.users && evContent.users[this.userId] !== undefined) {
155 this.powerLevel = evContent.users[this.userId];
156 } else if (evContent.users_default !== undefined) {
157 this.powerLevel = evContent.users_default;
158 } else {
159 this.powerLevel = 0;
160 }
161
162 this.powerLevelNorm = 0;
163
164 if (maxLevel > 0) {
165 this.powerLevelNorm = this.powerLevel * 100 / maxLevel;
166 } // emit for changes in powerLevelNorm as well (since the app will need to
167 // redraw everyone's level if the max has changed)
168
169
170 if (oldPowerLevel !== this.powerLevel || oldPowerLevelNorm !== this.powerLevelNorm) {
171 this._updateModifiedTime();
172
173 this.emit("RoomMember.powerLevel", powerLevelEvent, this);
174 }
175};
176/**
177 * Update this room member's typing event. May fire "RoomMember.typing" if
178 * this event changes this member's typing state.
179 * @param {MatrixEvent} event The typing event
180 * @fires module:client~MatrixClient#event:"RoomMember.typing"
181 */
182
183
184RoomMember.prototype.setTypingEvent = function (event) {
185 if (event.getType() !== "m.typing") {
186 return;
187 }
188
189 const oldTyping = this.typing;
190 this.typing = false;
191 const typingList = event.getContent().user_ids;
192
193 if (!utils.isArray(typingList)) {
194 // malformed event :/ bail early. TODO: whine?
195 return;
196 }
197
198 if (typingList.indexOf(this.userId) !== -1) {
199 this.typing = true;
200 }
201
202 if (oldTyping !== this.typing) {
203 this._updateModifiedTime();
204
205 this.emit("RoomMember.typing", event, this);
206 }
207};
208/**
209 * Update the last modified time to the current time.
210 */
211
212
213RoomMember.prototype._updateModifiedTime = function () {
214 this._modified = Date.now();
215};
216/**
217 * Get the timestamp when this RoomMember was last updated. This timestamp is
218 * updated when properties on this RoomMember are updated.
219 * It is updated <i>before</i> firing events.
220 * @return {number} The timestamp
221 */
222
223
224RoomMember.prototype.getLastModifiedTime = function () {
225 return this._modified;
226};
227
228RoomMember.prototype.isKicked = function () {
229 return this.membership === "leave" && this.events.member.getSender() !== this.events.member.getStateKey();
230};
231/**
232 * If this member was invited with the is_direct flag set, return
233 * the user that invited this member
234 * @return {string} user id of the inviter
235 */
236
237
238RoomMember.prototype.getDMInviter = function () {
239 // when not available because that room state hasn't been loaded in,
240 // we don't really know, but more likely to not be a direct chat
241 if (this.events.member) {
242 // TODO: persist the is_direct flag on the member as more member events
243 // come in caused by displayName changes.
244 // the is_direct flag is set on the invite member event.
245 // This is copied on the prev_content section of the join member event
246 // when the invite is accepted.
247 const memberEvent = this.events.member;
248 let memberContent = memberEvent.getContent();
249 let inviteSender = memberEvent.getSender();
250
251 if (memberContent.membership === "join") {
252 memberContent = memberEvent.getPrevContent();
253 inviteSender = memberEvent.getUnsigned().prev_sender;
254 }
255
256 if (memberContent.membership === "invite" && memberContent.is_direct) {
257 return inviteSender;
258 }
259 }
260};
261/**
262 * Get the avatar URL for a room member.
263 * @param {string} baseUrl The base homeserver URL See
264 * {@link module:client~MatrixClient#getHomeserverUrl}.
265 * @param {Number} width The desired width of the thumbnail.
266 * @param {Number} height The desired height of the thumbnail.
267 * @param {string} resizeMethod The thumbnail resize method to use, either
268 * "crop" or "scale".
269 * @param {Boolean} allowDefault (optional) Passing false causes this method to
270 * return null if the user has no avatar image. Otherwise, a default image URL
271 * will be returned. Default: true. (Deprecated)
272 * @param {Boolean} allowDirectLinks (optional) If true, the avatar URL will be
273 * returned even if it is a direct hyperlink rather than a matrix content URL.
274 * If false, any non-matrix content URLs will be ignored. Setting this option to
275 * true will expose URLs that, if fetched, will leak information about the user
276 * to anyone who they share a room with.
277 * @return {?string} the avatar URL or null.
278 */
279
280
281RoomMember.prototype.getAvatarUrl = function (baseUrl, width, height, resizeMethod, allowDefault, allowDirectLinks) {
282 if (allowDefault === undefined) {
283 allowDefault = true;
284 }
285
286 const rawUrl = this.getMxcAvatarUrl();
287
288 if (!rawUrl && !allowDefault) {
289 return null;
290 }
291
292 const httpUrl = (0, _contentRepo.getHttpUriForMxc)(baseUrl, rawUrl, width, height, resizeMethod, allowDirectLinks);
293
294 if (httpUrl) {
295 return httpUrl;
296 } else if (allowDefault) {
297 return (0, _contentRepo.getIdenticonUri)(baseUrl, this.userId, width, height);
298 }
299
300 return null;
301};
302/**
303 * get the mxc avatar url, either from a state event, or from a lazily loaded member
304 * @return {string} the mxc avatar url
305 */
306
307
308RoomMember.prototype.getMxcAvatarUrl = function () {
309 if (this.events.member) {
310 return this.events.member.getDirectionalContent().avatar_url;
311 } else if (this.user) {
312 return this.user.avatarUrl;
313 }
314
315 return null;
316};
317
318function calculateDisplayName(selfUserId, displayName, roomState) {
319 if (!displayName || displayName === selfUserId) {
320 return selfUserId;
321 } // First check if the displayname is something we consider truthy
322 // after stripping it of zero width characters and padding spaces
323
324
325 if (!utils.removeHiddenChars(displayName)) {
326 return selfUserId;
327 }
328
329 if (!roomState) {
330 return displayName;
331 } // Next check if the name contains something that look like a mxid
332 // If it does, it may be someone trying to impersonate someone else
333 // Show full mxid in this case
334
335
336 let disambiguate = /@.+:.+/.test(displayName);
337
338 if (!disambiguate) {
339 // Also show mxid if the display name contains any LTR/RTL characters as these
340 // make it very difficult for us to find similar *looking* display names
341 // E.g "Mark" could be cloned by writing "kraM" but in RTL.
342 disambiguate = /[\u200E\u200F\u202A-\u202F]/.test(displayName);
343 }
344
345 if (!disambiguate) {
346 // Also show mxid if there are other people with the same or similar
347 // displayname, after hidden character removal.
348 const userIds = roomState.getUserIdsWithDisplayName(displayName);
349 disambiguate = userIds.some(u => u !== selfUserId);
350 }
351
352 if (disambiguate) {
353 return displayName + " (" + selfUserId + ")";
354 }
355
356 return displayName;
357}
358/**
359 * Fires whenever any room member's name changes.
360 * @event module:client~MatrixClient#"RoomMember.name"
361 * @param {MatrixEvent} event The matrix event which caused this event to fire.
362 * @param {RoomMember} member The member whose RoomMember.name changed.
363 * @param {string?} oldName The previous name. Null if the member didn't have a
364 * name previously.
365 * @example
366 * matrixClient.on("RoomMember.name", function(event, member){
367 * var newName = member.name;
368 * });
369 */
370
371/**
372 * Fires whenever any room member's membership state changes.
373 * @event module:client~MatrixClient#"RoomMember.membership"
374 * @param {MatrixEvent} event The matrix event which caused this event to fire.
375 * @param {RoomMember} member The member whose RoomMember.membership changed.
376 * @param {string?} oldMembership The previous membership state. Null if it's a
377 * new member.
378 * @example
379 * matrixClient.on("RoomMember.membership", function(event, member, oldMembership){
380 * var newState = member.membership;
381 * });
382 */
383
384/**
385 * Fires whenever any room member's typing state changes.
386 * @event module:client~MatrixClient#"RoomMember.typing"
387 * @param {MatrixEvent} event The matrix event which caused this event to fire.
388 * @param {RoomMember} member The member whose RoomMember.typing changed.
389 * @example
390 * matrixClient.on("RoomMember.typing", function(event, member){
391 * var isTyping = member.typing;
392 * });
393 */
394
395/**
396 * Fires whenever any room member's power level changes.
397 * @event module:client~MatrixClient#"RoomMember.powerLevel"
398 * @param {MatrixEvent} event The matrix event which caused this event to fire.
399 * @param {RoomMember} member The member whose RoomMember.powerLevel changed.
400 * @example
401 * matrixClient.on("RoomMember.powerLevel", function(event, member){
402 * var newPowerLevel = member.powerLevel;
403 * var newNormPowerLevel = member.powerLevelNorm;
404 * });
405 */
\No newline at end of file