From 309c27b4dd72e1bd66908d391c3a109d305dcd87 Mon Sep 17 00:00:00 2001 From: yushen Date: Fri, 13 Feb 2026 16:31:43 +0800 Subject: [PATCH] feat(desktop): add API client with auth headers Add fetch wrapper for desktop renderer to call Multica backend REST API. Attaches sid, device-id, and os-type headers automatically using useAuthStore and electronAPI.auth.getDeviceIdHeader(). Co-Authored-By: Claude Opus 4.6 --- .../src/renderer/src/service/request.ts | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 apps/desktop/src/renderer/src/service/request.ts diff --git a/apps/desktop/src/renderer/src/service/request.ts b/apps/desktop/src/renderer/src/service/request.ts new file mode 100644 index 00000000..ee4c27a3 --- /dev/null +++ b/apps/desktop/src/renderer/src/service/request.ts @@ -0,0 +1,72 @@ +import { useAuthStore } from '../stores/auth' + +// Backend API host — change this when switching environments +const API_HOST = 'https://api-dev.copilothub.ai' + +/** + * Fetch request wrapper for desktop app. + * Attaches sid, device-id, and os-type headers automatically. + */ +export async function request(url: string, options: RequestInit = {}): Promise { + const sid = useAuthStore.getState().sid + const deviceIdHeader = await window.electronAPI.auth.getDeviceIdHeader() + + const config: RequestInit = { + ...options, + headers: { + 'Content-Type': 'application/json', + 'os-type': '3', + ...(deviceIdHeader && { 'device-id': deviceIdHeader }), + ...(sid && { sid }), + ...options.headers, + }, + } + + const response = await fetch(`${API_HOST}${url}`, config) + + let data: T + const contentType = response.headers.get('content-type') + if (contentType?.includes('application/json')) { + data = await response.json() + } else { + const text = await response.text() + data = { message: text || response.statusText } as T + } + + if (!response.ok) { + console.error('API Error:', { + status: response.status, + url, + data, + }) + throw new Error( + (data as { errMsg?: string; message?: string })?.errMsg || + (data as { message?: string })?.message || + `Request failed with status ${response.status}`, + ) + } + + return data +} + +// GET request +export function get(url: string, params?: Record) { + const filteredParams = params + ? Object.fromEntries( + Object.entries(params).filter(([, v]) => v !== undefined && v !== null), + ) + : undefined + const queryString = + filteredParams && Object.keys(filteredParams).length > 0 + ? `?${new URLSearchParams(filteredParams as Record).toString()}` + : '' + return request(url + queryString, { method: 'GET' }) +} + +// POST request +export function post(url: string, data?: unknown) { + return request(url, { + method: 'POST', + body: JSON.stringify(data), + }) +}