I'm trying to implement a component in React that handles initialization and communication with a shared WebSocket across the entire application. I've created the following WithWebSocket component that utilizes a Shared Worker to manage communication with the WebSocket.
The code works correctly in some cases, but I'm facing difficulties in others. Here's the code:
import React, { createContext, useContext, useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { getAuthCookieDecoded } from 'utils/auth';
import { useLocation } from 'react-router-dom';
import { setToStorage, getFromStorage } from 'utils/storage';
import Axios from 'axios';
import Cookies from 'js-cookie';
import { DOMAIN } from 'config/endpoints';
const URL_WEBSOCKET = `wss://wes.not${DOMAIN}`;
export const WebSocketContext = createContext(null);
const { tcode: janisClient = '' } = getAuthCookieDecoded() || {};
const WithWebSocket = ({ children }) => {
const [websocketInfo, setWebsocketInfo] = useState(null);
const [wsToken, setWsToken] = useState('');
const workerRef = useRef(null);
const { pathname } = useLocation();
const enablePaths = ['/delivery/shipping/pick-up', '/delivery/shipping/express'];
const activeViews = enablePaths.includes(pathname);
const getAuthToken = async () => {
const secret = Cookies.get('JANIS_ACCESS_TOKEN');
try {
const { data: apiData } = await Axios.get(`https://notification${DOMAIN}/api/access-token`, {
headers: {
'Janis-Api-Secret': secret,
'Janis-Api-Key': 'Bearer',
'Janis-Client': janisClient
}
});
setWsToken(apiData.token);
} catch (err) {
console.warn({ err });
setWsToken('');
}
};
useEffect(() => {
if (!wsToken && activeViews) {
getAuthToken();
}
}, []);
useEffect(
() => {
try {
if (typeof SharedWorker === 'undefined')
throw new Error('This browser does not support Shared Workers');
if (!wsToken || workerRef.current) return;
/* Necesita estarse instanciando para que funcione el worker */
/* Validar dentro del worker */
const sharedWorkerInfo = getFromStorage('webSocketWorker');
workerRef.current = new SharedWorker(`/static/js/webSocketWorker.js`, {
name: 'webSocketWorker'
});
setToStorage('webSocketWorker', janisClient, 10);
if (workerRef.current) {
workerRef.current.port.postMessage({
URL: URL_WEBSOCKET.concat(`?authToken=${wsToken}`),
janisClient,
enablePaths: activeViews,
cacheWorkerInfo: sharedWorkerInfo
});
workerRef.current.port.onmessage = event => {
let dataToJson = null;
try {
dataToJson = JSON.parse(event.data);
} catch (reason) {
dataToJson = String(event.data);
}
const response = dataToJson.push || dataToJson.detail || dataToJson;
if (response) {
const parseResponse = typeof response === 'string' ? JSON.parse(response) : response;
parseResponse.event = dataToJson.event || '';
setWebsocketInfo(parseResponse);
}
};
workerRef.current.port.onerror = async event => {
console.error('error websocket :>> ', event);
};
}
} catch (error) {
console.error(error.message);
}
},
[wsToken]
);
return <WebSocketContext.Provider value={websocketInfo}>{children}</WebSocketContext.Provider>;
};
Code for the Shared Worker:
/* eslint-disable func-names */
const ports = [];
let ws;
let client;
const cleanPorts = port => {
const index = ports.indexOf(port);
if (index !== -1) {
ports.splice(index, 1);
}
};
const sendDataToWebSocket = (websocket, Jclient) => {
websocket.send(
JSON.stringify({
action: 'subscribe',
data: {
events: ['delivery:shipping:pickup', 'delivery:shipping:express'],
client: Jclient
}
})
);
};
this.onconnect = event => {
const port = event.ports[0];
ports.push(port);
port.onmessage = ev => {
const message = ev.data;
const { enablePaths, janisClient, URL, cacheWorkerInfo } = message;
if (!enablePaths || !janisClient || (cacheWorkerInfo === janisClient && ws)) return;
if (client !== janisClient && ws) {
ws.close();
cleanPorts(port);
}
if (!client) ws = ws || new WebSocket(URL);
ws.onopen = () => {
sendDataToWebSocket(ws, janisClient);
client = janisClient;
};
ws.onmessage = e => {
const websocketMessage = e.data;
port.postMessage(websocketMessage);
ports.forEach(p => {
p.postMessage(websocketMessage);
});
};
ws.onclose = () => {
cleanPorts(port);
};
};
};
I tried saving the worker's information in local storage to validate it from the worker's side during a reload, but upon reloading, it no longer receives information in the WebSocket connection it maintains. I believe the issue lies in the fact that upon pressing F5, the component is rebuilt, generating another token and causing the entire functionality to reset.