Chat app using Azure Web PubSub, Static Web Apps and other Azure services

Overview

Chatr - Azure Web PubSub Sample App

This is a demonstration & sample application designed to be a simple multi-user web based chat system.
It provides persistent group chats, user to user private chats, a user list, idle (away from keyboard) detection and several other features.

It is built on several Azure technologies, including: Web PubSub, Static Web Apps and _Table Storage

👁‍🗨 Note. This was created as a personal project, created to aid learning while building something interesting. The code comes with all the caveats you might expect from such a project.

Goals:

  • Learn about using websockets
  • Write a 'fun' thing
  • Try out the new Azure Web PubSub service
  • Use the authentication features of Azure Static Web Apps
  • Deploy everything using Azure Bicep

Use cases & key features:

  • Sign-in with Microsoft, Twitter or GitHub accounts
  • Realtime chat with users
  • Shared group chats, only the creator can remove the chat
  • Detects where users are idle and away from keyboard (default is one minute)
  • Private 'user to user' chats, with notifications and popups

Screenshot

Architecture

Client / Frontend

This is the main web frontend as used by end users via the browser.

The source for this is found in client/ and consists of a static standalone pure ES6 JS application, no bundling or Node.js is required. It is written using Vue.js as a supporting framework, and Bulma as a CSS framework.

Some notes:

  • ES6 modules are used so the various JS files can use import/export without the need to bundle.
  • Vue.js is used as a browser side library loaded from CDN with <script> tag, this is an elegant & lightweight approach supported by modern browsers, rather than the usual vue-cli style app which requires Node and webpack etc.
  • client/js/app.js shows how to create a Vue.js app with child components using this approach. The majority of client logic is here.
  • client/js/components/chat.js is a Vue.js component used to host each chat tab in the application
  • The special .auth/ endpoint provided by Static Web Apps is used to sign users in and fetch their user details, such as userId.

Server

This is the backend, handling websocket events to and from Azure Web PubSub, and providing REST API for some operations.

The source for this is found in api/ and consists of a Node.js Azure Function App. It connects to Azure Table Storage to persist group chat and user data (Table Storage was picked as it's simple & cheap). This is not hosted in a standalone Azure Function App but instead deployed into the Static Web App as part of it's serverless API support

There are four HTTP functions all served from the default /api/ path

  • eventHandler - Webhook receiver for "upstream" events sent from Azure Web PubSub service, contains the majority of application logic. Not called directly by the client, only Azure WebPub Sub.
  • getToken - Called by the client to get an access token and URL to connect via WebSockets to the Azure Web PubSub service. Must be called with userId in the URL query, e.g. GET /api/getToken?userId={user}
  • getUsers - Returns a list of signed in users, note the route for this function is /api/users
  • getChats - Returns a list of active group chats, note the route for this function is /api/chats

State is handled with state.js which is an ES6 module exporting functions supporting state CRUD for users and chats. This module carries out all the interaction with Azure Tables, and provides a relatively transparent interface, so a different storage backend could be swapped in.

WebSocket & API Message Flows

There is two way message flow between clients and the server via Azure Web PubSub and event handlers

The json.webpubsub.azure.v1 subprotocol is used rather than basic WebSockets, this provides a number of features: users can be added to groups, clients can send custom events (using type: event), and also send messages direct to other clients without going via the server (using type: sendToGroup)

Notes:

  • Chat IDs are simply randomly generated GUIDs, these correspond to "groups" in the subprotocol.
  • Private chats are a special case, they are not persisted in state, and they do not trigger chatCreated events. Also the user doesn't issue a joinChat event to join them, that is handled by the server as a kind of "push" to the clients.
  • User IDs are simply strings which are considered to be unique, this could be improved, e.g. with prefixing.

Client Messaging

Events & chat are sent using the json.webpubsub.azure.v1 subprotocol

Chat messages sent from the client use sendToGroup and a custom JSON payload with three fields message, fromUserId & fromUserName, these messages are relayed client to client by Azure Web PubSub, the server is never notified of them:

{
  type: 'sendToGroup',
  group: <chatId>,
  dataType: 'json',
  data: {
    message: <message text>,
    fromUserId: <userId>,
    fromUserName: <userName>,
  },
}

Events destined for the backend server are sent as WebSocket messages from the client via the same subprotocol with the event type, and an application specific sub-type, e.g.

{
  type: 'event',
  event: 'joinChat',
  dataType: 'text',
  data: <chatId>,
}

The types of events are:

  • createChat - Request the server you want to create a group chat
  • createPrivateChat - Request the server you want to create a private chat
  • joinChat - To join a chat, the server will add user to the group for that chatId
  • leaveChat - To leave a group chat
  • deleteChat - Called from a chat owner to delete a chat
  • userEnterIdle - Let the server know user is now idle
  • userExitIdle - Let the server know user is no longer idle

The backend API eventHandler function has cases for each of these user events, along with handlers for connection & disconnection system events.

Server Messaging

Messages sent from the server have a custom Chatr app specific payload as follows:

{
  chatEvent: <eventType>,
  data: <JSON object type dependant>
}

Where eventType is one of:

  • chatCreated - Let all users know a new group chat has been created
  • chatDeleted - Let all users know a group chat has been removed
  • userOnline - Let all users know a user has come online
  • userOffline - Let all users know a user has left
  • joinPrivateChat - Sent to both the initiator and recipient of a private chat
  • userIsIdle - Sent to all users when a user enters idle state
  • userNotIdle - Sent to all users when a user exits idle state

The client code in client/js/app.js handles these messages as they are received by the client, and reacts accordingly.

Some Notes on Design and Service Choice

The plan of this project was to use Azure Web PubSub and Azure Static Web Apps, and to host the server side component as a set of serverless functions in the Static Web Apps API support (which is in fact Azure Functions under the hood). Azure Static Web Apps was selected because it has amazing support for codeless and config-less user sign-in and auth, which I wanted to leverage.

Some comments on this approach:

  • API support in Static Web Apps is quite limited and can't support the new bindings and triggers for Web PubSub. HOWEVER You don't need to use these bindings 😂 . You can create a standard HTTP function to act as a webhook event handler instead of using the webPubSubConnection binding. For sending messages back to Web PubSub, the server SDK can simply be used within the function code rather than using the webPubSub output binding.
  • Table Storage was picked for persisting state as it has a good JS SDK (the new SDK in @azure/data-table was used), it's extremely lightweight and cheap and was good enough for this project, see deails below

State & Entity Design

State in Azure Tables consists of two tables (collections) named chats and users

Chats Table

As each chat contains nested objects inside the members field, each chat is stored as a JSON string in a field called data. The PartitionKey is not used and hardcoded to a string "chatr". The RowKey and the id field inside the data object are the same.

  • PartitionKey: "chatr"
  • RowKey: The chatId (random GUID created client side)
  • data: JSON stringified chat entity

Example of a chat data entity

{
  "id": "eab4b030-1a3d-499a-bd89-191578395910",
  "name": "This is a group chat",
  "members": {
    "0987654321": {
      "userId": "0987654321",
      "userName": "Another Guy"
    },
    "1234567890": {
      "userId": "1234567890",
      "userName": "Ben"
    }
  },
  "owner": "1234567890"
}

Users Table

Users are stored as entities with the fields (columns) described below. As there are no nested fields, there is no need to encode as a JSON string. Again the PartitionKey is not used and hardcoded to a string "chatr".

  • PartitionKey: "chatr"
  • RowKey: The userId field returned from Static Web Apps auth endpoint
  • userName: The username (could be email address or handle) of the user
  • userProvider: Which auth provided the user signed in with twitter, aad or github
  • idle: Boolean, indicating if the user us currently idle

Running and Deploying the App

Working Locally

See makefile

$ make
help                 💬 This help message
lint                 🔎 Lint & format, will not fix but sets exit code on error
lint-fix             📜 Lint & format, will try to fix errors and modify code
run                  🏃 Run server locally using Static Web Apps CLI
clean                🧹 Clean up project
deploy               🚀 Deploy everything to Azure using Bicep
tunnel               🚇 Start loophole tunnel to expose localhost

Deploying to Azure

Deployment is slightly complex due to the number of components and the configuration between them. The makefile target deploy should deploy everything for you in a single step using Bicep templates found in the deploy/ folder

See readme in deploy folder for details and instructions

Running Locally

This is possible but requires a little effort as the Azure Web PubSub service needs to be able call the HTTP endpoint on your location machine, so a tunnel has employed.

When running locally the Static Web Apps CLI is used and this provides a fake user authentication endpoint for us.

A summary of the steps is:

  • Deploy an Azure Storage account, get name and access key.
  • Deploy an Azure Web Pub Sub instance, get connection string from the 'Keys' page.
  • Copy api/local.settings.sample.json to api/local.settings.json and edit the required settings values.
  • Start a localhost tunnel service such as ngrok or loophole. The tunnel should expose port 7071 over HTTP.
    I use loophole as it allows me to set a custom host & DNS name, e.g.
    • loophole http 7071 --hostname chatr
  • In Azure Web Pub Sub settings.
    • Add a hub named chat
    • In the URL template put https://{{hostname-of-tunnel-service}}/api/eventHandler
    • In system events tick connected and disconnected
  • Run make run
  • Open http://localhost:4280/index.html

Known Issues

  • Won't run in Firefox as top level await is not yet supported
You might also like...

Unofficial API client for the Tidbyt API. Use this client to control Tidbyt devices and integrate with other services.

Tidbyt Client for Node.js Unofficial API client for the Tidbyt API. Use this client to control Tidbyt devices and integrate with other services. Insta

Dec 17, 2022

A lightweight Nano Node implementation made for wallets, exchanges and other services.

About This is a Light Nano Node implementation made for Wallets, Exchanges and other services. This Node has been built to be compatible with the offi

Jun 25, 2022

The Chat'Inn is a simple and minimal realtime chat application whose database is powered by firebase and firestore.

The Chat-in The Chat'Inn is a simple and minimal realtime chat application whose database is powered by firebase and firestore. The frontend part is c

Aug 8, 2022

Chat View let's you quickly and easily create elegant Chat UIs in your Markdown Files.

Chat View let's you quickly and easily create elegant Chat UIs in your Markdown Files.

Obsidian Chat View Plugin Chat View let's you quickly and easily create elegant Chat UIs in your Markdown Files. Usage Every chat message must be pref

Dec 27, 2022

Omnichannel Live Chat Widget UI Components offers a re-usable component-based library to help create a custom chat widget that can be connected to the Dynamics 365 Customer Service experience.

Omnichannel Live Chat Widget UI Components @microsoft/omnichannel-chat-widget is a React-based UI component library which allows you to build your own

Dec 15, 2022

Replaces Youtube Chat with Destiny.gg chat.

Replaces Youtube Chat with Destiny.gg chat.

A lightweight extension that replaces the native Youtube Live chat with an embeded destiny.gg chat. Note: This is in no way affiliated with Destiny.gg

Jul 27, 2022

Jumpstart Your Static Web Apps Learning Journey with #30Days Of Content and Activities

30DaysOfSWA - A Learning Journey Welcome to #30DaysOfSWA - a project to give beginners and experienced developers a tour of Azure Static Web Apps from

Nov 23, 2022

This is the backend of Wherechat, which is a chat application that allows users to find and meet each other through their location on the map.

This is the backend of Wherechat, which is a chat application that allows users to find and meet each other through their location on the map.

wherechat-backend About the project This is the backend of Wherechat, which is a chat application that allows users to find and meet each other throug

Nov 23, 2022

A fast and powerful http toolkit that take a list of domains to find active domains and other information such as status-code, title, response-time , server, content-type and many other

A fast and powerful http toolkit that take a list of domains to find active domains and other information such as status-code, title, response-time , server, content-type and many other

HTTPFY curently in beta so you may see problems. Please open a Issue on GitHub and report them! A Incredible fast and Powerful HTTP toolkit Report Bug

Dec 22, 2022
Comments
  • Nov 2021 updates

    Nov 2021 updates

    Refresh for SDK updates and security fix:

    • Fixes #5
    • Updated to @azure/web-pubsub v1.0.0-beta.4 - fixes API version compatibility now PubSub is GA
    • Updated to @azure/data-tables: v12.1.2
    opened by benc-uk 10
  • Add signature validation

    Add signature validation

    It looks like the current sample is vulnerable to spoofing. The function should validate the signature on every request: https://azure.github.io/azure-webpubsub/references/protocol-cloudevents.html#web-pubsub-service-atrribute-extension

    https://github.com/benc-uk/chatr/blob/67e124127304b05dba73356f3ebae9a8419464e6/api/eventHandler/index.js#L43

    opened by anthonychu 2
  • Fix async and linting for client

    Fix async and linting for client

    • ❌ Removes: The top level async/await from the client, which was only required when we were fetching config from the API
    • 💚 Adds: linting with eslint to the client code

    Fixes #1
    Fixes #3

    opened by benc-uk 1
Releases(0.1.0)
  • 0.1.0(Nov 11, 2021)

    New features

    • 👀 Improved UI for chats, now almost looks like a real chat app
    • 🧹 Clean up script to remove old users & chats

    Fixed & Updated

    • 💪 Bicep version tweaks
    • 💩 Better error handling and experience for users
    Source code(tar.gz)
    Source code(zip)
  • 0.0.9(Nov 11, 2021)

    New features

    • 🔐 More secure! Validation of the HMAC signature on incoming messages in the eventHandler

    Fixed & Updated

    • 🔨Updated to latest PubSub and Table SDKs - now works with GA PubSub service 😍
    • 🪲 Private chat UX bug
    • 🔮 Switched to ESM modules for loading Vue.js
    Source code(tar.gz)
    Source code(zip)
  • 0.0.8(May 17, 2021)

    New features

    • Idle and away detection
    • Able to see what service a user is signed in with
    • Group chat owners can delete the chat
    • UX improvements
    • Docs
    • Some bugs squashed
    Source code(tar.gz)
    Source code(zip)
  • 0.0.6(May 10, 2021)

    First release after refactoring out of container & ACI and back to using Functions, much, MUCH cleaner codebase and deployment architecture

    Source code(tar.gz)
    Source code(zip)
Owner
Ben Coleman
Geek, long time dev and code tinkerer. Currently a senior engineer at @microsoft
Ben Coleman
Vamos a realizar un juego muy sencillo en TypeScript, posteriormente lo vamos a desplegar en Microsoft Azure con Servicio de Azure Static Web Apps.

Taller TypeScript Descripción Vamos a realizar un juego muy sencillo en TypeScript, posteriormente lo vamos a desplegar en Microsoft Azure con Servici

Manuel Ortiz 7 Oct 10, 2022
Live demo using Angular, github.dev, codespaces, copilot, azure static web apps, and devcontainers

One More Change! @ NgConf 2022 This is a quick project template for demoing github.dev, Codespaces, Copilot, Azure Static Web Apps, and Visual Studio

John Papa 14 Dec 15, 2022
Live demo using Angular, github.dev, codespaces, copilot, azure static web apps, and devcontainers

Cloud Computing with Codespaces First seen in the presentation One More Change! @ NgConf 2022 This is a quick project template for demoing github.dev,

John Papa 9 Sep 13, 2022
A cache for @azure/msal-node that uses Azure KeyVault as a store

@intility/msal-keyvault-cache A cache for @azure/msal-node that uses Azure KeyVault as a store. Usage Install with npm install @intility/msal-keyvault

Intility 10 Mar 17, 2022
This repository demonstrates how to integrate your Dialogflow agent with 3rd-party services services using a Node.JS backend service

This repository demonstrates how to integrate your Dialogflow agent with 3rd-party services services using a Node.JS backend service. Integrating your service allows you to take actions based on end-user expressions and send dynamic responses back to the end-user.

ddayto 10 Jul 21, 2022
Gofiber with NextJS Static HTML is a small Go program to showcase for bundling a static HTML export of a Next.js app

Gofiber and NextJS Static HTML Gofiber with NextJS Static HTML is a small Go program to showcase for bundling a static HTML export of a Next.js app. R

Mai 1 Jan 22, 2022
Calculates maximum composite SLA for a list of sequentially provided cloud services or your custom-defined services.

SlaMax Calculates maximum composite SLA for a list of sequentially provided cloud services or your custom-defined services. Here are a few use-cases y

Mikael Vesavuori 4 Sep 19, 2022
Football Chat Bot. Integrated into Amazon Web Services

football-chat-bot-aws Football Chat Bot. Integrated into Amazon Web Services Local Setup Clone application repository: git clone https://github.com/to

Andrey Kuznetsov 5 Dec 29, 2022
O Web-Chat é um projeto com o intuito de criar um chat de ajuda, que contém uma experiência dinâmica e salva as informações preenchidas pelo usuário usando um formulário.

Web-Chat Introdução O Web-Chat é um projeto com o intuito de criar um chat de ajuda, que contém uma experiência dinâmica e salva as informações preenc

BiaGrenzel 5 Oct 5, 2022