VirtualRoom / WebRTC

VirtualRoom is the ReactJS client of the Festi Chat plugin. It shows one chat room in the browser: text chat, member list, group video call and webinar.

The client uses these technologies:

  • React 17 and Create React App (react-scripts 5).
  • socket.io-client for the connection to the signalling server.
  • peerjs for the WebRTC connections between the browsers.
  • zustand for the state.
  • i18next for the translations: en, ru, uk and kk.

Requirements

  • Node.js and npm. The repository does not state a Node.js version.
  • PHP and the Festi CLI in the vendor directory of the Festi project. The build uses the festi-react command.
  • A Festi project with the Chat plugin. The plugin gives the room data to the client.
  • A signalling server that uses the Socket.IO protocol. The server is not in this repository.
  • A PeerJS server.

Location in a Festi project

This repository is the git submodule static/virtualroom of the Chat plugin. The build scripts use paths relative to this location:

<project>/
├── vendor/festi-team/festi-framework-cli/bin/festi-react
└── plugins/Chat/static/
    ├── virtualroom/          # this repository (source)
    └── festi-virtualroom/    # the build that the Chat plugin loads

The .htaccess file denies web access to the source directory.

Install

The commands on this page were not run for this page.

  1. Get the source. Run the command in the directory of the Chat plugin.

    git submodule update --init static/virtualroom
  2. Go to static/virtualroom.

  3. Copy .env.example to .env. The file sets PUBLIC_URL to /plugins/Chat/static/festi-virtualroom/.
  4. Change PUBLIC_URL if your project has the Chat plugin at a different URL.
  5. Install the dependencies.

    npm install

Development

Build and deploy the client after each change of a source file:

npm run watch

npm-watch looks at the js, jsx and css files in src. It runs npm run build after each change. Open the page of a chat room in the Festi project to see the result.

The repository also has the scripts of Create React App for a development server:

npm start
HTTPS=true npm start

npm start and npm run dev set REACT_APP_ENV to local. The client reads its data from window.roomInfo. public/index.html does not set this object.

Production build

npm run build

The command does these steps:

  1. react-scripts build writes the build to the build directory.
  2. The postbuild script runs festi-react --mode deploy --name festi-virtualroom --path ./.
  3. festi-react copies the files of build/static to ../festi-virtualroom under names without the content hash.
  4. The script removes the contents of ../festi-virtualroom/static.
  5. The script copies build/static/media to ../festi-virtualroom/static/.

Commit the changed files of static/festi-virtualroom in the repository of the Chat plugin.

Use by the Chat plugin

The template templates/room_chat.phtml of the Chat plugin starts the client:

  1. It writes the room data into window.roomInfo.
  2. It adds the <div id="root"> element.
  3. It loads main.css, main.js and runtime-main.js from static/festi-virtualroom.

The js_version setting of the Festi project is the v parameter of these URLs. Increase js_version after a new build to prevent the use of old files from the browser cache.

window.roomInfo

ChatPlugin::fetchRoom() sets the values. See the README of the Chat plugin for the PHP options.

Key Description
chatApiUrl The base URL of the chat API.
roomID The ID of the room.
hostUserID The ID of the author of the room.
userID The ID of the current user. The client makes a UUID if the value is 0.
userName The name of the current user.
isInitiator true if the current user is the author of the room.
type The room type: text, group or webinar.
privacy The privacy of the room.
hasVideoChat The room has a video chat.
isAllowVideoChatInText A text room shows the Call button.
messagesLimit The number of messages in one page.
messageMaxLength The maximum length of a message.
isStaticMembersList The client gets the member list from the API.
iceServers The ICE servers for WebRTC.
signalingServerUrl The URL of the signalling server.
peerServerConfig The options of the PeerJS server.
lang The language: en, ru, uk or kk. The default is en.
timeFormat The time format of the messages. The default is HH:mm.
isMobile The request came from a mobile device.
name The name of the room.
shareUrl, sharedID The URL and the ID to share the room. The client uses sharedID as the room ID in API requests.
lastReadMessageID The ID of the last message that the user read.
preferredCodecs The preferred video codecs. The source keeps the value and does not use it.
audioIndicatorStatus Show the audio level indicator.
autoReconnect The automatic reconnect flag. The source keeps the value and does not use it.
debugEnabled Write debug messages to the browser console.
logEnabled The client does not use this value. See Log server.
logEnv The environment name in the log messages if the build does not set REACT_APP_LOGGER_ENV.
isAnonymous The user is not logged in. The client asks for a name.
isDynamic The client sends the name and the avatar of the user to the other users.

The room type selects the component: text uses TextWrapper, group uses GroupWrapper, webinar uses WebinarWrapper.

Chat API

src/api/index.js sends JSON requests to these paths. Each path is relative to chatApiUrl.

Path Method Function
chats/<room ID>/members/ GET Get the members of the room.
chats/<room ID>/messages/ GET Get messages. Parameters: message_id, newer_message_id or older_message_id.
chats/<room ID>/messages/create/ POST Send a message.
chats/<room ID>/messages/<message ID>/remove/ DELETE Delete a message.
chats/<room ID>/messages/<message ID>/read/ POST Mark a message as read.
chats/<room ID>/user/<user ID>/ GET Get the data of a user.
chats/<room ID>/action/ GET Write a user action to the log: start-call, end-call, start-stream, end-stream.
chats/<room ID>/join/ POST Join the room.
chats/<room ID>/peer/ POST Save peer data.
chats/<room ID>/peers/ POST Get peer data.

Log server

src/libs/Logger.js sends a log message only if the build has a logger token. src/libs/VirtualRoomEngine.js calls it only if logEnabled is true in the settings store. src/stores/useVirtualRoomSettings.js does not copy logEnabled from window.roomInfo. Thus the client sends no log message. Set these variables in .env before the build:

Variable Description
REACT_APP_LOGGER_HOST The URL of the log server.
REACT_APP_LOGGER_TOKEN The token for the log server. Do not commit it.
REACT_APP_LOGGER_ENV The environment name in the log. The default is logEnv or default.

Code style

npx prettier --write src/

Signalling workflow

The client uses two servers to connect the browsers:

  • The signalling server (Socket.IO) knows the users of a room. It sends room events to them.
  • The PeerJS server gives a peer ID to each client and helps to open the WebRTC connection.

Group call

The diagram shows the events in src/libs/SignalServerWebSocket.js and src/libs/VirtualRoomEngine.js. The signalling server is not in this repository. Thus the receiver of call_users is an assumption.

sequenceDiagram participant P1 as Peer 1 participant S as Signalling Server participant PS as PeerJS Server participant P2 as Peer 2 P1->>S: Connect P1->>S: join (roomID, userID) S-->>P1: room_users (userIDs) P1->>P1: Request stream (getUserMedia) P1->>PS: Open peer PS-->>P1: peerID P1->>S: call (roomID, userID, peerID) P2->>S: Connect P2->>S: join (roomID, userID) S-->>P1: join (userID of Peer 2) S-->>P2: room_users (userIDs) P2->>P2: Request stream (getUserMedia) P2->>PS: Open peer PS-->>P2: peerID P2->>S: call (roomID, userID, peerID) S-->>P2: call_users (userID and peerID of Peer 1) P2->>P1: Data connection (initiator) P2->>P1: Media call with stream P1-->>P2: update_media_status P1-->>P2: Answer with stream P2->>S: Disconnect S-->>P1: leave (userID of Peer 2)

Text messages and other events

The client sends an event to all users of the room with the user_event message of the signalling server.

Event type Function
message A user sent a text message.
delete_message A user deleted a text message.
update_media_status A user changed the microphone, camera or screen status.
user_details An anonymous or dynamic user sent a name and an avatar.
call_me The speaker of a webinar started the stream. Each listener then calls the speaker.

First design

The diagram below is the first design of the workflow. In this design, the signalling server transmits the WebRTC Signal messages between the peers. The source now uses the PeerJS server for this step.

workflow

sequenceDiagram participant P1 as Peer 1 participant S as Signaling Server participant P2 as Peer 2 P1->>S: Connect P1->>P1: Request stream ( getUserMedia ) P1->>S: Join to roomID P2->>S: Connect P2->>P2: Request stream ( getUserMedia ) P2->>S: Join to roomID S->>P1: Joined userID ( Peer 2 ) P1->>P1: Add peer [non initiator] ( Peer 2 ) S->>P2: Join userID ( Peer 1 ) P2->>P2: Add peer [initiator] ( Peer 1 ) P1->>S: Signal S->>P2: Signal ( Peer 1 ) P2->>S: Signal S->>P1: Signal ( Peer 2 )