diff --git a/packages/shim/src/electron/ipc-renderer.js b/packages/shim/src/electron/ipc-renderer.js index 6aa2668..57b198c 100644 --- a/packages/shim/src/electron/ipc-renderer.js +++ b/packages/shim/src/electron/ipc-renderer.js @@ -1,5 +1,6 @@ import { showVaultManager } from "../ui-registry.js"; import { vaultService } from "@ignis/services"; +import { arrayBufferToBase64, base64ToArrayBuffer } from "../util/base64.js"; const listeners = new Map(); @@ -85,29 +86,6 @@ const syncHandlers = { resources: () => "", }; -function arrayBufferToBase64(buf) { - const bytes = new Uint8Array(buf); - let binary = ""; - const chunk = 8192; - - for (let i = 0; i < bytes.length; i += chunk) { - binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk)); - } - - return btoa(binary); -} - -function base64ToArrayBuffer(base64) { - const binary = atob(base64); - const bytes = new Uint8Array(binary.length); - - for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.charCodeAt(i); - } - - return bytes.buffer; -} - async function handleRequestUrl(requestId, request) { try { let body = request.body; diff --git a/packages/shim/src/fs/echo-guard.js b/packages/shim/src/fs/echo-guard.js index 82f3d60..79f369a 100644 --- a/packages/shim/src/fs/echo-guard.js +++ b/packages/shim/src/fs/echo-guard.js @@ -1,17 +1,10 @@ // Shared echo suppression for file watcher. -// fs operations mark paths as "locally modified" so the watcher client -// can skip events that originated from this client. +// fs operations mark paths as "locally modified" so the watcher client can skip events that originated from this client. +import { normalize } from "../util/path.js"; const ECHO_SUPPRESS_MS = 1500; const recentOps = new Map(); // normalized path -> timestamp -function normalize(p) { - return (p || "") - .replace(/\\/g, "/") - .replace(/^\/+/, "") - .replace(/\/+$/, ""); -} - export function markLocalOp(path) { recentOps.set(normalize(path), Date.now()); } diff --git a/packages/shim/src/fs/input-cache.js b/packages/shim/src/fs/input-cache.js index 1645f9f..1ed24ab 100644 --- a/packages/shim/src/fs/input-cache.js +++ b/packages/shim/src/fs/input-cache.js @@ -5,19 +5,14 @@ // - 5-minute TTL per entry // - Entries kept until TTL expires (plugins may read the same file multiple times) +import { normalize } from "../util/path.js"; + const MAX_SIZE = 200 * 1024 * 1024; const TTL_MS = 5 * 60 * 1000; const cache = new Map(); // path -> { data, size, createdAt } let currentSize = 0; -function normalize(p) { - return (p || "") - .replace(/\\/g, "/") - .replace(/^\/+/, "") - .replace(/\/+$/, ""); -} - function evictExpired() { const now = Date.now(); diff --git a/packages/shim/src/fs/transforms.js b/packages/shim/src/fs/transforms.js index 9a04365..46dca2c 100644 --- a/packages/shim/src/fs/transforms.js +++ b/packages/shim/src/fs/transforms.js @@ -2,9 +2,7 @@ // Path resolvers map logical paths to physical paths; read transforms post-process bytes after a read; write transforms pre-process bytes before a write. // All hooks run at the shim's public surface, so caches and transport see only physical paths and as-stored bytes. -function normalize(p) { - return (p || "").replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, ""); -} +import { normalize } from "../util/path.js"; // --- Path resolvers --- diff --git a/packages/shim/src/fs/virtual-files.js b/packages/shim/src/fs/virtual-files.js index 121e6e7..4952ba1 100644 --- a/packages/shim/src/fs/virtual-files.js +++ b/packages/shim/src/fs/virtual-files.js @@ -1,8 +1,6 @@ // Virtual plugin source served from memory; the fs shim's read path checks here before disk. -function normalize(p) { - return (p || "").replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, ""); -} +import { normalize } from "../util/path.js"; const virtualFiles = new Map(); diff --git a/packages/shim/src/globals.js b/packages/shim/src/globals.js index c829d7e..ffc04f7 100644 --- a/packages/shim/src/globals.js +++ b/packages/shim/src/globals.js @@ -4,6 +4,8 @@ import { unregisterPopupWindow, } from "./electron/remote/window.js"; import { showVaultManager } from "./ui-registry.js"; +import { arrayBufferToBase64, base64ToArrayBuffer } from "./util/base64.js"; +import { isSameOrigin } from "./util/url.js"; function installProcess() { window.process = processShim; @@ -115,51 +117,6 @@ function installWindowOpen() { }; } -function arrayBufferToBase64(buf) { - const bytes = new Uint8Array(buf); - let binary = ""; - const chunk = 8192; - - for (let i = 0; i < bytes.length; i += chunk) { - binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk)); - } - - return btoa(binary); -} - -function base64ToArrayBuffer(base64) { - const binary = atob(base64); - const bytes = new Uint8Array(binary.length); - - for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.charCodeAt(i); - } - - return bytes.buffer; -} - -export function isSameOrigin(url) { - if ( - !url || - url.startsWith("/") || - url.startsWith("./") || - url.startsWith("../") - ) { - return true; - } - - if (url.startsWith("data:") || url.startsWith("blob:")) { - return true; - } - - try { - const parsed = new URL(url, window.location.origin); - return parsed.origin === window.location.origin; - } catch { - return true; - } -} - function installFetchShim() { const originalFetch = window.fetch.bind(window); window.__originalFetch = originalFetch; diff --git a/packages/shim/src/request-url.js b/packages/shim/src/request-url.js index 8a43b6e..39f304e 100644 --- a/packages/shim/src/request-url.js +++ b/packages/shim/src/request-url.js @@ -1,30 +1,8 @@ // Override window.requestUrl to proxy external requests through our server, bypassing CORS. // Obsidian sets window.requestUrl in app.js, so we override it after app.js loads. -import { isSameOrigin } from "./globals.js"; - -function base64ToArrayBuffer(base64) { - const binary = atob(base64); - const bytes = new Uint8Array(binary.length); - - for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.charCodeAt(i); - } - - return bytes.buffer; -} - -function arrayBufferToBase64(buf) { - const bytes = new Uint8Array(buf); - let binary = ""; - const chunk = 8192; - - for (let i = 0; i < bytes.length; i += chunk) { - binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk)); - } - - return btoa(binary); -} +import { isSameOrigin } from "./util/url.js"; +import { arrayBufferToBase64, base64ToArrayBuffer } from "./util/base64.js"; async function proxyRequestUrl(request) { if (typeof request === "string") { diff --git a/packages/shim/src/util/base64.js b/packages/shim/src/util/base64.js new file mode 100644 index 0000000..01b6772 --- /dev/null +++ b/packages/shim/src/util/base64.js @@ -0,0 +1,26 @@ +// Base64 codec for the binary bodies exchanged with the server proxy. + +function arrayBufferToBase64(buf) { + const bytes = new Uint8Array(buf); + let binary = ""; + const chunk = 8192; + + for (let i = 0; i < bytes.length; i += chunk) { + binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk)); + } + + return btoa(binary); +} + +function base64ToArrayBuffer(base64) { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + + return bytes.buffer; +} + +export { arrayBufferToBase64, base64ToArrayBuffer }; diff --git a/packages/shim/src/util/path.js b/packages/shim/src/util/path.js new file mode 100644 index 0000000..bbc514f --- /dev/null +++ b/packages/shim/src/util/path.js @@ -0,0 +1,7 @@ +// Canonical key form for fs paths: backslashes to forward slashes, no leading or trailing slash. +// Used by caches and registries that key on path. +function normalize(p) { + return (p || "").replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, ""); +} + +export { normalize }; diff --git a/packages/shim/src/util/url.js b/packages/shim/src/util/url.js new file mode 100644 index 0000000..7c6071a --- /dev/null +++ b/packages/shim/src/util/url.js @@ -0,0 +1,24 @@ +// True when a request URL targets the page's own origin (so it can skip the cross-origin proxy). +function isSameOrigin(url) { + if ( + !url || + url.startsWith("/") || + url.startsWith("./") || + url.startsWith("../") + ) { + return true; + } + + if (url.startsWith("data:") || url.startsWith("blob:")) { + return true; + } + + try { + const parsed = new URL(url, window.location.origin); + return parsed.origin === window.location.origin; + } catch { + return true; + } +} + +export { isSameOrigin };