Handle dev TrackerHub poll timeouts in proxy

This commit is contained in:
2026-05-07 00:50:27 +03:00
parent eadd42cacc
commit fb6c778d8b
2 changed files with 39 additions and 40 deletions
+33 -8
View File
@@ -10,7 +10,7 @@
* /* → localhost:8081 (Modern.js SSR + HMR) * /* → localhost:8081 (Modern.js SSR + HMR)
*/ */
import express from "express"; import express from "express";
import { createProxyMiddleware } from "http-proxy-middleware"; import { createProxyMiddleware, responseInterceptor } from "http-proxy-middleware";
import { HttpsProxyAgent } from "https-proxy-agent"; import { HttpsProxyAgent } from "https-proxy-agent";
import { execFile, spawn } from "node:child_process"; import { execFile, spawn } from "node:child_process";
import { resolve } from "node:path"; import { resolve } from "node:path";
@@ -159,6 +159,25 @@ function normalizeTrackerCookie(cookie) {
return [...parts, "Path=/tracker/hub", "SameSite=Lax"].join("; "); return [...parts, "Path=/tracker/hub", "SameSite=Lax"].join("; ");
} }
function applyTrackerCookieHeaders(proxyRes, res) {
const setCookie = proxyRes.headers["set-cookie"];
if (Array.isArray(setCookie)) {
res.setHeader("set-cookie", setCookie.map(normalizeTrackerCookie));
} else if (typeof setCookie === "string") {
res.setHeader("set-cookie", normalizeTrackerCookie(setCookie));
}
}
function isTrackerLongPollTimeout(proxyRes, req) {
const requestUrl = req.originalUrl ?? req.url ?? "";
return (
req.method === "GET" &&
(requestUrl.startsWith("/tracker/hub?id=") ||
requestUrl.startsWith("/hub?id=")) &&
proxyRes.statusCode === 504
);
}
// --- SignalR TrackerHub proxy --- // --- SignalR TrackerHub proxy ---
// Browser-direct localhost → platform.test.aeroflot.ru fails CORS. Keep the // Browser-direct localhost → platform.test.aeroflot.ru fails CORS. Keep the
// hub same-origin in development and let proxy-helper / gost route the // hub same-origin in development and let proxy-helper / gost route the
@@ -170,15 +189,21 @@ const trackerProxy = createProxyMiddleware({
ws: true, ws: true,
logLevel: "warn", logLevel: "warn",
pathRewrite: (path) => `/tracker${path}`, pathRewrite: (path) => `/tracker${path}`,
selfHandleResponse: true,
on: { on: {
proxyRes(proxyRes) { proxyRes: responseInterceptor(async (buffer, proxyRes, req, res) => {
const setCookie = proxyRes.headers["set-cookie"]; applyTrackerCookieHeaders(proxyRes, res);
if (Array.isArray(setCookie)) {
proxyRes.headers["set-cookie"] = setCookie.map(normalizeTrackerCookie); if (isTrackerLongPollTimeout(proxyRes, req)) {
} else if (typeof setCookie === "string") { console.warn(`Tracker long poll timed out upstream, returning empty 200 for ${req.originalUrl ?? req.url}`);
proxyRes.headers["set-cookie"] = normalizeTrackerCookie(setCookie); res.statusCode = 200;
res.statusMessage = "OK";
res.setHeader("content-type", "text/plain");
return "";
} }
},
return buffer;
}),
}, },
...(SYSTEM_PROXY ? { agent: new HttpsProxyAgent(SYSTEM_PROXY) } : {}), ...(SYSTEM_PROXY ? { agent: new HttpsProxyAgent(SYSTEM_PROXY) } : {}),
}); });
+6 -32
View File
@@ -224,8 +224,6 @@ export function _resetSharedConnections(): void {
// ---- Default builder (dynamic import) ---- // ---- Default builder (dynamic import) ----
const DEV_TRACKER_LONG_POLL_TIMEOUT_MS = 10000;
export function _shouldUseLongPollingForDevTrackerProxy(url: string): boolean { export function _shouldUseLongPollingForDevTrackerProxy(url: string): boolean {
try { try {
const parsed = new URL(url, "http://localhost"); const parsed = new URL(url, "http://localhost");
@@ -243,39 +241,15 @@ async function defaultBuildConnection(
url: string, url: string,
delays: number[], delays: number[],
): Promise<HubConnectionLike> { ): Promise<HubConnectionLike> {
const { const { HubConnectionBuilder, HttpTransportType, LogLevel } = await import(
DefaultHttpClient, "@microsoft/signalr"
HttpClient, );
HubConnectionBuilder,
HttpTransportType,
LogLevel,
NullLogger,
} = await import("@microsoft/signalr");
const builder = new HubConnectionBuilder(); const builder = new HubConnectionBuilder();
const useDevTrackerLongPolling = _shouldUseLongPollingForDevTrackerProxy(url); const withUrlOptions = _shouldUseLongPollingForDevTrackerProxy(url)
const withUrlOptions = useDevTrackerLongPolling ? { transport: HttpTransportType.LongPolling }
? {
transport: HttpTransportType.LongPolling,
httpClient: new (class extends HttpClient {
private readonly inner = new DefaultHttpClient(NullLogger.instance);
send(request: Parameters<InstanceType<typeof HttpClient>["send"]>[0]) {
if (request.method !== "GET") {
return this.inner.send(request);
}
return this.inner.send({
...request,
timeout: DEV_TRACKER_LONG_POLL_TIMEOUT_MS,
});
}
})(),
}
: undefined; : undefined;
return (withUrlOptions return (withUrlOptions ? builder.withUrl(url, withUrlOptions) : builder.withUrl(url))
? builder.withUrl(url, withUrlOptions)
: builder.withUrl(url))
.withAutomaticReconnect(delays) .withAutomaticReconnect(delays)
// Suppress SignalR's internal console logging. The hub is optional // Suppress SignalR's internal console logging. The hub is optional
// (failures degrade to polling) and we already handle status via // (failures degrade to polling) and we already handle status via