Revise to use atom values and simplify; modify to check for valid subscription via self-heal; modify togglePusher to defer to the users push notif setting before visibility changes

This commit is contained in:
Gigiaj 2025-06-29 23:27:55 -05:00
parent b689089599
commit cfea393f2d

View file

@ -9,125 +9,108 @@ export async function requestBrowserNotificationPermission(): Promise<Notificati
const permission: NotificationPermission = await Notification.requestPermission();
return permission;
} catch (error) {
console.error('Error requesting notification permission:', error);
return 'denied';
}
}
export async function enablePushNotifications(
mx: MatrixClient,
clientConfig: ClientConfig
clientConfig: ClientConfig,
pushSubscriptionAtom: Atom<PushSubscriptionJSON | null, [PushSubscription | null], void>
): Promise<void> {
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
throw new Error('Push messaging is not supported in this browser.');
}
if (!mx || !mx.getHomeserverUrl() || !mx.getAccessToken()) {
throw new Error('Matrix client is not properly initialized or authenticated.');
}
if (
!clientConfig.pushNotificationDetails?.vapidPublicKey ||
!clientConfig.pushNotificationDetails?.webPushAppID ||
!clientConfig.pushNotificationDetails?.pushNotifyUrl
) {
throw new Error('One or more push configuration constants are missing.');
}
const [pushSubAtom, setPushSubscription] = pushSubscriptionAtom;
const registration = await navigator.serviceWorker.ready;
const currentBrowserSub = await registration.pushManager.getSubscription();
let subscription = await registration.pushManager.getSubscription();
if (!subscription) {
try {
subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: clientConfig.pushNotificationDetails?.vapidPublicKey,
});
} catch (subscribeError: any) {
if (Notification.permission === 'denied') {
throw new Error('Notification permission denied. Please enable in browser settings.');
}
throw new Error(`Failed to subscribe: ${subscribeError.message || String(subscribeError)}`);
}
}
const pwaAppIdForPlatform = clientConfig.pushNotificationDetails?.webPushAppID;
if (!pwaAppIdForPlatform) {
await subscription.unsubscribe();
throw new Error('Could not determine PWA App ID for push endpoint.');
}
const subJson = subscription.toJSON();
const p256dhKey = subJson.keys?.p256dh;
const authKey = subJson.keys?.auth;
if (!p256dhKey || !authKey) {
await subscription.unsubscribe();
throw new Error('Push subscription keys (p256dh, auth) are missing.');
}
/* Self-Healing Check. Effectively checks if the browser has invalidated our subscription and recreates it
only when necessary. This prevents us from needing an external call to get back the web push info.
*/
if (currentBrowserSub && pushSubAtom && currentBrowserSub.endpoint === pushSubAtom.endpoint) {
console.error('Valid saved subscription found. Ensuring pusher is enabled on homeserver...');
const pusherData = {
kind: 'http' as const,
app_id: pwaAppIdForPlatform,
pushkey: p256dhKey,
app_id: clientConfig.pushNotificationDetails?.webPushAppID,
pushkey: pushSubAtom.keys!.p256dh!,
app_display_name: 'Cinny',
device_display_name:
(await mx.getDevice(mx.getDeviceId() ?? '')).display_name ?? 'Unknown device',
device_display_name: 'This Browser',
lang: navigator.language || 'en',
data: {
url: clientConfig.pushNotificationDetails?.pushNotifyUrl,
format: 'event_id_only' as const,
endpoint: subscription.endpoint,
p256dh: p256dhKey,
auth: authKey,
endpoint: pushSubAtom.endpoint,
p256dh: pushSubAtom.keys!.p256dh!,
auth: pushSubAtom.keys!.auth!,
},
append: false,
};
navigator.serviceWorker.controller?.postMessage({
url: mx.baseUrl,
type: 'togglePush',
pusherData,
token: mx.getAccessToken(),
});
return;
}
console.error('No valid saved subscription. Performing full, new subscription...');
if (currentBrowserSub) {
await currentBrowserSub.unsubscribe();
}
const newSubscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: clientConfig.pushNotificationDetails?.vapidPublicKey,
});
setPushSubscription(newSubscription);
const subJson = newSubscription.toJSON();
const pusherData = {
kind: 'http' as const,
app_id: clientConfig.pushNotificationDetails?.webPushAppID,
pushkey: subJson.keys!.p256dh!,
app_display_name: 'Cinny',
device_display_name:
(await mx.getDevice(mx.getDeviceId() ?? '')).display_name ?? 'Unknown Device',
lang: navigator.language || 'en',
data: {
url: clientConfig.pushNotificationDetails?.pushNotifyUrl,
format: 'event_id_only' as const,
endpoint: newSubscription.endpoint,
p256dh: subJson.keys!.p256dh!,
auth: subJson.keys!.auth!,
},
enabled: false,
'org.matrix.msc3881.enabled': false,
'org.matrix.msc3881.device_id': mx.getDeviceId(),
append: false,
};
try {
navigator.serviceWorker.controller?.postMessage({
url: mx.baseUrl,
type: 'togglePush',
pusherData,
token: mx.getAccessToken(),
});
} catch (pusherError: any) {
await subscription.unsubscribe();
throw new Error(
`Failed to set up push with Matrix server: ${pusherError.message || String(pusherError)}`
);
}
}
/**
* Disables push notifications by telling the homeserver to delete the pusher,
* but keeps the browser subscription locally for a fast re-enable.
*/
export async function disablePushNotifications(
mx: MatrixClient,
clientConfig: ClientConfig
clientConfig: ClientConfig,
pushSubscriptionAtom: Atom<PushSubscriptionJSON | null, [PushSubscription | null], void>
): Promise<void> {
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
return;
}
const [pushSubAtom] = pushSubscriptionAtom;
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.getSubscription();
if (!subscription) {
return;
}
const pwaAppIdForPlatform = clientConfig.pushNotificationDetails?.webPushAppID;
await subscription.unsubscribe();
const subJson = subscription.toJSON();
const p256dhKey = subJson.keys?.p256dh;
const authKey = subJson.keys?.auth;
if (mx && mx.getAccessToken() && pwaAppIdForPlatform) {
const pusherData = {
kind: null,
app_id: pwaAppIdForPlatform,
pushkey: p256dhKey,
app_id: clientConfig.pushNotificationDetails?.webPushAppID,
pushkey: pushSubAtom?.keys?.p256dh,
};
navigator.serviceWorker.controller?.postMessage({
@ -137,29 +120,19 @@ export async function disablePushNotifications(
token: mx.getAccessToken(),
});
}
}
export async function deRegisterAllPushers(mx: MatrixClient): Promise<void> {
const response = await mx.getPushers();
const pushers = response.pushers || [];
if (pushers.length === 0) {
return;
}
if (pushers.length === 0) return;
const deletionPromises = pushers.map((pusher) => {
const pusherToDelete: Partial<IPusher> & { kind: null; app_id: string; pushkey: string } = {
const pusherToDelete = {
kind: null,
app_id: pusher.app_id,
pushkey: pusher.pushkey,
...(pusher.data && { data: pusher.data }),
...(pusher.profile_tag && { profile_tag: pusher.profile_tag }),
};
return mx
.setPusher(pusherToDelete as any)
.then(() => ({ status: 'fulfilled', app_id: pusher.app_id }))
.catch((err) => ({ status: 'rejected', app_id: pusher.app_id, error: err }));
return mx.setPusher(pusherToDelete as any);
});
await Promise.allSettled(deletionPromises);
@ -168,11 +141,15 @@ export async function deRegisterAllPushers(mx: MatrixClient): Promise<void> {
export async function togglePusher(
mx: MatrixClient,
clientConfig: ClientConfig,
visible: boolean
visible: boolean,
usePushNotifications: boolean,
pushSubscriptionAtom: Atom<PushSubscriptionJSON | null, [PushSubscription | null], void>
): Promise<void> {
if (usePushNotifications) {
if (visible) {
disablePushNotifications(mx, clientConfig);
await disablePushNotifications(mx, clientConfig, pushSubscriptionAtom);
} else {
enablePushNotifications(mx, clientConfig);
await enablePushNotifications(mx, clientConfig, pushSubscriptionAtom);
}
}
}