refactor(mesh): route inspectStackServices through proxyFetch (#1082)

inspectStackServices was constructing its Authorization, x-sencho-tier,
and x-sencho-variant headers inline. proxyFetch is the centralized
helper used by listStacksOnNode, pushOverrideToNode, triggerRemeshRedeploy,
and removeOverrideFromNode. Equivalent today for a GET-no-body call, but
any future header added to proxyFetch (audit context, license fingerprint,
request id) would silently miss inspectStackServices.

Wire output is byte-identical: same URL, same method, same three headers,
same 5 s timeout. The new catch arm converts MeshError('push_failed') back
to the existing warn + return [] contract callers (optInStack,
refreshAliasCache) rely on. Pattern mirrors listStacksOnNode.
This commit is contained in:
Anso
2026-05-17 05:06:45 -04:00
committed by GitHub
parent 662036bcb7
commit dcc43205e0
+14 -12
View File
@@ -1180,19 +1180,14 @@ export class MeshService extends EventEmitter implements MeshForwarderHost {
if (!node) return [];
if (node.type !== 'remote') return this.inspectLocalStackServices(stackName);
const target = NodeRegistry.getInstance().getProxyTarget(nodeId);
if (!target) {
console.warn(`[MeshService] inspectStackServices: no proxy target for node ${nodeId} (${sanitizeForLog(node.name)})`);
return [];
}
try {
const url = `${target.apiUrl.replace(/\/$/, '')}/api/mesh/local-services/${encodeURIComponent(stackName)}`;
const headers: Record<string, string> = {};
if (target.apiToken) headers['Authorization'] = `Bearer ${target.apiToken}`;
const proxyHeaders = LicenseService.getInstance().getProxyHeaders();
headers[PROXY_TIER_HEADER] = proxyHeaders.tier;
headers[PROXY_VARIANT_HEADER] = proxyHeaders.variant || '';
const res = await fetch(url, { headers, signal: AbortSignal.timeout(5_000) });
const res = await this.proxyFetch(
nodeId,
'GET',
`/api/mesh/local-services/${encodeURIComponent(stackName)}`,
undefined,
5_000,
);
if (!res.ok) {
console.error(`[MeshService] inspectStackServices: HTTP ${res.status} from node ${nodeId} (${sanitizeForLog(node.name)})`);
return [];
@@ -1200,6 +1195,13 @@ export class MeshService extends EventEmitter implements MeshForwarderHost {
const body = await res.json() as { services?: Array<{ service: string; ports: number[] }> };
return body.services ?? [];
} catch (err) {
// proxyFetch throws MeshError('push_failed') when getProxyTarget
// returns null (e.g. pilot tunnel offline). Treat the same as a
// non-OK response: empty list.
if (err instanceof MeshError && err.code === 'push_failed') {
console.warn(`[MeshService] inspectStackServices: no proxy target for node ${nodeId} (${sanitizeForLog(node.name)})`);
return [];
}
console.error('[MeshService] inspectStackServices remote unreachable:', sanitizeForLog((err as Error).message));
return [];
}