Open the price stream on the TV board with the TV key header.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -15,6 +15,7 @@ export type TvServerTime = {
|
||||
unix: number
|
||||
iso: string
|
||||
timezone: string
|
||||
stream_key?: string
|
||||
}
|
||||
|
||||
export const getTvServerTime = async (): Promise<TvServerTime | null> => {
|
||||
|
||||
@@ -44,9 +44,9 @@ class AuthPriceStream extends EventTarget {
|
||||
onmessage: (() => void) | null = null
|
||||
private abort = new AbortController()
|
||||
|
||||
constructor(url: string) {
|
||||
constructor(url: string, tvKey = '') {
|
||||
super()
|
||||
void this.start(url)
|
||||
void this.start(url, tvKey)
|
||||
}
|
||||
|
||||
close() {
|
||||
@@ -54,10 +54,12 @@ class AuthPriceStream extends EventTarget {
|
||||
this.abort.abort()
|
||||
}
|
||||
|
||||
private async start(url: string) {
|
||||
private async start(url: string, tvKey = '') {
|
||||
try {
|
||||
const headers: Record<string, string> = { Accept: 'text/event-stream' }
|
||||
if (tvKey) headers['X-Tv-Key'] = tvKey
|
||||
const response = await fetch(url, {
|
||||
headers: { Accept: 'text/event-stream' },
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
signal: this.abort.signal,
|
||||
})
|
||||
@@ -119,6 +121,7 @@ class AuthPriceStream extends EventTarget {
|
||||
|
||||
let eventSource: AuthPriceStream | null = null
|
||||
let subscribers = 0
|
||||
let tvStreamKey: string | null = null
|
||||
let stopped = false
|
||||
let getSetting: ((key: string) => unknown) | null = null
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | undefined
|
||||
@@ -655,7 +658,7 @@ function connect() {
|
||||
markConnectingLifecycle()
|
||||
|
||||
try {
|
||||
eventSource = new AuthPriceStream(url)
|
||||
eventSource = new AuthPriceStream(url, tvStreamKey || '')
|
||||
} catch (error) {
|
||||
console.warn('Price SSE connect failed', error)
|
||||
handleStreamLost()
|
||||
@@ -740,6 +743,22 @@ function unbindBrowserNetworkListeners() {
|
||||
window.removeEventListener('offline', onBrowserOffline)
|
||||
}
|
||||
|
||||
export function startTvPriceStream(
|
||||
settingGetter: (key: string) => unknown,
|
||||
key: string,
|
||||
) {
|
||||
const secret = key.trim()
|
||||
if (!secret) return
|
||||
tvStreamKey = secret
|
||||
startPriceSocket(settingGetter)
|
||||
}
|
||||
|
||||
export function stopTvPriceStream() {
|
||||
if (!tvStreamKey) return
|
||||
tvStreamKey = null
|
||||
stopPriceSocket()
|
||||
}
|
||||
|
||||
export function startPriceSocket(settingGetter: (key: string) => unknown) {
|
||||
getSetting = settingGetter
|
||||
subscribers += 1
|
||||
|
||||
@@ -10,6 +10,7 @@ import { usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { getStoreClosedMessage, isStoreClosedBySchedule, NO_QUOTE_MESSAGE } from '@/utils/storeHours'
|
||||
import { isMeltedTradeGroup, isMetalCommodityGroup } from '@/utils/productCategories'
|
||||
import { priceSocketConnected, priceStreamLifecycle } from '@/utils/priceSocketState'
|
||||
import { startTvPriceStream, stopTvPriceStream } from '@/utils/priceSocket'
|
||||
import logoFallback from '@/assets/img/logo_full_.png'
|
||||
//@ts-ignore
|
||||
import moment from 'moment-jalaali'
|
||||
@@ -21,7 +22,7 @@ moment.loadPersian({ usePersianDigits: false })
|
||||
|
||||
const CURSOR_HIDE_MS = 2500
|
||||
const TV_HTML_CLASS = 'app-layout--tv'
|
||||
const REFRESH_MS = 3000
|
||||
const REFRESH_MS = 15000
|
||||
/** دقت تابلو: هزار تومان = ۱۰٬۰۰۰ ریال — باقیمانده به پایین حذف میشود */
|
||||
const TV_TOMAN_STEP = 1000
|
||||
|
||||
@@ -121,6 +122,14 @@ export function useTvMonitorPage() {
|
||||
let listTimer: ReturnType<typeof setInterval> | undefined
|
||||
/** اختلاف میلیثانیهٔ ساعت سرور (Asia/Tehran) با Date.now محلی */
|
||||
let serverOffsetMs = 0
|
||||
let tvStreamReady = false
|
||||
|
||||
function openTvStream(key: string) {
|
||||
const secret = key.trim()
|
||||
if (!secret || tvStreamReady) return
|
||||
tvStreamReady = true
|
||||
startTvPriceStream((settingKey) => settingsStore.getSettingByKey(settingKey), secret)
|
||||
}
|
||||
|
||||
const brandName = computed(
|
||||
() => String(getConfigApp('name_fa') || getConfigApp('name') || 'تابلو قیمت'),
|
||||
@@ -150,6 +159,7 @@ export function useTvMonitorPage() {
|
||||
if (!payload?.unix) return
|
||||
const rttHalf = (t1 - t0) / 2
|
||||
serverOffsetMs = payload.unix * 1000 + rttHalf - t1
|
||||
if (payload.stream_key) openTvStream(payload.stream_key)
|
||||
}
|
||||
|
||||
const parsianCoins = computed(() =>
|
||||
@@ -246,7 +256,11 @@ export function useTvMonitorPage() {
|
||||
() => priceSocketConnected.value || priceStreamLifecycle.value === 'open',
|
||||
)
|
||||
|
||||
let productsInFlight = false
|
||||
|
||||
async function loadProducts() {
|
||||
if (productsInFlight) return
|
||||
productsInFlight = true
|
||||
try {
|
||||
const list = await getTvProducts()
|
||||
products.value = (Array.isArray(list) ? list : []).map(normalizeTvProduct)
|
||||
@@ -255,6 +269,7 @@ export function useTvMonitorPage() {
|
||||
products.value = []
|
||||
}
|
||||
} finally {
|
||||
productsInFlight = false
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
@@ -390,6 +405,7 @@ export function useTvMonitorPage() {
|
||||
document.removeEventListener('visibilitychange', onVisibilityChange)
|
||||
void releaseWakeLock()
|
||||
stopLastPricePolling()
|
||||
stopTvPriceStream()
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
<div class="tv-monitor__quote-prices">
|
||||
<div class="tv-monitor__quote-item tv-monitor__quote-item--sell">
|
||||
<span>فروش</span>
|
||||
<strong :class="{ 'is-empty': !mainQuoteReady }">{{ liveQuoteLabel(liveSell) }}</strong>
|
||||
<strong :class="{ 'is-empty': !mainQuoteReady }">{{ liveQuoteLabel(liveBuy) }}</strong>
|
||||
<em v-if="mainQuoteReady">تومان</em>
|
||||
</div>
|
||||
<div class="tv-monitor__quote-item tv-monitor__quote-item--buy">
|
||||
<span>خرید</span>
|
||||
<strong :class="{ 'is-empty': !mainQuoteReady }">{{ liveQuoteLabel(liveBuy) }}</strong>
|
||||
<strong :class="{ 'is-empty': !mainQuoteReady }">{{ liveQuoteLabel(liveSell) }}</strong>
|
||||
<em v-if="mainQuoteReady">تومان</em>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user