【问题标题】:Can't get custom push notification event working in PWA (Firebase)无法在 PWA (Firebase) 中获取自定义推送通知事件
【发布时间】:2019-04-22 20:30:21
【问题描述】:

我一直在寻找如何让我的自定义推送通知正常工作的几个小时。以下是我设置项目的方式:没有前端框架、Node.js/Express.js 后端、作为推送管理器的 Firebase 云消息传递 (FCM) 和自定义服务工作者。我目前在 localhost 上托管应用程序,并且设置了 HTTPS 和一个 manifest.json,其中包含要开始的最少字段。 manifest.json 包含一个 start_url 字段,该字段指向 /index.html,即应用程序的登录页面。该应用程序与 webpack v. 4 捆绑在一起。

后端

在后端,我在特定路由器中设置了 Firebase Admin SDK,并向 FCM 服务器发送了一个类似于以下内容的自定义通知对象:

let fcMessage = {
  data : {
    title : 'Foo',
    tag : 'url to view that opens bar.html'
  }
};

当后端发生有趣的事件时,它会检索包含 FCM 注册令牌的用户列表并将通知发送到 FCM 服务器。这部分效果很好。

前端

我在前端有两个服务人员。在我的前端 index.js 中,我在域根目录中注册了一个名为 sw.js 的自定义服务工作者,并告诉 firebase 使用该服务工作者,如下所示:

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js')
             .then(registration => {
                  messaging.useServiceWorker(registration);
             })
             .catch(err => console.error(`OOps! ${err}`));
}

FCM 及其凭据已设置,用户可以订阅推送通知。我不会在这里展示该代码,因为它可以工作,而且我不认为这是问题所在。

现在谈谈服务人员本身。我的域根目录中有一个 firebase-messaging-sw.js 文件。它包含以下代码:

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

firebase.initializeApp(configuration);
const messaging = firebase.messaging();

配置只是所有​​信用的占位符。再次,这些东西有效。

我想要做的是不使用 FCM 推送通知,而是创建我自己的推送通知,其中包含用户可以单击并转到该视图的视图的 URL。以下代码几乎可以工作,是我在另一个网站上找到的hack

class CustomPushEvent extends Event {
  constructor(data) {
    super('push');

    Object.assign(this, data);
      this.custom = true;
    }
}
self.addEventListener('push', (e) => {
  console.log('[Service Worker] heard a push ', e);
  // Skip if event is our own custom event
  if (e.custom) return;

  // Keep old event data to override
  let oldData = e.data;

  // Create a new event to dispatch
  let newEvent = new CustomPushEvent({
    data: {
      json() {
        let newData = oldData.json();
        newData._notification = newData.notification;
        delete newData.notification;
        return newData;
      },
    },

    waitUntil: e.waitUntil.bind(e),
  })

    // Stop event propagation
    e.stopImmediatePropagation();

    // Dispatch the new wrapped event
    dispatchEvent(newEvent);
});

messaging.setBackgroundMessageHandler(function(payload) {
  if (payload.hasOwnProperty('_notification')) {
    return self.registration.showNotification(payload.data.title,
      {
        body : payload.data.text,
        actions : [
            { 
                action : `${payload.data.tag}`,
                title : 'Go to link'
            }
        ]
    });
  } else {
    return;
  }
});

  self.addEventListener('notificationclick', function(e) {
    console.log('CLICK');

    e.notification.close();

    e.waitUntil(clients.matchAll({ type : 'window' })
     .then(function(clientList) {
       console.log('client List ', clientList);
       const cLng = clientList.length;
       if (clientList.length > 0) {
          for (let i = 0; i < cLng; i++) {
            const client = clientList[i];

            if (client.url === '/' && 'focus' in client) {
              return client.focus();
            }
        }
    } else {
        console.log('no clients ', e.action);
        clients.openWindow(e.action)
            .then(client => {
                console.log('client ', client);
                return client.navigate(e.action);
            })
            .catch(err => console.error(`[Service Worker] cannot open client : ${err} `));
    }
  }))
});

该 hack 旨在捕获推送事件和 FCM 默认通知负载,而是通过通过 Notification API 制作的自定义负载来提供该负载。

上面的代码很好用,但前提是我把它放在 firebase-messaging-sw.js 文件中。这不是我真正想要做的:我想把它放在 sw.js 文件中,但是当我这样做时,sw.js 听不到任何推送事件,而是得到默认的 FCM 推送通知。我还尝试将整个 firebase-messaging-sw 脚本导入自定义服务工作者,但它仍然听不到消息事件。

为什么我想在我的 Service Worker 中使用它而不是在 Firebase 中使用它?它能够在传递到通知正文的“标签”字段的视图上打开应用程序。如果我使用 Firebase Service Worker,它会告诉我它不是活动的注册 Service Worker,虽然应用确实在新窗口中打开,但它只在 /index.html 上打开。

我做了一些小观察:当最后一段代码添加到 firebase-messaging-sw.js 文件时,clients 数组总是空的。自定义服务工作者安装正确,因为它处理应用程序外壳缓存并正常侦听所有其他事件。 firebase-messaging-sw service worker 也已正确安装。

【问题讨论】:

    标签: javascript firebase push-notification firebase-cloud-messaging progressive-web-apps


    【解决方案1】:

    在痛苦和恼怒之后,我发现了问题所在。它是应用程序架构(即传统的多页应用程序)和自定义服务工作者 sw.js 中格式错误的 url 的组合:

    sw.js

    self.addEventListener('fetch', e => {
      // in this app, if a fetch event url calls the back-end, it contains api and we
      // treat it differently from the app shell
    
      if (!e.request.url.includes('api')) {
        switch(e.request.url) {
            case `${endpoint}/bar`: // <-- this is the problem right here
                matcher(e, '/views/bar.html');
                break;
            case `${endpoint}/bar.js`:
                matcher(e, '/scripts/bar.js');
                break;
            case `${endpoint}/index.js`:
                matcher(e, '/index.js');
                break;
            case `${endpoint}/manifest.json`:
                matcher(e, '/manifest.json');
                break;
            case `${endpoint}/baz/`:
                matcher(e, '/views/bar.html');
                break;
            case `${endpoint}/baz.js`:
                matcher(e, '/scripts/bar.js');
                break;
            default:
                console.log('default');
                matcher(e, '/index.html');
        }
      }
    });
    

    Matcher 是将请求 url 与服务器上的文件匹配的函数。如果文件已经存在于缓存中,则返回缓存中的内容,如果缓存中不存在,则从服务器获取文件。

    每次用户点击通知时,都应该将他/她带到“栏”html 视图。在开关中它必须是:

    case `${endpoint}/bar/`:
    

    而不是

    case `${endpoint}/bar`:
    

    即使与消息相关的代码仍在 firebase-messaging-sw.js 文件中,但当浏览器处于后台时,它会创建一个新的 WindowClient。 WindowClient 受 sw.js 的影响,而不是 firebase-messaging-sw.js。结果,当窗口打开时,sw.js 拦截调用并从 firebase-messaging-sw.js 接管。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-09
      • 2020-07-23
      • 1970-01-01
      • 2021-06-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多