UNPKG

@jsxc/jsxc

Version:

Real-time XMPP chat application with video calls, file transfer and encrypted communication

215 lines (168 loc) 6.84 kB
import MessageArchiveManagementPlugin from './Plugin'; import Message from '../../Message'; import { IContact as Contact } from '../../Contact.interface'; import JID from '../../JID'; import UUID from '../../util/UUID'; import Utils from '../../util/Utils'; import Log from '../../util/Log'; import Translation from '../../util/Translation'; import * as Namespace from '../../connection/xmpp/namespace'; import { IMessage, MessageMark } from '@src/Message.interface'; import { IJID } from '@src/JID.interface'; import MultiUserContact from '@src/MultiUserContact'; export default class Archive { private archiveJid: IJID; private messageCache: JQuery<HTMLElement>[] = []; constructor(private plugin: MessageArchiveManagementPlugin, private contact: Contact) { let jid = contact.isGroupChat() ? contact.getJid() : plugin.getConnection().getJID(); this.archiveJid = new JID(jid.bare); } public clear() { this.setExhausted(false); this.setFirstResultId(undefined); } private getId() { return this.contact.getJid().bare; } private getFirstResultId(): string { return this.plugin.getStorage().getItem('firstResultId', this.getId()) || ''; } private setFirstResultId(resultId: string) { return this.plugin.getStorage().setItem('firstResultId', this.getId(), resultId); } public isExhausted(): boolean { return !!this.plugin.getStorage().getItem('exhausted', this.getId()); } private setExhausted(exhausted: boolean) { return this.plugin.getStorage().setItem('exhausted', this.getId(), exhausted); } public registerExhaustedHook(hook: (isExhausted) => void) { let storage = this.plugin.getStorage(); let key = storage.generateKey('exhausted', this.getId()); storage.registerHook(key, hook); } public nextMessages() { if (this.isExhausted()) { Log.debug('No more archived messages.'); return false; } if (this.messageCache.length > 0) { Log.debug('Ongoing message retrieval'); return false; } let queryId = UUID.v4(); this.plugin.addQueryContactRelation(queryId, this.contact); let firstResultId = this.getFirstResultId(); let endDate: Date; if (!firstResultId) { let lastMessage = this.contact.getTranscript().getLastMessage(); if (lastMessage) { endDate = lastMessage.getStamp(); endDate.setSeconds(endDate.getSeconds() - 1); } else { endDate = undefined; } } let connection = this.plugin.getConnection(); this.plugin .determineServerSupport(this.archiveJid) .then(version => { if (!version) { throw new Error(`Archive JID ${this.archiveJid.full} has no support for MAM.`); } let jid = !this.contact.isGroupChat() ? this.contact.getJid() : undefined; return connection.queryArchive(this.archiveJid, <string>version, queryId, jid, firstResultId, endDate); }) .then(this.onComplete) .catch(stanza => { Log.warn('Error while requesting archive', stanza); }); } public onForwardedMessage(forwardedElement: JQuery<HTMLElement>) { this.messageCache.push(forwardedElement); } public async parseForwardedMessage(forwardedElement: JQuery<HTMLElement>): Promise<IMessage> { let messageElement = forwardedElement.find('message'); let messageId = messageElement.attr('id'); if (messageElement.length !== 1) { return; } let from = new JID(messageElement.attr('from')); let to = new JID(messageElement.attr('to')); if (this.archiveJid.bare !== from.bare && this.archiveJid.bare !== to.bare) { return; } let delayElement = forwardedElement.find('delay[xmlns="urn:xmpp:delay"]'); let stamp = delayElement.length > 0 ? new Date(delayElement.attr('stamp')) : new Date(); let plaintextBody = Utils.removeHTML(messageElement.find('> body').text()); let htmlBody = messageElement.find('html body' + Namespace.getFilter('XHTML')); if (!plaintextBody) { return; } let direction = this.contact.getJid().bare === to.bare ? Message.DIRECTION.OUT : Message.DIRECTION.IN; let stanzaIdElement = messageElement.find('stanza-id[xmlns="urn:xmpp:sid:0"]'); let originIdElement = messageElement.find('origin-id[xmlns="urn:xmpp:sid:0"]'); let uid = direction === Message.DIRECTION.OUT && originIdElement.length ? originIdElement.attr('id') : stanzaIdElement.attr('id'); if (Message.exists(uid)) { return new Message(uid); } let messageProperties = { uid, attrId: messageId, peer: this.contact.getJid(), direction, plaintextMessage: plaintextBody, htmlMessage: htmlBody.html(), stamp: stamp.getTime(), mark: MessageMark.transferred, unread: false, sender: undefined, }; if (this.contact.isGroupChat()) { messageProperties.sender = { name: from.resource, }; let contact = <MultiUserContact>this.contact; messageProperties.direction = contact.getNickname() === from.resource ? Message.DIRECTION.OUT : Message.DIRECTION.IN; } return new Message(messageProperties); } public onComplete = async (stanza: Element) => { let stanzaElement = $(stanza); let finElement = stanzaElement.find(`fin[xmlns^="urn:xmpp:mam:"]`); if (finElement.length !== 1) { Log.warn('No fin element found'); return; } let transcript = this.contact.getTranscript(); while (this.messageCache.length > 0) { let messageElement = this.messageCache.pop(); try { let message = await this.parseForwardedMessage(messageElement); transcript.unshiftMessage(message); } catch (err) { continue; } } let isArchiveExhausted = finElement.attr('complete') === 'true'; let firstResultId = finElement.find('first').text(); let queryId = finElement.attr('queryid'); if (isArchiveExhausted) { let archiveExhaustedMessage = new Message({ peer: this.contact.getJid(), direction: Message.DIRECTION.SYS, plaintextMessage: Translation.t('Archive_exhausted'), mark: MessageMark.transferred, unread: false, }); transcript.unshiftMessage(archiveExhaustedMessage); } this.setExhausted(isArchiveExhausted); this.setFirstResultId(firstResultId); this.plugin.removeQueryContactRelation(queryId); }; }