I'm working on the Google Chrome Push Notification and I'm trying to send the payload to the google chrome worker but, I have no idea how I receive this payload.
I have an API to create and save the notifications in my database and I need send the values through the https://android.googleapis.com/gcm/send and receive on the worker.js
This is my worker.js
self.addEventListener('push', function(event) {
var title = 'Yay a message.';
var body = 'We have received a push message.';
var icon = '/images/icon-192x192.png';
var tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
});
And this is how I'm calling the GCM
curl --header "Authorization: key=AIzaSyDQjYDxeS9MM0LcJm3oR6B7MU7Ad2x2Vqc" --header "Content-Type: application/json" https://android.googleapis.com/gcm/send -d "{ \"data\":{\"foo\":\"bar\"}, \"registration_ids\":[\"APA91bGqJpCmyCnSHLjY6STaBQEumz3eFY9r-2CHTtbsUMzBttq0crU3nEXzzU9TxNpsYeFmjA27urSaszKtA0WWC3yez1hhneLjbwJqlRdc_Yj1EiqLHluVwHB6V4FNdXdKb_gc_-7rbkYkypI3MtHpEaJbWsj6M5Pgs4nKqQ2R-WNho82mnRU\"]}"
I tried to get event.data but, this is undefined.
Does anyone have any idea or sugestion?
Unfortunately it seems like an intended behavior:
A downside to the current implementation of the Push API in Chrome is
that you can’t send a payload with a push message. Nope, nothing. The
reason for this is that in a future implementation, payload will have
to be encrypted on your server before it’s sent to a push messaging
endpoint. This way the endpoint, whatever push provider it is, will
not be able to easily view the content of the push payload. This also
protects against other vulnerabilities like poor validation of HTTPS
certificates and man-in-the-middle attacks between your server and the
push provider. However, this encryption isn’t supported yet, so in the
meantime you’ll need to perform a fetch request to get information
needed to populate a notification.
As stated above, the workaround is to contact back your backend after receiving the push and fetch the stored data on the 3rd party server.
#gauchofunky's answer is correct. With some guidance from the folks on the Chromium dev slack channel and #gauchofunky I was able to piece something together. Here's how to work around the current limitations; hopefully my answer becomes obsolete soon!
First figure out how you're going to persist notifications on your backend. I'm using Node/Express and MongoDB with Mongoose and my schema looks like this:
var NotificationSchema = new Schema({
_user: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
subscriptionId: String,
title: String,
body: String,
sent: { type: Boolean, default: false }
});
Be sure to add an icon if you'd like to alter the icon. I use the same icon every time so mine's hardcoded in the service worker.
Figuring out the correct REST web service took some thought. GET seemed like an easy choice but the call to get a notification causes side effects, so GET is out. I ended up going with a POST to /api/notifications with a body of {subscriptionId: <SUBSCRIPTION_ID>}. Within the method we basically perform a dequeue:
var subscriptionId = req.body.subscriptionId;
Notification
.findOne({_user: req.user, subscriptionId: subscriptionId, sent: false})
.exec(function(err, notification) {
if(err) { return handleError(res, err); }
notification.sent = true;
notification.save(function(err) {
if(err) { return handleError(res, err); }
return res.status(201).json(notification);
});
});
In the service worker we need to for sure get the subscription before we make the fetch.
self.addEventListener('push', function(event) {
event.waitUntil(
self.registration.pushManager.getSubscription().then(function(subscription) {
fetch('/api/notifications/', {
method: 'post',
headers: {
'Authorization': 'Bearer ' + self.token,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
})
.then(function(response) { return response.json(); })
.then(function(data) {
self.registration.showNotification(data.title, {
body: data.body,
icon: 'favicon-196x196.png'
});
})
.catch(function(err) {
console.log('err');
console.log(err);
});
})
);
});
It's also worth noting that the subscription object changed from Chrome 43 to Chrome 45. In Chrome 45 the subscriptionId property was removed, just something to look out for - this code was written to work with Chrome 43.
I wanted to make authenticated calls to my backend so I needed to figure out how to get the JWT from my Angular application to my service worker. I ended up using postMessage. Here's what I do after registering the service worker:
navigator.serviceWorker.register('/service-worker.js', {scope:'./'}).then(function(reg) {
var messenger = reg.installing || navigator.serviceWorker.controller;
messenger.postMessage({token: $localStorage.token});
}).catch(function(err) {
console.log('err');
console.log(err);
});
In the service worker listen for the message:
self.onmessage.addEventListener('message', function(event) {
self.token = event.data.token;
});
Strangely enough, that listener works in Chrome 43 but not Chrome 45. Chrome 45 works with a handler like this:
self.addEventListener('message', function(event) {
self.token = event.data.token;
});
Right now push notifications take quite a bit of work to get something useful going - I'm really looking forward to payloads!
Actually, payload should be implemented in Chrome 50 (release date - April 19, 2016). In Chrome 50 (and in the current version of Firefox on desktop) you can send some arbitrary data along with the push so that the client can avoid making the extra request. All payload data must be encrypted.
Here is the the encryption details from developer : https://developers.google.com/web/updates/2016/03/web-push-encryption?hl=en
I just ran into this problem. Newer versions of firefox and chrome( version 50+) support payload transferring. The dev docs here details the implementation on how this works. An important thing to note is that google GCM or possibly client/chome (I dont know which one) will actually ignore the payload entirely if it is not encrypted.
This website has both client/server implementations of how to do the push and retrieval through service workers. The push library that examples use is merely a wrapper around a normal REST call
service worker example implementation:
self.addEventListener('push', function(event) {
var payload = event.data ? event.data.text() : 'no payload';
event.waitUntil(
self.registration.showNotification('ServiceWorker Cookbook', {
body: payload,
})
);
});
Server example implementation:
var webPush = require('web-push');
webPush.setGCMAPIKey(process.env.GCM_API_KEY);
module.exports = function(app, route) {
app.post(route + 'register', function(req, res) {
res.sendStatus(201);
});
app.post(route + 'sendNotification', function(req, res) {
setTimeout(function() {
webPush.sendNotification(req.body.endpoint, {
TTL: req.body.ttl,
payload: req.body.payload,
userPublicKey: req.body.key,
userAuth: req.body.authSecret,
}).then(function() {
res.sendStatus(201);
});
}, req.body.delay * 1000);
});
};
Client side javascript implementation example of printing out the the required fields.
navigator.serviceWorker.register('serviceWorker.js')
.then(function(registration) {
return registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription;
}
return registration.pushManager.subscribe({
userVisibleOnly: true
});
});
}).then(function(subscription) {
var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
endpoint = subscription.endpoint;
console.log("Endpoint: " + endpoint);
console.log("Key: " + key);
console.log("AuthSecret: " + authSecret);
});
To retrieve that data, you need to parse "event.data.text()" to a JSON object. I'm guessing something was updated since you tried to get this to work, but it works now. Unlucky!
However, since I made it to this post when searching for a solution myself, others would probably like a working answer. Here it is:
// Push message event handler
self.addEventListener('push', function(event) {
// If true, the event holds data
if(event.data){
// Need to parse to JSON format
// - Consider event.data.text() the "stringify()"
// version of the data
var payload = JSON.parse(event.data.text());
// For those of you who love logging
console.log(payload);
var title = payload.data.title;
var body = payload.data.body;
var icon = './assets/icons/icon.ico'
var tag = 'notification-tag';
// Wait until payload is fetched
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag,
data: {} // Keeping this here in case I need it later
})
);
} else {
console.log("Event does not have data...");
}
}); // End push listener
// Notification Click event
self.addEventListener('notificationclick', function(event) {
console.log("Notification Clicked");
}); // End click listener
Personally, I will be creating a "generic" notification in case my data is funky, and will also be using try/catch. I suggest doing the same.
Follow these steps to achieve this:
In the browser:
You need to get the subscription object and save it, so your server has access to it: Read more about it
navigator.serviceWorker.ready.then(serviceWorkerRegistration => {
serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true})
.then(subscription => {
//save subscription.toJSON() object to your server
})});
In the server:
install web-push npm package
And send a web push like this:
const webpush = require('web-push');
setImmediate(async () => {
const params = {
payload: {title: 'Hey', body: 'Hello World'}
};
//this is the subscription object you should get in the browser. This is a demo of how it should look like
const subscription = {"endpoint":"https://android.googleapis.com/gcm/send/deC24xZL8z4:APA91bE9ZWs2KvLdo71NGYvBHGX6ZO4FFIQCppMsZhiTXtM1S2SlAqoOPNxzLlPye4ieL2ulzzSvPue-dGFBszDcFbSkfb_VhleiJgXRA8UwgLn5Z20_77WroZ1LofWQ22g6bpIGmg2JwYAqjeca_gzrZi3XUpcWHfw","expirationTime":null,"keys":{"p256dh":"BG55fZ3zZq7Cd20vVouPXeVic9-3pa7RhcR5g3kRb13MyJyghTY86IO_IToVKdBmk_2kA9znmbqvd0-o8U1FfA3M","auth":"1gNTE1wddcuF3FUPryGTZOA"}};
if (subscription.keys) {
params.userPublicKey = subscription.keys.p256dh;
params.userAuth = subscription.keys.auth;
}
// this key you should take from firebase console for example
// settings -> cloud messaging -> Server key
webpush.setGCMAPIKey('AAAASwYmslc:APfA91bGy3tdKvuq90eOvz4AoUm6uPtbqZktZ9dAnElrlH4gglUiuvereTJJWxz8_dANEQciX9legijnJrxvlapI84bno4icD2D0cdVX3_XBOuW3aWrpoqsoxLDTdth86CjkDD4JhqRzxV7RrDXQZd_sZAOpC6f32nbA');
try {
const r = await webpush.sendNotification(subscription, JSON.stringify(params));
console.log(r);
}
catch (e) {
console.error(e);
}
});
Related
I have running an app in heroku, separate in back and front
back: node + express + mysql
front: vue
the app works fine, but I have an error random: sometimes i have duplicates records inserted from frontend. (I guess the error comes from the front)
from the frontend I use fetch to add the records
const requestOptions = {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
data: data,
...: ...,
}),
};
const response = await fetch(`url_backend_heroku/api/add`, requestOptions);
the records are inserted correctly , but sometimes are inserted duplicated with correct next id
Could it be that fetch is sending 2 requests in some cases?
before deploying in heroku on my local machine I never duplicate records
I've been going around for days and I can't find why this happens
Yeah it is possible you are sending 2 requests somewhere. Put logs in heroku BACK on the specific endpoint to see whats exactly happening.
Also while requesting from Front end check your network tab in developer tools to see if you are actually firing the request twice. Because as you said duplicate records but they have correct ID's , could only mean what you said.
Also, this might or might not be true, but heroku server sleeps on inactivity, so it is possible that might cause an issue but I am not entirely sure on that, will have to check the code and environment for that.
looking at the heroku (back) log it looks like 2 records were inserted normally
looking at the network tab in the browser, only 1 request appears: OPTIONS (204) and POST (200)
the table has an id that is the primary key nothing complicated
on the other hand I am on a Dynos Hobby plan that does not have sleep times (if the free)
put here part or my backend
database.js
const mysql = require('mysql')
const { promisify } = require('util')
const config = { database keys }
const pool = mysql.createPool(config);
pool.getConnection((err: any, connection: any) => {
if (err) {
if (err.code === 'PROTOCOL_CONNECTION_LOST') {
console.error('DATABASE CONNECTION WAS CLOSED')
}
if (err.code === 'ER_CON_COUNT_ERROR') {
console.error('DATABASE HAS TO MANY CONNECTIONS')
}
if (err.code === 'ECONNREFUSED') {
console.error('DATABASE CONNECTION WAS REFUSED')
}
}
if (connection) connection.release()
console.log('DB is Connected')
return
})
pool.query = promisify(pool.query)
export default pool
controller.js
import pool from '../database';
public async insert(req: Request, res: Response) {
let body = req.body
try {
const response = await pool.query('SELECT MAX(id) + 1 as idNew FROM table')
let data = {Id: response[0].idNew, ...body}
//before it had an auto_increment id and it also failed
const result = await pool.query('INSERT INTO table set ?', [data])
res.json({
insertId: response[0].idNew,
message: "Saved OK"
})
} catch (error) {
console.log("error", error)
return res.status(500).send("error")
}
}
can it be a fetch problem? don't try yet to use axios for example
I want users to pay a fee before a POST request from a front end form is processed. I have a Stripe webhook that works fine on the backend, but I'm not sure how to delay the front end posting of the form until after the payment confirmation is received.
In the code below, right now, createTour and createTourPay run at the same time. I would like for createTourPay to execute first, and the createTour only triggers after Stripe posts to my application from the webhook. How can I achieve this?
Controller File (webhook):
exports.webhookCheckout = (req, res, next) => {
const signature = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(`Webhook error: ${err.message}`);
}
if (
event.type === 'checkout.session.completed' &&
event.line_items.name === 'New Job Purchase'
) {
res.status(200).json({ recieved: true });
// Somehow, I want this to trigger the execution of the POST request in my front end JS file.
} else {
if (event.type === 'checkout.session.completed')
createBookingCheckout(event.data.object);
res.status(200).json({ recieved: true });
}
};
Front end JS file:
export const createTourPay = async myForm => {
try {
// 1) Get the checkout session from API response
const session = await axios(`/api/v1/tours/tour-pay`);
const complete = 1;
// console.log(session);
// 2) Create checkout form + charge the credit card
await stripe.redirectToCheckout({
sessionId: session.data.session.id
});
} catch (err) {
// console.log(err);
showAlert('error', err);
}
};
export const createTour = async myForm => {
try {
const startLocation = {
type: 'Point',
coordinates: [-10.185942, 95.774772],
address: '123 Main Street',
description: 'Candy Land'
};
const res = await axios({
method: 'POST',
headers: {
'Content-Type': `multipart/form-data; boundary=${myForm._boundary}`
},
url: '/api/v1/tours',
data: myForm
});
if (res.data.status === 'success') {
showAlert('success', 'NEW TOUR CREATED!');
window.setTimeout(() => {
location.assign('/');
}, 1500);
}
} catch (err) {
showAlert('error', err.response.data.message);
}
};
Broadly: don't do this. Instead, you in fact should create some pending/unpaid version of the "tour" (or any other product/service) in your system, then attach the unique id (eg: tour_123) to the Checkout session when you create it, either using the client_reference_id (doc) or metadata (doc):
const session = await stripe.checkout.sessions.create({
// ... other params
client_reference_id: 'tour_123',
metadata: { tour_id: 'tour_123' },
});
Then you'd use the webhook to inspect those values, and update your own database to indicate the payment has been made and that you can fulfill the order to the customer (ship product, send codes, allow access to service etc).
If you really want to proceed with a more synchronous flow, you can use separate auth and capture to sequence your customer experience and capture the funds later after authorizing and creating your tour entity.
Edit: a note about security
You should never trust client-side logic for restricted operations like creating a "paid" tour. A motivated user could, for example, simply call your /api/v1/tours create endpoint without ever going through your payment flow. Unless you validate a payment and track that state on your server you won't be able to know which of these had actually paid you.
I'm using https://github.com/Microsoft/BotFramework-WebChat/blob/master/README.md
I want the bot to receive a "join" message whenever the web-chat widget is displayed on the site.
The idea is that the human does not have to initiate the conversation. The bot can respond to somebody joining the conversation with a welcome message.
How is this possible?
This "Welcome feature" has been a long term discussion and topic since Webchat v3. It looks like it has been fixed 2 days ago with this pull request: https://github.com/Microsoft/BotFramework-WebChat/pull/1286
There is now a sample on how to do that, located here:
https://github.com/Microsoft/BotFramework-WebChat/blob/master/samples/15.d.backchannel-send-welcome-event/index.html
In a few words, the demo is the following:
(async function () {
// In this demo, we are using Direct Line token from MockBot.
// To talk to your bot, you should use the token exchanged using your Direct Line secret.
// You should never put the Direct Line secret in the browser or client app.
// https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication
const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' });
const { token } = await res.json();
// We are using a customized store to add hooks to connect event
const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
// When we receive DIRECT_LINE/CONNECT_FULFILLED action, we will send an event activity using WEB_CHAT/SEND_EVENT
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: { language: window.navigator.language }
}
});
}
return next(action);
});
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store
}, document.getElementById('webchat'));
document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));
Please note that due to the fact that this PR is quite new, it's not embedded in the latest release so you have to point to the master version of webchat.js file, not latest:
<script src="https://cdn.botframework.com/botframework-webchat/master/webchat.js"></script>
And it's working: your bot side is notified of an activity of type Event, where you will be able to reply to your user, before he typed anything:
while sending push notification i got ( Uncaught (in promise) ReferenceError: require is not defined(…)) error.here is my code
const endPoint = subscription.endpoint.slice(subscription.endpoint.lastIndexOf('/')+1);
console.log(endPoint);
var gcm = require('node-gcm');
var message = new gcm.Message({
notification: {
title: "Hello, World",
icon: "ic_launcher",
body: "This is a notification that will be displayed ASAP.",
tag:"hello"
}
});
var regTokens = [endPoint];
var sender = new gcm.Sender('AIzaSyD9Bcxd_MQZFoGjO1y_hPm-xUdgnM25Ny4'); //API Key
// Now the sender can be used to send messages
sender.send(message, { registrationTokens: regTokens }, function (error, response) {
if (error) {
console.error(error);
res.status(400);
}
else {
console.log(response);
res.status(200);
}
});
})
})
}
Screenshot of error
enter image description here
This code uses require, so it looks to me like you're trying to use node code in the browser. To do that you'll need to use something like Browserify, although I'm not sure that's going to work for node-gcm as it may have certain requirements about sending network requests without cross origin restrictions etc.
I inherited a Windows 8 application that is written with XAML. So in C# when I make this call
user = await MobileServices.MobileService
.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
(This is for Azure Mobile Services)
The user object is ONLY giving me the Token and the MicrosoftAccount:..............
In order to get to authenticate people, I need to be able to see WHO is requesting access...
I looking at articles like below, but I seem to be missing something? Is this javascript in the article something I would have to write in Node.js?
Example article:
http://blogs.msdn.com/b/carlosfigueira/archive/2013/12/12/expanded-login-scopes-in-azure-mobile-services.aspx
Currently to be able to get more information about the logged in user, you need to make a second call to the service to retrieve the user info. You don't really need to ask for additional login scopes (the topic of the post you mentioned) to retrieve the user name, since that is given by default for all the providers.
This post should have the code you need to write in the server side (node.js) to get more information about the logged in user. The TL;DR version is given below:
On the server side: add this custom API (I'll call it "userInfo"; set the permission of GET to "user", and all others to admin):
exports.get = function(request, response) {
var user = request.user;
user.getIdentities({
success: function(identities) {
var accessToken = identities.microsoft.accessToken;
var url = 'https://apis.live.net/v5.0/me/?method=GET&access_token=' + accessToken;
var requestCallback = function (err, resp, body) {
if (err || resp.statusCode !== 200) {
console.error('Error sending data to the provider: ', err);
response.send(statusCodes.INTERNAL_SERVER_ERROR, body);
} else {
try {
var userData = JSON.parse(body);
response.send(200, userData);
} catch (ex) {
console.error('Error parsing response from the provider API: ', ex);
response.send(statusCodes.INTERNAL_SERVER_ERROR, ex);
}
}
}
var req = require('request');
var reqOptions = {
uri: url,
headers: { Accept: "application/json" }
};
req(reqOptions, requestCallback);
}
});
}
On the client side, after a successful login, call that API:
user = await MobileServices.MobileService
.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
var userInfo = await MobileServices.MobileService.InvokeApiAsync(
"userInfo", HttpMethod.Get, null);
userInfo will contain a JObject with the user information. There is an open feature request to make this better at http://feedback.azure.com/forums/216254-mobile-services/suggestions/5211616-ability-to-intercept-the-login-response.