Commit 26cb98d9 authored by JC Brand's avatar JC Brand

Move playing of sounds to the notification plugin.

Also add a config setting for the icon shown in HTML5 notificatins.

updates #443
parent 9cce0ff6
...@@ -538,6 +538,14 @@ different approach. ...@@ -538,6 +538,14 @@ different approach.
If you're using MAM for archiving chat room messages, you might want to set If you're using MAM for archiving chat room messages, you might want to set
this option to zero. this option to zero.
notification_icon
-----------------
* Default: ``'/logo/conversejs.png'``
This option specifies which icon is shown in HTML5 notifications, as provided
by the ``src/converse-notification.js`` plugin.
ping_interval ping_interval
------------- -------------
......
...@@ -196,7 +196,7 @@ ...@@ -196,7 +196,7 @@
test_utils.openChatRoom('lounge', 'localhost', 'dummy'); test_utils.openChatRoom('lounge', 'localhost', 'dummy');
spyOn(converse, 'emit'); spyOn(converse, 'emit');
converse.play_sounds = true; converse.play_sounds = true;
spyOn(converse, 'notifyOfNewMessage'); spyOn(converse, 'playSoundNotification');
var view = this.chatboxviews.get('lounge@localhost'); var view = this.chatboxviews.get('lounge@localhost');
if (!view.$el.find('.chat-area').length) { view.renderChatArea(); } if (!view.$el.find('.chat-area').length) { view.renderChatArea(); }
var text = 'This message will play a sound because it mentions dummy'; var text = 'This message will play a sound because it mentions dummy';
...@@ -207,7 +207,7 @@ ...@@ -207,7 +207,7 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t(text); }).c('body').t(text);
view.onChatRoomMessage(message.nodeTree); view.onChatRoomMessage(message.nodeTree);
expect(converse.notifyOfNewMessage).toHaveBeenCalled(); expect(converse.playSoundNotification).toHaveBeenCalled();
text = "This message won't play a sound"; text = "This message won't play a sound";
message = $msg({ message = $msg({
...@@ -217,7 +217,7 @@ ...@@ -217,7 +217,7 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t(text); }).c('body').t(text);
view.onChatRoomMessage(message.nodeTree); view.onChatRoomMessage(message.nodeTree);
expect(converse.notifyOfNewMessage, 1); expect(converse.playSoundNotification, 1);
converse.play_sounds = false; converse.play_sounds = false;
text = "This message won't play a sound because it is sent by dummy"; text = "This message won't play a sound because it is sent by dummy";
...@@ -228,7 +228,7 @@ ...@@ -228,7 +228,7 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t(text); }).c('body').t(text);
view.onChatRoomMessage(message.nodeTree); view.onChatRoomMessage(message.nodeTree);
expect(converse.notifyOfNewMessage, 1); expect(converse.playSoundNotification, 1);
converse.play_sounds = false; converse.play_sounds = false;
}.bind(converse)); }.bind(converse));
......
...@@ -106,14 +106,6 @@ ...@@ -106,14 +106,6 @@
9: 'REDIRECT' 9: 'REDIRECT'
}; };
// XEP-0085 Chat states
// http://xmpp.org/extensions/xep-0085.html
var INACTIVE = 'inactive';
var ACTIVE = 'active';
var COMPOSING = 'composing';
var PAUSED = 'paused';
var GONE = 'gone';
// TODO Refactor into external MAM plugin // TODO Refactor into external MAM plugin
// XEP-0059 Result Set Management // XEP-0059 Result Set Management
var RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'count']; var RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'count'];
...@@ -201,20 +193,6 @@ ...@@ -201,20 +193,6 @@
}; };
converse.isOnlyChatStateNotification = function ($msg) {
// See XEP-0085 Chat State Notification
return (
$msg.find('body').length === 0 && (
$msg.find(ACTIVE).length !== 0 ||
$msg.find(COMPOSING).length !== 0 ||
$msg.find(INACTIVE).length !== 0 ||
$msg.find(PAUSED).length !== 0 ||
$msg.find(GONE).length !== 0
)
);
};
converse.log = function (txt, level) { converse.log = function (txt, level) {
var logger; var logger;
if (typeof console === "undefined" || typeof console.log === "undefined") { if (typeof console === "undefined" || typeof console.log === "undefined") {
...@@ -261,11 +239,21 @@ ...@@ -261,11 +239,21 @@
Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm'); Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
Strophe.addNamespace('XFORM', 'jabber:x:data'); Strophe.addNamespace('XFORM', 'jabber:x:data');
// Instance level constants
this.TIMEOUTS = { // Set as module attr so that we can override in tests. this.TIMEOUTS = { // Set as module attr so that we can override in tests.
'PAUSED': 20000, 'PAUSED': 20000,
'INACTIVE': 90000 'INACTIVE': 90000
}; };
// XEP-0085 Chat states
// http://xmpp.org/extensions/xep-0085.html
this.INACTIVE = 'inactive';
this.ACTIVE = 'active';
this.COMPOSING = 'composing';
this.PAUSED = 'paused';
this.GONE = 'gone';
// Detect support for the user's locale // Detect support for the user's locale
// ------------------------------------ // ------------------------------------
this.isConverseLocale = function (locale) { return typeof locales[locale] !== "undefined"; }; this.isConverseLocale = function (locale) { return typeof locales[locale] !== "undefined"; };
...@@ -378,7 +366,6 @@ ...@@ -378,7 +366,6 @@
message_carbons: false, // Support for XEP-280 message_carbons: false, // Support for XEP-280
no_trimming: false, // Set to true for phantomjs tests (where browser apparently has no width) no_trimming: false, // Set to true for phantomjs tests (where browser apparently has no width)
password: undefined, password: undefined,
play_sounds: false,
prebind: false, // XXX: Deprecated, use "authentication" instead. prebind: false, // XXX: Deprecated, use "authentication" instead.
prebind_url: null, prebind_url: null,
rid: undefined, rid: undefined,
...@@ -386,7 +373,6 @@ ...@@ -386,7 +373,6 @@
show_only_online_users: false, show_only_online_users: false,
show_toolbar: true, show_toolbar: true,
sid: undefined, sid: undefined,
sounds_path: '/sounds/',
storage: 'session', storage: 'session',
synchronize_availability: true, // Set to false to not sync with other clients or with resource name of the particular client that it should synchronize with synchronize_availability: true, // Set to false to not sync with other clients or with resource name of the particular client that it should synchronize with
use_vcards: true, use_vcards: true,
...@@ -453,7 +439,7 @@ ...@@ -453,7 +439,7 @@
/* Send out a Chat Status Notification (XEP-0352) */ /* Send out a Chat Status Notification (XEP-0352) */
if (converse.features[Strophe.NS.CSI] || true) { if (converse.features[Strophe.NS.CSI] || true) {
converse.connection.send($build(stat, {xmlns: Strophe.NS.CSI})); converse.connection.send($build(stat, {xmlns: Strophe.NS.CSI}));
this.inactive = (stat === INACTIVE) ? true : false; this.inactive = (stat === converse.INACTIVE) ? true : false;
} }
}; };
...@@ -468,7 +454,7 @@ ...@@ -468,7 +454,7 @@
return; return;
} }
if (this.inactive) { if (this.inactive) {
this.sendCSI(ACTIVE); this.sendCSI(converse.ACTIVE);
} }
if (this.auto_changed_status === true) { if (this.auto_changed_status === true) {
this.auto_changed_status = false; this.auto_changed_status = false;
...@@ -489,7 +475,7 @@ ...@@ -489,7 +475,7 @@
var stat = this.xmppstatus.getStatus(); var stat = this.xmppstatus.getStatus();
this.idle_seconds++; this.idle_seconds++;
if (this.csi_waiting_time > 0 && this.idle_seconds > this.csi_waiting_time && !this.inactive) { if (this.csi_waiting_time > 0 && this.idle_seconds > this.csi_waiting_time && !this.inactive) {
this.sendCSI(INACTIVE); this.sendCSI(converse.INACTIVE);
} }
if (this.auto_away > 0 && this.idle_seconds > this.auto_away && stat !== 'away' && stat !== 'xa') { if (this.auto_away > 0 && this.idle_seconds > this.auto_away && stat !== 'away' && stat !== 'xa') {
this.auto_changed_status = true; this.auto_changed_status = true;
...@@ -514,36 +500,6 @@ ...@@ -514,36 +500,6 @@
window.setInterval(this.onEverySecond.bind(this), 1000); window.setInterval(this.onEverySecond.bind(this), 1000);
}; };
this.shouldNotifyOfNewMessage = function ($message) {
var $forwarded = $message.find('forwarded');
if ($forwarded.length) {
return false;
}
var is_me = Strophe.getBareJidFromJid($message.attr('from')) === converse.bare_jid;
return !converse.isOnlyChatStateNotification($message) && !is_me;
};
this.notifyOfNewMessage = function ($message) {
/* Plays a sound to notify that a new message was recieved.
*
* Returns true if the notification was made and false otherwise.
*/
if (!this.shouldNotifyOfNewMessage($message)) {
return false;
}
var audio;
if (converse.play_sounds && typeof Audio !== "undefined") {
audio = new Audio(converse.sounds_path+"msg_received.ogg");
if (audio.canPlayType('/audio/ogg')) {
audio.play();
} else {
audio = new Audio(converse.sounds_path+"msg_received.mp3");
audio.play();
}
}
return true;
};
this.giveFeedback = function (message, klass) { this.giveFeedback = function (message, klass) {
$('.conn-feedback').each(function (idx, el) { $('.conn-feedback').each(function (idx, el) {
var $el = $(el); var $el = $(el);
...@@ -1413,11 +1369,11 @@ ...@@ -1413,11 +1369,11 @@
fullname = this.get('fullname'), fullname = this.get('fullname'),
is_groupchat = $message.attr('type') === 'groupchat', is_groupchat = $message.attr('type') === 'groupchat',
msgid = $message.attr('id'), msgid = $message.attr('id'),
chat_state = $message.find(COMPOSING).length && COMPOSING || chat_state = $message.find(converse.COMPOSING).length && converse.COMPOSING ||
$message.find(PAUSED).length && PAUSED || $message.find(converse.PAUSED).length && converse.PAUSED ||
$message.find(INACTIVE).length && INACTIVE || $message.find(converse.INACTIVE).length && converse.INACTIVE ||
$message.find(ACTIVE).length && ACTIVE || $message.find(converse.ACTIVE).length && converse.ACTIVE ||
$message.find(GONE).length && GONE, $message.find(converse.GONE).length && converse.GONE,
stamp, time, sender, from; stamp, time, sender, from;
if (is_groupchat) { if (is_groupchat) {
...@@ -1830,14 +1786,14 @@ ...@@ -1830,14 +1786,14 @@
}, },
handleChatStateMessage: function (message) { handleChatStateMessage: function (message) {
if (message.get('chat_state') === COMPOSING) { if (message.get('chat_state') === converse.COMPOSING) {
this.showStatusNotification(message.get('fullname')+' '+__('is typing')); this.showStatusNotification(message.get('fullname')+' '+__('is typing'));
this.clear_status_timeout = window.setTimeout(this.clearStatusNotification.bind(this), 10000); this.clear_status_timeout = window.setTimeout(this.clearStatusNotification.bind(this), 10000);
} else if (message.get('chat_state') === PAUSED) { } else if (message.get('chat_state') === converse.PAUSED) {
this.showStatusNotification(message.get('fullname')+' '+__('has stopped typing')); this.showStatusNotification(message.get('fullname')+' '+__('has stopped typing'));
} else if (_.contains([INACTIVE, ACTIVE], message.get('chat_state'))) { } else if (_.contains([converse.INACTIVE, converse.ACTIVE], message.get('chat_state'))) {
this.$content.find('div.chat-event').remove(); this.$content.find('div.chat-event').remove();
} else if (message.get('chat_state') === GONE) { } else if (message.get('chat_state') === converse.GONE) {
this.showStatusNotification(message.get('fullname')+' '+__('has gone away')); this.showStatusNotification(message.get('fullname')+' '+__('has gone away'));
} }
}, },
...@@ -1876,7 +1832,7 @@ ...@@ -1876,7 +1832,7 @@
type: 'chat', type: 'chat',
id: message.get('msgid') id: message.get('msgid')
}).c('body').t(message.get('message')).up() }).c('body').t(message.get('message')).up()
.c(ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}).up(); .c(converse.ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}).up();
}, },
sendMessage: function (message) { sendMessage: function (message) {
...@@ -1967,12 +1923,12 @@ ...@@ -1967,12 +1923,12 @@
window.clearTimeout(this.chat_state_timeout); window.clearTimeout(this.chat_state_timeout);
delete this.chat_state_timeout; delete this.chat_state_timeout;
} }
if (state === COMPOSING) { if (state === converse.COMPOSING) {
this.chat_state_timeout = window.setTimeout( this.chat_state_timeout = window.setTimeout(
this.setChatState.bind(this), converse.TIMEOUTS.PAUSED, PAUSED); this.setChatState.bind(this), converse.TIMEOUTS.PAUSED, converse.PAUSED);
} else if (state === PAUSED) { } else if (state === converse.PAUSED) {
this.chat_state_timeout = window.setTimeout( this.chat_state_timeout = window.setTimeout(
this.setChatState.bind(this), converse.TIMEOUTS.INACTIVE, INACTIVE); this.setChatState.bind(this), converse.TIMEOUTS.INACTIVE, converse.INACTIVE);
} }
if (!no_save && this.model.get('chat_state') !== state) { if (!no_save && this.model.get('chat_state') !== state) {
this.model.set('chat_state', state); this.model.set('chat_state', state);
...@@ -1996,11 +1952,11 @@ ...@@ -1996,11 +1952,11 @@
} }
converse.emit('messageSend', message); converse.emit('messageSend', message);
} }
this.setChatState(ACTIVE); this.setChatState(converse.ACTIVE);
} else if (!this.model.get('chatroom')) { // chat state data is currently only for single user chat } else if (!this.model.get('chatroom')) { // chat state data is currently only for single user chat
// Set chat state to composing if keyCode is not a forward-slash // Set chat state to composing if keyCode is not a forward-slash
// (which would imply an internal command and not a message). // (which would imply an internal command and not a message).
this.setChatState(COMPOSING, ev.keyCode === KEY.FORWARD_SLASH); this.setChatState(converse.COMPOSING, ev.keyCode === KEY.FORWARD_SLASH);
} }
}, },
...@@ -2155,7 +2111,7 @@ ...@@ -2155,7 +2111,7 @@
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
if (converse.connection.connected) { if (converse.connection.connected) {
this.model.destroy(); this.model.destroy();
this.setChatState(INACTIVE); this.setChatState(converse.INACTIVE);
} else { } else {
this.hide(); this.hide();
} }
...@@ -2167,7 +2123,7 @@ ...@@ -2167,7 +2123,7 @@
converse.chatboxviews.trimChats(this); converse.chatboxviews.trimChats(this);
utils.refreshWebkit(); utils.refreshWebkit();
this.$content.scrollTop(this.model.get('scroll')); this.$content.scrollTop(this.model.get('scroll'));
this.setChatState(ACTIVE).focus(); this.setChatState(converse.ACTIVE).focus();
converse.emit('chatBoxMaximized', this); converse.emit('chatBoxMaximized', this);
}, },
...@@ -2183,7 +2139,7 @@ ...@@ -2183,7 +2139,7 @@
// save the scroll position to restore it on maximize // save the scroll position to restore it on maximize
this.model.save({'scroll': this.$content.scrollTop()}); this.model.save({'scroll': this.$content.scrollTop()});
// Minimizes a chat box // Minimizes a chat box
this.setChatState(INACTIVE).model.minimize(); this.setChatState(converse.INACTIVE).model.minimize();
this.$el.hide('fast', utils.refreshwebkit); this.$el.hide('fast', utils.refreshwebkit);
converse.emit('chatBoxMinimized', this); converse.emit('chatBoxMinimized', this);
}, },
...@@ -2282,7 +2238,7 @@ ...@@ -2282,7 +2238,7 @@
// localstorage // localstorage
this.model.save(); this.model.save();
} }
this.setChatState(ACTIVE); this.setChatState(converse.ACTIVE);
this.scrollDown(); this.scrollDown();
if (focus) { if (focus) {
this.focus(); this.focus();
...@@ -2389,7 +2345,6 @@ ...@@ -2389,7 +2345,6 @@
if (msgid && chatbox.messages.findWhere({msgid: msgid})) { if (msgid && chatbox.messages.findWhere({msgid: msgid})) {
return true; // We already have this message stored. return true; // We already have this message stored.
} }
converse.notifyOfNewMessage($message);
chatbox.createMessage($message, $delay, archive_id); chatbox.createMessage($message, $delay, archive_id);
converse.roster.addResource(contact_jid, resource); converse.roster.addResource(contact_jid, resource);
converse.emit('message', message); converse.emit('message', message);
...@@ -3160,7 +3115,7 @@ ...@@ -3160,7 +3115,7 @@
return; return;
} }
plugin.converse = converse; plugin.converse = converse;
_.each(Object.keys(plugin.overrides), function (key) { _.each(Object.keys(plugin.overrides || {}), function (key) {
/* We automatically override all methods and Backbone views and /* We automatically override all methods and Backbone views and
* models that are in the "overrides" namespace. * models that are in the "overrides" namespace.
*/ */
......
...@@ -10,46 +10,77 @@ ...@@ -10,46 +10,77 @@
define("converse-notification", ["converse-core", "converse-api"], factory); define("converse-notification", ["converse-core", "converse-api"], factory);
}(this, function (converse, converse_api) { }(this, function (converse, converse_api) {
"use strict"; "use strict";
var utils = converse_api.env.utils; var $ = converse_api.env.jQuery,
var Strophe = converse_api.env.Strophe; utils = converse_api.env.utils,
Strophe = converse_api.env.Strophe,
_ = converse_api.env._;
// For translations // For translations
var __ = utils.__.bind(converse); var __ = utils.__.bind(converse);
var ___ = utils.___; var ___ = utils.___;
if (!("Notification" in window)) { var supports_html5_notification = "Notification" in window;
// HTML5 notifications aren't supported.
converse.log( if (supports_html5_notification && Notification.permission !== 'denied') {
"Not loading the notifications plugin because this browser "+
"doesn't support HTML5 notifications.");
return;
}
// Ask user to enable HTML5 notifications // Ask user to enable HTML5 notifications
Notification.requestPermission(); Notification.requestPermission();
}
converse_api.plugins.add('notification', { converse_api.plugins.add('notification', {
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.
//
// New functions which don't exist yet can also be added.
notifyOfNewMessage: function ($message) {
var result = this._super.notifyOfNewMessage.apply(this, arguments);
if (result && (this.windowState === 'blur') && (Notification.permission === "granted")) {
this.showMessageNotification($message);
}
return result;
}
},
initialize: function () { initialize: function () {
/* The initialize function gets called as soon as the plugin is /* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery. * loaded by converse.js's plugin machinery.
*/ */
var converse = this.converse; var converse = this.converse;
// Configuration values for this plugin
var settings = {
play_sounds: false,
sounds_path: '/sounds/',
notification_icon: '/logo/conversejs.png'
};
_.extend(converse.default_settings, settings);
_.extend(converse, settings);
_.extend(converse, _.pick(converse.user_settings, Object.keys(settings)));
converse.isOnlyChatStateNotification = function ($msg) {
// See XEP-0085 Chat State Notification
return (
$msg.find('body').length === 0 && (
$msg.find(converse.ACTIVE).length !== 0 ||
$msg.find(converse.COMPOSING).length !== 0 ||
$msg.find(converse.INACTIVE).length !== 0 ||
$msg.find(converse.PAUSED).length !== 0 ||
$msg.find(converse.GONE).length !== 0
)
);
};
converse.shouldNotifyOfNewMessage = function ($message) {
var $forwarded = $message.find('forwarded');
if ($forwarded.length) {
return false;
}
var is_me = Strophe.getBareJidFromJid($message.attr('from')) === converse.bare_jid;
return !converse.isOnlyChatStateNotification($message) && !is_me;
};
converse.playSoundNotification = function ($message) {
/* Plays a sound to notify that a new message was recieved.
*/
// XXX Eventually this can be refactored to use Notification's sound
// feature, but no browser currently supports it.
// https://developer.mozilla.org/en-US/docs/Web/API/notification/sound
var audio;
if (converse.play_sounds && typeof Audio !== "undefined") {
audio = new Audio(converse.sounds_path+"msg_received.ogg");
if (audio.canPlayType('/audio/ogg')) {
audio.play();
} else {
audio = new Audio(converse.sounds_path+"msg_received.mp3");
audio.play();
}
}
};
converse.showChatStateNotification = function (event, contact) { converse.showChatStateNotification = function (event, contact) {
/* Show an HTML5 notification indicating that a contact changed /* Show an HTML5 notification indicating that a contact changed
...@@ -77,20 +108,39 @@ ...@@ -77,20 +108,39 @@
setTimeout(n.close.bind(n), 5000); setTimeout(n.close.bind(n), 5000);
}; };
converse.on('contactStatusChanged', converse.showChatStateNotification);
converse.showMessageNotification = function ($message) { converse.showMessageNotification = function ($message) {
/* Show an HTML5 notification of a received message. /* Shows an HTML5 Notification to indicate that a new chat
* message was received.
*/ */
if (!supports_html5_notification ||
this.windowState !== 'blur' ||
Notification.permission !== "granted") {
return;
}
var contact_jid = Strophe.getBareJidFromJid($message.attr('from')); var contact_jid = Strophe.getBareJidFromJid($message.attr('from'));
var roster_item = converse.roster.get(contact_jid); var roster_item = converse.roster.get(contact_jid);
var n = new Notification(__(___("%1$s says"), roster_item.get('fullname')), { var n = new Notification(__(___("%1$s says"), roster_item.get('fullname')), {
body: $message.children('body').text(), body: $message.children('body').text(),
lang: converse.i18n.locale_data.converse[""].lang, lang: converse.i18n.locale_data.converse[""].lang,
icon: 'logo/conversejs.png' icon: converse.notification_icon
}); });
setTimeout(n.close.bind(n), 5000); setTimeout(n.close.bind(n), 5000);
}; };
converse.notifyOfNewMessage = function (message) {
/* Event handler for the on('message') event. Will call methods
* to play sounds and show HTML5 notifications.
*/
var $message = $(message);
if (!converse.shouldNotifyOfNewMessage($message)) {
return false;
}
converse.playSoundNotification($message);
converse.showMessageNotification($message);
};
converse.on('contactStatusChanged', converse.showChatStateNotification);
converse.on('message', converse.showMessageNotification);
} }
}); });
})); }));
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment