Build a real-time whiteboard application with Azure Web PubSub

The COVID-19 pandemic has led to unprecedented measures and is also changing the way we work. In addition to restricting travel and canceling large events, a growing number of companies are encouraging to work remotely. In order to continue working efficiently and creating value under these new circumstances, organizations need to adopt different applications with different scenarios, e.g., web conference, remote collaboration, etc. The real-time whiteboard is one of the tools to help you build remote collaboration and bring your teams together, anytime, anywhere, e.g., running productive and engaging online meetings and workshops, building and developing ideas with distributed teams, explaining complex process and system with visual map or diagram, etc. The Azure Web PubSub (AWPS) which is a fully managed service could help you build the real-time whiteboard application. Let’s learn how to build a whiteboard demo together.

What’s the whiteboard demo?

This whiteboard demo demonstrates how to build a web application for real time collaboration using Azure and other related technologies. The fundamental feature of this application is allowing anyone painting on it and synchronizing the paint to others in real-time. The user could paint with the basic paint tool, touching on mobile devices or uploading images.

Before we start digging into details, you can first play with this demo online. Open this site and input your username, then draw anything you like in the whiteboard. Open another browser window you'll see your changes are synchronized in real-time.

You can also find the source code of this demo here.

Synchronize real-time data between client applications

One of the key features in this whiteboard is its ability to synchronize the drawing between multiple client apps in real-time. This is implemented by using WebSocket technology, which is commonly used in web applications for bidirectional communication. If you're already familiar with WebSocket and look into the implementation in server.js, you'll notice it's very different than a typical WebSocket server. In a typical WebSocket application, server needs to manage all client connections and handle data transfer between clients. So you can imagine in a whiteboard app, server will receive the drawing from one client and broadcast it to all other clients, which will generate huge traffic when everyone is drawing and processing all those data will be a big burden to the server.

If you look at our whiteboard server, you'll see it's a standard express.js server application (we don't go through details about how to use express.js here, you can refer to its official docs to learn more). Instead of having code to handle WebSocket connections, it creates a WebPubSubEventHandler and use it as a middleware in the express app.

let handler = new WebPubSubEventHandler(hubName, ['*'], {
  path: '/eventhandler',
  handleConnect: ...
  onConnected: ...
  onDisconnected: ...
  handleUserEvent: ...
});
app.use(handler.getMiddleware());

By using this Web PubSub event handler, we're leveraging Azure Web PubSub service to manage the client connections for us.

In the event handler there're some callbacks like onConnected and onDisconnected, which are similar to open and close events in WebSocket, but the key difference here is when using Azure Web PubSub service, the connection is connected to the service, your server just gets a notification when this happens but does not need to manage the lifetime of the connection. This is usually challenging in real scenarios where you need to handle things like connection routing and load balancing. In Azure Web PubSub they're all taken care of by the service.

Also in the server code you'll notice there is no code to pass the data from one client to another, this is purely done at client side. Look at the client you'll see code like this:

this._webSocket.send(JSON.stringify({
  type: 'sendToGroup',
  group: group,
  dataType: 'json',
  data: data
}));

This code sends a message to the WebSocket connection but there is no code at server side to handle it! This is because it is processed at service side. Azure Web PubSub can understand message from client (in this case it's asking service to send this message to a group) and send the message to the corresponding clients (which is called publish/subscribe pattern). So you can see by using Azure Web PubSub you can save a lot of server resources (like CPU and network bandwidth) by offloading WebSocket connections to the service.

Maintain state at server side

Even Azure Web PubSub helps us deliver real-time updates between clients, client still needs to send drawing data to server so it can be saved at server side. So next time when a new user opens the whiteboard, they can see all paintings others draw before.

This is done by sending an event from client to server. Event is another communication pattern (comparing to publish/subscribe pattern we use for sending real-time updates) in Azure Web PubSub for clients to send data to server.

In client, there is code like this:

this._webSocket.send(JSON.stringify({
  type: 'event',
  event: 'message',
  dataType: 'json',
  data: data
}));

This code sends an event to server and there is corresponding server code to handle it:

let handler = new WebPubSubEventHandler(hubName, ['*'], {
  path: '/eventhandler',
  ...
  handleUserEvent: async (req, res) => {
    let message = req.data;
    switch (message.name) {
      case 'addShape': ...
      case 'removeShape': ...
      case 'clear': ...
    }
    res.success();
  }
});

You can see the code above handles three types of events (when user adds a new shape, removes an existing shape or clears the whiteboard) and save the data to a local diagram object (for demo purpose, in a real application you should use a persistent storage to store this diagram).

You can see there is still data communication between client and server, but comparing to the real-time updates between clients (which happens whenever a user moves their mouse on the screen), this only happens when a user finishes drawing a shape, so the amount of data is much less than the real-time updates.

Besides user events, Azure Web PubSub also supports some system events like connected and disconnected so server can know the status of client connections. You can see in the server code, they're used to track the total number of clients online.

Use WebSocket API in client

Azure Web PubSub uses WebSocket API for its client programming interface. As long as your programming language supports WebSocket, you don't need to install any third-party library. You already see how to send messages through WebSocket in previous sections, but there are a few more things you need to be aware before using it:

Authenticate with service

Azure Web PubSub doesn't support anonymous connection, so in order to connect to the service each client needs to authenticate with it using a JWT token. Azure Web PubSub SDK already provides an API to generate the token from connection string. A recommended implementation is to expose a Web API (usually called negotiate) at server to return this token (the API itself can be protected by your own authentication mechanism). In the demo app it's implemented like this:

app.get('/negotiate', async (req, res) => {
  let token = await serviceClient.getAuthenticationToken({
    roles: ['webpubsub.sendToGroup.draw']
  });
  res.json({
    url: token.url
  });
})

In the negotiate you can also control the permission of client (like which group it can send message to).

Create connection

Create connection is really simple but you need to specify a subprotocol if you want to directly send messages between clients.

let res = await fetch('/negotiate');
let url = res.json().url;
let ws = new WebSocket(url, 'json.webpubsub.azure.v1');

With json.webpubsub.azure.v1 subprotocol, you'll be able to join, leave and publish messages from client (more details can be found here).

If you don't specify subprotocol you can still connect, but all messages you send will be treated as events and be sent to server.

Handle reconnect

It's very common that WebSocket connection will drop due to things like network glitches, long time inactivity at client side, etc. So to improve the stability of the client app you should always consider reconnect when it disconnects.

In Javascript if a WebSocket connection is closed you need to create a new WebSocket object to reconnect, which means for all callbacks you registered on the old object you need to re-register in the new one. In this demo we created a simple WebSocketClient class to wrap the raw WebSocket object so it will automatically reconnect and re-register all callbacks. Check out the source code to see how it works.

Next Steps

Now, we already go through the key points to build the real-time whiteboard application with Azure Web PubSub service. If you are looking for more details about this demo, you can refer to the whiteboard application on Github where the code is hosted, along with information and docs on how to deploy and run it yourself.

If you are trying to build your first real-time application with Azure Web PubSub, you could also get more helpful resources from the getting stared contents. We are looking forward your feedback and ideas to help us become better via Azure Feedback Forum!

More Information

This blog is also posted on Tech Community and you could also get more blogs about the Web PubSub and its new features here.

At last, thanks for Ken Chen offering the demo and technical details.

17