Better error handling with Promises? - javascript

I am currently experimenting Google Firebase functions to access Google APIs. It's running fine, but I am a little bit lost in trying to manage the errors that could be detected ...
In the .HTTPS getGoogleUsers functions , I would like to return an HTTP status code ( 200 or error code ) , and the data ( or error message )
As far as I can see , I can get errors:
from the connect() function ( 500: Internal server error or 401 Unauthorized )
from the listUsers() function ( 500: Internal server error or 400 Bad Request )
Am I totally or partially wrong ? what should be my strategy in this case ?
thanks for feedback ..
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const {google} = require('googleapis');
const KEY = require('./service-key.json');
// Create JSON Web Token Client
function connect () {
return new Promise((resolve, reject) => {
const jwtClient = new google.auth.JWT(
KEY.client_email,
null,
KEY.private_key,
['https://www.googleapis.com/auth/admin.directory.user'],
'adminuser#mydomain.com'
);
jwtClient.authorize((err) => {
if(err) {
reject(err);
} else {
resolve(jwtClient);
}
});
});
}
function listUsers (client) {
return new Promise((resolve, reject) => {
google.admin('directory_v1').users.list({
auth: client,
domain: 'mydomain.com',
}, (err, response) => {
if (err) {
reject(err);
}
resolve(response.data.users);
});
});
}
function getAllUsers () {
connect()
.then(client => {
return listUsers(client);
})
.catch(error => {
return error;
})
}
exports.getGoogleUsers = functions.https.onRequest((req, res) => {
const users = getAllUsers();
if (error) {
status = error.status;
data = error.message;
} else {
status = 200;
data = users;
}
res.send({ status: status, datas: data })
});

I think you are looking for
function getAllUsers () {
return connect().then(listUsers);
//^^^^^^
}
exports.getGoogleUsers = functions.https.onRequest((req, res) => {
getAllUsers().then(users => {
return {status: 200, datas: users};
}, error => {
return {status: error.status, datas: error.message};
}).then(response => {
res.send(response);
});
});
This uses the .then(…, …) method with two callbacks to distinguish between success and error case, and to wait for the result of the promise.

Related

unable to catch any form of error or response from firebase notification callback function in Node js

I am using the package "fcm-node" in order to send notifications to certain device id.
the sendNotification function is as follows:
const FCM = require('fcm-node');
const serverKey = process.env.SERVER_KEY;
const fcm = new FCM(serverKey);
function sendNotification(registrationToken, title, body, type, key) {
const message = {
to: registrationToken,
collapse_key: key,
notification: {
title: title,
body: body,
delivery_receipt_requested: true,
sound: `ping.aiff`
},
data: {
type: type,
my_key: key,
}
};
fcm.send(message, function (err, value) {
if (err) {
console.log(err);
return false;
} else {
console.log(value);
return value;
}
});
};
module.exports = {
sendNotification
};
The api function I use to call this function is as follows:
router.get('/test', async (req, res, next) => {
const promise = new Promise((resolve, reject) => {
let data = sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
console.log(data)
if (data == false) reject(data);
else resolve(data);
});
promise
.then((data) => { return res.status(200).send(data); })
.catch((data) => { return res.status(500).send(data) })
});
When I console.log the "err" and "value" from the sendNotification, I get either of the followings:
{"multicast_id":4488027446433525506,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1652082785265643%557c6f39557c6f39"}]};
{"multicast_id":8241007545302148303,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
In case it is successful, I made sure that the device is receiving the notification.
The problem is in the api's data. It is always "undefined" and weither send notification is successful or not I get the 200 Ok status.
What seems to be the problem?
You can't return anything from the function (err, value) {} callback of a node-style asynchrnous function.
Your sendNotification() function needs to return a promise. util.promisify() makes the conversion from a node-style asynchronous function to a promise-returning asynchronous function convenient. Note the return, it's important:
const FCM = require('fcm-node');
const serverKey = process.env.SERVER_KEY;
const fcm = new FCM(serverKey);
const { promisify } = require('util');
fcm.sendAsync = promisify(fcm.send);
function sendNotification(registrationToken, title, body, type, key) {
return fcm.sendAsync({
to: registrationToken,
collapse_key: key,
notification: {
title: title,
body: body,
delivery_receipt_requested: true,
sound: `ping.aiff`
},
data: {
type: type,
my_key: key,
}
});
}
module.exports = {
sendNotification
};
Now you can do what you had in mind
router.get('/test', async (req, res, next) => {
try {
const data = await sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
return res.status(200).send(data);
} catch (err) {
return res.status(500).send(err);
}
});
Maybe it will help, at first try to return your response (the promise) in sendNotification, as actually you have a void function, that's why it's always undefined and after in your route
router.get('/test', async (req, res, next) => {
try {
const data = sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
if (data) {
return res.status(200).send(data);
}
} catch(err) {
return res.status(500).send(err);
}
});

Call Firebase Function in javascript

I have a Cloud Function deployed to Firebase, and my iOS and Android apps use it fine, all works good. Below is the function deployed.
const admin = require('firebase-admin');
const firebase_tools = require('firebase-tools');
const functions = require('firebase-functions');
admin.initializeApp();
exports.deleteUser = functions
.runWith({
timeoutSeconds: 540,
memory: '2GB'
})
.https.onCall((data, context) => {
const userId = context.auth.uid;
var promises = [];
// DELETE DATA
var paths = ['users/' + userId, 'messages/' + userId, 'chat/' + userId, 'like/' + userId];
paths.forEach((path) => {
promises.push(
recursiveDelete(path).then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE FILES
const bucket = admin.storage().bucket();
var image_paths = ["avatar/" + userId, "avatar2/" + userId, "avatar3/" + userId];
image_paths.forEach((path) => {
promises.push(
bucket.file(path).delete().then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE USER
promises.push(
admin.auth().deleteUser(userId)
.then( () => {
console.log('Successfully deleted user');
return true;
})
.catch((error) => {
console.log('Error deleting user:', error);
})
);
return Promise.all(promises).then(() => {
return true;
}).catch(er => {
console.error('...', er);
});
});
function recursiveDelete(path, context) {
return firebase_tools.firestore
.delete(path, {
project: process.env.GCLOUD_PROJECT,
recursive: true,
yes: true,
token: functions.config().fb.token
})
.then(() => {
return {
path: path
}
}).catch( (error) => {
console.log('error: ', error);
return error;
});
}
// [END recursive_delete_function]
How can I execute this script with a button in javascript? A standard .js file locally? I also need to be able to pass in a userId manually.
In my react native app I call it like:
const deleteUser = async () => {
functions().httpsCallable('deleteUser')()
signOut();
}
But in my javascript file (nothing to do with my react native app), I need to pass in a userId and call that same function to delete the user.
There are a number of ways to go about executing a cloud function within your client side application.
Depending on how you have the function setup, you can either pass in a parameter or data via the body in the request.
For example, using express (similar to other frameworks):
// fetch(‘api.com/user/foo’, {method: ‘DELETE’} )
app.delete(‘/user/:uid’, (req, res) => {
const uid = req.params.uid;
// execute function
})
// fetch(‘api.com/user’, {method: ‘DELETE’, body: { uid: foo } } )
app.delete(‘/user’, (req, res) => {
const uid = req.body.uid;
// execute function
})
// fetch(‘api.com/user?uid=foo’, {method: ‘DELETE’} )
app.delete(‘/user’, (req, res) => {
const uid = req.query.uid;
// execute function
})
Full Example:
<button onclick=“deleteUser(uid)”>Delete Me</button>
<script>
function deleteUser(uid) {
fetch(`api.com/user/${uid}`, { method: ‘DELETE’});
// rest of function
}
</script>
Was able to call my firebase function with the following:
userId was accessible like so const { userId } = data; from my function script
async function deleteAccount(userId) {
const deleteUser = firebase.functions().httpsCallable("deleteUser");
deleteUser({ userId }).then((result) => {
console.log(result.data);
});
}

Unsubscribe email using Fetch api Javascript

I have a form where i enter an email and it gets ''subscribed'' in a user.json file using a fetch api on node server.My task is to :
upon clicking on the "Unsubscribe" button, implement the functionality for unsubscribing from the community list. For that, make POST Ajax request using http://localhost:3000/unsubscribe endpoint.
I tried to make the function but it wasnt succeseful so i deleted it. Also,i need to do the following :
While the requests to http://localhost:3000/subscribe and
http://localhost:3000/unsubscribe endpoints are in progress, prevent
additional requests upon clicking on "Subscribe" and "Unsubscribe".
Also, disable them (use the disabled attribute) and style them using
opacity: 0.5.
For me ajax requests,fetch and javascript is something new,so i dont know really well how to do this task,if you could help me i'll be happy,thanks in advance.
fetch code for subscribing:
import { validateEmail } from './email-validator.js'
export const sendSubscribe = (emailInput) => {
const isValidEmail = validateEmail(emailInput)
if (isValidEmail === true) {
sendData(emailInput);
}
}
export const sendHttpRequest = (method, url, data) => {
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: data ? {
'Content-Type': 'application/json'
} : {}
}).then(response => {
if (response.status >= 400) {
return response.json().then(errResData => {
const error = new Error('Something went wrong!');
error.data = errResData;
throw error;
});
}
return response.json();
});
};
const sendData = (emailInput) => {
sendHttpRequest('POST', 'http://localhost:8080/subscribe', {
email: emailInput
}).then(responseData => {
return responseData
}).catch(err => {
console.log(err, err.data);
window.alert(err.data.error)
});
}
index.js from route node server:
const express = require('express');
const router = express.Router();
const FileStorage = require('../services/FileStorage');
/* POST /subscribe */
router.post('/subscribe', async function (req, res) {
try {
if (!req.body || !req.body.email) {
return res.status(400).json({ error: "Wrong payload" });
}
if (req.body.email === 'forbidden#gmail.com') {
return res.status(422).json({ error: "Email is already in use" });
}
const data = {email: req.body.email};
await FileStorage.writeFile('user.json', data);
await res.json({success: true})
} catch (e) {
console.log(e);
res.status(500).send('Internal error');
}
});
/* GET /unsubscribe */
router.post('/unsubscribe ', async function (req, res) {
try {
await FileStorage.deleteFile('user.json');
await FileStorage.writeFile('user-analytics.json', []);
await FileStorage.writeFile('performance-analytics.json', []);
await res.json({success: true})
} catch (e) {
console.log(e);
res.status(500).send('Internal error');
}
});
module.exports = router;
And user.json file looks like this :
{"email":"Email#gmail.com"}
This is my attempt for unsubscribing :
export const unsubscribeUser = () => {
try {
const response = fetch('http://localhost:8080/unsubscribe', {
method: "POST"
});
if (!response.ok) {
const message = 'Error with Status Code: ' + response.status;
throw new Error(message);
}
const data = response.json();
console.log(data);
} catch (error) {
console.log('Error: ' + error);
}
}
It gives the following errors:
Error: Error: Error with Status Code: undefined
main.js:2
main.js:2 POST http://localhost:8080/unsubscribe 404 (Not Found)
FileStorage.js:
const fs = require('fs');
const fsp = fs.promises;
class FileStorage {
static getRealPath(path) {
return `${global.appRoot}/storage/${path}`
}
static async checkFileExist(path, mode = fs.constants.F_OK) {
try {
await fsp.access(FileStorage.getRealPath(path), mode);
return true
} catch (e) {
return false
}
}
static async readFile(path) {
if (await FileStorage.checkFileExist(path)) {
return await fsp.readFile(FileStorage.getRealPath(path), 'utf-8');
} else {
throw new Error('File read error');
}
}
static async readJsonFile(path) {
const rawJson = await FileStorage.readFile(path);
try {
return JSON.parse(rawJson);
} catch (e) {
return {error: 'Non valid JSON in file content'};
}
}
static async writeFile(path, content) {
const preparedContent = typeof content !== 'string' && typeof content === 'object' ? JSON.stringify(content) : content;
return await fsp.writeFile(FileStorage.getRealPath(path), preparedContent);
}
static async deleteFile(path) {
if (!await FileStorage.checkFileExist(path, fs.constants.F_OK | fs.constants.W_OK)) {
return await fsp.unlink(FileStorage.getRealPath(path));
}
return true;
}
}
module.exports = FileStorage;
You should consider using a database for handling CRUD operations on your persisted data. If you must use filestorage, theres a flat file DB library called lowdb that can make working the files easier.
As for preventing duplicate requests, you can track if user has already made a request.
let fetchBtn = document.getElementById('fetch')
let isFetching = false
fetchBtn.addEventListener('click', handleClick)
async function handleClick(){
if (isFetching) return // do nothing if request already made
isFetching = true
disableBtn()
const response = await fetchMock()
isFetching = false
enableBtn()
}
function fetchMock(){
// const response = await fetch("https://example.com");
return new Promise(resolve => setTimeout (() => resolve('hello'), 2000))
}
function disableBtn(){
fetchBtn.setAttribute('disabled', 'disabled');
fetchBtn.style.opacity = "0.5"
}
function enableBtn(){
fetchBtn.removeAttribute('disabled');
fetchBtn.style.opacity = "1"
}
<button type="button" id="fetch">Fetch</button>

get route is correct but api is still not working(fetching nothing)

I am trying to make a get route for this API:
https://api.nasa.gov/mars-photos/api/v1/rovers/opportunity/photos?sol=1000&api_key=92Ll6nGuQhfGjZnT2gxaUgiBhlCJ9K1zi2Fv5ONn
And although the syntax for the get route, the API still doesn't work in postman nor in client-side.
Here's the get route code:
app.get('/roverInfo/:rover_name', async (req, res) => {
const { rover_name } = req.params
try {
let images = await fetch(`https://api.nasa.gov/mars-photos/api/v1/rovers/${rover_name}/photos?sol=1000&api_key=${process.env.API_KEY}`).then((res) => res.json())
res.send({ images })
} catch (err) {
console.log('error:', err)
}
})
sandbox here
and here's the client-side request:
const showRovers = async (rovers) => {
try {
await fetch(`https://localhost:3000/roverInfo/:rover_name`)
.then((res) => {
return res.json()
})
.then((rovers) => updateStore(store, { rovers }), console.log(rovers))
} catch (error) {
console.log('errors:', error)
}
}
and here's the error I am getting:
Failed to load resource: net::ERR_SSL_PROTOCOL_ERROR
ADVISE: Don't mix await/async with .then, use either one
app.get("/roverInfo/:rover_name", async (req, res) => {
const { rover_name } = req.params;
try {
const res = await fetch(
`https://api.nasa.gov/mars-photos/api/v1/rovers/${rover_name}/photos?sol=1000&api_key=${process.env.API_KEY}`
) // removed .then
const images = await res.json(); // await response to json
res.send({ images });
} catch (err) {
console.log("error:", err);
}
});
02. should be http instead of https
03. need to pass rover name to param instead of using :rover_name
let getRovers = showRovers('opportunity');
const showRovers = async (roverName) => {
try {
console.log("roverName", roverName)
// use http here
await fetch(`http://localhost:3000/roverInfo/${roverName}`)
.then((res) => {
return res.json();
})
.then((rovers) => updateStore(store, { rovers }));
} catch (error) {
console.log("errors:", error);
}
};

Serverless querying NodeJS MSSQL and cannot get result back to callback

I'm writing a small Serverless function to query a MSSQL db using the node mssql library (https://www.npmjs.com/package/mssql#callbacks)
I've read the documentation and I think I'm doing everything right but getting confused - I can see the result in my logs, but the main function callback is not being called and therefore data not being outputted by the API (basically the whole thing times out)
Heres my Lambda function:
import {success, error} from './libs/response-lib';
import {EPDBConfig} from "./libs/Database-lib";
import sql from "mssql";
import config from "./config";
export function main(event, context, callback) {
console.log("start");
EPDBConfig().then(dbConfig => {
if(config.debug) console.log("Hello!");
let EPDBconfig = {
user: dbConfig.dbuser,
password: dbConfig.dbpassword,
server: dbConfig.dbhost,
database: dbConfig.dbname
};
sql.connect(EPDBconfig)
.then(pool => {
return pool.request()
.input('student_no', sql.Int, 129546)
.query('select * from Student where StudentNo = #student_no')
}).then(result => {
console.log("success!");
if(config.debug) console.log('result', result);
return result;
}).catch(err => {
if(config.debug) console.log('err1', err);
return err;
});
sql.on('error', err => {
if(config.debug) console.log('err2', err);
return callback(null, error(err));
});
sql.on('done', result => {
if(config.debug) console.log('done', result);
return callback(null, success(result));
});
}).catch(err => {
if(config.debug) console.log('err3', err);
return callback(null, error(err));
})
}
DB Config is pulled from AWS KMS for secure vars
import AWS from "aws-sdk";
import config from "../config";
const kms = new AWS.KMS({
region: AWS.config.region
});
export function EPDBConfig() {
//DECRYPT THE DATABASE CONNECTION DETAILS
return new Promise((resolve, reject) => {
let params = {
CiphertextBlob: Buffer(process.env.epdb, 'base64')
};
kms.decrypt(params, function(err, data) {
if (err) {
reject(err);
} // an error occurred
else {
let dbParams = JSON.parse(String(data.Plaintext));
resolve(dbParams);
}
});
});
}
and response lib:
export function success(data, message) {
return buildResponse(200, true, data, message);
}
export function error(data, message) {
return buildResponse(400, false, data, message);
}
export function unauthorized(data, message) {
return buildResponse(401, false, data, message);
}
export function forbidden(data, message) {
return buildResponse(403, false, data, message);
}
export function exception(data, message) {
return buildResponse(500, false, data, message);
}
function buildResponse(statusCode, successState, data, message) {
var body = {
success: successState,
message: message
};
if (successState) {
body.data = data;
}
return {
statusCode: statusCode,
headers: {
'Access-Control-Allow-Origin' : '*',
'Access-Control-Allow-Credentials': true
},
body: JSON.stringify(body)
};
}
Can anyone point out where I'm going wrong here? I think I have a whole pile of promises going on. The sql.on('done', result => { ... doesn't appear to work, and I tried adding 'return callback(null, success(result));' in the area where I have 'success'
Please help me!
So, I endded up resolving this with a bit of refactoring:
import sql from "mssql";
import _ from "lodash";
import {success, error} from './libs/response-lib';
import {DB} from "./libs/Database-lib";
import {Student} from "./models/ep.student";
export function main(event, context, callback) {
DB().then(pool => {
return new Promise((resolve, reject) => {
pool.connect(err => {
if(err) reject(err);
pool.request()
.input('StudentNo', sql.Int, 129546)
.execute('StoredProcedure')
.then(result => {
if(process.env.debug) console.log('result', result);
let student = new Student();
_.assign(student, result.recordset[0]);
resolve(student);
pool.close();
}).catch(err => {
reject(err);
pool.close();
});
});
});
}).then(result => {
if(process.env.debug) console.log('result', result);
callback(null, success(result));
}).catch(err => {
if(process.env.debug) console.log('err', err);
callback(null, error(err));
});
}
The first issue was that I wasn't terminating my connection - just a note that I switched to using a stored procedure as this was always going to be the case.
The second issue was that I wasn't really using promises correctly (i think) I wrapped up my connection in a promise and only when I had the response did I resolve or reject it.

Categories

Resources