© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
R. StringerReal-Time Twilio and Flybasehttps://doi.org/10.1007/978-1-4842-7074-5_3

3. Building a Real-Time Group Chat App

Roger Stringer1  
(1)
Penticton, BC, Canada
 

Last November, I went to a conference with several coworkers, and we wanted to keep everyone up to date and organized to keep track of what our plans were.

We set up a group chat system that let one of the members send an SMS and everyone else get the message, and if someone replied, we’d all see the reply.

That was handy, and today, I’m going to show you how to build a similar web app. The app will consist of a simple control panel where you can manage who is part of a group and a backend that will handle incoming and outgoing text messages and route them to the proper group members.

You will also be able to send and receive messages from a page on the site in real time, for when you may not have your phone on you but want to send a message to the group and vice versa.

Ingredients

You’ll want to have these set up before you continue on.

We’ll use Flybase (http://flybase.io/) to handle the data storage and real-time aspects of the app, Twilio (www.twilio.com/) to handle the actual SMS work, and Node.js for the system itself.

We’re going to build this particular app for one single group, but it wouldn’t be hard to extend it for multiple groups.

Then finally, we’ll host this app on Heroku as a free app (https://heroku.com/, a handy hosting platform for getting your projects up and running quickly, especially handy when combined with Flybase and Twilio).

Node.js will be the backend portion of our app; it’s where we will build our listeners for Twilio to talk to whenever we send or receive a text message.

Flybase is a real-time app platform and will be our datastore of choice for our app. It will be used to manage who is a member of a group and to store incoming and outgoing messages and whom they came from. If you haven’t already, sign up (https://app.flybase.io/signup) for a free Flybase account now and then create a new app from inside your dashboard. You’ll use this app for your group chat system.

Twilio is our ever-handy phone API, which lets us build services like a group chat app or even a call center. Don’t have a Twilio account yet? Sign up for free (www.twilio.com/try-twilio).

Getting Started

We first need to set up our Node.js app.

Besides the Twilio and Flybase modules, we’ll be using the Express framework (http://expressjs.com/) to set up our node web server to receive the POST request from Twilio, so we’ll need to install the express package. We’ll also be using the body-parser module, so we are going to install that as well.

Let’s create our package.json file:
javascript
{
  "name": "group-chat",
  "version": "0.0.1",
  "description": "SMS Group Chat powered by Flybase, Twilio and Node.js",
  "main": "app.js",
  "repository": "https://github.com/flybaseio/group-chat",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [
    "twilio",
    "flybase",
    "sms"
  ],
  "author": "Roger Stringer",
  "license": "MIT",
  "dependencies": {
    "body-parser": "~1.16.0",
    "ejs": "~2.5.5",
    "express": "~4.14.0",
    "flybase": "^1.8.2",
    "less-middleware": "~2.2.0",
    "method-override": "~2.3.7",
    "moment": "~2.17.1",
    "node-buzz": "~1.1.0",
    "twilio": "~2.11.1"
  }
}
Save this file, and from the terminal, run the following command:
javascript
npm install

This will create a node_modules folder containing all of the modules we want to use.

Let’s set up our folder structure and create a folder called views. This is where we will keep our frontend.

Now, create a folder called public. This will host our static files. Inside that folder, create a css folder and a js folder. We’ll come back to these later.

The first file we want to create is config.js; this will hold our configuration information:
javascript
module.exports = {
    // Twilio API keys
    twilio: {
        sid: "ACCOUNTSID",
        token: "AUTHTOKEN",
        from_number: "YOUR-NUMBER"
    },
    flybase: {
            api_key: "YOUR-API-KEY",
            app_name: "YOUR-FLYBASE-APP"
    },
    un: 'admin',
    pw: 'password'
};
This file is for our configuration. We can access anything in here at anytime by referencing the file and calling the keys. For example, to get our Flybase API Key, we would call
javascript
var config = require('./config');
console.log( config.flybase.api_key );

Replace ACCOUNTSID, AUTHTOKEN, and YOUR-NUMBER with your Twilio credentials and a phone number in your Twilio account that you’ll be using.

Then, replace YOUR-API-KEY and YOUR-FLYBASE-APP with your Flybase API Key.

At the beginning of our app.js file, we’ll need to require express and initialize it into a variable called app. We’re also going to use the bodyParser middleware (https://github.com/expressjs/body-parser) to make it easy to use the data we’ll be getting in our POST request.

Create a new file called app.js and require the twilio, express, and flybase packages:
javascript
var express = require('express');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var path = require('path');
var config = require('./config');
var app = express();
app.set('views', path.join(process.cwd(), 'views'));
app.set('view engine', 'ejs');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded( extended: true }));
app.use(express.static(__dirname + '/public')); // set the static files location /public/img will be /img for users
var port = process.env.PORT || 8080; // set our port
var twilio = require('twilio');
var client = twilio(config.twilio.sid, config.twilio.token );
var flybase = require('flybase');
var messagesRef = flybase.init(config.flybase.app_name, "messages", config.flybase.api_key);
var groupRef = flybase.init(config.flybase.app_name, "groups", config.flybase.api_key);

Flybase uses collections to organize data inside apps, so one app could have several collections. If you’re familiar with relational databases, this is the equivalent of a table.

We’ll be using two collections for our project: one will contain messages, and the other will contain groups. With that in mind, we’ve created two different references to our Flybase app, one for messages and one for our group. This is the start of our app. Next, we’ll build our web interface to manage group members and also allow for sending and receiving messages.

After that, we’ll build our Twilio interface, and you’ll have a fun app to play with.

Sending and Receiving Texts

We’ll need to add a few things to send and receive texts. Our first step is to add a listener for Twilio.

Twilio uses webhooks (https://en.wikipedia.org/wiki/Webhook) to let your server know when an incoming message or phone call comes into our app. We need to set up an endpoint that we can tell Twilio to use for the messaging webhook.

We’re going to add a route for /message that responds with some TwiML (www.twilio.com/docs/api/twiml). TwiML is a basic set of instructions you can use to tell Twilio what to do when you receive an incoming call or SMS message. Our code will look like this:
javascript
// listen for incoming sms messages
app.post('/message', function (request, response) {
      groupRef.where( {"memberNumber":request.param('From')} ).limit(1).on( "value", function ( data ){
            if( data.count() ){
                  data.forEach( function( snapshot ){
                        var member = snapshot.value();
                        messagesRef.push({
                              sid: request.param('MessageSid'),
                              type:'text',
                              tstamp: new Date().toLocaleString(),
                              fromName:member.memberName,
                              fromNumber:request.param('From'),
                              message:request.param('Body'),
                              media:"",
                              fromCity:request.param('FromCity'),
                              fromState:request.param('FromState'),
                              fromCountry:request.param('FromCountry'),
                              groupNumber:request.param('To')
                        });
                  });
            }
      });
      var numMedia = parseInt( request.param('NumMedia') );
      if (numMedia > 0) {
            for (i = 0; i < numMedia; i++) {
                  var mediaUrl = request.param('MediaUrl' + i);
                  groupRef.where( {"memberNumber":request.param('From')} ).limit(1).on( "value", function ( data ){
                        if( data.count() ){
                              data.forEach( function( snapshot ){
                                    var member = snapshot.value();
                                    messagesRef.push({
                                          sid: request.param('MessageSid'),
                                          type:'text',
                                          tstamp: new Date().toLocaleString(),
                                          fromName:member.memberName,
                                          fromNumber:request.param('From'),
                                          message:"",
                                          media:mediaUrl,
                                          fromCity:request.param('FromCity'),
                                          fromState:request.param('FromState'),
                                          fromCountry:request.param('FromCountry'),
                                          groupNumber:request.param('To')
                                    });
                              });
                        }
                  });
            }
      }
      var resp = new twilio.TwimlResponse();
      resp.message('Message received.');
      response.writeHead(200, {
            'Content-Type':'text/xml'
      });
      response.end(resp.toString());
});

This will listen for any incoming SMS messages and store them inside your Flybase app, specifically inside the messages collection.

As part of storing the message, we perform a lookup to find the groups member with the same phone number the message was sent from. We then use this lookup to verify the member is part of the group and also to get the member’s name.

If no member was found, then no message gets sent.

Once a message has been received, we use the Twilio node library to initialize a new TwimlResponse . We then use the message keyword (www.twilio.com/docs/api/twiml/sms/message) to set what we want to respond to the message with. In this case, we’ll just say “Message received.”

We’ll then set the content-type of our response to text/xml and send the string representation of the TwimlResponse we built.

Listening for Changes

As part of our app.js code, we also want to add some asynchronous listeners to listen for changes to our Flybase app:
javascript
// when a new message is added to the Flybase app, send it via Twilio...
messagesRef.on("added", function (data ){
      var snapshot = data.value();
      sendMessage(
            snapshot.groupNumber,
            snapshot.fromName,
            snapshot.fromNumber,
            snapshot.message,
            snapshot.media || ""
      );
});
groupRef.on("added", function ( data ){
      var snapshot = data.value();
      var msg = snapshot.memberName + ' has joined the group';
      messagesRef.push({
            sid: "",
            type:'',
            tstamp: new Date().toLocaleString(),
            fromName:"Admin",
            fromNumber:"",
            message:msg,
            media:"",
            fromCity:"",
            fromState:"",
            fromCountry:"",
            groupNumber:snapshot.groupNumber
      });
});
groupRef.on("removed", function ( data ){
      var snapshot = data.value();
      var msg = snapshot.memberName + ' has left the group';
      //      send broadcast that a group member has been removed
      messagesRef.push({
            sid: "",
            type:'',
            tstamp: new Date().toLocaleString(),
            fromName:"Admin",
            fromNumber:"",
            message:msg,
            media:"",
            fromCity:"",
            fromState:"",
            fromCountry:"",
            groupNumber:snapshot.groupNumber
      });
});
//      broadcast a message to the group
function sendMessage( group_number, from_name, from_number, message, media ){
      var msg = from_name + ": " + message;
      groupRef.where( {"memberNumber":{"$not":from_number}} ).on( "value", function ( data ){
            if( data.count() ){
                  data.forEach( function( snapshot ){
                        var member = snapshot.value();
                        var msgObj = {
                              to:member.memberNumber,
                              from:group_number,
                              body:msg
                        };
                        if( media !== "" ){
                              msgObj.mediaUrl = media;
                        }
                        client.sendMessage( msgObj, function( err, data ) {});
                  });
            }
      });
}

We’ve set up three asynchronous listeners, one for the messages collection, which listens for any messages being “added” to it and, when it receives a notification of a new message, calls our sendMessage function to send the message to the other members of the group.

The other two asynchronous listeners are for our groups collection: the first one listens for any new members being added to a group and then sends an announcement that the member has joined the group.

The last listener will listen for any members being “removed” from a group and sends an announcement that the member has left the group.

Finally, our sendMessage function is used for sending messages on to the other group members; it will perform a query to return all members of the group, excluding the person who sent the message, and send the message on to each member.

Messages will appear formatted with the member’s name followed by the message:
John: How about pizza after work?
Finally, let’s set our server to listen on port 8080 and tell it what to do when we view it from a browser:
javascript
// frontend routes =========================
// Create basic  middleware used to authenticate all admin requests
var auth = express.basicAuth(config.un, config.pw);
// route to handle all frontend requests with a password to protect unauthorized access....
app.get('*', auth, function(req, res) {
      res.render('index', {
            api_key:config.flybase.api_key,
            app_name:config.flybase.app_name,
            group_number:config.twilio.from_number
      });
});
var server = app.listen(port, function() {
      console.log('Listening on port %d', server.address().port);
});

This is the backend portion of our group chat app. It listens for incoming text messages, stores them in our Flybase app, and then sends them to the other members of the group.

Now, we need to build our control panel, where the admin can manage group members and also send and receive messages.

Managing Your Group

We’re going to build a simple web interface to manage our group members.

The data we store for our group members will consist of the following three pieces of data:
  • - Group phone number (the Twilio number we stored in the twilio_number variable in the "Getting Started" section)

  • - Member name

  • - Member phone number

We’ll also display a basic chat box that will let our admin send messages and see what messages are being sent.

First, let’s create our view. In the /views folder, create a file called index.ejs:
`
HTML
<!doctype html>
<html>
<head>
      <link href='//fonts.googleapis.com/css?family=Lato:400,300italic,400italic&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
      <link rel="stylesheet" type="text/css" href="//angular-ui.github.com/ng-grid/css/ng-grid.css" />
      <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
      <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet">
      <link rel="stylesheet" type="text/css" href="/css/style.css">
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
      <script src="https://cdn.flybase.io/flybase.js?20150217"></script>
      <script src="https://cdn.flybase.io/libs/phone.js"></script>
      <script src="/js/group.js"></script>
      <title>Group Chat, powered by Flybase and Twilio</title>
</head>
<body>
      <div class='container'>
            <div class="row">
                  <div class="col-md-6">
                        <h3>Group Members</h3>
                        <div id="group_wrapper"></div>
                        <hr />
                        <h2>Add new member</h2>
                        <div class="well">
                              <form id="group_form" method="post" accept-charset="utf-8" class="form-inline">
                                    <div class="form-group">
                                          <div class="input-group">
                                                <div class="input-group-addon"><i class="fa fa-pencil"></i></div>
                                                <input type="text" class="form-control" id="name" name="name" placeholder="name">
                                          </div>
                                    </div>
                                    <div class="form-group">
                                          <div class="input-group">
                                                <div class="input-group-addon"><i class="fa fa-mobile"></i></div>
                                                <input type="tel" class="form-control" id="phone" name="phone" placeholder="+11112223333"/>
                                          </div>
                                    </div>
                                    <button type="submit" class="btn btn-primary">Save</button>
                              </form>
                        </div>
                  </div>
                  <div class="col-md-4 col-md-offset-1">
                        <div id="chatBox" class='chat'>
                              <header>Chat Log</header>
                              <ul id='messagesDiv' class='chat-messages'></ul>
                              <footer>
                                    <form id="msg_form" method="post" accept-charset="utf-8" class="form-inline">
                                          <input type="text" id="messageInput" placeholder="Type a message..." />
                                    </form>
                              </footer>
                        </div>
                  </div>
      </div>
      <script>
            $(function(){
//                  initialize our Flybase object
                  var myGroupManager = new groupManager( "<%= api_key %>", "<%= app_name %>", "<?%= group_number %>");
                  myGroupManager.start();
            });
      </script>
</body>
</html>

This will display our control panel, which will be split into two panes, the left side for viewing group members and the right side for viewing the chat log.

At the bottom of the page, we’re initializing our groupManager class . We’ll create that file shortly.

Next, let’s create our style sheet. In the public/css folder, create a file called style.css:
css
body{font-size:12pt;font-family:helvetica}
.chatWindow{float:left;margin:20px;border:1px solid #000;width:300px;background:#e5e5e5;border-radius:5px}
.chatName{margin-bottom:10px;background:#666;color:#fff;padding:4px}
.messages{padding:4px}
.message_outbound{color:blue;text-align:right}
.tstamp{font-size:9px;padding:2px;margin-bottom:10px;border-bottom:1px dotted #666;color:#666}
.error{color:red;text-align:center}
.messageForm textarea{float:left;width:220px;margin:5px}
#phone{width:140px;}
#chatBox{background-color: #f8f8f8;background: rgb(229, 228, 228);margin:10px;}
.hide {display: none; }
.chat {font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;border-radius: 3px;-webkit-box-shadow: 0px 8px 20px rgba(0, 0, 0, 0.2);box-shadow: 0px 8px 20px rgba(0, 0, 0, 0.2);background-color: #dfe3ea;border: 1px solid #CCC;overflow: auto;padding: 0px;font-size: 18px;line-height: 22px;color: #666; }
.chat header {background-color: #EEE;background: -webkit-gradient(linear, left top, left bottom, from(#EEEEEE), to(#DDDDDD));background: -webkit-linear-gradient(top, #EEEEEE, #DDDDDD);background: linear-gradient(top, #EEEEEE, #DDDDDD);-webkit-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.9), 0px 1px 2px rgba(0, 0, 0, 0.1);box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.9), 0px 1px 2px rgba(0, 0, 0, 0.1);border-radius: 3px 3px 0px 0px;border-bottom: 1px solid #CCC;line-height: 24px;font-size: 12px;text-align: center;color: #999; }
.chat input {-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;-webkit-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.2);box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.2);border-radius: 3px;padding: 0px 10px;height: 30px;font-size: 18px;width: 100%;font-weight: normal;outline: none; }
.chat .chat-toolbar {background-color: #FFF;padding: 10px;position: relative;border-bottom: 1px solid #CCC; }
.chat .chat-toolbar label {text-transform: uppercase;line-height: 32px;font-size: 14px;color: #999;position: absolute;top: 10px;left: 20px;z-index: 1; }
.chat .chat-toolbar input {-webkit-box-shadow: none;box-shadow: none;border: 1px solid #FFF;padding-left: 100px;color: #999; }
.chat .chat-toolbar input:active, .chat .chat-toolbar input:focus {color: #1d9dff;border: 1px solid #FFF; }
.chat ul {list-style: none;margin: 0px;padding: 20px;height: 200px;overflow: auto; }
.chat ul li {margin-bottom: 10px;line-height: 24px; }
.chat ul li:last-child {margin: 0px; }
.chat ul .chat-username {margin-right: 10px; }
.chat footer {display: block;padding: 10px; }
.chat footer input {border: 1px solid #ced3db;height: 40px; width:75%;}
Now, let’s move on to the brains of our system. Inside the public/js folder, we’ll create a file called group.js:
javascript
var groupManager = function(api_key, app_name, group_number) {
//      store the group number
        this.group_number = group_number;
//      reference to our messages collection...
        this.messagesRef = new Flybase(api_key, app_name, "messages");
//      reference to our group collection...
        this.groupRef = new Flybase(api_key, app_name, "groups");
        this.group_members = [];
};

This is the first part of our groupManager class . So far, we’ve told it to start up two Flybase references, one called messagesRef and one called groupRef. We also stored our group number as a variable called group_number.

Now, let’s set up our actions:
javascript
groupManager.prototype.start = function(){
      var _this = this;
// list group members if any
      this.groupRef.on("value", function( data ){
            if( data.count() ){
                  data.forEach( function( snapshot ){
                        var member = snapshot.value();
                        _this.group_members[member._id] = member;
                  });
            }
            _this.displayGroup();
      });
// listen for new members being added
      this.groupRef.on("added", function( snapshot ){
            var member = snapshot.value();
            _this.group_members[member._id] = member;
            _this.displayGroup();
      });
// save new group member to our app
      $("#group_form").submit( function(e){
            e.preventDefault();
            var member = {
                  'groupNumber': _this.group_number,
                  'memberName': $("#name").val(),
                  'memberNumber': clean_phone( $("#phone").val() )
            };
            _this.groupRef.push( member );
            $("#name").val('');
            $("#phone").val('');
            return false;
      });
// listen for members being removed
      $('div').on('click','a.delete', function(e){
            var _id = e.target.id;
            _this.groupRef.remove(_id);
            return false;
      });
      this.groupRef.on("removed", function( snapshot ){
            var member = snapshot.value();
            _this.group_members[member._id] = undefined;
            _this.displayGroup();
      });
// list any existing chat message
      this.messagesRef.on('value', function (data) {
            if( data.count() ){
                  data.forEach( function(message){
                        _this.displayChatMessage(message.value() );
                  });
            }
      });
// listen for incoming chat messages
      this.messagesRef.on('added', function (data) {
            var message = data.value();
            _this.displayChatMessage( message );
      });
// listen for outgoing chat messages
      $('#msg_form').submit( function(e){
            e.preventDefault();
            var message = {
                        "tstamp": new Date().toLocaleString(),
                        "fromName": "Admin",
                        "fromNumber": "",
                        "message": $('#messageInput').val(),
                        "fromCity": "",
                        "fromState": "",
                        "fromCountry": "",
                        "groupNumber": _this.group_number
            }
            _this.messagesRef.push( message );
            $('#messageInput').val('');
            return false;
      });
};

Our function sets up our asynchronous listeners, as well as listeners for form submissions and members being deleted by pressing the delete button.

If a group member is added, then the member will be added to the groups collection, and a notification will be sent to the other members of the group. The listing of group members will also show the new member.

If a person is removed, their name will vanish from the list, and a message will be sent to the remaining group members.

The other side of our groupManager class is the actual chatting side of our program. When the admin types in a message, it will get sent to the other group members. At the same time, when another group member sends a message, the admin will see the message in the chat box.

We have two functions left: one to display all members of a group and the other to display chat messages.

For our groups, we store information in a class-wide variable called group_members. This lets us quickly add, update, or remove members as we receive notifications about it:
javascript
// Display group members
groupManager.prototype.displayGroup = function(){
      $('#group_wrapper').html('');
      for (var i in this.group_members ) {
            var member = this.group_members[i];
            if( member !== undefined ){
                  var html = '';
                  html = '<span>'+member.memberName+' ( ' + member.memberNumber + ' )</span> <a href="#delete" class="delete" id="' + member._id+'">[remove]</a>';
                  $('<div/>').prepend( html ).appendTo($('#group_wrapper'));
            }
      }
};
Our last function displays each chat message as it is received:
javascript
// Display chat messages
groupManager.prototype.displayChatMessage = function( message ){
      var _this = this;
      var msg = message.message;
      if( message.media !== "" ){
            msg += '<br /><img src="' + message.media + '" />';
      }
      $('<li/>')
            .attr("id",message._id)
            .html(msg)
            .prepend(
                  $("<strong class='example-chat-username' />").text(message.fromName+': ')
                  ).appendTo( $('#messagesDiv') );
      $('#messagesDiv')[0].scrollTop = $('#messagesDiv')[0].scrollHeight;
};
One last thing to do is to start our app:
javascript
node app.js

We’ve told our app to run on port 8080, so if you go to your web browser and type in http://localhost:8080/, you should see your group chat.

Hosting It on Heroku

Heroku is great for making server configurations easy and painless. We can build faster and worry about the things that matter to us instead of trying to configure our own servers. This works perfectly with our philosophy here at Flybase and lets us build things quickly. Let’s look at how we can deploy our group chat app to Heroku in mere seconds.

Go ahead and go to http://heroku.com and create your free account. The dashboard is incredibly simple and user-friendly.

Next, you’ll want to install the Heroku Toolbelt. The Heroku Toolbelt will give us access to the Heroku Command Line Utility. The Heroku Toolbelt program comes in different operating systems, which you can download from the links provided in the following:

After we install the Toolbelt, we’ll have access to the heroku command.

Now, you’ll want to perform the following operations:
  1. 1.

    git init inside the folder you created your group chat app in to create a new git repository

     
  2. 2.

    heroku login to log into Heroku

     
  3. 3.

    heroku create to create the application within Heroku

     
  4. 4.

    git push heroku master to push your group chat repository to Heroku

     
  5. 5.

    heroku ps:scale web=1 to tell Heroku to create a dyno (a worker, to respond to web requests)

     
  6. 6.

    heroku open to open your web browser at your new custom URL

     

And that’s it. Your app is now running on Heroku.

Assigning Your Group Chat to a Phone Number in Twilio

Now, we want to go back to our Twilio account and open the phone number we were using to send messages.

When you create your app on Heroku, you can give it a unique URL. For example, let’s say

https://my-group-chat.herokuapp.com/

Our URL to receive messages via SMS will now be https://my-group-chat.herokuapp.com/message.

Now send an SMS message to your Twilio number, and you should get a response back. If you don’t, take a look at the Twilio App Monitor (www.twilio.com/user/account/developer-tools/app-monitor) to help determine what went wrong.

Summary

We’ve built a real-time group chat app using Flybase (http://flybase.io) and Twilio (http://twilio.com).

This group chat app can even handle incoming media (pictures, Word docs, videos, etc.) from Twilio and resend it on to the rest of the group.

You can find our group chat app here at GitHub (https://github.com/flybaseio/group-chat).

This app can be used for a group of people to carry on a conversation. This can be handy when attending events.

You could use this to notify attendees of upcoming talks. For example, a conference could add their attendees to a group and then send a broadcast when it is time for a talk to begin, when it is lunchtime, or for an emergency.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.65.65