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:
parent
5bd7a4c511
commit
e9131ea558
37 changed files with 651 additions and 318 deletions
|
@ -71,14 +71,14 @@ SENTRY_DSN=
|
|||
# Disable if you want to write logs to a file
|
||||
RAILS_LOG_TO_STDOUT=true
|
||||
LOG_LEVEL=info
|
||||
LOG_SIZE=500
|
||||
LOG_SIZE=500
|
||||
|
||||
# Credentials to access sidekiq dashboard in production
|
||||
SIDEKIQ_AUTH_USERNAME=
|
||||
SIDEKIQ_AUTH_PASSWORD=
|
||||
|
||||
### This environment variables are only required if you are setting up social media channels
|
||||
#facebook
|
||||
#facebook
|
||||
FB_VERIFY_TOKEN=
|
||||
FB_APP_SECRET=
|
||||
FB_APP_ID=
|
||||
|
@ -101,3 +101,8 @@ CHARGEBEE_API_KEY=
|
|||
CHARGEBEE_SITE=
|
||||
CHARGEBEE_WEBHOOK_USERNAME=
|
||||
CHARGEBEE_WEBHOOK_PASSWORD=
|
||||
|
||||
## Push Notification
|
||||
## generate a new key value here : https://d3v.one/vapid-key-generator/
|
||||
# VAPID_PUBLIC_KEY=
|
||||
# VAPID_PRIVATE_KEY=
|
||||
|
|
|
@ -13,7 +13,7 @@ Layout/LineLength:
|
|||
Metrics/ClassLength:
|
||||
Max: 125
|
||||
RSpec/ExampleLength:
|
||||
Max: 15
|
||||
Max: 25
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
Style/FrozenStringLiteralComment:
|
||||
|
|
3
Gemfile
3
Gemfile
|
@ -80,6 +80,9 @@ gem 'sidekiq'
|
|||
##-- used for single column multiple binary flags in notification settings/feature flagging --##
|
||||
gem 'flag_shih_tzu'
|
||||
|
||||
##-- Push notification service --##
|
||||
gem 'webpush'
|
||||
|
||||
group :development do
|
||||
gem 'annotate'
|
||||
gem 'bullet'
|
||||
|
|
|
@ -219,6 +219,7 @@ GEM
|
|||
haikunator (1.1.0)
|
||||
hana (1.3.5)
|
||||
hashie (4.1.0)
|
||||
hkdf (0.3.0)
|
||||
http-accept (1.7.0)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
|
@ -489,6 +490,9 @@ GEM
|
|||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
webpush (1.0.0)
|
||||
hkdf (~> 0.2)
|
||||
jwt (~> 2.0)
|
||||
websocket-driver (0.7.1)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.4)
|
||||
|
@ -532,7 +536,7 @@ DEPENDENCIES
|
|||
letter_opener
|
||||
listen
|
||||
mini_magick
|
||||
mock_redis
|
||||
mock_redis!
|
||||
pg
|
||||
pry-rails
|
||||
puma
|
||||
|
@ -568,6 +572,7 @@ DEPENDENCIES
|
|||
valid_email2
|
||||
web-console
|
||||
webpacker
|
||||
webpush
|
||||
wisper (= 2.0.0)
|
||||
|
||||
RUBY VERSION
|
||||
|
|
28
app/builders/notification_subscription_builder.rb
Normal file
28
app/builders/notification_subscription_builder.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
class NotificationSubscriptionBuilder
|
||||
pattr_initialize [:params, :user!]
|
||||
|
||||
def perform
|
||||
# if multiple accounts were used to login in same browser
|
||||
move_subscription_to_user if identifier_subscription && identifier_subscription.user_id != user.id
|
||||
build_identifier_subscription if identifier_subscription.blank?
|
||||
identifier_subscription
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def identifier
|
||||
@identifier ||= params[:subscription_attributes][:endpoint] if params[:subscription_type] == 'browser_push'
|
||||
end
|
||||
|
||||
def identifier_subscription
|
||||
@identifier_subscription ||= NotificationSubscription.find_by(identifier: identifier)
|
||||
end
|
||||
|
||||
def move_subscription_to_user
|
||||
@identifier_subscription.update(user_id: user.id)
|
||||
end
|
||||
|
||||
def build_identifier_subscription
|
||||
user.notification_subscriptions.create(params.merge(identifier: identifier))
|
||||
end
|
||||
end
|
|
@ -20,10 +20,11 @@ class Api::V1::Accounts::NotificationSettingsController < Api::BaseController
|
|||
end
|
||||
|
||||
def notification_setting_params
|
||||
params.require(:notification_settings).permit(selected_email_flags: [])
|
||||
params.require(:notification_settings).permit(selected_email_flags: [], selected_push_flags: [])
|
||||
end
|
||||
|
||||
def update_flags
|
||||
@notification_setting.selected_email_flags = notification_setting_params[:selected_email_flags]
|
||||
@notification_setting.selected_push_flags = notification_setting_params[:selected_push_flags]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,8 @@ class Api::V1::NotificationSubscriptionsController < Api::BaseController
|
|||
before_action :set_user
|
||||
|
||||
def create
|
||||
notification_subscription = @user.notification_subscriptions.create(notification_subscription_params)
|
||||
notification_subscription = NotificationSubscriptionBuilder.new(user: @user, params: notification_subscription_params).perform
|
||||
|
||||
render json: notification_subscription
|
||||
end
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
|||
before_action :set_contact
|
||||
|
||||
def toggle_typing
|
||||
head :ok if conversation.nil?
|
||||
head :ok && return if conversation.nil?
|
||||
|
||||
if permitted_params[:typing_status] == 'on'
|
||||
trigger_typing_event(CONVERSATION_TYPING_ON)
|
||||
|
|
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;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
7
app/jobs/notification/email_notification_job.rb
Normal file
7
app/jobs/notification/email_notification_job.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class Notification::EmailNotificationJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(notification)
|
||||
Notification::EmailNotificationService.new(notification: notification).perform
|
||||
end
|
||||
end
|
7
app/jobs/notification/push_notification_job.rb
Normal file
7
app/jobs/notification/push_notification_job.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class Notification::PushNotificationJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(notification)
|
||||
Notification::PushNotificationService.new(notification: notification).perform
|
||||
end
|
||||
end
|
|
@ -19,6 +19,7 @@ class AgentBot < ApplicationRecord
|
|||
|
||||
def push_event_data
|
||||
{
|
||||
id: id,
|
||||
name: name,
|
||||
avatar_url: avatar_url,
|
||||
type: 'agent_bot'
|
||||
|
|
|
@ -41,7 +41,9 @@ class Notification < ApplicationRecord
|
|||
private
|
||||
|
||||
def process_notification_delivery
|
||||
Notification::EmailNotificationService.new(notification: self).perform
|
||||
# Notification::PushNotificationService.new(notification: self).perform
|
||||
Notification::PushNotificationJob.perform_later(self)
|
||||
|
||||
# Queuing after 2 minutes so that we won't send emails for read notifications
|
||||
Notification::EmailNotificationJob.set(wait: 2.minutes).perform_later(self)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# Table name: notification_subscriptions
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# identifier :string
|
||||
# subscription_attributes :jsonb not null
|
||||
# subscription_type :integer not null
|
||||
# created_at :datetime not null
|
||||
|
@ -11,11 +12,13 @@
|
|||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_notification_subscriptions_on_user_id (user_id)
|
||||
# index_notification_subscriptions_on_identifier (identifier) UNIQUE
|
||||
# index_notification_subscriptions_on_user_id (user_id)
|
||||
#
|
||||
|
||||
class NotificationSubscription < ApplicationRecord
|
||||
belongs_to :user
|
||||
validates :identifier, presence: true
|
||||
|
||||
SUBSCRIPTION_TYPES = {
|
||||
browser_push: 1,
|
||||
|
|
|
@ -128,6 +128,7 @@ class User < ApplicationRecord
|
|||
|
||||
def push_event_data
|
||||
{
|
||||
id: id,
|
||||
name: name,
|
||||
avatar_url: avatar_url,
|
||||
type: 'user'
|
||||
|
|
|
@ -2,11 +2,14 @@ class Notification::EmailNotificationService
|
|||
pattr_initialize [:notification!]
|
||||
|
||||
def perform
|
||||
# don't send emails if user read the push notification already
|
||||
return if notification.read_at.present?
|
||||
return unless user_subscribed_to_notification?
|
||||
|
||||
# TODO : Clean up whatever happening over here
|
||||
# Segregate the mailers properly
|
||||
AgentNotifications::ConversationNotificationsMailer.public_send(notification
|
||||
.notification_type.to_s, notification.primary_actor, notification.user).deliver_later
|
||||
.notification_type.to_s, notification.primary_actor, notification.user).deliver_now
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,17 +1,73 @@
|
|||
class Notification::PushNotificationService
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
pattr_initialize [:notification!]
|
||||
|
||||
def perform
|
||||
return unless user_subscribed_to_notification?
|
||||
# TODO: implement the push delivery logic here
|
||||
|
||||
notification_subscriptions.each do |subscription|
|
||||
send_browser_push(subscription) if subscription.browser_push?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
delegate :user, to: :notification
|
||||
delegate :notification_subscriptions, to: :user
|
||||
delegate :notification_settings, to: :user
|
||||
|
||||
def user_subscribed_to_notification?
|
||||
notification_setting = notification.user.notification_settings.find_by(account_id: notification.account.id)
|
||||
notification_setting = notification_settings.find_by(account_id: notification.account.id)
|
||||
return true if notification_setting.public_send("push_#{notification.notification_type}?")
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= notification.primary_actor
|
||||
end
|
||||
|
||||
def push_message_title
|
||||
if notification.notification_type == 'conversation_creation'
|
||||
return "A new conversation [ID -#{conversation.display_id}] has been created in #{conversation.inbox.name}"
|
||||
end
|
||||
|
||||
if notification.notification_type == 'conversation_assignment'
|
||||
return "A new conversation [ID -#{conversation.display_id}] has been assigned to you."
|
||||
end
|
||||
|
||||
''
|
||||
end
|
||||
|
||||
def push_message
|
||||
{
|
||||
title: push_message_title,
|
||||
tag: "#{notification.notification_type}_#{conversation.display_id}",
|
||||
url: push_url
|
||||
}
|
||||
end
|
||||
|
||||
def push_url
|
||||
app_account_conversation_url(account_id: conversation.account_id, id: conversation.display_id)
|
||||
end
|
||||
|
||||
def send_browser_push(subscription)
|
||||
Webpush.payload_send(
|
||||
message: JSON.generate(push_message),
|
||||
endpoint: subscription.subscription_attributes['endpoint'],
|
||||
p256dh: subscription.subscription_attributes['p256dh'],
|
||||
auth: subscription.subscription_attributes['auth'],
|
||||
vapid: {
|
||||
subject: push_url,
|
||||
public_key: ENV['VAPID_PUBLIC_KEY'],
|
||||
private_key: ENV['VAPID_PRIVATE_KEY']
|
||||
},
|
||||
ssl_timeout: 5,
|
||||
open_timeout: 5,
|
||||
read_timeout: 5
|
||||
)
|
||||
rescue Webpush::ExpiredSubscription
|
||||
subscription.destroy!
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,3 +3,5 @@ json.user_id @notification_setting.user_id
|
|||
json.account_id @notification_setting.account_id
|
||||
json.all_email_flags @notification_setting.all_email_flags
|
||||
json.selected_email_flags @notification_setting.selected_email_flags
|
||||
json.all_push_flags @notification_setting.all_push_flags
|
||||
json.selected_push_flags @notification_setting.selected_push_flags
|
||||
|
|
|
@ -34,7 +34,10 @@
|
|||
hostURL: '<%= ENV.fetch('FRONTEND_URL', '') %>',
|
||||
fbAppId: '<%= ENV.fetch('FB_APP_ID', nil) %>',
|
||||
billingEnabled: <%= ActiveModel::Type::Boolean.new.cast(ENV.fetch('BILLING_ENABLED', false)) %>,
|
||||
signupEnabled: '<%= ENV.fetch('ENABLE_ACCOUNT_SIGNUP', true) %>'
|
||||
signupEnabled: '<%= ENV.fetch('ENABLE_ACCOUNT_SIGNUP', true) %>',
|
||||
<% if ENV['VAPID_PUBLIC_KEY'] %>
|
||||
vapidPublicKey: new Uint8Array(<%= Base64.urlsafe_decode64(ENV['VAPID_PUBLIC_KEY']).bytes %>),
|
||||
<% end %>
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class AddIndexOnNotificationSubscriptions < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :notification_subscriptions, :identifier, :string
|
||||
add_index :notification_subscriptions, :identifier, unique: true
|
||||
end
|
||||
end
|
18
db/schema.rb
18
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2020_05_03_151130) do
|
||||
ActiveRecord::Schema.define(version: 2020_05_04_144712) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pgcrypto"
|
||||
|
@ -284,6 +284,8 @@ ActiveRecord::Schema.define(version: 2020_05_03_151130) do
|
|||
t.jsonb "subscription_attributes", default: "{}", null: false
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.string "identifier"
|
||||
t.index ["identifier"], name: "index_notification_subscriptions_on_identifier", unique: true
|
||||
t.index ["user_id"], name: "index_notification_subscriptions_on_user_id"
|
||||
end
|
||||
|
||||
|
@ -316,6 +318,20 @@ ActiveRecord::Schema.define(version: 2020_05_03_151130) do
|
|||
t.boolean "payment_source_added", default: false
|
||||
end
|
||||
|
||||
create_table "super_admins", force: :cascade do |t|
|
||||
t.string "email", default: "", null: false
|
||||
t.string "encrypted_password", default: "", null: false
|
||||
t.datetime "remember_created_at"
|
||||
t.integer "sign_in_count", default: 0, null: false
|
||||
t.datetime "current_sign_in_at"
|
||||
t.datetime "last_sign_in_at"
|
||||
t.inet "current_sign_in_ip"
|
||||
t.inet "last_sign_in_ip"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.index ["email"], name: "index_super_admins_on_email", unique: true
|
||||
end
|
||||
|
||||
create_table "taggings", id: :serial, force: :cascade do |t|
|
||||
t.integer "tag_id"
|
||||
t.string "taggable_type"
|
||||
|
|
168
public/sw.js
168
public/sw.js
|
@ -1,137 +1,37 @@
|
|||
/* eslint-disable */
|
||||
/** *
|
||||
*
|
||||
* The rest of the code is auto-generated. Please don't update this file
|
||||
* directly; instead, make changes to your Workbox build configuration
|
||||
* and re-run your build process.
|
||||
* See https://goo.gl/2aRDsh
|
||||
*/
|
||||
/* eslint-disable no-restricted-globals, no-console */
|
||||
/* globals clients */
|
||||
self.addEventListener('push', event => {
|
||||
let notification = event.data && event.data.json();
|
||||
|
||||
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(notification.title, {
|
||||
tag: notification.tag,
|
||||
data: {
|
||||
url: notification.url,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
|
||||
* requests for URLs in the manifest.
|
||||
* See https://goo.gl/S9QRab
|
||||
*/
|
||||
self.__precacheManifest = [
|
||||
{
|
||||
"url": "android-icon-144x144.png",
|
||||
"revision": "d9e3ad004635d6d3154da20ef6e53077"
|
||||
},
|
||||
{
|
||||
"url": "android-icon-192x192.png",
|
||||
"revision": "8f2f76058ff81bb03e390ed941f68a70"
|
||||
},
|
||||
{
|
||||
"url": "android-icon-36x36.png",
|
||||
"revision": "70b2fa97615a1ccf8fa373674928d0e3"
|
||||
},
|
||||
{
|
||||
"url": "android-icon-48x48.png",
|
||||
"revision": "c0e8a16e2ea4430deddac82979f97c60"
|
||||
},
|
||||
{
|
||||
"url": "android-icon-72x72.png",
|
||||
"revision": "98f4881cce0daf4b89f0b30825b16d80"
|
||||
},
|
||||
{
|
||||
"url": "android-icon-96x96.png",
|
||||
"revision": "02cf787c7a88eb898976d79ad0b4e041"
|
||||
},
|
||||
{
|
||||
"url": "apple-icon-114x114.png",
|
||||
"revision": "544c150aa39d3ecfd6071e3c54d1503e"
|
||||
},
|
||||
{
|
||||
"url": "apple-icon-120x120.png",
|
||||
"revision": "3b10208d8f4b09c5c3631eb5e4e67d9a"
|
||||
},
|
||||
{
|
||||
"url": "apple-icon-144x144.png",
|
||||
"revision": "d9e3ad004635d6d3154da20ef6e53077"
|
||||
},
|
||||
{
|
||||
"url": "apple-icon-152x152.png",
|
||||
"revision": "a866770945a41e5bcf29706f37e5beba"
|
||||
},
|
||||
{
|
||||
"url": "apple-icon-180x180.png",
|
||||
"revision": "327e9272f10374d2859d2a26c86698ec"
|
||||
},
|
||||
{
|
||||
"url": "apple-icon-57x57.png",
|
||||
"revision": "ee6e09647e6a26e29655ed4091a6d577"
|
||||
},
|
||||
{
|
||||
"url": "apple-icon-60x60.png",
|
||||
"revision": "136acdd5567a57f0b30c4704c93ce412"
|
||||
},
|
||||
{
|
||||
"url": "apple-icon-72x72.png",
|
||||
"revision": "98f4881cce0daf4b89f0b30825b16d80"
|
||||
},
|
||||
{
|
||||
"url": "apple-icon-76x76.png",
|
||||
"revision": "5de2acd8f66a8fa583830286231abe88"
|
||||
},
|
||||
{
|
||||
"url": "apple-icon-precomposed.png",
|
||||
"revision": "03175edf677b78aae0c7ce1c90996bcc"
|
||||
},
|
||||
{
|
||||
"url": "apple-icon.png",
|
||||
"revision": "03175edf677b78aae0c7ce1c90996bcc"
|
||||
},
|
||||
{
|
||||
"url": "apple-touch-icon-precomposed.png",
|
||||
"revision": "d41d8cd98f00b204e9800998ecf8427e"
|
||||
},
|
||||
{
|
||||
"url": "apple-touch-icon.png",
|
||||
"revision": "d41d8cd98f00b204e9800998ecf8427e"
|
||||
},
|
||||
{
|
||||
"url": "favicon-16x16.png",
|
||||
"revision": "df49c81fbfd18e43ea9199153f1d5e1f"
|
||||
},
|
||||
{
|
||||
"url": "favicon-32x32.png",
|
||||
"revision": "e781cbd8ca95543e247fa913eef30f9c"
|
||||
},
|
||||
{
|
||||
"url": "favicon-512x512.png",
|
||||
"revision": "48e48806ef9cbe9edcbe81a08713dc7f"
|
||||
},
|
||||
{
|
||||
"url": "favicon-96x96.png",
|
||||
"revision": "02cf787c7a88eb898976d79ad0b4e041"
|
||||
},
|
||||
{
|
||||
"url": "favicon.ico",
|
||||
"revision": "788f4b1590d83444281e0c96792fd42b"
|
||||
},
|
||||
{
|
||||
"url": "ms-icon-144x144.png",
|
||||
"revision": "d9e3ad004635d6d3154da20ef6e53077"
|
||||
},
|
||||
{
|
||||
"url": "ms-icon-150x150.png",
|
||||
"revision": "0770f6909fd7676a02922cd34d23ff15"
|
||||
},
|
||||
{
|
||||
"url": "ms-icon-310x310.png",
|
||||
"revision": "492181f5f2a4c199936f7f03c70e4914"
|
||||
},
|
||||
{
|
||||
"url": "ms-icon-70x70.png",
|
||||
"revision": "c1b4c1be97c6768c0e5547c2b07bf2a2"
|
||||
}
|
||||
].concat(self.__precacheManifest || []);
|
||||
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
|
||||
self.addEventListener('notificationclick', event => {
|
||||
let notification = event.notification;
|
||||
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: 'window' }).then(windowClients => {
|
||||
let matchingWindowClients = windowClients.filter(
|
||||
client => client.url === notification.data.url
|
||||
);
|
||||
|
||||
if (matchingWindowClients.length) {
|
||||
let firstWindow = matchingWindowClients[0];
|
||||
if (firstWindow && 'focus' in firstWindow) {
|
||||
firstWindow.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (clients.openWindow) {
|
||||
clients.openWindow(notification.data.url);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
@ -46,7 +46,7 @@ describe ::ContactMergeAction do
|
|||
expect do
|
||||
described_class.new(account: new_account, base_contact: base_contact,
|
||||
mergee_contact: mergee_contact).perform
|
||||
end .to raise_error('contact does not belong to the account')
|
||||
end.to raise_error('contact does not belong to the account')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -70,7 +70,7 @@ RSpec.describe 'Contacts API', type: :request do
|
|||
expect do
|
||||
post "/api/v1/accounts/#{account.id}/contacts", headers: admin.create_new_auth_token,
|
||||
params: valid_params
|
||||
end .to change(Contact, :count).by(1)
|
||||
end.to change(Contact, :count).by(1)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
|
|
|
@ -17,14 +17,66 @@ RSpec.describe 'Notifications Subscriptions API', type: :request do
|
|||
|
||||
it 'creates a notification subscriptions' do
|
||||
post '/api/v1/notification_subscriptions',
|
||||
params: { notification_subscription: { subscription_type: 'browser_push', 'subscription_attributes': { test: 'test' } } },
|
||||
params: {
|
||||
notification_subscription: {
|
||||
subscription_type: 'browser_push',
|
||||
'subscription_attributes': {
|
||||
endpoint: 'test',
|
||||
p256dh: 'test',
|
||||
auth: 'test'
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['subscription_type']).to eq('browser_push')
|
||||
expect(json_response['subscription_attributes']).to eq({ 'test' => 'test' })
|
||||
expect(json_response['subscription_attributes']['auth']).to eq('test')
|
||||
end
|
||||
|
||||
it 'returns existing notification subscription if subscription exists' do
|
||||
subscription = create(:notification_subscription, user: agent)
|
||||
post '/api/v1/notification_subscriptions',
|
||||
params: {
|
||||
notification_subscription: {
|
||||
subscription_type: 'browser_push',
|
||||
'subscription_attributes': {
|
||||
endpoint: 'test',
|
||||
p256dh: 'test',
|
||||
auth: 'test'
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['id']).to eq(subscription.id)
|
||||
end
|
||||
|
||||
it 'move notification subscription to user if its of another user' do
|
||||
subscription = create(:notification_subscription, user: create(:user))
|
||||
post '/api/v1/notification_subscriptions',
|
||||
params: {
|
||||
notification_subscription: {
|
||||
subscription_type: 'browser_push',
|
||||
'subscription_attributes': {
|
||||
endpoint: 'test',
|
||||
p256dh: 'test',
|
||||
auth: 'test'
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['id']).to eq(subscription.id)
|
||||
expect(json_response['user_id']).to eq(agent.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
10
spec/factories/notification_subscriptions.rb
Normal file
10
spec/factories/notification_subscriptions.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :notification_subscription do
|
||||
user
|
||||
identifier { 'test' }
|
||||
subscription_type { 'browser_push' }
|
||||
subscription_attributes { { endpoint: 'test', auth: 'test' } }
|
||||
end
|
||||
end
|
|
@ -11,16 +11,11 @@ describe NotificationListener do
|
|||
describe 'conversation_created' do
|
||||
let(:event_name) { :'conversation.created' }
|
||||
|
||||
before do
|
||||
creation_mailer = double
|
||||
allow(AgentNotifications::ConversationNotificationsMailer).to receive(:conversation_creation).and_return(creation_mailer)
|
||||
allow(creation_mailer).to receive(:deliver_later).and_return(true)
|
||||
end
|
||||
|
||||
context 'when conversation is created' do
|
||||
it 'sends email to inbox members who have notifications turned on' do
|
||||
it 'creates notifications for inbox members who have notifications turned on' do
|
||||
notification_setting = agent_with_notification.notification_settings.first
|
||||
notification_setting.selected_email_flags = [:email_conversation_creation]
|
||||
notification_setting.selected_push_flags = []
|
||||
notification_setting.save!
|
||||
|
||||
create(:inbox_member, user: agent_with_notification, inbox: inbox)
|
||||
|
@ -29,13 +24,13 @@ describe NotificationListener do
|
|||
event = Events::Base.new(event_name, Time.zone.now, conversation: conversation)
|
||||
|
||||
listener.conversation_created(event)
|
||||
expect(AgentNotifications::ConversationNotificationsMailer).to have_received(:conversation_creation)
|
||||
.with(conversation, agent_with_notification)
|
||||
expect(notification_setting.user.notifications.count).to eq(1)
|
||||
end
|
||||
|
||||
it 'does not send and email to inbox members who have notifications turned off' do
|
||||
notification_setting = agent_with_notification.notification_settings.first
|
||||
it 'does not create notification for inbox members who have notifications turned off' do
|
||||
notification_setting = agent_with_out_notification.notification_settings.first
|
||||
notification_setting.unselect_all_email_flags
|
||||
notification_setting.unselect_all_push_flags
|
||||
notification_setting.save!
|
||||
|
||||
create(:inbox_member, user: agent_with_out_notification, inbox: inbox)
|
||||
|
@ -44,8 +39,7 @@ describe NotificationListener do
|
|||
event = Events::Base.new(event_name, Time.zone.now, conversation: conversation)
|
||||
|
||||
listener.conversation_created(event)
|
||||
expect(AgentNotifications::ConversationNotificationsMailer).not_to have_received(:conversation_creation)
|
||||
.with(conversation, agent_with_out_notification)
|
||||
expect(notification_setting.user.notifications.count).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -122,28 +122,19 @@ RSpec.describe Conversation, type: :model do
|
|||
expect(conversation.reload.assignee).to eq(agent)
|
||||
end
|
||||
|
||||
it 'send assignment mailer' do
|
||||
allow(AgentNotifications::ConversationNotificationsMailer).to receive(:conversation_assignment).and_return(assignment_mailer)
|
||||
allow(assignment_mailer).to receive(:deliver_later)
|
||||
|
||||
Current.user = conversation.assignee
|
||||
|
||||
it 'creates a new notification for the agent' do
|
||||
expect(update_assignee).to eq(true)
|
||||
# send_email_notification_to_assignee
|
||||
expect(AgentNotifications::ConversationNotificationsMailer).to have_received(:conversation_assignment).with(conversation, agent)
|
||||
|
||||
expect(assignment_mailer).to have_received(:deliver_later) if ENV.fetch('SMTP_ADDRESS', nil).present?
|
||||
expect(agent.notifications.count).to eq(1)
|
||||
end
|
||||
|
||||
it 'does not send assignment mailer if notification setting is turned off' do
|
||||
allow(AgentNotifications::ConversationNotificationsMailer).to receive(:conversation_assignment).and_return(assignment_mailer)
|
||||
|
||||
it 'does not create assignment notification if notification setting is turned off' do
|
||||
notification_setting = agent.notification_settings.first
|
||||
notification_setting.unselect_all_email_flags
|
||||
notification_setting.unselect_all_push_flags
|
||||
notification_setting.save!
|
||||
|
||||
expect(update_assignee).to eq(true)
|
||||
expect(AgentNotifications::ConversationNotificationsMailer).not_to have_received(:conversation_assignment).with(conversation, agent)
|
||||
expect(agent.notifications.count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue