Obsidian에서 Discourse로 게시할 때 발생하는 CORS 문제

저는 작성을 위해 Obsidian을 사용하기 시작했고, 제 Discourse 포럼에 게시물을 올리는 Obsidian 플러그인을 만들어 보려 했습니다. 이론적으로는 쉽게 만들 수 있지만, 첫 시도에 대한 응답은 다음과 같았습니다:

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.

요청은 클라이언트에서 발생하지만, 이 클라이언트는 사용자의 기기입니다. 제 이해가 맞다면, 이는 안전합니다:

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}` };
	}
}

제가 놓치고 있는 명백한 우회 방법이 있을 수도 있습니다. 만약 그렇지 않다면, Discourse에서 이를 작동하게 할 방법이 있을까요? Obsidian Discourse 플러그인은 유용할 것 같습니다. (적절한 구현은 위에 올린 것보다 더 복잡할 것입니다.)

처음에는 혼란스러웠지만, 이제 이해했습니다. 본인만의 키를 사용하여 Obsidian 앱을 직접 구성했고, 접근 권한은 본인에게만 있다는 것이죠. 이는 브라우저에서 발생하는 것이 아니므로 인터넷상의 다른 사람들이 키를 가지고 있지 않습니다.

저는 임의의 폼을 처리하는 Discourse 플러그인을 하나 가지고 있습니다(https://www.formhoster.com/ 에서 사용 가능합니다). 폼을 처리하는 사이트에 로그인된 상태라면 현재 사용자로 동작하도록 만들고 싶었는데, 제가 생각하기에 동일한 CORS 문제에 부딪혀 금방 포기했습니다. 제 경우는 Obsidian 같은 앱이 아니라 브라우저에서였지만, 문제의 성격은 비슷할 것 같습니다.

결국 제가 좋은 아이디어를 가지고 있지는 않다는 뜻이지만, 다른 누군가가 좋은 방법을 제시해 주기를 희망합니다. :person_shrugging:

맞습니다. Obsidian은 로컬에서 실행되는 Electron 앱입니다. 로컬 스토리지를 사용하므로 API 키는 사용자의 기기에 그대로 유지됩니다.

CORS 문제에 대한 해결책이 있는 것으로 밝혀졌습니다. 아직 데스크톱 컴퓨터에서만 테스트해 보았습니다. 또한, Discourse는 정말 훌륭합니다!

위에 게시한 코드는 다음과 같이 수정해야 했습니다:

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

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

다음 질문은 사용자가 앱에서 User API 키를 요청할 수 있도록 하는 방법에 관한 것이지만, 이는 별개의 문제입니다.

이거 지금 작업 중인 플러그인인가요? 이걸 사용해보고 싶은 유저(그리고 저 포함)가 있어요. 직접 재현해볼 수는 있지만, 더 쉬운 방법이 있다면…

이것은 무언가의 시작이었습니다. 제 개인적인 용도로는 Obsidian 볼트를 Discourse 포럼과 동기화할 수 있는 CLI(명령줄) 애플리케이션에 더 관심이 있습니다. CLI 앱은 볼트 전체 또는 하위 디렉터리를 동기화하는 것, 노트 내의 내부 링크를 처리하는 것, 업로드를 처리하는 것 등을 더 쉽게 다룰 수 있게 해줍니다. CLI 앱 접근법은 제가 진행 중인 다른 작업들과도 잘 어울리며, 이를 통해 유용한 것을 배울 수 있습니다.

CLI 앱의 큰 단점은 사용자들이 컴퓨터에 Ruby를 설치해야 한다는 점입니다. 또한 데스크톱 컴퓨터에서만 작동합니다. Windows 컴퓨터에서 사용할 경우 몇 가지 기술적 도전이 있을 것입니다.

앱이 공유할 준비가 되면 여기에 링크를 게시하겠습니다.

CLI 앱이 아닌 Obsidian Discourse 플러그인에 사람들이 관심이 있다면, 미래에 다시 살펴볼 것입니다. 다른 누군가가 저보다 먼저 그것을 만들 수도 있습니다. 저는 위에 게시된 코드 예제보다 더 많이 진행하지 않았습니다.

지금까지 이 정도라면 꽤 단순해 보입니다. 이 시점에서 이미지는 어떻게 처리할지 궁금합니다. 한번 시도해 볼 의향이 있습니다. 저는 이전에 옵시디언 플러그인을 작성해 본 적이 없지만, 제가 살펴본 바로는 (다른 플러그인들을 슬쩍 살펴보았지만) 그렇게 복잡해 보이지 않습니다.

여기부터 시작하면 됩니다: Build a plugin - Developer Documentation. 제가 만든 것을 링크로 공유하고 싶지만, 지난주에 컴퓨터 운영 체제를 재설치하기 전에 Github에 올리지 않은 소수의 (바라건대 유일한) 프로젝트 중 하나입니다.

Node로 어떻게 해야 하는지 알아봐야 합니다. 여기 좋은 Ruby 예제가 있습니다: discourse_api/examples/upload_file.rb at main · discourse/discourse_api · GitHub. 이 예제는 다음을 호출합니다: discourse_api/lib/discourse_api/api/uploads.rb at main · discourse/discourse_api · GitHub.

이 부분을 공유해 주실 수 있나요?

음, 그 코드를 잃어버린 건 좀 무모했네요. 기억으로는 main.ts에서 정의된 DiscoursePlugin을 this.addCommand 호출을 처리하는 파일로 가져오는 중이었어요. 시작하려면 main.ts에서 모든 것을 처리할 수 있습니다. 예제 플러그인으로 시작하는 가이드를 따르는 걸 추천해요. 먼저 그걸 가지고 이것저것 시도해 보고 무언가를 동작하게 해보세요. 제가 그 플러그인을 제 플러그인의 템플릿으로 사용했거든요.

여기에도 좋은 예시들이 있습니다: obsidian-wordpress/src at main · devbean/obsidian-wordpress · GitHub.

음, 일단 여기까지로 하겠습니다. 꽤 좋은 진전을 이뤘지만, 마음에 들지 않는 부분이 몇 가지 있습니다.

  • API 키가 평문으로 저장되어 있습니다 (매우 위험함)
  • 이미지가 업로드되지 않습니다. 아직 탐색하지 않은 설정 항목이 몇 가지 있습니다. 하지만 AWS S3 버킷을 설정하는 것은 별로이고 싶지 않습니다. 나중에 그 부분을 살펴보겠습니다.
  • 일부 스타일 선택은 더 좋게 할 수 있지만, 일단 기능적인 것을 원했습니다.
  • README를 업데이트해야 합니다. 하지만 역시 기능적인 것만 만들고 게시하고 싶었습니다.

이를 우회할 수 있는 방법이 있을까 궁금합니다. CLI 앱에서는 비밀번호로 암호화하고 있습니다. API 키를 사용하는 모든 작업 전에 사용자가 비밀번호를 제공해야 합니다.