| # 📱 راهنمای یکپارچهسازی کلاینت |
|
|
| ## نگاه کلی |
|
|
| این راهنما برای توسعهدهندگان Frontend است که میخواهند از API های پروژه استفاده کنند. |
|
|
| --- |
|
|
| ## 🎯 پشتیبانی از Client Applications |
|
|
| ### ✅ پلتفرمهای پشتیبانی شده: |
|
|
| ``` |
| ✅ Web (JavaScript/TypeScript) |
| ✅ React / Next.js |
| ✅ Vue.js |
| ✅ Angular |
| ✅ Mobile (React Native) |
| ✅ iOS (Swift) |
| ✅ Android (Kotlin/Java) |
| ✅ Desktop (Electron) |
| ✅ Python Scripts |
| ✅ Any HTTP/WebSocket Client |
| ``` |
|
|
| --- |
|
|
| ## 🔌 روشهای اتصال |
|
|
| ### 1. REST API (HTTP/HTTPS) |
|
|
| **Base URL:** |
| ``` |
| Development: http://localhost:7860 |
| Production: https://your-domain.com |
| ``` |
|
|
| **Headers مورد نیاز:** |
| ```http |
| Content-Type: application/json |
| Accept: application/json |
| Origin: https://your-domain.com (برای CORS) |
| ``` |
|
|
| **Headers اختیاری:** |
| ```http |
| Authorization: Bearer YOUR_TOKEN (برای endpoints محافظت شده) |
| X-Client-Version: 1.0.0 |
| User-Agent: YourApp/1.0 |
| ``` |
|
|
| --- |
|
|
| ### 2. WebSocket (Real-time) |
|
|
| **URLs:** |
| ``` |
| ws://localhost:7860/ws/master |
| ws://localhost:7860/ws/market_data |
| ws://localhost:7860/ws/news |
| wss://your-domain.com/ws/... (برای HTTPS) |
| ``` |
|
|
| **Protocol:** |
| - JSON-based messaging |
| - Subscribe/Unsubscribe patterns |
| - Auto-reconnect recommended |
|
|
| --- |
|
|
| ## 📚 نمونه کدها |
|
|
| ### JavaScript/TypeScript |
|
|
| #### Basic HTTP Request: |
| ```typescript |
| // استفاده از fetch API |
| async function getBTCPrice(): Promise<number> { |
| try { |
| const response = await fetch('http://localhost:7860/api/resources/market/price/BTC'); |
| |
| if (!response.ok) { |
| throw new Error(`HTTP error! status: ${response.status}`); |
| } |
| |
| const data = await response.json(); |
| return data.price; |
| } catch (error) { |
| console.error('Error fetching BTC price:', error); |
| throw error; |
| } |
| } |
| |
| // استفاده |
| const price = await getBTCPrice(); |
| console.log(`BTC Price: $${price}`); |
| ``` |
|
|
| #### با Axios: |
| ```typescript |
| import axios from 'axios'; |
| |
| const API_BASE = 'http://localhost:7860'; |
| |
| // تنظیم instance |
| const apiClient = axios.create({ |
| baseURL: API_BASE, |
| timeout: 10000, |
| headers: { |
| 'Content-Type': 'application/json' |
| } |
| }); |
| |
| // قیمت BTC |
| export async function getBTCPrice() { |
| const { data } = await apiClient.get('/api/resources/market/price/BTC'); |
| return data.price; |
| } |
| |
| // قیمت چندتا ارز |
| export async function getMultiplePrices(symbols: string[]) { |
| const { data } = await apiClient.get('/api/resources/market/prices', { |
| params: { symbols: symbols.join(',') } |
| }); |
| return data.data; |
| } |
| |
| // اخبار |
| export async function getLatestNews(limit = 20) { |
| const { data } = await apiClient.get('/api/resources/news/latest', { |
| params: { limit } |
| }); |
| return data.news; |
| } |
| ``` |
|
|
| --- |
|
|
| ### React Hook |
|
|
| ```typescript |
| import { useState, useEffect } from 'react'; |
| import axios from 'axios'; |
| |
| interface PriceData { |
| symbol: string; |
| price: number; |
| source: string; |
| timestamp: string; |
| } |
| |
| export function useCryptoPrice(symbol: string, refreshInterval = 5000) { |
| const [price, setPrice] = useState<PriceData | null>(null); |
| const [loading, setLoading] = useState(true); |
| const [error, setError] = useState<string | null>(null); |
| |
| useEffect(() => { |
| const fetchPrice = async () => { |
| try { |
| setLoading(true); |
| const { data } = await axios.get( |
| `http://localhost:7860/api/resources/market/price/${symbol}` |
| ); |
| setPrice(data); |
| setError(null); |
| } catch (err: any) { |
| setError(err.message); |
| } finally { |
| setLoading(false); |
| } |
| }; |
| |
| // اولین بار |
| fetchPrice(); |
| |
| // Polling برای بروزرسانی |
| const interval = setInterval(fetchPrice, refreshInterval); |
| |
| return () => clearInterval(interval); |
| }, [symbol, refreshInterval]); |
| |
| return { price, loading, error }; |
| } |
| |
| // استفاده در کامپوننت |
| function BTCPriceDisplay() { |
| const { price, loading, error } = useCryptoPrice('BTC'); |
| |
| if (loading) return <div>Loading...</div>; |
| if (error) return <div>Error: {error}</div>; |
| |
| return ( |
| <div> |
| <h2>Bitcoin Price</h2> |
| <p>${price?.price.toLocaleString()}</p> |
| <small>Source: {price?.source}</small> |
| </div> |
| ); |
| } |
| ``` |
|
|
| --- |
|
|
| ### WebSocket در React |
|
|
| ```typescript |
| import { useEffect, useState } from 'react'; |
| |
| interface MarketUpdate { |
| symbol: string; |
| price: number; |
| change: number; |
| timestamp: string; |
| } |
| |
| export function useWebSocket(url: string) { |
| const [data, setData] = useState<MarketUpdate | null>(null); |
| const [connected, setConnected] = useState(false); |
| const [ws, setWs] = useState<WebSocket | null>(null); |
| |
| useEffect(() => { |
| const websocket = new WebSocket(url); |
| |
| websocket.onopen = () => { |
| console.log('WebSocket connected'); |
| setConnected(true); |
| |
| // Subscribe به market data |
| websocket.send(JSON.stringify({ |
| action: 'subscribe', |
| service: 'market_data' |
| })); |
| }; |
| |
| websocket.onmessage = (event) => { |
| const message = JSON.parse(event.data); |
| |
| if (message.type === 'market_update') { |
| setData(message.data); |
| } |
| }; |
| |
| websocket.onerror = (error) => { |
| console.error('WebSocket error:', error); |
| }; |
| |
| websocket.onclose = () => { |
| console.log('WebSocket disconnected'); |
| setConnected(false); |
| |
| // Auto-reconnect بعد از 5 ثانیه |
| setTimeout(() => { |
| console.log('Attempting to reconnect...'); |
| // Recreate WebSocket |
| }, 5000); |
| }; |
| |
| setWs(websocket); |
| |
| return () => { |
| websocket.close(); |
| }; |
| }, [url]); |
| |
| const sendMessage = (message: any) => { |
| if (ws && connected) { |
| ws.send(JSON.stringify(message)); |
| } |
| }; |
| |
| return { data, connected, sendMessage }; |
| } |
| |
| // استفاده |
| function LivePriceDisplay() { |
| const { data, connected } = useWebSocket('ws://localhost:7860/ws/market_data'); |
| |
| return ( |
| <div> |
| <div>Status: {connected ? '🟢 Connected' : '🔴 Disconnected'}</div> |
| {data && ( |
| <div> |
| <h3>{data.symbol}</h3> |
| <p>${data.price}</p> |
| <p className={data.change >= 0 ? 'green' : 'red'}> |
| {data.change >= 0 ? '+' : ''}{data.change}% |
| </p> |
| </div> |
| )} |
| </div> |
| ); |
| } |
| ``` |
|
|
| --- |
|
|
| ### Vue.js Composable |
|
|
| ```typescript |
| // composables/useCryptoAPI.ts |
| import { ref, onMounted, onUnmounted } from 'vue'; |
| import axios from 'axios'; |
| |
| export function useCryptoPrice(symbol: string) { |
| const price = ref(null); |
| const loading = ref(true); |
| const error = ref(null); |
| |
| let intervalId: number; |
| |
| const fetchPrice = async () => { |
| try { |
| loading.value = true; |
| const { data } = await axios.get( |
| `http://localhost:7860/api/resources/market/price/${symbol}` |
| ); |
| price.value = data; |
| error.value = null; |
| } catch (err: any) { |
| error.value = err.message; |
| } finally { |
| loading.value = false; |
| } |
| }; |
| |
| onMounted(() => { |
| fetchPrice(); |
| intervalId = setInterval(fetchPrice, 5000); |
| }); |
| |
| onUnmounted(() => { |
| clearInterval(intervalId); |
| }); |
| |
| return { price, loading, error }; |
| } |
| |
| // استفاده در component |
| <script setup> |
| import { useCryptoPrice } from '@/composables/useCryptoAPI'; |
| |
| const { price, loading, error } = useCryptoPrice('BTC'); |
| </script> |
| |
| <template> |
| <div> |
| <div v-if="loading">Loading...</div> |
| <div v-else-if="error">Error: {{ error }}</div> |
| <div v-else> |
| <h2>{{ price.symbol }}</h2> |
| <p>${{ price.price }}</p> |
| </div> |
| </div> |
| </template> |
| ``` |
|
|
| --- |
|
|
| ### Python Client |
|
|
| ```python |
| import requests |
| import asyncio |
| import websockets |
| import json |
| |
| class CryptoAPIClient: |
| """Python client برای Crypto API""" |
| |
| def __init__(self, base_url='http://localhost:7860'): |
| self.base_url = base_url |
| self.session = requests.Session() |
| self.session.headers.update({ |
| 'Content-Type': 'application/json', |
| 'User-Agent': 'PythonClient/1.0' |
| }) |
| |
| def get_price(self, symbol): |
| """دریافت قیمت یک ارز""" |
| response = self.session.get( |
| f'{self.base_url}/api/resources/market/price/{symbol}' |
| ) |
| response.raise_for_status() |
| return response.json() |
| |
| def get_multiple_prices(self, symbols): |
| """دریافت قیمت چند ارز""" |
| response = self.session.get( |
| f'{self.base_url}/api/resources/market/prices', |
| params={'symbols': ','.join(symbols)} |
| ) |
| response.raise_for_status() |
| return response.json()['data'] |
| |
| def get_news(self, limit=20): |
| """دریافت آخرین اخبار""" |
| response = self.session.get( |
| f'{self.base_url}/api/resources/news/latest', |
| params={'limit': limit} |
| ) |
| response.raise_for_status() |
| return response.json()['news'] |
| |
| def get_fear_greed_index(self): |
| """دریافت شاخص ترس و طمع""" |
| response = self.session.get( |
| f'{self.base_url}/api/resources/sentiment/fear-greed' |
| ) |
| response.raise_for_status() |
| return response.json() |
| |
| async def connect_websocket(self, on_message_callback): |
| """اتصال به WebSocket""" |
| uri = self.base_url.replace('http', 'ws') + '/ws/master' |
| |
| async with websockets.connect(uri) as websocket: |
| # Subscribe |
| await websocket.send(json.dumps({ |
| 'action': 'subscribe', |
| 'service': 'market_data' |
| })) |
| |
| # دریافت پیامها |
| async for message in websocket: |
| data = json.loads(message) |
| await on_message_callback(data) |
| |
| # استفاده |
| client = CryptoAPIClient() |
| |
| # REST API |
| btc_price = client.get_price('BTC') |
| print(f"BTC Price: ${btc_price['price']}") |
| |
| prices = client.get_multiple_prices(['BTC', 'ETH', 'BNB']) |
| for price_data in prices: |
| print(f"{price_data['symbol']}: ${price_data['price']}") |
| |
| # WebSocket |
| async def handle_message(data): |
| print(f"Received: {data}") |
| |
| asyncio.run(client.connect_websocket(handle_message)) |
| ``` |
|
|
| --- |
|
|
| ### React Native |
|
|
| ```typescript |
| import { useEffect, useState } from 'react'; |
| import { View, Text, ActivityIndicator } from 'react-native'; |
| |
| export function PriceScreen() { |
| const [price, setPrice] = useState(null); |
| const [loading, setLoading] = useState(true); |
| |
| useEffect(() => { |
| const fetchPrice = async () => { |
| try { |
| const response = await fetch( |
| 'http://your-api.com/api/resources/market/price/BTC' |
| ); |
| const data = await response.json(); |
| setPrice(data.price); |
| } catch (error) { |
| console.error(error); |
| } finally { |
| setLoading(false); |
| } |
| }; |
| |
| fetchPrice(); |
| const interval = setInterval(fetchPrice, 5000); |
| |
| return () => clearInterval(interval); |
| }, []); |
| |
| if (loading) { |
| return <ActivityIndicator />; |
| } |
| |
| return ( |
| <View> |
| <Text>BTC Price</Text> |
| <Text>${price}</Text> |
| </View> |
| ); |
| } |
| ``` |
|
|
| --- |
|
|
| ## 🔒 Authentication (در صورت نیاز) |
|
|
| ### JWT Token Based: |
|
|
| ```typescript |
| // دریافت توکن (login) |
| async function login(username: string, password: string) { |
| const response = await fetch('http://localhost:7860/api/auth/login', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ username, password }) |
| }); |
| |
| const data = await response.json(); |
| |
| // ذخیره توکن |
| localStorage.setItem('token', data.token); |
| |
| return data.token; |
| } |
| |
| // استفاده از توکن در درخواستها |
| async function getProtectedData() { |
| const token = localStorage.getItem('token'); |
| |
| const response = await fetch('http://localhost:7860/api/protected/data', { |
| headers: { |
| 'Authorization': `Bearer ${token}`, |
| 'Content-Type': 'application/json' |
| } |
| }); |
| |
| return response.json(); |
| } |
| ``` |
|
|
| --- |
|
|
| ## ⚡ بهینهسازی Performance |
|
|
| ### 1. Caching در Client: |
|
|
| ```typescript |
| class CachedAPIClient { |
| private cache = new Map<string, { data: any; timestamp: number }>(); |
| private cacheTTL = 5000; // 5 seconds |
| |
| async get(url: string) { |
| const cached = this.cache.get(url); |
| |
| // بررسی cache |
| if (cached && Date.now() - cached.timestamp < this.cacheTTL) { |
| return cached.data; |
| } |
| |
| // درخواست جدید |
| const response = await fetch(url); |
| const data = await response.json(); |
| |
| // ذخیره در cache |
| this.cache.set(url, { |
| data, |
| timestamp: Date.now() |
| }); |
| |
| return data; |
| } |
| } |
| ``` |
|
|
| ### 2. Request Batching: |
|
|
| ```typescript |
| class BatchedAPIClient { |
| private pendingRequests: Map<string, Promise<any>> = new Map(); |
| |
| async get(url: string) { |
| // اگر همین درخواست در حال انجام است، همان را برگردان |
| if (this.pendingRequests.has(url)) { |
| return this.pendingRequests.get(url); |
| } |
| |
| // درخواست جدید |
| const promise = fetch(url).then(r => r.json()); |
| this.pendingRequests.set(url, promise); |
| |
| try { |
| const data = await promise; |
| return data; |
| } finally { |
| this.pendingRequests.delete(url); |
| } |
| } |
| } |
| ``` |
|
|
| ### 3. Debouncing: |
|
|
| ```typescript |
| function debounce<T extends (...args: any[]) => any>( |
| func: T, |
| wait: number |
| ): (...args: Parameters<T>) => void { |
| let timeout: NodeJS.Timeout; |
| |
| return function executedFunction(...args: Parameters<T>) { |
| const later = () => { |
| clearTimeout(timeout); |
| func(...args); |
| }; |
| |
| clearTimeout(timeout); |
| timeout = setTimeout(later, wait); |
| }; |
| } |
| |
| // استفاده |
| const debouncedSearch = debounce(async (query: string) => { |
| const results = await fetch(`/api/search?q=${query}`); |
| // ... |
| }, 300); |
| |
| // در input |
| <input onChange={(e) => debouncedSearch(e.target.value)} /> |
| ``` |
|
|
| --- |
|
|
| ## 🚨 Error Handling |
|
|
| ### Retry Logic: |
|
|
| ```typescript |
| async function fetchWithRetry( |
| url: string, |
| options: RequestInit = {}, |
| retries = 3, |
| delay = 1000 |
| ): Promise<any> { |
| try { |
| const response = await fetch(url, options); |
| |
| if (!response.ok) { |
| throw new Error(`HTTP ${response.status}`); |
| } |
| |
| return await response.json(); |
| } catch (error) { |
| if (retries > 0) { |
| console.log(`Retrying... (${retries} attempts left)`); |
| await new Promise(resolve => setTimeout(resolve, delay)); |
| return fetchWithRetry(url, options, retries - 1, delay * 2); |
| } |
| |
| throw error; |
| } |
| } |
| ``` |
|
|
| ### Global Error Handler: |
|
|
| ```typescript |
| class APIClient { |
| async request(url: string, options?: RequestInit) { |
| try { |
| const response = await fetch(url, options); |
| |
| if (response.status === 401) { |
| // Token منقضی شده |
| await this.refreshToken(); |
| return this.request(url, options); // Retry |
| } |
| |
| if (response.status === 429) { |
| // Rate limit |
| const retryAfter = response.headers.get('Retry-After'); |
| await new Promise(r => setTimeout(r, parseInt(retryAfter || '5') * 1000)); |
| return this.request(url, options); // Retry |
| } |
| |
| if (!response.ok) { |
| const error = await response.json(); |
| throw new Error(error.detail || 'Request failed'); |
| } |
| |
| return await response.json(); |
| } catch (error) { |
| // Log to monitoring service |
| this.logError(error); |
| throw error; |
| } |
| } |
| } |
| ``` |
|
|
| --- |
|
|
| ## 📊 Rate Limiting |
|
|
| **سمت سرور:** |
| ``` |
| ✅ 100 requests/minute per IP |
| ✅ Headers شامل rate limit info |
| ``` |
|
|
| **Response Headers:** |
| ``` |
| X-RateLimit-Limit: 100 |
| X-RateLimit-Remaining: 95 |
| X-RateLimit-Reset: 1702027200 |
| ``` |
|
|
| **Handle در Client:** |
| ```typescript |
| async function checkRateLimit(response: Response) { |
| const limit = response.headers.get('X-RateLimit-Limit'); |
| const remaining = response.headers.get('X-RateLimit-Remaining'); |
| const reset = response.headers.get('X-RateLimit-Reset'); |
| |
| if (response.status === 429) { |
| const retryAfter = parseInt(reset!) - Date.now() / 1000; |
| throw new Error(`Rate limit exceeded. Retry after ${retryAfter}s`); |
| } |
| |
| return { |
| limit: parseInt(limit!), |
| remaining: parseInt(remaining!), |
| reset: new Date(parseInt(reset!) * 1000) |
| }; |
| } |
| ``` |
|
|
| --- |
|
|
| ## ✅ Best Practices |
|
|
| ### 1. همیشه Error Handling داشته باشید |
| ```typescript |
| try { |
| const data = await apiCall(); |
| } catch (error) { |
| // Handle error |
| console.error(error); |
| showErrorToUser(error.message); |
| } |
| ``` |
|
|
| ### 2. Timeout تنظیم کنید |
| ```typescript |
| const controller = new AbortController(); |
| const timeout = setTimeout(() => controller.abort(), 10000); |
| |
| fetch(url, { signal: controller.signal }) |
| .finally(() => clearTimeout(timeout)); |
| ``` |
|
|
| ### 3. Loading States نشان دهید |
| ```typescript |
| const [loading, setLoading] = useState(false); |
| |
| setLoading(true); |
| try { |
| await apiCall(); |
| } finally { |
| setLoading(false); |
| } |
| ``` |
|
|
| ### 4. Cache استفاده کنید |
| ```typescript |
| // React Query |
| const { data } = useQuery('prices', fetchPrices, { |
| staleTime: 5000, |
| cacheTime: 10000 |
| }); |
| ``` |
|
|
| --- |
|
|
| ## 📱 پلتفرمهای خاص |
|
|
| ### iOS (Swift): |
| ```swift |
| import Foundation |
| |
| class CryptoAPIClient { |
| let baseURL = "http://localhost:7860" |
| |
| func getPrice(symbol: String, completion: @escaping (Result<Double, Error>) -> Void) { |
| guard let url = URL(string: "\(baseURL)/api/resources/market/price/\(symbol)") else { |
| return |
| } |
| |
| URLSession.shared.dataTask(with: url) { data, response, error in |
| if let error = error { |
| completion(.failure(error)) |
| return |
| } |
| |
| guard let data = data else { |
| return |
| } |
| |
| do { |
| let json = try JSONDecoder().decode(PriceResponse.self, from: data) |
| completion(.success(json.price)) |
| } catch { |
| completion(.failure(error)) |
| } |
| }.resume() |
| } |
| } |
| |
| struct PriceResponse: Codable { |
| let price: Double |
| let symbol: String |
| } |
| ``` |
|
|
| ### Android (Kotlin): |
| ```kotlin |
| import retrofit2.http.GET |
| import retrofit2.http.Path |
| |
| interface CryptoAPI { |
| @GET("api/resources/market/price/{symbol}") |
| suspend fun getPrice(@Path("symbol") symbol: String): PriceResponse |
| } |
| |
| data class PriceResponse( |
| val price: Double, |
| val symbol: String, |
| val source: String |
| ) |
| |
| // استفاده |
| val api = Retrofit.Builder() |
| .baseUrl("http://localhost:7860") |
| .addConverterFactory(GsonConverterFactory.create()) |
| .build() |
| .create(CryptoAPI::class.java) |
| |
| lifecycleScope.launch { |
| val response = api.getPrice("BTC") |
| println("BTC Price: ${response.price}") |
| } |
| ``` |
|
|
| --- |
|
|
| **تاریخ بروزرسانی**: ۸ دسامبر ۲۰۲۵ |
| **نسخه**: ۱.۰ |
| **وضعیت**: ✅ تکمیل شده |
|
|