added assets into stream

This commit is contained in:
2026-06-27 16:02:16 +03:30
parent 4d004c3515
commit 2d491eb7a3
6 changed files with 132 additions and 23 deletions
+3 -1
View File
@@ -113,7 +113,9 @@ export async function bootstrapApp() {
await withTimeout(settingsStore.fetchSettings(), 45000, 'Settings loading timeout') await withTimeout(settingsStore.fetchSettings(), 45000, 'Settings loading timeout')
initPriceRealtimeChannel((key) => settingsStore.getSettingByKey(key)) if (getToken()) {
initPriceRealtimeChannel((key) => settingsStore.getSettingByKey(key))
}
if (getToken()) { if (getToken()) {
await withTimeout(userStore.getMyInfo(), 45000, 'User session loading timeout') await withTimeout(userStore.getMyInfo(), 45000, 'User session loading timeout')
+49 -8
View File
@@ -1,12 +1,57 @@
import { ref } from 'vue' import { ref } from 'vue'
import { getWalletAssets } from '@/api/wallet' import { getWalletAssets } from '@/api/wallet'
import { getProductsCatalog } from '@/composables/useProductsCatalog' import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog'
import { useWalletStore } from '@/store/walletStore'
import type { WalletAssetsSummary } from '@/models/walletAssets' import type { WalletAssetsSummary } from '@/models/walletAssets'
import { enrichInventoryCardsWithCatalog } from '@/utils/productAssets' import { enrichInventoryCardsWithCatalog } from '@/utils/productAssets'
const assetsSummary = ref<WalletAssetsSummary | null>(null) const assetsSummary = ref<WalletAssetsSummary | null>(null)
let assetsPromise: Promise<WalletAssetsSummary> | null = null let assetsPromise: Promise<WalletAssetsSummary> | null = null
function enrichAssetsSummary(data: WalletAssetsSummary): WalletAssetsSummary {
const catalog = getProductsCatalogRef().value
return {
...data,
inventory_cards: enrichInventoryCardsWithCatalog(data.inventory_cards ?? [], catalog ?? []),
count_products: enrichInventoryCardsWithCatalog(data.count_products ?? [], catalog ?? []),
}
}
function syncWalletStoreFromAssets(data: WalletAssetsSummary) {
const walletStore = useWalletStore()
if (!walletStore.walletInfo) {
return
}
const goldMetal = data.metal_totals?.find((metal) => metal.metal_id === 1) ?? data.metal_totals?.[0]
walletStore.walletInfo.cash = data.cash_wallet ?? walletStore.walletInfo.cash
if (goldMetal) {
walletStore.walletInfo.gold = goldMetal.melted_weight ?? walletStore.walletInfo.gold
walletStore.walletInfo.gold_sum = goldMetal.total_weight ?? walletStore.walletInfo.gold_sum
}
}
export async function applyWalletAssetsFromStream(payload: unknown) {
if (!payload || typeof payload !== 'object') {
return
}
const data = payload as WalletAssetsSummary
if (data.meta == null) {
return
}
if (!getProductsCatalogRef().value.length) {
await getProductsCatalog()
}
assetsSummary.value = enrichAssetsSummary(data)
syncWalletStoreFromAssets(assetsSummary.value)
}
export function useWalletAssets() { export function useWalletAssets() {
async function loadWalletAssets(force = false) { async function loadWalletAssets(force = false) {
if (!force && assetsSummary.value) { if (!force && assetsSummary.value) {
@@ -17,14 +62,10 @@ export function useWalletAssets() {
return assetsPromise return assetsPromise
} }
// Reuses cached getProductsCatalog() — same catalog as chart offset logic
assetsPromise = Promise.all([getWalletAssets(), getProductsCatalog(force)]) assetsPromise = Promise.all([getWalletAssets(), getProductsCatalog(force)])
.then(([data, catalog]) => { .then(([data]) => {
assetsSummary.value = { assetsSummary.value = enrichAssetsSummary(data)
...data, syncWalletStoreFromAssets(assetsSummary.value)
inventory_cards: enrichInventoryCardsWithCatalog(data.inventory_cards ?? [], catalog ?? []),
count_products: enrichInventoryCardsWithCatalog(data.count_products ?? [], catalog ?? []),
}
return assetsSummary.value return assetsSummary.value
}) })
.finally(() => { .finally(() => {
+2
View File
@@ -3,6 +3,7 @@ import {
findStoredAuthToken, findStoredAuthToken,
persistAuthToken, persistAuthToken,
} from '@/utils/authToken' } from '@/utils/authToken'
import { stopPriceRealtimeAuth } from '@/utils/priceSocket'
import { ref } from 'vue' import { ref } from 'vue'
import {useConfigStore} from "@/store/configStore"; import {useConfigStore} from "@/store/configStore";
@@ -85,6 +86,7 @@ export async function authenticateWithFingerprint() {
function removeToken() { function removeToken() {
stopPriceRealtimeAuth()
return clearStoredAuthTokens() return clearStoredAuthTokens()
} }
+74 -12
View File
@@ -2,6 +2,7 @@ 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 { findStoredAuthToken } from '@/utils/authToken'
import { priceSocketConnected, priceStreamLifecycle, priceWssEnabled } from '@/utils/priceSocketState' import { priceSocketConnected, priceStreamLifecycle, priceWssEnabled } from '@/utils/priceSocketState'
export { priceSocketConnected, priceStreamLifecycle, priceWssEnabled, shouldUsePricePolling } from '@/utils/priceSocketState' export { priceSocketConnected, priceStreamLifecycle, priceWssEnabled, shouldUsePricePolling } from '@/utils/priceSocketState'
@@ -56,8 +57,16 @@ function resolveStreamUrl(): string | null {
return null return null
} }
const token = findStoredAuthToken()
if (!token) {
return null
}
const base = String(getConfigApp('node_server') || window.location.origin).replace(/\/$/, '') const base = String(getConfigApp('node_server') || window.location.origin).replace(/\/$/, '')
return `${base}${resolveStreamPath()}` const url = new URL(`${base}${resolveStreamPath()}`)
url.searchParams.set('token', token)
return url.toString()
} }
function clearReconnectTimer() { function clearReconnectTimer() {
@@ -205,6 +214,11 @@ async function applyListFromStream(products: Product[]) {
applyProductsListFromSocket(products) applyProductsListFromSocket(products)
} }
async function applyAssetsFromStream(payload: unknown) {
const { applyWalletAssetsFromStream } = await import('@/composables/useWalletAssets')
applyWalletAssetsFromStream(payload)
}
function handleListPayload(payload: unknown) { function handleListPayload(payload: unknown) {
if (!Array.isArray(payload) || !payload.length) { if (!Array.isArray(payload) || !payload.length) {
return return
@@ -214,6 +228,15 @@ function handleListPayload(payload: unknown) {
void applyListFromStream(payload as Product[]) void applyListFromStream(payload as Product[])
} }
function handleAssetsPayload(payload: unknown) {
if (!payload || typeof payload !== 'object') {
return
}
touchStreamActivity()
void applyAssetsFromStream(payload)
}
function bindStreamEvents(source: EventSource) { function bindStreamEvents(source: EventSource) {
source.addEventListener('last', (event) => { source.addEventListener('last', (event) => {
try { try {
@@ -231,6 +254,14 @@ function bindStreamEvents(source: EventSource) {
} }
}) })
source.addEventListener('assets', (event) => {
try {
handleAssetsPayload(JSON.parse(event.data))
} catch (error) {
console.warn('Price SSE assets parse failed', error)
}
})
source.addEventListener('ping', () => { source.addEventListener('ping', () => {
touchStreamActivity() touchStreamActivity()
}) })
@@ -414,27 +445,58 @@ function onVisibilityChange() {
export function isPriceWebSocketEnabled(settingGetter: (key: string) => unknown): boolean { export function isPriceWebSocketEnabled(settingGetter: (key: string) => unknown): boolean {
getSetting = settingGetter getSetting = settingGetter
if (!isTruthySetting(settingGetter('wss_enabled'), true)) {
return false
}
if (!findStoredAuthToken()) {
return false
}
return resolveStreamUrl() !== null return resolveStreamUrl() !== null
} }
let channelStarted = false let channelStarted = false
export function initPriceRealtimeChannel(settingGetter: (key: string) => unknown) { function shutdownPriceRealtimeChannel() {
if (!channelStarted && subscribers <= 0) {
return
}
channelStarted = false
stopped = true
subscribers = 0
disconnect()
unbindBrowserNetworkListeners()
if (typeof document !== 'undefined') {
document.removeEventListener('visibilitychange', onVisibilityChange)
}
}
export function syncPriceRealtimeChannel(settingGetter: (key: string) => unknown) {
getSetting = settingGetter
priceWssEnabled.value = isTruthySetting(settingGetter('wss_enabled'), true) priceWssEnabled.value = isTruthySetting(settingGetter('wss_enabled'), true)
if (channelStarted) { if (!priceWssEnabled.value || !isPriceWebSocketEnabled(settingGetter)) {
shutdownPriceRealtimeChannel()
return
}
if (channelStarted && subscribers > 0) {
forceReconnect()
return return
} }
channelStarted = true channelStarted = true
stopped = false
if (!priceWssEnabled.value) {
return
}
if (!isPriceWebSocketEnabled(settingGetter)) {
return
}
startPriceSocket(settingGetter) startPriceSocket(settingGetter)
} }
export function initPriceRealtimeChannel(settingGetter: (key: string) => unknown) {
syncPriceRealtimeChannel(settingGetter)
}
export function stopPriceRealtimeAuth() {
shutdownPriceRealtimeChannel()
}
+3 -1
View File
@@ -26,7 +26,7 @@
'z-100 self-center w-36', 'z-100 self-center w-36',
config.config?.name === 'garoosi' config.config?.name === 'garoosi'
? 'h-auto object-contain' ? 'h-auto object-contain'
: 'rounded-full aspect-square', : 'rounded aspect-square',
]" ]"
style="margin-bottom: 60px" style="margin-bottom: 60px"
> >
@@ -375,6 +375,7 @@ import router from '@/router'
import { useUserStore } from '@/store/userStore' import { useUserStore } from '@/store/userStore'
import { useWalletStore } from '@/store/walletStore' import { useWalletStore } from '@/store/walletStore'
import { useSettingsStore } from '@/store/settingsStore' import { useSettingsStore } from '@/store/settingsStore'
import { syncPriceRealtimeChannel } from '@/utils/priceSocket'
import { setTruth } from '@/app/authBoot' import { setTruth } from '@/app/authBoot'
import {useConfigStore} from "@/store/configStore"; import {useConfigStore} from "@/store/configStore";
import { import {
@@ -480,6 +481,7 @@ async function handleOnComplete(value: string) {
async function login(accessToken: string | undefined) { async function login(accessToken: string | undefined) {
setToken(accessToken) setToken(accessToken)
syncPriceRealtimeChannel((key) => setting.getSettingByKey(key))
await userStore.getMyInfo() await userStore.getMyInfo()
// console.log(URLUserType()) // console.log(URLUserType())
// console.log(userStore.userInfo?.type) // console.log(userStore.userInfo?.type)
+1 -1
View File
@@ -26,7 +26,7 @@
'z-100 self-center w-36', 'z-100 self-center w-36',
config.config?.name === 'garoosi' config.config?.name === 'garoosi'
? 'h-auto object-contain mb-[60px]' ? 'h-auto object-contain mb-[60px]'
: 'rounded-full aspect-square', : 'rounded aspect-square',
]" ]"
> >
<div v-if="variant !== 'desktop'" class="font-bold text-center text-xl">ثبت نام</div> <div v-if="variant !== 'desktop'" class="font-bold text-center text-xl">ثبت نام</div>