Make HTTP requests from plugins

Hi!
How to properly make requests to remote websites from a plugin? I tried simple fetch("URL"), but it is blocked by CORS: Access to fetch at 'https://url' from origin 'app://obsidian.md' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

4 Likes

My personal fave way to work around CORS issues is to create a middleware server that you control. From there, you can make whatever external calls you want. Then from your plugin fetch($YOUR_SERVER) because you control both ends you can set the correct CORS policy.

A super simple (and free) way to do this is using Glitch’s express server template. Add the cors npm package and use it with express: app.use(cors()). You’ll be able to fetch any endpoint you setup in express within your plugin.

There are other ways and some are faster/easier, but controlling both sides of the request is the best way imho.

4 Likes

Sorry for dumb questions, I’m not really familiar with the javascript ecosystem and approaches. I understand the CORS concept in the context of web pages. But shouldn’t it be possible to make an arbitrary web request from a local application such as obsidian and ignore CORS headers? Creating, hosting and maintaining a separate server sounds like too much of a hassle.

Obsidian is an electron app which means it is essentially Chrome under the hood. You’ll need to handle CORS like you would in any other client-side cross domain request just as the error indicates.

You could try throwing no-cors on the fetch request. Or Google handling cors with fetch.

If you want to go the quick-n-dirty route lookup ‘cors anywhere’. I wouldn’t ship a plugin with cors anywhere though as it’s liable to break.

Setting up a server of Glitch is pretty fast and easy tbh. Just use the express template and move your cross-browser fetching into the glitch app.

1 Like

Regular webpages opened in Chrome don’t get access to the file system, while obsidian plugins can use the javascript fs module for local files. I thought that something similar is available for web requests.

1 Like

If you are okay with your plugin only working in the desktop you can use the package node-fetch. GitHub - node-fetch/node-fetch: A light-weight module that brings the Fetch API to Node.js node’s libraries bypass cors restrictions but are only available in the desktop apps.

4 Likes

Hi,

I have a similar problem. I want to implement a CalDav Client Plugin and get the same error.

I try to implement a middleware with express and the cores package, but it doesn’t work. Can someone give me please a code example, a tutorial or a link to the Glichßs express server?

Thanks
Kantiran

You can overcome this by using node.js’ http/https module instead of fetch (or use a convenience module like request). It doesn’t have the same limitations.

require('request')('http://example.com/', (error, response, body) => {
    console.log(JSON.parse(body));
});
2 Likes

I want to get google books API data, but I’m running into this same CORs policy issue. @intellectronica, I don’t think your solution works for me, at least while trying in the dev console. Some people might have luck with this article. I haven’t got it to work yet.

I’m not working on a plugin, but I’m using a dataviewJS code block to download image data.

Hello @mjduck,

This may interest you !

It’s a script to be used inside Quickadd plugin to fetch book data from Google Books API, and then automatically create a note from a template.

You can check line 183 of the script how the request is made using request. I have not encountered CORs issues with that function.

The author of Quickadd plugin made a script to fetch movie data using OMDB API (using the same request function) which helped me a lot to write this Google Books API script.

1 Like

Well that’s neat. Thanks @JamesKF !

1 Like

In plugin you should import requestUrl function provided by obsidian package, see the declaration at obsidian-api/obsidian.d.ts at c01fc3074deeb3dfc6ee02546d113b448735b294 · obsidianmd/obsidian-api · GitHub

4 Likes

Is this usable from within a Templater template or DataviewJS? That is, is it usable within arbitrary JS executing within an Obsidian plugin?

Asking as something of a node/TS newbie but experienced developer. I assume that this depends on what sort of library search paths are available from within the environment provided at runtime by these respective plugins?

1 Like

DataviewJS and Templater have access to most functions.
These included.
You can check by opening the Console in the Developer tools, and see if it shows up.

The one thing they can’t do is anything related to event handling.

1 Like

So how would a basic dataviewjs codeblock look that incorporates such a requestUrl call? How would the response be handled inside such a dataviewjs codeblock?
Imagine the request I want to use returns a JSON string which contains data/numbers I want to see in a table (or even visualize with the Obsidian Charts plugin)?

As a quick example:
One of my notes has a weather forecast embedded that uses DVJS

Here is a part of it

const headers = {
"Authorization": "Bearer whatever"
}

const forecast = await requestUrl({url: "https://weatherapi/forecast", headers})

dv.table(["Datum", "Icon", "Beschreibung", "Min", "Max", "Regen", "Luftfeuchtigkeit"], forecast.json.map(day => {
return [
	dv.date(day.date).day + ".",
	"![icon|50](" + day.icon + ")",
	day.description,
	day.temperature_min + "°C",
	day.temperature_max + "°C",
	day.probability_of_precipitation + "%",
	day.humidity + "%"
]}))

The data returned by that API looks like this:

[
    {
        "date": "2022-06-26T11:00:00Z",
        "sunrise": "2022-06-26T02:55:38Z",
        "sunset": "2022-06-26T20:00:30Z",
        "temperature_day": 19.19,
        "temperature_night": 18.76,
        "temperature_min": 18.3,
        "temperature_max": 21.55,
        "feels_like_day": 19.38,
        "feels_like_night": 18.65,
        "pressure": 1016,
        "humidity": 85,
        "dew_point": 16.65,
        "uvi": 6.26,
        "clouds": 100,
        "visibility": 0,
        "wind_speed": 7.47,
        "wind_degree": 0,
        "description": "Leichter Regen",
        "icon": "icon_url_here",
        "probability_of_precipitation": 0.5
    },
....
]

2 Likes

Awesome, @joethei, I guess this helps a lot! Will dive into this, asap! :star_struck:
Thanks a lot!!! :pray:

I am using requestUrl to avoid this issue but it seems that I am getting an issue anyway. Error thrown:

{
“status”:400,
“headers”:{
“vary”:“Origin, Access-Control-Request-Method, Access-Control-Request-Headers”,
“x-content-type-options”:“nosniff”,
“x-frame-options”:“DENY”,
“x-xss-protection”:“1; mode=block”
}
}

Anyone got any ideas?

You’d have to post the request code for anyone to give any hints.

HTTP status 400 “indicates that the server cannot or will not process the request due to something that is perceived to be a client error (for example, malformed request syntax”.

So it sounds like the code to create the request is invalid.

I do apologise, first time posting here … not something I am used to doing :slight_smile:

Here is the code, it’s a pretty simple call:

const options: RequestUrlParam = {
    url: url,
    method: 'POST',
    headers: {
        'X-Api-Key': this.settings.apiToken,
        'Content-Type': 'application/json'
    },
    body: json
}


var response: RequestUrlResponse;

try
{
    response = await requestUrl(options);

    return response.text;
}
catch(e) {
    console.log(JSON.stringify(e);
}

I captured the actual request and sent it to the server (headers and all) using Postman (just to check that what I am sending is valid) and the server accepted it no problem.

I can’t actually see anything of my call appearing in the Network tab of Developer Tools. I can see other traffic but nothing on this call. All seems a bit weird but I am very new to this so hoping someone can spot something obvious.

It might also be worth (or not) pointing out that it is throwing an error rather than returning the status via the response.