feat: enhance WebSocket message handling and styling

- Add new styling for incoming messages in StyledWrapper.
- Update WSMessagesList to handle message sorting and focus.
- Refactor response sort order handling in WSResponseSortOrder.
- Improve WebSocket connection management in ws-client.
This commit is contained in:
Siddharth Gelera
2025-09-17 14:01:25 +05:30
parent 1bbfa40ae4
commit 58917aa97e
6 changed files with 57 additions and 40 deletions

View File

@@ -3,8 +3,13 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
overflow-y: auto;
.ws-message.new {
color: white;
background-color: ${({theme}) => theme.table.striped};
}
.ws-message:not(:last-child) {
border-bottom: 1px solid ${(props) => props.theme.table.border};
border-bottom: 1px solid ${({theme}) => theme.table.border};
}
.ws-message:not(:last-child).open {

View File

@@ -7,7 +7,8 @@ import { useTheme } from 'providers/Theme';
import { useState } from 'react';
import { useSelector } from 'react-redux';
import _ from 'lodash';
import { forwardRef } from 'react';
import { useRef } from 'react';
import { useEffect } from 'react';
const getContentMeta = (content) => {
if (typeof content === 'object') {
@@ -60,27 +61,42 @@ const TypeIcon = ({type})=>{
}[type]
}
const WSMessageItem = ({ message, isLast }) => {
const WSMessageItem = ({ message, inFocus }) => {
const [isOpen, setIsOpen] = useState(false);
const preferences = useSelector((state) => state.app.preferences);
const { displayedTheme } = useTheme();
const [isNew, setIsNew] = useState(false)
const notified = useRef(false)
const isIncoming = message.type === 'incoming';
const isInfo = message.type === 'info';
let parsedContent = parseContent(message.message);
const dataType = getDataTypeText(parsedContent.type);
useEffect(()=>{
if(notified.current === true) return
const dateDiff = Date.now() - new Date(message.timestamp).getTime()
if(dateDiff < 1000 * 10){
setIsNew(true)
setTimeout(()=>{
notified.current = true
setIsNew(false)
},2500)
}
}, [message])
return (
<div
ref={(node) => {
if (!node) return;
if (isLast) node.scrollIntoView();
if (inFocus) node.scrollIntoView()
}}
className={classnames('ws-message flex flex-col py-2', {
className={classnames('ws-message flex flex-col p-2', {
'ws-incoming': isIncoming,
'ws-outgoing': !isIncoming,
'open': isOpen
'open': isOpen,
'new': isNew
})}
>
<div
@@ -141,18 +157,13 @@ const WSMessagesList = ({ order = -1, messages = [] }) => {
if (!messages.length) {
return <div className="p-4 text-gray-500">No messages yet.</div>;
}
const ordered = order === -1 ? messages : messages.slice().reverse()
return (
<StyledWrapper className="ws-messages-list flex flex-col gap-1 mt-4">
{messages
.toSorted((x, y) => {
let a = order == -1 ? x : y
let b = order == -1 ? y : x
return (new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
})
{ordered
.map((msg, idx, src) => {
const isLast = src.length - 1 === idx;
return <WSMessageItem isLast={isLast} id={idx} message={msg} />;
const inFocus = order === -1 ? src.length - 1 === idx : idx === 0;
return <WSMessageItem inFocus={inFocus} id={idx} message={msg} />;
})}
</StyledWrapper>
);

View File

@@ -7,7 +7,7 @@ import { wsUpdateResponseSortOrder } from 'providers/ReduxStore/slices/collectio
const WSResponseSortOrder = ({ collection, item }) => {
const dispatch = useDispatch();
const order = item.response?.initiatedWsResponse?.sortOrder ?? -1
const order = item.response?.sortOrder ?? -1
const toggleSortOrder = ()=>{
dispatch(
@@ -20,10 +20,10 @@ const WSResponseSortOrder = ({ collection, item }) => {
return (
<StyledWrapper className="ml-2 flex items-center">
<button onClick={toggleSortOrder} title={order > 0 ? 'Latest Last' : 'Latest First'}>
{ order == -1
? <IconSortAscending2 size={16} strokeWidth={1.5} />
: <IconSortDescending2 size={16} strokeWidth={1.5} />}
<button onClick={toggleSortOrder} title={order === -1 ? 'Latest Last' : 'Latest First'}>
{ order === -1
? <IconSortDescending2 size={16} strokeWidth={1.5} />
: <IconSortAscending2 size={16} strokeWidth={1.5} />}
</button>
</StyledWrapper>
);

View File

@@ -22,7 +22,7 @@ const WSResult = ({ response }) => {
{response.error}
</div>
) : (
<WSMessagesList order={response?.initiatedWsResponse?.sortOrder} messages={response.responses || []} />
<WSMessagesList order={response?.sortOrder} messages={response.responses || []} />
);
};

View File

@@ -2771,6 +2771,7 @@ export const collectionsSlice = createSlice({
item.requestSent = eventData;
item.requestSent.timestamp = Date.now();
item.response = {
...initiatedWsResponse,
initiatedWsResponse,
statusText: 'CONNECTING'
};
@@ -2891,7 +2892,7 @@ export const collectionsSlice = createSlice({
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
if (item) {
item.response.initiatedWsResponse.sortOrder = item.response?.initiatedWsResponse?.sortOrder ? -item.response.initiatedWsResponse.sortOrder : -1;
item.response.sortOrder = item.response.sortOrder ? -item.response.sortOrder : -1;
}
}
}

View File

@@ -94,10 +94,7 @@ class WsClient {
});
// Set up event handlers
this.#setupWsEventHandlers(wsConnection, requestId, collectionUid, {
url: parsedUrl.fullUrl,
headers
});
this.#setupWsEventHandlers(wsConnection, requestId, collectionUid, {keepAlive,keepAliveInterval});
// Store the connection
this.#addConnection(requestId, wsConnection);
@@ -105,18 +102,6 @@ class WsClient {
// Emit connecting event
this.eventCallback('ws:connecting', requestId, collectionUid);
wsConnection.addEventListener('open', () => {
this.#flushQueue(requestId, collectionUid);
if (keepAlive) {
const handle = setInterval(() => {
wsConnection.isAlive = false;
wsConnection.ping();
}, keepAliveInterval);
this.connectionKeepAlive.set(requestId, handle);
}
});
return wsConnection;
} catch (error) {
console.error('Error creating WebSocket connection:', error);
@@ -259,10 +244,25 @@ class WsClient {
* @param {WebSocket} ws - The WebSocket instance
* @param {string} requestId - The request ID
* @param {string} collectionUid - The collection UID
* @param {object} options
* @param {boolean} options.keepAlive - keep the connection alive
* @param {number} options.keepAliveInterval - What the interval for keeping interval
* @private
*/
#setupWsEventHandlers(ws, requestId, collectionUid) {
#setupWsEventHandlers(ws, requestId, collectionUid, options) {
ws.on('open', () => {
this.#flushQueue(requestId, collectionUid);
if(options.keepAlive){
const handle = setInterval(() => {
console.log("pinging to keep alive")
ws.isAlive = false;
ws.ping();
}, options.keepAliveInterval);
this.connectionKeepAlive.set(requestId, handle);
}
this.eventCallback('ws:open', requestId, collectionUid, {
timestamp: Date.now(),
url: ws.url