Files
mwitnessing/components/PwaManager.tsx
2024-06-28 19:47:59 +03:00

478 lines
21 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from 'react';
import common from '../src/helpers/common'; // Ensure this path is correct
//use session to get user role
import { useSession } from "next-auth/react"
import e from 'express';
import ProtectedRoute from './protectedRoute';
import { UserRole } from '@prisma/client';
function PwaManager({ userId, subs }) {
//ToDo: for iOS, try to use apn? https://github.com/node-apn/node-apn/blob/master/doc/apn.markdown
const isSupported = () =>
'Notification' in window &&
'serviceWorker' in navigator &&
'PushManager' in window
const [inProgress, setInProgress] = useState(false)
const [deferredPrompt, setDeferredPrompt] = useState(null);
const [isPWAInstalled, setIsPWAInstalled] = useState(false);
const [isStandAlone, setIsStandAlone] = useState(false);
const [isSubscribed, setIsSubscribed] = useState(false);
const [subscription, setSubscription] = useState(null);
const [registration, setRegistration] = useState(null);
const [notificationPermission, setNotificationPermission] = useState(isSupported() && Notification.permission);
const [_subs, setSubs] = useState(subs)
const { data: session } = useSession();
// let isAdmin = ProtectedRoute.IsInRole(UserRole.ADMIN);
let isAdmin = false;
if (session) {
isAdmin = session.user.role === UserRole.ADMIN;
}
// Handle PWA installation
useEffect(() => {
if (isSupported()) {
setNotificationPermission(Notification.permission);
getSubscriptionCount();
}
// Handle Push Notification Subscription
if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.ready.then(swreg => {
swreg.pushManager.getSubscription().then(sub => {
if (sub) {
setSubscription(sub);
setIsSubscribed(true);
}
});
setRegistration(swreg);
});
}
// Check if the app is running in standalone mode
// const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
// if (isStandalone) {
// console.log('Running in standalone mode');
// setIsPWAInstalled(true);
// }
if (window.matchMedia('(display-mode: standalone)').matches) {
setIsStandAlone(true);
}
const handleBeforeInstallPrompt = (e) => {
e.preventDefault();
setDeferredPrompt(e);
};
const handleAppInstalled = () => {
setIsPWAInstalled(true);
};
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
window.addEventListener('appinstalled', handleAppInstalled);
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
window.removeEventListener('appinstalled', handleAppInstalled);
};
}, []);
const installPWA = async (e) => {
console.log('Attempting to install PWA');
e.preventDefault(); // Prevent default button action
if (deferredPrompt) {
console.log('Prompting install');
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log('Installation outcome:', outcome);
if (outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
setIsPWAInstalled(true);
} else {
console.log('User dismissed the A2HS prompt');
}
setDeferredPrompt(null); // Clear the deferred prompt to manage its lifecycle
} else {
console.log('No deferred prompt available');
}
};
const subscribeToNotifications = async (e) => {
try {
e.preventDefault();
if (!navigator.serviceWorker) {
console.error('Service worker is not supported by this browser.');
return;
}
const registration = await navigator.serviceWorker.ready;
if (!registration) {
console.error('Service worker registration not found.');
return;
}
let vapidPublicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;
if (!vapidPublicKey) {
// Fetch the public key from the server if not present in env variables
const response = await fetch('/api/notify', { method: 'GET' });
const responseData = await response.json();
vapidPublicKey = responseData.pk;
setSubs(responseData.subs);
if (!vapidPublicKey) {
throw new Error("Failed to fetch VAPID public key from server.");
}
}
const sub = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: common.base64ToUint8Array(vapidPublicKey)
});
// Call your API to save subscription data on server
if (session.user?.id != null) {
await fetch(`/api/notify`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ subscription: sub, id: session.user.id })
}).then(async response => {
if (!response.ok) {
//throw new Error('Failed to save subscription data on server.');
console.log('Failed to save subscription data on server.');
}
else {
console.log('Subscription data saved on server.');
const s = await response.json();
setSubs(s.subs);
setSubscription(sub);
setIsSubscribed(true);
console.log('Web push subscribed!');
}
});
}
console.log(sub);
} catch (error) {
console.error('Error subscribing to notifications:', error);
}
};
const unsubscribeFromNotifications = async (e) => {
try {
e.preventDefault();
if (subscription) {
await subscription.unsubscribe();
// Call your API to delete or invalidate subscription data on server
setSubscription(null);
setIsSubscribed(false);
if (session?.user?.id != null) {
await fetch(`/api/notify`,
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
//send the current subscription to be removed
body: JSON.stringify({ id: session.user.id, subscriptionId: subscription.endpoint })
}
).then(async (response) => {
if (!response.ok) {
throw new Error('Failed to delete subscription data on server.');
}
else {
console.log('Subscription data deleted on server.');
const s = await response.json();
setSubs(s.subs);
}
});
}
console.log('Web push unsubscribed!');
}
} catch (error) {
console.error('Error unsubscribing from notifications:', error);
}
};
const getSubscriptionCount = async () => {
try {
const response = await fetch('/api/notify?id=' + session.user.id, { method: 'GET' });
if (!response.ok) {
throw new Error('Failed to fetch subscription data.');
}
const result = await response.json();
setSubs(result.subs);
} catch (error) {
console.error('Error fetching subscription data:', error);
}
};
// Function to request push notification permission
const requestNotificationPermission = async (e) => {
e.preventDefault();
if (isSupported()) {
const permission = await Notification.requestPermission();
setNotificationPermission(permission);
if (permission === "granted") {
// User granted permission
subscribeToNotifications(null); // Pass the required argument here
} else {
// User denied or dismissed permission
console.log("Push notifications permission denied.");
}
}
else {
console.error('Web push not supported');
}
};
// Function to toggle push notifications
const togglePushNotifications = async (e) => {
e.preventDefault();
if (notificationPermission === "granted") {
// If already subscribed, unsubscribe
unsubscribeFromNotifications(null); // Pass null as the argument
} else if (notificationPermission === "default" || notificationPermission === "denied") {
// Request permission if not already granted
await requestNotificationPermission(e);
}
};
const sendTestNotification = async (e) => {
e.preventDefault();
if (!subscription) {
console.error('Web push not subscribed');
return;
}
await fetch('/api/notify', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(
{
id: session.user.id,
title: "Тестово уведомление",
message: "Това е тестово уведомление",
actions: [{ action: 'test', title: 'Тест', icon: '✅' },
{ action: 'close', title: 'Затвори', icon: '❌' }]
})
});
/*
await fetch('/api/notify', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: pub.id,
message: "Тестово съобщение",
title: "Това е тестово съобщение от https://sofia.mwitnessing.com",
actions: [
{ action: 'OK', title: 'OK', icon: '✅' },
{ action: 'close', title: 'Затвори', icon: '❌' }
]
// actions: [
// {
// title: 'Open URL',
// action: 'open_url',
// icon: '/images/open-url.png'
// },
// {
// title: 'Dismiss',
// action: 'dismiss',
// icon: '/images/dismiss.png'
// }
// ]
})
})
*/
};
// async function sendTestReminder(event: MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> {
// event.preventDefault();
// if (!subscription) {
// console.error('Web push not subscribed');
// return;
// }
// await fetch('/api/notify', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify({
// broadcast: true,
// message: "Мили братя, искаме да ви напомним да ни изпратите вашите предпочитания за юни до 25-то число като използвате меню 'Възможности'. Ако имате проблем, моля пишете ни на 'specialnosvidetelstvanesofia@gmail.com'"
// })
// });
// }
// async function sendTestCoverMe(event: MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> {
// event.preventDefault();
// if (!subscription) {
// console.error('Web push not subscribed');
// return;
// }
// await fetch('/api/notify', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify({
// id: session.user.id,
// message: "Брат ТЕСТ търси заместник за 24-ти май от 10:00 ч. Можеш ли да го покриеш?",
// //use fontawesome icons for actions
// actions: [{ action: 'covermeaccepted', title: 'Да ', icon: '✅' }]
// })
// });
// }
async function deleteAllSubscriptions(event: MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> {
event.preventDefault();
await fetch(`/api/notify`,
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
//send the current subscription to be removed
body: JSON.stringify({ id: session.user.id })
}
).then(async response => {
if (!response.ok) {
throw new Error('Failed to delete subscription data on server.');
}
else {
console.log('ALL subscriptions data deleted on server.');
if (subscription) {
await subscription.unsubscribe();
}
setSubs("");
setSubscription(null);
setIsSubscribed(false);
}
});
}
if (!isSupported()) {
return (
<div>
<p>Това устройство не поддържа нотификации</p>
</div>
);
}
else {
return (
<>
<div>
<h1>{isAdmin && " PWA (admin)"}</h1>
{!isStandAlone && !isPWAInstalled && (
<button
onClick={installPWA}
className="bg-blue-500 hover:bg-blue-700 text-white text-xs py-1 px-2 rounded-full focus:outline-none focus:shadow-outline transition duration-150 ease-in-out"
>
Инсталирай приложението
</button>
)}
{isPWAInstalled && <p>Инсталирано!</p>}
{/* {isStandAlone && <p>PWA App</p>} */}
<button
onClick={isSubscribed ? unsubscribeFromNotifications : subscribeToNotifications}
disabled={false} // Since the button itself acts as a toggle, the disabled attribute might not be needed
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${isSubscribed ? 'bg-red-500 hover:bg-red-700 text-white' : 'bg-green-500 hover:bg-green-700 text-white'}`} >
{isSubscribed ? 'Спри известията' : 'Показвай известия'}
</button>
<button
onClick={deleteAllSubscriptions}
className="text-xs py-1 px-2 rounded-full focus:outline-none bg-red-500 hover:bg-red-700 text-white"
>
Спри известията на всички мои устройства {_subs != "" ? `(${_subs})` : ""}
</button>
<button
onClick={sendTestNotification}
disabled={!isSubscribed}
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${!isSubscribed ? 'cursor-not-allowed bg-gray-300 text-gray-500' : 'bg-yellow-500 hover:bg-yellow-600 text-white'
}`}
>
Тестово уведомление
</button>
</div >
{isAdmin &&
<div>
{/* <button
onClick={sendTestReminder}
disabled={!isSubscribed}
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${!isSubscribed ? 'cursor-not-allowed bg-gray-300 text-gray-500' : 'bg-yellow-500 hover:bg-yellow-600 text-white'
}`}
>
Broadcast Reminder
</button> */}
{/* <button
onClick={sendTestCoverMe}
disabled={!isSubscribed}
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${!isSubscribed ? 'cursor-not-allowed bg-gray-300 text-gray-500' : 'bg-yellow-500 hover:bg-yellow-600 text-white'
}`}
>
Broadcast CoverMe
</button> */}
</div>
}
{
notificationPermission !== "granted" && (
<button
onClick={togglePushNotifications}
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${notificationPermission === "denied" ? 'bg-red-500 hover:bg-red-700 text-white' : 'bg-green-500 hover:bg-green-700 text-white'
}`}
>
{notificationPermission === "denied" ? 'Notifications Denied!' : 'Enable Notifications'}
</button>
)
}
{
isAdmin && <div>
<div>
<a href="https://t.me/mwhitnessing_bot" className="inline-flex items-center ml-4" target="_blank">
<img src="/content/icons/telegram-svgrepo-com.svg" alt="Телеграм" width="32" height="32" className="align-middle" />
<span className="align-middle">Телеграм</span>
</a>
<a href="/api/auth/apple-signin" className="inline-flex items-center ml-4 bg-gray-100 button" target="_blank">
<span className="align-middle">Apple sign-in</span>
</a>
</div>
</div>
}
</>
);
}
}
export default PwaManager;
//get server side props - subs count
export const getServerSideProps = async (context) => {
//ToDo: get the number of subscriptions from the database
return {
props: {
subs: 0
}
}
}