CORS issue posting to Discourse from Obsidian

I’ve started using Obsidian for writing so figured I’d make an Obsidian plugin for posting to my Discourse forum. In theory that’s easy to do, but the response from my first attempt was:

Access to fetch at 'http://localhost:4200/posts.json' from origin 'app://obsidian.md' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

The request is being made from the client, but the client is the user’s device. If I’m understanding things correctly, this is safe:

import DiscoursePlugin from "./main";
import { TFile } from "obsidian";

export async function publishToDiscourse(
	plugin: DiscoursePlugin,
	activeFile: TFile
): Promise<{ message: string }> {
	try {
		const content = await plugin.app.vault.read(activeFile);
		const baseUrl = plugin.settings.baseUrl;
		const apiKey = plugin.settings.apiKey;

		const headers = new Headers({
			"Content-Type": "application/json",
			"Api-Key": apiKey,
			"Api-Username": "scossar", // this needs to be a setting
		});
		const body = JSON.stringify({
			title: activeFile.name,
			raw: content, // TODO: parse the content to fix internal links
			category: 1, // this needs to be a setting
		});
		// TODO: check to see if the note has already been published
		const url = `${baseUrl}/posts.json`;

		const response = await fetch(url, { method: "POST", headers, body });

		if (!response.ok) {
			console.error("Error publishing to Discourse:", response.status);
			return { message: "Error publishing to Discourse" };
		}
        
		const jsonResponse = await response.json();
		// TODO: use the response to add a discoursePostId file property
		console.log(`jsonResponse: ${JSON.stringify(jsonResponse, null, 2)}`);
		return { message: "success" };
	} catch (error) {
		console.error("Error publishing to Discourse:", error);
		return { message: `Error: ${error.message}` };
	}
}

It’s possible there’s an obvious workaround that I’m missing. If not, is there any way that Discourse could allow it to work? An Obsidian Discourse plugin seems like it could be useful. (A proper implementation would be more complex than what I posted above.)

3 Likes

I was confused at first, but I see, you’ve configured your own Obsidian app with your key and only you have access to it. This isn’t happening in a browser so people on the internet don’t have your key.

I have a Discourse plugin that processes arbitrary forms (you can use it at https://www.formhoster.com/). I wanted to make it able to work as the current user if the user was logged in to the site processing the form and I hit what I think is the same CORS issue and gave up pretty quickly. Mine was in a browser rather than an app like obsidian, but I think the issue might be similar.

All that to say that I think I don’t have any good ideas, but I’m hoping someone else will. :person_shrugging:

1 Like

Right, Obsidian is an Electron app that’s running locally. It’s using local storage, so the API key stays on the user’s device.

It turns out there’s a solution for the CORS issue. I’ve only tested it on my desktop computer so far. Also, Discourse is great!

The code I posted above needed to be modified to:

import DiscoursePlugin from "./main";
import { requestUrl, TFile } from "obsidian";

//...
		const response = await requestUrl({
			url: url,
			method: "POST",
			contentType: "application/json",
			body,
			headers,
		});
//...

The next question will be about allowing users to request User API keys from the app, but that’s a separate issue.

3 Likes

Is this a plugin you’re working on? I have a user (and myself) who would love to be able to use this. I can try to recreate what you’ve done, but if there’s an easier way…

It was the start of something. For my own purposes I’m more interested in a CLI (command line) application that allows an Obsidian Vault to be synced with a Discourse forum. A CLI app makes it easier to deal with syncing an entire vault or subdirectory, handle internal links in Notes, deal with uploads, etc. The CLI app approach also fits in with some other work I’m doing, so I’m learning something useful from it.

The big downside of the CLI app is that it requires anyone who uses it to have Ruby installed on their computer. It’s also only going to work on desktop computers. There would be some technical challenges with using it on Windows computers.

I’ll post a link to the app on here once it’s ready to share.

If an Obsidian Discourse plugin, as opposed to a CLI app, is something that people are interested in, I’ll look at it again in the future. It’s possible someone else will get to that before me. I didn’t take it much further than the code example that’s posted above.

1 Like

If this is all you have so far, it looks fairly simple. I’m wondering how it would handle images at this point. I’d be willing to take a stab at it. I’ve never written a plugin for Obsidian before, but from what I’ve looked at (wayward glances at other plugins), it doesn’t look terribly complicated.

1 Like

This is the place to start: Build a plugin - Developer Documentation. I’d link to what I’d done, but It’s one of the few (hopefully only) projects that I didn’t push to Github before reinstalling the operating system on my computer last week.

You’ll have to figure out how to do it with Node. There’s a good Ruby example here: discourse_api/examples/upload_file.rb at main · discourse/discourse_api · GitHub. It calls: discourse_api/lib/discourse_api/api/uploads.rb at main · discourse/discourse_api · GitHub.

Can you share this part?

Hmm, loosing that code was kind of reckless. From memory, I was importing DiscoursePlugin defined in main.ts into a file that was handling a call to this.addCommand. To get started, you can do everything in main.ts. I’d just follow the guide for getting started with the example plugin. Start by messing around with it and trying to make something happen. I know that I used that plugin as the template for mine.

There are some good examples here too: obsidian-wordpress/src at main · devbean/obsidian-wordpress · GitHub.

1 Like

Welp, I’m gonna put a pin in this for now. I’ve made pretty good progress, but there are a few things I’m not a fan of.

  • API Key is stored as plaintext (VERY BAD)
  • images don’t upload, there are some configuration settings I haven’t explored yet. But I’m not a fan of setting up an AWS S3 bucket. I’ll look into that later.
  • some stylistic choices could be better, but I wanted something functional
  • The README needs to be updated. But, again, I just wanted something functional and posted.
1 Like

I wonder if there’s a way around that. In the CLI app I’m encrypting it with a password. The user has to supply the password before any operation that uses the API key.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.