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