diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/boot/after_store.js | 19 | ||||
| -rw-r--r-- | src/components/attachment/attachment.js | 2 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.js | 2 | ||||
| -rw-r--r-- | src/components/settings/settings.js | 5 | ||||
| -rw-r--r-- | src/components/settings/settings.vue | 12 | ||||
| -rw-r--r-- | src/components/status/status.vue | 2 | ||||
| -rw-r--r-- | src/components/timeline/timeline.js | 5 | ||||
| -rw-r--r-- | src/components/user_profile/user_profile.vue | 18 | ||||
| -rw-r--r-- | src/components/user_settings/user_settings.js | 55 | ||||
| -rw-r--r-- | src/components/user_settings/user_settings.vue | 14 | ||||
| -rw-r--r-- | src/i18n/en.json | 5 | ||||
| -rw-r--r-- | src/i18n/ru.json | 1 | ||||
| -rw-r--r-- | src/main.js | 39 | ||||
| -rw-r--r-- | src/modules/config.js | 1 | ||||
| -rw-r--r-- | src/modules/interface.js | 13 | ||||
| -rw-r--r-- | src/modules/users.js | 42 | ||||
| -rw-r--r-- | src/services/push/push.js | 69 | ||||
| -rw-r--r-- | src/sw.js | 38 |
18 files changed, 291 insertions, 51 deletions
diff --git a/src/boot/after_store.js b/src/boot/after_store.js index a80baaf5..07337595 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -17,17 +17,21 @@ import FollowRequests from '../components/follow_requests/follow_requests.vue' import OAuthCallback from '../components/oauth_callback/oauth_callback.vue' import UserSearch from '../components/user_search/user_search.vue' -const afterStoreSetup = ({store, i18n}) => { +const afterStoreSetup = ({ store, i18n }) => { window.fetch('/api/statusnet/config.json') .then((res) => res.json()) .then((data) => { - const {name, closed: registrationClosed, textlimit, server} = data.site + const { name, closed: registrationClosed, textlimit, server, vapidPublicKey } = data.site store.dispatch('setInstanceOption', { name: 'name', value: name }) store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) store.dispatch('setInstanceOption', { name: 'server', value: server }) + if (vapidPublicKey) { + store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) + } + var apiConfig = data.site.pleromafe window.fetch('/static/config.json') @@ -38,8 +42,17 @@ const afterStoreSetup = ({store, i18n}) => { return {} }) .then((staticConfig) => { + const overrides = window.___pleromafe_dev_overrides || {} + const env = window.___pleromafe_mode.NODE_ENV + // This takes static config and overrides properties that are present in apiConfig - var config = Object.assign({}, staticConfig, apiConfig) + let config = {} + if (overrides.staticConfigPreference && env === 'development') { + console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG') + config = Object.assign({}, apiConfig, staticConfig) + } else { + config = Object.assign({}, staticConfig, apiConfig) + } var theme = (config.theme) var background = (config.background) diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 16114c30..97c4f283 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -11,7 +11,7 @@ const Attachment = { ], data () { return { - nsfwImage, + nsfwImage: this.$store.state.config.nsfwCensorImage || nsfwImage, hideNsfwLocal: this.$store.state.config.hideNsfw, preloadImage: this.$store.state.config.preloadImage, loopVideo: this.$store.state.config.loopVideo, diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 789243cf..f9252f73 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -46,7 +46,7 @@ const PostStatusForm = { statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser) } - const scope = (this.copyMessageScope && this.$store.state.config.copyScope || this.copyMessageScope === 'direct') + const scope = (this.copyMessageScope && this.$store.state.config.scopeCopy || this.copyMessageScope === 'direct') ? this.copyMessageScope : this.$store.state.users.currentUser.default_scope diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index c9e12708..681ccda8 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -47,6 +47,7 @@ const settings = { scopeCopyLocal: user.scopeCopy, scopeCopyDefault: this.$t('settings.values.' + instance.scopeCopy), stopGifs: user.stopGifs, + webPushNotificationsLocal: user.webPushNotifications, loopSilentAvailable: // Firefox Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || @@ -142,6 +143,10 @@ const settings = { }, stopGifs (value) { this.$store.dispatch('setOption', { name: 'stopGifs', value }) + }, + webPushNotificationsLocal (value) { + this.$store.dispatch('setOption', { name: 'webPushNotifications', value }) + if (value) this.$store.dispatch('registerPushNotifications') } } } diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index 6cdc82da..3f920de5 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -143,6 +143,18 @@ </li> </ul> </div> + + <div class="setting-item"> + <h2>{{$t('settings.notifications')}}</h2> + <ul class="setting-list"> + <li> + <input type="checkbox" id="webPushNotifications" v-model="webPushNotificationsLocal"> + <label for="webPushNotifications"> + {{$t('settings.enable_web_push_notifications')}} + </label> + </li> + </ul> + </div> </div> <div :label="$t('settings.theme')" > diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 96709084..067980ac 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -54,7 +54,7 @@ </h4> </div> <div class="media-heading-right"> - <router-link @click.native="activatePanel('timeline')" :to="{ name: 'conversation', params: { id: status.id } }"> + <router-link class="timeago" @click.native="activatePanel('timeline')" :to="{ name: 'conversation', params: { id: status.id } }"> <timeago :since="status.created_at" :auto-update="60"></timeago> </router-link> <div class="visibility-icon" v-if="status.visibility"> diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index a651f619..f28b85bd 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -2,6 +2,7 @@ import Status from '../status/status.vue' import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js' import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue' import UserCard from '../user_card/user_card.vue' +import { throttle } from 'lodash' const Timeline = { props: [ @@ -88,7 +89,7 @@ const Timeline = { this.paused = false } }, - fetchOlderStatuses () { + fetchOlderStatuses: throttle(function () { const store = this.$store const credentials = store.state.users.currentUser.credentials store.commit('setLoading', { timeline: this.timelineName, value: true }) @@ -101,7 +102,7 @@ const Timeline = { userId: this.userId, tag: this.tag }).then(() => store.commit('setLoading', { timeline: this.timelineName, value: false })) - }, + }, 1000, this), fetchFollowers () { const id = this.userId this.$store.state.api.backendInteractor.fetchFollowers({ id }) diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 91d4acd2..4d2853a6 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -3,6 +3,16 @@ <div v-if="user" class="user-profile panel panel-default"> <user-card-content :user="user" :switcher="true" :selected="timeline.viewing"></user-card-content> </div> + <div v-else class="panel user-profile-placeholder"> + <div class="panel-heading"> + <div class="title"> + {{ $t('settings.profile_tab') }} + </div> + </div> + <div class="panel-body"> + <i class="icon-spin3 animate-spin"></i> + </div> + </div> <Timeline :title="$t('user_profile.timeline_title')" :timeline="timeline" :timeline-name="'user'" :user-id="userId"/> </div> </template> @@ -21,4 +31,12 @@ align-items: stretch; } } +.user-profile-placeholder { + .panel-body { + display: flex; + justify-content: center; + align-items: middle; + padding: 7em; + } +} </style> diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index 1712e64c..422a057e 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -4,11 +4,12 @@ import StyleSwitcher from '../style_switcher/style_switcher.vue' const UserSettings = { data () { return { - newname: this.$store.state.users.currentUser.name, - newbio: this.$store.state.users.currentUser.description, - newlocked: this.$store.state.users.currentUser.locked, - newnorichtext: this.$store.state.users.currentUser.no_rich_text, - newdefaultScope: this.$store.state.users.currentUser.default_scope, + newName: this.$store.state.users.currentUser.name, + newBio: this.$store.state.users.currentUser.description, + newLocked: this.$store.state.users.currentUser.locked, + newNoRichText: this.$store.state.users.currentUser.no_rich_text, + newDefaultScope: this.$store.state.users.currentUser.default_scope, + newHideNetwork: this.$store.state.users.currentUser.hide_network, followList: null, followImportError: false, followsImported: false, @@ -40,31 +41,45 @@ const UserSettings = { }, vis () { return { - public: { selected: this.newdefaultScope === 'public' }, - unlisted: { selected: this.newdefaultScope === 'unlisted' }, - private: { selected: this.newdefaultScope === 'private' }, - direct: { selected: this.newdefaultScope === 'direct' } + public: { selected: this.newDefaultScope === 'public' }, + unlisted: { selected: this.newDefaultScope === 'unlisted' }, + private: { selected: this.newDefaultScope === 'private' }, + direct: { selected: this.newDefaultScope === 'direct' } } } }, methods: { updateProfile () { const name = this.newname - const description = this.newbio - const locked = this.newlocked + const description = this.newBio + const locked = this.newLocked + // Backend notation. /* eslint-disable camelcase */ - const default_scope = this.newdefaultScope - const no_rich_text = this.newnorichtext - this.$store.state.api.backendInteractor.updateProfile({params: {name, description, locked, default_scope, no_rich_text}}).then((user) => { - if (!user.error) { - this.$store.commit('addNewUsers', [user]) - this.$store.commit('setCurrentUser', user) - } - }) + const default_scope = this.newDefaultScope + const no_rich_text = this.newNoRichText + const hide_network = this.newHideNetwork /* eslint-enable camelcase */ + this.$store.state.api.backendInteractor + .updateProfile({ + params: { + name, + description, + locked, + // Backend notation. + /* eslint-disable camelcase */ + default_scope, + no_rich_text, + hide_network + /* eslint-enable camelcase */ + }}).then((user) => { + if (!user.error) { + this.$store.commit('addNewUsers', [user]) + this.$store.commit('setCurrentUser', user) + } + }) }, changeVis (visibility) { - this.newdefaultScope = visibility + this.newDefaultScope = visibility }, uploadFile (slot, e) { const file = e.target.files[0] diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index 234a7d86..11629440 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -9,11 +9,11 @@ <div class="setting-item" > <h2>{{$t('settings.name_bio')}}</h2> <p>{{$t('settings.name')}}</p> - <input class='name-changer' id='username' v-model="newname"></input> + <input class='name-changer' id='username' v-model="newName"></input> <p>{{$t('settings.bio')}}</p> - <textarea class="bio" v-model="newbio"></textarea> + <textarea class="bio" v-model="newBio"></textarea> <p> - <input type="checkbox" v-model="newlocked" id="account-locked"> + <input type="checkbox" v-model="newLocked" id="account-locked"> <label for="account-locked">{{$t('settings.lock_account_description')}}</label> </p> <div v-if="scopeOptionsEnabled"> @@ -26,10 +26,14 @@ </div> </div> <p> - <input type="checkbox" v-model="newnorichtext" id="account-no-rich-text"> + <input type="checkbox" v-model="newNoRichText" id="account-no-rich-text"> <label for="account-no-rich-text">{{$t('settings.no_rich_text_description')}}</label> </p> - <button :disabled='newname.length <= 0' class="btn btn-default" @click="updateProfile">{{$t('general.submit')}}</button> + <p> + <input type="checkbox" v-model="newHideNetwork" id="account-hide-network"> + <label for="account-no-rich-text">{{$t('settings.hide_network_description')}}</label> + </p> + <button :disabled='newName.length <= 0' class="btn btn-default" @click="updateProfile">{{$t('general.submit')}}</button> </div> <div class="setting-item"> <h2>{{$t('settings.avatar')}}</h2> diff --git a/src/i18n/en.json b/src/i18n/en.json index 92429e4b..5b50b86d 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -133,7 +133,7 @@ "inputRadius": "Input fields", "checkboxRadius": "Checkboxes", "instance_default": "(default: {value})", - "instance_default_simple" : "(default)", + "instance_default_simple": "(default)", "interface": "Interface", "interfaceLanguage": "Interface language", "invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.", @@ -151,6 +151,7 @@ "notification_visibility_mentions": "Mentions", "notification_visibility_repeats": "Repeats", "no_rich_text_description": "Strip rich text formatting from all posts", + "hide_network_description": "Don't show who I'm following and who's following me", "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding", "panelRadius": "Panels", "pause_on_unfocused": "Pause streaming when tab is not focused", @@ -190,6 +191,8 @@ "false": "no", "true": "yes" }, + "notifications": "Notifications", + "enable_web_push_notifications": "Enable web push notifications", "style": { "switcher": { "keep_color": "Keep colors", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index c764005a..13c7fec3 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -126,6 +126,7 @@ "notification_visibility_mentions": "Упоминания", "notification_visibility_repeats": "Повторы", "no_rich_text_description": "Убрать форматирование из всех постов", + "hide_network_description": "Не показывать кого я читаю и кто меня читает", "nsfw_clickthrough": "Включить скрытие NSFW вложений", "panelRadius": "Панели", "pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе", diff --git a/src/main.js b/src/main.js index 378fe95c..6ce2df13 100644 --- a/src/main.js +++ b/src/main.js @@ -50,6 +50,35 @@ const persistedStateOptions = { 'oauth' ] } + +const registerPushNotifications = store => { + store.subscribe((mutation, state) => { + const vapidPublicKey = state.instance.vapidPublicKey + const permission = state.interface.notificationPermission === 'granted' + const isUserMutation = mutation.type === 'setCurrentUser' + + if (isUserMutation && vapidPublicKey && permission) { + return store.dispatch('registerPushNotifications') + } + if (data['nsfwCensorImage']) { + store.dispatch('setOption', { name: 'nsfwCensorImage', value: data['nsfwCensorImage'] }) + } + + const user = state.users.currentUser + const isVapidMutation = mutation.type === 'setInstanceOption' && mutation.payload.name === 'vapidPublicKey' + + if (isVapidMutation && user && permission) { + return store.dispatch('registerPushNotifications') + } + + const isPermMutation = mutation.type === 'setNotificationPermission' && mutation.payload === 'granted' + + if (isPermMutation && user && vapidPublicKey) { + return store.dispatch('registerPushNotifications') + } + }) +} + createPersistedState(persistedStateOptions).then((persistedState) => { const store = new Vuex.Store({ modules: { @@ -62,10 +91,16 @@ createPersistedState(persistedStateOptions).then((persistedState) => { chat: chatModule, oauth: oauthModule }, - plugins: [persistedState], + plugins: [persistedState, registerPushNotifications], strict: false // Socket modifies itself, let's ignore this for now. // strict: process.env.NODE_ENV !== 'production' }) - afterStoreSetup({store, i18n}) + afterStoreSetup({ store, i18n }) }) + +// These are inlined by webpack's DefinePlugin +/* eslint-disable */ +window.___pleromafe_mode = process.env +window.___pleromafe_commit_hash = COMMIT_HASH +window.___pleromafe_dev_overrides = DEV_OVERRIDES diff --git a/src/modules/config.js b/src/modules/config.js index 72839476..ccfd0190 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -24,6 +24,7 @@ const defaultState = { likes: true, repeats: true }, + webPushNotifications: true, muteWords: [], highlight: {}, interfaceLanguage: browserLocale, diff --git a/src/modules/interface.js b/src/modules/interface.js index 132fb08d..956c9cb3 100644 --- a/src/modules/interface.js +++ b/src/modules/interface.js @@ -3,12 +3,13 @@ import { set, delete as del } from 'vue' const defaultState = { settings: { currentSaveStateNotice: null, - noticeClearTimeout: null + noticeClearTimeout: null, + notificationPermission: null }, browserSupport: { cssFilter: window.CSS && window.CSS.supports && ( window.CSS.supports('filter', 'drop-shadow(0 0)') || - window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)') + window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)') ) } } @@ -23,10 +24,13 @@ const interfaceMod = { } set(state.settings, 'currentSaveStateNotice', { error: false, data: success }) set(state.settings, 'noticeClearTimeout', - setTimeout(() => del(state.settings, 'currentSaveStateNotice'), 2000)) + setTimeout(() => del(state.settings, 'currentSaveStateNotice'), 2000)) } else { set(state.settings, 'currentSaveStateNotice', { error: true, errorData: error }) } + }, + setNotificationPermission (state, permission) { + state.notificationPermission = permission } }, actions: { @@ -35,6 +39,9 @@ const interfaceMod = { }, settingsSaved ({ commit, dispatch }, { success, error }) { commit('settingsSaved', { success, error }) + }, + setNotificationPermission ({ commit }, permission) { + commit('setNotificationPermission', permission) } } } diff --git a/src/modules/users.js b/src/modules/users.js index 6d966c3b..25d1c81f 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -1,8 +1,9 @@ import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import { compact, map, each, merge } from 'lodash' import { set } from 'vue' +import registerPushNotifications from '../services/push/push.js' import oauthApi from '../services/new_api/oauth' -import {humanizeErrors} from './errors' +import { humanizeErrors } from './errors' // TODO: Unify with mergeOrAdd in statuses.js export const mergeOrAdd = (arr, obj, item) => { @@ -11,17 +12,28 @@ export const mergeOrAdd = (arr, obj, item) => { if (oldItem) { // We already have this, so only merge the new info. merge(oldItem, item) - return {item: oldItem, new: false} + return { item: oldItem, new: false } } else { // This is a new item, prepare it arr.push(item) obj[item.id] = item - return {item, new: true} + if (item.screen_name && !item.screen_name.includes('@')) { + obj[item.screen_name] = item + } + return { item, new: true } } } +const getNotificationPermission = () => { + const Notification = window.Notification + + if (!Notification) return Promise.resolve(null) + if (Notification.permission === 'default') return Notification.requestPermission() + return Promise.resolve(Notification.permission) +} + export const mutations = { - setMuted (state, { user: {id}, muted }) { + setMuted (state, { user: { id }, muted }) { const user = state.usersObject[id] set(user, 'muted', muted) }, @@ -45,7 +57,7 @@ export const mutations = { setUserForStatus (state, status) { status.user = state.usersObject[status.user.id] }, - setColor (state, { user: {id}, highlighted }) { + setColor (state, { user: { id }, highlighted }) { const user = state.usersObject[id] set(user, 'highlight', highlighted) }, @@ -77,8 +89,15 @@ const users = { mutations, actions: { fetchUser (store, id) { - store.rootState.api.backendInteractor.fetchUser({id}) - .then((user) => store.commit('addNewUsers', user)) + store.rootState.api.backendInteractor.fetchUser({ id }) + .then((user) => store.commit('addNewUsers', [user])) + }, + registerPushNotifications (store) { + const token = store.state.currentUser.credentials + const vapidPublicKey = store.rootState.instance.vapidPublicKey + const isEnabled = store.rootState.config.webPushNotifications + + registerPushNotifications(isEnabled, vapidPublicKey, token) }, addNewStatuses (store, { statuses }) { const users = map(statuses, 'user') @@ -143,6 +162,9 @@ const users = { commit('setCurrentUser', user) commit('addNewUsers', [user]) + getNotificationPermission() + .then(permission => commit('setNotificationPermission', permission)) + // Set our new backend interactor commit('setBackendInteractor', backendInteractorService(accessToken)) @@ -161,12 +183,8 @@ const users = { store.commit('addNewUsers', mutedUsers) }) - if ('Notification' in window && window.Notification.permission === 'default') { - window.Notification.requestPermission() - } - // Fetch our friends - store.rootState.api.backendInteractor.fetchFriends({id: user.id}) + store.rootState.api.backendInteractor.fetchFriends({ id: user.id }) .then((friends) => commit('addNewUsers', friends)) }) } else { diff --git a/src/services/push/push.js b/src/services/push/push.js new file mode 100644 index 00000000..1ac304d1 --- /dev/null +++ b/src/services/push/push.js @@ -0,0 +1,69 @@ +import runtime from 'serviceworker-webpack-plugin/lib/runtime' + +function urlBase64ToUint8Array (base64String) { + const padding = '='.repeat((4 - base64String.length % 4) % 4) + const base64 = (base64String + padding) + .replace(/-/g, '+') + .replace(/_/g, '/') + + const rawData = window.atob(base64) + return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0))) +} + +function isPushSupported () { + return 'serviceWorker' in navigator && 'PushManager' in window +} + +function registerServiceWorker () { + return runtime.register() + .catch((err) => console.error('Unable to register service worker.', err)) +} + +function subscribe (registration, isEnabled, vapidPublicKey) { + if (!isEnabled) return Promise.reject(new Error('Web Push is disabled in config')) + if (!vapidPublicKey) return Promise.reject(new Error('VAPID public key is not found')) + + const subscribeOptions = { + userVisibleOnly: true, + applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) + } + return registration.pushManager.subscribe(subscribeOptions) +} + +function sendSubscriptionToBackEnd (subscription, token) { + return window.fetch('/api/v1/push/subscription/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ + subscription, + data: { + alerts: { + follow: true, + favourite: true, + mention: true, + reblog: true + } + } + }) + }) + .then((response) => { + if (!response.ok) throw new Error('Bad status code from server.') + return response.json() + }) + .then((responseData) => { + if (!responseData.id) throw new Error('Bad response from server.') + return responseData + }) +} + +export default function registerPushNotifications (isEnabled, vapidPublicKey, token) { + if (isPushSupported()) { + registerServiceWorker() + .then((registration) => subscribe(registration, isEnabled, vapidPublicKey)) + .then((subscription) => sendSubscriptionToBackEnd(subscription, token)) + .catch((e) => console.warn(`Failed to setup Web Push Notifications: ${e.message}`)) + } +} diff --git a/src/sw.js b/src/sw.js new file mode 100644 index 00000000..6cecb3f3 --- /dev/null +++ b/src/sw.js @@ -0,0 +1,38 @@ +/* eslint-env serviceworker */ + +import localForage from 'localforage' + +function isEnabled () { + return localForage.getItem('vuex-lz') + .then(data => data.config.webPushNotifications) +} + +function getWindowClients () { + return clients.matchAll({ includeUncontrolled: true }) + .then((clientList) => clientList.filter(({ type }) => type === 'window')) +} + +self.addEventListener('push', (event) => { + if (event.data) { + event.waitUntil(isEnabled().then((isEnabled) => { + return isEnabled && getWindowClients().then((list) => { + const data = event.data.json() + + if (list.length === 0) return self.registration.showNotification(data.title, data) + }) + })) + } +}) + +self.addEventListener('notificationclick', (event) => { + event.notification.close() + + event.waitUntil(getWindowClients().then((list) => { + for (var i = 0; i < list.length; i++) { + var client = list[i] + if (client.url === '/' && 'focus' in client) { return client.focus() } + } + + if (clients.openWindow) return clients.openWindow('/') + })) +}) |
