367 lines
13 KiB
Markdown
367 lines
13 KiB
Markdown
<div dir="rtl">
|
||
|
||
# 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
|
||
```
|
||
|
||
</div>
|