Mercurial > xmpp-account-manager
changeset 24:6c620e9f7d2c
Add support for retrieving all MAM messages, and for downloading it in a XEP-0227-like format.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Sun, 23 Dec 2018 15:39:51 +0100 |
parents | e99984564b17 |
children | d9da5c3e305d |
files | index.xhtml mam.js strophe.mam.js strophe.rsm.js util.js |
diffstat | 5 files changed, 185 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/index.xhtml +++ b/index.xhtml @@ -156,8 +156,9 @@ Enter your nickname, so people you know <td><textarea id="mam-prefs-never"/></td> </tr> </table> +<button id="mam-retrieve">Retrieve my message archive</button> <img width="24" height="24" id="mam-retrieve-spinner" hidden=""/><br/> <button disabled="">View my message archive</button><br/> -<button disabled="">Download my entire message archive</button><br/> +<button id="mam-download" disabled="">Download my entire message archive</button><br/> <button disabled="">⚠️ Purge my entire message archive</button> </p> </div> @@ -183,6 +184,8 @@ Enter your nickname, so people you know <script src="util.js"/> <script src="strophe.js"/> +<script src="strophe.rsm.js"/> +<script src="strophe.mam.js"/> <script src="client.js"/> <script src="nickname.js"/> <script src="avatar.js"/>
--- a/mam.js +++ b/mam.js @@ -6,6 +6,14 @@ function initMAM(connection) { const prefs_never = document.getElementById('mam-prefs-never'); const prefs_spinner = document.getElementById('mam-prefs-spinner'); + const mam_retrieve = document.getElementById('mam-retrieve'); + const download_button = document.getElementById('mam-download'); + const retrieve_spinner = document.getElementById('mam-retrieve-spinner'); + + const mam_data = []; + + download_button.disabled = true; + const iq = $iq({type: 'get'}) .c('prefs', {xmlns: NS.mam}); connection.sendIQ(iq, onMAMPrefs, onMAMPrefsError.bind(null, 'query failed.')); @@ -80,4 +88,42 @@ function initMAM(connection) { console.log('Error while changing MAM preferences.'); spinnerError(prefs_spinner); } + + mam_retrieve.addEventListener('click', function (evt) { + connection.mam.query(null, { + onMessage: function (message, delay, id) { + console.log('Got a MAM message:', message, delay, id); + const result = document.createElementNS(NS.mam, 'result'); + result.setAttributeNS(null, 'id', id); + const forwarded = document.createElementNS(NS.forward, 'forwarded'); + forwarded.appendChild(delay); + forwarded.appendChild(message); + result.appendChild(forwarded); + mam_data.push(result); + }, + onComplete: function () { + console.log('Received all messages, you can now download a backup of your archive.'); + download_button.disabled = false; + spinnerOk(retrieve_spinner); + }, + }); + displaySpinner(retrieve_spinner); + }); + + download_button.addEventListener('click', function (evt) { + // TODO: extend XEP-0227 with MAM support. + const messages = document.createElementNS('https://prosody.im/protocol/pie-mam', 'message-archive'); + for (let result of mam_data) + messages.appendChild(result); + const blob = new Blob([messages.outerHTML], {type: 'text/xml'}); + const link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); + link.hidden = false; + link.href = URL.createObjectURL(blob); + link.download = Strophe.getBareJidFromJid(connection.jid) + '-archive.xml'; + console.log(link.download); + document.body.appendChild(link); + link.click(); + URL.revokeObjectURL(link.href); + document.body.removeChild(link); + }); }
new file mode 100644 --- /dev/null +++ b/strophe.mam.js @@ -0,0 +1,82 @@ +/* XEP-0313: Message Archive Management + * Copyright (C) 2012 Kim Alvefur + * Copyright (C) 2018 Emmanuel Gil Peyrot + * + * This file is MIT/X11 licensed. Please see the + * LICENSE.txt file in the source package for more information. + * + * Modified by: Chris Tunbridge (github.com/Destreyf/) + * Updated to support v0.3 of the XMPP XEP-0313 standard + * http://xmpp.org/extensions/xep-0313.html + * + */ +//import { $iq, Strophe } from 'strophe.js'; + +Strophe.addConnectionPlugin('mam', { + _c: null, + _p: [ 'with', 'start', 'end' ], + init: function (conn) { + this._c = conn; + Strophe.addNamespace('MAM', 'urn:xmpp:mam:2'); + Strophe.addNamespace('Forward', 'urn:xmpp:forward:0'); + }, + query: function (jid, options) { + var _c = this._c; + var _p = this._p; + var attr = { + type:'set', + to:jid + }; + options = options || {}; + var queryid = options.queryid; + if (queryid) { + delete options.queryid; + } else { + queryid = _c.getUniqueId(); + } + var iq = $iq(attr).c('query', {xmlns: Strophe.NS.MAM, queryid: queryid}).c('x',{xmlns:'jabber:x:data', type:'submit'}); + + iq.c('field',{var:'FORM_TYPE', type:'hidden'}).c('value').t(Strophe.NS.MAM).up().up(); + for (var i = 0; i < _p.length; i++) { + var pn = _p[i]; + var p = options[pn]; + delete options[pn]; + if (p) { + iq.c('field',{var:pn}).c('value').t(p).up().up(); + } + } + iq.up(); + + var onMessage = options.onMessage; + delete options.onMessage; + var onComplete = options.onComplete; + delete options.onComplete; + iq.cnode(new Strophe.RSM(options).toXML()); + + var handler = _c.addHandler(function (message) { + // TODO: check the emitter too! + var result = message.firstChild; + if (!result || result.namespaceURI !== Strophe.NS.MAM || result.localName !== 'result' || result.getAttributeNS(null, 'queryid') !== queryid) + return true; + var id = result.getAttributeNS(null, 'id'); + var forwarded = result.firstChild; + if (!forwarded || forwarded.namespaceURI !== Strophe.NS.Forward || forwarded.localName !== 'forwarded') + return true; + var delay = null; + var childMessage = null; + for (var child of forwarded.childNodes.values()) { + if (child.namespaceURI === 'urn:xmpp:delay' && child.localName === 'delay' && delay === null) + delay = child; + else if (child.namespaceURI === 'jabber:client' && child.localName === 'message' && childMessage === null) + childMessage = child; + } + if (childMessage !== null && delay !== null) + onMessage(childMessage, delay, id); + return true; + }, Strophe.NS.MAM, 'message', null); + return _c.sendIQ(iq, function(){ + _c.deleteHandler(handler); + onComplete.apply(this, arguments); + }); + } +});
new file mode 100644 --- /dev/null +++ b/strophe.rsm.js @@ -0,0 +1,52 @@ +//import { $build, Strophe } from 'strophe.js'; + +Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm'); + +Strophe.RSM = function(options) { + this.attribs = ['max', 'first', 'last', 'after', 'before', 'index', 'count']; + + if (typeof options.xml != 'undefined') { + this.fromXMLElement(options.xml); + } else { + for (var ii = 0; ii < this.attribs.length; ii++) { + var attrib = this.attribs[ii]; + this[attrib] = options[attrib]; + } + } +}; + +Strophe.RSM.prototype = { + toXML: function() { + var xml = $build('set', {xmlns: Strophe.NS.RSM}); + for (var ii = 0; ii < this.attribs.length; ii++) { + var attrib = this.attribs[ii]; + if (typeof this[attrib] != 'undefined') { + xml = xml.c(attrib).t(this[attrib].toString()).up(); + } + } + return xml.tree(); + }, + + next: function(max) { + var newSet = new Strophe.RSM({max: max, after: this.last}); + return newSet; + }, + + previous: function(max) { + var newSet = new Strophe.RSM({max: max, before: this.first}); + return newSet; + }, + + fromXMLElement: function(xmlElement) { + for (var ii = 0; ii < this.attribs.length; ii++) { + var attrib = this.attribs[ii]; + var elem = xmlElement.getElementsByTagName(attrib)[0]; + if (typeof elem != 'undefined' && elem !== null) { + this[attrib] = Strophe.getText(elem); + if (attrib == 'first') { + this.index = elem.getAttribute('index'); + } + } + } + } +};