From 5d78f61f87b1b903297b1829c1c7079841b2e421 Mon Sep 17 00:00:00 2001 From: Mamalizz Date: Thu, 27 Mar 2025 14:15:14 +0330 Subject: [PATCH] tried fixing infinite loop --- .../global/usePushNotifications.ts | 92 +++++++++++++++++++ frontend/nuxt.config.ts | 57 ++++++------ frontend/plugins/pwaUpdate.client.ts | 60 ++++++++++-- frontend/public/sw.js | 32 ++++--- 4 files changed, 192 insertions(+), 49 deletions(-) create mode 100644 frontend/composables/global/usePushNotifications.ts diff --git a/frontend/composables/global/usePushNotifications.ts b/frontend/composables/global/usePushNotifications.ts new file mode 100644 index 0000000..ec4caaa --- /dev/null +++ b/frontend/composables/global/usePushNotifications.ts @@ -0,0 +1,92 @@ +import { API_ENDPOINTS } from "~/constants"; +import useSubscribeNotification from "~/composables/api/notifications/useSubscribeNotification"; +import { useToast } from "~/composables/global/useToast"; + +interface VapidKeys { + publicKey: string; +} + +export const usePushNotifications = () => { + const isSupported = ref(false); + const permission = usePermission("notifications"); + const subscription = useLocalStorage( + "push-subscription", + null + ); + const vapid = ref(null); + + const { mutateAsync: subscribeNotification } = useSubscribeNotification(); + const { addToast } = useToast(); + + const unsubscribe = async () => { + const swRegistration = await navigator.serviceWorker.ready; + const existingSubscription = + await swRegistration.pushManager.getSubscription(); + if (existingSubscription) { + await existingSubscription.unsubscribe(); + } + }; + + onMounted(async () => { + if (typeof window !== "undefined" && "serviceWorker" in navigator) { + isSupported.value = true; + vapid.value = await $fetch("/api/vapid"); + } + }); + + const subscribe = async () => { + if (!isSupported.value || !vapid.value?.publicKey) { + throw new Error("Push notifications not supported"); + } + + const swRegistration = await navigator.serviceWorker.ready; + + await unsubscribe(); + + const applicationServerKey = vapid.value.publicKey + .replace(/-/g, "+") + .replace(/_/g, "/"); + + const convertedKey = Uint8Array.from(atob(applicationServerKey), (c) => + c.charCodeAt(0) + ); + + const pushSubscription = await swRegistration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: convertedKey, + }); + + const subscriptionJson = pushSubscription.toJSON(); + + subscribeNotification( + { + body: subscriptionJson, + }, + { + onSuccess: () => { + addToast({ + message: "اعلانات برای دستگاه شما فعال شد", + }); + }, + onError: () => { + addToast({ + message: "خطایی در فعال شدن اعلانات رخ داد", + options: { + status: "error", + }, + }); + }, + } + ); + + subscription.value = subscriptionJson; + }; + + return { + isSupported, + permission, + subscribe, + unsubscribe, + subscription, + }; +}; diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts index d2178a2..4ba51f6 100644 --- a/frontend/nuxt.config.ts +++ b/frontend/nuxt.config.ts @@ -6,45 +6,45 @@ export default defineNuxtConfig({ css: [ "~/assets/css/tailwind.css", "swiper/css", - "animate.css/animate.min.css" + "animate.css/animate.min.css", ], routeRules: { - "/products": { prerender: false, ssr: false } + "/products": { prerender: false, ssr: false }, }, app: { head: { - title: "فروشگاه هی ملز" + title: "فروشگاه هی ملز", }, pageTransition: { enterActiveClass: "animate__animated animate__fadeIn animate__faster", leaveActiveClass: "animate__animated animate__fadeOut animate__faster", - mode: "out-in" + mode: "out-in", }, layoutTransition: { enterActiveClass: "animate__animated animate__fadeIn animate__faster", leaveActiveClass: "animate__animated animate__fadeOut animate__faster", - mode: "out-in" - } + mode: "out-in", + }, }, postcss: { plugins: { "@tailwindcss/postcss": {}, - autoprefixer: {} - } + autoprefixer: {}, + }, }, components: [ { path: "~/components", - pathPrefix: false - } + pathPrefix: false, + }, ], icon: { @@ -52,9 +52,9 @@ export default defineNuxtConfig({ customCollections: [ { prefix: "ci", - dir: "./public/icons" - } - ] + dir: "./public/icons", + }, + ], }, modules: [ @@ -65,21 +65,23 @@ export default defineNuxtConfig({ "DM Sans": "100..900", Inter: "100..900", download: true, - inject: false - } - } + inject: false, + }, + }, ], "@nuxt/icon", "reka-ui/nuxt", "@vueuse/nuxt", "@formkit/auto-animate/nuxt", - "@vite-pwa/nuxt" + "@vite-pwa/nuxt", ], pwa: { strategies: "injectManifest", srcDir: "public", filename: "sw.js", + registerType: + process.env.NODE_ENV === "production" ? "autoUpdate" : "prompt", manifest: { name: "Heymlz", short_name: "Heymlz", @@ -88,27 +90,30 @@ export default defineNuxtConfig({ { src: "/logo/logo-192x192.png", sizes: "192x192", - type: "image/png" + type: "image/png", }, { src: "/logo/logo-512x512.png", sizes: "512x512", - type: "image/png" - } - ] + type: "image/png", + }, + ], }, workbox: { navigateFallback: "/", clientsClaim: true, - skipWaiting: true + skipWaiting: true, + }, + devOptions: { + enabled: process.env.NODE_ENV === "production", + type: "module", }, - devOptions: { enabled: true, type: "module" } }, runtimeConfig: { public: { API_BASE_URL: process.env.API_BASE_URL, - DEBUG: process.env.DEBUG - } - } + DEBUG: process.env.DEBUG, + }, + }, }); diff --git a/frontend/plugins/pwaUpdate.client.ts b/frontend/plugins/pwaUpdate.client.ts index ac9ce3c..dc162ab 100644 --- a/frontend/plugins/pwaUpdate.client.ts +++ b/frontend/plugins/pwaUpdate.client.ts @@ -8,29 +8,73 @@ export default defineNuxtPlugin(() => { const { isInstalledAsPWA } = usePWA(); if ("serviceWorker" in navigator && isInstalledAsPWA.value) { + // Initialize Workbox wb = new Workbox("/sw.js"); - wb.addEventListener("waiting", () => { - checkForUpdate(); - }); + navigator.serviceWorker + .register("/sw.js") + .then((registration) => { + // Native Service Worker API for update detection + registration.addEventListener("updatefound", () => { + const newWorker = registration.installing; + if (newWorker) { + newWorker.addEventListener("statechange", () => { + if (newWorker.state === "installed") { + // Only show prompt if there's a controller (not first install) + if (navigator.serviceWorker.controller) { + checkForUpdate(); + } + } + }); + } + }); - wb.register() - .then((registration: any) => { + // Workbox events for consistency + wb?.addEventListener("waiting", () => { + checkForUpdate(); + }); + + // Check if there's already a waiting worker if (registration.waiting) { checkForUpdate(); } + + // Periodic update checks (optional) + setInterval(() => { + registration.update().catch((err) => { + console.debug( + "Service worker update check failed:", + err + ); + }); + }, 60 * 60 * 1000); // Check every hour }) - .catch((error) => { - console.error("Service worker registration failed:", error); + .catch((err) => { + console.error("Service worker registration failed:", err); }); + + // Register Workbox + wb.register().catch((error) => { + console.error("Workbox registration failed:", error); + }); } const checkForUpdate = () => { - updateAvailable.value = true; + if (!updateAvailable.value) { + updateAvailable.value = true; + } }; + const handleUpdate = () => { if (wb) { + // Send skip waiting message wb.messageSW({ type: "SKIP_WAITING" }).then(() => { + // Notify all tabs to reload + if (navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage({ + type: "CLIENT_RELOAD", + }); + } window.location.reload(); }); } diff --git a/frontend/public/sw.js b/frontend/public/sw.js index 3b750c8..52b01b0 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -3,33 +3,36 @@ import { precacheAndRoute } from "workbox-precaching"; // Precaching configuration for PWA assets precacheAndRoute(self.__WB_MANIFEST); -// Version - const VERSION = "1.0.4"; -// Service Worker Installation +// Only enable skipWaiting and claim in production +const isProduction = process.env.NODE_ENV === "production"; + self.addEventListener("install", (event) => { - self.skipWaiting(); + if (isProduction) { + self.skipWaiting(); + } }); -// Service Worker Activation self.addEventListener("activate", (event) => { event.waitUntil( (async () => { - const clients = await self.clients.matchAll({ type: "window" }); - - // Notify all open clients about the version - clients.forEach((client) => - client.postMessage({ type: "VERSION_CHECK", version: VERSION }) - ); - - self.clients.claim(); + if (isProduction) { + const clients = await self.clients.matchAll({ type: "window" }); + clients.forEach((client) => + client.postMessage({ + type: "VERSION_CHECK", + version: VERSION, + }) + ); + self.clients.claim(); + } console.log("Service Worker Activated (Version: " + VERSION + ")"); })() ); }); -// Push Notification Handler for Django Web Push +// Rest of your existing handlers remain the same... self.addEventListener("push", (event) => { try { const payload = event.data?.json() || { @@ -51,7 +54,6 @@ self.addEventListener("push", (event) => { } }); -// Notification Click Handler self.addEventListener("notificationclick", (event) => { event.notification.close(); event.waitUntil(clients.openWindow(event.notification.data?.url || "/"));