fix: resolve symlinks in project directory paths (#1316)
* fix: resolve symlinks in project directory paths OpenCode stores sessions using the canonical (realpath) directory, but OpenChamber passed the unresolved symlink path in several places. The string-match directory filter would fail when a project was accessed via a symlink, making sessions invisible. Changes: - Add safeRealpathSync to settings normalization — project paths and lastDirectory are canonicalized at persistence time - Add Express middleware before the API proxy to resolve symlinks in ?directory= query params on in-flight requests - Resolve symlinks in /api/fs/list so the directory browser returns canonical paths, allowing the "already added" check to work correctly - Reconcile the in-memory projects store when the server responds with normalized paths, preventing temporary duplicates Fixes #1315 * fix: avoid sync realpath in opencode proxy --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
90b3d4760e
commit
c5862cc6ee
@@ -5,6 +5,31 @@ import {
|
||||
collectForwardProxyHeaders,
|
||||
shouldForwardProxyResponseHeader,
|
||||
} from '../../proxy-headers.js';
|
||||
import { createRealpathCache } from '../path-realpath-cache.js';
|
||||
|
||||
export const createDirectoryQueryCanonicalizer = ({ realpath, ...cacheOptions } = {}) => {
|
||||
const realpathCache = createRealpathCache({ fallbackOnError: true, realpath, ...cacheOptions });
|
||||
|
||||
return async (requestUrl) => {
|
||||
if (typeof requestUrl !== 'string' || !requestUrl.includes('directory=')) {
|
||||
return requestUrl;
|
||||
}
|
||||
|
||||
const url = new URL(requestUrl, 'http://localhost');
|
||||
const directory = url.searchParams.get('directory');
|
||||
if (!directory) {
|
||||
return requestUrl;
|
||||
}
|
||||
|
||||
const canonicalDirectory = await realpathCache.resolve(directory);
|
||||
if (!canonicalDirectory || canonicalDirectory === directory) {
|
||||
return requestUrl;
|
||||
}
|
||||
|
||||
url.searchParams.set('directory', canonicalDirectory);
|
||||
return `${url.pathname}${url.search}`;
|
||||
};
|
||||
};
|
||||
|
||||
export const waitForSseDrain = (res, signal) => new Promise((resolve) => {
|
||||
if (signal?.aborted || res.writableEnded || res.destroyed) {
|
||||
@@ -94,6 +119,9 @@ export const registerOpenCodeProxy = (app, deps) => {
|
||||
|
||||
const isAbortError = (error) => error?.name === 'AbortError';
|
||||
const FALLBACK_PROXY_TARGET = 'http://127.0.0.1:3902';
|
||||
const canonicalizeDirectoryQuery = createDirectoryQueryCanonicalizer({
|
||||
realpath: fs?.promises?.realpath?.bind(fs.promises),
|
||||
});
|
||||
|
||||
const normalizeProxyTarget = (candidate) => {
|
||||
if (typeof candidate !== 'string') {
|
||||
@@ -416,5 +444,20 @@ export const registerOpenCodeProxy = (app, deps) => {
|
||||
},
|
||||
});
|
||||
|
||||
// Best-effort fallback for stale clients still sending symlink paths.
|
||||
// Settings and project selection normalize at source; this cached async path
|
||||
// avoids blocking the proxy hot path on every directory-scoped request.
|
||||
app.use('/api', async (req, _res, next) => {
|
||||
try {
|
||||
const rewrittenUrl = await canonicalizeDirectoryQuery(req.url);
|
||||
if (rewrittenUrl !== req.url) {
|
||||
req.url = rewrittenUrl;
|
||||
}
|
||||
} catch {
|
||||
// Pass through as-is if URL parsing or realpath resolution fails.
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
app.use('/api', apiProxy);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user