From fc6911caad0ad1da1d357aaa0d971558b0580dd9 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 21 Oct 2021 16:22:54 -0500 Subject: [PATCH] Push notifications: refactor, restore --- .../actions/push_notifications/registerer.js | 160 +++++++++--------- app/soapbox/actions/push_subscriptions.js | 3 +- app/soapbox/features/ui/index.js | 2 + app/soapbox/main.js | 5 - 4 files changed, 85 insertions(+), 85 deletions(-) diff --git a/app/soapbox/actions/push_notifications/registerer.js b/app/soapbox/actions/push_notifications/registerer.js index 91b6cd6a3..156641c2b 100644 --- a/app/soapbox/actions/push_notifications/registerer.js +++ b/app/soapbox/actions/push_notifications/registerer.js @@ -1,7 +1,7 @@ -import api from '../../api'; import { decode as decodeBase64 } from '../../utils/base64'; import { pushNotificationsSetting } from '../../settings'; import { setBrowserSupport, setSubscription, clearSubscription } from './setter'; +import { createPushSubsription, updatePushSubscription } from 'soapbox/actions/push_subscriptions'; // Taken from https://www.npmjs.com/package/web-push const urlBase64ToUint8Array = (base64String) => { @@ -13,10 +13,9 @@ const urlBase64ToUint8Array = (base64String) => { return decodeBase64(base64); }; -const getApplicationServerKey = getState => { - const key = getState().getIn(['auth', 'app', 'vapid_key']); - if (!key) console.error('Could not get vapid key. Push notifications will not work.'); - return key; +const getVapidKey = getState => { + const state = getState(); + return state.getIn(['auth', 'app', 'vapid_key']) || state.getIn(['instance', 'pleroma', 'vapid_public_key']); }; const getRegistration = () => navigator.serviceWorker.ready; @@ -28,23 +27,25 @@ const getPushSubscription = (registration) => const subscribe = (registration, getState) => registration.pushManager.subscribe({ userVisibleOnly: true, - applicationServerKey: urlBase64ToUint8Array(getApplicationServerKey(getState)), + applicationServerKey: urlBase64ToUint8Array(getVapidKey(getState)), }); const unsubscribe = ({ registration, subscription }) => subscription ? subscription.unsubscribe().then(() => registration) : registration; const sendSubscriptionToBackend = (subscription, me) => { - const params = { subscription }; + return (dispatch, getState) => { + const params = { subscription }; - if (me) { - const data = pushNotificationsSetting.get(me); - if (data) { - params.data = data; + if (me) { + const data = pushNotificationsSetting.get(me); + if (data) { + params.data = data; + } } - } - return api().post('/api/web/push_subscriptions', params).then(response => response.data); + return dispatch(createPushSubsription(params)); + }; }; // Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload @@ -53,85 +54,86 @@ const supportsPushNotifications = ('serviceWorker' in navigator && 'PushManager' export function register() { return (dispatch, getState) => { const me = getState().get('me'); + const vapidKey = getVapidKey(getState); + dispatch(setBrowserSupport(supportsPushNotifications)); - if (supportsPushNotifications) { - if (!getApplicationServerKey(getState)) { - console.error('The VAPID public key is not set. You will not be able to receive Web Push Notifications.'); - return; - } - - getRegistration() - .then(getPushSubscription) - .then(({ registration, subscription }) => { - if (subscription !== null) { - // We have a subscription, check if it is still valid - const currentServerKey = (new Uint8Array(subscription.options.applicationServerKey)).toString(); - const subscriptionServerKey = urlBase64ToUint8Array(getApplicationServerKey(getState)).toString(); - const serverEndpoint = getState().getIn(['push_notifications', 'subscription', 'endpoint']); - - // If the VAPID public key did not change and the endpoint corresponds - // to the endpoint saved in the backend, the subscription is valid - if (subscriptionServerKey === currentServerKey && subscription.endpoint === serverEndpoint) { - return subscription; - } else { - // Something went wrong, try to subscribe again - return unsubscribe({ registration, subscription }).then(registration => { - return subscribe(registration, getState); - }).then( - subscription => sendSubscriptionToBackend(subscription, me)); - } - } - - // No subscription, try to subscribe - return subscribe(registration, getState).then( - subscription => sendSubscriptionToBackend(subscription, me)); - }) - .then(subscription => { - // If we got a PushSubscription (and not a subscription object from the backend) - // it means that the backend subscription is valid (and was set during hydration) - if (!(subscription instanceof PushSubscription)) { - dispatch(setSubscription(subscription)); - if (me) { - pushNotificationsSetting.set(me, { alerts: subscription.alerts }); - } - } - }) - .catch(error => { - if (error.code === 20 && error.name === 'AbortError') { - console.warn('Your browser supports Web Push Notifications, but does not seem to implement the VAPID protocol.'); - } else if (error.code === 5 && error.name === 'InvalidCharacterError') { - console.error('The VAPID public key seems to be invalid:', getApplicationServerKey(getState)); - } - - // Clear alerts and hide UI settings - dispatch(clearSubscription()); - if (me) { - pushNotificationsSetting.remove(me); - } - - return getRegistration() - .then(getPushSubscription) - .then(unsubscribe); - }) - .catch(console.warn); - } else { + if (!supportsPushNotifications) { console.warn('Your browser does not support Web Push Notifications.'); + return; } + + if (!vapidKey) { + console.error('The VAPID public key is not set. You will not be able to receive Web Push Notifications.'); + return; + } + + getRegistration() + .then(getPushSubscription) + .then(({ registration, subscription }) => { + if (subscription !== null) { + // We have a subscription, check if it is still valid + const currentServerKey = (new Uint8Array(subscription.options.applicationServerKey)).toString(); + const subscriptionServerKey = urlBase64ToUint8Array(vapidKey).toString(); + const serverEndpoint = getState().getIn(['push_notifications', 'subscription', 'endpoint']); + + // If the VAPID public key did not change and the endpoint corresponds + // to the endpoint saved in the backend, the subscription is valid + if (subscriptionServerKey === currentServerKey && subscription.endpoint === serverEndpoint) { + return subscription; + } else { + // Something went wrong, try to subscribe again + return unsubscribe({ registration, subscription }).then(registration => { + return subscribe(registration, getState); + }).then( + subscription => dispatch(sendSubscriptionToBackend(subscription, me))); + } + } + + // No subscription, try to subscribe + return subscribe(registration, getState) + .then(subscription => dispatch(sendSubscriptionToBackend(subscription, me))); + }) + .then(subscription => { + // If we got a PushSubscription (and not a subscription object from the backend) + // it means that the backend subscription is valid (and was set during hydration) + if (!(subscription instanceof PushSubscription)) { + dispatch(setSubscription(subscription)); + if (me) { + pushNotificationsSetting.set(me, { alerts: subscription.alerts }); + } + } + }) + .catch(error => { + if (error.code === 20 && error.name === 'AbortError') { + console.warn('Your browser supports Web Push Notifications, but does not seem to implement the VAPID protocol.'); + } else if (error.code === 5 && error.name === 'InvalidCharacterError') { + console.error('The VAPID public key seems to be invalid:', vapidKey); + } + + // Clear alerts and hide UI settings + dispatch(clearSubscription()); + + if (me) { + pushNotificationsSetting.remove(me); + } + + return getRegistration() + .then(getPushSubscription) + .then(unsubscribe); + }) + .catch(console.warn); }; } export function saveSettings() { - return (_, getState) => { + return (dispatch, getState) => { const state = getState().get('push_notifications'); - const subscription = state.get('subscription'); const alerts = state.get('alerts'); const data = { alerts }; const me = getState().get('me'); - api().put(`/api/web/push_subscriptions/${subscription.get('id')}`, { - data, - }).then(() => { + return dispatch(updatePushSubscription({ data })).then(() => { if (me) { pushNotificationsSetting.set(me, data); } diff --git a/app/soapbox/actions/push_subscriptions.js b/app/soapbox/actions/push_subscriptions.js index d0bfa653d..5b47a4c93 100644 --- a/app/soapbox/actions/push_subscriptions.js +++ b/app/soapbox/actions/push_subscriptions.js @@ -21,6 +21,7 @@ export function createPushSubsription(params) { dispatch({ type: PUSH_SUBSCRIPTION_CREATE_REQUEST, params }); return api(getState).post('/api/v1/push/subscription', params).then(({ data: subscription }) => { dispatch({ type: PUSH_SUBSCRIPTION_CREATE_SUCCESS, params, subscription }); + return subscription; }).catch(error => { dispatch({ type: PUSH_SUBSCRIPTION_CREATE_FAIL, params, error }); }); @@ -38,7 +39,7 @@ export function fetchPushSubsription() { }; } -export function updatePushSubsription(params) { +export function updatePushSubscription(params) { return (dispatch, getState) => { dispatch({ type: PUSH_SUBSCRIPTION_UPDATE_REQUEST, params }); return api(getState).put('/api/v1/push/subscription', params).then(({ data: subscription }) => { diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 134501bbe..83f1bbbec 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -35,6 +35,7 @@ import EmptyPage from 'soapbox/pages/default_page'; import AdminPage from 'soapbox/pages/admin_page'; import RemoteInstancePage from 'soapbox/pages/remote_instance_page'; import { connectUserStream } from '../../actions/streaming'; +import { register as registerPushNotifications } from 'soapbox/actions/push_notifications'; import { Redirect } from 'react-router-dom'; import Icon from 'soapbox/components/icon'; import { isStaff, isAdmin } from 'soapbox/utils/accounts'; @@ -507,6 +508,7 @@ class UI extends React.PureComponent { dispatch(fetchCustomEmojis()); this.connectStreaming(); + dispatch(registerPushNotifications()); } componentDidUpdate(prevProps) { diff --git a/app/soapbox/main.js b/app/soapbox/main.js index 91822efef..4828f996e 100644 --- a/app/soapbox/main.js +++ b/app/soapbox/main.js @@ -1,9 +1,6 @@ 'use strict'; import './precheck'; -// FIXME: Push notifications are temporarily removed -// import * as registerPushNotifications from './actions/push_notifications'; -// import { default as Soapbox, store } from './containers/soapbox'; import { default as Soapbox } from './containers/soapbox'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -27,8 +24,6 @@ function main() { if (NODE_ENV === 'production') { // avoid offline in dev mode because it's harder to debug OfflinePluginRuntime.install(); - // FIXME: Push notifications are temporarily removed - // store.dispatch(registerPushNotifications.register()); } perf.stop('main()'); });