I'm trying to modernize a website to become a PWA. One part are push notifications. So far so good, I added the manifest with the gcm_sender_id and subscribed to the push service. I tried self-generated vapid-keys or the one provided from google firebase. Snippet:
var reg;
navigator.serviceWorker.ready
.then(function(swreg) {
console.log('SW ready');
reg = swreg;
return swreg.pushManager.getSubscription();
})
.then(function(sub) {
console.log('SW ready.then');
if (sub === null) {
var vapidPublicKey = 'BEP1z-pU7KfRqXjQGbB_af7YydA1Hxzb3EWDYyb5q44YEflE8RaT0wISdJJnvQlzBkVuC4CupAqU2wm0SxCjxpk';
// taken^^ from the fcm console under cloud messaging web configuration
var convertedVapidPublicKey = urlBase64ToUint8Array(vapidPublicKey);
console.log(convertedVapidPublicKey);
var retval = reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: convertedVapidPublicKey
});
return retval;
}
return sub;
})
.then(function(newSub) {
console.log('Received PushSubscription: ', JSON.stringify(newSub));
return newSub
})
.then(function(res) {
console.log('SW ready.then.then.then');
if (res.ok) {
displayConfirmNotification();
}
})
.catch(function(err) {
console.log(err);
});
I get a subscription:
Received PushSubscription: {"endpoint":"https://fcm.googleapis.com/fcm/send/eU1Mfq-f1So:APA91bEds1D8pio29fNYdMoAQfd_zXcfai7OR_JzppN-Dq5hMhv3_tI9HZHlATBuwBcaPYC6bkmwMXm6IVZ1T83o8r3l8Dyyvxlvy191MjVwc1eKsy8lD1E2sGNaKrZrb5a2qDr9vUi9","expirationTime":null,"keys":{"p256dh":"BLiNeIZqBMTskRwnV5YCtdQFAcbj_-l2fyhOBpcRSOklLy7iv0Ru0XHjJJqOauHeYWk_9rpAjM7lVyVr7_oGHyE","auth":"yXHXHjFu0EiXRXjN8KMEyA"}}
So far so good. As far as I understood the process, the browser (in this case Chrome on Android) contacts the google chrome servers and files that subscription. The used vapid key enables firebase to contact the chrome servers, which then trigger the notification through proprietary code, which varies for each browser.
Now I tried to actually use my subscription:
$ curl "https://fcm.googleapis.com/fcm/send/eU1Mfq-f1So:APA91bEds1D8pio29fNYdMoAQfd_zXcfai7OR_JzppN-Dq5hMhv3_tI9HZHlATBuwBcaPYC6bkmwMXm6IVZ1T83o8r3l8Dyyvxlvy191MjVwc1eKsy8lD1E2sGNaKrZrb5a2qDr9vUi9" --request POST --header "TTL: 60" --header "Content-Length: 0" --header "Authorization: key=[server key from fcm console under cloud messaging server key (first entry)"
the key in the authorization header does not correspond to the sender ID used to subscribe this user. Please ensure you are using the correct sender ID and server Key from the Firebase console.
I replaced the keys a couple of times. Nothing changes. Google gives me like nothing on this string.
I have node.js express, postgres, angular 8 app.
I got it working by adding the "gcm_sender_id": in the manifest.webmanifest file (or manifest.json
I also used firebase generated public and private keys
your gcm_sender_id is your project id in google cloud or firebase sender id
Related
I have several thousand OneSignal web push notification tokens I want to import to FCM. Is there a way to do this?
I see this endpoint which requires the https://fcm.googleapis.com/fcm/send/...key... endpoint that OneSignal gives me, but I don't know what to put in for auth and p256dh.
https://developers.google.com/instance-id/reference/server#create_registration_tokens_for_apns_tokens
So yes this can be done. First you will need to contact OneSignal support and get the public and private VAPID keys for your app. Each app in your dashboard will have a different set.
Next you will need to make an API call to OneSignal in order to export the users in a CSV file.
You can find the API url in the docs and use curl or use your favorite language. I used Node + Axios to make my calls. The API call will supply you with a link to download the CSV.
Here is the documentation https://documentation.onesignal.com/reference#csv-export
You want to make sure you add the "extra_fields" parameter to your request with the "web_auth" and "web_p256" fields added. The CSV will provide you with the other piece of the puzzle which is the endpoint url in their identifier column.
Once you have all this information you can now send pushes using a library such as web-push for Node
https://github.com/web-push-libs/web-push
Hope that helps!
EDIT
As Cedric stated the actual push payload is a little bit more complicated because you need to comply with the OneSignal Service worker data handling.
You can see the formatting starting at line 313 here
If you are using a library like web-push for Node to send your push payloads your payload would be formatted something like this for a standard push to a OneSignal service worker.
const uuidv1 = require('uuid/v1')
const webpush = require('web-push')
let subscription = {
endpoint: 'USER ENDPOINT URL',
keys: {
auth: 'USER AUTH KEY',
p256dh: 'USER P256 KEY'
}
}
let vapid = { private: 'VAPID PRIVATE KEY', public: 'VAPID PUBLIC KEY' }
// Format Message for OneSignal Service Worker
let notification = JSON.stringify({
custom: {
i: uuidv1(), //Generate UUID for the OneSignal Service worker to consume
u: 'CLICK URL'
},
title: 'TOP TITLE',
alert: 'MESSAGE BODY',
icon: 'ICON IMAGE URL'
})
webpush.setVapidDetails('mailto: sendError#YourEmail.com', vapid.public, vapid.private)
webpush.sendNotification(subscription, notification)
It's much more complex than Dan's answer. If your users don't subscribe to your own service worker, it won't work. OS will send its default notification when an 'unknown' error occurs, which it will send "You have new updates" as a notification to the user even though you passed different payload. You also need to pass: "custom": { "i": uuidv1() } to your payload for it to work. (don't forget to install uuid first through npm and call it). Check out this link and you'll figure out what other payload props you need to pass.
A couple of years ago I implemented push notification with service worker on a project I was working on, by registering an app on Firebase, and using the registration number as part of the manifest.json file on the server side app. In that case I requested the user to allow notifications, got the browser registration once, saved on server side, and all works fine.
I'm now trying to implement a similar solution, but using the VAPID (https://developers.google.com/web/ilt/pwa/introduction-to-push-notifications#using_vapid).
Browser registers correctly, sends the registration to the server side app, and the app is able to send push notifications.
The issue I got is that after at least 24 hours, when I try to send a push notification to an already registered subscription, I get InvalidSubscription response (410 NotRegistered).
Using VAPID, does the browser registration expire after a few hours? do I need to get new registration every certain amount of hours? If yes, how? For example, if user never revisits the site within a day or so, how am I able to keep sending them notifications? I can't find any clear reference for this issue I'm experiencing.
Here is the JS code I use within the SW to get the browser registration:
function postPushReg(sub){
var rawKey = sub.getKey ? sub.getKey('p256dh') : '';
var key = rawKey ?
btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) :
'';
var rawAuthSecret = sub.getKey ? sub.getKey('auth') : '';
var authSecret = rawAuthSecret ?
btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) :
'';
fetch('https://XXXXX', {
method: 'post',
headers: {'Content-type': 'application/json'},
body: JSON.stringify({endpoint: sub.endpoint, key: key, authSecret: authSecret}),
});
}
self.addEventListener('install', function(event){
self.registration.pushManager.getSubscription()
.then(function(sub){
if (sub) return postPushReg(sub);
return self.registration.pushManager.subscribe({userVisibleOnly: true,
applicationServerKey: urlB64ToUint8Array('XXX')})
.then(function(sub){
postPushReg(sub);
});
});
});
self.addEventListener('push', function(e){
...
});
This is the Rails/Ruby server side gem (webpush) I use to send the notification:
Webpush.payload_send(
message: "msg",
endpoint: j['endpoint'],
p256dh: j['key'],
auth: j['authSecret'],
vapid: {
subject: "mailto:XXXX",
public_key: "XXX",
private_key: "XXX",
}
)
Again, within the first few hours everything works, then I get 410 NotRegistered response.
Trying the same suggestion posted here: Web Push with VAPID: 400/401 Unauthorized Registration , it is now working fine. I get the browser registration only once, and after 2 days it is still working fine
How do I use notification actions with the Firebase Messaging SDK on the web?
There are a few common pitfalls people hit when attempting this.
Firebase Notifications - There is a feature of the Firebase Messaging SD
K's none as "Firebase Notifications". When you send a push message to a Firebase Instance-ID (IID) token, you can use a "notification" key which the SDK's will look for and if found, construct a notification for you. The benefit of this is that you have to write no code to show a notification. The downside is that it can be restrictive if you want to do anything complex or perform work on the device once the notification is received. So to use actions, you MUST NOT USE THIS. Instead call the FCM API with the IID token and a "data" payload.
Data Payload - The data payload has a restriction where it can only be key value pairs, where the value must be a string, i.e. no arrays. What this means is that you can't just send an array of actions and construct a notification with that. The way around this is to create a JSON string, send that to the FCM API and then parse and use the JSON on the device.
Time for an example.
Calling the FCM API
The format of your payload should be something like this:
{
"data": {
"some-data": "Im a string",
"some-other-data": "Im also a string",
"json-data": "{\"actions\": [{\"action\":\"yes\", \"title\":\"Yes\"},{\"action\":\"no\",\"title\":\"No\"}]}"
},
"to": "YOUR-IID-TOKEN"
}
You can send this with curl like so:
curl -X POST -H "Authorization: key=YOUR-SERVER-KEY" -H "Content-Type: application/json" -d '{
"data": {
"some-data": "Im a string",
"some-other-data": "Im also a string",
"json-data": "{\"actions\": [{\"action\":\"yes\", \"title\":\"Yes\"},{\"action\":\"no\",\"title\":\"No\"}]}"
},
"to": "YOUR-IID-TOKEN"
}' "https://fcm.googleapis.com/fcm/send"
With that you'll be able to get the data in the onBackgroundMessage callback in your service worker.
Receiving the Payload on the Device
In a service worker we could have the following code:
messaging.setBackgroundMessageHandler(function(payload) {
console.log('Message received: ', payload);
});
Which would print out the following in the console:
Notice the JSON data is still just a string, not an object.
Next up we can parse the JSON data and check its the right format to use as our notification actions.
We can change our code to the following:
messaging.setBackgroundMessageHandler(function(payload) {
console.log('Message received: ', payload);
const parsedJSON = JSON.parse(payload.data['json-data']);
console.log('Actions:', parsedJSON);
});
This will give the following log:
With this, we can finally create our notification with the following code:
messaging.setBackgroundMessageHandler(function(payload) {
console.log('Message received: ', payload);
const parsedJSON = JSON.parse(payload.data['json-data']);
console.log('Actions:', parsedJSON);
// Customize notification here
const notificationTitle = 'Actions Title';
const notificationOptions = {
body: 'Actions body.',
actions: parsedJSON.actions,
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
Now you should have a notification with actions:
Testing
As Meggin as pointed out in the comments, it's not obvious how to test it, so a few guiding principles.
The biggest pain point is that if your web server sets a cache header for you service worker file, it won't update between refreshes, one way to fix this it to open your service worker file in a new tab and refresh that page until your service worker is up to date (This is viewing the actual source code of your service worker). Then when you refresh your web page your service worker will be the latest one and you can tell it's updated by the number next to the service worker incrementing.
Alternatively, just unregister the service worker the service worker and refresh the page - this should give you the latest service worker.
To test your notification, you'll need to click a tab that is for a different web page before sending a push message.
The reason for this is that if the user is currently on one of your pages, the push message is sent to the pages onMessage() callback instead of the onBackgroundMessage() callback.
Following Matt's advice, I was able to get a proper notification with content from my firebase function passed into my service worker (including actions), but I had to pass all of my data through the one json object, otherwise it wouldn't work for me.
Here's what my firebase functions code looks like:
function sendPayload(tokenArray) {
const payload = {
"data": {
"jsondata": "{\"body\":\"Meggin needs help\", \"title\":\"Can you help her make the code work?\",\"actions\": [{\"action\":\"yes\", \"title\":\"Yes\"},{\"action\":\"no\",\"title\":\"No\"}]}"
}
};
admin.messaging().sendToDevice(tokenArray, payload)
.then(function(response) {
// See the MessagingDevicesResponse reference documentation for
// the contents of response.
console.log("Successfully sent message:", response);
})
.catch(function(error) {
console.log("Error sending message:", error);
});
}
And here's what my code looks like in my service worker:
messaging.setBackgroundMessageHandler(function(payload) {
console.log('Payload received: ', payload);
const parsedJSON = JSON.parse(payload.data.jsondata);
console.log("What does actions look like? " + parsedJSON.actions);
console.log("What does title look like? " + parsedJSON.title);
const notificationTitle = parsedJSON.title;
const parsedBody = parsedJSON.body;
const parsedActions = parsedJSON.actions;
// Customize notification here
const notificationOptions = {
body: parsedBody,
actions: parsedActions,
};
return self.registration.showNotification(notificationTitle, notificationOptions);
});
It's worth noting that one major hurdle that helped me get passed this is understanding how to test push notifications and service workers!
You actually can't see my notification unless the browser is closed, so obviously, you can't watch the console.
But then once you've pushed the notification, you go into the console, and change the file at the top of console to be the service worker file specifically.
And then you can see the console logs!
I realize this might seem obvious to many people, but it wasn't to me, and it's crucial to understanding how to parse the payload and get it to do what you want!
Currently developing a Cordova app and wanted to use the IBM Bluemix Push Notification service to send user based push notifications.
According to the documentation here, seems like the first step is to call MFPPush.initialize(appGuid, clientSecret), which I tried to do. But this function is not present in the plugin interface and therefore I get an 'undefined' error when running the app.
Moreover, the doc also talks about calling MFPPush.registerDevice({},success,failure,userId). However, when I look at the plugin javascript interface, it only takes 3 parameters.
Could someone please give some advice to help me sort this out?
Thanks.
I just ran the Bluemix Cordova hellopush sample which should help you out. Make sure you follow the directions in the README, and make sure to change the route and guid in your index.js (it should look something like this):
route: "http://imfpush.ng.bluemix.net",
guid: "djkslk3j2-4974-4324-8e82-421c02ce847c",
You will be able to find the route and guid in your Push Notifications service credentials.
After running it by following the directions (and ensuring that you have GCM / APNS set up correctly for whatever platform you are using), you should be greeted with this screen after clicking register:
#johan #joe Cordova app can use the IBM Bluemix Push Notification service to send user based push notifications. Please follow the below example using BMSPush to register for Push Notifications.
// initialize BMSCore SDK
BMSClient.initialize("Your Push service region");
// initialize BMSPush SDK
var appGUID = "Your Push service appGUID";
var clientSecret = "Your Push service clientSecret";
// Initialize for normal push notifications
var options = {}
BMSPush.initialize(appGUID,clientSecret,options);
// Initialize for iOS actionable push notifications and custom deviceId
var options ={"categories":{
"Category_Name1":[
{
"IdentifierName":"IdentifierName_1",
"actionName":"actionName_1",
"IconName":"IconName_1"
},
{
"IdentifierName":"IdentifierName_2",
"actionName":"actionName_2",
"IconName":"IconName_2"
}
]},
"deviceId":"mydeviceId"
};
BMSPush.initialize(appGUID, clientSecret, options);
var success = function(response) { console.log("Success: " + response); };
var failure = function(response) { console.log("Error: " + response); };
// Register device for push notification without UserId
BMSPush.registerDevice(options, success, failure);
// Register device for push notification with UserId
var options = {"userId": "Your User Id value"};
BMSPush.registerDevice(options, success, failure);
Please go through the Bluemix Cordova Plugin Push SDK doc link.
I am thinking about keeping all registration ids(push token) in DB and sending notifications to user from iPhone. I tried something like this but did not get any notification.
func sendPNMessage() {
FIRMessaging.messaging().sendMessage(
["body": "hey"],
to: TOKEN_ID,
withMessageID: "1",
timeToLive: 108)
}
What I am doing wrong or maybe it is impossible at all?
Currently it's not possible to send messages from the application itself.
You can send messages from the Firebase Web Console, or from a custom server using the server-side APIs.
What you might want to do is to contact a server (like via http call) and that server will send the message to the user.
This way ensure that the API-KEY of the server is protected.
PS: the sendMessage(..) api is called upstream feature, and can be used to send messages from your app to your server, if you server has an XMPP connection with the FCM server.
Yes you can send push notification through Firebase.Please make sure do NOT include the server-key into your client. There are ways "for not so great people" to find it and do stuff... The Proper way to achieve that is for your client to instruct your app-server to send the notification.
You have to send a HTTP-Post to the Google-API-Endpoint.
You need the following headers:
Content-Type: application/json
Authorization: key={your_server_key}
You can obtain your server key within in the Firebase-Project.
HTTP-Post-Content: Sample
{
"notification": {
"title": "Notification Title",
"text": "The Text of the notification."
},
"project_id": "<your firebase-project-id",
"to":"the specific client-device-id"
}
Google Cloud Functions make it now possible send push notifications from device-to-device without an app server.
From the Google Cloud Functions documentation:
Developers can use Cloud Functions to keep users engaged and up to
date with relevant information about an app. Consider, for example, an
app that allows users to follow one another's activities in the app.
In such an app, a function triggered by Realtime Database writes to
store new followers could create Firebase Cloud Messaging (FCM)
notifications to let the appropriate users know that they have gained
new followers.
Example:
The function triggers on writes to the Realtime Database path where followers are stored.
The function composes a message to send via FCM.
FCM sends the notification message to the user's device.
Here is a demo project for sending device-to-device push notifications with Firebase and Google Cloud Functions.
Diego's answer is very accurate but there's also cloud functions from firebase it's very convenient to send notifications in every change in the db. For example let's say you're building chat application and sending notification in every new follower change.
This function sample is very good example.
For more information about cloud functions you can check official docs.
I have an app that has a "send feedback to developer" section. I also have a User collection in my firestore database. When a user logs into the app, I have that Users data update their FCM token with the following code in my SceneDelegate.swift:
import Firebase
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
authListener = Auth.auth().addStateDidChangeListener({ (auth, user) in
Auth.auth().removeStateDidChangeListener(self.authListener!)
if user != nil {
DispatchQueue.main.async {
let docRef = Firestore.firestore().collection("User").document((user?.email)!)
docRef.getDocument { (snapshot, error) in
guard let snapshot = snapshot else {return}
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM registration token: \(error)")
} else if let token = token {
docRef.updateData(["FCMtoken":token])
print("FCM registration token: \(token)")
}
}
}
}
}
})
guard let _ = (scene as? UIWindowScene) else { return }
}
then in my feedback view controller i have this code to send my specific device (but you can look up/fetch which specific device you want in your database where the FCMtoken is stored where i have INSERT-DEVICE-TOKEN-HERE). The url to send to is "https://fcm.googleapis.com/fcm/send" and you can find YOUR-APP-FCM-KEY by going to your project settings in firebase, going to cloud messaging tab and its the server key.
func sendMePushNotification() {
let token = "INSERT-DEVICE-TOKEN-HERE"
if let url = URL(string: "https://fcm.googleapis.com/fcm/send") {
var request = URLRequest(url: url)
request.allHTTPHeaderFields = ["Content-Type":"application/json", "Authorization":"key=YOUR-APP-FCM-KEY"]
request.httpMethod = "POST"
request.httpBody = "{\"to\":\"\(token)\",\"notification\":{\"title\":\"Feedback Sent!\",\"body\":\"\(self.feedbackBox.text!)\",\"sound\":\"default\",\"badge\":\"1\"},\"data\": {\"customDataKey\": \"customDataValue\"}}".data(using: .utf8)
URLSession.shared.dataTask(with: request) { (data, urlresponse, error) in
if error != nil {
print("error")
} else {
print("Successfully sent!.....")
}
}.resume()
}
}
Use onesignal,you can send device to notifications or device to segments ,it can work with firebase in this way
Use onesignal functions to create a specific id,save it in a firebase database ,then when the id can be put in another function that is used to send a notification
Notes: 1-i am using it in my apps with firebase works perfectly
2-i can submit that code,just someone comments so i can find this answer