Testing van be done at https://snappymail.eu/demo/
This commit is contained in:
djmaze 2020-11-09 20:14:04 +01:00
parent 0ec37b7e90
commit 542d9c91e9
10 changed files with 215 additions and 270 deletions

View file

@ -1,128 +1,128 @@
import * as Links from 'Common/Links';
class Audio {
notificator = null;
player = null;
let notificator = null,
player = null,
canPlay = type => player && !!player.canPlayType(type).replace('no', ''),
supported = false;
supportedMp3 = false;
supportedOgg = false;
supportedWav = false;
supportedNotification = false;
audioCtx = AudioContext || window.webkitAudioContext,
constructor() {
this.player = this.createNewObject();
// Safari can't play without user interaction
this.supported = !!this.player && !!this.player.play;
if (this.supported && this.player && this.player.canPlayType) {
this.supportedMp3 = !!this.player.canPlayType('audio/mpeg;').replace(/no/, '');
this.supportedWav = !!this.player.canPlayType('audio/wav; codecs="1"').replace(/no/, '');
this.supportedOgg = !!this.player.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, '');
this.supportedNotification = this.supported && this.supportedMp3;
play = (url, name) => {
if (player) {
player.src = url;
player.play();
name = name.trim();
dispatchEvent(new CustomEvent('audio.start', {detail:name.replace(/\.([a-z0-9]{3})$/, '') || 'audio'}));
}
},
if (!this.player || (!this.supportedMp3 && !this.supportedOgg && !this.supportedWav)) {
this.supported = false;
this.supportedMp3 = false;
this.supportedOgg = false;
this.supportedWav = false;
this.supportedNotification = false;
}
if (this.supported && this.player) {
const stopFn = () => this.stop();
this.player.addEventListener('ended', stopFn);
this.player.addEventListener('error', stopFn);
addEventListener('audio.api.stop', stopFn);
}
}
createNewObject() {
createNewObject = () => {
try {
const player = window.Audio ? new Audio() : null;
if (player && player.canPlayType && player.pause && player.play) {
const player = new Audio;
if (player.canPlayType && player.pause && player.play) {
player.preload = 'none';
player.loop = false;
player.autoplay = false;
player.muted = false;
return player;
}
return player;
} catch (e) {} // eslint-disable-line no-empty
} catch (e) {
console.error(e);
}
return null;
},
// The AudioContext is not allowed to start.
// It must be resumed (or created) after a user gesture on the page. https://goo.gl/7K7WLu
// Setup listeners to attempt an unlock
unlockEvents = [
'click','dblclick',
'contextmenu',
'auxclick',
'mousedown','mouseup',
'pointerup',
'touchstart','touchend',
'keydown','keyup'
],
unlock = () => {
if (audioCtx) {
console.log('AudioContext ' + audioCtx.state);
audioCtx.resume();
}
unlockEvents.forEach(type => document.removeEventListener(type, unlock, true));
// setTimeout(()=>Audio.playNotification(1),1);
};
if (audioCtx) {
audioCtx = audioCtx ? new audioCtx : null;
audioCtx.onstatechange = unlock;
}
unlockEvents.forEach(type => document.addEventListener(type, unlock, true));
/**
* Browsers can't play without user interaction
*/
const SMAudio = new class {
supported = false;
supportedMp3 = false;
supportedOgg = false;
supportedWav = false;
constructor() {
player || (player = createNewObject());
this.supported = !!player;
if (player) {
this.supportedMp3 = !!canPlay('audio/mpeg;');
this.supportedWav = !!canPlay('audio/wav; codecs="1"');
this.supportedOgg = !!canPlay('audio/ogg; codecs="vorbis"');
const stopFn = () => this.pause();
player.addEventListener('ended', stopFn);
player.addEventListener('error', stopFn);
addEventListener('audio.api.stop', stopFn);
}
}
paused() {
return this.supported ? !!this.player.paused : true;
return !player || player.paused;
}
stop() {
if (this.supported && this.player.pause) {
this.player.pause();
}
dispatchEvent(new CustomEvent('audio.stop'));
this.pause();
}
pause() {
this.stop();
}
clearName(name = '', ext = '') {
name = name.trim();
if (ext && '.' + ext === name.toLowerCase().substr((ext.length + 1) * -1)) {
name = name.substr(0, name.length - 4).trim();
}
return name || 'audio';
player && player.pause();
dispatchEvent(new CustomEvent('audio.stop'));
}
playMp3(url, name) {
if (this.supported && this.supportedMp3) {
this.player.src = url;
this.player.play();
dispatchEvent(new CustomEvent('audio.start', {detail:this.clearName(name, 'mp3')}));
}
this.supportedMp3 && play(url, name);
}
playOgg(url, name) {
if (this.supported && this.supportedOgg) {
this.player.src = url;
this.player.play();
name = this.clearName(name, 'oga');
name = this.clearName(name, 'ogg');
dispatchEvent(new CustomEvent('audio.start', {detail:name}));
}
this.supportedOgg && play(url, name);
}
playWav(url, name) {
if (this.supported && this.supportedWav) {
this.player.src = url;
this.player.play();
dispatchEvent(new CustomEvent('audio.start', {detail:this.clearName(name, 'wav')}));
}
this.supportedWav && play(url, name);
}
playNotification() {
if (this.supported && this.supportedMp3) {
if (!this.notificator) {
this.notificator = this.createNewObject();
this.notificator.src = Links.sound('new-mail.mp3');
playNotification(silent) {
if ('running' == audioCtx.state && (this.supportedMp3 || this.supportedOgg)) {
if (!notificator) {
notificator = createNewObject();
notificator.src = Links.sound('new-mail.'+ (this.supportedMp3 ? 'mp3' : 'ogg'));
}
if (this.notificator && this.notificator.play) {
this.notificator.play();
if (notificator) {
notificator.volume = silent ? 0.01 : 1;
notificator.play();
}
} else {
console.log('No audio: ' + audioCtx.state);
}
}
}
};
export default new Audio();
export default SMAudio;

View file

@ -197,16 +197,6 @@ export const MessageSelectAction = {
Unflagged: 6
};
/**
* @enum {number}
*/
export const DesktopNotification = {
Allowed: 0,
NotAllowed: 1,
Denied: 2,
NotSupported: 9
};
/**
* @enum {number}
*/

View file

@ -30,11 +30,9 @@ class GeneralUserSettings {
this.layout = SettingsStore.layout;
this.usePreviewPane = SettingsStore.usePreviewPane;
this.soundNotificationIsSupported = NotificationStore.soundNotificationIsSupported;
this.enableSoundNotification = NotificationStore.enableSoundNotification;
this.enableDesktopNotification = NotificationStore.enableDesktopNotification;
this.isDesktopNotificationSupported = NotificationStore.isDesktopNotificationSupported;
this.isDesktopNotificationDenied = NotificationStore.isDesktopNotificationDenied;
this.showImages = SettingsStore.showImages;
@ -96,6 +94,10 @@ class GeneralUserSettings {
NotificationStore.playSoundNotification(true);
}
testSystemNotification() {
NotificationStore.displayDesktopNotification('SnappyMail', 'Test notification', { 'Folder': '', 'Uid': '' });
}
onBuild() {
setTimeout(() => {
const f0 = settingsSaveHelperSimpleFunction(this.editorDefaultTypeTrigger, this),
@ -157,10 +159,6 @@ class GeneralUserSettings {
}, 50);
}
onShow() {
this.enableDesktopNotification.valueHasMutated();
}
selectLanguage() {
showScreenPopup(require('View/Popup/Languages'), [this.language, this.languages(), LanguageStore.userLanguage()]);
}

View file

@ -16,7 +16,7 @@ import {
clearNewMessageCache
} from 'Common/Cache';
import { mailBox, notificationMailIcon } from 'Common/Links';
import { mailBox } from 'Common/Links';
import { i18n, getNotification } from 'Common/Translator';
import { EmailCollectionModel } from 'Model/EmailCollection';
@ -245,7 +245,6 @@ class MessageUserStore {
const len = newMessages.length;
if (3 < len) {
NotificationStore.displayDesktopNotification(
notificationMailIcon(),
AccountStore.email(),
i18n('MESSAGE_LIST/NEW_MESSAGE_NOTIFICATION', {
'COUNT': len
@ -255,7 +254,6 @@ class MessageUserStore {
} else {
newMessages.forEach(item => {
NotificationStore.displayDesktopNotification(
notificationMailIcon(),
EmailCollectionModel.reviveFromJson(item.From).toString(),
item.Subject,
{ 'Folder': item.Folder, 'Uid': item.Uid }

View file

@ -1,154 +1,96 @@
import ko from 'ko';
import { DesktopNotification } from 'Common/Enums';
import Audio from 'Common/Audio';
import * as Links from 'Common/Links';
/**
* Might not work due to the new ServiceWorkerRegistration.showNotification
*/
const HTML5Notification = window.Notification ? Notification : null,
HTML5NotificationStatus = () => (HTML5Notification && HTML5Notification.permission) || 'denied',
dispatchMessage = data => {
focus();
if (data.Folder && data.Uid) {
dispatchEvent(new CustomEvent('mailbox.message.show', {detail:data}));
}
};
let DesktopNotifications = false,
WorkerNotifications = navigator.serviceWorker;
// Are Notifications supported in the service worker?
if (WorkerNotifications && ServiceWorkerRegistration && ServiceWorkerRegistration.prototype.showNotification) {
console.log('ServiceWorker supported');
/* Listen for close requests from the ServiceWorker */
WorkerNotifications.addEventListener('message', event => {
const obj = JSON.parse(event.data);
obj && 'notificationclick' === obj.action && dispatchMessage(obj.data);
});
} else {
WorkerNotifications = null;
console.log('WorkerNotifications not supported');
}
class NotificationUserStore {
constructor() {
this.enableSoundNotification = ko.observable(false);
this.soundNotificationIsSupported = ko.observable(false);
this.allowDesktopNotification = ko.observable(false);
this.enableDesktopNotification = ko.observable(false)/*.extend({ notify: 'always' })*/;
this.desktopNotificationPermissions = ko
.computed(() => {
this.allowDesktopNotification();
this.isDesktopNotificationDenied = ko.observable('denied' === HTML5NotificationStatus());
let result = DesktopNotification.NotSupported;
const NotificationClass = this.notificationClass();
if (NotificationClass && NotificationClass.permission) {
switch (NotificationClass.permission.toLowerCase()) {
case 'granted':
result = DesktopNotification.Allowed;
break;
case 'denied':
result = DesktopNotification.Denied;
break;
case 'default':
result = DesktopNotification.NotAllowed;
break;
// no default
}
} else if (window.webkitNotifications && window.webkitNotifications.checkPermission) {
result = window.webkitNotifications.checkPermission();
}
return result;
})
.extend({ notify: 'always' });
this.enableDesktopNotification = ko
.computed({
read: () =>
this.allowDesktopNotification() && DesktopNotification.Allowed === this.desktopNotificationPermissions(),
write: (value) => {
if (value) {
const NotificationClass = this.notificationClass(),
permission = this.desktopNotificationPermissions();
if (NotificationClass && DesktopNotification.Allowed === permission) {
this.allowDesktopNotification(true);
} else if (NotificationClass && DesktopNotification.NotAllowed === permission) {
NotificationClass.requestPermission(() => {
this.allowDesktopNotification.valueHasMutated();
if (DesktopNotification.Allowed === this.desktopNotificationPermissions()) {
if (this.allowDesktopNotification()) {
this.allowDesktopNotification.valueHasMutated();
} else {
this.allowDesktopNotification(true);
}
} else {
if (this.allowDesktopNotification()) {
this.allowDesktopNotification(false);
} else {
this.allowDesktopNotification.valueHasMutated();
}
}
});
} else {
this.allowDesktopNotification(false);
}
} else {
this.allowDesktopNotification(false);
}
}
})
.extend({ notify: 'always' });
if (!this.enableDesktopNotification.valueHasMutated) {
this.enableDesktopNotification.valueHasMutated = () => {
this.allowDesktopNotification.valueHasMutated();
};
}
this.isDesktopNotificationSupported = ko.computed(
() => DesktopNotification.NotSupported !== this.desktopNotificationPermissions()
);
this.isDesktopNotificationDenied = ko.computed(
() =>
DesktopNotification.NotSupported === this.desktopNotificationPermissions() ||
DesktopNotification.Denied === this.desktopNotificationPermissions()
);
this.initNotificationPlayer();
}
initNotificationPlayer() {
if (Audio && Audio.supportedNotification) {
this.soundNotificationIsSupported(true);
} else {
this.enableSoundNotification(false);
this.soundNotificationIsSupported(false);
}
this.enableDesktopNotification.subscribe(value => {
DesktopNotifications = !!value;
if (value && HTML5Notification && 'granted' !== HTML5Notification.permission) {
HTML5Notification.requestPermission(() =>
this.isDesktopNotificationDenied('denied' === HTML5Notification.permission)
);
}
});
}
/**
* Used with SoundNotification setting
*/
playSoundNotification(skipSetting) {
if (Audio && Audio.supportedNotification && (skipSetting ? true : this.enableSoundNotification())) {
if (skipSetting ? true : this.enableSoundNotification()) {
Audio.playNotification();
}
}
displayDesktopNotification(imageSrc, title, text, messageData) {
if (this.enableDesktopNotification()) {
const NotificationClass = this.notificationClass(),
notification = NotificationClass
? new NotificationClass(title, {
body: text,
icon: imageSrc
})
: null;
if (notification) {
if (notification.show) {
notification.show();
}
if (messageData) {
notification.onclick = () => {
focus();
if (messageData.Folder && messageData.Uid) {
dispatchEvent(new CustomEvent('mailbox.message.show', {detail:messageData}));
}
};
}
setTimeout(
(function(localNotifications) {
return () => {
if (localNotifications.cancel) {
localNotifications.cancel();
} else if (localNotifications.close) {
localNotifications.close();
}
};
})(notification),
7000
);
/**
* Used with DesktopNotifications setting
*/
displayDesktopNotification(title, text, messageData, imageSrc) {
if (DesktopNotifications && 'granted' === HTML5NotificationStatus()) {
const options = {
body: text,
icon: imageSrc || Links.notificationMailIcon(),
data: messageData
};
if (WorkerNotifications) {
// Service-Worker-Allowed HTTP header to allow the scope.
WorkerNotifications.register('/serviceworker.js')
// WorkerNotifications.register(Links.staticPrefix('js/serviceworker.js'), {scope:'/'})
.then(() =>
WorkerNotifications.ready.then(registration =>
/* Show the notification */
registration
.showNotification(title, options)
.then(() =>
registration.getNotifications().then((/*notifications*/) => {
/* Send an empty message so the Worker knows who the client is */
registration.active.postMessage('');
})
)
)
)
.catch(e => console.error(e));
} else {
const notification = new HTML5Notification(title, options);
notification.show && notification.show();
notification.onclick = messageData ? () => dispatchMessage(messageData) : null;
setTimeout(() => notification.close(), 7000);
}
}
}
@ -157,13 +99,6 @@ class NotificationUserStore {
this.enableSoundNotification(!!rl.settings.get('SoundNotification'));
this.enableDesktopNotification(!!rl.settings.get('DesktopNotifications'));
}
/**
* @returns {*|null}
*/
notificationClass() {
return window.Notification && Notification.requestPermission ? Notification : null;
}
}
export default new NotificationUserStore();

15
dev/serviceworker.js Normal file
View file

@ -0,0 +1,15 @@
'use strict';
self.addEventListener('message', event => self.client = event.source);
const fn = event => {
self.client.postMessage(
JSON.stringify({
data: event.notification.data,
action: event.type
})
);
};
self.onnotificationclose = fn;
self.onnotificationclick = fn;