diff options
author | mightymop <totzkotz@gmail.com> | 2022-02-26 22:28:42 +0300 |
---|---|---|
committer | mightymop <totzkotz@gmail.com> | 2022-02-27 05:50:58 +0300 |
commit | 1592fd1306c55b403b023c22b53c7439be51e8c4 (patch) | |
tree | 57b49131b2b2e0cfdda95872a47c4881bad52abf | |
parent | fda803221c0e44590c80db8d124519cf3b5c151b (diff) |
feat: sync muc messages on join
-rw-r--r-- | src/connection/AbstractConnection.ts | 42 | ||||
-rw-r--r-- | src/connection/Connection.interface.ts | 2 | ||||
-rw-r--r-- | src/plugins/mam/Archive.ts | 58 | ||||
-rw-r--r-- | src/plugins/mam/Plugin.ts | 58 |
4 files changed, 148 insertions, 12 deletions
diff --git a/src/connection/AbstractConnection.ts b/src/connection/AbstractConnection.ts index f678ec17..99ba62a6 100644 --- a/src/connection/AbstractConnection.ts +++ b/src/connection/AbstractConnection.ts @@ -316,6 +316,48 @@ abstract class AbstractConnection { return this.sendIQ(iq); } + public queryArchiveNewMessages(startDate: Date, archive: JID, version: string, queryId: string): Promise<Element> { + let iq = $iq({ + type: 'set', + to: archive.bare, + }); + + iq.c('query', { + xmlns: version, + queryid: queryId, + }); + + iq.c('x', { + xmlns: 'jabber:x:data', + type: 'submit', + }); + + iq.c('field', { + var: 'FORM_TYPE', + type: 'hidden', + }) + .c('value') + .t(version) + .up() + .up(); + + iq.c('field', { + var: 'start', + }) + .c('value') + .t(startDate.toISOString()) + .up() + .up(); + + iq.up().c('set', { + xmlns: 'http://jabber.org/protocol/rsm', + }); + + iq.up(); + + return this.sendIQ(iq); + } + public changePassword(newPassword: string): Promise<Element> { let iq = $iq({ type: 'set', diff --git a/src/connection/Connection.interface.ts b/src/connection/Connection.interface.ts index c05cdde4..51cfaa2e 100644 --- a/src/connection/Connection.interface.ts +++ b/src/connection/Connection.interface.ts @@ -58,6 +58,8 @@ export interface IConnection { end?: Date ): Promise<Element>; + queryArchiveNewMessages(startDate: Date, archive: IJID, version: string, queryId: string): Promise<Element>; + changePassword(newPassword: string): Promise<Element>; close(); diff --git a/src/plugins/mam/Archive.ts b/src/plugins/mam/Archive.ts index befdfe28..eb77efb2 100644 --- a/src/plugins/mam/Archive.ts +++ b/src/plugins/mam/Archive.ts @@ -14,6 +14,7 @@ import MultiUserContact from '@src/MultiUserContact'; export default class Archive { private archiveJid: IJID; private messageCache: JQuery<HTMLElement>[] = []; + private syncNewMessages: boolean = false; constructor(private plugin: MessageArchiveManagementPlugin, private contact: Contact) { let jid = contact.isGroupChat() ? contact.getJid() : plugin.getConnection().getJID(); @@ -53,6 +54,38 @@ export default class Archive { storage.registerHook(key, hook); } + public newMessages() { + if (this.messageCache.length > 0) { + Log.debug('Ongoing message retrieval'); + return false; + } + + let queryId = UUID.v4(); + + this.plugin.addQueryContactRelation(queryId, this.contact); + let connection = this.plugin.getConnection(); + + let firstMessage = this.contact.getTranscript().getFirstChatMessage(); + + if (firstMessage !== undefined && firstMessage !== null && (<any>firstMessage).data !== undefined) { + let startDate = firstMessage.getStamp(); + startDate.setMilliseconds(startDate.getMilliseconds() + 1); + this.plugin + .determineServerSupport(this.archiveJid) + .then(version => { + if (!version) { + throw new Error(`Archive JID ${this.archiveJid.full} has no support for MAM.`); + } + this.syncNewMessages = true; + return connection.queryArchiveNewMessages(startDate, this.archiveJid, <string>version, queryId); + }) + .then(this.onComplete) + .catch(stanza => { + Log.warn('Error while requesting archive', stanza); + }); + } + } + public nextMessages() { if (this.isExhausted()) { Log.debug('No more archived messages.'); @@ -90,7 +123,7 @@ export default class Archive { } let jid = !this.contact.isGroupChat() ? this.contact.getJid() : undefined; - + this.syncNewMessages = false; return connection.queryArchive(this.archiveJid, <string>version, queryId, jid, firstResultId, endDate); }) .then(this.onComplete) @@ -178,6 +211,9 @@ export default class Archive { } let transcript = this.contact.getTranscript(); + if (this.syncNewMessages) { + this.messageCache.reverse(); + } while (this.messageCache.length > 0) { let messageElement = this.messageCache.pop(); @@ -185,7 +221,13 @@ export default class Archive { try { let message = await this.parseForwardedMessage(messageElement); - transcript.unshiftMessage(message); + if (this.syncNewMessages) { + if (!transcript.findMessageByAttrId(message.getAttrId())) { + transcript.pushMessage(message); + } + } else { + transcript.unshiftMessage(message); + } } catch (err) { continue; } @@ -204,7 +246,17 @@ export default class Archive { unread: false, }); - transcript.unshiftMessage(archiveExhaustedMessage); + if (this.syncNewMessages) { + if ( + !transcript.getFirstMessage().isSystem() || + (transcript.getFirstMessage().isSystem() && + transcript.getFirstMessage().getPlaintextMessage() !== Translation.t('Archive_exhausted')) + ) { + transcript.pushMessage(archiveExhaustedMessage); + } + } else { + transcript.unshiftMessage(archiveExhaustedMessage); + } } this.setExhausted(isArchiveExhausted); diff --git a/src/plugins/mam/Plugin.ts b/src/plugins/mam/Plugin.ts index fe6c7737..dd6131fb 100644 --- a/src/plugins/mam/Plugin.ts +++ b/src/plugins/mam/Plugin.ts @@ -9,6 +9,7 @@ import Archive from './Archive'; import Contact from '@src/Contact'; import PluginAPI from '@src/plugin/PluginAPI'; import { IContact } from '@src/Contact.interface'; +import Client from '@src/Client'; const MIN_VERSION = '4.0.0'; const MAX_VERSION = '99.0.0'; @@ -56,19 +57,41 @@ export default class MessageArchiveManagementPlugin extends AbstractPlugin { pluginAPI.registerChatWindowInitializedHook((chatWindow: ChatWindow, contact: Contact) => { this.addLoadButtonIfEnabled(chatWindow, contact); + if (contact.isGroupChat()) { + this.syncMUCConversation(contact); + } }); pluginAPI.registerChatWindowClearedHook((chatWindow: ChatWindow, contact: Contact) => { let archiveJid = this.getArchiveJid(contact); + let fadeElement = chatWindow.getDom().find('.jsxc-window-fade'); + if (fadeElement.find('.jsxc-mam-load-more').length > 0) { + fadeElement.find('.jsxc-mam-load-more').remove(); + } + + let messageAreaElement = chatWindow.getDom().find('.jsxc-message-area'); + messageAreaElement.off('scroll'); + if (this.supportCache[archiveJid.bare]) { this.getArchive(contact.getJid()).clear(); } + + setTimeout(() => { + this.addLoadButtonIfEnabled(chatWindow, contact, false); + }, 500); }); this.pluginAPI.getConnection().registerHandler(this.onMamMessage, null, 'message', null); } + private syncMUCConversation(contact: IContact) { + let archive = this.getArchive(contact.getJid()); + Promise.resolve(archive.newMessages()).then(() => { + this.pluginAPI.Log.debug('Finished MAM Sync'); + }); + } + public getStorage() { return this.pluginAPI.getStorage(); } @@ -122,21 +145,24 @@ export default class MessageArchiveManagementPlugin extends AbstractPlugin { return new JID(jid.bare); } - private addLoadButtonIfEnabled(chatWindow: ChatWindow, contact: Contact) { + private addLoadButtonIfEnabled(chatWindow: ChatWindow, contact: Contact, loadNow: boolean = true) { let archivingJid = this.getArchiveJid(contact); this.determineServerSupport(archivingJid).then(version => { if (version) { - this.addLoadButton(chatWindow.getDom(), contact); + this.addLoadButton(chatWindow.getDom(), contact, loadNow); } }); } - private addLoadButton(chatWindowElement: JQuery<HTMLElement>, contact: Contact) { + private addLoadButton(chatWindowElement: JQuery<HTMLElement>, contact: Contact, loadNow: boolean) { + let fadeElement = chatWindowElement.find('.jsxc-window-fade'); + if (fadeElement.find('.jsxc-mam-load-more').length > 0) { + return; + } 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()); @@ -150,18 +176,32 @@ export default class MessageArchiveManagementPlugin extends AbstractPlugin { element.append(spanElement); messageAreaElement.on('scroll', function () { - const topDelta = 10; - const scrollTop = Math.abs(this.scrollTop); - const isAtTop = this.clientHeight + scrollTop + topDelta >= this.scrollHeight; + let scrollHeight: number = messageAreaElement[0].scrollHeight; + let clientHeight: number = messageAreaElement[0].clientHeight; + let scrollTop: number = messageAreaElement[0].scrollTop; + if (scrollTop < 0) scrollTop = scrollTop * -1; + + let autoLoadOnScrollToTop = Client.getOption('autoLoadOnScrollToTop') || false; + + if ( + (clientHeight + 42 > scrollHeight - scrollTop && !archive.isExhausted()) || + messageAreaElement.text().trim().length === 0 + ) { + if (autoLoadOnScrollToTop) { + archive.nextMessages(); + } - if (isAtTop && !archive.isExhausted()) { element.addClass(classNameShow); } else { element.removeClass(classNameShow); } }); - setTimeout(() => messageAreaElement.trigger('scroll'), 1000); + if (loadNow) { + messageAreaElement.trigger('scroll'); + } else { + element.addClass(classNameShow); + } if (!archive.isExhausted()) { chatWindowElement.addClass(classNameMamEnable); |