use group chat messages in html5 client

This commit is contained in:
Joao Siebel 2018-07-26 11:56:26 -03:00
parent aef2fe577d
commit d8391f450b
15 changed files with 166 additions and 63 deletions

View File

@ -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';

View File

@ -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);
}

View File

@ -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) {

View File

@ -10,7 +10,7 @@ if (Meteor.isServer) {
export default GroupChat;
export const CHAT_ACCESS = {
const CHAT_ACCESS = {
PUBLIC: 'PUBLIC_ACCESS',
PRIVATE: 'PRIVATE_ACCESS',
};

View File

@ -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);
}

View File

@ -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) {

View File

@ -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 }) => {

View File

@ -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),

View File

@ -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,

View File

@ -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,
}
}
/>}

View File

@ -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);

View File

@ -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,
};

View File

@ -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>
);

View File

@ -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: {

View File

@ -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';