# Rango WebSocket — جایگزینی Polling در Shahoo **تاریخ:** 2026-07-19 **آخرین به‌روزرسانی پیاده‌سازی:** 2026-07-20 **وابستگی:** [01-dynamic-markets-remediation.md](./01-dynamic-markets-remediation.md) (فاز ۳+ توصیه می‌شود) **مرجع Gereh:** `Gereh/src/modules/public/ranger/` **Infra:** Rango 2.6.1 — `quay.io/openware/rango:2.6.1` > **وضعیت کد (۲۰۲۶-۰۷-۲۰):** WS-0 → WS-4 پیاده‌سازی شده؛ follow-up: thrashing/refcount، sequence resync، private reconnect، compose shahoo-ranger، setupProxy. > باقی ops: WS0.3/0.4 staging + Cloudflare، WS5.1/5.3 latency/load. > Public: `RangerProvider` + `global.tickers` / `ob-inc` / `trades` / `kline-*`. > Private: BFF `GET /api/1/ranger/credentials` + WS proxy `/api/1/ranger/ws` → Rango private با cookie session. > Fallback: `REACT_APP_RANGER_ENABLED=false` یا قطع WS → polling قبلی. --- ## ۱. خلاصه اجرایی Shahoo امروز **HTTP polling** دارد؛ Gereh از **Rango (Ranger) WebSocket** استفاده می‌کند. هدف: latency کمتر در order book، tickers، trades و (در فاز بعد) balances/orders. | معیار | Polling (فعلی) | Rango WS (هدف) | |-------|----------------|----------------| | Order book | هر **۵s** | push فوری (`ob-inc` / `update`) | | Tickers (stats) | هر **۵s** | `global.tickers` | | Recent trades | هر **۱۰s** | `{market}.trades` | | Chart realtime | stub (CryptoCompare comment) | `{market}.kline-{period}` | | بار سرور | N × clients × freq | یک WS per client | | Auth private | — | `order`, `trade`, `balances` | **توصیه:** بعد از **MarketsContext داینامیک** (doc 01 فاز ۳) شروع شود — subscribe به streamها به `marketId` Peatio (مثلاً `btcirt`) وابسته است. --- ## ۲. وضعیت فعلی Shahoo (Polling Map) | فایل | interval | API | جایگزین Rango | |------|----------|-----|----------------| | `src/features/User/context/index.js` | 5s | `GET /api/1/ohlcvs/stats` | `global.tickers` | | `OrderBook/RecentTransactionsList/index.js` | 5s | `GET /api/1/orders/orderBook/...` | `{id}.ob-inc` یا `{id}.update` | | `hook/useGetOdrerBook/useGetOdrerBook.js` | 8s | order book | همان | | `MarketTable/RecentMarketTrades/index.js` | 10s | trades | `{id}.trades` | | `MarketRecentTransactions/index.js` | 10s | trades | `{id}.trades` | | `TVChartContainer/datafeeds/streaming.js` | — | stub (غیرفعال) | `{id}.kline-{period}` | **تنظیمات interval:** `src/core/utils/IntervalApiTime.js` ```js export const orderBookTime = 5; export const statsTime = 5; export const recentMarketTime = 10; ``` --- ## ۳. پروتکل Rango (OpenDAX) ### ۳.۱. URL اتصال ``` wss://{domain}/api/v2/ranger/public/?stream=global.tickers&stream=btcirt.trades&... wss://{domain}/api/v2/ranger/private/?stream=order&stream=trade&... ``` الگوی Gereh (`helpers.ts`): ```ts generateSocketURI(baseUrl, streams) => `${baseUrl}/?stream=${streams.sort().join('&stream=')}` ``` ### ۳.۲. کانال‌های public | Stream | payload | مصرف Shahoo | |--------|---------|-------------| | `global.tickers` | `{ btcusdt: { last, open, high, low, volume, ... } }` | `StatsContext` / sidebar قیمت | | `{market}.trades` | `{ trades: [...] }` | Recent trades | | `{market}.update` | full order book snapshot | Order book (legacy) | | `{market}.ob-snap` | snapshot | incremental OB | | `{market}.ob-inc` | incremental + sequence | Order book (preferred) | | `{market}.kline-{period}` | OHLCV bar | TradingView streaming | `period`: `1h`, `4h`, `1d`, … (مطابق Gereh `periodsMapString`) ### ۳.۳. کانال‌های private (JWT / session) | Stream | مصرف | |--------|------| | `order` | open orders update | | `trade` | history push | | `balances` | wallet realtime (Finex) | | `deposit_address` | آدرس واریز | ### ۳.۴. پیام subscribe/unsubscribe ```json { "event": "subscribe", "streams": ["btcirt.trades"] } { "event": "unsubscribe", "streams": ["btcirt.trades"] } ``` پاسخ: `{ "success": { "message": "subscribed", "streams": [...] } }` --- ## ۴. شکاف‌های اتصال Shahoo ↔ Rango ### G1 — URL runtime ندارد 🔴 Shahoo `public/config/env.js` فقط `app`, `captcha` دارد — **`rangerUrl` نیست**. **راه‌حل:** اضافه به `window.env`: ```js window.env.api = { rangerUrl: 'wss://shahoo.fibitex.com/api/v2/ranger', }; ``` Traefik روی `shahoo.*` باید `/api/v2/ranger` → Rango route داشته باشد (مثل Gereh روی `www.*`). ### G2 — Auth model متفاوت 🔴 | Gereh | Shahoo | |-------|--------| | Cookie + Envoy JWT | BFF opaque token + Barong cookies در server-side | | WS private با session مرورگر | مرورگر JWT Peatio ندارد | **گزینه‌ها:** | # | رویکرد | + | − | |---|--------|---|---| | **A** | Public WS مستقیم از browser؛ private همچنان polling/BFF | سریع MVP | orders/balances realtime نمی‌آید | | **B** | BFF: `GET /api/1/ranger/credentials` → Peatio JWT از gateway | private channels | endpoint + امنیت | | **C** | BFF WebSocket proxy | مخفی JWT | پیچیدگی ops | **توصیه:** **A برای MVP** (public market data) → **B برای فاز ۲** (private). ### G3 — نگاشت ticker 🔴 Rango: `{ "btcirt": { last, open, ... } }` (market id lowercase) Shahoo: `statMap["BTC_IRT"]` (uppercase key) **راه‌حل:** mapper مشترک در `src/lib/ranger/mapTickers.js` (یا reuse BFF `marketIdToShahooStatKeys` logic در frontend). ### G4 — Order book format 🟡 BFF فعلی: `{ sells: [{unitPrice, size}], buys: [...] }` Rango `ob-inc`: `{ asks, bids, sequence }` — نیاز adapter مثل Gereh `depthDataIncrement`. ### G5 — Incremental sequence 🟡 Gereh: اگر `sequence` break شود → disconnect + reconnect + REST prefetch. Shahoo باید همان pattern را پیاده کند. ### G6 — Cloudflare / Traefik 🟡 WebSocket روی staging/production: - Cloudflare: WebSocket proxy **فعال** - Traefik: `Upgrade` header pass-through - تست: `wscat -c "wss://shahoo.fibitex.com/api/v2/ranger/public/?stream=global.tickers"` --- ## ۵. معماری هدف ``` ┌─────────────────────────────────────────────────────────┐ │ Shahoo SPA │ │ RangerContext (singleton WS manager) │ │ ├─ public: global.tickers + market streams │ │ ├─ private: (فاز ۲) order, trade │ │ └─ mappers → StatsContext, OrderBook, Trades, Chart │ └───────────────────────┬─────────────────────────────────┘ │ wss://shahoo.fibitex.com/api/v2/ranger ▼ Traefik → Rango :8080 ▲ RabbitMQ ← Dena pushers ``` **Fallback:** اگر WS قطع شد → polling قبلی (feature flag `REACT_APP_RANGER_ENABLED`). --- ## ۶. فازبندی اجرایی ### فاز WS-0 — زیرساخت (DevOps + config) 🔴 | ID | تسک | repo | وضعیت | |----|------|------|--------| | **WS0.1** | `rangerUrl` در `public/config/env.js` + Alvand-P template `shahoo.env.js.erb` | Shahoo + Alvand-P | ✅ | | **WS0.2** | Traefik route `/api/v2/ranger` روی host `shahoo.*` → rango | Alvand-P | ✅ | | **WS0.3** | تست connectivity: `global.tickers` از staging | QA | ⬜ | | **WS0.4** | Cloudflare WebSocket برای `shahoo.fibitex.com` | DevOps | ⬜ | --- ### فاز WS-1 — Core client (Shahoo) 🔴 | ID | تسک | فایل(ها) | وضعیت | |----|------|----------|--------| | **WS1.1** | `src/lib/ranger/rangerClient.js` — connect, reconnect, heartbeat | جدید | ✅ | | **WS1.2** | `generateSocketURI`, subscribe/unsubscribe | `rangerClient.js` | ✅ | | **WS1.3** | `RangerContext` + `useRanger()` | `src/context/rangerContext.js` | ✅ | | **WS1.4** | Feature flag: `REACT_APP_RANGER_ENABLED` + fallback polling | `.env`, hooks | ✅ | | **WS1.5** | `mapRangerTickersToStatMap` | `src/lib/ranger/mapTickers.js` | ✅ | **مرجع پیاده‌سازی:** `Gereh/src/modules/public/ranger/sagas/rangerSaga.ts` (بدون Redux — Context + useReducer). --- ### فاز WS-2 — جایگزینی polling (public) 🔴 | ID | تسک | جایگزین | polling حذف‌شده | وضعیت | |----|------|---------|-----------------|--------| | **WS2.1** | Tickers → `StatsContext` | `global.tickers` | `User/context` stats loop | ✅ | | **WS2.2** | Order book | `{id}.ob-inc` + REST prefetch | `RecentTransactionsList`, `useGetOdrerBook` | ✅ | | **WS2.3** | Recent trades | `{id}.trades` | `RecentMarketTrades`, `MarketRecentTransactions` | ✅ | | **WS2.4** | Market switch → resubscribe | `rangerSubscribeMarket` pattern | — | ✅ | | **WS2.5** | Landing tickers (public page) | `global.tickers` | `Landing/pages` getStats | ✅ | **WS2.4:** هنگام تغییر route `/market/BTC-IRT` → unsubscribe `ethirt.*` → subscribe `btcirt.*`. --- ### فاز WS-3 — Chart streaming 🟡 | ID | تسک | فایل | وضعیت | |----|------|------|--------| | **WS3.1** | فعال‌سازی `streaming.js` با Rango kline | `datafeeds/streaming.js` | ✅ | | **WS3.2** | subscribe `{marketId}.kline-4h` (match TV resolution) | `rangerClient` + datafeed | ✅ | | **WS3.3** | unsubscribe on symbol change / unmount | datafeed | ✅ | --- ### فاز WS-4 — Private channels 🟡 | ID | تسک | repo | وضعیت | |----|------|------|--------| | **WS4.1** | BFF: `GET /api/1/ranger/credentials` (+ alias `/token`) + private WS proxy | Shahoo-BFF | ✅ | | **WS4.2** | اتصال private بعد از login (`connectPrivate`) | Shahoo `rangerContext` | ✅ | | **WS4.3** | `order` stream → refresh open orders | Market / Orders UI | ✅ | | **WS4.4** | `trade` stream → history refresh | MarketYourTransaction | ✅ | | **WS4.5** | `balances` → wallets refetch | Wallets context | ✅ | --- ### فاز WS-5 — QA + Performance 🟢 | ID | تسک | وضعیت | |----|------|--------| | **WS5.1** | Latency: order book update < 500ms vs polling 5s | ⬜ staging | | **WS5.2** | Reconnect بعد از sleep/tab background | ✅ (client 1s reconnect) | | **WS5.3** | 50+ concurrent WS روی staging (load) | ⬜ ops | | **WS5.4** | Fallback polling وقتی WS down | ✅ | | **WS5.5** | حذف intervalهای unused از `IntervalApiTime.js` | ⏸️ نگه داشته (fallback) | --- ## ۷. مسیر بحرانی و وابستگی ``` doc-01: T3.1 MarketsContext (marketId از catalog) ↓ WS0.* (infra + rangerUrl) ↓ WS1.* (rangerClient + Context) ↓ WS2.1 (tickers) → WS2.2 (orderbook) → WS2.3 (trades) ↓ WS3.* (chart) — موازی با WS2.3 ↓ WS4.* (private) — بعد از تصمیم auth ↓ WS5.* (QA) ``` **موازی با doc-01:** WS0 می‌تواند همزمان با فاز ۱ doc-01 (نمودار/CAD) شروع شود. **WS2+** بعد از `MarketsContext` — market id باید از catalog بیاید نه hardcode. --- ## ۸. تخمین زمان | فاز | تخمین | |-----|--------| | WS-0 | ۰.۵–۱ روز (DevOps) | | WS-1 | ۱–۲ روز | | WS-2 | ۲–۳ روز | | WS-3 | ۱ روز | | WS-4 | ۲–۳ روز | | WS-5 | ۱ روز | | **جمع MVP (WS-0→2)** | **~۵–۷ روز** | --- ## ۹. تست ```bash # connectivity (بعد از WS0) npx wscat -c "wss://shahoo.fibitex.com/api/v2/ranger/public/?stream=global.tickers" # browser DevTools → Network → WS → frames # انتظار: JSON با کلید btcirt, btcusdt, ... ``` **چک UI:** - [ ] قیمت sidebar بدون 5s delay به‌روز شود - [ ] order book بعد از trade دیگران فوری update - [ ] Network tab: polling `/ohlcvs/stats` متوقف (با flag روشن) - [ ] قطع WS → fallback polling کار کند --- ## ۱۰. Todo checklist ``` فاز WS-0 — Infra [x] WS0.1 rangerUrl در env.js + template [x] WS0.2 Traefik /api/v2/ranger روی shahoo.* [ ] WS0.3 تست global.tickers staging [ ] WS0.4 Cloudflare WebSocket فاز WS-1 — Core [x] WS1.1 rangerClient.js [x] WS1.2 subscribe/unsubscribe [x] WS1.3 RangerContext + useRanger [x] WS1.4 REACT_APP_RANGER_ENABLED + fallback [x] WS1.5 mapRangerTickersToStatMap فاز WS-2 — Public replace polling [x] WS2.1 tickers → StatsContext [x] WS2.2 order book ob-inc [x] WS2.3 recent trades [x] WS2.4 market switch resubscribe [x] WS2.5 landing tickers فاز WS-3 — Chart [x] WS3.1 streaming.js + Rango kline [x] WS3.2 resolution mapping [x] WS3.3 cleanup on unmount فاز WS-4 — Private [x] WS4.1 BFF ranger credentials + private WS proxy [x] WS4.2 private WS connect after login [x] WS4.3 order stream → refresh [x] WS4.4 trade stream → refresh [x] WS4.5 balances → wallets refresh فاز WS-5 — QA [x] WS5.2 reconnect + WS5.4 fallback [ ] WS5.1 latency / WS5.3 load / Cloudflare ``` --- ## ۱۱. commit message convention ``` WS1.1: add rangerClient with reconnect WS2.1: replace stats polling with global.tickers WS2.2: incremental order book via ob-inc ```