How can we make the browser notification icon and body dynamically customized based upon the #challenge.image and the #challenge.description?
I got the notification to pop up by clicking webpush-button, but only the title of the message is dynamically changing. How can I make it work for body and icon too?
challenges/show.html.erb
<script>
$('.webpush-button').on('click', (e) => {
navigator.serviceWorker.ready
.then((serviceWorkerRegistration) => {
serviceWorkerRegistration.pushManager.getSubscription()
.then((subscription) => {
$.post('/push', {
subscription: subscription.toJSON(),
message: "<%= #challenge.name %>", # This works dynamically for the title, but how can I get message to also dynamically send body and icon so I can use <%= #challenge.image %> for icon and <%= #challenge.description %> for body?
});
});
});
});
</script>
push_notifications_controller
class PushNotificationsController < ApplicationController
def push
Webpush.payload_send(
message: params[:message],
endpoint: params[:subscription][:endpoint],
p256dh: params[:subscription][:keys][:p256dh],
auth: params[:subscription][:keys][:auth],
vapid: {
subject: "mailto:sender#example.com",
public_key: ENV['VAPID_PUBLIC_KEY'],
private_key: ENV['VAPID_PRIVATE_KEY']
}
)
end
end
application.js
if (navigator.serviceWorker) {
navigator.serviceWorker.register('/serviceworker.js')
.then(function(reg) {
console.log('Service worker change, registered the service worker');
});
}
// Otherwise, no push notifications :(
else {
console.error('Service worker is not supported in this browser');
}
// When serviceWorker is supported, installed, and activated,
// subscribe the pushManager property with the vapidPublicKey
navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: window.vapidPublicKey
});
});
// Let's check if the browser supports notifications
if (!("Notification" in window)) {
console.error("This browser does not support desktop notification");
}
// Let's check whether notification permissions have already been granted
else if (Notification.permission === "granted") {
console.log("Permission to receive notifications has been granted");
}
// Otherwise, we need to ask the user for permission
else if (Notification.permission !== 'denied') {
Notification.requestPermission(function (permission) {
// If the user accepts, let's create a notification
if (permission === "granted") {
console.log("Permission to receive notifications has been granted");
}
});
}
serviceworker.js.erb
console.log('[Service Worker] Hello world!');
var CACHE_VERSION = 'v1';
var CACHE_NAME = CACHE_VERSION + ':sw-cache-';
function onInstall(event) {
console.log('[Serviceworker]', "Installing!", event);
event.waitUntil(
caches.open(CACHE_NAME).then(function prefill(cache) {
return cache.addAll([
// make sure serviceworker.js is not required by application.js
// if you want to reference application.js from here
'<%#= asset_path "application.js" %>',
'<%= asset_path "application.css" %>',
'/offline.html',
]);
})
);
}
function onActivate(event) {
console.log('[Serviceworker]', "Activating!", event);
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
// Return true if you want to remove this cache,
// but remember that caches are shared across
// the whole origin
return cacheName.indexOf(CACHE_VERSION) !== 0;
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
}
// Borrowed from https://github.com/TalAter/UpUp
function onFetch(event) {
event.respondWith(
// try to return untouched request from network first
fetch(event.request).catch(function() {
// if it fails, try to return request from the cache
return caches.match(event.request).then(function(response) {
if (response) {
return response;
}
// if not found in cache, return default offline content for navigate requests
if (event.request.mode === 'navigate' ||
(event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) {
console.log('[Serviceworker]', "Fetching offline content", event);
return caches.match('/offline.html');
}
})
})
);
}
self.addEventListener("push", (event) => {
let title = (event.data && event.data.text()) || "Yay a message";
let body = "We have received a push message";
let icon = '/assets/default.png';
event.waitUntil(
self.registration.showNotification(title, { title,body, icon })
)
});
self.addEventListener('install', onInstall);
self.addEventListener('activate', onActivate);
self.addEventListener('fetch', onFetch);
serviceworker-companion.js
if (navigator.serviceWorker) {
navigator.serviceWorker.register('/serviceworker.js', { scope: './' })
.then(function(reg) {
console.log('[Companion]', 'Service worker registered!');
});
}
I implemented push via serviceworker gem, webpush gem, VAPID tutorial.
The event.data object in the self.addEventListener("push", (event)=> {...}), has a method json(), that method convert to json the event.data value.
This will works only if the sended data is in the proper json format.
Here is an example how I'm using web push:
self.addEventListener("push", function onPush(event) {
var data = event.data.json()
event.waitUntil(self.registration.showNotification(data.message.title, {
body: data.message.body,
icon: data.message.icon,
actions: [
{ action: 'Button one', title: "Button one text" },
{ action: 'Button two', title: "Button two text" }
]
}));
});
Here is a method from my user.rb model:
def send_web_push(title: , body: )
# the web_push is a payload with I save to the user record,
# that's allow me to send web push with background worker.
payload = {
endpoint: web_push['endpoint'],
keys: {
auth: web_push['auth'],
p256dh: web_push['p256dh']
}
}
push = WebPush.new(payload)
push.set_vapid_details(
'mailto:support#awesomesupewebapp.com',
Rails.application.secrets.web_push_public_key,
Rails.application.secrets.web_push_private_key
)
# below is a `:message` key in json format
push.send_notification({
message: {
title: title,
body: body,
icon: "/this_is_my_icon.png"
}
}.to_json)
end
I use some other ruby gem for web_push, but its common I assume. From that example you should see how is the body, title and icon keys can be changed.
view
<%= content_tag(:button, "Foo", class: "webpush-button") %>
<script>
$('.webpush-button').on('click', (e) => {
navigator.serviceWorker.ready
.then((serviceWorkerRegistration) => {
serviceWorkerRegistration.pushManager.getSubscription()
.then((subscription) => {
$.post('/push', {
subscription: subscription.toJSON(),
message: "<%= #challenge.name %>",
body: "<%= #challenge.why %>",
icon: "/assets/default.png"
});
});
});
});
</script>
controller
class PushNotificationsController < ApplicationController
def push
Webpush.payload_send(
message: JSON.generate({
title: params[:message],
body: params[:body],
icon: params[:icon]
}),
endpoint: params[:subscription][:endpoint],
p256dh: params[:subscription][:keys][:p256dh],
auth: params[:subscription][:keys][:auth],
vapid: {
subject: "mailto:sender#example.com",
public_key: ENV['VAPID_PUBLIC_KEY'],
private_key: ENV['VAPID_PRIVATE_KEY']
}
)
end
end
js
self.addEventListener("push", (event) => {
var data = event.data.json();
let title = (event.data && data.title) || "Yay a message";
let body = (event.data && data.body) || "We have received a push message";
let icon = (event.data && data.icon) || "/assets/blue-astronaut.png";
event.waitUntil(
self.registration.showNotification(title, { title,body, icon })
)
});
Related
The notification options that I am passing to my notifications are not passing to the notification and I am getting a default notification. (Title is the website, body is "The site has been updated in the background").
Service worker is an adapted create-react-app service worker.
Also, the console.log statements in the push event handler are not passing to the browser. Why is this?
The push event listener is directly after the load event listener in the CRA Service Worker
Web-Push API Call to create a web-push notification:
router.post('/:userid', auth, async (req, res) => {
try {
const user = await User.findById(req.params.userid);
user.pushSubscriptions.forEach((sub) => {
if (sub === null) {
return;
}
webpush.setVapidDetails(
'mailto:contact#email.com',
config.get('vapidPublic'),
config.get('vapidSecret')
);
const options = {
endpoint: sub.endpoint,
expirationTime: sub.expirationTime,
keys: {
p256dh: sub.keys.p256dh,
auth: sub.keys.auth,
},
};
console.log(options.endpoint);
webpush
.sendNotification(
options,
JSON.stringify({
title: 'NotifTitle',
body: 'Body',
})
)
.catch((error) => console.log(error));
});
return res.status(200).json({ msg: 'Notification Sent' });
} catch (error) {
console.log(error);
return res.status(500);
}
});
Push listener in sw.js:
window.addEventListener('push', (event) => {
console.log('Notification Recieved', event);
//Fallback data
let data = {
title: 'TestABC',
body: '123456',
};
if (event.data) {
data = JSON.parse(event.data.text());
}
//Notification options
var options = {
body: data.body,
icon: '../public/logo192.png',
image: '../public/logo192.png',
};
event.waitUntil(
console.log(options),
navigator.serviceWorker.registration.showNotification(
data.title,
options
)
);
});
Thanks
try to convert data like this
data = event.data.json();
you can read more here
I'm trying to implement push messages. I followed a tutorial to register a service worker and a push service subscription :
//requesting notification permisison
Notification.requestPermission(status => {
console.log('Notification permission status:', status);
if (status == 'granted') {
if ('serviceWorker' in navigator && 'PushManager' in window) {
console.log('Service Worker and Push is supported');
navigator.serviceWorker.register('/sw.js')
.then(function (registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(function (error) {
console.error('Service Worker Error', error);
});
} else {
console.error('Push messaging is not supported');
}
} else {
console.warn("Notifications denied");
}
});
navigator.serviceWorker.ready.then(function (registration) {
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'<mypublickey>'
)
})
.then(function (subscription) {
//successfull
console.log('push service subscription successfull: ' + JSON.stringify(subscription));
})
.catch(function (e) {
console.error(e);
})
})
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
In my browser console, I get the following subscription
push service subscription successfull:{
"endpoint":"https://fcm.googleapis.com/fcm/send/id",
"expirationTime":null,
"keys":{
"p256dh":"myp256dh",
"auth":"myauth"
}
}
Then to send my push, I'm using the web-push php library. Here's my code to send a push :
<?php
use Minishlink\WebPush\MessageSentReport;
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
class Test
{
public function execute()
{
$auth = [
'VAPID' => [
'subject' => 'mailto:me#website.com',
'publicKey' => 'mypubkey',
'privateKey' => 'myprivatekey'
]
];
$notifications = [
[
'subscription' => Subscription::Create([
"endpoint" => 'myendpoint',
"keys" => [
'p256dh' => 'myp256dh',
'auth' => 'myauth'
],
]),
'payload' => '{"msg":"hello world!"}'
]
];
$webPush = new WebPush($auth);
$webPush->sendNotification(
$notifications[0]['subscription'],
$notifications[0]['payload']
);
/** #var MessageSentReport $report */
foreach ($webPush->flush() as $report) {
$endpoint = $report->getEndpoint();
if ($report->isSuccess()) {
echo "[v] Message sent successfully for subscription {$endpoint}.";
} else {
echo $report->getReason();
}
}
}
}
And this is what I'm getting from this code
[v] Message sent successfully for subscription myendpoint.
And this does not trigger any notification in the browser. I'm using chrome, and the 'send test push message' in the dev tools works and displays a notification. There aren't any log in the browser console when I execute my php code to send the notification. I have also tried in Firefox and have reached the same results.
Any idea what could be wrong ?
Here's my service worker
sw.js
self.addEventListener('push', event => {
console.log('received a push!');
event.waitUntil(
self.registration.showNotification('This is a push');
);
});
I would like to implement push notifications to a website, but I can't trigger push event
with PHP and web push library, even though it seems that message is sent successfully.
I've tried to trigger the event from the console(chrome), and It works.
Service worker:
self.addEventListener('install', async event => {
// works fine
console.log('install event')
});
self.addEventListener("push", function(event) {
// console logs work only when I press push button in the console
console.log( 'push event' );
console.log( event );
var data = event.data.json();
event.waitUntil(self.registration.showNotification(data.title, {
body: data.body,
icon: data.icon,
tag: data.tag
}));
});
Php endpoint(push.php):
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
$auth = [
'VAPID' => [
'subject' => 'mailto:me#website.com', // can be a mailto: or your website address
'publicKey' => 'BL9qxdhqL_CM1ROLo6AUfeBvEyUuD7EHT3lAz8ksBZSYPsdE6q__uU2FoX9lr5FtmWtlHs-HRMHen3Ki8WWSVA4', // (recommended) uncompressed public key P-256 encoded in Base64-URL
'privateKey' => '7ldG3QYcY9KStB07ytTnd0CRCVSxbHfHYLyWEmgBKo0', // (recommended) in fact the secret multiplier of the private key encoded in Base64-URL
],
];
$subscription = Subscription::create([
// I'm using post just for test, in production I will fetch endpoints from database
'endpoint' => $_POST['endpoint'], // I get the value from subscription object
"keys" => [
'p256dh' => 'BL9qxdhqL_CM1ROLo6AUfeBvEyUuD7EHT3lAz8ksBZSYPsdE6q__uU2FoX9lr5FtmWtlHs-HRMHen3Ki8WWSVA4',
'auth' => '7ldG3QYcY9KStB07ytTnd0CRCVSxbHfHYLyWEmgBKo0'
],
'contentEncoding' => 'aesgcm',
]);
$webPush = new WebPush($auth);
$sent = $webPush->sendNotification($subscription, 'Hi');
foreach ($webPush->flush() as $report) {
$endpoint = $report->getRequest()->getUri()->__toString();
if ($report->isSuccess()) {
// I get this in the response
echo "[v] Message sent successfully for subscription {$endpoint}.";
} else {
echo "[x] Message failed to sent for subscription {$endpoint}: {$report->getReason()}";
}
}
My javascript:
async function registerSw () {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js').then(function(registration) {
subscribeToPush();
}).catch(error => console.log(error));
} else {
alert('Service workers not supported');
}
}
async function subscribeToPush () {
navigator.serviceWorker.ready.then(function(registration) {
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey:
urlB64ToUint8Array('BL9qxdhqL_CM1ROLo6AUfeBvEyUuD7EHT3lAz8ksBZSYPsdE6q__uU2FoX9lr5FtmWtlHs-HRMHen3Ki8WWSVA4')
})
.then(function(subscription) {
// The subscription was successful
ajax(subscription);
})
.catch(function(e) {
console.log( e );
});
});
}
function urlB64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function ajax (subscription) {
console.log( 'in ajax' );
console.log( subscription );
$.ajax({
url: 'push.php',
type: 'POST',
data: {
'endpoint': subscription.endpoint,
},
success: function( response ) {
// response = JSON.parse( response );
console.log(typeof response);
console.log(response);
// I get message was sent successfuly here
},
error: function (xhr, status, error) {
console.log( xhr.responseText );
}
});
}
Service worker is registered successfully, I can ask for user permission and send values from subscription object back to backend, everything goes fine with web push, but event is still not being triggered.
Any help would be appreciated, thanks.
One of possible problems is that you need to allow PUT and DELETE type of requests on your server.
Problem was that "keys" value of associative array that I've passed to Subscription::create had wrong hardcoded values.
I am building a web push notification system and I am using the concept used in this example:
https://github.com/Minishlink/web-push-php-example
I have the following code in my JS file. It checks for API support, checks if notifications are not disabled, registers the service worker, asks for permission to display notifications, if allowed subscribes the user and sends the details to the server. If the user is already subscribed, it updates the endpoint value in the DB.
When I run this on Firefox 61, it works fine, but when I run it on Chrome 67 I get this error:
Uncaught (in promise) TypeError: Cannot read property 'getKey' of null
at pushSubscribe (notification.js:48)
at navigator.serviceWorker.ready.then.then.subscription (notification.js:30)
My understanding is that Chrome does not detect the subscription when the service worker is registered and the user is subscribed, hence it gives the error. Any ideas how to fix this?
Thanks.
document.addEventListener('DOMContentLoaded', () => {
// Feature detection
if (!('serviceWorker' in navigator)) {
alert('Service Worker API isn’t supported.');
} else if (!('PushManager' in window)) {
alert('Push API isn’t supported.');
} else if (!('Notification' in window)) {
alert('Notifications API isn’t supported.');
} else if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
alert('Notifications aren’t supported.');
// Check permission
} else if (Notification.permission == 'denied') {
alert('Notifications are disabled.');
} else {
// Register service worker
navigator.serviceWorker.register('/service-worker.js').then(() => {
navigator.serviceWorker.ready.then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.getSubscription()).then(subscription => {
if (!subscription) {
// Subscribe user
navigator.serviceWorker.ready.then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('BNwjaNBKGM13IAef-gJr7C95W3yLJe2F5X0zLnwacN3zCnZK15Vqf3ijeHl9k7K0yBFX3ZwxAmldPoVDpi6iETA'),
})).then(subscription => {
return pushSubscribe(subscription);
});
}
// Update endpoint
return pushSubscribe(subscription);
});
});
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function pushSubscribe(subscription) {
const key = subscription.getKey('p256dh');
const token = subscription.getKey('auth');
const contentEncoding = (PushManager.supportedContentEncodings || ['aesgcm'])[0];
return fetch('/scripts/notification-subscribe.php', {
method: 'POST',
body: JSON.stringify({
endpoint: subscription.endpoint,
publicKey: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : null,
authToken: token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null,
contentEncoding,
user: userId, // generated beforehand
}),
}).then(() => subscription);
}
}
});
if(subscription){
const key = subscription.getKey('p256dh');
const token = subscription.getKey('auth');
const contentEncoding = (PushManager.supportedContentEncodings || ['aesgcm'])[0];
return fetch('/scripts/notification-subscribe.php', {
method: 'POST',
body: JSON.stringify({
endpoint: subscription.endpoint,
publicKey: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : null,
authToken: token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null,
contentEncoding,
user: 1,
}),
}).then(() => subscription);
}
}
Just modify this function hope pushSubscription with if block it works
I have developed a push notification service for my web site. the service worker is:
'use strict';
self.addEventListener('push', function (event) {
var msg = {};
if (event.data) {
msg = event.data.json();
}
let notificationTitle = msg.title;
const notificationOptions = {
body: msg.body,//body
dir:'rtl',//direction
icon: msg.icon,//image
data: {
url: msg.url,//click
},
};
event.waitUntil(
Promise.all([
self.registration.showNotification(
notificationTitle, notificationOptions),
])
);
});
self.addEventListener('notificationclick', function (event) {
event.notification.close();
let clickResponsePromise = Promise.resolve();
if (event.notification.data && event.notification.data.url) {
clickResponsePromise = clients.openWindow(event.notification.data.url);
}
const fetchOptions =
{ method: 'post'};
fetch('http://localhost:5333/usrh.ashx?click=true', fetchOptions).
then(function (response)
{
if (response.status >= 400 && response.status < 500)
{
throw new Error('Failed to send push message via web push protocol');
}
}).catch((err) =>
{
this.showErrorMessage('Ooops Unable to Send a Click', err);
});
});
self.addEventListener('notificationclose', function (event) {
const fetchOptions =
{ method: 'post'};
fetch('http://localhost:5333/usrh.ashx?close=true', fetchOptions).
then(function (response)
{
if (response.status >= 400 && response.status < 500)
{
throw new Error('Failed to send push message via web push protocol');
}
}).catch((err) =>
{
this.showErrorMessage('Ooops Unable to Send a Click', err);
});
});
self.addEventListener('pushsubscriptionchange', function () {
const fetchOptions = {
method: 'post'
,
};
fetch('http://localhost:5333/usru.ashx', fetchOptions)
.then(function (response) {
if (response.status >= 400 && response.status < 500) {
console.log('Failed web push response: ', response, response.status);
throw new Error('Failed to update users.');
}
})
.catch((err) => {
this.showErrorMessage('Ooops Unable to Send a user', err);
});
});
I have subscribed the users successfully using the following code:
registerServiceWorker() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('http://localhost:5333/service-worker.js')
.catch((err) => {
this.showErrorMessage('Unable to Register SW', 'Sorry this demo requires a service worker to work and it ' + 'failed to install - sorry :(');
console.error(err);
});
} else {
this.showErrorMessage('Service Worker Not Supported', 'Sorry this demo requires service worker support in your browser. ' +
'Please try this demo in Chrome or Firefox Nightly.');
}
}
and
class PushClient {
constructor(subscriptionUpdate, appkeys) {
this._subscriptionUpdate = subscriptionUpdate;
this._publicApplicationKey = appkeys;
if (!('serviceWorker' in navigator)) {
return;
}
if (!('PushManager' in window)) {
return;
}
if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
return;
}
navigator.serviceWorker.ready.then(() => {
this.setUpPushPermission();
});
}
setUpPushPermission() {
return navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
return serviceWorkerRegistration.pushManager.getSubscription();
})
.then((subscription) => {
if (!subscription) {
return;
}
this._subscriptionUpdate(subscription);
})
.catch((err) => {
console.log('setUpPushPermission() ', err);
});
}
subscribeDevice() {
return new Promise((resolve, reject) => {
if (Notification.permission === 'denied') {
sc(3);
return reject(new Error('Push messages are blocked.'));
}
if (Notification.permission === 'granted') {
sc(3);
return resolve();
}
if (Notification.permission === 'default') {
Notification.requestPermission((result) => {
if (result === 'denied') {
sc(0);
} else if (result === 'granted') {
sc(1);
} else {
sc(2);
}
if (result !== 'granted') {
reject(new Error('Bad permission result'));
}
resolve();
});
}
})
.then(() => {
return navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
return serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true
, applicationServerKey: this._publicApplicationKey.publicKey
,
});
})
.then((subscription) => {
this._subscriptionUpdate(subscription);
if (subscription) {
this.sendPushMessage(subscription);
}
})
.catch((subscriptionErr) => { });
})
.catch(() => { });
}
toBase64(arrayBuffer, start, end) {
start = start || 0;
end = end || arrayBuffer.byteLength;
const partialBuffer = new Uint8Array(arrayBuffer.slice(start, end));
return btoa(String.fromCharCode.apply(null, partialBuffer));
}
unsubscribeDevice() {
navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
return serviceWorkerRegistration.pushManager.getSubscription();
})
.then((pushSubscription) => {
if (!pushSubscription) {
this._subscriptionUpdate(null);
return;
}
return pushSubscription.unsubscribe()
.then(function (successful) {
if (!successful) {
console.error('We were unable to unregister from push');
}
});
})
.then(() => {
this._subscriptionUpdate(null);
})
.catch((err) => {
console.error('Error thrown while revoking push notifications. ' + 'Most likely because push was never registered', err);
});
}
sendPushMessage(subscription) {
let payloadPromise = Promise.resolve(null);
payloadPromise = JSON.parse(JSON.stringify(subscription));
const vapidPromise = EncryptionHelperFactory.createVapidAuthHeader(this._publicApplicationKey, subscription.endpoint, 'http://localhost:5333/');
return Promise.all([payloadPromise, vapidPromise, ])
.then((results) => {
const payload = results[0];
const vapidHeaders = results[1];
let infoFunction = this.getWebPushInfo;
infoFunction = () => {
return this.getWebPushInfo(subscription, payload, vapidHeaders);
};
const requestInfo = infoFunction();
this.sendRequestToProxyServer(requestInfo);
});
}
getWebPushInfo(subscription, payload, vapidHeaders) {
let body = null;
const headers = {};
headers.TTL = 60;
if (payload) {
headers.Encryption = `auth=${payload.keys.auth}`;
headers['Crypto-Key'] = `p256dh=${payload.keys.p256dh}`;
headers['Content-Encoding'] = 'aesgcm';
} else {
headers['Content-Length'] = 0;
}
if (vapidHeaders) {
headers.Authorization = `WebPush ${vapidHeaders.authorization}`;
if (headers['Crypto-Key']) {
headers['Crypto-Key'] = `${headers['Crypto-Key']}; ` + `p256ecdsa=${vapidHeaders.p256ecdsa}`;
} else {
headers['Crypto-Key'] = `p256ecdsa=${vapidHeaders.p256ecdsa}`;
}
}
const response = {
headers: headers
, endpoint: subscription.endpoint
,
};
if (body) {
response.body = body;
}
return response;
}
sendRequestToProxyServer(requestInfo) {
const fetchOptions = {
method: 'post'
,
};
if (requestInfo.body && requestInfo.body instanceof ArrayBuffer) {
requestInfo.body = this.toBase64(requestInfo.body);
fetchOptions.body = requestInfo;
}
fetchOptions.body = JSON.stringify(requestInfo);
fetch('http://localhost:5333/usrh.ashx', fetchOptions)
.then(function (response) {
if (response.status >= 400 && response.status < 500) {
console.log('Failed web push response: ', response, response.status);
throw new Error('Failed to send push message via web push protocol');
}
})
.catch((err) => {
this.showErrorMessage('Ooops Unable to Send a Push', err);
});
}
}
All these codes are in javascript. I can successfully recieve user subscription infromarion on my server like:
Authorization: WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwcxxxxx
Crypto-Key: p256dh=BBp90dwDWxxxxc1TfdBjFPqxxxxxwjO9fCip-K_Eebmg=; p256ecdsa=BDd3_hVL9fZi9Yboxxxxxxo
endpoint: https://fcm.googleapis.com/fcm/send/cxxxxxxxxxxxxxxJRorOMHKLQ3gtT7
Encryption: auth=9PzQZ1mut99qxxxxxxxxxxyw==
Content-Encoding: aesgcm
Also I can successfully send a push to this user using the code bellow in C#:
public static async Task<bool> SendNotificationByte(string endpoint, string[] Keys, byte[] userSecret, byte[] data = null,
int ttl = 0, ushort padding = 0, bool randomisePadding = false, string auth="")
{
#region send
HttpRequestMessage Request = new HttpRequestMessage(HttpMethod.Post, endpoint);
Request.Headers.TryAddWithoutValidation("Authorization", auth);
Request.Headers.Add("TTL", ttl.ToString());
if (data != null && Keys[1] != null && userSecret != null)
{
EncryptionResult Package = EncryptMessage(Decode(Keys[1]), userSecret, data, padding, randomisePadding);
Request.Content = new ByteArrayContent(Package.Payload);
Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
Request.Content.Headers.ContentLength = Package.Payload.Length;
Request.Content.Headers.ContentEncoding.Add("aesgcm");
Request.Headers.Add("Crypto-Key", "dh=" + Encode(Package.PublicKey)+" ;"+Keys[2]+"="+Keys[3]);
Request.Headers.Add("Encryption", "salt=" + Encode(Package.Salt));
}
using (HttpClient HC = new HttpClient())
{
HttpResponseMessage res = await HC.SendAsync(Request).ConfigureAwait(false);
if (res.StatusCode == HttpStatusCode.Created)
return true;
else return false;
}
#endregion
}
The problem is that after a period of time (about 20 hours or even less), when I want to send a push to this user I got the following errors:
firefox subscription:
{StatusCode: 410, ReasonPhrase: 'Gone', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Access-Control-Allow-Headers: content-encoding,encryption,crypto-key,ttl,encryption-key,content-type,authorization
Access-Control-Allow-Methods: POST
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: location,www-authenticate
Connection: keep-alive
Cache-Control: max-age=86400
Date: Tue, 21 Feb 2017 08:19:03 GMT
Server: nginx
Content-Length: 179
Content-Type: application/json
}}
chrome subscription:
{StatusCode: 400, ReasonPhrase: 'UnauthorizedRegistration', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Alt-Svc: quic=":443"; ma=2592000; v="35,34"
Vary: Accept-Encoding
Transfer-Encoding: chunked
Accept-Ranges: none
Cache-Control: max-age=0, private
Date: Tue, 21 Feb 2017 08:18:35 GMT
Server: GSE
Content-Type: text/html; charset=UTF-8
Expires: Tue, 21 Feb 2017 08:18:35 GMT
}}
I think I missed something, that makes the subscription expires, or have to make the users to resubscribe when their subscription information is changed or expired, but I do not know how?!!
The Problem is solved by sending a push echo notification to the subscribed users to resubscribe them. I have wrote a job in which I send a push echo periodically and resubscribe the users and update their information.
To do so I send an special message called "push echo" using the code bellow:
self.addEventListener('push', function (event) {
lastEventName = 'push';
var msg = {};
if (event.data) {
msg = event.data.json();
if (!!msg.isEcho) {
self.registration.pushManager.getSubscription()
.then(function (subscription) {
if (!subscription) {
} else {
subscription.unsubscribe().then(function () {
self.registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: base64UrlToUint8Array('xxxxxxxxxxxxxxxx') })
.then(function (subscription) {
resubscription(subscription);
});
});
}
});
return;
}
}
if (!!msg.isEcho)
return;
let notificationTitle = msg.title;
const notificationOptions = {
body: msg.body,
dir: 'rtl',
icon: msg.icon,
data: {
url: msg.url,
id: msg.id,
key: msg.key
},
};
event.waitUntil(
Promise.all([
self.registration.showNotification(
notificationTitle, notificationOptions),
])
);
const fetchOptions =
{ method: 'post', mode: 'no-cors' };
fetch('http://example.com', fetchOptions).
then(function (response) {
if (response.status >= 400 && response.status < 500) {
throw new Error('Failed to send push message via web push protocol');
}
lastEventName = 'view';
}).catch((err) => {
this.showErrorMessage('Ooops Unable to Send a Click', err);
});
});
In the resubscription method you can unsubscribe and then subscribe the user and update server data.
I think the issue is about how you send your applicationServerKey. I just have done an example of what you want to do and I had to send that key encoded with this function:
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
so you have to create your subscription object in this way :
registration.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(this._publicApplicationKey.publicKey),
})
Mainly what I did was follow this tutorial.
I have that working example in this github repo. The README file is in Spanish, but I think it can help you.
I think the problem can be solved by resubscribing the users.
Some clues:
The sw needs to be registered and activated, at the time the push event arrives. This mean that you may not clean session, use private browsing mode, clean cache of computer in between.
The push event must come from the same origin.