2026-01-13 14:49:19 +01:00

257 lines
8.4 KiB
TypeScript

import { configStorage } from '@/lib/configStorage'
import type { RelayConfig } from '@/lib/configStorageTypes'
import { t } from '@/lib/i18n'
import { relaySessionManager } from '@/lib/relaySessionManager'
import { userConfirm } from '@/lib/userConfirm'
export async function loadRelays(params: {
setRelays: (value: RelayConfig[]) => void
setLoading: (value: boolean) => void
setError: (value: string | null) => void
}): Promise<void> {
try {
params.setLoading(true)
params.setError(null)
const config = await configStorage.getConfig()
const updated = await maybeDisableFailedRelays(config.relays)
params.setRelays(sortByPriority(updated))
} catch (e) {
const errorMessage = e instanceof Error ? e.message : t('settings.relay.error.loadFailed')
params.setError(errorMessage)
console.error('[RelayManager] Error loading relays:', e)
} finally {
params.setLoading(false)
}
}
export async function toggleEnabled(params: {
id: string
enabled: boolean
setRelays: (value: RelayConfig[]) => void
setError: (value: string | null) => void
onConfigChange: (() => void) | undefined
}): Promise<void> {
try {
await configStorage.updateRelay(params.id, { enabled: params.enabled })
await reloadRelays({ setRelays: params.setRelays, setError: params.setError })
params.onConfigChange?.()
} catch (e) {
const errorMessage = e instanceof Error ? e.message : t('settings.relay.error.updateFailed')
params.setError(errorMessage)
console.error('[RelayManager] Error updating relay:', e)
}
}
export async function updateUrl(params: {
id: string
url: string
setRelays: (value: RelayConfig[]) => void
setError: (value: string | null) => void
setEditingId: (value: string | null) => void
onConfigChange: (() => void) | undefined
}): Promise<void> {
try {
await configStorage.updateRelay(params.id, { url: params.url })
await reloadRelays({ setRelays: params.setRelays, setError: params.setError })
params.setEditingId(null)
params.onConfigChange?.()
} catch (e) {
const errorMessage = e instanceof Error ? e.message : t('settings.relay.error.urlFailed')
params.setError(errorMessage)
console.error('[RelayManager] Error updating URL:', e)
}
}
export async function addRelay(params: {
newUrl: string
setError: (value: string | null) => void
setNewUrl: (value: string) => void
setShowAddForm: (value: boolean | ((prev: boolean) => boolean)) => void
setRelays: (value: RelayConfig[]) => void
onConfigChange: (() => void) | undefined
}): Promise<void> {
const normalizedUrl = normalizeRelayUrl(params.newUrl)
if (!normalizedUrl) {
params.setError(t('settings.relay.error.urlRequired'))
return
}
try {
void new URL(normalizedUrl)
await configStorage.addRelay(normalizedUrl, true)
params.setNewUrl('')
params.setShowAddForm(false)
await reloadRelays({ setRelays: params.setRelays, setError: params.setError })
params.onConfigChange?.()
} catch (e) {
params.setError(getAddRelayErrorMessage(e))
console.error('[RelayManager] Error adding relay:', e)
}
}
export async function removeRelay(params: {
id: string
setRelays: (value: RelayConfig[]) => void
setError: (value: string | null) => void
onConfigChange: (() => void) | undefined
}): Promise<void> {
const confirmed = await userConfirm(t('settings.relay.remove.confirm'))
if (!confirmed) {
return
}
try {
await configStorage.removeRelay(params.id)
await reloadRelays({ setRelays: params.setRelays, setError: params.setError })
params.onConfigChange?.()
} catch (e) {
const errorMessage = e instanceof Error ? e.message : t('settings.relay.error.removeFailed')
params.setError(errorMessage)
console.error('[RelayManager] Error removing relay:', e)
}
}
export async function updatePriorities(params: {
relays: RelayConfig[]
setError: (value: string | null) => void
onConfigChange: (() => void) | undefined
}): Promise<void> {
try {
const updates = params.relays.map((relay, index) => {
const priority = index + 1
return relay.priority !== priority ? configStorage.updateRelay(relay.id, { priority }) : Promise.resolve()
})
await Promise.all(updates)
params.onConfigChange?.()
} catch (e) {
const errorMessage = e instanceof Error ? e.message : t('settings.relay.error.priorityFailed')
params.setError(errorMessage)
console.error('[RelayManager] Error updating priorities:', e)
}
}
export function onDragStart(params: {
e: React.DragEvent<HTMLDivElement>
id: string
setDraggedId: (value: string | null) => void
}): void {
params.setDraggedId(params.id)
const { dataTransfer } = params.e
dataTransfer.effectAllowed = 'move'
dataTransfer.setData('text/plain', params.id)
}
export function onDragOver(params: {
e: React.DragEvent<HTMLDivElement>
id: string
setDragOverId: (value: string | null) => void
}): void {
params.e.preventDefault()
const { dataTransfer } = params.e
dataTransfer.dropEffect = 'move'
params.setDragOverId(params.id)
}
export async function onDrop(params: {
e: React.DragEvent<HTMLDivElement>
targetId: string
relays: RelayConfig[]
draggedId: string | null
setRelays: (value: RelayConfig[]) => void
setDraggedId: (value: string | null) => void
setDragOverId: (value: string | null) => void
setError: (value: string | null) => void
onConfigChange: (() => void) | undefined
}): Promise<void> {
params.e.preventDefault()
params.setDragOverId(null)
if (!params.draggedId || params.draggedId === params.targetId) {
params.setDraggedId(null)
return
}
const reordered = reorderRelays({ relays: params.relays, draggedId: params.draggedId, targetId: params.targetId })
if (!reordered) {
params.setDraggedId(null)
return
}
params.setRelays(reordered)
params.setDraggedId(null)
await updatePriorities({ relays: reordered, setError: params.setError, onConfigChange: params.onConfigChange })
}
export function getRelayCardClassName(relayId: string, draggedId: string | null, dragOverId: string | null): string {
if (draggedId === relayId) {
return 'opacity-50 border-neon-cyan'
}
if (dragOverId === relayId) {
return 'border-neon-green shadow-lg'
}
return 'border-neon-cyan/30'
}
function sortByPriority(relays: RelayConfig[]): RelayConfig[] {
return relays.sort((a, b) => a.priority - b.priority)
}
async function reloadRelays(params: { setRelays: (value: RelayConfig[]) => void; setError: (value: string | null) => void }): Promise<void> {
try {
const config = await configStorage.getConfig()
params.setRelays(sortByPriority(config.relays))
} catch (e) {
const errorMessage = e instanceof Error ? e.message : t('settings.relay.error.loadFailed')
params.setError(errorMessage)
console.error('[RelayManager] Error reloading relays:', e)
}
}
async function maybeDisableFailedRelays(relays: RelayConfig[]): Promise<RelayConfig[]> {
const failedRelays = relaySessionManager.getFailedRelays()
if (failedRelays.length === 0) {
return relays
}
const toDisable = relays.filter((relay) => relay.enabled && failedRelays.includes(relay.url))
if (toDisable.length === 0) {
return relays
}
for (const relay of toDisable) {
await configStorage.updateRelay(relay.id, { enabled: false })
}
const updated = await configStorage.getConfig()
return updated.relays
}
function normalizeRelayUrl(rawUrl: string): string | null {
const trimmed = rawUrl.trim()
if (trimmed.length === 0) {
return null
}
if (trimmed.startsWith('ws://') || trimmed.startsWith('wss://')) {
return trimmed
}
return `wss://${trimmed}`
}
function getAddRelayErrorMessage(error: unknown): string {
if (error instanceof TypeError && error.message.includes('Invalid URL')) {
return t('settings.relay.error.invalidUrl')
}
if (error instanceof Error) {
return error.message
}
return t('settings.relay.error.addFailed')
}
function reorderRelays(params: { relays: RelayConfig[]; draggedId: string; targetId: string }): RelayConfig[] | null {
const draggedIndex = params.relays.findIndex((relay) => relay.id === params.draggedId)
const targetIndex = params.relays.findIndex((relay) => relay.id === params.targetId)
if (draggedIndex === -1 || targetIndex === -1) {
return null
}
const next = [...params.relays]
const removed = next[draggedIndex]
if (!removed) {
return null
}
next.splice(draggedIndex, 1)
next.splice(targetIndex, 0, removed)
return next
}