import { Hono } from 'hono'; import { basicAuth } from 'hono/basic-auth'; import { DEVICE_NAME, PROTON_IMAP_PASSWORD, PROTON_IMAP_USERNAME } from '@/constants/config'; import { isImapConnected } from '@/modules/proton-mail'; import { checkSignalCli, finishLink, generateLinkQR, getAccount, hasValidAccount, initSignal, } from '@/modules/signal'; import { getAllMappings, remove } from '@/modules/store'; import { verifyApiKey } from '@/utils/auth'; import { formatPhoneNumber, formatUptime } from '@/utils/format'; let cachedQR: string | null = null; let qrCacheTime = 0; let generatingPromise: Promise | null = null; const QR_CACHE_TTL = 10 * 60 * 1000; const admin = new Hono(); admin.use( '*', basicAuth({ verifyUser: (_, password, c) => verifyApiKey(password, c), realm: 'SUP Admin - Username: any, Password: API_KEY', }), ); admin.get('/api/health', async (c) => { const signalOk = await checkSignalCli(); const linked = signalOk && (await hasValidAccount()); const hasProtonConfig = PROTON_IMAP_USERNAME && PROTON_IMAP_PASSWORD; const result: Record = { uptime: formatUptime(process.uptime()), signal: { daemon: signalOk ? 'running' : 'stopped', linked, }, }; if (hasProtonConfig) { result.protonMail = isImapConnected() ? 'connected' : 'disconnected'; } return c.json(result); }); admin.get('/fragment/health', async (c) => { const { html } = await handleHealthFragment(); return c.html(html); }); admin.get('/fragment/signal-info', async (c) => c.html(await handleSignalInfoFragment())); admin.get('/fragment/endpoints', async (c) => c.html(await handleEndpointsFragment())); admin.get('/fragment/link-qr', async (c) => c.html(await handleQRSection())); admin.delete('/action/delete-endpoint/:endpoint', async (c) => { const endpoint = decodeURIComponent(c.req.param('endpoint')); if (!endpoint) { return c.text('Invalid endpoint', 400); } remove(endpoint); return c.html(await handleEndpointsFragment()); }); const handleHealthFragment = async () => { const signalOk = await checkSignalCli(); const linked = signalOk && (await hasValidAccount()); const imap = isImapConnected(); const hasProtonConfig = PROTON_IMAP_USERNAME && PROTON_IMAP_PASSWORD; const accountNumber = getAccount(); const html = `
Signal Network: ${signalOk ? 'Connected' : 'Disconnected'}
Account: ${linked ? 'Linked' : 'Unlinked'} ${linked && accountNumber ? `${formatPhoneNumber(accountNumber)}` : ''}
${ hasProtonConfig ? `
Proton Mail: ${imap ? 'Connected' : 'Disconnected'} ${imap ? `${PROTON_IMAP_USERNAME}` : ''}
` : '' }
${await handleSignalInfoFragment()}
`; return { html, linked }; }; const handleSignalInfoFragment = async () => { if (await hasValidAccount()) { cachedQR = null; return ``; } return handleQRSection(); }; const handleEndpointsFragment = async () => { const endpoints = getAllMappings(); if (endpoints.length === 0) { return '

No endpoints registered

'; } return ` `; }; const handleQRSection = async () => { if (await hasValidAccount()) { return '

Account already linked

'; } const now = Date.now(); if ((!cachedQR || now - qrCacheTime > QR_CACHE_TTL) && !generatingPromise) { generatingPromise = (async () => { try { const qr = await generateLinkQR(); cachedQR = qr; qrCacheTime = Date.now(); finishLink() .then(async () => { await initSignal(); }) .catch(() => {}) .finally(() => { generatingPromise = null; cachedQR = null; qrCacheTime = 0; }); return qr; } catch (error) { generatingPromise = null; throw error; } })(); } if (generatingPromise && !cachedQR) { try { await generatingPromise; } catch { return '

Signal daemon is starting up, please refresh in a few seconds...

'; } } return `

Scan this QR code with your Signal app:

Settings → Linked Devices → Link New Device

QR Code
`; }; export default admin;