summaryrefslogtreecommitdiff
path: root/api/client.ts
diff options
context:
space:
mode:
Diffstat (limited to 'api/client.ts')
-rw-r--r--api/client.ts147
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();