I'm trying to save data into Firebase storage.
Generally, my method and function works, in FireBase logs I get:
Function execution took 1442 ms, finished with status code: 201
alert15
alert14: null||[object Object]
alert12:
alert11:
Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove
these restrictions
Function execution started
My function:
const functions = require('firebase-functions');
const cors = require("cors")({origin: true});
const fs = require("fs");
const UUID = require("uuid-v4");
const gcconfig = {
projectId: "myrojectid",
keyFilename: "mykeyfile.json"
};
const gcs = require("#google-cloud/storage")(gcconfig);
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
exports.storeImage = functions.https.onRequest((request, response) => {
console.log("alert11: ")
cors(request, response, () => {
console.log("alert12: ")
const body = JSON.parse(request.body);
fs.writeFileSync("/tmp/uploaded-image.jpg", body.image, "base64", err => {
console.log(err => console.log("alert13: " + err));
return response.status(500).json({error: err})
});
const bucket = gcs.bucket("myapp.appspot.com");
const uuid = UUID();
bucket.upload(
"/tmp/uploaded-image.jpg",
{
uploadType: "media",
destination: "/places2/" + uuid + ".jpg",
metadata: {
metadata: {
contentType: "image/jpeg",
firebaseStorageDownloadTokens: uuid
}
}
}, (err, file) => {
console.log("alert14: " + err + "||" + file)
if (!err) {
console.log("alert15");
response.status(201).json({
imageUrl:
"https://firebasestorage.googleapis.com/v0/b/" +
bucket.name +
"/o/" +
encodeURIComponent(file.name) +
"?alt=media&token=" +
uuid
})
} else {
console.log("alert16: ")
console.log(err);
response.status(500).json({error: err})
}
});
});
});
My method:
import {ADD_PLACE, DELETE_PLACE} from './actionTypes';
export const addPlace = (placeName, location, image) => {
return dispatch => {
fetch("https://us-central1-myapp.cloudfunctions.net/storeImage", {
method: "POST",
body: JSON.stringify({
image: image.base64
})
})
.catch(err=> console.log(err))
.then(res => {res.json(); console.log("alert2 " + {res})})
.then(parsedRes => {
console.log("alert1: " + parsedRes);
const placeData = {
name: placeName,
location: location,
image: parsedRes.imageUrl
};
return fetch("https://myapp.firebaseio.com/places.json", {
method: "POST",
body: JSON.stringify(placeData)
}).catch(err => console.log("alert13: " + err))
})
.catch(err => console.log("alert4", err))
.then(res => res.json())
.catch(err => console.log("alert5: " + err))
.then(parsedRes => {
console.log("alert6", parsedRes);
}).catch(err => console.log("alert17: " + err));
};
};
export const deletePlace = (key) => {
return {
type: DELETE_PLACE,
placeKey: key
};
};
but in local console in my IDE I got this:
alert1: undefined
'alert4', { [TypeError: undefined is not an object (evaluating 'parsedRes.imageUrl')]
I wasted 3 days for this and still 0 progress.
What can be wrong here ? How to fix it ?
You're not using promise chaining correctly. You need to explicitly return the result of a then() callback to the next handler in the chain. Without returning anything, the next then() callback will get undefined. For example:
.then(res => {res.json(); console.log("alert2 " + {res})})
In this line of code, you're not returning anything to pass along to the next handler in the chain.
In fact, the above then() callback is unnecessary because it's not kicking off any other async work. You could just call res.json() in the subsequent then() block, just before the second fetch. You typically only add another then() block when you have more async work to do as a result of the prior async work.
Related
Below is my firebase function:
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]
When calling this function, how can I pass in the context.auth.id?
Below is what i've tried:
async function deleteAccount(userId) {
const deleteUser = firebase.functions().httpsCallable("deleteUser");
deleteUser({ userId }).then((result) => {
console.log(result.data);
});
}
But im getting the following error:
Unhandled error TypeError: Cannot read property 'uid' of undefined
I know the the context.auth.id is available server side but In this instance I need a way i can pass it in.
You don't have to pass user's UID in callable cloud function. The user must be logged in with Firebase authentication and Firebase SDKs will take care of the rest.
Can you try logging current user in deleteAccount function before calling cloud function just to ensure user is logged in? Also context.auth.uid is UID of user that is calling the function. If you want to access the userId that you are passing in the function, refactor the code as shown below.
The deleteUser() function would take only 1 parameter that's the data you want to pass in Cloud functions.
// not deleteUser({}, { userId })
deleteUser({ userId }).then((result) => {
console.log(result.data);
});
When you are explicitly passing any data in Cloud function, that can be access from data and not context:
const { userId } = data;
I'm trying to test the error handling portion of my function but I am not sure how to do this... I am using someone's API that is always running, so how can I simulate the API not connecting?
async function getElephant() {
const proxyurl = 'https://cors-anywhere.herokuapp.com/'
const url = 'https://elephant-api.herokuapp.com/elephants/random'
fetch(proxyurl + url)
.then((resp) => { return resp.json() })
.then((data) => {
data.forEach((elephant) => {
const { name, sex, species, note } = elephant
document.getElementById('name').value = name
document.getElementById('gender').value = sex
document.getElementById('species').value = species
document.getElementById('about').value = note
})
})
// .catch(() => console.log("Can't access " + url + " blocked?"))
.catch(() => ("Can't access"))
}
My test:
test('Test .catch block, failure message to connect to url', async () => {
expect.assertions(1);
return expect(getElephant()).rejects.toEqual('Can't access');
})
and also tried using fetch-mock utility
test('Test .catch block, failure message to connect to url', async () => {
const url = 'https://lephant-api.herokuapp.com/elephants/random'; //Try misspelling url to catch error
fetchMock.get(url, {
status: 400,
body: JSON.stringify('BAD CONNECTION')
})
const response = await getElephant(url)
const result = await response.json()
expect(result).toThrow("Can't access")
})
Any advice is appreciated!
I hope this example helps you how to handle errors with try catch
function addTask() {
x = "test input";
try {
if(x == "") throw "empty"; // error cases
if(isNaN(x)) throw "not a number";
x = Number(x);
if(x > 10) throw "too high";
}
catch(err) { // if there's an error
console.error("Input is " + err); // write the error in console
}
finally { // Lets you execute code, after try and catch, regardless of the result
console.log("Done");
}
}
addTask();
I have 2 Firebase functions that I want to execute when there is an Http request, one function (createEmailList) to save data in the Firebase database, the other (zohoCrmHook) to to save in a 3rd party CRM called Zoho.
When the functions are deployed to Firebase, the functions log shows that both are properly deployed. However, when the Http request is made from the frontend, the log shows that only one of the functions (createEmailList) is being executed.
As the log shows, the first function createEmailList is being executed and the data shows up in the Firebase database with no problem. However, The second function zohoCrmHook is not even being executed.
index.js
const functions = require('firebase-functions');
const admin = require("firebase-admin")
const serviceAccount = require("./service_account.json");
const createEmailList = require('./createEmailList')
// zoho
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)
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 }))
res.status(204).end();
})
}
zohoCrmHook.js
const axios = require('axios');
const functions = require('firebase-functions');
// 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 = async (req, res) => {
const newLead = {
'data': [
{
'Email': req.body.email,
'Last_Name': req.body.lastName,
'First_Name': 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(e);
}
}
else res.json({ message: 'Lead already in CRM' })
}
function getAccessToken () {
const url = `https://accounts.zoho.com/oauth/v2/token?refresh_token=${refreshToken}&client_id=${clientId}&client_secret=${clientSecret}&grant_type=refresh_token`;
return new Promise((resolve, reject) => {
axios.post(url)
.then((response) => {
return resolve(response);
})
.catch(e => console.log(e))
});
}
function getLeads(token) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
axios.get(url, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
return resolve(response);
})
.catch(e => console.log(e))
})
}
function createLead(token, lead) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
const data = JSON.stringify(lead);
axios.post(url, data, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
console.log(response.data)
return resolve(response);
})
.catch(e => reject(e))
})
}
function checkLeads(leads, currentLead) {
return leads.filter(lead => lead.Email === currentLead)
}
Since you're exporting two functions.https.onRequest declarations, you'll end up with two Cloud Functions, each with their own URL/endpoint. So if that's what you need, you'll need to configure two web hooks that call these functions.
From reading your question however, it sounds more like you want a single Cloud Function that does two things, in which case you should only have one functions.https.onRequest declaration that then calls two regular JavaScript functions (for example).
So something more like:
exports.myWebHook = functions.https.onRequest(function(req, res) {
zohoCrmHook(...);
createEmailList(...);
})
You'll need to figure out what to pass into the two function calls here, as you can't pass the request and response along.
Alternatively you can call the two Cloud Functions from here, but that typically just drives up your cost with little benefit.
I'm getting the following error:
TypeError: Converting circular structure to JSON
at JSON.stringify (<anonymous>)
at stringify (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/response.js:1123:12)
at ServerResponse.json (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/response.js:260:14)
at cors (/Users/landing-page-backend/functions/zohoCrmHook.js:45:43)
at process._tickCallback (internal/process/next_tick.js:68:7)
for the HTTP response in my createLead function, despite the fact that the function is executed properly and does what it's supposed to do (which is to create an entry in my CRM).
I've pointed out in the following code where the error occurs:
const axios = require('axios');
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true })
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)); // this is where the error occurs
} catch (e) {
console.log(e);
}
} else {
return res.json({ message: 'Lead already in CRM' })
}
})
}
function getAccessToken () {
const url = `https://accounts.zoho.com/oauth/v2/token?refresh_token=${refreshToken}&client_id=${clientId}&client_secret=${clientSecret}&grant_type=refresh_token`;
return new Promise((resolve, reject) => {
axios.post(url)
.then((response) => {
return resolve(response);
})
.catch(e => console.log("getAccessToken error", e))
});
}
function getLeads(token) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
axios.get(url, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
return resolve(response);
})
.catch(e => console.log("getLeads error", e))
})
}
function createLead(token, lead) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
const data = JSON.stringify(lead);
axios.post(url, data, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
console.log("response in createLead", response)
return resolve(response);
})
.catch(e => reject(e))
})
}
function checkLeads(leads, currentLead) {
return leads.filter(lead => lead.Email === currentLead)
}
Console.logging all the parameters indicate that the they are not the issue. The configuration of the Zoho API is, also, not the issue considering the fact that the entries are being properly made into the CRM. My assumption is that it would have to do with the HTTP response in the JSON format.
You're trying to convert a promise to JSON, not going to work. Your createLead function returns a promise, not a JSON. The promise is the 'circular object'.
I believe my promises aren't being finished because I'm not handling them correctly. At the end of my code, within Promise.all(), console.log(payload) is displaying {}. When it should display something like this:
{
project1: {
description: '...',
stats: {python: 50, css: 50}
},
project2: {
description: '...',
stats: {python: 25, css: 75}
},
project3: {
description: '...',
stats: {python: 10, css: 90}
}
}
code:
app.get("/github", (req, res) => {
const authorizationHeader = {headers: {Authorization: 'Basic ' + keys.github.accessToken}};
const user = 'liondancer';
const githubEndpoint = 'api.github.com/repos/';
var payload = {};
let promises = req.query.projects.map(project => {
let datum = {};
const githubAPIUrl = path.join(githubEndpoint, user, project);
return fetch('https://' + githubAPIUrl + '/languages', authorizationHeader).then(res => {
// Get Languages of a project
if (!isStatus2XX(res)) {
throw 'Status code not 2XX:' + res.status;
}
return res.json();
}).then(res => {
let languagePercentages = {};
let total = 0;
// get total
Object.keys(res).forEach(key => {
total += Number.parseInt(res[key]);
});
// compute percentages
Object.keys(res).forEach(key => {
languagePercentages[key] = (Number.parseInt(res[key]) / total * 100).toFixed(1);
});
datum.stats = languagePercentages;
// Get description of a project
fetch('https://' + githubAPIUrl).then(res => {
if (!isStatus2XX(res)) {
throw 'Status code not 2XX: ' + res.status;
}
return res.json();
}).then(res => {
datum.description = res.description;
payload[project] = datum;
});
}).catch(err => {
console.log('Github API error: ' + err);
});
});
Promise.all(promises).then(() => {
console.log(payload);
res.send(payload);
}).catch(err => {
console.log('nothing ever works...: ' + err);
});
});
At first I replaced .map with .forEach() to have the code execute and the code seemed to have worked properly. payload had the values I expected. However, now that I want to send the aggregated results, I cant seem the properly execute the promises in the correct order or if at all.
just change this line
fetch('https://' + githubAPIUrl).then(res => {
into this
return fetch('https://' + githubAPIUrl).then(res => {
so promise.all will resolve after all nested promises have resolved so payload filled up.