diff --git a/CHANGELOG.md b/CHANGELOG.md index b027f00..7d8de81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. +## [0.6.2] - Slifer (2026-03-24) + +### Added + +- File watcher system with WebSocket-based live sync for external vault changes +- Real-time detection of file create, modify, delete, and rename operations +- Echo guard to suppress events from local operations (prevents feedback loops) +- Automatic reconnection with exponential backoff for WebSocket client + ## [0.6.1] - Slifer (2026-03-24) ### Added diff --git a/package.json b/package.json index d1a7c04..164a992 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ignis", - "version": "0.6.1", + "version": "0.6.2", "private": true, "description": "An Electron shim and server bridge for running Obsidian in a browser.", "scripts": { diff --git a/server/routes/fs.js b/server/routes/fs.js index dc2206e..48efcc8 100644 --- a/server/routes/fs.js +++ b/server/routes/fs.js @@ -279,9 +279,12 @@ router.delete("/unlink", async (req, res) => { res.json({ ok: true }); } catch (e) { - const status = e.code === "ENOENT" ? 404 : 500; - - res.status(status).json({ error: e.message, code: e.code }); + if (e.code === "ENOENT") { + // File already gone - desired outcome achieved + res.json({ ok: true }); + } else { + res.status(500).json({ error: e.message, code: e.code }); + } } }); diff --git a/src/shims/fs/watch.js b/src/shims/fs/watch.js index ace86a8..1c436b7 100644 --- a/src/shims/fs/watch.js +++ b/src/shims/fs/watch.js @@ -11,29 +11,65 @@ export function createFsWatch(transport) { if (!watchers.has(path)) { watchers.set(path, new Set()); } - watchers.get(path).add(listener); - // TODO: send watch subscription to server via transport + // Wrapper that holds both direct listener and .on() listeners + const entry = { + direct: typeof listener === "function" ? listener : null, + eventListeners: new Map(), // event name -> Set + call(eventType, filename) { + if (this.direct) { + this.direct(eventType, filename); + } + const fns = this.eventListeners.get("change"); + if (fns) { + for (const fn of fns) { + try { + fn(eventType, filename); + } catch (e) { + console.error("[shim:fs:watch] Listener error:", e); + } + } + } + }, + }; + + watchers.get(path).add(entry); // Return a watcher-like object return { close() { const set = watchers.get(path); if (set) { - set.delete(listener); + set.delete(entry); + if (set.size === 0) { watchers.delete(path); - // TODO: send unwatch to server } } }, - on() { + on(event, fn) { + if (!entry.eventListeners.has(event)) { + entry.eventListeners.set(event, new Set()); + } + + entry.eventListeners.get(event).add(fn); + return this; }, - once() { - return this; + once(event, fn) { + const wrapped = (...args) => { + this.removeListener(event, wrapped); + fn(...args); + }; + + return this.on(event, wrapped); }, - removeListener() { + removeListener(event, fn) { + const fns = entry.eventListeners.get(event); + + if (fns) { + fns.delete(fn); + } return this; }, }; @@ -41,12 +77,27 @@ export function createFsWatch(transport) { // Internal: called when transport receives a file-change event _dispatch(eventType, filePath) { + const normFile = (filePath || "").replace(/^\/+/, ""); + let matched = false; + for (const [watchPath, listeners] of watchers) { - if (filePath === watchPath || filePath.startsWith(watchPath + "/")) { - const relativeName = filePath.slice(watchPath.length + 1) || filePath; - for (const fn of listeners) { + const normWatch = (watchPath || "").replace(/^\/+/, ""); + // Empty normWatch means root watcher - matches everything + const isMatch = + normWatch === "" || + normFile === normWatch || + normFile.startsWith(normWatch + "/"); + + if (isMatch) { + matched = true; + const relativeName = + normWatch === "" + ? normFile + : normFile.slice(normWatch.length + 1) || normFile; + + for (const entry of listeners) { try { - fn(eventType, relativeName); + entry.call(eventType, relativeName); } catch (e) { console.error("[shim:fs:watch] Listener error:", e); } diff --git a/src/shims/loader.js b/src/shims/loader.js index 8dd8735..355a1f1 100644 --- a/src/shims/loader.js +++ b/src/shims/loader.js @@ -1,6 +1,7 @@ import { installRequire } from "./require.js"; import { installGlobals } from "./globals.js"; import { initialize } from "./init.js"; +import { fsShim } from "./fs/index.js"; installGlobals(); // process, Buffer, window overrides (before require so Buffer is available) installRequire(); // shim registry, window.require