GitHub API fails with JavaScript FETCH - javascript

GitHub seems to have made updates since end of 2021.
https://developer.github.com/changes/2020-02-10-deprecating-auth-through-query-param/
I have followed numerous resources where the below code increases the amount of requests one can do per hour. Now the below request does not work. Instead the documentation says to use CURL, so for instance the below works in a terminal:
curl -u client_id:secret_key https://api.github.com/users/<username>
I want to do this in JavaScript, I am playing around with a GitHub user finder app in JavaScript. Can someone please show me how I can get this to actually work. The code I am using is below.
TL:DR: I can access the github API using the code below and receive a JSON object to display, but it's limited to 60 requests per hour. GitHub documentation says that since end of 2021 query parameters are not allowed anymore so I'm lost now. How can I do this in JavaScript now?
const client_id = "df2429c311a306c35233";
const client_secret = "5c23233326680aa21629451a6401d36ec";
const fetchUsers = async (user) => {
const api_call = await fetch(`https://api.github.com/users/${user}?client_id=df5429c311a306c356f4&
client_secret=${client_secret}`);
const data = await api_call.json();
return {data};
};
EDIT/UPDATE:
const inputValue = document.querySelector("#search");
const searchButton = document.querySelector(".searchButton");
const nameContainer = document.querySelector(".main__profile-name");
const unContainer = document.querySelector(".main__profile-username");
const reposContainer = document.querySelector(".main__profile-repos");
const urlContainer = document.querySelector(".main__profile-url");
const client_id = "<user_id>";
const client_secret = "<client_secret>";
const headers = {
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64'))
}
const fetchUsers = async (user) => {
const api_call = await fetch(`https://api.github.com/users/${user}`, {
method: 'GET',
headers: headers
});
const data = await api_call.json();
return {data};
};
const showData = () => {
fetchUsers(inputValue.value).then((res) => {
console.log(res);
nameContainer.innerHTML = `Name: <span class="main__profile-value">${res.data.name}</span>`
unContainer.innerHTML = `Username: <span class="main__profile-value">${res.data.login}</span>`
reposContainer.innerHTML = `Repos: <span class="main__profile-value">${res.data.public_repos}</span>`
urlContainer.innerHTML = `Url: <span class="main__profile-value">${res.data.url}</span>`
})
};
searchButton.addEventListener("click", () => {
showData();
})

Those behave as username and password of the basic authentication type. Hence your Api request should have the following header.
const headers = {
'Authorization': 'Basic ' + btoa(CLIENT_ID + ':' + CLIENT_SECRET)
}
Please note that btoa function is being used because browsers don't have a native support of Buffer. If btoa throws error then try with window.btoa and use it like
const response = await fetch(url, {method:'GET',
headers: headers,
})

Related

React - axios fetching empty array [duplicate]

I am currently working on social media mern stack react app. I am using node js and express as my backend services , also using mongoose to store my data and axios and redux thunk which connect the backend to the front end. Till now I had no issue recieving and sending data to the server. Right now I am trying to create search post get request ,base on a keyword the user entered. The issue with it, that when I am sending the keyword to the server instead of recieving the string it gets undefined value, like redux thunk not sending anything. I will be very thankful if someone could help me with that. I am watching the code over and over again and can't find out the reason for that.
My post controller class(I copied only the relevant function):
import express from "express";
const app = express();
import Post from "../model/PostModel.js";
import ErrorHandlng from "../utilities/ErrorHandling.js";
import bodyParser from "body-parser";
import catchAsync from "../utilities/CatchAsync.js";
import User from "../model/UserModel.js";
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
export const getPosts = catchAsync(async (req, res, next) => {
const data = req.body.keyword;
const page = parseInt(req.query.page || "0");
const PAGE_SIZE = 20;
const query = new RegExp(data, "i");
const total = await Post.countDocuments({});
const posts = await Post.find({ $or: [{ title: query }, { content: query }] })
.limit(PAGE_SIZE)
.skip(PAGE_SIZE * page);
if (!posts) {
return next(new ErrorHandlng("No posts were found", 400));
}
res.status(200).json({
status: "success",
data: {
totalPages: Math.ceil(total / PAGE_SIZE),
posts,
},
});
});
My api class(front end,copied only the calling for that specific get request):
import axios from "axios";
const baseURL = "http://localhost:8000";
axios.defaults.withCredentials = true;
const API = axios.create({
baseURL,
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
export const getPostsByKeyword = (keyword, page) =>
API.get(`/post/getPostsByKey?page=${page}`, keyword);
Post slice class:
export const fetchPostsByKeyWord = createAsyncThunk(
"post/getKeyword",
async ({ keyword, page }, { fulfillWithValue, rejectWithValue }) => {
try {
const response = await api.getPostsByKeyword(keyword, page);
if (response.statusCode === "400") {
throw new Error("There are no available posts");
}
const fetchData = await response.data.data.posts;
const totalPages = await response.data.data.totalPages;
return fulfillWithValue({ fetchData, totalPages });
} catch (err) {
console.log(err.response.message);
}
}
);
const initialState = { status: "undefined" };
const PostSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: {},
});
export const postActions = PostSlice.actions;
export default PostSlice;
Calling the backend:
dispatch(fetchPostsByKeyWord({ keyword, page }))
.unwrap()
.then((originalPromiseResults) => {
console.log("thte " + " " + originalPromiseResults.totalPages);
console.log("The data is" + originalPromiseResults.fetchData);
setTotalPages(originalPromiseResults.totalPages);
})
.catch((err) => {
console.log(err.message);
});
As you can see I have not copied the whole code, I copied only the parts that are relevants for the question.
Browsers cannot currently send GET requests with a request body. XMLHttpRequest (which Axios uses) will ignore it and fetch() will trigger an error.
See also HTTP GET with request body for extra discussion on why trying this might be a bad idea.
You should instead pass everything required in the query string, preferably via the params option so it is correctly encoded...
export const getPostsByKeyword = (keyword, page) =>
API.get("/post/getPostsByKey", { params: { page, keyword } });
and grab the data via req.query server-side.
const { page, keyword } = req.query;
With vanilla JS, you can use URLSearchParams to construct the query string...
const params = new URLSearchParams({ page, keyword });
// XHR
const xhr = new XMLHttpRequest();
xhr.open("GET", `/post/getPostsByKey?${params}`);
// Fetch
fetch(`/post/getPostsByKey?${params}`); // GET is the default method
Your Axios instance creation could also be a lot simpler...
Axios is usually quite good at setting the correct content-type header, you don't have to
Your Express app isn't doing any content-negotiation so you don't need to set the accept header
Unless you're actually using cookies (which it doesn't look like), you don't need credential support
const API = axios.create({ baseURL });

How to prevent async - await freezing in javascript?

Good day I have a custom adonisjs command that pulls from an API.
async handle (args, options) {
// Status
// Open = 1979
// Get all jobs with open status.
const pullJobController = new PullJobsFromJobAdderController;
let token = await pullJobController.get_token();
if(token){
const jobs = await this._getOpenJobs('https://jobs/open-jobs', token , 1979);
}
}
async _getOpenJobs(url, accessToken, status) {
url = url + '?statusId=' + status
const headers = {
'Authorization': 'Bearer ' + accessToken
}
const options = {
method: 'GET',
url: url,
headers: headers
}
return (await rp(options).then(function (result) {
return {
status: true,
info: JSON.parse(result)
}
}).catch(function (error) {
return {
status: false
}
}));
} // _getOpenJobs()
PullJobsFromJobAdderController
async get_token()
{
// This works if directly returning the token.
// return "9ade34acxxa4265fxx4b5x6ss7fs61ez";
const settings = await this.settings();
const jobAdderObject = new this.JobAdder(settings.jobadder['client.id'], settings.jobadder['client.secret'])
const jobadderOauthObject = this.model('JobadderOauth');
const accessInfo = await jobadderOauthObject.jobdderLatestAccess();
let isAccessExpired = await this.checkAccessValidity(accessInfo.created_at);
let accessToken = accessInfo.access_token;
let apiEndpoint = accessInfo.api_endpoint;
if(isAccessExpired === true){
let refreshTokenInfo = await jobAdderObject.refrehToken(accessInfo.refresh_token)
if (refreshTokenInfo.status === true) {
let refreshTokenDetails = JSON.parse(refreshTokenInfo.info)
accessToken = refreshTokenDetails.access_token
apiEndpoint = refreshTokenDetails.api
await jobadderOauthObject.create({
code: accessInfo.code,
access_token: refreshTokenDetails.access_token,
refresh_token: refreshTokenDetails.refresh_token,
scope: 'read write offline_access',
api_endpoint: refreshTokenDetails.api
})
}
}
return accessToken;
} // get_token()
The function async get_token works as expected, it supplies me with a fresh token to be used by the adonisjs command. However it freezes after running the command.
But if I return the string token directly. The custom command handle() works as expected and terminates after running.
Scenario 1: (Directly returning the token string from PullJobsFromJobAdderController)
I run my custom command "adonis pull:jobs" and it runs as expected displaying in the terminal the result of the pulled data from the api.
Terminal is ready to accept another command.
Scenario 2: (Comment out the directly returned string token from PullJobsFromJobAdderController)
I run my custom command "adonis pull:jobs" and it runs as expected
displaying in the terminal the result of the pulled data from the
api.
Terminal is not accepting commands until I press ctrl+c and terminate the current job/command.
Perhaps I am missing something regarding async await calls.
Can someone point / help me to the right direction?
TIA
I got it, for anyone else having this kind of problem with adonis commands:
wrap the task inside your handle in a try... catch block then always have Database.close() and process.exit() in finally.

fetch url with cookies

so im scraping a web and i need to use a specific cookies but i dont know how to exactly use "fetch"
const url="https://www.example.com";
let response = await fetch(url),
html = await response.text();
let $ = cheerio.load(html)
var example= $('.exampleclass').text();
Now i can scrape the web but in case i would have to use a specific cookies i dont know how to put in on the fetch.
In python was something like that
response = requests.get(url, headers=headers, cookies=cookies)
Thank you!
You can add the cookies on the headers on node-fetch, I've made a helper function that you can use for your purposes:
const cookieMaker = object => {
const cookie = [];
for (const [key, value] of Object.entries(object)) {
cookie.push(`${key}=${value}`);
}
return cookie.join('; ');
};
const fetchText = async (url, cookie) => {
const r = await fetch(url, {
headers: {
Cookie: cookieMaker(cookie),
},
});
return await r.text();
};
fetchText('http://someurl.com', { token: 'abc', myValue: 'def' });

Loading headers from SecureStore in apisauce

All the endpoints in the backend require Authorization header. This header is stored in SecureStore.
Problem Statement
I want to load the Authorization header ( JWT Token ), for every API call after logging in.
Now this requires an async operation i.e.authStorage.getToken.
This is my client.js ( the apisauce client ).
client.js
import { create } from "apisauce";
import authStorage from "../auth/storage";
import IP from "../config/network";
const restoreToken = async () => {
return await authStorage.getToken("idToken");
};
const apiClient = (auth_token = "") =>
create({
baseURL: "http://" + IP + ":8990",
headers: { Authorization: auth_token }, // This I've added later
});
export default apiClient;
This is the PostsApi which uses apiClient to make the calls.
PostsApi.js
import apiClient from "./client";
const endpoint = "/api/";
const bookmarkEndpoint = "/bookmark/";
const getPosts = (last_id = 0, limit = 10) => {
return apiClient.get(endpoint + "?last_id=" + last_id + "&limit=" + limit);
};
const toggleBookmark = (item_id) => {
return apiClient.get(bookmarkEndpoint + "?item_id=" + item_id);
};
export default {
getPosts,
toggleBookmark,
};
My Understanding
I understand that if I can add the header in client.js itself, it would be injected everytime there's an API call.
I've tried :
const restoreToken = async () => {
return await authStorage.getToken("idToken");
};
But I am not sure how to call this async operation in client.js
Bonus Question
This token ( idToken ) would be reloaded every hour, so it's best to get the token from SecureStore everytime instead of saving it once.
Thanks.
Accepted answer and what worked for me
Worked for me
apisauce's setHeader : Documented here
Accepted answer is a detailed drilling of the axios setting up of headers. So if someone's using axios client directly they can see the accepted answer else, if you're an apisauce user, use the setHeader functionality provided with the library.
Cheers.
You will have to store your token with the state (can be redux or local state).
During save/refresh/reload the token, you will have set headers of the HTTP client.
You can set header using below command (example)
export const setAuthToken = (token) => {
apiClient.defaults.headers.common['Authorization'] = ''
delete apiClient.defaults.headers.common['Authorization']
if (token) {
apiClient.defaults.headers.common['Authorization'] = `Bearer ${token}`
}
}
Call the above function to set a token during reload/refresh/creation of token.
const restoreToken = async () => {
return await authStorage.getToken("idToken").then(token => setAuthToken(token));
};

“Unauthorized” response when cityiq API

I am trying to access the Current powered by GE CityIQ API to develop a parking app, I followed the API documentation however I cannot seem to successfully query because I do not have an access token. I have a user name and password as well as the urls and predix zone id for parking provided by the city I am using. When I try and run my javascript and log my access token the response is “Unauthorized”. Do i have to raise a request to the city for the access token?
The code is written in javascript and is using node.js and node-fetch.
Here is my code:
const fetch = require("node-fetch")
function request(url, headers, body) {
let options = { headers: headers, body:body}
return fetch(url, options).then(result => {
if (result.status>=400) return(result.statusText)
else return result.text().then(txt => {
try { return JSON.parse(txt) }
catch (err) { return txt }
})
})
}
// my credentials
const developer, uaa, metadataservice, eventservice, predixZone
developer = '{user}:{pass}'
uaa='{uaaURL}'
eventservice='{eventURL}'
metadataservice='{metadataURL}'
predixZone='{predixzoneParking}'
async function example(event){
let devToken = (await request(uaa+'?grant_type=client_credentials', {authorization: 'Basic '+developer}))
console.log(devToken)
let output = (await request(metadataservice+'/assets/search?q=eventTypes:PKIN',{authorization: 'Bearer '+devToken,'predix-zone-id':predixZone})).content
console.log(output)
}
example()
What am I doing wrong or probably missing?
It looks like you have not base64 encoded your username and password.
At the top of your code:
const btoa = str => new Buffer(str).toString('base64')
When you declare your user name and pass:
developer = btoa('{user}:{pass}')

Categories