Firebase functions onRequest to onCall - javascript

How do I pass this onRequest function to onCall? I am working from my localhost with emulators. Could someone give me some guidance, I have tried to follow the documentation of functions.https.onCall but I can't understand if I have to do any previous step.
export const getFileInformation = functions.https.onRequest( (req, res) => {
return cors( req, res, () => {
const urls = [
`url1`,
`url2`,
`url3`
];
const urlsCalls: any[] = [];
const resultados: any[] = [];
urls.forEach( url => {
urlsCalls.push(axios.get(url));
});
Promise.allSettled(urlsCalls)
.then( response => {
response.map( (element: any) => {
const item = element.value.data;
resultados.push(item);
});
console.log(resultados);
res.json(resultados);
})
.catch( error => {
console.log(error);
});
} );
});
I'm trying something as simple as this:
export const getFileInformation2 = functions.https.onCall( (data, context) => {
return { msg: 'Hello from Firebase!' };
});
But I get the following error:
{"error":{"message":"Bad Request","status":"INVALID_ARGUMENT"}}
How should I address an onCall function?

HTTPS Callable functions must be called using the POST method, the Content-Type must be application/json, and the body must contain a field called data for the data to be passed to the method.
See sample body:
{
"data": {
"someKey": "someValue"
}
}
When pasting the URL directly to a browser it is called using the GET method which returns an error Request has invalid method. GET and the reason you're getting {"error":{"message":"Bad Request","status":"INVALID_ARGUMENT"}} is you're not passing the required field which is data on your request body.
You can call a https.onCall using curl or Postman.
Sample call using curl:
curl -d '{"data": {"someKey": "someValue"}}' -H 'Content-Type: application/json' http://localhost:5001/[myProjectName]/us-central1/getFileInformation2
For more information, you may check :
Protocol specification for https.onCall

Lo resolví asi:
index.ts
export const getFileInformation2 = functions.https.onCall( (data, context) => {
const urls = [
`url1`,
`url2`,
`url3`
];
const urlsCalls: any[] = [];
const resultados: any[] = [];
urls.forEach( url => {
return urlsCalls.push(axios.get(url));
});
return Promise.allSettled(urlsCalls)
.then( response => {
response.map( (element: any) => {
const item = element.value.data;
resultados.push(item);
});
console.log(resultados);
return resultados
})
.catch( error => {
console.log(error);
});
});
Luego ejecuto emulators:
firebase emulators:start --only functions
Luego lo llamo desde mi frontEnd:
component.ts
getFilesInformation() {
const functions = getFunctions(getApp());
connectFunctionsEmulator(functions, 'localhost', 5001);
const getInfo = httpsCallable(functions, 'getFileInformation2')
getInfo().then( (result: any) => {
console.log(result.data);
})
}

Related

Test onCall https cloud function locally

I have this call export const notificationsCall = functions.https.onCall((data, context) => { }
When I deploy it and call it from my iOS app functions.httpsCallable("getNotifications").call() { (result, error) in } everything works fine and I do get proper response.
However when I run firebase emulators:start --only functions and navigate to the given url http://localhost:5001/post-dach/us-central1/getNotifications
All I get is this result.
I'm using Postman and am doing a Post request with
'Content-Type': 'application/json'
Body
{
"data": {
"aString": "some string"
}
}
Response
{
"result": null
}
Also when I try to log something in the terminal, it does not appear and all I get is a big log from firebase.
Any idea what I'm doing wrong here?
My function
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import { Notification, Request } from '../models'
const db = admin.firestore();
export const notificationsCall = functions.https.onCall((data, context) => {
const notificationsQuery = db.collection('notifications')
const notifications: Notification[] = [];
return notificationsQuery.get().then(snapshot => {
const requestsRef: FirebaseFirestore.DocumentReference[] = [];
const notIndexes: number[] = [];
snapshot.forEach((doc) => {
notifications.push(doc.data() as Notification);
});
notifications.forEach((notification, index) => {
if (notification.requestReference) {
requestsRef.push(notification.requestReference);
notIndexes.push(index);
}
});
return db.getAll(...requestsRef);
}).then((requests) => { // kthen requests
const packagesRef: FirebaseFirestore.DocumentReference[] = [];
requests.forEach((req, index) => {
const requestt = req.data() as Request
packagesRef.push((requestt).packageReference)
notifications[index].request = requestt
})
return db.getAll(...packagesRef)
}).then(packages => {
packages.forEach((pkg, index) => {
notifications[index].request.package = pkg;
})
return { notifications: notifications };
}).catch(err => { err })
});

Why does my update in a put request overwrite the whole record in Sequelize?

I'm trying to make a "edit" feature for my project, and I'm stuck at this part..
I have a put request :
export const updateEvent = (event, id) => (dispatch, getState) => {
request
.put(`${baseUrl}/event/${id}`)
.send(event)
.then(response => {
dispatch(updatedEvent(response.body))
})
.catch(error => console.log(error))
}
This is the route for the said put, with Sequelize as ORM:
router.put('/event/:id', async (req, res, next) => {
const { id } = req.params
try {
const event = await Event.findByPk(id)
const updatedEvent = await event.update(req.body)
res.send(updatedEvent)
} catch (error) {
next(error)
}
})
When I test it with postman, everything works as expected. Where I ran into my problem is when I'm sending the put data from React in the frontend.
I have a form, and I save my data in the local state, and then dispatch it to actions like this:
handleSubmit = e => {
e.preventDefault()
const id = this.props.event.id
const updatedEvent = {
name: this.state.name,
description: this.state.description,
picture: this.state.picture,
startDate: this.state.startDate,
endDate: this.state.endDate,
userId: this.props.userId
}
this.props.updateEvent(updatedEvent, id)
}
Any value that is left empty in the form is overwriting my fields with nothing (an empty string). How do I properly handle this?
A solution is to filter your object, such that you remove any properties which have empty values and therefore won't be included in the database update.
In your router.put():
router.put('/event/:id', async (req, res, next) => {
const { id } = req.params
try {
const event = await Event.findByPk(id);
// filter req.body to remove empty values
const { body } = req;
const filteredBody = Object.keys(body).reduce((resultObj, key) => {
if(body[key] != ''){
resultObj[key] = body[key];
}
return resultObj;
}, {});
const updatedEvent = await event.update(filteredBody);
res.send(updatedEvent)
} catch (error) {
next(error)
}
})

Making multiple Axios requests causing CORS errors

I have a HTTP POST request I'm making from the frontend using Axios to the Firebase Functions backend. I want to be able to send two requests at the same time to call two functions, createEmaileList and zohoCrmHook. The problem is, when I make a request to both functions at the same time, it gives me the CORS error. When I make a request to individual function, they work perfectly fine. How do I make a request to multiple functions at the same time?
Following is the frontend:
const handleSubmit = e => {
setLoading(true)
e.preventDefault()
axios.all([
axios.post(`${ROOT_URL}/createEmailList`, {
email,
firstName,
lastName
}),
axios.post(`${ROOT_URL}/zohoCrmHook`, {
email,
firstName,
lastName
})
])
.then(axios.spread((emailRes, crmRes) => {
if(emailRes.status===200 || emailRes.status===204 || crmRes.status===200 || crmRes.status===204 || crmRes.status===201){
setLoading(false)
closeModal()
}
}))
.catch(err=> console.log(err));
}
The backend index.js is as follows:
const functions = require('firebase-functions');
const admin = require("firebase-admin")
const serviceAccount = require("./service_account.json");
const createEmailList = require('./createEmailList')
const zohoCrmHook = require('./zohoCrmHook')
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://landing-page.firebaseio.com"
})
exports.zohoCrmHook = functions.https.onRequest(zohoCrmHook)
exports.createEmailList = functions.https.onRequest(createEmailList)
I've imported the cors module and implemented the function as following, but it still only works individually and not both at the same time
createEmailList.js
const admin = require('firebase-admin')
const cors = require('cors')({ origin: true })
module.exports = (req, res) => {
cors(req, res, () => {
if (!req.body.email) {
return res.status(422).send({ error: 'Bad Input'})
}
const email = String(req.body.email)
const firstName = String(req.body.firstName)
const lastName = String(req.body.lastName)
const data = {
email,
firstName,
lastName
}
const db = admin.firestore()
const docRef = db.collection('users')
.doc(email)
.set(data, { merge: false })
.catch(err => res.status(422).send({ error: err }))
return res.status(204).end();
})
}
zohoCrmHook.js
const axios = require('axios');
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true })
// zoho
const clientId = functions.config().zoho.client_id;
const clientSecret = functions.config().zoho.client_secret;
const refreshToken = functions.config().zoho.refresh_token;
const baseURL = 'https://accounts.zoho.com';
module.exports = (req, res) => {
cors(req, res, async () => {
const newLead = {
'data': [
{
'Email': String(req.body.email),
'Last_Name': String(req.body.lastName),
'First_Name': String(req.body.firstName),
}
],
'trigger': [
'approval',
'workflow',
'blueprint'
]
};
const { data } = await getAccessToken();
const accessToken = data.access_token;
const leads = await getLeads(accessToken);
const result = checkLeads(leads.data.data, newLead.data[0].Email);
if (result.length < 1) {
try {
return res.json(await createLead(accessToken, newLead));
} catch (e) {
console.log("createLead error", e);
}
} else {
return res.json({ message: 'Lead already in CRM' })
}
})
}
Update
I've also tried combining the two Firebase Functions into one as following:
exports.myWebHook = functions.https.onRequest(async (req, res) => {
createEmailList(req, res)
zohoCrmHook(req, res)
})
and converting the frontend axios request into one:
const handleSubmit = e => {
setLoading(true)
e.preventDefault()
axios.post(`${ROOT_URL}/myWebHook`, {
email,
firstName,
lastName
})
.then(res => {
if(res.status===200 || res.status===204){
setLoading(false)
closeModal()
}
})
.catch(err=> console.log(err));
}
But, it still gives the same CORS error:
Access to XMLHttpRequest at
'https://us-landing-page.cloudfunctions.net/myWebHook'
from origin 'https://www.website.com' has been blocked by CORS
policy: Response to preflight request doesn't pass access control
check: Redirect is not allowed for a preflight request.
Update2
I've tried to incorporating the CORS module in index.js as following and removed the CORS module from both of the functions.
exports.myWebHook = functions.https.onRequest((req, res) => {
cors(req, res, async () => {
zohoCrmHook(req, res)
createEmailList(req, res)
})
})
Now, the axios request to the server doesn't incur any CORS errors and the myWebHook function gets invoked with no issues, but the neither of zohoCrmHook nor createEmailList function gets invoked.
If we look at the code we see that CORS in not only imported but also invoked with an options object. So I think it is instantiating CORS twice and setting multiple times the same CORS headers, which is known to cause issues.
const cors = require('cors')({ origin: true })
My suggestion is, to instantiate CORS once in the entry point and add the functions as resources to the same CORS instance.

Can "ERROR Unhandled Promise Rejection" be a problem in aws lambda?

Can this anyhow in the feature damage the flow they belong to?
I have a lambda that works behind a API Gateway websocket endpoint.
This simply asks for a clientId and a message payload, query all connections on dynamo for that clientId (multi device realtime dashboard frontend) and updates all interested users.
It's working fine if you test trought "wscat" on command line but it is buggy on real world browser using js websocket api or c# websocket api.
Doest this exceptin has anything to do with it?
const AWS = require("aws-sdk");
let dynamo = new AWS.DynamoDB.DocumentClient();
require("aws-sdk/clients/apigatewaymanagementapi");
const ORDERS_TABLE = "ordersTable";
const successfullResponse = {
statusCode: 200,
body: "everything is alright"
};
module.exports.sendMessageHandler = (event, context, callback) => {
console.log(event);
sendMessageToAllConnectedClientDevices(event)
.then(data => {
console.log("sucesso", data);
callback(null, successfullResponse);
})
.catch(err => {
console.log("erro: ", err);
callback(null, JSON.stringify(err));
});
};
const sendMessageToAllConnectedClientDevices = async event => {
try {
const body = JSON.parse(event.body);
const { clientId } = body;
console.log(
"handler.sendMessageToAllConnectedClientDevices.clientId: ",
clientId
);
const connectionIds = await getConnectionIds(clientId);
return await Promise.all(
connectionIds.Items.map(connectionId => {
send(event, connectionId.connectionId);
})
);
} catch (error) {
console.log("erro sendMessageToAllConnectedClientDevices");
return error;
}
};
const getConnectionIds = async clientId => {
console.log("handler.getConnectionIds.clientId: ", clientId);
const params = {
TableName: ORDERS_TABLE,
// IndexName: "client_gsi",
FilterExpression: "clientId = :cliend_id",
// KeyConditionExpression: "clientId = :cliend_id",
ProjectionExpression: "connectionId",
ExpressionAttributeValues: {
":cliend_id": clientId
}
};
console.log("handler.getConnectionIds.params: ", JSON.stringify(params));
const data = await dynamo.scan(params).promise();
return data;
};
const send = async (event, connectionId) => {
const body = JSON.parse(event.body);
const postData = body.data;
const endpoint =
event.requestContext.domainName + "/" + event.requestContext.stage;
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: "2018-11-29",
endpoint: endpoint
});
const params = {
ConnectionId: connectionId,
Data: postData
};
return await apigwManagementApi.postToConnection(params).promise();
};
ERROR Unhandled Promise Rejection
I think problem is with API Gateway, check how you are handling information passing through to Lambda function (because browser sends some extra information as compared to command line call)

React Native - Invariant Violation: Objects are not valid as a React Child (Using Node JS for backend)

here I'm trying to have the sum of orders and the sum of their quantity in which I use Node JS for my backend. The problem is whenever I run my code -- my fetch functions seems not working properly or I'm missing something that I'm not aware.
But using postman, my API is working with the expected output. Buuut if I use it in my react-native code it show some errors.
Here's my code for backend:
OrderNo.js (models) //Backend
var Task = {
Sum:function(id,callback) {
return db.query("SELECT SUM(order_amount) AS TotalAmountOrdered FROM orders where order_no=?",[id],callback);
},
}
OrderNo.js (router) //Backend
var Task = require('../models/OrderNo');
router.get('/ForSum/:id?', (req, res, next) => {
Task.Sum(req.params.id,function(err,rows) {
if(err) {
res.json(err);
}
else {
res.json(rows);
}
});
});
NumOrder.js (models) //Backend
var Task = {
NumOrder:function(id,callback) {
return db.query("SELECT SUM(order_quantity) AS TotalItemsOrdered FROM orders where order_no=?",[id],callback);
},
}
NumOrder.js (router) //Backend
var Task = require('../models/NumOrder');
router.get('/num/:id?', (req, res, next) => {
Task.NumOrder(req.params.id,function(err,rows) {
if(err) {
res.json(err);
}
else {
res.json(rows);
}
});
});
And here's my code for React-Native
export default class Settlement extends Component {
constructor(props){
super(props)
this.state = {
orderDet: this.props.navigation.state.params.orderDet,
numOrder: [],
TotalSum: [],
};
}
fetchSum = async () => {
const response = await fetch("http://192.168.254.104:3308/OrderNo/ForSum/" + this.state.orderDet)
const json = await response.json()
this.setState({ TotalSum: json })
}
fetchNumOrders = async () => {
const response = await fetch("http://192.168.254.104:3308/NumOrder/num/" + this.state.orderDet )
const json = await response.json()
this.setState({ numOrder: json })
}
componentDidMount() {
this.fetchNumOrders();
this.fetchSum();
}
render() {
return (
<View>
<Text>Number of Orders: { this.state.numOrder }</Text>
<Text>Total Amount: ₱{ this.state.TotalSum }</Text>
</View>
)
}
}
And here is my DB
**PS: **I also tried " json[0].order_no " on each of my fetch function and there's no error, but my output is empty.
Based on your response object in the Postman, you need to do the following
this.state = {
orderDet: this.props.navigation.state.params.orderDet,
numOrder: null,
TotalSum: null,
};
fetchSum = async () => {
const response = await fetch("http://192.168.254.104:3308/OrderNo/ForSum/" + this.state.orderDet)
const json = await response.json()
this.setState({ TotalSum: json[0].TotalAmountOrdered })
}
fetchNumOrders = async () => {
const response = await fetch("http://192.168.254.104:3308/NumOrder/num/" + this.state.orderDet )
const json = await response.json()
this.setState({ numOrder: json[0].TotalItemsOrdered })
}
The error means that you cannot have object or array as the child the component i.e. <Text>. You can only have string or number displayed inside the component.
<Text>Number of Orders: { this.state.numOrder[0].TotalAmountOrdered }</Text>// Inside {} value of variable should be string or number not array or object
The error is that you are setting value of this.state.numOrder an array

Categories