From a52a393266744560c6c6b2cbd24da812d35562fb Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 11 Jun 2020 15:22:31 +0200 Subject: [PATCH 1/9] NotificationUtils: Extract preparation of notification object. --- src/modules/statuses.js | 41 ++---------------- .../notification_utils/notification_utils.js | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 9a2e0df1..073b15f1 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -13,7 +13,7 @@ import { omitBy } from 'lodash' import { set } from 'vue' -import { isStatusNotification } from '../services/notification_utils/notification_utils.js' +import { isStatusNotification, prepareNotificationObject } from '../services/notification_utils/notification_utils.js' import apiService from '../services/api/api.service.js' import { muteWordHits } from '../services/status_parser/status_parser.js' @@ -344,42 +344,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot state.notifications.idStore[notification.id] = notification if ('Notification' in window && window.Notification.permission === 'granted') { - const notifObj = {} - const status = notification.status - const title = notification.from_profile.name - notifObj.icon = notification.from_profile.profile_image_url - let i18nString - switch (notification.type) { - case 'like': - i18nString = 'favorited_you' - break - case 'repeat': - i18nString = 'repeated_you' - break - case 'follow': - i18nString = 'followed_you' - break - case 'move': - i18nString = 'migrated_to' - break - case 'follow_request': - i18nString = 'follow_request' - break - } - - if (notification.type === 'pleroma:emoji_reaction') { - notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji]) - } else if (i18nString) { - notifObj.body = rootGetters.i18n.t('notifications.' + i18nString) - } else if (isStatusNotification(notification.type)) { - notifObj.body = notification.status.text - } - - // Shows first attached non-nsfw image, if any. Should add configuration for this somehow... - if (status && status.attachments && status.attachments.length > 0 && !status.nsfw && - status.attachments[0].mimetype.startsWith('image/')) { - notifObj.image = status.attachments[0].url - } + const notifObj = prepareNotificationObject(notification, rootGetters.i18n) const reasonsToMuteNotif = ( notification.seen || @@ -393,7 +358,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot ) ) if (!reasonsToMuteNotif) { - let desktopNotification = new window.Notification(title, notifObj) + let desktopNotification = new window.Notification(notifObj.title, notifObj) // Chrome is known for not closing notifications automatically // according to MDN, anyway. setTimeout(desktopNotification.close.bind(desktopNotification), 5000) diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index eb479227..4576ea83 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -43,3 +43,45 @@ export const filteredNotificationsFromStore = (store, types) => { export const unseenNotificationsFromStore = store => filter(filteredNotificationsFromStore(store), ({ seen }) => !seen) + +export const prepareNotificationObject = (notification, i18n) => { + const notifObj = {} + const status = notification.status + const title = notification.from_profile.name + notifObj.title = title + notifObj.icon = notification.from_profile.profile_image_url + let i18nString + switch (notification.type) { + case 'like': + i18nString = 'favorited_you' + break + case 'repeat': + i18nString = 'repeated_you' + break + case 'follow': + i18nString = 'followed_you' + break + case 'move': + i18nString = 'migrated_to' + break + case 'follow_request': + i18nString = 'follow_request' + break + } + + if (notification.type === 'pleroma:emoji_reaction') { + notifObj.body = i18n.t('notifications.reacted_with', [notification.emoji]) + } else if (i18nString) { + notifObj.body = i18n.t('notifications.' + i18nString) + } else if (isStatusNotification(notification.type)) { + notifObj.body = notification.status.text + } + + // Shows first attached non-nsfw image, if any. Should add configuration for this somehow... + if (status && status.attachments && status.attachments.length > 0 && !status.nsfw && + status.attachments[0].mimetype.startsWith('image/')) { + notifObj.image = status.attachments[0].url + } + + return notifObj +} From 5b0190bef5a9756453d0220b80a18469639c8fe9 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 11 Jun 2020 15:22:58 +0200 Subject: [PATCH 2/9] ServiceWorker: Grab the notification and display it with i18n. --- src/sw.js | 76 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/src/sw.js b/src/sw.js index 6cecb3f3..0aab275b 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,6 +1,48 @@ /* eslint-env serviceworker */ import localForage from 'localforage' +import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js' +import { prepareNotificationObject } from './services/notification_utils/notification_utils.js' +import Vue from 'vue' +import VueI18n from 'vue-i18n' + +const messages = { + ar: require('./i18n/ar.json'), + ca: require('./i18n/ca.json'), + cs: require('./i18n/cs.json'), + de: require('./i18n/de.json'), + eo: require('./i18n/eo.json'), + es: require('./i18n/es.json'), + et: require('./i18n/et.json'), + eu: require('./i18n/eu.json'), + fi: require('./i18n/fi.json'), + fr: require('./i18n/fr.json'), + ga: require('./i18n/ga.json'), + he: require('./i18n/he.json'), + hu: require('./i18n/hu.json'), + it: require('./i18n/it.json'), + ja: require('./i18n/ja_pedantic.json'), + ja_easy: require('./i18n/ja_easy.json'), + ko: require('./i18n/ko.json'), + nb: require('./i18n/nb.json'), + nl: require('./i18n/nl.json'), + oc: require('./i18n/oc.json'), + pl: require('./i18n/pl.json'), + pt: require('./i18n/pt.json'), + ro: require('./i18n/ro.json'), + ru: require('./i18n/ru.json'), + te: require('./i18n/te.json'), + zh: require('./i18n/zh.json'), + en: require('./i18n/en.json') +} + +Vue.use(VueI18n) +const i18n = new VueI18n({ + // By default, use the browser locale, we will update it if neccessary + locale: 'en', + fallbackLocale: 'en', + messages +}) function isEnabled () { return localForage.getItem('vuex-lz') @@ -12,15 +54,33 @@ function getWindowClients () { .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() +const setLocale = async () => { + const state = await localForage.getItem('vuex-lz') + const locale = state.config.interfaceLanguage || 'en' + i18n.locale = locale +} - if (list.length === 0) return self.registration.showNotification(data.title, data) - }) - })) +const maybeShowNotification = async (event) => { + const enabled = await isEnabled() + const activeClients = await getWindowClients() + await setLocale() + if (enabled && activeClients) { + const data = event.data.json() + + const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}` + const notification = await fetch(url, { headers: { Authorization: 'Bearer ' + data.access_token } }) + let nj = await notification.json() + nj = parseNotification(nj) + + const res = prepareNotificationObject(nj, i18n) + + self.registration.showNotification(res.title, res) + } +} + +self.addEventListener('push', async (event) => { + if (event.data) { + event.waitUntil(maybeShowNotification(event)) } }) From e2ca107e01c5a0715aa1a1a68496fe514eb9e902 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 11 Jun 2020 15:24:01 +0200 Subject: [PATCH 3/9] ServiceWorker: Don't show message via sw if a client is active. --- src/sw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sw.js b/src/sw.js index 0aab275b..d0050b24 100644 --- a/src/sw.js +++ b/src/sw.js @@ -64,7 +64,7 @@ const maybeShowNotification = async (event) => { const enabled = await isEnabled() const activeClients = await getWindowClients() await setLocale() - if (enabled && activeClients) { + if (enabled && (activeClients.length === 0)) { const data = event.data.json() const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}` From 98819ae32c6f3962df955ee280cd6cc271625852 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 11 Jun 2020 15:49:39 +0200 Subject: [PATCH 4/9] NotificationUtils: Add tag to notifications. --- src/services/notification_utils/notification_utils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index 4576ea83..5cc19215 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -45,7 +45,9 @@ export const unseenNotificationsFromStore = store => filter(filteredNotificationsFromStore(store), ({ seen }) => !seen) export const prepareNotificationObject = (notification, i18n) => { - const notifObj = {} + const notifObj = { + tag: notification.id + } const status = notification.status const title = notification.from_profile.name notifObj.title = title From 9bfb3754c1bc0d1033afda97f2884e721d1ab3d8 Mon Sep 17 00:00:00 2001 From: lain Date: Sat, 13 Jun 2020 11:47:34 +0200 Subject: [PATCH 5/9] ServiceWorker: Use loader to only notification messages. This keeps the translation size very small and makes it easy to integrate all the languages, as dynamically loading them isn't easy in the service worker. --- src/lib/notification-i18n-loader.js | 9 +++++ src/sw.js | 55 +++++++++++++++-------------- 2 files changed, 37 insertions(+), 27 deletions(-) create mode 100644 src/lib/notification-i18n-loader.js diff --git a/src/lib/notification-i18n-loader.js b/src/lib/notification-i18n-loader.js new file mode 100644 index 00000000..a61755c8 --- /dev/null +++ b/src/lib/notification-i18n-loader.js @@ -0,0 +1,9 @@ +// This somewhat mysterious module +module.exports = function(source) { + var object = JSON.parse(source) + var smol = { + notifications: object.notifications || {} + } + + return JSON.stringify(smol) +} diff --git a/src/sw.js b/src/sw.js index d0050b24..6e31516a 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,4 +1,5 @@ /* eslint-env serviceworker */ +/* eslint-disable import/no-webpack-loader-syntax */ import localForage from 'localforage' import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js' @@ -7,33 +8,33 @@ import Vue from 'vue' import VueI18n from 'vue-i18n' const messages = { - ar: require('./i18n/ar.json'), - ca: require('./i18n/ca.json'), - cs: require('./i18n/cs.json'), - de: require('./i18n/de.json'), - eo: require('./i18n/eo.json'), - es: require('./i18n/es.json'), - et: require('./i18n/et.json'), - eu: require('./i18n/eu.json'), - fi: require('./i18n/fi.json'), - fr: require('./i18n/fr.json'), - ga: require('./i18n/ga.json'), - he: require('./i18n/he.json'), - hu: require('./i18n/hu.json'), - it: require('./i18n/it.json'), - ja: require('./i18n/ja_pedantic.json'), - ja_easy: require('./i18n/ja_easy.json'), - ko: require('./i18n/ko.json'), - nb: require('./i18n/nb.json'), - nl: require('./i18n/nl.json'), - oc: require('./i18n/oc.json'), - pl: require('./i18n/pl.json'), - pt: require('./i18n/pt.json'), - ro: require('./i18n/ro.json'), - ru: require('./i18n/ru.json'), - te: require('./i18n/te.json'), - zh: require('./i18n/zh.json'), - en: require('./i18n/en.json') + ar: require('./lib/notification-i18n-loader.js!./i18n/ar.json'), + ca: require('./lib/notification-i18n-loader.js!./i18n/ca.json'), + cs: require('./lib/notification-i18n-loader.js!./i18n/cs.json'), + de: require('./lib/notification-i18n-loader.js!./i18n/de.json'), + eo: require('./lib/notification-i18n-loader.js!./i18n/eo.json'), + es: require('./lib/notification-i18n-loader.js!./i18n/es.json'), + et: require('./lib/notification-i18n-loader.js!./i18n/et.json'), + eu: require('./lib/notification-i18n-loader.js!./i18n/eu.json'), + fi: require('./lib/notification-i18n-loader.js!./i18n/fi.json'), + fr: require('./lib/notification-i18n-loader.js!./i18n/fr.json'), + ga: require('./lib/notification-i18n-loader.js!./i18n/ga.json'), + he: require('./lib/notification-i18n-loader.js!./i18n/he.json'), + hu: require('./lib/notification-i18n-loader.js!./i18n/hu.json'), + it: require('./lib/notification-i18n-loader.js!./i18n/it.json'), + ja: require('./lib/notification-i18n-loader.js!./i18n/ja_pedantic.json'), + ja_easy: require('./lib/notification-i18n-loader.js!./i18n/ja_easy.json'), + ko: require('./lib/notification-i18n-loader.js!./i18n/ko.json'), + nb: require('./lib/notification-i18n-loader.js!./i18n/nb.json'), + nl: require('./lib/notification-i18n-loader.js!./i18n/nl.json'), + oc: require('./lib/notification-i18n-loader.js!./i18n/oc.json'), + pl: require('./lib/notification-i18n-loader.js!./i18n/pl.json'), + pt: require('./lib/notification-i18n-loader.js!./i18n/pt.json'), + ro: require('./lib/notification-i18n-loader.js!./i18n/ro.json'), + ru: require('./lib/notification-i18n-loader.js!./i18n/ru.json'), + te: require('./lib/notification-i18n-loader.js!./i18n/te.json'), + zh: require('./lib/notification-i18n-loader.js!./i18n/zh.json'), + en: require('./lib/notification-i18n-loader.js!./i18n/en.json') } Vue.use(VueI18n) From 1e57adf6d4a46ab5be677bba7ae3c7f0ba0fc520 Mon Sep 17 00:00:00 2001 From: lain Date: Sat, 13 Jun 2020 11:53:16 +0200 Subject: [PATCH 6/9] Linting + docs --- src/lib/notification-i18n-loader.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/notification-i18n-loader.js b/src/lib/notification-i18n-loader.js index a61755c8..71f9156a 100644 --- a/src/lib/notification-i18n-loader.js +++ b/src/lib/notification-i18n-loader.js @@ -1,5 +1,8 @@ -// This somewhat mysterious module -module.exports = function(source) { +// This somewhat mysterious module will load a json string +// and then extract only the 'notifications' part. This is +// meant to be used to load the partial i18n we need for +// the service worker. +module.exports = function (source) { var object = JSON.parse(source) var smol = { notifications: object.notifications || {} From 14540e2a078bd59ca8f13cd5c0b894acad3d639f Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 16 Jun 2020 13:27:36 +0200 Subject: [PATCH 7/9] Service Worker: Extract messages to own module. --- src/i18n/service_worker_messages.js | 35 +++++++++++++++++++++++++++++ src/sw.js | 32 +------------------------- 2 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 src/i18n/service_worker_messages.js diff --git a/src/i18n/service_worker_messages.js b/src/i18n/service_worker_messages.js new file mode 100644 index 00000000..270ed043 --- /dev/null +++ b/src/i18n/service_worker_messages.js @@ -0,0 +1,35 @@ +/* eslint-disable import/no-webpack-loader-syntax */ +// This module exports only the notification part of the i18n, +// which is useful for the service worker + +const messages = { + ar: require('../lib/notification-i18n-loader.js!./ar.json'), + ca: require('../lib/notification-i18n-loader.js!./ca.json'), + cs: require('../lib/notification-i18n-loader.js!./cs.json'), + de: require('../lib/notification-i18n-loader.js!./de.json'), + eo: require('../lib/notification-i18n-loader.js!./eo.json'), + es: require('../lib/notification-i18n-loader.js!./es.json'), + et: require('../lib/notification-i18n-loader.js!./et.json'), + eu: require('../lib/notification-i18n-loader.js!./eu.json'), + fi: require('../lib/notification-i18n-loader.js!./fi.json'), + fr: require('../lib/notification-i18n-loader.js!./fr.json'), + ga: require('../lib/notification-i18n-loader.js!./ga.json'), + he: require('../lib/notification-i18n-loader.js!./he.json'), + hu: require('../lib/notification-i18n-loader.js!./hu.json'), + it: require('../lib/notification-i18n-loader.js!./it.json'), + ja: require('../lib/notification-i18n-loader.js!./ja_pedantic.json'), + ja_easy: require('../lib/notification-i18n-loader.js!./ja_easy.json'), + ko: require('../lib/notification-i18n-loader.js!./ko.json'), + nb: require('../lib/notification-i18n-loader.js!./nb.json'), + nl: require('../lib/notification-i18n-loader.js!./nl.json'), + oc: require('../lib/notification-i18n-loader.js!./oc.json'), + pl: require('../lib/notification-i18n-loader.js!./pl.json'), + pt: require('../lib/notification-i18n-loader.js!./pt.json'), + ro: require('../lib/notification-i18n-loader.js!./ro.json'), + ru: require('../lib/notification-i18n-loader.js!./ru.json'), + te: require('../lib/notification-i18n-loader.js!./te.json'), + zh: require('../lib/notification-i18n-loader.js!./zh.json'), + en: require('../lib/notification-i18n-loader.js!./en.json') +} + +export default messages diff --git a/src/sw.js b/src/sw.js index 6e31516a..c971222e 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,41 +1,11 @@ /* eslint-env serviceworker */ -/* eslint-disable import/no-webpack-loader-syntax */ import localForage from 'localforage' import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js' import { prepareNotificationObject } from './services/notification_utils/notification_utils.js' import Vue from 'vue' import VueI18n from 'vue-i18n' - -const messages = { - ar: require('./lib/notification-i18n-loader.js!./i18n/ar.json'), - ca: require('./lib/notification-i18n-loader.js!./i18n/ca.json'), - cs: require('./lib/notification-i18n-loader.js!./i18n/cs.json'), - de: require('./lib/notification-i18n-loader.js!./i18n/de.json'), - eo: require('./lib/notification-i18n-loader.js!./i18n/eo.json'), - es: require('./lib/notification-i18n-loader.js!./i18n/es.json'), - et: require('./lib/notification-i18n-loader.js!./i18n/et.json'), - eu: require('./lib/notification-i18n-loader.js!./i18n/eu.json'), - fi: require('./lib/notification-i18n-loader.js!./i18n/fi.json'), - fr: require('./lib/notification-i18n-loader.js!./i18n/fr.json'), - ga: require('./lib/notification-i18n-loader.js!./i18n/ga.json'), - he: require('./lib/notification-i18n-loader.js!./i18n/he.json'), - hu: require('./lib/notification-i18n-loader.js!./i18n/hu.json'), - it: require('./lib/notification-i18n-loader.js!./i18n/it.json'), - ja: require('./lib/notification-i18n-loader.js!./i18n/ja_pedantic.json'), - ja_easy: require('./lib/notification-i18n-loader.js!./i18n/ja_easy.json'), - ko: require('./lib/notification-i18n-loader.js!./i18n/ko.json'), - nb: require('./lib/notification-i18n-loader.js!./i18n/nb.json'), - nl: require('./lib/notification-i18n-loader.js!./i18n/nl.json'), - oc: require('./lib/notification-i18n-loader.js!./i18n/oc.json'), - pl: require('./lib/notification-i18n-loader.js!./i18n/pl.json'), - pt: require('./lib/notification-i18n-loader.js!./i18n/pt.json'), - ro: require('./lib/notification-i18n-loader.js!./i18n/ro.json'), - ru: require('./lib/notification-i18n-loader.js!./i18n/ru.json'), - te: require('./lib/notification-i18n-loader.js!./i18n/te.json'), - zh: require('./lib/notification-i18n-loader.js!./i18n/zh.json'), - en: require('./lib/notification-i18n-loader.js!./i18n/en.json') -} +import messages from './i18n/service_worker_messages.js' Vue.use(VueI18n) const i18n = new VueI18n({ From aa725010c5a3eeeb2dae3e6618b057e08fc5fd49 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 19 Jun 2020 15:24:06 +0200 Subject: [PATCH 8/9] ServiceWorker: Use clearer variable names --- src/sw.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sw.js b/src/sw.js index c971222e..f5e34dd6 100644 --- a/src/sw.js +++ b/src/sw.js @@ -40,10 +40,10 @@ const maybeShowNotification = async (event) => { const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}` const notification = await fetch(url, { headers: { Authorization: 'Bearer ' + data.access_token } }) - let nj = await notification.json() - nj = parseNotification(nj) + const notificationJson = await notification.json() + const parsedNotification = parseNotification(notificationJson) - const res = prepareNotificationObject(nj, i18n) + const res = prepareNotificationObject(parsedNotification, i18n) self.registration.showNotification(res.title, res) } From ed908e0195db26ec9c51d124315bc084c5576a29 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 19 Jun 2020 15:25:57 +0200 Subject: [PATCH 9/9] Changelog: Add info about push notifications. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad03c760..19ac4ba3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Changed - Removed the use of with_move parameters when fetching notifications +- Push notifications now are the same as normal notfication, and are localized. ### Fixed - Weird bug related to post being sent seemingly after pasting with keyboard (hopefully)