{t('settings.general.h5AccessOpenHint')}
diff --git a/src/server/__tests__/h5-access-service.test.ts b/src/server/__tests__/h5-access-service.test.ts
index 79a57746..f76b96a4 100644
--- a/src/server/__tests__/h5-access-service.test.ts
+++ b/src/server/__tests__/h5-access-service.test.ts
@@ -3,6 +3,7 @@ import * as fs from 'node:fs/promises'
import * as os from 'node:os'
import * as path from 'node:path'
import {
+ findPrivateLanAddress,
H5AccessService,
resolveEffectiveH5PublicBaseUrl,
} from '../services/h5AccessService.js'
@@ -100,10 +101,10 @@ describe('H5AccessService', () => {
expect(result.settings.publicBaseUrl).toBe('https://chat.example.com/app')
})
- test('auto LAN mode replaces stale local public URLs but keeps public reverse proxies', () => {
+ test('auto LAN mode fills blank or loopback URLs but preserves manual LAN URLs', () => {
expect(resolveEffectiveH5PublicBaseUrl({
enabled: true,
- storedPublicBaseUrl: 'http://192.168.0.102:5179',
+ storedPublicBaseUrl: null,
configuredPublicBaseUrl: null,
autoPublicBaseUrl: 'http://192.168.0.102:39876',
})).toBe('http://192.168.0.102:39876')
@@ -115,6 +116,13 @@ describe('H5AccessService', () => {
autoPublicBaseUrl: 'http://192.168.0.102:39876',
})).toBe('http://192.168.0.102:39876')
+ expect(resolveEffectiveH5PublicBaseUrl({
+ enabled: true,
+ storedPublicBaseUrl: 'http://192.168.1.100:54064',
+ configuredPublicBaseUrl: null,
+ autoPublicBaseUrl: 'http://172.20.16.1:39876',
+ })).toBe('http://192.168.1.100:54064')
+
expect(resolveEffectiveH5PublicBaseUrl({
enabled: true,
storedPublicBaseUrl: 'https://chat.example.com/app',
@@ -123,6 +131,35 @@ describe('H5AccessService', () => {
})).toBe('https://chat.example.com/app')
})
+ test('auto LAN detection prefers physical adapters over WSL and Docker virtual adapters', () => {
+ expect(findPrivateLanAddress({
+ 'vEthernet (WSL)': [{
+ address: '172.20.16.1',
+ netmask: '255.255.240.0',
+ family: 'IPv4',
+ mac: '00:15:5d:00:00:01',
+ internal: false,
+ cidr: '172.20.16.1/20',
+ }],
+ 'Docker Desktop': [{
+ address: '172.17.0.1',
+ netmask: '255.255.0.0',
+ family: 'IPv4',
+ mac: '02:42:ac:11:00:01',
+ internal: false,
+ cidr: '172.17.0.1/16',
+ }],
+ 'Wi-Fi': [{
+ address: '192.168.1.100',
+ netmask: '255.255.255.0',
+ family: 'IPv4',
+ mac: 'aa:bb:cc:dd:ee:ff',
+ internal: false,
+ cidr: '192.168.1.100/24',
+ }],
+ })).toBe('192.168.1.100')
+ })
+
test('regenerateToken invalidates the previous token', async () => {
const service = new H5AccessService()
diff --git a/src/server/services/h5AccessService.ts b/src/server/services/h5AccessService.ts
index 5e03194e..09b3247a 100644
--- a/src/server/services/h5AccessService.ts
+++ b/src/server/services/h5AccessService.ts
@@ -175,47 +175,89 @@ export function resolveEffectiveH5PublicBaseUrl({
return storedPublicBaseUrl
}
- if (!storedPublicBaseUrl || isLocalOrPrivatePublicBaseUrl(storedPublicBaseUrl)) {
+ if (!storedPublicBaseUrl || isLocalPublicBaseUrl(storedPublicBaseUrl)) {
return autoPublicBaseUrl
}
return storedPublicBaseUrl
}
-function isLocalOrPrivatePublicBaseUrl(value: string): boolean {
+function isLocalPublicBaseUrl(value: string): boolean {
try {
const hostname = new URL(value).hostname
.trim()
.replace(/^\[/, '')
.replace(/\]$/, '')
.toLowerCase()
- return isLocalOrPrivateHost(hostname)
+ return isLocalHost(hostname)
} catch {
return false
}
}
-function isLocalOrPrivateHost(hostname: string): boolean {
+function isLocalHost(hostname: string): boolean {
return hostname === 'localhost' ||
hostname === '127.0.0.1' ||
hostname === '::1' ||
hostname === '0.0.0.0' ||
- isPrivateIPv4(hostname) ||
- hostname.startsWith('fc') ||
- hostname.startsWith('fd') ||
- hostname.startsWith('fe80:')
+ hostname === '::'
}
-function findPrivateLanAddress(): string | null {
- for (const entries of Object.values(os.networkInterfaces())) {
+type NetworkInterfaces = ReturnType