mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Merge H5 LAN URL refresh into main
The detached worktree fix corrects stale private-network H5 public URLs after the desktop server chooses a new ephemeral LAN port. Merge it into local main while leaving unrelated local release edits untouched. Constraint: Local main already had unrelated unstaged desktop release metadata edits. Rejected: Broad staging before the merge | would risk committing unrelated local release work. Confidence: high Scope-risk: narrow Tested: bun test src/server/__tests__/h5-access-service.test.ts Tested: bun run check:server Not-tested: physical phone browser on local WiFi after merge
This commit is contained in:
commit
bfe3c8b5e0
@ -2,7 +2,10 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
||||
import * as fs from 'node:fs/promises'
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
import { H5AccessService } from '../services/h5AccessService.js'
|
||||
import {
|
||||
H5AccessService,
|
||||
resolveEffectiveH5PublicBaseUrl,
|
||||
} from '../services/h5AccessService.js'
|
||||
import { ProviderService } from '../services/providerService.js'
|
||||
|
||||
let tmpDir: string
|
||||
@ -85,6 +88,41 @@ describe('H5AccessService', () => {
|
||||
expect(result.settings.publicBaseUrl).toBe('http://192.168.1.20:28670')
|
||||
})
|
||||
|
||||
test('configured public URL overrides stale stored local URLs', async () => {
|
||||
const service = new H5AccessService()
|
||||
await service.updateSettings({
|
||||
publicBaseUrl: 'http://192.168.0.102:5179',
|
||||
})
|
||||
|
||||
process.env.CLAUDE_H5_PUBLIC_BASE_URL = 'https://chat.example.com/app/'
|
||||
const result = await service.enable()
|
||||
|
||||
expect(result.settings.publicBaseUrl).toBe('https://chat.example.com/app')
|
||||
})
|
||||
|
||||
test('auto LAN mode replaces stale local public URLs but keeps public reverse proxies', () => {
|
||||
expect(resolveEffectiveH5PublicBaseUrl({
|
||||
enabled: true,
|
||||
storedPublicBaseUrl: 'http://192.168.0.102:5179',
|
||||
configuredPublicBaseUrl: null,
|
||||
autoPublicBaseUrl: 'http://192.168.0.102:39876',
|
||||
})).toBe('http://192.168.0.102:39876')
|
||||
|
||||
expect(resolveEffectiveH5PublicBaseUrl({
|
||||
enabled: true,
|
||||
storedPublicBaseUrl: 'http://127.0.0.1:5179',
|
||||
configuredPublicBaseUrl: null,
|
||||
autoPublicBaseUrl: 'http://192.168.0.102:39876',
|
||||
})).toBe('http://192.168.0.102:39876')
|
||||
|
||||
expect(resolveEffectiveH5PublicBaseUrl({
|
||||
enabled: true,
|
||||
storedPublicBaseUrl: 'https://chat.example.com/app',
|
||||
configuredPublicBaseUrl: null,
|
||||
autoPublicBaseUrl: 'http://192.168.0.102:39876',
|
||||
})).toBe('https://chat.example.com/app')
|
||||
})
|
||||
|
||||
test('regenerateToken invalidates the previous token', async () => {
|
||||
const service = new H5AccessService()
|
||||
|
||||
|
||||
@ -39,7 +39,12 @@ function toPublicSettings(settings: StoredH5AccessSettings): H5AccessSettings {
|
||||
enabled: settings.enabled,
|
||||
tokenPreview: settings.tokenPreview,
|
||||
allowedOrigins: settings.allowedOrigins,
|
||||
publicBaseUrl: settings.publicBaseUrl ?? (settings.enabled ? resolveAutoPublicBaseUrl() : null),
|
||||
publicBaseUrl: resolveEffectiveH5PublicBaseUrl({
|
||||
enabled: settings.enabled,
|
||||
storedPublicBaseUrl: settings.publicBaseUrl,
|
||||
configuredPublicBaseUrl: resolveConfiguredPublicBaseUrl(),
|
||||
autoPublicBaseUrl: resolveAutoLanPublicBaseUrl(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,7 +126,7 @@ function normalizePublicBaseUrl(input: unknown): string | null {
|
||||
return `${parsed.origin}${normalizedPath === '/' ? '' : normalizedPath}`
|
||||
}
|
||||
|
||||
function resolveAutoPublicBaseUrl(): string | null {
|
||||
function resolveConfiguredPublicBaseUrl(): string | null {
|
||||
const configured = process.env.CLAUDE_H5_PUBLIC_BASE_URL
|
||||
if (configured) {
|
||||
try {
|
||||
@ -131,6 +136,10 @@ function resolveAutoPublicBaseUrl(): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function resolveAutoLanPublicBaseUrl(): string | null {
|
||||
if (process.env.CLAUDE_H5_AUTO_PUBLIC_URL !== '1') {
|
||||
return null
|
||||
}
|
||||
@ -143,6 +152,60 @@ function resolveAutoPublicBaseUrl(): string | null {
|
||||
return `http://${host}:${ProviderService.getServerPort()}`
|
||||
}
|
||||
|
||||
export function resolveEffectiveH5PublicBaseUrl({
|
||||
enabled,
|
||||
storedPublicBaseUrl,
|
||||
configuredPublicBaseUrl,
|
||||
autoPublicBaseUrl,
|
||||
}: {
|
||||
enabled: boolean
|
||||
storedPublicBaseUrl: string | null
|
||||
configuredPublicBaseUrl: string | null
|
||||
autoPublicBaseUrl: string | null
|
||||
}): string | null {
|
||||
if (!enabled) {
|
||||
return storedPublicBaseUrl
|
||||
}
|
||||
|
||||
if (configuredPublicBaseUrl) {
|
||||
return configuredPublicBaseUrl
|
||||
}
|
||||
|
||||
if (!autoPublicBaseUrl) {
|
||||
return storedPublicBaseUrl
|
||||
}
|
||||
|
||||
if (!storedPublicBaseUrl || isLocalOrPrivatePublicBaseUrl(storedPublicBaseUrl)) {
|
||||
return autoPublicBaseUrl
|
||||
}
|
||||
|
||||
return storedPublicBaseUrl
|
||||
}
|
||||
|
||||
function isLocalOrPrivatePublicBaseUrl(value: string): boolean {
|
||||
try {
|
||||
const hostname = new URL(value).hostname
|
||||
.trim()
|
||||
.replace(/^\[/, '')
|
||||
.replace(/\]$/, '')
|
||||
.toLowerCase()
|
||||
return isLocalOrPrivateHost(hostname)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalOrPrivateHost(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:')
|
||||
}
|
||||
|
||||
function findPrivateLanAddress(): string | null {
|
||||
for (const entries of Object.values(os.networkInterfaces())) {
|
||||
for (const entry of entries ?? []) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user