mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
refactor loader
This commit is contained in:
47
src/shims/debug.js
Normal file
47
src/shims/debug.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const DEBUG = true;
|
||||
const _accessLog = new Map(); // "module.property" -> count
|
||||
|
||||
export 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];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function installDebugHelpers(rawRegistry) {
|
||||
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 })));
|
||||
};
|
||||
}
|
||||
110
src/shims/globals.js
Normal file
110
src/shims/globals.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import { processShim } from "./process.js";
|
||||
import {
|
||||
registerPopupWindow,
|
||||
unregisterPopupWindow,
|
||||
} from "./electron/remote/window.js";
|
||||
import { showVaultManager } from "../ui/bootstrap.js";
|
||||
|
||||
function installProcess() {
|
||||
window.process = processShim;
|
||||
}
|
||||
|
||||
function installBuffer() {
|
||||
if (typeof window.Buffer !== "undefined") return;
|
||||
|
||||
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;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function installWindowClose() {
|
||||
window.close = function () {
|
||||
console.log("[ignis] window.close() blocked");
|
||||
if (!window.__vaultConfig) {
|
||||
showVaultManager();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function installWindowOpen() {
|
||||
window.__popupIframe = null;
|
||||
const _originalOpen = window.open;
|
||||
|
||||
window.open = function (url, target, features) {
|
||||
if (url === "about:blank" || (features && features.includes("popup"))) {
|
||||
console.log("[ignis] intercepted popup:", url, features);
|
||||
|
||||
registerPopupWindow();
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.style.cssText =
|
||||
"position:fixed;left:-9999px;width:0;height:0;border:none;";
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
window.__popupIframe = iframe;
|
||||
|
||||
const iframeWin = iframe.contentWindow;
|
||||
|
||||
iframeWin.require = window.require;
|
||||
iframeWin.module = window.module;
|
||||
iframeWin.Buffer = window.Buffer;
|
||||
iframeWin.process = window.process;
|
||||
iframeWin.global = iframeWin;
|
||||
iframeWin.globalEnhance = window.globalEnhance;
|
||||
|
||||
iframeWin.close = function () {
|
||||
unregisterPopupWindow();
|
||||
iframe.remove();
|
||||
window.__popupIframe = null;
|
||||
};
|
||||
|
||||
return iframeWin;
|
||||
}
|
||||
return _originalOpen.call(window, url, target, features);
|
||||
};
|
||||
}
|
||||
|
||||
function installContextMenuFix() {
|
||||
// hacky fix to prevent browser from showing context menu while allowing obsidian context menu
|
||||
window.addEventListener(
|
||||
"contextmenu",
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
Object.defineProperty(e, "defaultPrevented", { get: () => false });
|
||||
},
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
export function installGlobals() {
|
||||
installProcess();
|
||||
installBuffer();
|
||||
installWindowClose();
|
||||
installWindowOpen();
|
||||
installContextMenuFix();
|
||||
}
|
||||
117
src/shims/init.js
Normal file
117
src/shims/init.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import { fsShim } from "./fs/index.js";
|
||||
import { installRequestUrlShim } from "./request-url.js";
|
||||
import { vaultService } from "../services/vault-service.js";
|
||||
import { showPluginInstallDialog } from "../ui/bootstrap.js";
|
||||
|
||||
function resolveVaultId() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
window.__currentVaultId =
|
||||
urlParams.get("vault") || localStorage.getItem("last-vault") || "";
|
||||
}
|
||||
|
||||
function initVaultConfig() {
|
||||
try {
|
||||
const vaultParam = window.__currentVaultId
|
||||
? "?vault=" + encodeURIComponent(window.__currentVaultId)
|
||||
: "";
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open("GET", "/api/vault/info" + vaultParam, false);
|
||||
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);
|
||||
} else {
|
||||
console.warn("[ignis] No vault found, will show manager");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[ignis] Failed to fetch vault config:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function initVaultList() {
|
||||
try {
|
||||
vaultService.listVaultsSync();
|
||||
} catch (e) {
|
||||
window.__vaultList = [];
|
||||
}
|
||||
}
|
||||
|
||||
function initMetadataCache() {
|
||||
try {
|
||||
const vaultParam = window.__currentVaultId
|
||||
? "?vault=" + encodeURIComponent(window.__currentVaultId)
|
||||
: "";
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open("GET", "/api/fs/tree" + vaultParam, false);
|
||||
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",
|
||||
);
|
||||
} else {
|
||||
console.error("[ignis] Failed to fetch metadata tree:", xhr.status);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[ignis] Failed to init metadata cache:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function initPluginPrompt() {
|
||||
if (
|
||||
!window.__ignisPlugin ||
|
||||
window.__ignisPlugin.installed ||
|
||||
window.__ignisPlugin.prompted
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const vaultId = window.__currentVaultId;
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
if (document.querySelector(".workspace")) {
|
||||
observer.disconnect();
|
||||
showPluginInstallDialog(vaultId);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function initialize() {
|
||||
resolveVaultId();
|
||||
initVaultConfig();
|
||||
initVaultList();
|
||||
initMetadataCache();
|
||||
installRequestUrlShim();
|
||||
initPluginPrompt();
|
||||
}
|
||||
@@ -1,294 +1,9 @@
|
||||
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 { processShim } from "./process.js";
|
||||
import { installRequestUrlShim } from "./request-url.js";
|
||||
import {
|
||||
registerPopupWindow,
|
||||
unregisterPopupWindow,
|
||||
} from "./electron/remote/window.js";
|
||||
import * as childProcessShim from "./node/child_process.js";
|
||||
import * as eventsShim from "./node/events.js";
|
||||
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, showVaultManager } from "../ui/bootstrap.js";
|
||||
import { installRequire } from "./require.js";
|
||||
import { installGlobals } from "./globals.js";
|
||||
import { initialize } from "./init.js";
|
||||
|
||||
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];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
child_process: childProcessShim,
|
||||
events: eventsShim,
|
||||
os: osShim,
|
||||
net: netShim,
|
||||
http: httpShim,
|
||||
https: httpShim,
|
||||
};
|
||||
|
||||
const shimRegistry = {};
|
||||
for (const [name, shim] of Object.entries(rawRegistry)) {
|
||||
shimRegistry[name] = wrapWithProxy(shim, name);
|
||||
}
|
||||
|
||||
const throwOnRequire = new Set(["btime", "get-fonts", "vibrancy-win"]);
|
||||
|
||||
window.require = function (moduleName) {
|
||||
// Strip node: prefix if present
|
||||
const normalizedName = moduleName.startsWith("node:")
|
||||
? moduleName.slice(5)
|
||||
: moduleName;
|
||||
|
||||
if (throwOnRequire.has(normalizedName)) {
|
||||
throw new Error(`Cannot find module '${moduleName}'`);
|
||||
}
|
||||
|
||||
if (shimRegistry[normalizedName]) {
|
||||
return shimRegistry[normalizedName];
|
||||
}
|
||||
|
||||
console.warn("[ignis] Unshimmed require:", moduleName);
|
||||
return wrapWithProxy({}, `UNKNOWN(${moduleName})`);
|
||||
};
|
||||
|
||||
window.process = processShim;
|
||||
|
||||
if (typeof window.Buffer === "undefined") {
|
||||
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;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
window.close = function () {
|
||||
console.log("[ignis] window.close() blocked");
|
||||
if (!window.__vaultConfig) {
|
||||
showVaultManager();
|
||||
}
|
||||
};
|
||||
|
||||
window.__popupIframe = null;
|
||||
const _originalOpen = window.open;
|
||||
window.open = function (url, target, features) {
|
||||
if (url === "about:blank" || (features && features.includes("popup"))) {
|
||||
console.log("[ignis] intercepted popup:", url, features);
|
||||
|
||||
registerPopupWindow();
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.style.cssText =
|
||||
"position:fixed;left:-9999px;width:0;height:0;border:none;";
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
window.__popupIframe = iframe;
|
||||
|
||||
const iframeWin = iframe.contentWindow;
|
||||
|
||||
iframeWin.require = window.require;
|
||||
iframeWin.module = window.module;
|
||||
iframeWin.Buffer = window.Buffer;
|
||||
iframeWin.process = window.process;
|
||||
iframeWin.global = iframeWin;
|
||||
iframeWin.globalEnhance = window.globalEnhance;
|
||||
|
||||
iframeWin.close = function () {
|
||||
unregisterPopupWindow();
|
||||
iframe.remove();
|
||||
window.__popupIframe = null;
|
||||
};
|
||||
|
||||
return iframeWin;
|
||||
}
|
||||
return _originalOpen.call(window, url, target, features);
|
||||
};
|
||||
|
||||
// hacky fix to prevent browser from showing context menu while allowing obsidian context menu
|
||||
window.addEventListener(
|
||||
"contextmenu",
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
Object.defineProperty(e, "defaultPrevented", { get: () => false });
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
const _urlParams = new URLSearchParams(window.location.search);
|
||||
window.__currentVaultId =
|
||||
_urlParams.get("vault") || localStorage.getItem("last-vault") || "";
|
||||
|
||||
(function initVaultConfig() {
|
||||
try {
|
||||
const vaultParam = window.__currentVaultId
|
||||
? "?vault=" + encodeURIComponent(window.__currentVaultId)
|
||||
: "";
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open("GET", "/api/vault/info" + vaultParam, false);
|
||||
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);
|
||||
} else {
|
||||
console.warn("[ignis] No vault found, will show manager");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[ignis] Failed to fetch vault config:", e);
|
||||
}
|
||||
})();
|
||||
|
||||
(function initVaultList() {
|
||||
try {
|
||||
vaultService.listVaultsSync();
|
||||
} catch (e) {
|
||||
window.__vaultList = [];
|
||||
}
|
||||
})();
|
||||
|
||||
(function initMetadataCache() {
|
||||
try {
|
||||
const vaultParam = window.__currentVaultId
|
||||
? "?vault=" + encodeURIComponent(window.__currentVaultId)
|
||||
: "";
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open("GET", "/api/fs/tree" + vaultParam, false);
|
||||
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",
|
||||
);
|
||||
} else {
|
||||
console.error("[ignis] Failed to fetch metadata tree:", xhr.status);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[ignis] Failed to init metadata cache:", e);
|
||||
}
|
||||
})();
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
installGlobals(); // process, Buffer, window overrides (before require so Buffer is available)
|
||||
installRequire(); // shim registry, window.require
|
||||
initialize(); // vault config, metadata cache, plugin prompt
|
||||
|
||||
console.log("[ignis] Shim loader initialized");
|
||||
|
||||
65
src/shims/require.js
Normal file
65
src/shims/require.js
Normal file
@@ -0,0 +1,65 @@
|
||||
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 * as childProcessShim from "./node/child_process.js";
|
||||
import * as eventsShim from "./node/events.js";
|
||||
import * as osShim from "./node/os.js";
|
||||
import * as netShim from "./node/net.js";
|
||||
import * as httpShim from "./node/http.js";
|
||||
import { wrapWithProxy, installDebugHelpers } from "./debug.js";
|
||||
|
||||
const rawRegistry = {
|
||||
electron: electronShim,
|
||||
"@electron/remote": remoteShim,
|
||||
"original-fs": fsShim,
|
||||
fs: fsShim,
|
||||
path: pathShim,
|
||||
url: urlShim,
|
||||
crypto: cryptoShim,
|
||||
child_process: childProcessShim,
|
||||
events: eventsShim,
|
||||
os: osShim,
|
||||
net: netShim,
|
||||
http: httpShim,
|
||||
https: httpShim,
|
||||
};
|
||||
|
||||
const shimRegistry = {};
|
||||
const throwOnRequire = new Set(["btime", "get-fonts", "vibrancy-win"]);
|
||||
|
||||
export function installRequire() {
|
||||
for (const [name, shim] of Object.entries(rawRegistry)) {
|
||||
shimRegistry[name] = wrapWithProxy(shim, name);
|
||||
}
|
||||
|
||||
// Add buffer shim (protobufjs inquire() checks for this)
|
||||
if (typeof window.Buffer !== "undefined") {
|
||||
shimRegistry.buffer = window.Buffer;
|
||||
}
|
||||
|
||||
// Add empty long shim (optional protobufjs dependency, gracefully handled)
|
||||
shimRegistry.long = undefined;
|
||||
|
||||
window.require = function (moduleName) {
|
||||
// Strip node: prefix if present
|
||||
const normalizedName = moduleName.startsWith("node:")
|
||||
? moduleName.slice(5)
|
||||
: moduleName;
|
||||
|
||||
if (throwOnRequire.has(normalizedName)) {
|
||||
throw new Error(`Cannot find module '${moduleName}'`);
|
||||
}
|
||||
|
||||
if (shimRegistry[normalizedName]) {
|
||||
return shimRegistry[normalizedName];
|
||||
}
|
||||
|
||||
console.warn("[ignis] Unshimmed require:", moduleName);
|
||||
return wrapWithProxy({}, `UNKNOWN(${moduleName})`);
|
||||
};
|
||||
|
||||
installDebugHelpers(rawRegistry);
|
||||
}
|
||||
Reference in New Issue
Block a user