diff --git a/.env b/.env index 614d1b9..a085b3c 100644 --- a/.env +++ b/.env @@ -14,9 +14,16 @@ NEXT_PUBLIC_PUBLIC_URL=https://localhost:3003 # // owner: dobromir.popov@gmail.com | Специално Свидетелстване София # // https://console.cloud.google.com/apis/credentials/oauthclient/926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com?project=grand-forge-108716 +# callback https://sofia.mwitnessing.com/api/auth/callback/google GOOGLE_ID=926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com GOOGLE_SECRET=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 + +# //https://sofia.mwitnessing.com/api/auth/callback/microsoft +# https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app +# owner: dobromirpopovgateway.onmicrosoft.com dobromir.popov@gateway.one (personal) Doby Popov P One +# callback https://sofia.mwhitnessing.com/api/auth/callback/azure-ad + AZURE_AD_CLIENT_ID=9e13bedd-1f9d-4c23-910e-a806aba308b6 # Application (client) ID AZURE_AD_CLIENT_SECRET=5ic8Q~GQmW-IUhuxzVGx3BE-i30GXDSpjfMHcb~z #client secret value AZURE_AD_TENANT_ID=f69d1a93-bfba-498a-9b60-e87c1bc26276 diff --git a/components/PwaManager.tsx b/components/PwaManager.tsx index c7787c8..1e30d27 100644 --- a/components/PwaManager.tsx +++ b/components/PwaManager.tsx @@ -6,7 +6,7 @@ import e from 'express'; import ProtectedRoute from './protectedRoute'; import { UserRole } from '@prisma/client'; -function PwaManager() { +function PwaManager({ 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 && @@ -21,7 +21,7 @@ function PwaManager() { const [subscription, setSubscription] = useState(null); const [registration, setRegistration] = useState(null); const [notificationPermission, setNotificationPermission] = useState(isSupported() && Notification.permission); - const [subs, setSubs] = useState("") + const [_subs, setSubs] = useState(subs) const { data: session } = useSession(); // let isAdmin = ProtectedRoute.IsInRole(UserRole.ADMIN); @@ -39,14 +39,14 @@ function PwaManager() { // Handle Push Notification Subscription if ('serviceWorker' in navigator && 'PushManager' in window) { - navigator.serviceWorker.ready.then(reg => { - reg.pushManager.getSubscription().then(sub => { + navigator.serviceWorker.ready.then(swreg => { + swreg.pushManager.getSubscription().then(sub => { if (sub) { setSubscription(sub); setIsSubscribed(true); } }); - setRegistration(reg); + setRegistration(swreg); }); } @@ -341,7 +341,7 @@ function PwaManager() { 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})` : ""} + Спри известията на всички мои устройства {_subs != "" ? `(${_subs})` : ""} {isAdmin && @@ -400,3 +400,13 @@ function PwaManager() { } } 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 + } + } +} diff --git a/components/PwaManagerNotifications.tsx b/components/PwaManagerNotifications.tsx new file mode 100644 index 0000000..cc82dfe --- /dev/null +++ b/components/PwaManagerNotifications.tsx @@ -0,0 +1,132 @@ +import React, { useEffect, useState } from 'react'; +import { useSession } from "next-auth/react"; +import common from '../src/helpers/common'; // Ensure this path is correct +import { set } from 'date-fns'; + +function PwaManagerNotifications() { + const [isPermissionGranted, setIsPermissionGranted] = useState(false); + const [subscription, setSubscription] = useState(null); + const [registration, setRegistration] = useState(null); + const { data: session } = useSession(); + + // Check if all required APIs are supported + const isSupported = () => + 'Notification' in window && + 'serviceWorker' in navigator && + 'PushManager' in window; + + + useEffect(() => { + if (isSupported()) { + requestNotificationPermission(null); + } + + }, []); + + const requestNotificationPermission = async (e) => { + if (e) { + e.preventDefault(); + if (Notification.permission === 'denied') { + console.log('Notification permission denied.'); + alert('Известията са забранени. Моля, разрешете известията от браузъра.'); + } + } + setIsPermissionGranted(Notification.permission === 'granted'); + if (Notification.permission === 'default') { + const permission = await Notification.requestPermission(); + if (permission === 'granted') { + console.log('Notification permission granted.'); + getSubscription(); + } else { + console.log('Notification permission denied.'); + } + } + if (Notification.permission === 'granted') { + getSubscription(); + } + }; + const getSubscription = async () => { + // Handle Push Notification Subscription + if ('serviceWorker' in navigator && 'PushManager' in window) { + navigator.serviceWorker.ready.then(registration => { + registration.pushManager.getSubscription().then(existingSubscription => { + if (existingSubscription) { + console.log('Already subscribed.'); + setSubscription(existingSubscription); + } else if (Notification.permission === "granted") { + // Permission was already granted but no subscription exists, so subscribe now + subscribeToNotifications(registration); + } + }); + }); + } + } + + const subscribeToNotifications = async () => { + const registration = await navigator.serviceWorker.ready; + let vapidPublicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY; // Ensure this is configured + 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; + if (!vapidPublicKey) { + throw new Error("Failed to fetch VAPID public key from server."); + } + } + const convertedVapidKey = common.base64ToUint8Array(vapidPublicKey); + + try { + const subscription = await registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: convertedVapidKey + }); + console.log('Subscribed to push notifications:', subscription); + setSubscription(subscription); + sendSubscriptionToServer(subscription); + } catch (error) { + console.error('Failed to subscribe to push notifications:', error); + } + }; + + const sendSubscriptionToServer = async (sub) => { + 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.'); + } + else { + console.log('Subscription data saved on server.'); + const s = await response.json(); + setSubscription(sub); + console.log('Web push subscribed!'); + } + }); + } + }; + + + return ( +
+ +
+ ); +} + + +export default PwaManagerNotifications; diff --git a/components/sidebar.tsx b/components/sidebar.tsx index c34c5b9..73ff708 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -6,6 +6,7 @@ import sidemenu, { footerMenu } from './sidemenuData.js'; // Move sidemenu data import axiosInstance from "src/axiosSecure"; import common from "src/helpers/common"; import LanguageSwitcher from "./languageSwitcher"; +import PwaManagerNotifications from "./PwaManagerNotifications"; import { useTranslations } from 'next-intl'; import { getTranslations } from 'next-intl/server'; import ProtectedPage from "pages/examples/protected"; @@ -203,6 +204,8 @@ function UserDetails({ session }) {

{session.user.name}

{session.user.role}

+ + { e.preventDefault(); signOut(); }}> {/* {t('logout')} */} изход