Sharing WebSocket Connections between Browser Tabs and Windows


Introduction
WebSocket connections are special communication channels that allow web browsers and servers to talk to each other in real-time. When you have multiple tabs or windows open in your browser, each one establishes its own WebSocket connection. This can be wasteful and affect the performance of your web application.
A better approach is to share a single WebSocket connection across all open tabs and windows. In this blog post, we’ll explore how to achieve this using BroadcastChannel and SharedWorker.
Why Share WebSocket Connections?
When multiple tabs create separate WebSocket connections, it can lead to:
- Increased server load: Multiple connections mean multiple active sockets on the server.
- Unnecessary network traffic: Each tab sends and receives duplicate data.
- Higher resource consumption: More connections mean higher CPU and memory usage.
By sharing a WebSocket connection, we reduce overhead, improve performance, and ensure a smoother user experience.
Frontend Setup in React
Let’s create a React app using the following command:
1//Create a react app using the following command
2npm create vite@latest my-websocket-app --template react
3cd my-websocket-app
4npm install
5
6//Command for run the React project
7npm run dev
8Solution 1: Using BroadcastChannel in React.js
The BroadcastChannel API allows communication between different tabs of the same origin. We can use it to create a single WebSocket connection in one tab and share data with others.
Implementation:
1. Create a WebSocket Context in React
We will use React's Context API to manage the WebSocket connection and share it across components.
1// WebSocketContext.js
2
3import React, { createContext, useEffect, useState } from "react";
4
5export const WebSocketContext = createContext(null);
6const channel = new BroadcastChannel("websocket_channel");
7
8export const WebSocketProvider = ({ children }) => {
9 const [messages, setMessages] = useState([]);
10 let socket = null;
11
12 useEffect(() => {
13 function connectWebSocket() {
14 socket = new WebSocket("wss://your-websocket-server.com"); // Replace with actual WebSocket URL
15 socket.onopen = () => {
16 console.log("WebSocket connected");
17 channel.postMessage({ type: "connected" });
18 };
19
20 socket.onmessage = (event) => {
21 const message = event.data;
22 setMessages((prev) => [...prev, message]);
23 channel.postMessage({ type: "message", data: message });
24 };
25
26 socket.onclose = () => {
27 console.log("WebSocket closed, reconnecting...");
28 setTimeout(connectWebSocket, 3000);
29 };
30 }
31
32 connectWebSocket();
33
34 channel.onmessage = (event) => {
35 if (event.data.type === "message") {
36 setMessages((prev) => [...prev, event.data.data]);
37 }
38 };
39 }, [ ]);
40
41 return (
42 <WebSocketContext.Provider value={{ messages }}>
43 {children}
44 </WebSocketContext.Provider>
45 );
46};
47
2. Use the WebSocket Context in a Component
We can now use the WebSocket context in any React component to access the shared WebSocket messages.
1// ChatComponent.js
2import React, { useContext } from "react";
3import { WebSocketContext } from "./WebSocketContext";
4
5const ChatComponent = () => {
6 const { messages } = useContext(WebSocketContext);
7
8 return (
9 <div>
10 <h2>Messages</h2>
11 <ul>
12 {messages.map((msg, index) => (
13 <li key={index}>{msg}</li>
14 ))}
15 </ul>
16 </div>
17 );
18};
19
20export default ChatComponent;
213. Wrap Your App with WebSocketProvider
To ensure all components can access the WebSocket, wrap your application with WebSocketProvider.
1// App.jsx
2import React from "react";
3import { WebSocketProvider } from "./WebSocketContext";
4import ChatComponent from "./ChatComponent";
5
6function App() {
7 return (
8 <WebSocketProvider>
9 <ChatComponent />
10 </WebSocketProvider>
11 );
12}
13
14export default App;
15Solution 2: Using SharedWorker in React.js
A SharedWorker runs in the background and can be used by multiple browser tabs to share a WebSocket connection.
Implementation:
1. Create a SharedWorker script
1/ sharedWorker.js
2self.onconnect = function (event) {
3 const port = event.ports[0];
4 let socket = new WebSocket("wss://your-websocket-server.com");
5
6 socket.onopen = () => {
7 console.log("WebSocket connected");
8 };
9
10 socket.onmessage = (event) => {
11 port.postMessage(event.data);
12 };
13
14 socket.onclose = () => {
15 console.log("WebSocket closed, reconnecting...");
16 setTimeout(() => {
17 socket = new WebSocket("wss://your-websocket-server.com");
18 }, 3000);
19 };
20
21 port.onmessage = (event) => {
22 if (socket.readyState === WebSocket.OPEN) {
23 socket.send(event.data);
24 }
25 };
26};
273. Use it in a React Component
1// ChatComponentWithWorker.jsx
2import React from "react";
3import { useSharedWorker } from "./useSharedWorker";
4
5const ChatComponentWithWorker = () => {
6 const { messages, sendMessage } = useSharedWorker();
7
8 return (
9 <div>
10 <h2>Messages</h2>
11 <ul>
12 {messages.map((msg, index) => (
13 <li key={index}>{msg}</li>
14 ))}
15 </ul>
16 <button onClick={() => sendMessage("Hello from tab!")}>Send Message</button>
17 </div>
18 );
19};
20
21export default ChatComponentWithWorker;
22Conclusion
By using BroadcastChannel and SharedWorker in a React.js application, we can efficiently share a single WebSocket connection across multiple tabs. BroadcastChannel is simple and works well for message sharing, while SharedWorker provides a persistent background process for better control.
Choose the approach that best fits your use case and optimize WebSocket usage in your React applications.