I have a file dedicated to handling Authentication functions for my React Native App. One of these functions getJWToken is an anonymous async function that tries to get the current user token by the following means:
const auth = db.auth()
// db === (Firebase.apps.length === 0 ? Firebase.initializeApp(config) : Firebase.app())
// the configuration of the Firebase DB occurs in a config file and db is exported.
const getJWToken = async () => {
auth.onAuthStateChanged(function(user) {
if (user) {
user.getIdToken()
.then(function(idToken) {
return idToken;
})
.catch(
(error) => {
console.log("Cannot get User Token: ", error);
}
)
}
});
}
Through console.log I'm able to see that the function returns the token as it's suppose to. The issues arrives when I'm using it in another async function in another file like so:
Service Folder index.js
import * as Auth from './AuthServices'
export {Auth}
App.tsx
// App.tsx
import {Auth} from './src/services'
// The rest in inside an Async Function
signOut: async () => {
let token
token = await Auth.getJWToken()
console.log("token: ", token) // results in undefined
}
Token results in undefined. I have no means of returning the token value from the promise function. I've tried using return on auth.onAuthStateChanged but that results in token evaluating to [Function Bound]. await on auth.onAuthStateChanged does nothing either. I'm truly stumped.
You have to return at two more places as show in the code . Please reply with this modification, ket us c
const auth = db.auth()
// db === (Firebase.apps.length === 0 ? Firebase.initializeApp(config) : Firebase.app())
// the configuration of the Firebase DB occurs in a config file and db is exported.
const getJWToken = async () => {
return auth.onAuthStateChanged(function(user) {
if (user) {
return user.getIdToken()
.then(function(idToken) {
return idToken;
})
.catch(
(error) => {
console.log("Cannot get User Token: ", error);
}
)
}
});
}
While I no longer use this code snippet I was able to solve it like so:
getJWToken
export const getJWToken = async () => {
let result;
// await is needed here
await auth.onAuthStateChanged(function(user) {
if (user) {
console.log("user")
// save this promise to result
result = user.getIdToken()
.then(function(idToken) {
return idToken;
})
.catch(
(error) => {
console.log("Cannot get User Token: ", error);
}
)
}
});
// return the user.getToken() promise
return result
Related
I see the answer here Mocking aws-sdk S3#putObject instance method using jest, but it's not working as-is and it's not explained at all so I don't know what's going on.
I have a function:
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
exports.saveImageToS3 = async (params) => {
try {
const s3resp = await s3.putObject(params).promise();
/* Happy path response looks like this:
data = {
ETag: "\"6805f2cfc46c0f04559748bb039d69ae\"",
VersionId: "psM2sYY4.o1501dSx8wMvnkOzSBB.V4a" // version optional
}*/
if (s3resp.hasOwnProperty('ETag')) { // expected successful response
return { success: true, key: params.Key } // returning key of item if we have reason to think this was successful.
} else {
console.warn("Unexpected s3 response format: ", s3resp)
return s3resp;
}
} catch (err) {
throw err;
}
}
I'd like to test if it works.
I do not understand how to mock the s3.putObject function.
I have tried:
describe('FUNCTION: saveImageToS3', () => {
const mockedPutObject = jest.fn();
jest.mock('aws-sdk', () => {
return class S3 {
putObject(params, cb) {
console.log("Mocked putObject function");
mockedPutObject(params, cb);
}
}
});
test('returns success object with correct key value', async () => {
await expect(await utils.saveImageToS3(params)).toEqual({ success: true, key: `original/testCamId/testCamId___2022-04-06T06-30-59Z.jpg` })
})
})
per the above-linked answer, but the test fails (times out, actually) and the output "Mocked putObject function" never is written to the console, telling me the mocked aws-sdk isn't being used...
I've followed several guides on how to correctly wait for the data from my ListFile() function. I have logged the output to the console in ListFile() so I know the data is correct. When I try and await for the function to complete and log the data, I get one of two outputs when experimenting with awaits and promises:
getData error: TypeError: undefined is not an object (evaluating 'response.json')
or
Promise {
"_U": 0,
"_V": 0,
"_W": null,
"_X": null,
}
I'm creating a React Native app and the function that calls ListFile() contains a bunch of other components that are rendered so I can't make the entire function an async function. Not sure what else to do here.
Calls ListFile()
const getData = async () => {
try {
const response = await ListFile();
const data = response.json();
console.log(data);
} catch (err) {
console.log("getData error: " + err);
}
}
getData(); //always returns getData error: TypeError: undefined is not an object (evaluating 'response.json')
ListFile()
async function ListFile() {
// Reference Firebase Container
var filesInStorageList = [];
let content = [];
const listRef = ref(fbStorage, filePath);
console.log("Listing Files");
// List all files in container
listAll(listRef)
.then((res) => {
res.prefixes.forEach((folderRef) => {
// All the prefixes under listRef.
// You may call listAll() recursively on them.
console.log(res);
});
res.items.forEach((itemRef) => {
// All the items under listRef.
let fileInStorage = itemRef["_location"]["path_"].slice(filePath.length);
filesInStorageList.push(fileInStorage);
// CORRECT DATA IS LOGGED console.log("Pushing " + fileInStorage);
// CORRECT DATA IS LOGGED console.log(filesInStorageList);
});
return filesInStorageList;
}).catch((error) => {
// Uh-oh, an error occurred!
console.log("ListFile - ERROR");
});
}
You're mixing async/await and Promises. Although they serve similar purposes, generally they are used separately to avoid situations like this where they are used together and then create unexpected results if one doesn't know what to look for.
You annotate your ListFile function with async, which means that it's expecting to find await somewhere. Instead, you use a Promise-based .then and .catch.
To convert your code, you could change it to this:
async function ListFile() {
// Reference Firebase Container
var filesInStorageList = [];
let content = [];
const listRef = ref(fbStorage, filePath);
console.log("Listing Files");
// List all files in container
try {
const res = await listAll(listRef);
//...do your forEach loops
return filesInStorageList;
} catch(err) {
//handle your error
}
}
You should add a return on the listAll method to return its value when calling it from getData(). See sample code below:
const getData = async () => {
try {
ListFile()
.then((response) => {
// do something here.
console.log(response);
});
} catch (err) {
console.log("getData error: " + err);
}
}
getData();
async function ListFile() {
// Reference Firebase Container
var filesInStorageList = [];
let content = [];
const filePath = "test/";
const listRef = ref(storage, filePath);
// List all files in container
return listAll(listRef)
.then((res) => {
res.prefixes.forEach((folderRef) => {
// All the prefixes under listRef.
// You may call listAll() recursively on them.
console.log(res);
});
res.items.forEach((itemRef) => {
// All the items under listRef.
let fileInStorage = itemRef["_location"]["path_"].slice(filePath.length);
filesInStorageList.push(fileInStorage);
// CORRECT DATA IS LOGGED console.log("Pushing " + fileInStorage);
// CORRECT DATA IS LOGGED console.log(filesInStorageList);
// console.log(filesInStorageList);
});
return filesInStorageList;
}).catch((error) => {
// Uh-oh, an error occurred!
console.log(error);
});
}
Also, as #jnpdx stated, you have to use a Promise-based .then and .catch on your ListFile()
I am running this asynchronous function in my React app -
const getMetaData = async (hashes: any) => {
console.log({ hashes });
try {
const data = hashes.map(async (hash: any) => {
const url = `http://localhost:3003/user/pinata/getmetadata/${hash}`;
const metadata = await axios.get(url);
return metadata.data.response;
});
console.log("data1", data);
const metadata = await Promise.all(data);
console.log('data2', metadata);
} catch (error) {
console.log('getMetaData Error', error);
}
};
console.log("data1", data) gives me -
data1 (12) [Promise, Promise, Promise, Promise, Promise, Promise, Promise, Promise, Promise, Promise, Promise, Promise]
The problem here is after I do a await Promise.all(data) I don't get data2 anywhere in the console. Maybe because the Promises are not even getting resolved?
Any idea what might be wrong?
Thanks in advance.
It seems that your code works fine when using SWAPI API so it can be that the API you use does not deliver data appropriately. I run the below code to test. Here's a link to codebox to play around with it if you want.
import axios from "axios";
const data = ["people", "planets", "starships"];
const getMetaData = async (hashes) => {
console.log({ hashes });
try {
const data = hashes.map(async (hash) => {
const url = `https://swapi.dev/api/${hash}`;
const metadata = await axios.get(url);
return metadata.data.results;
});
console.log("data1", data);
const metadata = await Promise.all(data);
console.log("data2", metadata);
} catch (error) {
console.log("getMetaData Error", error);
}
};
getMetaData(data);
With this code, it appears the most likely situation is that one of the promises in the loop is not resolving or rejecting. To confirm that, you can log every possible path with more local error handling so you can see exactly what happens to each request. I also added a timeout to the request so you can definitely find out if it's just not giving a response, but you can also see that by just looking at the logging for begin and end of each request in the loop:
function delay(msg, t) {
return new Promise((resolve, reject)) => {
setTimeout(() => {
reject(new Error(msg));
}), t);
});
}
const getMetaData = async (hashes: any) => {
console.log({ hashes });
try {
const data = hashes.map(async (hash: any, index: number) => {
try {
console.log(`Request: ${index}, hash: ${hash}`);
const url = `http://localhost:3003/user/pinata/getmetadata/${hash}`;
const metadata = await axios.get(url);
console.log(`Request: ${index}, result: ${metadata.data.response}`);
return metadata.data.response;
} catch (e) {
console.log(`Request: ${index} error: `, e);
throw e;
}
});
console.log("data1", data);
const metadata = await Promise.all(data.map((p: any, index: number) => {
return Promise.race(p, delay(`Timeout on request #${index}`, 5000));
});
console.log('data2', metadata);
} catch (error) {
console.log('getMetaData Error', error);
}
};
FYI, I don't really know Typescript syntax so if I've made any Typescript mistakes here, you can hopefully see the general idea and fix the syntax.
I just started to play with the firebase cloud function and firestore but when I'm using firestore inside the firebase cloud function (as below code) it's return and QuerySnapshot instead of return data. If anyone has got this issue before and solved it already then tell me. It would help me to resolve this issue too.
Thanks.
export async function allRestaurants(req: Request, res: Response) {
try {
// const { id } = req.params
const restaurantsRef = admin.firestore().collection('restaurants');
const snapshot = await restaurantsRef.get();
console.log(">>>>>>>>>", snapshot);
return res.status(200).send({ data: { restaurants: snapshot } })
} catch (err) {
return handleError(res, err)
}
}
It is normal that you get a QuerySnapshot, since the get() method returns a Promise that resolves with a QuerySnapshot.
It's up to you to generate the content you want to send back to the Cloud Function consumer.
For example, you can use the forEach() method to loop over the QuerySnapshot, or, as shown below, use the docs array.
export async function allRestaurants(req: Request, res: Response) {
try {
// const { id } = req.params
const restaurantsRef = admin.firestore().collection('restaurants');
const snapshot = await restaurantsRef.get();
const responseContent = snapshot.docs.map(doc => doc.data());
return res.status(200).send({ data: { restaurants: responseContent } })
} catch (err) {
return handleError(res, err)
}
}
I am calling the following cloud function from my Angular application, but the value returned is always null even though the cloud function is correctly logging the result. Not sure what I am doing incorrectly.
My Angular code is as follows:
const data = {test: testToProcess};
const process = this.fns.httpsCallable("gprocess"); // fns is Angular Fire Functions
process(data)
.toPromise() // since Angular Fire function returns an observable, Iam converting to a promise
.then(
(result) => {
console.log("function called: " + JSON.stringify(result));
},
(err) => {
console.log("Function call error " + JSON.stringify(err));
}
);
My cloud functions code is as follows:
import * as functions from "firebase-functions";
const fs = require("fs");
const { google } = require("googleapis");
const script = google.script("v1");
const scriptId = "MY_SCRIPT_ID";
export const gprocess = functions.https.onCall(async (data: any, context: any) => {
const test = data.test;
return fs.readFile("gapi_credentials.json", (err: any, content: string) => {
if (err) {return err;}
const credentials = JSON.parse(content); // load the credentials
const { client_secret, client_id, redirect_uris } = credentials.web;
const googleAuth = require("google-auth-library");
const functionsOauth2Client = new googleAuth.OAuth2Client(client_id, client_secret, redirect_uris); // Constuct an auth client
functionsOauth2Client.setCredentials({refresh_token: credentials.refresh_token,}); // Authorize a client with credentials
return runScript(functionsOauth2Client,scriptId,JSON.stringify(test))
.then((scriptData: any) => {
console.log("Script data in main function is" + JSON.stringify(scriptData));
return scriptData;
})
.catch((error) => {return error;});
});
});
function runScript(auth: any, scriptid: string, test: any) {
return new Promise(function (resolve, reject) {
script.scripts.run({
auth: auth,
scriptId: scriptid,
resource: {function: "doGet",devMode: true,parameters: test}
})
.then((err: any, respons: any) => {
if (err) {
console.log("API returned an error: " + JSON.stringify(err));
resolve(err);
} else if (respons) {
console.log("Script is run and response is " + JSON.stringify(respons));
resolve(respons);
}
});
});
}
The angular function is returning this result before the processing on the cloud function can be completed. IT is not waiting for the results to be returned from the cloud function.
detailed.component.ts:691 function called: null
After some time the results are logged on the cloud function console but this is not returned back to the angular client. The log on the cloud function is as follows and as shown below the correct result is logged:
5:53:32.633 PM gpublish Function execution started
5:53:32.694 PM gpublish Function execution took 61 ms, finished with status code: 204
5:53:33.185 PM gpublish Function execution started
5:53:33.804 PM gpublish Function execution took 620 ms, finished with status code: 200
5:54:31.494 PM gpublish Script is run and response is : {"config":... some result}
5:54:31.593 PM gpublish Script data in main function is{"config":... some result}
Please help!
Your function is not correctly returning a promise that resolves to the data to serialize and send to the client. The problem is that fs.readFile doesn't actually return a promise. It's asychronous and returns nothing, and that's what the client will receive. The callback is being invoked, but nothing inside that callback will affect what the client sees.
You will need to find another way of doing your file I/O that is either synchronous (such as fs.readFileSync), or actually works with promises instead of just an async callback.
I changed my code to use readFileSync as mentioned above (Thank you Doug!)
In addition, I stringified the data being sent back from the cloud function to the Angular client.
My cloud function now looks like this:
import * as functions from "firebase-functions";
const fs = require("fs");
const { google } = require("googleapis");
const script = google.script("v1");
const scriptId = "MY_SCRIPT_ID";
export const gprocess = functions.https.onCall(
async (data: any, context: any) => {
try {
const test = data.test;
const content = fs.readFileSync("credentials.json"); // CHANGED TO READFILESYNC
const credentials = JSON.parse(content);
const { client_secret, client_id, redirect_uris } = credentials.web;
const googleAuth = require("google-auth-library");
const functionsOauth2Client = ... some code to construct an auth client and authorise it
return runScript(functionsOauth2Client,scriptId,JSON.stringify(test)).then((scriptData: any) => {
return JSON.stringify(scriptData); // STRINGIFIED THE DATA
});
} catch (err) {
return JSON.stringify(err);
}
});
function runScript(auth: any, scriptid: string, test: any) {
return new Promise(function (resolve, reject) {
script.scripts.run({auth: auth,scriptId: scriptid,resource: {function: "doGet",parameters: test}})
.then((respons: any) => {resolve(respons.data);})
.catch((error: any) => {reject(error);});
});
}