bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/user-list/service.js

518 lines
13 KiB
JavaScript
Raw Normal View History

import Users from '/imports/api/users';
import VoiceUsers from '/imports/api/voice-users';
import GroupChat from '/imports/api/group-chat';
2019-08-03 02:18:33 +08:00
import { GroupChatMsg } from '/imports/api/group-chat-msg';
2018-10-02 21:48:12 +08:00
import Breakouts from '/imports/api/breakouts/';
import Meetings from '/imports/api/meetings';
2016-07-07 20:50:32 +08:00
import Auth from '/imports/ui/services/auth';
2016-07-11 20:34:58 +08:00
import UnreadMessages from '/imports/ui/services/unread-messages';
2017-03-01 06:40:16 +08:00
import Storage from '/imports/ui/services/storage/session';
import { EMOJI_STATUSES } from '/imports/utils/statuses';
import { makeCall } from '/imports/ui/services/api';
2017-03-22 05:52:10 +08:00
import _ from 'lodash';
import KEY_CODES from '/imports/utils/keyCodes';
import AudioService from '/imports/ui/components/audio/service';
import logger from '/imports/startup/client/logger';
2016-06-07 00:45:30 +08:00
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
const ROLE_VIEWER = Meteor.settings.public.user.role_viewer;
2016-06-07 00:45:30 +08:00
const DIAL_IN_CLIENT_TYPE = 'dial-in-user';
// session for closed chat list
const CLOSED_CHAT_LIST_KEY = 'closedChatList';
2019-01-14 21:23:35 +08:00
const mapActiveChats = (chat) => {
2017-06-03 03:25:02 +08:00
const currentUserId = Auth.userID;
if (chat.sender !== currentUserId) {
return chat.sender;
}
const { chatId } = chat;
const userId = GroupChat.findOne({ chatId }).users.filter(user => user !== currentUserId);
return userId[0];
2016-06-28 21:10:20 +08:00
};
2018-03-21 03:35:28 +08:00
const CUSTOM_LOGO_URL_KEY = 'CustomLogoUrl';
export const setCustomLogoUrl = path => Storage.setItem(CUSTOM_LOGO_URL_KEY, path);
const getCustomLogoUrl = () => Storage.getItem(CUSTOM_LOGO_URL_KEY);
2016-06-07 21:58:51 +08:00
const sortUsersByName = (a, b) => {
const aName = a.name.toLowerCase();
const bName = b.name.toLowerCase();
if (aName < bName) {
2016-06-07 00:45:30 +08:00
return -1;
} if (aName > bName) {
2016-06-07 00:45:30 +08:00
return 1;
} if (a.userId > b.userId) {
2016-06-07 00:45:30 +08:00
return -1;
} if (a.userId < b.userId) {
2016-06-07 00:45:30 +08:00
return 1;
2016-06-07 21:58:51 +08:00
}
return 0;
};
const sortUsersByEmoji = (a, b) => {
if (a.emoji && b.emoji && (a.emoji !== 'none' && b.emoji !== 'none')) {
if (a.emojiTime < b.emojiTime) {
return -1;
} if (a.emojiTime > b.emojiTime) {
return 1;
}
} if (a.emoji && a.emoji !== 'none') {
2016-06-07 00:45:30 +08:00
return -1;
} if (b.emoji && b.emoji !== 'none') {
2016-06-07 00:45:30 +08:00
return 1;
}
2016-06-07 21:58:51 +08:00
return 0;
};
const sortUsersByModerator = (a, b) => {
if (a.role === ROLE_MODERATOR && b.role === ROLE_MODERATOR) {
return 0;
} if (a.role === ROLE_MODERATOR) {
2016-06-07 00:45:30 +08:00
return -1;
} if (b.role === ROLE_MODERATOR) {
2016-06-07 00:45:30 +08:00
return 1;
2016-06-07 21:58:51 +08:00
}
return 0;
};
const sortUsersByPhoneUser = (a, b) => {
if (!a.clientType === DIAL_IN_CLIENT_TYPE && !b.clientType === DIAL_IN_CLIENT_TYPE) {
2017-12-12 23:03:33 +08:00
return 0;
} if (!a.clientType === DIAL_IN_CLIENT_TYPE) {
2016-06-07 00:45:30 +08:00
return -1;
} if (!b.clientType === DIAL_IN_CLIENT_TYPE) {
2016-06-07 00:45:30 +08:00
return 1;
}
return 0;
};
// current user's name is always on top
const sortUsersByCurrent = (a, b) => {
if (a.userId === Auth.userID) {
return -1;
} if (b.userId === Auth.userID) {
2018-07-05 01:51:59 +08:00
return 1;
}
return 0;
};
2016-06-07 21:58:51 +08:00
const sortUsers = (a, b) => {
let sort = sortUsersByCurrent(a, b);
if (sort === 0) {
sort = sortUsersByModerator(a, b);
}
2016-06-07 21:58:51 +08:00
if (sort === 0) {
sort = sortUsersByEmoji(a, b);
2016-06-07 21:58:51 +08:00
}
if (sort === 0) {
sort = sortUsersByPhoneUser(a, b);
}
if (sort === 0) {
sort = sortUsersByName(a, b);
}
return sort;
};
2016-06-28 21:10:20 +08:00
const sortChatsByName = (a, b) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1;
} if (a.name.toLowerCase() > b.name.toLowerCase()) {
2016-06-28 21:10:20 +08:00
return 1;
} if (a.id.toLowerCase() > b.id.toLowerCase()) {
2016-06-28 21:10:20 +08:00
return -1;
} if (a.id.toLowerCase() < b.id.toLowerCase()) {
2016-06-28 21:10:20 +08:00
return 1;
}
return 0;
};
const sortChatsByIcon = (a, b) => {
if (a.icon && b.icon) {
return sortChatsByName(a, b);
} if (a.icon) {
2016-06-28 21:10:20 +08:00
return -1;
} if (b.icon) {
2016-06-28 21:10:20 +08:00
return 1;
}
return 0;
};
const isPublicChat = chat => (
chat.id === 'public'
);
2016-06-28 21:10:20 +08:00
const sortChats = (a, b) => {
let sort = sortChatsByIcon(a, b);
if (sort === 0) {
sort = sortChatsByName(a, b);
}
return sort;
};
const userFindSorting = {
2017-07-26 22:09:07 +08:00
emojiTime: 1,
role: 1,
phoneUser: 1,
sortName: 1,
userId: 1,
2016-06-28 21:10:20 +08:00
};
2016-06-07 00:45:30 +08:00
const getUsers = () => {
let users = Users
.find({
meetingId: Auth.meetingID,
connectionStatus: 'online',
}, userFindSorting)
2017-04-13 04:19:39 +08:00
.fetch();
2016-06-07 00:45:30 +08:00
const currentUser = Users.findOne({ userId: Auth.userID }, { fields: { role: 1, locked: 1 } });
if (currentUser && currentUser.role === ROLE_VIEWER && currentUser.locked) {
const meeting = Meetings.findOne({ meetingId: Auth.meetingID },
{ fields: { 'lockSettingsProps.hideUserList': 1 } });
if (meeting && meeting.lockSettingsProps && meeting.lockSettingsProps.hideUserList) {
const moderatorOrCurrentUser = u => u.role === ROLE_MODERATOR || u.userId === Auth.userID;
users = users.filter(moderatorOrCurrentUser);
}
}
return users.sort(sortUsers);
2016-05-31 19:29:38 +08:00
};
2016-05-20 02:22:56 +08:00
const hasBreakoutRoom = () => Breakouts.find({ parentMeetingId: Auth.meetingID },
{ fields: {} }).count() > 0;
2018-10-02 21:48:12 +08:00
2019-01-14 21:23:35 +08:00
const getActiveChats = (chatID) => {
const privateChat = GroupChat
.find({ users: { $all: [Auth.userID] } }, { fields: { chatId: 1 } })
.fetch()
.map(chat => chat.chatId);
const filter = {
chatId: { $ne: PUBLIC_GROUP_CHAT_ID },
};
if (privateChat) {
filter.chatId = { $in: privateChat };
}
2019-01-14 21:23:35 +08:00
let activeChats = GroupChatMsg
.find(filter)
2017-04-13 04:19:39 +08:00
.fetch()
2019-01-14 21:23:35 +08:00
.map(mapActiveChats);
2016-06-28 21:10:20 +08:00
2016-06-30 22:45:19 +08:00
if (chatID) {
2019-01-14 21:23:35 +08:00
activeChats.push(chatID);
2016-06-28 21:10:20 +08:00
}
2019-01-14 21:23:35 +08:00
activeChats = _.uniq(_.compact(activeChats));
2016-06-28 21:10:20 +08:00
2019-01-14 21:23:35 +08:00
activeChats = Users
.find({ userId: { $in: activeChats } }, { fields: { userId: 1 } })
2017-06-03 03:25:02 +08:00
.map((op) => {
2019-01-14 21:23:35 +08:00
const activeChat = op;
activeChat.unreadCounter = UnreadMessages.count(op.userId);
2019-01-14 21:23:35 +08:00
return activeChat;
2017-04-13 04:19:39 +08:00
});
2017-03-01 06:40:16 +08:00
2017-06-03 03:25:02 +08:00
const currentClosedChats = Storage.getItem(CLOSED_CHAT_LIST_KEY) || [];
const filteredChatList = [];
2019-01-14 21:23:35 +08:00
activeChats.forEach((op) => {
// When a new private chat message is received, ensure the conversation view is restored.
2017-03-03 04:33:49 +08:00
if (op.unreadCounter > 0) {
2017-03-22 05:52:10 +08:00
if (_.indexOf(currentClosedChats, op.id) > -1) {
Storage.setItem(CLOSED_CHAT_LIST_KEY, _.without(currentClosedChats, op.id));
}
2017-03-03 04:33:49 +08:00
}
2017-03-01 06:40:16 +08:00
2019-01-14 21:23:35 +08:00
// Compare activeChats with session and push it into filteredChatList
// if one of the activeChat is not in session.
// It will pass to activeChats.
2017-03-22 05:52:10 +08:00
if (_.indexOf(currentClosedChats, op.id) < 0) {
2017-03-03 04:33:49 +08:00
filteredChatList.push(op);
2017-03-01 06:40:16 +08:00
}
2017-03-03 04:33:49 +08:00
});
2019-01-14 21:23:35 +08:00
activeChats = filteredChatList;
2016-06-28 21:10:20 +08:00
2019-01-14 21:23:35 +08:00
activeChats.push({
2016-06-28 21:10:20 +08:00
id: 'public',
name: 'Public Chat',
2017-03-02 09:03:02 +08:00
icon: 'group_chat',
unreadCounter: UnreadMessages.count(PUBLIC_GROUP_CHAT_ID),
2016-06-28 21:10:20 +08:00
});
2019-01-14 21:23:35 +08:00
return activeChats
2017-04-13 04:19:39 +08:00
.sort(sortChats);
2016-06-28 21:10:20 +08:00
};
2017-12-09 00:40:52 +08:00
const isVoiceOnlyUser = userId => userId.toString().startsWith('v_');
const isMeetingLocked = (id) => {
const meeting = Meetings.findOne({ meetingId: id }, { fields: { lockSettingsProps: 1 } });
let isLocked = false;
if (meeting.lockSettingsProps !== undefined) {
const lockSettings = meeting.lockSettingsProps;
if (lockSettings.disableCam
|| lockSettings.disableMic
2019-04-10 21:44:34 +08:00
|| lockSettings.disablePrivateChat
|| lockSettings.disablePublicChat
|| lockSettings.disableNote) {
isLocked = true;
}
}
return isLocked;
};
const areUsersUnmutable = () => {
2019-04-11 04:55:24 +08:00
const meeting = Meetings.findOne({ meetingId: Auth.meetingID });
if (meeting.usersProp) {
return meeting.usersProp.allowModsToUnmuteUsers;
2019-04-11 04:55:24 +08:00
}
return false;
};
2019-04-11 04:55:24 +08:00
const isMe = userId => userId === Auth.userID;
const isModerator = userId => Users.findOne({ userId },
{ fields: { role: 1 } }).role === ROLE_MODERATOR;
const curatedVoiceUser = (intId) => {
const voiceUser = VoiceUsers.findOne({ intId });
return {
isVoiceUser: voiceUser ? voiceUser.joined : false,
isMuted: voiceUser ? voiceUser.muted && !voiceUser.listenOnly : false,
isTalking: voiceUser ? voiceUser.talking && !voiceUser.muted : false,
isListenOnly: voiceUser ? voiceUser.listenOnly : false,
};
};
const getAvailableActions = (subjectUser, isBreakoutRoom) => {
const isDialInUser = isVoiceOnlyUser(subjectUser.userId) || subjectUser.phone_user;
const hasAuthority = isModerator(Auth.userID) || isMe(subjectUser.userId);
const allowedToChatPrivately = !isMe(subjectUser.userId) && !isDialInUser;
const voiceUser = curatedVoiceUser(subjectUser.userId);
const allowedToMuteAudio = hasAuthority
&& voiceUser.isVoiceUser
&& !voiceUser.isMuted
&& !voiceUser.isListenOnly;
const allowedToUnmuteAudio = hasAuthority
&& voiceUser.isVoiceUser
&& !voiceUser.isListenOnly
&& voiceUser.isMuted
&& (isMe(subjectUser.userId) || areUsersUnmutable());
const allowedToResetStatus = hasAuthority
&& subjectUser.emoji !== EMOJI_STATUSES.none
2019-01-14 21:23:35 +08:00
&& !isDialInUser;
2017-08-16 22:56:31 +08:00
// if currentUser is a moderator, allow removing other users
const allowedToRemove = isModerator(Auth.userID)
&& !isMe(subjectUser.userId)
&& !isBreakoutRoom;
2017-08-16 22:56:31 +08:00
const allowedToSetPresenter = isModerator(Auth.userID)
&& !subjectUser.presenter
2019-01-14 21:23:35 +08:00
&& !isDialInUser;
const allowedToPromote = isModerator(Auth.userID)
&& !isMe(subjectUser.userId)
&& !isModerator(subjectUser.userId)
&& !isDialInUser
&& !isBreakoutRoom;
2017-08-16 22:56:31 +08:00
const allowedToDemote = isModerator(Auth.userID)
&& !isMe(subjectUser.userId)
&& isModerator(subjectUser.userId)
&& !isDialInUser
&& !isBreakoutRoom;
const allowedToChangeStatus = isMe(subjectUser.userId);
const allowedToChangeUserLockStatus = isModerator(Auth.userID)
&& !isModerator(subjectUser.userId) && isMeetingLocked(Auth.meetingID);
2017-08-16 22:56:31 +08:00
return {
allowedToChatPrivately,
allowedToMuteAudio,
allowedToUnmuteAudio,
allowedToResetStatus,
allowedToRemove,
2017-08-16 22:56:31 +08:00
allowedToSetPresenter,
allowedToPromote,
allowedToDemote,
allowedToChangeStatus,
allowedToChangeUserLockStatus,
2017-08-16 22:56:31 +08:00
};
};
const normalizeEmojiName = emoji => (
emoji in EMOJI_STATUSES ? EMOJI_STATUSES[emoji] : emoji
);
2017-08-16 22:56:31 +08:00
const setEmojiStatus = (userId, emoji) => {
const statusAvailable = (Object.keys(EMOJI_STATUSES).includes(emoji));
return statusAvailable
? makeCall('setEmojiStatus', Auth.userID, emoji)
: makeCall('setEmojiStatus', userId, 'none');
};
const assignPresenter = (userId) => { makeCall('assignPresenter', userId); };
const removeUser = (userId) => {
2017-12-09 00:40:52 +08:00
if (isVoiceOnlyUser(userId)) {
makeCall('ejectUserFromVoice', userId);
} else {
makeCall('removeUser', userId);
2017-12-09 00:40:52 +08:00
}
};
2019-01-14 21:23:35 +08:00
const toggleVoice = (userId) => {
if (userId === Auth.userID) {
AudioService.toggleMuteMicrophone();
2019-01-14 21:23:35 +08:00
} else {
makeCall('toggleVoice', userId);
logger.info({
logCode: 'usermenu_option_mute_audio',
extraInfo: { logType: 'moderator_action' },
}, 'moderator muted user microphone');
2019-01-14 21:23:35 +08:00
}
};
2018-09-29 05:32:52 +08:00
const muteAllUsers = (userId) => { makeCall('muteAllUsers', userId); };
const muteAllExceptPresenter = (userId) => { makeCall('muteAllExceptPresenter', userId); };
const changeRole = (userId, role) => { makeCall('changeRole', userId, role); };
const roving = (event, changeState, elementsList, element) => {
this.selectedElement = element;
const menuOpen = Session.get('dropdownOpen') || false;
if (menuOpen) {
const menuChildren = document.activeElement.getElementsByTagName('li');
if ([KEY_CODES.ESCAPE, KEY_CODES.ARROW_LEFT].includes(event.keyCode)) {
document.activeElement.click();
}
if ([KEY_CODES.ARROW_UP].includes(event.keyCode)) {
menuChildren[menuChildren.length - 1].focus();
}
if ([KEY_CODES.ARROW_DOWN].includes(event.keyCode)) {
for (let i = 0; i < menuChildren.length; i += 1) {
if (menuChildren[i].hasAttribute('tabIndex')) {
menuChildren[i].focus();
break;
}
}
}
return;
}
if ([KEY_CODES.ESCAPE, KEY_CODES.TAB].includes(event.keyCode)) {
2017-12-07 01:05:32 +08:00
document.activeElement.blur();
changeState(null);
}
if (event.keyCode === KEY_CODES.ARROW_DOWN) {
const firstElement = elementsList.firstChild;
let elRef = element ? element.nextSibling : firstElement;
elRef = elRef || firstElement;
changeState(elRef);
}
if (event.keyCode === KEY_CODES.ARROW_UP) {
const lastElement = elementsList.lastChild;
let elRef = element ? element.previousSibling : lastElement;
elRef = elRef || lastElement;
changeState(elRef);
}
if ([KEY_CODES.ARROW_RIGHT, KEY_CODES.SPACE, KEY_CODES.ENTER].includes(event.keyCode)) {
document.activeElement.firstChild.click();
}
};
2019-07-31 19:53:53 +08:00
const hasPrivateChatBetweenUsers = (senderId, receiverId) => GroupChat
.findOne({ users: { $all: [receiverId, senderId] } });
const getGroupChatPrivate = (senderUserId, receiver) => {
if (!hasPrivateChatBetweenUsers(senderUserId, receiver.userId)) {
makeCall('createGroupChat', receiver);
}
};
const isUserModerator = (userId) => {
const u = Users.findOne({ userId });
return u ? u.role === ROLE_MODERATOR : false;
};
const toggleUserLock = (userId, lockStatus) => {
makeCall('toggleUserLock', userId, lockStatus);
};
const requestUserInformation = (userId) => {
makeCall('requestUserInformation', userId);
};
export default {
sortUsers,
setEmojiStatus,
assignPresenter,
removeUser,
toggleVoice,
2018-09-29 05:32:52 +08:00
muteAllUsers,
muteAllExceptPresenter,
changeRole,
2016-06-07 00:45:30 +08:00
getUsers,
2019-01-14 21:23:35 +08:00
getActiveChats,
2017-08-16 22:56:31 +08:00
getAvailableActions,
curatedVoiceUser,
2017-08-16 22:56:31 +08:00
normalizeEmojiName,
isMeetingLocked,
isPublicChat,
roving,
2018-03-21 03:35:28 +08:00
setCustomLogoUrl,
getCustomLogoUrl,
getGroupChatPrivate,
2018-10-02 21:48:12 +08:00
hasBreakoutRoom,
isUserModerator,
getEmojiList: () => EMOJI_STATUSES,
getEmoji: () => Users.findOne({ userId: Auth.userID }).emoji,
hasPrivateChatBetweenUsers,
toggleUserLock,
requestUserInformation,
};