mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-09-15 15:22:25 +03:00
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:
parent
b689089599
commit
cfea393f2d
1 changed files with 85 additions and 108 deletions
|
@ -9,125 +9,108 @@ 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,
|
|
||||||
});
|
|
||||||
} 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.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const pusherData = {
|
const pusherData = {
|
||||||
kind: 'http' as const,
|
kind: 'http' as const,
|
||||||
app_id: pwaAppIdForPlatform,
|
app_id: clientConfig.pushNotificationDetails?.webPushAppID,
|
||||||
pushkey: p256dhKey,
|
pushkey: pushSubAtom.keys!.p256dh!,
|
||||||
app_display_name: 'Cinny',
|
app_display_name: 'Cinny',
|
||||||
device_display_name:
|
device_display_name: 'This Browser',
|
||||||
(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: pushSubAtom.endpoint,
|
||||||
p256dh: p256dhKey,
|
p256dh: pushSubAtom.keys!.p256dh!,
|
||||||
auth: authKey,
|
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,
|
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 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 = {
|
const pusherData = {
|
||||||
kind: null,
|
kind: null,
|
||||||
app_id: pwaAppIdForPlatform,
|
app_id: clientConfig.pushNotificationDetails?.webPushAppID,
|
||||||
pushkey: p256dhKey,
|
pushkey: pushSubAtom?.keys?.p256dh,
|
||||||
};
|
};
|
||||||
|
|
||||||
navigator.serviceWorker.controller?.postMessage({
|
navigator.serviceWorker.controller?.postMessage({
|
||||||
|
@ -136,30 +119,20 @@ export async function disablePushNotifications(
|
||||||
pusherData,
|
pusherData,
|
||||||
token: mx.getAccessToken(),
|
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 (usePushNotifications) {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
disablePushNotifications(mx, clientConfig);
|
await disablePushNotifications(mx, clientConfig, pushSubscriptionAtom);
|
||||||
} else {
|
} else {
|
||||||
enablePushNotifications(mx, clientConfig);
|
await enablePushNotifications(mx, clientConfig, pushSubscriptionAtom);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue