Progressive Web App 推播通知行为 (25)

推播通知行为

推播通知行为主要分成视觉、後续事件两大部分,前几天的文章开箱了:

这篇文章主要会更进一步解说视觉上可以进行设定的参数,以及收到通知後的行为,关於行为相关的参数如下。

{
  "//": "行为相关参数",
  "tag": "<String>",
  "renotify": "<Boolean>",
  "data": "<Anything>",
  "requireInteraction": "<boolean>",
  "silent": "<Boolean>",

  "//": "视觉与行为参数",
  "actions": "<Array of Strings>"
}

如果担心觉得太抽象,欢迎先用以下连结的通知产生器先试玩:
https://tests.peter.sh/notification-generator/

这次的 Demo 连结如下也欢迎各位大大试玩看看:
https://linyencheng-push-notification.herokuapp.com/

通知行为设定

除了单纯 UI 显示相关的参数以外,接下来介绍一些会影响流程的相关参数:

  • 通知分组 (Tag)
  • 重复通知 (Renotify)
  • 通知动作 (Actions)
  • 静音通知设定 (Silent)
  • 通知互动设定 (Require Interaction)

通知分组 (Tag)

Tag 这个设定是方便让讯息不要一直叠加,只要有同个 Tag 的新讯息就会关掉旧的并用新的取代,要注意的是後面取代的通知都不会触发装置的声音或是震动。

const title = "Notification 1 of 3";
const options = {
  body: "With 'tag' of 'message-group-1'",
  tag: "message-group-1",
};
registration.showNotification(title, options);

重复通知 (Renotify)

因为 Tag 的实作不会触发声音或是震动,在聊天软件中的某些情况下需要再次被叮咚,所以在 tag 的基础上增加了 renotify 这个配置。

const title = "Notification 2 of 2";
const options = {
  tag: "renotify",
  renotify: true,
};
registration.showNotification(title, options);

静音通知设定 (Silent)

停用震动、铃声硬体通知。

const title = "Silent Notification";
const options = {
  silent: true,
};
registration.showNotification(title, options);

通知互动设定 (Require Interaction)

需要等使用者针对通知互动後才会关闭通知,Android 没有这个问题但 Windows 7 桌面版本的通知会在一定时间後消失,直到 Windows 10 才会在通知列,所以有了这个配置。

const title = "Require Interaction Notification";
const options = {
  body: "With \"requireInteraction: 'true'\".",
  requireInteraction: true,
};
registration.showNotification(title, options);

通知动作 (Actions)

透过定义 actions 就能够让通知带有按钮,将通知加上按钮後,就可以针对按钮补上後续的事件,以这次小编实作的功能来说,就是以下的配置。

self.registration.showNotification(data.title, {
  image: "https://linyencheng.github.io/img/404-bg.jpg",
  icon: "https://linyencheng.github.io/img/icon_wechat.png",
  vibrate: [200, 100, 200, 100, 400],
  body: "嗨,我是彦成,喜欢爬山的前端工程师,有个部落格叫前端三分钟 :)",
  actions: [
    { action: "know-more", title: "了解更多" },
    { action: "fans", title: "按赞粉专" },
  ],
});

实作後实际的画面

通知事件流程处理

经过相关进阶设定後,就是针对相关的事件进行後续的处理和优化:

  • 点击通知
    • 开启视窗
    • Focus Tab
  • 关闭通知
  • 合并通知

点击通知

透过 service worker 去监听点击的事件做後续处理。

self.addEventListener("notificationclick", function (event) {
  const clickedNotification = event.notification;
});

开启视窗

点击通知後开启视窗并开启网页。

const examplePage = "/hello.html";
const promiseChain = clients.openWindow(examplePage);
event.waitUntil(promiseChain);

Focus Tab

如果 URL 已经在浏览器中被开启就直接到那个页面。

const urlToOpen = new URL(examplePage, self.location.origin).href;

const promiseChain = clients
  .matchAll({
    type: "window",
    includeUncontrolled: true,
  })
  .then((windowClients) => {
    let matchingClient = null;

    for (let i = 0; i < windowClients.length; i++) {
      const windowClient = windowClients[i];
      if (windowClient.url === urlToOpen) {
        matchingClient = windowClient;
        break;
      }
    }

    if (matchingClient) {
      return matchingClient.focus();
    } else {
      return clients.openWindow(urlToOpen);
    }
  });

event.waitUntil(promiseChain);

关闭通知

关闭通知也有相关事件。

self.addEventListener("notificationclose", function (event) {
  const dismissedNotification = event.notification;
});

合并通知

假设今天加入了 requireInteraction 通知就会常驻,若是通知一直叠加上去也是非常恼人,这时候就可以透过合并通知的设计来优化用户体验。

  1. 先抓出现有的
  2. 把现有的关闭再发一则新的
const promiseChain = registration.getNotifications().then((notifications) => {
  let currentNotification;

  for (let i = 0; i < notifications.length; i++) {
    if (notifications[i].data && notifications[i].data.userName === userName) {
      currentNotification = notifications[i];
    }
  }

  return currentNotification;
});

if (currentNotification) {
  currentNotification.close();
}

实作

这次小编实作的程序码如下:

  1. 跳过等待直接生效
  2. 处理 push 事件,收到就显示通知,并且在通知加上 aciton
  3. 处理 notificationclick 事件,并且针对不同 action 加上各自的事件
  4. 开启不同的连结
self.addEventListener("install", function (event) {
  self.skipWaiting();
});

self.addEventListener("push", (event) => {
  const data = event.data.json();
  switch (data.title) {
    case "前端三分钟":
      self.registration.showNotification(data.title, {
        image: "https://linyencheng.github.io/img/404-bg.jpg",
        icon: "https://linyencheng.github.io/img/icon_wechat.png",
        vibrate: [200, 100, 200, 100, 400],
        body: "嗨,我是彦成,喜欢爬山的前端工程师,有个部落格叫前端三分钟 :)",
        actions: [
          { action: "know-more", title: "了解更多" },
          { action: "fans", title: "按赞粉专" },
        ],
      });
      break;
    default:
      self.registration.showNotification(data.title, {
        icon: "https://linyencheng.github.io/img/icon_wechat.png",
        body: "恭喜! 成功注册推播通知",
      });
      break;
  }
});

self.addEventListener("notificationclick", function (event) {
  let url;
  if (!event.action) {
    console.log("Notification Click.");
    return;
  }

  switch (event.action) {
    case "know-more":
      url = "https://linyencheng.github.io/";
      break;
    case "fans":
      url = "https://www.facebook.com/linyencheng.tw";
      break;
    default:
      console.log(`Unknown action clicked: '${event.action}'`);
      break;
  }

  event.notification.close(); // Android 需要触发关闭
  event.waitUntil(
    clients.matchAll({ type: "window" }).then((windowClients) => {
      // 看有没有开过了
      for (var i = 0; i < windowClients.length; i++) {
        var client = windowClients[i];
        // 有的话就 Focus 就好了
        if (client.url === url && "focus" in client) {
          return client.focus();
        }
      }
      // 开新的页面
      if (clients.openWindow) {
        return clients.openWindow(url);
      }
    })
  );
});

<<:  【程序】说是说不 转生成恶役菜鸟工程师避免 Bad End 的 30 件事 - 25

>>:  [Day23] 信箱验证API – views

我们截至今天为止,总共在 firebase 上做了 168 次 A/B Testing

我跟男友开发这个聊天软件三年,刚刚回去算我们截至今天为止,总共在 firebase 上做了 168 ...

方法论之间的差异:敏捷与瀑布

任何软件应用程序的开发都采用一种系统方法,该方法涉及从规划到部署的多个步骤,该方法称为软件开发生命周...

[笔记]vue-cli i18n 多语系应用练习

参考文章: https://medium.com/easons-murmuring/%E5%9C%A...

电子书阅读器上的浏览器 [Day10] 支援画面点击翻页

既然是电子书阅读器,一般人最常拿来用的功能应该就是看电子书吧。看电子书时如果要翻页的话,通常会点击画...

[Day08] JavaScript - 回圈_part 2

forEach 来看看forEach在MDN的定义 Array.prototype.forEach(...