add a typed API layer for the Tadrisino backend

اپ تا الان هیچ درخواستی به سرور نمی‌زد و همه‌ی داده‌ها داخلِ خودِ کامپوننت‌ها
هاردکد بود. این کامیت لایه‌ی ارتباط را اضافه می‌کند، بدونِ اینکه هیچ صفحه‌ای
را تغییر دهد.

عمداً فقط فایلِ جدید اضافه شده و App.tsx دست نخورده است، چون کارِ در جریانِ
برنچِ updateApp روی همان فایل است و نباید با هم تصادف کنند. وصل کردنِ
صفحه‌ها به این لایه کارِ بعدی‌ست.

هیچ پکیجِ جدیدی اضافه نشد — فقط fetch استاندارد.

چیزهایی که در client.ts هست برای حالتِ خراب‌اند، نه حالتِ خوب:
  • timeout روی هر درخواست — وگرنه بک‌اندِ کند سوکت‌ها را باز نگه می‌دارد تا
    مرورگر به سقفِ ۶ درخواستِ همزمان بخورد و اپ قفل شود.
  • backoff با jitter — بدونِ jitter همه‌ی کلاینت‌ها بعد از یک خطا همزمان
    دوباره می‌زنند و موجِ دوم از موجِ اول بدتر است.
  • تکرارِ خودکار فقط روی GET؛ POST فقط وقتی Idempotency-Key دارد (ثبتِ پاسخ،
    سفارش) تا دابل‌کلیکِ کاربر دو بار XP یا دو سفارش نسازد.
  • refresh تک‌نسخه‌ای — ۲۰ درخواستِ همزمانِ ۴۰۱ نباید ۲۰ بار refresh بزنند.
  • ادغامِ GETهای یکسانِ در جریان.
  • احترام به هدرِ Retry-After که بک‌اند موقعِ throttle می‌فرستد.

credentials: 'omit' عمدی‌ست: احرازِ هویت توکنِ Bearer در هدر است و سمتِ سرور
CORS_ALLOW_CREDENTIALS=False تنظیم شده.

.env هم به .gitignore اضافه شد؛ قبلاً فقط node_modules داخلش بود و آدرس/
تنظیماتِ محلی می‌توانست ناخواسته کامیت شود.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-13 19:46:05 +03:30
parent 3cca227337
commit be9f99d52a
7 changed files with 722 additions and 0 deletions

91
src/api/README.md Normal file
View File

@@ -0,0 +1,91 @@
# اتصال به بک‌اند
کلِ ارتباط با سرور از همین پوشه رد می‌شود. **هیچ‌جای اپ مستقیم `fetch` نزنید** —
مدیریتِ توکن، تکرارِ درخواست و رفتارِ درستِ خطا اینجا یک‌جا حل شده‌اند.
## راه‌اندازی
**هیچ تنظیماتی لازم نیست.** به‌طورِ پیش‌فرض به بک‌اندِ مشترک
(`https://api.math.tadrisino2.ir/api`) وصل می‌شود:
```bash
npm install
npm run dev
```
فقط اگر بک‌اند را روی ماشینِ خودتان بالا آورده‌اید:
```bash
cp .env.example .env # و خطِ VITE_API_BASE_URL را باز کنید
```
برای اینکه ببینید اتصال برقرار است، یک خط کافی‌ست (بعداً پاکش کنید):
```tsx
import { ConnectionCheck } from '@/api/ConnectionCheck';
// داخلِ خروجیِ App:
<ConnectionCheck />
```
گوشه‌ی صفحه سبز می‌شود و فهرستِ پایه‌ها را از سرور نشان می‌دهد، یا قرمز
می‌شود و می‌گوید دقیقاً کجای کار می‌لنگد.
## استفاده
```tsx
import { api, ApiError } from '@/api';
const grades = await api.grades();
const home = await api.home(); // پایه‌ی خودِ کاربر
const intro = await api.lessons.intro(lessonId);
const answer = await api.ai.ask('این رو نفهمیدم', { lessonId });
```
### ورود
```tsx
await api.auth.guest(deviceId); // «شروع سریع»، بدونِ شماره
// یا
const { devOtp } = await api.auth.requestOtp('09120000000');
await api.auth.login('09120000000', otp); // نامِ فیلد در سرور otp است
```
توکن خودش در `localStorage` ذخیره می‌شود و به هر درخواست اضافه می‌گردد.
### خطاها
```tsx
try {
await api.ai.ask(text, { lessonId });
} catch (error) {
if (error instanceof ApiError) {
if (error.isRateLimited) { /* ۴۲۹ — خیلی سریع پرسیده */ }
if (error.isProviderDown) { /* ۵۰۳ — سرویسِ AI موقتاً قطع */ }
if (error.isNetwork) { /* اصلاً نرسید یا timeout */ }
}
}
```
## چرا این‌قدر ملاحظه در `client.ts`
هر تکه‌اش برای حالتی‌ست که فقط زیرِ فشار پیش می‌آید:
| مسئله | اگر نباشد چه می‌شود |
|---|---|
| `timeout` روی هر درخواست | یک بک‌اندِ کند سوکت‌ها را باز نگه می‌دارد؛ مرورگر به سقفِ ۶ درخواستِ همزمان می‌خورد و کلِ اپ قفل می‌شود |
| backoff با **jitter** | هزار کلاینت بعد از یک خطا *همزمان* دوباره می‌زنند و موجِ دوم سرور را می‌خواباند |
| تکرارِ خودکار فقط روی `GET` | تکرارِ `POST` یعنی سفارش یا پاسخِ تکراری |
| `Idempotency-Key` روی ثبتِ پاسخ و سفارش | دابل‌کلیکِ کاربر دو بار XP یا دو سفارش می‌سازد |
| `refresh` تک‌نسخه‌ای | ۲۰ درخواستِ همزمانِ ۴۰۱ ⇒ ۲۰ بار refresh ⇒ سرور همه را throttle می‌کند |
| ادغامِ `GET`های یکسان | دو کامپوننت که با هم یک چیز می‌خواهند، دو برابر بار می‌سازند |
| احترام به هدرِ `Retry-After` | بک‌اند صریح می‌گوید چقدر صبر کن؛ نادیده گرفتنش throttle را طولانی‌تر می‌کند |
`credentials: 'omit'` هم عمدی‌ست: احرازِ هویت توکنِ Bearer در هدر است و سمتِ
سرور `CORS_ALLOW_CREDENTIALS=False` تنظیم شده. عوضش نکنید.
## چیزی که هنوز نیست
- صفحه‌ی ورود در اپ وجود ندارد؛ `api.auth` آماده است ولی UI ندارد.
- `api.home()` ساختارِ واقعیِ درس‌ها را می‌دهد، ولی وصل کردنش به صفحه‌ها
هنوز انجام نشده — این PR عمداً `App.tsx` را دست نزده تا با کارِ در جریان
تصادف نکند.