DEV Community

Cover image for Build Video/Chat App with AWS Websocket, WebRTC, and Vue Part 1
Kevin Odongo
Kevin Odongo

Posted on • Updated on

Build Video/Chat App with AWS Websocket, WebRTC, and Vue Part 1

In today's tutorial, we will handle how to build a video and chat app with AWS Websocket, AWS Kinesis, Lambda, Google WebRTC, and DyanamoDB as our database. There are so many products you can use to build a chat application. For video calls, you need to add the signaling capability to exchange WebRTC handshakes. That is where AWS WebSocket will come in handy. No infrastructure to manage but a robust API.

Other products that you can use

  • AWS Amplify subscriptions
  • Socket.io
  • Pusher
  • Firebase
  • And many more

AWS WebSocket APIs don't require repeated connection logic, and the client and server both have much less overhead once connected than they do with HTTP connections. HTTP polling is a technique that lets an application retrieve information based on a client pulling data

I am a fan of serverless technology and AWS Websocket is one product that fills this gap when you want repeated connection logic. In essence with serverless, you don't need to worry about your infrastructure management just your application.

Alt Text

In the next tutorial, we will add video capability in our application using AWS Kinesis Video stream.

Pricing

Pay only for messages sent and received and the total number of connection minutes. You may send and receive messages up to 128 kilobytes (KB) in size. Messages are metered in 32 KB increments. So, a 33 KB message is metered as two messages.

For WebSocket APIs, the API Gateway free tier includes one million messages (sent or received) and 750,000 connection minutes for up to 12 months.

Thereafter 1billion requests 1 USD and the next 5billion request 0.80 USD.

Brief explanation

Before you begin, here are a couple of the concepts of a WebSocket API in API Gateway. The first is a new resource type called a route. A route describes how API Gateway should handle a particular type of client request, and includes a routeKey parameter, a value that you provide to identify the route.

A WebSocket API is composed of one or more routes. To determine which route a particular inbound request should use, you provide a route selection expression. The expression is evaluated against an inbound request to produce a value that corresponds to one of your route’s routeKey values.

There are three special routeKey values that API Gateway allows you to use for a route:

  • $default—Used when the route selection expression produces a value that does not match any of the other route keys in your API routes. This can be used, for example, to implement a generic error handling mechanism.
  • $connect—The associated route is used when a client first connects to your WebSocket API.
  • $disconnect—The associated route is used when a client disconnects from your API. This call is made on a best-effort basis.

You are not required to provide routes for any of these special routeKey values.

You need an AWS account for this tutorial here is a link to register https://portal.aws.amazon.com/billing/signup#/start.

Vue Application.

AWS Websocket tutorials I have gone through last year when I was preparing for my AWS Solution Artichect exams had one problem they only showed you how to configure an AWS Websocket but as a Developer, you need to know how to integrate it into your application. This what this tutorial will help through with. The application will have these sections;

Chat Section

Users will be able to create a channel or group like Telegram and chat with one another.

Alt Text

Meeting Section

This section will allow us to create a meeting or join a meeting. The video stream will be handled with AWS Kinesis Video Stream. You can use any other API to handle this.

Alt Text

Video Section

This section will handle the video section for both the meeting and chats that switches to video calls.

Here is a GitHub link https://github.com/kevinodongo/video-chat-app.git to the above application that you can use for this tutorial.

Configure AWS Backend

We have a Vue App now let us configure our Backend in AWS and integrate our application to make it functional.

We will configure 3 Lambda functions:

  • connect_app
  • disconnect_app
  • onMessage

Let us create a table in Dynamo DB table called chat_app_table then create the functions as follows.

connect_app

This Lambda function will update our database once the $connect route request from users to the WebSocket API. Once connected the connection id will be saved persistently in the Dynamo DB

const AWS = require('aws-sdk');

let send = undefined;
function onConnect(event) {
    var dynamodb = new AWS.DynamoDB();
    send = async (data) => {
        await dynamodb.putItem(data).promise();
    }
}

// event
exports.handler = async (event) => {
   onConnect(event);
   var params = {
        TableName: "chat_app_table", // Table Name
        Item: {
            connectionId: { S: event.requestContext.connectionId } // connection ID

        }
    };
   await send(params);
    var msg = 'connected';
    return { 
        statusCode: 200, 
        body: JSON.stringify({ msg: msg}) /*required on lambda proxy integration*/
    };
};
Enter fullscreen mode Exit fullscreen mode

disconnect_app

This Lambda function will update our database once the $disconnect route request from users to the WebSocket API. Once disconnected the connection id will be deleted from the Dynamo DB

const AWS = require('aws-sdk');

let send = undefined;
function onConnect(event) {
    var dynamodb = new AWS.DynamoDB();
    send = async (data) => {
        await dynamodb.deleteItem(data).promise();
    }
}

// event
exports.handler = async (event) => {
   onConnect(event);
   var params = {
        TableName: "chat_app_table", // Table Name
        Key: {
            connectionId: { S: event.requestContext.connectionId } // connection ID

        }
    };
   await send(params);
    var msg = 'disconected';
    return { 
        statusCode: 200, 
        body: JSON.stringify({ msg: msg}) /*required on lambda proxy integration*/
    };
};
Enter fullscreen mode Exit fullscreen mode

onMessage

This Lambda function will handle the exchange of messages between users.

const AWS = require('aws-sdk');
var dynamodb = new AWS.DynamoDB();

let send = undefined;
function init(event) {
  const apigwManagementApi = new AWS.ApiGatewayManagementApi({
    apiVersion: '2018-11-29',
    endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
  });
  send = async (connectionId, data) => {
    await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: `${data}` }).promise();
  }
}

function onScan(err, data) {
    if (err) {
        console.error("Unable to scan the table. Error JSON:", JSON.stringify(err, null, 2));
    } else {
        // print all 
        console.log("Scan succeeded.", data);
        data.Items.forEach(function(data) {
            console.log('DATA', data)
        });

        // continue scanning if we have more movies, because
        // scan can retrieve a maximum of 1MB of data
        if (typeof data.LastEvaluatedKey != "undefined") {
            console.log("Scanning for more...");
            //params.ExclusiveStartKey = data.LastEvaluatedKey;
          //  dynamodb.scan(params, onScan);
        }
    }
}

exports.handler = async(event) => {
  // send the event
  init(event);

  // scan dynamodb table
  var scanParams = {
      TableName: "chat_app_table", // Table Name
      ExpressionAttributeNames: {
        "#ID": "connectionId"
      },
      ExpressionAttributeValues: {
         ":connectionId": {
           S: event.requestContext.connectionId
         },
      },
      FilterExpression: "connectionId = :connectionId", 
       ProjectionExpression: "#ID",
    };
  await dynamodb.scan(scanParams, onScan)
  const connectionId = event.requestContext.connectionId;
  let data = JSON.parse(event.body).data
  await send(connectionId, data);
  // the return value is ignored when this function is invoked from WebSocket gateway
  return {};
};
Enter fullscreen mode Exit fullscreen mode

Update the Lamda roles by allowing permission to interact with Dynamo DB and invoke API Gateway.

// Add this to your Lambda roles
{
            "Effect": "Allow",
            "Action": "execute-api:*",
            "Resource": "arn:aws:execute-api:us-east-2:[ID]:*/*/*/*"
        },
        {
            "Sid": "DynamoDBTableAccess",
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:DescribeTable",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query",
                "dynamodb:UpdateItem"
            ],
            "Resource": "arn:aws:dynamodb:us-east-2:[ID]:table/[TABLE_NAME]"
        },
Enter fullscreen mode Exit fullscreen mode

With three functions in place lets us create an AWS Websocket through the console and test our backend. For integration, we will be creating the API through the application. Ensure you have created the dynamo DB table that connection ids will always be saved during the connection. For chatting without Video will have to save the messages as well for future reference. Our Lambda function will be updated further as we go along with the tutorial.

For testing, you will need to install the following package wscat

yarn add wscat
Enter fullscreen mode Exit fullscreen mode

Go to API Gateway dashboard then Search for API Gateway and select Websocket

  1. Choose a name
  2. For Route Selection Expression, enter $request.body.action.
  3. Choose Lambda as you integrate and select each Lambda you created for each routing
  4. Then Create API

Alt Text

NOTE

You need to add a role for Lambda to able to invoke Lambda

// Provides full access to invoke APIs in Amazon API Gateway.
arn:aws:iam::aws:policy/AmazonAPIGatewayInvokeFullAccess
Enter fullscreen mode Exit fullscreen mode

Once you have saved go to your stage and get the URL with wss://

wscat -c wss://{YOUR-API-ID}.execute-api.{YOUR-REGION}.amazonaws.com/{STAGE}
Enter fullscreen mode Exit fullscreen mode
$ wscat -c wss://{YOUR-API-ID}.execute-api.{YOUR-REGION}.amazonaws.com/development
connected (press CTRL+C to quit)
> {"action":"sendmessage", "data":"hello world"}
< hello world
Enter fullscreen mode Exit fullscreen mode

That indicates we have connected successfully and we are able to send a message through the API. Anyone who is connected to the Socket will receive this message.

Here is a quick video to guide you in completing the above steps.

I hope these tutorials are helpful and you will be able to configure your Lambda functions, API Gateway, and Dynamo DB Table.

Thank you and see you in the next tutorial of integrating our application and make it function.

Discussion (10)

Collapse
acijoe profile image
Joe Hummel

Hi Kevin,

Thanks for the tutorial. Still working my way through. It is more helpful in learning to wire this stuff up manually like this, before using frameworks like Amplify or tools like CloudFormation.

I had a question and a possible correction to note.

Why did you change the table name in this process? I did not see a driver for that change, and was curious.

There may be an error in the wscat test part. I believe the "action" should be "onMessage" (instead of "sendmessage"). The test does not work for me otherwise, following along with this article.

Collapse
kevin_odongo35 profile image
Kevin Odongo Author

Hi Joe

I've got your point and let me clarify something. With AWS Websockets routes you can add another route for sending messages call it sendmessage and use the Lambda function onMessage (you can rename the lambda function sendmessage)

Here is the logic when a user creates a meeting we will persist a record in dynamoDB (connection.id, group_name, group_id). Create a dynamoDB Table and reference in the Lambda function for connection.

In my case i did not see the need to have a third route send message because once the user connects then his records will be fetched in the dynamoDB Table then a scan will be done in the DynamoDB to get all users with the same group_id. Once all users are retrieved then a message will be broadcasted to everyone. This is the logic you can separate from connect route and sendmessage route on your end if you are trying to achieve it that way.

Secondly, Amplfiy can also achieve real-time chat capability and can scale well. If your application is just for chat then i will suggest you use Amplify subscriptions. Its easy and you can define your schema quickly and be up and running.

With AWS Websocket you will have to do it manually. From protecting your routes using IAM, Lambda authorizer.

Amplify uses CloudFormation to build your environment. I will advice keep learning AWS Websocket they are quite powerful and also try AWS Amplfiy subscription

Collapse
acijoe profile image
Joe Hummel

Thanks so much for the response. I agree Amplify would make this easier. I have an app deployed on Amplify now. It depends on a REST API to generate live event (such as a conference) micro-sites on the platform deployed. The API is supplied by another app deployed using the Serverless framework/CloudFront. Both apps use the Vue framework for the frontend.

We have gotten to the point where we need to add features which require WebSockets. We first considered adding WebSockets to the existing API. That was considered a more significant effort than to retrieve the data into a data store on the Amplify-deployed app, and use AppSync.

I'm in the process of learning more about how WebSockets work on AWS, and how we can add them to the existing project. We do not currently host any data outside of the Cognito User Pool data (for user profile details, as well as authentication) on the Amplify app.

One of the features we are exploring to add is a video/chat feature for use by exhibitors and attendees. So, I thought reviewing this tutorial would help me to better understand a lot through one tutorial.

Thanks again for all the detail! I've read through the second part, and you're right - there's a lot more to it, without the convenience of the Amplify framework.

Thread Thread
kevin_odongo35 profile image
Kevin Odongo Author

Hey Joe

Let me do a video detailing this approach and I share to you to assist you through.

One thing with AWS Kinesis Video Stream there are a lot of hard limit. It is quite ideal for project like webcam ideally with one user as Master.

There is another AWS product AWS Chime that also offer Video and Chat. Ensure you go through the pricing because with AWS Chime there is no free tier.

With Video specifically if you are building from scratch and will be using WebRTC. AWS Websocket will handle the exchange of ice candidates. Another thing to keep in mind is how many will join the Video if it's more than 2 then consider deploying a Turn Server or getting a managed one this is because you will have to build a mesh architect and during video a lot is exchanged so you need an infrastructure that can scale.

The steps

@connect route - handle connection get connection id and persist in your db

@disconnect route - disconnected / in your case you won't need to clear the user or chat details because you are already using Cognito for Authentication which means this will be for only authenticated user. Have the logic on the front end for a user who created the chat to have a button to clear all conversation

@sendmessage route - You can add this route to separate your send message route. Another approach you can take is to listen to this route and fetch your Database for the new record when there is notification of activity.

Thread Thread
acijoe profile image
Joe Hummel

Thanks Kevin! I look forward to your video.

We are also considering AWS Chime. However, Chime doesn't provide the type of chat room features I'm looking to offer in a virtual event platform. Chime features seem well suited for our exhibitors - to allow attendees to start up instant Zoom-like meetings with exhibitor representatives. Doing this through Zoom requires a lot more prep and subscriptions.

Yes, I'm familiar with the costs for Chime, and found them to be pretty reasonable. We don't need daily use (yet), so the pay-as-you-use model may work out to be similar to costs to our current Zoom budget. Even if it results in somewhat higher costs, it would worth the convenience of having the service built into the platform, instead of linking in Zoom meetings now.

As far as number of participants, it depends. This could range from two to a dozen (maybe more) participants. For instance, take the case of a social/networking room for attendees to pop in at will, to virtually meet with other attendees or faculty, One person would start this as a host, and wait for people to drop in, over a period of time (say two hour windows).

Initially, this research into WebSockets was to provide a means to send instant messages or a bulletin board feature (stored alerts) for event attendees. There would be a notification icon on the header menu area, where messages from event management would be delivered and stored for attendees. The WebSockets would provide the instant delivery for online users. AppSync would store the messages and track read status for attendees/users. The video chatting is a feature we also wanted to add, but later, after we got familiar with this initial goal.

Hope this all makes sense. Happy to explain more, and provide access to the platform, if you're so inclined. It is a constant work in progress. We started building this last year, when the lockdowns started, and nobody wanted to meet in person.

Thread Thread
kevin_odongo35 profile image
Kevin Odongo Author

Hey Ian

Sorry for the late response why don't you reach me out on email ( crudavid36@gmail.com). Share the app link then we work together and add the functionalities you want

Collapse
krauserraw profile image
krauserraw

Hi.
I have done all the indicated steps, but I always receive the following response when sending the indicated json.
{"message": "Internal server error", "connectionId":"NpFiQfKTIAMCJJw=", "requestId":"NpF9OHMXoAMFl_g="}
any possible solution please?

Collapse
jrothman profile image
Joel Rothman

thanks Kevin, when will you publish the next Part?

Collapse
kevin_odongo35 profile image
Kevin Odongo Author

Part 2 is out

Collapse
ndelargy profile image
Neil