mirror of
https://github.com/cheeaun/phanpy.git
synced 2024-12-24 19:28:15 +03:00
234 lines
6.5 KiB
JavaScript
234 lines
6.5 KiB
JavaScript
|
// Utils for push notifications
|
||
|
import { api } from './api';
|
||
|
import { getCurrentAccount } from './store-utils';
|
||
|
|
||
|
// Subscription is an object with the following structure:
|
||
|
// {
|
||
|
// data: {
|
||
|
// alerts: {
|
||
|
// admin: {
|
||
|
// report: boolean,
|
||
|
// signUp: boolean,
|
||
|
// },
|
||
|
// favourite: boolean,
|
||
|
// follow: boolean,
|
||
|
// mention: boolean,
|
||
|
// poll: boolean,
|
||
|
// reblog: boolean,
|
||
|
// status: boolean,
|
||
|
// update: boolean,
|
||
|
// }
|
||
|
// },
|
||
|
// policy: "all" | "followed" | "follower" | "none",
|
||
|
// subscription: {
|
||
|
// endpoint: string,
|
||
|
// keys: {
|
||
|
// auth: string,
|
||
|
// p256dh: string,
|
||
|
// },
|
||
|
// },
|
||
|
// }
|
||
|
|
||
|
// Back-end CRUD
|
||
|
// =============
|
||
|
|
||
|
function createBackendPushSubscription(subscription) {
|
||
|
const { masto } = api();
|
||
|
return masto.v1.webPushSubscriptions.create(subscription);
|
||
|
}
|
||
|
|
||
|
function fetchBackendPushSubscription() {
|
||
|
const { masto } = api();
|
||
|
return masto.v1.webPushSubscriptions.fetch();
|
||
|
}
|
||
|
|
||
|
function updateBackendPushSubscription(subscription) {
|
||
|
const { masto } = api();
|
||
|
return masto.v1.webPushSubscriptions.update(subscription);
|
||
|
}
|
||
|
|
||
|
function removeBackendPushSubscription() {
|
||
|
const { masto } = api();
|
||
|
return masto.v1.webPushSubscriptions.remove();
|
||
|
}
|
||
|
|
||
|
// Front-end
|
||
|
// =========
|
||
|
|
||
|
export function isPushSupported() {
|
||
|
return 'serviceWorker' in navigator && 'PushManager' in window;
|
||
|
}
|
||
|
|
||
|
export function getRegistration() {
|
||
|
// return navigator.serviceWorker.ready;
|
||
|
return navigator.serviceWorker.getRegistration();
|
||
|
}
|
||
|
|
||
|
async function getSubscription() {
|
||
|
const registration = await getRegistration();
|
||
|
const subscription = registration
|
||
|
? await registration.pushManager.getSubscription()
|
||
|
: undefined;
|
||
|
return { registration, subscription };
|
||
|
}
|
||
|
|
||
|
function 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;
|
||
|
}
|
||
|
|
||
|
// Front-end <-> back-end
|
||
|
// ======================
|
||
|
|
||
|
export async function initSubscription() {
|
||
|
if (!isPushSupported()) return;
|
||
|
const { subscription } = await getSubscription();
|
||
|
let backendSubscription = null;
|
||
|
try {
|
||
|
backendSubscription = await fetchBackendPushSubscription();
|
||
|
} catch (err) {
|
||
|
if (/(not found|unknown)/i.test(err.message)) {
|
||
|
// No subscription found
|
||
|
} else {
|
||
|
// Other error
|
||
|
throw err;
|
||
|
}
|
||
|
}
|
||
|
console.log('INIT subscription', {
|
||
|
subscription,
|
||
|
backendSubscription,
|
||
|
});
|
||
|
|
||
|
// Check if the subscription changed
|
||
|
if (backendSubscription && subscription) {
|
||
|
const sameEndpoint = backendSubscription.endpoint === subscription.endpoint;
|
||
|
const { vapidKey } = getCurrentAccount();
|
||
|
const sameKey = backendSubscription.serverKey === vapidKey;
|
||
|
if (!sameEndpoint) {
|
||
|
throw new Error('Backend subscription endpoint changed');
|
||
|
}
|
||
|
if (sameKey) {
|
||
|
// Subscription didn't change
|
||
|
} else {
|
||
|
// Subscription changed
|
||
|
console.error('🔔 Subscription changed', {
|
||
|
sameEndpoint,
|
||
|
serverKey: backendSubscription.serverKey,
|
||
|
vapIdKey: vapidKey,
|
||
|
endpoint1: backendSubscription.endpoint,
|
||
|
endpoint2: subscription.endpoint,
|
||
|
sameKey,
|
||
|
key1: backendSubscription.serverKey,
|
||
|
key2: vapidKey,
|
||
|
});
|
||
|
throw new Error('Backend subscription key and vapid key changed');
|
||
|
// Only unsubscribe from backend, not from browser
|
||
|
// await removeBackendPushSubscription();
|
||
|
// // Now let's resubscribe
|
||
|
// // NOTE: I have no idea if this works
|
||
|
// return await updateSubscription({
|
||
|
// data: backendSubscription.data,
|
||
|
// policy: backendSubscription.policy,
|
||
|
// });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (subscription && !backendSubscription) {
|
||
|
// check if account's vapidKey is same as subscription's applicationServerKey
|
||
|
const { vapidKey } = getCurrentAccount();
|
||
|
const { applicationServerKey } = subscription.options;
|
||
|
const vapidKeyStr = urlBase64ToUint8Array(vapidKey).toString();
|
||
|
const applicationServerKeyStr = new Uint8Array(
|
||
|
applicationServerKey,
|
||
|
).toString();
|
||
|
const sameKey = vapidKeyStr === applicationServerKeyStr;
|
||
|
if (sameKey) {
|
||
|
// Subscription didn't change
|
||
|
} else {
|
||
|
// Subscription changed
|
||
|
console.error('🔔 Subscription changed', {
|
||
|
vapidKeyStr,
|
||
|
applicationServerKeyStr,
|
||
|
sameKey,
|
||
|
});
|
||
|
// Unsubscribe since backend doesn't have a subscription
|
||
|
await subscription.unsubscribe();
|
||
|
throw new Error('Subscription key and vapid key changed');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check if backend subscription returns 404
|
||
|
// if (subscription && !backendSubscription) {
|
||
|
// // Re-subscribe to backend
|
||
|
// backendSubscription = await createBackendPushSubscription({
|
||
|
// subscription,
|
||
|
// data: {},
|
||
|
// policy: 'all',
|
||
|
// });
|
||
|
// }
|
||
|
|
||
|
return { subscription, backendSubscription };
|
||
|
}
|
||
|
|
||
|
export async function updateSubscription({ data, policy }) {
|
||
|
console.log('🔔 Updating subscription', { data, policy });
|
||
|
if (!isPushSupported()) return;
|
||
|
let { registration, subscription } = await getSubscription();
|
||
|
let backendSubscription = null;
|
||
|
|
||
|
if (subscription) {
|
||
|
try {
|
||
|
backendSubscription = await updateBackendPushSubscription({
|
||
|
data,
|
||
|
policy,
|
||
|
});
|
||
|
// TODO: save subscription in user settings
|
||
|
} catch (error) {
|
||
|
// Backend doesn't have a subscription for this user
|
||
|
// Create a new one
|
||
|
backendSubscription = await createBackendPushSubscription({
|
||
|
subscription,
|
||
|
data,
|
||
|
policy,
|
||
|
});
|
||
|
// TODO: save subscription in user settings
|
||
|
}
|
||
|
} else {
|
||
|
// User is not subscribed
|
||
|
const { vapidKey } = getCurrentAccount();
|
||
|
if (!vapidKey) throw new Error('No server key found');
|
||
|
subscription = await registration.pushManager.subscribe({
|
||
|
userVisibleOnly: true,
|
||
|
applicationServerKey: urlBase64ToUint8Array(vapidKey),
|
||
|
});
|
||
|
backendSubscription = await createBackendPushSubscription({
|
||
|
subscription,
|
||
|
data,
|
||
|
policy,
|
||
|
});
|
||
|
// TODO: save subscription in user settings
|
||
|
}
|
||
|
|
||
|
return { subscription, backendSubscription };
|
||
|
}
|
||
|
|
||
|
export async function removeSubscription() {
|
||
|
if (!isPushSupported()) return;
|
||
|
const { subscription } = await getSubscription();
|
||
|
if (subscription) {
|
||
|
await removeBackendPushSubscription();
|
||
|
await subscription.unsubscribe();
|
||
|
}
|
||
|
}
|