Vite-PWA-плагин как добавить webpush (уведомления)

У меня был sw.js, который получает веб-уведомления. Но недавно я установил vite-PWA-плагин, и теперь я не могу добавлять уведомления по умолчанию.

Как я могу настроить этот vite.config.ts для добавления в сгенерированную реализацию serviceWorker.js webpush?

vite.config.ts:

import {defineConfig} from 'vite';
import laravel from 'laravel-vite-plugin';
import react from '@vitejs/plugin-react';

import path from 'path';
import {VitePWA} from "vite-plugin-pwa";

const manifest = {
    "theme_color"     : "#2B2B2B",
    "background_color": "#2B2B2B",
    "display"         : "standalone",
    "scope"           : "/",
    "start_url"       : "/farm",
    "name"            : "ColorBit",
    "short_name"      : "Mining",
    "description"     : "...",
    "icons"           : [
        {
            "src"  : "icons/icon-192x192.png",
            "sizes": "192x192",
            "type" : "image/png"
        },
        // ...
        {
            "src"    : "icons/maskable_icon.png",
            "sizes"  : "682x682",
            "type"   : "image/png",
            "purpose": "maskable"
        }
    ]
};

const getCache = ({ name, pattern, strategy = "CacheFirst" }: any) => ({
    urlPattern: pattern,
    handler: strategy,
    options: {
        cacheName: name,
        expiration: {
            maxEntries: 500,
            maxAgeSeconds: 60 * 60 * 24 * 60 // 2 months
        },
        cacheableResponse: {
            statuses: [0, 200]
        }
    }
});

export default defineConfig({
    plugins: [
        laravel({
            input  : [ 'resources/js/app.tsx',],
            refresh: true,
        }),
        react({
            fastRefresh: false
        }),
        VitePWA({
            registerType: 'autoUpdate',
            outDir      : path.resolve(__dirname, 'public'),
            manifest    : manifest,
            manifestFilename: 'manifest.webmanifest', // Change name for app manifest
            injectRegister  : false, // I register SW in app.ts, disable auto registration

            workbox         : {
                globDirectory: path.resolve(__dirname, 'public'), // Directory for caching
                globPatterns : [
                    '{build,images,sounds,icons}/**/*.{js,css,html,ico,png,jpg,mp4,svg}'
                ],
                navigateFallback: null, // Say that we don't need to cache index.html
                swDest       : 'public/serviceWorker.js',
                runtimeCaching: [
                    // Google fonts cache
                    getCache({
                        pattern: /^https://fonts\.googleapis\.com/.*/i,
                        name: "google-fonts-cache",
                    }),
                    // Google fonts api cache
                    getCache({
                        pattern: /^https://fonts\.gstatic\.com/.*/i,
                        name: "gstatic-fonts-cache"
                    }),
                    // Dynamic cache for assets in storage folder
                    getCache({
                        pattern: /.*storage.*/,
                        name: "dynamic-images-cache",
                    }),

                ]
            }
        })
    ],
    resolve: {
        alias     : {
            '@'          : path.resolve(__dirname, 'resources/js'),
            '@hooks'     : path.resolve(__dirname, 'resources/js/hooks'),
            '@assets'    : path.resolve(__dirname, 'resources/js/assets/'),
            '@components': path.resolve(__dirname, 'resources/js/components')
        },
        extensions: ['.js', '.ts', '.tsx', '.jsx'],
    },
});

Старая реализация webpush в sw.js:

// ^^^ Activate, Install, Fetch... ^^^

/* Webpush Notifications */

// Receive push notifications
self.addEventListener('push', function (e) {
    if (!(
        self.Notification &&
        self.Notification.permission === 'granted'
    )) {
        //notifications aren't supported or permission not granted!
        return;
    }

    if (e.data) {
        let message = e.data.json();
        e.waitUntil(self.registration.showNotification(message.title, {
            body: message.body,
            icon: message.icon,
            actions: message.actions
        }));
    }
});

// Click and open notification
self.addEventListener('notificationclick', function(event) {
    event.notification.close();

    if (event.action === 'farm') clients.openWindow("/farm");
    else if (event.action === 'home') clients.openWindow("/");
    else if (event.action === 'training') clients.openWindow("/mining-training");
    else if (event.action === 'dns') clients.openWindow("/shops/dns");
    else if (event.action === 'ali') clients.openWindow("/shops/aliexpress");
    else clients.openWindow("/farm");
}, false);
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
0
481
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Следует использовать параметр inject manifest и написать собственный serviceWorker с помощью подготовленных методов рабочего ящика (я так думаю, что документация по рабочему ящику очень плохая. Вы можете использовать некоторые методы из моей конфигурации)

vite.config.ts:

export default defineConfig({
  plugins: [
    laravel({
      input: ['resources/js/app.tsx', ],
      refresh: true,
    }),
    react({
      fastRefresh: false
    }),
    VitePWA({
      registerType: 'autoUpdate',
      outDir: path.resolve(__dirname, 'public'),
      manifest: manifest,
      manifestFilename: 'manifest.webmanifest', // Change name for app manifest
      injectRegister: false, // I register SW in app.ts, disable auto registration

      // HERE! For custom service worker
      srcDir: path.resolve(__dirname, 'resources/js/'),
      filename: 'serviceWorker.js',
      strategies: 'injectManifest',

      workbox: {
        globDirectory: path.resolve(__dirname, 'public'),
        globPatterns: [
          '{build,images,sounds,icons}/**/*.{js,css,html,ico,png,jpg,mp4,svg}'
        ],
      },
    })
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'resources/js'),
      '@hooks': path.resolve(__dirname, 'resources/js/hooks'),
      '@assets': path.resolve(__dirname, 'resources/js/assets/'),
      '@components': path.resolve(__dirname, 'resources/js/components')
    },
    extensions: ['.js', '.ts', '.tsx', '.jsx'],
  },

  // define: {
  //     // By default, Vite doesn't include shims for NodeJS/
  //     // necessary for React-joyride. And probably for another libs
  //     global: {},
  // },
});

/resouces/js/serviceWorker.js:

import {ExpirationPlugin} from 'workbox-expiration';
import {createHandlerBoundToURL, precacheAndRoute, cleanupOutdatedCaches} from 'workbox-precaching';
import {registerRoute} from 'workbox-routing';
import {CacheFirst} from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response/CacheableResponsePlugin';

// Register precache routes (static cache)
precacheAndRoute(self.__WB_MANIFEST || []);

// Clean up old cache
cleanupOutdatedCaches();

// Google fonts dynamic cache
registerRoute(
    /^https://fonts\.googleapis\.com/.*/i,
    new CacheFirst({
        cacheName: "google-fonts-cache",
        plugins: [
            new ExpirationPlugin({maxEntries: 500, maxAgeSeconds: 5184e3}),
            new CacheableResponsePlugin({statuses: [0, 200]})
        ]
    }), "GET");

// Google fonts dynamic cache
registerRoute(
    /^https://fonts\.gstatic\.com/.*/i, new CacheFirst({
        cacheName: "gstatic-fonts-cache",
        plugins: [
            new ExpirationPlugin({maxEntries: 500, maxAgeSeconds: 5184e3}),
            new CacheableResponsePlugin({statuses: [0, 200]})
        ]
    }), "GET");

// Dynamic cache for images from `/storage/`
registerRoute(
    /.*storage.*/, new CacheFirst({
        cacheName: "dynamic-images-cache",
        plugins: [
            new ExpirationPlugin({maxEntries: 500, maxAgeSeconds: 5184e3}),
            new CacheableResponsePlugin({statuses: [0, 200]})
        ]
    }), "GET");

// Install and activate service worker
self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', () => self.clients.claim());

// Receive push notifications
self.addEventListener('push', function (e) {
    if (!(
        self.Notification &&
        self.Notification.permission === 'granted'
    )) {
        //notifications aren't supported or permission not granted!
        console.info('nononono')
        return;
    }

    if (e.data) {
        let message = e.data.json();
        e.waitUntil(self.registration.showNotification(message.title, {
            body: message.body,
            icon: message.icon,
            actions: message.actions
        }));
    }
});

// Click and open notification
self.addEventListener('notificationclick', function(event) {
    event.notification.close();

    if (event.action === 'farm') clients.openWindow("/farm");
    else if (event.action === 'home') clients.openWindow("/");
    else if (event.action === 'training') clients.openWindow("/mining-training");
    else if (event.action === 'dns') clients.openWindow("/shops/dns");
    else if (event.action === 'ali') clients.openWindow("/shops/aliexpress");
    else if (event.action === 'avito') clients.openWindow("/avito");
    else if (event.action === 'friends') clients.openWindow("/friends");
    else if (event.action === 'locations') clients.openWindow("/locations");
    else if (event.action === 'vk-chat') clients.openWindow("https://vk.me/join/au1/k0nOTjLasxMO6wX50QuyPfYosyWdPEI = ");
    else clients.openWindow(event.action); // Open link from action
}, false);

В vite-pwa-plugin есть только немного информации о возможности создания webpush - документация

Я нашел некоторый код для сервис-воркера в этом репозитории и скопировал некоторый код из старого по умолчанию, сгенерированного конфигом vite.config.ts.

Пришлось сделать это сегодня с уведомлениями Firebase. Вот что я сделал на случай, если это поможет кому-то еще в будущем

Создал файл сервис-воркера (включил полную версию, потому что мне было трудно найти информацию о том, как добавлять уведомления)

importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");

if ("serviceWorker" in navigator) {
    navigator.serviceWorker
        .register("../firebase-messaging-sw.js")
        .then(function (registration) {
            console.info("Registration successful, scope is:", registration.scope);
        })
        .catch(function (err) {
            console.info("Service worker registration failed, error:", err);
        });
}

// Initialize the Firebase app in the service worker by passing the generated config
var firebaseConfig = {
    apiKey: "your apiKey",
    authDomain: "your authDomain",
    projectId: "you get the point by now right?",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
};

firebase.initializeApp(firebaseConfig);

// Retrieve firebase messaging
const messaging = firebase.messaging();

messaging.onBackgroundMessage(function (payload) {
    console.info("Received background message ", payload);

    const notificationTitle = payload.notification.title;
    const notificationOptions = {
        body: payload.notification.body,
        icon: "/images/yourLogo.png",
    };

    self.registration.showNotification(notificationTitle, notificationOptions);
});

В моем (React) приложении я зарегистрировался для уведомлений переднего плана, подобных этому

import { initializeApp } from "firebase/app";
import { getMessaging, getToken, onMessage } from "firebase/messaging";

const firebaseConfig = {
    apiKey: "your apiKey",
    authDomain: "your authDomain",
    projectId: "you get the point by now right?",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
};

const firebaseApp = initializeApp(firebaseConfig);
const messaging = getMessaging(firebaseApp);

export const getFirebaseToken = (setTokenFound) => {
    return getToken(messaging, {
        vapidKey: "key is generated for you on firebase",
    })
        .then((currentToken) => {
            if (currentToken) {
                console.info("current token for client: ", currentToken);
                setTokenFound(true);
                // Track the token -> client mapping, by sending to backend server
                // show on the UI that permission is secured
            } else {
                console.info("No registration token available. Request permission to generate one.");
                setTokenFound(false);
                // shows on the UI that permission is required
            }
        })
        .catch((err) => {
            console.info("An error occurred while retrieving token. ", err);
            // catch error while creating client token
        });
};

export const onMessageListener = () =>
    new Promise((resolve) => {
        onMessage(messaging, (payload) => {
            console.info("payload", payload);
            resolve(payload);
        });
    });

и назвал это в моем файле App.tsx следующим образом. Итак, теперь мы получаем приглашение, запрашивающее разрешение на уведомление, и мы можем видеть токен пользователя в консоли, если он принимает

    getFirebaseToken(setTokenFound);
    isTokenFound ? console.info("Token found") : console.info("Token not found");

    onMessageListener()
        .then((payload) => {
            toast.success(payload.notification.title, payload.notification.body);
            console.info(payload);
        })
        .catch((err) => console.info("failed: ", err));

В моем vite.config.js на верхнем уровне объекта, из которого я возвращаюсь defineConfig(), все, что мне нужно было сделать, это импортировать скрипт вот так

workbox: {
    importScripts: ["./firebase-messaging-sw.js"],
},

Этот последний бит занял у меня больше всего времени, чтобы понять .... надеюсь, что это кому-то поможет

Другие вопросы по теме