Firefox 44 推出网络推送功能

更新于 2016 年 2 月 20 日:推送服务现在需要在对端点的请求中明确包含“TTL”头。文章已更新以反映这一点。更多详细信息请参阅 Mozilla 服务博客

您是否曾经希望网站能够在发生重要事件时通知您,即使您没有打开该网站?也许您有传入的 WebRTC 通话、即时消息或财务更新。也许您的城市刚刚宣布了紧急扫雪计划。

有时您只想在发生某些事情时知道。

这就是网络推送的功能。它现在已在 Firefox 44 中可用。

网络推送是什么样子的?

只要您的浏览器正在运行,它就可以接收来自网站的通知,即使该网站未打开。这意味着您可以关闭电子邮件选项卡,并在收到新邮件时仍然收到通知。这对内存使用、性能和电池续航时间来说都是巨大的优势。

来自网站的通知与原生通知没有区别,Mozilla 的 Service Worker 食谱中有一些现场演示,您可以亲眼看到这一点。

Screenshot of a Push Notification on Mac OS X

与地理位置或网络摄像头访问类似,网络推送需要网站在向用户显示通知之前获得明确且可撤销的权限。

Screenshot of the in-browser Push Notification permissions prompt

隐私方面怎么样?

网络推送通过与充当消息中央中继的推送服务保持持久连接来工作。每个浏览器供应商都运行他们自己的推送服务,并且该服务旨在保护您的隐私

  1. 为了防止跨站点关联,每个网站都会为您的浏览器提供不同的匿名网络推送标识符。
  2. 为了阻止窃听,有效负载会被加密到仅由您的浏览器持有的公钥/私钥对。
  3. 只有在您拥有活动网络推送订阅时,Firefox 才会连接到推送服务。这可能是对网站的订阅,也可能是对 Firefox Hello 或 Firefox Sync 等浏览器功能的订阅。

您始终处于控制之中:推送通知是选择加入的,您可以随时从任何网站撤销权限,方法是从 页面信息面板 或“通知”部分(首选项 → 内容)中撤销。

网络推送是如何工作的?

在今天之前,人们不得不依赖应用程序、电子邮件或短信来及时获取通知。现在网络可以做到这一点。

网络推送是 Service Worker 标准的扩展,这意味着您可以在 Mozilla 的 Service Worker 食谱 中找到网络推送的出色且带注释的演示。MDN 还提供了关于网络推送的 优秀文档,如果您需要直接查看源代码,规范的最新编辑草案位于 GitHub 上

去年 10 月,Chris Mills 在 Hacks 上撰写了 一篇关于网络推送的精彩介绍,其中解释了 Service Worker 生命周期及其与推送的关系。回顾一下

  1. 网站可 向浏览器注册 Service Worker。Service Worker 是具有强大功能的小型 JavaScript 程序,例如拦截网络请求或即使在父网站关闭时也能运行。
  2. Service Worker 注册对象 公开了一个 pushManager 属性
  3. 网站使用 pushManager 来 获取现有订阅创建新订阅
  4. 订阅对象 公开了有关订阅的元数据,包括浏览器供应商推送服务上的唯一 端点 URL

每当网站向该端点发出 POST 请求时,推送服务就会将消息路由到您的浏览器,其中相应的 Service Worker 会收到一个 push 事件。然后,Service Worker 可以 显示通知 或采取其他操作。

对端点的 HTTP POST 请求必须包含“TTL”头,设置为应保存消息的秒数(如果用户不在线)。TTL 到期后,未送达的消息将过期且不会再送达。有关此标头的更多技术信息,请参阅 Mozilla 服务博客

在代码中,接收事件的 Service Worker 可能如下所示

self.addEventListener('push', function(event) {
  event.waitUntil(
    self.registration.showNotification('Example Notification', {
      body: 'Hello, world!',
    })
  );
})

同时,注册 Service Worker 并获得显示通知的权限可能类似于下面的代码。

注意:此代码示例使用 ES7 的草案 async/await 语法,因为它读起来最清楚。要在生产环境中使用它,请查看 纯 JavaScript 中的等效代码

async function registerForPush() {
  // Register the Service Worker
  let registration = await navigator.serviceWorker.register('service-worker.js');
  
  // Check if we already have a subscription
  let subscription = await registration.pushManager.getSubscription();
  
  // If not, try to subscribe.
  if (!subscription) {
    subscription = await registration.pushManager.subscribe();
  }
  
  // Save the subscription data on our website's backend.  
  await fetch('/save-push-endpoint', {
    method: 'post',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(subscription)
  });
  
  // Done! Now our backend can send Push messages by POSTing to subscription.endpoint!
}

同样,Service Worker 食谱 中有许多带有带注释源代码的现场演示。如果您感到困惑,请先阅读这些演示。

其他问题

用户的端点 URL 会发生更改吗?

端点可以随时更改。在实践中,这种情况应该很少见,但您应该始终准备好处理 pushsubscriptionchange 事件,并且在每次调用 getSubscription()subscribe() 时都应该检查新的端点。

浏览器的支持情况如何?

在撰写本文时,推送功能适用于桌面版 Firefox,并且 Chrome 部分支持。向 Chrome 推送也需要 一些额外的设置。Microsoft Edge 团队将推送列为 正在考虑中。更多信息请参阅 Can I Use?

如何让用户取消订阅推送?

使用 subscription.unsubscribe() 方法。不要忘记更新您的后端,以便停止向旧端点发送通知。

在实际发生之前,我能否检查 subscribe() 是否会提示用户?

可以!调用 pushManager.permissionState() 会返回一个 Promise,该 Promise 解析为当前的权限状态:"granted""denied""prompt"

请务必在评论中告诉我们您对推送 API 的看法。我们欢迎所有反馈,包括建议、问题和错误报告。

关于 Dan Callahan

Mozilla 开发者关系工程师,前 Mozilla Persona 开发者。

更多 Dan Callahan 的文章…


54 条评论

  1. Minishlink

    好消息!

    对于 Web Push 的服务器端,已经存在一些库
    – Marco Castelluccio 为 Node.js 开发的 web-push:https://github.com/marco-c/web-push
    – 我(Louis Lagrange)为 PHP 开发的 WebPush:https://github.com/Minishlink/web-push

    我不知道是否有任何 Python 实现。

    2016 年 1 月 26 日 08:50

  2. Valentin

    我想知道……推送服务器有什么要求?它是不是一个(非常)庞大的数据库?作为用户,我可以选择其他推送服务器吗?

    2016 年 1 月 26 日 09:18

    1. Dan Callahan

      我们的生产推送服务器是用 PyPy 编写的,可以在 https://github.com/mozilla-services/autopush 找到。据我所知,主要问题是每个连接的内存开销,因为该服务必须维护大量 Websocket 连接,其中大部分在任何给定时间都是空闲的。

      自托管不一定受支持,但并非不可能。一旦您启动并运行了另一个服务,您只需更改 about:config 中的 dom.push.serverURL 即可。

      2016 年 1 月 26 日 09:57

  3. Jesus Perales

    此实现与 Firefox OS 的网络推送兼容吗?

    2016 年 1 月 26 日 10:09

  4. g

    我想会有一个配置项可以无条件地关闭这堆垃圾,对吧?

    2016 年 1 月 26 日 12:54

    1. Dan Callahan

      现在,用户可见的设置是基于每个来源的。例如,您可以使用“不要再询问我 [在此网站上]”来关闭提示。如果您想完全关闭推送,请在 about:config 中切换 dom.push.enabled

      2016 年 1 月 26 日 13:16

  5. Bob

    我想 Netscape 只是受不了让 Netscape Netcaster 安息。

    2016 年 1 月 26 日 13:40

  6. David Piepgrass

    我对 Chrome 中此功能的行为感到震惊,Firefox 用户也会遇到类似的惊喜。问题在于,**没有任何迹象表明您可以从浏览器中未打开的网站接收“通知”**。

    当然,当 Facebook 选项卡打开时,它会询问我是否想要“通知”,我回答“当然!”我从未想过在关闭选项卡后我仍然会收到“通知”。

    为什么 Firefox 没有提到即使网站关闭也会收到通知?当所有选项卡都关闭时,是否仍会收到通知?不要告诉我答案:更改 UI 并告诉用户答案。

    2016 年 1 月 26 日 16:58

  7. epk

    “您是否曾经希望网站能够在发生重要事件时通知您,即使您没有打开该网站?”

    呃,没有,从未。不。

    但我确实希望浏览器继续通过 XUL 和 XPCOM 拥抱动态功能扩展。并且希望在 Thunderbird 及其底层核心上投入大量的开发时间。怎么样?

    2016 年 1 月 26 日 17:07

  8. Peter

    请为我是否可以在加载选项卡时接收通知以及在关闭选项卡后接收通知设置单独的设置。这些似乎是截然不同的情况,作为用户,我不希望我批准加载的选项卡通知我更新,这意味着他们可以随时向我发送消息,而不管它们是否在任何地方加载。

    2016 年 1 月 26 日 20:02

  9. Benoit

    Android 版 Firefox 中是否有此功能的实现?它可以让用户卸载几十个仅用于通知的应用程序(尤其是 Facebook)。

    2016 年 1 月 27 日 00:30

    1. Dan Callahan

      错误 1206207 是 Android 版 Firefox 中推送支持的元错误。 依赖关系树 看起来很有希望;它只是比桌面版花费的时间长一些,因为我们需要通过 Android 上的 Google Cloud Messaging 传输所有内容,而不是将浏览器直接连接到我们自己的推送服务。

      2016 年 1 月 27 日 06:15

  10. PhistucK

    我同意 David Piepgrass 和 Peter 的观点。

    此外,当消息被推送到浏览器时,Service Worker 是否需要显示通知?
    它能否仅更新缓存 API 中的一些陈旧数据,例如?
    如果是这样,这意味着允许通知会向用户打开带宽蠕虫罐,而无需征得同意……

    2016 年 1 月 27 日 04:39

    1. Dan Callahan

      Service Worker 不需要显示通知,但不会显示用户可见通知的消息会受到配额/限制。在 Service Worker 食谱 中有一个此功能的演示。

      对于需要更新缓存的用户,我们正在努力支持 Background Sync API

      2016 年 1月 27 日 06:36

  11. don

    在 Firefox 中是否有集中位置可以查看我已授予哪些网站显示通知的权限?这样我就可以随意禁用/启用它们?

    2016 年 1 月 27 日 04:42

    1. Dan Callahan

      是的!查看 about:preferences#content 中的“通知”部分

      2016 年 1 月 27 日 05:56

  12. Игорь Петренко

    你好!

    如何组织 tumblr.com | blogspot.com 的通知?也许有某种服务?

    2016 年 1 月 27 日 05:36

    1. Dan Callahan

      每个网站都必须自行编程以支持推送。有人可以创建通用的 RSS -> 推送服务,但我认为目前还没有。

      2016 年 1 月 27 日 06:09

  13. Andris

    我同意 Peter 的观点。
    区分这两种情况并在设置中实现它们非常重要。

    2016 年 1 月 27 日 05:50

  14. Steffen

    当我的服务器在响应对端点 URL 的 POST 请求时收到以下正文的 HTTP 404 错误时,它应该做什么?

    {“errno”:103,”code”:404,”error”:”Not Found”}

    根据https://github.com/mozilla-services/autopush/blob/master/docs/http.rst,这意味着“URL 终结点已过期”。这个错误是永久性的吗?我应该从数据库中删除受影响的终结点 URL 吗?

    (也许“410 Gone”会是一个更合适的状态码。)

    2016年1月27日 06:31

    1. Dan Callahan

      说得对。我已提交autopush/issues/312 建议使用 410 Gone 而不是 404 Not Found,因为正确的操作是将 404 视为永久性错误,停止使用该终结点,并在用户下次访问您的网站时尝试重新订阅。

      2016年1月27日 06:48

  15. Odin Hørthe Omdal

    这太棒了。感谢您为此付出努力,并祝贺您成为第一个以这种标准化的形式拥有此功能的浏览器。为 Google 的实现跳过额外的环节很麻烦。这要好得多 :)

    2016年1月27日 06:43

  16. mattip

    这很酷。使用 PyPy 更酷(我是一个贡献者 :))。我们可以将 PyPy 添加到 Mozilla 使用的工具中吗?
    https://wiki.mozilla.org/MOSS/Projects_in_use_by_Mozilla

    2016年1月27日 11:41

    1. Dan Callahan

      完成了!

      2016年1月27日 13:00

  17. PushEngage

    丹和 Firefox 团队的好消息。我们一直在等待这个。

    我们是一个跨浏览器管理推送通知的平台,并且正在努力立即集成 Firefox。

    2016年1月28日 02:48

  18. Mahavir

    Firefox、Chrome 和 Safari 在不久的将来是否有可能使用通用 API?

    2016年1月28日 10:27

  19. PhistucK

    @Mahavir –
    参见https://groups.google.com/a/chromium.org/d/msg/blink-dev/_LQX_cjAATA/blFj3FBVBQAJ – Google 也在开发 Web 推送协议。

    2016年1月28日 10:52

  20. abc

    这似乎既不在常规 FF44 发行说明中(https://www.mozilla.org/en-US/firefox/44.0/releasenotes/),也不在开发者发行说明中(https://hacks.mozilla.ac.cn/2015/11/developer-edition-44-creative-tools-and-more/)。

    2016年1月28日 13:50

    1. Dan Callahan

      糟糕!我已提交Bug 1233762 来解决这个问题。

      2016年2月1日 08:34

  21. Tim

    通知上的齿轮菜单实际上毫无用处。通知弹出,您将鼠标移到齿轮上并单击它,然后显示菜单,然后整个内容在您甚至无法阅读菜单之前就消失了,因为通知期限已结束,完全忽略了用户正在与之交互的事实。通知应该更智能地判断鼠标是否悬停在其上,尤其是菜单是否正在显示。

    2016年1月28日 20:51

    1. Dan Callahan

      发现问题。我已将其作为Bug 1244102 提交。

      2016年1月29日 06:07

  22. gialloporpora

    我有一个关于此的问题
    “为了阻止窃听,有效负载会加密到仅由您的浏览器持有的公钥/私钥对。”
    用于加密数据的算法是什么?

    2016年1月29日 05:32

    1. Dan Callahan

      具体细节仍在发生变化,但目前 Firefox 44 中的订阅支持getKey('p256dh'),它是在 P-256 曲线上的 ECDH 公钥。有关其在Web 推送加密的 IETF 草案中的使用详情。

      2016年1月29日 06:32

  23. 愤怒的用户

    在 Linux 上,这些通知似乎无法正常工作
    – 齿轮不显示,而是显示了一个难看的下拉菜单。
    – “x”(关闭)按钮根本不起作用。

    其他问题
    – 将时间更改为 12 秒而不是 4 秒?为什么?如果我真的很想阅读它,我会去查看页面……
    – 完全没有自定义外观、延迟等选项……

    2016年1月29日 07:29

  24. gialloporpora

    丹,非常感谢您的
    关于加密的说明。

    2016年1月29日 14:36

  25. Tim

    感谢您处理我的错误报告。:) 我本想回复您的回复,但我没有看到任何方法。:-/

    2016年1月30日 14:47

    1. Dan Callahan

      那是potch/hax/issues/22。欢迎提交补丁。;-)

      2016年1月30日 16:30

  26. PhistucK

    > 服务工作者不需要显示通知,但不会显示用户可见通知的消息会受到配额/节流的影响。服务工作者食谱中有一个演示。

    我点击了该按钮两次,收到了 94 个不可见的通知。这太多了。服务工作者可以下载大量数据(取决于您的连接速度)……

    配额是按天计算?按月?按小时?按用户?
    此外,我看到在我显示可见通知后,不可见通知配额会重置自身或其他操作。

    这似乎有害。允许通知不应意味着允许在选项卡关闭时交换数据。我认为这需要单独的权限。

    2016年2月1日 22:53

    1. Dan Callahan

      我们仍在调整确切的公式,但阅读Bug 1153504 是一个很好的起点。:)

      引用 Kit Cambridge 今天所说的话,“我们对其进行了一些更改。推送消息到达后,您现在有 3 秒钟的时间来显示通知。如果您至少显示一个,我们将不会扣除您的配额。如果您足够快,您可以在最初的 3 秒钟内发送大量后台更新,然后您的订阅将被取消。”

      2016年2月2日 08:49

  27. dst

    对于那些没有使用 Google 应用的 Android 用户,是否可以(可选地)让 Firefox Android 也直接使用 Mozilla 的服务器(或自托管服务器)?如果我想要一个尽可能多地与 Google 共享我的数据的移动浏览器,我会使用 Chrome ^^

    2016年2月2日 15:05

  28. Richard

    谁能告诉我这个测试推送可能出了什么问题?

    curl -X PUT -d “version=5” –insecure https://updates.push.services.mozilla.com/update/gAAAAABWs….pQ=

    文档说在没有有效负载的情况下不需要加密,并且完整的终结点与之匹配。

    PS。这对 Chrome 有效:–
    curl –insecure –header “Authorization: key=AIzaSyC….XZyJGmq6M” –header Content-Type:”application/json” https://android.googleapis.com/gcm/send -d “{\”registration_ids\”:[\”erEmvJgHoAQ:AP….bSKl\”]}”

    2016年2月4日 20:25

  29. Kit Cambridge

    PhistucK:非常好的问题。:-) 详细说明一下……

    配额是基于自上次访问网站以来天数的曲线。网站在第一天允许 16 条后台消息,第二天允许 8 条,第三天允许 5 条,依此类推。对于每个 `push` 事件,服务工作者有 3 秒钟的时间来显示通知。这允许网站使用标签更新现有通知。

    正如您所注意到的,可以在最初的 3 秒钟内发送大量后台消息。之后,订阅将被删除,并且仅在您下次访问后的 24 小时内恢复。这应该鼓励网站做正确的事情。

    我们不希望开发人员依赖确切的公式,因此我们对此含糊其辞。但是,这对于那些(完全可以理解地)担心后台活动的人来说是不透明的。我们可以为此在常见问题解答页面中添加一个条目。

    这有帮助吗?

    2016年2月4日 21:21

  30. PhistucK

    没有帮助。人们选择接收通知,他们的数据可能会浪费掉。抱歉,这种节流机制并不能解决对用户的糟糕解释和意外结果。

    2016年2月5日 05:55

  31. PhistucK

    > 那是 potch/hax/issues/22。欢迎提交补丁。;-)
    我想,不太受欢迎。:(
    https://github.com/potch/hax/pull/31

    2016年2月5日 12:20

    1. Dan Callahan

      对此表示歉意!我让一位队友在周一审查了它,但他一直很忙。我会看看我能否将其合并。非常感谢您提交它!:)

      2016年2月5日 13:15

  32. Richard Maher

    请跳过我上面的问题,因为那只是我使用了 PUT 而不是 POST 的简单情况。

    一个更紧迫的问题是,谁能提供一个 C# 和/或 JAVA 示例,说明服务器如何加密然后将包含有效负载的消息 POST 到 Firefox 44?

    这是我对 C# 论坛的问题:–
    https://forums.asp.net/t/2084499.aspx?C+server+PUSHing+an+encrypted+message+to+Firefox+44+example

    2016年2月7日 17:32

  33. narendra

    这看起来很棒!我对终结点订阅/取消订阅有一些疑问。

    – 终结点的生命周期是多久?它是用户会话、浏览器/机器/IP 组合的唯一标识吗?
    – 当终结点过期时,如何将其传达给服务后端?服务后端需要为用户删除过时/已过期的终结点。在发送消息期间使用 404 错误代码是否适合此用例?https://github.com/mozilla-services/autopush/blob/master/docs/http.rst#id7 中提到

    2016年2月8日 16:17

    1. Dan Callahan

      终结点基本上应该存在,直到用户撤销权限,这将表现为 4xx 响应代码。终结点对于每个浏览器配置文件都是唯一的。例如,即使您在同一台机器上同时运行开发者版和普通版 Firefox,并通过 Firefox 同步连接,每个实例也会创建和维护其自己的唯一终结点。

      2016年2月8日 16:52

  34. narendra

    @Dan “终结点对于每个浏览器配置文件都是唯一的”

    这是否意味着终结点不是特定于用户的?如果用户 1 登录我的 Web 应用,则会创建终结点并将其发送到服务后端,我在那里为用户 1 存储它。现在,如果用户 1 在同一浏览器上的我的 Web 应用中注销,而用户 2 登录,他是否会获得相同的终结点?发送给用户 1 的通知将发送给用户 2,对吧。

    我错过了什么?

    2016年2月8日 18:00

    1. Dan Callahan

      说得对。您的网站为这两个用户获取相同的终结点,因为它是同一个浏览器。

      如果浏览器在用户之间重置,例如在正确配置的公共终端中,或者如果用户在机器上具有不同的本地帐户,则您将获得不同的终结点。

      2016年2月8日 19:23

  35. 有没有办法“全局”禁用此功能?

    每次我在具有此功能的网站中输入时,Firefox 都会询问我是否想要通知……

    2016年2月14日 19:05

  36. Marco Colli

    我有一个网站使用推送 API 发送推送通知。一切正常,但是现在当我应用程序将通知 POST 到 Mozilla 服务器时,我遇到了以下问题

    响应 400 错误请求:{“errno”: 111, “message”: “缺少 TTL 标头”, “code”: 400, “error”: “错误请求”}

    这是什么意思?您指的是 HTTP 还是通知 TTL?请提供帮助或文档

    2016年2月19日 05:56

    1. Dan Callahan

      对此我深感抱歉——规范最近已更改为要求为消息设置显式超时,并且我们在没有充分通知或向后兼容性的情况下部署了新版本的推送服务。我目前正在更新文章。

      2016年2月19日 22:25

  37. Marco Colli

    您能否提供有关我们应该从 Mozilla 推送服务器期望的响应消息和代码(以及 errno)的文档?

    例如,某些终结点会产生以下错误
    404 未找到:{“errno”: 106, “message”: “没有此类订阅”, “code”: 404, “error”: “未找到”}

    我认为应该从我们的服务器中永久删除该终结点,因为该订阅已过期。但是我有一些疑问
    – 任何 404 未找到都意味着订阅已永久过期?
    – 我们是否也应该检查 errno (106)?或者可能是消息?

    担心的是删除仅暂时返回错误状态码的终结点。
    非常感谢!

    2016年2月20日 07:24

本文的评论已关闭。