我们 Mozilla 的主要沟通工具是 IRC。我在服务器上运行着名为 Irssi 的 IRC 客户端,该服务器通过 screen
不断连接到网络。它是一个近乎完美的解决方案,对我来说只有两个突出的问题。一个是缺乏表情符号字符(我可以忍受)。另一个更重要:没有简单的方法来安装接收通知的解决方案。这给了我们一个小型项目的想法——建立一个通用的通知发送服务器,可以与 Irssi 客户端一起工作。
应用程序概述
Mercurius 是一个通用的推送服务器,它允许开发人员将 Web 推送引入他们自己的原生/网络应用程序。订阅管理完全由 Mercurius 处理;用户在 Mercurius 网站上注册并收到一个令牌,其他应用程序可以使用该令牌通过 REST API 发送自定义推送通知。
Mercurius 使用 web-push Node.js 库来处理与 推送服务 的通信,并负责必要的有效负载加密。
您可以在 GitHub 上找到代码。
我们的应用程序中有四个角色
- 用户代理(浏览器),它在推送服务上订阅推送通知,并将订阅信息发送到应用程序服务器(Mercurius);
- 推送服务;
- Mercurius 应用程序服务器,它维护用户订阅列表,并通过推送服务向用户发送推送通知;
- Mercurius 客户端(在本例中是一个 Irssi 插件),它使用令牌(由 Mercurius 服务器提供给用户)通过 Mercurius 服务器向用户发送推送通知。
订阅推送通知
我们需要构建一个网页来允许用户订阅推送通知。推送 API 的 PushManager 接口可以帮助我们。它可以通过服务工作者注册进行访问
navigator.serviceWorker.register('service-worker.js')
.then(function(registration) {
// registration.pushManager
它提供了一个用于订阅用户的函数和一个用于获取已存在订阅的函数。我们首先尝试获取现有的订阅,如果失败,我们将重新注册用户
return registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription;
}
return registration.pushManager.subscribe({ userVisibleOnly: true })
.then(function(newSubscription) {
return newSubscription;
});
});
一旦我们有了订阅(包含推送服务上的端点 URL 和用户的公钥),我们就可以将它的信息发送到服务器,然后服务器将使用它向用户发送通知。
Fetch API 将 POST 请求发送到服务器(只是为了让演示更有趣一些)
var key = subscription.getKey ? subscription.getKey('p256dh') : '';
fetch('./register', {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
endpoint: subscription.endpoint,
key: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : '',
}),
});
保持订阅最新
用户对推送服务的订阅会定期刷新,或者在某些情况下会失效(有关详细说明,请参阅 ServiceWorker Cookbook 中的推送配额食谱),因此我们需要始终将服务器与用户订阅信息保持最新。
为此,我们将在服务工作者中为“pushsubscriptionchange”事件添加一个事件监听器
self.addEventListener('pushsubscriptionchange', function(event) {
// Handle the event by re-subscribing the user and sending the new info to the server
这样,当订阅发生变化时,我们会通知服务器新的端点 URL 和新的密钥。
显示视觉通知
一旦我们的用户订阅了推送服务和 Mercurial 服务器,我们就可以发送推送消息并显示视觉通知。Irssi 插件将向 Mercurius 服务器发送 POST 请求,要求它将通知发送给用户。Mercurius 服务器将通知发送到推送服务,然后推送服务将通知传递到浏览器。推送通知的有效负载将发送到端点 URL,并使用一些巧妙的加密方法。它提供了用于显示通知的实际参数。(您可以在此处阅读有关加密的更多信息:https://tools.ietf.org/html/draft-thomson-webpush-encryption-01)。
推送通知将在为页面注册的服务工作者中触发“push”事件,如果服务工作者不再处于活动状态,它将重新启动服务工作者。
因此,要向用户显示一些内容,我们只需要在服务工作者中处理“push”事件
self.addEventListener('push', function(event) {
var data = event.data ? event.data.json() : null;
var title = data ? data.title : 'Mercurius';
var body = data ? data.body : 'Notification';
event.waitUntil(self.registration.showNotification(title, {
body: body,
}));
});
正如我们之前提到的,我们使用推送消息的有效负载(有关参考,请参阅 Mozilla 的 Service Worker Cookbook 中的 推送有效负载食谱)将通知参数从 Irssi 插件(或任何其他 Mercurius 客户端)传递到服务工作者。您可以在前端看到它在工作
使用 Mocha 测试
我们使用 Mocha 框架 实现了一些 BDD 测试,以验证 Mercurius 服务器是否正常工作。我们还使用了 istanbul 来衡量代码覆盖率并指导我们的测试编写活动。
只需运行 npm test
即可查看其输出。
部署
将服务器部署到 Heroku 非常简单。我们选择让我们的持续集成服务 Travis CI 在构建 Mercurius 和成功运行测试后执行部署。这是我们针对 Travis CI 的自解释 YAML 配置文件
language: node_js
node_js:
- '0.12'
- '4'
deploy:
provider: heroku
api_key:
secure:
app: mozcurius
skip_cleanup: true
on:
repo: marco-c/mercurius
branch: master
node: '4'
我们使用 skip_cleanup 因为我们想将构建工件发布到 Heroku。
(加密的)API 密钥特定于您的用户帐户,可以使用 Heroku 和 Travis CI 命令行客户端创建
travis encrypt $(heroku auth:token) --add deploy.api_key
有关在 Travis CI 中集成 Heroku 部署的更多信息,请参阅 https://docs.travis-ci.cn/user/deployment/heroku/。
Irssi 客户端
Irssi Mercurius 客户端的作用是在每次提到用户时向 Mercurius 服务器发送通知。我修改了现有的脚本,该脚本将此信息保存到文件中,以便它现在保存并将请求发送到 Mercurius 服务器。加载脚本后,将在 Irssi 中注册一个新命令。它被称为 /mercurius
,它设置令牌、主机(如果您决定运行自己的主机)和通知的强度。还有一种方法可以按需停止和重新启动通知。
该插件是用 Perl 编写的。向 Mercurius 服务器发送请求发生在 sub notify 中,使用内置的 HTTP::Tiny 模块。我已经使用 request 方法而不是 post 方法来保证与旧版 Perl 版本的向后兼容性。
负责发送通知请求的子例程称为 notify
。这里在注释中给出了描述。
use HTTP::Tiny;
use JSON::PP;
# [...]
sub notify {
if ($enabled) {
my ($text) = @_;
# $host is a global variable set with "set_host" command
my $url = $host . '/notify';
my $http = HTTP::Tiny->new();
my $data = encode_json {
# $token is a global variable set with "set_token" command
"token" => $token,
"payload" => {
"title" => "IRSSI",
"body" => $text
}
};
my $response = $http->request('POST', $url, {
content => $data,
headers => {"Content-Type" => "application/json"}
}
);
# [...]
}
}
该插件已 放在 GitHub 上,并附带安装说明。
用法
/mercurius set_token {TOKEN}
使用它来设置令牌。您将收到一个通知,确认它已设置。
/mercurius set_host {HOST}
默认值为 "https://mozcurius.herokuapp.com"
。请注意,尾部斜杠已被删除。
/mercurius set_intense {0/1}
打开/关闭强力模式(默认值为 0
)。启用强力模式 (1
) 时,所有通知都会被发送。否则(默认情况下),如果用户处于活动私聊窗口中,则只会从该窗口发送第一条消息的通知。
/mercurius stop
和 /mercurius start
不言自明。
关于 Marco Castelluccio
Marco 是一位充满热情的 Mozilla 黑客(黑客和工程师的奇特混合体),他为 Firefox、PluotSorbet、Open Web Apps 做出了贡献并一直贡献着。最近,他一直在研究将机器学习和数据挖掘技术用于软件工程(测试、崩溃处理、错误管理等等)。
关于 Piotr Zalewa
Piotr Zalewa 是 Mozilla Dev Ecosystem 团队的高级 Web 开发人员。从事 Web 应用程序开发。他是 JSFiddle 的创建者。
5 条评论