Проблема с CORS при публикации в Discourse из Obsidian

Я начал использовать Obsidian для написания текстов, поэтому решил создать плагин для Obsidian для публикации на мой форум Discourse. Теоретически это должно быть легко, но ответ от моей первой попытки был следующим:

Доступ к fetch-запросу к 'http://localhost:4200/posts.json' с источника 'app://obsidian.md' заблокирован политикой CORS: ответ на предварительный запрос не проходит проверку доступа: в запрошенном ресурсе отсутствует заголовок 'Access-Control-Allow-Origin'. Если вам подходит непрозрачный ответ, установите режим запроса в 'no-cors', чтобы выполнить fetch-запрос с отключенным CORS.

Запрос выполняется с клиента, но клиентом является устройство пользователя. Если я правильно понимаю ситуацию, это безопасно:

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", // это нужно сделать настройкой
		});
		const body = JSON.stringify({
			title: activeFile.name,
			raw: content, // TODO: обработать содержимое для исправления внутренних ссылок
			category: 1, // это нужно сделать настройкой
		});
		// TODO: проверить, была ли заметка уже опубликована
		const url = `${baseUrl}/posts.json`;

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

		if (!response.ok) {
			console.error("Ошибка публикации в Discourse:", response.status);
			return { message: "Ошибка публикации в Discourse" };
		}
        
		const jsonResponse = await response.json();
		// TODO: использовать ответ для добавления свойства файла discoursePostId
		console.log(`jsonResponse: ${JSON.stringify(jsonResponse, null, 2)}`);
		return { message: "успех" };
	} catch (error) {
		console.error("Ошибка публикации в Discourse:", error);
		return { message: `Ошибка: ${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,
		});
//...

Следующий вопрос будет касаться возможности для пользователей запрашивать API-ключи пользователей непосредственно из приложения, но это отдельная тема.

Это плагин, над которым вы работаете? У меня есть пользователь (и я сам), который хотел бы иметь возможность использовать это. Я могу попробовать воссоздать то, что вы сделали, но если есть более простой способ…

Это было началом чего-то большего. Для собственных нужд я больше заинтересован в CLI-приложении (командная строка), которое позволяет синхронизировать хранилище Obsidian с форумом Discourse. CLI-приложение упрощает синхронизацию всего хранилища или подкаталога, обработку внутренних ссылок в заметках, работу с загрузками и т. д. Подход с CLI-приложением также согласуется с другой моей работой, поэтому я получаю от этого полезные навыки.

Главный недостаток CLI-приложения в том, что для его использования у всех, кто им пользуется, должен быть установлен Ruby на компьютере. Кроме того, оно будет работать только на настольных компьютерах. Использование его на компьютерах с Windows сопряжено с некоторыми техническими сложностями.

Как только приложение будет готово к публикации, я размещу здесь ссылку на него.

Если плагин Obsidian для Discourse (в отличие от CLI-приложения) интересен людям, я вернусь к этой идее в будущем. Возможно, кто-то другой займется этим раньше меня. Я не продвинулся дальше примера кода, приведенного выше.

Если на данный момент у вас есть только это, то всё выглядит довольно просто. Мне интересно, как это будет работать с изображениями. Я готов попробовать реализовать это. Я никогда раньше не писал плагины для Obsidian, но, судя по тому, что я видел (краткие взгляды на другие плагины), это не кажется слишком сложным.

Вот с чего стоит начать: 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.

Можете поделиться этой частью?

Хм, потеря этого кода была довольно безрассудной. По памяти, я импортировал DiscoursePlugin, определённый в main.ts, в файл, который обрабатывал вызов this.addCommand. Чтобы начать, вы можете сделать всё в main.ts. Я бы просто следовал руководству по началу работы с примером плагина. Начните с того, что поэкспериментируйте с ним и попробуйте что-то реализовать. Я знаю, что использовал этот плагин как шаблон для своего.

Здесь тоже есть хорошие примеры: obsidian-wordpress/src at main · devbean/obsidian-wordpress · GitHub.

Что ж, я пока поставлю это на паузу. Я добился довольно хорошего прогресса, но есть несколько моментов, которые мне не нравятся.

  • API-ключ хранится в открытом виде (ОЧЕНЬ ПЛОХО)
  • Изображения не загружаются; есть некоторые настройки конфигурации, которые я ещё не изучил. Но мне не нравится идея настройки бакета AWS S3. Я разберусь с этим позже.
  • Некоторые стилистические решения могли бы быть лучше, но я хотел получить работающее решение.
  • README нужно обновить. Но, опять же, я просто хотел получить работающий вариант и опубликовать его.

Интересно, есть ли способ обойти это. В CLI-приложении я шифрую его с помощью пароля. Пользователь должен ввести пароль перед любой операцией, использующей ключ API.