Skip to content

Commit b170f30

Browse files
committed
fix: all crashes in main event handlers
also makes client.socket public, do note I'm planning to rename it to client._socket in the future
1 parent 3294b7b commit b170f30

File tree

2 files changed

+143
-105
lines changed

2 files changed

+143
-105
lines changed

src/eventHandlers.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import type { ServerToClientEventsWithReserved } from "./types/socket.io.js";
2+
import type Client from "./index.js";
3+
4+
import { transformMessage, escapeName, transformSysMessage, transformNickChangeInfo, transformUser } from "./utils/transforms.js";
5+
6+
export type EventHandlersObject = {
7+
[K in keyof ServerToClientEventsWithReserved]?: EventHandlerFunction<K>;
8+
};
9+
10+
export type EventHandlerArguments<K extends keyof ServerToClientEventsWithReserved = keyof ServerToClientEventsWithReserved>
11+
= Parameters<ServerToClientEventsWithReserved[K]>;
12+
export type EventHandlerFunction<K extends keyof ServerToClientEventsWithReserved = keyof ServerToClientEventsWithReserved>
13+
= (this: Client, ...args: [ ...EventHandlerArguments<K>, (name: string) => void ]) => void;
14+
15+
const mainEventHandlers: EventHandlersObject = {
16+
werror(reason) {
17+
this.emit("werror", reason);
18+
},
19+
20+
message(rawMessage) {
21+
let message = transformMessage(rawMessage, this.users, this.unescapeMessages);
22+
23+
if (this.isBlocked(message.author.ID, message.author.sessionID, false)) return;
24+
25+
const messageFromSocialMediaBridgeMatch = /^\[(\w+)\] <strong>(\w+) \((\d+)\)<\/strong>:\n(.*)/.exec(message.content);
26+
if (messageFromSocialMediaBridgeMatch) {
27+
const [ , socialMediaApp, name, ID, extractedMessage ] = messageFromSocialMediaBridgeMatch;
28+
const originalMessage = message;
29+
const socialMediaUser = {
30+
name,
31+
ID,
32+
};
33+
const bridgedID = `BRIDGED-BY:${originalMessage.author.ID}-FROM:${socialMediaApp}-${socialMediaUser.name}-${socialMediaUser.ID}`;
34+
message = {
35+
type : message.type,
36+
color : message.color,
37+
content: extractedMessage,
38+
date : message.date,
39+
author : {
40+
color : message.author.color,
41+
flags : [],
42+
ID : bridgedID,
43+
sessionID : `${bridgedID}-0`,
44+
nickname : name,
45+
escapedName: escapeName(name),
46+
},
47+
bridged: {
48+
originalMessage,
49+
socialMediaApp,
50+
socialMediaUser,
51+
},
52+
};
53+
}
54+
55+
if (this.isBlocked(message.author)) return;
56+
57+
this.emit("message", message);
58+
void this.processCommands({
59+
message,
60+
send : (...args) => void this.sendMessage(...args),
61+
reply: (...args) => void this.sendMessage(`**@${message.author.escapedName}**`, ...args),
62+
});
63+
},
64+
65+
"sys-message"(rawSysMessage) {
66+
const sysMessage = transformSysMessage(rawSysMessage);
67+
this.emit("sys-message", sysMessage);
68+
//@ts-expect-error Don't worry, it's fine. Think about it, you'll understand.
69+
this.emit(`sys-message-${sysMessage.type}`, sysMessage);
70+
},
71+
72+
"nick-changed"(rawNickChangeInfo, setCachedName) {
73+
const nickChangeInfo = transformNickChangeInfo(rawNickChangeInfo, this.users);
74+
75+
if (nickChangeInfo.user.sessionID === this.sessionID) setCachedName(nickChangeInfo.newNickname);
76+
77+
nickChangeInfo.user.nickname = nickChangeInfo.newNickname;
78+
nickChangeInfo.user.escapedName = escapeName(nickChangeInfo.newNickname);
79+
80+
if (this.isBlocked(nickChangeInfo.user)) return;
81+
this.emit("nick-change", nickChangeInfo);
82+
},
83+
84+
"user-join"(rawUser) {
85+
const user = transformUser(rawUser);
86+
this.users[user.sessionID] = user;
87+
88+
if (this.isBlocked(user)) return;
89+
this.emit("user-join", user);
90+
},
91+
92+
"user-leave"(userLeaveInfo) {
93+
const user = this.users[userLeaveInfo.session_id];
94+
95+
if (!this.isBlocked(user)) this.emit("user-leave", user);
96+
97+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
98+
delete this.users[userLeaveInfo.session_id];
99+
},
100+
101+
"user-update"(userUpdateInfo) {
102+
const user = this.users[userUpdateInfo.user];
103+
104+
switch (userUpdateInfo.type) {
105+
case "tag-add":
106+
if (!userUpdateInfo.tag?.trim() || !userUpdateInfo.tagLabel) return;
107+
108+
if (!user.flags.includes(userUpdateInfo.tag)) user.flags.push(userUpdateInfo.tag);
109+
110+
if (this.isBlocked(user)) return;
111+
this.emit("tag-add", {
112+
user,
113+
newTag : userUpdateInfo.tag,
114+
newTagLabel: userUpdateInfo.tagLabel,
115+
});
116+
break;
117+
118+
default:
119+
console.error("Invalid userUpdateInfo type", userUpdateInfo);
120+
break;
121+
}
122+
},
123+
};
124+
125+
export function applyMainEventHandlers(client: Client, setCachedName: (name: string) => void): void {
126+
for (const [ event, handler ] of Object.entries(mainEventHandlers) as [
127+
keyof EventHandlersObject,
128+
EventHandlerFunction,
129+
][]) {
130+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
131+
client.socket!.on(event, (...args: EventHandlerArguments) => {
132+
try {
133+
handler.call(client, ...args, setCachedName);
134+
} catch (error) {
135+
console.error(`An error occurred while handling ${event}. Please report this problem to the server owner.`, error);
136+
}
137+
});
138+
}
139+
}

src/index.ts

Lines changed: 4 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ import type {
1818
} from "#types";
1919

2020
import { AuthError, ConnectionError, NotConnectedError } from "./errors.js";
21-
import { escapeName, normalizeCommand, transformMessage, transformNickChangeInfo, transformSysMessage, transformUser, trimMessage } from "#utils/transforms.js";
21+
import { normalizeCommand, transformUser, trimMessage } from "#utils/transforms.js";
2222
import helpCommand from "./helpCommand.js";
23+
import { applyMainEventHandlers } from "./eventHandlers.js";
2324

2425
let sheeshBots: string[] = [];
2526
setInterval( () => {
@@ -34,7 +35,7 @@ setInterval( () => {
3435
}, 10 * 60 * 1000);
3536

3637
export default class Client extends (EventEmitter as unknown as new () => TypedEmitter.default<ClientEvents>) {
37-
private socket?: MsgroomSocket;
38+
socket?: MsgroomSocket;
3839
#name: string;
3940
server: string;
4041

@@ -154,109 +155,7 @@ export default class Client extends (EventEmitter as unknown as new () => TypedE
154155
resolve();
155156
});
156157
//#endregion connecting to the server
157-
}).then( () => {
158-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
159-
this.socket!
160-
161-
//#region main events
162-
.on("werror", reason => {
163-
this.emit("werror", reason);
164-
})
165-
.on("message", rawMessage => {
166-
let message = transformMessage(rawMessage, this.users, this.unescapeMessages);
167-
168-
if (this.isBlocked(message.author.ID, message.author.sessionID, false)) return;
169-
170-
const messageFromSocialMediaBridgeMatch = /^\[(\w+)\] <strong>(\w+) \((\d+)\)<\/strong>:\n(.*)/.exec(message.content);
171-
if (messageFromSocialMediaBridgeMatch) {
172-
const [ , socialMediaApp, name, ID, extractedMessage ] = messageFromSocialMediaBridgeMatch;
173-
const originalMessage = message;
174-
const socialMediaUser = {
175-
name,
176-
ID,
177-
};
178-
const bridgedID = `BRIDGED-BY:${originalMessage.author.ID}-FROM:${socialMediaApp}-${socialMediaUser.name}-${socialMediaUser.ID}`;
179-
message = {
180-
type : message.type,
181-
color : message.color,
182-
content: extractedMessage,
183-
date : message.date,
184-
author : {
185-
color : message.author.color,
186-
flags : [],
187-
ID : bridgedID,
188-
sessionID : `${bridgedID}-0`,
189-
nickname : name,
190-
escapedName: escapeName(name),
191-
},
192-
bridged: {
193-
originalMessage,
194-
socialMediaApp,
195-
socialMediaUser,
196-
},
197-
};
198-
}
199-
200-
if (this.isBlocked(message.author)) return;
201-
202-
this.emit("message", message);
203-
void this.processCommands({
204-
message,
205-
send : (...args) => void this.sendMessage(...args),
206-
reply: (...args) => void this.sendMessage(`**@${message.author.escapedName}**`, ...args),
207-
});
208-
})
209-
.on("sys-message", rawSysMessage => {
210-
const sysMessage = transformSysMessage(rawSysMessage);
211-
this.emit("sys-message", sysMessage);
212-
//@ts-expect-error Don't worry, it's fine. Think about it, you'll understand.
213-
this.emit(`sys-message-${sysMessage.type}`, sysMessage);
214-
})
215-
.on("nick-changed", rawNickChangeInfo => {
216-
const nickChangeInfo = transformNickChangeInfo(rawNickChangeInfo, this.users);
217-
218-
if (nickChangeInfo.user.sessionID == this.sessionID) this.#name = nickChangeInfo.newNickname;
219-
220-
nickChangeInfo.user.nickname = nickChangeInfo.newNickname;
221-
nickChangeInfo.user.escapedName = escapeName(nickChangeInfo.newNickname);
222-
223-
if (this.isBlocked(nickChangeInfo.user)) return;
224-
this.emit("nick-change", nickChangeInfo);
225-
})
226-
.on("user-join", rawUser => {
227-
const user = transformUser(rawUser);
228-
this.users[user.sessionID] = user;
229-
230-
if (this.isBlocked(user)) return;
231-
this.emit("user-join", user);
232-
})
233-
.on("user-leave", userLeaveInfo => {
234-
const user = this.users[userLeaveInfo.session_id];
235-
236-
if (!this.isBlocked(user)) this.emit("user-leave", user);
237-
238-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
239-
delete this.users[userLeaveInfo.session_id];
240-
})
241-
.on("user-update", userUpdateInfo => {
242-
const user = this.users[userUpdateInfo.user];
243-
244-
switch (userUpdateInfo.type) {
245-
case "tag-add":
246-
if (!userUpdateInfo.tag?.trim() || !userUpdateInfo.tagLabel) return;
247-
248-
if (!user.flags.includes(userUpdateInfo.tag)) user.flags.push(userUpdateInfo.tag);
249-
250-
if (this.isBlocked(user)) return;
251-
this.emit("tag-add", {
252-
user,
253-
newTag : userUpdateInfo.tag,
254-
newTagLabel: userUpdateInfo.tagLabel,
255-
});
256-
}
257-
});
258-
//#endregion main events
259-
});
158+
}).then( () => void applyMainEventHandlers(this, name => this.#name = name));
260159
}
261160

262161
/**

0 commit comments

Comments
 (0)