diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index 1fe2521d1..72aba157e 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -660,7 +660,7 @@ show_desktop_notifications * Default: ``true`` Should HTML5 desktop notifications be shown when the browser is not visible or -blurred? +not focused? Requires the `src/converse-notification.js` plugin. diff --git a/spec/chatroom.js b/spec/chatroom.js index 47ae4ea7e..f4c19a11a 100644 --- a/spec/chatroom.js +++ b/spec/chatroom.js @@ -192,46 +192,6 @@ expect(converse.emit).toHaveBeenCalledWith('message', message.nodeTree); }.bind(converse)); - it("plays a sound when the current user is mentioned (if configured)", function () { - test_utils.openChatRoom('lounge', 'localhost', 'dummy'); - spyOn(converse, 'emit'); - converse.play_sounds = true; - spyOn(converse, 'playSoundNotification'); - var view = this.chatboxviews.get('lounge@localhost'); - if (!view.$el.find('.chat-area').length) { view.renderChatArea(); } - var text = 'This message will play a sound because it mentions dummy'; - var message = $msg({ - from: 'lounge@localhost/otheruser', - id: '1', - to: 'dummy@localhost', - type: 'groupchat' - }).c('body').t(text); - view.onChatRoomMessage(message.nodeTree); - expect(converse.playSoundNotification).toHaveBeenCalled(); - - text = "This message won't play a sound"; - message = $msg({ - from: 'lounge@localhost/otheruser', - id: '2', - to: 'dummy@localhost', - type: 'groupchat' - }).c('body').t(text); - view.onChatRoomMessage(message.nodeTree); - expect(converse.playSoundNotification, 1); - converse.play_sounds = false; - - text = "This message won't play a sound because it is sent by dummy"; - message = $msg({ - from: 'lounge@localhost/dummy', - id: '3', - to: 'dummy@localhost', - type: 'groupchat' - }).c('body').t(text); - view.onChatRoomMessage(message.nodeTree); - expect(converse.playSoundNotification, 1); - converse.play_sounds = false; - }.bind(converse)); - it("shows sent groupchat messages", function () { test_utils.openChatRoom('lounge', 'localhost', 'dummy'); spyOn(converse, 'emit'); diff --git a/spec/notification.js b/spec/notification.js new file mode 100644 index 000000000..e325c44b1 --- /dev/null +++ b/spec/notification.js @@ -0,0 +1,109 @@ +/*global converse */ +(function (root, factory) { + define([ + "jquery", + "underscore", + "mock", + "test_utils" + ], function ($, _, mock, test_utils) { + return factory($, _, mock, test_utils); + } + ); +} (this, function ($, _, mock, test_utils) { + "use strict"; + var $msg = converse_api.env.$msg; + + describe("Notifications", function () { + // Implement the protocol defined in https://xmpp.org/extensions/xep-0313.html#config + beforeEach(function () { + runs(function () { + test_utils.closeAllChatBoxes(); + test_utils.createContacts('current'); + }); + }); + + describe("When show_desktop_notifications is set to true", function () { + describe("And the desktop is not focused", function () { + describe("an HTML5 Notification", function () { + + it("is shown when a new message is received", function () { + // TODO: not yet testing show_desktop_notifications setting + spyOn(converse, 'showMessageNotification'); + spyOn(converse, 'areDesktopNotificationsEnabled').andReturn(true); + + var message = 'This message will show a desktop notification'; + var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost', + msg = $msg({ + from: sender_jid, + to: converse.connection.jid, + type: 'chat', + id: (new Date()).getTime() + }).c('body').t(message).up() + .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); + converse.chatboxes.onMessage(msg); // This will emit 'message' + expect(converse.showMessageNotification).toHaveBeenCalled(); + }); + + it("is shown when a user changes their chat state", function () { + // TODO: not yet testing show_desktop_notifications setting + spyOn(converse, 'areDesktopNotificationsEnabled').andReturn(true); + spyOn(converse, 'showChatStateNotification'); + var jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; + converse.roster.get(jid).set('chat_status', 'busy'); // This will emit 'contactStatusChanged' + expect(converse.showChatStateNotification).toHaveBeenCalled(); + }); + }); + }); + + describe("When a new contact request is received", function () { + it("an HTML5 Notification is received", function () { + // TODO + }); + }); + }); + + describe("When play_sounds is set to true", function () { + describe("A notification sound", function () { + + it("is played when the current user is mentioned in a chat room", function () { + test_utils.openChatRoom('lounge', 'localhost', 'dummy'); + converse.play_sounds = true; + spyOn(converse, 'playSoundNotification'); + var view = this.chatboxviews.get('lounge@localhost'); + if (!view.$el.find('.chat-area').length) { view.renderChatArea(); } + var text = 'This message will play a sound because it mentions dummy'; + var message = $msg({ + from: 'lounge@localhost/otheruser', + id: '1', + to: 'dummy@localhost', + type: 'groupchat' + }).c('body').t(text); + view.onChatRoomMessage(message.nodeTree); + expect(converse.playSoundNotification).toHaveBeenCalled(); + + text = "This message won't play a sound"; + message = $msg({ + from: 'lounge@localhost/otheruser', + id: '2', + to: 'dummy@localhost', + type: 'groupchat' + }).c('body').t(text); + view.onChatRoomMessage(message.nodeTree); + expect(converse.playSoundNotification, 1); + converse.play_sounds = false; + + text = "This message won't play a sound because it is sent by dummy"; + message = $msg({ + from: 'lounge@localhost/dummy', + id: '3', + to: 'dummy@localhost', + type: 'groupchat' + }).c('body').t(text); + view.onChatRoomMessage(message.nodeTree); + expect(converse.playSoundNotification, 1); + converse.play_sounds = false; + }.bind(converse)); + }); + }); + }); +})); diff --git a/src/converse-core.js b/src/converse-core.js index 54d992841..8e8db5cfa 100755 --- a/src/converse-core.js +++ b/src/converse-core.js @@ -868,6 +868,9 @@ }, attributes)); this.on('destroy', function () { this.removeFromRoster(); }.bind(this)); + this.on('change:chat_status', function (item) { + converse.emit('contactStatusChanged', item.attributes); + }); }, subscribe: function (message) { @@ -2099,7 +2102,6 @@ this.$el.find('div.chat-event').remove(); } } - converse.emit('contactStatusChanged', item.attributes); }, onStatusChanged: function (item) { diff --git a/src/converse-muc.js b/src/converse-muc.js index fb3b4af30..d2876d08f 100755 --- a/src/converse-muc.js +++ b/src/converse-muc.js @@ -888,17 +888,14 @@ onChatRoomMessage: function (message) { var $message = $(message), archive_id = $message.find('result[xmlns="'+Strophe.NS.MAM+'"]').attr('id'), - delayed = $message.find('delay').length > 0, $forwarded = $message.find('forwarded'), $delay; if ($forwarded.length) { $message = $forwarded.children('message'); $delay = $forwarded.children('delay'); - delayed = $delay.length > 0; } - var body = $message.children('body').text(), - jid = $message.attr('from'), + var jid = $message.attr('from'), msgid = $message.attr('id'), resource = Strophe.getResourceFromJid(jid), sender = resource && Strophe.unescapeNode(resource) || '', @@ -920,9 +917,6 @@ return true; } this.model.createMessage($message, $delay, archive_id); - if (!delayed && sender !== this.model.get('nick') && (new RegExp("\\b"+this.model.get('nick')+"\\b")).test(body)) { - converse.notifyOfNewMessage(); - } if (sender !== this.model.get('nick')) { // We only emit an event if it's not our own message converse.emit('message', message); diff --git a/src/converse-notification.js b/src/converse-notification.js index a9905aa0e..1d8465c22 100644 --- a/src/converse-notification.js +++ b/src/converse-notification.js @@ -56,11 +56,33 @@ ); }; - converse.shouldNotifyOfNewMessage = function ($message) { + converse.shouldNotifyOfGroupMessage = function ($message) { + /* Is this a group message worthy of notification? + */ + var jid = $message.attr('from'), + resource = Strophe.getResourceFromJid(jid), + sender = resource && Strophe.unescapeNode(resource) || ''; + if (sender === '' || $message.find('delay').length > 0) { + return false; + } + var room = converse.chatboxes.get(Strophe.getBareJidFromJid(jid)); + var body = $message.children('body').text(); + if (sender === room.get('nick') || !(new RegExp("\\b"+room.get('nick')+"\\b")).test(body)) { + return false; + } + return true; + }; + + converse.shouldNotifyOfMessage = function ($message) { + /* Is this a message worthy of notification? + */ var $forwarded = $message.find('forwarded'); if ($forwarded.length) { return false; } + if ($message.attr('type') === 'groupchat') { + return converse.shouldNotifyOfGroupMessage($message); + } var is_me = Strophe.getBareJidFromJid($message.attr('from')) === converse.bare_jid; return !converse.isOnlyChatStateNotification($message) && !is_me; }; @@ -83,16 +105,17 @@ } }; + converse.areDesktopNotificationsEnabled = function () { + return (supports_html5_notification && + converse.show_desktop_notifications && + converse.windowState === 'blur' && + Notification.permission === "granted"); + }; + converse.showMessageNotification = function ($message) { /* Shows an HTML5 Notification to indicate that a new chat * message was received. */ - if (!supports_html5_notification || - !converse.show_desktop_notifications || - converse.windowState !== 'blur' || - Notification.permission !== "granted") { - return; - } var contact_jid = Strophe.getBareJidFromJid($message.attr('from')); var roster_item = converse.roster.get(contact_jid); var n = new Notification(__(___("%1$s says"), roster_item.get('fullname')), { @@ -103,10 +126,9 @@ setTimeout(n.close.bind(n), 5000); }; - converse.handleChatStateNotification = function (evt, contact) { - /* Event handler for on('contactStatusChanged'). - * Will show an HTML5 notification to indicate that the chat - * status has changed. + converse.showChatStateNotification = function (contact) { + /* Creates an HTML5 Notification to inform of a change in a + * contact's chat state. */ var chat_state = contact.chat_status, message = null; @@ -130,21 +152,32 @@ setTimeout(n.close.bind(n), 5000); }; + converse.handleChatStateNotification = function (evt, contact) { + /* Event handler for on('contactStatusChanged'). + * Will show an HTML5 notification to indicate that the chat + * status has changed. + */ + if (converse.areDesktopNotificationsEnabled()) { + converse.showChatStateNotification(); + } + }; - converse.handleNewMessageNotification = function (evt, message) { + converse.handleMessageNotification = function (evt, 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)) { + if (!converse.shouldNotifyOfMessage($message)) { return false; } converse.playSoundNotification($message); - converse.showMessageNotification($message); + if (converse.areDesktopNotificationsEnabled()) { + converse.showMessageNotification($message); + } }; converse.on('contactStatusChanged', converse.handleChatStateNotification); - converse.on('message', converse.handleNewMessageNotification); + converse.on('message', converse.handleMessageNotification); } }); })); diff --git a/tests/main.js b/tests/main.js index ad7145fb8..39aea35aa 100644 --- a/tests/main.js +++ b/tests/main.js @@ -70,6 +70,7 @@ require([ "spec/chatbox", "spec/chatroom", "spec/minchats", + "spec/notification", "spec/profiling", "spec/ping", "spec/register",