Files
ignis/shims/loader.js
2026-03-07 12:23:08 +01:00

172 lines
5.2 KiB
JavaScript

// shim-loader.js
// Loaded before app.js. Defines window.require() and window.process
// to intercept all Electron/Node API calls from Obsidian's renderer code.
import { electronShim } from "./electron/index.js";
import { remoteShim } from "./electron/remote/index.js";
import { fsShim } from "./fs/index.js";
import { pathShim } from "./path.js";
import { urlShim } from "./url.js";
import { cryptoShim } from "./crypto/index.js";
import { btimeShim } from "./btime.js";
import { processShim } from "./process.js";
// Debug mode: wrap shims in Proxy to log all property accesses
const DEBUG = true;
const _accessLog = new Map(); // "module.property" -> count
function wrapWithProxy(obj, name) {
if (!DEBUG || !obj || typeof obj !== "object") return obj;
return new Proxy(obj, {
get(target, prop) {
if (
typeof prop === "string" &&
prop !== "then" &&
prop !== "toJSON" &&
!prop.startsWith("_")
) {
const key = `${name}.${prop}`;
_accessLog.set(key, (_accessLog.get(key) || 0) + 1);
if (!(prop in target)) {
console.warn(`[shim:MISS] ${key} - property not found on shim`);
}
}
return target[prop];
},
});
}
// Expose access log for debugging in console: window.__shimLog()
window.__shimLog = function () {
const sorted = [..._accessLog.entries()].sort((a, b) => b[1] - a[1]);
console.table(sorted.map(([k, v]) => ({ api: k, calls: v })));
};
window.__shimMisses = function () {
const sorted = [..._accessLog.entries()]
.filter(([k]) => {
const [mod, prop] = k.split(".");
const shim = rawRegistry[mod];
return shim && !(prop in shim);
})
.sort((a, b) => b[1] - a[1]);
console.table(sorted.map(([k, v]) => ({ api: k, calls: v })));
};
const rawRegistry = {
electron: electronShim,
"@electron/remote": remoteShim,
"original-fs": fsShim,
fs: fsShim,
path: pathShim,
url: urlShim,
crypto: cryptoShim,
btime: btimeShim,
};
const shimRegistry = {};
for (const [name, shim] of Object.entries(rawRegistry)) {
shimRegistry[name] = wrapWithProxy(shim, name);
}
// Modules that should throw on require (native modules that don't exist in browser)
const throwOnRequire = new Set(["btime", "get-fonts", "vibrancy-win"]);
window.require = function (moduleName) {
if (throwOnRequire.has(moduleName)) {
throw new Error(`Cannot find module '${moduleName}'`);
}
if (shimRegistry[moduleName]) {
return shimRegistry[moduleName];
}
console.warn("[obsidian-bridge] Unshimmed require:", moduleName);
return wrapWithProxy({}, `UNKNOWN(${moduleName})`);
};
window.process = processShim;
// Provide a global Buffer if needed
if (typeof window.Buffer === "undefined") {
// TODO: evaluate if a full Buffer polyfill is needed or if Uint8Array suffices
window.Buffer = {
from: function (data, encoding) {
if (typeof data === "string") {
return new TextEncoder().encode(data);
}
if (data instanceof ArrayBuffer) {
return new Uint8Array(data);
}
return new Uint8Array(data);
},
concat: function (arrays) {
const total = arrays.reduce((sum, a) => sum + a.length, 0);
const result = new Uint8Array(total);
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result;
},
isBuffer: function (obj) {
return obj instanceof Uint8Array;
},
};
}
// Prevent app.js from closing the window (browser blocks this anyway, but suppress the error)
const _origClose = window.close;
window.close = function () {
console.log("[obsidian-bridge] window.close() blocked");
};
// Pre-populate fs metadata cache synchronously before app.js runs.
// This ensures existsSync() works for the vault path during startup.
(function initMetadataCache() {
try {
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/fs/tree", false); // synchronous
xhr.send();
if (xhr.status === 200) {
const tree = JSON.parse(xhr.responseText);
fsShim._metadataCache.populate(tree);
// Also add the root path itself
fsShim._metadataCache.set("", { type: "directory" });
fsShim._metadataCache.set("/", { type: "directory" });
console.log(
"[obsidian-bridge] Metadata cache populated:",
fsShim._metadataCache.size,
"entries",
);
} else {
console.error(
"[obsidian-bridge] Failed to fetch metadata tree:",
xhr.status,
);
}
} catch (e) {
console.error("[obsidian-bridge] Failed to init metadata cache:", e);
}
})();
// Fetch vault config from server synchronously
(function initVaultConfig() {
try {
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/vault/info", false); // synchronous
xhr.send();
if (xhr.status === 200) {
const info = JSON.parse(xhr.responseText);
// Set the vault config that sendSync('vault') will return
window.__vaultConfig = {
id: info.name || "default-vault",
path: "/",
};
console.log("[obsidian-bridge] Vault config:", window.__vaultConfig);
}
} catch (e) {
console.error("[obsidian-bridge] Failed to fetch vault config:", e);
}
})();
console.log("[obsidian-bridge] Shim loader initialized");