Move converse-notifications plugin into a folder and split up
This commit is contained in:
parent
7f851208aa
commit
50dda3244e
@ -1,4 +1,4 @@
|
|||||||
/*global mock, converse, _ */
|
/*global mock, converse */
|
||||||
|
|
||||||
const { Strophe } = converse.env;
|
const { Strophe } = converse.env;
|
||||||
const $msg = converse.env.$msg;
|
const $msg = converse.env.$msg;
|
||||||
@ -15,9 +15,8 @@ describe("Notifications", function () {
|
|||||||
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
|
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
|
||||||
|
|
||||||
await mock.waitForRoster(_converse, 'current');
|
await mock.waitForRoster(_converse, 'current');
|
||||||
spyOn(_converse, 'showMessageNotification').and.callThrough();
|
const stub = jasmine.createSpyObj('MyNotification', ['onclick', 'close']);
|
||||||
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
|
spyOn(window, 'Notification').and.returnValue(stub);
|
||||||
spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
|
|
||||||
|
|
||||||
const message = 'This message will show a desktop notification';
|
const message = 'This message will show a desktop notification';
|
||||||
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
|
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();
|
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
|
||||||
await _converse.handleMessageStanza(msg); // This will emit 'message'
|
await _converse.handleMessageStanza(msg); // This will emit 'message'
|
||||||
await u.waitUntil(() => _converse.api.chatviews.get(sender_jid));
|
await u.waitUntil(() => _converse.api.chatviews.get(sender_jid));
|
||||||
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
|
expect(window.Notification).toHaveBeenCalled();
|
||||||
expect(_converse.showMessageNotification).toHaveBeenCalled();
|
|
||||||
done();
|
done();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -44,17 +42,9 @@ describe("Notifications", function () {
|
|||||||
if (!view.el.querySelectorAll('.chat-area').length) {
|
if (!view.el.querySelectorAll('.chat-area').length) {
|
||||||
view.renderChatArea();
|
view.renderChatArea();
|
||||||
}
|
}
|
||||||
let no_notification = false;
|
|
||||||
if (typeof window.Notification === 'undefined') {
|
const stub = jasmine.createSpyObj('MyNotification', ['onclick', 'close']);
|
||||||
no_notification = true;
|
spyOn(window, 'Notification').and.returnValue(stub);
|
||||||
window.Notification = function () {
|
|
||||||
return {
|
|
||||||
'close': function () {}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
spyOn(_converse, 'showMessageNotification').and.callThrough();
|
|
||||||
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
|
|
||||||
|
|
||||||
// Test mention with setting false
|
// Test mention with setting false
|
||||||
const nick = mock.chatroom_names[0];
|
const nick = mock.chatroom_names[0];
|
||||||
@ -66,8 +56,7 @@ describe("Notifications", function () {
|
|||||||
}).c('body').t(text).tree();
|
}).c('body').t(text).tree();
|
||||||
_converse.connection._dataRecv(mock.createRequest(makeMsg('romeo: this will NOT show a notification')));
|
_converse.connection._dataRecv(mock.createRequest(makeMsg('romeo: this will NOT show a notification')));
|
||||||
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
||||||
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 0);
|
expect(window.Notification).not.toHaveBeenCalled();
|
||||||
expect(_converse.showMessageNotification).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Test reference
|
// Test reference
|
||||||
const message_with_ref = $msg({
|
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();
|
.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));
|
_converse.connection._dataRecv(mock.createRequest(message_with_ref));
|
||||||
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
||||||
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
|
expect(window.Notification.calls.count()).toBe(1);
|
||||||
expect(_converse.showMessageNotification).toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Test mention with setting true
|
// Test mention with setting true
|
||||||
_converse.api.settings.set('notify_all_room_messages', true);
|
_converse.api.settings.set('notify_all_room_messages', true);
|
||||||
_converse.connection._dataRecv(mock.createRequest(makeMsg('romeo: this will show a notification')));
|
_converse.connection._dataRecv(mock.createRequest(makeMsg('romeo: this will show a notification')));
|
||||||
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
||||||
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 2);
|
expect(window.Notification.calls.count()).toBe(2);
|
||||||
expect(_converse.showMessageNotification).toHaveBeenCalled();
|
|
||||||
if (no_notification) {
|
|
||||||
delete window.Notification;
|
|
||||||
}
|
|
||||||
done();
|
done();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it("is shown for headline messages",
|
it("is shown for headline messages",
|
||||||
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
|
mock.initConverse(['rosterGroupsFetched'], {}, async (done, _converse) => {
|
||||||
|
|
||||||
spyOn(_converse, 'showMessageNotification').and.callThrough();
|
const stub = jasmine.createSpyObj('MyNotification', ['onclick', 'close']);
|
||||||
spyOn(_converse, 'isMessageToHiddenChat').and.returnValue(true);
|
spyOn(window, 'Notification').and.returnValue(stub);
|
||||||
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
|
|
||||||
const stanza = $msg({
|
const stanza = $msg({
|
||||||
'type': 'headline',
|
'type': 'headline',
|
||||||
'from': 'notify.example.com',
|
'from': 'notify.example.com',
|
||||||
@ -116,14 +99,13 @@ describe("Notifications", function () {
|
|||||||
const view = _converse.chatboxviews.get('notify.example.com');
|
const view = _converse.chatboxviews.get('notify.example.com');
|
||||||
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
await new Promise(resolve => view.model.messages.once('rendered', resolve));
|
||||||
expect(_converse.chatboxviews.keys().includes('notify.example.com')).toBeTruthy();
|
expect(_converse.chatboxviews.keys().includes('notify.example.com')).toBeTruthy();
|
||||||
expect(_converse.showMessageNotification).toHaveBeenCalled();
|
expect(window.Notification).toHaveBeenCalled();
|
||||||
done();
|
done();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it("is not shown for full JID headline messages if allow_non_roster_messaging is false", mock.initConverse((done, _converse) => {
|
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;
|
_converse.allow_non_roster_messaging = false;
|
||||||
spyOn(_converse, 'showMessageNotification').and.callThrough();
|
spyOn(window, 'Notification');
|
||||||
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
|
|
||||||
const stanza = $msg({
|
const stanza = $msg({
|
||||||
'type': 'headline',
|
'type': 'headline',
|
||||||
'from': 'someone@notify.example.com',
|
'from': 'someone@notify.example.com',
|
||||||
@ -135,11 +117,8 @@ describe("Notifications", function () {
|
|||||||
.c('x', {'xmlns': 'jabber:x:oob'})
|
.c('x', {'xmlns': 'jabber:x:oob'})
|
||||||
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
|
.c('url').t('imap://romeo@example.com/INBOX;UIDVALIDITY=385759043/;UID=18');
|
||||||
_converse.connection._dataRecv(mock.createRequest(stanza));
|
_converse.connection._dataRecv(mock.createRequest(stanza));
|
||||||
expect(
|
expect(_converse.chatboxviews.keys().includes('someone@notify.example.com')).toBeFalsy();
|
||||||
_.includes(_converse.chatboxviews.keys(),
|
expect(window.Notification).not.toHaveBeenCalled();
|
||||||
'someone@notify.example.com')
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(_converse.showMessageNotification).not.toHaveBeenCalled();
|
|
||||||
done();
|
done();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -148,12 +127,10 @@ describe("Notifications", function () {
|
|||||||
async (done, _converse) => {
|
async (done, _converse) => {
|
||||||
|
|
||||||
await mock.waitForRoster(_converse, 'current', 3);
|
await mock.waitForRoster(_converse, 'current', 3);
|
||||||
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
|
spyOn(window, 'Notification');
|
||||||
spyOn(_converse, 'showChatStateNotification');
|
|
||||||
const jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
|
const jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
|
||||||
_converse.roster.get(jid).presence.set('show', 'busy'); // This will emit 'contactStatusChanged'
|
_converse.roster.get(jid).presence.set('show', 'dnd');
|
||||||
await u.waitUntil(() => _converse.areDesktopNotificationsEnabled.calls.count() === 1);
|
expect(window.Notification).toHaveBeenCalled();
|
||||||
expect(_converse.showChatStateNotification).toHaveBeenCalled();
|
|
||||||
done()
|
done()
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
@ -161,11 +138,9 @@ describe("Notifications", function () {
|
|||||||
|
|
||||||
describe("When a new contact request is received", function () {
|
describe("When a new contact request is received", function () {
|
||||||
it("an HTML5 Notification is received", mock.initConverse((done, _converse) => {
|
it("an HTML5 Notification is received", mock.initConverse((done, _converse) => {
|
||||||
spyOn(_converse, 'areDesktopNotificationsEnabled').and.returnValue(true);
|
spyOn(window, 'Notification');
|
||||||
spyOn(_converse, 'showContactRequestNotification');
|
_converse.api.trigger('contactRequest', {'getDisplayName': () => 'Peter Parker'});
|
||||||
_converse.api.trigger('contactRequest', {'fullname': 'Peter Parker', 'jid': 'peter@parker.com'});
|
expect(window.Notification).toHaveBeenCalled();
|
||||||
expect(_converse.areDesktopNotificationsEnabled).toHaveBeenCalled();
|
|
||||||
expect(_converse.showContactRequestNotification).toHaveBeenCalled();
|
|
||||||
done();
|
done();
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
@ -180,7 +155,10 @@ describe("Notifications", function () {
|
|||||||
mock.createContacts(_converse, 'current');
|
mock.createContacts(_converse, 'current');
|
||||||
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
|
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
|
||||||
_converse.play_sounds = true;
|
_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');
|
const view = _converse.chatboxviews.get('lounge@montague.lit');
|
||||||
if (!view.el.querySelectorAll('.chat-area').length) {
|
if (!view.el.querySelectorAll('.chat-area').length) {
|
||||||
view.renderChatArea();
|
view.renderChatArea();
|
||||||
@ -194,8 +172,8 @@ describe("Notifications", function () {
|
|||||||
}).c('body').t(text);
|
}).c('body').t(text);
|
||||||
_converse.api.settings.set('notify_all_room_messages', true);
|
_converse.api.settings.set('notify_all_room_messages', true);
|
||||||
await view.model.handleMessageStanza(message.nodeTree);
|
await view.model.handleMessageStanza(message.nodeTree);
|
||||||
await u.waitUntil(() => _converse.playSoundNotification.calls.count());
|
await u.waitUntil(() => window.Audio.calls.count());
|
||||||
expect(_converse.playSoundNotification).toHaveBeenCalled();
|
expect(window.Audio).toHaveBeenCalled();
|
||||||
|
|
||||||
text = "This message won't play a sound";
|
text = "This message won't play a sound";
|
||||||
message = $msg({
|
message = $msg({
|
||||||
@ -205,7 +183,7 @@ describe("Notifications", function () {
|
|||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(text);
|
}).c('body').t(text);
|
||||||
await view.model.handleMessageStanza(message.nodeTree);
|
await view.model.handleMessageStanza(message.nodeTree);
|
||||||
expect(_converse.playSoundNotification, 1);
|
expect(window.Audio, 1);
|
||||||
_converse.play_sounds = false;
|
_converse.play_sounds = false;
|
||||||
|
|
||||||
text = "This message won't play a sound because it is sent by romeo";
|
text = "This message won't play a sound because it is sent by romeo";
|
||||||
@ -216,7 +194,7 @@ describe("Notifications", function () {
|
|||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(text);
|
}).c('body').t(text);
|
||||||
await view.model.handleMessageStanza(message.nodeTree);
|
await view.model.handleMessageStanza(message.nodeTree);
|
||||||
expect(_converse.playSoundNotification, 1);
|
expect(window.Audio, 1);
|
||||||
_converse.play_sounds = false;
|
_converse.play_sounds = false;
|
||||||
done();
|
done();
|
||||||
}));
|
}));
|
||||||
|
@ -21,7 +21,7 @@ import "./plugins/mam-views.js";
|
|||||||
import "./plugins/minimize/index.js"; // Allows chat boxes to be minimized
|
import "./plugins/minimize/index.js"; // Allows chat boxes to be minimized
|
||||||
import "./plugins/muc-views/index.js"; // Views related to MUC
|
import "./plugins/muc-views/index.js"; // Views related to MUC
|
||||||
import "./plugins/headlines-view/index.js";
|
import "./plugins/headlines-view/index.js";
|
||||||
import "./plugins/notifications.js";
|
import "./plugins/notifications/index.js";
|
||||||
import "./plugins/omemo.js";
|
import "./plugins/omemo.js";
|
||||||
import "./plugins/profile/index.js";
|
import "./plugins/profile/index.js";
|
||||||
import "./plugins/push.js"; // XEP-0357 Push Notifications
|
import "./plugins/push.js"; // XEP-0357 Push Notifications
|
||||||
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
53
src/plugins/notifications/index.js
Normal file
53
src/plugins/notifications/index.js
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
284
src/plugins/notifications/utils.js
Normal file
284
src/plugins/notifications/utils.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user