import { TARGET } from '../../constants/target'; import { handleABTesting } from '../../helpers/ab-tests'; import { getDefaultCanTrack } from '../../helpers/canTrack'; import { logger } from '../../helpers/logger'; import { getPreviewContent } from '../../helpers/preview-lru-cache/get'; import { getSdkHeaders } from '../../helpers/sdk-headers'; import type { BuilderContent } from '../../types/builder-content'; import { fetch } from '../get-fetch'; import { isBrowser } from '../is-browser'; import { generateContentUrl } from './generate-content-url'; import type { GetContentOptions } from './types'; const checkContentHasResults = (content: ContentResponse): content is ContentResults => 'results' in content; /** * Returns the first content entry that matches the given options. */ export async function fetchOneEntry(options: GetContentOptions): Promise { const finalLocale = options.locale || options.userAttributes?.locale; if (finalLocale) { options.locale = finalLocale; options.userAttributes = { locale: finalLocale, ...options.userAttributes }; } const allContent = await fetchEntries({ ...options, limit: 1 }); if (allContent) { return allContent[0] || null; } return null; } type ContentResults = { results: BuilderContent[]; }; type ContentResponse = ContentResults | { status: number; message: string; }; const _fetchContent = async (options: GetContentOptions) => { const url = generateContentUrl(options); const _fetch = options.fetch ?? fetch; const fetchOptions = { ...options.fetchOptions, headers: { ...(options.fetchOptions as any)?.headers, ...getSdkHeaders() } }; const res = await _fetch(url.href, fetchOptions); const content = await (res.json() as Promise); return content; }; /** * @internal Exported only for testing purposes. Do not use. */ export const _processContentResult = async (options: GetContentOptions, content: ContentResults, url: URL = generateContentUrl(options)): Promise => { const canTrack = getDefaultCanTrack(options.canTrack); const isPreviewing = url.search.includes(`preview=`); if (TARGET === 'rsc' && isPreviewing) { const newResults: BuilderContent[] = []; for (const item of content.results) { const previewContent = getPreviewContent(url.searchParams); newResults.push(previewContent || item); } content.results = newResults; } if (!canTrack) return content.results; if (!(isBrowser() || TARGET === 'reactNative')) return content.results; /** * For client-side navigations, it is ideal to handle AB testing at this point instead of using our * complex multi-rendering variants approach, which is only needed for SSR'd content. * * This is also where react-native would handle AB testing. */ try { const newResults: BuilderContent[] = []; for (const item of content.results) { newResults.push(await handleABTesting({ item, canTrack })); } content.results = newResults; } catch (e) { logger.error('Could not process A/B tests. ', e); } return content.results; }; /** * Returns a paginated array of entries that match the given options. */ export async function fetchEntries(options: GetContentOptions) { const url = generateContentUrl(options); const content = await _fetchContent(options); if (!checkContentHasResults(content)) { logger.error('Error fetching data. ', { url, content, options }); throw content; } return _processContentResult(options, content); }