Tab Synchronization with BroadcastChannel - qyjohn/Hands-on-Linux GitHub Wiki

Summary

Tab synchronization is a common feature request for web applications. In this tutorial. we demonstrate how to achieve client-side tab synchronization with BroadcastChannel.

Demo Environment

In this tutorial, we use a simple PHP application as the demo. Although the demo is built with PHP, it can be anything because JavaScript is doing the real work on the client side.

Launch an EC2 instance for this tutorial. Open port 22 for SSH access and port 80 for HTTP access. SSH into the EC2 instance and install Apache2 and PHP.

If you are using Amazon Linux 2, do the following:

# Update 
sudo yum update -y

# Install Apache2
sudo yum install -y httpd

# Check which version of PHP is available
sudo amazon-linux-extras | grep php

# Install PHP 8.1
sudo amazon-linux-extras enable php8.1
sudo yum clean metadata
sudo yum install php php-common

# Set Permissions 
sudo usermod -a -G apache ec2-user
sudo chown -R ec2-user:apache /var/www
sudo chmod 2775 /var/www
find /var/www -type d -exec sudo chmod 2775 {} \;

# Start Apache2
sudo service httpd start

If you are using Ubuntu 22.04, do the following:

# Install Apache2 and PHP
sudo apt update
sudo apt install apache2 php

# Set Permissions 
sudo usermod -a -G www-data ubuntu
sudo chown -R ubuntu:www-data /var/www
sudo chmod 2775 /var/www
find /var/www -type d -exec sudo chmod 2775 {} \;

# Start Apache2
sudo service apache2 start

To verify Apache2 and PHP have been successfully installed and configured, create a new file /var/www/html/index.php with the following content:

<?php
    phpinfo();
?>

At this point, you should be able to access your web server via http://Public-IP-Address/index.php.

The Problem

In this demo, we create a simple web application periodically retrieves a value from the web server and show the updated value in the browser.

We use update.php to generate a random value upon request:

<?php
    $data = random_int(1, 1000000);
    echo $data;
?>

We use index.html as the web front end. In the web page, we have an empty DIV to show the content:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Random Number Demo</title>
    <script src="update.js"></script> 
  </head>
  <body>
    <div id="content">12345</div>
  </body>
</html>

We use update.js to fetch the content from update.php and update the web page every 10 seconds:

update();
setInterval(update, 10000);

function update()
{
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200)
            document.getElementById("content").innerHTML = xhr.responseText;
    }
    xhr.open("GET", "update.php", true);
    xhr.send(null);
}

Open two tabs with the same application, obviously they are out of sync - the random numbers in the tabs are not the same. How do we keep both tabs (and even more tabs) in sync?

The Solution

The following version of update.js solves the problem with BroadcastChannel. When a tab gets an update from the server, it updates itself and broadcasts a message to the BroadcastChannel. Other tabs listening on the BroadcastChannel receives the updated data and update themselves accordingly. It should be noted that the tab broadcasting does not receive the data broadcasted by itself.

const id = Math.floor(Math.random() * 1000000);
console.log("Tab ID: " + id);

var value = "12345";
setInterval(update, 10000);

const channel = new BroadcastChannel('demo_channel');
channel.onmessage = (messageEvent) => {
    const data = messageEvent.data;
    value = data.value;
    refresh();
};

function refresh()
{
    document.getElementById("content").innerHTML = value;
}

function update()
{
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
            value = xhr.responseText;
            refresh();
            msg = { id: id, value: value };
            channel.postMessage(msg);
        }
    }
    xhr.open("GET", "update.php", true);
    xhr.send(null);
}
⚠️ **GitHub.com Fallback** ⚠️