mirror of
https://github.com/ruvnet/RuView
synced 2026-08-03 19:21:42 +00:00
feat: Implement RSSI service for iOS and Web platforms
- Added IosRssiService to handle synthetic RSSI data for iOS. - Created WebRssiService to simulate RSSI scanning on the web. - Defined shared types for WifiNetwork and RssiService in rssi.service.ts. - Introduced simulation service to generate synthetic sensing data. - Implemented WebSocket service for real-time data handling with reconnection logic. - Established Zustand stores for managing application state related to MAT and pose data. - Developed theme context and utility functions for consistent styling and formatting. - Added type definitions for various application entities including API responses and sensing data. - Created utility functions for color mapping and URL validation. - Configured TypeScript settings for the mobile application.
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import axios, { type AxiosError, type AxiosInstance, type AxiosRequestConfig } from 'axios';
|
||||
import { API_POSE_FRAMES_PATH, API_POSE_STATUS_PATH, API_POSE_ZONES_PATH } from '@/constants/api';
|
||||
import type { ApiError, HistoricalFrames, PoseStatus, ZoneConfig } from '@/types/api';
|
||||
|
||||
class ApiService {
|
||||
private baseUrl = '';
|
||||
private client: AxiosInstance;
|
||||
|
||||
constructor() {
|
||||
this.client = axios.create({
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setBaseUrl(url: string): void {
|
||||
this.baseUrl = url ?? '';
|
||||
}
|
||||
|
||||
private buildUrl(path: string): string {
|
||||
if (!this.baseUrl) {
|
||||
return path;
|
||||
}
|
||||
if (path.startsWith('http://') || path.startsWith('https://')) {
|
||||
return path;
|
||||
}
|
||||
const normalized = this.baseUrl.replace(/\/$/, '');
|
||||
return `${normalized}${path.startsWith('/') ? path : `/${path}`}`;
|
||||
}
|
||||
|
||||
private normalizeError(error: unknown): ApiError {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const axiosError = error as AxiosError<{ message?: string }>;
|
||||
const message =
|
||||
axiosError.response?.data && typeof axiosError.response.data === 'object' && 'message' in axiosError.response.data
|
||||
? String((axiosError.response.data as { message?: string }).message)
|
||||
: axiosError.message || 'Request failed';
|
||||
return {
|
||||
message,
|
||||
status: axiosError.response?.status,
|
||||
code: axiosError.code,
|
||||
details: axiosError.response?.data,
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return { message: error.message };
|
||||
}
|
||||
|
||||
return { message: 'Unknown error' };
|
||||
}
|
||||
|
||||
private async requestWithRetry<T>(config: AxiosRequestConfig, retriesLeft: number): Promise<T> {
|
||||
try {
|
||||
const response = await this.client.request<T>({
|
||||
...config,
|
||||
url: this.buildUrl(config.url || ''),
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (retriesLeft > 0) {
|
||||
return this.requestWithRetry<T>(config, retriesLeft - 1);
|
||||
}
|
||||
throw this.normalizeError(error);
|
||||
}
|
||||
}
|
||||
|
||||
get<T>(path: string): Promise<T> {
|
||||
return this.requestWithRetry<T>({ method: 'GET', url: path }, 2);
|
||||
}
|
||||
|
||||
post<T>(path: string, body: unknown): Promise<T> {
|
||||
return this.requestWithRetry<T>({ method: 'POST', url: path, data: body }, 2);
|
||||
}
|
||||
|
||||
getStatus(): Promise<PoseStatus> {
|
||||
return this.get<PoseStatus>(API_POSE_STATUS_PATH);
|
||||
}
|
||||
|
||||
getZones(): Promise<ZoneConfig[]> {
|
||||
return this.get<ZoneConfig[]>(API_POSE_ZONES_PATH);
|
||||
}
|
||||
|
||||
getFrames(limit: number): Promise<HistoricalFrames> {
|
||||
return this.get<HistoricalFrames>(`${API_POSE_FRAMES_PATH}?limit=${encodeURIComponent(String(limit))}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const apiService = new ApiService();
|
||||
Reference in New Issue
Block a user