websocket issue

import { Hono } from 'hono';
import { createBunWebSocket } from 'hono/bun';

import type { WSContext, WSEvents, WSMessageReceive } from "hono/ws";
const { upgradeWebSocket, websocket } = createBunWebSocket();
const app = new Hono();

type Message = {
room: string;
user: string;
text: string;
timestamp: string;
};

const chatClients: { [room: string]: { clients: WSContext[], messages: Message[] } } = {};

app.get(
'/ws',
upgradeWebSocket((c) => {
let room: string;
let clients: WSContext[] = [];
let messages: Message[] = [];

return {
onOpen(event, ws) {
room = c.req.query('room');
if (!room) {
ws.close();
return;
}

if (!chatClients[room]) {
chatClients[room] = { clients: [], messages: [] };
}
({ clients, messages } = chatClients[room]);

clients.push(ws);
},
onMessage(event) {
const msg: Message = JSON.parse(${event.data});
msg.timestamp = new Date().toISOString();

messages.push(msg);

clients.forEach(client => {
client.send(JSON.stringify(msg));
});
},
onClose(event, ws) {
const index = clients.indexOf(ws);
if (index !== -1) {
clients.splice(index, 1);
}
},
};
})
);

app.get('/api/messages/:room', (c) => {
const room = c.req.param('room');
const messages = chatClients[room]?.messages || [];
return c.json(messages);
});

Bun.serve({
fetch: app.fetch,
port: 3001,
websocket,
});
import { Hono } from 'hono';
import { createBunWebSocket } from 'hono/bun';

import type { WSContext, WSEvents, WSMessageReceive } from "hono/ws";
const { upgradeWebSocket, websocket } = createBunWebSocket();
const app = new Hono();

type Message = {
room: string;
user: string;
text: string;
timestamp: string;
};

const chatClients: { [room: string]: { clients: WSContext[], messages: Message[] } } = {};

app.get(
'/ws',
upgradeWebSocket((c) => {
let room: string;
let clients: WSContext[] = [];
let messages: Message[] = [];

return {
onOpen(event, ws) {
room = c.req.query('room');
if (!room) {
ws.close();
return;
}

if (!chatClients[room]) {
chatClients[room] = { clients: [], messages: [] };
}
({ clients, messages } = chatClients[room]);

clients.push(ws);
},
onMessage(event) {
const msg: Message = JSON.parse(${event.data});
msg.timestamp = new Date().toISOString();

messages.push(msg);

clients.forEach(client => {
client.send(JSON.stringify(msg));
});
},
onClose(event, ws) {
const index = clients.indexOf(ws);
if (index !== -1) {
clients.splice(index, 1);
}
},
};
})
);

app.get('/api/messages/:room', (c) => {
const room = c.req.param('room');
const messages = chatClients[room]?.messages || [];
return c.json(messages);
});

Bun.serve({
fetch: app.fetch,
port: 3001,
websocket,
});
in this example, the onClose wont work, because the ws isnt the same object as in the array, how do i even identify the the websocket to remove from the array
No description
17 Replies
blurSkye πŸ‡΅πŸ‡ΈπŸ‰
@Micha also needs help with this
Micha
Michaβ€’6mo ago
@blurSkye πŸ‡΅πŸ‡ΈπŸ‰ I guess, I found a workaround. I tried to store the ws object in a local variable _ws to create a closure like behavior. _ws can then be used later on in every other event like onMessage or onClose. It doesn't seem like a general good solution, because we just omit the provided ws object in the other events. But as a workaround, it works πŸ˜„
Micha
Michaβ€’6mo ago
No description
Micha
Michaβ€’6mo ago
import { Hono } from 'hono';
import { createBunWebSocket } from 'hono/bun';
import { WSContext } from 'hono/dist/types/helper/websocket';

const { upgradeWebSocket, websocket } = createBunWebSocket();

const app = new Hono();
const webSocketClients: WSContext[] = [];

app.get('/', upgradeWebSocket(() => {
let _ws: WSContext;

return {
onOpen(_, ws: WSContext) {
_ws = ws;
webSocketClients.push(ws);
},
onMessage(event) {
webSocketClients.forEach(webSocketClient => { // broadcast
if (webSocketClient === _ws) { return; } // possible to check, if current `webSocketClient` is sender
webSocketClient.send(`${event.data}`);
});
},
onClose() {
const disconnectedWebSocketClientIndex = webSocketClients.indexOf(_ws);
if (disconnectedWebSocketClientIndex === -1) { return; }

webSocketClients.splice(disconnectedWebSocketClientIndex, 1);
}
};
}));

Bun.serve({
fetch: app.fetch,
websocket
});
import { Hono } from 'hono';
import { createBunWebSocket } from 'hono/bun';
import { WSContext } from 'hono/dist/types/helper/websocket';

const { upgradeWebSocket, websocket } = createBunWebSocket();

const app = new Hono();
const webSocketClients: WSContext[] = [];

app.get('/', upgradeWebSocket(() => {
let _ws: WSContext;

return {
onOpen(_, ws: WSContext) {
_ws = ws;
webSocketClients.push(ws);
},
onMessage(event) {
webSocketClients.forEach(webSocketClient => { // broadcast
if (webSocketClient === _ws) { return; } // possible to check, if current `webSocketClient` is sender
webSocketClient.send(`${event.data}`);
});
},
onClose() {
const disconnectedWebSocketClientIndex = webSocketClients.indexOf(_ws);
if (disconnectedWebSocketClientIndex === -1) { return; }

webSocketClients.splice(disconnectedWebSocketClientIndex, 1);
}
};
}));

Bun.serve({
fetch: app.fetch,
websocket
});
Will the local _ws object be garbage collected, when it's removed from the webSocketClients array?
Artifex
Artifexβ€’6mo ago
If you are doing it in bun maybe it's better to use the pubsub? Here is simple example.
app.get(
'pubsub',
upgradeWebSocket((c) => {
return {
onOpen(evt, ws) {
const raw = ws.raw as ServerWebSocket;
raw.subscribe('pubsub');
},
onMessage(evt, ws) {
const raw = ws.raw as ServerWebSocket;
console.log(raw.isSubscribed('pubsub'));
server.publish('pubsub', `Subscriber ${evt.data}`);
}
};
})
);

const server = serve({
port: process.env.PORT || 3000,
hostname: '0.0.0.0',
fetch: hono.fetch,
websocket
});

console.log(`Listening on ${server.hostname}:${server.port} πŸ”₯`);
app.get(
'pubsub',
upgradeWebSocket((c) => {
return {
onOpen(evt, ws) {
const raw = ws.raw as ServerWebSocket;
raw.subscribe('pubsub');
},
onMessage(evt, ws) {
const raw = ws.raw as ServerWebSocket;
console.log(raw.isSubscribed('pubsub'));
server.publish('pubsub', `Subscriber ${evt.data}`);
}
};
})
);

const server = serve({
port: process.env.PORT || 3000,
hostname: '0.0.0.0',
fetch: hono.fetch,
websocket
});

console.log(`Listening on ${server.hostname}:${server.port} πŸ”₯`);
Artifex
Artifexβ€’6mo ago
Bun
WebSockets – API | Bun Docs
Bun supports server-side WebSockets with on-the-fly compression, TLS support, and a Bun-native pubsub API.
blurSkye πŸ‡΅πŸ‡ΈπŸ‰
probably considering it is orphaned
Micha
Michaβ€’6mo ago
That's exactly what I need. Thank you very much!
blurSkye πŸ‡΅πŸ‡ΈπŸ‰
@Michashiii, cant split app routes that use websocket into a different file
46 | websocketListeners.onOpen(new Event("open"), createWSContext(ws));
47 | }
48 | },
49 | close(ws, code, reason) {
50 | const websocketListeners = websocketConns[ws.data.connId];
51 | if (websocketListeners.onClose) {
^
TypeError: undefined is not an object (evaluating 'websocketListeners.onClose')
at close (/home/desync/projectx/discord-clone/backend/node_modules/hono/dist/adapter/bun/websocket.js:51:10)
at /home/desync/projectx/discord-clone/backend/node_modules/hono/dist/adapter/bun/websocket.js:29:28
40 | };
41 | };
42 | const websocket = {
43 | open(ws) {
44 | const websocketListeners = websocketConns[ws.data.connId];
45 | if (websocketListeners.onOpen) {
^
TypeError: undefined is not an object (evaluating 'websocketListeners.onOpen')
at open (/home/desync/projectx/discord-clone/backend/node_modules/hono/dist/adapter/bun/websocket.js:45:10)
at /home/desync/projectx/discord-clone/backend/node_modules/hono/dist/adapter/bun/websocket.js:29:28
46 | websocketListeners.onOpen(new Event("open"), createWSContext(ws));
47 | }
48 | },
49 | close(ws, code, reason) {
50 | const websocketListeners = websocketConns[ws.data.connId];
51 | if (websocketListeners.onClose) {
^
TypeError: undefined is not an object (evaluating 'websocketListeners.onClose')
at close (/home/desync/projectx/discord-clone/backend/node_modules/hono/dist/adapter/bun/websocket.js:51:10)
at /home/desync/projectx/discord-clone/backend/node_modules/hono/dist/adapter/bun/websocket.js:29:28
40 | };
41 | };
42 | const websocket = {
43 | open(ws) {
44 | const websocketListeners = websocketConns[ws.data.connId];
45 | if (websocketListeners.onOpen) {
^
TypeError: undefined is not an object (evaluating 'websocketListeners.onOpen')
at open (/home/desync/projectx/discord-clone/backend/node_modules/hono/dist/adapter/bun/websocket.js:45:10)
at /home/desync/projectx/discord-clone/backend/node_modules/hono/dist/adapter/bun/websocket.js:29:28
you get this error, imagineeeeeeeeeeeeeeeeeee i had the backend entirely written, now i discovery this
Micha
Michaβ€’5mo ago
I also tried to put all websocket related stuff into its own file. I exported websocket and imported it in my main file where Bun.serve() is located. I don't know if you have the same problem, but that worked for me.
No description
blurSkye πŸ‡΅πŸ‡ΈπŸ‰
yes what if i have multiple modules using websockets i do have that 😭
Micha
Michaβ€’5mo ago
Ah I see. In this case, in don't know how that would work.
blurSkye πŸ‡΅πŸ‡ΈπŸ‰
here is a simple example(no need to do bun install, forgor to remove node modules πŸ’€ )
blurSkye πŸ‡΅πŸ‡ΈπŸ‰
@Nico sorry to mention you randomly, i think you are a hono js developer? how does websocket module spliting work? it appears we cant split code that use websocket into a different file
Nico
Nicoβ€’5mo ago
You can always ping me. I’m not apart of the Hono team, I just moderate on here and help contribute. I’m not too familiar with websockets on Hono I’ve only used them once. But I can give it a try later today. Also try asking on GitHub discussions, the core team checks that more often
blurSkye πŸ‡΅πŸ‡ΈπŸ‰
ok found a way, damn how did i not think of this πŸ’€
import { createBunWebSocket, } from 'hono/bun'
import type { UpgradeWebSocket } from './node_modules/hono/dist/types/helper/websocket';
interface BunServerWebSocket<T> {
send(data: string | ArrayBufferLike, compress?: boolean): void;
close(code?: number, reason?: string): void;
data: T;
readyState: 0 | 1 | 2 | 3;
}
interface BunWebSocketHandler<T> {
open(ws: BunServerWebSocket<T>): void;
close(ws: BunServerWebSocket<T>, code?: number, reason?: string): void;
message(ws: BunServerWebSocket<T>, message: string | Uint8Array): void;
}

interface BunWebSocketData {
connId: number;
url: URL;
protocol: string;
}

const { upgradeWebSocket, websocket }: { upgradeWebSocket: UpgradeWebSocket, websocket: BunWebSocketHandler<BunWebSocketData> } = createBunWebSocket();

export { upgradeWebSocket, websocket };
import { createBunWebSocket, } from 'hono/bun'
import type { UpgradeWebSocket } from './node_modules/hono/dist/types/helper/websocket';
interface BunServerWebSocket<T> {
send(data: string | ArrayBufferLike, compress?: boolean): void;
close(code?: number, reason?: string): void;
data: T;
readyState: 0 | 1 | 2 | 3;
}
interface BunWebSocketHandler<T> {
open(ws: BunServerWebSocket<T>): void;
close(ws: BunServerWebSocket<T>, code?: number, reason?: string): void;
message(ws: BunServerWebSocket<T>, message: string | Uint8Array): void;
}

interface BunWebSocketData {
connId: number;
url: URL;
protocol: string;
}

const { upgradeWebSocket, websocket }: { upgradeWebSocket: UpgradeWebSocket, websocket: BunWebSocketHandler<BunWebSocketData> } = createBunWebSocket();

export { upgradeWebSocket, websocket };
you need a file that defines websocket, and import upgradeWebSocket in the routes and import websocket to serve in the main.ts file one of the interfaces needed for websocket wasnt exported sooo i copied all the interfaces needed πŸ’€
Micha
Michaβ€’5mo ago
Nice thanks
Want results from more Discord servers?
Add your server