FeathersJS Error Handler Unexpected Token < Issue - json

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);
}

Related

Firebase Cloud Function - RangeError: Maximum call stack size exceeded

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
);
});
});

Getting an error when using methods.myMethod.call() with web3js

I am getting an error trying to call an existing smart contract function using call().
The error is "Returned values aren't valid, did it run Out of Gas? You might also see this error if you are not using the correct ABI for the contract you are retrieving data from, requesting data from a block number that does not exist, or querying a node which is not fully synced." My code is below
let url = 'https://api.etherscan.io/api?module=contract&action=getabi&address=0x672C1f1C978b8FD1E9AE18e25D0E55176824989c&apikey=<api-key>';
request(url, (err, res, body) => {
if (err) {
console.log(err);
}
let data = JSON.parse(body);
let contract_abi = JSON.parse(data.result);
let contract_address = '0x672C1f1C978b8FD1E9AE18e25D0E55176824989c';
const contract = new web3.eth.Contract(contract_abi, contract_address);
contract.methods.totalSupply().call()
.then(result => {
console.log('result', result);
}).catch(err => {
console.log('error: ', err);
})
})
When I execute the same function using send() it works, however I need the return value of the function which is why I want to use call(). I am using ganache to set up a local test network which is working fine. Thanks!

Stripe Error Message 405 - "append .json to uri to use rest api"

I'm using Stripe, and trying to send a test webhook to my URL and database hosted by Firebase. When I "send test webhook," I get the following error message in the Stripe Console:
Test Webhook Error: 405
"append .json to your request URI to use the rest API"
My code is a direct copy of the tutorial: https://github.com/GaryH21/Stripe-Webhooks-Tutorial/blob/master/functions/index.js
Here is the code of my index.js:
const functions = require('firebase-functions');
const stripe = require("stripe")(functions.config().keys.webhooks);
const admin = require('firebase-admin')
admin.initializeApp();
const endpointSecret = functions.config().keys.signing;
exports.events = functions.https.onRequest((request, response) => {
let sig = request.headers["stripe-signature"];
try {
let event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret)
return admin.database().ref('/events').push(event)
.then((snapshot) => {
return response.json({ received: true, ref: snapshot.ref.toString() })
})
.catch((err) => {
console.error(err)
return response.status(500).end() // error saving to database
})
} catch (err) {
return response.status(400).end() // signing signature failed
}
})
exports.exampleDataBaseTrigger = functions.database.ref('/events/{eventId}').onCreate((snapshot, context) => {
return console.log({
eventId: context.params.eventid,
data: snapshot.val()
})
})
The only time in the tutorial and in my code that .json is used is in the line: return response.json({ received: true, ref: snapshot.ref.toString() })
Should I be appending .json onto "request" somewhere, such as in request.RawBody?
It isn't a problem with the signing keys, as that would give the 400 Error message, which I already dealt with and fixed.
I would be happy to share the code of other files in my app, but as far as I can tell none of the rest is relevant to the problem. Thank you very much.

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() });
});
});
}

Node Function not returning JSON object

This is my Library function file named verifyToken.js
require('dotenv').config();
const CognitoExpress = require("cognito-express");
//Initializing CognitoExpress constructor
const cognitoExpress = new CognitoExpress({
cognitoUserPoolId: process.env.USER_POOL_ID, // User Pool id (created via Console->Cognito Manage User Pool)
region: process.env.REGION, //User Pool Region
tokenUse: "id", //Possible Values: access | id
tokenExpiration: 3600000 //Up to default expiration of 1 hour (3600000 ms)
});
//Our middleware that authenticates all APIs under our 'authenticatedRoute' Router
module.exports = function(){
this.verifyToken=(function(token, req, res) {
//I'm passing in the id token in header under key Token
let TokenFromClient = token;
//Fail if token not present in header.
if (!TokenFromClient) return "Token missing from header";
cognitoExpress.validate(TokenFromClient, function(err, response) {
//If API is not authenticated, Return 401 with error message.
if (err) {
return res.send({success:false, msg:"Token Authenication Error", Error:err});
}
//Else API has been authenticated. Proceed.
return res.send({success:true, msg:"Token Authenication result", result: response});
});
});
}
I am calling this function from controller
var verify_tokenController = {};
//Verify AWS token recieved through client header by calling verifyToken Library function
verify_tokenController.verify = function(req, res) {
//Import verifyToken Library function
require('../library/verifyToken')();
var auth = verifyToken(req.header('Token'), req, res);
console.log(auth);
};
module.exports = verify_tokenController;
But I am always receiving undefined at console. How to return err or response back to the calling controller. Both of the return variable are JSON object type.
It is becuase cognitoExpress.validate is an async operation. it will run and the callback of this function will run. But by the time this all happens. Your following line
console.log(auth); gets hit already and you see undefined becuase you are actually not returning anything from verifyToken method at that time.
This how javascript and async operations work.
You can either use the callback approach but that's not what I would recommend. So I'm modifying your code with promises and see if it works for you.
Your verifyToken.js should look as follow:
require('dotenv').config();
const CognitoExpress = require("cognito-express");
//Initializing CognitoExpress constructor
const cognitoExpress = new CognitoExpress({
cognitoUserPoolId: process.env.USER_POOL_ID, // User Pool id (created via Console->Cognito Manage User Pool)
region: process.env.REGION, //User Pool Region
tokenUse: "id", //Possible Values: access | id
tokenExpiration: 3600000 //Up to default expiration of 1 hour (3600000 ms)
});
//Our middleware that authenticates all APIs under our 'authenticatedRoute' Router
module.exports = function(){
this.verifyToken=(function(token, req, res) {
return new Promise(function(resolve, reject) {
//I'm passing in the id token in header under key Token
let TokenFromClient = token;
//Fail if token not present in header.
if (!TokenFromClient) return "Token missing from header";
cognitoExpress.validate(TokenFromClient, function(err, response) {
//If API is not authenticated, Return 401 with error message.
if (err) {
reject(err);
} else {
//Else API has been authenticated. Proceed.
resolve(response);
}
});
});
});
}
Your controller should look like as follow:
var verify_tokenController = {};
//Verify AWS token recieved through client header by calling verifyToken Library function
verify_tokenController.verify = function(req, res) {
//Import verifyToken Library function
require('../library/verifyToken')();
verifyToken(req.header('Token'), req, res)
.then(function(response){
console.log(resposne, 'response')
res.send({success:true, msg:"Token Authenication result", result: response});
})
.catch(function(err) {
res.send({success:false, msg:"Token Authenication Error", Error:err});
});
};
module.exports = verify_tokenController;
Try the above solution.