only allow one client to connect at a time

Controversial change, but offloading the problem of being a bouncer onto bouncers is a better idea
This commit is contained in:
emerson 2022-10-18 18:54:26 -04:00
parent dd054af414
commit d4e3137d93
2 changed files with 81 additions and 127 deletions

View file

@ -7,7 +7,7 @@ import { MatrixUser } from './MatrixUser.js';
import { IRCMessage, parseIRCMessage } from './Message.js';
import { Server } from './Server.js';
export abstract class Client {
export class Client {
capVersion: string
enabledCaps: Map<string, string>
allCaps: Map<string, string>
@ -839,12 +839,12 @@ export abstract class Client {
this.writeMessage(`${msgToSend}\r\n`);
}
abstract writeMessage(message: string): void;
writeMessage(message: string): void {};
closeConnectionWithError(message: string) {
this.sendMessage(this.server.name, 'ERROR', [message], new Map());
this.closeConnection();
}
abstract closeConnection(): void;
closeConnection(): void {};
}

View file

@ -16,7 +16,7 @@ export class Server {
private directMessages: Set<string>
private matrixUsers: Map<string, MatrixUser>
public ourMatrixUser: MatrixUser;
private clients: Set<Client>
private client: Client
public nickToMatrixUser: Map<string, MatrixUser>
public eventIdStore: Map<string, Client>
public eventIDToLabel: Map<string, string>
@ -41,7 +41,7 @@ export class Server {
this.directMessages = new Set();
this.matrixUsers = new Map();
this.nickToMatrixUser = new Map();
this.clients = new Set();
this.client = new Client(this);
this.eventIdStore = new Map();
this.eventIDToLabel = new Map();
this.nextBatch = "";
@ -139,7 +139,7 @@ export class Server {
this.invitedChannels.delete(targetChannel.roomId);
this.channels.set(targetChannel.name, targetChannel);
this.roomIdToChannel.set(targetChannel.roomId, targetChannel);
this.clients.forEach(c => this.joinNewIRCClient(c, targetChannel));
this.joinNewIRCClient(this.client, targetChannel);
if (this.initialSync === false && this.syncLocks.size === 0) {
this.initialSync = true;
this.doLog('Synced to network!');
@ -179,10 +179,6 @@ export class Server {
return this.ourMatrixUser.getMask();
}
getClients(): Set<Client> {
return this.clients;
}
joinNewIRCClient(client: Client, targetChannel: Channel) {
if (client.enabledCaps.has('extended-join')) {
client.sendMessage(this.getMask(), "JOIN", [targetChannel.name, this.ourMatrixUser.accountName, this.mxid], new Map([['account', this.ourMatrixUser.realname]]));
@ -198,18 +194,24 @@ export class Server {
targetChannel.matrixUsers.set(sourceUser.nick, sourceUser);
const prefix = sourceUser.getMask();
const joinTags = new Map([["account", sourceUser.accountName], ['time', new Date(event["origin_server_ts"]).toISOString()]])
this.sendToAllWithCap('extended-join', prefix, "JOIN", [targetChannel.name, sourceUser.accountName, sourceUser.realname], joinTags);
this.sendToAllWithoutCap('extended-join', prefix, "JOIN", [targetChannel.name], joinTags);
if (this.client.enabledCaps.has('extended-join')) {
this.client.sendMessage(prefix, "JOIN", [targetChannel.name, sourceUser.accountName, sourceUser.realname], joinTags);
} else {
this.client.sendMessage(prefix, "JOIN", [targetChannel.name], joinTags);
}
}
}
addClient(client: Client) {
this.clients.add(client);
if (this.client.isRegistered) {
this.client.closeConnectionWithError("New client connected");
}
this.client = client;
if (this.initialSync) {
for (const channel of this.channels.values()) {
this.joinNewIRCClient(client, channel);
}
this.invitedChannels.forEach(roomId => this.sendToAll(this.name, "INVITE", [this.ourMatrixUser.nick, roomId], new Map()));
this.invitedChannels.forEach(roomId => this.client.sendMessage(this.name, "INVITE", [this.ourMatrixUser.nick, roomId], new Map()));
}
}
@ -311,10 +313,14 @@ export class Server {
return;
}
if (presence === 'online') {
this.sendToAllWithCap('away-notify', matrixUser.getMask(), 'AWAY', []);
if (this.client.enabledCaps.has('away-notify')) {
this.client.sendMessage(matrixUser.getMask(), 'AWAY', []);
}
} else {
const awayMessage = (status === '') ? "user is away" : status;
this.sendToAllWithCap('away-notify', matrixUser.getMask(), 'AWAY', [awayMessage]);
if (this.client.enabledCaps.has('away-notify')) {
const awayMessage = (status === '') ? "user is away" : status;
this.client.sendMessage(matrixUser.getMask(), 'AWAY', [awayMessage]);
}
}
}
@ -336,7 +342,9 @@ export class Server {
if (this.eventIDToLabel.has(event["event_id"])) {
tags.set("label", this.eventIDToLabel.get(event["event_id"]) || "")
}
this.sendToAllWithCap("message-tags", sourceUser.getMask(), "TAGMSG", [targetChannel.name], tags)
if (this.client.enabledCaps.has('message-tags')) {
this.client.sendMessage(sourceUser.getMask(), "TAGMSG", [targetChannel.name], tags)
}
}
updateRoomName(newNameEvent: any, targetChannel: Channel) {
@ -349,15 +357,13 @@ export class Server {
this.channels.delete(oldName);
targetChannel.name = newName;
this.channels.set(newName, targetChannel);
this.clients.forEach(client => {
if (client.enabledCaps.has("draft/channel-rename")) {
client.sendMessage(this.name, "RENAME", [oldName, targetChannel.name, "New channel name set"], new Map());
}
else {
client.sendMessage(this.getMask(), "PART", [oldName, `Renaming channel to ${newName}`], new Map());
this.joinNewIRCClient(client, targetChannel);
}
})
if (this.client.enabledCaps.has("draft/channel-rename")) {
this.client.sendMessage(this.name, "RENAME", [oldName, targetChannel.name, "New channel name set"], new Map());
}
else {
this.client.sendMessage(this.getMask(), "PART", [oldName, `Renaming channel to ${newName}`], new Map());
this.joinNewIRCClient(this.client, targetChannel);
}
}
handleEncryptedMessage(event: any, targetChannel: Channel) {
@ -367,9 +373,7 @@ export class Server {
messageTags.set('msgid', event["event_id"]);
messageTags.set('time', new Date(event["origin_server_ts"]).toISOString());
messageTags.set('account', sourceUser.accountName);
this.clients.forEach((channel) => {
channel.sendMessage(sourceUser.getMask(), 'NOTICE', [targetChannel.name, "Sent an encrypted message, use another client to view"], messageTags);
});
this.client.sendMessage(sourceUser.getMask(), 'NOTICE', [targetChannel.name, "Sent an encrypted message, use another client to view"], messageTags);
}
handleMatrixGuestAccess(event: any, targetChannel: Channel) {
@ -412,15 +416,13 @@ export class Server {
messageTags.set('account', sourceUser.accountName);
if (membershipStatus === "invite") {
const reason = content["reason"];
this.clients.forEach(c => {
if (c.enabledCaps.has('invite-notify')) {
if (c.enabledCaps.has('reflectionircd.chat/extended-invite')) {
c.sendMessage(sourceUser.getMask(), 'INVITE', [targetUser.nick, targetChannel.name, reason], messageTags)
} else {
c.sendMessage(sourceUser.getMask(), 'INVITE', [targetUser.nick, targetChannel.name], messageTags)
}
if (this.client.enabledCaps.has('invite-notify')) {
if (this.client.enabledCaps.has('reflectionircd.chat/extended-invite')) {
this.client.sendMessage(sourceUser.getMask(), 'INVITE', [targetUser.nick, targetChannel.name, reason], messageTags)
} else {
this.client.sendMessage(sourceUser.getMask(), 'INVITE', [targetUser.nick, targetChannel.name], messageTags)
}
});
}
}
else if (membershipStatus === "join") {
this.checkForLazyJoin(event, sourceUser, targetChannel);
@ -430,15 +432,11 @@ export class Server {
return;
if (targetUser.mxid === sourceUser.mxid) {
const reason = content["reason"] || 'User left';
this.clients.forEach((client) => {
client.sendMessage(sourceUser.getMask(), 'PART', [targetChannel.name, reason], messageTags);
});
this.client.sendMessage(sourceUser.getMask(), 'PART', [targetChannel.name, reason], messageTags);
}
else {
const reason = content["reason"] || 'User was kicked';
this.clients.forEach((client) => {
client.sendMessage(sourceUser.getMask(), 'KICK', [targetChannel.name, targetUser.nick, reason], messageTags);
});
this.client.sendMessage(sourceUser.getMask(), 'KICK', [targetChannel.name, targetUser.nick, reason], messageTags);
}
targetChannel.matrixUsers.delete(targetUser.nick)
}
@ -446,9 +444,7 @@ export class Server {
if (!targetChannel.matrixUsers.has(targetUser.nick))
return;
const reason = content["reason"] || 'User was banned';
this.clients.forEach((channel) => {
channel.sendMessage(sourceUser.getMask(), 'KICK', [targetChannel.name, targetUser.nick, reason], messageTags);
});
this.client.sendMessage(sourceUser.getMask(), 'KICK', [targetChannel.name, targetUser.nick, reason], messageTags);
targetChannel.matrixUsers.delete(targetUser.nick)
}
else {
@ -494,43 +490,35 @@ export class Server {
if (!msg) {
return;
}
this.clients.forEach((client) => {
if (this.eventIdStore.get(event["event_id"]) === client) {
if (client.enabledCaps.has('echo-message')) {
client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], tags)
}
} else {
client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], tags)
}
});
if (this.eventIdStore.get(event["event_id"]) === this.client) {
if (this.client.enabledCaps.has('echo-message')) {
this.client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], tags)
}
} else {
this.client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], tags)
}
} else {
this.clients.forEach(client => {
if (this.eventIdStore.get(event["event_id"]) === client && !client.enabledCaps.has('echo-message')) {
return;
}
if (client.enabledCaps.has('draft/multiline')) {
const batchLabel = Math.random().toString(36).substring(2,7);
const batchTags = new Map();
batchTags.set('batch', batchLabel);
client.sendMessage(sourceUser.getMask(), 'BATCH', [`+${batchLabel}`, 'draft/multiline', targetChannel.name], tags);
for (const msg of msgArray) {
if (msg) {
this.clients.forEach((client) => {
client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], batchTags)
});
}
}
client.sendMessage(sourceUser.getMask(), 'BATCH', [`-${batchLabel}`], new Map());
} else {
for (const msg of msgArray) {
if (msg) {
this.clients.forEach((client) => {
client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], tags)
});
}
if (this.eventIdStore.get(event["event_id"]) === this.client && !this.client.enabledCaps.has('echo-message')) {
return;
}
if (this.client.enabledCaps.has('draft/multiline')) {
const batchLabel = Math.random().toString(36).substring(2,7);
const batchTags = new Map();
batchTags.set('batch', batchLabel);
this.client.sendMessage(sourceUser.getMask(), 'BATCH', [`+${batchLabel}`, 'draft/multiline', targetChannel.name], tags);
for (const msg of msgArray) {
if (msg) {
this.client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], batchTags)
}
}
})
this.client.sendMessage(sourceUser.getMask(), 'BATCH', [`-${batchLabel}`], new Map());
} else {
for (const msg of msgArray) {
if (msg) {
this.client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], tags)
}
}
}
}
}
@ -555,20 +543,14 @@ export class Server {
if (oldPl === undefined) {
targetChannel.powerLevels.set(thisMatrixUser.nick, newPl);
const modeChange = this.convertPLToMode(newPl, "+");
this.clients.forEach(c => {
c.sendMessage(sourceUser.getMask(), "MODE", [targetChannel.name, modeChange, thisMatrixUser.nick]);
})
this.client.sendMessage(sourceUser.getMask(), "MODE", [targetChannel.name, modeChange, thisMatrixUser.nick]);
}
else if (oldPl !== newPl) {
const oldModeChange = this.convertPLToMode(oldPl, "-");
this.clients.forEach(c => {
c.sendMessage(sourceUser.getMask(), "MODE", [targetChannel.name, oldModeChange, thisMatrixUser.nick]);
})
this.client.sendMessage(sourceUser.getMask(), "MODE", [targetChannel.name, oldModeChange, thisMatrixUser.nick]);
if (newPl !== 0) {
const newModeChange = this.convertPLToMode(newPl, "+");
this.clients.forEach(c => {
c.sendMessage(sourceUser.getMask(), "MODE", [targetChannel.name, newModeChange, thisMatrixUser.nick]);
})
this.client.sendMessage(sourceUser.getMask(), "MODE", [targetChannel.name, newModeChange, thisMatrixUser.nick]);
} else {
targetChannel.powerLevels.delete(thisMatrixUser.nick)
}
@ -582,9 +564,7 @@ export class Server {
const oldPl = targetChannel.powerLevels.get(pl);
if (!oldPl) return;
const oldMode = this.convertPLToMode(oldPl, "-");
this.clients.forEach(c => {
c.sendMessage(sourceUser.getMask(), "MODE", [targetChannel.name, oldMode, nextUser.nick]);
})
this.client.sendMessage(sourceUser.getMask(), "MODE", [targetChannel.name, oldMode, nextUser.nick]);
}
}
}
@ -598,10 +578,9 @@ export class Server {
tags.set('reflectionircd.chat/delete-message', event["redacts"]);
tags.set('account', sourceUser.accountName);
tags.set('time', new Date(event["origin_server_ts"]).toISOString())
this.clients.forEach((client) => {
if (client.enabledCaps.has("reflectionircd.chat/edit-message"))
client.sendMessage(sourceUser.getMask(), 'DELETEMSG', [targetChannel.name, reason], tags);
});
if (this.client.enabledCaps.has("reflectionircd.chat/edit-message")) {
this.client.sendMessage(sourceUser.getMask(), 'DELETEMSG', [targetChannel.name, reason], tags);
}
}
handleMatrixTopic(event: any, targetChannel: Channel) {
@ -621,9 +600,7 @@ export class Server {
if (this.eventIDToLabel.has(event["event_id"])) {
messageTags.set("label", this.eventIDToLabel.get(event["event_id"]) || "")
}
this.clients.forEach((client) => {
client.sendMessage(topicSetter.getMask(), 'TOPIC', [targetChannel.name, topicText], messageTags);
});
this.client.sendMessage(topicSetter.getMask(), 'TOPIC', [targetChannel.name, topicText], messageTags);
}
handleMatrixSticker(event: any, targetChannel: Channel) {
@ -642,7 +619,7 @@ export class Server {
uri = `${this.homeserver}/_matrix/media/v3/download/${mxcregex.groups.servername}/${mxcregex.groups.mediaid}`;
}
const messageContent = `\x01ACTION sent a sticker: ${altText}: ${uri}\x01`;
this.sendToAll(sourceUser.getMask(), 'PRIVMSG', [targetChannel.name, messageContent]);
this.client.sendMessage(sourceUser.getMask(), 'PRIVMSG', [targetChannel.name, messageContent]);
}
handleMatrixTyping(event: any, targetChannel: Channel) {
@ -656,32 +633,9 @@ export class Server {
if (matrixUser !== undefined) {
const typingTags = new Map();
typingTags.set('+typing', 'active');
this.sendToAllWithCap('message-tags', matrixUser.getMask(), 'TAGMSG', [targetChannel.name], typingTags);
}
})
}
sendToAll(prefix: string, command: string, params: string[], tags: Map<string, string> = new Map(), skipClient: Client|null = null) {
this.clients.forEach(client => {
if (client !== skipClient) {
client.sendMessage(prefix, command, params, tags);
}
})
}
sendToAllWithCap(cap: string, prefix: string, command: string, params: string[], tags: Map<string, string> = new Map(), skipClient: Client|null = null) {
this.clients.forEach(client => {
if (client !== skipClient && client.enabledCaps.has(cap)) {
client.sendMessage(prefix, command, params, tags);
}
})
}
sendToAllWithoutCap(cap: string, prefix: string, command: string, params: string[], tags: Map<string, string> = new Map(), skipClient: Client|null = null) {
this.clients.forEach(client => {
if (client !== skipClient && !client.enabledCaps.has(cap)) {
client.sendMessage(prefix, command, params, tags);
if (this.client.enabledCaps.has('message-tags')) {
this.client.sendMessage(matrixUser.getMask(), 'TAGMSG', [targetChannel.name], typingTags);
}
}
})
}