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};