Files
hapi/web/src/hooks/useAuthSource.ts
T
weishu 5defb6dbfc feat: add wireguard relay integration for public server access with End-to-End Encryption
Integrates tunwg (WireGuard tunnel) to enable optional public access
to the hapi server. Tunnel is disabled by default and enabled via
--relay flag or HAPI_TUNNEL=true environment variable.

Users can now run 'hapi server --relay' and get a direct link like:
https://app.hapi.run/?server=https://xxx.relay.hapi.run&token=xxx
2026-01-13 20:18:11 +08:00

156 lines
4.6 KiB
TypeScript

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { getTelegramWebApp, isTelegramEnvironment } from './useTelegram'
import type { AuthSource } from './useAuth'
const ACCESS_TOKEN_PREFIX = 'hapi_access_token::'
function getTelegramInitData(): string | null {
const tg = getTelegramWebApp()
if (tg?.initData) {
return tg.initData
}
// Fallback: check URL parameters (for testing or alternative flows)
const query = new URLSearchParams(window.location.search)
const tgWebAppData = query.get('tgWebAppData')
if (tgWebAppData) {
return tgWebAppData
}
const initData = query.get('initData')
return initData || null
}
function getTokenFromUrlParams(): string | null {
if (typeof window === 'undefined') return null
const query = new URLSearchParams(window.location.search)
return query.get('token')
}
function getAccessTokenKey(baseUrl: string): string {
return `${ACCESS_TOKEN_PREFIX}${baseUrl}`
}
function getStoredAccessToken(key: string): string | null {
try {
return localStorage.getItem(key)
} catch {
return null
}
}
function storeAccessToken(key: string, token: string): void {
try {
localStorage.setItem(key, token)
} catch {
// Ignore storage errors
}
}
function clearStoredAccessToken(key: string): void {
try {
localStorage.removeItem(key)
} catch {
// Ignore storage errors
}
}
export function useAuthSource(baseUrl: string): {
authSource: AuthSource | null
isLoading: boolean
isTelegram: boolean
setAccessToken: (token: string) => void
clearAuth: () => void
} {
const [authSource, setAuthSource] = useState<AuthSource | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [isTelegram, setIsTelegram] = useState(false)
const retryCountRef = useRef(0)
const accessTokenKey = useMemo(() => getAccessTokenKey(baseUrl), [baseUrl])
// Initialize auth source on mount, with retry for delayed Telegram initData
useEffect(() => {
retryCountRef.current = 0
setAuthSource(null)
setIsTelegram(false)
setIsLoading(true)
const telegramInitData = getTelegramInitData()
if (telegramInitData) {
// Telegram Mini App environment
setAuthSource({ type: 'telegram', initData: telegramInitData })
setIsTelegram(true)
setIsLoading(false)
return
}
// Check for URL token parameter (for direct access links)
const urlToken = getTokenFromUrlParams()
if (urlToken) {
storeAccessToken(accessTokenKey, urlToken) // Save to localStorage for refresh
setAuthSource({ type: 'accessToken', token: urlToken })
setIsLoading(false)
return
}
// Check for stored access token as fallback
const storedToken = getStoredAccessToken(accessTokenKey)
if (storedToken) {
setAuthSource({ type: 'accessToken', token: storedToken })
setIsLoading(false)
return
}
// Check if we're in a Telegram environment before polling
if (!isTelegramEnvironment()) {
// Plain browser - show login prompt immediately
setIsLoading(false)
return
}
// Telegram environment detected - poll for delayed initData
// Telegram WebApp SDK may initialize slightly after page mount
const maxRetries = 20
const retryInterval = 250 // ms
const interval = setInterval(() => {
retryCountRef.current += 1
const initData = getTelegramInitData()
if (initData) {
setAuthSource({ type: 'telegram', initData })
setIsTelegram(true)
setIsLoading(false)
clearInterval(interval)
} else if (retryCountRef.current >= maxRetries) {
// Give up - show login prompt for browser access
setIsLoading(false)
clearInterval(interval)
}
}, retryInterval)
return () => {
clearInterval(interval)
}
}, [accessTokenKey])
const setAccessToken = useCallback((token: string) => {
storeAccessToken(accessTokenKey, token)
setAuthSource({ type: 'accessToken', token })
}, [accessTokenKey])
const clearAuth = useCallback(() => {
clearStoredAccessToken(accessTokenKey)
setAuthSource(null)
}, [accessTokenKey])
return {
authSource,
isLoading,
isTelegram,
setAccessToken,
clearAuth
}
}