import { parseEula, setEulaAccepted } from '@modrinth/server' import { injectNotificationManager } from '@modrinth/ui' import { onScopeDispose, ref, useTemplateRef } from 'vue' import type { ComponentExposed } from 'vue-component-type-helpers' import type EulaModal from '@/components/multiplayer/servers/EulaModal.vue' import { resumeModpackInstall } from '@/composables/useServerInstalls' import { type ServerView, setServerExitReasonHandler, useServers } from '@/composables/useServers' import { serverEventListener, servers as serversApi } from '@/helpers/servers' import { injectDownloadManager } from '@/providers/download-manager' /** * Shared "start with EULA gate" flow used by the servers overview and the * server detail page. Bind the returned `eulaModal` ref to an `` * rendered with `ref="eulaModal"` in the component template. */ export function useServerLifecycle() { const { startServer } = useServers() const { handleError } = injectNotificationManager() // [SERVER-DOWNLOAD-BRIDGE] Capture the download manager once during Vue // setup context. See the note in `startModpackServerInstall` for why // this must be done here and not later. let downloadManager: ReturnType | null = null try { downloadManager = injectDownloadManager() } catch { // Not inside a provider tree — server downloads will not appear in sidebar. } const eulaModal = useTemplateRef>('eulaModal') const eulaText = ref('') let pendingId = '' // Listen for real-time EULA prompt detection from the Rust backend let unregisterEulaListener: (() => void) | null = null serverEventListener((serverId, payload) => { if (payload.event === 'eula_required' && payload.server_id === pendingId) { eulaText.value = payload.eula_text eulaModal.value?.show() } }).then((unregister) => { unregisterEulaListener = unregister }) onScopeDispose(() => { unregisterEulaListener?.() }) /** * Starts the server, gating on the EULA file: * - eula.txt missing → code-create it with `eula=false`, then show the modal * - eula.txt present and `eula=false` → show the modal * - eula.txt present and `eula=true` → start directly */ async function tryStartServer(server: ServerView) { const accepted = await ensureEula(server.id) if (accepted) { await launchServer(server.id) } else { pendingId = server.id eulaModal.value?.show() } } /** * Reads the server's eula.txt. When absent, writes a code-created * `eula=false` file so the acceptance prompt can be shown without booting * the server. Returns whether the EULA is already accepted. */ async function ensureEula(serverId: string): Promise { let text: string try { text = await serversApi.readFile(serverId, 'eula.txt') } catch { // eula.txt doesn't exist yet — code-create it with eula=false so the // manual start gate can offer the prompt without starting the jar. text = setEulaAccepted('', false) await serversApi.writeFile(serverId, 'eula.txt', text).catch(() => {}) } const doc = parseEula(text) if (doc.accepted) return true eulaText.value = text return false } /** * Starts the server and, when the start itself fails over an unaccepted * EULA, falls back to the confirmation dialog instead of just the error. */ async function launchServer(serverId: string) { const started = await startServer(serverId) if (started) return try { const text = await serversApi.readFile(serverId, 'eula.txt') if (parseEula(text).accepted) return eulaText.value = text pendingId = serverId eulaModal.value?.show() } catch { // Start failed for a non-EULA reason; that error was already surfaced. } } async function acceptEula() { const id = pendingId if (!id) return try { const updated = setEulaAccepted(eulaText.value, true) await serversApi.writeFile(id, 'eula.txt', updated) pendingId = '' eulaModal.value?.hide() // Start the server after accepting EULA await startServer(id) } catch (error) { console.error(error) } } function declineEula() { pendingId = '' eulaModal.value?.hide() } /** * Offers the EULA dialog after the server exited on its own over an * unaccepted EULA (detected from the process's final output). Accepting * writes `eula.txt` and restarts, matching the pre-start gate. */ async function offerEulaAfterExit(serverId: string) { try { const text = await serversApi.readFile(serverId, 'eula.txt') if (parseEula(text).accepted) return eulaText.value = text pendingId = serverId eulaModal.value?.show() } catch { // No eula.txt to show; the exit stays unexplained. } } const unregisterExitReasonHandler = setServerExitReasonHandler((serverId, reason) => { if (reason === 'eula') void offerEulaAfterExit(serverId) }) onScopeDispose(unregisterExitReasonHandler) /** Resumes or retries an interrupted/failed modpack download for this server. */ async function resumeInstall(server: ServerView) { try { // [SERVER-DOWNLOAD-BRIDGE] Pass the download manager captured // during setup so the synthetic job appears in sidebar. await resumeModpackInstall(server, downloadManager) } catch (error) { handleError(error) } } return { eulaModal, eulaText, tryStartServer, acceptEula, declineEula, resumeInstall, offerEulaAfterExit, } }