GitHub user dosubot[bot] added a comment to the discussion: How the WebSocket 
server interacts with Redis ?

<!-- Answer -->
The frontend connects to the superset-websocket server using a WebSocket 
client, authenticating via a JWT token stored in a cookie (set by the Flask 
backend). The WebSocket URL is typically configured via 
`GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL`, and the frontend appends a `last_id` 
query parameter to support reconnection and missed event delivery. Incoming 
messages are JSON objects containing async event data, including any custom 
fields you add in the backend payload (like `request_id`, `type`, `axis`, etc.) 
[example and 
details](https://github.com/apache/superset/blob/master/superset-websocket/README.md).

Superset’s frontend manages listeners for async events keyed by `job_id`, so 
components can react to updates for specific jobs. If a listener isn’t 
registered when an event arrives, the event is retried a few times before 
logging a warning. If the WebSocket connection fails, the frontend falls back 
to polling via a REST API [see 
asyncEvent.ts](https://github.com/apache/superset/blob/master/superset-frontend/src/middleware/asyncEvent.ts).

**Example: Minimal WebSocket Client (from Superset frontend)**

```typescript
// Simplified from superset-frontend/src/middleware/asyncEvent.ts
let ws: WebSocket | null = null;
let lastReceivedEventId: string | null = null;

function wsConnect() {
  let url = config.GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL;
  if (lastReceivedEventId) url += `?last_id=${lastReceivedEventId}`;
  ws = new WebSocket(url);

  ws.addEventListener('open', () => {
    // Connected
  });

  ws.addEventListener('message', event => {
    const asyncEvent = JSON.parse(event.data);
    // Handle event, e.g. dispatch to listeners keyed by asyncEvent.job_id
  });

  ws.addEventListener('close', () => {
    // Optionally reconnect or fall back to polling
  });
}
```

**Example: Utility Client App (from superset-websocket utils)**

```javascript
// Simplified from 
superset-websocket/utils/client-ws-app/public/javascripts/app.js
const socket = new WebSocket('ws://127.0.0.1:8080?last_id=...');
socket.addEventListener('open', () => {
  socket.send('Hello Server!');
});
socket.addEventListener('message', event => {
  const data = JSON.parse(event.data);
  // Process async event data
});
```
[See full 
example](https://github.com/apache/superset/blob/master/superset-websocket/utils/client-ws-app/public/javascripts/app.js)

**Listener Management Pattern**

Superset’s frontend keeps a dictionary of listeners by `job_id`:

```typescript
const listenersByJobId: { [jobId: string]: (event: AsyncEvent) => void } = {};

function addListener(jobId: string, fn: (event: AsyncEvent) => void) {
  listenersByJobId[jobId] = fn;
}

function removeListener(jobId: string) {
  delete listenersByJobId[jobId];
}
```
When a message arrives, it’s dispatched to the appropriate listener based on 
`job_id` [see 
asyncEvent.ts](https://github.com/apache/superset/blob/master/superset-frontend/src/middleware/asyncEvent.ts).

**Best Practices**

- Make sure your backend includes all required custom fields in the event 
payload.
- The frontend expects JSON messages and can access any custom fields you add.
- Use the listener pattern to handle updates for specific jobs.
- Handle reconnection and fallback to polling for reliability.

Let me know if you want a more detailed React hook or integration example!


<!-- Dosu Comment Footer -->
*To reply, just mention [@dosu](https://go.dosu.dev/dosubot).*

---
<sup>How did I do? 
[Good](https://app.dosu.dev/response-feedback/4b93ade1-562b-4c45-a0d3-56c391040c99?feedback_type=great_response)
 | 
[Irrelevant](https://app.dosu.dev/response-feedback/4b93ade1-562b-4c45-a0d3-56c391040c99?feedback_type=irrelevant_answer)
 | 
[Incorrect](https://app.dosu.dev/response-feedback/4b93ade1-562b-4c45-a0d3-56c391040c99?feedback_type=incorrect_sources)
 | 
[Verbose](https://app.dosu.dev/response-feedback/4b93ade1-562b-4c45-a0d3-56c391040c99?feedback_type=too_verbose)
 | 
[Hallucination](https://app.dosu.dev/response-feedback/4b93ade1-562b-4c45-a0d3-56c391040c99?feedback_type=hallucination)
 | [Report 
🐛](https://app.dosu.dev/response-feedback/4b93ade1-562b-4c45-a0d3-56c391040c99?feedback_type=bug_report)
 | 
[Other](https://app.dosu.dev/response-feedback/4b93ade1-562b-4c45-a0d3-56c391040c99?feedback_type=other)</sup>

[![Chat with 
Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/a28d3c7e-a9d3-459e-9fb6-3a6f9ff4f357/ask?utm_source=github)&nbsp;[![Open
 in 
Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20frontend%20connects%20to%20the%20superset-websocket%20server%20using%20a%20WebSocket%20client%2C%20authenticating%20via%20a%20JWT%20token%20stored%20in%20a%20cookie%20%28set%20by%20the%20Flask%20backend%29.%20The%20WebSocket%20URL%20is%20typically%20configured%20via%20%60GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL%60%2C%20and%20the%20frontend%20appends%20a%20%60last_id%60%20query%20parameter%20to%20support%20reconnection%20and%20missed%20event%20delivery.%20Incoming%20messages%20are%20JSON%20objects%20containing%20async%20event%20data%2C%20including%20any%20custom%20fields%20you%20add%20in%20the%20backend%20payload%20%28like%20%60request_id%60%2C%20%60type%60%2C%20%60axis%60%2C%20etc.%29%C2%A0%5Bexample%20and%20details%5D%28https%3A//github.com/apache/su
 
perset/blob/master/superset-websocket/README.md%29.%0A%0ASuperset%E2%80%99s%20frontend%20manages%20listeners%20for%20async%20events%20keyed%20by%20%60job_id%60%2C%20so%20components%20can%20react%20to%20updates%20for%20specific%20jobs.%20If%20a%20listener%20isn%E2%80%99t%20registered%20when%20an%20event%20arrives%2C%20the%20event%20is%20retried%20a%20few%20times%20before%20logging%20a%20warning.%20If%20the%20WebSocket%20connection%20fails%2C%20the%20frontend%20falls%20back%20to%20polling%20via%20a%20REST%20API%C2%A0%5Bsee%20asyncEvent.ts%5D%28https%3A//github.com/apache/superset/blob/master/superset-frontend/src/middleware/asyncEvent.ts%29.%0A%0A%2A%2AExample%3A%20Minimal%20WebSocket%20Client%20%28from%20Superset%20frontend%29%2A%2A%0A%0A%60%60%60typescript%0A//%20Simplified%20from%20superset-frontend/src/middleware/asyncEvent.ts%0Alet%20ws%3A%20WebSocket%20%7C%20null%20%3D%20null%3B%0Alet%20lastReceivedEventId%3A%20string%20%7C%20null%20%3D%20null%3B%0A%0Afunction%20wsConnect%28%29%
 
20%7B%0A%20%20let%20url%20%3D%20config.GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL%3B%0A%20%20if%20%28lastReceivedEventId%29%20url%20%2B%3D%20%60%3Flast_id%3D%24%7BlastReceivedEventId%7D%60%3B%0A%20%20ws%20%3D%20new%20WebSocket%28url%29%3B%0A%0A%20%20ws.addEventListener%28%27open%27%2C%20%28%29%20%3D%3E%20%7B%0A%20%20%20%20//%20Connected%0A%20%20%7D%29%3B%0A%0A%20%20ws.addEventListener%28%27message%27%2C%20event%20%3D%3E%20%7B%0A%20%20%20%20const%20asyncEvent%20%3D%20JSON.parse%28event.data%29%3B%0A%20%20%20%20//%20Handle%20event%2C%20e.g.%20dispatch%20to%20listeners%20keyed%20by%20asyncEvent.job_id%0A%20%20%7D%29%3B%0A%0A%20%20ws.addEventListener%28%27close%27%2C%20%28%29%20%3D%3E%20%7B%0A%20%20%20%20//%20Optionally%20reconnect%20or%20fall%20back%20to%20polling%0A%20%20%7D%29%3B%0A%7D%0A%60%60%60%0A%0A%2A%2AExample%3A%20Utility%20Client%20App%20%28from%20superset-websocket%20utils%29%2A%2A%0A%0A%60%60%60javascript%0A//%20Simplified%20from%20superset-websocket/utils/client-ws-app/public/java
 
scripts/app.js%0Aconst%20socket%20%3D%20new%20WebSocket%28%27ws%3A//127.0.0.1%3A8080%3Flast_id%3D...%27%29%3B%0Asocket.addEventListener%28%27open%27%2C%20%28%29%20%3D%3E%20%7B%0A%20%20socket.send%28%27Hello%20Server%21%27%29%3B%0A%7D%29%3B%0Asocket.addEventListener%28%27message%27%2C%20event%20%3D%3E%20%7B%0A%20%20const%20data%20%3D%20JSON.parse%28event.data%29%3B%0A%20%20//%20Process%20async%20event%20data%0A%7D%29%3B%0A%60%60%60%0A%5BSee%20full%20example%5D%28https%3A//github.com/apache/superset/blob/master/superset-websocket/utils/client-ws-app/public/javascripts/app.js%29%0A%0A%2A%2AListener%20Management%20Pattern%2A%2A%0A%0ASuperset%E2%80%99s%20frontend%20keeps%20a%20dictionary%20of%20listeners%20by%20%60job_id%60%3A%0A%0A%60%60%60typescript%0Aconst%20listenersByJobId%3A%20%7B%20%5BjobId%3A%20string%5D%3A%20%28event%3A%20AsyncEvent%29%20%3D%3E%20void%20%7D%20%3D%20%7B%7D%3B%0A%0Afunction%20addListener%28jobId%3A%20string%2C%20fn%3A%20%28event%3A%20AsyncEvent%29%20%3D%3E%20void%
 
29%20%7B%0A%20%20listenersByJobId%5BjobId%5D%20%3D%20fn%3B%0A%7D%0A%0Afunction%20removeListener%28jobId%3A%20string%29%20%7B%0A%20%20delete%20listenersByJobId%5BjobId%5D%3B%0A%7D%0A%60%60%60%0AWhen%20a%20message%20arrives%2C%20it%E2%80%99s%20dispatched%20to%20the%20appropriate%20listener%20based%20on%20%60job_id%60%C2%A0%5Bsee%20asyncEvent.ts%5D%28https%3A//github.com/apache/superset/blob/master/superset-frontend/src/middleware/asyncEvent.ts%29.%0A%0A%2A%2ABest%20Practices%2A%2A%0A%0A-%20Make%20sure%20your%20backend%20includes%20all%20required%20custom%20fields%20in%20the%20event%20payload.%0A-%20The%20frontend%20expects%20JSON%20messages%20and%20can%20access%20any%20custom%20fields%20you%20add.%0A-%20Use%20the%20listener%20pattern%20to%20handle%20updates%20for%20specific%20jobs.%0A-%20Handle%20reconnection%20and%20fallback%20to%20polling%20for%20reliability.%0A%0ALet%20me%20know%20if%20you%20want%20a%20more%20detailed%20React%20hook%20or%20integration%20example%21)&nbsp;[![Join
 Dis
 
cord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share
 on 
X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/apache/superset/discussions/36383)

GitHub link: 
https://github.com/apache/superset/discussions/36383#discussioncomment-15148250

----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to: 
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to