TypeScript · 5090 bytes Raw Blame History
1 import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'
2 import type {
3 AuthRequest,
4 AuthResponse,
5 DirectoryListing,
6 FileItem,
7 UploadResponse,
8 NetworkStatus,
9 NodeStatus
10 } from '@shared/types'
11
12 class ApiClient {
13 private client: AxiosInstance
14
15 constructor(baseURL: string = '/api') {
16 this.client = axios.create({
17 baseURL,
18 timeout: 30000,
19 headers: {
20 'Content-Type': 'application/json',
21 },
22 })
23
24 // Request interceptor to add auth token
25 this.client.interceptors.request.use(
26 (config) => {
27 const token = localStorage.getItem('auth_token')
28 if (token) {
29 config.headers.Authorization = `Bearer ${token}`
30 }
31 return config
32 },
33 (error) => Promise.reject(error)
34 )
35
36 // Response interceptor for error handling
37 this.client.interceptors.response.use(
38 (response) => response,
39 async (error) => {
40 if (error.response?.status === 401) {
41 // Try to refresh token
42 const refreshToken = localStorage.getItem('refresh_token')
43 if (refreshToken) {
44 try {
45 const response = await this.refreshAuth(refreshToken)
46 localStorage.setItem('auth_token', response.token)
47
48 // Retry original request
49 const originalRequest = error.config
50 originalRequest.headers.Authorization = `Bearer ${response.token}`
51 return this.client.request(originalRequest)
52 } catch (refreshError) {
53 // Refresh failed, redirect to login
54 localStorage.removeItem('auth_token')
55 localStorage.removeItem('refresh_token')
56 window.location.href = '/login'
57 }
58 } else {
59 // No refresh token, redirect to login
60 window.location.href = '/login'
61 }
62 }
63 return Promise.reject(error)
64 }
65 )
66 }
67
68 // Authentication
69 async login(credentials: AuthRequest): Promise<AuthResponse> {
70 const response = await this.client.post<AuthResponse>('/auth/login', credentials)
71 return response.data
72 }
73
74 async refreshAuth(refreshToken: string): Promise<AuthResponse> {
75 const response = await this.client.post<AuthResponse>('/auth/refresh', { refreshToken })
76 return response.data
77 }
78
79 async logout(): Promise<void> {
80 await this.client.post('/auth/logout')
81 }
82
83 async getCurrentUser(): Promise<{ id: string; username: string }> {
84 const response = await this.client.get('/auth/me')
85 return response.data
86 }
87
88 // Files
89 async listFiles(path: string = '/'): Promise<DirectoryListing> {
90 const response = await this.client.get<DirectoryListing>('/files', {
91 params: { path },
92 })
93 return response.data
94 }
95
96 async uploadFile(
97 file: File,
98 path: string = '/',
99 options: {
100 encrypted?: boolean;
101 onProgress?: (progress: number) => void;
102 } = {}
103 ): Promise<UploadResponse> {
104 const formData = new FormData()
105 formData.append('file', file)
106 formData.append('path', path)
107 if (options.encrypted !== undefined) {
108 formData.append('encrypted', options.encrypted.toString())
109 }
110
111 const response = await this.client.post<UploadResponse>('/files/upload', formData, {
112 headers: {
113 'Content-Type': 'multipart/form-data',
114 },
115 onUploadProgress: (progressEvent) => {
116 if (options.onProgress && progressEvent.total) {
117 const progress = (progressEvent.loaded / progressEvent.total) * 100
118 options.onProgress(progress)
119 }
120 },
121 })
122 return response.data
123 }
124
125 async downloadFile(fileId: string): Promise<Blob> {
126 const response = await this.client.get(`/files/${fileId}/download`, {
127 responseType: 'blob',
128 })
129 return response.data
130 }
131
132 async getFileInfo(fileId: string): Promise<FileItem> {
133 const response = await this.client.get<FileItem>(`/files/${fileId}/info`)
134 return response.data
135 }
136
137 async deleteFile(fileId: string): Promise<void> {
138 await this.client.delete(`/files/${fileId}`)
139 }
140
141 // Status
142 async getHealthStatus(): Promise<any> {
143 const response = await this.client.get('/health')
144 return response.data
145 }
146
147 async getNetworkStatus(): Promise<NetworkStatus> {
148 const response = await this.client.get<NetworkStatus>('/status/network')
149 return response.data
150 }
151
152 async getNodeStatus(): Promise<NodeStatus> {
153 const response = await this.client.get<NodeStatus>('/status/node')
154 return response.data
155 }
156
157 // WebSocket connection for real-time updates
158 createStatusWebSocket(): WebSocket {
159 const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
160 const wsUrl = `${protocol}//${window.location.host}/api/status/ws`
161 return new WebSocket(wsUrl)
162 }
163
164 // Generic request method
165 async request<T>(config: AxiosRequestConfig): Promise<T> {
166 const response = await this.client.request<T>(config)
167 return response.data
168 }
169 }
170
171 // Create singleton instance
172 export const apiClient = new ApiClient()
173 export default apiClient