diff --git a/src/server/__tests__/h5-access-service.test.ts b/src/server/__tests__/h5-access-service.test.ts index f76b96a4..99ddf5c9 100644 --- a/src/server/__tests__/h5-access-service.test.ts +++ b/src/server/__tests__/h5-access-service.test.ts @@ -101,7 +101,7 @@ describe('H5AccessService', () => { expect(result.settings.publicBaseUrl).toBe('https://chat.example.com/app') }) - test('auto LAN mode fills blank or loopback URLs but preserves manual LAN URLs', () => { + test('auto LAN mode fills blank or loopback URLs and refreshes manual LAN ports', () => { expect(resolveEffectiveH5PublicBaseUrl({ enabled: true, storedPublicBaseUrl: null, @@ -121,7 +121,7 @@ describe('H5AccessService', () => { storedPublicBaseUrl: 'http://192.168.1.100:54064', configuredPublicBaseUrl: null, autoPublicBaseUrl: 'http://172.20.16.1:39876', - })).toBe('http://192.168.1.100:54064') + })).toBe('http://192.168.1.100:39876') expect(resolveEffectiveH5PublicBaseUrl({ enabled: true, @@ -131,6 +131,22 @@ describe('H5AccessService', () => { })).toBe('https://chat.example.com/app') }) + test('auto LAN mode keeps full reverse proxy URLs intact', () => { + expect(resolveEffectiveH5PublicBaseUrl({ + enabled: true, + storedPublicBaseUrl: 'https://192.168.1.100:8443', + configuredPublicBaseUrl: null, + autoPublicBaseUrl: 'http://192.168.1.100:39876', + })).toBe('https://192.168.1.100:8443') + + expect(resolveEffectiveH5PublicBaseUrl({ + enabled: true, + storedPublicBaseUrl: 'http://192.168.1.100:8080/h5', + configuredPublicBaseUrl: null, + autoPublicBaseUrl: 'http://192.168.1.100:39876', + })).toBe('http://192.168.1.100:8080/h5') + }) + test('auto LAN detection prefers physical adapters over WSL and Docker virtual adapters', () => { expect(findPrivateLanAddress({ 'vEthernet (WSL)': [{ diff --git a/src/server/services/h5AccessService.ts b/src/server/services/h5AccessService.ts index 09b3247a..30b3afba 100644 --- a/src/server/services/h5AccessService.ts +++ b/src/server/services/h5AccessService.ts @@ -179,6 +179,11 @@ export function resolveEffectiveH5PublicBaseUrl({ return autoPublicBaseUrl } + const refreshedLanUrl = refreshLanPublicBaseUrlPort(storedPublicBaseUrl, autoPublicBaseUrl) + if (refreshedLanUrl) { + return refreshedLanUrl + } + return storedPublicBaseUrl } @@ -203,6 +208,27 @@ function isLocalHost(hostname: string): boolean { hostname === '::' } +function refreshLanPublicBaseUrlPort(storedPublicBaseUrl: string, autoPublicBaseUrl: string): string | null { + try { + const stored = new URL(storedPublicBaseUrl) + const auto = new URL(autoPublicBaseUrl) + const storedPath = stored.pathname.replace(/\/+$/, '') + + if ( + stored.protocol !== 'http:' || + storedPath !== '' || + !isPrivateIPv4(stored.hostname) || + !auto.port + ) { + return null + } + + return `${stored.protocol}//${stored.hostname}:${auto.port}` + } catch { + return null + } +} + type NetworkInterfaces = ReturnType const PHYSICAL_INTERFACE_RE = /\b(wi-?fi|wlan|wireless|ethernet|lan|en\d+|eth\d+)\b/i