From 7421db285702b346686c2546c90d850b00aaaefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Fri, 17 Apr 2026 20:06:22 +0800 Subject: [PATCH] fix(desktop): align haha-oauth handler with sibling error-handling convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code review follow-up on 142eb9cd: - 加 outer try/catch + errorResponse wrapper (parity with providers.ts) - 400 响应改用 ApiError.badRequest (shape 跟其他 handler 一致: {error: 'BAD_REQUEST', message}) - test 加上对 error shape 的断言 Co-Authored-By: Claude Opus 4.7 (1M context) --- src/server/__tests__/haha-oauth-api.test.ts | 2 + src/server/api/haha-oauth.ts | 124 ++++++++++---------- 2 files changed, 65 insertions(+), 61 deletions(-) diff --git a/src/server/__tests__/haha-oauth-api.test.ts b/src/server/__tests__/haha-oauth-api.test.ts index f2444cd0..69f992ef 100644 --- a/src/server/__tests__/haha-oauth-api.test.ts +++ b/src/server/__tests__/haha-oauth-api.test.ts @@ -64,6 +64,8 @@ describe('POST /api/haha-oauth/start', () => { const { req, url, segments } = buildReq('POST', '/api/haha-oauth/start', {}) const res = await handleHahaOAuthApi(req, url, segments) expect(res.status).toBe(400) + const body = (await res.json()) as { error: string; message?: string } + expect(body.error).toBe('BAD_REQUEST') }) }) diff --git a/src/server/api/haha-oauth.ts b/src/server/api/haha-oauth.ts index a39c34aa..dfe54d7b 100644 --- a/src/server/api/haha-oauth.ts +++ b/src/server/api/haha-oauth.ts @@ -10,6 +10,7 @@ import { z } from 'zod' import { hahaOAuthService } from '../services/hahaOAuthService.js' +import { ApiError, errorResponse } from '../middleware/errorHandler.js' const StartRequestSchema = z.object({ serverPort: z.number().int().positive(), @@ -27,71 +28,72 @@ export async function handleHahaOAuthApi( url: URL, segments: string[], ): Promise { - const action = segments[2] // segments: ['api', 'haha-oauth', ] + try { + const action = segments[2] // segments: ['api', 'haha-oauth', ] - if (action === 'start' && req.method === 'POST') { - let body: unknown - try { - body = await req.json() - } catch { - return Response.json({ error: 'Invalid JSON body' }, { status: 400 }) + if (action === 'start' && req.method === 'POST') { + let body: unknown + try { + body = await req.json() + } catch { + throw ApiError.badRequest('Invalid JSON body') + } + const parsed = StartRequestSchema.safeParse(body) + if (!parsed.success) { + throw ApiError.badRequest('serverPort (positive integer) required') + } + const session = hahaOAuthService.startSession({ + serverPort: parsed.data.serverPort, + }) + return Response.json({ + authorizeUrl: session.authorizeUrl, + state: session.state, + }) } - const parsed = StartRequestSchema.safeParse(body) - if (!parsed.success) { - return Response.json( - { error: 'serverPort (positive integer) required' }, - { status: 400 }, - ) + + if (action === 'callback' && req.method === 'GET') { + const code = url.searchParams.get('code') + const state = url.searchParams.get('state') + const error = url.searchParams.get('error') + + if (error) { + return html(renderCallbackPage(false, `OAuth provider returned: ${error}`)) + } + if (!code || !state) { + return html(renderCallbackPage(false, 'Missing code or state parameter')) + } + + try { + await hahaOAuthService.completeSession(code, state) + return html(renderCallbackPage(true, null)) + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + return html(renderCallbackPage(false, msg)) + } } - const session = hahaOAuthService.startSession({ - serverPort: parsed.data.serverPort, - }) - return Response.json({ - authorizeUrl: session.authorizeUrl, - state: session.state, - }) + + if ((action === undefined || action === 'status') && req.method === 'GET') { + const tokens = await hahaOAuthService.loadTokens() + if (!tokens) { + return Response.json({ loggedIn: false }) + } + return Response.json({ + loggedIn: true, + expiresAt: tokens.expiresAt, + scopes: tokens.scopes, + subscriptionType: tokens.subscriptionType, + }) + } + + if (action === undefined && req.method === 'DELETE') { + await hahaOAuthService.deleteTokens() + return Response.json({ ok: true }) + } + + return Response.json({ error: 'Not Found' }, { status: 404 }) + } catch (error) { + return errorResponse(error) } - - if (action === 'callback' && req.method === 'GET') { - const code = url.searchParams.get('code') - const state = url.searchParams.get('state') - const error = url.searchParams.get('error') - - if (error) { - return html(renderCallbackPage(false, `OAuth provider returned: ${error}`)) - } - if (!code || !state) { - return html(renderCallbackPage(false, 'Missing code or state parameter')) - } - - try { - await hahaOAuthService.completeSession(code, state) - return html(renderCallbackPage(true, null)) - } catch (err) { - const msg = err instanceof Error ? err.message : String(err) - return html(renderCallbackPage(false, msg)) - } - } - - if ((action === undefined || action === 'status') && req.method === 'GET') { - const tokens = await hahaOAuthService.loadTokens() - if (!tokens) { - return Response.json({ loggedIn: false }) - } - return Response.json({ - loggedIn: true, - expiresAt: tokens.expiresAt, - scopes: tokens.scopes, - subscriptionType: tokens.subscriptionType, - }) - } - - if (action === undefined && req.method === 'DELETE') { - await hahaOAuthService.deleteTokens() - return Response.json({ ok: true }) - } - - return Response.json({ error: 'Not Found' }, { status: 404 }) } function renderCallbackPage(success: boolean, errorMsg: string | null): string {