PwaManagerNotifications automatic permission request and subscription

This commit is contained in:
Dobromir Popov
2024-05-08 15:50:51 +03:00
parent 0e8d1284c2
commit 71b5f198ed
4 changed files with 158 additions and 6 deletions

7
.env
View File

@ -14,9 +14,16 @@ NEXT_PUBLIC_PUBLIC_URL=https://localhost:3003
# // owner: dobromir.popov@gmail.com | Специално Свидетелстване София # // owner: dobromir.popov@gmail.com | Специално Свидетелстване София
# // https://console.cloud.google.com/apis/credentials/oauthclient/926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com?project=grand-forge-108716 # // 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_ID=926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com
GOOGLE_SECRET=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 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_ID=9e13bedd-1f9d-4c23-910e-a806aba308b6 # Application (client) ID
AZURE_AD_CLIENT_SECRET=5ic8Q~GQmW-IUhuxzVGx3BE-i30GXDSpjfMHcb~z #client secret value AZURE_AD_CLIENT_SECRET=5ic8Q~GQmW-IUhuxzVGx3BE-i30GXDSpjfMHcb~z #client secret value
AZURE_AD_TENANT_ID=f69d1a93-bfba-498a-9b60-e87c1bc26276 AZURE_AD_TENANT_ID=f69d1a93-bfba-498a-9b60-e87c1bc26276

View File

@ -6,7 +6,7 @@ import e from 'express';
import ProtectedRoute from './protectedRoute'; import ProtectedRoute from './protectedRoute';
import { UserRole } from '@prisma/client'; 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 //ToDo: for iOS, try to use apn? https://github.com/node-apn/node-apn/blob/master/doc/apn.markdown
const isSupported = () => const isSupported = () =>
'Notification' in window && 'Notification' in window &&
@ -21,7 +21,7 @@ function PwaManager() {
const [subscription, setSubscription] = useState(null); const [subscription, setSubscription] = useState(null);
const [registration, setRegistration] = useState(null); const [registration, setRegistration] = useState(null);
const [notificationPermission, setNotificationPermission] = useState(isSupported() && Notification.permission); const [notificationPermission, setNotificationPermission] = useState(isSupported() && Notification.permission);
const [subs, setSubs] = useState("") const [_subs, setSubs] = useState(subs)
const { data: session } = useSession(); const { data: session } = useSession();
// let isAdmin = ProtectedRoute.IsInRole(UserRole.ADMIN); // let isAdmin = ProtectedRoute.IsInRole(UserRole.ADMIN);
@ -39,14 +39,14 @@ function PwaManager() {
// Handle Push Notification Subscription // Handle Push Notification Subscription
if ('serviceWorker' in navigator && 'PushManager' in window) { if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.ready.then(reg => { navigator.serviceWorker.ready.then(swreg => {
reg.pushManager.getSubscription().then(sub => { swreg.pushManager.getSubscription().then(sub => {
if (sub) { if (sub) {
setSubscription(sub); setSubscription(sub);
setIsSubscribed(true); setIsSubscribed(true);
} }
}); });
setRegistration(reg); setRegistration(swreg);
}); });
} }
@ -341,7 +341,7 @@ function PwaManager() {
onClick={deleteAllSubscriptions} onClick={deleteAllSubscriptions}
className="text-xs py-1 px-2 rounded-full focus:outline-none bg-red-500 hover:bg-red-700 text-white" 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})` : ""}
</button> </button>
</div> </div>
{isAdmin && {isAdmin &&
@ -400,3 +400,13 @@ function PwaManager() {
} }
} }
export default 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
}
}
}

View File

@ -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 (
<div>
<button
onClick={isPermissionGranted ? undefined : requestNotificationPermission}
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out
${isPermissionGranted ? 'bg-blue-400 text-white'
: 'border border-blue-300 text-blue-300 bg-transparent hover:bg-blue-100'
}`}
disabled={isPermissionGranted}
>
{!isSupported() ? "не поддъжа известия" : (isPermissionGranted ? 'Известията включени' : 'Включи известията')}
</button>
</div >
);
}
export default PwaManagerNotifications;

View File

@ -6,6 +6,7 @@ import sidemenu, { footerMenu } from './sidemenuData.js'; // Move sidemenu data
import axiosInstance from "src/axiosSecure"; import axiosInstance from "src/axiosSecure";
import common from "src/helpers/common"; import common from "src/helpers/common";
import LanguageSwitcher from "./languageSwitcher"; import LanguageSwitcher from "./languageSwitcher";
import PwaManagerNotifications from "./PwaManagerNotifications";
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
import ProtectedPage from "pages/examples/protected"; import ProtectedPage from "pages/examples/protected";
@ -203,6 +204,8 @@ function UserDetails({ session }) {
<div className="overflow-hidden"> <div className="overflow-hidden">
<p className="mx-1 mt-1 text-sm font-medium text-gray-800 dark:text-gray-200">{session.user.name}</p> <p className="mx-1 mt-1 text-sm font-medium text-gray-800 dark:text-gray-200">{session.user.name}</p>
<p className="mx-1 mt-1 text-sm font-medium text-gray-600 dark:text-gray-400">{session.user.role}</p> <p className="mx-1 mt-1 text-sm font-medium text-gray-600 dark:text-gray-400">{session.user.role}</p>
<PwaManagerNotifications />
<a href="/api/auth/signout" className={styles.button} onClick={(e) => { e.preventDefault(); signOut(); }}> <a href="/api/auth/signout" className={styles.button} onClick={(e) => { e.preventDefault(); signOut(); }}>
{/* {t('logout')} */} {/* {t('logout')} */}
изход изход