decentralised message store
1import type { WebSocket } from "ws"; 2import * as crypto from "node:crypto"; 3import { SESSIONS_SECRET } from "@/lib/utils/crypto"; 4import z from "zod"; 5 6export const sessionInfoSchema = z.object({ 7 id: z.string(), 8 token: z.string(), 9 fingerprint: z.string(), 10}); 11export type SessionInfo = z.infer<typeof sessionInfoSchema>; 12 13export type Session = Map<string, WebSocket>; 14 15export const sessions: Session = new Map<string, WebSocket>(); 16 17export const generateSessionId = () => { 18 return crypto.randomUUID(); 19}; 20 21export const generateSessionInfo = (sessionId: string): SessionInfo => { 22 const token = crypto.randomBytes(32).toString("base64url"); 23 24 const hmac = crypto.createHmac("sha256", SESSIONS_SECRET); 25 hmac.update(`${token}:${sessionId}`); 26 const fingerprint = hmac.digest("hex"); 27 28 return { id: sessionId, token, fingerprint }; 29}; 30 31export const verifySessionToken = ({ 32 token, 33 fingerprint, 34 id: sessionId, 35}: SessionInfo) => { 36 const hmac = crypto.createHmac("sha256", SESSIONS_SECRET); 37 hmac.update(`${token}:${sessionId}`); 38 const expectedFingerprint = hmac.digest("hex"); 39 40 try { 41 return crypto.timingSafeEqual( 42 Buffer.from(fingerprint, "hex"), 43 Buffer.from(expectedFingerprint, "hex"), 44 ); 45 } catch { 46 return false; 47 } 48}; 49 50export const assignSessionTo = ({ 51 sessionInfo, 52 socket, 53}: { 54 sessionInfo: SessionInfo; 55 socket: WebSocket; 56}) => { 57 try { 58 const sessionId = sessionInfo.id; 59 sessions.set(sessionId, socket); 60 return { ok: true }; 61 } catch (err: unknown) { 62 return { ok: false, err }; 63 } 64}; 65 66export const dropSession = (sessionId: string) => { 67 try { 68 if (!sessions.has(sessionId)) 69 return { 70 ok: false, 71 err: new Error( 72 `Could not find a session socket for id ${sessionId} `, 73 ), 74 }; 75 const session = sessions.get(sessionId); 76 if (!session) 77 return { 78 ok: false, 79 err: new Error( 80 "`sessionId` key exists, but could not get the session socket for some reason?", 81 ), 82 }; 83 session.close(); 84 sessions.delete(sessionId); 85 return { ok: true }; 86 } catch (err: unknown) { 87 return { ok: false, err }; 88 } 89};