import type { Language } from '../../runtime'; import { AsyncData } from '../../runtime'; import type { IVideoData } from '../../types'; import { resolveReferer } from '../../utils/use-fetch-video/resolve-referer'; // ?view — id/url ролика, на который переключились через плеер (next / автопереход). // Пока он есть и меняется, useAsyncData подсасывает свежие данные этого ролика, // не трогая path (landing остаётся в URL). export const useFetchVideo = async ( apiMethod: (...args: any[]) => Promise, ) => { const lang = useLang() as Language; const slug = useSlug(); const route = useRoute(); const router = useRouter(); const key = AsyncData.Video; // Cookie-зеркало referer'а: единственное клиент-записываемое хранилище, которое уходит // на сервер с запросом при reload → доступно во время SSR-фетча (sessionStorage/localStorage // клиентские, до SSR-фетча недоступны). Session-cookie (без maxAge/expires) ≈ семантике // сессии просмотра. sameSite: 'lax' — referer нужен только на своих переходах. const refererCookie = useCookie<{ type: string; value: string } | null>('video-referer', { path: '/', sameSite: 'lax', }); // Разбор предыдущей записи History API (нативно, без middleware). Три исхода: // - navigated:false — back нет: reload/прямой заход. referer из history неизвестен. // На сервере back пуст ВСЕГДА → SSR-фетч при reload попадает сюда и падает на cookie. // - navigated:true, referer:{...} — пришли из листинга (категория/тег/поиск): карточка-источник. // - navigated:true, referer:null — навигация была, но не из листинга (video→video related, // главная и т.п.): исходной карточки нет → дефолтный плейсхолдер, cookie сбрасываем. const historyState = computed(() => { const back = router.options.history.state.back; if (!back || typeof back !== 'string') return { navigated: false, referer: null }; const from = router.resolve(back); return { navigated: true, referer: resolveReferer(String(from.name || ''), from.params) }; }); // Была навигация → берём вычисленный из history referer (в т.ч. дефолт при video→video). // Не было (reload/прямой заход) → падаем на cookie от прошлого перехода. const referer = computed(() => (historyState.value.navigated ? historyState.value.referer : refererCookie.value) ?? null, ); // Синхронизируем cookie ТОЛЬКО при реальной навигации: из листинга — пишем карточку-источник, // из не-листинга — сбрасываем в дефолт, чтобы последующий reload не унаследовал чужую тумбу. // Пишем только на клиенте — на сервере cookie read-only. if (import.meta.client) { watch(historyState, (state) => { if (state.navigated) refererCookie.value = state.referer; }, { immediate: true }); } // view кладётся тем же ключом, что и основной фетч (url при FetchByUrlEnabled, // иначе id) — поэтому тот же apiMethod принимает и его без переключений. const view = computed(() => { const raw = route.query.view; const value = Array.isArray(raw) ? raw[0] : raw; return value ? String(value) : ''; }); const identifier = computed(() => view.value || slug.value); const stateKey = computed(() => `data-${key}-${identifier.value}-${lang}`); const headers: Record = {}; const event = useRequestEvent(); if (event) { const ua = event.node.req.headers['user-agent']; if (ua) headers['user-agent'] = ua; } if (import.meta.client && !headers['user-agent']) { headers['user-agent'] = navigator.userAgent; } // Ключ уникален на идентификатор (не константа AsyncData.Video): иначе при // переходе из ?view-состояния на реальный path кэш этого слота отдаёт данные // прошлого ролика вместо свежего фетча. const { data, status, error, refresh } = await useAsyncData( stateKey.value, () => useApiFetcher( stateKey.value, apiMethod, lang, identifier.value, undefined, headers, referer.value ?? undefined, )(), ); // Рефетч ТОЛЬКО при смене ?view (переключение ролика плеером/next-video без смены path). // НЕ следим за identifier/slug: смена path-параметра сама пересоздаёт страницу (дефолтный // key = путь с подставленным параметром) → setup перезапускается и фетчит заново. // Следить за slug нельзя: route.params.slug общий для всех [slug]-роутов, и при уходе на // /categories/ и т.п. watch дёргал бы getVideoById с чужим слагом (400 + ложный 404). // ?view при таком уходе не становится истинным, поэтому рефетча на уходе нет. watch(view, (value) => { if (value) refresh(); }); return { data, status, error }; };