squash all commits and fix service worker (file has to be at this specific path by convention):
Added pwa subscription storage for publishers
This commit is contained in:
7
.env
7
.env
@ -2,7 +2,7 @@
|
|||||||
# HOST=localhost
|
# HOST=localhost
|
||||||
# PORT=3003
|
# PORT=3003
|
||||||
# NEXT_PUBLIC_PUBLIC_URL=http://localhost:3003
|
# NEXT_PUBLIC_PUBLIC_URL=http://localhost:3003
|
||||||
|
ENV_ENV='.env'
|
||||||
# Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32
|
# Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32
|
||||||
NEXTAUTH_SECRET=ed8a9681efc414df89dfd03cd188ed58
|
NEXTAUTH_SECRET=ed8a9681efc414df89dfd03cd188ed58
|
||||||
|
|
||||||
@ -69,5 +69,6 @@ MAILTRAP_PASS=c7bc05f171c96c
|
|||||||
TELEGRAM_BOT=false
|
TELEGRAM_BOT=false
|
||||||
TELEGRAM_BOT_TOKEN=7050075088:AAH6VRpNCyQd9x9sW6CLm6q0q4ibUgYBfnM
|
TELEGRAM_BOT_TOKEN=7050075088:AAH6VRpNCyQd9x9sW6CLm6q0q4ibUgYBfnM
|
||||||
|
|
||||||
NEXT_PUBLIC_VAPID_PUBLIC_KEY=BGxXJ0jdsQ4ihE7zp8mxrBO-QPSjeEtO9aCtPoMTuxc1VLW0OfRIt-DYinK9ekjTl2w-j0eQbeprIyBCpmmfciI
|
WEB_PUSH_EMAIL=mwitnessing@gmail.com
|
||||||
VAPID_PRIVATE_KEY=VXHu2NgcyM4J4w3O4grkS_0yLwWHCvVKDJexyBjqgx0
|
NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY=BGxXJ0jdsQ4ihE7zp8mxrBO-QPSjeEtO9aCtPoMTuxc1VLW0OfRIt-DYinK9ekjTl2w-j0eQbeprIyBCpmmfciI
|
||||||
|
WEB_PUSH_PRIVATE_KEY=VXHu2NgcyM4J4w3O4grkS_0yLwWHCvVKDJexyBjqgx0
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
NODE_TLS_REJECT_UNAUTHORIZED=0
|
NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||||
# NODE_EXTRA_CA_CERTS=C:\\Users\\popov\\AppData\\Local\\mkcert
|
# NODE_EXTRA_CA_CERTS=C:\\Users\\popov\\AppData\\Local\\mkcert
|
||||||
|
ENV_ENV=.env.development
|
||||||
PROTOCOL=https
|
PROTOCOL=https
|
||||||
PORT=3003
|
PORT=3003
|
||||||
HOST=localhost
|
HOST=localhost
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
NODE_ENV=test
|
NODE_ENV=test
|
||||||
|
ENV_ENV=test.staging
|
||||||
|
|
||||||
PROTOCOL=http
|
PROTOCOL=http
|
||||||
HOST=staging.mwitnessing.com
|
HOST=staging.mwitnessing.com
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import common from '../src/helpers/common'; // Ensure this path is correct
|
import common from '../src/helpers/common'; // Ensure this path is correct
|
||||||
|
//use session to get user role
|
||||||
|
import { useSession } from "next-auth/react"
|
||||||
|
|
||||||
function PwaManager() {
|
function PwaManager() {
|
||||||
const [deferredPrompt, setDeferredPrompt] = useState(null);
|
const [deferredPrompt, setDeferredPrompt] = useState(null);
|
||||||
@ -10,6 +12,8 @@ function PwaManager() {
|
|||||||
const [registration, setRegistration] = useState(null);
|
const [registration, setRegistration] = useState(null);
|
||||||
const [notificationPermission, setNotificationPermission] = useState(Notification.permission);
|
const [notificationPermission, setNotificationPermission] = useState(Notification.permission);
|
||||||
|
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
// Handle PWA installation
|
// Handle PWA installation
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
@ -54,7 +58,7 @@ function PwaManager() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const installPWA = async (e) => {
|
const installPWA = async (e) => {
|
||||||
|
console.log('installing PWA');
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (deferredPrompt) {
|
if (deferredPrompt) {
|
||||||
deferredPrompt.prompt();
|
deferredPrompt.prompt();
|
||||||
@ -66,35 +70,54 @@ function PwaManager() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Utility function for converting base64 string to Uint8Array
|
|
||||||
const base64ToUint8Array = base64 => {
|
|
||||||
const padding = '='.repeat((4 - (base64.length % 4)) % 4);
|
|
||||||
const b64 = (base64 + padding).replace(/-/g, '+').replace(/_/g, '/');
|
|
||||||
const rawData = window.atob(b64);
|
|
||||||
const outputArray = new Uint8Array(rawData.length);
|
|
||||||
for (let i = 0; i < rawData.length; ++i) {
|
|
||||||
outputArray[i] = rawData.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return outputArray;
|
|
||||||
};
|
|
||||||
|
|
||||||
const subscribeToNotifications = async (e) => {
|
const subscribeToNotifications = async (e) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!navigator.serviceWorker) {
|
||||||
|
console.error('Service worker is not supported by this browser.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const registration = await navigator.serviceWorker.ready;
|
||||||
if (!registration) {
|
if (!registration) {
|
||||||
console.error('Service worker registration not found.');
|
console.error('Service worker registration not found.');
|
||||||
registration
|
|
||||||
return;
|
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' });
|
||||||
|
vapidPublicKey = await response.text();
|
||||||
|
if (!vapidPublicKey) {
|
||||||
|
throw new Error("Failed to fetch VAPID public key from server.");
|
||||||
|
}
|
||||||
|
}
|
||||||
const sub = await registration.pushManager.subscribe({
|
const sub = await registration.pushManager.subscribe({
|
||||||
userVisibleOnly: true,
|
userVisibleOnly: true,
|
||||||
applicationServerKey: base64ToUint8Array(process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY)
|
applicationServerKey: common.base64ToUint8Array(vapidPublicKey)
|
||||||
});
|
});
|
||||||
// Call your API to save subscription data on server
|
// 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(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to save subscription data on server.');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('Subscription data saved on server.');
|
||||||
setSubscription(sub);
|
setSubscription(sub);
|
||||||
setIsSubscribed(true);
|
setIsSubscribed(true);
|
||||||
console.log('Web push subscribed!');
|
console.log('Web push subscribed!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
console.log(sub);
|
console.log(sub);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error subscribing to notifications:', error);
|
console.error('Error subscribing to notifications:', error);
|
||||||
@ -109,6 +132,24 @@ function PwaManager() {
|
|||||||
// Call your API to delete or invalidate subscription data on server
|
// Call your API to delete or invalidate subscription data on server
|
||||||
setSubscription(null);
|
setSubscription(null);
|
||||||
setIsSubscribed(false);
|
setIsSubscribed(false);
|
||||||
|
if (session?.user?.id != null) {
|
||||||
|
await fetch(`/api/notify`,
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ id: session?.user.id }),
|
||||||
|
}
|
||||||
|
).then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete subscription data on server.');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('Subscription data deleted on server.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
console.log('Web push unsubscribed!');
|
console.log('Web push unsubscribed!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error unsubscribing from notifications:', error);
|
console.error('Error unsubscribing from notifications:', error);
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||||
|
//we're currently using next-pwa (which uses GeneraeSW automatically), so we don't need to use workbox-webpack-plugin
|
||||||
|
const { InjectManifest, GenerateSW } = require('workbox-webpack-plugin');
|
||||||
|
|
||||||
const withPWA = require('next-pwa')({
|
const withPWA = require('next-pwa')({
|
||||||
dest: 'public',
|
dest: 'public',
|
||||||
register: true, // ?
|
register: true, // ?
|
||||||
publicExcludes: ["!_error*.js"], //?
|
publicExcludes: ["!_error*.js"], //?
|
||||||
|
skipWaiting: true,
|
||||||
//disable: process.env.NODE_ENV === 'development',
|
// disable: process.env.NODE_ENV === 'development',
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = withPWA({
|
module.exports = withPWA({
|
||||||
@ -19,26 +21,56 @@ module.exports = withPWA({
|
|||||||
compress: false,
|
compress: false,
|
||||||
pageExtensions: ['ts', 'tsx', 'md', 'mdx'], // Replace `jsx?` with `tsx?`
|
pageExtensions: ['ts', 'tsx', 'md', 'mdx'], // Replace `jsx?` with `tsx?`
|
||||||
env: {
|
env: {
|
||||||
env: process.env.NODE_ENV,
|
env: process.env.APP_ENV,
|
||||||
server: process.env.NEXT_PUBLIC_PUBLIC_URL
|
server: process.env.NEXT_PUBLIC_PUBLIC_URL
|
||||||
},
|
},
|
||||||
webpack(config, { isServer }) {
|
// pwa: {
|
||||||
|
// dest: 'public',
|
||||||
|
// register: true,
|
||||||
|
// publicExcludes: ["!_error*.js"],
|
||||||
|
// disable: process.env.NODE_ENV === 'development',
|
||||||
|
// // sw: './worker/index.js', // Custom service worker file name
|
||||||
|
// },
|
||||||
|
|
||||||
config.optimization.minimize = true,
|
// plugins: [
|
||||||
productionBrowserSourceMaps = true,
|
// // new InjectManifest({
|
||||||
|
// // // These are some common options, and not all are required.
|
||||||
|
// // // Consult the docs for more info.
|
||||||
|
// // //exclude: [/.../, '...'],
|
||||||
|
// // maximumFileSizeToCacheInBytes: 1 * 1024 * 1024,
|
||||||
|
// // swSrc: './worker.js',
|
||||||
|
// // }),
|
||||||
|
// // new GenerateSW({
|
||||||
|
// // //disable all files
|
||||||
|
// // maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
|
||||||
|
// // // swSrc: './worker.js',
|
||||||
|
// // }),
|
||||||
|
// ],
|
||||||
|
webpack: (config, { isServer, buildId, dev }) => {
|
||||||
|
// Configure optimization and source maps
|
||||||
|
config.optimization.minimize = !dev;
|
||||||
|
//config.productionBrowserSourceMaps = true;
|
||||||
|
// Enable source maps based on non-production environments
|
||||||
|
if (!dev) {
|
||||||
|
config.devtool = 'source-map';
|
||||||
|
}
|
||||||
|
// Add custom fallbacks
|
||||||
|
config.resolve.fallback = { ...config.resolve.fallback, fs: false };
|
||||||
|
|
||||||
config.resolve.fallback = {
|
// InjectManifest configuration
|
||||||
|
if (!isServer) {
|
||||||
|
// config.plugins.push(new InjectManifest({
|
||||||
|
// // swSrc: './worker.js', // Path to source service worker file
|
||||||
|
// // swDest: '/worker.js', // Destination filename in the build output
|
||||||
|
// maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, // Adjust as needed
|
||||||
|
// exclude: [/\.map$/, /_error.js$/, /favicon.ico$/] // Customize exclusion patterns
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
// if you miss it, all the other options in fallback, specified
|
// Bundle Analyzer Configuration
|
||||||
// by next.js will be dropped.
|
|
||||||
...config.resolve.fallback,
|
|
||||||
|
|
||||||
fs: false, // the solution
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Only run the bundle analyzer for production builds and when the ANALYZE environment variable is set
|
|
||||||
if (process.env.ANALYZE && !isServer) {
|
if (process.env.ANALYZE && !isServer) {
|
||||||
|
//const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
new BundleAnalyzerPlugin({
|
new BundleAnalyzerPlugin({
|
||||||
analyzerMode: 'static',
|
analyzerMode: 'static',
|
||||||
|
915
package-lock.json
generated
915
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -108,6 +108,7 @@
|
|||||||
"webpack-bundle-analyzer": "^4.10.1",
|
"webpack-bundle-analyzer": "^4.10.1",
|
||||||
"winston": "^3.13.0",
|
"winston": "^3.13.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0",
|
"winston-daily-rotate-file": "^5.0.0",
|
||||||
|
"workbox-webpack-plugin": "^7.1.0",
|
||||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz",
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz",
|
||||||
"xlsx-style": "^0.8.13",
|
"xlsx-style": "^0.8.13",
|
||||||
"xml-js": "^1.6.11",
|
"xml-js": "^1.6.11",
|
||||||
|
@ -26,9 +26,47 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
|
|||||||
// appleWebApp: true,
|
// appleWebApp: true,
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// (custom) Service worker registration and push notification logic
|
||||||
|
// function registerServiceWorkerAndPushNotifications() {
|
||||||
|
// useEffect(() => {
|
||||||
|
// const registerServiceWorker = async () => {
|
||||||
|
// if ('serviceWorker' in navigator) {
|
||||||
|
// try {
|
||||||
|
// const registration = await navigator.serviceWorker.register('/worker/index.js')
|
||||||
|
// .then((registration) => console.log('reg: ', registration));
|
||||||
|
// } catch (error) {
|
||||||
|
// console.log('Service Worker registration failed:', error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const askForNotificationPermission = async () => {
|
||||||
|
// if ('serviceWorker' in navigator && 'PushManager' in window) {
|
||||||
|
// try {
|
||||||
|
// const permission = await Notification.requestPermission();
|
||||||
|
// if (permission === 'granted') {
|
||||||
|
// console.log('Notification permission granted.');
|
||||||
|
// // TODO: Subscribe the user to push notifications here
|
||||||
|
// } else {
|
||||||
|
// console.log('Notification permission not granted.');
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('Error during service worker registration:', error);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// console.log('Service Worker or Push notifications not supported in this browser.');
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// registerServiceWorker();
|
||||||
|
// askForNotificationPermission();
|
||||||
|
// }, []);
|
||||||
|
// }
|
||||||
|
|
||||||
//function SmwsApp({ Component, pageProps: { locale, messages, session, ...pageProps }, }: AppProps<{ session: Session }>) {
|
//function SmwsApp({ Component, pageProps: { locale, messages, session, ...pageProps }, }: AppProps<{ session: Session }>) {
|
||||||
function SmwsApp({ Component, pageProps, session, locale, messages }) {
|
function SmwsApp({ Component, pageProps, session, locale, messages }) {
|
||||||
|
//registerServiceWorkerAndPushNotifications();
|
||||||
|
|
||||||
// dynamic locale loading using our API endpoint
|
// dynamic locale loading using our API endpoint
|
||||||
// const [locale, setLocale] = useState(_locale);
|
// const [locale, setLocale] = useState(_locale);
|
||||||
// const [messages, setMessages] = useState(_messages);
|
// const [messages, setMessages] = useState(_messages);
|
||||||
|
@ -86,9 +86,9 @@ export const authOptions: NextAuthOptions = {
|
|||||||
// // Return null if user data could not be retrieved
|
// // Return null if user data could not be retrieved
|
||||||
// return null
|
// return null
|
||||||
const users = [
|
const users = [
|
||||||
{ id: "1", name: "admin", email: "admin@example.com", password: "admin123", role: "ADMIN" },
|
{ id: "1", name: "admin", email: "admin@example.com", password: "admin123", role: "ADMIN", static: true },
|
||||||
{ id: "2", name: "krasi", email: "krasi@example.com", password: "krasi123", role: "ADMIN" },
|
{ id: "2", name: "krasi", email: "krasi@example.com", password: "krasi123", role: "ADMIN", static: true },
|
||||||
{ id: "3", name: "popov", email: "popov@example.com", password: "popov123", role: "ADMIN" }
|
{ id: "3", name: "popov", email: "popov@example.com", password: "popov123", role: "ADMIN", static: true }
|
||||||
];
|
];
|
||||||
|
|
||||||
const user = users.find(user =>
|
const user = users.find(user =>
|
||||||
@ -174,6 +174,10 @@ export const authOptions: NextAuthOptions = {
|
|||||||
callbacks: {
|
callbacks: {
|
||||||
// https://codevoweb.com/implement-authentication-with-nextauth-in-nextjs-14/
|
// https://codevoweb.com/implement-authentication-with-nextauth-in-nextjs-14/
|
||||||
async signIn({ user, account, profile }) {
|
async signIn({ user, account, profile }) {
|
||||||
|
if (account.provider === 'credentials' && user?.static) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var prisma = common.getPrismaClient();
|
var prisma = common.getPrismaClient();
|
||||||
|
|
||||||
console.log("[nextauth] signIn:", account.provider, user.email)
|
console.log("[nextauth] signIn:", account.provider, user.email)
|
||||||
|
@ -1,20 +1,81 @@
|
|||||||
|
|
||||||
const webPush = require('web-push')
|
const webPush = require('web-push')
|
||||||
|
|
||||||
|
import common from '../../src/helpers/common';
|
||||||
|
|
||||||
|
//generate and store VAPID keys in .env.local if not already done
|
||||||
|
if (!process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY || !process.env.WEB_PUSH_PRIVATE_KEY) {
|
||||||
|
const { publicKey, privateKey } = webPush.generateVAPIDKeys()
|
||||||
|
console.log('VAPID keys generated:')
|
||||||
|
console.log('Public key:', publicKey)
|
||||||
|
console.log('Private key:', privateKey)
|
||||||
|
console.log('Store these keys in your .env.local file:')
|
||||||
|
console.log('NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY=', publicKey)
|
||||||
|
console.log('WEB_PUSH_PRIVATE_KEY=', privateKey)
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
webPush.setVapidDetails(
|
webPush.setVapidDetails(
|
||||||
`mailto:${process.env.WEB_PUSH_EMAIL}`,
|
`mailto:${process.env.WEB_PUSH_EMAIL}`,
|
||||||
process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY,
|
process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY,
|
||||||
process.env.VAPID_PRIVATE_KEY
|
process.env.WEB_PUSH_PRIVATE_KEY
|
||||||
)
|
)
|
||||||
|
|
||||||
const Notification = (req, res) => {
|
const Notification = async (req, res) => {
|
||||||
if (req.method == 'POST') {
|
if (req.method == 'GET') {
|
||||||
const { subscription } = req.body
|
res.statusCode = 200
|
||||||
|
res.setHeader('Allow', 'POST')
|
||||||
|
// send the public key in the response headers
|
||||||
|
//res.setHeader('Content-Type', 'text/plain')
|
||||||
|
res.end(process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY)
|
||||||
|
res.end()
|
||||||
|
}
|
||||||
|
if (req.method == 'PUT') {
|
||||||
|
// store the subscription object in the database
|
||||||
|
// publisher.pushSubscription = subscription
|
||||||
|
const prisma = common.getPrismaClient();
|
||||||
|
const { subscription, id } = req.body
|
||||||
|
const publisher = await prisma.publisher.update({
|
||||||
|
where: { id },
|
||||||
|
data: { pushSubscription: subscription }
|
||||||
|
})
|
||||||
|
console.log('Subscription for publisher', id, 'updated:', subscription)
|
||||||
|
res.statusCode = 200
|
||||||
|
res.end()
|
||||||
|
}
|
||||||
|
if (req.method == 'DELETE') {
|
||||||
|
// remove the subscription object from the database
|
||||||
|
// publisher.pushSubscription = null
|
||||||
|
const prisma = common.getPrismaClient();
|
||||||
|
const { id } = req.body
|
||||||
|
const publisher = await prisma.publisher.update({
|
||||||
|
where: { id },
|
||||||
|
data: { pushSubscription: null }
|
||||||
|
})
|
||||||
|
console.log('Subscription for publisher', id, 'deleted')
|
||||||
|
res.statusCode = 200
|
||||||
|
res.end()
|
||||||
|
}
|
||||||
|
|
||||||
webPush
|
|
||||||
|
if (req.method == 'POST') {
|
||||||
|
const { subscription, id, broadcast, title = 'Hello Web Push', message = 'Your web push notification is here!' } = req.body
|
||||||
|
if (broadcast) {
|
||||||
|
await broadcastPush(title, message)
|
||||||
|
res.statusCode = 200
|
||||||
|
res.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else if (id) {
|
||||||
|
await sendPush(id, title, message)
|
||||||
|
res.statusCode = 200
|
||||||
|
res.end()
|
||||||
|
return
|
||||||
|
} else if (subscription) {
|
||||||
|
await webPush
|
||||||
.sendNotification(
|
.sendNotification(
|
||||||
subscription,
|
subscription,
|
||||||
JSON.stringify({ title: 'Hello Web Push', message: 'Your web push notification is here!' })
|
JSON.stringify({ title, message })
|
||||||
)
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
res.writeHead(response.statusCode, response.headers).end(response.body)
|
res.writeHead(response.statusCode, response.headers).end(response.body)
|
||||||
@ -28,6 +89,7 @@ const Notification = (req, res) => {
|
|||||||
res.end()
|
res.end()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
res.statusCode = 405
|
res.statusCode = 405
|
||||||
res.end()
|
res.end()
|
||||||
@ -35,3 +97,48 @@ const Notification = (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default Notification
|
export default Notification
|
||||||
|
|
||||||
|
//export pushNotification(userId or email) for use in other files
|
||||||
|
export const sendPush = async (id, title, message) => {
|
||||||
|
const prisma = common.getPrismaClient();
|
||||||
|
const publisher = await prisma.publisher.findUnique({
|
||||||
|
where: { id }
|
||||||
|
})
|
||||||
|
if (!publisher.pushSubscription) {
|
||||||
|
console.log('No push subscription found for publisher', id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await webPush
|
||||||
|
.sendNotification(
|
||||||
|
publisher.pushSubscription,
|
||||||
|
JSON.stringify({ title, message })
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
console.log('Push notification sent to publisher', id)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Error sending push notification to publisher', id, ':', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//export breoadcastNotification for use in other files
|
||||||
|
export const broadcastPush = async (title, message) => {
|
||||||
|
const prisma = common.getPrismaClient();
|
||||||
|
const publishers = await prisma.publisher.findMany({
|
||||||
|
where: { pushSubscription: { not: null } }
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const publisher of publishers) {
|
||||||
|
await webPush
|
||||||
|
.sendNotification(
|
||||||
|
publisher.pushSubscription,
|
||||||
|
JSON.stringify({ title, message })
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
console.log('Push notification sent to publisher', publisher.id)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Error sending push notification to publisher', publisher.id, ':', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,6 +15,8 @@ import { toast } from 'react-toastify';
|
|||||||
import ProtectedRoute from '../../../components/protectedRoute';
|
import ProtectedRoute from '../../../components/protectedRoute';
|
||||||
import ConfirmationModal from '../../../components/ConfirmationModal';
|
import ConfirmationModal from '../../../components/ConfirmationModal';
|
||||||
import LocalShippingIcon from '@mui/icons-material/LocalShipping';
|
import LocalShippingIcon from '@mui/icons-material/LocalShipping';
|
||||||
|
// import notify api
|
||||||
|
import { sendPush, broadcastPush } from '../../api/notify';
|
||||||
const { DateTime } = require('luxon');
|
const { DateTime } = require('luxon');
|
||||||
|
|
||||||
// import { FaPlus, FaCogs, FaTrashAlt, FaSpinner } from 'react-icons/fa'; // Import FontAwesome icons
|
// import { FaPlus, FaCogs, FaTrashAlt, FaSpinner } from 'react-icons/fa'; // Import FontAwesome icons
|
||||||
@ -734,7 +736,19 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
|
|||||||
<span title="участия тази седмица" className={`badge py-1 px-2 rounded-full text-xs ${pub.currentWeekAssignments ? 'bg-yellow-500 text-white' : 'bg-yellow-200 text-gray-400'}`}>{pub.currentWeekAssignments || 0}</span>
|
<span title="участия тази седмица" className={`badge py-1 px-2 rounded-full text-xs ${pub.currentWeekAssignments ? 'bg-yellow-500 text-white' : 'bg-yellow-200 text-gray-400'}`}>{pub.currentWeekAssignments || 0}</span>
|
||||||
<span title="участия този месец" className={`badge py-1 px-2 rounded-full text-xs ${pub.currentMonthAssignments ? 'bg-green-500 text-white' : 'bg-green-200 text-gray-400'}`}>{pub.currentMonthAssignments || 0}</span>
|
<span title="участия този месец" className={`badge py-1 px-2 rounded-full text-xs ${pub.currentMonthAssignments ? 'bg-green-500 text-white' : 'bg-green-200 text-gray-400'}`}>{pub.currentMonthAssignments || 0}</span>
|
||||||
<span tooltip="участия миналия месец" title="участия миналия месец" className={`badge py-1 px-2 rounded-full text-xs ${pub.previousMonthAssignments ? 'bg-blue-500 text-white' : 'bg-blue-200 text-gray-400'}`}>{pub.previousMonthAssignments || 0}</span>
|
<span tooltip="участия миналия месец" title="участия миналия месец" className={`badge py-1 px-2 rounded-full text-xs ${pub.previousMonthAssignments ? 'bg-blue-500 text-white' : 'bg-blue-200 text-gray-400'}`}>{pub.previousMonthAssignments || 0}</span>
|
||||||
<button tooltip="желани участия този месец" title="желани участия" className={`badge py-1 px-2 rounded-md text-xs ${pub.desiredShiftsPerMonth ? 'bg-purple-500 text-white' : 'bg-purple-200 text-gray-400'}`}>{pub.desiredShiftsPerMonth || 0}</button>
|
<button tooltip="желани участия на месец" title="желани участия" className={`badge py-1 px-2 rounded-md text-xs ${pub.desiredShiftsPerMonth ? 'bg-purple-500 text-white' : 'bg-purple-200 text-gray-400'}`}>{pub.desiredShiftsPerMonth || 0}</button>
|
||||||
|
<button tooltip="push" title="push" className={`badge py-1 px-2 rounded-md text-xs bg-red-100`}
|
||||||
|
onClick={async () => {
|
||||||
|
await fetch('/api/notify', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ broadcast: true, message: "Тестово съобщение", title: "Това е тестово съобщение от https://sofia.mwitnessing.com" })
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>+</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -226,8 +226,8 @@ export const getServerSideProps = async (context) => {
|
|||||||
|
|
||||||
// log first availability startTime to verify timezone and UTC conversion
|
// log first availability startTime to verify timezone and UTC conversion
|
||||||
|
|
||||||
console.log("First availability startTime: " + items[0].startTime);
|
console.log("First availability startTime: " + items[0]?.startTime);
|
||||||
console.log("First availability startTime: " + items[0].startTime.toLocaleString());
|
console.log("First availability startTime: " + items[0]?.startTime.toLocaleString());
|
||||||
|
|
||||||
const prisma = common.getPrismaClient();
|
const prisma = common.getPrismaClient();
|
||||||
let cartEvents = await prisma.cartEvent.findMany({
|
let cartEvents = await prisma.cartEvent.findMany({
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `Publisher` ADD COLUMN `pushSubscription` JSON NULL;
|
@ -123,6 +123,7 @@ model Publisher {
|
|||||||
Message Message[]
|
Message Message[]
|
||||||
EventLog EventLog[]
|
EventLog EventLog[]
|
||||||
lastLogin DateTime?
|
lastLogin DateTime?
|
||||||
|
pushSubscription Json?
|
||||||
}
|
}
|
||||||
|
|
||||||
model Availability {
|
model Availability {
|
||||||
|
@ -49,6 +49,7 @@ console.log("process.env.APPLE_APP_ID = ", process.env.APPLE_APP_ID);
|
|||||||
logger.info("App started on " + process.env.PROTOCOL + "://" + process.env.HOST + ":" + process.env.PORT + "");
|
logger.info("App started on " + process.env.PROTOCOL + "://" + process.env.HOST + ":" + process.env.PORT + "");
|
||||||
logger.info("process.env.GIT_COMMIT_ID = " + process.env.GIT_COMMIT_ID);
|
logger.info("process.env.GIT_COMMIT_ID = " + process.env.GIT_COMMIT_ID);
|
||||||
logger.info("process.env.APP_ENV = " + process.env.APP_ENV);
|
logger.info("process.env.APP_ENV = " + process.env.APP_ENV);
|
||||||
|
logger.info("process.env.ENV_ENV = " + process.env.ENV_ENV);
|
||||||
logger.info("process.env.NODE_ENV = " + process.env.NODE_ENV);
|
logger.info("process.env.NODE_ENV = " + process.env.NODE_ENV);
|
||||||
logger.info("process.env.APPLE_APP_ID = " + process.env.APPLE_APP_ID);
|
logger.info("process.env.APPLE_APP_ID = " + process.env.APPLE_APP_ID);
|
||||||
logger.info("process.env.EMAIL_SERVICE = " + process.env.EMAIL_SERVICE);
|
logger.info("process.env.EMAIL_SERVICE = " + process.env.EMAIL_SERVICE);
|
||||||
|
@ -31,7 +31,7 @@ const axiosServer = async (context) => {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//redirect to next-auth login page
|
//redirect to next-auth login page
|
||||||
context.res.writeHead(302, { Location: '/api/auth/signin' });
|
context.res.writeHead(302, { Location: encodeURIComponent('/api/auth/signin') });
|
||||||
context.res.end();
|
context.res.end();
|
||||||
return { props: {} };
|
return { props: {} };
|
||||||
}
|
}
|
||||||
|
@ -912,6 +912,19 @@ function adjustDateForDST(date, timezone) {
|
|||||||
}
|
}
|
||||||
exports.adjustDateForDST = adjustDateForDST;
|
exports.adjustDateForDST = adjustDateForDST;
|
||||||
|
|
||||||
|
|
||||||
|
exports.base64ToUint8Array = function (base64String) {
|
||||||
|
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||||
|
const base64 = (base64String + padding)
|
||||||
|
.replace(/-/g, '+')
|
||||||
|
.replace(/_/g, '/');
|
||||||
|
const rawData = atob(base64);
|
||||||
|
const buffer = new Uint8Array(rawData.length);
|
||||||
|
for (let i = 0; i < rawData.length; ++i) {
|
||||||
|
buffer[i] = rawData.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
// exports.getInitials = function (names) {
|
// exports.getInitials = function (names) {
|
||||||
// const parts = names.split(' '); // Split the full name into parts
|
// const parts = names.split(' '); // Split the full name into parts
|
||||||
// if (parts.length === 0) {
|
// if (parts.length === 0) {
|
||||||
|
37
workbox-config.js
Normal file
37
workbox-config.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js');
|
||||||
|
|
||||||
|
// // ToDo: probably not used now as we use next-pwa( check config)
|
||||||
|
// // Only import the modules you need; skip precaching and routing if not needed
|
||||||
|
|
||||||
|
// workbox.core.skipWaiting();
|
||||||
|
// workbox.core.clientsClaim();
|
||||||
|
|
||||||
|
// //workbox.precaching.cleanupOutdatedCaches();
|
||||||
|
// //disable precaching
|
||||||
|
// workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
|
||||||
|
|
||||||
|
// module.exports = {
|
||||||
|
// // Other webpack config...
|
||||||
|
// plugins: [
|
||||||
|
// // Other plugins...
|
||||||
|
// new InjectManifest({
|
||||||
|
// // These are some common options, and not all are required.
|
||||||
|
// // Consult the docs for more info.
|
||||||
|
// exclude: [/.../, '...'],
|
||||||
|
// maximumFileSizeToCacheInBytes: 1 * 1024 * 1024,
|
||||||
|
// // swSrc: './worker.js',
|
||||||
|
// }),
|
||||||
|
// ],
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // Example: Set up push notification handling
|
||||||
|
// self.addEventListener('push', event => {
|
||||||
|
// console.log('Push event received at workbox.config: ', event);
|
||||||
|
// const data = event.data.json();
|
||||||
|
// event.waitUntil(
|
||||||
|
// self.registration.showNotification(data.title, {
|
||||||
|
// body: data.message,
|
||||||
|
// icon: '/path/to/icon.png'
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// });
|
Reference in New Issue
Block a user