2018-04-16 18:08:00 +02:00
|
|
|
|
// Converse.js
|
2017-08-16 11:16:22 +02:00
|
|
|
|
// http://conversejs.org
|
|
|
|
|
//
|
2018-04-16 18:08:00 +02:00
|
|
|
|
// Copyright (c) 2012-2018, the Converse.js developers
|
2017-08-16 11:16:22 +02:00
|
|
|
|
// Licensed under the Mozilla Public License (MPLv2)
|
|
|
|
|
|
|
|
|
|
(function (root, factory) {
|
2018-02-15 15:50:11 +01:00
|
|
|
|
define([
|
|
|
|
|
"converse-core",
|
2018-04-17 15:17:39 +02:00
|
|
|
|
"emojione",
|
2018-04-18 10:03:21 +02:00
|
|
|
|
"filesize",
|
2018-05-24 21:09:33 +02:00
|
|
|
|
"templates/chatboxes.html",
|
2018-04-18 16:55:26 +02:00
|
|
|
|
"backbone.overview",
|
2018-06-06 11:04:23 +02:00
|
|
|
|
"utils/form"
|
2018-02-15 15:50:11 +01:00
|
|
|
|
], factory);
|
2018-04-18 10:03:21 +02:00
|
|
|
|
}(this, function (converse, emojione, filesize, tpl_chatboxes) {
|
2017-08-16 11:16:22 +02:00
|
|
|
|
"use strict";
|
2018-02-15 15:50:11 +01:00
|
|
|
|
|
2018-04-18 16:55:26 +02:00
|
|
|
|
const { $msg, Backbone, Promise, Strophe, b64_sha1, moment, sizzle, utils, _ } = converse.env;
|
2018-04-17 15:17:39 +02:00
|
|
|
|
const u = converse.env.utils;
|
|
|
|
|
|
2018-07-07 22:44:07 +02:00
|
|
|
|
Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0');
|
|
|
|
|
|
2018-04-17 15:17:39 +02:00
|
|
|
|
|
2017-08-16 11:16:22 +02:00
|
|
|
|
converse.plugins.add('converse-chatboxes', {
|
|
|
|
|
|
2018-05-11 19:50:26 +02:00
|
|
|
|
dependencies: ["converse-roster", "converse-vcard"],
|
2018-05-03 18:34:28 +02:00
|
|
|
|
|
2017-08-16 11:16:22 +02:00
|
|
|
|
overrides: {
|
|
|
|
|
// Overrides mentioned here will be picked up by converse.js's
|
|
|
|
|
// plugin architecture they will replace existing methods on the
|
|
|
|
|
// relevant objects or classes.
|
|
|
|
|
|
2018-03-25 12:53:40 +02:00
|
|
|
|
initStatus: function (reconnecting) {
|
2017-08-16 11:16:22 +02:00
|
|
|
|
const { _converse } = this.__super__;
|
2018-03-25 12:53:40 +02:00
|
|
|
|
if (!reconnecting) {
|
|
|
|
|
_converse.chatboxviews.closeAllChatBoxes();
|
|
|
|
|
}
|
2017-08-16 11:16:22 +02:00
|
|
|
|
return this.__super__.initStatus.apply(this, arguments);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
initialize () {
|
|
|
|
|
/* The initialize function gets called as soon as the plugin is
|
|
|
|
|
* loaded by converse.js's plugin machinery.
|
|
|
|
|
*/
|
2018-04-11 15:29:41 +02:00
|
|
|
|
const { _converse } = this,
|
2018-07-07 22:44:07 +02:00
|
|
|
|
{ __ } = _converse;
|
2017-08-16 11:16:22 +02:00
|
|
|
|
|
2018-05-02 17:07:57 +02:00
|
|
|
|
// Configuration values for this plugin
|
|
|
|
|
// ====================================
|
|
|
|
|
// Refer to docs/source/configuration.rst for explanations of these
|
|
|
|
|
// configuration settings.
|
|
|
|
|
_converse.api.settings.update({
|
2018-05-31 06:38:40 +02:00
|
|
|
|
'filter_by_resource': false,
|
|
|
|
|
'auto_join_private_chats': [],
|
|
|
|
|
'forward_messages': false,
|
2018-05-02 17:07:57 +02:00
|
|
|
|
});
|
2017-08-16 12:31:17 +02:00
|
|
|
|
_converse.api.promises.add([
|
|
|
|
|
'chatBoxesFetched',
|
2018-05-02 17:07:57 +02:00
|
|
|
|
'chatBoxesInitialized',
|
|
|
|
|
'privateChatsAutoJoined'
|
2017-08-16 12:31:17 +02:00
|
|
|
|
]);
|
2017-08-16 11:16:22 +02:00
|
|
|
|
|
2017-10-31 23:04:46 +01:00
|
|
|
|
function openChat (jid) {
|
|
|
|
|
if (!utils.isValidJID(jid)) {
|
2018-04-14 21:13:02 +02:00
|
|
|
|
return _converse.log(
|
2017-10-31 23:04:46 +01:00
|
|
|
|
`Invalid JID "${jid}" provided in URL fragment`,
|
|
|
|
|
Strophe.LogLevel.WARN
|
|
|
|
|
);
|
|
|
|
|
}
|
2018-07-30 18:16:32 +02:00
|
|
|
|
_converse.api.chats.open(jid);
|
2017-10-31 23:04:46 +01:00
|
|
|
|
}
|
|
|
|
|
_converse.router.route('converse/chat?jid=:jid', openChat);
|
|
|
|
|
|
|
|
|
|
|
2017-12-06 22:09:46 +01:00
|
|
|
|
_converse.Message = Backbone.Model.extend({
|
2018-04-17 15:17:39 +02:00
|
|
|
|
|
|
|
|
|
defaults () {
|
2017-12-06 22:09:46 +01:00
|
|
|
|
return {
|
2018-04-17 15:17:39 +02:00
|
|
|
|
'msgid': _converse.connection.getUniqueId(),
|
|
|
|
|
'time': moment().format()
|
2017-12-06 22:09:46 +01:00
|
|
|
|
};
|
2018-04-17 15:17:39 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
initialize () {
|
2018-05-14 13:29:33 +02:00
|
|
|
|
this.setVCard();
|
2018-04-17 15:17:39 +02:00
|
|
|
|
if (this.get('file')) {
|
|
|
|
|
this.on('change:put', this.uploadFile, this);
|
|
|
|
|
|
|
|
|
|
if (!_.includes([_converse.SUCCESS, _converse.FAILURE], this.get('upload'))) {
|
|
|
|
|
this.getRequestSlotURL();
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-04 17:26:43 +02:00
|
|
|
|
if (this.isOnlyChatStateNotification()) {
|
|
|
|
|
window.setTimeout(this.destroy.bind(this), 20000);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2018-05-24 10:48:17 +02:00
|
|
|
|
getVCardForChatroomOccupant () {
|
|
|
|
|
const chatbox = this.collection.chatbox,
|
|
|
|
|
nick = Strophe.getResourceFromJid(this.get('from'));
|
|
|
|
|
|
|
|
|
|
if (chatbox.get('nick') === nick) {
|
|
|
|
|
return _converse.xmppstatus.vcard;
|
|
|
|
|
} else {
|
|
|
|
|
let vcard;
|
|
|
|
|
if (this.get('vcard_jid')) {
|
|
|
|
|
vcard = _converse.vcards.findWhere({'jid': this.get('vcard_jid')});
|
|
|
|
|
}
|
|
|
|
|
if (!vcard) {
|
|
|
|
|
let jid;
|
2018-05-14 13:29:33 +02:00
|
|
|
|
const occupant = chatbox.occupants.findWhere({'nick': nick});
|
2018-05-24 10:48:17 +02:00
|
|
|
|
if (occupant && occupant.get('jid')) {
|
|
|
|
|
jid = occupant.get('jid');
|
|
|
|
|
this.save({'vcard_jid': jid}, {'silent': true});
|
|
|
|
|
} else {
|
|
|
|
|
jid = this.get('from');
|
|
|
|
|
}
|
|
|
|
|
vcard = _converse.vcards.findWhere({'jid': jid}) || _converse.vcards.create({'jid': jid});
|
2018-05-14 13:29:33 +02:00
|
|
|
|
}
|
2018-05-24 10:48:17 +02:00
|
|
|
|
return vcard;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setVCard () {
|
|
|
|
|
if (this.get('type') === 'groupchat') {
|
|
|
|
|
this.vcard = this.getVCardForChatroomOccupant();
|
2018-05-14 13:29:33 +02:00
|
|
|
|
} else {
|
|
|
|
|
const jid = this.get('from');
|
|
|
|
|
this.vcard = _converse.vcards.findWhere({'jid': jid}) || _converse.vcards.create({'jid': jid});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2018-05-04 17:26:43 +02:00
|
|
|
|
isOnlyChatStateNotification () {
|
2018-05-07 12:57:05 +02:00
|
|
|
|
return u.isOnlyChatStateNotification(this);
|
2018-04-17 15:17:39 +02:00
|
|
|
|
},
|
|
|
|
|
|
2018-05-03 20:05:45 +02:00
|
|
|
|
getDisplayName () {
|
|
|
|
|
if (this.get('type') === 'groupchat') {
|
|
|
|
|
return this.get('nick');
|
|
|
|
|
} else {
|
|
|
|
|
return this.vcard.get('fullname') || this.get('from');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2018-04-17 15:17:39 +02:00
|
|
|
|
sendSlotRequestStanza () {
|
|
|
|
|
/* Send out an IQ stanza to request a file upload slot.
|
|
|
|
|
*
|
|
|
|
|
* https://xmpp.org/extensions/xep-0363.html#request
|
|
|
|
|
*/
|
|
|
|
|
const file = this.get('file');
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
const iq = converse.env.$iq({
|
|
|
|
|
'from': _converse.jid,
|
|
|
|
|
'to': this.get('slot_request_url'),
|
|
|
|
|
'type': 'get'
|
|
|
|
|
}).c('request', {
|
|
|
|
|
'xmlns': Strophe.NS.HTTPUPLOAD,
|
|
|
|
|
'filename': file.name,
|
|
|
|
|
'size': file.size,
|
|
|
|
|
'content-type': file.type
|
|
|
|
|
})
|
|
|
|
|
_converse.connection.sendIQ(iq, resolve, reject);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getRequestSlotURL () {
|
|
|
|
|
this.sendSlotRequestStanza().then((stanza) => {
|
|
|
|
|
const slot = stanza.querySelector('slot');
|
|
|
|
|
if (slot) {
|
|
|
|
|
this.save({
|
|
|
|
|
'get': slot.querySelector('get').getAttribute('url'),
|
|
|
|
|
'put': slot.querySelector('put').getAttribute('url'),
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
return this.save({
|
|
|
|
|
'type': 'error',
|
2018-04-18 11:49:53 +02:00
|
|
|
|
'message': __("Sorry, could not determine file upload URL.")
|
2018-04-17 15:17:39 +02:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}).catch((e) => {
|
|
|
|
|
_converse.log(e, Strophe.LogLevel.ERROR);
|
|
|
|
|
return this.save({
|
|
|
|
|
'type': 'error',
|
|
|
|
|
'message': __("Sorry, could not determine upload URL.")
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
uploadFile () {
|
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
|
xhr.onreadystatechange = () => {
|
|
|
|
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
|
|
|
_converse.log("Status: " + xhr.status, Strophe.LogLevel.INFO);
|
|
|
|
|
if (xhr.status === 200 || xhr.status === 201) {
|
|
|
|
|
this.save({
|
|
|
|
|
'upload': _converse.SUCCESS,
|
2018-04-27 16:57:01 +02:00
|
|
|
|
'oob_url': this.get('get'),
|
2018-04-17 15:17:39 +02:00
|
|
|
|
'message': this.get('get')
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2018-04-24 10:09:45 +02:00
|
|
|
|
xhr.onerror();
|
2018-04-17 15:17:39 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2018-04-24 11:08:26 +02:00
|
|
|
|
|
2018-04-17 15:17:39 +02:00
|
|
|
|
xhr.upload.addEventListener("progress", (evt) => {
|
|
|
|
|
if (evt.lengthComputable) {
|
|
|
|
|
this.set('progress', evt.loaded / evt.total);
|
|
|
|
|
}
|
|
|
|
|
}, false);
|
2018-04-24 11:08:26 +02:00
|
|
|
|
|
2018-04-17 15:17:39 +02:00
|
|
|
|
xhr.onerror = () => {
|
2018-05-06 12:03:13 +02:00
|
|
|
|
let message;
|
2018-04-24 10:09:45 +02:00
|
|
|
|
if (xhr.responseText) {
|
2018-05-06 12:03:13 +02:00
|
|
|
|
message = __('Sorry, could not succesfully upload your file. Your server’s response: "%1$s"', xhr.responseText)
|
|
|
|
|
} else {
|
|
|
|
|
message = __('Sorry, could not succesfully upload your file.');
|
2018-04-24 10:09:45 +02:00
|
|
|
|
}
|
2018-04-17 15:17:39 +02:00
|
|
|
|
this.save({
|
2018-04-24 10:09:45 +02:00
|
|
|
|
'type': 'error',
|
2018-04-17 15:17:39 +02:00
|
|
|
|
'upload': _converse.FAILURE,
|
2018-04-24 10:09:45 +02:00
|
|
|
|
'message': message
|
2018-04-17 15:17:39 +02:00
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
xhr.open('PUT', this.get('put'), true);
|
|
|
|
|
xhr.setRequestHeader("Content-type", 'application/octet-stream');
|
|
|
|
|
xhr.send(this.get('file'));
|
2017-12-06 22:09:46 +01:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_converse.Messages = Backbone.Collection.extend({
|
|
|
|
|
model: _converse.Message,
|
|
|
|
|
comparator: 'time'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2018-05-22 16:44:58 +02:00
|
|
|
|
_converse.ChatBox = _converse.ModelWithVCardAndPresence.extend({
|
2018-06-07 14:09:19 +02:00
|
|
|
|
defaults () {
|
|
|
|
|
return {
|
|
|
|
|
'bookmarked': false,
|
|
|
|
|
'chat_state': undefined,
|
|
|
|
|
'num_unread': 0,
|
|
|
|
|
'type': 'chatbox',
|
|
|
|
|
'message_type': 'chat',
|
|
|
|
|
'url': '',
|
|
|
|
|
'hidden': _.includes(['mobile', 'fullscreen'], _converse.view_mode)
|
|
|
|
|
}
|
2017-12-06 22:09:46 +01:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
initialize () {
|
2018-05-22 16:44:58 +02:00
|
|
|
|
_converse.ModelWithVCardAndPresence.prototype.initialize.apply(this, arguments);
|
|
|
|
|
|
2018-05-10 14:44:46 +02:00
|
|
|
|
_converse.api.waitUntil('rosterContactsFetched').then(() => {
|
2018-05-10 22:14:37 +02:00
|
|
|
|
this.addRelatedContact(_converse.roster.findWhere({'jid': this.get('jid')}));
|
2018-05-10 14:44:46 +02:00
|
|
|
|
});
|
2017-12-06 22:09:46 +01:00
|
|
|
|
this.messages = new _converse.Messages();
|
2018-05-18 12:21:02 +02:00
|
|
|
|
this.messages.browserStorage = new Backbone.BrowserStorage[_converse.storage](
|
2017-12-06 22:09:46 +01:00
|
|
|
|
b64_sha1(`converse.messages${this.get('jid')}${_converse.bare_jid}`));
|
2018-04-16 18:08:00 +02:00
|
|
|
|
this.messages.chatbox = this;
|
2018-03-07 10:31:33 +01:00
|
|
|
|
|
2018-04-17 15:17:39 +02:00
|
|
|
|
this.messages.on('change:upload', (message) => {
|
|
|
|
|
if (message.get('upload') === _converse.SUCCESS) {
|
|
|
|
|
this.sendMessageStanza(message);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2018-04-22 04:20:14 +02:00
|
|
|
|
this.on('change:chat_state', this.sendChatState, this);
|
|
|
|
|
|
2017-12-06 22:09:46 +01:00
|
|
|
|
this.save({
|
|
|
|
|
// The chat_state will be set to ACTIVE once the chat box is opened
|
|
|
|
|
// and we listen for change:chat_state, so shouldn't set it to ACTIVE here.
|
|
|
|
|
'box_id' : b64_sha1(this.get('jid')),
|
|
|
|
|
'time_opened': this.get('time_opened') || moment().valueOf(),
|
|
|
|
|
'user_id' : Strophe.getNodeFromJid(this.get('jid'))
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2018-05-10 22:14:37 +02:00
|
|
|
|
addRelatedContact (contact) {
|
|
|
|
|
if (!_.isUndefined(contact)) {
|
|
|
|
|
this.contact = contact;
|
|
|
|
|
this.trigger('contactAdded', contact);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2018-05-10 13:43:03 +02:00
|
|
|
|
getDisplayName () {
|
|
|
|
|
return this.vcard.get('fullname') || this.get('jid');
|
|
|
|
|
},
|
|
|
|
|
|
2018-07-16 00:49:00 +02:00
|
|
|
|
handleMessageCorrection (stanza) {
|
|
|
|
|
const replace = sizzle(`replace[xmlns="${Strophe.NS.MESSAGE_CORRECT}"]`, stanza).pop();
|
|
|
|
|
if (replace) {
|
|
|
|
|
const msgid = replace && replace.getAttribute('id') || stanza.getAttribute('id'),
|
|
|
|
|
message = msgid && this.messages.findWhere({msgid}),
|
|
|
|
|
older_versions = message.get('older_versions') || [];
|
|
|
|
|
older_versions.push(message.get('message'));
|
|
|
|
|
message.save({
|
|
|
|
|
'message': _converse.chatboxes.getMessageBody(stanza),
|
|
|
|
|
'older_versions': older_versions,
|
|
|
|
|
'edited': true
|
|
|
|
|
});
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
|
2018-04-14 21:13:02 +02:00
|
|
|
|
createMessageStanza (message) {
|
|
|
|
|
/* Given a _converse.Message Backbone.Model, return the XML
|
|
|
|
|
* stanza that represents it.
|
|
|
|
|
*
|
|
|
|
|
* Parameters:
|
|
|
|
|
* (Object) message - The Backbone.Model representing the message
|
|
|
|
|
*/
|
2018-04-11 15:29:41 +02:00
|
|
|
|
const stanza = $msg({
|
2018-04-14 21:13:02 +02:00
|
|
|
|
'from': _converse.connection.jid,
|
|
|
|
|
'to': this.get('jid'),
|
2018-04-17 15:17:39 +02:00
|
|
|
|
'type': this.get('message_type'),
|
2018-07-07 22:44:07 +02:00
|
|
|
|
'id': message.get('edited') && _converse.connection.getUniqueId() || message.get('msgid'),
|
2018-04-14 21:13:02 +02:00
|
|
|
|
}).c('body').t(message.get('message')).up()
|
|
|
|
|
.c(_converse.ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}).up();
|
2018-04-11 15:29:41 +02:00
|
|
|
|
|
2018-04-14 21:13:02 +02:00
|
|
|
|
if (message.get('is_spoiler')) {
|
|
|
|
|
if (message.get('spoiler_hint')) {
|
|
|
|
|
stanza.c('spoiler', {'xmlns': Strophe.NS.SPOILER }, message.get('spoiler_hint')).up();
|
|
|
|
|
} else {
|
|
|
|
|
stanza.c('spoiler', {'xmlns': Strophe.NS.SPOILER }).up();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (message.get('file')) {
|
|
|
|
|
stanza.c('x', {'xmlns': Strophe.NS.OUTOFBAND}).c('url').t(message.get('message')).up();
|
|
|
|
|
}
|
2018-07-07 22:44:07 +02:00
|
|
|
|
if (message.get('edited')) {
|
|
|
|
|
stanza.c('replace', {
|
|
|
|
|
'xmlns': Strophe.NS.MESSAGE_CORRECT,
|
|
|
|
|
'id': message.get('msgid')
|
|
|
|
|
}).up();
|
|
|
|
|
}
|
2018-04-11 15:29:41 +02:00
|
|
|
|
return stanza;
|
|
|
|
|
},
|
|
|
|
|
|
2018-04-16 17:46:48 +02:00
|
|
|
|
sendMessageStanza (message) {
|
|
|
|
|
const messageStanza = this.createMessageStanza(message);
|
2018-04-14 21:13:02 +02:00
|
|
|
|
_converse.connection.send(messageStanza);
|
|
|
|
|
if (_converse.forward_messages) {
|
|
|
|
|
// Forward the message, so that other connected resources are also aware of it.
|
|
|
|
|
_converse.connection.send(
|
2018-04-17 15:17:39 +02:00
|
|
|
|
$msg({
|
|
|
|
|
'to': _converse.bare_jid,
|
|
|
|
|
'type': this.get('message_type'),
|
|
|
|
|
'id': message.get('msgid')
|
|
|
|
|
}).c('forwarded', {'xmlns': Strophe.NS.FORWARD})
|
|
|
|
|
.c('delay', {
|
|
|
|
|
'xmns': Strophe.NS.DELAY,
|
|
|
|
|
'stamp': moment().format()
|
|
|
|
|
}).up()
|
|
|
|
|
.cnode(messageStanza.tree())
|
2018-04-14 21:13:02 +02:00
|
|
|
|
);
|
|
|
|
|
}
|
2018-04-11 15:29:41 +02:00
|
|
|
|
},
|
|
|
|
|
|
2018-04-17 15:17:39 +02:00
|
|
|
|
getOutgoingMessageAttributes (text, spoiler_hint) {
|
|
|
|
|
const fullname = _converse.xmppstatus.get('fullname'),
|
|
|
|
|
is_spoiler = this.get('composing_spoiler');
|
|
|
|
|
|
|
|
|
|
return {
|
2018-05-01 11:46:30 +02:00
|
|
|
|
'fullname': fullname,
|
2018-05-03 20:05:45 +02:00
|
|
|
|
'from': _converse.bare_jid,
|
2018-04-17 15:17:39 +02:00
|
|
|
|
'sender': 'me',
|
|
|
|
|
'time': moment().format(),
|
|
|
|
|
'message': text ? u.httpToGeoUri(emojione.shortnameToUnicode(text), _converse) : undefined,
|
|
|
|
|
'is_spoiler': is_spoiler,
|
|
|
|
|
'spoiler_hint': is_spoiler ? spoiler_hint : undefined
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
2018-04-14 21:13:02 +02:00
|
|
|
|
sendMessage (attrs) {
|
|
|
|
|
/* Responsible for sending off a text message.
|
|
|
|
|
*
|
|
|
|
|
* Parameters:
|
|
|
|
|
* (Message) message - The chat message
|
|
|
|
|
*/
|
2018-07-08 10:13:15 +02:00
|
|
|
|
const message = this.messages.findWhere('correcting')
|
|
|
|
|
if (message) {
|
|
|
|
|
const older_versions = message.get('older_versions') || [];
|
|
|
|
|
older_versions.push(message.get('message'));
|
|
|
|
|
message.save({
|
|
|
|
|
'message': attrs.message,
|
|
|
|
|
'older_versions': older_versions,
|
|
|
|
|
'edited': true,
|
|
|
|
|
'correcting': false
|
|
|
|
|
});
|
|
|
|
|
return this.sendMessageStanza(message);
|
2018-07-07 22:44:07 +02:00
|
|
|
|
}
|
|
|
|
|
return this.sendMessageStanza(this.messages.create(attrs));
|
2018-04-11 15:29:41 +02:00
|
|
|
|
},
|
2018-04-14 21:13:02 +02:00
|
|
|
|
|
2018-04-22 04:20:14 +02:00
|
|
|
|
sendChatState () {
|
|
|
|
|
/* Sends a message with the status of the user in this chat session
|
|
|
|
|
* as taken from the 'chat_state' attribute of the chat box.
|
|
|
|
|
* See XEP-0085 Chat State Notifications.
|
|
|
|
|
*/
|
|
|
|
|
_converse.connection.send(
|
|
|
|
|
$msg({'to':this.get('jid'), 'type': 'chat'})
|
|
|
|
|
.c(this.get('chat_state'), {'xmlns': Strophe.NS.CHATSTATES}).up()
|
|
|
|
|
.c('no-store', {'xmlns': Strophe.NS.HINTS}).up()
|
|
|
|
|
.c('no-permanent-store', {'xmlns': Strophe.NS.HINTS})
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
2018-04-17 15:17:39 +02:00
|
|
|
|
sendFiles (files) {
|
2018-04-14 21:13:02 +02:00
|
|
|
|
_converse.api.disco.supports(Strophe.NS.HTTPUPLOAD, _converse.domain).then((result) => {
|
2018-04-18 10:03:21 +02:00
|
|
|
|
const item = result.pop(),
|
|
|
|
|
data = item.dataforms.where({'FORM_TYPE': {'value': Strophe.NS.HTTPUPLOAD, 'type': "hidden"}}).pop(),
|
|
|
|
|
max_file_size = window.parseInt(_.get(data, 'attributes.max-file-size.value')),
|
|
|
|
|
slot_request_url = _.get(item, 'id');
|
|
|
|
|
|
2018-04-17 15:17:39 +02:00
|
|
|
|
if (!slot_request_url) {
|
2018-04-18 10:03:21 +02:00
|
|
|
|
this.messages.create({
|
|
|
|
|
'message': __("Sorry, looks like file upload is not supported by your server."),
|
|
|
|
|
'type': 'error',
|
|
|
|
|
});
|
|
|
|
|
return;
|
2018-04-14 21:13:02 +02:00
|
|
|
|
}
|
2018-04-17 15:17:39 +02:00
|
|
|
|
_.each(files, (file) => {
|
2018-04-18 10:03:21 +02:00
|
|
|
|
if (!window.isNaN(max_file_size) && window.parseInt(file.size) > max_file_size) {
|
|
|
|
|
return this.messages.create({
|
|
|
|
|
'message': __('The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.',
|
|
|
|
|
file.name, filesize(max_file_size)),
|
|
|
|
|
'type': 'error',
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
this.messages.create(
|
|
|
|
|
_.extend(
|
|
|
|
|
this.getOutgoingMessageAttributes(), {
|
|
|
|
|
'file': file,
|
|
|
|
|
'progress': 0,
|
|
|
|
|
'slot_request_url': slot_request_url,
|
|
|
|
|
'type': this.get('message_type'),
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
2018-04-17 15:17:39 +02:00
|
|
|
|
});
|
|
|
|
|
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
|
2018-04-11 15:29:41 +02:00
|
|
|
|
},
|
|
|
|
|
|
2018-07-06 00:45:53 +02:00
|
|
|
|
getMessageAttributesFromStanza (stanza, original_stanza) {
|
2018-02-02 21:53:53 +01:00
|
|
|
|
/* Parses a passed in message stanza and returns an object
|
|
|
|
|
* of attributes.
|
|
|
|
|
*
|
|
|
|
|
* Parameters:
|
2018-07-06 00:45:53 +02:00
|
|
|
|
* (XMLElement) stanza - The message stanza
|
2018-02-02 21:53:53 +01:00
|
|
|
|
* (XMLElement) delay - The <delay> node from the
|
|
|
|
|
* stanza, if there was one.
|
|
|
|
|
* (XMLElement) original_stanza - The original stanza,
|
|
|
|
|
* that contains the message stanza, if it was
|
|
|
|
|
* contained, otherwise it's the message stanza itself.
|
|
|
|
|
*/
|
2018-05-01 11:46:30 +02:00
|
|
|
|
const { _converse } = this.__super__,
|
|
|
|
|
{ __ } = _converse,
|
2018-06-07 12:45:53 +02:00
|
|
|
|
archive = sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, original_stanza).pop(),
|
|
|
|
|
spoiler = sizzle(`spoiler[xmlns="${Strophe.NS.SPOILER}"]`, original_stanza).pop(),
|
|
|
|
|
delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, original_stanza).pop(),
|
2018-07-06 00:45:53 +02:00
|
|
|
|
chat_state = stanza.getElementsByTagName(_converse.COMPOSING).length && _converse.COMPOSING ||
|
|
|
|
|
stanza.getElementsByTagName(_converse.PAUSED).length && _converse.PAUSED ||
|
|
|
|
|
stanza.getElementsByTagName(_converse.INACTIVE).length && _converse.INACTIVE ||
|
|
|
|
|
stanza.getElementsByTagName(_converse.ACTIVE).length && _converse.ACTIVE ||
|
|
|
|
|
stanza.getElementsByTagName(_converse.GONE).length && _converse.GONE;
|
2018-05-01 11:46:30 +02:00
|
|
|
|
|
2018-02-04 18:55:38 +01:00
|
|
|
|
const attrs = {
|
2017-12-06 22:09:46 +01:00
|
|
|
|
'chat_state': chat_state,
|
2018-06-07 12:45:53 +02:00
|
|
|
|
'is_archived': !_.isNil(archive),
|
|
|
|
|
'is_delayed': !_.isNil(delay),
|
|
|
|
|
'is_spoiler': !_.isNil(spoiler),
|
2018-07-16 00:49:00 +02:00
|
|
|
|
'message': _converse.chatboxes.getMessageBody(stanza) || undefined,
|
2018-07-06 00:45:53 +02:00
|
|
|
|
'msgid': stanza.getAttribute('id'),
|
2018-05-01 11:46:30 +02:00
|
|
|
|
'time': delay ? delay.getAttribute('stamp') : moment().format(),
|
2018-07-06 00:45:53 +02:00
|
|
|
|
'type': stanza.getAttribute('type')
|
2017-12-06 22:09:46 +01:00
|
|
|
|
};
|
2018-05-01 11:46:30 +02:00
|
|
|
|
if (attrs.type === 'groupchat') {
|
2018-07-06 00:45:53 +02:00
|
|
|
|
attrs.from = stanza.getAttribute('from');
|
2018-05-01 11:46:30 +02:00
|
|
|
|
attrs.nick = Strophe.unescapeNode(Strophe.getResourceFromJid(attrs.from));
|
2018-08-01 18:30:19 +02:00
|
|
|
|
if (Strophe.getResourceFromJid(attrs.from) === this.get('nick')) {
|
2018-05-01 11:46:30 +02:00
|
|
|
|
attrs.sender = 'me';
|
|
|
|
|
} else {
|
|
|
|
|
attrs.sender = 'them';
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2018-07-06 00:45:53 +02:00
|
|
|
|
attrs.from = Strophe.getBareJidFromJid(stanza.getAttribute('from'));
|
2018-05-01 11:46:30 +02:00
|
|
|
|
if (attrs.from === _converse.bare_jid) {
|
|
|
|
|
attrs.sender = 'me';
|
|
|
|
|
attrs.fullname = _converse.xmppstatus.get('fullname');
|
|
|
|
|
} else {
|
|
|
|
|
attrs.sender = 'them';
|
|
|
|
|
attrs.fullname = this.get('fullname');
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-06 00:45:53 +02:00
|
|
|
|
_.each(sizzle(`x[xmlns="${Strophe.NS.OUTOFBAND}"]`, stanza), (xform) => {
|
2018-04-18 16:55:26 +02:00
|
|
|
|
attrs['oob_url'] = xform.querySelector('url').textContent;
|
|
|
|
|
attrs['oob_desc'] = xform.querySelector('url').textContent;
|
|
|
|
|
});
|
2018-02-04 18:55:38 +01:00
|
|
|
|
if (spoiler) {
|
2018-02-06 21:21:21 +01:00
|
|
|
|
attrs.spoiler_hint = spoiler.textContent.length > 0 ? spoiler.textContent : '';
|
2018-02-04 18:55:38 +01:00
|
|
|
|
}
|
|
|
|
|
return attrs;
|
2017-12-06 22:09:46 +01:00
|
|
|
|
},
|
|
|
|
|
|
2018-06-07 12:36:11 +02:00
|
|
|
|
createMessage (message, original_stanza) {
|
2018-02-02 21:53:53 +01:00
|
|
|
|
/* Create a Backbone.Message object inside this chat box
|
|
|
|
|
* based on the identified message stanza.
|
|
|
|
|
*/
|
2018-06-07 12:36:11 +02:00
|
|
|
|
const attrs = this.getMessageAttributesFromStanza(message, original_stanza);
|
2018-05-07 14:31:02 +02:00
|
|
|
|
const is_csn = u.isOnlyChatStateNotification(attrs);
|
2018-06-07 12:45:53 +02:00
|
|
|
|
if (is_csn && (attrs.is_delayed || (attrs.type === 'groupchat' && Strophe.getResourceFromJid(attrs.from) == this.get('nick')))) {
|
2018-06-06 09:20:33 +02:00
|
|
|
|
// XXX: MUC leakage
|
|
|
|
|
// No need showing delayed or our own CSN messages
|
2018-05-07 13:50:07 +02:00
|
|
|
|
return;
|
2018-05-07 14:31:02 +02:00
|
|
|
|
} else if (!is_csn && !attrs.file && !attrs.message && !attrs.oob_url && attrs.type !== 'error') {
|
2018-05-07 13:50:07 +02:00
|
|
|
|
// TODO: handle <subject> messages (currently being done by ChatRoom)
|
2018-05-07 12:57:05 +02:00
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
return this.messages.create(attrs);
|
|
|
|
|
}
|
2017-12-06 22:09:46 +01:00
|
|
|
|
},
|
|
|
|
|
|
2018-06-07 13:40:20 +02:00
|
|
|
|
isHidden () {
|
2017-12-06 22:09:46 +01:00
|
|
|
|
/* Returns a boolean to indicate whether a newly received
|
2018-02-02 21:53:53 +01:00
|
|
|
|
* message will be visible to the user or not.
|
|
|
|
|
*/
|
2017-12-06 22:09:46 +01:00
|
|
|
|
return this.get('hidden') ||
|
|
|
|
|
this.get('minimized') ||
|
|
|
|
|
this.isScrolledUp() ||
|
|
|
|
|
_converse.windowState === 'hidden';
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
incrementUnreadMsgCounter (stanza) {
|
|
|
|
|
/* Given a newly received message, update the unread counter if
|
2018-02-14 13:44:17 +01:00
|
|
|
|
* necessary.
|
|
|
|
|
*/
|
2017-12-06 22:09:46 +01:00
|
|
|
|
if (_.isNull(stanza.querySelector('body'))) {
|
|
|
|
|
return; // The message has no text
|
|
|
|
|
}
|
2018-06-07 13:40:20 +02:00
|
|
|
|
if (utils.isNewMessage(stanza) && this.isHidden()) {
|
2017-12-06 22:09:46 +01:00
|
|
|
|
this.save({'num_unread': this.get('num_unread') + 1});
|
|
|
|
|
_converse.incrementMsgCounter();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2018-04-29 15:34:19 +02:00
|
|
|
|
clearUnreadMsgCounter () {
|
|
|
|
|
u.safeSave(this, {'num_unread': 0});
|
2017-12-06 22:09:46 +01:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
isScrolledUp () {
|
|
|
|
|
return this.get('scrolled', true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2017-08-16 11:16:22 +02:00
|
|
|
|
_converse.ChatBoxes = Backbone.Collection.extend({
|
|
|
|
|
comparator: 'time_opened',
|
|
|
|
|
|
|
|
|
|
model (attrs, options) {
|
|
|
|
|
return new _converse.ChatBox(attrs, options);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
registerMessageHandler () {
|
2018-06-06 09:20:33 +02:00
|
|
|
|
_converse.connection.addHandler((stanza) => {
|
|
|
|
|
this.onMessage(stanza);
|
|
|
|
|
return true;
|
|
|
|
|
}, null, 'message', 'chat');
|
|
|
|
|
_converse.connection.addHandler((stanza) => {
|
|
|
|
|
this.onErrorMessage(stanza);
|
|
|
|
|
return true;
|
|
|
|
|
}, null, 'message', 'error');
|
2017-08-16 11:16:22 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
chatBoxMayBeShown (chatbox) {
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onChatBoxesFetched (collection) {
|
2018-02-21 22:40:51 +01:00
|
|
|
|
/* Show chat boxes upon receiving them from sessionStorage */
|
2017-08-16 11:16:22 +02:00
|
|
|
|
collection.each((chatbox) => {
|
|
|
|
|
if (this.chatBoxMayBeShown(chatbox)) {
|
|
|
|
|
chatbox.trigger('show');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
_converse.emit('chatBoxesFetched');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onConnected () {
|
2018-06-05 11:48:48 +02:00
|
|
|
|
this.browserStorage = new Backbone.BrowserStorage.session(
|
2017-08-16 11:16:22 +02:00
|
|
|
|
b64_sha1(`converse.chatboxes-${_converse.bare_jid}`));
|
|
|
|
|
this.registerMessageHandler();
|
|
|
|
|
this.fetch({
|
2018-06-05 13:01:47 +02:00
|
|
|
|
'add': true,
|
|
|
|
|
'success': this.onChatBoxesFetched.bind(this)
|
2017-08-16 11:16:22 +02:00
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onErrorMessage (message) {
|
|
|
|
|
/* Handler method for all incoming error message stanzas
|
|
|
|
|
*/
|
|
|
|
|
const from_jid = Strophe.getBareJidFromJid(message.getAttribute('from'));
|
|
|
|
|
if (utils.isSameBareJID(from_jid, _converse.bare_jid)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
const chatbox = this.getChatBox(from_jid);
|
|
|
|
|
if (!chatbox) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-06-07 12:36:11 +02:00
|
|
|
|
chatbox.createMessage(message, message);
|
2017-08-16 11:16:22 +02:00
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
|
2018-07-16 00:49:00 +02:00
|
|
|
|
getMessageBody (stanza) {
|
|
|
|
|
/* Given a message stanza, return the text contained in its body.
|
|
|
|
|
*/
|
|
|
|
|
const type = stanza.getAttribute('type');
|
|
|
|
|
if (type === 'error') {
|
|
|
|
|
const error = stanza.querySelector('error');
|
|
|
|
|
return _.propertyOf(error.querySelector('text'))('textContent') ||
|
|
|
|
|
__('Sorry, an error occurred:') + ' ' + error.innerHTML;
|
|
|
|
|
} else {
|
|
|
|
|
return _.propertyOf(stanza.querySelector('body'))('textContent');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2018-07-06 00:45:53 +02:00
|
|
|
|
onMessage (stanza) {
|
2017-08-16 11:16:22 +02:00
|
|
|
|
/* Handler method for all incoming single-user chat "message"
|
2018-02-02 21:53:53 +01:00
|
|
|
|
* stanzas.
|
|
|
|
|
*
|
|
|
|
|
* Parameters:
|
2018-07-06 00:45:53 +02:00
|
|
|
|
* (XMLElement) stanza - The incoming message stanza
|
2018-02-02 21:53:53 +01:00
|
|
|
|
*/
|
2018-07-06 00:45:53 +02:00
|
|
|
|
let from_jid = stanza.getAttribute('from'),
|
|
|
|
|
to_jid = stanza.getAttribute('to');
|
2018-07-06 01:04:34 +02:00
|
|
|
|
const to_resource = Strophe.getResourceFromJid(to_jid);
|
2017-08-16 11:16:22 +02:00
|
|
|
|
|
|
|
|
|
if (_converse.filter_by_resource && (to_resource && to_resource !== _converse.resource)) {
|
|
|
|
|
_converse.log(
|
|
|
|
|
`onMessage: Ignoring incoming message intended for a different resource: ${to_jid}`,
|
|
|
|
|
Strophe.LogLevel.INFO
|
|
|
|
|
);
|
|
|
|
|
return true;
|
2018-07-06 00:45:53 +02:00
|
|
|
|
} else if (utils.isHeadlineMessage(_converse, stanza)) {
|
2017-08-16 11:16:22 +02:00
|
|
|
|
// XXX: Ideally we wouldn't have to check for headline
|
|
|
|
|
// messages, but Prosody sends headline messages with the
|
|
|
|
|
// wrong type ('chat'), so we need to filter them out here.
|
|
|
|
|
_converse.log(
|
|
|
|
|
`onMessage: Ignoring incoming headline message sent with type 'chat' from JID: ${from_jid}`,
|
|
|
|
|
Strophe.LogLevel.INFO
|
|
|
|
|
);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-07-06 01:04:34 +02:00
|
|
|
|
|
|
|
|
|
const forwarded = stanza.querySelector('forwarded'),
|
|
|
|
|
original_stanza = stanza;
|
|
|
|
|
|
2017-08-16 11:16:22 +02:00
|
|
|
|
if (!_.isNull(forwarded)) {
|
2018-07-06 01:04:34 +02:00
|
|
|
|
const forwarded_message = forwarded.querySelector('message'),
|
|
|
|
|
forwarded_from = forwarded_message.getAttribute('from'),
|
|
|
|
|
is_carbon = !_.isNull(stanza.querySelector(`received[xmlns="${Strophe.NS.CARBONS}"]`));
|
|
|
|
|
|
2017-08-16 11:16:22 +02:00
|
|
|
|
if (is_carbon && Strophe.getBareJidFromJid(forwarded_from) !== from_jid) {
|
|
|
|
|
// Prevent message forging via carbons
|
|
|
|
|
// https://xmpp.org/extensions/xep-0280.html#security
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-07-06 00:45:53 +02:00
|
|
|
|
stanza = forwarded_message;
|
|
|
|
|
from_jid = stanza.getAttribute('from');
|
|
|
|
|
to_jid = stanza.getAttribute('to');
|
2017-08-16 11:16:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const from_bare_jid = Strophe.getBareJidFromJid(from_jid),
|
2018-07-06 01:04:34 +02:00
|
|
|
|
from_resource = Strophe.getResourceFromJid(from_jid),
|
|
|
|
|
is_me = from_bare_jid === _converse.bare_jid;
|
2017-08-16 11:16:22 +02:00
|
|
|
|
|
2018-06-07 12:36:11 +02:00
|
|
|
|
let contact_jid;
|
2017-08-16 11:16:22 +02:00
|
|
|
|
if (is_me) {
|
|
|
|
|
// I am the sender, so this must be a forwarded message...
|
|
|
|
|
contact_jid = Strophe.getBareJidFromJid(to_jid);
|
|
|
|
|
} else {
|
|
|
|
|
contact_jid = from_bare_jid;
|
|
|
|
|
}
|
|
|
|
|
// Get chat box, but only create a new one when the message has a body.
|
2018-03-14 14:02:08 +01:00
|
|
|
|
const attrs = {
|
|
|
|
|
'fullname': _.get(_converse.api.contacts.get(contact_jid), 'attributes.fullname')
|
|
|
|
|
}
|
2018-07-06 01:04:34 +02:00
|
|
|
|
const chatbox = this.getChatBox(contact_jid, attrs, !_.isNull(stanza.querySelector('body')));
|
2018-07-16 00:49:00 +02:00
|
|
|
|
if (chatbox && !chatbox.handleMessageCorrection(stanza)) {
|
|
|
|
|
const msgid = stanza.getAttribute('id'),
|
2018-07-06 01:04:34 +02:00
|
|
|
|
message = msgid && chatbox.messages.findWhere({msgid});
|
2018-07-16 00:49:00 +02:00
|
|
|
|
if (!message) {
|
2018-07-06 01:04:34 +02:00
|
|
|
|
// Only create the message when we're sure it's not a duplicate
|
2017-08-16 11:16:22 +02:00
|
|
|
|
chatbox.incrementUnreadMsgCounter(original_stanza);
|
2018-07-06 00:45:53 +02:00
|
|
|
|
chatbox.createMessage(stanza, original_stanza);
|
2017-08-16 11:16:22 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_converse.emit('message', {'stanza': original_stanza, 'chatbox': chatbox});
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
|
2018-02-21 22:40:51 +01:00
|
|
|
|
getChatBox (jid, attrs={}, create) {
|
2017-08-16 11:16:22 +02:00
|
|
|
|
/* Returns a chat box or optionally return a newly
|
2018-02-21 22:40:51 +01:00
|
|
|
|
* created one if one doesn't exist.
|
|
|
|
|
*
|
|
|
|
|
* Parameters:
|
|
|
|
|
* (String) jid - The JID of the user whose chat box we want
|
|
|
|
|
* (Boolean) create - Should a new chat box be created if none exists?
|
|
|
|
|
* (Object) attrs - Optional chat box atributes.
|
|
|
|
|
*/
|
|
|
|
|
if (_.isObject(jid)) {
|
|
|
|
|
create = attrs;
|
|
|
|
|
attrs = jid;
|
|
|
|
|
jid = attrs.jid;
|
|
|
|
|
}
|
2018-03-13 19:11:49 +01:00
|
|
|
|
jid = Strophe.getBareJidFromJid(jid.toLowerCase());
|
2018-03-11 13:56:53 +01:00
|
|
|
|
|
2017-08-16 11:16:22 +02:00
|
|
|
|
let chatbox = this.get(Strophe.getBareJidFromJid(jid));
|
|
|
|
|
if (!chatbox && create) {
|
2018-03-14 13:06:38 +01:00
|
|
|
|
_.extend(attrs, {'jid': jid, 'id': jid});
|
2018-02-21 22:40:51 +01:00
|
|
|
|
chatbox = this.create(attrs, {
|
|
|
|
|
'error' (model, response) {
|
|
|
|
|
_converse.log(response.responseText);
|
|
|
|
|
}
|
|
|
|
|
});
|
2017-08-16 11:16:22 +02:00
|
|
|
|
}
|
|
|
|
|
return chatbox;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
_converse.ChatBoxViews = Backbone.Overview.extend({
|
|
|
|
|
|
|
|
|
|
_ensureElement () {
|
|
|
|
|
/* Override method from backbone.js
|
2018-02-21 22:29:21 +01:00
|
|
|
|
* If the #conversejs element doesn't exist, create it.
|
|
|
|
|
*/
|
2017-08-16 11:16:22 +02:00
|
|
|
|
if (!this.el) {
|
2018-02-09 16:02:56 +01:00
|
|
|
|
let el = _converse.root.querySelector('#conversejs');
|
2017-08-16 11:16:22 +02:00
|
|
|
|
if (_.isNull(el)) {
|
|
|
|
|
el = document.createElement('div');
|
|
|
|
|
el.setAttribute('id', 'conversejs');
|
2018-02-09 16:02:56 +01:00
|
|
|
|
const body = _converse.root.querySelector('body');
|
|
|
|
|
if (body) {
|
|
|
|
|
body.appendChild(el);
|
|
|
|
|
} else {
|
|
|
|
|
// Perhaps inside a web component?
|
|
|
|
|
_converse.root.appendChild(el);
|
2018-01-23 14:47:46 +01:00
|
|
|
|
}
|
2017-08-16 11:16:22 +02:00
|
|
|
|
}
|
|
|
|
|
el.innerHTML = '';
|
|
|
|
|
this.setElement(el, false);
|
|
|
|
|
} else {
|
|
|
|
|
this.setElement(_.result(this, 'el'), false);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2018-02-21 22:29:21 +01:00
|
|
|
|
initialize () {
|
|
|
|
|
this.model.on("add", this.onChatBoxAdded, this);
|
|
|
|
|
this.model.on("destroy", this.removeChat, this);
|
2018-04-24 14:35:47 +02:00
|
|
|
|
this.el.classList.add(`converse-${_converse.view_mode}`);
|
2018-02-21 22:29:21 +01:00
|
|
|
|
this.render();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render () {
|
2018-02-21 22:40:51 +01:00
|
|
|
|
try {
|
|
|
|
|
this.el.innerHTML = tpl_chatboxes();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this._ensureElement();
|
|
|
|
|
this.el.innerHTML = tpl_chatboxes();
|
|
|
|
|
}
|
2018-02-21 22:29:21 +01:00
|
|
|
|
this.row_el = this.el.querySelector('.row');
|
|
|
|
|
},
|
|
|
|
|
|
2018-02-15 15:50:11 +01:00
|
|
|
|
insertRowColumn (el) {
|
|
|
|
|
/* Add a new DOM element (likely a chat box) into the
|
|
|
|
|
* the row managed by this overview.
|
|
|
|
|
*/
|
|
|
|
|
this.row_el.insertAdjacentElement('afterBegin', el);
|
|
|
|
|
},
|
|
|
|
|
|
2017-08-16 11:16:22 +02:00
|
|
|
|
onChatBoxAdded (item) {
|
|
|
|
|
// Views aren't created here, since the core code doesn't
|
|
|
|
|
// contain any views. Instead, they're created in overrides in
|
|
|
|
|
// plugins, such as in converse-chatview.js and converse-muc.js
|
|
|
|
|
return this.get(item.get('id'));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
removeChat (item) {
|
|
|
|
|
this.remove(item.get('id'));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
closeAllChatBoxes () {
|
|
|
|
|
/* This method gets overridden in src/converse-controlbox.js if
|
2018-05-14 13:29:33 +02:00
|
|
|
|
* the controlbox plugin is active.
|
|
|
|
|
*/
|
2017-08-16 11:16:22 +02:00
|
|
|
|
this.each(function (view) { view.close(); });
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
chatBoxMayBeShown (chatbox) {
|
|
|
|
|
return this.model.chatBoxMayBeShown(chatbox);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2018-02-21 22:29:21 +01:00
|
|
|
|
// TODO: move to converse-chatboxviews.js and use there in the API
|
|
|
|
|
_converse.getViewForChatBox = function (chatbox) {
|
|
|
|
|
if (!chatbox) { return; }
|
|
|
|
|
return _converse.chatboxviews.get(chatbox.get('id'));
|
|
|
|
|
};
|
|
|
|
|
|
2018-05-02 17:07:57 +02:00
|
|
|
|
function autoJoinChats () {
|
|
|
|
|
/* Automatically join private chats, based on the
|
|
|
|
|
* "auto_join_private_chats" configuration setting.
|
|
|
|
|
*/
|
|
|
|
|
_.each(_converse.auto_join_private_chats, function (jid) {
|
|
|
|
|
if (_converse.chatboxes.where({'jid': jid}).length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (_.isString(jid)) {
|
|
|
|
|
_converse.api.chats.open(jid);
|
|
|
|
|
} else {
|
|
|
|
|
_converse.log(
|
|
|
|
|
'Invalid jid criteria specified for "auto_join_private_chats"',
|
|
|
|
|
Strophe.LogLevel.ERROR);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
_converse.emit('privateChatsAutoJoined');
|
|
|
|
|
}
|
2018-04-18 16:55:26 +02:00
|
|
|
|
|
2018-05-10 22:14:37 +02:00
|
|
|
|
|
2018-02-21 22:29:21 +01:00
|
|
|
|
/************************ BEGIN Event Handlers ************************/
|
2018-05-02 17:07:57 +02:00
|
|
|
|
_converse.on('chatBoxesFetched', autoJoinChats);
|
|
|
|
|
|
2018-05-10 22:14:37 +02:00
|
|
|
|
|
|
|
|
|
_converse.api.waitUntil('rosterContactsFetched').then(() => {
|
|
|
|
|
_converse.roster.on('add', (contact) => {
|
|
|
|
|
/* When a new contact is added, check if we already have a
|
|
|
|
|
* chatbox open for it, and if so attach it to the chatbox.
|
|
|
|
|
*/
|
|
|
|
|
const chatbox = _converse.chatboxes.findWhere({'jid': contact.get('jid')});
|
2018-05-12 10:57:40 +02:00
|
|
|
|
if (chatbox) {
|
|
|
|
|
chatbox.addRelatedContact(contact);
|
|
|
|
|
}
|
2018-05-10 22:14:37 +02:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2018-04-18 16:55:26 +02:00
|
|
|
|
_converse.on('addClientFeatures', () => {
|
2018-07-07 22:44:07 +02:00
|
|
|
|
_converse.api.disco.own.features.add(Strophe.NS.MESSAGE_CORRECT);
|
2018-05-11 13:31:48 +02:00
|
|
|
|
_converse.api.disco.own.features.add(Strophe.NS.HTTPUPLOAD);
|
|
|
|
|
_converse.api.disco.own.features.add(Strophe.NS.OUTOFBAND);
|
2018-04-18 16:55:26 +02:00
|
|
|
|
});
|
|
|
|
|
|
2017-08-16 12:31:17 +02:00
|
|
|
|
_converse.api.listen.on('pluginsInitialized', () => {
|
|
|
|
|
_converse.chatboxes = new _converse.ChatBoxes();
|
|
|
|
|
_converse.chatboxviews = new _converse.ChatBoxViews({
|
|
|
|
|
'model': _converse.chatboxes
|
|
|
|
|
});
|
|
|
|
|
_converse.emit('chatBoxesInitialized');
|
2017-08-16 11:16:22 +02:00
|
|
|
|
});
|
|
|
|
|
|
2018-06-07 14:29:47 +02:00
|
|
|
|
_converse.api.listen.on('clearSession', () => {
|
2018-06-05 11:48:48 +02:00
|
|
|
|
_converse.chatboxviews.closeAllChatBoxes();
|
|
|
|
|
});
|
|
|
|
|
|
2018-05-24 15:09:01 +02:00
|
|
|
|
_converse.api.listen.on('presencesInitialized', () => _converse.chatboxes.onConnected());
|
2018-02-21 22:29:21 +01:00
|
|
|
|
/************************ END Event Handlers ************************/
|
2017-08-16 11:16:22 +02:00
|
|
|
|
|
|
|
|
|
|
2018-02-21 22:29:21 +01:00
|
|
|
|
/************************ BEGIN API ************************/
|
2017-08-16 11:16:22 +02:00
|
|
|
|
_.extend(_converse.api, {
|
2018-08-01 12:08:18 +02:00
|
|
|
|
/**
|
|
|
|
|
* The "chats" grouping (used for one-on-one chats)
|
|
|
|
|
*
|
|
|
|
|
* @namespace
|
|
|
|
|
*/
|
2017-08-16 11:16:22 +02:00
|
|
|
|
'chats': {
|
2018-02-21 22:29:21 +01:00
|
|
|
|
'create' (jids, attrs) {
|
2017-08-16 11:16:22 +02:00
|
|
|
|
if (_.isUndefined(jids)) {
|
2018-03-14 13:06:38 +01:00
|
|
|
|
_converse.log(
|
|
|
|
|
"chats.create: You need to provide at least one JID",
|
|
|
|
|
Strophe.LogLevel.ERROR
|
|
|
|
|
);
|
2017-08-16 11:16:22 +02:00
|
|
|
|
return null;
|
2018-03-14 13:06:38 +01:00
|
|
|
|
}
|
|
|
|
|
if (_.isString(jids)) {
|
2018-03-14 19:25:19 +01:00
|
|
|
|
if (attrs && !_.get(attrs, 'fullname')) {
|
2018-03-14 13:06:38 +01:00
|
|
|
|
attrs.fullname = _.get(_converse.api.contacts.get(jids), 'attributes.fullname');
|
|
|
|
|
}
|
2018-02-21 22:29:21 +01:00
|
|
|
|
const chatbox = _converse.chatboxes.getChatBox(jids, attrs, true);
|
2017-10-31 23:04:46 +01:00
|
|
|
|
if (_.isNil(chatbox)) {
|
2018-02-21 22:29:21 +01:00
|
|
|
|
_converse.log("Could not open chatbox for JID: "+jids, Strophe.LogLevel.ERROR);
|
2017-10-31 23:04:46 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
2018-02-21 22:29:21 +01:00
|
|
|
|
return chatbox;
|
2017-08-16 11:16:22 +02:00
|
|
|
|
}
|
2018-03-14 13:06:38 +01:00
|
|
|
|
return _.map(jids, (jid) => {
|
|
|
|
|
attrs.fullname = _.get(_converse.api.contacts.get(jid), 'attributes.fullname');
|
|
|
|
|
return _converse.chatboxes.getChatBox(jid, attrs, true).trigger('show');
|
|
|
|
|
});
|
2018-02-21 22:29:21 +01:00
|
|
|
|
},
|
2018-08-01 12:08:18 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Opens a new one-on-one chat.
|
|
|
|
|
*
|
|
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @param {String|string[]} name - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com']
|
|
|
|
|
* @returns {Promise} Promise which resolves with the Backbone.Model representing the chat.
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* // To open a single chat, provide the JID of the contact you're chatting with in that chat:
|
|
|
|
|
* converse.plugins.add('myplugin', {
|
|
|
|
|
* initialize: function() {
|
|
|
|
|
* var _converse = this._converse;
|
|
|
|
|
* // Note, buddy@example.org must be in your contacts roster!
|
|
|
|
|
* _converse.api.chats.open('buddy@example.com').then((chat) => {
|
|
|
|
|
* // Now you can do something with the chat model
|
|
|
|
|
* });
|
|
|
|
|
* }
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* // To open an array of chats, provide an array of JIDs:
|
|
|
|
|
* converse.plugins.add('myplugin', {
|
|
|
|
|
* initialize: function () {
|
|
|
|
|
* var _converse = this._converse;
|
|
|
|
|
* // Note, these users must first be in your contacts roster!
|
|
|
|
|
* _converse.api.chats.open(['buddy1@example.com', 'buddy2@example.com']).then((chats) => {
|
|
|
|
|
* // Now you can do something with the chat models
|
|
|
|
|
* });
|
|
|
|
|
* }
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
*/
|
2018-02-21 22:29:21 +01:00
|
|
|
|
'open' (jids, attrs) {
|
2018-07-30 18:16:32 +02:00
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
Promise.all([
|
|
|
|
|
_converse.api.waitUntil('rosterContactsFetched'),
|
|
|
|
|
_converse.api.waitUntil('chatBoxesFetched')
|
|
|
|
|
]).then(() => {
|
|
|
|
|
if (_.isUndefined(jids)) {
|
|
|
|
|
const err_msg = "chats.open: You need to provide at least one JID";
|
|
|
|
|
_converse.log(err_msg, Strophe.LogLevel.ERROR);
|
|
|
|
|
reject(new Error(err_msg));
|
|
|
|
|
} else if (_.isString(jids)) {
|
|
|
|
|
resolve(_converse.api.chats.create(jids, attrs).trigger('show'));
|
|
|
|
|
} else {
|
|
|
|
|
resolve(_.map(jids, (jid) => _converse.api.chats.create(jid, attrs).trigger('show')));
|
|
|
|
|
}
|
|
|
|
|
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
|
|
|
|
|
});
|
2017-08-16 11:16:22 +02:00
|
|
|
|
},
|
2018-08-01 12:08:18 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a chat model. The chat should already be open.
|
|
|
|
|
*
|
|
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @param {String|string[]} name - e.g. 'buddy@example.com' or ['buddy1@example.com', 'buddy2@example.com']
|
|
|
|
|
* @returns {Backbone.Model}
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* // To return a single chat, provide the JID of the contact you're chatting with in that chat:
|
|
|
|
|
* const model = _converse.api.chats.get('buddy@example.com');
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* // To return an array of chats, provide an array of JIDs:
|
|
|
|
|
* const models = _converse.api.chats.get(['buddy1@example.com', 'buddy2@example.com']);
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* // To return all open chats, call the method without any parameters::
|
|
|
|
|
* const models = _converse.api.chats.get();
|
|
|
|
|
*
|
|
|
|
|
*/
|
2017-08-16 11:16:22 +02:00
|
|
|
|
'get' (jids) {
|
|
|
|
|
if (_.isUndefined(jids)) {
|
|
|
|
|
const result = [];
|
|
|
|
|
_converse.chatboxes.each(function (chatbox) {
|
|
|
|
|
// FIXME: Leaky abstraction from MUC. We need to add a
|
|
|
|
|
// base type for chat boxes, and check for that.
|
|
|
|
|
if (chatbox.get('type') !== 'chatroom') {
|
2018-02-21 22:29:21 +01:00
|
|
|
|
result.push(chatbox);
|
2017-08-16 11:16:22 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
} else if (_.isString(jids)) {
|
2018-02-21 22:29:21 +01:00
|
|
|
|
return _converse.chatboxes.getChatBox(jids);
|
2017-08-16 11:16:22 +02:00
|
|
|
|
}
|
2018-02-21 22:29:21 +01:00
|
|
|
|
return _.map(jids, _.partial(_converse.chatboxes.getChatBox.bind(_converse.chatboxes), _, {}, true));
|
2017-08-16 11:16:22 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2018-02-21 22:40:51 +01:00
|
|
|
|
/************************ END API ************************/
|
2017-08-16 11:16:22 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return converse;
|
|
|
|
|
}));
|