Feature: Add web push notification permission in frontend (#766)
Add webpush notification permission in frontend Co-authored-by: Sojan <sojan@pepalo.com>
This commit is contained in:
9
app/javascript/dashboard/api/notificationSubscription.js
Normal file
9
app/javascript/dashboard/api/notificationSubscription.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class NotificationSubscriptions extends ApiClient {
|
||||
constructor() {
|
||||
super('notification_subscriptions');
|
||||
}
|
||||
}
|
||||
|
||||
export default new NotificationSubscriptions();
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<button
|
||||
type="submit"
|
||||
:type="type"
|
||||
:disabled="disabled"
|
||||
:class="computedClass"
|
||||
@click="onClick"
|
||||
@@ -39,6 +39,10 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'submit',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
computedClass() {
|
||||
|
||||
93
app/javascript/dashboard/helper/pushHelper.js
Normal file
93
app/javascript/dashboard/helper/pushHelper.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/* eslint-disable no-console */
|
||||
import NotificationSubscriptions from '../api/notificationSubscription';
|
||||
import auth from '../api/auth';
|
||||
|
||||
export const verifyServiceWorkerExistence = (callback = () => {}) => {
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
// Service Worker isn't supported on this browser, disable or hide UI.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!('PushManager' in window)) {
|
||||
// Push isn't supported on this browser, disable or hide UI.
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.serviceWorker
|
||||
.register('/sw.js')
|
||||
.then(registration => callback(registration))
|
||||
.catch(registrationError => {
|
||||
// eslint-disable-next-line
|
||||
console.log('SW registration failed: ', registrationError);
|
||||
});
|
||||
};
|
||||
|
||||
export const hasPushPermissions = () => {
|
||||
if ('Notification' in window) {
|
||||
return Notification.permission === 'granted';
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const generateKeys = str =>
|
||||
btoa(String.fromCharCode.apply(null, new Uint8Array(str)))
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_');
|
||||
|
||||
export const getPushSubscriptionPayload = subscription => ({
|
||||
subscription_type: 'browser_push',
|
||||
subscription_attributes: {
|
||||
endpoint: subscription.endpoint,
|
||||
p256dh: generateKeys(subscription.getKey('p256dh')),
|
||||
auth: generateKeys(subscription.getKey('auth')),
|
||||
},
|
||||
});
|
||||
|
||||
export const sendRegistrationToServer = subscription => {
|
||||
if (auth.isLoggedIn()) {
|
||||
return NotificationSubscriptions.create(
|
||||
getPushSubscriptionPayload(subscription)
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const registerSubscription = (onSuccess = () => {}) => {
|
||||
if (!window.chatwootConfig.vapidPublicKey) {
|
||||
return;
|
||||
}
|
||||
navigator.serviceWorker.ready
|
||||
.then(serviceWorkerRegistration =>
|
||||
serviceWorkerRegistration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: window.chatwootConfig.vapidPublicKey,
|
||||
})
|
||||
)
|
||||
.then(sendRegistrationToServer)
|
||||
.then(() => {
|
||||
onSuccess();
|
||||
})
|
||||
.catch(() => {
|
||||
window.bus.$emit(
|
||||
'newToastMessage',
|
||||
'This browser does not support desktop notification'
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const requestPushPermissions = ({ onSuccess }) => {
|
||||
if (!('Notification' in window)) {
|
||||
window.bus.$emit(
|
||||
'newToastMessage',
|
||||
'This browser does not support desktop notification'
|
||||
);
|
||||
} else if (Notification.permission === 'granted') {
|
||||
registerSubscription(onSuccess);
|
||||
} else if (Notification.permission !== 'denied') {
|
||||
Notification.requestPermission(permission => {
|
||||
if (permission === 'granted') {
|
||||
registerSubscription(onSuccess);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -10,11 +10,11 @@
|
||||
"REMOVE_IMAGE": "Remove",
|
||||
"UPLOAD_IMAGE": "Upload image",
|
||||
"UPDATE_IMAGE": "Update image",
|
||||
"PROFILE_SECTION" : {
|
||||
"PROFILE_SECTION": {
|
||||
"TITLE": "Profile",
|
||||
"NOTE": "Your email address is your identity and is used to log in."
|
||||
},
|
||||
"PASSWORD_SECTION" : {
|
||||
"PASSWORD_SECTION": {
|
||||
"TITLE": "Password",
|
||||
"NOTE": "Updating your password would reset your logins in multiple devices."
|
||||
},
|
||||
@@ -22,15 +22,25 @@
|
||||
"TITLE": "Access Token",
|
||||
"NOTE": "This token can be used if you are building an API based integration"
|
||||
},
|
||||
"EMAIL_NOTIFICATIONS_SECTION" : {
|
||||
"EMAIL_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Email Notifications",
|
||||
"NOTE": "Update your email notification preferences here",
|
||||
"CONVERSATION_ASSIGNMENT": "Send email notifications when a conversation is assigned to me",
|
||||
"CONVERSATION_CREATION": "Send email notifications when a new conversation is created",
|
||||
"UPDATE_SUCCESS": "Your email notification preferences are updated successfully",
|
||||
"CONVERSATION_CREATION": "Send email notifications when a new conversation is created"
|
||||
},
|
||||
"API": {
|
||||
"UPDATE_SUCCESS": "Your notification preferences are updated successfully",
|
||||
"UPDATE_ERROR": "There is an error while updating the preferences, please try again"
|
||||
},
|
||||
"PROFILE_IMAGE":{
|
||||
"PUSH_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Push Notifications",
|
||||
"NOTE": "Update your push notification preferences here",
|
||||
"CONVERSATION_ASSIGNMENT": "Send push notifications when a conversation is assigned to me",
|
||||
"CONVERSATION_CREATION": "Send push notifications when a new conversation is created",
|
||||
"HAS_ENABLED_PUSH": "You have enabled push for this browser.",
|
||||
"REQUEST_PUSH": "Enable push notifications"
|
||||
},
|
||||
"PROFILE_IMAGE": {
|
||||
"LABEL": "Profile Image"
|
||||
},
|
||||
"NAME": {
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
<template>
|
||||
<div class="profile--settings--row row">
|
||||
<div class="columns small-3 ">
|
||||
<h4 class="block-title">
|
||||
{{ $t('PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.TITLE') }}
|
||||
</h4>
|
||||
<p>{{ $t('PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.NOTE') }}</p>
|
||||
</div>
|
||||
<div class="columns small-9">
|
||||
<div>
|
||||
<input
|
||||
v-model="selectedNotifications"
|
||||
class="email-notification--checkbox"
|
||||
type="checkbox"
|
||||
value="email_conversation_creation"
|
||||
@input="handleInput"
|
||||
/>
|
||||
<label for="conversation_creation">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.CONVERSATION_CREATION'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
v-model="selectedNotifications"
|
||||
class="email-notification--checkbox"
|
||||
type="checkbox"
|
||||
value="email_conversation_assignment"
|
||||
@input="handleInput"
|
||||
/>
|
||||
<label for="conversation_assignment">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.CONVERSATION_ASSIGNMENT'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global bus */
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectedNotifications: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedEmailFlags: 'userNotificationSettings/getSelectedEmailFlags',
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
selectedEmailFlags(value) {
|
||||
this.selectedNotifications = value;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('userNotificationSettings/get');
|
||||
},
|
||||
methods: {
|
||||
async handleInput(e) {
|
||||
const selectedValue = e.target.value;
|
||||
if (this.selectedEmailFlags.includes(e.target.value)) {
|
||||
const selectedEmailFlags = this.selectedEmailFlags.filter(
|
||||
flag => flag !== selectedValue
|
||||
);
|
||||
this.selectedNotifications = selectedEmailFlags;
|
||||
} else {
|
||||
this.selectedNotifications = [
|
||||
...this.selectedEmailFlags,
|
||||
selectedValue,
|
||||
];
|
||||
}
|
||||
try {
|
||||
this.$store.dispatch(
|
||||
'userNotificationSettings/update',
|
||||
this.selectedNotifications
|
||||
);
|
||||
bus.$emit(
|
||||
'newToastMessage',
|
||||
this.$t(
|
||||
'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.UPDATE_SUCCESS'
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
bus.$emit(
|
||||
'newToastMessage',
|
||||
this.$t(
|
||||
'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.UPDATE_ERROR'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~dashboard/assets/scss/variables.scss';
|
||||
|
||||
.email-notification--checkbox {
|
||||
font-size: $font-size-large;
|
||||
}
|
||||
</style>
|
||||
@@ -82,7 +82,7 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<email-notifications />
|
||||
<notification-settings />
|
||||
<div class="profile--settings--row row">
|
||||
<div class="columns small-3 ">
|
||||
<h4 class="block-title">
|
||||
@@ -111,11 +111,11 @@ import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import { required, minLength, email } from 'vuelidate/lib/validators';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { clearCookiesOnLogout } from '../../../../store/utils/api';
|
||||
import EmailNotifications from './EmailNotifications';
|
||||
import NotificationSettings from './NotificationSettings';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EmailNotifications,
|
||||
NotificationSettings,
|
||||
Thumbnail,
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="profile--settings--row row">
|
||||
<div class="columns small-3 ">
|
||||
<h4 class="block-title">
|
||||
{{ $t('PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.TITLE') }}
|
||||
</h4>
|
||||
<p>
|
||||
{{ $t('PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.NOTE') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="columns small-9">
|
||||
<div>
|
||||
<input
|
||||
v-model="selectedEmailFlags"
|
||||
class="notification--checkbox"
|
||||
type="checkbox"
|
||||
value="email_conversation_creation"
|
||||
@input="handleEmailInput"
|
||||
/>
|
||||
<label for="conversation_creation">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.CONVERSATION_CREATION'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
v-model="selectedEmailFlags"
|
||||
class="notification--checkbox"
|
||||
type="checkbox"
|
||||
value="email_conversation_assignment"
|
||||
@input="handleEmailInput"
|
||||
/>
|
||||
<label for="conversation_assignment">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.CONVERSATION_ASSIGNMENT'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="vapidPublicKey" class="profile--settings--row row push-row">
|
||||
<div class="columns small-3 ">
|
||||
<h4 class="block-title">
|
||||
{{ $t('PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.TITLE') }}
|
||||
</h4>
|
||||
<p>{{ $t('PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.NOTE') }}</p>
|
||||
</div>
|
||||
<div class="columns small-9">
|
||||
<p v-if="hasEnabledPushPermissions">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.HAS_ENABLED_PUSH'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<div v-else>
|
||||
<woot-submit-button
|
||||
:button-text="
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.REQUEST_PUSH'
|
||||
)
|
||||
"
|
||||
class="button nice small"
|
||||
type="button"
|
||||
@click="onRequestPermissions"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
v-model="selectedPushFlags"
|
||||
class="notification--checkbox"
|
||||
type="checkbox"
|
||||
value="push_conversation_creation"
|
||||
@input="handlePushInput"
|
||||
/>
|
||||
<label for="conversation_creation">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.CONVERSATION_CREATION'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
v-model="selectedPushFlags"
|
||||
class="notification--checkbox"
|
||||
type="checkbox"
|
||||
value="push_conversation_assignment"
|
||||
@input="handlePushInput"
|
||||
/>
|
||||
<label for="conversation_assignment">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.CONVERSATION_ASSIGNMENT'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import configMixin from 'shared/mixins/configMixin';
|
||||
import {
|
||||
hasPushPermissions,
|
||||
requestPushPermissions,
|
||||
verifyServiceWorkerExistence,
|
||||
} from '../../../../helper/pushHelper';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin, configMixin],
|
||||
data() {
|
||||
return {
|
||||
selectedEmailFlags: [],
|
||||
selectedPushFlags: [],
|
||||
hasEnabledPushPermissions: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
emailFlags: 'userNotificationSettings/getSelectedEmailFlags',
|
||||
pushFlags: 'userNotificationSettings/getSelectedPushFlags',
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
emailFlags(value) {
|
||||
this.selectedEmailFlags = value;
|
||||
},
|
||||
pushFlags(value) {
|
||||
this.selectedPushFlags = value;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (hasPushPermissions()) {
|
||||
this.getPushSubscription();
|
||||
}
|
||||
|
||||
this.$store.dispatch('userNotificationSettings/get');
|
||||
},
|
||||
methods: {
|
||||
onRegistrationSuccess() {
|
||||
this.hasEnabledPushPermissions = true;
|
||||
},
|
||||
onRequestPermissions() {
|
||||
requestPushPermissions({
|
||||
onSuccess: this.onRegistrationSuccess,
|
||||
});
|
||||
},
|
||||
getPushSubscription() {
|
||||
verifyServiceWorkerExistence(registration =>
|
||||
registration.pushManager
|
||||
.getSubscription()
|
||||
.then(subscription => {
|
||||
console.log(subscription);
|
||||
if (!subscription) {
|
||||
this.hasEnabledPushPermissions = false;
|
||||
} else {
|
||||
this.hasEnabledPushPermissions = true;
|
||||
}
|
||||
})
|
||||
.catch(error => console.log(error))
|
||||
);
|
||||
},
|
||||
async updateNotificationSettings() {
|
||||
try {
|
||||
this.$store.dispatch('userNotificationSettings/update', {
|
||||
selectedEmailFlags: this.selectedEmailFlags,
|
||||
selectedPushFlags: this.selectedPushFlags,
|
||||
});
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.API.UPDATE_SUCCESS'));
|
||||
} catch (error) {
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.API.UPDATE_ERROR'));
|
||||
}
|
||||
},
|
||||
handleEmailInput(e) {
|
||||
this.selectedEmailFlags = this.toggleInput(
|
||||
this.selectedEmailFlags,
|
||||
e.target.value
|
||||
);
|
||||
|
||||
this.updateNotificationSettings();
|
||||
},
|
||||
handlePushInput(e) {
|
||||
this.selectedPushFlags = this.toggleInput(
|
||||
this.selectedPushFlags,
|
||||
e.target.value
|
||||
);
|
||||
|
||||
this.updateNotificationSettings();
|
||||
},
|
||||
toggleInput(selected, current) {
|
||||
if (selected.includes(current)) {
|
||||
const newSelectedFlags = selected.filter(flag => flag !== current);
|
||||
return newSelectedFlags;
|
||||
}
|
||||
return [...selected, current];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~dashboard/assets/scss/variables.scss';
|
||||
|
||||
.notification--checkbox {
|
||||
font-size: $font-size-large;
|
||||
}
|
||||
|
||||
// Hide on Safari
|
||||
.push-row:not(:root:root) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -17,6 +17,9 @@ export const getters = {
|
||||
getSelectedEmailFlags: $state => {
|
||||
return $state.record.selected_email_flags;
|
||||
},
|
||||
getSelectedPushFlags: $state => {
|
||||
return $state.record.selected_push_flags;
|
||||
},
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
@@ -35,12 +38,13 @@ export const actions = {
|
||||
}
|
||||
},
|
||||
|
||||
update: async ({ commit }, params) => {
|
||||
update: async ({ commit }, { selectedEmailFlags, selectedPushFlags }) => {
|
||||
commit(types.default.SET_USER_NOTIFICATION_UI_FLAG, { isUpdating: true });
|
||||
try {
|
||||
const response = await UserNotificationSettings.update({
|
||||
notification_settings: {
|
||||
selected_email_flags: params,
|
||||
selected_email_flags: selectedEmailFlags,
|
||||
selected_push_flags: selectedPushFlags,
|
||||
},
|
||||
});
|
||||
commit(types.default.SET_USER_NOTIFICATION, response.data);
|
||||
|
||||
@@ -26,6 +26,10 @@ import router from '../dashboard/routes';
|
||||
import store from '../dashboard/store';
|
||||
import vueActionCable from '../dashboard/helper/actionCable';
|
||||
import constants from '../dashboard/constants';
|
||||
import {
|
||||
verifyServiceWorkerExistence,
|
||||
registerSubscription,
|
||||
} from '../dashboard/helper/pushHelper';
|
||||
|
||||
Vue.config.env = process.env;
|
||||
|
||||
@@ -66,15 +70,12 @@ window.onload = () => {
|
||||
vueActionCable.init();
|
||||
};
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker
|
||||
.register('/sw.js')
|
||||
.then(registration => {
|
||||
console.log('SW registered: ', registration);
|
||||
})
|
||||
.catch(registrationError => {
|
||||
console.log('SW registration failed: ', registrationError);
|
||||
});
|
||||
});
|
||||
}
|
||||
window.addEventListener('load', () => {
|
||||
verifyServiceWorkerExistence(registration =>
|
||||
registration.pushManager.getSubscription().then(subscription => {
|
||||
if (subscription) {
|
||||
registerSubscription();
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -6,5 +6,8 @@ export default {
|
||||
twilioCallbackURL() {
|
||||
return `${this.hostURL}/twilio/callback`;
|
||||
},
|
||||
vapidPublicKey() {
|
||||
return window.chatwootConfig.vapidPublicKey;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user