diff options
| author | JustZvan <justzvan@justzvan.xyz> | 2026-02-06 13:22:33 +0100 |
|---|---|---|
| committer | JustZvan <justzvan@justzvan.xyz> | 2026-02-06 13:22:33 +0100 |
| commit | 7eb8ccae48b0cc18a9dcaa9c3626a02df8e6d919 (patch) | |
| tree | 57b7dd06ac9aa7053c671d916f7183e3b4fa9410 /api/client.ts | |
feat: initial commit!
Diffstat (limited to 'api/client.ts')
| -rw-r--r-- | api/client.ts | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/api/client.ts b/api/client.ts new file mode 100644 index 0000000..703b1f0 --- /dev/null +++ b/api/client.ts @@ -0,0 +1,147 @@ +import Constants from "expo-constants"; +import * as SecureStore from "expo-secure-store"; + +function getApiBaseUrl() { + const envUrl = (process as any)?.env?.API_BASE_URL; + const extraUrl = + (Constants.manifest as any)?.extra?.API_BASE_URL || + (Constants.expoConfig as any)?.extra?.API_BASE_URL; + if (envUrl) return envUrl; + if (extraUrl) return extraUrl; + + throw new Error("API_BASE_URL is not defined in environment or Expo config"); +} + +const API_BASE_URL = getApiBaseUrl(); + +const AUTH_TOKEN_KEY = "buddy_auth_token"; +const SELECTED_DEVICE_KEY = "buddy_selected_device"; + +class ApiClient { + private token: string | null = null; + private selectedDeviceId: string | null = null; + + async initialize() { + try { + this.token = await SecureStore.getItemAsync(AUTH_TOKEN_KEY); + this.selectedDeviceId = + await SecureStore.getItemAsync(SELECTED_DEVICE_KEY); + } catch (e) { + console.error("Failed to load auth token", e); + } + } + + async setToken(token: string) { + this.token = token; + await SecureStore.setItemAsync(AUTH_TOKEN_KEY, token); + } + + async clearToken() { + this.token = null; + await SecureStore.deleteItemAsync(AUTH_TOKEN_KEY); + } + + async setSelectedDevice(deviceId: string) { + this.selectedDeviceId = deviceId; + await SecureStore.setItemAsync(SELECTED_DEVICE_KEY, deviceId); + } + + getSelectedDeviceId(): string | null { + return this.selectedDeviceId; + } + + isAuthenticated(): boolean { + return this.token !== null; + } + + private async request<T>( + method: "GET" | "POST" | "PUT" | "DELETE", + endpoint: string, + body?: any, + ): Promise<T> { + const headers: HeadersInit = { + "Content-Type": "application/json", + }; + + if (this.token) { + headers["Authorization"] = `Bearer ${this.token}`; + } + + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + method, + headers, + body: body ? JSON.stringify(body) : undefined, + }); + + if (!response.ok) { + const error = await response + .json() + .catch(() => ({ reason: "Unknown error" })); + throw new Error(error.reason || `HTTP ${response.status}`); + } + + return response.json(); + } + + async get<T>(endpoint: string): Promise<T> { + return this.request<T>("GET", endpoint); + } + + async post<T>(endpoint: string, body?: any): Promise<T> { + return this.request<T>("POST", endpoint, body); + } + + async delete<T>(endpoint: string, body?: any): Promise<T> { + return this.request<T>("DELETE", endpoint, body); + } + + // Auth endpoints + async signIn( + email: string, + password: string, + ): Promise<{ success: boolean; token?: string; reason?: string }> { + const result = await this.post<{ + success: boolean; + token: string; + reason: string; + }>("/signin", { + email, + password, + }); + + if (result.success && result.token) { + await this.setToken(result.token); + } + + return result; + } + + async signUp( + email: string, + password: string, + ): Promise<{ success: boolean; token?: string; reason?: string }> { + const result = await this.post<{ + success: boolean; + token: string; + reason: string; + }>("/signup", { + email, + password, + }); + + if (result.success && result.token) { + await this.setToken(result.token); + } + + return result; + } + + async signOut() { + await this.clearToken(); + } +} + +export const apiClient = new ApiClient(); + +// Initialize on import +apiClient.initialize(); |