forked from partykit/partykit-nextjs-chat-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchatRooms.ts
127 lines (109 loc) · 3.69 KB
/
chatRooms.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import type * as Party from "partykit/server";
import { User } from "./utils/auth";
import { json, notFound } from "./utils/response";
/**
* The chatRooms party's purpose is to keep track of all chat rooms, so we want
* every client to connect to the same room instance by sharing the same room id.
*/
export const SINGLETON_ROOM_ID = "list";
/** Chat room sends an update when participants join/leave */
export type RoomInfoUpdateRequest = {
id: string;
connections: number;
action: "enter" | "leave";
user?: User;
};
/** Chat room notifies us when it's deleted */
export type RoomDeleteRequest = {
id: string;
action: "delete";
};
/** Chat rooms sends us information about connections and users */
export type RoomInfo = {
id: string;
connections: number;
users: {
username: string;
joinedAt: string;
leftAt?: string;
present: boolean;
image?: string;
}[];
};
export default class ChatRoomsServer implements Party.Server {
options: Party.ServerOptions = {
hibernate: true,
// this opts the chat room into hibernation mode, which
// allows for a higher number of concurrent connections
};
constructor(public party: Party.Party) {}
async onConnect(connection: Party.Connection) {
// when a websocket connection is established, send them a list of rooms
connection.send(JSON.stringify(await this.getActiveRooms()));
}
async onRequest(req: Party.Request) {
// we only allow one instance of chatRooms party
if (this.party.id !== SINGLETON_ROOM_ID) return notFound();
// Clients fetch list of rooms for server rendering pages via HTTP GET
if (req.method === "GET") return json(await this.getActiveRooms());
// Chatrooms report their connections via HTTP POST
// update room info and notify all connected clients
if (req.method === "POST") {
const roomList = await this.updateRoomInfo(req);
this.party.broadcast(JSON.stringify(roomList));
return json(roomList);
}
// admin api for clearing all rooms (not used in UI)
if (req.method === "DELETE") {
await this.party.storage.deleteAll();
return json({ message: "All room history cleared" });
}
return notFound();
}
/** Fetches list of active rooms */
async getActiveRooms(): Promise<RoomInfo[]> {
const rooms = await this.party.storage.list<RoomInfo>();
return [...rooms.values()];
}
/** Updates list of active rooms with information received from chatroom */
async updateRoomInfo(req: Party.Request) {
const update = (await req.json()) as
| RoomInfoUpdateRequest
| RoomDeleteRequest;
if (update.action === "delete") {
await this.party.storage.delete(update.id);
return this.getActiveRooms();
}
const persistedInfo = await this.party.storage.get<RoomInfo>(update.id);
if (!persistedInfo && update.action === "leave") {
return this.getActiveRooms();
}
const info = persistedInfo ?? {
id: update.id,
connections: 0,
users: [],
};
info.connections = update.connections;
const user = update.user;
if (user) {
if (update.action === "enter") {
// bump user to the top of the list on entry
info.users = info.users.filter((u) => u.username !== user.username);
info.users.unshift({
username: user.username,
image: user.image,
joinedAt: new Date().toISOString(),
present: true,
});
} else {
info.users = info.users.map((u) =>
u.username === user.username
? { ...u, present: false, leftAt: new Date().toISOString() }
: u
);
}
}
await this.party.storage.put(update.id, info);
return this.getActiveRooms();
}
}