How can I get data from an asp.net API that uses cookie authentication from inside a custom function.
I followed the examples from https://learn.microsoft.com/en-us/office/dev/add-ins/excel/custom-functions-web-reqs. I can make requests but fetch does not seem to be including the cookies in subsequent requests. It seems like fetch has been nerfed in custom functions.
/**
* #customfunction
*/
async function CalcbenchData(): Promise<number> {
let batchURL = 'https://www.calcbench.com/api/NormalizedAPIBatch'
let data = [{ "metric": "revenue", "ticker": "msft", "year": 2015, "period": 1, "datatype": 1 }]
await login()
return postData(batchURL, data)
}
/**
* the reponse from this function sets the ASP.net authentication token cookie
*/
async function login() {
let email = encodeURIComponent('username')
let password = encodeURIComponent('password')
let url = `https://www.calcbench.com/account/LogOn?email=${email}&password=${password}`
await fetch(url,{
method: 'GET',
mode: 'same-origin'
});
}
function postData(url = '', data = {}): Promise<number> {
return fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'same-origin', // no-cors, cors, *same-origin
headers: {
'Content-Type': 'application/json',
},
redirect: 'follow', // manual, *follow, error
referrer: 'no-referrer', // no-referrer, *client
body: JSON.stringify(data), // body data type must match "Content-Type" header
}).then(response => {
return response.json()
}).then(json => {
return json.value
}) // parses JSON response into native JavaScript objects
}
7/25/2019 17:49:54 Verbose Runtime [Console] [Log] Unexpected CustomFunctions [Execution] [End] [Failure] [RejectedPromise] Function=CALCBENCHDATA TypeError: Network request failed {}
this is currently not possible with the custom functions runtime on windows (as it is a seperate process from the authentication dialog) but is under active development on how we enable this. Please watch this item on GitHub for upcoming updates: https://github.com/OfficeDev/Excel-Custom-Functions/issues/118. We'll likely update it in a couple of weeks time.
Thanks
Related
I'm currently learning how to use axios and fetch api. I'm trying to make a request using a fetch api like this:
let response = await fetch('https://online.yoco.com/v1/charges/', {
method: 'POST',
headers: {
'X-Auth-Secret-Key': process.env.SECRET_KEY,
},
body: {
token: paymentToken,
amountInCents: 2799,
currency: 'ZAR'
}
});
let responseData = await response.json()
And an axios post request like this:
axios.post(
'https://online.yoco.com/v1/charges/',
{
token: 'tok_test_DjaqoUgmzwYkwesr3euMxyUV4g',
amountInCents: 2799,
currency: 'ZAR',
},
{
headers: {
'X-Auth-Secret-Key': SECRET_KEY,
},
},
)
.then(res => {
//code
})
.catch(error => {
// handle errors
})
Is the request the same or not?
Cause the fetch returns an error
No, those aren't the same, in two ways:
If you look at MDN's documentation for fetch, you'll see that it says this about body:
body
Any body that you want to add to your request: this can be a Blob, an ArrayBuffer, a TypedArray, a DataView, a FormData, a URLSearchParams, string object or literal, or a ReadableStream object. This latest possibility is still experimental; check the compatibility information to verify you can use it. Note that a request using the GET or HEAD method cannot have a body.
Notice that a plain object is not on that list.
You're not checking for HTTP errors. This is unfortunately a footgun in the fetch API (I wrote about it here): It only rejects its promise on network errors, not HTTP errors like 404.
I'm going to assume that your API accepts JSON. If so, you need to include the Content-Type header and call JSON.stringify:
let response = await fetch("https://online.yoco.com/v1/charges/", {
method: "POST",
headers: {
"X-Auth-Secret-Key": process.env.SECRET_KEY,
"Content-Type": "application/json", // ***
},
body: JSON.stringify({ // ***
token: paymentToken,
amountInCents: 2799,
currency: "ZAR",
}), // ***
});
if (!response.ok) { // ***
throw new Error(`HTTP error ${response.status}`); // ***
} // ***
let responseData = await response.json();
In laravel 8 app "jd-dotlogics/laravel-grapesjs": "^3" is used and
https://github.com/Ju99ernaut/grapesjs-tailwind plugin is used to add custom block at grapejs editor, which
looks like https://prnt.sc/cITyK6U2AKzM
I need to add custom blocks based on data of our app at this area.
Reading https://github.com/Ju99ernaut/grapesjs-tailwind page I did not find any possibility to add custom blocks in sinilar way.
So in file config/laravel-grapesjs.php I replaced path to grapesjs-tailwind file :
[
'name' => 'grapesjs-tailwind',
'options' => [],
'scripts' => [
// 'https://unpkg.com/grapesjs-tailwind'
'js/custom-grapesjs-tailwind.min.js'
]
]
I saved file as public/js/custom-grapesjs-tailwind.min.js and unpacking it try to this file manually.
All these items are filled in big array like : https://prnt.sc/VihL339Z2-g1
I try to run request with axios, but I have a problem that I can not to import axios in plain js file:
window.axios = require('axios');
window.axios.get('pages/{page_id}/get-custom-blocks')
.then(({data}) => {
I got error :
ReferenceError: require is not defined
With line :
import { axios } from 'axios'
I got error :
Uncaught SyntaxError: Cannot use import statement outside a module
If there is a way to use axios in public/js/custom-grapesjs-tailwind.min.js ?
Are there some other similar decisions with grapesjs compatible with "jd-dotlogics/laravel-grapesjs" ?
UPDTATED BLOCK :
Looking at doc https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
I do it as next :
c = retrieveGetData('/pages/4/get-custom-blocks', {})
.then(data => {
console.log('retrieveGetData data::')
console.log(data); // JSON data parsed by `data.json()` call
});
...
async function retrieveGetData(url = '', data = {}) {
const response = await fetch(url, data = {}, {
method: 'GET', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
let retArray = response.json()
console.log('retArray::')
console.log(retArray)
console.log('retrieveGetData retArray.customBlocks::')
console.log(retArray.customBlocks)
console.log('retrieveGetData retArray.Promise::')
console.log(retArray.Promise)
console.log('retrieveGetData retArray.PromiseResult::')
console.log(retArray.PromiseResult)
console.log('retrieveGetData retArray.PromiseResult.customBlocks::')
console.log(retArray.PromiseResult.customBlocks)
return retArray.customBlocks;
}
In browser's console I see that I got returned data : https://prnt.sc/2llG-UG8fnRD
I expected with response.json() to get valid array of data, but looks like my request is not valid ?
UPDATED BLOCK # 2 :
I remade function retrieveGetData so that it returns response object:
async function retrieveGetData(url = '', data = {}) {
console.log('retrieveGetData url::')
console.log(url)
const response = await fetch(url, data = {}, {
method: 'GET', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
return response
}
and calling this method :
retrieveGetData('/pages/4/get-custom-blocks', {})
.then(data => { // get data from server - got Response object
console.log('retrieveGetData data::')
console.log(data);
let json_data =data.json() // I got Promise pending object
console.log('get-custom-blocks json_data::')
console.log(json_data)
let c = json_data.customBlocks // get custom data - I got undefined data
console.log('get-custom-blocks c::')
console.log(c)
c.forEach((function (t) { // run circle for custom data
e.add(t.id, {
label: t.label,
attributes: {class: t.class},
content: t.content,
category: {label: t.category, open: 'Blog' === t.category}
})
}))
});
I see in browser's console : https://prnt.sc/VuYb-IyK1LNf
What is wrong in my code ?
UPDATED BLOCK # 3 :
Yes, declaration of retrieveGetData has async and await calling of axios :
async function retrieveGetData(url = '', data = {}) {
const response = await fetch(url, data = {}, {
method: 'GET', // *GET, POST, PUT, DELETE, etc.
...
});
return response
}
and calling it :
await retrieveGetData('/pages/4/get-custom-blocks', {})
.then(data => { // get data from server
console.log('retrieveGetData data::')
console.log(data); // JSON data parsed by `data.json()` call
let json_data =data.json()
console.log('get-custom-blocks json_data::')
console.log(json_data)
let c = json_data.customBlocks // get custom data
console.log('get-custom-blocks c::')
console.log(c)
c.forEach((function (t) { // run circle for custom data
e.add(t.id, {
label: t.label,
attributes: {class: t.class},
content: t.content,
category: {label: t.category, open: 'Blog' === t.category}
})
}))
});
But in this case I got error :
Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules (at custom-grapesjs-tailwind.min.js:504:9)
#matiaslauriti, your code in answer is not clear.
I do not see where from “data” var ?
What kind of code is it
retrievedData = await retrieveGetData
?
Please write this block in details...
My webpack.mix.js has :
const mix = require('laravel-mix');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css')
.sourceMaps();
mix.js('resources/js/editor-modules.js', 'public/js')
.sass('resources/sass/pages.scss', 'public/css');
mix.js('resources/js/editor-config.js', 'public/vendor/laravel-grapesjs/assets')
.sass('resources/sass/grapesjs-editor.scss', 'public/css');
Thanks!
Based on your new code, the issue would be that you are missing an await:
retrievedData = await retrieveGetData('/pages/4/get-custom-blocks', {});
c = data.json();
Remember that the function having this code must also have async defined.
EDIT:
So, let me update the code:
const retrievedData = await retrieveGetData('/pages/4/get-custom-blocks', {});
const c = data.json();
What I am saying on my answer is that you need to use await, so c is not a promise that you pass (check your original question) but real data.
So your code needs to be wrapped in an async function like this:
async function xxxx() {
const retrievedData = await retrieveGetData('/pages/4/get-custom-blocks', {});
return data.json(); // Return that or store it on variable c as you did
}
As your original question was related to the laravel-grapesjs package. So I wrote the answer here. Copying it here for your reference.
You can add it below way -- And the axios global variable will be available in your custom-grapesjs-tailwind.min.js file
[
'name' => 'grapesjs-tailwind',
'options' => [],
'scripts' => [
'https://unpkg.com/axios',
'js/custom-grapesjs-tailwind.min.js'
]
]
I have the following code which works when I run it as a local serverless function with netlify dev, but I need it to run cross origin from a dev server to the hosted server function. I put the function in a aws lambda function but I am getting a cross origin blocked error on my https:dev.website.com, I thought I have the correct headers in the return object so not sure why I am getting a cross origin error.
Any help would be great
const sanityClient = require("#sanity/client");
const client = sanityClient({
projectId: "random-id",
dataset: "production",
useCdn: true,
});
exports.lambdaHandler = async (event, context) => {
var body = JSON.parse(event.body);
//console.log(body.price_id)
try {
const checkPriceId = async (test) => {
const query = `*[_type == "products" && price_id == "${body.price_id}"]`;
const documents = await client.fetch(query, {}); // this could throw
return documents.map((document) => document.sold);
};
var ok = checkPriceId().then((test) => {
return new Promise(function (resolve, reject) {
//console.log(test) // this will log the return value from line 7
console.log(test);
resolve(test);
});
});
var bools = await ok;
// prettier-ignore
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods':'GET, POST, OPTION',
},
body: JSON.stringify({
sold: bools,
}),
};
} catch (err) {
return { statusCode: 500, body: err.toString() };
}
};
This is my request to the function if that helps
var fetchUrl = https://random.executue-api.aws.com/prod/sold //not exact
var fetchData = async function () {
const response = await fetch(fetchUrl, {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
price_id: final,
}),
})
.then(res => {
return res.json()
})
.catch(error => console.log(error))
return response
}
Update:
I tried adding cors the way suggested in the answer below, but it failed seen below so I tried manually adding the method response seen after.
I still get a cross domain error. And I have changed the domain so it is now https as well. Really stuck here.
I was looking into this more, and it seems like before it does the actual post it does a cors check at the options method, so I added in the same access control headers, and deployed but did not work. Don't quite get this.
Your headers look ok to me. (note: If you mix HTTP and HTTPS you are most likely to get a mixed content error in the client). If it is ONLY a CORS issue that you are seeing in the console in the web browser, then you might not have configured the API Gateway correctly in AWS.
In AWS, go to API Gateway and you should see something like the below:
Make sure that you enable CORS and then redeploy.
UPDATE:
Just looking at a previous implementation of a lambda function I setup with AWS. The headers I declared were as follows:
headers: {
"Content-Type" : "application/json",
"Access-Control-Allow-Origin" : "*",
"Allow" : "GET, OPTIONS, POST",
"Access-Control-Allow-Methods" : "GET, OPTIONS, POST",
"Access-Control-Allow-Headers" : "*",
"Access-Control-Allow-Credentials" : true
}
Your headers look OK to me though. However, when you created the method in the API Gateway, did you select Use Proxy Lambda Integration? (see screenshot).
Your client side fetch request looks ok. For reference mine was:
const url = 'your url';
const options = {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
};
fetch(url, options).then(res => res.json());
Unrelated to this issue, but its not advisable to mix Async/Await with .then promise chaining. But this isn't the issue you are having. Just something to note.
Check the values from your Integration Response / try setting them manually for both OPTIONS and POST (and if that works, make sure you are passing through the response correctly from the lambda).
Your POST action should only require the Access-Control-Allow-Origin header. The other two (Access-Control-Allow-Methods, Access-Control-Allow-Headers) belong in the OPTION action. See this writeup, and note the full example exchange for a preflighted request (in grey): https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests
I am writing this app.js client file that performs a post request to a server (code below):
const fetch = require('node-fetch');
/* Function to POST data */
const postData = async ( url = 'http://localhost/8000/add/', data = {})=>{
console.log(data)
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
try {
const newData = await response.json();
console.log(newData);
return newData
}catch(error) {
console.log("errors", error);
// appropriately handle the error
}
}
// TODO-Call Function
postData('/addAnimal', {animal: 'girrafe'});
I keep on getting this error when I run with node app.js:
node .\app.js
{ animal: 'girrafe' }
D:\Downloads\FEWD\WebAPIs\N&E\node_modules\node-fetch\lib\index.js:1305
throw new TypeError('Only absolute URLs are supported');
^
TypeError: Only absolute URLs are supported
at getNodeRequestOptions (D:\Downloads\FEWD\WebAPIs\N&E\node_modules\node-fetch\lib\index.js:1305:9)
D:\Downloads\FEWD\WebAPIs\N&E\node_modules\node-fetch\lib\index.js:1305
throw new TypeError('Only absolute URLs are supported');
^
TypeError: Only absolute URLs are supported
at getNodeRequestOptions (D:\Downloads\FEWD\WebAPIs\N&E\node_modules\node-fetch\lib\index.js:1305:9)
at D:\Downloads\FEWD\WebAPIs\N&E\node_modules\node-fetch\lib\index.js:1410:19
at new Promise (<anonymous>)
at fetch (D:\Downloads\FEWD\WebAPIs\N&E\node_modules\node-fetch\lib\index.js:1407:9)
at postData (D:\Downloads\FEWD\WebAPIs\N&E\demo\app.js:22:31)
at Object.<anonymous> (D:\Downloads\FEWD\WebAPIs\N&E\demo\app.js:42:3)
Any suggestion as to why, would be much appreciated.
Thank you
Your url = 'http://localhost/8000/add' sets the default parameter to that URL, then you overwrite it with '/addAnimal' when you provide that as an argument. You should provide an absolute URL:
postData('http://localhost/8000/addAnimal', {animal: 'girrafe'});
As a side note, I'm pretty sure localhost/8000 is a typo and should be localhost:8000.
I'm trying to fetch() text/plain data from a remote service. If I place a breakpoint in the promise "then" chain, the text data from the server is available. Without the breakpoint, I get a fetch() exception.
I am using a prototype design pattern (see below). When I place a breakpoint in the "then" chain as shown below, the data from the remote service is successfully retrieved. Without the breakpoint, the catch() is executed and the error is:
TypeError: Failed to fetch
I'm totally stumped and would appreciate any help!
Note, the server (a python app) sends back html, with
self.send_header("Access-Control-Allow-Origin", "*")
Also, if I use Ajax (FWIW, it works). I'd like to get it working with fetch() however.
function Fetch(Msg) {
// Msg contains some info on how to construct the JSON message to transmit -- not relevant here.
this.help = `
The Fetch object specifies communication basics using
the fetch(...) mechanism.
`;
// some misc object vars...
}
Fetch.prototype = {
constructor: Fetch,
postData: async function (url = '', data = {}) {
const response = await fetch(url, {
method: 'POST,
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'text/plain',
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
// body data type must match "Content-Type" header
body: JSON.stringify(data)
});
return await response.text(); //
},
handleErrorsInResponse: function (response) {
var debug = new Debug("Fetch.handleErrorsInResponse");
debug.entering();
debug.leaving();
},
handleReponse: function (response) {
var debug = new Debug("Fetch.handleResponse");
debug.entering();
console.log(response);
debug.leaving();
},
handleErrorsInFetch: function (response) {
var debug = new Debug("Fetch.handleErrorsInFetch");
debug.entering();
console.log(response);
debug.leaving();
},
call: function (payload) {
this.postData(
'http://some.url/',
payload)
.then(this.handleErrorsInResponse) // If I place a breakpoint here it works!
.then(this.handleReponse)
.catch(this.handleErrorsInFetch);
},
}
// Ultimately called by something like
comms = new Fetch();
someData = {"key": someJSON};
comms.call(someData);
Remove the wait on the response.
Replace
return await response.text();
by
return response.text();