Reduce Auth Requests - node.js

I am making a few node.js scripts using google-api-nodejs-client.
Here is the basic auth request to interact with the api:
const { google } = require("googleapis");
const auth = new google.auth.GoogleAuth({
keyFile: "credentials.json",
scopes: "https://www.googleapis.com/auth/spreadsheets",
});
const getAuthClient = async () => {
try {
return await auth.getClient();
} catch (error) {
console.error(error);
}
};
const sheetsClient = async () => {
const client = await getAuthClient();
return await google.sheets({ version: "v4", auth: client });
};
module.exports = { sheetsClient };
Now, whenever I create a function that needs to use the sheetsClient I need to set it up like this (the examples below are generic examples, I will have other calls to the api where I'll need to get the sheets client. In some cases I'll need to read (get the client) and the write (get the client again) in different functions called one after the other:
const { google } = require("googleapis");
const { sheetsClient } = require("./googleAuth");
const createSheet = async (name) => {
const client = await sheetsClient();
const sheet = await client.spreadsheets.create({
resource: {
properties: {
title,
},
},
});
};
const updateSheet = async (name) => {
const client = await sheetsClient();
const sheet = await client.spreadsheets.update({
resource: {
properties: {
title,
},
},
});
};
const deleteSheet = async (name) => {
const client = await sheetsClient();
const sheet = await client.spreadsheets.delete({
resource: {
properties: {
title,
},
},
});
};
Is there a better way to get access to the client without having to call it everytime within a function?

there are many possibilities.
the easiest may be to call this only once, outside of all functions.
const { google } = require("googleapis");
const { sheetsClient } = require("./googleAuth");
// globally defined
const client = ( async () => await sheetsClient())();
// rest of code
const createSheet = async (name) => {
// deleted : const client = await sheetsClient();
const sheet = await client.spreadsheets.create({
resource: {
properties: {
title,
},
},
});
};
this will create a global client variable in this js file.
then you can remove its declaration from every function.
the code will still run smoothly but there will be only one authentication.
Another way to deal with your problem is to assure that the auth function really is executed only once by using a flag. (this solution is related to memoization)
var client = null;
const getAuthClient = async () => {
if (client) return client;
try {
client = await auth.getClient();
return client;
} catch (error) {
console.error(error);
}
};

Related

Async function to scrape subreddits using Cheerio returns undefined

The script by itself works great (entering the url manually, writing a json file using the fs module, node script_name.js) but within a Express get request it returns undefined.
So I've built a simple frontend to let the user enter the subreddit name to be scraped.
And here's where the problem is:
Express controller
const run = require("../run");
requestPosts: async (req, res) => {
try {
const { subreddit } = req.body;
const response = await run(subreddit);
//console.log(response);
res.json(response);
} catch (error) {
console.error(error);
}
},
Cheerio functions
const axios = require("axios");
const { load } = require("cheerio");
let posts = [];
async function getImage(postLink) {
const { data } = await axios(postLink);
const $ = load(data);
return $("a.post-link").attr("href");
}
async function run(url) {
try {
console.log(url);
const { data } = await axios(url);
const $ = load(data);
$(".thing.linkflair.link").map(async (i, e) => {
const title = $(e)
.find(".entry.unvoted .top-matter .title .title")
.text();
const user = $(e)
.find(".entry.unvoted .top-matter .tagline .author")
.text();
const profileLink = `https://old.reddit.com/user/${user}`;
const postLink = `https://old.reddit.com/${$(e).find("a").attr("href")}`;
// const thumbail = $(e).find("a img").attr("src");
const image = await getImage(postLink);
posts.push({
id: i + 1,
title,
postLink,
image,
user: { user, profileLink },
});
});
const nextPage = $(".next-button a").attr("href");
if (nextPage) {
await run(nextPage);
} else {
return posts;
}
} catch (error) {
console.error(error);
}
}
module.exports = run;
I've tried working with Promise((resolve, reject) => {}).
I think it's returning undefined because maybe the code its not synchronized.
(idk if it makes sense, i've just started programming)
.map() is not promise-aware and does not wait for your promises to finish. So, $(".thing.linkflair.link").map() finishes long before any of the asynchronous functions inside its callback do. Thus you try to return posts BEFORE it has been populated.
Passing an async callback to .map() will return an array of promises. You can use Promise.all() on those promises to know when they are done and once you're doing that, you may as well just return each post object rather that using a higher level scoped/shared object, thus making the code more self contained.
I would suggest this code:
async function run(url) {
try {
console.log(url);
const { data } = await axios(url);
const $ = load(data);
const posts = await Promise.all($(".thing.linkflair.link").map(async (i, e) => {
const title = $(e)
.find(".entry.unvoted .top-matter .title .title")
.text();
const user = $(e)
.find(".entry.unvoted .top-matter .tagline .author")
.text();
const profileLink = `https://old.reddit.com/user/${user}`;
const postLink = `https://old.reddit.com/${$(e).find("a").attr("href")}`;
// const thumbail = $(e).find("a img").attr("src");
const image = await getImage(postLink);
// return a post object
return {
id: i + 1,
title,
postLink,
image,
user: { user, profileLink },
};
}));
const nextPage = $(".next-button a").attr("href");
if (nextPage) {
const newPosts = await run(nextPage);
// add these posts to the ones we already have
posts.push(...newPosts);
}
return posts;
} catch (error) {
console.error(error);
}
}

How can I not confirm the action from Google api every time?

I have a Desktop App for Google Drive that create and set perms that i need. This app should work only for one account with google drive.
My problem is that when I launch an action, I always have to confirm this action in explorer. Can I somehow automatically send my data and confirmation to the server? I read about the access token, but it seems to be suitable only for web applications. I am based on the documentation from the Google API site.
And in future this should work from console.
My code right now:
const fs = require('fs').promises;
const path = require('path');
const process = require('process');
const {authenticate} = require('#google-cloud/local-auth');
const {google} = require('googleapis');
const { AuthClient } = require('google-auth-library');
const SCOPES = ['https://www.googleapis.com/auth/drive'];
const TOKEN_PATH = path.join(process.cwd(), 'token.json');
const CREDENTIALS_PATH = path.join(process.cwd(), 'credentials.json');
async function loadSavedCredentialsIfExist() {
try {
const content = await fs.readFile(TOKEN_PATH);
const credentials = JSON.parse(content);
return google.auth.fromJSON(credentials)
} catch (err) {
return null;
}
}
async function saveCredentails(client) {
const content = await fs.readFile(CREDENTIALS_PATH);
const keys = JSON.parse(content);
const key = keys.installed || keys.web;
const payload = JSON.stringify({
type: 'authorized_user',
access_type: 'offline',
client_id: key.client_id,
client_secred: key.client_secret,
refresh_token: client.credentials.refresh_token,
});
await fs.writeFile(TOKEN_PATH, payload);
}
async function authorize() {
let client = await loadSavedCredentialsIfExist();
if (client) {
return client;
}
client = await authenticate({
scopes: SCOPES,
keyfilePath: CREDENTIALS_PATH,
});
if (client.credentials) {
await saveCredentails(client);
}
return client;
}
async function createFolder(authClient) {
const service = google.drive({version: 'v3', auth: authClient});
const fileMetadata = {
name: 'testmeows',
mimeType: 'application/vnd.google-apps.folder',
};
try {
const file = await service.files.create({
resource: fileMetadata,
fields: 'id',
});
console.log('Folder Id:', file.data.id);
const body = {"role": "writer", "type": "anyone"}
const result = await service.permissions.create({
resource: body,
fileId: file.data.id,
//fields: 'id',
});
const align = `https://drive.google.com/drive/folders/${file.data.id}?usp=sharing`;
console.log(align);
} catch (err) {
throw err;
}
}
//module.exports = test;ф
authorize().then(createFolder).catch(console.error);
Well, how to better get refresh token without user invention and opening explorer on Desktop App Google Api?

Mocking function to unit test Serverless Lambda

I am really struggling to understand unit testing within a Serverless Application. So I obviously have my handler, and I have a single Lambda function
const responses = require('../utils/jsonResponse');
const someConnector = require('../services/connectToService/connectToService');
module.exports = async (event) => {
const connectionParams = {
//some env variables
};
try {
const token = await someConnector.connectToService(connectionParams);
return responses.status(token, 200);
} catch (e) {
return responses.status(
`Issue connecting to service - ${e.message}`,
500,
);
}
};
So this Lambda function is pretty straight forward, gets some environment variables, and awaits a response from a service. It then returns the response.
So I have already done integration tests for this which is fine, but now I wanted to do a Unit test. I wanted to test this function in isolation, so essentially I want to mock connectToService to return my own responses.
So I came up with the following
require('dotenv').config();
const { expect } = require('chai');
const sinon = require('sinon');
let sandbox = require("sinon").createSandbox();
const LambdaTester = require('lambda-tester');
const handler = require('../../../handler');
const msConnector = require('../../../services/connectToService/connectToService');
describe('Testing handler', async (done) => {
describe('endpoint someEndpoint returns 200', () => {
it('Should resolve with 200', async () => {
before(() => {
sandbox = sinon.createSandbox();
sandbox.stub(msConnector, 'connectToService').resolves('some-token');
});
afterEach(() => {
sandbox.restore();
});
await LambdaTester(handler.someEndpoint)
.expectResult((result) => {
console.log(result);
expect(result.statusCode).to.equal(200);
});
});
});
done();
});
msConnector is the filename of the service, connectToService is the function name. What I want to do is not invoke this function, but return some-token when my Lambda calls it.
However, I have the console.log, and what I get from that is the real token, not some-token.
This tells me that the mocked function is really being called and executed and returning the real value.
So how can I mock this to make sure it returns some-token?
Thanks
Service function
const { DOMParser } = require('#xmldom/xmldom');
const axios = require('axios');
const { loginRequest } = require('./xml/login');
const connectToService = async (connectionParams) => {
//this injects config details into XML
const xmlRequest = loginRequest(
connectionParams.username,
connectionParams.password,
connectionParams.url,
);
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': xmlRequest.length,
},
};
const token = await axios
.post(connectionParams.msHost, xmlRequest, config)
.then((res) => {
const dom = new DOMParser().parseFromString(res.data, 'text/xml');
if (
dom.documentElement
.getElementsByTagName('wsse:secToken')
.item(0)
) {
return dom.documentElement
.getElementsByTagName('wsse:secToken')
.item(0).firstChild.nodeValue;
}
throw new Error('Invalid Username/Password');
})
.catch((err) => {
throw new Error(`Error making connection - ${err.message}`);
});
return token;
};
module.exports = {
connectToService,
};
The function connectToService may be not same copy between you mocked and called.
Because you overwrote a new object by module.exports = .... This causes you probably get different object for each require.
Try to do the below approach sharing the same object for all require.
const { DOMParser } = require('#xmldom/xmldom');
const axios = require('axios');
const { loginRequest } = require('./xml/login');
const connectToService = async (connectionParams) => {
//this injects config details into XML
const xmlRequest = loginRequest(
connectionParams.username,
connectionParams.password,
connectionParams.url,
);
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': xmlRequest.length,
},
};
const token = await axios
.post(connectionParams.msHost, xmlRequest, config)
.then((res) => {
const dom = new DOMParser().parseFromString(res.data, 'text/xml');
if (
dom.documentElement
.getElementsByTagName('wsse:secToken')
.item(0)
) {
return dom.documentElement
.getElementsByTagName('wsse:secToken')
.item(0).firstChild.nodeValue;
}
throw new Error('Invalid Username/Password');
})
.catch((err) => {
throw new Error(`Error making connection - ${err.message}`);
});
return token;
};
module.exports.connectToService = connectToService;

Data not saved into Firestore through scheduler function with Firebase Cloud Functions

I'm trying to create APIs with Firebase Cloud Functions so as to build a system to make what's collected through the APIs and other third-party APIs routinely saved into Firestore. Here is my source code index.js at this moment, and I'd like to control all the processes in this one file.
/functions/index.js
const firebaseAdmin = require("firebase-admin");
const firebaseFunctions = require("firebase-functions");
firebaseAdmin.initializeApp();
const fireStore = firebaseAdmin.firestore();
const express = require("express");
const axios = require("axios");
const cors = require("cors");
const serviceToken = "SERVICE-TOKEN";
const serviceBaseUrl = "https://api.service.com/";
const app = express();
app.use(cors());
const getAllExamples = async () => {
var url = `${serviceBaseUrl}/examples?token=${serviceToken}`;
var config = {
method: "get",
url: url,
headers: {}
};
axios(config).then((res) => {
console.log("Data saved!");
return res.data;
}).catch((err) => {
console.log("Data not saved: ", err);
return err;
});
}
const setExample = async (documentId, dataObject) => {
fireStore.collection("examples").doc(documentId).set(dataObject).then(() => {
console.log("Document written!");
}).catch((err) => {
console.log("Document not written: ", err);
});
}
app.get("/getAllExamples", (req, res) => {
getAllExamples().then((response) => res.send(response));
});
app.put("/setExample", (req, res) => {
setExample(req.params.documentId).then((response) => res.send(response));
});
const api = firebaseFunctions.https.onRequest(app);
module.exports = { api };
module.exports.updateExamplesRoutinely = firebaseFunctions.pubsub.schedule("0 0 * * *").timeZone("America/Los_Angeles").onRun((context) => {
var examples = getAllExamples();
for(var i = 0; i < examples.length; i++) {
var example = examples[i];
var exampleId = example["id"];
if(exampleId && example) setExample(exampleId, example);
}
});
As a result, updateExamplesRoutinely is properly triggered every 00:00 in PST, but NO data is stored in Firebase and NO console logs about Firebase data updates and found in Cloud Functions Logs.
Output data collected through https://api.service.com/ is something like this below:
[
{
id: "BB345",
name: "Tim Gordon",
age: 24,
email: "tgordon#yahoo.com"
},
{
id: "CC098",
name: "John Doe",
age: 28,
email: "john.doe#gmail.com"
},
{
id: "DD777",
name: "Adam Smith",
age: 39,
email: "adamsmith#outlook.com"
},
...
]
Simply, these 3 problems are what I would like to get resolved.
How should I call APIs defined in index.js through Cloud Functions' scheduler?
How should I save data into Firestore inside Cloud Functions' scheduler?
What's the best "async" way to call the third-party APIs, wait and collect the results, and pass them to other events or functions?
How should I call APIs defined in index.js through Cloud Functions'
scheduler?
I guess you want to say "How should I call the methods defined in index.js". There is no reason to call the APIs exposed by the Cloud Funtion from this same Cloud Function.
So to call the methods, do as follows:
const getAllExamples = async () => {
var url = `${serviceBaseUrl}/examples?token=${serviceToken}`;
var config = {
method: "get",
url: url,
headers: {}
};
return axios(config).then((res) => { // !!! note the return here
console.log("Data saved!");
return res.data;
}).catch((err) => {
console.log("Data not saved: ", err);
return err;
});
}
const setExample = async (documentId, dataObject) => {
// !!! note the return on next line
return fireStore.collection("examples").doc(documentId).set(dataObject).then(() => {
console.log("Document written!");
}).catch((err) => {
console.log("Document not written: ", err);
});
}
module.exports.updateExamplesRoutinely = firebaseFunctions.pubsub.schedule("0 0 * * *").timeZone("America/Los_Angeles").onRun(async (context) => {
const examples = await getAllExamples();
const promises = [];
for(var i = 0; i < examples.length; i++) {
var example = examples[i];
var exampleId = example["id"];
if(exampleId && example) promises.push(setExample(exampleId, example));
}
return Promise.all(promises);
// See how we return the promise returned by Promise.all
// !!! This is IMPORTANT, see https://firebase.google.com/docs/functions/terminate-functions
});
How should I save data into Firestore inside Cloud Functions'
scheduler?
The above code will save the data in Firestore. You actually want to execute a variable number of call to the setExample() method, you need to use Promise.all() as shown above. This way, setExample() will write to Firestore for each example.
What's the best "async" way to call the third-party APIs, wait and
collect the results, and pass them to other events or functions?
See how we adapt the updateExamplesRoutinely Cloud Function: we use the async and await keywords.
module.exports.updateExamplesRoutinely = firebaseFunctions.pubsub.schedule("0 0 * * *").timeZone("America/Los_Angeles").onRun(async (context) => {
const examples = await getAllExamples();
// ...

AsyncStorage before list render React Native

I have an app that list incidents of a animals charity ong.
After the login, the ong is directed to your incidents list, so i must pass your ID of login page to load this list.
I'm trying to load the variables of AsyncStorage in a function and then pass it to a callback that load the incidents list in React useEffect.
Code:
export default function Incidents() {
const [incidents, setIncidents] = useState([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [id, setID] = useState('');
const [name, setName] = useState('');
loadStorage:
loadStorage = async function(callback) {
try {
const ongid = await AsyncStorage.getItem('ongID');
const ongname = await AsyncStorage.getItem('ongName');
if (ongid && ongname !== null) {
setID(ongid);
setName(ongname);
}
} catch (e) {
alert(e);
}
callback();
}
loadIncidents:
loadIncidents = async function() {
if (loading) {
return;
}
if (total > 0 && incidents.lenght === total) {
return;
}
try {
const response = await api.get('profiles', {
headers: {
Authorization: id,
},
params: { page },
});
setIncidents([ ... incidents, ... response.data]);
setTotal(response.headers['x-total-count']);
setPage(page +1);
setLoading(false);
} catch (e) {
alert(e); //When i access the incident list page i got the error: 'Error: Request failed with status code 400'
}
}
useEffect:
useEffect(() => {
navigation.addListener('focus', () => {
loadStorage(loadIncidents);
});
}, []);
The error in the alert (e) line of loadIncidents happens because useEffect is not fully calling the loadStorage (loadIncidents) part the first time I enter the application.
I have another page called newIncident, if I navigate to it after pressing OK on this error and go back to the incident list page (by navite.goBack ()) the list will be loaded with all the incidents from the logged ONG.
Need this behavior when I first enter the page that lists the ONG's incidents. Since the both methods are asynchronous, i'm not be able to figure out how to do this.
Maybe you can consider using react-native-easy-app. After completing the initial binding, it can enable you to access the properties in AsyncStorage synchronously in the form of assignment and value, because it is simple enough, so you will not appear above Various problems.
Did it using promises. code below:
async function loadStorage() {
var ongid = '';
var ongname = '';
try {
ongid = await AsyncStorage.getItem('ongID');
ongname = await AsyncStorage.getItem('ongName');
} catch (e) {
alert(e);
}
return new Promise((resolve, reject) => {
if (ongid !== null) {
setID(ongid);
setName(ongname);
const ong = { 'name': ongname, 'id': ongid}
return resolve(ong);
}
})
}
Then in useEffect information loads like this:
loadStorage()
.then(loadIncidents)

Resources