I am trying to pass a query string into my serverless function but it keeps returning an empty object.
search = (searchTerm) => {
// let url = `${URL}${searchTerm}`;
return fetch(`/.netlify/functions/token-hider?search=${searchTerm}`)
.then((response) => response.json())
.then((result) => {
console.log(result.results);
return results;
});
form.addEventListener("submit", (e) => {
e.preventDefault();
let searchTerm = input.value;
search(searchTerm);
});
const axios = require("axios");
const qs = require("qs");
exports.handler = async function (event, context) {
// apply our function to the queryStringParameters and assign it to a variable
const API_PARAMS = qs.stringify(event.queryStringParameters.search);
console.log(event);
// const API_PARAMS = qs.stringify(event.queryStringParameters);
console.log("API_PARAMS", API_PARAMS);
// Get env var values defined in our Netlify site UI
// TODO: customize your URL and API keys set in the Netlify Dashboard
// this is secret too, your frontend won't see this
const { KEY } = process.env;
const URL = `https://api.unsplash.com/search/photos?page=1&per_page=50&client_id=${KEY}&query=${API_PARAMS}`;
console.log("Constructed URL is ...", URL);
try {
const { data } = await axios.get(URL);
// refer to axios docs for other methods if you need them
// for example if you want to POST data:
// axios.post('/user', { firstName: 'Fred' })
return {
statusCode: 200,
body: JSON.stringify(data),
};
} catch (error) {
const { status, statusText, headers, data } = error.response;
return {
statusCode: error.response.status,
body: JSON.stringify({ status, statusText, headers, data }),
};
}
};
it works when i hard code the query string, and i can console log the search term and it is defined.
Since Netlify redirect mechanism is not able to provide you the data of which rule it matched, you could try to match the original request in your function to determine what it should do.
Hope this helps you solve your specific issue!
Here is the reference
Related
I have this async function to get an API access token:
const getAccessToken = async () => {
try {
const body = new URLSearchParams({
grant_type: 'client_credentials',
scope: 'manage:all'
}).toString();
const config = {
headers: {
Content_Type: 'application/x-www-form-urlencoded'
},
auth: {
username: clientId,
password: clientSecret
}
};
const { data: res } = await axios.post(
`${baseUrl}/oauth2/token`,
body,
config
);
return res;
} catch (err) {
console.log(err);
}
};
It return a promise which I use in the following function to log the access token to the console:
getAccessToken().then(res => {
console.log(res.access_token);
});
It logs the token as a string.
Now I want to use this string value in another function in my code. Do I just call the function where I need the value like above and replace the console.log with return?
How do I use this string value in other parts of my code?
One way of doing it to call the function everywhere,
async function anotherFunction() {
const res = await getAccessToken();
console.log(res.access_token)
}
Another way of doing this is to call the function on your page load save the token in session/local storage/cookies and use the token in other functions by fetching it from session/local storage/cookies to avoid making multiple API calls.
On page load
const res = await getAccessToken();
localStorage.setItem('token', res.access_token);
async function anotherFunction() {
const res = localStorage.getItem('token');;
console.log(res.access_token)
}
Here is the function that I wanna test, it takes a token and a description as props. Normally in React code, I can get token from useContext.
export const updateUserProfileAbout = async (
token,
description
) => {
const dataUpdateTemplateDescriptionRes = await patchData(`me/`, token, {
about:description,
});
const dataUpdateTemplateDescriptionJson = await dataUpdateTemplateDescriptionRes.json();
return dataUpdateTemplateDescriptionJson;
};
And here is my custom patchData function:
const patchData = async (urn, token, data = "") => {
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${token.access}`,
};
const body = data ? JSON.stringify(data) : null;
let response;
if (body) {
response = await fetch(`${host}/api/${urn}`, {
method: "PATCH",
headers,
body,
});
} else {
response = await fetch(`${host}/api/${urn}`, {
method: "PATCH",
headers,
});
}
if (!response.ok) throw new Error(response.status);
return response;
};
You are right. You don't need the token. All you need to do for mocking the fetch is the following:
jest.spyOn(global, 'fetch').mockImplementationOnce(
jest.fn(() => Promise.resolve()) as jest.Mock);
If you want to retrieve a specific object from a json response, you can use:
jest.spyOn(global, 'fetch').mockImplementationOnce(
jest.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ myObject }) })) as jest.Mock);
You can also reject it to trigger the error catch:
jest.spyOn(global, 'fetch').mockImplementationOnce(
jest.fn(() => Promise.reject()) as jest.Mock);
If you want to return something multiple times, change the mockImplementationOnce to whatever you need (maybe mockImplementation, for returning it every time you call it).
If you also want to expect the call of the fetch just add a constant:
const myFetch = jest.spyOn(global, 'fetch').mockImplementationOnce(
jest.fn(() => Promise.reject()) as jest.Mock);
You can then expect it via: expect(myFetch).toBecalledTimes(1);
After one more day of researching, I might be wrong though but I don't think I have to care about token or authorization when unit testing for front-end. All I need is jest.fn() to mock function and jest.spyOn(global, "fetch") to track fetch API.
For more information, here are some references that I read:
https://codewithhugo.com/jest-fn-spyon-stub-mock/
https://dev.to/qmenoret/mocks-and-spies-with-jest-32gf
https://www.pluralsight.com/guides/how-does-jest.fn()-work
https://www.loupetestware.com/post/mocking-api-calls-with-jest
Multiple people have brought up issues similar to mine in this community and cloudflare's community. It still seems largely unsolved so I’m asking in hopes of a solution.
I’m trying to create a feature for users to sign up through mailchimp. User info goes from browser to workers to mail chimp. I’m getting the following errors:
TypeError: Failed to execute function: parameter 1 is not of type
‘Response’. at line 0, col -2
Request.Body is not being read
Request from Client:
const response = await axios({
method: "post",
url: "http://127.0.0.1:8787/signup",
data: {
MERGE0: email,
MERGE1: firstName,
MERGE2: lastName,
},
headers: {
"Content-Type": "application/json",
}
});
Workers part 1 (function to read request body):
https://developers.cloudflare.com/workers/examples/read-post
async function readRequestBody(request) {
const { headers } = request
const contentType = headers.get('content-type') || ''
if (contentType.includes('application/json')) {
return JSON.stringify(await request.json())
} else if (contentType.includes('application/text')) {
return request.text()
} else if (contentType.includes('text/html')) {
return request.text()
} else if (contentType.includes('form')) {
const formData = await request.formData()
const body = {}
for (const entry of formData.entries()) {
body[entry[0]] = entry[1]
}
return JSON.stringify(body)
} else {
// Perhaps some other type of data was submitted in the form
// like an image, or some other binary data.
return 'a file'
}
}
Workers part 2 (to Post JSON File to Mail Chimp):
https://developers.cloudflare.com/workers/examples/post-json
async function gatherResponse(response) {
const { headers } = response
const contentType = headers.get('content-type') || ''
if (contentType.includes('application/json')) {
return JSON.stringify(await response.json())
} else if (contentType.includes('application/text')) {
return response.text()
} else if (contentType.includes('text/html')) {
return response.text()
} else {
return response.text()
}
}
Workers Part 3 (to Handle Post Request):
async function eventHandler(request) {
const pathname = request.url
try {
if (pathname.indexOf('signup') !== -1) {
const reqBody = await readRequestBody(request)
const { MERGE0, MERGE1, MERGE2 } = reqBody
// Construct req data
const data = {
members: [
{
email_address: MERGE0,
status: 'subscribed',
merge_fields: {
FNAME: MERGE1,
LNAME: MERGE2,
},
},
],
}
const postData = JSON.stringify(data)
const options = {
method: 'POST',
headers: {
Authorization: `auth ${MAILCHIMP_API_KEY}`,
},
body: postData,
}
const url = `https://us5.api.mailchimp.com/3.0/lists/${MAILCHIMP_AUDIENCE_ID}`
const res = await fetch(url, options)
const results = await gatherResponse(res)
return results
}
} catch (err) {
console.log(err)
}
}
addEventListener('fetch', event => {
event.respondWith(eventHandler(event.request))
})
A few other posts I’ve referenced:
https://community.cloudflare.com/t/fetch-with-post-method-ignores-body/147758/3
https://community.cloudflare.com/t/how-to-post-with-a-body-as-readable-stream/211335
https://community.cloudflare.com/t/using-get-fetch-for-api-javascript-worker/98297
You have this code:
event.respondWith(eventHandler(event.request))
The event.respondWith() function needs to take a Response object as its parameter. However, your eventHandler() function does not return a Response. In some cases, the function does not return a result at all, and in other cases, it returns a string.
In cases where you don't want to modify the request/response, you can have eventHandler pass through the request to origin like so:
return fetch(request);
In cases where you have received a response from the origin, and you want to return it directly to the client unmodified, you should just return repsonse instead of return response.text().
In cases where you have created some new response text that you want to return, you need to wrap it in a Response, like:
return new Respnose(text, {headers: {"Content-Type": "text/plain"}});
I am building a simple single-page react website that uses two API's one is rest API and the second is GraphQl using simple hooks
What I am trying to do is:
first fetching the anime_chan data and passing the character name that I get from anime_chan data to GraphQl query search variable so that it gives me the data of that character
The flow I want is -
fetch rest API (anime_chan)
update the search variable in GraphQl query
fetch GraphQl API (ani_list)
set anime_chan_data
set ani_list_info
Problem is:
The GraphQl query search variable is never gets updated
Code
function App(){
const [variables, setVariables] = useState({
search:'lelouch'
});
// I also tried this
/* var variables = {
search: 'lelouch',
} */
console.log('before: '+ variables.search);
var query = `
query($search: String) {
Character(search: $search) {
name {
first
}
image {
large
medium
}
siteUrl
}
}`;
var ani_list_url = 'https://graphql.anilist.co',
options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query: query,
variables: variables
})
};
const anime_chan_url = 'https://animechan.vercel.app/api/random';
const [anime_chan_data, setAnime_chan_data] = useState({
loading:true,
data:{},
});
const [ani_list_info, setAni_list_info] = useState({});
const get_anime_chan_Data = async() =>{
const response = await fetch(anime_chan_url);
const anime_chan_data = await response.json();
console.log(anime_chan_data);
setAnime_chan_data({
loading:false,
data: anime_chan_data,
});
};
useEffect(() => {
get_anime_chan_Data();
setVariables({
search:anime_chan_data.character
});
console.log('After: '+ variables.search);
fetch(ani_list_url, options).then(handleResponse)
.then(handleData)
.catch(handleError);
function handleResponse(response) {
return response.json().then(function (json) {
return response.ok ? json : Promise.reject(json);
});
}
function handleData(info) {
console.log(info);
setAni_list_info(info);
}
function handleError(error) {
alert('Error, check console');
console.error(error);
}
}, []);
return (
<>...</>
}
I need to encode my faunadb instance's id because I use it in an URL of this type
/mission/(id number)/team
I create instance with this:
/* code from functions/todos-create.js */
import faunadb from 'faunadb' /* Import faunaDB sdk */
/* configure faunaDB Client with our secret */
const q = faunadb.query
const client = new faunadb.Client({
secret: process.env.FAUNADB_SECRET
})
/* export our lambda function as named "handler" export */
exports.handler = (event, context, callback) => {
/* parse the string body into a useable JS object */
const eventBody = JSON.stringify(event.body)
const data = JSON.parse(eventBody)
const mission = {
data: JSON.parse(data)
}
// {"title":"What I had for breakfast ..","completed":true}
/* construct the fauna query */
return client.query(q.Create(q.Ref("classes/missions"), mission))
.then((response) => {
console.log("success", response)
/* Success! return the response with statusCode 200 */
return callback(null, {
statusCode: 200,
body: JSON.stringify(response)
})
}).catch((error) => {
console.log("error", error)
/* Error! return the error with statusCode 400 */
return callback(null, {
statusCode: 400,
body: JSON.stringify(error)
})
})
}
I call this lambda with a function in a service:
createMission(data) {
return fetch('/.netlify/functions/mission-create', {
body: JSON.stringify(data),
method: 'POST'
}).then(response => {
return response.json();
});
}
and then I load this at the init of my page with adress '/mission/(id number)/team' :
this._missionService.readById(this.route.snapshot.params.missionId)
with this lambda by a service again:
import faunadb from 'faunadb'
const q = faunadb.query
const client = new faunadb.Client({
secret: process.env.FAUNADB_SECRET
})
exports.handler = (event, context, callback) => {
const id = event.path.match(/([^\/]*)\/*$/)[0];
console.log(`Function 'mission-read' invoked. Read id: ${id}`)
return client.query(q.Get(q.Ref(q.Class("missions"), id)))
.then((response) => {
console.log("success", response)
return callback(null, {
statusCode: 200,
body: JSON.stringify(response)
})
}).catch((error) => {
console.log("error", error)
return callback(null, {
statusCode: 400,
body: JSON.stringify(error)
})
})
}
Actually this is not secure because url handling with id number is possible to access all my missions.
I want to encode in base64 this id, but I don't know how to do this, I begin in dev and I thought first encrypt id in service and decrypt it in service but someone said me front is not secure, then I want to use base64 in back.
Thanks for advice !
The solution to this problem is definitely not to obfuscate the id with base64 (which will protect nothing), but instead you need to associate a user id with your sensitive information in the database.
You then can have them authenticate against your auth/data service to establish identity. Then the service will only serve them records that are associated to them.
The tricky part of this solution is writing an auth protocol for your lambda services.