import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { Meteor } from 'meteor/meteor'; import { defineMessages, injectIntl } from 'react-intl'; import injectNotify from '/imports/ui/components/common/toast/inject-notify/component'; import AudioService from '/imports/ui/components/audio/service'; import ChatPushAlert from './push-alert/component'; import { stripTags, unescapeHtml, uniqueId } from '/imports/utils/string-utils'; import Service from '../service'; import Styled from './styles'; import { usePreviousValue } from '/imports/ui/components/utils/hooks'; import { Session } from 'meteor/session'; import { isEqual } from 'radash'; const CHAT_CONFIG = Meteor.settings.public.chat; const PUBLIC_CHAT_CLEAR = CHAT_CONFIG.chat_clear; const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id; const POLL_RESULT_KEY = CHAT_CONFIG.system_messages_keys.chat_poll_result; const EXPORTED_PRESENTATION_KEY = CHAT_CONFIG.system_messages_keys.chat_exported_presentation; const propTypes = { pushAlertEnabled: PropTypes.bool.isRequired, audioAlertEnabled: PropTypes.bool.isRequired, unreadMessagesCountByChat: PropTypes.arrayOf(PropTypes.object), unreadMessagesByChat: PropTypes.arrayOf(PropTypes.array), idChatOpen: PropTypes.string.isRequired, intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, }; const defaultProps = { unreadMessagesCountByChat: null, unreadMessagesByChat: null, }; const intlMessages = defineMessages({ appToastChatPublic: { id: 'app.toast.chat.public', description: 'when entry various message', }, appToastChatPrivate: { id: 'app.toast.chat.private', description: 'when entry various message', }, appToastChatSystem: { id: 'app.toast.chat.system', description: 'system for use', }, publicChatClear: { id: 'app.chat.clearPublicChatMessage', description: 'message of when clear the public chat', }, publicChatMsg: { id: 'app.toast.chat.public', description: 'public chat toast message title', }, privateChatMsg: { id: 'app.toast.chat.private', description: 'private chat toast message title', }, pollResults: { id: 'app.toast.chat.poll', description: 'chat toast message for polls', }, pollResultsClick: { id: 'app.toast.chat.pollClick', description: 'chat toast click message for polls', }, exportedPresentation: { id: 'app.toast.chat.exportedPresentation', description: 'chat toast message for exportedPresentation', } }); const ALERT_INTERVAL = 5000; // 5 seconds const ALERT_DURATION = 4000; // 4 seconds const ChatAlert = (props) => { const { audioAlertEnabled, pushAlertEnabled, idChatOpen, unreadMessagesCountByChat, unreadMessagesByChat, intl, layoutContextDispatch, } = props; const [unreadMessagesCount, setUnreadMessagesCount] = useState(0); const [unreadMessages, setUnreadMessages] = useState([]); const [lastAlertTimestampByChat, setLastAlertTimestampByChat] = useState({}); const [alertEnabledTimestamp, setAlertEnabledTimestamp] = useState(null); const prevUnreadMessages = usePreviousValue(unreadMessages); // audio alerts useEffect(() => { if (audioAlertEnabled) { const unreadObject = unreadMessagesCountByChat; const unreadCount = document.hidden ? unreadObject.reduce((a, b) => a + b.unreadCounter, 0) : unreadObject.filter((chat) => chat.chatId !== idChatOpen) .reduce((a, b) => a + b.unreadCounter, 0); if (unreadCount > unreadMessagesCount) { AudioService.playAlertSound(`${Meteor.settings.public.app.cdn + Meteor.settings.public.app.basename + Meteor.settings.public.app.instanceId}` + '/resources/sounds/notify.mp3'); } setUnreadMessagesCount(unreadCount); } }, [unreadMessagesCountByChat]); // push alerts useEffect(() => { if (pushAlertEnabled) { setAlertEnabledTimestamp(new Date().getTime()); } }, [pushAlertEnabled]); useEffect(() => { if (pushAlertEnabled) { const alertsObject = unreadMessagesByChat; let timewindowsToAlert = []; let filteredTimewindows = []; alertsObject.forEach((chat) => { filteredTimewindows = filteredTimewindows.concat( chat.filter((timeWindow) => timeWindow.timestamp > alertEnabledTimestamp), ); }); filteredTimewindows.forEach((timeWindow) => { const durationDiff = ALERT_DURATION - (new Date().getTime() - timeWindow.timestamp); if ((timeWindow.lastTimestamp > timeWindow.timestamp && durationDiff > 0 && timeWindow.lastTimestamp > (lastAlertTimestampByChat[timeWindow.chatId] || 0)) || timeWindow.timestamp > (lastAlertTimestampByChat[timeWindow.chatId] || 0) + ALERT_INTERVAL) { timewindowsToAlert = timewindowsToAlert .filter((item) => item.chatId !== timeWindow.chatId); const newTimeWindow = { ...timeWindow }; newTimeWindow.durationDiff = durationDiff; timewindowsToAlert.push(newTimeWindow); const newLastAlertTimestampByChat = { ...lastAlertTimestampByChat }; if (timeWindow.timestamp > (lastAlertTimestampByChat[timeWindow.chatId] || 0)) { newLastAlertTimestampByChat[timeWindow.chatId] = timeWindow.timestamp; setLastAlertTimestampByChat(newLastAlertTimestampByChat); } } }); setUnreadMessages(timewindowsToAlert); } }, [unreadMessagesByChat]); const mapContentText = (message) => { const contentMessage = message .map((content) => { if (content.text === PUBLIC_CHAT_CLEAR) { return intl.formatMessage(intlMessages.publicChatClear); } return unescapeHtml(stripTags(content.text)); }); return contentMessage; }; const createMessage = (name, message) => ( {name} { mapContentText(message) .reduce((acc, text) => [...acc, (
), text], []) }
); const createPollMessage = () => ( {intl.formatMessage(intlMessages.pollResults)} {intl.formatMessage(intlMessages.pollResultsClick)} ); const createExportedPresentationMessage = (filename) => ( {intl.formatMessage(intlMessages.exportedPresentation)} {filename} ); if (isEqual(prevUnreadMessages, unreadMessages)) { return null; } return pushAlertEnabled ? unreadMessages.map((timeWindow) => { const mappedMessage = Service.mapGroupMessage(timeWindow); let content = null; let isPollResult = false; if (mappedMessage) { if (mappedMessage.id.includes(POLL_RESULT_KEY)) { content = createPollMessage(); isPollResult = true; } else if (mappedMessage.id.includes(EXPORTED_PRESENTATION_KEY)) { content = createExportedPresentationMessage(mappedMessage.extra.filename); } else { content = createMessage(mappedMessage.sender.name, mappedMessage.content.slice(-5)); } } const messageChatId = mappedMessage.chatId === 'MAIN-PUBLIC-GROUP-CHAT' ? PUBLIC_CHAT_ID : mappedMessage.chatId; const newUnreadMessages = unreadMessages .filter((message) => message.key !== mappedMessage.key); return content ? ( {intl.formatMessage(intlMessages.appToastChatPublic)} : {intl.formatMessage(intlMessages.appToastChatPrivate)} } onOpen={ () => { if (isPollResult) { Session.set('ignorePollNotifications', true); } setUnreadMessages(newUnreadMessages); } } onClose={ () => { if (isPollResult) { Session.set('ignorePollNotifications', false); } setUnreadMessages(newUnreadMessages); } } alertDuration={timeWindow.durationDiff} layoutContextDispatch={layoutContextDispatch} /> ) : null; }) : null; }; ChatAlert.propTypes = propTypes; ChatAlert.defaultProps = defaultProps; export default injectNotify(injectIntl(ChatAlert));