From 6a3c8f2558b0251ec766165ced5727a24f3fa848 Mon Sep 17 00:00:00 2001 From: gnezim Date: Wed, 6 May 2026 23:55:18 +0300 Subject: [PATCH] Fix dev TrackerHub transport --- scripts/dev-server.mjs | 27 +++++++++++++++++++++++++++ src/shared/signalr/connection.test.ts | 17 +++++++++++++++++ src/shared/signalr/connection.ts | 25 ++++++++++++++++++++++--- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/scripts/dev-server.mjs b/scripts/dev-server.mjs index 1a049456..daa8abf4 100644 --- a/scripts/dev-server.mjs +++ b/scripts/dev-server.mjs @@ -142,6 +142,23 @@ app.use(["/api", "/flights"], (req, res) => { } }); +function normalizeTrackerCookie(cookie) { + if (!cookie.startsWith("signal-id=")) return cookie; + + const parts = cookie + .split(";") + .map((part) => part.trim()) + .filter( + (part) => + part.length > 0 && + !part.toLowerCase().startsWith("domain=") && + !part.toLowerCase().startsWith("path=") && + !part.toLowerCase().startsWith("samesite="), + ); + + return [...parts, "Path=/tracker/hub", "SameSite=Lax"].join("; "); +} + // --- 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 @@ -153,6 +170,16 @@ const trackerProxy = createProxyMiddleware({ ws: true, logLevel: "warn", pathRewrite: (path) => `/tracker${path}`, + 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); + } + }, + }, ...(SYSTEM_PROXY ? { agent: new HttpsProxyAgent(SYSTEM_PROXY) } : {}), }); app.use("/tracker", trackerProxy); diff --git a/src/shared/signalr/connection.test.ts b/src/shared/signalr/connection.test.ts index 9a08502f..91c6c81c 100644 --- a/src/shared/signalr/connection.test.ts +++ b/src/shared/signalr/connection.test.ts @@ -3,6 +3,7 @@ import { SignalRConnection, getSharedConnection, _resetSharedConnections, + _shouldUseLongPollingForDevTrackerProxy, type ConnectionStatus, } from "./connection.js"; @@ -269,3 +270,19 @@ describe("getSharedConnection", () => { expect(a).not.toBe(b); }); }); + +describe("_shouldUseLongPollingForDevTrackerProxy", () => { + it("enables LongPolling only for the local tracker proxy", () => { + expect( + _shouldUseLongPollingForDevTrackerProxy( + "http://localhost:8080/tracker/hub", + ), + ).toBe(true); + expect(_shouldUseLongPollingForDevTrackerProxy("/tracker/hub")).toBe(true); + expect( + _shouldUseLongPollingForDevTrackerProxy( + "https://platform.test.aeroflot.ru/tracker/hub", + ), + ).toBe(false); + }); +}); diff --git a/src/shared/signalr/connection.ts b/src/shared/signalr/connection.ts index 3e05a098..0ef3faff 100644 --- a/src/shared/signalr/connection.ts +++ b/src/shared/signalr/connection.ts @@ -224,13 +224,32 @@ export function _resetSharedConnections(): void { // ---- Default builder (dynamic import) ---- +export function _shouldUseLongPollingForDevTrackerProxy(url: string): boolean { + try { + const parsed = new URL(url, "http://localhost"); + const isLocalHost = + parsed.hostname === "localhost" || + parsed.hostname === "127.0.0.1" || + parsed.hostname === "::1"; + return isLocalHost && parsed.pathname.replace(/\/$/, "") === "/tracker/hub"; + } catch { + return false; + } +} + async function defaultBuildConnection( url: string, delays: number[], ): Promise { - const { HubConnectionBuilder, LogLevel } = await import("@microsoft/signalr"); - return new HubConnectionBuilder() - .withUrl(url) + const { HubConnectionBuilder, HttpTransportType, LogLevel } = await import( + "@microsoft/signalr" + ); + const builder = new HubConnectionBuilder(); + const withUrlOptions = _shouldUseLongPollingForDevTrackerProxy(url) + ? { transport: HttpTransportType.LongPolling } + : undefined; + + 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