How to register an expo push notification device - push-notification

I am trying out the Expo push notification service. I ran the code in the overview by clicking on "Try this example on snack", then pointed my iPad to the QR code, which loaded the example in Expo Go. When I tap "Press to Send Notification" indeed I see a notification, all good.
Using the same token printed on the iPad app ("Your expo push token: ExponentPushToken[token-goes-here]") I tried calling the Expo API by running a curl command on my PC in order to trigger the push notification remotely with HTTPS POST, while keeping the app in the foreground:
curl -H "Content-Type: application/json" -X POST "https://exp.host/--/api/v2/push/send" -d '{
"to": "ExponentPushToken[token-goes-here]",
"title":"hallo",
"body": "world"
}'
But I get:
{"data":{"status":"error","message":"\"ExponentPushToken[token-goes-here]\" is not a registered push notification recipient","details":{"error":"DeviceNotRegistered"}}}
Looking at the code for sendPushNotification(expoPushToken) I see it uses the same API with fetch:
// Can use this function below, OR use Expo's Push Notification Tool-> https://expo.io/notifications
async function sendPushNotification(expoPushToken) {
const message = {
to: expoPushToken,
sound: 'default',
title: 'Original Title',
body: 'And here is the body!',
data: { someData: 'goes here' },
};
await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
});
}
So why does it work on the iPad, but not on the PC using the exact same token? By the way, I double-checked the token and tried the web based push notifications tool and got a "This is not a valid Expo push token" error. Am I missing a step? I haven't done any actual build yet but thought it would just work.

The method shown in the question worked afterwards. The same curl command later returned "status":"ok" and I got the notification on the mobile device. This was likely a temporary issue on the Expo side.
If this happens again, and the API has not changed, the suggestion would be to try again later or contact Expo.

Related

NVM request working in Postman but not in Python

I am trying to execute a request in the New Voice Media API (request documentation) from python. When I execute this request in Postman, everything works perfectly, but from my script I get this error :
{'status': 400, 'title': 'One or more validation errors occurred', 'detail': 'The ScheduleId property is required.'}
Here is my Postman setup :
Here is my python request :
headers = {
'Accept': 'application/vnd.newvoicemedia.v4+json',
'Authorization': 'Bearer '+acces_token_users_write,
}
url = 'https://emea.api.newvoicemedia.com/useradmin/users/'+user_id+'/schedules'
body = {'agentSchedules': [{'scheduleId': schedule_id }]}
response = requests.put(url, headers=headers, data=body)
print(response.json())
Every other NVM API request seems to work perfectly from python. I've also tried writing the key 'scheduleId' with different caps combinations.
Do you have any idea what could cause this difference Postman / Python ?

FCM Firebase Push Messaging sender ID missmatch

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

Push Notifications in Perl

I am implementing push notifications for a site that has a Perl back end. Firebase is the push notification service i am using. I have spent a fair bit of time with this and looked at a number of guides and some useful resources on SO. I have come up with a working implementation with just one issue. The problem is when send out a push notification it seems to arrive on the client/browser as an empty message. That is no data containing the 'title' and 'body' is retrievable on the client/browser side when the push notification arrives.
I have tried using both firebases older and newer api and either way it ends up with the same outcome of empty push notifications arriving on the client/browser. I have tested this on chrome,firefox and android and the same thing happens.
Here is the perl code that sends the push notification. I have excluded generating the bearer token to limit how much code there is to read.
#SEND PUSH NOTIFICATION
my $push_subscriber = <get subscriber details from db>
my $end_point_host = $push_subscriber->{endpoint};
my $end_point = "https://$end_point_host/v1/projects/<my project
id>/messages:send";
my $request = HTTP::Request->new('POST',$end_point);
$request->header('Authorization'=>"Bearer $bearer_token");
$request->header('Content-Type' => 'application/json');
$request->content(JSON::encode_json ({
message => {
token => $push_subscriber->{subscription_id},
notification => {
title => 'test',
body => 'test content'
},
webpush => {
headers => {
Urgency => 'high'
},
notification => {
body => 'test content',
requireInteraction => 'true'
}
}
}}));
#send the request
$ua->request($request));
Here is the client/browser side javascript that is called when a push notification arrives. This is inside service-worker.js
self.addEventListener('push', function(e) {
var body;
if (e.data) {//THE PROBLEM IS HERE. No 'data' object exists
body = e.data.text();
} else {
body = "Empty Message";
}
var options = {
body: body
};
e.waitUntil(
self.registration.showNotification('My Notification', options)
);
});
The point where the problem presents itself is pointed out in the above javascript. Any help/feedback would be much appreciated. Thanks.
I ended up getting this working by just re-writing my client side subscription code. In my case the bell icon subscription on/off button along with all the js code to make it work.
Basically i went from using googles solution to a firebase specific solution with this guide.
https://firebase.google.com/docs/cloud-messaging/js/receive
You only need to store the 'token' on your server and the endpoint is always - https://fcm.googleapis.com/v1/projects/YOUR PROJECT ID/messages:send
The firebase guide contains a sample file where you can subscribe/unsubscribe for push notifications.
https://github.com/firebase/quickstart-js/blob/4be200b1c55616535159365b74bfd1fc128c1ebf/messaging/index.html
Once i had this working i could then cut it down and re-write it into just a simple notification button.
For some reason the provided firebase-messaging-sw.js from the guide didn't work for me but using service-worker.js shown in my OP did and so i can now receive push notifications along with their title, body and other data.
This here is how i generate the bearer token used in my OP sample perl code to send out a push notification.
Google API OAuth2 : “invalid_grant” error when requesting token for Service Account with Perl
That should hopefully cover everything you need to know if you are wanting to do push notifications on a site with a Perl back end. Hopefully this helps someone else wanting to do the same thing.

Can I import OneSignal tokens to FCM?

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.

How can I use notification actions with Firebase Messaging Web SDK

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!

Resources