added sse2

This commit is contained in:
2026-06-18 20:04:50 +03:30
parent f3aaf9d7c0
commit e27fdeb102
4 changed files with 62 additions and 29 deletions
+3 -7
View File
@@ -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
View File
@@ -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() {
+14 -2
View File
@@ -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()
}
}