mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
improve cold boot
This commit is contained in:
@@ -7,8 +7,39 @@
|
||||
<link href="app.css" type="text/css" rel="stylesheet"/>
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<link href="assets/overrides.css" type="text/css" rel="stylesheet"/>
|
||||
<style>
|
||||
#ignis-status {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 18px;
|
||||
background: #202020;
|
||||
color: #b3b3b3;
|
||||
font: 14px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
z-index: 9999;
|
||||
transition: opacity 200ms ease-out;
|
||||
}
|
||||
#ignis-status.fade { opacity: 0; pointer-events: none; }
|
||||
#ignis-status img {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
animation: ignis-pulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
#ignis-status-label { font-size: 13px; opacity: 0.75; }
|
||||
@keyframes ignis-pulse {
|
||||
0%, 100% { opacity: 0.85; transform: scale(1); }
|
||||
50% { opacity: 1; transform: scale(1.04); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="theme-dark">
|
||||
<div id="ignis-status">
|
||||
<img src="favicon.png" alt=""/>
|
||||
<div id="ignis-status-label">Loading Obsidian...</div>
|
||||
</div>
|
||||
<!-- Ignis shims: must run before any Obsidian code. -->
|
||||
<script type="text/javascript" src="__IGNIS_UI_SRC__"></script>
|
||||
<script type="text/javascript" src="__SHIM_LOADER_SRC__"></script>
|
||||
@@ -16,11 +47,41 @@
|
||||
<script>
|
||||
(function () {
|
||||
var scripts = __OBSIDIAN_SCRIPTS__;
|
||||
var label = document.getElementById("ignis-status-label");
|
||||
var status = document.getElementById("ignis-status");
|
||||
var loaded = 0;
|
||||
|
||||
function update() {
|
||||
if (label) {
|
||||
label.textContent = "Loading Obsidian " + loaded + "/" + scripts.length;
|
||||
}
|
||||
}
|
||||
|
||||
function done() {
|
||||
if (!status) return;
|
||||
status.classList.add("fade");
|
||||
setTimeout(function () {
|
||||
if (status && status.parentNode) status.parentNode.removeChild(status);
|
||||
}, 250);
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
var s = document.createElement("script");
|
||||
s.type = "text/javascript";
|
||||
s.src = scripts[i];
|
||||
s.async = false;
|
||||
s.onload = function () {
|
||||
loaded++;
|
||||
update();
|
||||
if (loaded === scripts.length) done();
|
||||
};
|
||||
s.onerror = function () {
|
||||
loaded++;
|
||||
update();
|
||||
if (loaded === scripts.length) done();
|
||||
};
|
||||
document.body.appendChild(s);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -52,6 +52,7 @@ const fsRoutes = require("./routes/fs");
|
||||
const vaultRoutes = require("./routes/vault");
|
||||
const proxyRoutes = require("./routes/proxy");
|
||||
const versionRoutes = require("./routes/version");
|
||||
const bootstrapRoutes = require("./routes/bootstrap");
|
||||
|
||||
app.use("/assets", express.static(path.join(__dirname, "assets")));
|
||||
|
||||
@@ -60,6 +61,7 @@ app.use("/api/vault", vaultRoutes);
|
||||
app.use("/api/proxy", proxyRoutes);
|
||||
app.use("/api/version", versionRoutes);
|
||||
app.use("/api/plugins", pluginRoutes);
|
||||
app.use("/api/bootstrap", bootstrapRoutes);
|
||||
|
||||
// Serve vault files for resource URLs (images, attachments, etc.)
|
||||
// Vault ID is the first path segment: /vault-files/<vault-id>/path/to/file
|
||||
@@ -153,6 +155,9 @@ const server = app.listen(config.port, async () => {
|
||||
|
||||
await updateBridgePluginInAllVaults(config.vaultRoot);
|
||||
await initPlugins({ app, config, wss, watcher });
|
||||
bootstrapRoutes.warmUp().catch((e) =>
|
||||
console.warn("[bootstrap] warm-up error:", e.message),
|
||||
);
|
||||
});
|
||||
|
||||
const wss = setupWebSocket(server);
|
||||
|
||||
252
server/routes/bootstrap.js
vendored
Normal file
252
server/routes/bootstrap.js
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
// Bootstrap endpoint for cold start.
|
||||
//
|
||||
// Combines vault info, vault list, metadata tree, and plugin list into a
|
||||
// single pre-compressed response. Cache is per-vault and invalidated by
|
||||
// directory mtime check + explicit invalidateVault() calls from the write/delete routes.
|
||||
|
||||
const express = require("express");
|
||||
const fs = require("fs");
|
||||
const fsp = fs.promises;
|
||||
const path = require("path");
|
||||
const zlib = require("zlib");
|
||||
const config = require("../config");
|
||||
const { isBridgePluginInstalled, getIgnisMeta } = require("../bridge-plugin");
|
||||
const { getDiscoveredPlugins } = require("../plugin-system/manager");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// vaultId -> { response, dirMtimes, compressed: { br, gz } }
|
||||
const cache = new Map();
|
||||
|
||||
// vaultId -> Promise<entry> (in-flight build dedup)
|
||||
const pendingBuilds = new Map();
|
||||
|
||||
function preCompress(buf) {
|
||||
return Promise.all([
|
||||
new Promise((resolve, reject) => {
|
||||
zlib.brotliCompress(
|
||||
buf,
|
||||
{ params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 4 } },
|
||||
(err, result) => (err ? reject(err) : resolve(result)),
|
||||
);
|
||||
}),
|
||||
new Promise((resolve, reject) => {
|
||||
zlib.gzip(buf, { level: 6 }, (err, result) =>
|
||||
err ? reject(err) : resolve(result),
|
||||
);
|
||||
}),
|
||||
]).then(([br, gz]) => ({ br, gz }));
|
||||
}
|
||||
|
||||
async function walkTree(rootPath) {
|
||||
const tree = {};
|
||||
const dirMtimes = {};
|
||||
|
||||
async function walk(dir, prefix) {
|
||||
const stat = await fsp.stat(dir);
|
||||
dirMtimes[prefix] = stat.mtimeMs;
|
||||
|
||||
const entries = await fsp.readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const rel = prefix ? prefix + "/" + entry.name : entry.name;
|
||||
const full = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
tree[rel] = { type: "directory" };
|
||||
await walk(full, rel);
|
||||
} else {
|
||||
try {
|
||||
const s = await fsp.stat(full);
|
||||
|
||||
tree[rel] = {
|
||||
type: "file",
|
||||
size: s.size,
|
||||
mtime: s.mtimeMs,
|
||||
ctime: s.ctimeMs,
|
||||
};
|
||||
} catch {
|
||||
tree[rel] = { type: "file" };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await walk(rootPath, "");
|
||||
|
||||
return { tree, dirMtimes };
|
||||
}
|
||||
|
||||
async function buildVaultInfo(vaultId, vaultPath) {
|
||||
const pluginInstalled = await isBridgePluginInstalled(vaultPath);
|
||||
const ignisMeta = await getIgnisMeta(vaultPath);
|
||||
|
||||
return {
|
||||
id: vaultId,
|
||||
name: vaultId,
|
||||
path: vaultPath,
|
||||
platform: process.platform,
|
||||
version: config.obsidianVersion,
|
||||
ignisPlugin: {
|
||||
installed: pluginInstalled,
|
||||
prompted: ignisMeta.pluginPrompted || false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildVaultList() {
|
||||
return Object.entries(config.vaults).map(([id, vaultPath]) => ({
|
||||
id,
|
||||
name: id,
|
||||
path: vaultPath,
|
||||
}));
|
||||
}
|
||||
|
||||
async function dirMtimesUnchanged(vaultPath, dirMtimes) {
|
||||
const checks = await Promise.all(
|
||||
Object.entries(dirMtimes).map(async ([relDir, oldMtime]) => {
|
||||
const absDir = relDir
|
||||
? path.join(vaultPath, relDir.split("/").join(path.sep))
|
||||
: vaultPath;
|
||||
|
||||
try {
|
||||
const s = await fsp.stat(absDir);
|
||||
return s.mtimeMs === oldMtime;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return checks.every(Boolean);
|
||||
}
|
||||
|
||||
async function buildEntry(vaultId) {
|
||||
const vaultPath = config.getVaultPath(vaultId);
|
||||
|
||||
if (!vaultPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cached = cache.get(vaultId);
|
||||
|
||||
if (cached && (await dirMtimesUnchanged(vaultPath, cached.dirMtimes))) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const t0 = Date.now();
|
||||
const [vault, { tree, dirMtimes }] = await Promise.all([
|
||||
buildVaultInfo(vaultId, vaultPath),
|
||||
walkTree(vaultPath),
|
||||
]);
|
||||
|
||||
const response = {
|
||||
vault,
|
||||
vaultList: buildVaultList(),
|
||||
tree,
|
||||
plugins: getDiscoveredPlugins(),
|
||||
};
|
||||
|
||||
const jsonBuf = Buffer.from(JSON.stringify(response));
|
||||
let compressed = {};
|
||||
|
||||
try {
|
||||
compressed = await preCompress(jsonBuf);
|
||||
} catch (e) {
|
||||
console.warn("[bootstrap] precompression failed:", e.message);
|
||||
}
|
||||
|
||||
const entry = { response, dirMtimes, compressed };
|
||||
cache.set(vaultId, entry);
|
||||
|
||||
const ms = Date.now() - t0;
|
||||
const fileCount = Object.keys(tree).filter(
|
||||
(k) => tree[k].type === "file",
|
||||
).length;
|
||||
const dirCount = Object.keys(dirMtimes).length;
|
||||
|
||||
console.log(
|
||||
`[bootstrap] vault=${vaultId} build files=${fileCount} dirs=${dirCount} time=${ms}ms`,
|
||||
);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
async function getOrBuild(vaultId) {
|
||||
if (pendingBuilds.has(vaultId)) {
|
||||
return pendingBuilds.get(vaultId);
|
||||
}
|
||||
|
||||
const promise = buildEntry(vaultId).finally(() => {
|
||||
pendingBuilds.delete(vaultId);
|
||||
});
|
||||
|
||||
pendingBuilds.set(vaultId, promise);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function invalidateVault(vaultId) {
|
||||
cache.delete(vaultId);
|
||||
}
|
||||
|
||||
async function warmUp() {
|
||||
const ids = Object.keys(config.vaults);
|
||||
|
||||
for (const id of ids) {
|
||||
try {
|
||||
await buildEntry(id);
|
||||
} catch (e) {
|
||||
console.warn(`[bootstrap] warm-up failed for vault ${id}:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/", async (req, res) => {
|
||||
const vaultId = req.query.vault || config.defaultVaultId;
|
||||
|
||||
if (!vaultId || !config.getVaultPath(vaultId)) {
|
||||
return res.status(404).json({ error: "Vault not found", id: vaultId });
|
||||
}
|
||||
|
||||
try {
|
||||
const entry = await getOrBuild(vaultId);
|
||||
|
||||
if (!entry) {
|
||||
return res.status(404).json({ error: "Vault not found" });
|
||||
}
|
||||
|
||||
const ae = req.headers["accept-encoding"] || "";
|
||||
const { compressed } = entry;
|
||||
let buf, encoding;
|
||||
|
||||
if (ae.includes("br") && compressed.br) {
|
||||
buf = compressed.br;
|
||||
encoding = "br";
|
||||
} else if (
|
||||
(ae.includes("gzip") || ae.includes("deflate")) &&
|
||||
compressed.gz
|
||||
) {
|
||||
buf = compressed.gz;
|
||||
encoding = "gzip";
|
||||
}
|
||||
|
||||
if (buf) {
|
||||
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
||||
res.setHeader("Content-Encoding", encoding);
|
||||
res.setHeader("Content-Length", buf.length);
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
|
||||
return res.status(200).end(buf);
|
||||
}
|
||||
|
||||
res.json(entry.response);
|
||||
} catch (e) {
|
||||
console.error("[bootstrap] error:", e);
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports.invalidateVault = invalidateVault;
|
||||
module.exports.warmUp = warmUp;
|
||||
@@ -4,6 +4,7 @@ const path = require("path");
|
||||
const archiver = require("archiver");
|
||||
const config = require("../config");
|
||||
const { writeCoalesced, getPending } = require("../write-coalescer");
|
||||
const bootstrapRoutes = require("./bootstrap");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -64,9 +65,17 @@ function getVaultRoot(req, res) {
|
||||
res.status(404).json({ error: "Vault not found", id: vaultId });
|
||||
return null;
|
||||
}
|
||||
|
||||
req._vaultId = vaultId;
|
||||
return vaultPath;
|
||||
}
|
||||
|
||||
function invalidateBootstrap(req) {
|
||||
if (req._vaultId) {
|
||||
bootstrapRoutes.invalidateVault(req._vaultId);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve a client-provided path to an absolute path within a vault.
|
||||
// Strips leading slashes so paths from the client are always treated as relative to the vault root.
|
||||
function resolveVaultPath(vaultRoot, relativePath) {
|
||||
@@ -258,6 +267,7 @@ router.post("/writeFile", async (req, res) => {
|
||||
|
||||
const result = await writeCoalesced(resolved, data, encoding);
|
||||
|
||||
invalidateBootstrap(req);
|
||||
res.json({ ok: true, mtime: result.mtime, size: result.size });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message, code: e.code });
|
||||
@@ -275,6 +285,7 @@ router.post("/appendFile", async (req, res) => {
|
||||
try {
|
||||
await fs.promises.appendFile(resolved, req.body.content, "utf-8");
|
||||
|
||||
invalidateBootstrap(req);
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message, code: e.code });
|
||||
@@ -294,6 +305,7 @@ router.post("/mkdir", async (req, res) => {
|
||||
recursive: !!req.body.recursive,
|
||||
});
|
||||
|
||||
invalidateBootstrap(req);
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message, code: e.code });
|
||||
@@ -318,6 +330,7 @@ router.post("/rename", async (req, res) => {
|
||||
try {
|
||||
await fs.promises.rename(oldResolved, newResolved);
|
||||
|
||||
invalidateBootstrap(req);
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message, code: e.code });
|
||||
@@ -342,6 +355,7 @@ router.post("/copyFile", async (req, res) => {
|
||||
try {
|
||||
await fs.promises.copyFile(srcResolved, destResolved);
|
||||
|
||||
invalidateBootstrap(req);
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message, code: e.code });
|
||||
@@ -359,6 +373,7 @@ router.delete("/unlink", async (req, res) => {
|
||||
try {
|
||||
await fs.promises.unlink(resolved);
|
||||
|
||||
invalidateBootstrap(req);
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
if (e.code === "ENOENT") {
|
||||
@@ -381,6 +396,7 @@ router.delete("/rmdir", async (req, res) => {
|
||||
try {
|
||||
await fs.promises.rmdir(resolved);
|
||||
|
||||
invalidateBootstrap(req);
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message, code: e.code });
|
||||
@@ -399,6 +415,8 @@ router.delete("/rm", async (req, res) => {
|
||||
await fs.promises.rm(resolved, {
|
||||
recursive: req.query.recursive === "true",
|
||||
});
|
||||
|
||||
invalidateBootstrap(req);
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message, code: e.code });
|
||||
@@ -453,6 +471,8 @@ router.post("/utimes", async (req, res) => {
|
||||
req.body.atime / 1000,
|
||||
req.body.mtime / 1000,
|
||||
);
|
||||
|
||||
invalidateBootstrap(req);
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message, code: e.code });
|
||||
|
||||
@@ -8,6 +8,7 @@ const {
|
||||
setIgnisMeta,
|
||||
installBridgePlugin,
|
||||
} = require("../bridge-plugin");
|
||||
const bootstrapRoutes = require("./bootstrap");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -68,6 +69,7 @@ router.post("/create", async (req, res) => {
|
||||
await installBridgePlugin(vaultPath);
|
||||
|
||||
config.refreshVaults();
|
||||
bootstrapRoutes.invalidateVault(name);
|
||||
|
||||
res.json({ ok: true, id: name, path: vaultPath });
|
||||
} catch (e) {
|
||||
@@ -100,6 +102,8 @@ router.post("/rename", async (req, res) => {
|
||||
await fs.promises.rename(vaultPath, newPath);
|
||||
|
||||
config.refreshVaults();
|
||||
bootstrapRoutes.invalidateVault(vaultId);
|
||||
bootstrapRoutes.invalidateVault(newName);
|
||||
|
||||
res.json({ ok: true, id: newName, path: newPath });
|
||||
} catch (e) {
|
||||
@@ -126,6 +130,7 @@ router.delete("/remove", async (req, res) => {
|
||||
await fs.promises.rm(vaultPath, { recursive: true });
|
||||
|
||||
config.refreshVaults();
|
||||
bootstrapRoutes.invalidateVault(vaultId);
|
||||
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
|
||||
@@ -12,7 +12,62 @@ function resolveVaultId() {
|
||||
window.__workspaceName = urlParams.get("workspace") || "";
|
||||
}
|
||||
|
||||
function initVaultConfig() {
|
||||
// Single round-trip bootstrap: vault info + vault list + metadata tree + plugins.
|
||||
// Returns the parsed response, or null if the call failed (no vault, network error, etc.)
|
||||
function fetchBootstrap() {
|
||||
if (!window.__currentVaultId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open(
|
||||
"GET",
|
||||
"/api/bootstrap?vault=" + encodeURIComponent(window.__currentVaultId),
|
||||
false,
|
||||
);
|
||||
xhr.send();
|
||||
|
||||
if (xhr.status === 200) {
|
||||
return JSON.parse(xhr.responseText);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("[ignis] Bootstrap fetch failed:", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function applyVaultInfo(info) {
|
||||
window.__currentVaultId = info.id;
|
||||
localStorage.setItem("last-vault", info.id);
|
||||
window.__obsidianVersion = info.version || "0.0.0";
|
||||
|
||||
window.__vaultConfig = {
|
||||
id: info.id,
|
||||
path: "/",
|
||||
};
|
||||
|
||||
window.__ignisPlugin = info.ignisPlugin || null;
|
||||
|
||||
console.log("[ignis] Vault:", window.__vaultConfig);
|
||||
console.log("[ignis] Obsidian version:", window.__obsidianVersion);
|
||||
}
|
||||
|
||||
function applyTree(tree) {
|
||||
fsShim._metadataCache.populate(tree);
|
||||
fsShim._metadataCache.set("", { type: "directory" });
|
||||
fsShim._metadataCache.set("/", { type: "directory" });
|
||||
|
||||
console.log(
|
||||
"[ignis] Metadata cache populated:",
|
||||
fsShim._metadataCache.size,
|
||||
"entries",
|
||||
);
|
||||
}
|
||||
|
||||
function initVaultConfigFallback() {
|
||||
try {
|
||||
const vaultParam = window.__currentVaultId
|
||||
? "?vault=" + encodeURIComponent(window.__currentVaultId)
|
||||
@@ -24,21 +79,7 @@ function initVaultConfig() {
|
||||
xhr.send();
|
||||
|
||||
if (xhr.status === 200) {
|
||||
const info = JSON.parse(xhr.responseText);
|
||||
|
||||
window.__currentVaultId = info.id;
|
||||
localStorage.setItem("last-vault", info.id);
|
||||
window.__obsidianVersion = info.version || "0.0.0";
|
||||
|
||||
window.__vaultConfig = {
|
||||
id: info.id,
|
||||
path: "/",
|
||||
};
|
||||
|
||||
window.__ignisPlugin = info.ignisPlugin || null;
|
||||
|
||||
console.log("[ignis] Vault:", window.__vaultConfig);
|
||||
console.log("[ignis] Obsidian version:", window.__obsidianVersion);
|
||||
applyVaultInfo(JSON.parse(xhr.responseText));
|
||||
} else {
|
||||
console.warn("[ignis] No vault found, will show manager");
|
||||
}
|
||||
@@ -47,7 +88,7 @@ function initVaultConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
function initVaultList() {
|
||||
function initVaultListFallback() {
|
||||
try {
|
||||
vaultService.listVaultsSync();
|
||||
} catch (e) {
|
||||
@@ -55,7 +96,7 @@ function initVaultList() {
|
||||
}
|
||||
}
|
||||
|
||||
function initMetadataCache() {
|
||||
function initMetadataCacheFallback() {
|
||||
try {
|
||||
const vaultParam = window.__currentVaultId
|
||||
? "?vault=" + encodeURIComponent(window.__currentVaultId)
|
||||
@@ -67,17 +108,7 @@ function initMetadataCache() {
|
||||
xhr.send();
|
||||
|
||||
if (xhr.status === 200) {
|
||||
const tree = JSON.parse(xhr.responseText);
|
||||
|
||||
fsShim._metadataCache.populate(tree);
|
||||
fsShim._metadataCache.set("", { type: "directory" });
|
||||
fsShim._metadataCache.set("/", { type: "directory" });
|
||||
|
||||
console.log(
|
||||
"[ignis] Metadata cache populated:",
|
||||
fsShim._metadataCache.size,
|
||||
"entries",
|
||||
);
|
||||
applyTree(JSON.parse(xhr.responseText));
|
||||
} else {
|
||||
console.error("[ignis] Failed to fetch metadata tree:", xhr.status);
|
||||
}
|
||||
@@ -114,7 +145,48 @@ function initPluginPrompt() {
|
||||
// this prevents headless sync from being disabled as a result of a different device syncing "Active core plugins list".
|
||||
// i.e ensure Ignis always has sync: false if headless sync is active.
|
||||
// This may be somewhat overengineered. Could revisit later.
|
||||
function initCoreSyncGuard() {
|
||||
function applyCoreSyncGuard(plugins) {
|
||||
const vaultId = window.__currentVaultId;
|
||||
|
||||
if (!vaultId || !plugins) {
|
||||
return;
|
||||
}
|
||||
|
||||
const headlessSync = plugins.find(
|
||||
(p) => p.id === "headless-sync" && p.bundledPluginId,
|
||||
);
|
||||
|
||||
if (!headlessSync || !headlessSync.enabledVaults.includes(vaultId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[ignis] Headless sync active for this vault, patching core-plugins.json reads",
|
||||
);
|
||||
window.__ignisHeadlessSyncActive = true;
|
||||
|
||||
registerReadTransform(".obsidian/core-plugins.json", (data) => {
|
||||
if (!window.__ignisHeadlessSyncActive) {
|
||||
return data;
|
||||
}
|
||||
|
||||
let text =
|
||||
typeof data === "string" ? data : new TextDecoder().decode(data);
|
||||
|
||||
try {
|
||||
const config = JSON.parse(text);
|
||||
|
||||
if (config.sync === true) {
|
||||
config.sync = false;
|
||||
return JSON.stringify(config);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
function initCoreSyncGuardFallback() {
|
||||
const vaultId = window.__currentVaultId;
|
||||
|
||||
if (!vaultId) {
|
||||
@@ -127,43 +199,9 @@ function initCoreSyncGuard() {
|
||||
xhr.open("GET", "/api/plugins", false);
|
||||
xhr.send();
|
||||
|
||||
if (xhr.status !== 200) {
|
||||
return;
|
||||
if (xhr.status === 200) {
|
||||
applyCoreSyncGuard(JSON.parse(xhr.responseText));
|
||||
}
|
||||
|
||||
const plugins = JSON.parse(xhr.responseText);
|
||||
const headlessSync = plugins.find(
|
||||
(p) => p.id === "headless-sync" && p.bundledPluginId,
|
||||
);
|
||||
|
||||
if (!headlessSync || !headlessSync.enabledVaults.includes(vaultId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[ignis] Headless sync active for this vault, patching core-plugins.json reads",
|
||||
);
|
||||
window.__ignisHeadlessSyncActive = true;
|
||||
|
||||
registerReadTransform(".obsidian/core-plugins.json", (data) => {
|
||||
if (!window.__ignisHeadlessSyncActive) {
|
||||
return data;
|
||||
}
|
||||
|
||||
let text =
|
||||
typeof data === "string" ? data : new TextDecoder().decode(data);
|
||||
|
||||
try {
|
||||
const config = JSON.parse(text);
|
||||
|
||||
if (config.sync === true) {
|
||||
config.sync = false;
|
||||
return JSON.stringify(config);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return data;
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn("[ignis] Failed to init core sync guard:", e);
|
||||
}
|
||||
@@ -171,11 +209,22 @@ function initCoreSyncGuard() {
|
||||
|
||||
export function initialize() {
|
||||
resolveVaultId();
|
||||
initVaultConfig();
|
||||
resolveWorkspaceName();
|
||||
initVaultList();
|
||||
initMetadataCache();
|
||||
initCoreSyncGuard();
|
||||
|
||||
const bootstrap = fetchBootstrap();
|
||||
|
||||
if (bootstrap) {
|
||||
applyVaultInfo(bootstrap.vault);
|
||||
window.__vaultList = bootstrap.vaultList;
|
||||
applyTree(bootstrap.tree);
|
||||
applyCoreSyncGuard(bootstrap.plugins);
|
||||
} else {
|
||||
initVaultConfigFallback();
|
||||
initVaultListFallback();
|
||||
initMetadataCacheFallback();
|
||||
initCoreSyncGuardFallback();
|
||||
}
|
||||
|
||||
installRequestUrlShim();
|
||||
initWorkspacePatch();
|
||||
initPluginPrompt();
|
||||
|
||||
Reference in New Issue
Block a user