使用 FCM 在前台浏览器中每个标签页接收一条通知

7

我正在使用FCM API接收来自浏览器的推送通知。 firebase-messaging-sw.js 按预期工作,messaging.setBackgroundMessageHandler 只在web应用程序在后台时触发一次。但是,当应用程序处于前台时,我会为每个浏览器标签接收到一个通知(如果我在3个标签中打开应用,则会收到3个通知)。我想知道该如何处理此问题,因为我找不到任何与此问题相关的参考资料。以下是前台FCM消息的代码:

import NotificationActionCreators from '../actions/NotificationActionCreators';
import NotificationService from './NotificationService';
import LocalStorageService from './LocalStorageService';
import { FIREBASE_SCRIPT, FCM_URL, FCM_API_KEY, FCM_AUTH_DOMAIN, FCM_PROJECT_ID, FCM_SENDER_ID, PUSH_PUBLIC_KEY } from '../constants/Constants';


class ServiceWorkerService {

constructor() {
    this._messaging = null;
    this._subscriptionData = null;
}

// This function is called once
init() {
    this.loadScript(FIREBASE_SCRIPT, () => this.onFirebaseLoaded());
}

onFirebaseLoaded() {
    // Initialize Firebase
    let config = {
        apiKey: FCM_API_KEY,
        authDomain: FCM_AUTH_DOMAIN,
        projectId: FCM_PROJECT_ID,
        messagingSenderId: FCM_SENDER_ID
    };
    firebase.initializeApp(config);
    this._messaging = firebase.messaging();

    this.requestPermission();

    // Callback fired if Instance ID token is updated.
    this._messaging.onTokenRefresh(() => {
        this._messaging.getToken()
            .then((refreshedToken) => {
                console.log('Token refreshed.');
                NotificationActionCreators.unSubscribe(this._subscriptionData).then(() => {
                    // Indicate that the new Instance ID token has not yet been sent to the
                    // app server.
                    this.setTokenSentToServer(false);
                    // Send Instance ID token to app server.
                    this.sendTokenToServer(refreshedToken);
                }, () => console.log('Error unsubscribing user'));
            })
            .catch(function(err) {
                console.log('Unable to retrieve refreshed token ', err);
            });
    });

    // Handle incoming messages.
    // *** THIS IS FIRED ONCE PER TAB ***
    this._messaging.onMessage(function(payload) {
        console.log("Message received. ", payload);
        const data = payload.data;

        NotificationActionCreators.notify(data);
    });
}

requestPermission() {
    console.log('Requesting permission...');
    return this._messaging.requestPermission()
        .then(() => {
            console.log('Notification permission granted.');
            this.getToken();
        })
        .catch(function(err) {
            console.log('Unable to get permission to notify.', err);
        });
}

getToken() {
    // Get Instance ID token. Initially this makes a network call, once retrieved
    // subsequent calls to getToken will return from cache.
    return this._messaging.getToken()
        .then((currentToken) => {
            if (currentToken) {
                this.sendTokenToServer(currentToken);
            } else {
                // Show permission request.
                console.log('No Instance ID token available. Request permission to generate one.');
                this.setTokenSentToServer(false);
            }
        })
        .catch(function(err) {
            console.log('An error occurred while retrieving token. ', err);
            this.setTokenSentToServer(false);
        });
}

sendTokenToServer(currentToken) {
    const subscriptionData = {
        endpoint: FCM_URL + currentToken,
        platform: 'Web'
    };
    if (!this.isTokenSentToServer()) {
        console.log('Sending token to server...');
        this.updateSubscriptionOnServer(subscriptionData);
    } else {
        console.log('Token already sent to server so won\'t send it again ' +
            'unless it changes');
    }
    this._subscriptionData = subscriptionData;
}

isTokenSentToServer() {
    return LocalStorageService.get('sentToServer') == 1;
}

setTokenSentToServer(sent) {
    LocalStorageService.set('sentToServer', sent ? 1 : 0);
}

updateSubscriptionOnServer(subscriptionData) {
    if (subscriptionData) {
        NotificationActionCreators.subscribe(subscriptionData);
        this.setTokenSentToServer(true);
        this._subscriptionData = subscriptionData;
    } else {
        console.log('Not subscribed');
    }
}

unSubscribe() {
    this.removeSetTokenSentToServer();
    return this._messaging.getToken()
        .then((currentToken) => {
            return this._messaging.deleteToken(currentToken)
                .then(() => {
                    console.log('Token deleted.');
                    return NotificationActionCreators.unSubscribe(this._subscriptionData);
                })
                .catch(function(err) {
                    console.log('Unable to delete token. ', err);
                    return new Promise(function(resolve, reject) {
                        reject(error)
                    });
                });
       })
        .catch(function(err) {
            console.log('Error retrieving Instance ID token. ', err);
            return new Promise(function(resolve, reject) {
                reject(error)
            });
        });
    }
}

removeSetTokenSentToServer() {
    LocalStorageService.remove('sentToServer');
}

loadScript = function (url, callback) {
    let head = document.getElementsByTagName('head')[0];
    let script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;

    script.onload = callback;

    // Fire the loading
    head.appendChild(script);
}
}

有没有办法只在找到第一个选项卡时显示通知?
3个回答

10

我发现实现这一点的唯一方法是检查并在本地存储中设置一个“通知ID”变量,并使用随机时间的setTimeout

this._messaging.onMessage(function(payload) {
    const data = payload.data;
    // This prevents to show one notification for each tab
    setTimeout(() => {
        if (localStorage.getItem('lastNotificationId')) != parseInt(data.notId)) {
            localStorage.setItem('lastNotificationId', parseInt(data.notId))
            NotificationActionCreators.notify(data);
        }
    }, Math.random() * 1000);
});

notId 是在推送通知中发送的,它是该通知的标识符。


还有其他事情吗?因为在我们公司,超时不被视为良好的实践:P - Sarvagya Saxena

2

使用 document.hidden 来检测活动选项卡

if (!document.hidden) {
    NotificationActionCreators.notify(data);
}

1
如果当前选项卡不是我的网站怎么办?在这种情况下,我猜通知会被跳过。 - Harshit verma
1
@Harshitverma @masoud 是正确的,你需要在前台处理 document.hidden,因为 FCM 会在一个标签页处于前台时通知所有标签页。 - Joseph

0
一种方法是为每个选项卡创建一个唯一的变量,例如Math.random()或(new Date()).getMilliseconds(),并将其与令牌一起存储在服务器上。现在,服务器可以通过将变量附加到消息来针对每个选项卡进行定位,每个选项卡在执行操作之前都会检查消息变量。
为了减少定位已关闭的选项卡的可能性,请在每个请求中发送变量,这样服务器始终会针对最新的选项卡进行定位。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接