Managing subscription of push notification on client and server sides - javascript

I erase all my previous questions on how to use the code give into the documentation of workbox push notification to show you the solution I found more clearly.
before to show you the code, I will explain that I took long time to understand. You use normaly three files that work with service worker. In my case server.js, index.js and sw.js. When the browser get all the files it need to display your site, I also get the index.js file. This file start the service worker with navigator.serviceWorker.register('sw.js'). Then, it test if the user has already accepted to receive notification. At the end, it check if subscription exist or not and manage its.
The subscription part is very important and difficult to manage. With swRegistration.pushManager.subscribe() we will get an sub object. This object contains the endpoint, the auth and the p256th. Those informations are very important and we need to send to our server to store it into our database. the fetch() element do it. By this way, we will be able to send a notification to our tagerted user in the futur.
The last thing you need to know and how to create private and public Vapid keys. To generate it, display into your terminal the console.log that are below my privateVapidKey variable inside server.js. Then copy-paste both results into variable as I did server.js and alse copy-paste publicsVapidKey into index.js file.
server.js :
var express = require('express');
var app = express();
var https = require('https');
var fs = require('fs');
var webPush = require('web-push');
var bodyParser = require('body-parser');
https.createServer({
key: fs.readFileSync('key_localhost2.pem'),
cert: fs.readFileSync('cert_localhost2.pem'),
passphrase: 'localhost',
}, app).listen(8080);
//*****************************************************************
//-------------------------- TEMPLATES --------------------------
//*****************************************************************
//moteur de modèles ou de templates
app.set('view engine', 'ejs');
//*****************************************************************
//-------------------------- MIDDLEWARE --------------------------
//*****************************************************************
app
.use('/static', express.static(__dirname + '/public'))
.use(express.static(__dirname + '/public/js'))
.use(bodyParser.json());
//*****************************************************************
//--------------------------- ROUTES ------------------------------
//*****************************************************************
app.get('/', function (request, response) {
response.render('./pages/index.ejs');
});
var publicVapidKey = "BKwLqQWMQpLfSNGb-VXCsAPE1H5o7Oh3VxDiEIqWWOm2OdAoFPqr9K9WI7dKKtjYYHLTKm7tjJO04091pDXZiJs"
var privateVapidKey = "483sZs2cZUxSQegGKKOZXLl_b7_ywBF_qJO77gXFsHE"
//console.log('Publics keys : ' + vapidKeys.publicKey)
//console.log('Private key : ' + vapidKeys.privateKey)
webPush.setVapidDetails(
'mailto:localhost:8080',
publicVapidKey,
privateVapidKey
);
var pushSubscription;
app.post('/subscription_push_notification', function (req, resp) {
pushSubscription = req.body;
console.log(pushSubscription)
//I'm able to save this information into my database
endpointVal = req.body.endpoint;
authVal = req.body.keys.auth;
p256dhVal = req.body.keys.p256dh;
setTimeout(function () {
if (endpointVal) {
var payload = 'Here is a payload!';
webPush.sendNotification(
pushSubscription,
payload
).catch(function (err) {
console.log(err);
});
}
}, 2000)
resp.json({});
});
index.js :
window.addEventListener('load', function () {
//*********************************************************
// Start SW, permission notification, register subscribe
//*********************************************************
if ('serviceWorker' in navigator) {
//+++++++++++++++++++++++++++++
//Register Service worker
navigator.serviceWorker.register('sw.js')
.then(function (swRegistration) {
//Ask to notification permission
displayNotification();
var publicVapidKey = "BKwLqQWMQpLfSNGb-VXCsAPE1H5o7Oh3VxDiEIqWWOm2OdAoFPqr9K9WI7dKKtjYYHLTKm7tjJO04091pDXZiJs";
var applicationServerKey = urlBase64ToUint8Array(publicVapidKey);
//Manage push notifiaction
swRegistration.pushManager.getSubscription().then(function (sub) {
if (sub === null) {
// Update UI to ask user to register for Push
console.log('Not subscribed to push service!');
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
}).then(function (sub) {
// We have a subscription, update the database
console.log('Endpoint URL: ', sub.endpoint);
fetch('/subscription_push_notification', {
method: 'POST',
body : JSON.stringify(sub),
headers: {
'content-type':'application/json'
}
});
}).catch(function (e) {
if (Notification.permission === 'denied') {
console.warn('Permission for notifications was denied');
} else {
console.error('Unable to subscribe to push', e);
}
});
} else {
// We have a subscription, update the database
console.log('Subscription object: ', sub);
fetch('/subscription_push_notification', {
method: 'POST',
body : JSON.stringify(sub),
headers: {
'content-type':'application/json'
}
});
}
});
})
.catch(function (err) {
console.log('Service Worker registration failed: ', err);
})
}
//*********************************************************
// Function ask to notification permission
//*********************************************************
function displayNotification() {
if (Notification.permission === 'granted') {
//Mean, the notification is accepted.
console.log('Notification accepted...');
} else if (Notification.permission === "blocked" || Notification.permission === "denied") {
// the user has previously denied notification. Can't reprompt.
console.log('Notification blocked...');
} else {
// show a prompt to the user
console.log('Prompt to accept notification');
Notification.requestPermission();
}
}
//*********************************************************
// Transform public Vapid key
//*********************************************************
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
});
sw.js :
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.4.1/workbox-sw.js');
if (workbox) {
console.log(`Yay! Workbox is loaded 🎉`);
} else {
console.log(`Boo! Workbox didn't load 😬`);
}
//*********************************************************
// Save file from site to work in offline mode
//*********************************************************
workbox.precaching.precacheAndRoute([
{ url: '/', revision: '383676' },
{ url: '/static/css/style.css', revision: '383677' },
{ url: '/static/js/index.js', revision: '383678' },
]);
//*********************************************************
// The notification click event
//*********************************************************
//Inform when the notification close/hide from the window
self.addEventListener('notificationclick', function (e) {
console.log('notification was clicked')
var notification = e.notification;
var action = e.action;
if (action === 'close') {
notification.close();
} else {
clients.openWindow('https://www.google.fr');
};
});
//*********************************************************
// Handling the push event in the service worker
//*********************************************************
self.addEventListener('push', function (e) {
console.log('Hi man !!!')
var options = {
body: e.data.text(),
icon: '/static/img/logo_menu.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: '2'
},
};
e.waitUntil(
self.registration.showNotification('Hello world!', options)
);
});

Related

Firebase Functions Event Source stuck on pending

I'm trying to use an event emitter to trigger specific functions that I need.Here is the nodejs code.
Event source functions locally on localhost but once deployed, I cannot detect the event triggers. However, the logs on the firebase functions logger show the function being carried out. However, when tested, the function does not provide any feedback to the event
const EventEmitter = require('events');
const Stream = new EventEmitter();
// 'Access-Control-Allow-Origin': '*',
// 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
exports.eventEmitter = (req, res) => {
console.log(req.body);
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
})
Stream.on('push', function(event, data) {
console.log('send0');
res.write('event: ' + String(event) + '\n' + 'data:' + JSON.stringify(data) + '\n\n');
});
setInterval(() => {
console.log('send1');
Stream.emit('push', 'message', { msg: 'it works! hurraaay' });
}, 5000);
}
When I test this code locally for event emission, using the following Angular code, The event is detected and logged on the console. However, once I deploy the function to firebase functions, I get a pending request on the network tab of the browser that doesn't have any log and seems to not complete at all and my angular code does inevitably detects nothing
getStream() {
return Observable.create(observer => {
let url = 'link to emitter';
let eventSource = new EventSource(url);
eventSource.addEventListener('message', function(e) {
console.log(e.data);
}, false);
eventSource.addEventListener('open', function(e) {
// Connection was opened.
console.log("open");
}, false);
eventSource.addEventListener('error', function(e) {
if (eventSource.readyState == EventSource.CLOSED) {
// Connection was closed.
console.log("closed");
}
}, false);
eventSource.addEventListener('message', message => { console.log(message.data); });
eventSource.onmessage = (event) => {
let json = JSON.parse(event.data);
if (json !== undefined && json !== '') {
this.zone.run(() => observer.next(json));
}
};
eventSource.onerror = (error) => {
if (eventSource.readyState === 0) {
console.log('The stream has been closed by the server.');
eventSource.close();
observer.complete();
} else {
observer.error('EventSource error: ' + error);
}
}
});
}
Cloud Functions HTTP endpoints do not support streaming data to clients like a websocket. It can only receive an entire payload up front, then send an entire payload back to the client. There are no chunked responses. The limit for the payload is 10MB. The default timeout for a function invocation is 60s and can only be increased to 9m max. If you need streaming, then Cloud Functions is not the right product for your use case.

Getting Cognito User and Group inside Amplify AWS API function

I used Amplify Cli to create the templated api 'amplify add api' with new lambda function and cognito authentication. This is the code generated in the index.js file of the lambda function:
/************************************
index.js
*************************************/
const awsServerlessExpress = require('aws-serverless-express');
const app = require('./app');
const server = awsServerlessExpress.createServer(app);
exports.handler = (event, context) => {
console.log(`EVENT: ${JSON.stringify(event)}`);
awsServerlessExpress.proxy(server, event, context);
};
/************************************
HTTP put method for insert object in app.js
*************************************/
app.put(path, function(req, res) {
if (userIdPresent) {
req.body['userId'] = req.apiGateway.event.requestContext.identity.cognitoIdentityId || UNAUTH;
} else {
// Get the unique ID given by cognito for this user, it is passed to lambda as part of a large string in event.requestContext.identity.cognitoAuthenticationProvider
let userSub = req.apiGateway.event.requestContext.identity.cognitoAuthenticationProvider.split(':CognitoSignIn:')[1];
let requestIp = req.apiGateway.event.requestContext.identity.sourceIp;
let userPoolId = process.env.AUTH_TDOCWEBAPP001_USERPOOLID;
let request = {
UserPoolId: userPoolId, // Set your cognito user pool id
AttributesToGet: [
'email',
'given_name',
'family_name',
],
Filter: 'sub = "' + userSub + '"',
Limit: 1
}
//let users = await cognitoClient.listUsers(request).promise(); //Doesn't work because await not allowed
let users = cognitoClient.listUsers(request);
console.log("got user in put:", users[0]);
// TODO: Get the group that the user belongs to with "cognito:grouops"?
// Set userId and sortKey
req.body['userId'] = users[0].sub;
req.body['sortKey'] = sortKeyValue;
req.body['updatedByIp'] = requestIp;
req.body['createdAt'] = new Date().toISOString(); //ISO 8601 suppored by DynamoDB
req.body['updatedAt'] = new Date().toISOString();
req.body['isDeleted'] = false;
}
let putItemParams = {
TableName: tableName,
Item: req.body
}
dynamodb.put(putItemParams, (err, data) => {
if(err) {
res.statusCode = 500;
res.json({error: err, url: req.url, body: req.body});
} else{
res.json({success: 'put call succeed!', url: req.url, data: data})
}
});
});
So right now, when I call the lambda via the API, i get users is undefined. I'm trying to get the user object and then the groups that it belongs. Not sure how to do it if the function doesn't allow async... Please help
I found it myself the solution.
let users = await cognitoClient.listUsers(request);
or
let users = cognitoClient.listUsers(request, function(err, data) {...});
I needed wait to get users from the Cognito.

Imgur API call: 443 ENOTFOUND error troubleshooting

I'm receiving the same error every time I call the following node.js code. I haven't used Authorization Headers before in node.js, so I must be doing something wrong. Help or point me towards good documentation? The code is trying to retrieve as search for 'yellow' in imgur's library. My console always prints the same ENOTFOUND error each time I try to run it.
var express = require("express");
var moment = require("moment");
var http = require("http");
var express = require("express");
var moment = require("moment");
var https = require("https");
var mongo = require('mongodb').MongoClient;
var mystatus="";
var app=express();
var imgurClientID = "<my id>";
var imgurClientSecret = "<my client secret>";
var options = {
hostname: 'https://api.imgur.com/3/gallery/search?q=yellow',
method: "POST",
headers:{
Authorization: imgurClientID,
}
}
app.get('/home', function(req,res){
var myreq = https.request(options, function(res){
console.log('Status: '+res.statusCode);
console.log('Headers: '+JSON.stringify(res.headers));
res.setEncoding('utf8');
res.on('data', function(body){
console.log("Body: "+body);
});
});
myreq.on('error', function(e){
console.log("Error!: "+e);
})
myreq.end();
});
// https.get("https://Client-ID:"+imgurClientID+"#api.imgur.com/3/gallery/search?q=yellow", function(res){
// res.setHeader("Client-ID", imgurClientID);
// var body = "";
// res.on('data', function(chunk){
// body+=chunk;
// });
// res.on('end', function(){
// var imgurRes = JSON.parse(body);
// console.log(imgurRes);
// });
// }).on('error', function(e){
// console.log("Error!: "+e);
// })
app.get('/new/:name*', function(req,res){
//detect if name is a URL
//if it is not a proper http, return 'could not load' status
if (req.params.name.substr(0,4).toLowerCase() != "http")
{
mystatus = "Could not load";
res.json({url_part1: req.params.name,
url_part2: req.params[0],
status: mystatus
});
}
http.get(req.params.name+req.params[0], function(thisres){
var wwwpath = req.params[0].substr(2, req.params[0].length-2);
//do not look for the site unless it returns a 200 status code
if (thisres.statusCode != 200){
loadstatus = "Could not load";
}
//otherwise, find the site in the MongoDB list!
else {
//indicate that the site is a working URL
loadstatus = "Loaded!";
//prepare the database
mongo.connect("mongodb://dickorydock:$iderHouseRul3z#ds145365.mlab.com:45365/urlrosetta", function(err, db) {
var shorturl = db.collection("shorturl");
//find maximum site number
shorturl.find().sort({site_number:-1}).limit(1).toArray(function(err,documents){
max_site_number = documents[0]["site_number"];
})
//look for the site in the existing database
shorturl.find(
{original_url: wwwpath}
, {_id: 0, original_url: 1,short_url: 1, site_number:1})
.toArray(function(err,documents){
//if we found it, no need to add it again -- just return the existing site number
if (documents.length>0){
var sitenumber = documents[0]["site_number"];
}
//if we didn't find the working URL, add it
else {
var sitenumber = max_site_number + 3;
var newsitejson={original_url: wwwpath, site_number: sitenumber}
shorturl.insert(newsitejson)
}
//return the info about the short link
res.json({original_url: req.params.name+"://"+wwwpath,
short_url: "http://dickorydock-shorturl.herokuapp.com/"+sitenumber
});
db.close()
})
})
}
})
//if there is an error in finding the site in the URL, return 'could not load' status
.on('error', function(e){
console.error(e.code);
res.json({error: "Not a valid URL - try again."})
})
})
app.get('/:shortnum*', function(req,res){
var sitenumber = parseInt(req.params.shortnum);
var siteextra = req.params[0];
if (siteextra.length > 0){
res.json({error: "Not a valid reference - try again."})
}
else {
mongo.connect("mongodb://dickorydock:$iderHouseRul3z#ds145365.mlab.com:45365/urlrosetta", function(err, db) {
var shorturl = db.collection("shorturl");
//look for the site in the existing database, and either redirect or give an error
shorturl.find(
{site_number: sitenumber}
, {_id: 0, original_url: 1, site_number:1})
.toArray(function(err,documents){
if (documents.length>0){
res.redirect("http://"+documents[0]["original_url"])
}
else if (documents.length == 0){
res.json({error: "Not a valid reference - try again."})
}
db.close()
})
})
}
})
app.listen(8080, function(){
//app.listen(process.env.PORT, function(){
console.log("App listening")
});
Try:
var options = {
hostname: 'api.imgur.com',
path: '/3/gallery/search?q=yellow',
method: "POST",
headers:{
Authorization: imgurClientID,
}
}
you are getting that error because it's trying to hit https://api.imgur.com/3/gallery/search?q=yellow:443, the above should send it to the right location

nodejs does not emit data to client

I have simple nodejs app with sockets and I've faced an error where I can't find any solution. So I'm emiting from app to client and nothing happens there. Or client can't receive it - I don't know, because I can't check if it was successfully emited to client. This is the error I got when I tried to debug callback of emit:
Error: Callbacks are not supported when broadcasting
This my app code:
http.listen(6060, function () {
console.log("Listening on *: 6060");
});
io.set('authorization', function (handshakeData, accept) {
var domain = handshakeData.headers.referer.replace('http://', '').replace('https://', '').split(/[/?#]/)[0];
if ('***' == domain) {
accept(null, true);
} else {
return accept('You must be logged in to take an action in this site!', false);
}
});
io.use(function (sock, next) {
var handshakeData = sock.request;
var userToken = handshakeData._query.key;
if (typeof userToken !== null && userToken !== 0 && userToken !== '0' && userToken.length > 0) {
connection.query('***',
[xssfilter.filter(validator.escape(userToken))],
function (error, data) {
if (error) {
debug('Cant receive user data from database by token');
next(new Error('Failed to parse user data! Please login!'));
} else {
// load data to this user.
_updateUsers(xssfilter.filter(validator.escape(userToken)), 'add', data[0], sock.id);
_loadPreData();
next(null, true);
}
});
} else {
debug('Cant receive user token');
next(new Error('Failed to parse user data! Please login!'));
}
sock.on("disconnect", function () {
_updateUsers(false, 'remove', false, sock.id);
});
});
// we need to show people online count
io.emit('online-count', {
count: Object.keys(connectedUsers).length
});
And the function used above:
function _updateUsers(userToken, action, userData, sockedID) {
switch (action) {
case 'add':
connectedUsers[sockedID] = {...};
io.emit('online-count', io.emit('online-count', {
count: Object.keys(connectedUsers).length
}););
break;
case 'remove':
delete connectedUsers[sockedID];
io.emit('online-count', io.emit('online-count', {
count: Object.keys(connectedUsers).length
}););
break;
}
}
so after emiting online-count I should accept it on the client side as I'm doing it:
var socket;
socket = io(globalData.socketConn, {query: "key=" + globalData.userData.token});
socket.on('connect', function (data) {
console.log('Client side successfully connected with APP.');
});
socket.on('error', function (err) {
error('danger', 'top', err);
});
socket.on('online-count', function (data) {
console.log('Got online count: ' + data.count);
$('#online_count').html(data.count);
});
but the problem is with this online-count.. Nothing happens and it seems that it's not was even sent from node app. Any suggestions?
The problem was with my logic - I was sending online count only if new user were connecting/disconnecting. Problem were solved by adding function to repeat itself every few seconds and send online count to client side.

Socket.io - Sending data from a pusher stream to the client

I am struggling to send a stream of data being consumed via pusher-client-node to the client using Socket.IO.
I am receiving my data in Node.JS like this:
var API_KEY = 'cb65d0a7a72cd94adf1f';
var pusher = new Pusher(API_KEY, {
encrypted: true
});
var channel = pusher.subscribe("ticker.160");
channel.bind("message", function (data) {
//console.log(data);
});
My data, which comes in continuously, looks like that:
{ channel: 'ticker.160',
trade:
{ timestamp: 1420031543,
datetime: '2014-12-31 08:12:23 EST',
marketid: '160',
topsell: { price: '0.00007650', quantity: '106.26697381' }}
My Socket.IO code looks like this:
/**
* Socket.io
*/
var io = require("socket.io").listen(server, {log: true});
var users = [];
var stream = channel.bind("message", function (data) {
console.log(data);
});
io.on("connection", function (socket) {
// The user it's added to the array if it doesn't exist
if(users.indexOf(socket.id) === -1) {
users.push(socket.id);
}
// Log
logConnectedUsers();
socket.emit('someevent', { attr: 'value' } )
stream.on("newdata", function(data) {
// only broadcast when users are online
if(users.length > 0) {
// This emits the signal to the user that started
// the stream
socket.emit('someevent', { attr: 'value' } )
}
else {
// If there are no users connected we destroy the stream.
// Why would we keep it running for nobody?
stream.destroy();
stream = null;
}
});
// This handles when a user is disconnected
socket.on("disconnect", function(o) {
// find the user in the array
var index = users.indexOf(socket.id);
if(index != -1) {
// Eliminates the user from the array
users.splice(index, 1);
}
logConnectedUsers();
});
});
// A log function for debugging purposes
function logConnectedUsers() {
console.log("============= CONNECTED USERS ==============");
console.log("== :: " + users.length);
console.log("============================================");
}
I am quite new to Node.JS and Socket.IO and struggle to use my pusher stream in Node.JS. Therefore my question: How to connect my Socket.IO code with my Pusher code?
You need to use socket/io rooms ...
server:
var channel = pusher.subscribe("ticker.160"); //subscribe to pusher
//pass messages from pusher to the matching room in socket.io
channel.bind("message", function (data) {
io.to('ticker.160').emit('room-message', {room:'ticker.160', data:data});
});
...
io.on("connection", function (socket) {
...
socket.on('join', function(room){
socket.join(room);
});
socket.on('leave', function(room){
socket.leave(room);
});
});
client:
io.emit('join','ticker.160');
io.on('room-message', function(message){
switch(message.room) {
case 'ticker.160': return doSomething(message.data);
...
}
});

Categories