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.
If you're using MAM for archiving chat room messages, you might want to set
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
-------------
......
......@@ -196,7 +196,7 @@
test_utils.openChatRoom('lounge', 'localhost', 'dummy');
spyOn(converse, 'emit');
converse.play_sounds = true;
spyOn(converse, 'notifyOfNewMessage');
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';
......@@ -207,7 +207,7 @@
type: 'groupchat'
}).c('body').t(text);
view.onChatRoomMessage(message.nodeTree);
expect(converse.notifyOfNewMessage).toHaveBeenCalled();
expect(converse.playSoundNotification).toHaveBeenCalled();
text = "This message won't play a sound";
message = $msg({
......@@ -217,7 +217,7 @@
type: 'groupchat'
}).c('body').t(text);
view.onChatRoomMessage(message.nodeTree);
expect(converse.notifyOfNewMessage, 1);
expect(converse.playSoundNotification, 1);
converse.play_sounds = false;
text = "This message won't play a sound because it is sent by dummy";
......@@ -228,7 +228,7 @@
type: 'groupchat'
}).c('body').t(text);
view.onChatRoomMessage(message.nodeTree);
expect(converse.notifyOfNewMessage, 1);
expect(converse.playSoundNotification, 1);
converse.play_sounds = false;
}.bind(converse));
......
......@@ -106,14 +106,6 @@
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
// XEP-0059 Result Set Management
var RSM_ATTRIBUTES = ['max', 'first', 'last', 'after', 'before', 'index', 'count'];
......@@ -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) {
var logger;
if (typeof console === "undefined" || typeof console.log === "undefined") {
......@@ -261,11 +239,21 @@
Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
Strophe.addNamespace('XFORM', 'jabber:x:data');
// Instance level constants
this.TIMEOUTS = { // Set as module attr so that we can override in tests.
'PAUSED': 20000,
'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
// ------------------------------------
this.isConverseLocale = function (locale) { return typeof locales[locale] !== "undefined"; };
......@@ -378,7 +366,6 @@
message_carbons: false, // Support for XEP-280
no_trimming: false, // Set to true for phantomjs tests (where browser apparently has no width)
password: undefined,
play_sounds: false,
prebind: false, // XXX: Deprecated, use "authentication" instead.
prebind_url: null,
rid: undefined,
......@@ -386,7 +373,6 @@
show_only_online_users: false,
show_toolbar: true,
sid: undefined,
sounds_path: '/sounds/',
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
use_vcards: true,
......@@ -453,7 +439,7 @@
/* Send out a Chat Status Notification (XEP-0352) */
if (converse.features[Strophe.NS.CSI] || true) {
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 @@
return;
}
if (this.inactive) {
this.sendCSI(ACTIVE);
this.sendCSI(converse.ACTIVE);
}
if (this.auto_changed_status === true) {
this.auto_changed_status = false;
......@@ -489,7 +475,7 @@
var stat = this.xmppstatus.getStatus();
this.idle_seconds++;
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') {
this.auto_changed_status = true;
......@@ -514,36 +500,6 @@
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) {
$('.conn-feedback').each(function (idx, el) {
var $el = $(el);
......@@ -1413,11 +1369,11 @@
fullname = this.get('fullname'),
is_groupchat = $message.attr('type') === 'groupchat',
msgid = $message.attr('id'),
chat_state = $message.find(COMPOSING).length && COMPOSING ||
$message.find(PAUSED).length && PAUSED ||
$message.find(INACTIVE).length && INACTIVE ||
$message.find(ACTIVE).length && ACTIVE ||
$message.find(GONE).length && GONE,
chat_state = $message.find(converse.COMPOSING).length && converse.COMPOSING ||
$message.find(converse.PAUSED).length && converse.PAUSED ||
$message.find(converse.INACTIVE).length && converse.INACTIVE ||
$message.find(converse.ACTIVE).length && converse.ACTIVE ||
$message.find(converse.GONE).length && converse.GONE,
stamp, time, sender, from;
if (is_groupchat) {
......@@ -1830,14 +1786,14 @@
},
handleChatStateMessage: function (message) {
if (message.get('chat_state') === COMPOSING) {
if (message.get('chat_state') === converse.COMPOSING) {
this.showStatusNotification(message.get('fullname')+' '+__('is typing'));
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'));
} 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();
} else if (message.get('chat_state') === GONE) {
} else if (message.get('chat_state') === converse.GONE) {
this.showStatusNotification(message.get('fullname')+' '+__('has gone away'));
}
},
......@@ -1876,7 +1832,7 @@
type: 'chat',
id: message.get('msgid')
}).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) {
......@@ -1967,12 +1923,12 @@
window.clearTimeout(this.chat_state_timeout);
delete this.chat_state_timeout;
}
if (state === COMPOSING) {
if (state === converse.COMPOSING) {
this.chat_state_timeout = window.setTimeout(
this.setChatState.bind(this), converse.TIMEOUTS.PAUSED, PAUSED);
} else if (state === PAUSED) {
this.setChatState.bind(this), converse.TIMEOUTS.PAUSED, converse.PAUSED);
} else if (state === converse.PAUSED) {
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) {
this.model.set('chat_state', state);
......@@ -1996,11 +1952,11 @@
}
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
// Set chat state to composing if keyCode is not a forward-slash
// (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 @@
if (ev && ev.preventDefault) { ev.preventDefault(); }
if (converse.connection.connected) {
this.model.destroy();
this.setChatState(INACTIVE);
this.setChatState(converse.INACTIVE);
} else {
this.hide();
}
......@@ -2167,7 +2123,7 @@
converse.chatboxviews.trimChats(this);
utils.refreshWebkit();
this.$content.scrollTop(this.model.get('scroll'));
this.setChatState(ACTIVE).focus();
this.setChatState(converse.ACTIVE).focus();
converse.emit('chatBoxMaximized', this);
},
......@@ -2183,7 +2139,7 @@
// save the scroll position to restore it on maximize
this.model.save({'scroll': this.$content.scrollTop()});
// Minimizes a chat box
this.setChatState(INACTIVE).model.minimize();
this.setChatState(converse.INACTIVE).model.minimize();
this.$el.hide('fast', utils.refreshwebkit);
converse.emit('chatBoxMinimized', this);
},
......@@ -2282,7 +2238,7 @@
// localstorage
this.model.save();
}
this.setChatState(ACTIVE);
this.setChatState(converse.ACTIVE);
this.scrollDown();
if (focus) {
this.focus();
......@@ -2389,7 +2345,6 @@
if (msgid && chatbox.messages.findWhere({msgid: msgid})) {
return true; // We already have this message stored.
}
converse.notifyOfNewMessage($message);
chatbox.createMessage($message, $delay, archive_id);
converse.roster.addResource(contact_jid, resource);
converse.emit('message', message);
......@@ -3160,7 +3115,7 @@
return;
}
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
* models that are in the "overrides" namespace.
*/
......
......@@ -10,46 +10,77 @@
define("converse-notification", ["converse-core", "converse-api"], factory);
}(this, function (converse, converse_api) {
"use strict";
var utils = converse_api.env.utils;
var Strophe = converse_api.env.Strophe;
var $ = converse_api.env.jQuery,
utils = converse_api.env.utils,
Strophe = converse_api.env.Strophe,
_ = converse_api.env._;
// For translations
var __ = utils.__.bind(converse);
var ___ = utils.___;
if (!("Notification" in window)) {
// HTML5 notifications aren't supported.
converse.log(
"Not loading the notifications plugin because this browser "+
"doesn't support HTML5 notifications.");
return;
}
var supports_html5_notification = "Notification" in window;
if (supports_html5_notification && Notification.permission !== 'denied') {
// Ask user to enable HTML5 notifications
Notification.requestPermission();
}
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 () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
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) {
/* Show an HTML5 notification indicating that a contact changed
......@@ -77,20 +108,39 @@
setTimeout(n.close.bind(n), 5000);
};
converse.on('contactStatusChanged', converse.showChatStateNotification);
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 roster_item = converse.roster.get(contact_jid);
var n = new Notification(__(___("%1$s says"), roster_item.get('fullname')), {
body: $message.children('body').text(),
lang: converse.i18n.locale_data.converse[""].lang,
icon: 'logo/conversejs.png'
icon: converse.notification_icon
});
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