From 9d8a38a4c49a3379aa8d19b43bc40ae6ee270042 Mon Sep 17 00:00:00 2001 From: imbytecat Date: Thu, 5 Mar 2026 14:06:43 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=20ORPC=20handler=20?= =?UTF-8?q?=E8=AF=AD=E4=B9=89=E3=80=81=E5=8A=A0=E5=9B=BA=20Electron=20?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E3=80=81=E4=BC=98=E5=8C=96=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E4=B8=8E=E8=BF=90=E8=A1=8C=E6=97=B6=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - todo.router: create 错误码 NOT_FOUND → INTERNAL_SERVER_ERROR,remove 增加存在性检查 - __root: devtools 仅在 DEV 环境渲染 - Electron: 添加 will-navigate 导航拦截、显式安全 webPreferences、deny-all 权限请求 - sidecar: 空 catch 块补充意图注释,新增 lastResolvedUrl getter - todo.contract: 硬编码 omit 改用 generatedFieldKeys - router: QueryClient 添加 staleTime/retry 默认值 - turbo: build 任务精细化 inputs 提升缓存命中率 - fields: id() 改为模块私有 --- apps/desktop/src/main/index.ts | 27 ++++++++++++++-- apps/desktop/src/main/sidecar.ts | 9 +++++- apps/server/src/router.tsx | 9 +++++- apps/server/src/routes/__root.tsx | 32 ++++++++++--------- .../src/server/api/contracts/todo.contract.ts | 13 ++------ .../src/server/api/routers/todo.router.ts | 8 +++-- apps/server/src/server/db/fields.ts | 2 +- apps/server/turbo.json | 1 + 8 files changed, 69 insertions(+), 32 deletions(-) diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 1b3b00b..b2ec28b 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -1,5 +1,5 @@ import { join } from 'node:path' -import { app, BrowserWindow, dialog, shell } from 'electron' +import { app, BrowserWindow, dialog, session, shell } from 'electron' import { createSidecarRuntime } from './sidecar' const DEV_SERVER_URL = 'http://localhost:3000' @@ -61,6 +61,8 @@ const createWindow = async () => { webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: true, + contextIsolation: true, + nodeIntegration: false, }, }) mainWindow = windowRef @@ -78,6 +80,21 @@ const createWindow = async () => { return { action: 'deny' } }) + windowRef.webContents.on('will-navigate', (event, url) => { + const allowed = [DEV_SERVER_URL, sidecar.lastResolvedUrl].filter((v): v is string => v != null) + const isAllowed = allowed.some((origin) => url.startsWith(origin)) + + if (!isAllowed) { + event.preventDefault() + + if (canOpenExternally(url)) { + void shell.openExternal(url) + } else if (!app.isPackaged) { + console.warn(`Blocked navigation to: ${url}`) + } + } + }) + windowRef.on('closed', () => { if (mainWindow === windowRef) { mainWindow = null @@ -151,7 +168,13 @@ const handleWindowCreationError = (error: unknown, context: string) => { app .whenReady() - .then(() => ensureWindow()) + .then(() => { + session.defaultSession.setPermissionRequestHandler((_webContents, _permission, callback) => { + callback(false) + }) + + return ensureWindow() + }) .catch((error) => { handleWindowCreationError(error, 'Failed to create window') }) diff --git a/apps/desktop/src/main/sidecar.ts b/apps/desktop/src/main/sidecar.ts index 0bb7ba2..d57d8b5 100644 --- a/apps/desktop/src/main/sidecar.ts +++ b/apps/desktop/src/main/sidecar.ts @@ -27,6 +27,7 @@ type SidecarRuntimeOptions = { type SidecarRuntime = { resolveUrl: () => Promise stop: () => void + lastResolvedUrl: string | null } const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)) @@ -72,7 +73,9 @@ const isServerReady = async (url: string): Promise => { return true } - } catch {} + } catch { + // Expected: probe request fails while server is still starting up + } } return false @@ -239,11 +242,15 @@ export const createSidecarRuntime = (options: SidecarRuntimeOptions): SidecarRun throw new Error('Dev server not responding. Run `bun dev` in apps/server first.') } + state.url = options.devServerUrl return options.devServerUrl } return { resolveUrl, stop, + get lastResolvedUrl() { + return state.url + }, } } diff --git a/apps/server/src/router.tsx b/apps/server/src/router.tsx index 2625061..1390fbd 100644 --- a/apps/server/src/router.tsx +++ b/apps/server/src/router.tsx @@ -5,7 +5,14 @@ import type { RouterContext } from './routes/__root' import { routeTree } from './routeTree.gen' export const getRouter = () => { - const queryClient = new QueryClient() + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30 * 1000, + retry: 1, + }, + }, + }) const router = createRouter({ routeTree, diff --git a/apps/server/src/routes/__root.tsx b/apps/server/src/routes/__root.tsx index 3d68915..b7f758f 100644 --- a/apps/server/src/routes/__root.tsx +++ b/apps/server/src/routes/__root.tsx @@ -46,21 +46,23 @@ function RootDocument({ children }: Readonly<{ children: ReactNode }>) { {children} - , - }, - { - name: 'TanStack Query', - render: , - }, - ]} - /> + {import.meta.env.DEV && ( + , + }, + { + name: 'TanStack Query', + render: , + }, + ]} + /> + )} diff --git a/apps/server/src/server/api/contracts/todo.contract.ts b/apps/server/src/server/api/contracts/todo.contract.ts index b00a15e..8aed5cc 100644 --- a/apps/server/src/server/api/contracts/todo.contract.ts +++ b/apps/server/src/server/api/contracts/todo.contract.ts @@ -1,21 +1,14 @@ import { oc } from '@orpc/contract' import { createInsertSchema, createSelectSchema, createUpdateSchema } from 'drizzle-orm/zod' import { z } from 'zod' +import { generatedFieldKeys } from '@/server/db/fields' import { todoTable } from '@/server/db/schema' const selectSchema = createSelectSchema(todoTable) -const insertSchema = createInsertSchema(todoTable).omit({ - id: true, - createdAt: true, - updatedAt: true, -}) +const insertSchema = createInsertSchema(todoTable).omit(generatedFieldKeys) -const updateSchema = createUpdateSchema(todoTable).omit({ - id: true, - createdAt: true, - updatedAt: true, -}) +const updateSchema = createUpdateSchema(todoTable).omit(generatedFieldKeys) export const list = oc.input(z.void()).output(z.array(selectSchema)) diff --git a/apps/server/src/server/api/routers/todo.router.ts b/apps/server/src/server/api/routers/todo.router.ts index ea639bc..40c988f 100644 --- a/apps/server/src/server/api/routers/todo.router.ts +++ b/apps/server/src/server/api/routers/todo.router.ts @@ -15,7 +15,7 @@ export const create = os.todo.create.use(db).handler(async ({ context, input }) const [newTodo] = await context.db.insert(todoTable).values(input).returning() if (!newTodo) { - throw new ORPCError('NOT_FOUND') + throw new ORPCError('INTERNAL_SERVER_ERROR', { message: 'Failed to create todo' }) } return newTodo @@ -32,5 +32,9 @@ export const update = os.todo.update.use(db).handler(async ({ context, input }) }) export const remove = os.todo.remove.use(db).handler(async ({ context, input }) => { - await context.db.delete(todoTable).where(eq(todoTable.id, input.id)) + const [deleted] = await context.db.delete(todoTable).where(eq(todoTable.id, input.id)).returning({ id: todoTable.id }) + + if (!deleted) { + throw new ORPCError('NOT_FOUND') + } }) diff --git a/apps/server/src/server/db/fields.ts b/apps/server/src/server/db/fields.ts index de8a9b9..afea8cc 100644 --- a/apps/server/src/server/db/fields.ts +++ b/apps/server/src/server/db/fields.ts @@ -4,7 +4,7 @@ import { v7 as uuidv7 } from 'uuid' // id -export const id = (name: string) => uuid(name) +const id = (name: string) => uuid(name) export const pk = (name: string, strategy?: 'native' | 'extension') => { switch (strategy) { // PG 18+ diff --git a/apps/server/turbo.json b/apps/server/turbo.json index 10677c7..60b4196 100644 --- a/apps/server/turbo.json +++ b/apps/server/turbo.json @@ -4,6 +4,7 @@ "tasks": { "build": { "env": ["NODE_ENV", "VITE_*"], + "inputs": ["src/**", "public/**", "package.json", "tsconfig.json", "vite.config.ts"], "outputs": [".output/**"] }, "compile": {