Skip to content

notificationClick does not work as expected if original url is open #8748

@altendorfme

Description

@altendorfme

Operating System

Windows 11

Environment (if applicable)

132.0.6834.159

Firebase SDK Version

10.14.0

Firebase SDK Product(s)

Messaging

Project Tooling

Javascript

Detailed Problem Description

In the serviceWorker of a webpush, if a tab of the site where the webpush is open, when clicking on the notification it only goes to the tab, but does not redirect to the push link.

If I put notificationclick before const messaging = firebase.messaging();, it works, but then I don't have data on the correct push URL.
It has already been tested on SDK 11.2.0 as well and the error is the same.

// Firebase Cloud Messaging Service Worker
importScripts('https://www.gstatic.com/firebasejs/10.14.0/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/10.14.0/firebase-messaging-compat.js');

// Consistent unique identifier
const SERVICE_WORKER_ID = 'XYZ';

// Firebase configuration
const firebaseConfig = {
    apiKey: "XYZ_XYZ",
    authDomain: "XYZ.firebaseapp.com",
    projectId: "XYZ",
    storageBucket: "pXYZ.firebasestorage.app",
    messagingSenderId: "XYZ",
    appId: "1:XYZ:web:XYZ"
};

// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();

// Advanced duplicate prevention with localStorage
const NOTIFICATION_STORAGE_KEY = 'pushbase_recent_notifications';
const ACTIVE_SW_KEY = 'pushbase_active_service_worker';

// Ensure only one service worker is active
function checkServiceWorkerSingleton() {
    const now = Date.now();
    const activeWorker = JSON.parse(localStorage.getItem(ACTIVE_SW_KEY) || '{}');

    // If no active worker or worker is old, claim this one
    if (!activeWorker.id || (now - activeWorker.timestamp > 60000)) {
        localStorage.setItem(ACTIVE_SW_KEY, JSON.stringify({
            id: SERVICE_WORKER_ID,
            timestamp: now
        }));
        return true;
    }

    // If another worker is active, do not process
    return activeWorker.id === SERVICE_WORKER_ID;
}

function storeNotification(notificationKey) {
    try {
        const now = Date.now();
        const recentNotifications = JSON.parse(localStorage.getItem(NOTIFICATION_STORAGE_KEY) || '[]');

        // Remove old notifications (older than 5 minutes)
        const filteredNotifications = recentNotifications.filter(
            item => now - item.timestamp < 5 * 60 * 1000
        );

        // Add new notification
        filteredNotifications.push({
            key: notificationKey,
            timestamp: now
        });

        localStorage.setItem(NOTIFICATION_STORAGE_KEY, JSON.stringify(filteredNotifications));
    } catch (error) {
        console.error('Error storing notification:', error);
    }
}

function isDuplicateNotification(payload) {
    try {
        const now = Date.now();
        const notificationKey = JSON.stringify({
            title: payload.notification?.title,
            body: payload.notification?.body,
            data: payload.data
        });

        const recentNotifications = JSON.parse(localStorage.getItem(NOTIFICATION_STORAGE_KEY) || '[]');

        // Check for duplicate within last 5 minutes
        const isDuplicate = recentNotifications.some(
            item => item.key === notificationKey && now - item.timestamp < 5 * 60 * 1000
        );

        return isDuplicate;
    } catch (error) {
        console.error('Error checking duplicate notification:', error);
        return false;
    }
}

// Intercept and filter background messages
messaging.onBackgroundMessage((payload) => {
    // Ensure only one service worker processes the message
    if (!checkServiceWorkerSingleton()) {
        console.log('Skipping notification - another service worker is active');
        return;
    }

    console.log('Raw payload received:', payload);

    // Check for duplicate notification across all browser contexts
    if (isDuplicateNotification(payload)) {
        console.log('Blocked duplicate notification');
        return;
    }

    // Store notification to prevent duplicates
    const notificationKey = JSON.stringify({
        title: payload.notification?.title,
        body: payload.notification?.body,
        data: payload.data
    });
    storeNotification(notificationKey);

    // Extract notification details
    const notification = payload.notification || {};
    const data = payload.data || {};
    const fcmOptions = payload.webpush.fcm_options || {};

    // Prepare notification options
    const notificationOptions = {
        body: notification.body || data.body || 'New notification',
        icon: notification.icon || data.icon || '/public/assets/pushbase.svg',
        badge: notification.badge || data.badge || '/public/assets/pushbase.svg',
        tag: SERVICE_WORKER_ID, // Use consistent tag

        // Advanced configuration
        requireInteraction: notification.requireInteraction || data.requireInteraction || false,
        renotify: false, // Prevent renotification
        silent: notification.silent || data.silent || false,

        // Additional metadata
        data: {
            url: fcmOptions.link || notification.click_action || data.url,
            campaignId: data.campaignId,
            segmentId: data.segmentId,
            type: data.type,
            swId: SERVICE_WORKER_ID
        }
    };

    // Optional image
    if (notification.image || data.image) {
        notificationOptions.image = notification.image || data.image;
    }

    // Show unique notification
    self.registration.showNotification(
        notification.title || data.title || 'PushBase Notification',
        notificationOptions
    );
});

// Notification click handler
self.addEventListener('notificationclick', (event) => {
    event.notification.close();

    const notificationData = event.notification.data;

    // Open URL or default
    event.waitUntil(
        clients.openWindow(
            notificationData.url || '/teste2'
        )
    );
});

// Ensure single service worker instance
self.addEventListener('install', (event) => {
    self.skipWaiting(); // Take over immediately
});

self.addEventListener('activate', (event) => {
    event.waitUntil(self.clients.claim());
});

Steps and code to reproduce issue

Send push, receive push, click and not open correct url!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions