I'm trying push notification with service worker in PWA. After registering service worker. I have added event listener for install and activate and inside the activate event listener added logic for registering to push service through push manager subscription but the problem is no logs are present in console regarding the activate and install events whereas trying to test push through chrome dev tools and not working here is my code:
index.js
serviceWorker.register()
worker.js
const publicVapidKey = "BOdud_KdO16dL40D56LbksLa6ElXFAJu-2XEdIKQUmehzomo6FES2jJ1niaUYrCobfI8U5rkqeQNjPB03mKZMvY"
export const register = () => {
console.log('register called');
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/worker.js')
.then((responseRegister) => {
console.log('register object', responseRegister);
window.registration = responseRegister;
})
.catch((err) => {
console.log('error is ', err);
})
}
}
export const unregister = () => {
console.log("unregister called");
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations()
.then(function (registrations) {
console.log('registrations', registrations);
for (let registration of registrations) {
registration.unregister()
}
}).catch(function (err) {
console.log('Service Worker registration failed: ', err);
});
}
}
window.self.addEventListener('install', (event) => {
console.log('service worker installed', event);
})
window.self.addEventListener('activate', (event) => {
console.log('service worker activated', event);
console.log('window', window);
window.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
}).then((subscription) => {
console.log('subscription', subscription);
})
.catch((err) => {
console.log('error in subscribing to push', err);
});
})
window.self.addEventListener("push", e => {
const data = e.data.json();
console.log("Push Recieved...");
window.registration.showNotification(
data.title, {
body: "Notified by Traversy Media!",
});
});
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;
}
clicking on push doesn't fire any push event
Related
I'm working with Webpush notification in Laravel. It shows permission denied when i click on send notification button, and i want to update the count as soon as i press that send notification button. I'm following this link : https://medium.com/#sagarmaheshwary31/push-notifications-with-laravel-and-webpush-446884265aaa
I'm using following code:
enable-push.js
'use strict';
const swReady = navigator.serviceWorker.ready;
document.addEventListener('DOMContentLoaded', function () {
initSW();
});
function initSW() {
if (!"serviceWorker" in navigator) {
//service worker isn't supported
return;
}
//don't use it here if you use service worker
//for other stuff.
if (!"PushManager" in window) {
//push isn't supported
return;
}
//register the service worker
navigator.serviceWorker.register('../sw.js')
.then(() => {
console.log('serviceWorker installed!')
initPush();
})
.catch((err) => {
console.log(err)
});
}
function initPush() {
if (!swReady) {
return;
}
new Promise(function (resolve, reject) {
const permissionResult = Notification.requestPermission(function (result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
})
.then((permissionResult) => {
console.log(permissionResult);
if (permissionResult !== 'granted') {
throw new Error('We weren\'t granted permission.');
}
subscribeUser();
});
}
/**
* Subscribe the user to push
*/
function subscribeUser() {
swReady
.then((registration) => {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
"BIHpWZJBO9Ilqq0UG4+zKmOcYFZBbYIf2cC/sNnEOeA9DCihcDCBhwSVO+ELV9UN/ktv0ksAgoOF53zFT+h9vh="
)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then((pushSubscription) => {
console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
storePushSubscription(pushSubscription);
});
}
/**
* send PushSubscription to server with AJAX.
* #param {object} pushSubscription
*/
function storePushSubscription(pushSubscription) {
const token = document.querySelector('meta[name=csrf-token]').getAttribute('content');
fetch('/push', {
method: 'POST',
body: JSON.stringify(pushSubscription),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': token
}
})
.then((res) => {
return res.json();
})
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
});
}
/**
* urlBase64ToUint8Array
*
* #param {string} base64String a public vapid key
*/
function urlBase64ToUint8Array(base64String) {
var padding = '='.repeat((4 - base64String.length % 4) % 4);
var base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
var rawData = window.atob(base64);
var outputArray = new Uint8Array(rawData.length);
for (var i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
Please help to sort out my issue,
I'm trying to do web push notifications that are clickable and when user click on it he should be redirected to some url.
I'm using Laravel 5.4 with package:
https://github.com/laravel-notification-channels/webpush
Here is some of the code:
app.blade.php
<script>
var _registration = null;
function registerServiceWorker() {
return navigator.serviceWorker.register('{{ asset('assets/js/service-worker.js') }}')
.then(function(registration) {
console.log('Service worker successfully registered.');
_registration = registration;
return registration;
})
.catch(function(err) {
console.error('Unable to register service worker.', err);
});
}
function askPermission() {
return new Promise(function(resolve, reject) {
const permissionResult = Notification.requestPermission(function(result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
})
.then(function(permissionResult) {
if (permissionResult !== 'granted') {
throw new Error('We weren\'t granted permission.');
}
else{
subscribeUserToPush();
}
});
}
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 getSWRegistration(){
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (_registration != null) {
resolve(_registration);
}
else {
reject(Error("It broke"));
}
});
return promise;
}
function subscribeUserToPush() {
getSWRegistration()
.then(function(registration) {
console.log(registration);
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
"{{env('VAPID_PUBLIC_KEY')}}"
)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
sendSubscriptionToBackEnd(pushSubscription);
return pushSubscription;
});
}
function sendSubscriptionToBackEnd(subscription) {
return fetch('/api/save-subscription/{{Auth::user()->id}}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
})
.then(function(response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function(responseData) {
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
function enableNotifications(){
//register service worker
//check permission for notification/ask
askPermission();
}
registerServiceWorker();
</script>
service-worker.js
self.addEventListener('push', function(event) {
if (event.data) {
var data = event.data.json();
self.registration.showNotification(data.title, {
body: data.body,
icon: data.icon
});
console.log('This push event has data: ', event.data.text());
} else {
console.log('This push event has no data.');
}
});
self.addEventListener('notificationclick', function(event) {
console.log('Notification click: tag ', event.notification.tag);
event.notification.close();
var url = 'https://google.com';
console.log('kliknieto');
event.waitUntil(
clients.matchAll({
type: 'window'
})
.then(function(windowClients) {
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
});
Notification:
public function toWebPush($notifiable, $notification)
{
return (new WebPushMessage)
->title('Some title')
->icon(asset("img/img.jpg"))
->body('Some body')
->action('View action', 'view_action')
->data(['id' => $notification->id, 'link' => $this->link]);
}
Generally it display the notification, but when I am trying to click on it I want to be redirected to https://google.com, but nothing happens. Do you have any idea how to fix it?
The problem has been solved after reset Google Chrome settings to default.
Found the solution. The action method accepts title and action, but the action is not an url. You should add notificationclick event to your service-worker.js file.
self.addEventListener('notificationclick', function (event) {
event.notification.close();
event.waitUntil(
self.clients.openWindow('https://www.example.com/')
);
});
Reference - https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/notificationclick_event
I have used this Firebase Database code in a previous project:
const getDeviceUser = admin.database().ref(`/users/${notification.to}/`).once('value');
I am now trying to convert it for Firestore. I am basically trying to get my users fcm's when a notification is being sent. I have tried many things, but haven't seen the new way to accomplish this.
EDIT: here is my code.
exports.sendFavoriteNotification = functions.firestore.document('users/{userUid}/notifications/{notificationId}').onCreate(event => {
const notification = event.data.data();
const user = event.params.userUid;
const getDeviceUser = admin.database().ref(`/users/${notification.to}/`).once('value');
// Get the follower profile.
const getProfilePromise = admin.auth().getUser(notification.sender);
return Promise.all([getDeviceUser, getProfilePromise]).then(results => {
const tokensSnapshot = results[0];
const liker = results[1];
// Check if there are any device tokens.
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
//console.log('There are', tokensSnapshot.numChildren(), 'tokens to send notifications to.');
console.log('Fetched follower profile', liker);
// Notification details.
const payload = {
notification : {
title : 'You have a new like!',
body : `${liker.displayName} just liked your photo.`,
badge: '1',
sound: 'default'
}
};
// Listing all tokens.
var tokens = admin.firestore.ref(`/users/${notification.to}/`).get('fcm');
// Send notifications to all tokens.
admin.messaging().sendToDevice(tokens.data(), payload);
return admin.messaging().sendToDevice(tokens, payload).then(response => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.update({
fcm: FieldValue.delete()
}));
}
}
});
return Promise.all(tokensToRemove);
});
});
});
Hope this will help. This is my code after 2 days of trying to learn how to convert from realtime database to firestore. It is based on a firebase project: https://github.com/MahmoudAlyuDeen/FirebaseIM
let functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotificationToFirestone = functions.firestore.document('/notifications/{pushId}')
.onCreate(event => {
const pushId = event.data.id;
const message = event.data.data();
const senderUid = message.from;
const receiverUid = message.to;
const db = admin.firestore();
if (senderUid === receiverUid) {
console.log('pushId: '+ pushId);
return db.collection('notifications').doc(pushId).delete();;
} else {
const ref = db.collection('users').doc(receiverUid);
const query = new Promise(
function (resolve, reject) {
ref.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
reject(new Error('No such document!'));
} else {
console.log('Document data:', doc.data().instanceId);
resolve(doc.data().instanceId);
}
})
.catch(err => {
console.log('Error getting document', err);
reject(err);
});
});
const getSenderUidPromise = admin.auth().getUser(senderUid);
return Promise.all([query, getSenderUidPromise]).then(results => {
//console.log('instanceId = Result[0]: ' + results[0]);
//console.log('sender = Result[1]: ' + results[1]);
const instanceId = results[0];
const sender = results[1];
//console.log('notifying ' + receiverUid + ' about ' + message.body + ' from ' + senderUid);
//console.log('instanceId este' + instanceId);
const payload = {
notification: {
title: sender.displayName,
body: message.body,
icon: sender.photoURL
}
};
admin.messaging().sendToDevice(instanceId, payload)
.then(function (response) {
console.log("Message sent: ", response);
})
.catch(function (error) {
console.log("Error sending message: ", error);
});
});
}
});
Service Worker Message Handler:
let angularClient;
self.addEventListener('message', function(event) {
// store the client which sent the message into angularClient variable
angularClient = event.ports[0];
});
Service Worker Notification Click Handler, Which sends data to angularClient
self.addEventListener('notificationclick', function(event) {
event.notification.close();
var url = /localhost:8080|example.com|https:\/\/www.example.com/;
var newurl = "/" + event.notification.data.url;
if (event.notification.data.url) {
newurl = event.notification.data.url;
}
function endsWith(str, suffix) {
console.log(str);
console.log(suffix);
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
event.waitUntil(
clients.matchAll({
type: 'window'
})
.then(function(windowClients) {
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
if (url.test(client.url) && 'focus' in client) {
if (endsWith(client.url, newurl)) {
console.log("URL matched");
console.log("sending 1");
angularClient.postMessage(sendToAngularPayload);
return client.focus();
}
return client.navigate(newurl)
.then(client => client.focus());
}
}
if (clients.openWindow) {
console.log("sending 2");
angularClient.postMessage(sendToAngularPayload); //sendToAngularPayload is defined when notification is received in firebase's messaging.setBackgroundMessageHandler.
return clients.openWindow('https://www.example.com/#/' +
event.notification.data.url);
}
})
);
},true);
AngularJs Controller with functions
Function to send message to service worker so that it stores this client
$scope.checkServiceWorker = function() {
if ('serviceWorker' in navigator) {
// ensure service worker is ready
navigator.serviceWorker.ready.then(function(reg) {
console.log("Send message");
// PING to service worker, later we will use this ping to
//identify our client.
sendMessage().then(function(event) {
console.log(event);
}).catch(function(error) {
console.log("error", error);
location.reload();
});
}).catch(function() {
console.log('SW not ready');
$scope.checkServiceWorker();
});
}
}
sendMessage function with onMessage handler
function sendMessage() {
return new Promise(function(resolve, reject) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
console.log("on message handler", event);
if (event.data.error) {
reject(event.data.error);
} else {
console.log('inside resolve', event.data);
console.log("Ping received from SW");
console.log(event);
resolve(event.data);
}
};
console.log("Sending");
navigator.serviceWorker.controller.postMessage("ping",
[messageChannel.port2]);
console.log("sent");
});
}
The problem is that onMessage Handler inside angularjs controller gets fired 90% of the times, but sometimes it does not. As I can see in the developer console, the execution stops in serviceworker.js after I print "sending 1" in the notification click handler, and does not show rest of the logs inside the controller's onMessage handler.
worker.ts
self.addEventListener("push", e => {
const data = e.data.json();
console.log("Push received");
console.log("data ", data);
self.registration.showNotification(data.title, {
body: "Notified",
})
// Broadcasting from a ServiceWorker to every client
self.clients.matchAll().then(all => all.map(client => client.postMessage(data)));
})
The listener is added on navigator.serviceWorker and not on a specific
worker
AngularJs controller:
constructor() {
navigator.serviceWorker.addEventListener('message', e => console.log(e.data));
}
I want to display drive file metadata
"iconLink",
"thumbnailLink"
of each of the files in drive, but getting output for fields like kind, id, name, mimeType only. While other fields are not displayed.
function loadDriveApi() {
gapi.client.load('drive', 'v3', listFiles);
}
function listFiles() {
var request = gapi.client.drive.files.list({});
request.execute(function(resp) {
var files = resp.files;
if (files && files.length > 0) {
for (var i = 0; i < files.length; i++) {
var file = files[i];
appendPre(file.iconLink);
}
} else {
appendPre('No files found.');
}
});
}
at least you will need the scope: https://www.googleapis.com/auth/drive.metadata.readonly + OAuth (API-Key & Client-ID)
you can test it at: https://developers.google.com/drive/v3/reference/files/list
in the "fields" input add: files(iconLink,thumbnailLink)
if you use https://apis.google.com/js/api.js, be sure to add your domain to API-Key -> HTTP-Referrer & Client-ID -> JavaScript-Source & Forwarding-URI (# https://console.developers.google.com/apis/credentials)
you can find a basic gapi usage sample here: https://github.com/google/google-api-javascript-client/blob/51aa25bed4f6c36d8e76fd3b9f7e280ded945c98/samples/loadedDiscovery.html
I promisified the gapi client a bit some time ago, because I disliked the mix of callbacks and thenables in the methods.. this worked for me (assuming api.js was already loaded), but only hold 100 file entries in the response.
window.gapiPromisified = {
apiKey: 'XXXXXXXXXXX',
clientId: 'XXXXX-XXXXXX.apps.googleusercontent.com'
}
window.gapiPromisified.init = function init () {
return new Promise(resolve => {
gapi.load('client:auth2', () => {
if (!document.getElementById('gapiAuthButton')) {
let authButton = document.createElement('button')
authButton.id = 'gapiAuthButton'
authButton.style.display = 'none'
authButton.style.marginLeft = 'auto'
authButton.style.marginRight = 0
document.body.insertBefore(authButton, document.body.firstChild)
authButton.addEventListener('click', () => {
let GoogleAuth = gapi.auth2.getAuthInstance()
if (GoogleAuth.isSignedIn.get()) {
GoogleAuth.signOut()
} else {
GoogleAuth.signIn()
}
})
}
gapi.client.setApiKey(this.apiKey)
gapi.auth2.init({ client_id: this.clientId })
.then(() => resolve())
})
})
}
window.gapiPromisified.signIn = function signIn () {
return new Promise(resolve => {
let GoogleAuth = gapi.auth2.getAuthInstance()
// Listen for sign-in state changes
GoogleAuth.isSignedIn.listen(isSignedIn => {
let authButton = document.getElementById('gapiAuthButton')
if (isSignedIn) {
authButton.textContent = 'Sign-out'
resolve()
} else {
authButton.textContent = 'Sign-in'
}
})
// Handle the initial sign-in state
let authButton = document.getElementById('gapiAuthButton')
let isSignedIn = GoogleAuth.isSignedIn.get()
authButton.textContent = (isSignedIn) ? 'Sign-out' : 'Sign-in'
document.getElementById('gapiAuthButton').style.display = 'block'
if (isSignedIn) {
resolve()
} else {
GoogleAuth.signIn()
}
})
}
window.gapiPromisified.getReady = function getReady () {
if (!gapi.hasOwnProperty('auth2')) {
return this.init()
.then(() => this.signIn())
} else {
if (gapi.auth2.getAuthInstance().isSignedIn.get()) {
return Promise.resolve()
} else {
return this.signIn()
}
}
}
window.gapiPromisified.getScopes = function getScopes (scopes) {
return new Promise((resolve, reject) => {
let GoogleUser = gapi.auth2.getAuthInstance().currentUser.get()
if (GoogleUser.hasGrantedScopes(scopes)) {
resolve()
} else {
// method returns goog.Thenable
GoogleUser.grant({ 'scope': scopes })
.then(onFulfilled => {
resolve(onFulfilled)
}, onRejected => {
reject(onRejected)
})
}
})
}
window.gapiPromisified.loadAPI = function loadAPI (urlOrObject) {
return new Promise((resolve, reject) => {
// method returns goog.Thenable
gapi.client.load(urlOrObject)
.then(onFulfilled => {
resolve(onFulfilled)
}, onRejected => {
reject(onRejected)
})
})
}
window.gapiPromisified.metadata = function metadata () {
return new Promise((resolve, reject) => {
this.getReady()
.then(() => this.getScopes('https://www.googleapis.com/auth/drive.metadata.readonly'))
.then(() => this.loadAPI('https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'))
.then(() => {
gapi.client.drive.files.list({
fields: 'files(iconLink,thumbnailLink)'
})
.then(onFulfilled => {
resolve(onFulfilled)
}, onRejected => {
reject(onRejected)
})
})
})
}
window.gapiPromisified.metadata()
.then(res => {
res.result.files.forEach(file => {
console.log(file.iconLink)
console.log(file.thumbnailLink)
})
})
In the v3 you need to specify which fields you want included in the metadata response. See the fields= parameter