From 452fb175413b099e126ac63dcb564115f6551e35 Mon Sep 17 00:00:00 2001 From: Nystik <236107-Nystik@users.noreply.gitlab.com> Date: Sun, 22 Mar 2026 15:05:37 +0100 Subject: [PATCH 01/11] add compression middleware to server --- package-lock.json | 53 +++++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + server/index.js | 2 ++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c853069..34a010e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "ignis", - "version": "0.1.0", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ignis", - "version": "0.1.0", + "version": "0.4.0", "dependencies": { "chokidar": "^3.6.0", + "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.21.0", "ws": "^8.16.0" @@ -661,6 +662,45 @@ "periscopic": "^3.1.0" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -1357,6 +1397,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", diff --git a/package.json b/package.json index 30cc8b4..f14abbb 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "chokidar": "^3.6.0", + "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.21.0", "ws": "^8.16.0" diff --git a/server/index.js b/server/index.js index 30785a9..c83c42d 100644 --- a/server/index.js +++ b/server/index.js @@ -1,5 +1,6 @@ const express = require("express"); const path = require("path"); +const compression = require("compression"); const config = require("./config"); const { setupWebSocket } = require("./ws"); const { installPluginInAllVaults } = require("./install-plugin"); @@ -12,6 +13,7 @@ const ANSI_RESET = "\x1b[0m"; const app = express(); app.use(express.json({ limit: "50mb" })); +app.use(compression()); // logger middleware app.use((req, res, next) => { From 5b01a9cdad6f5170c3562ac1f93299ee25ca7cec Mon Sep 17 00:00:00 2001 From: Nystik <236107-Nystik@users.noreply.gitlab.com> Date: Sun, 22 Mar 2026 16:17:26 +0100 Subject: [PATCH 02/11] ask user to install bridge plugin for vault copied to server at runtime --- server/install-plugin.js | 48 +++++++++- server/routes/vault.js | 53 ++++++++++- src/services/vault-service.js | 6 ++ src/shims/loader.js | 24 +++++ src/ui/bootstrap.js | 36 ++++++++ .../layout/PluginInstallDialog.svelte | 89 +++++++++++++++++++ src/ui/index.js | 1 + 7 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 src/ui/components/layout/PluginInstallDialog.svelte diff --git a/server/install-plugin.js b/server/install-plugin.js index c8cbb81..d6ba3c1 100644 --- a/server/install-plugin.js +++ b/server/install-plugin.js @@ -1,6 +1,46 @@ const fs = require("fs"); const path = require("path"); +// .ignis metadata helpers +async function getIgnisMeta(vaultPath) { + const ignisDir = path.join(vaultPath, ".ignis"); + const metaFile = path.join(ignisDir, "meta.json"); + + try { + const content = await fs.promises.readFile(metaFile, "utf-8"); + return JSON.parse(content); + } catch (e) { + return {}; + } +} + +async function setIgnisMeta(vaultPath, data) { + const ignisDir = path.join(vaultPath, ".ignis"); + const metaFile = path.join(ignisDir, "meta.json"); + + await fs.promises.mkdir(ignisDir, { recursive: true }); + await fs.promises.writeFile(metaFile, JSON.stringify(data, null, 2)); +} + +async function checkPluginInstalled(vaultPath) { + const pluginDir = path.join( + vaultPath, + ".obsidian", + "plugins", + "ignis-bridge", + ); + const manifestPath = path.join(pluginDir, "manifest.json"); + const mainPath = path.join(pluginDir, "main.js"); + + try { + await fs.promises.access(manifestPath); + await fs.promises.access(mainPath); + return true; + } catch (e) { + return false; + } +} + async function installPluginInVault(vaultPath) { const obsidianDir = path.join(vaultPath, ".obsidian"); const pluginDir = path.join(obsidianDir, "plugins", "ignis-bridge"); @@ -62,4 +102,10 @@ async function installPluginInAllVaults(vaultRoot) { } } -module.exports = { installPluginInVault, installPluginInAllVaults }; +module.exports = { + installPluginInVault, + installPluginInAllVaults, + getIgnisMeta, + setIgnisMeta, + checkPluginInstalled, +}; diff --git a/server/routes/vault.js b/server/routes/vault.js index 1d8503d..daf30eb 100644 --- a/server/routes/vault.js +++ b/server/routes/vault.js @@ -2,6 +2,12 @@ const express = require("express"); const fs = require("fs"); const config = require("../config"); const path = require("path"); +const { + checkPluginInstalled, + getIgnisMeta, + setIgnisMeta, + installPluginInVault, +} = require("../install-plugin"); const router = express.Router(); @@ -19,7 +25,7 @@ router.get("/list", (req, res) => { }); // GET /api/vault/info?vault= - returns info for a specific vault -router.get("/info", (req, res) => { +router.get("/info", async (req, res) => { const vaultId = req.query.vault || config.defaultVaultId; const vaultPath = config.getVaultPath(vaultId); @@ -27,12 +33,19 @@ router.get("/info", (req, res) => { return res.status(404).json({ error: "Vault not found", id: vaultId }); } + const pluginInstalled = await checkPluginInstalled(vaultPath); + const ignisMeta = await getIgnisMeta(vaultPath); + res.json({ id: vaultId, name: vaultId, path: vaultPath, platform: process.platform, version: config.obsidianVersion, + ignisPlugin: { + installed: pluginInstalled, + prompted: ignisMeta.pluginPrompted || false, + }, }); }); @@ -143,4 +156,42 @@ router.delete("/remove", async (req, res) => { } }); +// POST /api/vault/install-plugin { vault, dismiss } - install plugin or mark as prompted +router.post("/install-plugin", async (req, res) => { + const vaultId = req.body?.vault; + const dismiss = req.body?.dismiss || false; + + if (!vaultId) { + return res.status(400).json({ error: "Missing vault ID" }); + } + + const vaultPath = config.getVaultPath(vaultId); + + if (!vaultPath) { + return res.status(404).json({ error: "Vault not found" }); + } + + try { + const meta = await getIgnisMeta(vaultPath); + + if (dismiss) { + // User clicked "Don't Ask Again" or "Not Now" + meta.pluginPrompted = true; + await setIgnisMeta(vaultPath, meta); + + return res.json({ ok: true, prompted: true }); + } else { + // User wants to install the plugin + const installed = await installPluginInVault(vaultPath); + + meta.pluginPrompted = true; + await setIgnisMeta(vaultPath, meta); + + return res.json({ ok: true, installed, prompted: true }); + } + } catch (e) { + res.status(500).json({ error: e.message, code: e.code }); + } +}); + module.exports = router; diff --git a/src/services/vault-service.js b/src/services/vault-service.js index 64a0d85..0480d6b 100644 --- a/src/services/vault-service.js +++ b/src/services/vault-service.js @@ -48,6 +48,8 @@ export const vaultService = { body: JSON.stringify({ name }), }); + this._setVaultTrust(name); + return this.listVaults(); }, @@ -121,6 +123,10 @@ export const vaultService = { target.location.href = "/?vault=" + encodeURIComponent(id); }, + _setVaultTrust(vaultId, trusted = true) { + localStorage.setItem("enable-plugin-" + vaultId, String(trusted)); + }, + _migrateLocalStorage(oldId, newId) { const pluginKey = "enable-plugin-"; diff --git a/src/shims/loader.js b/src/shims/loader.js index d517d61..d80be06 100644 --- a/src/shims/loader.js +++ b/src/shims/loader.js @@ -16,6 +16,7 @@ import * as osShim from "./node/os.js"; import * as netShim from "./node/net.js"; import * as httpShim from "./node/http.js"; import { vaultService } from "../services/vault-service.js"; +import { showPluginInstallDialog } from "../ui/bootstrap.js"; const DEBUG = true; const _accessLog = new Map(); // "module.property" -> count @@ -208,6 +209,8 @@ window.__currentVaultId = path: "/", }; + window.__ignisPlugin = info.ignisPlugin || null; + console.log("[ignis] Vault:", window.__vaultConfig); console.log("[ignis] Obsidian version:", window.__obsidianVersion); } else { @@ -259,4 +262,25 @@ window.__currentVaultId = installRequestUrlShim(); +// Check if plugin install prompt is needed (once per session, after workspace loads) +if ( + window.__ignisPlugin && + !window.__ignisPlugin.installed && + !window.__ignisPlugin.prompted +) { + const vaultId = window.__currentVaultId; + + const observer = new MutationObserver(() => { + if (document.querySelector(".workspace")) { + observer.disconnect(); + showPluginInstallDialog(vaultId); + } + }); + + observer.observe(document.documentElement, { + childList: true, + subtree: true, + }); +} + console.log("[ignis] Shim loader initialized"); diff --git a/src/ui/bootstrap.js b/src/ui/bootstrap.js index 0028722..9e4345d 100644 --- a/src/ui/bootstrap.js +++ b/src/ui/bootstrap.js @@ -48,6 +48,42 @@ export function showConfirmDialog( }); } +export function showPluginInstallDialog(vaultId) { + return new Promise((resolve) => { + const dialog = new window.IgnisUI.PluginInstallDialog({ + target: document.body, + }); + + dialog.$on("install", async () => { + try { + await fetch("/api/vault/install-plugin", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ vault: vaultId }), + }); + } catch (e) { + console.error("[ignis] Failed to install plugin:", e); + } + dialog.$destroy(); + resolve("install"); + }); + + dialog.$on("dismiss", async () => { + try { + await fetch("/api/vault/install-plugin", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ vault: vaultId, dismiss: true }), + }); + } catch (e) { + console.error("[ignis] Failed to dismiss plugin prompt:", e); + } + dialog.$destroy(); + resolve("dismiss"); + }); + }); +} + export function showPromptDialog( title, label, diff --git a/src/ui/components/layout/PluginInstallDialog.svelte b/src/ui/components/layout/PluginInstallDialog.svelte new file mode 100644 index 0000000..34db792 --- /dev/null +++ b/src/ui/components/layout/PluginInstallDialog.svelte @@ -0,0 +1,89 @@ + + + + + + + +
+

This vault doesn't have the Ignis Bridge plugin installed.

+

+ The plugin adds additional functionality such as file uploads. + Obsidian will work without it, but some features will be unavailable. +

+
+ + + + +
+ + diff --git a/src/ui/index.js b/src/ui/index.js index 11fd0d4..6fdb228 100644 --- a/src/ui/index.js +++ b/src/ui/index.js @@ -2,3 +2,4 @@ export { default as VaultManager } from "./views/VaultManager.svelte"; export { default as MessageDialog } from "./components/layout/MessageDialog.svelte"; export { default as ConfirmDialog } from "./components/layout/ConfirmDialog.svelte"; export { default as PromptDialog } from "./components/layout/PromptDialog.svelte"; +export { default as PluginInstallDialog } from "./components/layout/PluginInstallDialog.svelte"; From f14838044d21081d7b540531cccf91eabb0be5ca Mon Sep 17 00:00:00 2001 From: Nystik <236107-Nystik@users.noreply.gitlab.com> Date: Sun, 22 Mar 2026 16:29:24 +0100 Subject: [PATCH 03/11] Fix vault manager not showing if no vaults exist --- server/config.js | 7 +++++-- src/shims/loader.js | 5 ++++- src/ui/bootstrap.js | 1 - 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/server/config.js b/server/config.js index 16b4ba0..38129d0 100644 --- a/server/config.js +++ b/server/config.js @@ -32,8 +32,11 @@ function discoverVaults() { console.error("[config] Failed to read VAULT_ROOT:", vaultRoot, e.message); } - // Create a default vault if none exist - if (Object.keys(vaults).length === 0) { + // Optionally create a default vault if none exist + if ( + Object.keys(vaults).length === 0 && + process.env.AUTO_CREATE_DEFAULT === "true" + ) { const defaultPath = path.join(vaultRoot, "My Vault"); try { diff --git a/src/shims/loader.js b/src/shims/loader.js index d80be06..b55f7d2 100644 --- a/src/shims/loader.js +++ b/src/shims/loader.js @@ -16,7 +16,7 @@ import * as osShim from "./node/os.js"; import * as netShim from "./node/net.js"; import * as httpShim from "./node/http.js"; import { vaultService } from "../services/vault-service.js"; -import { showPluginInstallDialog } from "../ui/bootstrap.js"; +import { showPluginInstallDialog, showVaultManager } from "../ui/bootstrap.js"; const DEBUG = true; const _accessLog = new Map(); // "module.property" -> count @@ -135,6 +135,9 @@ if (typeof window.Buffer === "undefined") { window.close = function () { console.log("[ignis] window.close() blocked"); + if (!window.__vaultConfig) { + showVaultManager(); + } }; window.__popupIframe = null; diff --git a/src/ui/bootstrap.js b/src/ui/bootstrap.js index 9e4345d..8192cda 100644 --- a/src/ui/bootstrap.js +++ b/src/ui/bootstrap.js @@ -1,7 +1,6 @@ import { vaultService } from "../services/vault-service.js"; export function showVaultManager() { - if (!document.querySelector(".workspace")) return; if (document.querySelector(".vault-manager-overlay")) return; new window.IgnisUI.VaultManager({ From 1a0d749c687d9bdc6d68e00ebe40288eeaf4be21 Mon Sep 17 00:00:00 2001 From: Nystik <236107-Nystik@users.noreply.gitlab.com> Date: Sun, 22 Mar 2026 16:30:39 +0100 Subject: [PATCH 04/11] cleanup some dead code. --- server/config.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/config.js b/server/config.js index 38129d0..d9fcc67 100644 --- a/server/config.js +++ b/server/config.js @@ -3,12 +3,8 @@ const fs = require("fs"); // VAULT_ROOT: a directory that contains vault folders. // Each subdirectory is a vault. New vaults are created as new subdirs. -// Falls back to parent of VAULT_PATH (single-vault compatibility) or ./vaults. const vaultRoot = - process.env.VAULT_ROOT || - (process.env.VAULT_PATH - ? path.dirname(process.env.VAULT_PATH) - : path.join(__dirname, "..", "vaults")); + process.env.VAULT_ROOT || path.join(__dirname, "..", "vaults"); // Ensure vault root exists try { From 50f02631658a5c60e4e38715426e4e050f852cad Mon Sep 17 00:00:00 2001 From: Nystik <236107-Nystik@users.noreply.gitlab.com> Date: Sun, 22 Mar 2026 18:50:23 +0100 Subject: [PATCH 05/11] add cache-busting --- scripts/patch-obsidian.js | 11 +++++++---- server/index.js | 14 ++++++++++++++ server/version.js | 23 +++++++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 server/version.js diff --git a/scripts/patch-obsidian.js b/scripts/patch-obsidian.js index c11b673..023353e 100644 --- a/scripts/patch-obsidian.js +++ b/scripts/patch-obsidian.js @@ -6,6 +6,7 @@ const fs = require("fs"); const path = require("path"); +const { getVersion } = require("../server/version"); const asarDir = process.argv[2]; if (!asarDir) { @@ -13,7 +14,7 @@ if (!asarDir) { process.exit(1); } -function patchHtml(filePath) { +function patchHtml(filePath, version) { const backupPath = filePath + ".orig"; if (!fs.existsSync(filePath) && !fs.existsSync(backupPath)) { @@ -46,8 +47,8 @@ function patchHtml(filePath) { // Inject ignis scripts before the first \n' + - '\n' + + `\n` + + `\n` + ' @@ -246,6 +258,11 @@ +