@jsxc/jsxc
Version:
Real-time XMPP chat application with video calls, file transfer and encrypted communication
216 lines (167 loc) • 6.59 kB
text/typescript
import { AbstractPlugin, IMetaData } from '../../plugin/AbstractPlugin';
import ChatWindow from '../../ui/ChatWindow';
import Translation from '../../util/Translation';
import PersistentMap from '../../util/PersistentMap';
import JID from '../../JID';
import { IJID } from '../../JID.interface';
import * as Namespace from '../../connection/xmpp/namespace';
import Archive from './Archive';
import Contact from '@src/Contact';
import PluginAPI from '@src/plugin/PluginAPI';
import { IContact } from '@src/Contact.interface';
const MIN_VERSION = '4.0.0';
const MAX_VERSION = '99.0.0';
const MAM1 = 'urn:xmpp:mam:1';
const MAM2 = 'urn:xmpp:mam:2';
Namespace.register('MAM1', MAM1);
Namespace.register('MAM2', MAM2);
export default class MessageArchiveManagementPlugin extends AbstractPlugin {
public static getId(): string {
return 'mam';
}
public static getName(): string {
return 'Message Archive Management';
}
public static getDescription(): string {
return;
}
public static getMetaData(): IMetaData {
return {
description: Translation.t('setting-mam-enable'),
xeps: [
{
id: 'XEP-0313',
name: 'Message Archive Management',
version: '0.6.3',
},
],
};
}
private archives: { [key: string]: Archive } = {};
private queryContactRelation: PersistentMap;
private supportCache: { [archiveJid: string]: string | boolean } = {};
constructor(pluginAPI: PluginAPI) {
super(MIN_VERSION, MAX_VERSION, pluginAPI);
this.queryContactRelation = new PersistentMap(pluginAPI.getStorage(), 'mam', 'query');
pluginAPI.registerChatWindowInitializedHook((chatWindow: ChatWindow, contact: Contact) => {
this.addLoadButtonIfEnabled(chatWindow, contact);
});
pluginAPI.registerChatWindowClearedHook((chatWindow: ChatWindow, contact: Contact) => {
let archiveJid = this.getArchiveJid(contact);
if (this.supportCache[archiveJid.bare]) {
this.getArchive(contact.getJid()).clear();
}
});
this.pluginAPI.getConnection().registerHandler(this.onMamMessage, null, 'message', null);
}
public getStorage() {
return this.pluginAPI.getStorage();
}
public getConnection() {
return this.pluginAPI.getConnection();
}
public addQueryContactRelation(queryId: string, contact: IContact) {
this.queryContactRelation.set(queryId, contact.getJid().bare);
}
public removeQueryContactRelation(queryId: string) {
this.queryContactRelation.remove(queryId);
}
public async determineServerSupport(archivingJid: IJID) {
if (typeof this.supportCache[archivingJid.bare] !== 'undefined') {
return this.supportCache[archivingJid.bare];
}
let discoInfoRepository = this.pluginAPI.getDiscoInfoRepository();
let version: string | boolean = false;
try {
let discoInfo = await discoInfoRepository.getCapabilities(archivingJid);
if (discoInfo && discoInfo.hasFeature(MAM2)) {
version = MAM2;
} else if (discoInfo && discoInfo.hasFeature(MAM1)) {
version = MAM1;
}
} catch (err) {
this.pluginAPI.Log.warn('Could not determine MAM server support:', err);
}
if (version) {
this.pluginAPI.Log.debug(archivingJid.bare + ' supports ' + version);
} else {
this.pluginAPI.Log.debug(archivingJid.bare + ' has no support for MAM');
}
this.supportCache[archivingJid.bare] = version;
return version;
}
private getArchiveJid(contact: Contact) {
let jid = contact.isGroupChat() ? contact.getJid() : this.getConnection().getJID();
return new JID(jid.bare);
}
private addLoadButtonIfEnabled(chatWindow: ChatWindow, contact: Contact) {
let archivingJid = this.getArchiveJid(contact);
this.determineServerSupport(archivingJid).then(version => {
if (version) {
this.addLoadButton(chatWindow.getDom(), contact);
}
});
}
private addLoadButton(chatWindowElement: JQuery<HTMLElement>, contact: Contact) {
let classNameShow = 'jsxc-show';
let classNameMamEnable = 'jsxc-mam-enabled';
let messageAreaElement = chatWindowElement.find('.jsxc-message-area');
let fadeElement = chatWindowElement.find('.jsxc-window-fade');
let archive = this.getArchive(contact.getJid());
let element = $('<div>');
element.addClass('jsxc-mam-load-more');
element.appendTo(fadeElement);
let spanElement = $('<span>').text(Translation.t('Load_older_messages'));
spanElement.click(() => {
archive.nextMessages();
});
element.append(spanElement);
messageAreaElement.on('scroll', function () {
const topDelta = 10;
const scrollTop = Math.abs(this.scrollTop);
const isAtTop = this.clientHeight + scrollTop + topDelta >= this.scrollHeight;
if (isAtTop && !archive.isExhausted()) {
element.addClass(classNameShow);
} else {
element.removeClass(classNameShow);
}
});
setTimeout(() => messageAreaElement.trigger('scroll'), 1000);
if (!archive.isExhausted()) {
chatWindowElement.addClass(classNameMamEnable);
}
archive.registerExhaustedHook(isExhausted => {
if (isExhausted) {
chatWindowElement.removeClass(classNameMamEnable);
} else {
chatWindowElement.addClass(classNameMamEnable);
messageAreaElement.scroll();
}
});
}
private onMamMessage = (stanza: string): boolean => {
let stanzaElement = $(stanza);
let resultElement = stanzaElement.find(`result[xmlns^="urn:xmpp:mam:"]`);
let queryId = resultElement.attr('queryid');
if (resultElement.length !== 1 || !queryId) {
return true;
}
let forwardedElement = resultElement.find('forwarded[xmlns="urn:xmpp:forward:0"]');
if (forwardedElement.length !== 1) {
return true;
}
let bareJid = this.queryContactRelation.get(queryId);
if (!bareJid) {
return true;
}
let jid = new JID(bareJid);
this.getArchive(jid).onForwardedMessage(forwardedElement);
return true;
};
public getArchive(jid: IJID) {
if (!this.archives[jid.bare]) {
this.archives[jid.bare] = new Archive(this, this.pluginAPI.getContact(jid));
}
return this.archives[jid.bare];
}
}