forked from glitch-soc/mastodon
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
65c87ca
commit 0dce26b
Showing
9 changed files
with
228 additions
and
139 deletions.
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
app/javascript/flavours/glitch/actions/push_notifications/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { | ||
SET_BROWSER_SUPPORT, | ||
SET_SUBSCRIPTION, | ||
CLEAR_SUBSCRIPTION, | ||
SET_ALERTS, | ||
setAlerts, | ||
} from './setter'; | ||
import { register, saveSettings } from './registerer'; | ||
|
||
export { | ||
SET_BROWSER_SUPPORT, | ||
SET_SUBSCRIPTION, | ||
CLEAR_SUBSCRIPTION, | ||
SET_ALERTS, | ||
register, | ||
}; | ||
|
||
export function changeAlerts(key, value) { | ||
return dispatch => { | ||
dispatch(setAlerts(key, value)); | ||
dispatch(saveSettings()); | ||
}; | ||
} |
149 changes: 149 additions & 0 deletions
149
app/javascript/flavours/glitch/actions/push_notifications/registerer.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import axios from 'axios'; | ||
import { pushNotificationsSetting } from 'flavours/glitch/util/settings'; | ||
import { setBrowserSupport, setSubscription, clearSubscription } from './setter'; | ||
|
||
// Taken from https://www.npmjs.com/package/web-push | ||
const urlBase64ToUint8Array = (base64String) => { | ||
const padding = '='.repeat((4 - base64String.length % 4) % 4); | ||
const base64 = (base64String + padding) | ||
.replace(/\-/g, '+') | ||
.replace(/_/g, '/'); | ||
|
||
const rawData = window.atob(base64); | ||
const outputArray = new Uint8Array(rawData.length); | ||
|
||
for (let i = 0; i < rawData.length; ++i) { | ||
outputArray[i] = rawData.charCodeAt(i); | ||
} | ||
return outputArray; | ||
}; | ||
|
||
const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content'); | ||
|
||
const getRegistration = () => navigator.serviceWorker.ready; | ||
|
||
const getPushSubscription = (registration) => | ||
registration.pushManager.getSubscription() | ||
.then(subscription => ({ registration, subscription })); | ||
|
||
const subscribe = (registration) => | ||
registration.pushManager.subscribe({ | ||
userVisibleOnly: true, | ||
applicationServerKey: urlBase64ToUint8Array(getApplicationServerKey()), | ||
}); | ||
|
||
const unsubscribe = ({ registration, subscription }) => | ||
subscription ? subscription.unsubscribe().then(() => registration) : registration; | ||
|
||
const sendSubscriptionToBackend = (subscription, me) => { | ||
const params = { subscription }; | ||
|
||
if (me) { | ||
const data = pushNotificationsSetting.get(me); | ||
if (data) { | ||
params.data = data; | ||
} | ||
} | ||
|
||
return axios.post('/api/web/push_subscriptions', params).then(response => response.data); | ||
}; | ||
|
||
// Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload | ||
const supportsPushNotifications = ('serviceWorker' in navigator && 'PushManager' in window && 'getKey' in PushSubscription.prototype); | ||
|
||
export function register () { | ||
return (dispatch, getState) => { | ||
dispatch(setBrowserSupport(supportsPushNotifications)); | ||
const me = getState().getIn(['meta', 'me']); | ||
|
||
if (me && !pushNotificationsSetting.get(me)) { | ||
const alerts = getState().getIn(['push_notifications', 'alerts']); | ||
if (alerts) { | ||
pushNotificationsSetting.set(me, { alerts: alerts }); | ||
} | ||
} | ||
|
||
if (supportsPushNotifications) { | ||
if (!getApplicationServerKey()) { | ||
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()).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(subscribe).then( | ||
subscription => sendSubscriptionToBackend(subscription, me)); | ||
} | ||
} | ||
|
||
// No subscription, try to subscribe | ||
return subscribe(registration).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()); | ||
} | ||
|
||
// Clear alerts and hide UI settings | ||
dispatch(clearSubscription()); | ||
if (me) { | ||
pushNotificationsSetting.remove(me); | ||
} | ||
|
||
try { | ||
getRegistration() | ||
.then(getPushSubscription) | ||
.then(unsubscribe); | ||
} catch (e) { | ||
|
||
} | ||
}); | ||
} else { | ||
console.warn('Your browser does not support Web Push Notifications.'); | ||
} | ||
}; | ||
} | ||
|
||
export function saveSettings() { | ||
return (_, getState) => { | ||
const state = getState().get('push_notifications'); | ||
const subscription = state.get('subscription'); | ||
const alerts = state.get('alerts'); | ||
const data = { alerts }; | ||
|
||
axios.put(`/api/web/push_subscriptions/${subscription.get('id')}`, { | ||
data, | ||
}).then(() => { | ||
const me = getState().getIn(['meta', 'me']); | ||
if (me) { | ||
pushNotificationsSetting.set(me, data); | ||
} | ||
}); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
export default class Settings { | ||
|
||
constructor(keyBase = null) { | ||
this.keyBase = keyBase; | ||
} | ||
|
||
generateKey(id) { | ||
return this.keyBase ? [this.keyBase, `id${id}`].join('.') : id; | ||
} | ||
|
||
set(id, data) { | ||
const key = this.generateKey(id); | ||
try { | ||
const encodedData = JSON.stringify(data); | ||
localStorage.setItem(key, encodedData); | ||
return data; | ||
} catch (e) { | ||
return null; | ||
} | ||
} | ||
|
||
get(id) { | ||
const key = this.generateKey(id); | ||
try { | ||
const rawData = localStorage.getItem(key); | ||
return JSON.parse(rawData); | ||
} catch (e) { | ||
return null; | ||
} | ||
} | ||
|
||
remove(id) { | ||
const data = this.get(id); | ||
if (data) { | ||
const key = this.generateKey(id); | ||
try { | ||
localStorage.removeItem(key); | ||
} catch (e) { | ||
} | ||
} | ||
return data; | ||
} | ||
|
||
} | ||
|
||
export const pushNotificationsSetting = new Settings('mastodon_push_notification_data'); |
Oops, something went wrong.