added sse2
This commit is contained in:
+3
-7
@@ -3,6 +3,7 @@ import type { Price, PriceUp } from '@/models/home'
|
||||
import { ref, watch } from 'vue'
|
||||
import {
|
||||
priceSocketConnected,
|
||||
priceStreamLifecycle,
|
||||
priceWssEnabled,
|
||||
shouldUsePricePolling,
|
||||
} from '@/utils/priceSocketState'
|
||||
@@ -63,14 +64,9 @@ function syncLastPricePolling() {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [priceWssEnabled.value, priceSocketConnected.value] as const,
|
||||
([wssEnabled, connected], prev) => {
|
||||
const prevConnected = prev?.[1]
|
||||
() => [priceWssEnabled.value, priceSocketConnected.value, priceStreamLifecycle.value] as const,
|
||||
() => {
|
||||
syncLastPricePolling()
|
||||
|
||||
if (wssEnabled && prevConnected && !connected && lastSubscribers > 0) {
|
||||
void getLastPrices()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
+39
-12
@@ -2,14 +2,16 @@ import type { Price } from '@/models/home'
|
||||
import type { Product } from '@/models/products'
|
||||
import { Modal } from 'ant-design-vue'
|
||||
import { getConfigApp } from '@/utils/config'
|
||||
import { priceSocketConnected, priceWssEnabled } from '@/utils/priceSocketState'
|
||||
import { priceSocketConnected, priceStreamLifecycle, priceWssEnabled } from '@/utils/priceSocketState'
|
||||
|
||||
export { priceSocketConnected, priceWssEnabled, shouldUsePricePolling } from '@/utils/priceSocketState'
|
||||
export { priceSocketConnected, priceStreamLifecycle, priceWssEnabled, shouldUsePricePolling } from '@/utils/priceSocketState'
|
||||
|
||||
const RECONNECT_BASE_MS = 3000
|
||||
const RECONNECT_MAX_MS = 30000
|
||||
const FAILURES_BEFORE_ALERT = 4
|
||||
const STALE_STREAM_MS = 45000
|
||||
/** بدون event قیمت؛ ping هر ۵ث از سرور activity را تازه میکند */
|
||||
const STALE_STREAM_MS = 120000
|
||||
const FALLBACK_POLL_DELAY_MS = 2500
|
||||
|
||||
let eventSource: EventSource | null = null
|
||||
let subscribers = 0
|
||||
@@ -21,6 +23,7 @@ let reconnectAttempt = 0
|
||||
let lastStreamEventAt = 0
|
||||
let networkAlertOpen = false
|
||||
let listenersBound = false
|
||||
let fallbackPollTimer: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
function isTruthySetting(value: unknown, defaultValue = true): boolean {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
@@ -95,6 +98,23 @@ function startStaleCheck() {
|
||||
}, 15000)
|
||||
}
|
||||
|
||||
function clearFallbackPollTimer() {
|
||||
if (fallbackPollTimer) {
|
||||
clearTimeout(fallbackPollTimer)
|
||||
fallbackPollTimer = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleFallbackPoll() {
|
||||
clearFallbackPollTimer()
|
||||
fallbackPollTimer = setTimeout(() => {
|
||||
fallbackPollTimer = undefined
|
||||
if (!stopped && subscribers > 0 && !priceSocketConnected.value) {
|
||||
void runFallbackPoll()
|
||||
}
|
||||
}, FALLBACK_POLL_DELAY_MS)
|
||||
}
|
||||
|
||||
async function runFallbackPoll() {
|
||||
if (!priceWssEnabled.value || priceSocketConnected.value) {
|
||||
return
|
||||
@@ -211,6 +231,10 @@ function bindStreamEvents(source: EventSource) {
|
||||
}
|
||||
})
|
||||
|
||||
source.addEventListener('ping', () => {
|
||||
touchStreamActivity()
|
||||
})
|
||||
|
||||
source.onmessage = () => {
|
||||
touchStreamActivity()
|
||||
}
|
||||
@@ -220,9 +244,10 @@ function handleStreamLost(scheduleRetry = true) {
|
||||
const wasConnected = priceSocketConnected.value
|
||||
disconnectSourceOnly()
|
||||
priceSocketConnected.value = false
|
||||
priceStreamLifecycle.value = scheduleRetry ? 'connecting' : 'closed'
|
||||
|
||||
if (wasConnected || scheduleRetry) {
|
||||
void runFallbackPoll()
|
||||
if ((wasConnected || scheduleRetry) && scheduleRetry) {
|
||||
scheduleFallbackPoll()
|
||||
}
|
||||
|
||||
if (scheduleRetry && !stopped && subscribers > 0) {
|
||||
@@ -233,6 +258,7 @@ function handleStreamLost(scheduleRetry = true) {
|
||||
|
||||
function disconnectSourceOnly() {
|
||||
stopStaleCheck()
|
||||
clearFallbackPollTimer()
|
||||
if (eventSource) {
|
||||
eventSource.onopen = null
|
||||
eventSource.onerror = null
|
||||
@@ -250,6 +276,7 @@ function connect() {
|
||||
const url = resolveStreamUrl()
|
||||
if (!url) {
|
||||
priceSocketConnected.value = false
|
||||
priceStreamLifecycle.value = 'closed'
|
||||
return
|
||||
}
|
||||
|
||||
@@ -261,6 +288,8 @@ function connect() {
|
||||
disconnectSourceOnly()
|
||||
}
|
||||
|
||||
priceStreamLifecycle.value = 'connecting'
|
||||
|
||||
try {
|
||||
eventSource = new EventSource(url)
|
||||
} catch (error) {
|
||||
@@ -274,7 +303,9 @@ function connect() {
|
||||
eventSource.onopen = () => {
|
||||
reconnectAttempt = 0
|
||||
clearReconnectTimer()
|
||||
clearFallbackPollTimer()
|
||||
networkAlertOpen = false
|
||||
priceStreamLifecycle.value = 'open'
|
||||
priceSocketConnected.value = true
|
||||
touchStreamActivity()
|
||||
startStaleCheck()
|
||||
@@ -282,13 +313,7 @@ function connect() {
|
||||
|
||||
eventSource.onerror = () => {
|
||||
const state = eventSource?.readyState
|
||||
if (state === EventSource.OPEN) {
|
||||
return
|
||||
}
|
||||
|
||||
if (state === EventSource.CONNECTING) {
|
||||
priceSocketConnected.value = false
|
||||
void runFallbackPoll()
|
||||
if (state === EventSource.OPEN || state === EventSource.CONNECTING) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -298,8 +323,10 @@ function connect() {
|
||||
|
||||
function disconnect() {
|
||||
clearReconnectTimer()
|
||||
clearFallbackPollTimer()
|
||||
disconnectSourceOnly()
|
||||
priceSocketConnected.value = false
|
||||
priceStreamLifecycle.value = 'idle'
|
||||
}
|
||||
|
||||
export function forceReconnect() {
|
||||
|
||||
@@ -6,11 +6,23 @@ export const priceSocketConnected = ref(false)
|
||||
/** از settings در bootstrap ست میشود (کلید wss_enabled = قیمت لحظهای روشن) */
|
||||
export const priceWssEnabled = ref(false)
|
||||
|
||||
/** polling: realtime خاموش = همیشه | روشن + SSE وصل = نه | روشن + SSE قطع = بله */
|
||||
export type PriceStreamLifecycle = 'idle' | 'connecting' | 'open' | 'closed'
|
||||
|
||||
/** چرخهٔ اتصال SSE — برای جلوگیری از polling همزمان با reconnect */
|
||||
export const priceStreamLifecycle = ref<PriceStreamLifecycle>('idle')
|
||||
|
||||
/** polling: realtime خاموش = همیشه | SSE در حال اتصال/وصل = نه | قطع واقعی = بله */
|
||||
export function shouldUsePricePolling(): boolean {
|
||||
if (!priceWssEnabled.value) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !priceSocketConnected.value
|
||||
if (
|
||||
priceStreamLifecycle.value === 'connecting'
|
||||
|| priceStreamLifecycle.value === 'open'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { loadProductCategoryTabNames } from '@/utils/categoryTabs'
|
||||
import { getLastPrices, startLastPricePolling, stopLastPricePolling } from '@/utils/price'
|
||||
import {
|
||||
priceSocketConnected,
|
||||
priceStreamLifecycle,
|
||||
priceWssEnabled,
|
||||
shouldUsePricePolling,
|
||||
} from '@/utils/priceSocketState'
|
||||
@@ -127,14 +128,9 @@ function syncProductListPolling() {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [priceWssEnabled.value, priceSocketConnected.value] as const,
|
||||
([wssEnabled, connected], prev) => {
|
||||
const prevConnected = prev?.[1]
|
||||
() => [priceWssEnabled.value, priceSocketConnected.value, priceStreamLifecycle.value] as const,
|
||||
() => {
|
||||
syncProductListPolling()
|
||||
|
||||
if (wssEnabled && prevConnected && !connected && initialized.value) {
|
||||
void refreshProductsPageData()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -158,7 +154,9 @@ export function onProductsPageProviderMounted() {
|
||||
providerMountCount += 1
|
||||
pricePollingMountCount += 1
|
||||
if (pricePollingMountCount === 1) {
|
||||
void getLastPrices()
|
||||
if (shouldUsePricePolling()) {
|
||||
void getLastPrices()
|
||||
}
|
||||
startLastPricePolling()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user