Spaces:
Running
Running
| import { appLogger, readAppLogEntries, subscribeAppLogs, type AppLogEntry } from '@/lib/app-logger'; | |
| import { verifyPasswordHash } from '@/lib/server-runtime'; | |
| import { NextRequest, NextResponse } from 'next/server'; | |
| export const dynamic = 'force-dynamic'; | |
| function isAuthorized(request: NextRequest): boolean { | |
| const appPassword = process.env.APP_PASSWORD?.trim(); | |
| if (!appPassword) return false; | |
| const authorization = request.headers.get('authorization') || ''; | |
| const [scheme, passwordHash] = authorization.split(/\s+/, 2); | |
| if (scheme.toLowerCase() !== 'bearer') return false; | |
| return typeof passwordHash === 'string' && verifyPasswordHash(passwordHash, appPassword); | |
| } | |
| function encodeSseEvent(encoder: TextEncoder, entry: AppLogEntry): Uint8Array { | |
| return encoder.encode(`data: ${JSON.stringify(entry)}\n\n`); | |
| } | |
| function encodeSseComment(encoder: TextEncoder, value: string): Uint8Array { | |
| return encoder.encode(`: ${value}\n\n`); | |
| } | |
| export async function GET(request: NextRequest) { | |
| if (!process.env.APP_PASSWORD?.trim()) { | |
| return NextResponse.json({ error: '日志查看需要先配置 APP_PASSWORD。' }, { status: 403 }); | |
| } | |
| if (!isAuthorized(request)) { | |
| return NextResponse.json({ error: '未授权:缺少或无效的访问码哈希。' }, { status: 401 }); | |
| } | |
| appLogger.info('日志查看器已连接。'); | |
| const encoder = new TextEncoder(); | |
| let unsubscribe = () => {}; | |
| const readableStream = new ReadableStream({ | |
| start(controller) { | |
| controller.enqueue(encodeSseComment(encoder, 'connected')); | |
| const sendEntry = (entry: AppLogEntry) => { | |
| try { | |
| controller.enqueue(encodeSseEvent(encoder, entry)); | |
| } catch (error) { | |
| appLogger.warn('日志查看器发送实时日志失败。', { | |
| error: error instanceof Error ? error.message : String(error) | |
| }); | |
| unsubscribe(); | |
| } | |
| }; | |
| readAppLogEntries().forEach((entry) => { | |
| try { | |
| controller.enqueue(encodeSseEvent(encoder, entry)); | |
| } catch (error) { | |
| appLogger.warn('日志查看器发送历史日志失败。', { | |
| error: error instanceof Error ? error.message : String(error) | |
| }); | |
| unsubscribe(); | |
| } | |
| }); | |
| unsubscribe = subscribeAppLogs(sendEntry); | |
| request.signal.addEventListener( | |
| 'abort', | |
| () => { | |
| unsubscribe(); | |
| try { | |
| controller.close(); | |
| } catch { | |
| // 连接可能已由客户端关闭。 | |
| } | |
| }, | |
| { once: true } | |
| ); | |
| }, | |
| cancel() { | |
| unsubscribe(); | |
| } | |
| }); | |
| return new Response(readableStream, { | |
| headers: { | |
| 'Content-Type': 'text/event-stream', | |
| 'Cache-Control': 'no-cache, no-transform', | |
| Connection: 'keep-alive' | |
| } | |
| }); | |
| } | |