diff --git a/.cursor/skills/abshode-domain-lookup/learned-notes.md b/.cursor/skills/abshode-domain-lookup/learned-notes.md index 1802d4c..9c88df0 100644 --- a/.cursor/skills/abshode-domain-lookup/learned-notes.md +++ b/.cursor/skills/abshode-domain-lookup/learned-notes.md @@ -35,3 +35,5 @@ - 2026-10-05 | ?????? ?? ??? ????? | boutUs/components/AboutUsContentPanel.vue | ??? ?????? ???? ??? ???? ?? ????? ???? ?? ??? ?? - 2026-10-06 | بیلد آموزش ENOENT 1.png | EducateContentPanel.vue + assets/img/1.png,2.png,4.png + banner1/2 | فایل‌ها در گیت بودند و از دیسک پاک شده بودند؛ git restore - 2026-10-17 | باز/بسته خرید فروش محصول | `utils/productTradeOpen.ts` + `gold/index.vue` | `open_buy`/`open_sell`؛ بدون ProductItem هر دو باز +- 2026-10-17 | نمودار مهمان ۴۰۱ | `usePriceChart.ts` + `api/home.ts` + سرور `routes/api.php` `POST /api/price/all` | مهمان product/list نمی‌زند؛ تاریخچه نمودار از `/api/price/all` (همان priceData) +- 2026-10-17 | قیمت زنده مهمان | `bootstrap.ts` + `priceSocket.ts` + سرور `/api/price/stream` بدون auth | SSE بدون توکن؛ polling پشتیبان product/list فقط با توکن diff --git a/src/api/home.ts b/src/api/home.ts index d230c2e..be1c8bd 100644 --- a/src/api/home.ts +++ b/src/api/home.ts @@ -8,4 +8,7 @@ export const getPrice = async () => await useAxios.get(`/api/price/up`) export const getLastPrice = async () => await useAxios.get(`/api/price/last`) -export const getAllPrices = async () => await useAxios.post('/customer/prices/all') \ No newline at end of file +export const getAllPrices = async () => await useAxios.post('/customer/prices/all') + +/** نمودار مهمان — همان بدنهٔ prices/all، بدون توکن مشتری */ +export const getPublicChartPrices = async () => await useAxios.post('/api/price/all') \ No newline at end of file diff --git a/src/app/bootstrap.ts b/src/app/bootstrap.ts index c50da08..88ffc89 100644 --- a/src/app/bootstrap.ts +++ b/src/app/bootstrap.ts @@ -117,9 +117,7 @@ export async function bootstrapApp() { await withTimeout(settingsStore.fetchSettings(), 45000, 'Settings loading timeout') - if (getToken()) { - initPriceRealtimeChannel((key) => settingsStore.getSettingByKey(key)) - } + initPriceRealtimeChannel((key) => settingsStore.getSettingByKey(key)) if (getToken()) { await withTimeout(userStore.getMyInfo(), 45000, 'User session loading timeout') diff --git a/src/utils/priceSocket.ts b/src/utils/priceSocket.ts index 6020aa6..a48fb89 100644 --- a/src/utils/priceSocket.ts +++ b/src/utils/priceSocket.ts @@ -38,7 +38,7 @@ const STREAM_CONNECTING = 0 const STREAM_OPEN = 1 const STREAM_CLOSED = 2 -/** EventSource نمی‌تواند Authorization بفرستد؛ استریم همچنان auth:customer می‌خواهد. */ +/** fetch + ReadableStream — با توکن اختلاف قیمت ویژه؛ بدون توکن همان مظنهٔ عمومی (مهمان). */ class AuthPriceStream extends EventTarget { readyState = STREAM_CONNECTING onopen: (() => void) | null = null @@ -46,7 +46,7 @@ class AuthPriceStream extends EventTarget { onmessage: (() => void) | null = null private abort = new AbortController() - constructor(url: string, token: string) { + constructor(url: string, token?: string) { super() void this.start(url, token) } @@ -56,13 +56,18 @@ class AuthPriceStream extends EventTarget { this.abort.abort() } - private async start(url: string, token: string) { + private async start(url: string, token?: string) { try { + const headers: Record = { + Accept: 'text/event-stream', + } + const bearer = String(token ?? '').trim() + if (bearer) { + headers.Authorization = `Bearer ${bearer}` + } + const response = await fetch(url, { - headers: { - Accept: 'text/event-stream', - Authorization: `Bearer ${token}`, - }, + headers, cache: 'no-store', signal: this.abort.signal, }) @@ -191,10 +196,6 @@ function resolveStreamUrl(): string | null { return null } - if (!findStoredAuthToken()) { - return null - } - const base = String(getConfigApp('node_server') || window.location.origin).replace(/\/$/, '') return `${base}${resolveStreamPath()}` } @@ -301,10 +302,12 @@ async function runFallbackPoll() { '@/views/base/products/composables/useProductsPageData' ) - const [priceResult] = await Promise.allSettled([ - getLastPrices(), - refreshProductsPageData(), - ]) + const pollTasks: Promise[] = [getLastPrices()] + if (findStoredAuthToken()) { + pollTasks.push(refreshProductsPageData()) + } + + const [priceResult] = await Promise.allSettled(pollTasks) const priceOk = priceResult.status === 'fulfilled' @@ -643,7 +646,7 @@ function connect() { const url = resolveStreamUrl() const token = findStoredAuthToken() - if (!url || !token) { + if (!url) { priceSocketConnected.value = false priceStreamLifecycle.value = 'closed' clearConnectingLifecycle() @@ -661,7 +664,7 @@ function connect() { markConnectingLifecycle() try { - eventSource = new AuthPriceStream(url, token) + eventSource = new AuthPriceStream(url, token ?? undefined) } catch (error) { console.warn('Price SSE connect failed', error) handleStreamLost() @@ -824,10 +827,6 @@ export function isPriceWebSocketEnabled(settingGetter: (key: string) => unknown) return false } - if (!findStoredAuthToken()) { - return false - } - return resolveStreamUrl() !== null } diff --git a/src/views/base/home/composables/usePriceChart.ts b/src/views/base/home/composables/usePriceChart.ts index 8fce7b9..e1b2590 100644 --- a/src/views/base/home/composables/usePriceChart.ts +++ b/src/views/base/home/composables/usePriceChart.ts @@ -1,6 +1,7 @@ import { computed, ref, watch } from 'vue' -import { getAllPrices } from '@/api/home' +import { getAllPrices, getPublicChartPrices } from '@/api/home' import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog' +import { getToken } from '@/utils' import { getTradeBuyPrice, getTradeSellPrice } from '@/utils/price' export type ChartMetalType = 'katod' | 'maftol' | 'shamsh' @@ -132,7 +133,7 @@ async function fetchPrices(force = false) { chartFetchPromise = (async () => { try { chartLoading.value = true - const response = await getAllPrices() + const response = await (getToken() ? getAllPrices() : getPublicChartPrices()) const data = resolvePricesMap(response) cachedPrices.value = data applyTimelineFromCache() @@ -149,6 +150,14 @@ async function fetchPrices(force = false) { } async function loadMetalChartOffsets(force = false) { + if (!getToken()) { + if (!metalOffsetLoaded) { + syncChartSeries() + metalOffsetLoaded = true + } + return + } + if (!force && metalOffsetLoaded && catalogProducts.value.length > 0) { return } diff --git a/src/views/login/components/Login.vue b/src/views/login/components/Login.vue index 79cb63b..4719177 100644 --- a/src/views/login/components/Login.vue +++ b/src/views/login/components/Login.vue @@ -221,7 +221,7 @@
حساب کاربری ندارید؟ diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 7f2d852..b260749 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -8,9 +8,7 @@

برای ادامه، رمز ورود خود را انتخاب کنید.

-

حساب جدید

ثبت نام

-

اطلاعات خود را برای ایجاد حساب وارد کنید