diff --git a/shims/btime.js b/shims/btime.js new file mode 100644 index 0000000..4e8582b --- /dev/null +++ b/shims/btime.js @@ -0,0 +1,5 @@ +// Shim for the btime native module (file birth time) +// Obsidian wraps this in try/catch: try{this.btime=window.require("btime")}catch(e){} +// Returning null causes graceful degradation - mtime is used instead. + +export const btimeShim = null; diff --git a/shims/crypto/create-hash.js b/shims/crypto/create-hash.js new file mode 100644 index 0000000..2b8c75a --- /dev/null +++ b/shims/crypto/create-hash.js @@ -0,0 +1,60 @@ +// Shim for crypto.createHash +// Obsidian uses createHash('SHA256') for signature verification (main process only) +// and possibly for content hashing in the renderer. +// Uses SubtleCrypto where possible. + +export function createHash(algorithm) { + const alg = algorithm.toUpperCase().replace('-', ''); + const subtleAlg = alg === 'SHA256' ? 'SHA-256' : alg === 'SHA1' ? 'SHA-1' : alg === 'SHA512' ? 'SHA-512' : alg; + + let inputData = new Uint8Array(0); + + return { + update(data) { + if (typeof data === 'string') { + data = new TextEncoder().encode(data); + } + // Concatenate + const merged = new Uint8Array(inputData.length + data.length); + merged.set(inputData); + merged.set(data, inputData.length); + inputData = merged; + return this; + }, + + // Note: digest is sync in Node but we may need async. + // For now provide sync hex/base64 via a simple JS implementation. + // TODO: evaluate if any sync call sites exist; if not, make this async. + digest(encoding) { + // Fallback: simple sync hash (for SHA-256 only) + // This is a placeholder - swap in a proper sync implementation if needed + console.warn('[shim:crypto] createHash.digest - using placeholder'); + const hash = simpleHash(inputData); + if (encoding === 'hex') return hash; + if (encoding === 'base64') return btoa(hash); + return hash; + }, + + // Async alternative for contexts that can await + async digestAsync(encoding) { + const hashBuffer = await crypto.subtle.digest(subtleAlg, inputData); + const hashArray = new Uint8Array(hashBuffer); + if (encoding === 'hex') { + return Array.from(hashArray).map(b => b.toString(16).padStart(2, '0')).join(''); + } + if (encoding === 'base64') { + return btoa(String.fromCharCode(...hashArray)); + } + return hashArray; + }, + }; +} + +// Very basic placeholder hash - not cryptographic, just for bootstrapping +function simpleHash(data) { + let hash = 0; + for (let i = 0; i < data.length; i++) { + hash = ((hash << 5) - hash + data[i]) | 0; + } + return Math.abs(hash).toString(16).padStart(8, '0'); +} diff --git a/shims/crypto/index.js b/shims/crypto/index.js new file mode 100644 index 0000000..08ff284 --- /dev/null +++ b/shims/crypto/index.js @@ -0,0 +1,12 @@ +// Crypto shim +// Obsidian uses: scrypt, randomBytes, createHash + +import { randomBytes } from './random-bytes.js'; +import { createHash } from './create-hash.js'; +import { scrypt } from './scrypt.js'; + +export const cryptoShim = { + randomBytes, + createHash, + scrypt, +}; diff --git a/shims/crypto/random-bytes.js b/shims/crypto/random-bytes.js new file mode 100644 index 0000000..87ba782 --- /dev/null +++ b/shims/crypto/random-bytes.js @@ -0,0 +1,20 @@ +// Shim for crypto.randomBytes +// Uses Web Crypto API under the hood + +export function randomBytes(size) { + const buf = new Uint8Array(size); + crypto.getRandomValues(buf); + + // Add Buffer-like convenience methods + buf.toString = function(encoding) { + if (encoding === 'hex') { + return Array.from(this).map(b => b.toString(16).padStart(2, '0')).join(''); + } + if (encoding === 'base64') { + return btoa(String.fromCharCode(...this)); + } + return new TextDecoder().decode(this); + }; + + return buf; +} diff --git a/shims/crypto/scrypt.js b/shims/crypto/scrypt.js new file mode 100644 index 0000000..0eb066f --- /dev/null +++ b/shims/crypto/scrypt.js @@ -0,0 +1,29 @@ +// Shim for crypto.scrypt +// Delegates to window.scrypt which is already loaded by Obsidian's own scrypt.js + +export function scrypt(password, salt, keylen, options, callback) { + // Node signature: scrypt(password, salt, keylen, options, callback) + // Obsidian's app.js checks for window.require("crypto") and uses it if available, + // otherwise falls back to window.scrypt - so this shim just delegates to the latter. + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + const N = options?.N || 32768; + const r = options?.r || 8; + const p = options?.p || 1; + + if (window.scrypt && window.scrypt.scrypt) { + // Use the browser scrypt library already loaded by Obsidian + const pwBytes = typeof password === 'string' ? new TextEncoder().encode(password) : password; + const saltBytes = typeof salt === 'string' ? new TextEncoder().encode(salt) : salt; + + window.scrypt.scrypt(pwBytes, saltBytes, N, r, p, keylen) + .then((result) => callback(null, new Uint8Array(result))) + .catch((err) => callback(err)); + } else { + callback(new Error('scrypt not available')); + } +} diff --git a/shims/path.js b/shims/path.js new file mode 100644 index 0000000..fac79d0 --- /dev/null +++ b/shims/path.js @@ -0,0 +1,6 @@ +// Path shim - delegates to path-browserify (bundled via esbuild alias) +// Configured for posix mode since vault paths are normalized to forward slashes. + +import pathBrowserify from 'path'; + +export const pathShim = pathBrowserify; diff --git a/shims/url.js b/shims/url.js new file mode 100644 index 0000000..9bc0d34 --- /dev/null +++ b/shims/url.js @@ -0,0 +1,24 @@ +// URL shim +// Obsidian uses: pathToFileURL, fileURLToPath, URL, URLSearchParams + +export const urlShim = { + URL: globalThis.URL, + URLSearchParams: globalThis.URLSearchParams, + + pathToFileURL(p) { + // Return an object with .href matching Node's url.pathToFileURL behavior + const encoded = encodeURI(p.replace(/\\/g, '/')); + const href = 'file:///' + encoded.replace(/^\/+/, ''); + return { href, toString: () => href }; + }, + + fileURLToPath(url) { + let str = typeof url === 'string' ? url : url.href || url.toString(); + if (str.startsWith('file:///')) { + str = str.slice(8); + } else if (str.startsWith('file://')) { + str = str.slice(7); + } + return decodeURI(str); + }, +};