From 4ee86776a2dc082608fcc4ab99bd9cf79a540fb1 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 1 Mar 2021 19:16:07 +0000 Subject: [PATCH 1/4] Update bridge library --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 17e80b0e..3658a87e 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "htmlparser2": "^4.1.0", "leven": "^3.1.0", "marked": "^2.0.0", - "matrix-appservice-bridge": "^2.4.1", + "matrix-appservice-bridge": "^2.5.0", "parse-entities": "^1.2.0", "pg": "8.3.3", "prom-client": "^11.2.1", From 14de01f6554e0571b7994ce31c5d16ef4fda51e1 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 1 Mar 2021 19:18:39 +0000 Subject: [PATCH 2/4] Add support for fetching server features --- src/xmppjs/XJSInstance.ts | 27 ++++++++++++++++++++++++++- src/xmppjs/XMPPConstants.ts | 1 + 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/xmppjs/XJSInstance.ts b/src/xmppjs/XJSInstance.ts index bf36c175..6b28f1c3 100644 --- a/src/xmppjs/XJSInstance.ts +++ b/src/xmppjs/XJSInstance.ts @@ -28,6 +28,7 @@ import { XmppJsGateway } from "./XJSGateway"; import { IStza, StzaBase, StzaIqDisco, StzaIqDiscoInfo, StzaIqPing, StzaIqPingError, StzaIqVcardRequest } from "./Stanzas"; import { Util } from "../Util"; import uuid from "uuid/v4"; +import { XMPPFeatures } from "./XMPPConstants"; const xLog = Logging.get("XMPP-conn"); const log = Logging.get("XmppJsInstance"); @@ -58,6 +59,7 @@ class XmppProtocol extends BifrostProtocol { export const XMPP_PROTOCOL = new XmppProtocol(); const SEEN_MESSAGES_SIZE = 16384; +const FEATURE_SET_EXPIRES_AFTER_MS = 24 * 60 * 60 * 1000; // 24hs export class XmppJsInstance extends EventEmitter implements IBifrostInstance { public readonly presenceCache: PresenceCache; @@ -75,6 +77,7 @@ export class XmppJsInstance extends EventEmitter implements IBifrostInstance { private xmppGateway: XmppJsGateway|null; private activeMUCUsers: Set; private lastMessageInMUC: Map; + private remoteServerFeatures = new Map(); constructor(private config: Config) { super(); this.canWrite = false; @@ -444,6 +447,29 @@ export class XmppJsInstance extends EventEmitter implements IBifrostInstance { ); Metrics.remoteCall("xmpp.iq.vc2"); return res; + + public async checkFeaturesForServer(server: string): Promise { + const cachedFeatures = this.remoteServerFeatures.get(server); + if (cachedFeatures && cachedFeatures.expiresAfter < Date.now()) { + return cachedFeatures.features; + } + const iqRequest = new StzaIqDiscoInfo(this.myAddress.toString(), server, uuid()); + try { + const result = await this.sendIq(iqRequest); + const features: XMPPFeatures[] = result.getChild("query").getChildren("feature").map((e) => e.attr('var')); + this.remoteServerFeatures.set( + server, + { + expiresAfter: Date.now() + FEATURE_SET_EXPIRES_AFTER_MS, + features, + } + ); + return features; + } + catch (ex) { + log.warn(`Failed to determine server features`); + return []; + } } private generateIdforMsg(stanza: Element) { @@ -540,7 +566,6 @@ export class XmppJsInstance extends EventEmitter implements IBifrostInstance { } } - private async handleMessageStanza(stanza: Element, alias: string|null) { if (!stanza.attrs.from || !stanza.attrs.to) { return; diff --git a/src/xmppjs/XMPPConstants.ts b/src/xmppjs/XMPPConstants.ts index 681603a1..b1f9146d 100644 --- a/src/xmppjs/XMPPConstants.ts +++ b/src/xmppjs/XMPPConstants.ts @@ -17,4 +17,5 @@ export enum XMPPFeatures { IqSearch = "jabber:iq:search", MessageCorrection = "urn:xmpp:message-correct:0", XHTMLIM = "http://jabber.org/protocol/xhtml-im", + ExtendedStanzaAddressing = "http://jabber.org/protocol/address" } \ No newline at end of file From c9e004396b17c4e0937cb2089000426f7e2caf6b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 1 Mar 2021 19:19:02 +0000 Subject: [PATCH 3/4] Refactor vcard fetcher to use our shiny amazing sendIq function --- src/xmppjs/XJSInstance.ts | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/xmppjs/XJSInstance.ts b/src/xmppjs/XJSInstance.ts index 6b28f1c3..809624eb 100644 --- a/src/xmppjs/XJSInstance.ts +++ b/src/xmppjs/XJSInstance.ts @@ -430,23 +430,15 @@ export class XmppJsInstance extends EventEmitter implements IBifrostInstance { const whoJid = jid(who); who = `${whoJid.local}@${whoJid.domain}`; log.info(`Fetching vCard for ${who}`); - const res = new Promise((resolve: (e: Element) => void, reject) => { - const timeout = setTimeout(() => reject(Error("Timeout")), 5000); - this.once(`iq.${id}`, (stanza: Element) => { - clearTimeout(timeout); - const vCard = (stanza.getChild("vCard") as unknown as Element); // Bad typigns. - if (vCard) { - resolve(vCard); - } - reject(Error("No vCard given")); - }); - }); - // Remove the resource - await this.xmppSend( - new StzaIqVcardRequest(sender || this.xmppAddress.toString(), who, id), - ); + const iqRequest = new StzaIqVcardRequest(sender || this.xmppAddress.toString(), who, id); Metrics.remoteCall("xmpp.iq.vc2"); - return res; + const stanza = await this.sendIq(iqRequest); + const vCard = (stanza.getChild("vCard") as unknown as Element); // Bad typigns. + if (vCard) { + return vCard; + } + throw Error("No vCard in response"); + } public async checkFeaturesForServer(server: string): Promise { const cachedFeatures = this.remoteServerFeatures.get(server); From 971df9ea670415ef6191c46f21f3907ac400734e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 1 Mar 2021 19:19:26 +0000 Subject: [PATCH 4/4] Use mutlicast for stanza reflection --- src/xmppjs/XJSGateway.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/xmppjs/XJSGateway.ts b/src/xmppjs/XJSGateway.ts index ad46775c..4fbabc53 100644 --- a/src/xmppjs/XJSGateway.ts +++ b/src/xmppjs/XJSGateway.ts @@ -13,7 +13,7 @@ import { StzaPresenceItem, StzaMessage, StzaMessageSubject, StzaPresenceError, StzaBase, StzaPresenceKick, PresenceAffiliation, PresenceRole } from "./Stanzas"; import { IGateway } from "../bifrost/Gateway"; import { GatewayMUCMembership, IGatewayMemberXmpp, IGatewayMemberMatrix } from "./GatewayMUCMembership"; -import { XMPPStatusCode } from "./XMPPConstants"; +import { XMPPFeatures, XMPPStatusCode } from "./XMPPConstants"; import { AutoRegistration } from "../AutoRegistration"; import { GatewayStateResolve } from "./GatewayStateResolve"; import { MatrixMembershipEvent } from "../MatrixTypes"; @@ -190,10 +190,21 @@ export class XmppJsGateway implements IGateway { const preserveFrom = stanza.attrs.from; try { stanza.attrs.from = member!.anonymousJid; - const devices = this.members.getXmppMembersDevices(chatName); - for (const deviceJid of devices) { - stanza.attrs.to = deviceJid; - this.xmpp.xmppWriteToStream(stanza); + const members = this.members.getXmppMembers(chatName); + for (const member of members) { + // This is cached. + const canMulticast = (await this.xmpp.checkFeaturesForServer(member.realJid.domain)).includes( + XMPPFeatures.ExtendedStanzaAddressing, + ) + if (canMulticast) { + stanza.attrs.to = member.realJid; + this.xmpp.xmppSend(stanza.toString()); + } else { + member.devices.forEach((device) => { + stanza.attrs.to = device; + this.xmpp.xmppSend(stanza.toString()); + }); + } } } catch (err) { log.warn("Failed to reflect XMPP message:", err); @@ -205,6 +216,7 @@ export class XmppJsGateway implements IGateway { } public reflectXMPPStanza(chatName: string, stanza: StzaBase) { + // TODO: Consider optimising by using multicast const xmppDevices = [...this.members.getXmppMembersDevices(chatName)]; return Promise.all(xmppDevices.map((device) => { stanza.to = device;