Move converse-notifications plugin into a folder and split up

This commit is contained in:
JC Brand 2021-02-01 14:38:42 +01:00
parent 7f851208aa
commit 50dda3244e
5 changed files with 368 additions and 390 deletions

View File

@ -1,4 +1,4 @@
/*global mock, converse, _ */
/*global mock, converse */
const { Strophe } = converse.env;
const $msg = converse.env.$msg;
@ -15,9 +15,8 @@ describe("Notifications", function () {
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
await mock.waitForRoster(_converse, 'current');
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
const stub = jasmine.createSpyObj('MyNotification', ['onclick', 'close']);
spyOn(window, 'Notification').and.returnValue(stub);
const message = 'This message will show a desktop notification';
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
@ -30,8 +29,7 @@ describe("Notifications", function () {
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
await _converse.handleMessageStanza(msg); // This will emit 'message'
await u.waitUntil(() => _converse.api.chatviews.get(sender_jid));
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
expect(_converse.showMessageNotification).toHaveBeenCalled();
expect(window.Notification).toHaveBeenCalled();
done();
}));
@ -44,17 +42,9 @@ describe("Notifications", function () {
if (!view.el.querySelectorAll('.chat-area').length) {
view.renderChatArea();
}
let no_notification = false;
if (typeof window.Notification === 'undefined') {
no_notification = true;
window.Notification = function () {
return {
'close': function () {}
};
};
}
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
const stub = jasmine.createSpyObj('MyNotification', ['onclick', 'close']);
spyOn(window, 'Notification').and.returnValue(stub);
// Test mention with setting false
const nick = mock.chatroom_names[0];
@ -66,8 +56,7 @@ describe("Notifications", function () {
}).c('body').t(text).tree();
_converse.connection._dataRecv(mock.createRequest(makeMsg('romeo: this will NOT show a notification')));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 0);
expect(_converse.showMessageNotification).not.toHaveBeenCalled();
expect(window.Notification).not.toHaveBeenCalled();
// Test reference
const message_with_ref = $msg({
@ -79,27 +68,21 @@ describe("Notifications", function () {
.c('reference', {'xmlns':'urn:xmpp:reference:0', 'begin':'0', 'end':'5', 'type':'mention', 'uri':'xmpp:romeo@montague.lit'}).tree();
_converse.connection._dataRecv(mock.createRequest(message_with_ref));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
expect(_converse.showMessageNotification).toHaveBeenCalled();
expect(window.Notification.calls.count()).toBe(1);
// Test mention with setting true
_converse.api.settings.set('notify_all_room_messages', true);
_converse.connection._dataRecv(mock.createRequest(makeMsg('romeo: this will show a notification')));
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 2);
expect(_converse.showMessageNotification).toHaveBeenCalled();
if (no_notification) {
delete window.Notification;
}
expect(window.Notification.calls.count()).toBe(2);
done();
}));
it("is shown for headline messages",
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
const stub = jasmine.createSpyObj('MyNotification', ['onclick', 'close']);
spyOn(window, 'Notification').and.returnValue(stub);
const stanza = $msg({
'type': 'headline',
'from': 'notify.example.com',
@ -116,14 +99,13 @@ describe("Notifications", function () {
const view = _converse.chatboxviews.get('notify.example.com');
await new Promise(resolve => view.model.messages.once('rendered', resolve));
expect(_converse.chatboxviews.keys().includes('notify.example.com')).toBeTruthy();
expect(_converse.showMessageNotification).toHaveBeenCalled();
expect(window.Notification).toHaveBeenCalled();
done();
}));
it("is not shown for full JID headline messages if allow_non_roster_messaging is false", mock.initConverse((done, _converse) => {
_converse.allow_non_roster_messaging = false;
spyOn(_converse, 'showMessageNotification').and.callThrough();
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(window, 'Notification');
const stanza = $msg({
'type': 'headline',
'from': 'someone@notify.example.com',
@ -135,11 +117,8 @@ describe("Notifications", function () {
.c('x', {'xmlns': 'jabber:x:oob'})
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
_converse.connection._dataRecv(mock.createRequest(stanza));
expect(
_.includes(_converse.chatboxviews.keys(),
'someone@notify.example.com')
).toBeFalsy();
expect(_converse.showMessageNotification).not.toHaveBeenCalled();
expect(_converse.chatboxviews.keys().includes('someone@notify.example.com')).toBeFalsy();
expect(window.Notification).not.toHaveBeenCalled();
done();
}));
@ -148,12 +127,10 @@ describe("Notifications", function () {
async (done, _converse) => {
await mock.waitForRoster(_converse, 'current', 3);
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(_converse, 'showChatStateNotification');
spyOn(window, 'Notification');
const jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
_converse.roster.get(jid).presence.set('show', 'busy'); // This will emit 'contactStatusChanged'
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
expect(_converse.showChatStateNotification).toHaveBeenCalled();
_converse.roster.get(jid).presence.set('show', 'dnd');
expect(window.Notification).toHaveBeenCalled();
done()
}));
});
@ -161,11 +138,9 @@ describe("Notifications", function () {
describe("When a new contact request is received", function () {
it("an HTML5 Notification is received", mock.initConverse((done, _converse) => {
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
spyOn(_converse, 'showContactRequestNotification');
_converse.api.trigger('contactRequest', {'fullname': 'Peter Parker', 'jid': 'peter@parker.com'});
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
expect(_converse.showContactRequestNotification).toHaveBeenCalled();
spyOn(window, 'Notification');
_converse.api.trigger('contactRequest', {'getDisplayName': () => 'Peter Parker'});
expect(window.Notification).toHaveBeenCalled();
done();
}));
});
@ -180,7 +155,10 @@ describe("Notifications", function () {
mock.createContacts(_converse, 'current');
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
_converse.play_sounds = true;
spyOn(_converse, 'playSoundNotification');
const stub = jasmine.createSpyObj('MyAudio', ['play', 'canPlayType']);
spyOn(window, 'Audio').and.returnValue(stub);
const view = _converse.chatboxviews.get('lounge@montague.lit');
if (!view.el.querySelectorAll('.chat-area').length) {
view.renderChatArea();
@ -194,8 +172,8 @@ describe("Notifications", function () {
}).c('body').t(text);
_converse.api.settings.set('notify_all_room_messages', true);
await view.model.handleMessageStanza(message.nodeTree);
await u.waitUntil(() => _converse.playSoundNotification.calls.count());
expect(_converse.playSoundNotification).toHaveBeenCalled();
await u.waitUntil(() => window.Audio.calls.count());
expect(window.Audio).toHaveBeenCalled();
text = "This message won't play a sound";
message = $msg({
@ -205,7 +183,7 @@ describe("Notifications", function () {
type: 'groupchat'
}).c('body').t(text);
await view.model.handleMessageStanza(message.nodeTree);
expect(_converse.playSoundNotification, 1);
expect(window.Audio, 1);
_converse.play_sounds = false;
text = "This message won't play a sound because it is sent by romeo";
@ -216,7 +194,7 @@ describe("Notifications", function () {
type: 'groupchat'
}).c('body').t(text);
await view.model.handleMessageStanza(message.nodeTree);
expect(_converse.playSoundNotification, 1);
expect(window.Audio, 1);
_converse.play_sounds = false;
done();
}));

View File

@ -21,7 +21,7 @@ import "./plugins/mam-views.js";
import "./plugins/minimize/index.js"; // Allows chat boxes to be minimized
import "./plugins/muc-views/index.js"; // Views related to MUC
import "./plugins/headlines-view/index.js";
import "./plugins/notifications.js";
import "./plugins/notifications/index.js";
import "./plugins/omemo.js";
import "./plugins/profile/index.js";
import "./plugins/push.js"; // XEP-0357 Push Notifications

View File

@ -1,337 +0,0 @@
/**
* @module converse-notification
* @copyright 2020, the Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/
import Favico from 'favico.js-slevomat';
import log from "@converse/headless/log";
import { __ } from '../i18n';
import { _converse, api, converse } from "@converse/headless/core";
const { Strophe } = converse.env;
const u = converse.env.utils;
const supports_html5_notification = "Notification" in window;
converse.env.Favico = Favico;
let favicon;
function updateUnreadFavicon () {
if (api.settings.get('show_tab_notifications')) {
favicon = favicon ?? new converse.env.Favico({type: 'circle', animation: 'pop'});
const chats = _converse.chatboxes.models;
const num_unread = chats.reduce((acc, chat) => (acc + (chat.get('num_unread') || 0)), 0);
favicon.badge(num_unread);
}
}
converse.plugins.add('converse-notification', {
dependencies: ["converse-chatboxes"],
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
api.settings.extend({
// ^ a list of JIDs to ignore concerning chat state notifications
chatstate_notification_blacklist: [],
notification_delay: 5000,
notification_icon: 'logo/conversejs-filled.svg',
notify_all_room_messages: false,
notify_nicknames_without_references: false,
play_sounds: true,
show_chat_state_notifications: false,
show_desktop_notifications: true,
show_tab_notifications: true,
sounds_path: api.settings.get("assets_path")+'/sounds/',
});
/**
* Is this a group message for which we should notify the user?
* @private
* @method _converse#shouldNotifyOfGroupMessage
* @param { MUCMessageAttributes } attrs
*/
_converse.shouldNotifyOfGroupMessage = function (attrs) {
if (!attrs?.body) {
return false;
}
const jid = attrs.from;
const muc_jid = attrs.from_muc;
const notify_all = api.settings.get('notify_all_room_messages');
const room = _converse.chatboxes.get(muc_jid);
const resource = Strophe.getResourceFromJid(jid);
const sender = resource && Strophe.unescapeNode(resource) || '';
let is_mentioned = false;
const nick = room.get('nick');
if (api.settings.get('notify_nicknames_without_references')) {
is_mentioned = (new RegExp(`\\b${nick}\\b`)).test(attrs.body);
}
const references_me = (r) => {
const jid = r.uri.replace(/^xmpp:/, '');
return jid == _converse.bare_jid || jid === `${muc_jid}/${nick}`;
}
const is_referenced = attrs.references.reduce((acc, r) => acc || references_me(r), false);
const is_not_mine = sender !== nick;
const should_notify_user = notify_all === true
|| (Array.isArray(notify_all) && notify_all.includes(muc_jid))
|| is_referenced
|| is_mentioned;
return is_not_mine && !!should_notify_user;
};
/**
* Given parsed attributes for a message stanza, get the related
* chatbox and check whether it's hidden.
* @private
* @method _converse#isMessageToHiddenChat
* @param { MUCMessageAttributes } attrs
*/
_converse.isMessageToHiddenChat = function (attrs) {
return _converse.chatboxes.get(attrs.from)?.isHidden() ?? false;
};
/**
* @private
* @method _converse#shouldNotifyOfMessage
* @param { MessageData|MUCMessageData } data
*/
_converse.shouldNotifyOfMessage = function (data) {
const { attrs, stanza } = data;
if (!attrs || stanza.querySelector('forwarded') !== null) {
return false;
}
if (attrs['type'] === 'groupchat') {
return _converse.shouldNotifyOfGroupMessage(attrs);
} else if (attrs.is_headline) {
// We want to show notifications for headline messages.
return _converse.isMessageToHiddenChat(attrs);
}
const is_me = Strophe.getBareJidFromJid(attrs.from) === _converse.bare_jid;
return !u.isOnlyChatStateNotification(stanza) &&
!u.isOnlyMessageDeliveryReceipt(stanza) &&
!is_me &&
(api.settings.get('show_desktop_notifications') === 'all' || _converse.isMessageToHiddenChat(attrs));
};
/**
* Plays a notification sound
* @private
* @method _converse#playSoundNotification
*/
_converse.playSoundNotification = function () {
if (api.settings.get('play_sounds') && window.Audio !== undefined) {
const audioOgg = new Audio(api.settings.get('sounds_path')+"msg_received.ogg");
const canPlayOgg = audioOgg.canPlayType('audio/ogg');
if (canPlayOgg === 'probably') {
return audioOgg.play();
}
const audioMp3 = new Audio(api.settings.get('sounds_path')+"msg_received.mp3");
const canPlayMp3 = audioMp3.canPlayType('audio/mp3');
if (canPlayMp3 === 'probably') {
audioMp3.play();
} else if (canPlayOgg === 'maybe') {
audioOgg.play();
} else if (canPlayMp3 === 'maybe') {
audioMp3.play();
}
}
};
_converse.areDesktopNotificationsEnabled = function () {
return supports_html5_notification &&
api.settings.get('show_desktop_notifications') &&
Notification.permission === "granted";
};
/**
* Shows an HTML5 Notification with the passed in message
* @private
* @method _converse#showMessageNotification
* @param { MessageData|MUCMessageData } data
*/
_converse.showMessageNotification = function (data) {
const { attrs } = data;
if (attrs.is_error) {
return;
}
if (!_converse.areDesktopNotificationsEnabled()) {
return;
}
let title, roster_item;
const full_from_jid = attrs.from,
from_jid = Strophe.getBareJidFromJid(full_from_jid);
if (attrs.type === 'headline') {
if (!from_jid.includes('@') || api.settings.get("allow_non_roster_messaging")) {
title = __("Notification from %1$s", from_jid);
} else {
return;
}
} else if (!from_jid.includes('@')) {
// workaround for Prosody which doesn't give type "headline"
title = __("Notification from %1$s", from_jid);
} else if (attrs.type === 'groupchat') {
title = __("%1$s says", Strophe.getResourceFromJid(full_from_jid));
} else {
if (_converse.roster === undefined) {
log.error("Could not send notification, because roster is undefined");
return;
}
roster_item = _converse.roster.get(from_jid);
if (roster_item !== undefined) {
title = __("%1$s says", roster_item.getDisplayName());
} else {
if (api.settings.get("allow_non_roster_messaging")) {
title = __("%1$s says", from_jid);
} else {
return;
}
}
}
const body = attrs.is_encrypted ? __('Encrypted message received') : attrs.body;
if (!body) {
return;
}
const n = new Notification(title, {
'body': body,
'lang': _converse.locale,
'icon': api.settings.get('notification_icon'),
'requireInteraction': !_converse.notification_delay
});
if (api.settings.get('notification_delay')) {
setTimeout(n.close.bind(n), api.settings.get('notification_delay'));
}
n.onclick = function (event) {
event.preventDefault();
window.focus();
const chat = _converse.chatboxes.get(from_jid);
chat.maybeShow(true);
}
n.onclick.bind(_converse);
};
_converse.showChatStateNotification = function (contact) {
/* Creates an HTML5 Notification to inform of a change in a
* contact's chat state.
*/
if (_converse.chatstate_notification_blacklist.includes(contact.jid)) {
// Don't notify if the user is being ignored.
return;
}
const chat_state = contact.chat_status;
let message = null;
if (chat_state === 'offline') {
message = __('has gone offline');
} else if (chat_state === 'away') {
message = __('has gone away');
} else if ((chat_state === 'dnd')) {
message = __('is busy');
} else if (chat_state === 'online') {
message = __('has come online');
}
if (message === null) {
return;
}
const n = new Notification(contact.getDisplayName(), {
body: message,
lang: _converse.locale,
icon: _converse.notification_icon
});
setTimeout(n.close.bind(n), 5000);
};
_converse.showContactRequestNotification = function (contact) {
const n = new Notification(contact.getDisplayName(), {
body: __('wants to be your contact'),
lang: _converse.locale,
icon: _converse.notification_icon
});
setTimeout(n.close.bind(n), 5000);
};
_converse.showFeedbackNotification = function (data) {
if (data.klass === 'error' || data.klass === 'warn') {
const n = new Notification(data.subject, {
body: data.message,
lang: _converse.locale,
icon: _converse.notification_icon
});
setTimeout(n.close.bind(n), 5000);
}
};
_converse.handleChatStateNotification = function (contact) {
/* Event handler for on('contactPresenceChanged').
* Will show an HTML5 notification to indicate that the chat
* status has changed.
*/
if (_converse.areDesktopNotificationsEnabled() && api.settings.get('show_chat_state_notifications')) {
_converse.showChatStateNotification(contact);
}
};
_converse.handleMessageNotification = function (data) {
/* Event handler for the on('message') event. Will call methods
* to play sounds and show HTML5 notifications.
*/
if (!_converse.shouldNotifyOfMessage(data)) {
return false;
}
/**
* Triggered when a notification (sound or HTML5 notification) for a new
* message has will be made.
* @event _converse#messageNotification
* @type { MessageData|MUCMessageData}
* @example _converse.api.listen.on('messageNotification', stanza => { ... });
*/
api.trigger('messageNotification', data);
_converse.playSoundNotification();
_converse.showMessageNotification(data);
};
_converse.handleContactRequestNotification = function (contact) {
if (_converse.areDesktopNotificationsEnabled(true)) {
_converse.showContactRequestNotification(contact);
}
};
_converse.handleFeedback = function (data) {
if (_converse.areDesktopNotificationsEnabled(true)) {
_converse.showFeedbackNotification(data);
}
};
_converse.requestPermission = function () {
if (supports_html5_notification && !['denied', 'granted'].includes(Notification.permission)) {
// Ask user to enable HTML5 notifications
Notification.requestPermission();
}
};
/************************ BEGIN Event Handlers ************************/
api.listen.on('clearSession', () => (favicon = null)); // Needed for tests
api.waitUntil('chatBoxesInitialized').then(
() => _converse.chatboxes.on('change:num_unread', updateUnreadFavicon));
api.listen.on('pluginsInitialized', function () {
// We only register event handlers after all plugins are
// registered, because other plugins might override some of our
// handlers.
api.listen.on('contactRequest', _converse.handleContactRequestNotification);
api.listen.on('contactPresenceChanged', _converse.handleChatStateNotification);
api.listen.on('message', _converse.handleMessageNotification);
api.listen.on('feedback', _converse.handleFeedback);
api.listen.on('connected', _converse.requestPermission);
});
}
});

View File

@ -0,0 +1,53 @@
/**
* @module converse-notification
* @copyright 2020, the Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/
import { _converse, api, converse } from '@converse/headless/core';
import {
clearFavicon,
handleChatStateNotification,
handleContactRequestNotification,
handleFeedback,
handleMessageNotification,
requestPermission,
updateUnreadFavicon
} from './utils.js';
converse.plugins.add('converse-notification', {
dependencies: ['converse-chatboxes'],
initialize () {
api.settings.extend({
// ^ a list of JIDs to ignore concerning chat state notifications
chatstate_notification_blacklist: [],
notification_delay: 5000,
notification_icon: 'logo/conversejs-filled.svg',
notify_all_room_messages: false,
notify_nicknames_without_references: false,
play_sounds: true,
show_chat_state_notifications: false,
show_desktop_notifications: true,
show_tab_notifications: true,
sounds_path: api.settings.get('assets_path') + '/sounds/'
});
/************************ Event Handlers ************************/
api.listen.on('clearSession', clearFavicon); // Needed for tests
api.waitUntil('chatBoxesInitialized').then(() =>
_converse.chatboxes.on('change:num_unread', updateUnreadFavicon)
);
api.listen.on('pluginsInitialized', function () {
// We only register event handlers after all plugins are
// registered, because other plugins might override some of our
// handlers.
api.listen.on('contactRequest', handleContactRequestNotification);
api.listen.on('contactPresenceChanged', handleChatStateNotification);
api.listen.on('message', handleMessageNotification);
api.listen.on('feedback', handleFeedback);
api.listen.on('connected', requestPermission);
});
}
});

View File

@ -0,0 +1,284 @@
import Favico from 'favico.js-slevomat';
import log from '@converse/headless/log';
import { __ } from 'i18n';
import { _converse, api, converse } from '@converse/headless/core';
const { Strophe, u } = converse.env;
const supports_html5_notification = 'Notification' in window;
converse.env.Favico = Favico;
let favicon;
export function isMessageToHiddenChat (attrs) {
return _converse.isTestEnv() || (_converse.chatboxes.get(attrs.from)?.isHidden() ?? false);
}
export function areDesktopNotificationsEnabled () {
return _converse.isTestEnv() || (
supports_html5_notification &&
api.settings.get('show_desktop_notifications') &&
Notification.permission === 'granted'
);
}
export function clearFavicon () {
favicon = null;
}
export function updateUnreadFavicon () {
if (api.settings.get('show_tab_notifications')) {
favicon = favicon ?? new converse.env.Favico({ type: 'circle', animation: 'pop' });
const chats = _converse.chatboxes.models;
const num_unread = chats.reduce((acc, chat) => acc + (chat.get('num_unread') || 0), 0);
favicon.badge(num_unread);
}
}
/**
* Is this a group message for which we should notify the user?
* @private
* @param { MUCMessageAttributes } attrs
*/
export function shouldNotifyOfGroupMessage (attrs) {
if (!attrs?.body) {
return false;
}
const jid = attrs.from;
const muc_jid = attrs.from_muc;
const notify_all = api.settings.get('notify_all_room_messages');
const room = _converse.chatboxes.get(muc_jid);
const resource = Strophe.getResourceFromJid(jid);
const sender = (resource && Strophe.unescapeNode(resource)) || '';
let is_mentioned = false;
const nick = room.get('nick');
if (api.settings.get('notify_nicknames_without_references')) {
is_mentioned = new RegExp(`\\b${nick}\\b`).test(attrs.body);
}
const references_me = r => {
const jid = r.uri.replace(/^xmpp:/, '');
return jid == _converse.bare_jid || jid === `${muc_jid}/${nick}`;
};
const is_referenced = attrs.references.reduce((acc, r) => acc || references_me(r), false);
const is_not_mine = sender !== nick;
const should_notify_user =
notify_all === true ||
(Array.isArray(notify_all) && notify_all.includes(muc_jid)) ||
is_referenced ||
is_mentioned;
return is_not_mine && !!should_notify_user;
}
/**
* @private
* @method shouldNotifyOfMessage
* @param { MessageData|MUCMessageData } data
*/
function shouldNotifyOfMessage (data) {
const { attrs, stanza } = data;
if (!attrs || stanza.querySelector('forwarded') !== null) {
return false;
}
if (attrs['type'] === 'groupchat') {
return shouldNotifyOfGroupMessage(attrs);
} else if (attrs.is_headline) {
// We want to show notifications for headline messages.
return isMessageToHiddenChat(attrs);
}
const is_me = Strophe.getBareJidFromJid(attrs.from) === _converse.bare_jid;
return (
!u.isOnlyChatStateNotification(stanza) &&
!u.isOnlyMessageDeliveryReceipt(stanza) &&
!is_me &&
(api.settings.get('show_desktop_notifications') === 'all' || isMessageToHiddenChat(attrs))
);
}
export function showFeedbackNotification (data) {
if (data.klass === 'error' || data.klass === 'warn') {
const n = new Notification(data.subject, {
body: data.message,
lang: _converse.locale,
icon: _converse.notification_icon
});
setTimeout(n.close.bind(n), 5000);
}
}
/**
* Creates an HTML5 Notification to inform of a change in a
* contact's chat state.
*/
function showChatStateNotification (contact) {
if (_converse.chatstate_notification_blacklist.includes(contact.jid)) {
// Don't notify if the user is being ignored.
return;
}
const chat_state = contact.presence.get('show');
let message = null;
if (chat_state === 'offline') {
message = __('has gone offline');
} else if (chat_state === 'away') {
message = __('has gone away');
} else if (chat_state === 'dnd') {
message = __('is busy');
} else if (chat_state === 'online') {
message = __('has come online');
}
if (message === null) {
return;
}
const n = new Notification(contact.getDisplayName(), {
body: message,
lang: _converse.locale,
icon: _converse.notification_icon
});
setTimeout(() => n.close(), 5000);
}
/**
* Shows an HTML5 Notification with the passed in message
* @private
* @param { MessageData|MUCMessageData } data
*/
function showMessageNotification (data) {
const { attrs } = data;
if (attrs.is_error) {
return;
}
if (!areDesktopNotificationsEnabled()) {
return;
}
let title, roster_item;
const full_from_jid = attrs.from,
from_jid = Strophe.getBareJidFromJid(full_from_jid);
if (attrs.type === 'headline') {
if (!from_jid.includes('@') || api.settings.get('allow_non_roster_messaging')) {
title = __('Notification from %1$s', from_jid);
} else {
return;
}
} else if (!from_jid.includes('@')) {
// workaround for Prosody which doesn't give type "headline"
title = __('Notification from %1$s', from_jid);
} else if (attrs.type === 'groupchat') {
title = __('%1$s says', Strophe.getResourceFromJid(full_from_jid));
} else {
if (_converse.roster === undefined) {
log.error('Could not send notification, because roster is undefined');
return;
}
roster_item = _converse.roster.get(from_jid);
if (roster_item !== undefined) {
title = __('%1$s says', roster_item.getDisplayName());
} else {
if (api.settings.get('allow_non_roster_messaging')) {
title = __('%1$s says', from_jid);
} else {
return;
}
}
}
const body = attrs.is_encrypted ? __('Encrypted message received') : attrs.body;
if (!body) {
return;
}
const n = new Notification(title, {
'body': body,
'lang': _converse.locale,
'icon': api.settings.get('notification_icon'),
'requireInteraction': !_converse.notification_delay
});
if (api.settings.get('notification_delay')) {
setTimeout(() => n.close(), api.settings.get('notification_delay'));
}
n.onclick = function (event) {
event.preventDefault();
window.focus();
const chat = _converse.chatboxes.get(from_jid);
chat.maybeShow(true);
}
}
function playSoundNotification () {
if (api.settings.get('play_sounds') && window.Audio !== undefined) {
const audioOgg = new Audio(api.settings.get('sounds_path') + 'msg_received.ogg');
const canPlayOgg = audioOgg.canPlayType('audio/ogg');
if (canPlayOgg === 'probably') {
return audioOgg.play();
}
const audioMp3 = new Audio(api.settings.get('sounds_path') + 'msg_received.mp3');
const canPlayMp3 = audioMp3.canPlayType('audio/mp3');
if (canPlayMp3 === 'probably') {
audioMp3.play();
} else if (canPlayOgg === 'maybe') {
audioOgg.play();
} else if (canPlayMp3 === 'maybe') {
audioMp3.play();
}
}
}
/**
* Event handler for the on('message') event. Will call methods
* to play sounds and show HTML5 notifications.
*/
export function handleMessageNotification (data) {
if (!shouldNotifyOfMessage(data)) {
return false;
}
/**
* Triggered when a notification (sound or HTML5 notification) for a new
* message has will be made.
* @event _converse#messageNotification
* @type { MessageData|MUCMessageData}
* @example _converse.api.listen.on('messageNotification', stanza => { ... });
*/
api.trigger('messageNotification', data);
playSoundNotification();
showMessageNotification(data);
}
export function handleFeedback (data) {
if (areDesktopNotificationsEnabled(true)) {
showFeedbackNotification(data);
}
}
/**
* Event handler for on('contactPresenceChanged').
* Will show an HTML5 notification to indicate that the chat status has changed.
*/
export function handleChatStateNotification (contact) {
if (areDesktopNotificationsEnabled() && api.settings.get('show_chat_state_notifications')) {
showChatStateNotification(contact);
}
}
function showContactRequestNotification (contact) {
const n = new Notification(contact.getDisplayName(), {
body: __('wants to be your contact'),
lang: _converse.locale,
icon: _converse.notification_icon
});
setTimeout(() => n.close(), 5000);
}
export function handleContactRequestNotification (contact) {
if (areDesktopNotificationsEnabled(true)) {
showContactRequestNotification(contact);
}
}
export function requestPermission () {
if (supports_html5_notification && !['denied', 'granted'].includes(Notification.permission)) {
// Ask user to enable HTML5 notifications
Notification.requestPermission();
}
}