How do you retrieve notification/data from a request? - javascript

I'm trying to send a basic notification using Firebase Cloud Messaging, but I cannot for the life of me figure out how to include any actual content in the notification.
I'm starting with essentially a stripped-down version of what can be found here. I have three files, a manifest.json, which looks like this:
{ "gcm_sender_id": "my sender id" }
an index.html, which looks like this:
<html>
<head><link rel="manifest" href="manifest.json"></head>
<body>
<div id="endpoint-show">There doesn't seem to be an endpoint right now</div>
<script src="https://www.gstatic.com/firebasejs/3.5.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/3.5.0/firebase-messaging.js"></script>
<script>
var config = {apiKey: "my key", authDomain: "my domain", messagingSenderId: "my id"};
firebase.initializeApp(config);
const messaging = firebase.messaging();
messaging.requestPermission()
.then(function() { console.log('Notification permission granted.'); })
.catch(function(err) { console.log('Unable to get permission to notify. ', err); });
navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true})
.then(function(subscription) {
document.getElementById("endpoint-show").innerHTML = "<p>" + subscription.endpoint.split('/\').slice(-1)[0] + "</p>";
})
});
navigator.serviceWorker.register('./service-worker.js')
</script>
</body>
<html>
and a service-worker.js, which looks like:
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
event.waitUntil(self.registration.showNotification("title", { body: "body" }));
});
This seems to be about the minimum amount of code required to register a subscription for push notifications and establish a service worker to handle them.
I then send notifications using a curl command like the one shown here. Ie, POST to https://fcm.googleapis.com/fcm/send with those certain headers set and a body that contains a "too" field which is equal to the one that my idex.html shows. This works, insofar that I get a notification on my computer with the title "title" and the body "body".
But here is where I am stuck: It seems like the only data I can send through this request is the fact that a notification happens, and not any other (actual) data. The first example I linked just hard-codes a notification, as does my own code, but I would like to be able to send a request with arbitrary data. The documentation here seems to indicate that I should be able to set either the data or notification field of the request, and get a notification that has that data, but that doesn't seem to work. I have set both fields in the request I'm making, so the whole thing looks something like this (which I am sending using postman):
POST /fcm/send HTTP/1.1
Host: fcm.googleapis.com
Content-Type: application/json
Authorization: key=my key
Cache-Control: no-cache
Postman-Token: some token
{
"notification": {
"body": "notification body",
"title": "notification title"
},
"data": {
"body": "data body",
"title": "data title"
},
"to" : "my endpoint"
}
But I can't figure out how to actually retrieve any of that data. The event that my service worker captures does not contain any of this data. None of the documentation I have looked at seems to describe how to actually get this data through the web, only on Android or iOS, but obviously it is possible since many website implement dynamic notifications.
I suspect that I have some fundamental misunderstanding of how all of this works, which would not be surprising since I have very little experience with any kind of web development. If what I'm trying to do is impossible, or much be done a completely different way, let me know.
And just to reiterate, here is exactly what I am trying to do:
Send a request (whether that is to a webserver that I write or to firebase).
Have a notifcation pop up on my Chrome with information from that request.

You seem to be mixing the new firebase messaging lib with the old style service worker code.
After you get the permission, you need to call the getToken API.
// service.js
// after request permission is successful
// Get Instance ID token. Initially this makes a network call, once retrieved
// subsequent calls to getToken will return from cache.
messaging.getToken()
.then(function(currentToken) {
if (currentToken) {
sendTokenToServer(currentToken);
updateUIForPushEnabled(currentToken);
} else {
// Show permission request.
console.log('No Instance ID token available. Request permission to generate one.');
// Show permission UI.
updateUIForPushPermissionRequired();
setTokenSentToServer(false);
}
})
.catch(function(err) {
console.log('An error occurred while retrieving token. ', err);
showToken('Error retrieving Instance ID token. ', err);
setTokenSentToServer(false);
});
Its debatable, but you also need to create a service work with filename as firebase-messaging-sw.js. Reference can be found here.
And in this service worker you need to put something like this:
// firebase-messaging-sw.js
'use strict';
console.log('Starting service worker');
if( 'function' === typeof importScripts) {
importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-messaging.js');
importScripts('core/decoder.js');
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': 'YOUR-SENDER-ID'
});
// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function(payload) {
var shinyData = payload || {};
console.log('[firebase-messaging-sw.js] Received background message ', payload, shinyData);
return self.registration.showNotification(shinyData.title, {
body: shinyData.body,
icon: shinyData.icon,
data: {url: shinyData.tag}
})
});
}
You may find this gist I created helpful.

Related

Firebase Cloud Messaging AJAX POST in JavaScript

I have the following code for TESTING PURPOSES:
$.ajax({
url: 'https://fcm.googleapis.com/v1/projects/[PROJECT]/messages:send',
type: 'POST',
headers:{
"Authorization":"Bearer "+[Access Token from FireBase Auth]
},
contentType:"application/json",
data: {
"message":{
"token": [TOKEN from messaging.getToken],
"notification" : {
"body" : "This is an FCM notification message!",
"title" : "FCM Message",
}
}
},
success: function () { },
error: function () { },
});
This always results in the following response with a 401()...
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
}
What am I doing wrong?
In the docs we linked in comments: https://firebase.google.com/docs/cloud-messaging/js/first-message
Under Retrieve Registration Token, you see this code:
messaging.getToken().then(function(currentToken) {
if (currentToken) {
sendTokenToServer(currentToken);
updateUIForPushEnabled(currentToken);
} else {
// Show permission request.
console.log('No Instance ID token available. Request permission to generate one.');
// Show permission UI.
updateUIForPushPermissionRequired();
setTokenSentToServer(false);
}
}).catch(function(err) {
console.log('An error occurred while retrieving token. ', err);
showToken('Error retrieving Instance ID token. ', err);
setTokenSentToServer(false);
});
You'll notice the sendTokenToServer() function, that's not their function, that's supposed to be yours. You call their getToken() and in the promise you take the result and send it up, would look like this:
function sendTokenToServer(currentToken) {
$.post({
url: 'yourServer.com/some_token_receiving_endpoint',
type: 'post',
data: {token: currentToken}
});
}
Then on the server, you'd receive that, and store it, likely in a database, related to their profile information.
Then, either at that moment, or, at a later time, you can query your database for those you want to notify, grab that token, and in conjunction with your access token stored securely on your server, you can then send the notification from there.
Typically, NodeJS, PHP, Python, or Ruby. As events happen, or on a schedule, your server can send notifications like this:
<?php
// Get some http client service for your language
$client = new GuzzleHttp\Client();
// Get your user or users (with their tokens that you've stored)
$user = Db.someQueryReturningUser();
// Your message
$jsonData = '{
"message":{
"token": [TOKEN from messaging.getToken],
"notification" : {
"body" : "This is an FCM notification message!",
"title" : "FCM Message",
}
}
}';
// Send Mesage
$client->post('https://fcm.googleapis.com/v1/projects/[PROJECT]/messages:send',
[
'headers' => [
'Authorization' => 'Bearer ' . [Access Token from FireBase Auth]
],
'json' => $jsonData
]);
In a very broad sense, what you're doing wrong is trying to call the FCM APIs from a web browser client. FCM messages are intended to be sent from a backend server under your total control. The authorization token that you need to send is going to effectively have admin privileges to send messages to any and all of your users, and you don't want to give that up to clients, as it's massive security issue.
From the documentation:
Requests sent to FCM from your app server or trusted environment must
be authorized. The FCM HTTP v1 API uses a short-lived OAuth 2.0 access
token generated for a service account associated with your Firebase
project. The legacy protocols use long-lived API keys retrieved from
the Firebase console. In both cases, you must add the required
credential to each message request sent to FCM.
In other words, you're not supposed to give clients access to send messages with your privileged service account credentials. The rest of that page of documentation describes how to actually do the world of authorizing the send request.

Node.js AWS SNS subscription , confirmation

I'm trying to connect an SNS topic to my Meteor ( node ) JS application, but it seems like i'm not getting the right response when i try to subscribe and stuff.
I have few questions regarding this matter. but first , this is my topic and code :
I created a topic in SNS, and got it's ARN.
I Set my AMI Policy to be
able to use SNS. Got my access key and secret key
Wrote this on my LOCALHOST server :
AWS.config.update({
accessKeyId: 'something',
secretAccessKey: 'someotherthing+a4f23',
region: 'eu-west-1'
});
let sns = new AWS.SNS();
var params = {
Protocol: 'http', /* required */
TopicArn: 'arn:aws:sns:eu-west-1:888472248156:ps-tracking', /* required */
Endpoint: 'http://URL:4000'
};
sns.subscribe(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
I'm still running my node app on LOCALHOST at this point
then i switch to my AWS SNS panel and create a subscription, choose HTTP as protocol and type in the ENDPOINT URL.
First Question
Is there any possibility that i can get this to work on localhost without moving it to the live server, if so how ?
so when i run the appliction i get this message in the console :
{ ResponseMetadata: { RequestId: '64a88abb-7997-5f47-bfcc-d8cfc5281ca3' },
SubscriptionArn: 'pending confirmation' }
i switch to my AWS panel i see
even when i move all this to the live server, with the same data, i'm getting this pending message. and i don't know what i should do !
I made an function for typescript to confirm the subscription. Just pass in your header and body from the express route.
Also the content type of the sns request is something like text/plain and the bodyParser used in most express apps won't process the body so the body.Token will be empty. To solve this use a middleware before you body parser to augment the request coming in.
Process subscription confirmation
import AWS from "aws-sdk";
const snsInstance = new AWS.SNS();
function isConfirmSubscription(headers: {
'x-amz-sns-message-type': string
}) {
return headers['x-amz-sns-message-type'] === 'SubscriptionConfirmation'
}
function confirmSubscription(
headers: {
'x-amz-sns-topic-arn': string,
'x-amz-sns-message-type': string
},
body: {Token: string}
): Promise<string>{
return new Promise(((resolve, reject) =>{
if(!isConfirmSubscription(headers)){
return resolve('No SubscriptionConfirmation in sns headers')
}
snsInstance.confirmSubscription({
TopicArn: headers['x-amz-sns-topic-arn'],
Token : body.Token
}, (err, res)=>{
console.log(err);
if(err){
return reject(err)
}
return resolve(res.SubscriptionArn);
});
}))
}
SubscriptionConfirmation content type modifier
app.use(function(req, res, next) {
if (req.headers['x-amz-sns-message-type']) {
req.headers['content-type'] = 'application/json;charset=UTF-8';
}
next();
});
You need to confirm the subscription.
After you subscribe your endpoint, Amazon SNS will send a subscription confirmation message to the endpoint. You should already have code that performs the actions described in Step 1 deployed to your endpoint. Specifically, the code at the endpoint must retrieve the SubscribeURL value from the subscription confirmation message and either visit the location specified by SubscribeURL itself or make it available to you so that you can manually visit the SubscribeURL, for example, using a web browser. Amazon SNS will not send messages to the endpoint until the subscription has been confirmed. When you visit the SubscribeURL, the response will contain an XML document containing an element SubscriptionArn that specifies the ARN for the subscription. You can also use the Amazon SNS console to verify that the subscription is confirmed: The Subscription ID will display the ARN for the subscription instead of the PendingConfirmation value that you saw when you first added the subscription.
Sending Amazon SNS Messages to HTTP/HTTPS Endpoints
If you need to confirm the subscription to an SNS Topic, you can use the AWS-NODE-SDK using the Request sent from SNS:
{
"Type" : "SubscriptionConfirmation",
"MessageId" : "165545c9-2a5c-472c-8df2-7ff2be2b3b1b",
"Token" : "2336412f37fb687f5d51e6e241d09c805a5a57b30d712f794cc5f6a988666d92768dd60a747ba6f3beb71854e285d6ad02428b09ceece29417f1f02d609c582afbacc99c583a916b9981dd2728f4ae6fdb82efd087cc3b7849e05798d2d2785c03b0879594eeac82c01f235d0e717736",
"TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
"Message" : "You have chosen to subscribe to the topic arn:aws:sns:us-west-2:123456789012:MyTopic.\nTo confirm the subscription, visit the SubscribeURL included in this message.",
"SubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:123456789012:MyTopic&Token=2336412f37fb687f5d51e6e241d09c805a5a57b30d712f794cc5f6a988666d92768dd60a747ba6f3beb71854e285d6ad02428b09ceece29417f1f02d609c582afbacc99c583a916b9981dd2728f4ae6fdb82efd087cc3b7849e05798d2d2785c03b0879594eeac82c01f235d0e717736",
"Timestamp" : "2012-04-26T20:45:04.751Z",
"SignatureVersion" : "1",
"Signature" : "EXAMPLEpH+DcEwjAPg8O9mY8dReBSwksfg2S7WKQcikcNKWLQjwu6A4VbeS0QHVCkhRS7fUQvi2egU3N858fiTDN6bkkOxYDVrY0Ad8L10Hs3zH81mtnPk5uvvolIC1CXGu43obcgFxeL3khZl8IKvO61GWB6jI9b5+gLPoBc1Q=",
"SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem"
}
To make the confirmation you will need the TopicArn from the header & Token found on the body:
AWS.config.update({
accessKeyId : 'ACCESS_KEY',
secretAccessKey: 'ACCESS_SECRET_KEY',
region : 'region'
});
// Create S3 Object from AWS SDK
const sns = new AWS.SNS();
// Request options
let options = {
TopicArn: req.headers['x-amz-sns-topic-arn'],
Token : req.body.Token
}
// Confirm Token Subscription
sns.confirmSubscription(options, callback);
Note: AWS will send the SubscriptionConfirmation & Notifications to the same endpoint, you can differentiate those by using the header 'x-amz-sns-message-type'

How to create Microsoft Account Sign-in to my website, similar to Google?

I'm working on a web project (HTML, CSS, JavaScript, with back-end in PHP). I've successfully got a Google Sign-in working, using their simple API, but can't get the Microsoft equivalent to function. The official online solutions to this seem to rely on .NET or PHP Composer. I'll try composer if that's the only way but a pure JS/PHP method would be easiest.
I've tried to use the following:
https://github.com/microsoftgraph/msgraph-sdk-javascript
https://github.com/AzureAD/microsoft-authentication-library-for-js
The code below is the closest I've come to a working solution. I can get some kind of user ID (which appears to be unique and constant for each user). This might be enough to set up the login system I want, but it would be ideal if I could also fetch their name and profile picture.
<script class="pre">
var userAgentApplication = new Msal.UserAgentApplication("MY CLIENT ID", null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
})
userAgentApplication.loginPopup(["user.read"]).then(function (token) {
var user = userAgentApplication.getUser(); //this is good
//user.userIdentifier seems to be a unique ID
//I will store this and use it for future verification
console.log(user);
//START
// get an access token
userAgentApplication.acquireTokenSilent(["user.read"]).then(function (token) {
console.log("ATS promise resolved");
}, function (error) {
console.log(error);
// interaction required
if (error.indexOf("interaction_required") != -1) {
userAgentApplication.acquireTokenPopup(["user.read"]).then(function (token) {
// success
console.log("s2");
}, function (error) {
console.log("e2");
// error
});
}
});
//END
// signin successful
}, function (error) {
console.log(error);
// handle error
});
</script>
(this code won't run as I've pasted it because it relies on the MSAL script from the second github link, and needs an application client ID)
After getting the access token with scope user.read , you could call microsoft graph api to get sign-in user's profile information such as displayName , businessPhones :
https://graph.microsoft.com/v1.0/me
Content-Type:application/json
Authorization:Bearer {token}
To get user's profile photo :
GET https://graph.microsoft.com/v1.0/me/photo/$value
In addition , if you are using Microsoft Graph JavaScript Client Library in first link , you could get user's displayName and profile photo by :
client
.api('/me')
.select("displayName")
.get((err, res) => {
if (err) {
console.log(err);
return;
}
console.log(res);
});
// Example of downloading the user's profile photo and displaying it in an img tag
client
.api('/me/photo/$value')
.responseType('blob')
.get((err, res, rawResponse) => {
if (err) throw err;
const url = window.URL;
const blobUrl = url.createObjectURL(rawResponse.xhr.response);
document.getElementById("profileImg").setAttribute("src", blobUrl);
});
Please refer to code sample here .

Service worker data with curl

In the Chrome Push notification API, I would like to set a customisable notification.
I am talking about service-worker.
Here is the code of the example service worker.
self.addEventListener('push', function(event) {
var data = {};
if (event.data) {
data = event.data.json();
}
var title = data.message || "Something Has Happened";
var body = 'We have received a push message !';
var icon = '/images/icon-192x192.png';
var tag = 'simple-push-demo-notification-tag';
console.log(event);
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
});
How can I catch the data from a curl message with something like this :
curl --header "Authorization: key=---personnal_key---" --header Content-Type:"application/json" https://android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"---ID---\"],\"title\":\"ok\"}"
I did read the example, but I can't see one talking about the combination of CURL and service worker together.
Is that even possible ?
In the current iteration of the push notification api you may not send data (see: http://updates.html5rocks.com/2015/03/push-notificatons-on-the-open-web). For now the only way to send specific data is to store it in some database on the server and then have the service-worker fetch it from the server. However, as you would expect this can lead to many security issues. One option is to use the subscription for verification. You can get the subscription id from the service worker like:
registration.pushManager.getSubscription().then(function(ps) {
console.log(ps.subscriptionId)
})
Then, assuming how you set up your server, you can make a fetch from the service worker to your server. It might look something like this
fetch("/"+subscriptionId+"/getLatest").then(function(res) {
res.json().then(function(data) {
self.registration.showNotification(data.title, {
body: data.body,
icon: data.icon,
tag: data.tag
})
})
})
However, all of this requires you to set up an external server. You cannot send data through the cURL request.

How to use Azure mobile service REST api?

The new custom API script allows a lot of customization through any type of connection.
I found that this website Custom API in Azure Mobile Services – Client SDKs describes the custom API.
var client = new WindowsAzure.MobileServiceClient('http://myservice.azure-mobile.net/', 'mykey');
client.invokeApi('query', {
method: 'POST'
});
But I couldn't run this code. It is supposed to show a message "Hello post World!".
I put the code inside tags in an HTML file and ran it but nothing happened.
Any help?
The call you have is making a call to your service, but it's ignoring its response. Assuming you have a custom API called 'query' (since it's what you're passing to invokeApi) with the following body:
exports.post = function(request, response) {
response.send(200, { message: 'Hello world' });
};
Your client code is calling it and (if everything goes fine) receiving the response, but it's not doing anything with it. There are a couple of ways to find out whether the call is being made. For example, you can add a log entry in the API and check the logs in your service:
exports.post = function(request, response) {
console.log('The API was called');
response.send(200, { message: 'Hello world' });
};
Or you can use a networking tool (the browser developer tools or Fiddler, for example) to see if the request is being made. Or you can actually do something with the result in the client side:
var client = new WindowsAzure.MobileServiceClient('http://myservice.azure-mobile.net/', 'mykey');
client.invokeApi('query', {
method: 'POST'
}).done(
function(result) { alert('Result: ' + JSON.stringify(result)); },
function(error) { alert('Error: ' + error); }
);
One thing which you need to look at if you're calling the API from a browser is whether the domain from where the page is being loaded is in the 'allow requests from host names' list, under the 'configure' tab, 'cross-origin resource sharing (cors)' section. If it's not, then you may get an error instead of the response you want.

Categories