Instant Messaging with Jitsi Meet, Gifted Chat, React Native, and Redux

D. G.
8 min readOct 23, 2018

--

( *THIS FUNCTIONALITY IS NOW PART OF MASTER at https://github.com/jitsi/jitsi-meet )

In this article I will recap the steps i took to add text message chat to the Jitsi Meet mobile application.

Jitsi Meet is an open source video conferencing application for the web and the phone built with reactjs and react native respectively. The app facilitates video and audio communication between 2 or more people in a very efficient way. To read more about Jitsi Meet visit the Jitsi Meet website: https://jitsi.org/jitsi-meet/.

I had been working on a react native application that requires certain messaging features. I looked at Jitsi Meet as part of my research for the application. As I was figuring out the Jitsi Meet codebase I realized that unlike the web version the mobile app does not support chat. I decided to see what it would take to enable react-native-gifted-chat, a popular chat solution for react native, as the chat solution for the mobile version of Jitsi Meet.

I wanted to accomplish the following: I wanted have a button at the bottom of the app for opening the chat and displaying unread message count when the chat is closed. I wanted to display the chat in a transparent modal component as a top layer. To close the chat you would need to touch the screen to the side of the chat. The following screen prints show the UI design:

*To open the chat tap the button at the bottom left corner of the app ( screen shot 1 ). Unread message count is displayed in red ( screen shot 2 ). The chat appears in a modal component ( screen shot 3 ). Tap the screen to the left or right of the chat to close the chat.

The Jitsi Meet application relies heavily on redux store and redux thunk for managing application data and ui events. Once I got the hang of the redux “madness” and directory structure of the application adding the chat is pretty straight forward.

Below you’ll find instructions to setup Jitsi Meet and react-native-gifted-chat for messaging. The full codebase is here: https://github.com/jitsi/jitsi-meet/pull/3577/files

*Note that i have only done these steps for iOS and macOS.

Install the jitsi-meet app

Download the code from GitHub at https://github.com/jitsi/jitsi-meet.

Create a new directory jitsi-meet-gifted-chat and copy downloaded content over to it. cd to jitsi-meet-gifted-chat and follow the rest of the instructions:

https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md:

Adding the chat

1. Install the following dependencies:

npm install react-native-icon-badge react-native-modal --save

npm install github:FaridSafi/react-native-gifted-chat#96f8cf8a7a55a4b5f68105679351af38dd8523bc --save

react-native-gifted chat — popular chat implementation for react native. https://github.com/FaridSafi/react-native-gifted-chat

react-native-icon-badge — handy module to display unread message count when chat is closed. https://github.com/yanqiw/react-native-icon-badge

react-native-modal — extends react native’s modal and provides extra features. The chat opens as modal over video content with a transparent background. https://github.com/react-native-community/react-native-modal

2. Create these files:

  • The Chat Component

Create file Chat.native.js in react/features/chat/components ( if the file already exists paste the content below )

// @flowimport React, { Component } from 'react';// eslint-disable-next-line react-native/split-platform-componentsimport { connect } from 'react-redux';import { translate } from '../../base/i18n';import { GiftedChat } from 'react-native-gifted-chat';import { sendMessage } from '../actions';import { getLocalParticipant } from '../../base/participants';import type { Dispatch } from 'redux';/*** The type of the React {@code Component} props of {@link Chat}.*/type Props = {/*** The local participant's ID.*/_localParticipant: Object,/*** All the chat messages in the conference.*/_messages: Array<Object>,/*** Send a text message.** @protected*/_onSendMessage: Function};/*** The chat page of the mobile (i.e. React Native) application.*/class Chat extends Component<Props> {/*** Initializes a new Conference instance.** @param {Object} props - The read-only properties with which the new* instance is to be initialized.*/constructor(props: Props) {super(props);this._onSend = this._onSend.bind(this);this._renderMessage = this._renderMessage.bind(this);}_onSend: (Array<Object>) => void;/*** Called by {@code render} to create the chat div.** @param {string} message - The chat message to display.* @param {string} id - The chat message ID to use as a unique key.* @returns {Array<ReactElement>}*/_onSend([ message ]) {this.props._onSendMessage(message.text);}_renderMessage: (Object) => void;/*** Called by {@code _onSubmitMessage} to create the chat message.** @param {string} message - The chat message to display.* @param {string} id - The chat message ID to use as a unique key.* @returns {Array<ReactElement>}*/_renderMessage(message: Object, id: string) {return ({_id: id,text: message.message,createdAt: new Date(message.timestamp),user: {_id: message.id,name: message.displayName}});}/*** Implements React's {@link Component#render()}.*x* @inheritdoc* @returns {ReactElement}*/render() {const messages = this.props._messages.map(this._renderMessage);return (<GiftedChatmessages = { messages.reverse() }onSend = { this._onSend }user = {{ _id: this.props._localParticipant.id }} />);}}/*** Maps redux actions to {@link Chat}'s React* {@code Component} props.** @param {Function} dispatch - The redux action {@code dispatch} function.* @returns {{*     _onAddPeople,*     _onShareRoom* }}* @private*/function _mapDispatchToProps(dispatch: Dispatch<*>) {return {/*** Dispatch a text message.** @private* @param {string} text - the text message to be sent* @returns {void}* @type {Function}*/_onSendMessage(text: string) {dispatch(sendMessage(text));}};}/*** Maps (parts of) the redux state to the associated {@code Conference}'s props.** @param {Object} state - The redux state.* @private* @returns {{*     _messages: Array* }}*/function _mapStateToProps(state) {const { messages } = state['features/chat'];return {_messages: messages,_localParticipant: getLocalParticipant(state)};}export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));
  • Chat Button Component for opening and closing the chat:

Create file ChatButton.native.js in react/features/chat/components

// @flowimport { connect } from 'react-redux';import type { Dispatch } from 'redux';import { AbstractButton } from '../../base/toolbox';import type { AbstractButtonProps } from '../../base/toolbox';import { toggleChat } from '../actions';type Props = AbstractButtonProps & {/*** Display Chat.** @protected*/_onDisplayChat: Function};/*** Implements an {@link AbstractButton} to enter add/invite people to the* current call/conference/meeting.*/class ChatButton extends AbstractButton<Props, *> {accessibilityLabel = 'toolbar.accessibilityLabel.chat';iconName = 'chat';label = '';/*** Handles clicking / pressing the button, and opens the appropriate dialog.** @private* @returns {void}*/_handleClick() {this.props._onDisplayChat();}/*** Implements React's {@link Component#render()}.** @inheritdoc* @returns {React$Node}*/render() {return (super.render());}}/*** Maps redux actions to {@link InviteButton}'s React* {@code Component} props.** @param {Function} dispatch - The redux action {@code dispatch} function.* @returns {{*     _onAddPeople,*     _onShareRoom* }}* @private*/function _mapDispatchToProps(dispatch: Dispatch<*>) {return {/*** Launches native invite dialog.** @private* @returns {void}* @type {Function}*/_onDisplayChat() {dispatch(toggleChat());}};}export default connect(null, _mapDispatchToProps)(ChatButton);
  • Create empty file sounds.native.js in react/features/chat

3. Make the following changes to existing files

  • Add an entry for ChatButton in react/features/chat/components/index.js

export ChatButton from ‘./ChatButton’;

  • Make the following changes to react/features/chat/middleware.js

Chat is only available in the web version of Jitsi Meet so we need to make minor adjustment to correctly register the chat middleware to work with mobile chat

  • Make the following changes to react/features/chat/actions.js

Add a function for the CLEAR_CHAT action. The action is triggered when connecting to a different conference to clear existing messages ( from a previous conference ) in redux

Add CLEAR_CHAT to imports, then add:

export function clearChat() {
return {
type: CLEAR_CHAT};}
  • Make the following changes to react/features/chat/actionTypes.js

Add a new CLEAR_CHAT action type. The action is triggered when connecting to a different conference to clear existing messages ( from a previous conference ) in redux

export const CLEAR_CHAT = Symbol(‘CLEAR_CHAT’);

  • Make the following changes to react/features/chat/reducer.js

Add the CLEAR_CHAT action to imports from actionTypes

import { ADD_MESSAGE, TOGGLE_CHAT, CLEAR_CHAT } from ‘./actionTypes’;

Handle the CLEAR_CHAT action to set messages to an empty array

case CLEAR_CHAT:return {…state,messages: []};}
  • Make the following changes to react/features/conference/components/conference.native.js

import:

Add Text to imports from react-native

import { ChatButton, toggleChat, Chat, clearChat } from ‘../../chat’;

import { getUnreadCount } from ‘../../chat/functions’;

import {
toolbarButtonStyles,
toolbarToggledButtonStyles} from ‘../../toolbox/components/native/styles’;

// Displays unread message count when the chat is closed

import IconBadge from ‘react-native-icon-badge’;

// The chat is displayed in the modal

import Modal from ‘react-native-modal’;

Define new properties in Props = {}

// True when chat is open

_isChatOpen: boolean

// Dispatches a toggle chat action when the screen is touched to the side

// of the chat, which results in closing the chat

_onToggleChat: Function

// Unread message count is displayed when the chat is closed

_unreadMessageCount: number

In the render function declare variable unreadMessageCount

const unreadMessageCount = this.props._unreadMessageCount;

Define the Modal Component after <StatusBar>

<ModalanimationType = { ‘slide’ }onBackdropPress = { this.props._onToggleChat }transparent = { true }visible = { this.props._isChatOpen }><Chat /></Modal>

Define the IconBadge component to display the chat button and unread message count after the filmstrip

<View style = { styles.chatIconBadgeContainer }>
<IconBadge
BadgeElement = {<Text style = { styles.badgeText } >{ unreadMessageCount }</Text>}Hidden = { unreadMessageCount === 0 }IconBadgeStyle = {styles.chatIconBadge}MainElement = {<ChatButtoniconName = { this.props._isChatOpen? ‘icon-chat toggled’: ‘icon-chat’ }showLabel = { false }styles = { toolbarButtonStyles }toggledStyles ={ toolbarToggledButtonStyles } />} /></View>

in _mapDispatchToProps add the following line in _onConnect(). When connecting to a conference a clear_chat action will be dispatched to clear messages in the chat

dispatch(clearChat());

in _mapDispatchToProps declare

_onToggleChat() {
dispatch(toggleChat());
}

In _mapStateToProps declare the following variables

const { isOpen } = state[‘features/chat’];

const unreadMessageCount = getUnreadCount(state);

and assigned them in the return statement:

_isChatOpen: isOpen,

_unreadMessageCount: unreadMessageCount

  • Make the following changes to react/features/conference/components/styles.js
chatIconBadgeContainer: {
flexDirection: ‘row’,
alignItems: ‘center’,justifyContent: ‘flex-start’},chatIconBadge: {width: 20,height: 20,backgroundColor: ‘#FF0000’},badgeText: {color: ‘#000000’}

That’s it … have fun coding!!

--

--