Skip to content

Commit 28369e4

Browse files
committed
feat: use SSE
1 parent 206b029 commit 28369e4

File tree

10 files changed

+174
-147
lines changed

10 files changed

+174
-147
lines changed

common/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// common/type/types.ts
22

3+
import type { Message, UserID } from "./zod/types.ts";
4+
35
export type {
46
UserID,
57
GUID,
@@ -34,3 +36,17 @@ export type {
3436
InitSharedRoom,
3537
UpdateRoom,
3638
} from "./zod/types.ts";
39+
40+
export type SSEChatEvents = {
41+
"Chat:Append": {
42+
message: Message;
43+
};
44+
"Chat:Update": {
45+
id: number;
46+
message: Message;
47+
};
48+
"Chat:Delete": {
49+
id: number;
50+
};
51+
"Chat:Ping": "";
52+
};

server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "src/index.ts",
66
"scripts": {
77
"prepare": "bun --env-file=./.env.dev prisma generate --sql || (echo 'please set DATABASE_URL in server/.env.dev' && exit 1)",
8-
"dev": "bun --watch src/main.ts",
8+
"dev": "bun --watch src/index.ts",
99
"build": "tsc",
1010
"serve": "bun target/main.js"
1111
},

server/src/index.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { Hono } from "hono";
22
import { cors } from "hono/cors";
33
import { HTTPException } from "hono/http-exception";
4-
import { initializeSocket } from "./lib/socket/socket";
54
import { allUrlMustBeValid, env } from "./lib/utils";
65
import chatRoutes from "./router/chat";
76
import coursesRoutes from "./router/courses";
87
import matchesRoutes from "./router/matches";
98
import pictureRoutes from "./router/picture";
109
import requestsRoutes from "./router/requests";
10+
import sseRoutes from "./router/sse";
1111
import subjectsRoutes from "./router/subjects";
1212
import usersRoutes from "./router/users";
1313

@@ -30,11 +30,10 @@ if (corsOptions.origin.length > 1) {
3030
const app = new Hono()
3131
.onError((err, c) => {
3232
if (err instanceof HTTPException) {
33-
c.status(err.status);
34-
return c.json({ error: err });
33+
throw err;
3534
}
36-
c.status(500);
37-
return c.json({ error: err });
35+
console.log(err);
36+
return c.json({ error: err }, 500);
3837
})
3938

4039
.use(cors(corsOptions))
@@ -49,14 +48,7 @@ const app = new Hono()
4948
.route("/subjects", subjectsRoutes)
5049
.route("/requests", requestsRoutes)
5150
.route("/matches", matchesRoutes)
52-
.route("/chat", chatRoutes);
51+
.route("/chat", chatRoutes)
52+
.route("/sse", sseRoutes);
5353

54-
export function main() {
55-
const server = Bun.serve({
56-
fetch: app.fetch,
57-
port: process.env.PORT ?? 3000,
58-
});
59-
// ??
60-
return initializeSocket(app, corsOptions);
61-
}
6254
export default app;

server/src/lib/socket/socket.ts

Lines changed: 0 additions & 79 deletions
This file was deleted.

server/src/main.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

server/src/router/chat.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import { z } from "zod";
1111
import * as db from "../database/chat";
1212
import { getUserId } from "../firebase/auth/db";
1313
import * as core from "../functions/chat";
14-
import * as ws from "../lib/socket/socket";
1514
import { json, param } from "../lib/validator";
15+
import * as sse from "../router/sse";
1616

17-
const userid_param = param({ userid: z.number() });
17+
const userid_param = param({ userid: z.coerce.number() });
1818
const router = new Hono()
1919
.get("/overview", async (c) => {
2020
const id = await getUserId(c);
@@ -29,9 +29,13 @@ const router = new Hono()
2929
const send = c.req.valid("json");
3030
const result = await core.sendDM(user, friend, send);
3131
if (result.ok) {
32-
ws.sendMessage(result.body, friend);
32+
sse.send(friend, {
33+
event: "Chat:Append",
34+
data: { message: result.body },
35+
});
3336
}
34-
return c.json(result.body); // status: result.code
37+
c.status(result.code);
38+
return c.json(result.body);
3539
})
3640

3741
// GET a DM Room with userId, CREATE one if not found.
@@ -118,7 +122,13 @@ const router = new Hono()
118122

119123
const result = await core.updateMessage(user, id, content);
120124
if (result.ok) {
121-
ws.updateMessage(result.body, friend);
125+
sse.send(friend, {
126+
event: "Chat:Update",
127+
data: {
128+
id: result.body.id,
129+
message: result.body,
130+
},
131+
});
122132
}
123133
c.status(result.code);
124134
return c.json(result.body);
@@ -134,7 +144,12 @@ const router = new Hono()
134144
const id = c.req.valid("param").id;
135145
const friend = c.req.valid("json").friend;
136146
await db.deleteMessage(id as MessageID, user);
137-
ws.deleteMessage(id, friend);
147+
sse.send(friend, {
148+
event: "Chat:Delete",
149+
data: {
150+
id,
151+
},
152+
});
138153
c.status(204);
139154
return c.json({});
140155
},

server/src/router/sse.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { SSEChatEvents, UserID } from "common/types";
2+
import { Hono } from "hono";
3+
import { HTTPException } from "hono/http-exception";
4+
import { streamSSE } from "hono/streaming";
5+
import { getUserIdFromToken } from "../firebase/auth/db";
6+
7+
export const sseChatPath = (id: UserID) => `sse:chat:${id}`;
8+
type SSEChatEventEnum = "Chat:Append" | "Chat:Update" | "Chat:Delete";
9+
type SSEChatEvent<T extends SSEChatEventEnum> = {
10+
event: T;
11+
data: SSEChatEvents[T];
12+
};
13+
export function send<T extends SSEChatEventEnum>(
14+
to: UserID,
15+
event: SSEChatEvent<T>,
16+
) {
17+
const bc = new BroadcastChannel(sseChatPath(to));
18+
bc.postMessage(event);
19+
bc.close();
20+
}
21+
22+
// https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API
23+
// https://hono.dev/docs/helpers/streaming
24+
const route = new Hono().get("/chat", async (c) => {
25+
const token = c.req.query("token");
26+
if (!token)
27+
throw new HTTPException(400, {
28+
message: "token required in param",
29+
});
30+
31+
const userId = await getUserIdFromToken(token);
32+
return streamSSE(c, async (stream) => {
33+
const bc = new BroadcastChannel(sseChatPath(userId));
34+
bc.onmessage = (e: MessageEvent) => {
35+
const event = e.data as SSEChatEvent<SSEChatEventEnum>;
36+
stream.writeSSE({
37+
event: event.event,
38+
data: JSON.stringify(event.data),
39+
});
40+
};
41+
42+
stream.onAbort(() => {
43+
bc.close();
44+
});
45+
while (true) {
46+
await Bun.sleep(2000);
47+
stream.writeSSE({
48+
event: "Ping",
49+
data: "",
50+
});
51+
}
52+
});
53+
});
54+
55+
export default route;

web/app/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import "@fontsource/roboto/700.css";
1111
import BanLandscape from "~/components/BanLandscape";
1212
import { AlertProvider } from "~/components/common/alert/AlertProvider";
1313
import { ModalProvider } from "~/components/common/modal/ModalProvider";
14+
import SSEProvider from "~/components/data/socket";
1415
import AuthProvider from "~/firebase/auth/AuthProvider";
1516

1617
const theme = createTheme({
@@ -50,7 +51,7 @@ export default function RootLayout({
5051
<AlertProvider>
5152
<ModalProvider>
5253
<BanLandscape />
53-
{children}
54+
<SSEProvider>{children}</SSEProvider>
5455
</ModalProvider>
5556
</AlertProvider>
5657
</AuthProvider>

web/components/data/socket.ts

Lines changed: 0 additions & 43 deletions
This file was deleted.

0 commit comments

Comments
 (0)