Skip to main content

کلاینت وب

کلاینت وب یک اپ React در apps/calendar-web/ است — نه خروجی Wasm آزمایشیِ KMP. مثل اندروید، آفلاین‌اول کار می‌کند: هر چهار موجودیت ابتدا در IndexedDB (با Dexie) خوانده و نوشته می‌شوند و بعداً با API همگام می‌شوند. محدودیت فعلی رابط کاربری وب: ساخت یادآور و زیروظیفه وجود ندارد؛ آن دو در اندروید ساخته می‌شوند.

نقشهٔ کد​

مسیرها نسبت به apps/calendar-web/src/:

مسیرمسئولیت
lib/db/schema.tsschema فعلی ۵: چهار موجودیت، outbox حساب‌محور، cursor، canonical و مالک DB
features/*/local.tsثبت ردیف و queueLocalEdit در یک تراکنش؛ سپس kick موتور sync
lib/sync/syncEngine.tspush/pull، بازیابی ارسال نیمه‌کاره، بازکلید تقویم پیش‌فرض
lib/sync/syncApi.ts و leaderElection.tsپیاده‌سازی پروتکل ۲ و سرصفحهٔ X-Sync-Protocol: 2؛ انتخاب تب رهبر با Web Locks
features/auth/AuthContext.tsx و ProtectedRoute.tsxبررسی نشست، مالکیت داده و تصمیم خروج/تعویض حساب
features/sync/بنر مشکلات sync و ConflictList/hooks.ts برای حل تعارض‌ها
lib/apiClient.ts و tokenStore.tsBearer، refresh تک‌پروازی، نسل احراز هویت و توکن‌های localStorage

DTOها از @taghvimam/contracts می‌آیند و VITE_API_URL مبدأ API است. فرمان‌های اجرا و تست در تست‌ها.

تغییر و ایمنی حساب​

چرخهٔ sync در وب همان قرارداد همگام‌سازی است؛ نکات پیاده‌سازی:

  • حساب بدون cursor اول pull کامل می‌گیرد؛ دور عادی push → pull → refreshConflicts است.
  • تب رهبر (با Web Locks انتخاب می‌شود) دورها را با این triggerها اجرا می‌کند: آنلاین‌شدن، visible شدن تب، تایمر ۶۰ ثانیه و kick ویرایش کاربر.
  • پاسخی که به آیتمی نمی‌خورد صف را مصرف نمی‌کند؛ اگر پاسخ push گم شود، همان درخواست با همان شناسه/محتوا نگه داشته و بعداً تکرار می‌شود تا رسیدِ سرور جواب بدهد.
  • pull صفحه و cursor را اتمیک ذخیره می‌کند؛ پیش‌نویس باز کاربر با cursorReset پاک نمی‌شود.

بارگذاری و نشست:

  • بارگذاری اولیه، /me و مالک DB را تأیید می‌کند و فقط بعد از آن داده نمایش یا sync می‌شود.
  • قطعی موقت فقط «تلاش مجدد» است؛ ابطال نشست یعنی ورود دوباره — بدون پاک‌کردن DB محلی.
  • تعویض حساب A→B بدون تصمیم صریح (خروجی یا دورریزِ کار A) انجام نمی‌شود؛ دادهٔ A به B نسبت داده نمی‌شود. fence نسل/مالک، پاسخ دیررس حساب قبلی را رد می‌کند.
  • خروج وقتی کار محلی هست، سه راه دارد: ارسال کامل، خروجی JSON و بعد دورریز، یا دورریز با تأیید دوم. اگر کار جدیدی بعد از آن دیالوگ انجام شود، تصمیم تازه لازم است. بدون کار محلی، خروج مستقیم است.
  • تب قدیمیِ بدون سرصفحهٔ پروتکل، 426 می‌گیرد و outboxش محفوظ می‌ماند؛ راه‌حل، بارگذاری مجدد صفحه است، نه حذف IndexedDB.

مشکل همگام‌سازی را چطور حل کنم؟​

بنر مشکلات، خطاهای outbox و تعارض‌های تاریخی را در یک دیالوگ فهرست می‌کند؛ بستن پنجره هیچ تصمیمی نمی‌گیرد و چیزی پاک نمی‌کند.

مشکلانتخاب موجود
اختلاف نسخه (stale)نسخهٔ سرور، یا نسخهٔ خودم با base و شناسهٔ تازه
حذف در سروربازگردانی صریح با تأیید دوم؛ رکورد خودکار زنده نمی‌شود
invalid_itemاصلاح فیلدهای پیشنهادی و ارسال دوباره
unknown_parent / parent_deletedانتخاب تقویم زنده و انتقال رکورد
has_live_dependentsدیدن وابستگان و تعیین تکلیف آن‌ها اول
identity_mismatchشناسهٔ تازه برای ارسال همان تغییر
تعارض تاریخیرد یا بازگردانی از تاریخچه؛ UI حداکثر ۵۰ مورد اول را نشان می‌دهد

مهاجرت schema قدیمی، outbox و cursor را حساب‌محور می‌کند؛ دادهٔ بدون مالک تا تأیید مالکیت ارسال نمی‌شود.

تست‌های راهنمای همین رفتارها: lib/sync/*.test.ts، lib/db/schema.upgrade.test.ts و features/auth/AuthContext.test.tsx.