fix the api token expire time! Reviewed-on: https://git.pelekan.org/Saeed0920/inogen/pulls/11 Co-authored-by: saeed0920 <sd.eed1381@gmail.com> Co-committed-by: saeed0920 <sd.eed1381@gmail.com>
334 lines
8.1 KiB
TypeScript
334 lines
8.1 KiB
TypeScript
import toast from "react-hot-toast";
|
|
|
|
interface ApiResponse<T = any> {
|
|
message: string;
|
|
data: T;
|
|
state: number;
|
|
time: number;
|
|
errorCode: number;
|
|
resultType: number;
|
|
}
|
|
|
|
class ApiService {
|
|
apiUrl = import.meta.env.VITE_API_URL;
|
|
private baseURL = this.apiUrl;
|
|
private token: string | null = null;
|
|
|
|
constructor() {
|
|
// Initialize token from localStorage
|
|
this.initializeToken();
|
|
}
|
|
|
|
private initializeToken() {
|
|
try {
|
|
const savedToken = localStorage.getItem("auth_token");
|
|
if (savedToken) {
|
|
const tokenData = JSON.parse(savedToken);
|
|
this.token = tokenData.accessToken;
|
|
}
|
|
} catch (error) {
|
|
console.error("Error initializing token:", error);
|
|
}
|
|
}
|
|
|
|
public setToken(token: string) {
|
|
this.token = token;
|
|
}
|
|
|
|
public clearToken() {
|
|
this.token = null;
|
|
}
|
|
|
|
private handleSessionExpired(message?: string) {
|
|
this.clearToken();
|
|
localStorage.removeItem("auth_token");
|
|
localStorage.removeItem("auth_user");
|
|
try {
|
|
sessionStorage.setItem("sessionExpired", "1");
|
|
} catch {}
|
|
if (message) {
|
|
toast.error(message);
|
|
}
|
|
window.location.href = "/login";
|
|
}
|
|
|
|
private async request<T = any>(
|
|
endpoint: string,
|
|
options: RequestInit = {},
|
|
): Promise<ApiResponse<T>> {
|
|
const url = `${this.baseURL}${endpoint}`;
|
|
|
|
const defaultHeaders: HeadersInit = {
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
// Add authorization header if token exists
|
|
if (this.token) {
|
|
defaultHeaders.Authorization = `Bearer ${this.token}`;
|
|
}
|
|
|
|
const config: RequestInit = {
|
|
...options,
|
|
headers: {
|
|
...defaultHeaders,
|
|
...options.headers,
|
|
},
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(url, config);
|
|
const data: ApiResponse<T> = await response.json();
|
|
|
|
if (data.errorCode === 301) {
|
|
this.handleSessionExpired("نشست شما منقضی شده است. لطفا دوباره وارد شوید.");
|
|
throw new Error(data.message || "Session expired");
|
|
}
|
|
|
|
// Handle different response states
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
data.message || `HTTP error! status: ${response.status}`,
|
|
);
|
|
}
|
|
|
|
if (data.state !== 0) {
|
|
throw new Error(data.message || "API error occurred");
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error("API request failed:", error);
|
|
|
|
// Handle network errors (propagate up; UI decides how to toast)
|
|
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
const err = Object.assign(new Error("شبکه در دسترس نیست"), {
|
|
code: "NETWORK_ERROR",
|
|
});
|
|
throw err;
|
|
}
|
|
|
|
// Handle authentication errors
|
|
if (error instanceof Error && error.message.includes("401")) {
|
|
this.handleSessionExpired();
|
|
throw error;
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// POST to external absolute endpoint while reusing token handling
|
|
public async postAbsolute<T = any>(
|
|
fullUrl: string,
|
|
data?: any,
|
|
): Promise<ApiResponse<T>> {
|
|
const defaultHeaders: HeadersInit = {
|
|
"Content-Type": "application/json;charset=UTF-8",
|
|
};
|
|
|
|
if (this.token) {
|
|
defaultHeaders.Authorization = `Bearer ${this.token}`;
|
|
}
|
|
|
|
const config: RequestInit = {
|
|
method: "POST",
|
|
headers: defaultHeaders,
|
|
body: data ? JSON.stringify(data) : undefined,
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(fullUrl, config);
|
|
const apiData: ApiResponse<T> = await response.json();
|
|
|
|
if (apiData.errorCode === 301) {
|
|
this.handleSessionExpired("نشست شما منقضی شده است. لطفا دوباره وارد شوید.");
|
|
throw new Error(apiData.message || "Session expired");
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(apiData?.message || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
if (typeof apiData?.state !== "undefined" && apiData.state !== 0) {
|
|
throw new Error(apiData.message || "API error occurred");
|
|
}
|
|
|
|
return apiData;
|
|
} catch (error) {
|
|
console.error("API absolute POST failed:", error);
|
|
throw error as Error;
|
|
}
|
|
}
|
|
|
|
// Innovation process function call wrapper
|
|
public async call<T = any>(payload: any) {
|
|
const url = "https://inogen-back.pelekan.org/api/call";
|
|
return this.postAbsolute<T>(url, payload);
|
|
}
|
|
|
|
// GET request
|
|
public async get<T = any>(endpoint: string): Promise<ApiResponse<T>> {
|
|
return this.request<T>(endpoint, {
|
|
method: "GET",
|
|
});
|
|
}
|
|
|
|
// POST request
|
|
public async post<T = any>(
|
|
endpoint: string,
|
|
data?: any,
|
|
): Promise<ApiResponse<T>> {
|
|
return this.request<T>(endpoint, {
|
|
method: "POST",
|
|
body: data ? JSON.stringify(data) : undefined,
|
|
});
|
|
}
|
|
|
|
// PUT request
|
|
public async put<T = any>(
|
|
endpoint: string,
|
|
data?: any,
|
|
): Promise<ApiResponse<T>> {
|
|
return this.request<T>(endpoint, {
|
|
method: "PUT",
|
|
body: data ? JSON.stringify(data) : undefined,
|
|
});
|
|
}
|
|
|
|
// DELETE request
|
|
public async delete<T = any>(endpoint: string): Promise<ApiResponse<T>> {
|
|
return this.request<T>(endpoint, {
|
|
method: "DELETE",
|
|
});
|
|
}
|
|
|
|
// PATCH request
|
|
public async patch<T = any>(
|
|
endpoint: string,
|
|
data?: any,
|
|
): Promise<ApiResponse<T>> {
|
|
return this.request<T>(endpoint, {
|
|
method: "PATCH",
|
|
body: data ? JSON.stringify(data) : undefined,
|
|
});
|
|
}
|
|
|
|
// Authentication methods
|
|
public async login(username: string, password: string) {
|
|
try {
|
|
const response = await this.post("/login", {
|
|
username,
|
|
password,
|
|
});
|
|
|
|
if (response.state === 0) {
|
|
const parsedData = JSON.parse(response.data);
|
|
this.setToken(parsedData.Token.AccessToken);
|
|
return {
|
|
success: true,
|
|
data: parsedData,
|
|
message: response.message,
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: false,
|
|
message: response.message || "ورود ناموفق",
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : "خطای غیرمنتظره",
|
|
};
|
|
}
|
|
}
|
|
|
|
public async logout() {
|
|
try {
|
|
// Call logout endpoint if it exists
|
|
await this.post("/logout");
|
|
} catch (error) {
|
|
console.error("Logout API call failed:", error);
|
|
} finally {
|
|
// Clear token regardless of API call success
|
|
this.clearToken();
|
|
localStorage.removeItem("auth_token");
|
|
localStorage.removeItem("auth_user");
|
|
}
|
|
}
|
|
|
|
// Profile methods
|
|
public async getProfile() {
|
|
return this.get("/profile");
|
|
}
|
|
|
|
public async updateProfile(data: any) {
|
|
return this.put("/profile", data);
|
|
}
|
|
|
|
// Select method for dynamic data queries
|
|
public async select(
|
|
data:
|
|
| {
|
|
ProcessName: string;
|
|
OutputFields: string[];
|
|
Pagination: { PageNumber: number; PageSize: number };
|
|
Sorts: [string, string][];
|
|
Conditions: any[];
|
|
}
|
|
| any,
|
|
) {
|
|
return this.post("/select", data);
|
|
}
|
|
|
|
// Projects methods
|
|
public async getProjects() {
|
|
return this.get("/projects");
|
|
}
|
|
|
|
// Dashboard methods
|
|
public async getDashboardStats() {
|
|
return this.get("/dashboard/stats");
|
|
}
|
|
|
|
public async getDashboardRecentActivity() {
|
|
return this.get("/dashboard/recent-activity");
|
|
}
|
|
|
|
// File upload method
|
|
public async uploadFile(file: File, endpoint: string = "/upload") {
|
|
const formData = new FormData();
|
|
formData.append("file", file);
|
|
|
|
const config: RequestInit = {
|
|
method: "POST",
|
|
headers: this.token ? { Authorization: `Bearer ${this.token}` } : {},
|
|
body: formData,
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(`${this.baseURL}${endpoint}`, config);
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
data.message || `HTTP error! status: ${response.status}`,
|
|
);
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error("File upload failed:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create and export a singleton instance
|
|
const apiService = new ApiService();
|
|
|
|
export default apiService;
|
|
|
|
// Export types for use in components
|
|
export type { ApiResponse };
|