From d8391f450ba7bdc68a72504b746525e400f48c5d Mon Sep 17 00:00:00 2001 From: Joao Siebel <joaos_desenv@imdt.com.br> Date: Thu, 26 Jul 2018 11:56:26 -0300 Subject: [PATCH] use group chat messages in html5 client --- .../imports/api/group-chat-msg/index.js | 9 +- .../server/methods/sendGroupChatMsg.js | 18 ++- .../api/group-chat-msg/server/publishers.js | 15 +-- .../imports/api/group-chat/index.js | 2 +- .../server/methods/createGroupChat.js | 29 +++-- .../api/group-chat/server/publishers.js | 15 +-- .../imports/startup/client/base.jsx | 1 + .../imports/ui/components/chat/container.jsx | 8 +- .../imports/ui/components/chat/service.js | 108 ++++++++++++++++++ .../ui/components/user-list/component.jsx | 3 + .../ui/components/user-list/container.jsx | 2 + .../ui/components/user-list/service.js | 10 ++ .../user-list/user-list-content/component.jsx | 2 + .../user-participants/component.jsx | 6 +- bigbluebutton-html5/server/main.js | 1 + 15 files changed, 166 insertions(+), 63 deletions(-) diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/index.js b/bigbluebutton-html5/imports/api/group-chat-msg/index.js index a88d49148d..fb37887407 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/index.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/index.js @@ -1,14 +1,12 @@ import { Meteor } from 'meteor/meteor'; -const GroupChat = new Mongo.Collection('group-chat-msg'); +const GroupChatMsg = new Mongo.Collection('group-chat-msg'); if (Meteor.isServer) { - GroupChat._ensureIndex({ - meetingId: 1, chatId: 1, access: 1, users: 1, - }); + GroupChatMsg._ensureIndex({ meetingId: 1, chatId: 1 }); } -export default GroupChat; +export default GroupChatMsg; export const CHAT_ACCESS = { PUBLIC: 'PUBLIC_ACCESS', @@ -17,3 +15,4 @@ export const CHAT_ACCESS = { export const CHAT_ACCESS_PUBLIC = CHAT_ACCESS.PUBLIC; export const CHAT_ACCESS_PRIVATE = CHAT_ACCESS.PRIVATE; +export const GROUP_MESSAGE_PUBLIC_ID = 'MAIN-PUBLIC-GROUP-CHAT'; diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js index 4bf8896b9d..2b387daba7 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js @@ -29,6 +29,7 @@ const parseMessage = (message) => { export default function sendGroupChatMsg(credentials, chatId, message) { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'SendGroupChatMessageMsg'; const { meetingId, requesterUserId, requesterToken } = credentials; @@ -37,19 +38,14 @@ export default function sendGroupChatMsg(credentials, chatId, message) { check(requesterToken, String); check(message, Object); - const eventName = 'SendGroupChatMessageMsg'; + const parsedMessage = parseMessage(message.message); + + message.message = parsedMessage - const parsedMessage = parseMessage(message); const payload = { - chatId, - // correlationId: `${Date.now()}`, - sender: { - id: requesterUserId, - name: '', - }, - // color: '1', - message: parsedMessage, + msg: message, + chatId: chatId }; - return RedisPubSub.publishUserMessage(CHANNEL, eventName, meetingId, requesterUserId, payload); + return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); } diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/publishers.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/publishers.js index d9f6af4f38..8bfb688a99 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/publishers.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/publishers.js @@ -1,11 +1,10 @@ +import GroupChatMsg, { CHAT_ACCESS_PUBLIC, GROUP_MESSAGE_PUBLIC_ID } from '/imports/api/group-chat-msg'; import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import Logger from '/imports/startup/server/logger'; import mapToAcl from '/imports/startup/mapToAcl'; -import { GroupChat, CHAT_ACCESS_PUBLIC } from '/imports/api/group-chat-msg'; - function groupChatMsg(credentials) { const { meetingId, requesterUserId, requesterToken } = credentials; @@ -15,17 +14,7 @@ function groupChatMsg(credentials) { Logger.info(`Publishing group-chat-msg for ${meetingId} ${requesterUserId} ${requesterToken}`); - return GroupChat.find({ - $or: [ - { - access: CHAT_ACCESS_PUBLIC, - meetingId, - }, { - users: { $in: [requesterUserId] }, - meetingId, - }, - ], - }); + return GroupChatMsg.find({ meetingId }); } function publish(...args) { diff --git a/bigbluebutton-html5/imports/api/group-chat/index.js b/bigbluebutton-html5/imports/api/group-chat/index.js index 6d4f5c43dd..f26082d0a5 100644 --- a/bigbluebutton-html5/imports/api/group-chat/index.js +++ b/bigbluebutton-html5/imports/api/group-chat/index.js @@ -10,7 +10,7 @@ if (Meteor.isServer) { export default GroupChat; -export const CHAT_ACCESS = { +const CHAT_ACCESS = { PUBLIC: 'PUBLIC_ACCESS', PRIVATE: 'PRIVATE_ACCESS', }; diff --git a/bigbluebutton-html5/imports/api/group-chat/server/methods/createGroupChat.js b/bigbluebutton-html5/imports/api/group-chat/server/methods/createGroupChat.js index a9b6ea40e4..ccefeb7690 100644 --- a/bigbluebutton-html5/imports/api/group-chat/server/methods/createGroupChat.js +++ b/bigbluebutton-html5/imports/api/group-chat/server/methods/createGroupChat.js @@ -1,31 +1,28 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import RedisPubSub from '/imports/startup/server/redis'; +import { CHAT_ACCESS_PUBLIC, CHAT_ACCESS_PRIVATE } from '/imports/api/group-chat' -export default function createGroupChat(credentials) { + +export default function createGroupChat(credentials, receiver) { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'CreateGroupChatReqMsg'; + const { meetingId, requesterUserId, requesterToken } = credentials; check(meetingId, String); check(requesterUserId, String); check(requesterToken, String); + check(receiver, Object); - const eventName = 'CreateGroupChatReqMsg'; - - const payload = { - // TODO: Implement this together with #4988 - // correlationId: String, - // name: String, - // access: String, - // users: Vector[String], - // msg: Vector[{ - // correlationId: String, - // sender: GroupChatUser, - // color: String, - // message: String - // }], + let payload = { + correlationId: `${requesterUserId}-${Date.now()}`, + msg: [], + users: [receiver.id], + access: CHAT_ACCESS_PRIVATE, + name: receiver.name }; - return RedisPubSub.publishUserMessage(CHANNEL, eventName, meetingId, requesterUserId, payload); + return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); } diff --git a/bigbluebutton-html5/imports/api/group-chat/server/publishers.js b/bigbluebutton-html5/imports/api/group-chat/server/publishers.js index 39657bea68..1c3daaad2a 100644 --- a/bigbluebutton-html5/imports/api/group-chat/server/publishers.js +++ b/bigbluebutton-html5/imports/api/group-chat/server/publishers.js @@ -1,11 +1,10 @@ +import GroupChat, { CHAT_ACCESS_PUBLIC } from '/imports/api/group-chat'; import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import Logger from '/imports/startup/server/logger'; import mapToAcl from '/imports/startup/mapToAcl'; -import { GroupChat, CHAT_ACCESS_PUBLIC } from '/imports/api/group-chat'; - function groupChat(credentials) { const { meetingId, requesterUserId, requesterToken } = credentials; @@ -15,17 +14,7 @@ function groupChat(credentials) { Logger.info(`Publishing group-chat for ${meetingId} ${requesterUserId} ${requesterToken}`); - return GroupChat.find({ - $or: [ - { - access: CHAT_ACCESS_PUBLIC, - meetingId, - }, { - users: { $in: [requesterUserId] }, - meetingId, - }, - ], - }); + return GroupChat.find({ meetingId }); } function publish(...args) { diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx index 46d5e1b9e2..4a2f4d4416 100755 --- a/bigbluebutton-html5/imports/startup/client/base.jsx +++ b/bigbluebutton-html5/imports/startup/client/base.jsx @@ -100,6 +100,7 @@ Base.defaultProps = defaultProps; const SUBSCRIPTIONS_NAME = [ 'users', 'chat', 'meetings', 'polls', 'presentations', 'slides', 'captions', 'breakouts', 'voiceUsers', 'whiteboard-multi-user', 'screenshare', + 'group-chat', 'group-chat-msg', ]; const BaseContainer = withRouter(withTracker(({ params, router }) => { diff --git a/bigbluebutton-html5/imports/ui/components/chat/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx index 37d2e99eec..38b19cb92f 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/container.jsx @@ -52,9 +52,11 @@ export default injectIntl(withTracker(({ params, intl }) => { let systemMessageIntl = {}; if (chatID === PUBLIC_CHAT_KEY) { - messages = ChatService.reduceAndMapMessages((ChatService.getPublicMessages())); + messages = ChatService.reduceAndMapGroupMessages(ChatService.getPublicGroupMessages()); } else { - messages = ChatService.getPrivateMessages(chatID); + // messages = ChatService.getPrivateMessages(chatID); + messages = ChatService.getPrivateGroupMessages(chatID); + const user = ChatService.getUser(chatID); chatName = user.name; systemMessageIntl = { 0: user.name }; @@ -115,7 +117,7 @@ export default injectIntl(withTracker(({ params, intl }) => { handleSendMessage: (message) => { ChatService.updateScrollPosition(chatID, null); - return ChatService.sendMessage(chatID, message); + return ChatService.sendGroupMessage(chatID, message); }, handleScrollUpdate: position => ChatService.updateScrollPosition(chatID, position), diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 085e0c2eec..859ae4d5be 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -1,6 +1,8 @@ import Chats from '/imports/api/chat'; import Users from '/imports/api/users'; import Meetings from '/imports/api/meetings'; +import GroupChatMsg, { GROUP_MESSAGE_PUBLIC_ID, CHAT_ACCESS_PRIVATE } from '/imports/api/group-chat-msg'; +import GroupChat from '/imports/api/group-chat'; import Auth from '/imports/ui/services/auth'; import UnreadMessages from '/imports/ui/services/unread-messages'; import Storage from '/imports/ui/services/storage/session'; @@ -48,6 +50,21 @@ const mapMessage = (message) => { return mappedMessage; }; +const mapGroupMessage = (message) => { + const mappedMessage = { + id: message._id, + content: message.content, + time: message.timestamp, // + message.from_tz_offset, + sender: null, + }; + + if (message.sender !== SYSTEM_CHAT_TYPE) { + mappedMessage.sender = getUser(message.sender); + } + + return mappedMessage; +}; + const reduceMessages = (previous, current) => { const lastMessage = previous[previous.length - 1]; const currentMessage = current; @@ -78,6 +95,42 @@ const reduceMessages = (previous, current) => { const reduceAndMapMessages = messages => (messages.reduce(reduceMessages, []).map(mapMessage)); +const reduceAndMapGroupMessages = messages => (messages.reduce(reduceGroupMessages, []).map(mapGroupMessage)); + +const reduceGroupMessages = (previous, current) => { + const lastMessage = previous[previous.length - 1]; + const currentMessage = current; + currentMessage.content = [{ + id: current.id, + text: current.message, + time: current.timestamp, + }]; + if (!lastMessage || !currentMessage.chatId === GROUP_MESSAGE_PUBLIC_ID) { + return previous.concat(currentMessage); + } + // Check if the last message is from the same user and time discrepancy + // between the two messages exceeds window and then group current message + // with the last one + const timeOfLastMessage = lastMessage.content[lastMessage.content.length - 1].time; + if (lastMessage.sender === currentMessage.sender + && (currentMessage.timestamp - timeOfLastMessage) <= GROUPING_MESSAGES_WINDOW) { + lastMessage.content.push(currentMessage.content.pop()); + return previous; + } + + return previous.concat(currentMessage); +}; + +const getPublicGroupMessages = () => { + const publicGroupMessages = GroupChatMsg.find({ + chatId: GROUP_MESSAGE_PUBLIC_ID + }, { + sort: ['timestamp'] + }).fetch(); + + return publicGroupMessages; +}; + const getPublicMessages = () => { const publicMessages = Chats.find({ type: { $in: [PUBLIC_CHAT_TYPE, SYSTEM_CHAT_TYPE] }, @@ -101,6 +154,28 @@ const getPrivateMessages = (userID) => { return reduceAndMapMessages(messages); }; +const getPrivateGroupMessages = (chatID) => { + const sender = getUser(Auth.userID); + + const privateChat = GroupChat.findOne({ users: { $all: [chatID, sender.id] } }); + + let messages = []; + + if (privateChat) { + const { + chatId + } = privateChat; + + messages = GroupChatMsg.find({ + chatId: chatId + }, { + sort: ['timestamp'] + }).fetch(); + } + + return reduceAndMapGroupMessages(messages, []); +}; + const isChatLocked = (receiverID) => { const isPublic = receiverID === PUBLIC_CHAT_ID; @@ -132,6 +207,35 @@ const lastReadMessageTime = (receiverID) => { return UnreadMessages.get(chatType); }; +const sendGroupMessage = (chatID, message) => { + + const isPublicChat = chatID === 'public'; + + let chatId = GROUP_MESSAGE_PUBLIC_ID; + + const sender = getUser(Auth.userID); + + if (!isPublicChat) { + let privateChat = GroupChat.findOne({ users: { $all: [chatID, sender.id] } }); + + if (privateChat) { + chatId = privateChat.chatId; + } + } + + const payload = { + color: "0", + correlationId: `${sender.id}-${Date.now()}`, + sender: { + id: sender.id, + name: sender.name + }, + message: message + }; + + return makeCall('sendGroupChatMsg', chatId, payload); +}; + const sendMessage = (receiverID, message) => { const isPublic = receiverID === PUBLIC_CHAT_ID; @@ -247,8 +351,11 @@ const getNotified = (chat) => { export default { reduceAndMapMessages, + reduceAndMapGroupMessages, + getPublicGroupMessages, getPublicMessages, getPrivateMessages, + getPrivateGroupMessages, getUser, getScrollPosition, hasUnreadMessages, @@ -256,6 +363,7 @@ export default { isChatLocked, updateScrollPosition, updateUnreadMessage, + sendGroupMessage, sendMessage, closePrivateChat, removeFromClosedChatsSession, diff --git a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx index 27c04e3ac7..2bcd6de254 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx @@ -27,6 +27,7 @@ const propTypes = { toggleVoice: PropTypes.func.isRequired, changeRole: PropTypes.func.isRequired, roving: PropTypes.func.isRequired, + getGroupChatPrivate: PropTypes.func.isRequired, }; const SHOW_BRANDING = Meteor.settings.public.app.branding.displayBrandingArea; const defaultProps = { @@ -62,6 +63,7 @@ class UserList extends Component { isPublicChat, roving, CustomLogoUrl, + getGroupChatPrivate, } = this.props; return ( @@ -91,6 +93,7 @@ class UserList extends Component { isMeetingLocked, isPublicChat, roving, + getGroupChatPrivate, } } />} diff --git a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx index f30d12964c..a21bd8419c 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx @@ -22,6 +22,7 @@ const propTypes = { toggleVoice: PropTypes.func.isRequired, changeRole: PropTypes.func.isRequired, roving: PropTypes.func.isRequired, + getGroupChatPrivate: PropTypes.func.isRequired, }; const UserListContainer = props => <UserList {...props} />; @@ -46,4 +47,5 @@ export default withTracker(({ chatID, compact }) => ({ roving: Service.roving, CustomLogoUrl: Service.getCustomLogoUrl(), compact, + getGroupChatPrivate: Service.getGroupChatPrivate, }))(UserListContainer); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js index 11290c48f1..3981d91207 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/service.js +++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js @@ -1,5 +1,6 @@ import Users from '/imports/api/users'; import Chat from '/imports/api/chat'; +import GroupChat from '/imports/api/group-chat'; import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import UnreadMessages from '/imports/ui/services/unread-messages'; @@ -393,6 +394,14 @@ const roving = (event, itemCount, changeState) => { } }; +const getGroupChatPrivate = (sender, receiver) => { + let privateChat = GroupChat.findOne({ users: { $all: [receiver.id, sender.id] } }); + + if (!privateChat) { + makeCall("createGroupChat", receiver); + } +}; + export default { setEmojiStatus, assignPresenter, @@ -409,4 +418,5 @@ export default { roving, setCustomLogoUrl, getCustomLogoUrl, + getGroupChatPrivate, }; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx index 74025e823c..227f2159ba 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/component.jsx @@ -24,6 +24,7 @@ const propTypes = { toggleVoice: PropTypes.func.isRequired, changeRole: PropTypes.func.isRequired, roving: PropTypes.func.isRequired, + getGroupChatPrivate: PropTypes.func.isRequired, }; const defaultProps = { @@ -65,6 +66,7 @@ class UserContent extends Component { normalizeEmojiName={this.props.normalizeEmojiName} isMeetingLocked={this.props.isMeetingLocked} roving={this.props.roving} + getGroupChatPrivate={this.props.getGroupChatPrivate} /> </div> ); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx index df0745e206..e77065cc4a 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx @@ -142,13 +142,17 @@ class UserParticipants extends Component { setEmojiStatus, removeUser, toggleVoice, + getGroupChatPrivate, } = this.props; const userActions = { openChat: { label: () => intl.formatMessage(intlMessages.ChatLabel), - handler: (router, user) => router.push(`/users/chat/${user.id}`), + handler: (router, user) => { + getGroupChatPrivate(currentUser, user) + router.push(`/users/chat/${user.id}`) + }, icon: 'chat', }, clearStatus: { diff --git a/bigbluebutton-html5/server/main.js b/bigbluebutton-html5/server/main.js index 2a41da487e..d9900dbedf 100644 --- a/bigbluebutton-html5/server/main.js +++ b/bigbluebutton-html5/server/main.js @@ -12,6 +12,7 @@ import '/imports/api/slides/server'; import '/imports/api/breakouts/server'; import '/imports/api/chat/server'; import '/imports/api/group-chat/server'; +import '/imports/api/group-chat-msg/server'; import '/imports/api/screenshare/server'; import '/imports/api/voice-users/server'; import '/imports/api/whiteboard-multi-user/server'; -- GitLab