I am new to Google Chrome Push notifications and I was just reading some questions and answers here, on stackoverflow and I have ended with this easy push notification javascript.
navigator.serviceWorker.register('sw.js');
function notify() {
Notification.requestPermission(function(result) {
if (result === 'granted') {
navigator.serviceWorker.ready.then(function(registration) {
registration.showNotification('test notification', {
body: 'Hey I am test!',
icon: 'image.png',
});
});
}
});
}
Its just simple notification, but I need open a new window with other webpage after click on notification.
I know it is possible, but I cant find examples using "serviceWorker" syntax.
Please help. Thanks.
I am guessing you are in a Service Worker context, because that's where Push Notifications are received. So you have the self object to add a event listener to, that will react to a click on the notification.
(Place this code in your sw.js file, which is your Service Worker script.)
self.addEventListener('notificationclick', function(event) {
let url = 'https://example.com/some-path/';
event.notification.close(); // Android needs explicit close.
event.waitUntil(
clients.matchAll({type: 'window'}).then( windowClients => {
// Check if there is already a window/tab open with the target URL
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
// If so, just focus it.
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
// If not, then open the target URL in a new window/tab.
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
});
If you want to open website with dynamic URL received from FCM push notification or any other web push notification then
BELOW IS AN EXAMPLE OF SERVICE WORKER USED FOR FCM PUSH NOTIFICATION
messaging.setBackgroundMessageHandler(function(payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
var notificationTitle = payload.data.title; //or payload.notification or whatever your payload is
var notificationOptions = {
body: payload.data.body,
icon: payload.data.icon,
data: { url:payload.data.click_action }, //the url which we gonna use later
actions: [{action: "open_url", title: "Read Now"}]
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
and handle click event with below code
self.addEventListener('notificationclick', function(event) {
switch(event.action){
case 'open_url':
clients.openWindow(event.notification.data.url); //which we got from above
break;
case 'any_other_action':
clients.openWindow("https://www.example.com");
break;
}
}
, false);
Hope it helps!
(This code refers to firebase messaging) I was also searching for a soluting and the answer was very easy, but there was no doc saying it clearly. You need to put "click_action" = "your url" inside the notification json. Here is an example:
notification: {
title: "Come",
icon: '../../../../assets/logo.png',
vibrate: [300,100,400,100,400,100,400],
body: "some text",
click_action : "your link"
}
Hope it helps.
{
"notification": {
"title": "Hey there",
"body": "Subscribe to might ghost hack youtube channel",
"click_action" : "http://localhost:4200"
},
"to":"YOUR_TOKEN"
}
This worked for me
"#angular/fire": "^6.1.5",
"firebase": "^7.0 || ^8.0"
Related
I use the REST API from Firebase to send notifications to all browsers. I use PHP to send the message and handle the result with the service worker.
The problem I'm facing is that each time I send a notification, I get two notifications in the browser. One corresponds to the notification content send from PHP (foreground notification) and the other one is coming from the service worker (background notification).
While I understand the need for both, I would like to show only one depending on the context (context = browser open or not).
For the sending, I'm using the following array:
$additional["url1"]="https://www.orange.be";
$additional["url2"]="https://www.proximus.be";
$additional["url3"]="https://www.base.be";
$additional = json_encode($additional);
$fields = array(
'to'=>$to,
'notification' => [
'title' => 'new title',
'body' => 'aaa' ,
'color' => "#FF33CC",
'data'=>$additional
],
"priority" => "high",
);
And in the service worker, I have this:
messaging.onBackgroundMessage((payload) => {
//console.log('Messaging:');
//console.log(messaging);
console.log('Payload:');
console.log(payload);
additional = payload.data["gcm.notification.data"];
console.log(additional)
additional = JSON.parse(additional);
console.log(additional);
const notificationTitle = payload.notification["title"]+'(bg)';
const notificationOptions = {
body: payload.notification["body"],
icon: '/firebase-logo.png'
};
self.registration.showNotification(notificationTitle,notificationOptions);
});
If I remove the last line (self.registration), I only receive the foreground notification coming from PHP.
How can I detect the context to use either the foreground or the background notification instead of both at the same time?
Thanks
Messages received in background are handled using the onBackgroundMessage and for foreground onMessage.
Background Handler
messaging.onBackgroundMessage((payload) => {
console.log('Background Message received. ', payload);
// ...
});
Foreground Handler
messaging.onMessage((payload) => {
console.log('Message received. ', payload);
// ...
});
I found a solution based on another post here. I removed the notification from the json and passed everything through variables:
$fields = array(
'to'=> $to,
'data'=> [
"title"=> "My title",
"description"=>"My description ",
"link"=>"destination link",
"badge"=>"image url",
"image"=>"image url"],
"priority" => "high",
);
And inside the service worker, I capture the data with payload.data and associate it to the notification options
messaging.onBackgroundMessage((payload) => {
console.log('Messaging / bg');
console.log(messaging);
console.log('Payload:');
console.log(payload);
additional = payload.data;
console.log(additional)
timing = Date.now();
if (typeof additional["timing"] !=="undefined")
{
timing = additional["timing"];
}
const notificationTitle =additional["title"]+'(bg)';
const notificationOptions = {
body: additional["description"],
icon: additional["image"],
image: additional["image"],
badge: additional["badge"],
eventTime: timing,
};
// DISPLAY NOTIFICATION
self.registration.showNotification(notificationTitle,notificationOptions);});
I am having problems to implement notifications using firebase. The click event does not work. I am using the HTTP 1 version sending the bearer token.
{
"message": {
"token": "8888****usertoken****8888",
"notification": {
"title": "Background Message Title",
"body": "Background message body"
},
"webpush": {
"fcm_options": {
"link": "https://dummypage.com"
}
}
}
}
I have also tried click_action, action, and many other variations that just did not work.
I am using version 8.0.0
According to the documentation found on this link https://firebase.google.com/docs/cloud-messaging/js/send-multiple, I should be able to implement it using fcm_options.
I tried a workaround implementing messaging.onBackgroundMessage, but when I implement this method and use self.registration.showNotification, the notification is displayed twice. one triggered by the browse and the other by this code.
Registering self.addEventListener('notificationclick' only seems to work when I implement onBackgroundMessage.
I followed the documentation, but it is driving me crazy.
This is my service worker code:
importScripts('https://www.gstatic.com/firebasejs/8.0.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/8.0.0/firebase-messaging.js');
var firebaseConfig = {
apiKey: "xxxxxx",
authDomain: "xxxxxxxx.firebaseapp.com",
databaseURL: "https://xxxxxx.firebaseio.com",
projectId: "xxx-xxx",
storageBucket: "xxx-xxx.appspot.com",
messagingSenderId: "222222222",
appId: "1:2222:web:22222"
};
console.log("fire base messaging")
firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();
messaging.onBackgroundMessage(function (payload) {
console.log("onBackgroundMessage", payload)
var dataFromServer = payload.notification;
var notificationTitle = dataFromServer.title;
var notificationOptions = {
body: dataFromServer.body,
image: dataFromServer.image,
data: {
url: "https://google.com"
}
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
////Code for adding event on click of notification
self.addEventListener('notificationclick', function (event) {
console.log("notificationclick", event)
var urlToRedirect = event.notification.data.url;
event.notification.close();
event.waitUntil(self.clients.openWindow(urlToRedirect));
});
Turns out I was passing an entire URL to webpush.fcm_options.link = "https://google.com", all I had to do was to pass only the relative path like webpush.fcm_options.link = "/mypage".
So the request to send would be like this:
{
"message": {
"token": "8888****usertoken****8888",
"notification": {
"title": "Background Message Title",
"body": "Background message body"
},
"webpush": {
"fcm_options": {
"link": "/mypage"
}
}
}
}
I don't see in the docs say it is only the relative path. It even states that HTTPS is required. I spent a few hours on this one, I hope it helps somebody else.
https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#WebpushFcmOptions
I was having the same issue. I added a notificationclick event handler. You can use the data param in the notification event to open a new tab or focus an already opened one.
The code you already have is fine, now adding the listener looks like this:
// messaging.onBackgroundMessage(...);
function handleClick (event) {
event.notification.close();
// Open the url you set on notification.data
clients.openWindow(event.notification.data.url)
}
self.addEventListener('notificationclick', handleClick);
This resources might be helpful
similar question on SO
showNotification
notificationclick
Notifications are working using legacy API but unfortunately clicking the notification still does nothing. This is my code for sending the notification.
var notification = {
'title': title,
'body': body,
'icon': 'hourglass.png',
'click_action': router.resolve(route).href
}
var payload = {
'notification': notification,
// 'webpush': {
// 'fcm_options': {
// 'link': '/' + router.resolve(route).href
// }
// }
}
if(registrationIds.length == 1) {
payload['to'] = registrationIds[0]
} else if( registrationIds.length > 1){
payload['registration_ids'] = registrationIds
}
return new Promise((resolve, reject) => {
if (registrationIds.length) {
fetch('https://fcm.googleapis.com/fcm/send', {
'method': 'POST',
'headers': {
'Authorization': 'key=' + key,
'Content-Type': 'application/json'
},
'body': JSON.stringify(payload)
}).then(function(response) {
resolve(true)
}).catch(function(error) {
console.error('sendNotification error', error);
reject(false)
})
}
else {
console.log('This timer has no registered clients.')
reject(false)
}
})
Edit: I think most of my confusion stemmed from finding examples with the V1 API and mixing them up with the legacy API. I needed click_action instead of the fcm_options.link in the payload. I updated my code and is now working as intended.
I'm developing a webApp, and want to send notifications using a ServiceWorker.
I have my sw.js and my main.js. I'm able to register my Service Worker and I'm able to send notifications using a command like:
navigator.serviceWorker.getRegistration().then(function(reg) {
reg.showNotification('Title', {
body: 'body'
});
});
But this is not calling my push event defined in the 'sw.js'.
For reference here is my "sw.js":
self.addEventListener('push', function(event) {
console.log('[Service Worker] Push Received.');
console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);
const title = 'title test from push';
const options = {
body: 'body test from push.',
icon: 'images/icon.png'
};
event.waitUntil(self.registration.showNotification(title, options));
});
const title = 'Push Codelab';
const options = {
body: 'Yay it works.',
icon: 'images/icon.png',
badge: 'images/badge.png'
};
self.registration.showNotification(title, options);
As additional information, in Chrome if I go in the Developer tool => Application Tab => Service Workers, and click on "push" button, the correct push event is raised. But, again, I'm not able to trigger it from my main page.
I want to show my notification to the user till i desired by default it is shown up to 19 sec approx. can somebody tell me about any trick regarding this?
i also tried to update again and again to keep it showing but not succeed, actually not got appropriate syntax for doing that.
currently i am using below code to register service worker.
by this code i am able show notification for 19 sec approx, but i want to show it for 1 min.
var url = "https://example.com/json-data.php?param="+Math.random();
self.addEventListener('push', function(event) {
event.waitUntil(
fetch(url).then(function(response) {
if (response.status !== 200) {
// Either show a message to the user explaining the error
// or enter a generic message and handle the
// onnotificationclick event to direct the user to a web page
console.log('Looks like there was a problem. Status Code: ' + response.status);
throw new Error();
}
// Examine the text in the response
return response.json().then(function(data) {
if (data.error || !data.notification) {
console.log('The API returned an error.', data.error);
throw new Error();
}
var title = data.notification.title;
var message = data.notification.message;
var icon = data.notification.icon;
return self.registration.showNotification(title, {
body: message,
icon: icon,
data: {
url: data.notification.url
}
});
});
}).catch(function(err) {
console.log('Unable to retrieve data', err);
var title = 'An error occurred';
var message = 'We were unable to get the information for this push message';
var icon = 'img/design19.jpg';
var notificationTag = 'notification-error';
return self.registration.showNotification(title, {
body: message,
icon: icon,
tag: notificationTag
});
})
);
});
// The user has clicked on the notification ...
self.addEventListener('notificationclick', function(event) {
console.log(event.notification.data.url);
// Android doesn't close the notification when you click on it
// See: http://crbug.com/463146
event.notification.close();
// This looks to see if the current is already open and
// focuses if it is
event.waitUntil(
clients.matchAll({
type: "window"
})
.then(function(clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url == '/' && 'focus' in client)
return client.focus();
}
if (clients.openWindow) {
return clients.openWind`enter code here`ow(event.notification.data.url);
}`enter code here`
})
);
});
There is no parameter as of now on setting the timeout for the notification. It is by default that the notification will show for 20 seconds then the desktop version of Chrome will auto-minimize the notification.
Alternatively, there is a parameter in the options requireInteraction which is false by default. By enabling this to true will make the notification stay visible until the user has interacted with it.
I think you can't directly set how long a notification should be shown.
A possible hacky way to do it would be, once browsers will support persistent notifications (I don't know if Chrome or Firefox do at the moment), to show a persistent notification and then close it after a timeout.
According to hacky way Marco said, It Works!
"notification" => [
"title" => isset($arrData['title']) ? $arrData['title'] : '',
"body" => isset($arrData['description']) ? $arrData['description'] : '',
"icon" => s3_url("images/push-logo.jpg"),
"click_action" => isset($arrData['target_url']) ? $arrData['target_url'] : '',
"image" => isset($arrData['image']) ? $arrData['image'] : '',
],
"data" => [
"requireInteraction" => true,
"duration" => (20 * 1000), // 20 sec
],
"to" => "/topics/$topic",
And set the requireInterationTrue on onMessage , after push notification is shown take duration from data and close notification inside a setTimeout
messaging.onMessage(function(payload) {
console.log('Message received. ', payload.data);
const noteTitle = payload.notification.title;
const noteRequireInteraction = (payload.data.requireInteraction === 'true');
const noteDuration = payload.data.duration;
const noteOptions = {
body: payload.notification.body,
icon: payload.notification.icon,
image: payload.notification.image,
requireInteraction: noteRequireInteraction,
};
if (!document.hidden) {
var notification = new Notification(noteTitle, noteOptions);
setTimeout(function () {
notification.close();
}, noteDuration);
notification.onclick = function(event) {
event.preventDefault();
if(typeof payload.notification.click_action != 'undefined' && payload.notification.click_action != '')
window.open(payload.notification.click_action,'_blank');
notification.close();
}
}
});
So I'm trying to capture web audio from a tab and pass it into another script that works with DOM elements on the page.
EXTENSION SCRIPT
In the background.js, I use the following script:
chrome.tabCapture.capture(constraints, function(stream) {
console.log("\ngot stream");
console.log(stream);
chrome.tabs.sendMessage(tabID, {
"message": "stream",
"stream": stream
});
});
The Developer Toolkit shows me that the created object is indeed a MediaStream object. (Which I want and appears to be working fine).
EXTENSION CONSOLE:
MediaStream {onremovetrack: null, onaddtrack: null, onended: null, ended: false, id: "c0jm4lYJus3XCwQgesUGT9lpyPQiWlGKHb7q"…}
CONTENT SCRIPT
I use a content script (injected), on the page itself to then pull the JSON serialized object back out:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message === "stream") {
var thisStream = request.stream;
console.log(thisStream);
if (!thisStream) {
console.log("stream is null");
return;
}
loadStream(thisStream);
}
else if (request.message === "statusChanged") {
console.log("statusChanged");
}
});
PAGE CONSOLE
Unfortunately, because of JSON serialization, the object type is lost:
Object {onremovetrack: null, onaddtrack: null, onended: null, ended: false, id: "c0jm4lYJus3XCwQgesUGT9lpyPQiWlGKHb7q"…}
I need the object recast as a MediaStream object and have tried the following things which all failed:
Attempt 1: FAILED
var stream = new webkitMediaStream;
function loadStream(thisStream) {
stream = thisStream;
}
Attempt 2: FAILED
var stream;
function loadStream(thisStream) {
stream = new webkitMediaStream(thisStream);
}
Attempt 3: FAILED
var stream;
function loadStream(thisStream) {
stream = Object.create(webkitMediaStream, thisStream);
}
NOTE:
The constructor for the MediaStream object IS webkitMediaStream.
I need either a better method for passing the object from the extension script (the only place the chrome.tab.capture() method works from) to the content script (the only place that has access to and can modify the DOM elements of the page),
OR
I need a way of recasting the JSON serialized object back into a fully functional MediaStream object.
Thanks in advance!
JRad the Bad
Extension messages are always JSON-serialized, so it's indeed obvious that you cannot send a MediaStream from the background page to the web page. The question is, do you really need to send the MediaStream from the background to the content script?
If you only need to, e.g. display the video, then you can use URL.createObjectURL to get a blob:-URL for the stream and assign it to video.src to see a video. The URL created by URL.createObjectURL can only be used by a page at the same origin, so you need to create the <video> tag in a chrome-extension:// page; either in a tab, or in a frame. If you want to do this in a frame, make sure that the page is listed in web_accessible_resources.
If you DO really need a MediaStream object of the tab in the tab, then RTCPeerConnection can be used to send the stream. This WebRTC API is normally used to exchange media streams between peers in a network, but it can also be used to send streams from one page to another page in another tab or browser.
Here's a full example. Visit any web page, and click on the extension button. Then the extension will insert a video in the page showing the current tab.
background.js
function sendStreamToTab(tabId, stream) {
var pc = new webkitRTCPeerConnection({iceServers:[]});
pc.addStream(stream);
pc.createOffer(function(offer) {
pc.setLocalDescription(offer, function() {
// Use chrome.tabs.connect instead of sendMessage
// to make sure that the lifetime of the stream
// is tied to the lifetime of the consumer (tab).
var port = chrome.tabs.connect(tabId, {name: 'tabCaptureSDP'});
port.onDisconnect.addListener(function() {
stopStream(stream);
});
port.onMessage.addListener(function(sdp) {
pc.setRemoteDescription(new RTCSessionDescription(sdp));
});
port.postMessage(pc.localDescription);
});
});
}
function stopStream(stream) {
var tracks = this.getTracks();
for (var i = 0; i < tracks.length; ++i) {
tracks[i].stop();
}
}
function captureTab(tabId) {
// Note: this method must be invoked by the user as defined
// in https://crbug.com/489258, e.g. chrome.browserAction.onClicked.
chrome.tabCapture.capture({
audio: true,
video: true,
audioConstraints: {
mandatory: {
chromeMediaSource: 'tab',
},
},
videoConstraints: {
mandatory: {
chromeMediaSource: 'tab',
},
},
}, function(stream) {
if (!stream) {
alert('Stream creation failed: ' + chrome.runtime.lastError.message);
}
chrome.tabs.executeScript(tabId, {file: 'contentscript.js'}, function() {
if (chrome.runtime.lastError) {
stopStream(stream);
alert('Script injection failed:' + chrome.runtime.lastError.message);
} else {
sendStreamToTab(tabId, stream);
}
});
});
}
chrome.browserAction.onClicked.addListener(function(tab) {
captureTab(tab.id);
});
contentscript.js
function onReceiveStream(stream) {
// Just to show that we can receive streams:
var video = document.createElement('video');
video.style.border = '1px solid black';
video.src = URL.createObjectURL(stream);
document.body.insertBefore(video, document.body.firstChild);
}
function onReceiveOfferSDP(sdp, sendResponse) {
var pc = new webkitRTCPeerConnection({iceServers:[]});
pc.onaddstream = function(event) {
onReceiveStream(event.stream);
};
pc.setRemoteDescription(new RTCSessionDescription(sdp), function() {
pc.createAnswer(function(answer) {
pc.setLocalDescription(answer);
sendResponse(pc.localDescription);
});
});
}
// Run once to prevent the message from being handled twice when
// executeScript is called multiple times.
if (!window.hasRun) {
window.hasRun = 1;
chrome.runtime.onConnect.addListener(function(port) {
if (port.name === 'tabCaptureSDP') {
port.onMessage.addListener(function(remoteDescription) {
onReceiveOfferSDP(remoteDescription, function(sdp) {
port.postMessage(sdp);
});
});
}
});
}
manifest.json
{
"name": "tabCapture to tab",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_title": "Capture tab"
},
"permissions": [
"activeTab",
"tabCapture"
]
}