From d4e3137d934960c38e41995a27a574ae3c1df0da Mon Sep 17 00:00:00 2001 From: emerson Date: Tue, 18 Oct 2022 18:54:26 -0400 Subject: [PATCH] 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 --- src/Client.ts | 6 +- src/Server.ts | 202 +++++++++++++++++++------------------------------- 2 files changed, 81 insertions(+), 127 deletions(-) diff --git a/src/Client.ts b/src/Client.ts index 969f312..884cd16 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -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 allCaps: Map @@ -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 {}; } \ No newline at end of file diff --git a/src/Server.ts b/src/Server.ts index cf8d9a6..55ca533 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -16,7 +16,7 @@ export class Server { private directMessages: Set private matrixUsers: Map public ourMatrixUser: MatrixUser; - private clients: Set + private client: Client public nickToMatrixUser: Map public eventIdStore: Map public eventIDToLabel: Map @@ -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 { - 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 = 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 = 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 = 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); + } } }) }