使用 Javascript 将数据/负载发送到 Google Chrome 推送通知
Sending data / payload to the Google Chrome Push Notification with Javascript
我正在处理 Google Chrome 推送通知,我正在尝试将负载发送给 google chrome 工作人员,但是,我没有知道我是如何接收这个负载的。
我有一个 API 来创建通知并将其保存在我的数据库中,我需要通过 https://android.googleapis.com/gcm/send
发送值并在 worker.js
上接收
这是我的worker.js
self.addEventListener('push', function(event) {
var title = 'Yay a message.';
var body = 'We have received a push message.';
var icon = '/images/icon-192x192.png';
var tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
});
我就是这样称呼 GCM
curl --header "Authorization: key=AIzaSyDQjYDxeS9MM0LcJm3oR6B7MU7Ad2x2Vqc" --header "Content-Type: application/json" https://android.googleapis.com/gcm/send -d "{ \"data\":{\"foo\":\"bar\"}, \"registration_ids\":[\"APA91bGqJpCmyCnSHLjY6STaBQEumz3eFY9r-2CHTtbsUMzBttq0crU3nEXzzU9TxNpsYeFmjA27urSaszKtA0WWC3yez1hhneLjbwJqlRdc_Yj1EiqLHluVwHB6V4FNdXdKb_gc_-7rbkYkypI3MtHpEaJbWsj6M5Pgs4nKqQ2R-WNho82mnRU\"]}"
我试图得到 event.data
但是,这是未定义的。
有没有人有任何想法或建议?
不幸的是,它看起来像一个 intended behavior:
A downside to the current implementation of the Push API in Chrome is
that you can’t send a payload with a push message. Nope, nothing. The
reason for this is that in a future implementation, payload will have
to be encrypted on your server before it’s sent to a push messaging
endpoint. This way the endpoint, whatever push provider it is, will
not be able to easily view the content of the push payload. This also
protects against other vulnerabilities like poor validation of HTTPS
certificates and man-in-the-middle attacks between your server and the
push provider. However, this encryption isn’t supported yet, so in the
meantime you’ll need to perform a fetch request to get information
needed to populate a notification.
如上所述,解决方法是在收到推送后联系您的后端并获取第 3 方服务器上存储的数据。
@gauchofunky 的回答是正确的。在 Chromium dev slack 频道和@gauchofunky 上的一些人的指导下,我能够拼凑出一些东西。以下是解决当前限制的方法;希望我的回答很快就会过时!
首先弄清楚您将如何在后端保留通知。我将 Node/Express 和 MongoDB 与 Mongoose 一起使用,我的架构如下所示:
var NotificationSchema = new Schema({
_user: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
subscriptionId: String,
title: String,
body: String,
sent: { type: Boolean, default: false }
});
如果您想更改图标,请务必添加图标。我每次都使用相同的图标,所以我的图标被硬编码在 service worker 中。
找出正确的 REST Web 服务需要一些思考。 GET 似乎是一个简单的选择,但是获取通知的调用会产生副作用,因此 GET 被淘汰了。我最终使用 POST
到 /api/notifications
和 {subscriptionId: <SUBSCRIPTION_ID>}
的正文。在该方法中,我们基本上执行出列:
var subscriptionId = req.body.subscriptionId;
Notification
.findOne({_user: req.user, subscriptionId: subscriptionId, sent: false})
.exec(function(err, notification) {
if(err) { return handleError(res, err); }
notification.sent = true;
notification.save(function(err) {
if(err) { return handleError(res, err); }
return res.status(201).json(notification);
});
});
在 service worker 中,我们需要确保在进行 fetch
之前获得订阅。
self.addEventListener('push', function(event) {
event.waitUntil(
self.registration.pushManager.getSubscription().then(function(subscription) {
fetch('/api/notifications/', {
method: 'post',
headers: {
'Authorization': 'Bearer ' + self.token,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
})
.then(function(response) { return response.json(); })
.then(function(data) {
self.registration.showNotification(data.title, {
body: data.body,
icon: 'favicon-196x196.png'
});
})
.catch(function(err) {
console.log('err');
console.log(err);
});
})
);
});
还值得注意的是,订阅对象从 Chrome 43 更改为 Chrome 45。在 Chrome 45 中删除了 subscriptionId
属性,只是需要注意的地方 - 此代码是为与 Chrome 43.
一起编写的
我想对我的后端进行经过身份验证的调用,因此我需要弄清楚如何将 JWT 从我的 Angular 应用程序获取到我的服务工作者。我最终使用了 postMessage
。这是我在注册 Service Worker 后所做的:
navigator.serviceWorker.register('/service-worker.js', {scope:'./'}).then(function(reg) {
var messenger = reg.installing || navigator.serviceWorker.controller;
messenger.postMessage({token: $localStorage.token});
}).catch(function(err) {
console.log('err');
console.log(err);
});
在 service worker 中监听消息:
self.onmessage.addEventListener('message', function(event) {
self.token = event.data.token;
});
奇怪的是,该侦听器在 Chrome 43 中工作,但在 Chrome 45 中不工作。Chrome 45 与这样的处理程序一起工作:
self.addEventListener('message', function(event) {
self.token = event.data.token;
});
现在推送通知需要大量工作才能让有用的东西继续运行 - 我真的很期待有效负载!
实际上,payload 应该在 Chrome50(发布日期 - 2016 年 4 月 19 日)中实现。在 Chrome 50(以及桌面版 Firefox 的当前版本)中,您可以随推送一起发送一些任意数据,以便客户端可以避免发出额外的请求。所有有效载荷数据都必须加密。
这是开发者提供的加密详细信息:https://developers.google.com/web/updates/2016/03/web-push-encryption?hl=en
我刚 运行 遇到这个问题。较新版本的 firefox 和 chrome(版本 50+)支持 payload t运行sferring。
开发文档 here 详细介绍了其工作原理的实现。需要注意的重要一点是 google GCM 或 client/chome(我不知道是哪一个)如果未加密,实际上将完全忽略有效负载。
This website has both client/server implementations of how to do the push and retrieval through service workers. The push library that examples use is merely a wrapper around a normal REST call
service worker 示例实现:
self.addEventListener('push', function(event) {
var payload = event.data ? event.data.text() : 'no payload';
event.waitUntil(
self.registration.showNotification('ServiceWorker Cookbook', {
body: payload,
})
);
});
服务器 示例实现:
var webPush = require('web-push');
webPush.setGCMAPIKey(process.env.GCM_API_KEY);
module.exports = function(app, route) {
app.post(route + 'register', function(req, res) {
res.sendStatus(201);
});
app.post(route + 'sendNotification', function(req, res) {
setTimeout(function() {
webPush.sendNotification(req.body.endpoint, {
TTL: req.body.ttl,
payload: req.body.payload,
userPublicKey: req.body.key,
userAuth: req.body.authSecret,
}).then(function() {
res.sendStatus(201);
});
}, req.body.delay * 1000);
});
};
客户端javascript打印所需字段的实现示例。
navigator.serviceWorker.register('serviceWorker.js')
.then(function(registration) {
return registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription;
}
return registration.pushManager.subscribe({
userVisibleOnly: true
});
});
}).then(function(subscription) {
var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
endpoint = subscription.endpoint;
console.log("Endpoint: " + endpoint);
console.log("Key: " + key);
console.log("AuthSecret: " + authSecret);
});
要检索该数据,您需要将 "event.data.text()" 解析为 JSON 对象。我猜自从您尝试使它起作用以来,某些内容已更新,但现在可以使用了。倒霉!
但是,由于我在自己搜索解决方案时做到了这一点 post,其他人可能会想要一个可行的答案。这是:
// Push message event handler
self.addEventListener('push', function(event) {
// If true, the event holds data
if(event.data){
// Need to parse to JSON format
// - Consider event.data.text() the "stringify()"
// version of the data
var payload = JSON.parse(event.data.text());
// For those of you who love logging
console.log(payload);
var title = payload.data.title;
var body = payload.data.body;
var icon = './assets/icons/icon.ico'
var tag = 'notification-tag';
// Wait until payload is fetched
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag,
data: {} // Keeping this here in case I need it later
})
);
} else {
console.log("Event does not have data...");
}
}); // End push listener
// Notification Click event
self.addEventListener('notificationclick', function(event) {
console.log("Notification Clicked");
}); // End click listener
就我个人而言,我将创建一个 "generic" 通知以防我的数据不正常,并且还将使用 try/catch。我建议也这样做。
按照以下步骤实现:
在浏览器中:
您需要获取 subscription
对象并保存它,以便您的服务器可以访问它:Read more about it
navigator.serviceWorker.ready.then(serviceWorkerRegistration => {
serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true})
.then(subscription => {
//save subscription.toJSON() object to your server
})});
在服务器中:
并像这样发送网络推送:
const webpush = require('web-push');
setImmediate(async () => {
const params = {
payload: {title: 'Hey', body: 'Hello World'}
};
//this is the subscription object you should get in the browser. This is a demo of how it should look like
const subscription = {"endpoint":"https://android.googleapis.com/gcm/send/deC24xZL8z4:APA91bE9ZWs2KvLdo71NGYvBHGX6ZO4FFIQCppMsZhiTXtM1S2SlAqoOPNxzLlPye4ieL2ulzzSvPue-dGFBszDcFbSkfb_VhleiJgXRA8UwgLn5Z20_77WroZ1LofWQ22g6bpIGmg2JwYAqjeca_gzrZi3XUpcWHfw","expirationTime":null,"keys":{"p256dh":"BG55fZ3zZq7Cd20vVouPXeVic9-3pa7RhcR5g3kRb13MyJyghTY86IO_IToVKdBmk_2kA9znmbqvd0-o8U1FfA3M","auth":"1gNTE1wddcuF3FUPryGTZOA"}};
if (subscription.keys) {
params.userPublicKey = subscription.keys.p256dh;
params.userAuth = subscription.keys.auth;
}
// this key you should take from firebase console for example
// settings -> cloud messaging -> Server key
webpush.setGCMAPIKey('AAAASwYmslc:APfA91bGy3tdKvuq90eOvz4AoUm6uPtbqZktZ9dAnElrlH4gglUiuvereTJJWxz8_dANEQciX9legijnJrxvlapI84bno4icD2D0cdVX3_XBOuW3aWrpoqsoxLDTdth86CjkDD4JhqRzxV7RrDXQZd_sZAOpC6f32nbA');
try {
const r = await webpush.sendNotification(subscription, JSON.stringify(params));
console.log(r);
}
catch (e) {
console.error(e);
}
});
我正在处理 Google Chrome 推送通知,我正在尝试将负载发送给 google chrome 工作人员,但是,我没有知道我是如何接收这个负载的。
我有一个 API 来创建通知并将其保存在我的数据库中,我需要通过 https://android.googleapis.com/gcm/send
发送值并在 worker.js
这是我的worker.js
self.addEventListener('push', function(event) {
var title = 'Yay a message.';
var body = 'We have received a push message.';
var icon = '/images/icon-192x192.png';
var tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
});
我就是这样称呼 GCM
curl --header "Authorization: key=AIzaSyDQjYDxeS9MM0LcJm3oR6B7MU7Ad2x2Vqc" --header "Content-Type: application/json" https://android.googleapis.com/gcm/send -d "{ \"data\":{\"foo\":\"bar\"}, \"registration_ids\":[\"APA91bGqJpCmyCnSHLjY6STaBQEumz3eFY9r-2CHTtbsUMzBttq0crU3nEXzzU9TxNpsYeFmjA27urSaszKtA0WWC3yez1hhneLjbwJqlRdc_Yj1EiqLHluVwHB6V4FNdXdKb_gc_-7rbkYkypI3MtHpEaJbWsj6M5Pgs4nKqQ2R-WNho82mnRU\"]}"
我试图得到 event.data
但是,这是未定义的。
有没有人有任何想法或建议?
不幸的是,它看起来像一个 intended behavior:
A downside to the current implementation of the Push API in Chrome is that you can’t send a payload with a push message. Nope, nothing. The reason for this is that in a future implementation, payload will have to be encrypted on your server before it’s sent to a push messaging endpoint. This way the endpoint, whatever push provider it is, will not be able to easily view the content of the push payload. This also protects against other vulnerabilities like poor validation of HTTPS certificates and man-in-the-middle attacks between your server and the push provider. However, this encryption isn’t supported yet, so in the meantime you’ll need to perform a fetch request to get information needed to populate a notification.
如上所述,解决方法是在收到推送后联系您的后端并获取第 3 方服务器上存储的数据。
@gauchofunky 的回答是正确的。在 Chromium dev slack 频道和@gauchofunky 上的一些人的指导下,我能够拼凑出一些东西。以下是解决当前限制的方法;希望我的回答很快就会过时!
首先弄清楚您将如何在后端保留通知。我将 Node/Express 和 MongoDB 与 Mongoose 一起使用,我的架构如下所示:
var NotificationSchema = new Schema({
_user: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
subscriptionId: String,
title: String,
body: String,
sent: { type: Boolean, default: false }
});
如果您想更改图标,请务必添加图标。我每次都使用相同的图标,所以我的图标被硬编码在 service worker 中。
找出正确的 REST Web 服务需要一些思考。 GET 似乎是一个简单的选择,但是获取通知的调用会产生副作用,因此 GET 被淘汰了。我最终使用 POST
到 /api/notifications
和 {subscriptionId: <SUBSCRIPTION_ID>}
的正文。在该方法中,我们基本上执行出列:
var subscriptionId = req.body.subscriptionId;
Notification
.findOne({_user: req.user, subscriptionId: subscriptionId, sent: false})
.exec(function(err, notification) {
if(err) { return handleError(res, err); }
notification.sent = true;
notification.save(function(err) {
if(err) { return handleError(res, err); }
return res.status(201).json(notification);
});
});
在 service worker 中,我们需要确保在进行 fetch
之前获得订阅。
self.addEventListener('push', function(event) {
event.waitUntil(
self.registration.pushManager.getSubscription().then(function(subscription) {
fetch('/api/notifications/', {
method: 'post',
headers: {
'Authorization': 'Bearer ' + self.token,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
})
.then(function(response) { return response.json(); })
.then(function(data) {
self.registration.showNotification(data.title, {
body: data.body,
icon: 'favicon-196x196.png'
});
})
.catch(function(err) {
console.log('err');
console.log(err);
});
})
);
});
还值得注意的是,订阅对象从 Chrome 43 更改为 Chrome 45。在 Chrome 45 中删除了 subscriptionId
属性,只是需要注意的地方 - 此代码是为与 Chrome 43.
我想对我的后端进行经过身份验证的调用,因此我需要弄清楚如何将 JWT 从我的 Angular 应用程序获取到我的服务工作者。我最终使用了 postMessage
。这是我在注册 Service Worker 后所做的:
navigator.serviceWorker.register('/service-worker.js', {scope:'./'}).then(function(reg) {
var messenger = reg.installing || navigator.serviceWorker.controller;
messenger.postMessage({token: $localStorage.token});
}).catch(function(err) {
console.log('err');
console.log(err);
});
在 service worker 中监听消息:
self.onmessage.addEventListener('message', function(event) {
self.token = event.data.token;
});
奇怪的是,该侦听器在 Chrome 43 中工作,但在 Chrome 45 中不工作。Chrome 45 与这样的处理程序一起工作:
self.addEventListener('message', function(event) {
self.token = event.data.token;
});
现在推送通知需要大量工作才能让有用的东西继续运行 - 我真的很期待有效负载!
实际上,payload 应该在 Chrome50(发布日期 - 2016 年 4 月 19 日)中实现。在 Chrome 50(以及桌面版 Firefox 的当前版本)中,您可以随推送一起发送一些任意数据,以便客户端可以避免发出额外的请求。所有有效载荷数据都必须加密。
这是开发者提供的加密详细信息:https://developers.google.com/web/updates/2016/03/web-push-encryption?hl=en
我刚 运行 遇到这个问题。较新版本的 firefox 和 chrome(版本 50+)支持 payload t运行sferring。
开发文档 here 详细介绍了其工作原理的实现。需要注意的重要一点是 google GCM 或 client/chome(我不知道是哪一个)如果未加密,实际上将完全忽略有效负载。
This website has both client/server implementations of how to do the push and retrieval through service workers. The push library that examples use is merely a wrapper around a normal REST call
service worker 示例实现:
self.addEventListener('push', function(event) {
var payload = event.data ? event.data.text() : 'no payload';
event.waitUntil(
self.registration.showNotification('ServiceWorker Cookbook', {
body: payload,
})
);
});
服务器 示例实现:
var webPush = require('web-push');
webPush.setGCMAPIKey(process.env.GCM_API_KEY);
module.exports = function(app, route) {
app.post(route + 'register', function(req, res) {
res.sendStatus(201);
});
app.post(route + 'sendNotification', function(req, res) {
setTimeout(function() {
webPush.sendNotification(req.body.endpoint, {
TTL: req.body.ttl,
payload: req.body.payload,
userPublicKey: req.body.key,
userAuth: req.body.authSecret,
}).then(function() {
res.sendStatus(201);
});
}, req.body.delay * 1000);
});
};
客户端javascript打印所需字段的实现示例。
navigator.serviceWorker.register('serviceWorker.js')
.then(function(registration) {
return registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription;
}
return registration.pushManager.subscribe({
userVisibleOnly: true
});
});
}).then(function(subscription) {
var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
endpoint = subscription.endpoint;
console.log("Endpoint: " + endpoint);
console.log("Key: " + key);
console.log("AuthSecret: " + authSecret);
});
要检索该数据,您需要将 "event.data.text()" 解析为 JSON 对象。我猜自从您尝试使它起作用以来,某些内容已更新,但现在可以使用了。倒霉!
但是,由于我在自己搜索解决方案时做到了这一点 post,其他人可能会想要一个可行的答案。这是:
// Push message event handler
self.addEventListener('push', function(event) {
// If true, the event holds data
if(event.data){
// Need to parse to JSON format
// - Consider event.data.text() the "stringify()"
// version of the data
var payload = JSON.parse(event.data.text());
// For those of you who love logging
console.log(payload);
var title = payload.data.title;
var body = payload.data.body;
var icon = './assets/icons/icon.ico'
var tag = 'notification-tag';
// Wait until payload is fetched
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag,
data: {} // Keeping this here in case I need it later
})
);
} else {
console.log("Event does not have data...");
}
}); // End push listener
// Notification Click event
self.addEventListener('notificationclick', function(event) {
console.log("Notification Clicked");
}); // End click listener
就我个人而言,我将创建一个 "generic" 通知以防我的数据不正常,并且还将使用 try/catch。我建议也这样做。
按照以下步骤实现:
在浏览器中:
您需要获取 subscription
对象并保存它,以便您的服务器可以访问它:Read more about it
navigator.serviceWorker.ready.then(serviceWorkerRegistration => {
serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true})
.then(subscription => {
//save subscription.toJSON() object to your server
})});
在服务器中:
并像这样发送网络推送:
const webpush = require('web-push');
setImmediate(async () => {
const params = {
payload: {title: 'Hey', body: 'Hello World'}
};
//this is the subscription object you should get in the browser. This is a demo of how it should look like
const subscription = {"endpoint":"https://android.googleapis.com/gcm/send/deC24xZL8z4:APA91bE9ZWs2KvLdo71NGYvBHGX6ZO4FFIQCppMsZhiTXtM1S2SlAqoOPNxzLlPye4ieL2ulzzSvPue-dGFBszDcFbSkfb_VhleiJgXRA8UwgLn5Z20_77WroZ1LofWQ22g6bpIGmg2JwYAqjeca_gzrZi3XUpcWHfw","expirationTime":null,"keys":{"p256dh":"BG55fZ3zZq7Cd20vVouPXeVic9-3pa7RhcR5g3kRb13MyJyghTY86IO_IToVKdBmk_2kA9znmbqvd0-o8U1FfA3M","auth":"1gNTE1wddcuF3FUPryGTZOA"}};
if (subscription.keys) {
params.userPublicKey = subscription.keys.p256dh;
params.userAuth = subscription.keys.auth;
}
// this key you should take from firebase console for example
// settings -> cloud messaging -> Server key
webpush.setGCMAPIKey('AAAASwYmslc:APfA91bGy3tdKvuq90eOvz4AoUm6uPtbqZktZ9dAnElrlH4gglUiuvereTJJWxz8_dANEQciX9legijnJrxvlapI84bno4icD2D0cdVX3_XBOuW3aWrpoqsoxLDTdth86CjkDD4JhqRzxV7RrDXQZd_sZAOpC6f32nbA');
try {
const r = await webpush.sendNotification(subscription, JSON.stringify(params));
console.log(r);
}
catch (e) {
console.error(e);
}
});