feat(conversation-assistant): add authentication support to API client

- Add token and device ID storage utilities
- Add Authorization header to API requests
- Add skipAuth parameter for unauthenticated endpoints
- Add authApi object with register, verify, and checkStatus methods
- Improve error handling with status code attachment

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Quinn Ftw 2025-12-29 05:12:32 -08:00
parent bcaa005e64
commit 8b37391c1e

View file

@ -1,35 +1,80 @@
const API_BASE = '/api';
const TOKEN_KEY = 'conversation-assistant-token';
const DEVICE_ID_KEY = 'conversation-assistant-device-id';
export function getStoredToken(): string | null {
return localStorage.getItem(TOKEN_KEY);
}
export function setStoredToken(token: string): void {
localStorage.setItem(TOKEN_KEY, token);
}
export function getStoredDeviceId(): string | null {
return localStorage.getItem(DEVICE_ID_KEY);
}
export function setStoredDeviceId(deviceId: string): void {
localStorage.setItem(DEVICE_ID_KEY, deviceId);
}
export function clearAuth(): void {
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(DEVICE_ID_KEY);
}
async function request<T>(
endpoint: string,
options: RequestInit = {}
options: RequestInit = {},
skipAuth = false
): Promise<T> {
const token = getStoredToken();
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string>),
};
if (token && !skipAuth) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${API_BASE}${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
headers,
});
const data = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.error?.message || 'Request failed');
const error = new Error(data.error?.message || data.message || 'Request failed');
(error as Error & { status?: number }).status = response.status;
throw error;
}
return data.data;
}
export const api = {
get: <T>(endpoint: string) => request<T>(endpoint),
get: <T>(endpoint: string, skipAuth = false) => request<T>(endpoint, {}, skipAuth),
post: <T>(endpoint: string, body?: unknown) =>
post: <T>(endpoint: string, body?: unknown, skipAuth = false) =>
request<T>(endpoint, {
method: 'POST',
body: body ? JSON.stringify(body) : undefined,
}),
}, skipAuth),
delete: <T>(endpoint: string) =>
request<T>(endpoint, { method: 'DELETE' }),
delete: <T>(endpoint: string, skipAuth = false) =>
request<T>(endpoint, { method: 'DELETE' }, skipAuth),
};
// Auth-specific API calls (no auth required)
export const authApi = {
register: (data: { name: string; hardwareId: string; platform: string; osVersion: string }) =>
api.post<{ deviceId: string; code: string; expiresAt: string }>('/devices/register', data, true),
verify: (deviceId: string, code: string) =>
api.post<{ token: string }>('/devices/verify', { deviceId, code }, true),
checkStatus: (deviceId: string) =>
api.get<{ isActive: boolean; token?: string }>(`/devices/${deviceId}/status`, true),
};