Websocket topic publish doesn't work until you refresh the page

I have followed along with this example here that @Jacek (sorry for tagging you) had created back in October and have websockets mostly working. However, what I have found is that when I try and publish to a specific topic that it won't send the message until after you refresh the page. In the linked example above, you can see this happen where if you just go into the project and the chat loads on the right, it won't ever broadcast the publish that is supposed to be going on every couple of seconds until you click the refresh icon above the chat and suddenly you are User #2 and the publish messages come in. So...I'm asking for help in understanding if there is something wrong with my code and the example code at the link or if there is some sort of bug going on with the underlying websocket system as it currently stands in Nitro?
StackBlitz
Nuxt 3 + Nitro WebSockets (with global queue) - StackBlitz
Starter of Nuxt 3 with daisyUI.
7 Replies
kapa.ai
kapa.ai3w ago
Hi @Jawa-the-Hutt. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the knowledge sources provided, I can offer some insights into your WebSocket issue with Nuxt and Nitro. However, it's important to note that the information available is limited and may not fully address your specific problem. The WebSocket support in Nuxt and Nitro is relatively new and experimental. According to the H3 WebSocket Guide, WebSocket support is currently experimental and available in the nightly channel. Regarding your specific issue where publish messages don't appear until after a page refresh, this could be related to how the WebSocket connection is established and how subscriptions are handled. In the example provided in the H3 WebSocket Guide, subscriptions are set up in the open handler:
open(peer) {
peer.send({ user: "server", message: `Welcome ${peer}!` });
peer.publish("chat", { user: "server", message: `${peer} joined!` });
peer.subscribe("chat");
},
open(peer) {
peer.send({ user: "server", message: `Welcome ${peer}!` });
peer.publish("chat", { user: "server", message: `${peer} joined!` });
peer.subscribe("chat");
},
It's possible that in your implementation, the subscription to the relevant topic isn't happening immediately when the WebSocket connection is established, which could explain why messages only appear after a refresh. To troubleshoot this issue, you could try the following: 1. Ensure that your client-side code is properly establishing the WebSocket connection when the page loads. 2. Check that you're subscribing to the correct topic as soon as the connection is opened. 3. Verify that your server-side code is correctly handling new connections and subscriptions. If the problem persists, it might be worth checking for any recent updates or known issues in the Nuxt or Nitro repositories. The Nuxt GitHub Issues page might have relevant information or similar reported problems. Given that WebSocket support is experimental, it's also possible that you're encountering a bug. If you've verified your implementation and still can't resolve the issue, consider reporting it to the Nuxt or Nitro issue trackers with a minimal reproduction of the problem. For the most up-to-date and accurate information on WebSocket implementation in Nuxt and Nitro, I recommend checking the official documentation and recent releases, as the feature may have evolved since the knowledge sources provided were last updated. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@486225426608553994> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Jacek
Jacek3w ago
It is a bit weird. Code looks OK at first glance and "something" works, but also not as we would intuitively expect. I tested it a bit and I found the following: - every time you refresh or open new tab (simulating additional chat member), open() calls .watch(...) subscribing to the external event bus - refreshing multiple times or adding more members result in sending duplicated messages - but the "first" subscription does not really send.. function it is executed, but the browser get's nothing in the WS messages queue BWT. It can get even worse. On one of the browsers I have refreshed couple times too fast, triggered some connection error and it cannot even get to open().. tried server restart, nuxt cleanup, full cache/storage reload and disabled cache.. this client is busted (whole browser had to be killed) I tried to upgrade to the latest Nuxt, but all it really changed is using GUID instead of regular numbers to identify members (i.e. peer.toString() produces GUID now). The behavior stays the same. Replacing peer.publish with peer.send seems to fix the primary problem. You should also keep track of chat members and unsubscribe from .watch(...). Demo
Jawa-the-Hutt
Jawa-the-HuttOP3w ago
yeah...just something as simple as this with no watcher involved replicates the problem:
import type { Peer } from 'crossws';

let interval;
export default defineWebSocketHandler({
open(peer: Peer) {
peer.send(JSON.stringify({ type: 'welcome', message: `Welcome ${peer.toString()}!` }));
peer.publish('chat', JSON.stringify({ user: 'server', message: `Welcome ${peer.id} to the chat!` }));
peer.subscribe('chat');

interval = setInterval(async () => {
console.log('Sending message to chat');
peer.publish('chat', 'here is a message from the server');
}, 2000);
},
message(peer: Peer, message) {
},
close(peer: Peer) {
// clearInterval(interval);
}
});
import type { Peer } from 'crossws';

let interval;
export default defineWebSocketHandler({
open(peer: Peer) {
peer.send(JSON.stringify({ type: 'welcome', message: `Welcome ${peer.toString()}!` }));
peer.publish('chat', JSON.stringify({ user: 'server', message: `Welcome ${peer.id} to the chat!` }));
peer.subscribe('chat');

interval = setInterval(async () => {
console.log('Sending message to chat');
peer.publish('chat', 'here is a message from the server');
}, 2000);
},
message(peer: Peer, message) {
},
close(peer: Peer) {
// clearInterval(interval);
}
});
and I agree...using peer.send does work, but limits a lot of the usable scenarios for the built in websockets from nitro Ultimately, I'm going a slightly different direction instead of having a watcher inside the open, I'm sending all the peers to the unstorage memory driver so that I can access the peers from anywhere else I need server side. this inside the open
let peers = await globalPeers.getItem<Map<string, Peer>>('peers');
if (!peers) peers = new Map<string, Peer>();
peers.set(peer.id, peer);
await globalPeers.setItemRaw<Map<string, Peer>>('peers', peers);
let peers = await globalPeers.getItem<Map<string, Peer>>('peers');
if (!peers) peers = new Map<string, Peer>();
peers.set(peer.id, peer);
await globalPeers.setItemRaw<Map<string, Peer>>('peers', peers);
then something like this function when I want to send something out
const fireMessageToClient = async (value: SeverSentMessage) => {
const peers = await globalPeers.getItem<Map<string, Peer>>('peers');
if (!peers) return;
for (const peer of peers.values()) {
peer.publish(value.channel, JSON.stringify(value.data));
}
};
const fireMessageToClient = async (value: SeverSentMessage) => {
const peers = await globalPeers.getItem<Map<string, Peer>>('peers');
if (!peers) return;
for (const peer of peers.values()) {
peer.publish(value.channel, JSON.stringify(value.data));
}
};
have figured out to make it work without the page refresh, but don't like it. really think this is bug in the underlying websocket implementation, but that's a complete guess on my part. so...server side, something like above, but I go ahead and clearInterval on the close.
import type { Peer } from 'crossws';

let interval;
export default defineWebSocketHandler({
open(peer: Peer) {
peer.send(JSON.stringify({ type: 'welcome', message: `Welcome ${peer.toString()}!` }));
peer.publish('chat', JSON.stringify({ user: 'server', message: `Welcome ${peer.id} to the chat!` }));
peer.subscribe('chat');

interval = setInterval(async () => {
console.log('Sending message to chat');
peer.publish('chat', 'here is a message from the server');
}, 2000);
},
message(peer: Peer, message) {
},
close(peer: Peer) {
clearInterval(interval);
}
});
import type { Peer } from 'crossws';

let interval;
export default defineWebSocketHandler({
open(peer: Peer) {
peer.send(JSON.stringify({ type: 'welcome', message: `Welcome ${peer.toString()}!` }));
peer.publish('chat', JSON.stringify({ user: 'server', message: `Welcome ${peer.id} to the chat!` }));
peer.subscribe('chat');

interval = setInterval(async () => {
console.log('Sending message to chat');
peer.publish('chat', 'here is a message from the server');
}, 2000);
},
message(peer: Peer, message) {
},
close(peer: Peer) {
clearInterval(interval);
}
});
client side, you open the connection, then close it and then open it again. so something like this:
import { useWebSocket } from '@vueuse/core';
let { send, data, status, close } = useWebSocket('ws://localhost:3000/_ws');
close();
({ send, data, status, close } = useWebSocket('ws://localhost:3000/_ws'));

watch(data, (newValue) => {
console.log('data watcher - ', newValue);
});
import { useWebSocket } from '@vueuse/core';
let { send, data, status, close } = useWebSocket('ws://localhost:3000/_ws');
close();
({ send, data, status, close } = useWebSocket('ws://localhost:3000/_ws'));

watch(data, (newValue) => {
console.log('data watcher - ', newValue);
});
Jacek
Jacek3w ago
I am not sure why you insist on .publish over .send. When user is posting in the chat he means to broadcast ("publish") a message. When your server notifies each connected user and holds reference to its connection (peer variable in watch callback closure), then it makes sense to use "send" and push single message. I guess .publish prevents echo-ing the message user types and posts in the chat room. If you adopt this perspective it is a feature, not a bug.
Jawa-the-Hutt
Jawa-the-HuttOP3w ago
thanks for the info and thanks for taking time to look at this. FWIW, I tagged you in the original message not to get your specific help, but to call out the fact you originated the example at stackblitz. So again...thanks for looking at this. ultimately I'm not doing a chat room. Was just using the chat example that's in all the documentation. I guess the .publish in the chat example would be akin to DM'ing another user directly via some sort of shared topic that included each peer's id. Where I'm going with the websockets is I need to be able to broadcast to a group of users who have subscribed to certain topics or even be able to broadcast to a specific peer that is subscribed to a topic that is based on the peer.id. Unless I've misunderstood some things, .send is not going to be able to do that.
Jacek
Jacek2w ago
Judging by the API it is trying to facilitate P2P (user to user) communications. The only way server is getting it's messages injected is through send. So unless your notifications are meant to originate from specific user action, then publish is not the correct method to use. In this model only the actual "peer-user" is allowed to broadcast while server needs to send each message individually. It is all implemented already in the demo and seems to work just fine.

Did you find this page helpful?