mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
add shim, basic stubs
This commit is contained in:
33
shims/electron/index.js
Normal file
33
shims/electron/index.js
Normal file
@@ -0,0 +1,33 @@
|
||||
// Electron module shim
|
||||
// Returned when Obsidian calls: window.require('electron')
|
||||
|
||||
import { ipcRenderer } from "./ipc-renderer.js";
|
||||
import { webFrame } from "./web-frame.js";
|
||||
import { remoteShim } from "./remote/index.js";
|
||||
|
||||
export const electronShim = {
|
||||
ipcRenderer,
|
||||
webFrame,
|
||||
remote: remoteShim,
|
||||
|
||||
// electron.deprecate - used by Obsidian to mark deprecated APIs
|
||||
deprecate: {
|
||||
function(fn, name) {
|
||||
return fn;
|
||||
},
|
||||
event(emitter, name) {},
|
||||
removeFunction(fn, name) {
|
||||
return fn;
|
||||
},
|
||||
log(message) {
|
||||
console.log("[electron:deprecate]", message);
|
||||
},
|
||||
warn(oldName, newName) {},
|
||||
promisify(fn) {
|
||||
return fn;
|
||||
},
|
||||
renameFunction(fn, newName) {
|
||||
return fn;
|
||||
},
|
||||
},
|
||||
};
|
||||
107
shims/electron/ipc-renderer.js
Normal file
107
shims/electron/ipc-renderer.js
Normal file
@@ -0,0 +1,107 @@
|
||||
// Shim for electron.ipcRenderer
|
||||
// Obsidian uses: .send(), .sendSync(), .on(), .once()
|
||||
//
|
||||
// sendSync channels discovered in app.js:
|
||||
// vault → {id, path} - critical for startup
|
||||
// version → string - app version
|
||||
// is-dev → boolean - dev mode flag
|
||||
// file-url → string - base URL prefix for vault assets
|
||||
// disable-update → boolean - whether updates are disabled
|
||||
// update → string - update status
|
||||
// disable-gpu → boolean - GPU acceleration toggle
|
||||
// frame → void - window frame style
|
||||
// set-icon → void - custom vault icon
|
||||
// get-icon → null|object - get custom vault icon
|
||||
// relaunch → void - restart app
|
||||
// starter → void - open vault chooser
|
||||
// help → void - open help
|
||||
// sandbox → void - open sandbox vault
|
||||
// copy-asar → boolean - install update
|
||||
|
||||
const listeners = new Map();
|
||||
|
||||
// Sync channel handlers - must return values synchronously
|
||||
const syncHandlers = {
|
||||
vault: () => window.__vaultConfig || { id: "default-vault", path: "/" },
|
||||
version: () => "1.8.9",
|
||||
"is-dev": () => false,
|
||||
"file-url": () => "",
|
||||
"disable-update": () => true,
|
||||
update: () => "",
|
||||
"disable-gpu": () => false,
|
||||
frame: () => null,
|
||||
"set-icon": () => null,
|
||||
"get-icon": () => null,
|
||||
relaunch: () => {
|
||||
window.location.reload();
|
||||
return null;
|
||||
},
|
||||
starter: () => null,
|
||||
help: () => {
|
||||
window.open("https://help.obsidian.md/", "_blank");
|
||||
return null;
|
||||
},
|
||||
sandbox: () => null,
|
||||
"copy-asar": () => false,
|
||||
"check-update": () => null,
|
||||
};
|
||||
|
||||
export const ipcRenderer = {
|
||||
send(channel, ...args) {
|
||||
console.log("[shim:ipcRenderer] send:", channel, args);
|
||||
// TODO: route to server via chosen sync mechanism if needed
|
||||
},
|
||||
|
||||
sendSync(channel, ...args) {
|
||||
console.log("[shim:ipcRenderer] sendSync:", channel, args);
|
||||
if (syncHandlers[channel]) {
|
||||
return syncHandlers[channel](...args);
|
||||
}
|
||||
console.warn("[shim:ipcRenderer] Unhandled sendSync channel:", channel);
|
||||
return null;
|
||||
},
|
||||
|
||||
on(channel, listener) {
|
||||
if (!listeners.has(channel)) {
|
||||
listeners.set(channel, []);
|
||||
}
|
||||
listeners.get(channel).push(listener);
|
||||
return ipcRenderer;
|
||||
},
|
||||
|
||||
once(channel, listener) {
|
||||
const wrapped = (...args) => {
|
||||
ipcRenderer.removeListener(channel, wrapped);
|
||||
listener(...args);
|
||||
};
|
||||
return ipcRenderer.on(channel, wrapped);
|
||||
},
|
||||
|
||||
removeListener(channel, listener) {
|
||||
const arr = listeners.get(channel);
|
||||
if (arr) {
|
||||
const idx = arr.indexOf(listener);
|
||||
if (idx >= 0) arr.splice(idx, 1);
|
||||
}
|
||||
return ipcRenderer;
|
||||
},
|
||||
|
||||
removeAllListeners(channel) {
|
||||
if (channel) {
|
||||
listeners.delete(channel);
|
||||
} else {
|
||||
listeners.clear();
|
||||
}
|
||||
return ipcRenderer;
|
||||
},
|
||||
|
||||
// Internal: emit an event to registered listeners (used by ws bridge)
|
||||
_emit(channel, ...args) {
|
||||
const arr = listeners.get(channel);
|
||||
if (arr) {
|
||||
for (const fn of arr) {
|
||||
fn({}, ...args);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
27
shims/electron/web-frame.js
Normal file
27
shims/electron/web-frame.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// Shim for electron.webFrame
|
||||
// Obsidian uses: getZoomLevel(), setZoomLevel()
|
||||
|
||||
let currentZoom = 0;
|
||||
|
||||
export const webFrame = {
|
||||
getZoomLevel() {
|
||||
return currentZoom;
|
||||
},
|
||||
|
||||
setZoomLevel(level) {
|
||||
currentZoom = level;
|
||||
// Approximate Electron's zoom behavior via CSS zoom
|
||||
// Electron zoom level 0 = 100%, each step is ~20%
|
||||
const scale = Math.pow(1.2, level);
|
||||
document.body.style.zoom = scale;
|
||||
},
|
||||
|
||||
getZoomFactor() {
|
||||
return Math.pow(1.2, currentZoom);
|
||||
},
|
||||
|
||||
setZoomFactor(factor) {
|
||||
currentZoom = Math.log(factor) / Math.log(1.2);
|
||||
document.body.style.zoom = factor;
|
||||
},
|
||||
};
|
||||
171
shims/loader.js
Normal file
171
shims/loader.js
Normal file
@@ -0,0 +1,171 @@
|
||||
// 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");
|
||||
22
shims/process.js
Normal file
22
shims/process.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// Shim for window.process
|
||||
// Obsidian checks process.platform, process.versions.electron, etc.
|
||||
|
||||
export const processShim = {
|
||||
platform: 'linux',
|
||||
versions: {
|
||||
electron: '28.0.0',
|
||||
node: '18.18.0',
|
||||
chrome: '120.0.0.0',
|
||||
},
|
||||
env: {},
|
||||
cwd: () => '/',
|
||||
nextTick: (fn, ...args) => setTimeout(() => fn(...args), 0),
|
||||
argv: [],
|
||||
type: 'renderer',
|
||||
resourcesPath: '/',
|
||||
stdout: { write: (s) => console.log(s) },
|
||||
stderr: { write: (s) => console.error(s) },
|
||||
on: () => {},
|
||||
once: () => {},
|
||||
removeListener: () => {},
|
||||
};
|
||||
Reference in New Issue
Block a user