From b53deae633b1012a4cfe47ebc4f6eb178e98f3b0 Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Sun, 21 Sep 2025 14:00:52 +0300 Subject: [PATCH] Improve service worker type safety --- frontend/public/sw.js | 57 ------------ frontend/src/service-worker/service-worker.ts | 89 +++++++++++++++++++ frontend/src/utils/pushNotifications.ts | 2 +- frontend/vite.config.ts | 47 +++++++++- 4 files changed, 135 insertions(+), 60 deletions(-) delete mode 100644 frontend/public/sw.js create mode 100644 frontend/src/service-worker/service-worker.ts diff --git a/frontend/public/sw.js b/frontend/public/sw.js deleted file mode 100644 index 62b3e16..0000000 --- a/frontend/public/sw.js +++ /dev/null @@ -1,57 +0,0 @@ -// Service Worker for Push Notifications -self.addEventListener("push", function(event) { - if (event.data) { - const data = event.data.json(); - - const options = { - body: data.body, - icon: data.icon || "/logo.png", - badge: "/logo.png", - image: data.image, - tag: data.tag || "message", - data: data.data, - actions: [ - { - action: "open", - title: "Open Chat" - }, - { - action: "close", - title: "Close" - } - ], - requireInteraction: true, - silent: false - }; - - event.waitUntil( - self.registration.showNotification(data.title, options) - ); - } -}); - -self.addEventListener("notificationclick", function(event) { - event.notification.close(); - - if (event.action === "open" || !event.action) { - event.waitUntil( - clients.matchAll({ type: "window" }).then(function(clientList) { - // If there's already a window open, focus it - for (let i = 0; i < clientList.length; i++) { - const client = clientList[i]; - if (client.url === self.location.origin && "focus" in client) { - return client.focus(); - } - } - // Otherwise, open a new window - if (clients.openWindow) { - return clients.openWindow(self.location.origin); - } - }) - ); - } -}); - -self.addEventListener("notificationclose", function(event) { - // Handle notification close if needed -}); diff --git a/frontend/src/service-worker/service-worker.ts b/frontend/src/service-worker/service-worker.ts new file mode 100644 index 0000000..6d48b01 --- /dev/null +++ b/frontend/src/service-worker/service-worker.ts @@ -0,0 +1,89 @@ +/// + +declare const self: ServiceWorkerGlobalScope; + +interface NotificationPayload { + title: string; + body: string; + icon?: string; + image?: string; + tag?: string; + data?: any; +} + +interface NotificationAction { + action: string; + title: string; +} + +interface NotificationOptions { + body: string; + icon: string; + badge: string; + image?: string; + tag: string; + data?: any; + actions: NotificationAction[]; + requireInteraction: boolean; + silent: boolean; +} + +// Service Worker for Push Notifications +self.addEventListener("push", function(event: ExtendableEvent) { + const pushEvent = event as PushEvent; + if (pushEvent.data) { + const data: NotificationPayload = pushEvent.data.json(); + + const options: NotificationOptions = { + body: data.body, + icon: data.icon || "/logo.png", + badge: "/logo.png", + image: data.image, + tag: data.tag || "message", + data: data.data, + actions: [ + { + action: "open", + title: "Open Chat" + }, + { + action: "close", + title: "Close" + } + ], + requireInteraction: true, + silent: false + }; + + event.waitUntil( + self.registration.showNotification(data.title, options) + ); + } +}); + +self.addEventListener("notificationclick", function(event: ExtendableEvent) { + const notificationEvent = event as NotificationEvent; + notificationEvent.notification.close(); + + if (notificationEvent.action === "open" || !notificationEvent.action) { + event.waitUntil( + self.clients.matchAll({ type: "window" }).then(function(clientList: readonly WindowClient[]) { + // If there's already a window open, focus it + for (let i = 0; i < clientList.length; i++) { + const client = clientList[i]; + if (client.url === self.location.origin && "focus" in client) { + return client.focus(); + } + } + // Otherwise, open a new window + if (self.clients.openWindow) { + return self.clients.openWindow(self.location.origin); + } + }) + ); + } +}); + +self.addEventListener("notificationclose", function(_event: ExtendableEvent) { + // Handle notification close if needed +}); diff --git a/frontend/src/utils/pushNotifications.ts b/frontend/src/utils/pushNotifications.ts index 115520b..e1aeca0 100644 --- a/frontend/src/utils/pushNotifications.ts +++ b/frontend/src/utils/pushNotifications.ts @@ -28,7 +28,7 @@ class PushNotificationManager { } try { - this.registration = await navigator.serviceWorker.register("/sw.js"); + this.registration = await navigator.serviceWorker.register("/assets/serviceWorker.js"); console.log("Service Worker registered successfully"); return true; } catch (error) { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index cb18e68..593b455 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -3,6 +3,9 @@ import { createHtmlPlugin } from "vite-plugin-html"; import autoprefixer from "autoprefixer"; import electron from "vite-plugin-electron/simple"; import react from "@vitejs/plugin-react"; +import { resolve } from "path"; + +const serviceWorkerPath = resolve(__dirname, "src/service-worker/service-worker.ts"); const plugins: PluginOption[] = [ react(), @@ -17,7 +20,31 @@ const plugins: PluginOption[] = [ minifyCSS: true, minifyJS: true } - }) + }), + { + name: 'service-worker-redirect', + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + if (req.url === '/assets/serviceWorker.js') { + try { + const traspiled = await server.transformRequest(serviceWorkerPath); + + res.writeHead(200, { + 'Content-Type': "application/javascript" + }); + res.end(traspiled!.code); + } catch (e) { + try { + res.writeHead(500); + res.end(); + } catch (e) {} + } + } else { + next(); + } + }); + } + } ] if (process.env.VITE_ELECTRON) { @@ -79,6 +106,22 @@ export default defineConfig({ }, cssMinify: true, assetsInlineLimit: 0, - outDir: process.env.VITE_ELECTRON ? "build/electron/dist" : "build/normal/dist" + outDir: process.env.VITE_ELECTRON ? "build/electron/dist" : "build/normal/dist", + rollupOptions: { + input: { + main: resolve(__dirname, "index.html"), + serviceWorker: serviceWorkerPath + }, + output: { + entryFileNames: (chunkInfo) => { + // Проверяем имя чанка + if (chunkInfo.name === 'serviceWorker') { + return 'assets/serviceWorker.js'; // Указываем фиксированное имя для этого скрипта + } + // Для остальных файлов используем стандартное именование с хэшем + return 'assets/[name]-[hash].js'; + } + } + } } }); \ No newline at end of file