mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 07:59:42 +02:00
feat(web): support a reverse-proxy base URL (--base-url / CODEMAN_BASE_URL)
Codeman can now be mounted under a sub-path behind a reverse proxy that forwards the prefix unchanged (e.g. https://host/codeman/). Default is `/` (root), which is byte-identical to the historical behavior. Design — few choke points, mirrored ingress/egress: - src/config/base-path.ts: pure single-source normalize/validate/join/strip. - Server ingress: stripBasePath() inside Fastify rewriteUrl, so routes stay declared prefix-agnostic; un-prefixed requests (hooks, health, docker bridge hitting the raw port) pass through unchanged. - Server egress: one onSend hook prepends the base to root-absolute Location headers (covers all redirects). - HTML: renderIndexHtml points <base href> at the mount and injects window.__CODEMAN_BASE__ — ONLY when a base is set (inert at root). - Frontend runtime URLs: CodemanBase.url() route builder in constants.js, applied transparently by a fetch wrapper and explicitly at the EventSource/WebSocket/window.open/<img|iframe|a>-src sites. - sw.js derives its base from self.location; manifest uses relative start_url/scope. - Web-tab proxy: proxyPrefixFor(cap, basePath) is the single base-aware root that cascades to the injected <base>, HTML/attr rewrites, runtimeUrlShim, Set-Cookie Path and Location; capabilityFromReferer strips the base off the browser Referer, while the ingress parsers stay base-agnostic (rewriteUrl already stripped it). --base-url rides the daemon relaunch (buildWebArgs) and the service unit (resolveServicePlan). constants.js is guarded against a missing `window` for isolated unit-test contexts. Tests: test/base-path.test.ts (pure helpers), base-path coverage in webview-proxy/render-index-html/daemon-control; CodemanBase stubbed in the vm-isolated panels-ui test contexts. Docs: Remote-Access.md (sub-path section + nginx example), security-architecture.md env table, CLAUDE.md pattern. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XUkPBxbumnct6qSrx4JDju
This commit is contained in:
committed by
Michael M. Tiller
co-authored by
Claude Opus 4.8
parent
6f7add7ce4
commit
7e4914d991
@@ -228,3 +228,42 @@ describe('WebServer.renderIndexHtml', () => {
|
||||
expect(html).not.toContain('gesture-codeman.js');
|
||||
});
|
||||
});
|
||||
|
||||
describe('WebServer.renderIndexHtml reverse-proxy base path', () => {
|
||||
const BASE_TEMPLATE = ['<head>', '<base href="/">', '<title>Codeman</title>', '</head>', '<body></body>'].join('\n');
|
||||
|
||||
function makeBaseServer(basePath: string) {
|
||||
// constructor: (port, https, testMode, host, titleHostname, allowUnauth, basePath)
|
||||
const server = new WebServer(0, false, true, '127.0.0.1', undefined, false, basePath);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(server as any).indexHtmlTemplate = BASE_TEMPLATE;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(server as any).readSettings = vi.fn(async () => ({}));
|
||||
return server;
|
||||
}
|
||||
|
||||
it('is inert at root — base tag unchanged and no base global injected', async () => {
|
||||
const server = makeBaseServer('');
|
||||
const html = await render(server);
|
||||
expect(html).toContain('<base href="/">');
|
||||
// At root the frontend reads a MISSING __CODEMAN_BASE__ as root, so nothing is
|
||||
// injected and the historical output is byte-identical.
|
||||
expect(html).not.toContain('__CODEMAN_BASE__');
|
||||
});
|
||||
|
||||
it('points the base tag and the base global at a sub-path mount', async () => {
|
||||
const server = makeBaseServer('/codeman');
|
||||
const html = await render(server);
|
||||
expect(html).toContain('<base href="/codeman/">');
|
||||
expect(html).toContain('window.__CODEMAN_BASE__="/codeman"');
|
||||
// The global rides right after <base>, before any (deferred) script.
|
||||
expect(html.indexOf('window.__CODEMAN_BASE__')).toBeLessThan(html.indexOf('</head>'));
|
||||
});
|
||||
|
||||
it('normalizes a raw operator prefix passed to the constructor', async () => {
|
||||
const server = makeBaseServer('codeman/');
|
||||
const html = await render(server);
|
||||
expect(html).toContain('<base href="/codeman/">');
|
||||
expect(html).toContain('window.__CODEMAN_BASE__="/codeman"');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user