From fb6c778d8b971d52c690e7c67785865f9b8167b6 Mon Sep 17 00:00:00 2001 From: gnezim Date: Thu, 7 May 2026 00:50:27 +0300 Subject: [PATCH] Handle dev TrackerHub poll timeouts in proxy --- scripts/dev-server.mjs | 41 +++++++++++++++++++++++++------- src/shared/signalr/connection.ts | 38 +++++------------------------ 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/scripts/dev-server.mjs b/scripts/dev-server.mjs index daa8abf4..4abdf2ac 100644 --- a/scripts/dev-server.mjs +++ b/scripts/dev-server.mjs @@ -10,7 +10,7 @@ * /* → localhost:8081 (Modern.js SSR + HMR) */ import express from "express"; -import { createProxyMiddleware } from "http-proxy-middleware"; +import { createProxyMiddleware, responseInterceptor } from "http-proxy-middleware"; import { HttpsProxyAgent } from "https-proxy-agent"; import { execFile, spawn } from "node:child_process"; import { resolve } from "node:path"; @@ -159,6 +159,25 @@ function normalizeTrackerCookie(cookie) { 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 --- // Browser-direct localhost → platform.test.aeroflot.ru fails CORS. Keep the // hub same-origin in development and let proxy-helper / gost route the @@ -170,15 +189,21 @@ const trackerProxy = createProxyMiddleware({ ws: true, logLevel: "warn", pathRewrite: (path) => `/tracker${path}`, + selfHandleResponse: true, on: { - proxyRes(proxyRes) { - const setCookie = proxyRes.headers["set-cookie"]; - if (Array.isArray(setCookie)) { - proxyRes.headers["set-cookie"] = setCookie.map(normalizeTrackerCookie); - } else if (typeof setCookie === "string") { - proxyRes.headers["set-cookie"] = normalizeTrackerCookie(setCookie); + proxyRes: responseInterceptor(async (buffer, proxyRes, req, res) => { + applyTrackerCookieHeaders(proxyRes, res); + + if (isTrackerLongPollTimeout(proxyRes, req)) { + console.warn(`Tracker long poll timed out upstream, returning empty 200 for ${req.originalUrl ?? req.url}`); + res.statusCode = 200; + res.statusMessage = "OK"; + res.setHeader("content-type", "text/plain"); + return ""; } - }, + + return buffer; + }), }, ...(SYSTEM_PROXY ? { agent: new HttpsProxyAgent(SYSTEM_PROXY) } : {}), }); diff --git a/src/shared/signalr/connection.ts b/src/shared/signalr/connection.ts index ddfc49ba..0ef3faff 100644 --- a/src/shared/signalr/connection.ts +++ b/src/shared/signalr/connection.ts @@ -224,8 +224,6 @@ export function _resetSharedConnections(): void { // ---- Default builder (dynamic import) ---- -const DEV_TRACKER_LONG_POLL_TIMEOUT_MS = 10000; - export function _shouldUseLongPollingForDevTrackerProxy(url: string): boolean { try { const parsed = new URL(url, "http://localhost"); @@ -243,39 +241,15 @@ async function defaultBuildConnection( url: string, delays: number[], ): Promise { - const { - DefaultHttpClient, - HttpClient, - HubConnectionBuilder, - HttpTransportType, - LogLevel, - NullLogger, - } = await import("@microsoft/signalr"); + const { HubConnectionBuilder, HttpTransportType, LogLevel } = await import( + "@microsoft/signalr" + ); const builder = new HubConnectionBuilder(); - const useDevTrackerLongPolling = _shouldUseLongPollingForDevTrackerProxy(url); - const withUrlOptions = useDevTrackerLongPolling - ? { - transport: HttpTransportType.LongPolling, - httpClient: new (class extends HttpClient { - private readonly inner = new DefaultHttpClient(NullLogger.instance); - - send(request: Parameters["send"]>[0]) { - if (request.method !== "GET") { - return this.inner.send(request); - } - - return this.inner.send({ - ...request, - timeout: DEV_TRACKER_LONG_POLL_TIMEOUT_MS, - }); - } - })(), - } + const withUrlOptions = _shouldUseLongPollingForDevTrackerProxy(url) + ? { transport: HttpTransportType.LongPolling } : undefined; - return (withUrlOptions - ? builder.withUrl(url, withUrlOptions) - : builder.withUrl(url)) + return (withUrlOptions ? builder.withUrl(url, withUrlOptions) : builder.withUrl(url)) .withAutomaticReconnect(delays) // Suppress SignalR's internal console logging. The hub is optional // (failures degrade to polling) and we already handle status via