Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn about undelivered notifications #1265

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions js/notifications-main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/notifications-main.js.map

Large diffs are not rendered by default.

16 changes: 15 additions & 1 deletion lib/Listener/BeforeTemplateRenderedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,23 @@
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Notification\IManager;
use OCP\Util;

class BeforeTemplateRenderedListener implements IEventListener {
protected IConfig $config;
protected IUserSession $userSession;
protected IInitialState $initialState;
protected IManager $notificationManager;

public function __construct(IConfig $config,
IUserSession $userSession,
IInitialState $initialState) {
IInitialState $initialState,
IManager $notificationManager) {
$this->config = $config;
$this->userSession = $userSession;
$this->initialState = $initialState;
$this->notificationManager = $notificationManager;
}

public function handle(Event $event): void {
Expand Down Expand Up @@ -84,6 +88,16 @@ public function handle(Event $event): void {
) === 'yes'
);

/**
* We want to keep offering our push notification service for free, but large
* users overload our infrastructure. For this reason we have to rate-limit the
* use of push notifications. If you need this feature, consider using Nextcloud Enterprise.
*/
$this->initialState->provideInitialState(
'throttled_push_notifications',
!$this->notificationManager->isFairUseOfFreePushService()
);

Util::addScript('notifications', 'notifications-main');
}
}
60 changes: 41 additions & 19 deletions src/Components/Notification.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<template>
<li class="notification" :data-id="notificationId" :data-timestamp="timestamp">
<div class="notification-heading">
<span v-tooltip.bottom="absoluteDate"
<span v-if="timestamp"
v-tooltip.bottom="absoluteDate"
class="notification-time live-relative-timestamp"
:data-timestamp="timestamp">{{ relativeDate }}</span>
<NcButton class="notification-dismiss-button"
<NcButton v-if="timestamp"
class="notification-dismiss-button"
type="tertiary"
:aria-label="t('notifications', 'Dismiss')"
@click="onDismissNotification">
Expand All @@ -14,7 +16,15 @@
</NcButton>
</div>

<a v-if="useLink" :href="link" class="notification-subject full-subject-link">
<a v-if="externalLink"
:href="externalLink"
class="notification-subject full-subject-link external"
target="_blank"
rel="noreferrer noopener">
<span class="image"><img :src="icon" class="notification-icon" alt=""></span>
<span class="subject">{{ subject }} ↗</span>
</a>
<a v-else-if="useLink" :href="link" class="notification-subject full-subject-link">
<span v-if="icon" class="image"><img :src="icon" class="notification-icon" alt=""></span>
<RichText v-if="subjectRich"
:text="subjectRich"
Expand Down Expand Up @@ -43,6 +53,18 @@
<div v-if="actions.length" class="notification-actions">
<Action v-for="(a, i) in actions" :key="i" v-bind="a" />
</div>
<div v-else-if="externalLink" class="notification-actions">
<NcButton type="primary"
href="https://nextcloud.com/pushnotifications"
class="action-button pull-right"
target="_blank"
rel="noreferrer noopener">
<template #icon>
<Message :size="20" />
</template>
{{ t('notifications', 'Contact Nextcloud GmbH') }} ↗
</NcButton>
</div>
</li>
</template>

Expand All @@ -51,6 +73,7 @@ import axios from '@nextcloud/axios'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js'
import Close from 'vue-material-design-icons/Close.vue'
import Message from 'vue-material-design-icons/Message.vue'
import { showError } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import { Howl } from 'howler'
Expand All @@ -69,6 +92,7 @@ export default {
Action,
NcButton,
Close,
Message,
RichText,
},

Expand All @@ -80,89 +104,77 @@ export default {
notificationId: {
type: Number,
default: -1,
required: true,
},
datetime: {
type: String,
default: '',
required: true,
},
app: {
type: String,
default: '',
required: true,
},
icon: {
type: String,
default: '',
required: true,
},
link: {
type: String,
default: '',
required: true,
},
externalLink: {
type: String,
default: '',
},
user: {
type: String,
default: '',
required: true,
},
message: {
type: String,
default: '',
required: true,
},
messageRich: {
type: String,
default: '',
required: true,
},
messageRichParameters: {
type: [Object, Array],
default() {
return {}
},
required: true,
},
subject: {
type: String,
default: '',
required: true,
},
subjectRich: {
type: String,
default: '',
required: true,
},
subjectRichParameters: {
type: [Object, Array],
default() {
return {}
},
required: true,
},
objectType: {
type: String,
default: '',
required: true,
},
objectId: {
type: String,
default: '',
required: true,
},
actions: {
type: Array,
default() {
return []
},
required: true,
},

index: {
type: Number,
default: -1,
required: true,
},
},

Expand All @@ -176,12 +188,22 @@ export default {

computed: {
timestamp() {
if (this.datetime === 'warning') {
return 0
}
return (new Date(this.datetime)).valueOf()
},
absoluteDate() {
if (this.datetime === 'warning') {
return ''
}
return moment(this.timestamp).format('LLL')
},
relativeDate() {
if (this.datetime === 'warning') {
return ''
}

const diff = moment().diff(moment(this.timestamp))
if (diff >= 0 && diff < 45000) {
return t('core', 'seconds ago')
Expand Down
92 changes: 84 additions & 8 deletions src/NotificationsApp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
:aria-label="t('notifications', 'Notifications')"
@open="onOpen">
<template #trigger>
<Bell v-if="notifications.length === 0"
<Bell v-if="notifications.length === 0 && webNotificationsGranted !== null && !hasThrottledPushNotifications"
:size="20"
:title="t('notifications', 'Notifications')"
fill-color="var(--color-primary-text)" />
Expand All @@ -22,6 +22,10 @@
fill="var(--color-primary-text)">
<path d="M 19,11.79 C 18.5,11.92 18,12 17.5,12 14.47,12 12,9.53 12,6.5 12,5.03 12.58,3.7 13.5,2.71 13.15,2.28 12.61,2 12,2 10.9,2 10,2.9 10,4 V 4.29 C 7.03,5.17 5,7.9 5,11 v 6 l -2,2 v 1 H 21 V 19 L 19,17 V 11.79 M 12,23 c 1.11,0 2,-0.89 2,-2 h -4 c 0,1.11 0.9,2 2,2 z" />
<path :class="isRedThemed ? 'notification__dot--white' : ''" class="notification__dot" d="M 21,6.5 C 21,8.43 19.43,10 17.5,10 15.57,10 14,8.43 14,6.5 14,4.57 15.57,3 17.5,3 19.43,3 21,4.57 21,6.5" />
<path v-if="hasThrottledPushNotifications"
:class="isOrangeThemed ? 'notification__dot--white' : ''"
class="notification__dot notification__dot--warning"
d="M 21,6.5 C 21,8.43 19.43,10 17.5,10 15.57,10 14,8.43 14,6.5 14,4.57 15.57,3 17.5,3 19.43,3 21,4.57 21,6.5" />
</svg>
</template>

Expand All @@ -32,6 +36,15 @@
<transition-group class="notification-wrapper"
name="list"
tag="ul">
<Notification v-if="hasThrottledPushNotifications"
:key="-2016"
datetime="warning"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

datetime=warning ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, its a hack to identify this notification inside the component...

app="core"
:icon="warningIcon"
external-link="https://nextcloud.com/pushnotifications"
:message="emptyContentDescription"
:subject="emptyContentMessage"
:index="2016" />
<Notification v-for="(n, index) in notifications"
:key="n.notificationId"
v-bind="n"
Expand All @@ -55,11 +68,23 @@

<!-- No notifications -->
<NcEmptyContent v-else
:title="webNotificationsGranted === null
? t('notifications', 'Requesting browser permissions to show notifications')
: t('notifications', 'No notifications')">
:title="emptyContentMessage"
:description="emptyContentDescription">
<template #icon>
<Bell />
<Bell v-if="!hasThrottledPushNotifications" />
<span v-else class="icon icon-alert-outline" />
</template>

<template #action>
<NcButton type="primary"
href="https://nextcloud.com/pushnotifications"
target="_blank"
rel="noreferrer noopener">
<template #icon>
<Message :size="20" />
</template>
{{ t('notifications', 'Contact Nextcloud GmbH') }} ↗
</NcButton>
</template>
</NcEmptyContent>
</transition>
Expand All @@ -74,10 +99,15 @@ import Close from 'vue-material-design-icons/Close.vue'
import axios from '@nextcloud/axios'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import { showError } from '@nextcloud/dialogs'
import { generateOcsUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import {
generateOcsUrl,
imagePath,
} from '@nextcloud/router'
import { getNotificationsData } from './services/notificationsService.js'
import { listen } from '@nextcloud/notify_push'
import Bell from 'vue-material-design-icons/Bell.vue'
import Message from 'vue-material-design-icons/Message.vue'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import { getCapabilities } from '@nextcloud/capabilities'
import HeaderMenu from './Components/HeaderMenu.vue'
Expand All @@ -89,6 +119,7 @@ export default {
NcButton,
Close,
Bell,
Message,
NcEmptyContent,
HeaderMenu,
Notification,
Expand All @@ -101,6 +132,7 @@ export default {
hasNotifyPush: false,
shutdown: false,
theming: getCapabilities()?.theming || {},
hasThrottledPushNotifications: loadState('notifications', 'throttled_push_notifications'),
notifications: [],
lastETag: null,
lastTabId: null,
Expand Down Expand Up @@ -133,13 +165,47 @@ export default {
}
return false
},
isOrangeThemed() {
nickvergessen marked this conversation as resolved.
Show resolved Hide resolved
if (this.theming?.color) {
const hsl = this.rgbToHsl(this.theming.color.substring(1, 3),
this.theming.color.substring(3, 5),
this.theming.color.substring(5, 7))
const h = hsl[0] * 360
return (h >= 305 || h <= 64) && hsl[1] > 0.7 && (hsl[2] > 0.1 || hsl[2] < 0.6)
}
return false
},

showBrowserNotifications() {
return this.backgroundFetching
&& this.webNotificationsGranted
&& this.userStatus !== 'dnd'
&& this.tabId === this.lastTabId
},

emptyContentMessage() {
if (this.webNotificationsGranted === null) {
return t('notifications', 'Requesting browser permissions to show notifications')
}

if (this.hasThrottledPushNotifications) {
return t('notifications', 'Push notifications might be unreliable')
}

return t('notifications', 'No notifications')
},

emptyContentDescription() {
if (this.hasThrottledPushNotifications) {
return t('notifications', 'Nextcloud GmbH sponsors a free push notification gateway for private users. To ensure good service, the gateway limits the number of push notifications per server. For enterprise users, a more scalable gateway is available. Contact Nextcloud GmbH for more information.')
}

return ''
},

warningIcon() {
return imagePath('core', 'actions/alert-outline.svg')
},
},

mounted() {
Expand Down Expand Up @@ -418,8 +484,18 @@ export default {
overflow: hidden;
}

.empty-content {
margin: 12vh 0;
::v-deep .empty-content {
margin: 12vh 10px;

p {
color: var(--color-text-maxcontrast);
}
}

.icon-alert-outline {
background-size: 64px;
width: 64px;
height: 64px;
}

.fade-enter-active,
Expand Down
Loading