All posts
engineeringfrontend

Showing all our Discord activities at once: fixing the live presence

Our team page shows exactly what all of us are doing on Discord in real time. Whether we're grinding VSCode, playing games, or jamming to Spotify. It runs on Lanyard over a WebSocket connection. But honestly, three things were completely broken, and fixing them turned out to be way more interesting than we expected.

1. It only showed one activity at a time

Discord lets you have multiple activities running at once, like having VSCode open while listening to Spotify. Before, our site was just picking the first one it found and ignoring the rest.

To fix this issue, we stopped trying to find a "primary" activity. Instead, we just render the whole list. We gave Spotify its own dedicated row (with the album art, title, and artist) and stacked all the other games and apps right below it.

2. It broke completely if you clicked away and came back

This was the fun one. Our presence client is a singleton with one WebSocket. When you left the /team page and clicked back onto it, React remounted the component and absolutely nuked the socket connection to open a new one.

The catch? The old socket's onclose handler was still firing in the background and nulling out the new socket we just made.

Here is how we fixed it: we tagged every handler against the specific socket instance it belongs to, and told it to bail if it isn't the current one.

ws.onclose = () => {
  if (ws !== this.ws) return; // stale socket - ignore it
  this.reconnect();
};

We also made it grab the initial data from the REST endpoint on every fresh connection, so the page doesn't just sit there looking completely blank for a few seconds while the gateway does its handshake.

3. The Spotify album art refused to load

Discord presence assets come in three different URL formats, and you have to write code to detect which one is which:

Asset prefixWhere it actually goes
mp:...media.discordapp.net/...
spotify:...i.scdn.co/image/...
raw snowflake IDcdn.discordapp.com/app-assets/{appId}/{id}

The image URLs themselves were perfectly fine. Turns out our Content-Security-Policy (CSP) was just roasting us. Our img-src rules didn't allow i.scdn.co, so the browser was quietly blocking Spotify's CDN without telling us. We added it to the allow-list, and the album art finally loaded everywhere.

The takeaway

Real-time UI has two massive traps: component lifecycles (sockets living longer than the actual page) and trust boundaries (CSP silently dropping random third-party assets).

The worst part is that neither of them throws a loud error in your console. Your UI just goes ghost and shows a blank card. Definitely check both of those first if your sockets are acting up!