Spaces:
Paused
Paused
Download apps/api/src/core/ws.js from nenbuj/we-superapp: direct link, hf CLI and curl.
- Browser
- Download file 2.77 kB
-
https://huggingface.co/spaces/nenbuj/we-superapp/resolve/main/apps/api/src/core/ws.js
- Command line
-
hf download hf://spaces/nenbuj/we-superapp/apps/api/src/core/ws.js
-
curl -L -o ws.js https://huggingface.co/spaces/nenbuj/we-superapp/resolve/main/apps/api/src/core/ws.js
2.77 kB
| // Ядро: WebSocket-слой + presence. Ведёт Claude. | |
| // Клиент: ws://localhost:3000/ws?token=<jwt>, затем шлёт { type:'subscribe', space_id }. | |
| // Сервер ретранслирует события bus.emit(space_id, event) всем подписчикам пространства. | |
| import { verifyToken } from './auth.js'; | |
| import { db } from './db.js'; | |
| import { bus } from './bus.js'; | |
| // space_id -> Set<connection state> | |
| const rooms = new Map(); | |
| function presenceOf(spaceId) { | |
| const set = rooms.get(spaceId); | |
| if (!set) return []; | |
| return [...new Set([...set].map((c) => c.userId))]; | |
| } | |
| function broadcastPresence(spaceId) { | |
| const online = presenceOf(spaceId); | |
| send(spaceId, { type: 'presence', space_id: spaceId, payload: { online } }); | |
| } | |
| function send(spaceId, event) { | |
| const set = rooms.get(spaceId); | |
| if (!set) return; | |
| const data = JSON.stringify(event); | |
| for (const c of set) { | |
| if (c.socket.readyState === 1) c.socket.send(data); | |
| } | |
| } | |
| export function registerWs(fastify) { | |
| fastify.get('/ws', { websocket: true }, (socket, req) => { | |
| const url = new URL(req.url, 'http://localhost'); | |
| const token = url.searchParams.get('token'); | |
| const decoded = token ? verifyToken(token) : null; | |
| if (!decoded) { | |
| socket.close(4001, 'unauthorized'); | |
| return; | |
| } | |
| const conn = { socket, userId: decoded.id, spaceId: null, unsub: null }; | |
| socket.on('message', (raw) => { | |
| let msg; | |
| try { | |
| msg = JSON.parse(raw.toString()); | |
| } catch { | |
| return; | |
| } | |
| if (msg.type === 'subscribe' && msg.space_id) { | |
| // проверяем членство | |
| const member = db | |
| .prepare('SELECT id FROM space_members WHERE space_id = ? AND user_id = ?') | |
| .get(msg.space_id, conn.userId); | |
| if (!member) { | |
| socket.send(JSON.stringify({ type: 'error', payload: { code: 'FORBIDDEN' } })); | |
| return; | |
| } | |
| leave(conn); | |
| join(conn, msg.space_id); | |
| } | |
| }); | |
| socket.on('close', () => leave(conn)); | |
| }); | |
| } | |
| function join(conn, spaceId) { | |
| conn.spaceId = spaceId; | |
| if (!rooms.has(spaceId)) rooms.set(spaceId, new Set()); | |
| rooms.get(spaceId).add(conn); | |
| // подписка на события пространства из шины | |
| conn.unsub = bus.onSpace(spaceId, (event) => { | |
| if (conn.socket.readyState === 1) conn.socket.send(JSON.stringify(event)); | |
| }); | |
| broadcastPresence(spaceId); | |
| } | |
| function leave(conn) { | |
| if (conn.unsub) { | |
| conn.unsub(); | |
| conn.unsub = null; | |
| } | |
| const set = conn.spaceId && rooms.get(conn.spaceId); | |
| if (set) { | |
| set.delete(conn); | |
| if (set.size === 0) rooms.delete(conn.spaceId); | |
| else broadcastPresence(conn.spaceId); | |
| } | |
| conn.spaceId = null; | |
| } | |