@pazznetwork/ngx-chat
Version:
[](https://travis-ci.com/pazznetwork/ngx-chat) [](https://coveralls.io/github/pazzn
585 lines • 112 kB
JavaScript
import { jid as parseJid, xml } from '@xmpp/client';
import { BehaviorSubject, Subject } from 'rxjs';
import { Direction } from '../../../../../core/message';
import { AbstractStanzaBuilder } from '../../abstract-stanza-builder';
import { StanzaBuilder } from '../../stanza-builder';
import { AbstractXmppPlugin } from '../abstract-xmpp-plugin';
import { MessageReceivedEvent } from '../message.plugin';
import { ServiceDiscoveryPlugin } from '../service-discovery.plugin';
import { Presence } from '../../../../../core/presence';
import { Room } from './room';
import { Affiliation } from './affiliation';
import { Role } from './role';
import { FORM_NS, getField, parseForm, serializeToSubmitForm, setFieldValue, } from '../../../../../core/form';
import { XmppResponseError } from '../../xmpp-response.error';
import { mucNs, mucAdminNs, mucOwnerNs, mucRoomConfigFormNs, mucUserNs } from './multi-user-chat-constants';
class QueryAffiliatedMemberListStanzaBuilder extends AbstractStanzaBuilder {
constructor(roomJid, queryType, affiliationOrRole) {
super();
this.roomJid = roomJid;
this.queryType = queryType;
this.affiliationOrRole = affiliationOrRole;
}
static build(roomJid, ...[queryType, affiliationOrRole]) {
return new QueryAffiliatedMemberListStanzaBuilder(roomJid, queryType, affiliationOrRole).toStanza();
}
toStanza() {
return xml('iq', { type: 'get', to: this.roomJid.toString() }, xml('query', { xmlns: mucAdminNs }, xml('item', { [this.queryType]: this.affiliationOrRole })));
}
}
class QueryOccupantListStanzaBuilder extends AbstractStanzaBuilder {
constructor(roomJid) {
super();
this.roomJid = roomJid;
}
static build(roomJid) {
return new QueryOccupantListStanzaBuilder(roomJid).toStanza();
}
toStanza() {
return xml('iq', { type: 'get', to: this.roomJid.toString() }, xml('query', { xmlns: ServiceDiscoveryPlugin.DISCO_ITEMS }));
}
}
class ModifyAffiliationsOrRolesStanzaBuilder extends AbstractStanzaBuilder {
constructor(roomJid, modifications) {
super();
this.roomJid = roomJid;
this.modifications = modifications;
}
static build(roomJid, modifications) {
return new ModifyAffiliationsOrRolesStanzaBuilder(roomJid, modifications).toStanza();
}
toStanza() {
return xml('iq', { to: this.roomJid.toString(), type: 'set' }, xml('query', { xmlns: mucAdminNs }, ...this.modifications.map(modification => this.buildItem(modification))));
}
buildItem(modification) {
const { reason, ...attrs } = modification;
return xml('item', 'userJid' in attrs
? {
jid: attrs.userJid.toString(),
affiliation: attrs.affiliation,
}
: {
nick: attrs.nick,
role: attrs.role,
}, reason ? xml('reason', {}, reason) : null);
}
}
/**
* The MultiUserChatPlugin tries to provide the necessary functionality for a multi-user text chat,
* whereby multiple XMPP users can exchange messages in the context of a room or channel, similar to Internet Relay Chat (IRC).
* For more details see:
* @see https://xmpp.org/extensions/xep-0045.html
*/
export class MultiUserChatPlugin extends AbstractXmppPlugin {
constructor(xmppChatAdapter, logService, serviceDiscoveryPlugin) {
super();
this.xmppChatAdapter = xmppChatAdapter;
this.logService = logService;
this.serviceDiscoveryPlugin = serviceDiscoveryPlugin;
this.rooms$ = new BehaviorSubject([]);
this.message$ = new Subject();
this.onInvitationSubject = new Subject();
this.onInvitation$ = this.onInvitationSubject.asObservable();
}
onOffline() {
this.rooms$.next([]);
}
handleStanza(stanza, archiveDelayElement) {
if (this.isRoomPresenceStanza(stanza)) {
return this.handleRoomPresenceStanza(stanza);
}
else if (this.isRoomMessageStanza(stanza)) {
return this.handleRoomMessageStanza(stanza, archiveDelayElement);
}
else if (this.isRoomSubjectStanza(stanza)) {
return this.handleRoomSubjectStanza(stanza, archiveDelayElement);
}
else if (this.isRoomInvitationStanza(stanza)) {
return this.handleRoomInvitationStanza(stanza);
}
return false;
}
/**
* Resolves if room could be configured as requested, rejects if room did exist or server did not accept configuration.
*/
async createRoom(options) {
const { roomId, nick } = options;
const service = await this.serviceDiscoveryPlugin.findService('conference', 'text');
const occupantJid = parseJid(roomId, service.jid, nick);
const { presenceResponse, room } = await this.joinRoomInternal(occupantJid);
const itemElement = presenceResponse.getChild('x').getChild('item');
if (itemElement.attrs.affiliation !== Affiliation.owner) {
throw new Error('error creating room, user is not owner: ' + presenceResponse.toString());
}
try {
await this.applyRoomConfiguration(room.roomJid, options);
room.name = options.name || undefined;
this.rooms$.next(this.rooms$.getValue());
}
catch (e) {
this.logService.error('room configuration rejected', e);
throw e;
}
return room;
}
async destroyRoom(roomJid) {
let roomDestroyedResponse;
try {
roomDestroyedResponse = await this.xmppChatAdapter.chatConnectionService.sendIq(xml('iq', { type: 'set', to: roomJid.toString() }, xml('query', { xmlns: mucOwnerNs }, xml('destroy'))));
}
catch (e) {
this.logService.error('error destroying room');
throw e;
}
// TODO: refactor so that we instead listen to the presence destroy stanza
const allRoomsWithoutDestroyedRoom = this.rooms$.getValue().filter(room => !room.roomJid.equals(roomJid));
this.rooms$.next(allRoomsWithoutDestroyedRoom);
return roomDestroyedResponse;
}
async joinRoom(occupantJid) {
const { room } = await this.joinRoomInternal(occupantJid);
this.rooms$.next(this.rooms$.getValue());
return room;
}
async getRoomInfo(roomJid) {
const roomInfoResponse = await this.xmppChatAdapter.chatConnectionService.sendIq(xml('iq', { type: 'get', to: roomJid.toString() }, xml('query', { xmlns: ServiceDiscoveryPlugin.DISCO_INFO })));
const formEl = roomInfoResponse
.getChild('query', ServiceDiscoveryPlugin.DISCO_INFO)
?.getChild('x', FORM_NS);
if (formEl) {
return parseForm(formEl);
}
return null;
}
async queryAllRooms() {
const conferenceServer = await this.serviceDiscoveryPlugin.findService('conference', 'text');
const to = conferenceServer.jid.toString();
const result = [];
let roomQueryResponse = await this.xmppChatAdapter.chatConnectionService.sendIq(xml('iq', { type: 'get', to }, xml('query', { xmlns: ServiceDiscoveryPlugin.DISCO_ITEMS })));
result.push(...this.extractRoomSummariesFromResponse(roomQueryResponse));
let resultSet = this.extractResultSetFromResponse(roomQueryResponse);
while (resultSet && resultSet.getChild('last')) {
const lastReceivedRoom = resultSet.getChildText('last');
roomQueryResponse = await this.xmppChatAdapter.chatConnectionService.sendIq(xml('iq', { type: 'get', to }, xml('query', { xmlns: ServiceDiscoveryPlugin.DISCO_ITEMS }, xml('set', { xmlns: 'http://jabber.org/protocol/rsm' }, xml('max', {}, '250'), xml('after', {}, lastReceivedRoom)))));
result.push(...this.extractRoomSummariesFromResponse(roomQueryResponse));
resultSet = this.extractResultSetFromResponse(roomQueryResponse);
}
await Promise.all(result.map(async (summary) => {
summary.roomInfo = await this.getRoomInfo(summary.jid);
}));
return result;
}
/**
* Get all members of a MUC-Room with their affiliation to the room using the rooms fullJid
* @param roomJid jid of the room
*/
async queryUserList(roomJid) {
const memberQueryResponses = await Promise.all([
...Object
.values(Affiliation)
.map(affiliation => this.xmppChatAdapter.chatConnectionService.sendIq(QueryAffiliatedMemberListStanzaBuilder.build(roomJid, 'affiliation', affiliation))),
...Object
.values(Role)
.map(role => this.xmppChatAdapter.chatConnectionService.sendIq(QueryAffiliatedMemberListStanzaBuilder.build(roomJid, 'role', role))),
]);
const members = new Map();
for (const memberQueryResponse of memberQueryResponses) {
memberQueryResponse
.getChild('query', mucAdminNs)
.getChildren('item')
.forEach((memberItem) => {
const userJid = parseJid(memberItem.attrs.jid);
const roomUser = members.get(userJid.bare().toString()) || {
userIdentifiers: [],
affiliation: Affiliation.none,
role: Role.none,
};
roomUser.userIdentifiers.push({
userJid,
nick: memberItem.attrs.nick && memberItem.attrs.nick,
});
// tslint:disable no-unused-expression
memberItem.attrs.affiliation && (roomUser.affiliation = memberItem.attrs.affiliation);
memberItem.attrs.role && (roomUser.role = memberItem.attrs.role);
// tslint:enable no-unused-expression
members.set(userJid.bare().toString(), roomUser);
});
}
return [...members.values()];
}
async modifyAffiliationOrRole(roomJid, modification) {
return await this.xmppChatAdapter.chatConnectionService.sendIq(ModifyAffiliationsOrRolesStanzaBuilder.build(roomJid, [modification]));
}
async sendMessage(room, body, thread) {
const from = this.xmppChatAdapter.chatConnectionService.userJid.toString();
const roomJid = room.roomJid.toString();
const roomMessageStanza = thread
? StanzaBuilder.buildRoomMessageWithThread(from, roomJid, body, thread)
: StanzaBuilder.buildRoomMessageWithBody(from, roomJid, body);
for (const plugin of this.xmppChatAdapter.plugins) {
plugin.beforeSendMessage(roomMessageStanza);
}
return await this.xmppChatAdapter.chatConnectionService.send(roomMessageStanza);
}
/**
* requests a configuration form for a room which returns with the default values
* for an example see:
* https://xmpp.org/extensions/xep-0045.html#registrar-formtype-owner
*/
async getRoomConfiguration(roomJid) {
const configurationForm = await this.xmppChatAdapter.chatConnectionService.sendIq(xml('iq', { type: 'get', to: roomJid.toString() }, xml('query', { xmlns: mucOwnerNs })));
const formElement = configurationForm.getChild('query').getChild('x', FORM_NS);
if (!formElement) {
throw new Error('room not configurable');
}
return parseForm(formElement);
}
async applyRoomConfiguration(roomJid, roomConfiguration) {
const roomConfigForm = await this.getRoomConfiguration(roomJid);
const formTypeField = getField(roomConfigForm, 'FORM_TYPE');
if (formTypeField.value !== mucRoomConfigFormNs) {
throw new Error(`unexpected form type for room configuration form: formType=${formTypeField.value}, formTypeField=${JSON.stringify(formTypeField)}`);
}
if (typeof roomConfiguration.name === 'string') {
setFieldValue(roomConfigForm, 'text-single', 'muc#roomconfig_roomname', roomConfiguration.name);
}
if (typeof roomConfiguration.nonAnonymous === 'boolean') {
setFieldValue(roomConfigForm, 'list-single', 'muc#roomconfig_whois', roomConfiguration.nonAnonymous ? 'anyone' : 'moderators');
}
if (typeof roomConfiguration.public === 'boolean') {
setFieldValue(roomConfigForm, 'boolean', 'muc#roomconfig_publicroom', roomConfiguration.public);
}
if (typeof roomConfiguration.membersOnly === 'boolean') {
setFieldValue(roomConfigForm, 'boolean', 'muc#roomconfig_membersonly', roomConfiguration.membersOnly);
}
if (typeof roomConfiguration.persistentRoom === 'boolean') {
setFieldValue(roomConfigForm, 'boolean', 'muc#roomconfig_persistentroom', roomConfiguration.persistentRoom);
}
if (typeof roomConfiguration.allowSubscription === 'boolean') {
setFieldValue(roomConfigForm, 'boolean', 'allow_subscription', roomConfiguration.allowSubscription);
}
await this.xmppChatAdapter.chatConnectionService.sendIq(xml('iq', { type: 'set', to: roomJid.toString() }, xml('query', { xmlns: mucOwnerNs }, serializeToSubmitForm(roomConfigForm))));
}
getRoomByJid(jid) {
return this.rooms$.getValue().find(room => room.roomJid.equals(jid.bare()));
}
async banUser(occupantJid, roomJid, reason) {
const userJid = await this.getUserJidByOccupantJid(occupantJid, roomJid);
const response = await this.modifyAffiliationOrRole(roomJid, {
userJid: userJid.bare(),
affiliation: Affiliation.outcast,
reason,
});
this.logService.debug(`ban response ${response.toString()}`);
return response;
}
async unbanUser(occupantJid, roomJid) {
const userJid = await this.getUserJidByOccupantJid(occupantJid, roomJid);
const banList = (await this.getBanList(roomJid)).map(bannedUser => bannedUser.userJid);
this.logService.debug(`ban list: ${JSON.stringify(banList)}`);
if (!banList.find(bannedJid => bannedJid.equals(userJid))) {
throw new Error(`error unbanning: ${userJid} isn't on the ban list`);
}
const response = await this.modifyAffiliationOrRole(roomJid, { userJid, affiliation: Affiliation.none });
this.logService.debug('unban response: ' + response.toString());
return response;
}
async getBanList(roomJid) {
const iq = xml('iq', { to: roomJid.toString(), type: 'get' }, xml('query', { xmlns: mucAdminNs }, xml('item', { affiliation: Affiliation.outcast })));
const response = await this.xmppChatAdapter.chatConnectionService.sendIq(iq);
return response.getChild('query').getChildren('item').map(item => ({
userJid: parseJid(item.attrs.jid),
affiliation: item.attrs.affiliation,
reason: item.getChild('reason')?.getText(),
}));
}
async inviteUser(inviteeJid, roomJid, invitationMessage) {
const from = this.xmppChatAdapter.chatConnectionService.userJid.toString();
const stanza = xml('message', { to: roomJid.toString(), from }, xml('x', { xmlns: mucUserNs }, xml('invite', { to: inviteeJid.toString() }, invitationMessage ? xml('reason', {}, invitationMessage) : null)));
await this.xmppChatAdapter.chatConnectionService.send(stanza);
}
async declineRoomInvite(occupantJid, reason) {
const to = occupantJid.bare().toString();
const from = this.xmppChatAdapter.chatConnectionService.userJid.toString();
const stanza = xml('message', { to, from }, xml('x', { xmlns: mucUserNs }, xml('decline', { to }, reason ? xml('reason', {}, reason) : null)));
await this.xmppChatAdapter.chatConnectionService.send(stanza);
}
async kickOccupant(nick, roomJid, reason) {
const response = await this.modifyAffiliationOrRole(roomJid, { nick, role: Role.none, reason });
this.logService.debug(`kick occupant response: ${response.toString()}`);
return response;
}
async changeUserNickname(newNick, roomJid) {
const newRoomJid = parseJid(roomJid.toString());
newRoomJid.resource = newNick;
const stanza = xml('presence', {
to: newRoomJid.toString(),
from: this.xmppChatAdapter.chatConnectionService.userJid.toString(),
});
await this.xmppChatAdapter.chatConnectionService.send(stanza);
}
async leaveRoom(occupantJid, status) {
const stanza = xml('presence', {
to: occupantJid.toString(),
from: this.xmppChatAdapter.chatConnectionService.userJid.toString(),
type: Presence[Presence.unavailable],
}, status ? xml('status', {}, status) : null);
await this.xmppChatAdapter.chatConnectionService.send(stanza);
this.logService.debug(`occupant left room: occupantJid=${occupantJid.toString()}`);
}
async changeRoomSubject(roomJid, subject) {
const from = this.xmppChatAdapter.chatConnectionService.userJid.toString();
await this.xmppChatAdapter.chatConnectionService.send(xml('message', { to: roomJid.toString(), from, type: 'groupchat' }, xml('subject', {}, subject)));
this.logService.debug(`room subject changed: roomJid=${roomJid.toString()}, new subject=${subject}`);
}
isRoomInvitationStanza(stanza) {
let x;
return stanza.name === 'message'
&& (x = stanza.getChild('x', mucUserNs)) != null
&& (x.getChild('invite') != null || x.getChild('decline') != null);
}
async grantMembership(userJid, roomJid, reason) {
await this.setAffiliation(userJid, roomJid, Affiliation.member, reason);
}
async revokeMembership(userJid, roomJid, reason) {
await this.setAffiliation(userJid, roomJid, Affiliation.none, reason);
}
async grantAdmin(userJid, roomJid, reason) {
await this.setAffiliation(userJid, roomJid, Affiliation.admin, reason);
}
async revokeAdmin(userJid, roomJid, reason) {
await this.setAffiliation(userJid, roomJid, Affiliation.member, reason);
}
async grantModeratorStatus(occupantNick, roomJid, reason) {
await this.setRole(occupantNick, roomJid, Role.moderator, reason);
}
async revokeModeratorStatus(occupantNick, roomJid, reason) {
await this.setRole(occupantNick, roomJid, Role.participant, reason);
}
isRoomPresenceStanza(stanza) {
return stanza.name === 'presence' && (stanza.getChild('x', mucNs)
|| stanza.getChild('x', mucUserNs)) != null;
}
handleRoomPresenceStanza(stanza) {
const stanzaType = stanza.attrs.type;
if (stanzaType === 'error') {
this.logService.error(stanza);
throw new Error('error handling message, stanza: ' + stanza);
}
const occupantJid = parseJid(stanza.attrs.from);
const roomJid = occupantJid.bare();
const xEl = stanza.getChild('x', mucUserNs);
const itemEl = xEl.getChild('item');
const subjectOccupant = {
occupantJid,
affiliation: itemEl.attrs.affiliation,
role: itemEl.attrs.role,
nick: occupantJid.resource,
};
const room = this.getOrCreateRoom(occupantJid);
const statusCodes = xEl.getChildren('status').map(status => status.attrs.code);
const isCurrenUser = statusCodes.includes('110');
if (stanzaType === 'unavailable') {
const actor = itemEl.getChild('actor')?.attrs.nick;
const reason = itemEl.getChild('reason')?.getText();
if (statusCodes.includes('333')) {
if (isCurrenUser) {
this.rooms$.next(this.rooms$.getValue().filter(r => !r.jidBare.equals(roomJid)));
}
return room.handleOccupantConnectionError(subjectOccupant, isCurrenUser);
}
else if (statusCodes.includes('307')) {
if (isCurrenUser) {
this.rooms$.next(this.rooms$.getValue().filter(r => !r.jidBare.equals(roomJid)));
}
return room.handleOccupantKicked(subjectOccupant, isCurrenUser, actor, reason);
}
else if (statusCodes.includes('301')) {
if (isCurrenUser) {
this.rooms$.next(this.rooms$.getValue().filter(r => !r.jidBare.equals(roomJid)));
}
return room.handleOccupantBanned(subjectOccupant, isCurrenUser, actor, reason);
}
else if (statusCodes.includes('303')) {
const handled = room.handleOccupantChangedNick(subjectOccupant, isCurrenUser, xEl.getChild('item').attrs.nick);
if (handled && isCurrenUser) {
this.rooms$.next(this.rooms$.getValue());
}
return handled;
}
else if (statusCodes.includes('321')) {
if (isCurrenUser) {
this.rooms$.next(this.rooms$.getValue().filter(r => !r.jidBare.equals(roomJid)));
}
return room.handleOccupantLostMembership(subjectOccupant, isCurrenUser);
}
else {
if (isCurrenUser) {
this.rooms$.next(this.rooms$.getValue().filter(r => !r.jidBare.equals(roomJid)));
}
return room.handleOccupantLeft(subjectOccupant, isCurrenUser);
}
}
else if (!stanzaType) {
if (room.hasOccupant(subjectOccupant.occupantJid)) {
const oldOccupant = room.getOccupant(subjectOccupant.occupantJid);
return room.handleOccupantModified(subjectOccupant, oldOccupant, isCurrenUser);
}
else {
return room.handleOccupantJoined(subjectOccupant, isCurrenUser);
}
}
return false;
}
getOrCreateRoom(roomJid) {
roomJid = roomJid.bare();
let room = this.getRoomByJid(roomJid);
if (!room) {
room = new Room(roomJid, this.logService);
this.rooms$.next([room, ...this.rooms$.getValue()]);
}
return room;
}
async joinRoomInternal(roomJid) {
if (this.getRoomByJid(roomJid.bare())) {
throw new Error('can not join room more than once: ' + roomJid.bare().toString());
}
const userJid = this.xmppChatAdapter.chatConnectionService.userJid;
const occupantJid = parseJid(roomJid.local, roomJid.domain, roomJid.resource || userJid.local);
let roomInfo = null;
try {
roomInfo = await this.getRoomInfo(occupantJid.bare());
}
catch (e) {
if (!(e instanceof XmppResponseError) || e.errorCondition !== 'item-not-found') {
throw e;
}
}
try {
const presenceResponse = await this.xmppChatAdapter.chatConnectionService.sendAwaitingResponse(xml('presence', { to: occupantJid.toString() }, xml('x', { xmlns: mucNs })));
this.handleRoomPresenceStanza(presenceResponse);
const room = this.getOrCreateRoom(occupantJid.bare());
room.nick = occupantJid.resource;
if (roomInfo) {
room.name = getField(roomInfo, 'muc#roomconfig_roomname')?.value;
room.description = getField(roomInfo, 'muc#roominfo_description')?.value || '';
}
return { presenceResponse, room };
}
catch (e) {
this.logService.error('error joining room', e);
throw e;
}
}
extractRoomSummariesFromResponse(iq) {
return iq
.getChild('query', ServiceDiscoveryPlugin.DISCO_ITEMS)
?.getChildren('item')
?.reduce((acc, item) => {
const { jid, name } = item.attrs;
if (typeof jid === 'string' && typeof name === 'string') {
acc.push({
jid: parseJid(jid),
name,
roomInfo: null,
});
}
return acc;
}, []) || [];
}
extractResultSetFromResponse(iq) {
return iq
.getChild('query', ServiceDiscoveryPlugin.DISCO_ITEMS)
?.getChild('set', 'http://jabber.org/protocol/rsm');
}
isRoomMessageStanza(stanza) {
return stanza.name === 'message' && stanza.attrs.type === 'groupchat' && !!stanza.getChildText('body')?.trim();
}
handleRoomMessageStanza(messageStanza, archiveDelayElement) {
const delayElement = archiveDelayElement ?? messageStanza.getChild('delay');
const datetime = delayElement?.attrs.stamp
? new Date(delayElement.attrs.stamp)
: new Date() /* TODO: replace with entity time plugin */;
const from = parseJid(messageStanza.attrs.from);
const room = this.getRoomByJid(from.bare());
if (!room) {
// there are several reasons why we can receive a message for an unknown room:
// - this is a message delivered via MAM/MUCSub but the room it was stored for
// - is gone (was destroyed)
// - user was banned from room
// - room wasn't joined yet
// - this is some kind of error on developer's side
this.logService.warn(`received stanza for unknown room: ${from.bare().toString()}`);
return false;
}
const message = {
body: messageStanza.getChildText('body').trim(),
datetime,
id: messageStanza.attrs.id,
from,
direction: from.equals(room.occupantJid) ? Direction.out : Direction.in,
delayed: !!delayElement,
fromArchive: archiveDelayElement != null,
};
const messageReceivedEvent = new MessageReceivedEvent();
for (const plugin of this.xmppChatAdapter.plugins) {
plugin.afterReceiveMessage(message, messageStanza, messageReceivedEvent);
}
if (!messageReceivedEvent.discard) {
room.addMessage(message);
}
if (!message.delayed) {
this.message$.next(room);
}
return true;
}
isRoomSubjectStanza(stanza) {
return stanza.name === 'message'
&& stanza.attrs.type === 'groupchat'
&& stanza.getChild('subject') != null
&& stanza.getChild('body') == null;
}
handleRoomSubjectStanza(stanza, archiveDelayElement) {
const roomJid = parseJid(stanza.attrs.from).bare();
const room = this.getRoomByJid(roomJid);
if (!room) {
throw new Error(`unknown room trying to change room subject: roomJid=${roomJid.toString()}`);
}
// The archive only stores non-empty subjects. The current value of the subject is sent directly after entering a room by the room,
// not the archive.
// If a subject was first set, then unset, we would first receive the empty subject on room entry and then overwrite it with the
// previous non-empty value from archive. This is why we want to always ignore subjects from archive.
// This actually looks like a bug in MAM, it seems that MAM interprets messages with just subject in them as if they were chat
// messages and not room metadata. This would explain why empty subjects are not stored.
if (archiveDelayElement) {
return true;
}
room.subject = stanza.getChild('subject').getText().trim();
this.rooms$.next(this.rooms$.getValue());
return true;
}
handleRoomInvitationStanza(stanza) {
const xEl = stanza.getChild('x', mucUserNs);
const invitationEl = xEl.getChild('invite') ?? xEl.getChild('decline');
this.onInvitationSubject.next({
type: invitationEl.name,
roomJid: parseJid(stanza.attrs.from),
roomPassword: xEl.getChild('password')?.getText(),
from: parseJid(invitationEl.attrs.from),
message: invitationEl.getChild('reason')?.getText(),
});
return true;
}
async setAffiliation(occupantJid, roomJid, affiliation, reason) {
const userJid = await this.getUserJidByOccupantJid(occupantJid, roomJid);
return await this.modifyAffiliationOrRole(roomJid, { userJid, affiliation, reason });
}
async setRole(occupantNick, roomJid, role, reason) {
return await this.modifyAffiliationOrRole(roomJid, { nick: occupantNick, role, reason });
}
async getUserJidByOccupantJid(occupantJid, roomJid) {
const users = await this.queryUserList(roomJid);
return users.find(roomUser => roomUser.userIdentifiers.find(ids => ids.nick === occupantJid.resource || ids.userJid.bare().equals(occupantJid.bare())))?.userIdentifiers?.[0].userJid;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibXVsdGktdXNlci1jaGF0LnBsdWdpbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3NyYy9saWIvc2VydmljZXMvYWRhcHRlcnMveG1wcC9wbHVnaW5zL211bHRpLXVzZXItY2hhdC9tdWx0aS11c2VyLWNoYXQucGx1Z2luLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxHQUFHLElBQUksUUFBUSxFQUFFLEdBQUcsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUdwRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE9BQU8sRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUNoRCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFHeEQsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDdEUsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBRXJELE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzdELE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ3pELE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ3JFLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUN4RCxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQzlCLE9BQU8sRUFBRSxXQUFXLEVBQTJCLE1BQU0sZUFBZSxDQUFDO0FBQ3JFLE9BQU8sRUFBRSxJQUFJLEVBQW9CLE1BQU0sUUFBUSxDQUFDO0FBS2hELE9BQU8sRUFFSCxPQUFPLEVBQ1AsUUFBUSxFQUNSLFNBQVMsRUFDVCxxQkFBcUIsRUFDckIsYUFBYSxHQUVoQixNQUFNLDBCQUEwQixDQUFDO0FBQ2xDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzlELE9BQU8sRUFB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