// @flow import UIUtil from '../../../modules/UI/util/UIUtil'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app'; import { CONFERENCE_JOINED, getCurrentConference } from '../base/conference'; import { JitsiConferenceEvents } from '../base/lib-jitsi-meet'; import { getParticipantById, getParticipantDisplayName } from '../base/participants'; import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux'; import { playSound, registerSound, unregisterSound } from '../base/sounds'; import { isButtonEnabled, showToolbox } from '../toolbox'; import { SEND_MESSAGE } from './actionTypes'; import { addMessage, clearMessages, toggleChat } from './actions'; import { INCOMING_MSG_SOUND_ID } from './constants'; import { INCOMING_MSG_SOUND_FILE } from './sounds'; declare var APP: Object; declare var interfaceConfig : Object; /** * Implements the middleware of the chat feature. * * @param {Store} store - The redux store. * @returns {Function} */ MiddlewareRegistry.register(store => next => action => { switch (action.type) { case APP_WILL_MOUNT: store.dispatch( registerSound(INCOMING_MSG_SOUND_ID, INCOMING_MSG_SOUND_FILE)); break; case APP_WILL_UNMOUNT: store.dispatch(unregisterSound(INCOMING_MSG_SOUND_ID)); break; case CONFERENCE_JOINED: _addChatMsgListener(action.conference, store); break; case SEND_MESSAGE: { const { conference } = store.getState()['features/base/conference']; if (conference) { const escapedMessage = UIUtil.escapeHtml(action.message); if (typeof APP !== 'undefined') { APP.API.notifySendingChatMessage(escapedMessage); } conference.sendTextMessage(escapedMessage); } break; } } return next(action); }); /** * Set up state change listener to perform maintenance tasks when the conference * is left or failed, e.g. clear messages or close the chat modal if it's left * open. */ StateListenerRegistry.register( state => getCurrentConference(state), (conference, { dispatch, getState }, previousConference) => { if (conference !== previousConference) { // conference changed, left or failed... if (getState()['features/chat'].isOpen) { // Closes the chat if it's left open. dispatch(toggleChat()); } // Clear chat messages. dispatch(clearMessages()); } }); /** * Registers listener for {@link JitsiConferenceEvents.MESSAGE_RECEIVED} that * will perform various chat related activities. * * @param {JitsiConference} conference - The conference instance on which the * new event listener will be registered. * @param {Object} store - The redux store object. * @private * @returns {void} */ function _addChatMsgListener(conference, { dispatch, getState }) { if ((typeof interfaceConfig === 'object' && interfaceConfig.filmStripOnly) || (typeof APP !== 'undefined' && !isButtonEnabled('chat'))) { // We don't register anything on web if we're in filmStripOnly mode, or // the chat button is not enabled in interfaceConfig. return; } conference.on( JitsiConferenceEvents.MESSAGE_RECEIVED, (id, message, timestamp) => { // Logic for all platforms: const state = getState(); const { isOpen: isChatOpen } = state['features/chat']; if (!isChatOpen) { dispatch(playSound(INCOMING_MSG_SOUND_ID)); } // Provide a default for for the case when a message is being // backfilled for a participant that has left the conference. const participant = getParticipantById(state, id) || {}; const displayName = getParticipantDisplayName(getState, id); const hasRead = participant.local || isChatOpen; const timestampToDate = timestamp ? new Date(timestamp) : new Date(); const millisecondsTimestamp = timestampToDate.getTime(); dispatch(addMessage({ displayName, hasRead, id, messageType: participant.local ? 'local' : 'remote', message, timestamp: millisecondsTimestamp })); if (typeof APP !== 'undefined') { // Logic for web only: APP.API.notifyReceivedChatMessage({ body: message, id, nick: displayName, ts: timestamp }); dispatch(showToolbox(4000)); } } ); }