From 2f702560373b9f8e5141bcdfd0cc34aaea70c025 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 22 Jan 2021 17:36:53 +0100 Subject: [PATCH] Multiple clients per app --- commands.js | 20 +++++++--- components/app.js | 96 ++++++++++++++++++++++++++++------------------- 2 files changed, 72 insertions(+), 44 deletions(-) diff --git a/commands.js b/commands.js index 88727b3..048f185 100644 --- a/commands.js +++ b/commands.js @@ -1,5 +1,13 @@ import { SERVER_BUFFER } from "/state.js"; +function getActiveClient(app) { + var buf = app.state.buffers.get(app.state.activeBuffer); + if (!buf) { + return null; + } + return app.clients.get(buf.network); +} + export default { "query": (app, args) => { var nick = args[0]; @@ -33,7 +41,7 @@ export default { if (!channel) { throw new Error("Missing channel name"); } - app.client.send({ command: "JOIN", params: [channel] }); + getActiveClient(app).send({ command: "JOIN", params: [channel] }); }, "part": (app, args) => { var reason = args.join(" "); @@ -45,12 +53,12 @@ export default { if (reason) { params.push(reason); } - app.client.send({ command: "PART", params }); + getActiveClient(app).send({ command: "PART", params }); }, "msg": (app, args) => { var target = args[0]; var text = args.slice(1).join(" "); - app.client.send({ command: "PRIVMSG", params: [target, text] }); + getActiveClient(app).send({ command: "PRIVMSG", params: [target, text] }); }, "me": (app, args) => { var action = args.join(" "); @@ -63,12 +71,12 @@ export default { }, "nick": (app, args) => { var newNick = args[0]; - app.client.send({ command: "NICK", params: [newNick] }); + getActiveClient(app).send({ command: "NICK", params: [newNick] }); }, "notice": (app, args) => { var target = args[0]; var text = args.slice(1).join(" "); - app.client.send({ command: "NOTICE", params: [target, text] }); + getActiveClient(app).send({ command: "NOTICE", params: [target, text] }); }, "buffer": (app, args) => { var name = args[0]; @@ -89,7 +97,7 @@ export default { if (args.length > 0) { params.push(args.join(" ")); } - app.client.send({ command: "TOPIC", params }); + getActiveClient(app).send({ command: "TOPIC", params }); }, "reconnect": (app, args) => { app.reconnect(); diff --git a/components/app.js b/components/app.js index e774acc..9c1cb50 100644 --- a/components/app.js +++ b/components/app.js @@ -139,7 +139,6 @@ function getBuffer(state, id) { } export default class App extends Component { - client = null; state = { connectParams: { serverURL: null, @@ -157,6 +156,7 @@ export default class App extends Component { activeBuffer: null, error: null, }; + clients = new Map(); pendingHistory = Promise.resolve(null); endOfHistory = new Map(); receipts = new Map(); @@ -368,10 +368,12 @@ export default class App extends Component { } addMessage(netID, bufName, msg) { + var client = this.clients.get(netID); + msg.key = messagesCount; messagesCount++; - msg.isHighlight = irc.isHighlight(msg, this.client.nick); + msg.isHighlight = irc.isHighlight(msg, client.nick); if (!msg.tags) { msg.tags = {}; @@ -393,7 +395,7 @@ export default class App extends Component { if (msg.isHighlight) { msgUnread = Unread.HIGHLIGHT; kind = "highlight"; - } else if (target == this.client.nick) { + } else if (target == client.nick) { msgUnread = Unread.HIGHLIGHT; kind = "private message"; } else { @@ -416,7 +418,7 @@ export default class App extends Component { } } - if (msg.prefix.name != this.client.nick && (msg.command != "PART" && msg.comand != "QUIT")) { + if (msg.prefix.name != client.nick && (msg.command != "PART" && msg.comand != "QUIT")) { this.createBuffer(netID, bufName); } @@ -456,7 +458,7 @@ export default class App extends Component { }); this.setState({ connectParams: params }); - this.client = new Client({ + var client = new Client({ url: params.serverURL, pass: params.serverPass, nick: params.nick, @@ -465,15 +467,17 @@ export default class App extends Component { saslPlain: params.saslPlain, }); - this.client.addEventListener("close", () => { + this.clients.set(netID, client); + + client.addEventListener("close", () => { this.handleClose(netID); }); - this.client.addEventListener("message", (event) => { + client.addEventListener("message", (event) => { this.handleMessage(netID, event.detail.message); }); - this.client.addEventListener("error", (event) => { + client.addEventListener("error", (event) => { this.setState({ error: event.detail, }); @@ -509,10 +513,11 @@ export default class App extends Component { clearTimeout(this.reconnectTimeoutID); this.reconnectTimeoutID = null; - if (this.client) { + var client = this.clients.get(netID); + if (client) { // Prevent auto-reconnect from kicking in - this.client.removeEventListener("close", this.handleClose); - this.client.close(); + client.removeEventListener("close", this.handleClose); + client.close(); } this.setNetworkState(netID, { status: Status.DISCONNECTED }); @@ -530,12 +535,13 @@ export default class App extends Component { } handleMessage(netID, msg) { + var client = this.clients.get(netID); switch (msg.command) { case irc.RPL_WELCOME: this.setNetworkState(netID, { status: Status.REGISTERED }); if (this.state.connectParams.autojoin.length > 0) { - this.client.send({ + client.send({ command: "JOIN", params: [this.state.connectParams.autojoin.join(",")], }); @@ -617,7 +623,7 @@ export default class App extends Component { case "NOTICE": case "PRIVMSG": var target = msg.params[0]; - if (target == this.client.nick) { + if (target == client.nick) { target = msg.prefix.name; } this.addMessage(netID, target, msg); @@ -631,7 +637,7 @@ export default class App extends Component { members.set(msg.prefix.name, null); return { members }; }); - if (msg.prefix.name != this.client.nick) { + if (msg.prefix.name != client.nick) { this.addMessage(netID, channel, msg); } if (channel == this.state.connectParams.autojoin[0]) { @@ -640,10 +646,10 @@ export default class App extends Component { } var receipt = this.getReceipt(channel, ReceiptType.READ); - if (msg.prefix.name == this.client.nick && receipt && this.client.enabledCaps["draft/chathistory"] && this.client.enabledCaps["server-time"]) { + if (msg.prefix.name == client.nick && receipt && client.enabledCaps["draft/chathistory"] && client.enabledCaps["server-time"]) { var after = receipt; var before = { time: msg.tags.time || irc.formatDate(new Date()) }; - this.fetchHistoryBetween(channel, after, before, CHATHISTORY_MAX_SIZE).catch((err) => { + this.fetchHistoryBetween(client, channel, after, before, CHATHISTORY_MAX_SIZE).catch((err) => { this.setState({ error: "Failed to fetch history: " + err }); this.receipts.delete(channel); this.saveReceipts(); @@ -660,7 +666,7 @@ export default class App extends Component { }); this.addMessage(netID, channel, msg); - if (msg.prefix.name == this.client.nick) { + if (msg.prefix.name == client.nick) { this.receipts.delete(channel); this.saveReceipts(); } @@ -753,13 +759,16 @@ export default class App extends Component { } open(target) { + var netID = getActiveNetworkID(this.state); + var client = this.clients.get(netID); + if (this.isChannel(target)) { - this.client.send({ command: "JOIN", params: [target] }); + client.send({ command: "JOIN", params: [target] }); } else { - this.client.send({ command: "WHO", params: [target] }); + client.send({ command: "WHO", params: [target] }); } - this.createBuffer(getActiveNetworkID(this.state), target); - this.switchBuffer({ name: target }); + this.createBuffer(netID, target); + this.switchBuffer({ network: netID, name: target }); } close(id) { @@ -782,17 +791,18 @@ export default class App extends Component { }); break; case BufferType.CHANNEL: - this.client.send({ command: "PART", params: [buf.name] }); + var client = this.clients.get(buf.network); + client.send({ command: "PART", params: [buf.name] }); // fallthrough case BufferType.NICK: this.switchBuffer({ name: SERVER_BUFFER }); this.setState((state) => { var buffers = new Map(state.buffers); - buffers.delete(target); + buffers.delete(buf.name); return { buffers }; }); - this.receipts.delete(target); + this.receipts.delete(buf.name); this.saveReceipts(); break; } @@ -822,12 +832,15 @@ export default class App extends Component { return; } - var msg = { command: "PRIVMSG", params: [target, text] }; - this.client.send(msg); + var netID = getActiveNetworkID(this.state); + var client = this.clients.get(netID); - if (!this.client.enabledCaps["echo-message"]) { - msg.prefix = { name: this.client.nick }; - this.addMessage(getActiveNetworkID(this.state), target, msg); + var msg = { command: "PRIVMSG", params: [target, text] }; + client.send(msg); + + if (!client.enabledCaps["echo-message"]) { + msg.prefix = { name: client.nick }; + this.addMessage(netID, target, msg); } } @@ -862,7 +875,11 @@ export default class App extends Component { if (!channel) { return; } - this.client.send({ command: "JOIN", params: [channel] }); + + var netID = getActiveNetworkID(this.state); + var client = this.clients.get(netID); + + client.send({ command: "JOIN", params: [channel] }); } autocomplete(prefix) { @@ -895,7 +912,7 @@ export default class App extends Component { return fromList(buf.members.keys(), prefix); } - roundtripChatHistory(params) { + roundtripChatHistory(client, params) { // Don't send multiple CHATHISTORY commands in parallel, we can't // properly handle batches and errors. this.pendingHistory = this.pendingHistory.catch(() => {}).then(() => { @@ -903,7 +920,7 @@ export default class App extends Component { command: "CHATHISTORY", params, }; - return this.client.roundtrip(msg, (event) => { + return client.roundtrip(msg, (event) => { var msg = event.detail.message; switch (msg.command) { @@ -913,7 +930,7 @@ export default class App extends Component { if (enter) { break; } - var batch = this.client.batches.get(name); + var batch = client.batches.get(name); if (batch.type == "chathistory") { return batch; } @@ -930,10 +947,10 @@ export default class App extends Component { } /* Fetch history in ascending order */ - fetchHistoryBetween(target, after, before, limit) { + fetchHistoryBetween(client, target, after, before, limit) { var max = Math.min(limit, CHATHISTORY_PAGE_SIZE); var params = ["AFTER", target, "timestamp=" + after.time, max]; - return this.roundtripChatHistory(params).then((batch) => { + return this.roundtripChatHistory(client, params).then((batch) => { limit -= batch.messages.length; if (limit <= 0) { throw new Error("Cannot fetch all chat history: too many messages"); @@ -941,7 +958,7 @@ export default class App extends Component { if (batch.messages.length == max) { // There are still more messages to fetch after.time = batch.messages[batch.messages.length - 1].tags.time; - return this.fetchHistoryBetween(target, after, before, limit); + return this.fetchHistoryBetween(client, target, after, before, limit); } }); } @@ -951,7 +968,10 @@ export default class App extends Component { if (!buf || buf.type == BufferType.SERVER) { return; } - if (!this.client.enabledCaps["draft/chathistory"] || !this.client.enabledCaps["server-time"]) { + + var client = this.clients.get(buf.network); + + if (!client.enabledCaps["draft/chathistory"] || !client.enabledCaps["server-time"]) { return; } if (this.endOfHistory.get(buf.id)) { @@ -969,7 +989,7 @@ export default class App extends Component { this.endOfHistory.set(buf.id, true); var params = ["BEFORE", buf.name, "timestamp=" + before, CHATHISTORY_PAGE_SIZE]; - this.roundtripChatHistory(params).then((batch) => { + this.roundtripChatHistory(client, params).then((batch) => { this.endOfHistory.set(buf.id, batch.messages.length < CHATHISTORY_PAGE_SIZE); }); }