I have this push server which sends push notifications to devices and logs the response to a json. The code goes:
push.js
module.exports.sendPushSubscription = async (post, recipient, p256, auth) => {
console.log("Mandando PUSHES");
const subscription = {
endpoint: `https://fcm.googleapis.com/fcm/send/${recipient}`,
expirationTime: null,
keys: {
p256dh: `${p256}`,
auth: `${auth}`
}
}
console.log(subscription);
error = webpush.sendNotification(subscription, JSON.stringify(post))
.then(() => {
console.log("Notificación enviada");
return "Sent!";
})
.catch(err => {
return err;
});
await Promise.all(error);
return error;
};
routes.js
recipients = recipients.split(',');
p256 = p256.split(',');
auth = auth.split(',');
let err = "";
for(var i=0;i<recipients.length;i++) {
err = "Trying";
err = push.sendPushSubscription(post, recipients[i], p256[i], auth[i]);
post.recipients.push({
recipient: recipients[i],
error: err
})
}
rsp.json(post);
However, despite the code might suggest the sendPushSubscription function should wait for the promises to end (it's usually only one) and return either Sent! or the error itself, the json always adds the recipient and an empty error. No matter if it succeeds or fails, the answer is:
"recipients": [
{
"recipient": "d96UkANUtAo:APA91bF1-...",
"error": {}
}
Any ideas? Thanks in advance.
Best regards,
Emiliano
Related
I'm new to the Express, and I'm trying to apply some error handling at the top level.
In my controllers file, I have a controller to get all tours.
exports.getAllTours = async (req: Request, res: Response) => {
//Execute query
const features = new APIFeatures(Tour.find(), req.query)
.filter()
.sort()
.limitFields()
.paginate();
// Endpoint: http://localhost:8000/api/v1/tours
// Enter a wrong URL here will not even trigger the console.log function.
// But I want to throw the error right here, not in the app.all('*')
console.log("features", features);
if (!features) {
throw new NotFoundError("Tours Not Found");
}
//same problem here.
const tours = await features.query;
console.log("tours", tours.length);
if (!tours) {
throw new NotFoundError("Tours Not Found");
}
res.status(200).json({
status: "success",
result: tours.length,
data: {
tours,
},
});
};
I have a CustomError class that extends the Error class like this.
const httpStatusCode = require("./httpStatusCode");
class CustomError extends Error {
constructor(message: string, statusCode: number, description: string) {
super(description);
//Object.setPrototypeOf(this, new.target.prototype);
this.message = message;
this.statusCode = statusCode;
}
}
module.exports = CustomError;
class NotFoundError extends CustomError {
constructor(message, statusCode) {
super(message, statusCode);
this.message = message;
this.statusCode = httpStatusCode.NOT_FOUND;
}
}
module.exports = NotFoundError;
Also an error handling middleware:
import { NextFunction, Request, Response, ErrorRequestHandler } from "express";
module.exports = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || "error";
res.status(err.statusCode).json({
status: err.status,
message: err.message,
});
};
In the end, I use the errorHandler middleware in the app to catch all the errors.
However, the problem is all the errors in the getAllTours controller will not be thrown, instead, they will be thrown in the app.all():
app.use("/api/v1/tours", tourRouter);
app.all("*", (req: Request, res: Response) => {
throw new NotFoundError("Page Not Found");
//next(new AppError(`Can't find ${req.originalUrl} on this server`, 404));
});
app.use(errorHandler);
I know since the endpoint has been changed and thrown in the app.all() make sense. But how can I manually throw an error in the getAllTours controller?
I use express-async-error so I could use the throw keyword in the async function.
I figure it out.
Handle Express async error
I had no idea Express version 4 could not handle the async errors by simply throwing a new error. I'm still not sure if Express Version 5 as it now could handle it.
But I use ExpressJS Async Errors to solve this issue in the end.
Im struggling with getting axios interceptors to work.
When my token expires, i need it to refresh the access token and retry the original request once the token is refreshed.
I have this part working.
The problem is if i have concurrent api calls it will only retry the first request when the token was first invalid.
Here is my interceptor code:
export default function execute() {
let isRefreshing = false
// Request
axios.interceptors.request.use(
config => {
var token = Storage.getAccessToken() //localStorage.getItem("token");
if (token) {
console.log('Bearer ' + token)
config.headers['Authorization'] = 'Bearer ' + token
}
return config
},
error => {
return Promise.reject(error)
}
)
// Response
axios.interceptors.response.use(
response => {
return response
},
error => {
const originalRequest = error.config
// token expired
if (error.response.status === 401) {
console.log('401 Error need to reresh')
originalRequest._retry = true
let tokenModel = {
accessToken: Storage.getAccessToken(),
client: 'Web',
refreshToken: Storage.getRefreshToken()
}
//Storage.destroyTokens();
var refreshPath = Actions.REFRESH
if (!isRefreshing) {
isRefreshing = true
return store
.dispatch(refreshPath, { tokenModel })
.then(response => {
isRefreshing = false
console.log(response)
return axios(originalRequest)
})
.catch(error => {
isRefreshing = false
console.log(error)
// Logout
})
} else {
console.log('XXXXX')
console.log('SOME PROBLEM HERE') // <------------------
console.log('XXXXX')
}
} else {
store.commit(Mutations.SET_ERROR, error.response.data.error)
}
return Promise.reject(error)
}
)
}
I'm not sure what i need in the else block highlighted above.
EDIT:
When I do
return axios(originalRequest)
in the else block it works, however im not happy with the behaviours. It basically retries all the requests again and again until the token is refreshed.
I would rather it just retried once after the token had been refreshed
Any ideas
Thanks
You can just have additional interceptor which can refresh token and execute your pending requests.
In this, countDownLatch class can help.
Here is sample Interceptor code,
class AutoRefreshTokenRequestInterceptorSample() : Interceptor {
companion object {
var countDownLatch = CountDownLatch(0)
var previousAuthToken = ""
const val SKIP_AUTH_TOKEN = "SkipAccessTokenHeader"
const val AUTHORIZATION_HEADER = "AUTHORIZATION_HEADER_KEY"
}
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response? {
val request = chain.request()
if (shouldExecuteRequest(request)) {
// Execute Request
val response = chain.proceed(request)
if (!response.isSuccessful) {
// Failed Case
val errorBody = response.peekBody(java.lang.Long.MAX_VALUE).string()
val error = parseErrorModel(errorBody)
// Gives Signal to HOLD the Request Queue
countDownLatch = CountDownLatch(1)
handleError(error!!)
// After updating token values, execute same request with updated values.
val updatedRequest = getUpdatedRequest(request)
// Gives Signal to RELEASE Request Queue
countDownLatch.countDown()
//Execute updated request
return chain.proceed(updatedRequest)
} else {
// success case
return response
}
}
// Change updated token values in pending request objects and execute them!
// If Auth header exists, and skip header not found then hold the request
if (shouldHoldRequest(request)) {
try {
// Make this request to WAIT till countdown latch has been set to zero.
countDownLatch.await()
} catch (e: Exception) {
e.printStackTrace()
}
// Once token is Updated, then update values in request model.
if (previousAuthToken.isNotEmpty() && previousAuthToken != "newAccessToken") {
val updatedRequest = getUpdatedRequest(request)
return chain.proceed(updatedRequest)
}
}
return chain.proceed(request)
}
private fun handleError(error: ErrorDto) {
// update your token as per your error code logic
//Here it will make new API call to update tokens and store it in your local preference.
}
/***
* returns Request object with updated token values.
*/
private fun getUpdatedRequest(request: Request): Request {
var updateAuthReqBuilder: Request.Builder = request.newBuilder()
var url = request.url().toString()
if (url.contains(previousAuthToken.trim()) && previousAuthToken.trim().isNotEmpty()) {
url = url.replace(previousAuthToken, "newAccessToken")
}
updateAuthReqBuilder = updateAuthReqBuilder.url(url)
// change headers if needed
return updateAuthReqBuilder.build()
}
private fun shouldExecuteRequest(request: Request) =
shouldHoldRequest(request) && isSharedHoldSignalDisabled()
/**
* If count down latch has any value then it is reported by previous request's error signal to hold the whole pending chain.
*/
private fun isSharedHoldSignalDisabled() = countDownLatch.count == 0L
private fun shouldHoldRequest(request: Request) = !hasSkipFlag(request) && hasAuthorizationValues(request)
private fun hasAuthorizationValues(request: Request) = isHeaderExist(request, AUTHORIZATION_HEADER)
private fun hasSkipFlag(request: Request) = isHeaderExist(request, SKIP_AUTH_TOKEN)
private fun isHeaderExist(request: Request, headerName: String): Boolean {
return request.header(headerName) != null
}
private fun parseErrorModel(errorBody: String): Error? {
val parser = JsonParser()
// Change this logic according to your requirement.
val jsonObject = parser.parse(errorBody).asJsonObject
if (jsonObject.has("Error") && jsonObject.get("Error") != null) {
val errorJsonObj = jsonObject.get("Error").asJsonObject
return decodeErrorModel(errorJsonObj)
}
return null
}
private fun decodeErrorModel(jsonObject: JsonObject): Error {
val error = Error()
// decode your error object here
return error
}
}
This is how I do:
let isRefreshing = false;
let failedQueue = [];
const processQueue = (error, token = null) => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};
axios.interceptors.response.use(
response => response,
error => {
const originalRequest = error.config;
if (error.response.status === 400) {
// If response is 400, logout
store.dispatch(logout());
}
// If 401 and I'm not processing a queue
if (error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// If I'm refreshing the token I send request to a queue
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
})
.then(() => {
originalRequest.headers.Authorization = getAuth();
return axios(originalRequest);
})
.catch(err => err);
}
// If header of the request has changed, it means I've refreshed the token
if (originalRequest.headers.Authorization !== getAuth()) {
originalRequest.headers.Authorization = getAuth();
return Promise.resolve(axios(originalRequest));
}
originalRequest._retry = true; // mark request a retry
isRefreshing = true; // set the refreshing var to true
// If none of the above, refresh the token and process the queue
return new Promise((resolve, reject) => {
// console.log('REFRESH');
refreshAccessToken() // The method that refreshes my token
.then(({ data }) => {
updateToken(data); // The method that sets my token to localstorage/Redux/whatever
processQueue(null, data.token); // Resolve queued
resolve(axios(originalRequest)); // Resolve current
})
.catch(err => {
processQueue(err, null);
reject(err);
})
.then(() => {
isRefreshing = false;
});
});
}
return Promise.reject(error);
},
);
I don't know what is the schema of your token (after decrypted) but one of the attributes which is a good practice to keep is the exp "expiration_date".
Said so, having the expiration date you can know when you should refresh your token.
Without understanding your architecture is hard to inform the right solution. But let's say you are doing everything manually, usually onIdle/onActive is when we check if the user session is still ok, so at this time you could use the token info to know if you should refresh its value.
It is important to understand this process because the token should be refreshed only if the user is constantly active and it is about to expire (like 2min before).
Please refer to angular version of the code for which i was facing the same problem and after changing many approaches this was my final code which is working at its best.
Re Initaite the last failed request after refresh token is provided
With axios the code is:
export const createBlaBla = (payload) => {
return axios.post('/some-url', payload)
.then(response => response)
.catch(err => err);
}
And then I'm using this with redux-saga like this:
function* createBlaBlaFlow(action) {
try {
const response = yield call(createBlaBla, action.payload);
if (response) {
yield put({
type: CREATE_BLA_BLA_SUCCESS
});
}
} catch (err) {
// I need the error data here ..
yield put({
type: CREATE_BLA_BLA_FAILURE,
payload: 'failed to create bla-bla'
});
}
}
In case of some error on the backend - like invalid data send to the backend - it returns a 400 response with some data:
{
"code":"ERR-1000",
"message":"Validation failed because ..."
"method":"POST",
"errorDetails":"..."
}
But I don't receive this useful data in the catch statement inside the saga. I can console.log() the data in the axios catch statement, also I can get it inside the try statement in the saga, but it never arrives in the catch.
Probably I need to do something else? ... Or the server shouldn't return 400 response in this case?
So, I came up with two solutions of this problem.
===
First one - very dump workaround, but actually it can be handy in some specific cases.
In the saga, right before we call the function with the axios call inside, we have a variable for the errors and a callback that sets that variable:
let errorResponseData = {};
const errorCallback = (usefulErrorData) => {
errorResponseData = usefulErrorData;
};
Then - in the axios method we have this:
export const createBlaBla = (payload, errCallback) => {
return axios.post('/some-url', payload)
.then(response => response)
.catch(err => {
if (err && err.response.data && typeof errCallback === 'function') {
errCallback(err.response.data);
}
return err;
});
}
This way, when we make request and the backend returns errors - we'll call the callback and will provide the errors from the backend there. This way - in the saga - we have the errors in a variable and can use it as we want.
===
However, another solution came to me from another forum.
The problem I have is because in the method with the axios call I have catch, which means that the errors won't bubble in the generator. So - if we modify the method with the axios call like this:
export const createBlaBla = (payload) => {
return axios.post('/some-url', payload)
}
Then in the catch statement in the saga we'll have the actual backend error.
Hope this helps someone else :)
In your API call you can do the following:
const someAPICall = (action) => {
return axios.put(`some/path/to/api`, data, {
withCredentials: true,
validateStatus: (status) => {
return (status == 200 || status === 403);
}
});
};
Please note the validateStatus() part - this way when axios will encounter 200 or 403 response, it will not throw Error and you will be able to process the response after
const response = yield call(someAPICall, action);
if (response.status === 200) {
// Proceed further
} else if (response.status === 403) {
// Inform user about error
} else {
...
}
I'm building facebook chatbot using AWS Lambda and MongoDB. At the moment, my application is pretty simple but I'm trying to nail down the basics before I move onto the complex stuff.
I understand AWS Lambda is stateless but I've read adding below line in handler along with variables initialized outside handler, I don't have to establish DB connection on every request.
context.callbackWaitsForEmptyEventLoop = false;
(I've read this from this article; https://www.mongodb.com/blog/post/optimizing-aws-lambda-performance-with-mongodb-atlas-and-nodejs)
I'm adding my entire code below
'use strict'
const
axios = require('axios'),
mongo = require('mongodb'),
MongoClient = mongo.MongoClient,
assert = require('assert');
var VERIFY_TOKEN = process.env.VERIFY_TOKEN;
var PAGE_ACCESS_TOKEN = process.env.PAGE_ACCESS_TOKEN;
var MONGO_DB_URI = process.env.MONGO_DB_URI;
let cachedDb = null;
let test = null;
exports.handler = (event, context, callback) => {
var method = event.context["http-method"];
context.callbackWaitsForEmptyEventLoop = false;
console.log("test :: " + test);
if (!test) {
test = "1";
}
// process GET request --> verify facebook webhook
if (method === "GET") {
var queryParams = event.params.querystring;
var rVerifyToken = queryParams['hub.verify_token']
if (rVerifyToken === VERIFY_TOKEN) {
var challenge = queryParams['hub.challenge'];
callback(null, parseInt(challenge))
} else {
var response = {
'body': 'Error, wrong validation token',
'statusCode': 403
};
callback(null, response);
}
// process POST request --> handle message
} else if (method === "POST") {
let body = event['body-json'];
body.entry.map((entry) => {
entry.messaging.map((event) => {
if (event.message) {
if (!event.message.is_echo && event.message.text) {
console.log("BODY\n" + JSON.stringify(body));
console.log("<<MESSAGE EVENT>>");
// retrieve message
let response = {
"text": "This is from webhook response for \'" + event.message.text + "\'"
}
// facebook call
callSendAPI(event.sender.id, response);
// store in DB
console.time("dbsave");
storeInMongoDB(event, callback);
}
} else if (event.postback) {
console.log("<<POSTBACK EVENT>>");
} else {
console.log("UNHANDLED EVENT; " + JSON.stringify(event));
}
})
})
}
}
function callSendAPI(senderPsid, response) {
console.log("call to FB");
let payload = {
recipient: {
id: senderPsid
},
message: response
};
let url = `https://graph.facebook.com/v2.6/me/messages?access_token=${PAGE_ACCESS_TOKEN}`;
axios.post(url, payload)
.then((response) => {
console.log("response ::: " + response);
}).catch(function(error) {
console.log(error);
});
}
function storeInMongoDB(messageEnvelope, callback) {
console.log("cachedDB :: " + cachedDb);
if (cachedDb && cachedDb.serverConfig.isConnected()) {
sendToAtlas(cachedDb.db("test"), messageEnvelope, callback);
} else {
console.log(`=> connecting to database ${MONGO_DB_URI}`);
MongoClient.connect(MONGO_DB_URI, function(err, db) {
assert.equal(null, err);
cachedDb = db;
sendToAtlas(db.db("test"), messageEnvelope, callback);
});
}
}
function sendToAtlas(db, message, callback) {
console.log("send to Mongo");
db.collection("chat_records").insertOne({
facebook: {
messageEnvelope: message
}
}, function(err, result) {
if (err != null) {
console.error("an error occurred in sendToAtlas", err);
callback(null, JSON.stringify(err));
} else {
console.timeEnd("dbsave");
var message = `Inserted a message into Atlas with id: ${result.insertedId}`;
console.log(message);
callback(null, message);
}
});
}
I did everything as instructed and referenced a few more similar cases but somehow on every request, "cachedDb" value is not saved from previous request and the app is establishing the connection all over again.
Then I also read that there is no guarantee the Lambda function is using the same container on multiple requests so I made another global variable "test". "test" variable value is logged "1" from the second request which means it's using the same container but again, "cachedDb" value is not saved.
What am I missing here?
Thanks in advance!
In short AWS Lambda function is not a permanently running service of any kind.
So, far I know AWS Lambda works on idea - "one container processes one request at a time".
It means when request comes and there is available running container for the Lambda function AWS uses it, else it starts new container.
If second request comes when first container executes Lambda function for first request AWS starts new container.
and so on...
Then there is no guarantee in what container (already running or new one) Lambda function will be executed, so... new container opens new DB connection.
Of course, there is an inactivity period and no running containers will be there after that. All will start over again by next request.
I have created two actions on OpenWhisk on Bluemix. Both independently work fine when I can call them from outside the OpenWhisk platform. But I want to call action1 from within action2, and am using the following syntax:
var openwhisk = require('openwhisk');
function main(args){
const name = 'action2';
const blocking = true;
const params = { param1: 'sthing'};
var ow = openwhisk();
ow.actions.invoke({name, blocking, params})
.then(result => {
console.log('result: ', result);
return result; // ?
}).catch(err => {
console.error('failed to invoke actions', err);
});
}
But I get an empty result and no console messages. Some help would be great.
Update1:
When adding as suggested the return option, to return the Promise of OpenWhisk, as follows:
return ow.actions.invoke({name, blocking, params})
.then(result => {
console.log('result: ', result);
return result;
}).catch(err => {
console.error('failed to invoke actions', err);
throw err;
});
the response value of action2 is not as expected but contains:
{ "isFulfilled": false, "isRejected": false }
where I expect the return message of action2 (which reads a Google Sheets API) and parses the result:
{
"duration": 139,
"name": "getEventCfps",
"subject": "me#email.com",
...
"response": {
"result": {
"message": [
{
"location": "Atlanta, GA",
"url": "https://werise.tech/",
"event": "We RISE Women in Tech Conference",
"cfp-deadline": "3/31/2017",
...
}
]
},
"success": true,
"status": "success"
},
...
}
So I am expecting I am not parsing the '.then(result' variable in action1 correctly? cause when I test action2 separately, from outside OpenWhisk via Postman or API Connect, or directly by 'Run this action' in OpenWhisk/Bluemix it returns the correct values.
Update2:
Alright solved. I was calling the ow.actions.invoke to action2 in a function that was called within the action1, this nesting of returns, caused the issue. When I moved the invoke code directly in the main function, all resolved as expected. Double trouble when nesting promises and returns. Mea culpa. Thanks everyone
You need to return a Promise in your function try this
var openwhisk = require('openwhisk');
function main(args){
const name = '/whisk.system/utils/echo';
const blocking = true;
const params = { param1: 'sthing'};
var ow = openwhisk();
return ow.actions.invoke({name, blocking, params})
.then(result => {
console.log('result: ', result);
return result;
}).catch(err => {
console.error('failed to invoke actions', err);
throw err;
});
}
If you just want to invoke the action:
var openwhisk = require('openwhisk');
function main(args) {
var ow = openwhisk();
const name = args.action;
const blocking = false
const result = false
const params = args;
ow.actions.invoke({
name,
blocking,
result,
params
});
return {
statusCode: 200,
body: 'Action ' + name + ' invoked successfully'
};
}
If you want to wait for the result of the invoked action:
var openwhisk = require('openwhisk');
function main(args) {
var ow = openwhisk();
const name = args.action;
const blocking = false
const result = false
const params = args;
return ow.actions.invoke({
name,
blocking,
result,
params
}).then(function (res) {
return {
statusCode: 200,
body: res
};
});
}