Firebase Cloud Function - RangeError: Maximum call stack size exceeded - google-cloud-functions

I am trying to write an onCall Firebase Cloud Function that calls an external Zoho Desk API to return a list of support tickets.
But whenever I call my Firebase Cloud Function it returns this error: Unhandled error RangeError: Maximum call stack size exceeded.
Most of the other answers I have found are in relation to Firebase document snapshots, and they say it is caused by a infinite loop. But I'm not sure how to apply that knowledge to my external api call.
Here is the Cloud Function in question:
export const getZohoDeskTickets = functions
.region('us-central1')
.https.onCall(async (data, context) => {
// Check if it passed App Check
if (context.app == undefined) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called from an App Check verified app.'
);
}
// Check the authentication
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError(
'unauthenticated',
'The function must be called while authenticated.'
);
}
// Do the operation
const zohoAccessToken = await getSecretVersion(
'zoho-self-client-api-access-token'
).catch((error) => {
throw new functions.https.HttpsError('unknown', error.message, error);
});
return axios
.get('https://desk.zoho.com/api/v1/tickets', {
headers: {
orgId: '123456789',
Authorization: `Zoho-oauthtoken ${zohoAccessToken}`,
},
})
.catch(async (error) => {
functions.logger.error(error.response.data);
throw new functions.https.HttpsError(
'unknown',
error.response.data.message,
error.response.data
);
});
});
UPDATE:
I found this helpful thread which showed that sometimes the infinite loop is not coming from the Cloud Function but the caller, and also that the call stack can be helpful for debugging.
In the interest of that, here is my call stack as shown in the Firebase Emulator logs. It doesn't make sense to me because it's just the same line repeated again and again:
Unhandled error RangeError: Maximum call stack size exceeded
at TCP.get [as reading] (_tls_wrap.js:617:7)
at Function.entries (<anonymous>)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:157:37)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
And here is the Chrome browser console call stack:
postJSON # index.esm2017.js?6f1f:499
call # index.esm2017.js?6f1f:553
await in call (async)
eval # index.esm2017.js?6f1f:485
eval # SupportPage.vue?c3cc:37
eval # index.js??clonedRule…tup=true&lang=ts:15
__awaiter # index.js??clonedRule…tup=true&lang=ts:11
handleClick # SupportPage.vue?c3cc:36
callWithErrorHandling # runtime-core.esm-bundler.js?f781:155
callWithAsyncErrorHandling # runtime-core.esm-bundler.js?f781:164
emit$1 # runtime-core.esm-bundler.js?f781:718
eval # runtime-core.esm-bundler.js?f781:7232
onClick # QBtn.js?9c40:148
callWithErrorHandling # runtime-core.esm-bundler.js?f781:155
callWithAsyncErrorHandling # runtime-core.esm-bundler.js?f781:164
invoker # runtime-dom.esm-bundler.js?9cec:366
And here is the calling function inside my Vue app:
<template>
<v-btn design="alpha" #click="handleClick">loadData</v-btn>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import VBtn from 'src/components/VBtn.vue';
import { httpsCallable, FunctionsError } from 'firebase/functions';
import { functions } from 'src/config/firebase';
const data = ref();
const getZohoDeskTickets = httpsCallable(functions, 'getZohoDeskTickets');
const isFunctionsError = (error: unknown): error is FunctionsError => {
return (error as FunctionsError).details !== undefined;
};
const handleClick = async () => {
const ticket = await getZohoDeskTickets().catch((error) => {
if (isFunctionsError(error)) {
console.log(error.code);
console.log(error.message);
console.log(error.details);
} else {
console.log(error);
}
});
data.value = ticket;
console.log(ticket);
return ticket;
};
</script>
But even with that I still cannot figure this out.
What is causing the infinite loop?
Or maybe it is something else causing this error?

Finally got it!
The solution came from this answer.
In short; I needed to add a .then() onto the returned axios chain like so:
export const getZohoDeskTickets = functions
.region('us-central1')
.https.onCall(async (data, context) => {
// Check if it passed App Check
if (context.app == undefined) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called from an App Check verified app.'
);
}
// Check the authentication
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError(
'unauthenticated',
'The function must be called while authenticated.'
);
}
// Do the operation
const zohoAccessToken = await getSecretVersion(
'zoho-self-client-api-access-token'
).catch((error) => {
throw new functions.https.HttpsError('unknown', error.message, error);
});
return axios
.get('https://desk.zoho.com/api/v1/tickets', {
headers: {
orgId: '774638961',
Authorization: `Zoho-oauthtoken ${zohoAccessToken}`,
},
})
.then((response) => {
functions.logger.info(response.data);
return response.data;
})
.catch((error) => {
functions.logger.error(error.response.data);
throw new functions.https.HttpsError(
'unknown',
error.response.data.message,
error.response.data
);
});
});

Related

How can i stop further execution of express middleware

I want to get this functionality if(thereIsSomeError) //stop executing further. for example if there some error accurs in middleware or in the callback then i don't want to execute callback(in the app.route) and the middleware further
I tried this code. But i'm still getting req.err as true. how can i fix this issue
// My MiddleWare
export let Middleware=()=> {
return (req,res,next)=>{
next()
console.log(req.err) // Problem is here.. i'm still getting req.err(true)
if(!req.err){
db.query(`query`,(error, responseData)=>{
if(error) console.log(error)
db.query(`second query`,{...// send data to the
database})
})
}
}
}
//End point
app.post('/addStudent',Middleware, (req, res) => {
//setting error to true initially
req.err=true;
let data = req.body
db.query(`query `, data.username, (err, d) => {
if (err) return res.json(err)
else {
// since no Error accured so set the error to false
req.err=false;
let q = 'query';
let values = {//data here}
db.query(q, values, (err, data) => {
if (err) return res.status(200).json(err)
else return res.status(200).json({ data })
})
}
})
})
First, a middleware runs BEFORE a request, NOT AFTER. If you set req.err = true in your POST endpoint, IT WILL STAY TRUE, meaning your database call will certainly return an error.
Second, to successfully abort a middleware call, use return. Returning a function stops it immediately. You can choose either to return next(err) to forward the error to the handler, or to use return res.send('Error') to terminate the response in the middleware.

Javascript - Return json from fetch in an Object

I'm trying to make an application to get the recipes from https://edamam.com and I'm using fetch and Request object.
I need to make 3 request, and i thought that most beautiful way for do it is make an Object and a method that return the data in JSON.
I declarated into constructor a variable called this.dataJson, and i want to save there the data in JSON from the response. For that purpose i use this.
The problem is that i have a undefined variable.
.then( data => {this.dataJson=data;
console.log(data)} )
This is all my code.
class Recipe{
constructor(url){
this.url=url;
this.dataJson;
this.response;
}
getJson(){
var obj;
fetch(new Request(this.url,{method: 'GET'}))
.then( response => response.json())
.then( data => {this.dataJson=data;
console.log(data)} )
.catch( e => console.error( 'Something went wrong' ) );
}
getData(){
console.log("NO UNDFEIND"+this.dataJson);
}
}
const pa= new Recipe('https://api.edamam.com/search?...');
pa.getJson();
pa.getData();
I'm new studying OOP in JS and more new in Fetch requests...
If you guys can help me... Thanks very much!
Here's a solution using async-await (and a placeholder API):
class Recipe {
constructor(url) {
this.url = url;
this.dataJson;
this.response;
}
// the async keyword ensures that this function returns
// a Promise object -> we can use .then() later (1)
async getJson() {
try {
const response = await fetch(new Request(this.url, {
method: 'GET'
}))
const json = await response.json()
this.dataJson = json
} catch (e) {
console.error('Something went wrong', e)
}
}
getData() {
console.log("NO UNDFEIND:", this.dataJson);
}
}
const pa = new Recipe('https://jsonplaceholder.typicode.com/todos/1');
// 1 - here we can use the "then", as pa.getJson() returns
// a Promise object
pa.getJson()
.then(() => {
pa.getData()
});
If we want to stay closer to your code, then:
class Recipe {
constructor(url) {
this.url = url;
this.dataJson;
this.response;
}
getJson() {
// var obj; // not needed
// the "fetch" always returns a Promise object
return fetch(new Request(this.url, { // return the fetch!
method: 'GET'
}))
.then(response => response.json())
.then(data => {
this.dataJson = data;
// console.log(data) // not needed
})
.catch(e => console.error('Something went wrong'));
}
getData() {
console.log("NO UNDFEIND:", this.dataJson); // different syntax here
}
}
const pa = new Recipe('https://jsonplaceholder.typicode.com/todos/1');
// using "then", because the "fetch" returned a Promise object
pa.getJson()
.then(() => {
pa.getData();
});
The problem with your original code is that you initiate the request (pa.getJson()) and then immediately (on the next line) you want to read the data (pa.getData()). pa.getData() is called synchronously (so it happens in milliseconds), but the request is asynchronous - the data needs time to arrive (probably hundreds of milliseconds) - so, it's not there when you try to read it (it simply hasn't arrived yet).
To avoid this you have to use a technique to handle this asynchronous nature of the request:
use a callback function (blee - so last decade)
use a Promise object with then() (much better) or async-await (yeee!)
and call the pa.getData() when the response has arrived (inside the callback function, in the then() or after awaiting the result).

The action did not produce a valid response and exited unexpectedly

I want to call a Node-RED flow from IBM Cloud Functions.
const https = require('https');
function main(params) {
const path = "/" + params.route + "?" + params.query_params ;
const options = {
hostname: params.hostname,
path: path,
port: 443,
method: 'GET'
};
return new Promise((resolve, reject) => {
https.get(options, (resp) => {
resp.on('data', (d) => {
let s = d.toString();
obj = JSON.parse(s);
resolve({ "gw_result": obj })
});
});
})
}
In the Node-RED flow I'm using a HTTP request to get data from another server. For test purposes I used a GET request to google.com but have same results using another Node-RED endpoint.
As soon as I invoke the web action I get the error message "The action did not produce a valid response and exited unexpectedly". The output of the Node-RED flow appears some seconds later in the web action's log although the Node-RED flow works properly and promptly (I used debug Node-RED debug nodes to check this).
The https GET request to Node-RED works well when I replace the http request in Node-RED by something else, e.g. a Function node, even when I use a Delay node to delay the response for a second or so.
This code works, although google.com does not return an object, of course.
var rp = require('request-promise');
function main(params) {
var uri = params.hostname + params.route + params.query_params
return new Promise(function (resolve, reject) {
rp(uri)
.then(function (parsedBody) {
obj = JSON.parse(parsedBody);
resolve({ "gw_result": obj
});
})
.catch(function (err) {
resolve({ message: 'failed!!', error: err.toString() });
});
});
}

Error handler ignored when NODE_ENV=production

I am building a simple REST API with Node/Express, and I'm having a hard time when I deploy it to production. When NODE_ENV=development, everything works as expected. I get back the JSON error and the correct status code. When NODE_ENV=production, I only get back an HTML page with the default error message and nothing else. I can read the status code, but I need to have access to the full JSON payload to identify the errors better. This is my code:
import Promise from 'bluebird'; // eslint-disable-line no-unused-vars
import express from 'express';
import config from './config';
import routes from './routes';
import { errorMiddleware, notFoundMiddleware } from './middlewares/error.middleware';
import mongoose from './config/mongoose.config';
// create app
const app = express();
(async () => {
// connect to mongoose
await mongoose.connect();
// pretty print on dev
if (process.env.NODE_ENV !== 'production') {
app.set('json spaces', 2);
}
// apply express middlewares
app.use(express.json());
// register v1 routes
app.use('/v1', routes);
// catch errors
app.use(notFoundMiddleware);
app.use(errorMiddleware);
// start server
app.listen(config.port, () => console.info(`server started on port ${config.port}`));
})();
export default app;
This is the notFoundMiddleware:
export default (req, res, next) => next(new Error('Not Found'));
This is the errorMiddleware:
const errorMiddleware = (err, req, res, next) => {
console.log('test'); // this works in development, but not in production
const error = {
status: err.status,
message: err.message
};
if (err.errors) {
error.errors = err.errors;
}
if (process.env.NODE_ENV !== 'production' && err.stack) {
error.stack = err.stack;
}
return res.status(error.status || 500).send({ error });
};
If you are runing on production server, try to use some logging provider like "papertrailapp" to see the error occurs in your app.
I've just stumbled upon the same problem. It turned out it's caused by a transpiler optimization applied when building production bundle - this one: https://babeljs.io/docs/en/babel-plugin-minify-dead-code-elimination
Express' error handlers should have the signature (err, req, res, next) => { ... } (be of arity 4). In your example next is not used anywhere in errorMiddleware function body and thus it gets eliminated (optimized-out) from function signature in production code.
Solution:
use keepFnArgs: true plugin option - possibly through https://webpack.js.org/plugins/babel-minify-webpack-plugin/ webpack configuration:
var MinifyPlugin = require("babel-minify-webpack-plugin")
module.exports = {
// ...
optimization: {
minimizer: [
new MinifyPlugin({
deadcode: {
keepFnArgs: true,
},
}, {}),
],
}
// ...
}
or alternatively pretend in your code that this argument is used:
const errMiddleware = (err, req, res, _next) => {
// ... your code ...
// ...
// cheat here:
_next
}

FeathersJS Error Handler Unexpected Token < Issue

I am working on an Express App with MongoDB and trying to utilize FeathersJS for all my services. Here I'm running a test try to get an error message from the server to the client, but I have an issue with the response from the error handler. My req headers have the correct application/json stuff, so I assumed the Error Handler should send valid json back.
I know I'm not using the next callback in my function, but when I try to do that it gives the same error, so I'm thinking it has to do with the Error Handler. Any direction here would be greatly appreciated!
The first error log is on the server, which is correct.
Bucket Services
error >>>>> Bucket validation failed
Possibly Unhandled Rejection: Bucket validation failed, Promise { <rejected> 'Bucket validation failed' }
>>>>>> Error: Unexpected token < in JSON at position 0
at convert (/Users/jaruesink/Documents/Projects/Buckets/node_modules/feathers-rest/node_modules/feathers-errors/lib/index.js:365:79)
at toError (/Users/jaruesink/Documents/Projects/Buckets/node_modules/feathers-rest/lib/client/base.js:24:37)
at process._tickCallback (internal/process/next_tick.js:103:7)
my create function within the BucketService class:
create({
amount,
isFund = false,
name,
type,
userID: owner
}, params, next) {
const new_bucket = new Bucket({ name, amount, type, isFund, owner });
return new_bucket.save((error) => {
console.log('error >>>>>', error.message);
if (error) { return Promise.reject(error.message); }
return Promise.resolve(new_bucket);
});
}
my router file:
const feathers = require('feathers');
const errorHandler = require('feathers-errors/handler');
const rest = require('feathers-rest');
const router = feathers();
const LoginService = require('../services/login_service');
const UserService = require('../services/user_service');
const BucketService = require('../services/bucket_service');
// Enable REST services
router.configure(rest());
router.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
router.use('/login', new LoginService());
router.use('/user', new UserService());
router.use('/bucket', new BucketService());
// Set up error handling
router.use(errorHandler());
module.exports = router;
I figured it out, the key was to correctly pass through a callback (next) function as the third parameter to handle errors. FeathersJS handles the Promise Rejections for you on errors. Then in my test I needed to convert the Feathers-Error to JSON before I could get the message.
I changed my test to:
it('can validate an incorrect bucket', (done) => {
const invalid_bucket = {
name: 'Invalid Bucket',
};
bucket_service.create(invalid_bucket, {}, (error) => {
error = error.toJSON();
assert(error.message.length > 0);
done();
});
});
and my create function to:
create({
amount,
isFund = false,
name,
type,
userID: owner
}, params, next) {
const new_bucket = new Bucket({ name, amount, type, isFund, owner });
return new_bucket.save()
.then(created_bucket => Promise.resolve(created_bucket))
.catch(next);
}