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 { ref, watch } from 'vue'
|
||||||
import {
|
import {
|
||||||
priceSocketConnected,
|
priceSocketConnected,
|
||||||
|
priceStreamLifecycle,
|
||||||
priceWssEnabled,
|
priceWssEnabled,
|
||||||
shouldUsePricePolling,
|
shouldUsePricePolling,
|
||||||
} from '@/utils/priceSocketState'
|
} from '@/utils/priceSocketState'
|
||||||
@@ -63,14 +64,9 @@ function syncLastPricePolling() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [priceWssEnabled.value, priceSocketConnected.value] as const,
|
() => [priceWssEnabled.value, priceSocketConnected.value, priceStreamLifecycle.value] as const,
|
||||||
([wssEnabled, connected], prev) => {
|
() => {
|
||||||
const prevConnected = prev?.[1]
|
|
||||||
syncLastPricePolling()
|
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 type { Product } from '@/models/products'
|
||||||
import { Modal } from 'ant-design-vue'
|
import { Modal } from 'ant-design-vue'
|
||||||
import { getConfigApp } from '@/utils/config'
|
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_BASE_MS = 3000
|
||||||
const RECONNECT_MAX_MS = 30000
|
const RECONNECT_MAX_MS = 30000
|
||||||
const FAILURES_BEFORE_ALERT = 4
|
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 eventSource: EventSource | null = null
|
||||||
let subscribers = 0
|
let subscribers = 0
|
||||||
@@ -21,6 +23,7 @@ let reconnectAttempt = 0
|
|||||||
let lastStreamEventAt = 0
|
let lastStreamEventAt = 0
|
||||||
let networkAlertOpen = false
|
let networkAlertOpen = false
|
||||||
let listenersBound = false
|
let listenersBound = false
|
||||||
|
let fallbackPollTimer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
|
||||||
function isTruthySetting(value: unknown, defaultValue = true): boolean {
|
function isTruthySetting(value: unknown, defaultValue = true): boolean {
|
||||||
if (value === null || value === undefined || value === '') {
|
if (value === null || value === undefined || value === '') {
|
||||||
@@ -95,6 +98,23 @@ function startStaleCheck() {
|
|||||||
}, 15000)
|
}, 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() {
|
async function runFallbackPoll() {
|
||||||
if (!priceWssEnabled.value || priceSocketConnected.value) {
|
if (!priceWssEnabled.value || priceSocketConnected.value) {
|
||||||
return
|
return
|
||||||
@@ -211,6 +231,10 @@ function bindStreamEvents(source: EventSource) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
source.addEventListener('ping', () => {
|
||||||
|
touchStreamActivity()
|
||||||
|
})
|
||||||
|
|
||||||
source.onmessage = () => {
|
source.onmessage = () => {
|
||||||
touchStreamActivity()
|
touchStreamActivity()
|
||||||
}
|
}
|
||||||
@@ -220,9 +244,10 @@ function handleStreamLost(scheduleRetry = true) {
|
|||||||
const wasConnected = priceSocketConnected.value
|
const wasConnected = priceSocketConnected.value
|
||||||
disconnectSourceOnly()
|
disconnectSourceOnly()
|
||||||
priceSocketConnected.value = false
|
priceSocketConnected.value = false
|
||||||
|
priceStreamLifecycle.value = scheduleRetry ? 'connecting' : 'closed'
|
||||||
|
|
||||||
if (wasConnected || scheduleRetry) {
|
if ((wasConnected || scheduleRetry) && scheduleRetry) {
|
||||||
void runFallbackPoll()
|
scheduleFallbackPoll()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scheduleRetry && !stopped && subscribers > 0) {
|
if (scheduleRetry && !stopped && subscribers > 0) {
|
||||||
@@ -233,6 +258,7 @@ function handleStreamLost(scheduleRetry = true) {
|
|||||||
|
|
||||||
function disconnectSourceOnly() {
|
function disconnectSourceOnly() {
|
||||||
stopStaleCheck()
|
stopStaleCheck()
|
||||||
|
clearFallbackPollTimer()
|
||||||
if (eventSource) {
|
if (eventSource) {
|
||||||
eventSource.onopen = null
|
eventSource.onopen = null
|
||||||
eventSource.onerror = null
|
eventSource.onerror = null
|
||||||
@@ -250,6 +276,7 @@ function connect() {
|
|||||||
const url = resolveStreamUrl()
|
const url = resolveStreamUrl()
|
||||||
if (!url) {
|
if (!url) {
|
||||||
priceSocketConnected.value = false
|
priceSocketConnected.value = false
|
||||||
|
priceStreamLifecycle.value = 'closed'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +288,8 @@ function connect() {
|
|||||||
disconnectSourceOnly()
|
disconnectSourceOnly()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
priceStreamLifecycle.value = 'connecting'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
eventSource = new EventSource(url)
|
eventSource = new EventSource(url)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -274,7 +303,9 @@ function connect() {
|
|||||||
eventSource.onopen = () => {
|
eventSource.onopen = () => {
|
||||||
reconnectAttempt = 0
|
reconnectAttempt = 0
|
||||||
clearReconnectTimer()
|
clearReconnectTimer()
|
||||||
|
clearFallbackPollTimer()
|
||||||
networkAlertOpen = false
|
networkAlertOpen = false
|
||||||
|
priceStreamLifecycle.value = 'open'
|
||||||
priceSocketConnected.value = true
|
priceSocketConnected.value = true
|
||||||
touchStreamActivity()
|
touchStreamActivity()
|
||||||
startStaleCheck()
|
startStaleCheck()
|
||||||
@@ -282,13 +313,7 @@ function connect() {
|
|||||||
|
|
||||||
eventSource.onerror = () => {
|
eventSource.onerror = () => {
|
||||||
const state = eventSource?.readyState
|
const state = eventSource?.readyState
|
||||||
if (state === EventSource.OPEN) {
|
if (state === EventSource.OPEN || state === EventSource.CONNECTING) {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state === EventSource.CONNECTING) {
|
|
||||||
priceSocketConnected.value = false
|
|
||||||
void runFallbackPoll()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,8 +323,10 @@ function connect() {
|
|||||||
|
|
||||||
function disconnect() {
|
function disconnect() {
|
||||||
clearReconnectTimer()
|
clearReconnectTimer()
|
||||||
|
clearFallbackPollTimer()
|
||||||
disconnectSourceOnly()
|
disconnectSourceOnly()
|
||||||
priceSocketConnected.value = false
|
priceSocketConnected.value = false
|
||||||
|
priceStreamLifecycle.value = 'idle'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function forceReconnect() {
|
export function forceReconnect() {
|
||||||
|
|||||||
@@ -6,11 +6,23 @@ export const priceSocketConnected = ref(false)
|
|||||||
/** از settings در bootstrap ست میشود (کلید wss_enabled = قیمت لحظهای روشن) */
|
/** از settings در bootstrap ست میشود (کلید wss_enabled = قیمت لحظهای روشن) */
|
||||||
export const priceWssEnabled = ref(false)
|
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 {
|
export function shouldUsePricePolling(): boolean {
|
||||||
if (!priceWssEnabled.value) {
|
if (!priceWssEnabled.value) {
|
||||||
return true
|
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 { getLastPrices, startLastPricePolling, stopLastPricePolling } from '@/utils/price'
|
||||||
import {
|
import {
|
||||||
priceSocketConnected,
|
priceSocketConnected,
|
||||||
|
priceStreamLifecycle,
|
||||||
priceWssEnabled,
|
priceWssEnabled,
|
||||||
shouldUsePricePolling,
|
shouldUsePricePolling,
|
||||||
} from '@/utils/priceSocketState'
|
} from '@/utils/priceSocketState'
|
||||||
@@ -127,14 +128,9 @@ function syncProductListPolling() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [priceWssEnabled.value, priceSocketConnected.value] as const,
|
() => [priceWssEnabled.value, priceSocketConnected.value, priceStreamLifecycle.value] as const,
|
||||||
([wssEnabled, connected], prev) => {
|
() => {
|
||||||
const prevConnected = prev?.[1]
|
|
||||||
syncProductListPolling()
|
syncProductListPolling()
|
||||||
|
|
||||||
if (wssEnabled && prevConnected && !connected && initialized.value) {
|
|
||||||
void refreshProductsPageData()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -158,7 +154,9 @@ export function onProductsPageProviderMounted() {
|
|||||||
providerMountCount += 1
|
providerMountCount += 1
|
||||||
pricePollingMountCount += 1
|
pricePollingMountCount += 1
|
||||||
if (pricePollingMountCount === 1) {
|
if (pricePollingMountCount === 1) {
|
||||||
void getLastPrices()
|
if (shouldUsePricePolling()) {
|
||||||
|
void getLastPrices()
|
||||||
|
}
|
||||||
startLastPricePolling()
|
startLastPricePolling()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user