lynkow 3.8.76 → 3.8.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/errors.ts","../src/utils/fetch.ts","../src/utils/query.ts","../src/services/base.ts","../src/services/contents.ts","../src/services/categories.ts","../src/services/tags.ts","../src/services/pages.ts","../src/services/blocks.ts","../src/utils/spam.ts","../src/services/forms.ts","../src/services/reviews.ts","../src/services/site.ts","../src/services/legal.ts","../src/services/cookies.ts","../src/services/seo.ts","../src/services/paths.ts","../src/core/environment.ts","../src/services/analytics.ts","../src/utils/theme.ts","../src/services/consent.ts","../src/services/branding.ts","../src/services/enhancements.ts","../src/services/media-helper.ts","../src/services/search.ts","../src/core/cache.ts","../src/core/logger.ts","../src/utils/events.ts","../src/utils/locale.ts","../src/client.ts","../src/types/index.ts","../src/utils/json-ld.ts"],"names":["LynkowError","_LynkowError","message","code","status","details","cause","response","body","error","isLynkowError","getErrorCode","fetchWithError","url","options","errorData","errors","buildQueryString","params","searchParams","key","value","CACHE_TTL","BaseService","config","path","query","baseUrl","queryString","locale","queryWithLocale","fetchOptions","cacheKey","ttl","pattern","localOptions","CACHE_PREFIX","ContentsService","filters","slug","CategoriesService","TagsService","PagesService","BlocksService","generateSpamFields","sessionStartTime","FormsService","data","spamFields","ReviewsService","slugOrId","result","SiteService","LegalService","CookiesService","preferences","SeoService","part","contentPath","normalizedPath","PathsService","isBrowser","isServer","browserOnly","fn","fallback","browserOnlyAsync","TRACKER_SCRIPT_ID","AnalyticsService","resolve","reject","checkLoaded","script","storedMode","event","parseBackgroundLuminance","bgColor","match","r","g","b","detectSiteTheme","html","el","attr","colorScheme","normalized","luminance","onSiteThemeChange","callback","currentTheme","cleanups","checkTheme","newTheme","observer","observeOptions","mq","handler","STORAGE_KEY","CONSENT_EXPIRY_MS","SCRIPT_ID_PREFIX","DEFAULT_CATEGORIES","ConsentService","events","action","vid","values","k","allTrue","v","allFalse","stored","categories","storedConsent","wrapper","theme","newCategories","category","accepted","scripts","elements","elId","newScript","scriptId","i","colors","textColor","innerDiv","resolvedTheme","isDark","hex","position","borderRadius","fontSize","floatingStyles","positionStyle","primaryColor","texts","label","currentCategories","categoriesHTML","cat","acceptBtn","rejectBtn","prefsBtn","_config","form","closeBtn","modal","e","formData","BADGE_CONTAINER_ID","STYLES_ID","BrandingService","styleElement","container","CLONE_ATTR","EXECUTABLE_SCRIPT_TYPES","COPY_ICON","CHECK_ICON","ENHANCEMENT_STYLES","EnhancementsService","iframe","button","codeBlock","codeElement","err","toActivate","s","original","replacement","attrs","MediaHelperService","imageUrl","widths","fit","quality","gravity","parsed","w","cdnCgiIndex","cdnBase","afterOptions","firstSlash","relativePath","sitesIndex","avatarsIndex","SearchService","DEFAULT_TTL","STORAGE_PREFIX","memoryCache","createCache","defaultTtl","prefix","getKey","isExpired","entry","get","fullKey","set","remove","invalidate","keysToRemove","getOrSet","factory","cached","createLogger","args","level","createEventEmitter","listeners","on","listener","off","eventListeners","emit","once","wrappedListener","removeAllListeners","findLocale","candidate","enabledLocales","lower","l","detectLocale","defaultLocale","getStoredLocale","pathLocale","getLocaleFromPath","htmlLang","exactMatch","base","baseMatch","setStoredLocale","segments","firstSegment","isValidLocale","DEFAULT_BASE_URL","createClient","cache","logger","normalizedBaseUrl","internalConfig","state","services","updateServicesLocale","initialize","siteConfig","detected","createLynkowClient","isContentResolve","isCategoryResolve","renderJsonLdGraph","nodes","graph","node","_ignoredContext","rest"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error handling utilities for Lynkow SDK\n */\n\n/**\n * Error codes for Lynkow SDK errors.\n *\n * Each code maps to a specific category of failure. Use these in `catch` blocks\n * to handle different error types:\n *\n * | Code | HTTP Status | When it occurs |\n * |----------------------|-------------|-------------------------------------------------------------|\n * | `BAD_REQUEST` | 400 | Malformed request (missing required fields, bad format) |\n * | `VALIDATION_ERROR` | 400 | Request body fails server-side validation (see `details`) |\n * | `UNAUTHORIZED` | 401 | Missing or invalid authentication (bad site ID) |\n * | `FORBIDDEN` | 403 | Valid auth but insufficient permissions |\n * | `NOT_FOUND` | 404 | Resource does not exist or is not published |\n * | `RATE_LIMITED` | 429 | Too many requests; retry after the indicated delay |\n * | `TOO_MANY_REQUESTS` | 429 | Alias for `RATE_LIMITED` |\n * | `TIMEOUT` | - | Request was aborted due to timeout (client-side) |\n * | `NETWORK_ERROR` | - | DNS failure, connection refused, or network offline |\n * | `INTERNAL_ERROR` | 500 | Server-side error (bug or infrastructure issue) |\n * | `SERVICE_UNAVAILABLE`| 503 | API is temporarily down for maintenance |\n * | `UNKNOWN` | other | Unrecognized HTTP status or unexpected error |\n */\nexport type ErrorCode =\n | 'BAD_REQUEST'\n | 'VALIDATION_ERROR'\n | 'UNAUTHORIZED'\n | 'FORBIDDEN'\n | 'NOT_FOUND'\n | 'RATE_LIMITED'\n | 'TOO_MANY_REQUESTS'\n | 'TIMEOUT'\n | 'NETWORK_ERROR'\n | 'INTERNAL_ERROR'\n | 'SERVICE_UNAVAILABLE'\n | 'UNKNOWN'\n\n/**\n * A single error detail from the API's response body.\n * The API returns an array of these in the `errors` field of error responses.\n * For validation errors, each detail corresponds to one invalid field.\n */\nexport interface ApiErrorDetail {\n /**\n * Human-readable error message describing what went wrong.\n * Suitable for display to end users or logging.\n *\n * @example 'The title field is required'\n * @example 'Slug already exists'\n */\n message: string\n\n /**\n * Name of the form/request field that caused the validation error.\n * Only present for `VALIDATION_ERROR` responses.\n * `undefined` for non-field-specific errors (e.g. auth, rate limiting).\n * Use this to display inline validation errors next to form fields.\n *\n * @example 'title', 'email', 'rating'\n */\n field?: string\n\n /**\n * Machine-readable error rule or code from the validation layer.\n * Can be used for programmatic error handling or i18n error message lookups.\n * `undefined` when the API does not provide a structured code.\n *\n * @example 'required', 'unique', 'minLength'\n */\n code?: string\n}\n\n/**\n * Custom error class for Lynkow SDK.\n * Wraps HTTP errors, network failures, and timeouts into a structured format\n * with typed error codes, HTTP status, and optional field-level details.\n *\n * Use the `isLynkowError()` type guard to safely narrow caught errors.\n *\n * @example\n * ```typescript\n * try {\n * await lynkow.contents.getBySlug('my-article')\n * } catch (error) {\n * if (isLynkowError(error)) {\n * switch (error.code) {\n * case 'NOT_FOUND':\n * // Show 404 page\n * break\n * case 'VALIDATION_ERROR':\n * // Display field errors from error.details\n * break\n * case 'RATE_LIMITED':\n * // Back off and retry\n * break\n * }\n * }\n * }\n * ```\n */\nexport class LynkowError extends Error {\n /**\n * Error name, always `'LynkowError'`.\n * Useful for quick identification in logs and error monitoring tools.\n */\n override readonly name = 'LynkowError'\n\n /**\n * Categorized error code for programmatic handling.\n * See {@link ErrorCode} for the full list and when each code occurs.\n */\n readonly code: ErrorCode\n\n /**\n * HTTP status code from the server response.\n * `undefined` for client-side errors (`TIMEOUT`, `NETWORK_ERROR`)\n * where no HTTP response was received.\n *\n * @example 404, 429, 500\n */\n readonly status?: number\n\n /**\n * Array of detailed error information from the API response body.\n * Present for `VALIDATION_ERROR` responses where each entry describes\n * a specific field's validation failure. `undefined` for non-validation errors\n * or when the server response body could not be parsed.\n */\n readonly details?: ApiErrorDetail[]\n\n /**\n * The original `Error` that caused this `LynkowError`.\n * Present when wrapping a network error (`TypeError`) or abort error (`AbortError`).\n * `undefined` when the error originated from an HTTP response.\n */\n override readonly cause?: Error\n\n constructor(\n message: string,\n code: ErrorCode,\n status?: number,\n details?: ApiErrorDetail[],\n cause?: Error\n ) {\n super(message)\n this.code = code\n this.status = status\n this.details = details\n this.cause = cause\n\n // Maintain proper stack trace in V8 environments\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, LynkowError)\n }\n }\n\n /**\n * Create a `LynkowError` from an HTTP `Response` object.\n * Attempts to parse the response body as JSON to extract error details.\n * Falls back to the HTTP status text when the body is not valid JSON.\n *\n * @param response - The HTTP Response object with a non-2xx status\n * @returns A new `LynkowError` with the appropriate code, status, and details\n */\n static async fromResponse(response: Response): Promise<LynkowError> {\n const status = response.status\n let message = `HTTP ${status}`\n let details: ApiErrorDetail[] | undefined\n\n try {\n const body = await response.json()\n if (body.errors && Array.isArray(body.errors)) {\n details = body.errors\n message = body.errors[0]?.message || message\n } else if (body.error) {\n message = body.error\n } else if (body.message) {\n message = body.message\n }\n } catch {\n // Response body is not JSON, use status text\n message = response.statusText || message\n }\n\n const code = LynkowError.statusToCode(status)\n return new LynkowError(message, code, status, details)\n }\n\n /**\n * Create a `LynkowError` from a client-side network or abort error.\n *\n * - `AbortError` (from `AbortController`) becomes `TIMEOUT`\n * - `TypeError` (from `fetch()` DNS/connection failures) becomes `NETWORK_ERROR`\n * - Any other error becomes `UNKNOWN`\n *\n * @param error - The original error thrown by `fetch()`\n * @returns A new `LynkowError` wrapping the original error\n */\n static fromNetworkError(error: Error): LynkowError {\n if (error.name === 'AbortError') {\n return new LynkowError('Request timed out', 'TIMEOUT', undefined, undefined, error)\n }\n\n if (error.name === 'TypeError') {\n return new LynkowError(\n 'Network error - please check your connection',\n 'NETWORK_ERROR',\n undefined,\n undefined,\n error\n )\n }\n\n return new LynkowError(error.message || 'Unknown error', 'UNKNOWN', undefined, undefined, error)\n }\n\n /**\n * Map HTTP status code to error code\n */\n private static statusToCode(status: number): ErrorCode {\n switch (status) {\n case 400:\n return 'VALIDATION_ERROR'\n case 401:\n return 'UNAUTHORIZED'\n case 403:\n return 'FORBIDDEN'\n case 404:\n return 'NOT_FOUND'\n case 429:\n return 'RATE_LIMITED'\n default:\n return 'UNKNOWN'\n }\n }\n\n /**\n * Serialize the error to a plain object.\n * Useful for logging, error reporting services, and JSON serialization.\n * Does not include `cause` or stack trace.\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n status: this.status,\n details: this.details,\n }\n }\n}\n\n/**\n * Type guard to check if an unknown value is a `LynkowError`.\n * Use this in `catch` blocks to safely access `LynkowError` properties.\n *\n * @param error - The caught value to check\n * @returns `true` if the value is an instance of `LynkowError`\n *\n * @example\n * ```typescript\n * try {\n * await lynkow.contents.getBySlug('not-found')\n * } catch (error) {\n * if (isLynkowError(error) && error.code === 'NOT_FOUND') {\n * // Handle 404 -- error.status is 404\n * }\n * }\n * ```\n */\nexport function isLynkowError(error: unknown): error is LynkowError {\n return error instanceof LynkowError\n}\n","import { LynkowError, type ErrorCode, type ApiErrorDetail } from '../core/errors'\n\n/**\n * Converts an HTTP status code to an error code\n */\nfunction getErrorCode(status: number): ErrorCode {\n switch (status) {\n case 400:\n return 'BAD_REQUEST'\n case 401:\n return 'UNAUTHORIZED'\n case 403:\n return 'FORBIDDEN'\n case 404:\n return 'NOT_FOUND'\n case 422:\n return 'VALIDATION_ERROR'\n case 429:\n return 'TOO_MANY_REQUESTS'\n case 503:\n return 'SERVICE_UNAVAILABLE'\n default:\n return 'INTERNAL_ERROR'\n }\n}\n\n/**\n * Performs a fetch request with error handling\n */\nexport async function fetchWithError<T>(\n url: string,\n options: RequestInit\n): Promise<T> {\n let response: Response\n\n try {\n response = await fetch(url, options)\n } catch (error) {\n // Network error\n throw new LynkowError(\n 'Network error: Unable to reach the server',\n 'NETWORK_ERROR',\n 0,\n [{ message: error instanceof Error ? error.message : 'Unknown error' }]\n )\n }\n\n // OK response\n if (response.ok) {\n return response.json()\n }\n\n // HTTP error\n let errorData: Record<string, unknown> = {}\n try {\n errorData = await response.json()\n } catch {\n // No JSON body\n }\n\n const code = getErrorCode(response.status)\n const message =\n (errorData['error'] as string) ||\n (errorData['message'] as string) ||\n `HTTP error: ${response.status}`\n const errors: ApiErrorDetail[] =\n (errorData['errors'] as ApiErrorDetail[]) || [{ message }]\n\n throw new LynkowError(message, code, response.status, errors)\n}\n","/**\n * Serialize a flat object into a URL-encoded query string, dropping any\n * entry whose value is `undefined`, `null`, or the empty string so that\n * optional filters do not appear as `key=` in the final URL.\n *\n * Non-string values are coerced with `String(value)`; arrays and nested\n * objects are therefore serialized via their default `toString()` and\n * should be pre-flattened by the caller if a specific shape is needed.\n *\n * @param params - Flat record of query parameters. Order is preserved.\n * @returns URL-encoded query string without a leading `?`. Returns an\n * empty string when every value was filtered out.\n *\n * @example\n * ```typescript\n * buildQueryString({ page: 1, search: 'ab', tag: undefined, limit: '' })\n * // → 'page=1&search=ab'\n *\n * const url = `/api/items?${buildQueryString({ category: 'tech' })}`\n * ```\n */\nexport function buildQueryString(params: Record<string, unknown>): string {\n const searchParams = new URLSearchParams()\n\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null && value !== '') {\n searchParams.append(key, String(value))\n }\n }\n\n return searchParams.toString()\n}\n","import type { BaseRequestOptions } from '../types'\nimport type { Cache } from '../core/cache'\nimport { fetchWithError } from '../utils/fetch'\nimport { buildQueryString } from '../utils/query'\n\n/**\n * Normalized internal configuration\n */\nexport interface InternalConfig {\n siteId: string\n baseUrl: string\n locale?: string\n fetchOptions: RequestInit\n /** Optional cache manager for caching responses */\n cache?: Cache\n}\n\n/** Cache TTL constants (in milliseconds) */\nexport const CACHE_TTL = {\n /** Short TTL for frequently changing data (5 minutes) */\n SHORT: 5 * 60 * 1000,\n /** Medium TTL for moderately changing data (10 minutes) */\n MEDIUM: 10 * 60 * 1000,\n /** Long TTL for rarely changing data (30 minutes) */\n LONG: 30 * 60 * 1000,\n} as const\n\n/**\n * Base service that all specific services inherit from\n */\nexport abstract class BaseService {\n protected config: InternalConfig\n protected cache?: Cache\n\n constructor(config: InternalConfig) {\n this.config = config\n this.cache = config.cache\n }\n\n /**\n * Builds the full URL for an endpoint\n */\n protected buildEndpointUrl(\n path: string,\n query?: Record<string, unknown>\n ): string {\n const baseUrl = `${this.config.baseUrl}/public/${this.config.siteId}${path}`\n\n if (query && Object.keys(query).length > 0) {\n const queryString = buildQueryString(query)\n return `${baseUrl}?${queryString}`\n }\n\n return baseUrl\n }\n\n /**\n * Performs a GET request\n */\n protected async get<T>(\n path: string,\n query?: Record<string, unknown>,\n options?: BaseRequestOptions\n ): Promise<T> {\n // Add locale if configured\n const locale = options?.locale || this.config.locale\n const queryWithLocale = locale ? { ...query, locale } : query\n\n const url = this.buildEndpointUrl(path, queryWithLocale)\n const fetchOptions = this.mergeFetchOptions(options?.fetchOptions)\n\n return fetchWithError<T>(url, {\n method: 'GET',\n ...fetchOptions,\n })\n }\n\n /**\n * Performs a cached GET request\n * If cache is available and data exists, returns cached data\n * Otherwise fetches from API and caches the result\n *\n * @param cacheKey - Unique cache key\n * @param path - API endpoint path\n * @param query - Query parameters\n * @param options - Request options\n * @param ttl - Cache TTL in milliseconds (default: SHORT)\n */\n protected async getWithCache<T>(\n cacheKey: string,\n path: string,\n query?: Record<string, unknown>,\n options?: BaseRequestOptions,\n ttl: number = CACHE_TTL.SHORT\n ): Promise<T> {\n // If cache is available, use getOrSet\n if (this.cache) {\n return this.cache.getOrSet<T>(\n cacheKey,\n () => this.get<T>(path, query, options),\n ttl\n )\n }\n\n // No cache, just fetch\n return this.get<T>(path, query, options)\n }\n\n /**\n * Invalidates cache entries matching a pattern\n * @param pattern - Pattern to match cache keys\n */\n protected invalidateCache(pattern?: string): void {\n this.cache?.invalidate(pattern)\n }\n\n /**\n * Performs a POST request\n */\n protected async post<T>(\n path: string,\n body: Record<string, unknown>,\n options?: BaseRequestOptions\n ): Promise<T> {\n const url = this.buildEndpointUrl(path)\n const fetchOptions = this.mergeFetchOptions(options?.fetchOptions)\n\n return fetchWithError<T>(url, {\n method: 'POST',\n ...fetchOptions,\n headers: {\n 'Content-Type': 'application/json',\n ...fetchOptions.headers,\n },\n body: JSON.stringify(body),\n })\n }\n\n /**\n * Performs a GET request that returns plain text (XML, txt)\n */\n protected async getText(\n path: string,\n options?: BaseRequestOptions\n ): Promise<string> {\n const url = this.buildEndpointUrl(path)\n const fetchOptions = this.mergeFetchOptions(options?.fetchOptions)\n\n const response = await fetch(url, {\n method: 'GET',\n ...fetchOptions,\n })\n\n if (!response.ok) {\n throw new Error(`HTTP error: ${response.status}`)\n }\n\n return response.text()\n }\n\n /**\n * Merges local fetch options with global ones\n */\n private mergeFetchOptions(localOptions?: RequestInit): RequestInit {\n return {\n ...this.config.fetchOptions,\n ...localOptions,\n headers: {\n ...this.config.fetchOptions.headers,\n ...localOptions?.headers,\n },\n }\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n Content,\n ContentsFilters,\n ContentsListResponse,\n BaseRequestOptions,\n} from '../types'\n\nconst CACHE_PREFIX = 'contents_'\n\n/**\n * Service for retrieving published blog articles (contents).\n *\n * Accessible via `lynkow.contents`. All methods return only published content\n * visible to the public API. Responses are cached in-memory for 5 minutes\n * (SHORT TTL) when a cache adapter is configured on the client.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // List latest articles\n * const { data, meta } = await lynkow.contents.list({ limit: 10 })\n *\n * // Fetch a single article by slug\n * const article = await lynkow.contents.getBySlug('my-article')\n * ```\n */\nexport class ContentsService extends BaseService {\n /**\n * Retrieves a paginated list of published blog articles, sorted by\n * publication date (newest first by default). Results are cached for\n * 5 minutes per unique filter combination.\n *\n * @param filters - Optional filters to narrow down results:\n * - `page` / `limit` — pagination (defaults to page 1, 15 items)\n * - `category` — filter by category slug (e.g. `'tech'`, `'news'`)\n * - `tag` — filter by tag slug (e.g. `'featured'`)\n * - `search` — full-text search across title and body\n * - `sort` / `order` — sort field and direction (`'asc'` or `'desc'`)\n * - `locale` — override the client's default locale\n * @param options - Base request options (locale override, custom fetch options)\n * @returns An object containing `data` (array of `ContentSummary` with id, title, slug,\n * path, excerpt, featuredImage, publishedAt) and `meta` (pagination info with\n * total, currentPage, lastPage, perPage, hasMorePages)\n * @throws {LynkowError} With code `'NETWORK_ERROR'` if the API is unreachable\n *\n * @example\n * ```typescript\n * // Fetch the first page of articles in the \"tech\" category\n * const { data, meta } = await lynkow.contents.list({\n * page: 1,\n * limit: 10,\n * category: 'tech'\n * })\n *\n * // Search for articles\n * const results = await lynkow.contents.list({ search: 'typescript' })\n * ```\n */\n async list(\n filters?: ContentsFilters,\n options?: BaseRequestOptions\n ): Promise<ContentsListResponse> {\n const query: Record<string, unknown> = {}\n\n if (filters?.page) query['page'] = filters.page\n if (filters?.limit ?? filters?.perPage) query['limit'] = filters?.limit ?? filters?.perPage\n if (filters?.category) query['category'] = filters.category\n if (filters?.tag) query['tag'] = filters.tag\n if (filters?.search) query['search'] = filters.search\n if (filters?.sort) query['sort'] = filters.sort\n if (filters?.order) query['order'] = filters.order\n if (filters?.locale) query['locale'] = filters.locale\n\n const cacheKey = `${CACHE_PREFIX}list_${JSON.stringify(filters || {})}`\n return this.getWithCache<ContentsListResponse>(\n cacheKey,\n '/contents',\n query,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Retrieves a single published article by its URL-friendly slug.\n * Returns the full content including body (HTML), SEO metadata,\n * author, categories, tags, and structured data (JSON-LD).\n * Cached for 5 minutes per slug+locale combination.\n *\n * @param slug - The unique URL slug of the content (e.g. `'my-article'`, `'getting-started-with-typescript'`)\n * @param options - Request options; use `locale` to fetch a specific localized version\n * @returns The full `Content` object with body HTML, SEO fields (metaTitle, metaDescription,\n * canonicalUrl, ogImage), author info, associated categories/tags, and structuredData\n * @throws {LynkowError} With code `'NOT_FOUND'` if no published content matches the given slug\n *\n * @example\n * ```typescript\n * const content = await lynkow.contents.getBySlug('my-article')\n * console.log(content.title) // Article title\n * console.log(content.body) // HTML body string\n * console.log(content.author?.fullName) // Author name\n * console.log(content.categories) // Associated categories\n * ```\n */\n async getBySlug(\n slug: string,\n options?: BaseRequestOptions\n ): Promise<Content> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}slug_${slug}_${locale || 'default'}`\n return this.getWithCache<Content>(\n cacheKey,\n `/contents/slug/${encodeURIComponent(slug)}`,\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Invalidate every cached response produced by this service (list\n * queries, slug lookups, single-content fetches). Call after an admin\n * mutation or on receipt of a `content.*` webhook so the next public\n * request bypasses the 5-minute SWR cache and hits the origin.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // In a Next.js route handler receiving a Lynkow webhook:\n * if (event.type === 'content.updated') {\n * lynkow.contents.clearCache()\n * }\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n CategoriesListResponse,\n CategoryTreeResponse,\n CategoryDetailResponse,\n CategoryOptions,\n BaseRequestOptions,\n} from '../types'\n\nconst CACHE_PREFIX = 'categories_'\n\n/**\n * Service for retrieving content categories and their hierarchical structure.\n *\n * Accessible via `lynkow.categories`. Categories organize blog articles and can be\n * nested (parent/child). Each category includes a content count and optional image.\n * Responses are cached for 5 minutes (SHORT TTL) when a cache adapter is configured.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Flat list with content counts\n * const { data } = await lynkow.categories.list()\n *\n * // Hierarchical tree\n * const { data } = await lynkow.categories.tree()\n *\n * // Category detail with paginated articles\n * const { category, contents } = await lynkow.categories.getBySlug('tech')\n * ```\n */\nexport class CategoriesService extends BaseService {\n /**\n * Retrieves a flat list of all categories with their content counts.\n * Useful for building navigation menus, sidebars, or category filters.\n * Cached for 5 minutes per locale.\n *\n * @param options - Request options; use `locale` to fetch localized category names\n * @returns An object containing `data` (array of `CategoryWithCount` with name, slug, path,\n * contentCount, image, description), `blogUrlMode` (`'flat'` or `'nested'`), and `locale`\n *\n * @example\n * ```typescript\n * const { data, blogUrlMode } = await lynkow.categories.list()\n * data.forEach(cat => {\n * console.log(`${cat.name} (${cat.contentCount} articles)`)\n * })\n * ```\n */\n async list(options?: BaseRequestOptions): Promise<CategoriesListResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}list_${locale || 'default'}`\n return this.getWithCache<CategoriesListResponse>(\n cacheKey,\n '/categories',\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Retrieves the full category hierarchy as a nested tree structure.\n * Root categories appear at the top level, each with a `children` array\n * containing their subcategories (recursively). Useful for building\n * hierarchical navigation or breadcrumbs. Cached for 5 minutes per locale.\n *\n * @param options - Request options; use `locale` to fetch localized category names\n * @returns An object containing `data` (array of `CategoryTreeNode`, each with a\n * `children` array of nested subcategories), `blogUrlMode`, and `locale`\n *\n * @example\n * ```typescript\n * const { data } = await lynkow.categories.tree()\n * // Iterate root categories and their children\n * data.forEach(root => {\n * console.log(root.name)\n * root.children.forEach(child => {\n * console.log(` - ${child.name}`)\n * })\n * })\n * ```\n */\n async tree(options?: BaseRequestOptions): Promise<CategoryTreeResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}tree_${locale || 'default'}`\n return this.getWithCache<CategoryTreeResponse>(\n cacheKey,\n '/categories/tree',\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Retrieves a single category by its slug, along with a paginated list of\n * the published articles that belong to it. Cached for 5 minutes per\n * slug+locale+pagination combination.\n *\n * @param slug - The unique URL slug of the category (e.g. `'tech'`, `'news'`, `'tutorials'`)\n * @param options - Combined category and request options:\n * - `page` / `limit` — pagination for the category's articles (defaults to page 1)\n * - `locale` — override the client's default locale\n * @returns An object containing `category` (full `CategoryDetail` with name, slug, path,\n * description, image, contentCount, parentId), `contents` (paginated `ContentSummary`\n * array with `data` and `meta`), and `locale`\n * @throws {LynkowError} With code `'NOT_FOUND'` if no category matches the given slug\n *\n * @example\n * ```typescript\n * const { category, contents } = await lynkow.categories.getBySlug('tech', {\n * page: 1,\n * limit: 10\n * })\n * console.log(category.name) // \"Tech\"\n * console.log(contents.data.length) // Number of articles on this page\n * console.log(contents.meta.hasMorePages) // Whether more pages exist\n * ```\n */\n async getBySlug(\n slug: string,\n options?: CategoryOptions & BaseRequestOptions\n ): Promise<CategoryDetailResponse> {\n const query: Record<string, unknown> = {}\n\n if (options?.page) query['page'] = options.page\n if (options?.limit ?? options?.perPage) query['limit'] = options?.limit ?? options?.perPage\n\n const cacheKey = `${CACHE_PREFIX}slug_${slug}_${JSON.stringify(options || {})}`\n return this.getWithCache<CategoryDetailResponse>(\n cacheKey,\n `/categories/${encodeURIComponent(slug)}`,\n query,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Invalidate every cached category response (flat list, hierarchy\n * tree, detail views with paginated contents). Call after an admin\n * mutation or on receipt of a `category.*` webhook so the next public\n * request bypasses the 30-minute SWR cache.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // On category restructuring:\n * lynkow.categories.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type { TagsListResponse, BaseRequestOptions } from '../types'\n\nconst CACHE_PREFIX = 'tags_'\n\n/**\n * Service for retrieving content tags.\n *\n * Accessible via `lynkow.tags`. Tags are lightweight labels attached to\n * blog articles for cross-cutting classification (unlike categories which\n * are hierarchical). Responses are cached for 5 minutes (SHORT TTL).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n * const { data } = await lynkow.tags.list()\n * ```\n */\nexport class TagsService extends BaseService {\n /**\n * Retrieves the complete list of tags available on the site.\n * Returns all tags (no pagination). Cached for 5 minutes per locale.\n *\n * @param options - Request options; use `locale` to fetch localized tag names\n * @returns An object containing `data` (array of `Tag` with id, name, slug, and locale)\n *\n * @example\n * ```typescript\n * const { data } = await lynkow.tags.list()\n * data.forEach(tag => console.log(tag.name)) // \"Featured\", \"Tutorial\", etc.\n * ```\n */\n async list(options?: BaseRequestOptions): Promise<TagsListResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}list_${locale || 'default'}`\n return this.getWithCache<TagsListResponse>(\n cacheKey,\n '/tags',\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Invalidate every cached tag response (lists, tag-filtered contents).\n * Call after an admin mutation or on receipt of a `tag.*` webhook so\n * the next public request bypasses the SWR cache.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.tags.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type { Page, PagesListResponse, BaseRequestOptions } from '../types'\n\nconst CACHE_PREFIX = 'pages_'\n\n/**\n * Options for listing pages\n */\nexport interface PagesListOptions extends BaseRequestOptions {\n /**\n * Restrict results to pages carrying this tag slug (e.g. `'legal'` to\n * list Privacy Policy + Terms of Service). Tags are declared on each\n * page in the admin under SEO / Organization and used mainly for\n * grouping unrelated pages that share a purpose.\n *\n * Omit to return every published page for the site.\n */\n tag?: string\n}\n\n/**\n * Service for retrieving published pages (Site Blocks of type \"page\").\n *\n * Accessible via `lynkow.pages`. Pages are CMS-managed, schema-driven content\n * blocks (e.g. \"About\", \"Contact\", legal pages). Unlike blog articles, pages\n * use DataSource-resolved data instead of a rich text body. Responses are\n * cached for 5 minutes (SHORT TTL) when a cache adapter is configured.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // List all pages\n * const { data } = await lynkow.pages.list()\n *\n * // Fetch a page by slug or path\n * const page = await lynkow.pages.getBySlug('about')\n * const page = await lynkow.pages.getByPath('/services/consulting')\n * ```\n */\nexport class PagesService extends BaseService {\n /**\n * Retrieves a list of all published pages on the site. Optionally\n * filter by tag to get a subset (e.g. legal documents). Returns page\n * summaries without resolved data. Cached for 5 minutes per locale+tag.\n *\n * @param options - Request options:\n * - `tag` — filter pages by tag slug (e.g. `'legal'` for privacy policy, terms, etc.)\n * - `locale` — override the client's default locale\n * @returns An object containing `data` (array of `PageSummary` with id, slug, name, path,\n * locale, and updatedAt)\n *\n * @example\n * ```typescript\n * // List all pages\n * const { data } = await lynkow.pages.list()\n *\n * // List only legal documents\n * const { data } = await lynkow.pages.list({ tag: 'legal' })\n * ```\n */\n async list(options?: PagesListOptions): Promise<PagesListResponse> {\n const locale = options?.locale || this.config.locale\n const query: Record<string, unknown> = {}\n if (options?.tag) query['tag'] = options.tag\n\n const cacheKey = `${CACHE_PREFIX}list_${locale || 'default'}_${options?.tag || 'all'}`\n return this.getWithCache<PagesListResponse>(\n cacheKey,\n '/pages',\n query,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Retrieves a single page by its slug, including fully resolved DataSource data,\n * SEO settings, and alternate locale versions. Cached for 5 minutes per slug+locale.\n *\n * @param slug - The unique URL slug of the page (e.g. `'about'`, `'contact'`, `'privacy-policy'`)\n * @param options - Request options; use `locale` to fetch a specific localized version\n * @returns The full `Page` object with `data` (resolved DataSource values), `seo`\n * (meta title, description, OG/Twitter tags, canonical URL), and `alternates`\n * (other locale versions with their paths)\n * @throws {LynkowError} With code `'NOT_FOUND'` if no published page matches the slug\n *\n * @example\n * ```typescript\n * const page = await lynkow.pages.getBySlug('about')\n * console.log(page.data) // Resolved DataSource content\n * console.log(page.seo) // SEO metadata\n * console.log(page.alternates) // Other locale versions\n * ```\n */\n async getBySlug(slug: string, options?: BaseRequestOptions): Promise<Page> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}slug_${slug}_${locale || 'default'}`\n\n const response = await this.getWithCache<{ data: Page }>(\n cacheKey,\n `/pages/${encodeURIComponent(slug)}`,\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Retrieves a page by its full URL path, which may include nested segments\n * (e.g. `/services/consulting`). Useful when you have the path from a URL\n * but not the slug. Returns the same full page data as `getBySlug()`.\n * Cached for 5 minutes per path+locale.\n *\n * @param path - The full URL path of the page (e.g. `'/services/consulting'`, `'/about'`).\n * Must start with a forward slash.\n * @param options - Request options; use `locale` to fetch a specific localized version\n * @returns The full `Page` object with resolved data, SEO settings, and alternates\n * @throws {LynkowError} With code `'NOT_FOUND'` if no published page matches the path\n *\n * @example\n * ```typescript\n * // Resolve a page from the current URL\n * const page = await lynkow.pages.getByPath('/services/consulting')\n * console.log(page.name) // \"Consulting\"\n * ```\n */\n async getByPath(path: string, options?: BaseRequestOptions): Promise<Page> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}path_${path}_${locale || 'default'}`\n\n const response = await this.getWithCache<{ data: Page }>(\n cacheKey,\n '/page-by-path',\n { path },\n options,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Retrieves Schema.org JSON-LD structured data for a page, ready to be\n * embedded in a `<script type=\"application/ld+json\">` tag for search\n * engine optimization. Cached for 5 minutes per slug+locale.\n *\n * @param slug - The page slug to generate JSON-LD for (e.g. `'about'`, `'contact'`)\n * @param options - Request options; use `locale` to get locale-specific structured data\n * @returns A JSON-LD object (Schema.org format) as `Record<string, unknown>`.\n * The exact shape depends on the page type (e.g. WebPage, Organization, etc.)\n * @throws {LynkowError} With code `'NOT_FOUND'` if no published page matches the slug\n *\n * @example\n * ```typescript\n * const jsonLd = await lynkow.pages.getJsonLd('about')\n * // Embed in your HTML <head>\n * // <script type=\"application/ld+json\">{JSON.stringify(jsonLd)}</script>\n * ```\n */\n async getJsonLd(\n slug: string,\n options?: BaseRequestOptions\n ): Promise<Record<string, unknown>> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}jsonld_${slug}_${locale || 'default'}`\n\n const response = await this.getWithCache<{ data: Record<string, unknown> }>(\n cacheKey,\n `/pages/${encodeURIComponent(slug)}/json-ld`,\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Invalidate every cached page response (lists, slug lookups, path\n * lookups, and the JSON-LD endpoint). Call after an admin mutation or\n * on receipt of a `page.*` webhook so the next public request bypasses\n * the SWR cache and re-materializes the resolved data (including\n * `structuredData.graph`).\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.pages.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n SiteConfigResponse,\n GlobalBlockResponse,\n BaseRequestOptions,\n} from '../types'\n\nconst CACHE_PREFIX = 'globals_'\n\n/**\n * Service for retrieving global site blocks (header, footer, navigation, etc.).\n *\n * Accessible via `lynkow.globals` (aliased from `lynkow.blocks`). Global blocks\n * are reusable, schema-driven content components shared across all pages of the\n * site. They are resolved server-side with DataSources. Responses are cached for\n * 10 minutes (MEDIUM TTL) since global blocks change infrequently.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Fetch all global blocks at once (recommended for layouts)\n * const { data } = await lynkow.globals.siteConfig()\n * const header = data.globals['header']\n *\n * // Fetch a single global block\n * const { data } = await lynkow.globals.getBySlug('footer')\n * ```\n */\nexport class BlocksService extends BaseService {\n /**\n * Retrieves the complete site configuration including basic site info\n * (name, domain, logo, favicon) and all global blocks resolved in a\n * single request. This is the recommended way to fetch layout data for\n * your site shell (header, footer, navigation). Cached for 10 minutes per locale.\n *\n * @param options - Request options; use `locale` to fetch localized block data\n * @returns An object containing `data` with:\n * - `site` — basic site info (name, domain, logo, favicon)\n * - `locale` — the resolved locale code\n * - `globals` — a record mapping block slugs to their resolved `GlobalBlock` data\n * (e.g. `{ header: {...}, footer: {...}, nav: {...} }`)\n *\n * @example\n * ```typescript\n * const { data } = await lynkow.globals.siteConfig()\n * console.log(data.site.name) // \"My Site\"\n * const header = data.globals['header'] // Resolved header block data\n * const footer = data.globals['footer'] // Resolved footer block data\n * ```\n */\n async siteConfig(options?: BaseRequestOptions): Promise<SiteConfigResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}siteconfig_${locale || 'default'}`\n return this.getWithCache<SiteConfigResponse>(\n cacheKey,\n '/site-config',\n undefined,\n options,\n CACHE_TTL.MEDIUM\n )\n }\n\n /**\n * Retrieves a single global block by its slug, with fully resolved DataSource\n * data. Use this when you only need one specific block rather than all globals.\n * Cached for 10 minutes per slug+locale.\n *\n * @param slug - The unique slug of the global block (e.g. `'header'`, `'footer'`, `'nav'`, `'sidebar'`)\n * @param options - Request options; use `locale` to fetch a localized version\n * @returns An object containing `data` — a `GlobalBlock` with `slug`, `name`, and\n * `data` (the resolved DataSource values as `Record<string, unknown>`)\n * @throws {LynkowError} With code `'NOT_FOUND'` if no global block matches the slug\n *\n * @example\n * ```typescript\n * const { data } = await lynkow.globals.getBySlug('header')\n * console.log(data.name) // \"Header\"\n * console.log(data.data) // Resolved content (links, logo, menu items, etc.)\n * ```\n */\n async getBySlug(\n slug: string,\n options?: BaseRequestOptions\n ): Promise<GlobalBlockResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}${slug}_${locale || 'default'}`\n return this.getWithCache<GlobalBlockResponse>(\n cacheKey,\n `/global/${encodeURIComponent(slug)}`,\n undefined,\n options,\n CACHE_TTL.MEDIUM\n )\n }\n\n /**\n * Retrieves a single global block by slug.\n *\n * @deprecated Use `getBySlug()` instead. This method will be removed in the next major version.\n * @param slug - The block slug\n * @param options - Request options\n * @returns The resolved global block\n * @throws {LynkowError} With code `'NOT_FOUND'` when the slug does not\n * match any global block for the site, `'NETWORK_ERROR'` on transport\n * failure. Same surface as {@link getBySlug}.\n *\n * @example\n * ```typescript\n * // Deprecated -- use getBySlug() instead:\n * const { data } = await lynkow.globals.getBySlug('footer')\n * ```\n */\n async global(\n slug: string,\n options?: BaseRequestOptions\n ): Promise<GlobalBlockResponse> {\n return this.getBySlug(slug, options)\n }\n\n /**\n * Invalidate every cached global block response (site config payload\n * and per-slug lookups). Call after an admin mutation or on receipt of\n * a `globalBlock.updated` webhook so the next public request hits the\n * origin and re-resolves any referenced variables.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.globals.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","/**\n * Hidden anti-spam fields auto-injected into form and review submissions.\n * The Lynkow API's `spamProtection` middleware verifies both values server\n * side; submissions that fail either check are rejected with a generic\n * 200 OK so bots cannot probe the heuristic.\n */\nexport interface SpamFields {\n /**\n * Honeypot field. MUST be left empty by real users; rendered as a hidden\n * input so genuine browsers ignore it but naive scrapers fill it in.\n */\n _hp: string\n\n /**\n * Client-side session start time as Unix milliseconds since epoch\n * (the return value of `Date.now()`). The server rejects submissions\n * whose `(now - _ts)` is below the minimum fill-in threshold (bots) or\n * above the maximum session lifetime (stale).\n */\n _ts: number\n}\n\n/**\n * Generate the anti-spam fields to include on a form or review submission.\n *\n * The SDK's built-in `lynkow.forms.submit()` / `lynkow.reviews.create()`\n * call this automatically; you only need to use it directly if you build\n * a custom submission pipeline and bypass the service.\n *\n * @param sessionStartTime - Unix ms since epoch captured when the form\n * started being rendered or interacted with (typically `Date.now()` at\n * client creation time).\n * @returns `{ _hp, _ts }` ready to merge into the submission payload.\n *\n * @example\n * ```typescript\n * import { generateSpamFields } from 'lynkow'\n *\n * const sessionStart = Date.now()\n * // later, at submit time:\n * const payload = { name: 'Jane', email: 'jane@example.com', ...generateSpamFields(sessionStart) }\n * ```\n */\nexport function generateSpamFields(sessionStartTime: number): SpamFields {\n return {\n _hp: '', // Honeypot - always empty\n _ts: sessionStartTime, // Session timestamp\n }\n}\n","import { BaseService, CACHE_TTL, type InternalConfig } from './base'\nimport type {\n Form,\n FormSubmitData,\n FormSubmitResponse,\n SubmitOptions,\n BaseRequestOptions,\n} from '../types'\nimport { generateSpamFields } from '../utils/spam'\n\nconst CACHE_PREFIX = 'forms_'\n\n/**\n * Service for retrieving form schemas and submitting form data.\n *\n * Accessible via `lynkow.forms`. Forms are dynamic, CMS-managed forms with\n * configurable fields, validation rules, and spam protection. The service\n * automatically handles honeypot anti-spam fields on submissions. Form schemas\n * are cached for 10 minutes (MEDIUM TTL).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Fetch form schema to render the form\n * const form = await lynkow.forms.getBySlug('contact')\n *\n * // Submit form data\n * await lynkow.forms.submit('contact', {\n * name: 'Jane',\n * email: 'jane@example.com',\n * message: 'Hello!'\n * })\n * ```\n */\nexport class FormsService extends BaseService {\n /**\n * Client creation timestamp used for anti-spam time validation.\n * Submissions made too quickly after page load are flagged as spam.\n */\n private sessionStartTime: number\n\n constructor(config: InternalConfig) {\n super(config)\n this.sessionStartTime = Date.now()\n }\n\n /**\n * Retrieves a form definition by its slug, including the field schema,\n * validation rules, settings (submit label, success message), and spam\n * protection configuration (honeypot/reCAPTCHA). Cached for 10 minutes.\n *\n * @param slug - The unique slug of the form (e.g. `'contact'`, `'newsletter'`, `'feedback'`)\n * @returns The `Form` object with `schema` (array of `FormField` with type, label,\n * required, validation, options), `settings` (submitLabel, successMessage, redirectUrl),\n * and spam protection flags (honeypotEnabled, recaptchaEnabled, recaptchaSiteKey)\n * @throws {LynkowError} With code `'NOT_FOUND'` if no active form matches the slug\n *\n * @example\n * ```typescript\n * const form = await lynkow.forms.getBySlug('contact')\n * // Iterate fields to render the form dynamically\n * form.schema.forEach(field => {\n * console.log(field.name, field.type, field.required)\n * })\n * // Check spam protection config\n * if (form.recaptchaEnabled) {\n * // Render reCAPTCHA widget using form.recaptchaSiteKey\n * }\n * ```\n */\n async getBySlug(slug: string): Promise<Form> {\n const cacheKey = `${CACHE_PREFIX}${slug}`\n const response = await this.getWithCache<{ data: Form }>(\n cacheKey,\n `/forms/${encodeURIComponent(slug)}`,\n undefined,\n undefined,\n CACHE_TTL.MEDIUM\n )\n return response.data\n }\n\n /**\n * Submits form data to the API. Anti-spam honeypot fields (`_hp`, `_ts`) are\n * injected automatically by the SDK -- you do not need to add them yourself.\n * If the form has reCAPTCHA enabled, pass the token via `options.recaptchaToken`.\n *\n * @param slug - The slug of the form to submit to (e.g. `'contact'`)\n * @param data - Key-value pairs matching the form's field names.\n * Values can be `string`, `number`, `boolean`, or `File`.\n * @param options - Optional submission options:\n * - `recaptchaToken` — the reCAPTCHA v3 token (required if reCAPTCHA is enabled on the form)\n * - `fetchOptions` — custom fetch options (e.g. AbortSignal)\n * @returns A `FormSubmitResponse` with `message` (confirmation text), `status`\n * (`'success'` for immediate acceptance, `'pending'` if double opt-in is enabled),\n * and optionally `submissionId`\n * @throws {LynkowError} With code `'VALIDATION_ERROR'` if field validation fails\n * @throws {LynkowError} With code `'NOT_FOUND'` if the form slug is invalid\n * @throws {LynkowError} With code `'FORBIDDEN'` if spam protection rejects the submission\n *\n * @example\n * ```typescript\n * const result = await lynkow.forms.submit('contact', {\n * name: 'John Doe',\n * email: 'john@example.com',\n * message: 'Hello!'\n * })\n *\n * if (result.status === 'pending') {\n * // Show \"check your email\" confirmation\n * } else {\n * // Show success message\n * console.log(result.message)\n * }\n * ```\n */\n async submit(\n slug: string,\n data: FormSubmitData,\n options?: SubmitOptions & BaseRequestOptions\n ): Promise<FormSubmitResponse> {\n // Add anti-spam fields\n const spamFields = generateSpamFields(this.sessionStartTime)\n\n // Build the body - form data wrapped in 'data' field per API spec\n const body: Record<string, unknown> = {\n data,\n honeypot: spamFields._hp,\n ...spamFields, // Also include _hp and _ts at root for middleware\n }\n\n // Add reCAPTCHA token if provided\n if (options?.recaptchaToken) {\n body['recaptchaToken'] = options.recaptchaToken\n }\n\n return this.post<FormSubmitResponse>(\n `/forms/${encodeURIComponent(slug)}/submit`,\n body,\n options\n )\n }\n\n /**\n * Invalidate every cached form schema response. Call after an admin\n * mutation or on receipt of a `form.*` webhook so the next public\n * request re-fetches the latest field definitions (required fields,\n * validators, spam settings).\n *\n * Does not affect form submissions, which are never cached.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.forms.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL, type InternalConfig } from './base'\nimport type {\n Review,\n ReviewSettings,\n ReviewSubmitData,\n ReviewSubmitResponse,\n ReviewsListResponse,\n ReviewsFilters,\n SubmitOptions,\n BaseRequestOptions,\n} from '../types'\nimport { generateSpamFields } from '../utils/spam'\n\nconst CACHE_PREFIX = 'reviews_'\n\n/**\n * Service for retrieving and submitting customer reviews.\n *\n * Accessible via `lynkow.reviews`. Only reviews with status `'approved'`\n * are returned by the public API. New submissions go through moderation\n * if `requireApproval` is enabled in the review settings. Anti-spam\n * honeypot fields are injected automatically on submissions. Responses\n * are cached for 5 minutes (SHORT TTL).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // List approved reviews\n * const { data, meta } = await lynkow.reviews.list({ limit: 10 })\n *\n * // Submit a review\n * await lynkow.reviews.submit({\n * authorName: 'Alice',\n * rating: 5,\n * content: 'Great service!'\n * })\n * ```\n */\nexport class ReviewsService extends BaseService {\n /**\n * Client creation timestamp used for anti-spam time validation.\n * Submissions made too quickly after page load are flagged as spam.\n */\n private sessionStartTime: number\n\n constructor(config: InternalConfig) {\n super(config)\n this.sessionStartTime = Date.now()\n }\n\n /**\n * Retrieves a paginated list of approved customer reviews. Only reviews\n * that have passed moderation are returned. Cached for 5 minutes per\n * unique filter combination.\n *\n * @param filters - Optional filters to narrow down results:\n * - `page` / `limit` — pagination (defaults to page 1)\n * - `minRating` / `maxRating` — filter by rating range (1-5 scale)\n * - `sort` / `order` — sort field and direction (`'asc'` or `'desc'`)\n * @param options - Base request options (locale override, custom fetch options)\n * @returns An object containing `data` (array of `Review` with authorName, rating,\n * title, content, createdAt, and optional `response` from the site owner)\n * and `meta` (pagination info with total, currentPage, lastPage, hasMorePages)\n *\n * @example\n * ```typescript\n * // Fetch top-rated reviews (4+ stars)\n * const { data, meta } = await lynkow.reviews.list({\n * minRating: 4,\n * limit: 10\n * })\n *\n * data.forEach(review => {\n * console.log(`${review.authorName}: ${review.rating}/5`)\n * if (review.response) {\n * console.log(`Owner reply: ${review.response.content}`)\n * }\n * })\n * ```\n */\n async list(\n filters?: ReviewsFilters,\n options?: BaseRequestOptions\n ): Promise<ReviewsListResponse> {\n const query: Record<string, unknown> = {}\n\n if (filters?.page) query['page'] = filters.page\n if (filters?.limit ?? filters?.perPage) query['limit'] = filters?.limit ?? filters?.perPage\n if (filters?.minRating) query['minRating'] = filters.minRating\n if (filters?.maxRating) query['maxRating'] = filters.maxRating\n if (filters?.sort) query['sort'] = filters.sort\n if (filters?.order) query['order'] = filters.order\n\n const cacheKey = `${CACHE_PREFIX}list_${JSON.stringify(filters || {})}`\n return this.getWithCache<ReviewsListResponse>(\n cacheKey,\n '/reviews',\n query,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Retrieves a single approved review by its slug or numeric ID.\n * Returns the full review including any owner response. Cached for 5 minutes.\n *\n * @param slugOrId - The URL slug (e.g. `'excellent-service'`) or numeric ID\n * (as string, e.g. `'42'`) of the review\n * @returns The full `Review` object with authorName, rating (1-5), title,\n * content, locale, createdAt, and optional `response` (owner's reply)\n * @throws {LynkowError} With code `'NOT_FOUND'` if no approved review matches the slug/ID\n *\n * @example\n * ```typescript\n * const review = await lynkow.reviews.getBySlug('excellent-service')\n * console.log(review.rating) // 5\n * console.log(review.content) // \"Best experience ever!\"\n * ```\n */\n async getBySlug(slugOrId: string): Promise<Review> {\n const cacheKey = `${CACHE_PREFIX}slug_${slugOrId}`\n const response = await this.getWithCache<{ data: Review }>(\n cacheKey,\n `/reviews/${encodeURIComponent(slugOrId)}`,\n undefined,\n undefined,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Retrieves the public review settings for this site, including whether\n * reviews are enabled, moderation rules, rating scale, and field configuration.\n * Use this to dynamically render the review submission form. Cached for 10 minutes.\n *\n * @returns A `ReviewSettings` object with:\n * - `enabled` — whether reviews are active on this site\n * - `requireApproval` — whether new reviews need moderation before publication\n * - `allowAnonymous` — whether anonymous submissions are allowed\n * - `minRating` / `maxRating` — rating scale bounds (typically 1-5)\n * - `fields.title` — whether the title field is enabled/required\n * - `fields.email` — whether the email field is enabled/required\n *\n * @example\n * ```typescript\n * const settings = await lynkow.reviews.settings()\n * if (!settings.enabled) {\n * // Reviews are disabled, hide the form\n * return\n * }\n * if (settings.fields.email.required) {\n * // Render email field as required\n * }\n * ```\n *\n * @throws {LynkowError} With code `'NETWORK_ERROR'` on transport\n * failure, `'NOT_FOUND'` if the site does not exist.\n */\n async settings(): Promise<ReviewSettings> {\n const cacheKey = `${CACHE_PREFIX}settings`\n return this.getWithCache<ReviewSettings>(\n cacheKey,\n '/reviews/settings',\n undefined,\n undefined,\n CACHE_TTL.MEDIUM\n )\n }\n\n /**\n * Submits a new customer review. Anti-spam honeypot fields (`_hp`, `_ts`) are\n * injected automatically by the SDK. If reCAPTCHA is enabled on the site, pass\n * the token via `options.recaptchaToken`. After a successful submission, the\n * reviews cache is automatically invalidated.\n *\n * @param data - The review data to submit:\n * - `authorName` — reviewer's display name (required)\n * - `authorEmail` — reviewer's email (required if settings demand it)\n * - `rating` — numeric rating (must be within the site's min/max range, typically 1-5)\n * - `title` — optional review title (required if settings demand it)\n * - `content` — the review text (required)\n * @param options - Optional submission options:\n * - `recaptchaToken` — the reCAPTCHA v3 token if reCAPTCHA is enabled\n * - `fetchOptions` — custom fetch options (e.g. AbortSignal)\n * @returns A `ReviewSubmitResponse` with `message` (confirmation text), `status`\n * (`'success'` if auto-approved, `'pending'` if moderation is required),\n * and optionally `reviewId`\n * @throws {LynkowError} With code `'VALIDATION_ERROR'` if required fields are missing or invalid\n * @throws {LynkowError} With code `'FORBIDDEN'` if spam protection rejects the submission\n *\n * @example\n * ```typescript\n * const result = await lynkow.reviews.submit({\n * authorName: 'Alice',\n * rating: 5,\n * content: 'Excellent service!'\n * })\n *\n * if (result.status === 'pending') {\n * // Show \"your review is awaiting moderation\"\n * }\n * ```\n */\n async submit(\n data: ReviewSubmitData,\n options?: SubmitOptions & BaseRequestOptions\n ): Promise<ReviewSubmitResponse> {\n // Add anti-spam fields\n const spamFields = generateSpamFields(this.sessionStartTime)\n\n // Build the body\n const body: Record<string, unknown> = {\n ...data,\n ...spamFields,\n }\n\n // Add reCAPTCHA token if provided\n if (options?.recaptchaToken) {\n body['_recaptcha_token'] = options.recaptchaToken\n }\n\n const result = await this.post<ReviewSubmitResponse>('/reviews', body, options)\n\n // Invalidate cache after submission\n this.invalidateCache(CACHE_PREFIX)\n\n return result\n }\n\n /**\n * Invalidate every cached review response (lists, individual reviews,\n * settings). Automatically invoked after a successful {@link submit};\n * call it manually after an admin moderation action or on receipt of a\n * `review.*` webhook to keep public listings in sync.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // After an admin approved a pending review:\n * lynkow.reviews.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type { SiteConfig } from '../types'\n\nconst CACHE_PREFIX = 'site_'\n\n/**\n * Service for retrieving the public site configuration.\n *\n * Accessible via `lynkow.site`. Provides site-level metadata such as name,\n * domain, locales, timezone, branding settings, and analytics consent mode.\n * Cached for 10 minutes (MEDIUM TTL) since site configuration rarely changes.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n * const config = await lynkow.site.getConfig()\n * console.log(config.name) // \"My Site\"\n * console.log(config.enabledLocales) // ['en', 'fr']\n * ```\n */\nexport class SiteService extends BaseService {\n /**\n * Retrieves the complete public site configuration, including site identity\n * (name, domain, logo, favicon), localization settings (default locale,\n * enabled locales, i18n config with detection rules), branding options,\n * and analytics consent mode. Cached for 10 minutes.\n *\n * @returns A `SiteConfig` object with:\n * - `id` — site UUID\n * - `name`, `domain`, `description` — basic site identity\n * - `logoUrl`, `faviconUrl` — branding assets\n * - `defaultLocale`, `enabledLocales` — locale configuration\n * - `i18n` — rich i18n config (detection, switcher, locale names/flags)\n * - `showBranding` — whether the \"Powered by Lynkow\" badge should be shown\n * - `analytics.consentMode` — `'opt-in'`, `'opt-out'`, or `'disabled'`\n *\n * @example\n * ```typescript\n * const config = await lynkow.site.getConfig()\n * console.log(config.enabledLocales) // ['en', 'fr', 'de']\n * console.log(config.i18n.detection.enabled) // true\n * console.log(config.analytics.consentMode) // 'opt-in'\n * ```\n */\n async getConfig(): Promise<SiteConfig> {\n const cacheKey = `${CACHE_PREFIX}config`\n const response = await this.getWithCache<{ data: SiteConfig }>(\n cacheKey,\n '/site',\n undefined,\n undefined,\n CACHE_TTL.MEDIUM\n )\n return response.data\n }\n\n /**\n * Invalidate the cached site configuration. Call after a settings\n * change on the admin (branding, enabled locales, default author,\n * search config) or on receipt of a `site.updated` webhook so the next\n * `getConfig()` fetches fresh values.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // After enabling a new locale in the admin:\n * lynkow.site.clearCache()\n * const fresh = await lynkow.site.getConfig()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n LegalDocument,\n LegalListResponse,\n LegalDocumentResponse,\n BaseRequestOptions,\n} from '../types'\n\nconst CACHE_PREFIX = 'legal_'\n\n/**\n * Service for accessing legal documents (privacy policy, terms of service, etc.).\n *\n * Accessible via `lynkow.legal`. Legal documents are regular pages tagged with\n * `'legal'`. This service provides convenience methods to access them, but is\n * deprecated in favor of the `pages` service.\n *\n * Responses are cached for 5 minutes (SHORT TTL).\n *\n * @deprecated Use `lynkow.pages.list({ tag: 'legal' })` and `lynkow.pages.getBySlug(slug)` instead.\n * This service will be removed in the next major version.\n *\n * @example\n * ```typescript\n * // Deprecated approach:\n * const docs = await lynkow.legal.list()\n * const privacy = await lynkow.legal.getBySlug('privacy-policy')\n *\n * // Recommended approach:\n * const docs = await lynkow.pages.list({ tag: 'legal' })\n * const privacy = await lynkow.pages.getBySlug('privacy-policy')\n * ```\n */\nexport class LegalService extends BaseService {\n /**\n * Retrieves all published pages tagged with `'legal'`. Returns page summaries\n * including slug, name, path, and locale. Cached for 5 minutes per locale.\n *\n * @deprecated Use `lynkow.pages.list({ tag: 'legal' })` instead.\n *\n * @param options - Request options; use `locale` to fetch localized versions\n * @returns An array of `LegalDocument` objects (page summaries with name, slug, path)\n *\n * @example\n * ```typescript\n * // Deprecated -- migrate to pages service:\n * // Before: const docs = await lynkow.legal.list()\n * // After:\n * const docs = await lynkow.pages.list({ tag: 'legal' })\n * docs.data.forEach(doc => console.log(doc.slug, doc.name))\n * ```\n */\n async list(options?: BaseRequestOptions): Promise<LegalDocument[]> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}list_${locale || 'default'}`\n const response = await this.getWithCache<LegalListResponse>(\n cacheKey,\n '/pages',\n { tag: 'legal' },\n options,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Retrieves a single legal page by its slug, including resolved data and SEO settings.\n * Cached for 5 minutes per slug+locale.\n *\n * @deprecated Use `lynkow.pages.getBySlug(slug)` instead.\n *\n * @param slug - The page slug (e.g. `'privacy-policy'`, `'terms-of-service'`, `'cookie-policy'`)\n * @param options - Request options; use `locale` to fetch a specific localized version\n * @returns The `LegalDocument` object with full page data\n * @throws {LynkowError} With code `'NOT_FOUND'` if no published page matches the slug\n *\n * @example\n * ```typescript\n * // Deprecated -- migrate to pages service:\n * // Before: const privacy = await lynkow.legal.getBySlug('privacy-policy')\n * // After:\n * const privacy = await lynkow.pages.getBySlug('privacy-policy')\n * console.log(privacy.data.name, privacy.data.body)\n * ```\n */\n async getBySlug(\n slug: string,\n options?: BaseRequestOptions\n ): Promise<LegalDocument> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}slug_${slug}_${locale || 'default'}`\n const response = await this.getWithCache<LegalDocumentResponse>(\n cacheKey,\n `/pages/${encodeURIComponent(slug)}`,\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Invalidate every cached legal document response. Kept for\n * backwards compatibility with sites that still reference the legacy\n * `/legal/*` surface.\n *\n * @deprecated This service is deprecated. Use `lynkow.pages` instead,\n * which covers legal pages along with every other page type and is\n * actively maintained.\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.legal.clearCache() // Deprecated; prefer lynkow.pages.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n CookieConfig,\n CookiePreferences,\n ConsentLogResponse,\n BaseRequestOptions,\n} from '../types'\n\nconst CACHE_PREFIX = 'cookies_'\n\n/**\n * Low-level service for cookie consent API interactions.\n *\n * Accessible via `lynkow.cookies`. Provides raw API methods for fetching\n * consent configuration and logging user preferences. For a higher-level\n * experience with built-in banner UI and preferences modal, use the\n * `consent` service (`lynkow.consent`) instead.\n *\n * Responses are cached for 10 minutes (MEDIUM TTL).\n *\n * @remarks\n * This service provides the API-only layer. The `consent` module builds\n * on top of it with browser-side UI (banner, preferences modal, script injection).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n * const config = await lynkow.cookies.getConfig()\n * if (config.enabled) {\n * // Build your own consent UI\n * }\n * ```\n */\nexport class CookiesService extends BaseService {\n /**\n * Retrieves the cookie consent banner configuration from the API, including\n * categories (necessary, analytics, marketing, preferences), banner texts,\n * theming options, and third-party scripts to inject per category. Cached\n * for 10 minutes.\n *\n * @returns A `CookieConfig` object with:\n * - `enabled` — whether the consent banner is active\n * - `consentMode` — `'opt-in'` (explicit) or `'opt-out'` (implied)\n * - `position` — banner placement (`'bottom-left'` or `'bottom-right'`)\n * - `theme` — `'light'`, `'dark'`, or `'auto'`\n * - `categories` — array of `CookieCategory` (id, name, description, required)\n * - `texts` — banner copy (description, acceptAll, rejectAll, customize, save)\n * - `thirdPartyScripts` — scripts to inject when their category is accepted\n *\n * @example\n * ```typescript\n * const config = await lynkow.cookies.getConfig()\n * if (config.enabled) {\n * console.log(config.categories) // [{id: 'necessary', ...}, {id: 'analytics', ...}]\n * console.log(config.texts.acceptAll) // \"Accept all\"\n * }\n * ```\n */\n async getConfig(): Promise<CookieConfig> {\n const cacheKey = `${CACHE_PREFIX}config`\n const response = await this.getWithCache<{ data: CookieConfig }>(\n cacheKey,\n '/cookie-consent/config',\n undefined,\n undefined,\n CACHE_TTL.MEDIUM\n )\n return response.data\n }\n\n /**\n * Records the user's cookie consent preferences to the server for audit trail /\n * GDPR compliance. This is a write operation (POST) and is never cached.\n *\n * @param preferences - A record mapping category IDs to boolean acceptance values\n * (e.g. `{ necessary: true, analytics: true, marketing: false }`)\n * @param options - Base request options (custom fetch options)\n * @returns A `ConsentLogResponse` with `message` (confirmation text) and\n * `consentId` (unique audit trail ID)\n *\n * @example\n * ```typescript\n * const result = await lynkow.cookies.logConsent({\n * necessary: true,\n * analytics: true,\n * marketing: false\n * })\n * console.log(result.consentId) // Unique consent record ID\n * ```\n */\n async logConsent(\n preferences: CookiePreferences,\n options?: BaseRequestOptions\n ): Promise<ConsentLogResponse> {\n return this.post<ConsentLogResponse>(\n '/cookie-consent/log',\n { preferences },\n options\n )\n }\n\n /**\n * Invalidate the cached cookie consent configuration (categories,\n * scripts, banner appearance). Call after a settings change on the\n * admin or on receipt of a `cookies.updated` webhook so the next\n * `getConfig()` returns the updated categories and the consent banner\n * reflects the new policy.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.cookies.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService } from './base'\nimport type { BaseRequestOptions } from '../types'\n\n/**\n * Service for retrieving SEO-related files (sitemap, robots.txt, LLM-optimized\n * content, and individual Markdown exports).\n *\n * Accessible via `lynkow.seo`. All methods return plain text (XML, TXT, or Markdown)\n * and are NOT cached by the SDK since they are typically served as route handlers\n * with their own HTTP caching headers.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Serve sitemap.xml\n * const xml = await lynkow.seo.sitemap()\n *\n * // Serve robots.txt\n * const txt = await lynkow.seo.robots()\n *\n * // Serve llms.txt for AI crawlers\n * const md = await lynkow.seo.llmsTxt()\n * ```\n */\nexport class SeoService extends BaseService {\n /**\n * Retrieves the complete XML sitemap for the site. If the site uses Sitemap\n * Index mode, this returns the index file pointing to individual parts.\n * Typically served as a route handler with `Content-Type: application/xml`.\n *\n * @param options - Request options (custom fetch options)\n * @returns The sitemap XML content as a raw string\n * @throws {LynkowError} With code `'NETWORK_ERROR'` on transport\n * failure, `'NOT_FOUND'` if the site has sitemap generation disabled.\n *\n * @example\n * ```typescript\n * // In a Next.js route handler (app/sitemap.xml/route.ts)\n * export async function GET() {\n * const xml = await lynkow.seo.sitemap()\n * return new Response(xml, {\n * headers: { 'Content-Type': 'application/xml' }\n * })\n * }\n * ```\n */\n async sitemap(options?: BaseRequestOptions): Promise<string> {\n return this.getText('/sitemap.xml', options)\n }\n\n /**\n * Retrieves a specific sitemap part when Sitemap Index mode is enabled.\n * Each part contains a subset of URLs, useful for large sites that exceed\n * the 50,000 URL limit per sitemap file.\n *\n * @param part - The 1-indexed part number (e.g. `1`, `2`, `3`)\n * @param options - Request options (custom fetch options)\n * @returns The sitemap XML content for the specified part as a raw string\n * @throws {LynkowError} With code `'NOT_FOUND'` if the part number does not exist\n *\n * @example\n * ```typescript\n * // In a Next.js dynamic route handler (app/sitemap-[part].xml/route.ts)\n * export async function GET(req: Request, { params }: { params: { part: string } }) {\n * const xml = await lynkow.seo.sitemapPart(parseInt(params.part))\n * return new Response(xml, {\n * headers: { 'Content-Type': 'application/xml' }\n * })\n * }\n * ```\n */\n async sitemapPart(part: number, options?: BaseRequestOptions): Promise<string> {\n return this.getText(`/sitemap-${part}.xml`, options)\n }\n\n /**\n * Retrieves the generated robots.txt file for the site, including\n * crawl directives and sitemap references. Typically served as a\n * route handler with `Content-Type: text/plain`.\n *\n * @param options - Request options (custom fetch options)\n * @returns The robots.txt content as a raw string\n * @throws {LynkowError} With code `'NETWORK_ERROR'` on transport\n * failure, `'NOT_FOUND'` if the site has robots.txt disabled.\n *\n * @example\n * ```typescript\n * // In a Next.js route handler (app/robots.txt/route.ts)\n * export async function GET() {\n * const txt = await lynkow.seo.robots()\n * return new Response(txt, {\n * headers: { 'Content-Type': 'text/plain' }\n * })\n * }\n * ```\n */\n async robots(options?: BaseRequestOptions): Promise<string> {\n return this.getText('/robots.txt', options)\n }\n\n /**\n * Retrieves the llms.txt file, an LLM-optimized site index in Markdown format.\n * This file provides a structured overview of the site's content, designed for\n * AI crawlers and language models to understand the site's structure.\n * Supports locale-specific versions for multilingual sites.\n *\n * @param options - Request options:\n * - `locale` — fetch the index for a specific locale (e.g. `'en'`, `'fr'`).\n * When set, the API returns `/{locale}/llms.txt`. When omitted, returns\n * the default locale version.\n * @returns The llms.txt Markdown content as a raw string\n * @throws {LynkowError} With code `'NETWORK_ERROR'` on transport\n * failure, `'NOT_FOUND'` if `llmsTxtEnabled` is `false` on the site's\n * SEO settings or the locale has no llms.txt.\n *\n * @example\n * ```typescript\n * // In a Next.js route handler (app/llms.txt/route.ts)\n * export async function GET() {\n * const md = await lynkow.seo.llmsTxt()\n * return new Response(md, {\n * headers: { 'Content-Type': 'text/plain; charset=utf-8' }\n * })\n * }\n *\n * // Fetch the French version\n * const md = await lynkow.seo.llmsTxt({ locale: 'fr' })\n * ```\n */\n async llmsTxt(options?: BaseRequestOptions): Promise<string> {\n const locale = options?.locale\n const path = locale ? `/${locale}/llms.txt` : '/llms.txt'\n return this.getText(path, options)\n }\n\n /**\n * Retrieves the llms-full.txt file, which concatenates all published articles\n * and pages into a single Markdown document. Useful for AI/LLM ingestion of\n * the site's full content. Can be large for sites with many articles.\n * Supports locale-specific versions for multilingual sites.\n *\n * @param options - Request options:\n * - `locale` — fetch the full content for a specific locale (e.g. `'en'`).\n * When set, only content in that locale is included.\n * @returns The full Markdown content of all published articles and pages as a raw string\n * @throws {LynkowError} With code `'NETWORK_ERROR'` on transport\n * failure, `'NOT_FOUND'` if `llmsTxtEnabled` is `false` on the site's\n * SEO settings.\n *\n * @example\n * ```typescript\n * // In a Next.js route handler (app/llms-full.txt/route.ts)\n * export async function GET() {\n * const md = await lynkow.seo.llmsFullTxt()\n * return new Response(md, {\n * headers: { 'Content-Type': 'text/plain; charset=utf-8' }\n * })\n * }\n *\n * // Fetch the French version\n * const md = await lynkow.seo.llmsFullTxt({ locale: 'fr' })\n * ```\n */\n async llmsFullTxt(options?: BaseRequestOptions): Promise<string> {\n const locale = options?.locale\n const path = locale ? `/${locale}/llms-full.txt` : '/llms-full.txt'\n return this.getText(path, options)\n }\n\n /**\n * Retrieves a single content article or page as Markdown by its public URL path.\n * The SDK appends `.md` to the path automatically. This is useful for exposing\n * individual content items to LLMs or for Markdown-based rendering pipelines.\n *\n * @param contentPath - The content's public URL path (must start with `/` or will be auto-prefixed):\n * - For blog articles: use `Content.path` directly (already includes locale prefix)\n * - For pages in single-language sites: use `Page.path` directly\n * - For pages in multi-language sites: prepend the locale manually,\n * e.g. `/${page.locale}${page.path}`\n * @param options - Request options (custom fetch options)\n * @returns The content rendered as a Markdown string\n * @throws {LynkowError} With code `'NOT_FOUND'` if the path does not match any published content\n *\n * @example\n * ```typescript\n * // Get a blog article as Markdown (path includes locale automatically)\n * const article = await lynkow.contents.getBySlug('my-article')\n * const md = await lynkow.seo.getMarkdown(article.path)\n *\n * // Get a page as Markdown (mono-language)\n * const page = await lynkow.pages.getBySlug('about')\n * const md = await lynkow.seo.getMarkdown(page.path!)\n *\n * // Get a page as Markdown (multi-language — prepend locale)\n * const page = await lynkow.pages.getBySlug('about')\n * const md = await lynkow.seo.getMarkdown(`/${page.locale}${page.path}`)\n *\n * // In a Next.js catch-all route handler\n * export async function GET(req: Request) {\n * const url = new URL(req.url)\n * const path = url.pathname.replace(/\\.md$/, '')\n * const md = await lynkow.seo.getMarkdown(path)\n * return new Response(md, {\n * headers: { 'Content-Type': 'text/markdown; charset=utf-8' }\n * })\n * }\n * ```\n */\n async getMarkdown(contentPath: string, options?: BaseRequestOptions): Promise<string> {\n const normalizedPath = contentPath.startsWith('/') ? contentPath : `/${contentPath}`\n return this.getText(`${normalizedPath}.md`, options)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n PathsListResponse,\n ResolveResponse,\n Redirect,\n BaseRequestOptions,\n} from '../types'\nimport { LynkowError } from '../core/errors'\n\nconst CACHE_PREFIX = 'paths_'\n\n/**\n * Service for URL path resolution and static site generation (SSG).\n *\n * Accessible via `lynkow.paths`. Provides methods to list all available paths\n * for static generation, resolve a URL path to its content or category, and\n * check for configured redirects. Cached for 5 minutes (SHORT TTL).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Generate static params\n * const { paths } = await lynkow.paths.list()\n *\n * // Resolve a URL to content\n * const result = await lynkow.paths.resolve('/blog/tech/my-article')\n *\n * // Check for redirects\n * const redirect = await lynkow.paths.matchRedirect('/old-page')\n * ```\n */\nexport class PathsService extends BaseService {\n /**\n * Retrieves all available URL paths for the site, intended for static site\n * generation (SSG). Returns every published content and category path with\n * segments, type, locale, and last modification date. Cached for 5 minutes\n * per locale.\n *\n * @param options - Request options; use `locale` to fetch paths for a specific locale only\n * @returns A `PathsListResponse` containing:\n * - `paths` — array of `Path` objects, each with `path` (full URL path),\n * `segments` (split path parts for dynamic routes), `type` (`'content'` or `'category'`),\n * `locale`, and `lastModified` (ISO 8601 date)\n * - `blogUrlMode` — `'flat'` (slug only) or `'nested'` (category/slug)\n * - `locale` — resolved locale code, or null for all locales\n *\n * @example\n * ```typescript\n * // In Next.js generateStaticParams()\n * export async function generateStaticParams() {\n * const { paths } = await lynkow.paths.list()\n * return paths.map(p => ({\n * slug: p.segments\n * }))\n * }\n * ```\n */\n async list(options?: BaseRequestOptions): Promise<PathsListResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}list_${locale || 'all'}`\n return this.getWithCache<PathsListResponse>(\n cacheKey,\n '/paths',\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Resolves a URL path to its corresponding content article or category.\n * Returns a discriminated union: check the `type` field to determine whether\n * the path matched a content (`'content'`) or a category (`'category'`).\n * Cached for 5 minutes per path+locale.\n *\n * @param path - The URL path to resolve (e.g. `'/blog/tech/my-article'`, `'/blog/tech'`)\n * @param options - Request options; use `locale` to resolve in a specific locale context\n * @returns A `ResolveResponse` discriminated union:\n * - If `type === 'content'`: includes the full `Content` object (body, SEO, author, etc.)\n * - If `type === 'category'`: includes `CategoryDetail` and paginated `contents`\n * - Both include `locale` and `blogUrlMode`\n * @throws {LynkowError} With code `'NOT_FOUND'` if the path does not match any content or category\n *\n * @example\n * ```typescript\n * const result = await lynkow.paths.resolve('/blog/tech/my-article')\n *\n * if (result.type === 'content') {\n * // Render the full article\n * console.log(result.content.title)\n * } else if (result.type === 'category') {\n * // Render the category listing page\n * console.log(result.category.name)\n * console.log(result.contents.data.length) // Articles in this category\n * }\n * ```\n */\n async resolve(\n path: string,\n options?: BaseRequestOptions\n ): Promise<ResolveResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}resolve_${path}_${locale || 'default'}`\n return this.getWithCache<ResolveResponse>(\n cacheKey,\n '/resolve',\n { path },\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Checks if a URL path has a configured server-side redirect. Returns the\n * redirect rule if one matches, or `null` if no redirect is configured.\n * This method is NOT cached to ensure redirect changes take effect immediately.\n * A 404 response from the API is treated as \"no redirect found\" (returns `null`).\n *\n * @param path - The URL path to check for redirects (e.g. `'/old-page'`, `'/legacy/url'`)\n * @param options - Request options (custom fetch options)\n * @returns A `Redirect` object with `source`, `target`, `statusCode` (301/302/307/308),\n * and `preserveQueryString` flag, or `null` if no redirect matches\n * @throws {LynkowError} Re-throws any error other than 404 (e.g. network errors)\n *\n * @example\n * ```typescript\n * // In a Next.js middleware\n * const redirect = await lynkow.paths.matchRedirect(pathname)\n * if (redirect) {\n * return NextResponse.redirect(redirect.target, redirect.statusCode)\n * }\n * // No redirect, continue to normal page rendering\n * ```\n */\n async matchRedirect(\n path: string,\n options?: BaseRequestOptions\n ): Promise<Redirect | null> {\n try {\n const response = await this.get<{ data: Redirect }>(\n '/redirects/match',\n { path },\n options\n )\n return response.data\n } catch (error: unknown) {\n // 404 = no redirect found\n if (error instanceof LynkowError && error.status === 404) {\n return null\n }\n throw error\n }\n }\n\n /**\n * Invalidates all cached path responses (list and resolve lookups).\n * Call this after knowing the site's URL structure has changed\n * (e.g. new content published, slugs updated) to force fresh data.\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","/**\n * Environment detection utilities\n * Determines if code is running in browser or server environment\n */\n\n/**\n * Check if running in a browser environment\n */\nexport const isBrowser: boolean =\n typeof window !== 'undefined' &&\n typeof window.document !== 'undefined' &&\n typeof window.document.createElement !== 'undefined'\n\n/**\n * Check if running in a server environment (Node.js, Deno, etc.)\n */\nexport const isServer: boolean = !isBrowser\n\n/**\n * Execute a function only in browser environment\n * Returns the fallback value in server environment\n *\n * @param fn - Function to execute in browser\n * @param fallback - Value to return in server environment\n * @returns Result of fn() in browser, fallback in server\n *\n * @example\n * ```typescript\n * const visitorId = browserOnly(\n * () => localStorage.getItem('visitor_id'),\n * null\n * )\n * ```\n */\nexport function browserOnly<T>(fn: () => T, fallback: T): T {\n if (isBrowser) {\n return fn()\n }\n return fallback\n}\n\n/**\n * Execute an async function only in browser environment\n * Returns the fallback value in server environment\n *\n * @param fn - Async function to execute in browser\n * @param fallback - Value to return in server environment\n * @returns Promise resolving to result of fn() in browser, fallback in server\n *\n * @example\n * ```typescript\n * const config = await browserOnlyAsync(\n * () => fetchConfig(),\n * defaultConfig\n * )\n * ```\n */\nexport async function browserOnlyAsync<T>(fn: () => Promise<T>, fallback: T): Promise<T> {\n if (isBrowser) {\n return fn()\n }\n return fallback\n}\n\n/**\n * No-op function for browser-only features in server environment\n * Useful for methods that should silently do nothing on server\n */\nexport function noop(): void {\n // Intentionally empty\n}\n\n/**\n * Create a no-op version of a function for server environment\n *\n * @param fn - Function to wrap\n * @returns Original function in browser, no-op in server\n */\nexport function browserOnlyFn<T extends (...args: any[]) => any>(fn: T): T {\n if (isBrowser) {\n return fn\n }\n return noop as T\n}\n","/**\n * Analytics module for Lynkow SDK\n *\n * Browser-only module that loads and wraps the Lynkow tracker.js\n * All methods are no-op on server.\n */\n\nimport { isBrowser } from '../core/environment'\nimport type { InternalConfig } from './base'\n\n/**\n * Payload accepted by {@link AnalyticsService.trackPageview}. Every field\n * is optional; unset values fall back to the browser context at call time.\n */\nexport interface PageviewData {\n /**\n * URL path to record. Should start with `/`. Query string and hash are\n * preserved as-is; strip them when they do not identify a distinct\n * pageview (e.g. tracker-specific UTM parameters). Defaults to\n * `window.location.pathname` when omitted.\n */\n path?: string\n /**\n * Human-readable page title as it appears in the browser tab, used for\n * the analytics dashboard listing. Defaults to `document.title` when\n * omitted. Max ~255 characters before server-side truncation.\n */\n title?: string\n /**\n * Referring URL for this pageview, typically `document.referrer`.\n * Pass `''` to record \"direct\" traffic when you want to override the\n * browser's value. Omit to let the tracker fill it from\n * `document.referrer`.\n */\n referrer?: string\n}\n\n/**\n * Payload accepted by {@link AnalyticsService.trackEvent}. `type` is the\n * only required field; every other key becomes an ad-hoc property on the\n * event recorded by the tracker.\n */\nexport interface EventData {\n /**\n * Short slug identifying the event (e.g. `'cta_click'`,\n * `'signup_complete'`). Prefer snake_case so events group cleanly in\n * the analytics dashboard. Required and must be non-empty.\n */\n type: string\n /**\n * Arbitrary extra properties to attach to the event. Values must be\n * JSON-serializable. Keys prefixed with `_` are reserved for the\n * tracker and may be overwritten server-side.\n */\n [key: string]: unknown\n}\n\n/**\n * LynkowAnalytics global interface (from tracker.js)\n */\ninterface LynkowAnalyticsGlobal {\n init: (siteId: string, options?: { endpoint?: string }) => void\n track: (event: Record<string, unknown>) => void\n}\n\ndeclare global {\n interface Window {\n LynkowAnalytics?: LynkowAnalyticsGlobal\n }\n}\n\nconst TRACKER_SCRIPT_ID = 'lynkow-tracker'\n\n/**\n * Service for client-side analytics tracking via the Lynkow tracker.js script.\n *\n * Accessible via `lynkow.analytics`. This is a **browser-only** service -- all methods\n * are no-ops when called on the server (SSR/Node.js). The service lazily loads the\n * `tracker.js` script from the API, which auto-initializes using the site ID. The\n * tracker automatically captures pageviews; use `trackPageview()` for manual SPA\n * navigation tracking and `trackEvent()` for custom events.\n *\n * The service respects the site's consent mode: if consent mode is `'opt-in'`,\n * tracking only starts after consent is granted. The consent state is read from\n * `localStorage` (`_lkw_consent_mode` key).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Initialize analytics (loads tracker.js)\n * await lynkow.analytics.init()\n *\n * // Track a custom event\n * await lynkow.analytics.trackEvent({ type: 'purchase', amount: 49.99 })\n *\n * // Manually track SPA navigation\n * await lynkow.analytics.trackPageview({ path: '/new-page' })\n * ```\n */\nexport class AnalyticsService {\n private config: InternalConfig\n private enabled = true\n private initialized = false\n private loading = false\n private loadPromise: Promise<void> | null = null\n\n constructor(config: InternalConfig) {\n this.config = config\n }\n\n /**\n * Get the tracker script URL\n */\n private getTrackerUrl(): string {\n return `${this.config.baseUrl}/analytics/tracker.js`\n }\n\n /**\n * Load the tracker.js script\n */\n private loadTracker(): Promise<void> {\n if (!isBrowser) return Promise.resolve()\n\n // Already loaded\n if (window.LynkowAnalytics) {\n return Promise.resolve()\n }\n\n // Already loading\n if (this.loadPromise) {\n return this.loadPromise\n }\n\n this.loading = true\n this.loadPromise = new Promise((resolve, reject) => {\n // Check if script tag already exists\n if (document.getElementById(TRACKER_SCRIPT_ID)) {\n // Wait for it to load\n const checkLoaded = setInterval(() => {\n if (window.LynkowAnalytics) {\n clearInterval(checkLoaded)\n this.loading = false\n resolve()\n }\n }, 50)\n\n // Timeout after 10 seconds\n setTimeout(() => {\n clearInterval(checkLoaded)\n this.loading = false\n reject(new Error('Tracker script load timeout'))\n }, 10000)\n\n return\n }\n\n // Create and inject script\n const script = document.createElement('script')\n script.id = TRACKER_SCRIPT_ID\n script.src = this.getTrackerUrl()\n script.async = true\n script.setAttribute('data-site-id', this.config.siteId)\n if (this.config.baseUrl) {\n script.setAttribute('data-api-url', this.config.baseUrl)\n }\n\n // Pass consent mode to tracker if available in localStorage\n try {\n const storedMode = localStorage.getItem('_lkw_consent_mode')\n if (storedMode) {\n script.setAttribute('data-consent-mode', storedMode)\n }\n } catch {}\n\n script.onload = () => {\n this.loading = false\n // Wait a tick for the tracker to auto-initialize\n setTimeout(() => {\n if (window.LynkowAnalytics) {\n resolve()\n } else {\n reject(new Error('Tracker script loaded but LynkowAnalytics not found'))\n }\n }, 0)\n }\n\n script.onerror = () => {\n this.loading = false\n reject(new Error('Failed to load tracker script'))\n }\n\n document.head.appendChild(script)\n })\n\n return this.loadPromise\n }\n\n /**\n * Initializes analytics tracking by loading the tracker.js script into the\n * page and waiting for it to auto-initialize. This is idempotent -- calling\n * it multiple times has no effect after the first successful initialization.\n * No-op on server.\n *\n * The tracker script is loaded from `{baseUrl}/analytics/tracker.js` with\n * `data-site-id` and `data-api-url` attributes. If a consent mode is stored\n * in `localStorage`, it is passed via `data-consent-mode`.\n *\n * @returns Resolves when the tracker is loaded and ready, or immediately if\n * already initialized or running on the server\n * @throws Logs an error to console if the tracker script fails to load\n * (does not reject the promise)\n *\n * @example\n * ```typescript\n * // Manually initialize analytics in an SPA after client-side routing is ready\n * const lynkow = createClient({ siteId: '...' })\n * await lynkow.analytics.init()\n * ```\n */\n async init(): Promise<void> {\n if (!isBrowser || this.initialized) return\n\n try {\n await this.loadTracker()\n\n // The tracker auto-initializes via data-site-id attribute\n // But we can also manually init if needed\n if (window.LynkowAnalytics && !this.initialized) {\n // Tracker already auto-initialized via data-site-id\n this.initialized = true\n }\n } catch (error) {\n console.error('[Lynkow] Failed to initialize analytics:', error)\n }\n }\n\n /**\n * Tracks a custom event via the Lynkow analytics tracker. Automatically\n * initializes the tracker if not already loaded. No-op on server or when\n * tracking is disabled.\n *\n * @param event - Event data object. Must include a `type` string identifier\n * (e.g. `'purchase'`, `'signup'`, `'click'`). Additional properties are\n * passed through as custom event data (any key-value pairs).\n * @returns Resolves when the event has been queued for delivery\n *\n * @example\n * ```typescript\n * await lynkow.analytics.trackEvent({\n * type: 'purchase',\n * productId: 'abc123',\n * amount: 99.99\n * })\n * ```\n */\n async trackEvent(event: EventData): Promise<void> {\n if (!isBrowser || !this.enabled) return\n\n await this.init()\n\n if (window.LynkowAnalytics) {\n window.LynkowAnalytics.track(event)\n }\n }\n\n /**\n * Manually tracks a pageview event. The tracker automatically captures pageviews\n * on initial page load, so this method is only needed for SPA client-side\n * navigation (e.g. Next.js App Router route changes). Automatically initializes\n * the tracker if not already loaded. No-op on server or when tracking is disabled.\n *\n * @param data - Optional pageview data:\n * - `path` — page path (defaults to `window.location.pathname`)\n * - `title` — page title (defaults to `document.title`)\n * - `referrer` — referrer URL (defaults to `document.referrer`)\n * @returns Resolves when the pageview has been queued for delivery\n *\n * @example\n * ```typescript\n * // After SPA client-side navigation\n * await lynkow.analytics.trackPageview({ path: '/new-page' })\n *\n * // Or let it auto-detect from the current URL\n * await lynkow.analytics.trackPageview()\n * ```\n */\n async trackPageview(data?: PageviewData): Promise<void> {\n if (!isBrowser || !this.enabled) return\n\n await this.init()\n\n if (window.LynkowAnalytics) {\n window.LynkowAnalytics.track({\n type: 'pageview',\n path: data?.path || window.location.pathname,\n title: data?.title || document.title,\n referrer: data?.referrer || document.referrer,\n })\n }\n }\n\n /**\n * Enables analytics tracking. Tracking is enabled by default when the\n * service is created. Call this to re-enable after a previous `disable()` call.\n *\n * @example\n * ```typescript\n * // Re-enable tracking after the user grants analytics consent\n * lynkow.on('consent-changed', (categories) => {\n * if (categories.analytics) {\n * lynkow.analytics.enable()\n * } else {\n * lynkow.analytics.disable()\n * }\n * })\n * ```\n *\n * @returns void\n * @throws Never throws.\n */\n enable(): void {\n this.enabled = true\n }\n\n /**\n * Disables analytics tracking. While disabled, all `trackEvent()` and\n * `trackPageview()` calls become no-ops. The tracker script remains loaded;\n * call `destroy()` to fully remove it.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // Disable tracking when the user revokes analytics consent\n * lynkow.analytics.disable()\n * ```\n */\n disable(): void {\n this.enabled = false\n }\n\n /**\n * Returns whether analytics tracking is currently enabled (i.e. whether\n * `trackEvent` and `trackPageview` will actually emit). Does not\n * indicate whether the underlying tracker script has loaded; use\n * {@link isInitialized} for that.\n *\n * @returns `true` if tracking is enabled (default), `false` after\n * {@link disable} has been called.\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * if (lynkow.analytics.isEnabled()) {\n * // Safe to track\n * }\n * ```\n */\n isEnabled(): boolean {\n return this.enabled\n }\n\n /**\n * Returns whether the tracker.js script has been loaded and the\n * `window.LynkowAnalytics` global is available. Always `false` on the\n * server.\n *\n * @returns `true` if the tracker is fully loaded and ready to accept\n * events, `false` otherwise.\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * if (lynkow.analytics.isInitialized()) {\n * lynkow.analytics.trackEvent({ type: 'cta_click' })\n * }\n * ```\n */\n isInitialized(): boolean {\n return this.initialized && !!window.LynkowAnalytics\n }\n\n /**\n * Return the underlying `window.LynkowAnalytics` global for advanced\n * use cases that need direct access to the tracker's lower-level API\n * (e.g. calling `init` again with different options, or invoking\n * undocumented tracker internals).\n *\n * @returns The `LynkowAnalyticsGlobal` with `init()` and `track()`\n * methods, or `undefined` if running on the server or the tracker\n * script has not loaded yet.\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * const tracker = lynkow.analytics.getTracker()\n * tracker?.track({ custom: 'payload' })\n * ```\n */\n getTracker(): LynkowAnalyticsGlobal | undefined {\n if (!isBrowser) return undefined\n return window.LynkowAnalytics\n }\n\n /**\n * Removes the tracker.js script element from the DOM and resets the service\n * state. After calling `destroy()`, you can re-initialize by calling `init()`\n * again. No-op on server.\n *\n * @example\n * ```typescript\n * // Clean up analytics when unmounting (e.g. React useEffect cleanup)\n * useEffect(() => {\n * lynkow.analytics.init()\n * return () => lynkow.analytics.destroy()\n * }, [])\n * ```\n */\n destroy(): void {\n if (!isBrowser) return\n\n const script = document.getElementById(TRACKER_SCRIPT_ID)\n script?.remove()\n\n this.initialized = false\n this.loadPromise = null\n }\n}\n","/**\n * Site theme detection utility\n *\n * Detects the website's actual theme (dark/light) by inspecting DOM indicators,\n * rather than relying on the OS-level prefers-color-scheme media query.\n */\n\nimport { isBrowser } from '../core/environment'\n\n/**\n * Parse the luminance from a CSS background-color value.\n * Returns a number between 0 (black) and 1 (white), or null if unparseable/transparent.\n */\nfunction parseBackgroundLuminance(bgColor: string): number | null {\n // Match rgb(r, g, b) or rgba(r, g, b, a)\n const match = bgColor.match(\n /rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)(?:\\s*,\\s*([\\d.]+))?\\s*\\)/\n )\n if (!match) return null\n\n const alpha = match[4] !== undefined ? parseFloat(match[4]) : 1\n if (alpha === 0) return null // Fully transparent — can't determine theme\n\n const r = parseInt(match[1]!, 10)\n const g = parseInt(match[2]!, 10)\n const b = parseInt(match[3]!, 10)\n\n // Perceived luminance (ITU-R BT.601)\n return (0.299 * r + 0.587 * g + 0.114 * b) / 255\n}\n\n/**\n * Detect the website's theme by inspecting DOM indicators.\n *\n * Detection cascade:\n * 1. data-theme / data-mode / data-color-scheme attributes on <html> or <body>\n * 2. \"dark\" class on <html> or <body> (Tailwind convention)\n * 3. CSS color-scheme property on <html>\n * 4. Background color luminance of <body>\n * 5. Fallback: OS-level prefers-color-scheme media query\n *\n * @returns 'dark' or 'light'\n *\n * @example\n * ```typescript\n * // Apply a conditional class based on the site's current theme\n * const theme = detectSiteTheme()\n * document.body.classList.add(theme === 'dark' ? 'inverted-text' : 'default-text')\n * ```\n */\nexport function detectSiteTheme(): 'dark' | 'light' {\n if (!isBrowser) return 'light'\n\n const html = document.documentElement\n const body = document.body\n\n // 1. Check data attributes on <html> and <body>\n for (const el of [html, body]) {\n for (const attr of ['data-theme', 'data-mode', 'data-color-scheme']) {\n const value = el.getAttribute(attr)?.toLowerCase()\n if (value) {\n if (value.includes('dark')) return 'dark'\n if (value.includes('light')) return 'light'\n }\n }\n }\n\n // 2. Check for \"dark\" class (Tailwind / common convention)\n if (html.classList.contains('dark') || body.classList.contains('dark')) {\n return 'dark'\n }\n\n // 3. Check CSS color-scheme property\n try {\n const colorScheme = getComputedStyle(html).colorScheme\n if (colorScheme) {\n const normalized = colorScheme.toLowerCase().trim()\n if (normalized.startsWith('dark')) return 'dark'\n if (normalized.startsWith('light')) return 'light'\n }\n } catch {\n // getComputedStyle may fail in some environments\n }\n\n // 4. Check background color luminance\n try {\n const bgColor = getComputedStyle(body).backgroundColor\n const luminance = parseBackgroundLuminance(bgColor)\n if (luminance !== null) {\n return luminance < 0.5 ? 'dark' : 'light'\n }\n } catch {\n // getComputedStyle may fail in some environments\n }\n\n // 5. Fallback: OS-level preference\n try {\n if (window.matchMedia('(prefers-color-scheme: dark)').matches) {\n return 'dark'\n }\n } catch {\n // matchMedia may not be available\n }\n\n return 'light'\n}\n\n/**\n * Observe site theme changes in real-time.\n *\n * Watches for:\n * - Attribute changes on <html> and <body> (data-theme, data-mode, class, style)\n * - OS-level prefers-color-scheme media query changes\n *\n * @param callback Called with the new theme when a change is detected\n * @returns Cleanup function to stop observing\n *\n * @example\n * ```typescript\n * // Update a widget's appearance when the site theme changes\n * const stopObserving = onSiteThemeChange((theme) => {\n * widget.setTheme(theme)\n * })\n *\n * // Stop observing when no longer needed\n * stopObserving()\n * ```\n */\nexport function onSiteThemeChange(\n callback: (theme: 'dark' | 'light') => void\n): () => void {\n if (!isBrowser) return () => {}\n\n let currentTheme = detectSiteTheme()\n const cleanups: (() => void)[] = []\n\n const checkTheme = () => {\n const newTheme = detectSiteTheme()\n if (newTheme !== currentTheme) {\n currentTheme = newTheme\n callback(newTheme)\n }\n }\n\n // MutationObserver on <html> and <body> for attribute/class changes\n const observer = new MutationObserver(checkTheme)\n const observeOptions: MutationObserverInit = {\n attributes: true,\n attributeFilter: ['data-theme', 'data-mode', 'data-color-scheme', 'class', 'style'],\n }\n observer.observe(document.documentElement, observeOptions)\n observer.observe(document.body, observeOptions)\n cleanups.push(() => observer.disconnect())\n\n // matchMedia listener for OS-level changes\n try {\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n const handler = () => checkTheme()\n mq.addEventListener('change', handler)\n cleanups.push(() => mq.removeEventListener('change', handler))\n } catch {\n // matchMedia not available\n }\n\n return () => cleanups.forEach((fn) => fn())\n}\n","/**\n * Consent module for Lynkow SDK\n *\n * Hybrid module: API everywhere + UI browser-only\n */\n\nimport { isBrowser } from '../core/environment'\nimport { detectSiteTheme, onSiteThemeChange } from '../utils/theme'\nimport type { InternalConfig } from './base'\nimport type { CookieConfig, CookiePreferences, ThirdPartyScript } from '../types'\nimport type { EventEmitter } from '../utils/events'\n\n// Use same key as tracker.js for consistency\nconst STORAGE_KEY = '_lkw_consent'\n\n// Consent expires after 365 days\nconst CONSENT_EXPIRY_MS = 365 * 24 * 60 * 60 * 1000\n\n// Prefix for injected script element IDs\nconst SCRIPT_ID_PREFIX = 'lkw-script-'\n\n/**\n * User consent state for each cookie category Lynkow manages. Used as\n * input to {@link ConsentService.setCategories} and emitted as the payload\n * of the `'consent-changed'` client event.\n *\n * A category set to `false` means the corresponding scripts (from the\n * site's cookie configuration) MUST NOT be loaded; a category set to\n * `true` authorises them.\n */\nexport interface ConsentCategories {\n /**\n * Strictly necessary cookies (session, CSRF). Always `true`: users\n * cannot opt out because the site would be non-functional without them.\n * Included in the type so consent payloads remain uniform.\n */\n necessary: boolean\n /**\n * Consent to first-party and third-party analytics (e.g. the Lynkow\n * tracker, Google Analytics). When `false`, the analytics service\n * automatically becomes a no-op.\n */\n analytics: boolean\n /**\n * Consent to marketing and advertising cookies (ad networks,\n * retargeting). Required before loading scripts classified as\n * `marketing` in the site's cookie config.\n */\n marketing: boolean\n /**\n * Consent to personalization and preference cookies (theme, language,\n * non-essential UI state). Required before loading scripts classified\n * as `preferences` in the site's cookie config.\n */\n preferences: boolean\n}\n\n/**\n * Default consent categories (no consent given)\n */\nconst DEFAULT_CATEGORIES: ConsentCategories = {\n necessary: true,\n analytics: false,\n marketing: false,\n preferences: false,\n}\n\n/**\n * High-level consent management service with built-in banner UI and preferences modal.\n *\n * Accessible via `lynkow.consent`. This is a hybrid service:\n * - **API methods** (`getConfig`, `logConsent`) work everywhere (server + browser)\n * - **UI methods** (`show`, `hide`, `acceptAll`, `rejectAll`, `showPreferences`, etc.)\n * only work in the browser and are no-ops on the server\n *\n * Consent choices are persisted in `localStorage` under the `_lkw_consent` key\n * (compatible with tracker.js) and expire after 365 days. When consent is granted\n * for a category, any third-party scripts configured for that category are\n * automatically injected into the page.\n *\n * Emits a `'consent-changed'` event (via the SDK event emitter) and a\n * `'lynkow:consent:update'` CustomEvent on `document` whenever consent changes.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Show the consent banner (auto-skips if already consented)\n * lynkow.consent.show()\n *\n * // Check current consent\n * const categories = lynkow.consent.getCategories()\n * console.log(categories.analytics) // true or false\n *\n * // Programmatically accept all\n * lynkow.consent.acceptAll()\n * ```\n */\nexport class ConsentService {\n private config: InternalConfig\n private events: EventEmitter\n private bannerElement: HTMLElement | null = null\n private preferencesElement: HTMLElement | null = null\n private configCache: CookieConfig | null = null\n private injectedScriptIds = new Set<string>()\n private themeCleanup: (() => void) | null = null\n\n constructor(config: InternalConfig, events: EventEmitter) {\n this.config = config\n this.events = events\n }\n\n // === API Methods (work everywhere) ===\n\n /**\n * Fetches the cookie consent configuration from the API. The result is\n * cached in memory for the lifetime of the service instance (not TTL-based).\n * Works on both server and browser.\n *\n * @returns A `CookieConfig` object with banner settings, categories, texts,\n * theming options, and third-party script definitions.\n * @throws {Error} If the API request fails (non-2xx response). The SDK\n * does not wrap this in a {@link LynkowError} for this endpoint,\n * since consent config is fetched via a raw `fetch` call rather than\n * the shared request pipeline.\n *\n * @example\n * ```typescript\n * const config = await lynkow.consent.getConfig()\n * if (config.enabled) {\n * console.log('Categories:', config.categories.map((c) => c.id))\n * }\n * ```\n */\n async getConfig(): Promise<CookieConfig> {\n if (this.configCache) return this.configCache\n\n const url = `${this.config.baseUrl}/public/${this.config.siteId}/cookie-consent/config`\n const response = await fetch(url, {\n method: 'GET',\n headers: { 'Content-Type': 'application/json' },\n ...this.config.fetchOptions,\n })\n\n if (!response.ok) {\n throw new Error(`Failed to fetch consent config: ${response.status}`)\n }\n\n const data = await response.json()\n this.configCache = data.data as CookieConfig\n return this.configCache\n }\n\n /**\n * Logs the user's consent preferences to the server for GDPR audit trail.\n * Automatically generates or retrieves a persistent visitor ID from `localStorage`.\n * Errors are silently swallowed to avoid disrupting the user experience.\n * Works on both server and browser.\n *\n * @param preferences - Consent preferences as a record of category IDs to booleans\n * (e.g. `{ necessary: true, analytics: true, marketing: false }`)\n * @param action - Optional explicit action type. If omitted, the action is inferred\n * from the preferences (all true = `'accept_all'`, all false = `'reject_all'`,\n * mixed = `'customize'`). `'withdraw'` must be passed explicitly.\n * @returns A promise that resolves once the POST has completed or\n * been suppressed on error. Never rejects.\n * @throws Never throws. Network or server errors are caught and silently\n * discarded so the consent UI flow is never interrupted.\n *\n * @example\n * ```typescript\n * await lynkow.consent.logConsent(\n * { necessary: true, analytics: true, marketing: false },\n * 'customize'\n * )\n * ```\n */\n async logConsent(\n preferences: CookiePreferences,\n action?: 'accept_all' | 'reject_all' | 'customize' | 'withdraw'\n ): Promise<void> {\n const url = `${this.config.baseUrl}/public/${this.config.siteId}/cookie-consent/log`\n\n await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n visitorId: this.getOrCreateVisitorId(),\n action: action || this.inferAction(preferences),\n consentGiven: preferences,\n pageUrl: isBrowser ? window.location.href : undefined,\n }),\n ...this.config.fetchOptions,\n }).catch(() => {\n // Silently ignore errors\n })\n }\n\n // === Private Helpers ===\n\n private getOrCreateVisitorId(): string {\n if (!isBrowser) return 'server'\n\n const key = '_lkw_vid'\n try {\n let vid = localStorage.getItem(key)\n if (!vid) {\n vid = crypto.randomUUID()\n localStorage.setItem(key, vid)\n }\n return vid\n } catch {\n return crypto.randomUUID()\n }\n }\n\n private inferAction(\n preferences: CookiePreferences\n ): 'accept_all' | 'reject_all' | 'customize' {\n const values = Object.entries(preferences).filter(([k]) => k !== 'necessary')\n const allTrue = values.every(([, v]) => v === true)\n const allFalse = values.every(([, v]) => v === false)\n if (allTrue) return 'accept_all'\n if (allFalse) return 'reject_all'\n return 'customize'\n }\n\n // === Storage Methods ===\n // Format: { choices: { necessary, analytics, marketing, preferences } }\n // This format is compatible with tracker.js\n\n private getStoredConsent(): ConsentCategories | null {\n if (!isBrowser) return null\n\n try {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) {\n const data = JSON.parse(stored)\n // Handle both old format and new { choices: {...} } format\n if (data.choices) {\n // Check expiry if timestamp exists (old entries without timestamp are kept)\n if (data.timestamp && Date.now() - data.timestamp > CONSENT_EXPIRY_MS) {\n localStorage.removeItem(STORAGE_KEY)\n return null\n }\n return data.choices as ConsentCategories\n }\n return data as ConsentCategories\n }\n } catch {\n // localStorage unavailable\n }\n\n return null\n }\n\n private saveConsent(categories: ConsentCategories): void {\n if (!isBrowser) return\n\n try {\n // Save in { choices: {...}, timestamp } format for tracker.js compatibility + expiry\n localStorage.setItem(STORAGE_KEY, JSON.stringify({ choices: categories, timestamp: Date.now() }))\n } catch {\n // localStorage unavailable\n }\n\n this.events.emit('consent-changed', categories as unknown as Record<string, boolean>)\n\n // Dispatch custom event for tracker.js\n document.dispatchEvent(new CustomEvent('lynkow:consent:update', {\n detail: categories\n }))\n }\n\n // === UI Methods (browser-only) ===\n\n /**\n * Shows the consent banner in the browser. If the user has already consented\n * (choices stored in `localStorage`), the banner is skipped and accepted\n * third-party scripts are injected directly instead. Respects the site's\n * theme (light/dark/auto) and position settings. No-op on server.\n *\n * When theme is `'auto'`, a MutationObserver watches for site theme changes\n * and updates the banner colors in real-time.\n *\n * @returns void\n * @throws Never throws. Config-fetch rejections are caught internally.\n *\n * @example\n * ```typescript\n * // Show the consent banner on page load (skips if already consented)\n * const lynkow = createClient({ siteId: '...' })\n * lynkow.consent.show()\n * ```\n */\n show(): void {\n if (!isBrowser) return\n\n this.getConfig().then((config) => {\n if (!config.enabled) return\n\n // Store consent mode in localStorage for tracker.js to read\n try {\n if ((config as any).consentMode) {\n localStorage.setItem('_lkw_consent_mode', (config as any).consentMode)\n }\n } catch {}\n\n // If consent already stored, just inject scripts\n const storedConsent = this.getStoredConsent()\n if (storedConsent) {\n this.activateScripts(storedConsent)\n return\n }\n\n if (this.bannerElement) return\n\n const wrapper = document.createElement('div')\n wrapper.innerHTML = this.createBannerHTML(config)\n this.bannerElement = wrapper.firstElementChild as HTMLElement\n document.body.appendChild(this.bannerElement)\n this.attachBannerEvents()\n\n // Observe theme changes when theme is 'auto'\n if (config.theme === 'auto' && !this.themeCleanup) {\n this.themeCleanup = onSiteThemeChange((theme) => {\n this.updateConsentTheme(theme)\n })\n }\n })\n }\n\n /**\n * Hide and remove the consent banner from the DOM. Does not affect\n * stored consent preferences (the user will not be re-prompted on the\n * next page load if they already chose). No-op on server or if the\n * banner is not currently shown.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // Close the banner after the user clicked \"Accept all\" via custom UI:\n * lynkow.consent.hide()\n * ```\n */\n hide(): void {\n if (!isBrowser) return\n\n this.bannerElement?.remove()\n this.bannerElement = null\n this.cleanupThemeObserverIfIdle()\n }\n\n /**\n * Open the preferences modal so the user can toggle individual consent\n * categories (analytics, marketing, preferences). The `necessary`\n * category is always checked and disabled. Pre-populates checkboxes\n * with the user's current consent state. No-op on server or if the\n * modal is already mounted.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```tsx\n * <button onClick={() => lynkow.consent.showPreferences()}>\n * Cookie settings\n * </button>\n * ```\n */\n showPreferences(): void {\n if (!isBrowser) return\n if (this.preferencesElement) return\n\n this.getConfig().then((config) => {\n const categories = this.getCategories()\n\n const wrapper = document.createElement('div')\n wrapper.innerHTML = this.createPreferencesHTML(config, categories)\n this.preferencesElement = wrapper.firstElementChild as HTMLElement\n document.body.appendChild(this.preferencesElement)\n this.attachPreferencesEvents(config)\n\n // Observe theme changes when theme is 'auto'\n if (config.theme === 'auto' && !this.themeCleanup) {\n this.themeCleanup = onSiteThemeChange((theme) => {\n this.updateConsentTheme(theme)\n })\n }\n })\n }\n\n /**\n * Returns the user's current consent categories. If no consent has been\n * stored yet, returns default values (only `necessary: true`, all others `false`).\n * On the server, always returns defaults.\n *\n * @returns A `ConsentCategories` object with boolean values for `necessary`,\n * `analytics`, `marketing`, and `preferences`\n *\n * @example\n * ```typescript\n * // Read current preferences to conditionally load a third-party widget\n * const prefs = lynkow.consent.getCategories()\n * if (prefs.marketing) {\n * loadChatWidget()\n * }\n * ```\n */\n getCategories(): ConsentCategories {\n if (!isBrowser) return { ...DEFAULT_CATEGORIES }\n return this.getStoredConsent() || { ...DEFAULT_CATEGORIES }\n }\n\n /**\n * Checks whether the user has already made a consent choice (accepted,\n * rejected, or customized). Returns `false` if no consent is stored or\n * if the stored consent has expired (after 365 days).\n *\n * @returns `true` if consent choices exist in `localStorage`, `false` otherwise.\n * Always returns `false` on the server.\n *\n * @example\n * ```typescript\n * // Only show the consent banner if the user hasn't already decided\n * if (!lynkow.consent.hasConsented()) {\n * lynkow.consent.show()\n * }\n * ```\n */\n hasConsented(): boolean {\n if (!isBrowser) return false\n return this.getStoredConsent() !== null\n }\n\n /**\n * Accepts all cookie categories (necessary, analytics, marketing, preferences),\n * saves the choice to `localStorage`, logs consent to the server, injects all\n * accepted third-party scripts, and hides the banner. No-op on server.\n *\n * @example\n * ```typescript\n * // Wire up a one-click \"Accept All\" button in your own UI\n * document.getElementById('accept-cookies').addEventListener('click', () => {\n * lynkow.consent.acceptAll()\n * })\n * ```\n */\n acceptAll(): void {\n if (!isBrowser) return\n\n const categories: ConsentCategories = {\n necessary: true,\n analytics: true,\n marketing: true,\n preferences: true,\n }\n this.saveConsent(categories)\n this.logConsent(categories as unknown as CookiePreferences, 'accept_all')\n this.activateScripts(categories)\n this.hide()\n }\n\n /**\n * Rejects all optional cookie categories (analytics, marketing, preferences).\n * Only `necessary` remains true (cannot be disabled). Saves the choice to\n * `localStorage`, logs consent to the server, and hides the banner.\n * No third-party scripts are injected. No-op on server.\n *\n * @example\n * ```typescript\n * // Reject all non-essential cookies from a custom banner\n * document.getElementById('reject-cookies').addEventListener('click', () => {\n * lynkow.consent.rejectAll()\n * })\n * ```\n */\n rejectAll(): void {\n if (!isBrowser) return\n\n const categories: ConsentCategories = {\n necessary: true,\n analytics: false,\n marketing: false,\n preferences: false,\n }\n this.saveConsent(categories)\n this.logConsent(categories as unknown as CookiePreferences, 'reject_all')\n this.hide()\n }\n\n /**\n * Sets specific consent categories, merging with the current state.\n * The `necessary` category is always forced to `true` regardless of input.\n * Saves the choice, logs to server, and injects scripts for accepted categories.\n * No-op on server.\n *\n * @param categories - Partial consent categories to update. Unspecified categories\n * retain their current value. Example: `{ analytics: true, marketing: false }`\n *\n * @example\n * ```typescript\n * // Save custom preferences from a preferences form\n * lynkow.consent.setCategories({\n * analytics: true,\n * marketing: false,\n * preferences: true,\n * })\n * ```\n */\n setCategories(categories: Partial<ConsentCategories>): void {\n if (!isBrowser) return\n\n const current = this.getCategories()\n const newCategories: ConsentCategories = {\n ...current,\n ...categories,\n necessary: true, // Always required\n }\n this.saveConsent(newCategories)\n this.logConsent(newCategories as unknown as CookiePreferences, 'customize')\n this.activateScripts(newCategories)\n }\n\n /**\n * Resets all consent choices by clearing `localStorage`, removing any\n * previously injected third-party scripts, and re-showing the consent\n * banner. Useful for providing a \"manage cookies\" link that lets users\n * change their preferences. No-op on server.\n *\n * @returns void\n * @throws Never throws. localStorage errors are silently ignored.\n *\n * @example\n * ```typescript\n * // Add a \"Manage cookies\" link in the footer to let users re-choose\n * document.getElementById('manage-cookies').addEventListener('click', () => {\n * lynkow.consent.reset()\n * })\n * ```\n */\n reset(): void {\n if (!isBrowser) return\n\n this.removeInjectedScripts()\n\n try {\n localStorage.removeItem(STORAGE_KEY)\n } catch {\n // Silently ignore\n }\n\n this.events.emit('consent-changed', { ...DEFAULT_CATEGORIES } as unknown as Record<string, boolean>)\n this.show()\n }\n\n // === Script Injection ===\n\n private activateScripts(categories: ConsentCategories): void {\n if (!this.configCache?.thirdPartyScripts?.length) return\n\n for (const [category, accepted] of Object.entries(categories)) {\n if (accepted) {\n this.injectScriptsForCategory(category, this.configCache.thirdPartyScripts)\n }\n }\n }\n\n private injectScriptsForCategory(category: string, scripts: ThirdPartyScript[]): void {\n for (const script of scripts) {\n if (script.category !== category) continue\n if (this.injectedScriptIds.has(script.id)) continue\n\n this.injectedScriptIds.add(script.id)\n\n const wrapper = document.createElement('div')\n wrapper.innerHTML = script.script\n const elements = Array.from(wrapper.children)\n\n for (let i = 0; i < elements.length; i++) {\n const el = elements[i]!\n const elId = `${SCRIPT_ID_PREFIX}${script.id}-${i}`\n\n if (el.tagName === 'SCRIPT') {\n const newScript = document.createElement('script')\n // Copy all attributes from source element\n for (const attr of Array.from(el.attributes)) {\n newScript.setAttribute(attr.name, attr.value)\n }\n if ((el as HTMLScriptElement).textContent) {\n newScript.textContent = (el as HTMLScriptElement).textContent\n }\n newScript.id = elId\n document.head.appendChild(newScript)\n } else {\n (el as HTMLElement).id = elId\n document.body.appendChild(el)\n }\n }\n }\n }\n\n private removeInjectedScripts(): void {\n for (const scriptId of this.injectedScriptIds) {\n let i = 0\n let el: Element | null\n while ((el = document.getElementById(`${SCRIPT_ID_PREFIX}${scriptId}-${i}`))) {\n el.remove()\n i++\n }\n }\n this.injectedScriptIds.clear()\n }\n\n // === Private UI Helpers ===\n\n /**\n * Stop the theme observer if no consent UI is visible.\n */\n private cleanupThemeObserverIfIdle(): void {\n if (!this.bannerElement && !this.preferencesElement) {\n this.themeCleanup?.()\n this.themeCleanup = null\n }\n }\n\n /**\n * Update visible consent UI elements when the site theme changes.\n */\n private updateConsentTheme(theme: 'dark' | 'light'): void {\n const colors = this.configCache\n ? this.resolveColors(this.configCache, theme)\n : { bgColor: theme === 'dark' ? '#18181b' : '#ffffff', textColor: theme === 'dark' ? '#f4f4f5' : '#1a1a1a' }\n const { bgColor, textColor } = colors\n\n // Update banner\n if (this.bannerElement) {\n this.bannerElement.style.background = bgColor\n this.bannerElement.style.color = textColor\n }\n\n // Update preferences modal inner container\n if (this.preferencesElement) {\n const innerDiv = this.preferencesElement.querySelector(':scope > div') as HTMLElement\n if (innerDiv) {\n innerDiv.style.background = bgColor\n innerDiv.style.color = textColor\n }\n }\n }\n\n private resolveTheme(theme: string): 'light' | 'dark' {\n if (theme === 'auto') return detectSiteTheme()\n return theme === 'dark' ? 'dark' : 'light'\n }\n\n private resolveColors(config: CookieConfig, resolvedTheme: 'light' | 'dark') {\n if (config.themeStyles?.[resolvedTheme]) {\n return config.themeStyles[resolvedTheme]\n }\n const isDark = resolvedTheme === 'dark'\n return {\n primaryColor: config.primaryColor || '#0066cc',\n bgColor: isDark ? '#18181b' : '#ffffff',\n textColor: isDark ? '#f4f4f5' : '#1a1a1a',\n }\n }\n\n private contrastColor(hex: string): string {\n const r = parseInt(hex.slice(1, 3), 16)\n const g = parseInt(hex.slice(3, 5), 16)\n const b = parseInt(hex.slice(5, 7), 16)\n return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5 ? '#000000' : '#ffffff'\n }\n\n private createBannerHTML(config: CookieConfig): string {\n const position = config.position || 'bottom-right'\n const theme = config.theme || 'light'\n const borderRadius = config.borderRadius ?? 8\n const fontSize = config.fontSize ?? 13\n\n // Floating position styles\n const floatingStyles: Record<string, string> = {\n 'bottom-left': `bottom: 20px; left: 20px; max-width: 580px; border-radius: ${borderRadius}px;`,\n 'bottom-right': `bottom: 20px; right: 20px; max-width: 580px; border-radius: ${borderRadius}px;`,\n }\n\n const positionStyle = floatingStyles[position] || floatingStyles['bottom-right']\n\n const resolvedTheme = this.resolveTheme(theme)\n const colors = this.resolveColors(config, resolvedTheme)\n const { primaryColor, bgColor, textColor } = colors\n\n const texts = config.texts || {\n description:\n 'Ce site utilise des cookies pour ameliorer votre experience.',\n acceptAll: 'Accepter tout',\n rejectAll: 'Refuser',\n customize: 'Personnaliser',\n save: 'Enregistrer',\n }\n\n return `\n <div id=\"lynkow-consent-banner\" style=\"\n position: fixed;\n ${positionStyle}\n z-index: 99999;\n padding: 16px 20px;\n background: ${bgColor};\n color: ${textColor};\n box-shadow: 0 4px 20px rgba(0,0,0,0.15);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: ${fontSize}px;\n \">\n <div style=\"display: flex; align-items: center; gap: 16px; flex-wrap: wrap;\">\n <p style=\"margin: 0; line-height: 1.5; flex: 1; min-width: 200px;\">\n ${texts.description}${(() => {\n const url = config.cookiePolicyUrl || config.privacyPolicyUrl\n if (!url) return ''\n const label = texts.privacyPolicy || 'En savoir plus'\n return ` <a href=\"${url}\" target=\"_blank\" rel=\"noopener\" style=\"text-decoration: underline; color: inherit;\">${label}</a>`\n })()}\n </p>\n <div style=\"display: flex; gap: 8px; align-items: center; flex-wrap: wrap;\">\n <button id=\"lynkow-consent-accept\" style=\"\n padding: 8px 16px;\n background: ${primaryColor};\n color: ${this.contrastColor(primaryColor)};\n border: none;\n border-radius: ${borderRadius}px;\n cursor: pointer;\n font-size: ${fontSize}px;\n white-space: nowrap;\n \">${texts.acceptAll}</button>\n <button id=\"lynkow-consent-reject\" style=\"\n padding: 8px 16px;\n background: transparent;\n color: inherit;\n border: 1px solid currentColor;\n border-radius: ${borderRadius}px;\n cursor: pointer;\n font-size: ${fontSize}px;\n white-space: nowrap;\n \">${texts.rejectAll}</button>\n ${config.showCustomizeButton !== false ? `<button id=\"lynkow-consent-preferences\" style=\"\n padding: 8px 4px;\n background: transparent;\n color: inherit;\n border: none;\n cursor: pointer;\n font-size: ${fontSize}px;\n text-decoration: underline;\n white-space: nowrap;\n \">${texts.customize}</button>` : ''}\n </div>\n </div>\n </div>\n `\n }\n\n private createPreferencesHTML(\n config: CookieConfig,\n currentCategories: ConsentCategories\n ): string {\n const theme = config.theme || 'light'\n const borderRadius = config.borderRadius ?? 8\n const fontSize = config.fontSize ?? 13\n const resolvedTheme = this.resolveTheme(theme)\n const colors = this.resolveColors(config, resolvedTheme)\n const { primaryColor, bgColor, textColor } = colors\n\n const texts = config.texts || {\n save: 'Enregistrer',\n acceptAll: 'Accepter tout',\n rejectAll: 'Refuser',\n customize: 'Personnaliser',\n description: '',\n }\n\n const categories = config.categories || []\n const categoriesHTML = categories\n .map(\n (cat) => `\n <label style=\"display: flex; align-items: flex-start; gap: 10px; margin: 15px 0; cursor: ${cat.required ? 'not-allowed' : 'pointer'};\">\n <input\n type=\"checkbox\"\n name=\"${cat.id}\"\n ${currentCategories[cat.id as keyof ConsentCategories] ? 'checked' : ''}\n ${cat.required ? 'disabled checked' : ''}\n style=\"width: 18px; height: 18px; margin-top: 2px; accent-color: ${primaryColor};\"\n />\n <div style=\"flex: 1;\">\n <strong style=\"opacity: ${cat.required ? '0.6' : '1'};\">\n ${cat.name}${cat.required ? ' (requis)' : ''}\n </strong>\n <p style=\"margin: 5px 0 0 0; font-size: 13px; opacity: 0.8;\">\n ${cat.description}\n </p>\n </div>\n </label>\n `\n )\n .join('')\n\n return `\n <div id=\"lynkow-consent-preferences-modal\" style=\"\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 100000;\n background: rgba(0,0,0,0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n \">\n <div style=\"\n background: ${bgColor};\n color: ${textColor};\n padding: 30px;\n border-radius: ${borderRadius}px;\n max-width: 500px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n \">\n <form id=\"lynkow-consent-form\">\n ${categoriesHTML}\n\n <div style=\"display: flex; gap: 10px; margin-top: 25px; padding-top: 20px; border-top: 1px solid rgba(128,128,128,0.3);\">\n <button type=\"submit\" style=\"\n padding: 10px 20px;\n background: ${primaryColor};\n color: ${this.contrastColor(primaryColor)};\n border: none;\n border-radius: ${borderRadius}px;\n cursor: pointer;\n font-size: ${fontSize}px;\n \">${texts.save || 'Enregistrer'}</button>\n <button type=\"button\" id=\"lynkow-consent-close\" style=\"\n padding: 10px 20px;\n background: transparent;\n color: inherit;\n border: 1px solid currentColor;\n border-radius: ${borderRadius}px;\n cursor: pointer;\n font-size: ${fontSize}px;\n \">Annuler</button>\n </div>\n </form>\n </div>\n </div>\n `\n }\n\n private attachBannerEvents(): void {\n const acceptBtn = document.getElementById('lynkow-consent-accept')\n const rejectBtn = document.getElementById('lynkow-consent-reject')\n const prefsBtn = document.getElementById('lynkow-consent-preferences')\n\n acceptBtn?.addEventListener('click', () => {\n this.acceptAll()\n })\n\n rejectBtn?.addEventListener('click', () => {\n this.rejectAll()\n })\n\n prefsBtn?.addEventListener('click', () => {\n this.showPreferences()\n })\n }\n\n private attachPreferencesEvents(_config: CookieConfig): void {\n const form = document.getElementById(\n 'lynkow-consent-form'\n ) as HTMLFormElement\n const closeBtn = document.getElementById('lynkow-consent-close')\n const modal = document.getElementById('lynkow-consent-preferences-modal')\n\n form?.addEventListener('submit', (e) => {\n e.preventDefault()\n const formData = new FormData(form)\n\n const newCategories: ConsentCategories = {\n necessary: true,\n analytics: formData.has('analytics'),\n marketing: formData.has('marketing'),\n preferences: formData.has('preferences'),\n }\n\n this.setCategories(newCategories)\n this.hide()\n\n this.preferencesElement?.remove()\n this.preferencesElement = null\n this.cleanupThemeObserverIfIdle()\n })\n\n closeBtn?.addEventListener('click', () => {\n this.preferencesElement?.remove()\n this.preferencesElement = null\n this.cleanupThemeObserverIfIdle()\n })\n\n modal?.addEventListener('click', (e) => {\n if (e.target === modal) {\n this.preferencesElement?.remove()\n this.preferencesElement = null\n this.cleanupThemeObserverIfIdle()\n }\n })\n }\n\n /**\n * Clean up every DOM resource this service owns: removes the banner\n * and preferences modal, removes injected third-party scripts, and\n * stops the theme observer. Call when unmounting the SDK (e.g. inside\n * a React `useEffect` cleanup) so the page can be navigated without\n * leaking listeners. No-op on server.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```tsx\n * useEffect(() => {\n * lynkow.consent.show()\n * return () => lynkow.consent.destroy()\n * }, [])\n * ```\n */\n destroy(): void {\n this.themeCleanup?.()\n this.themeCleanup = null\n this.hide()\n this.preferencesElement?.remove()\n this.preferencesElement = null\n this.removeInjectedScripts()\n }\n}\n","/**\n * Branding module for Lynkow SDK\n *\n * Browser-only module. Displays \"Made with Lynkow\" badge for free plan users.\n * Badge HTML/CSS is fetched from the API so it can be updated server-side.\n */\n\nimport { BaseService, CACHE_TTL } from './base'\nimport { isBrowser } from '../core/environment'\nimport { detectSiteTheme, onSiteThemeChange } from '../utils/theme'\n\nconst BADGE_CONTAINER_ID = 'lynkow-badge-container'\nconst STYLES_ID = 'lynkow-badge-styles'\n\ninterface BadgeResponse {\n data: {\n html: string\n css: string\n }\n}\n\n/**\n * Service for displaying the \"Powered by Lynkow\" branding badge.\n *\n * Accessible via `lynkow.branding`. This is a **browser-only** service -- all\n * methods are no-ops on the server. The badge HTML and CSS are fetched from the\n * API (cached for 30 minutes, LONG TTL) so they can be updated server-side\n * without SDK changes. The badge automatically adapts to the site's light/dark\n * theme via a MutationObserver.\n *\n * The badge is shown for sites on the free plan (`siteConfig.showBranding === true`).\n * It fails silently if the fetch fails (the badge is not critical).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Inject the badge\n * await lynkow.branding.inject()\n *\n * // Check if visible\n * console.log(lynkow.branding.isVisible()) // true\n *\n * // Remove it\n * lynkow.branding.remove()\n * ```\n */\nexport class BrandingService extends BaseService {\n private containerElement: HTMLElement | null = null\n private themeCleanup: (() => void) | null = null\n\n /**\n * Fetches the badge HTML/CSS from the API and injects it into the page DOM.\n * The badge is appended to `document.body` and styled according to the site's\n * current theme (light/dark). A theme observer is set up to update the badge\n * if the site theme changes dynamically. Idempotent -- calling multiple times\n * has no effect if the badge is already injected. Fails silently on fetch\n * errors. No-op on server.\n *\n * @returns Resolves when the badge is injected, or immediately if already present\n * or running on the server\n *\n * @example\n * ```typescript\n * // Inject the powered-by badge after the page has loaded\n * const lynkow = createClient({ siteId: '...' })\n * await lynkow.branding.inject()\n * ```\n */\n async inject(): Promise<void> {\n if (!isBrowser) return\n if (document.getElementById(BADGE_CONTAINER_ID)) return\n\n try {\n const { data } = await this.getWithCache<BadgeResponse>(\n 'branding:badge',\n '/branding/badge',\n undefined,\n undefined,\n CACHE_TTL.LONG\n )\n\n // Inject styles\n if (!document.getElementById(STYLES_ID)) {\n const styleElement = document.createElement('style')\n styleElement.id = STYLES_ID\n styleElement.textContent = data.css\n document.head.appendChild(styleElement)\n }\n\n // Inject badge HTML\n const container = document.createElement('div')\n container.id = BADGE_CONTAINER_ID\n container.innerHTML = data.html\n\n // Apply theme class based on site's actual theme\n if (detectSiteTheme() === 'light') {\n container.classList.add('lynkow-badge-light')\n }\n\n document.body.appendChild(container)\n this.containerElement = container\n\n // Observe theme changes to update badge in real-time\n this.themeCleanup = onSiteThemeChange((theme) => {\n if (this.containerElement) {\n this.containerElement.classList.toggle('lynkow-badge-light', theme === 'light')\n }\n })\n } catch {\n // Fail silently — badge is not critical\n }\n }\n\n /**\n * Remove the branding badge and its associated `<style>` block from\n * the DOM and stop the theme observer. No-op on server or if the\n * badge is not currently injected.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.branding.remove()\n * ```\n */\n remove(): void {\n if (!isBrowser) return\n\n this.themeCleanup?.()\n this.themeCleanup = null\n\n this.containerElement?.remove()\n this.containerElement = null\n\n document.getElementById(STYLES_ID)?.remove()\n }\n\n /**\n * Check whether the branding badge is currently mounted in the DOM.\n * Useful for conditional logic (e.g. show a custom \"Powered by\" only\n * when the badge is not already rendered).\n *\n * @returns `true` if the badge container element exists in the\n * document, `false` otherwise. Always returns `false` on the server.\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * if (!lynkow.branding.isVisible()) {\n * // Render our own attribution\n * }\n * ```\n */\n isVisible(): boolean {\n if (!isBrowser) return false\n return document.getElementById(BADGE_CONTAINER_ID) !== null\n }\n\n /**\n * Alias for {@link remove}. Cleans up the badge, styles, and theme\n * observer. Matches the `destroy()` naming used on other lifecycle\n * services so cleanup code can stay uniform.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```tsx\n * useEffect(() => {\n * return () => lynkow.branding.destroy()\n * }, [])\n * ```\n */\n destroy(): void {\n this.remove()\n }\n}\n","/**\n * Content Enhancements module for Lynkow SDK\n *\n * Browser-only module. Adds interactive features to content rendered from the API:\n * - Copy button for code blocks\n * - (Future: lightbox for images, table of contents, etc.)\n */\n\nimport { isBrowser } from '../core/environment'\n\nconst STYLES_ID = 'lynkow-enhancements-styles'\nconst CLONE_ATTR = 'data-lynkow-clone'\n\n/**\n * MIME types the browser recognizes as executable JavaScript, per HTML spec\n * (https://html.spec.whatwg.org/#javascript-mime-type), plus `text/plain` which\n * is the consent-gated pattern: consumers render `<script type=\"text/plain\">`\n * to prevent execution and rely on `activateScripts()` to strip the type so\n * the clone runs.\n *\n * Anything outside this set (notably `application/ld+json`, `application/json`,\n * `importmap`, `speculationrules`) is data for the browser, not code, and must\n * not be cloned — doing so creates phantom duplicates in the DOM.\n */\nconst EXECUTABLE_SCRIPT_TYPES = new Set([\n '',\n 'module',\n 'text/plain',\n 'text/javascript',\n 'application/javascript',\n 'application/ecmascript',\n 'text/ecmascript',\n 'application/x-javascript',\n 'application/x-ecmascript',\n])\n\n// Copy icon SVG\nconst COPY_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg>`\n\n// Check icon SVG (for copied state)\nconst CHECK_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>`\n\nconst ENHANCEMENT_STYLES = `\n /*\n * Lynkow Content Enhancements\n * These styles ensure that content from the Lynkow API is displayed correctly,\n * even when the client uses CSS frameworks like Tailwind or CSS resets.\n */\n\n /* Text alignment - Ensure inline styles are respected */\n [style*=\"text-align: center\"] {\n text-align: center !important;\n }\n\n [style*=\"text-align: right\"] {\n text-align: right !important;\n }\n\n [style*=\"text-align: justify\"] {\n text-align: justify !important;\n }\n\n /* Preserve common inline formatting */\n h1, h2, h3, h4, h5, h6, p, blockquote {\n /* Allow inline text-align to override */\n text-align: inherit;\n }\n\n /* Code block enhancements */\n .code-block {\n position: relative;\n margin: 1rem 0;\n }\n\n .code-block-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0.5rem 1rem;\n background: #1e1e1e;\n border-radius: 0.5rem 0.5rem 0 0;\n border-bottom: 1px solid #333;\n }\n\n .code-block-language {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 0.75rem;\n color: #888;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n .code-block-copy {\n display: flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n color: #888;\n cursor: pointer;\n padding: 0.25rem;\n border-radius: 0.25rem;\n transition: color 0.2s, background-color 0.2s;\n }\n\n .code-block-copy:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.1);\n }\n\n .code-block-copy.copied {\n color: #22c55e;\n }\n\n .code-block pre {\n margin: 0;\n border-radius: 0 0 0.5rem 0.5rem;\n background: #1e1e1e;\n overflow-x: auto;\n }\n\n .code-block code {\n display: block;\n padding: 1rem;\n font-family: 'Fira Code', 'JetBrains Mono', 'SF Mono', Consolas, monospace;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #e4e4e4;\n }\n\n /* Light mode support */\n @media (prefers-color-scheme: light) {\n .code-block-header {\n background: #f5f5f5;\n border-bottom-color: #e0e0e0;\n }\n\n .code-block-language {\n color: #666;\n }\n\n .code-block-copy {\n color: #666;\n }\n\n .code-block-copy:hover {\n color: #1a1a1a;\n background: rgba(0, 0, 0, 0.05);\n }\n\n .code-block pre {\n background: #f5f5f5;\n }\n\n .code-block code {\n color: #1a1a1a;\n }\n }\n`\n\n/**\n * Service for adding interactive features to content rendered from the Lynkow API.\n *\n * Accessible via `lynkow.enhancements`. This is a **browser-only** service -- all\n * methods are no-ops on the server. Currently provides:\n * - **Copy button** for code blocks (elements with `[data-copy-code]` attribute)\n * - **Script activation** for inline scripts injected via `dangerouslySetInnerHTML`\n * - **Widget iframe auto-resize** for embedded Lynkow widgets\n * - **CSS normalization** to ensure content renders correctly with CSS frameworks (Tailwind, etc.)\n *\n * A `MutationObserver` automatically detects new content added to the DOM and\n * applies enhancements, making it compatible with SPA frameworks like React/Next.js.\n * Script clones are appended to `<head>` (not inline) to avoid React DOM reconciliation issues.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: 'your-site-id' })\n *\n * // Enhancements are automatically initialized by the client\n * // Or manually reinitialize after loading dynamic content:\n * lynkow.enhancements.init()\n * ```\n */\nexport class EnhancementsService {\n private initialized = false\n private observer: MutationObserver | null = null\n private pendingFrame: number | null = null\n private handleWidgetResize = (event: MessageEvent) => {\n if (!event.data || event.data.type !== 'lynkow-widget-resize') return\n const iframes = document.querySelectorAll<HTMLIFrameElement>(\n 'iframe[src*=\"/widgets/calendar/\"]'\n )\n iframes.forEach((iframe) => {\n if (iframe.contentWindow === event.source) {\n iframe.style.height = event.data.height + 'px'\n }\n })\n }\n\n /**\n * Inject styles into document head\n */\n private injectStyles(): void {\n if (!isBrowser) return\n if (document.getElementById(STYLES_ID)) return\n\n const styleElement = document.createElement('style')\n styleElement.id = STYLES_ID\n styleElement.textContent = ENHANCEMENT_STYLES\n document.head.appendChild(styleElement)\n }\n\n /**\n * Handle copy button click\n */\n private async handleCopyClick(button: HTMLElement): Promise<void> {\n const codeBlock = button.closest('.code-block')\n if (!codeBlock) return\n\n const codeElement = codeBlock.querySelector('code')\n if (!codeElement) return\n\n const code = codeElement.textContent || ''\n\n try {\n await navigator.clipboard.writeText(code)\n\n // Show success state\n button.classList.add('copied')\n button.innerHTML = CHECK_ICON\n\n // Reset after 2 seconds\n setTimeout(() => {\n button.classList.remove('copied')\n button.innerHTML = COPY_ICON\n }, 2000)\n } catch (err) {\n console.error('Lynkow SDK: Failed to copy code', err)\n }\n }\n\n /**\n * Bind copy functionality to all code blocks\n */\n private bindCodeBlockCopy(): void {\n if (!isBrowser) return\n\n const copyButtons = document.querySelectorAll<HTMLElement>('[data-copy-code]')\n\n copyButtons.forEach((button) => {\n // Avoid binding twice\n if (button.dataset['lynkowBound']) return\n button.dataset['lynkowBound'] = 'true'\n\n button.addEventListener('click', (e) => {\n e.preventDefault()\n this.handleCopyClick(button)\n })\n })\n }\n\n /**\n * Re-execute inline scripts that were inserted via innerHTML/dangerouslySetInnerHTML.\n * Browsers refuse to run <script> tags injected this way, so we clone them\n * into fresh <script> elements which the browser will execute.\n *\n * The clone is appended to <head> instead of replacing the original in-place.\n * This avoids mutating React's managed DOM tree, which would cause crashes\n * during client-side navigations in Next.js / React 19 (replaceChild would\n * desync the real DOM from React's virtual DOM, leading to\n * \"Node.insertBefore/removeChild: not a child of this node\" errors).\n */\n private activateScripts(container?: Node): void {\n if (!isBrowser) return\n\n const root = container instanceof HTMLElement ? container : document.body\n const scripts = root.querySelectorAll<HTMLScriptElement>('script:not([data-lynkow-activated])')\n\n // Filter to scripts we need to process: inline (no src), still in the DOM,\n // and of an executable JavaScript MIME type. Non-executable types like\n // `application/ld+json` must be skipped — cloning them into <head> would\n // duplicate structured data that Google (and other parsers) then flag as\n // \"duplicate field\" errors in Search Console.\n const toActivate = Array.from(scripts).filter(\n (s) => !s.src && s.isConnected && EXECUTABLE_SCRIPT_TYPES.has(s.type.toLowerCase())\n )\n if (toActivate.length === 0) return\n\n // Remove old clones from <head> before creating new ones.\n // This prevents accumulation across navigations: when React re-renders\n // a page, fresh script elements are created (without data-lynkow-activated),\n // and old clones from the previous page must be cleaned up.\n document.head.querySelectorAll(`script[${CLONE_ATTR}]`).forEach((el) => el.remove())\n\n toActivate.forEach((original) => {\n original.setAttribute('data-lynkow-activated', 'true')\n\n const replacement = document.createElement('script')\n // Copy attributes, but skip type=\"text/plain\" (used to prevent execution before activation)\n // and data-lynkow-activated (marker for the original only)\n const attrs = original.attributes\n for (let i = 0; i < attrs.length; i++) {\n const attr = attrs[i]\n if (!attr) continue\n if (attr.name === 'type' && attr.value === 'text/plain') continue\n if (attr.name === 'data-lynkow-activated') continue\n replacement.setAttribute(attr.name, attr.value)\n }\n replacement.textContent = original.textContent\n replacement.setAttribute(CLONE_ATTR, '')\n // Append to <head> — outside React's managed DOM tree\n document.head.appendChild(replacement)\n })\n }\n\n /**\n * Initializes content enhancements: injects CSS styles, binds copy-to-clipboard\n * handlers on code blocks, activates inline scripts, starts the widget iframe\n * resize listener, and sets up a `MutationObserver` to automatically enhance\n * newly added DOM content. Idempotent -- calling multiple times has no effect\n * after the first initialization. No-op on server.\n *\n * Call this manually if you need to re-initialize after the client is created\n * (e.g. after dynamically loading content outside the initial render).\n *\n * @example\n * ```typescript\n * // Reinitialize enhancements after dynamically loading new content\n * const html = await fetchArticleContent()\n * document.getElementById('article').innerHTML = html\n * lynkow.enhancements.init()\n * ```\n */\n init(): void {\n if (!isBrowser || this.initialized) return\n\n // Inject styles\n this.injectStyles()\n\n // Bind existing elements and activate scripts\n this.bindCodeBlockCopy()\n this.activateScripts()\n\n // Listen for widget iframe resize messages\n window.addEventListener('message', this.handleWidgetResize)\n\n // Set up MutationObserver for dynamic content.\n // Mutations are debounced via requestAnimationFrame to avoid running\n // mid-reconciliation during React concurrent renders / soft navigations.\n if (!this.observer) {\n this.observer = new MutationObserver(() => {\n if (this.pendingFrame !== null) return\n this.pendingFrame = requestAnimationFrame(() => {\n this.pendingFrame = null\n this.activateScripts()\n this.bindCodeBlockCopy()\n })\n })\n\n this.observer.observe(document.body, {\n childList: true,\n subtree: true,\n })\n }\n\n this.initialized = true\n }\n\n /**\n * Check whether the enhancements service has been initialized for the\n * current page. Returns `false` on the server and after a call to\n * {@link destroy} (until `init()` is invoked again).\n *\n * @returns `true` if `init()` has been called successfully, `false`\n * otherwise.\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * if (!lynkow.enhancements.isInitialized()) {\n * lynkow.enhancements.init()\n * }\n * ```\n */\n isInitialized(): boolean {\n return this.initialized\n }\n\n /**\n * Clean up every resource the enhancements service owns: disconnect the\n * MutationObserver, remove the widget resize listener, cancel any\n * pending animation frame, remove injected styles and cloned scripts\n * from `<head>`, and reset the initialized state. Safe to call\n * repeatedly; subsequent calls are no-ops. After `destroy()` you can\n * re-attach by calling `init()` again. No-op on server.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```tsx\n * useEffect(() => {\n * lynkow.enhancements.init()\n * return () => lynkow.enhancements.destroy()\n * }, [])\n * ```\n */\n destroy(): void {\n if (!isBrowser) return\n\n // Remove widget resize listener\n window.removeEventListener('message', this.handleWidgetResize)\n\n // Cancel pending debounced frame\n if (this.pendingFrame !== null) {\n cancelAnimationFrame(this.pendingFrame)\n this.pendingFrame = null\n }\n\n // Disconnect observer\n if (this.observer) {\n this.observer.disconnect()\n this.observer = null\n }\n\n // Remove styles and cloned scripts\n document.getElementById(STYLES_ID)?.remove()\n document.head.querySelectorAll(`script[${CLONE_ATTR}]`).forEach((el) => el.remove())\n\n this.initialized = false\n }\n}\n","/**\n * Options for building srcset URLs\n */\nexport interface SrcsetOptions {\n /**\n * Pixel widths to generate in the srcset, in ascending order. Each\n * width becomes a `Nw` entry in the returned string. Defaults to\n * `[400, 800, 1200, 1920]` to cover phone, tablet, desktop, large\n * desktop. Supply a custom list when you know your layout's\n * breakpoints to avoid bandwidth waste.\n */\n widths?: number[]\n /**\n * Cloudflare resize fit mode. `'scale-down'` (default) preserves\n * aspect ratio and never upscales; `'cover'` fills the box and crops;\n * `'contain'` fits inside the box with letterboxing; `'crop'` hard\n * crops to the exact dimensions. Must pair with `gravity` for\n * `'cover'` / `'crop'` when the subject is not centered.\n */\n fit?: 'cover' | 'contain' | 'scale-down' | 'crop'\n /**\n * JPEG / WebP quality on a 1-100 scale. Higher values produce larger\n * files. Default `80` strikes a good balance for photography; drop to\n * 60-70 for hero images on slow connections.\n */\n quality?: number\n /**\n * Focal point for `fit: 'cover' | 'crop'` as an `XxY` pair of\n * fractions (e.g. `'0.5x0.3'` keeps the horizontal center but biases\n * towards the upper third). Omit to center the crop.\n */\n gravity?: string\n}\n\n/**\n * Options for building a single transformed URL. Matches the Cloudflare\n * Image Transformations query parameters; omit any value to let the CDN\n * choose a sensible default.\n */\nexport interface TransformOptions {\n /**\n * Target width in pixels. When set alone, height is derived from the\n * image's aspect ratio (unless `fit` requires both).\n */\n w?: number\n /**\n * Target height in pixels. When set alone, width is derived from the\n * image's aspect ratio (unless `fit` requires both).\n */\n h?: number\n /**\n * Resize fit mode. See {@link SrcsetOptions.fit} for semantics; the\n * default here is `'scale-down'` to avoid accidental upscaling.\n */\n fit?: 'cover' | 'contain' | 'scale-down' | 'crop'\n /** Image quality 1-100 (default: 80) */\n quality?: number\n /**\n * Output image format. `'auto'` (default) lets the CDN negotiate\n * based on `Accept` (WebP on modern browsers, AVIF on the newest).\n * Force a specific format only when the consumer is known.\n */\n format?: 'auto' | 'webp' | 'avif' | 'jpeg'\n /**\n * Focal point for `fit: 'cover' | 'crop'` as an `XxY` pair of\n * fractions (see {@link SrcsetOptions.gravity}). Omit to center.\n */\n gravity?: string\n /**\n * Device Pixel Ratio multiplier on a `1`-`4` scale. Multiplies the\n * rendered width/height so a 400px box renders sharp at 2x on\n * Retina. Prefer using a `srcset` over a hard-coded DPR.\n */\n dpr?: number\n}\n\n/**\n * Service for building optimized image URLs using Cloudflare Image Transformations.\n *\n * Accessible via `lynkow.media`. This is a pure utility service (no API calls, no\n * caching) that constructs Cloudflare `/cdn-cgi/image/` URLs from Lynkow media URLs.\n * Works on both server and browser. Handles both original URLs (`/sites/...`) and\n * already-transformed URLs (`/cdn-cgi/image/...`), re-extracting the original path\n * when needed.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Build responsive srcset for an <img> tag\n * const srcset = lynkow.media.srcset(content.featuredImage)\n * // => \"https://cdn.../cdn-cgi/image/w=400,.../path 400w, ...1920w\"\n *\n * // Build a single transformed URL\n * const url = lynkow.media.transform(content.featuredImage, { w: 800, format: 'webp' })\n * ```\n */\nexport class MediaHelperService {\n /**\n * Generates an HTML `srcset` attribute value from a Lynkow image URL, suitable\n * for use in an `<img srcset=\"...\">` or `<source srcset=\"...\">` tag. Produces\n * one Cloudflare Image Transformation URL per width breakpoint.\n *\n * @param imageUrl - Original image URL from the Lynkow API (e.g. `content.featuredImage`).\n * Accepts `null` or `undefined` safely (returns empty string).\n * @param options - Configuration for the srcset:\n * - `widths` — array of pixel widths to generate (default: `[400, 800, 1200, 1920]`)\n * - `fit` — resize mode: `'cover'`, `'contain'`, `'scale-down'`, or `'crop'` (default: `'scale-down'`)\n * - `quality` — image quality 1-100 (default: `80`)\n * - `gravity` — focal point for crop mode (e.g. `'0.5x0.3'`)\n * @returns A srcset string like `\"url 400w, url 800w, url 1200w, url 1920w\"`,\n * or an empty string if the URL is null/undefined or not a valid Lynkow CDN URL\n *\n * @example\n * ```typescript\n * // Build a responsive <img> tag with multiple widths\n * const srcsetValue = lynkow.media.srcset(article.featuredImage, {\n * widths: [480, 768, 1024, 1440],\n * fit: 'cover',\n * quality: 85,\n * })\n * const imgTag = `<img srcset=\"${srcsetValue}\" sizes=\"(max-width: 768px) 100vw, 50vw\" alt=\"${article.title}\" />`\n * ```\n */\n srcset(imageUrl: string | null | undefined, options: SrcsetOptions = {}): string {\n if (!imageUrl) return ''\n\n const {\n widths = [400, 800, 1200, 1920],\n fit = 'scale-down',\n quality = 80,\n gravity,\n } = options\n\n const parsed = this.parseImageUrl(imageUrl)\n if (!parsed) return ''\n\n return widths\n .map((w) => {\n const params = [\n `w=${w}`,\n `fit=${fit}`,\n 'format=auto',\n `quality=${quality}`,\n gravity && `gravity=${gravity}`,\n ]\n .filter(Boolean)\n .join(',')\n\n return `${parsed.cdnBase}/cdn-cgi/image/${params}/${parsed.relativePath} ${w}w`\n })\n .join(', ')\n }\n\n /**\n * Generates a single Cloudflare Image Transformation URL from a Lynkow image URL.\n * Useful for thumbnails, hero images, or any context where you need a specific\n * size/format.\n *\n * @param imageUrl - Original image URL from the Lynkow API (e.g. `content.featuredImage`).\n * Accepts `null` or `undefined` safely (returns empty string).\n * @param options - Transformation options:\n * - `w` / `h` — target width/height in pixels\n * - `fit` — resize mode: `'cover'`, `'contain'`, `'scale-down'`, or `'crop'` (default: `'scale-down'`)\n * - `quality` — image quality 1-100 (default: `80`)\n * - `format` — output format: `'auto'`, `'webp'`, `'avif'`, or `'jpeg'` (default: `'auto'`)\n * - `gravity` — focal point for crop mode (e.g. `'0.5x0.3'`)\n * - `dpr` — device pixel ratio 1-4 for retina displays\n * @returns The transformed Cloudflare URL, the original URL if transformation is\n * not possible (non-Lynkow URL), or an empty string if the URL is null/undefined\n *\n * @example\n * ```typescript\n * // Build an optimized thumbnail URL for a product card\n * const thumbUrl = lynkow.media.transform(product.image, {\n * w: 400,\n * h: 300,\n * fit: 'cover',\n * format: 'webp',\n * quality: 75,\n * })\n * document.querySelector('.product-thumb').src = thumbUrl\n * ```\n */\n transform(imageUrl: string | null | undefined, options: TransformOptions = {}): string {\n if (!imageUrl) return ''\n\n const parsed = this.parseImageUrl(imageUrl)\n if (!parsed) return imageUrl || ''\n\n const params = [\n options.w && `w=${options.w}`,\n options.h && `h=${options.h}`,\n `fit=${options.fit || 'scale-down'}`,\n `format=${options.format || 'auto'}`,\n `quality=${options.quality || 80}`,\n options.gravity && `gravity=${options.gravity}`,\n options.dpr && `dpr=${options.dpr}`,\n ]\n .filter(Boolean)\n .join(',')\n\n return `${parsed.cdnBase}/cdn-cgi/image/${params}/${parsed.relativePath}`\n }\n\n /**\n * Extracts the CDN base and relative path from a Lynkow image URL.\n *\n * Handles both original URLs and already-transformed URLs.\n *\n * @returns Parsed URL parts, or null if the URL is not a valid Lynkow CDN URL\n */\n private parseImageUrl(\n imageUrl: string\n ): { cdnBase: string; relativePath: string } | null {\n // Case 1: Already a transformed URL\n const cdnCgiIndex = imageUrl.indexOf('/cdn-cgi/image/')\n if (cdnCgiIndex !== -1) {\n const cdnBase = imageUrl.substring(0, cdnCgiIndex)\n const afterOptions = imageUrl.substring(cdnCgiIndex + '/cdn-cgi/image/'.length)\n const firstSlash = afterOptions.indexOf('/')\n if (firstSlash === -1) return null\n const relativePath = afterOptions.substring(firstSlash + 1)\n return { cdnBase, relativePath }\n }\n\n // Case 2: Original URL with /sites/ path\n const sitesIndex = imageUrl.indexOf('/sites/')\n if (sitesIndex !== -1) {\n const cdnBase = imageUrl.substring(0, sitesIndex)\n const relativePath = imageUrl.substring(sitesIndex + 1)\n return { cdnBase, relativePath }\n }\n\n // Case 3: Original URL with /avatars/ path\n const avatarsIndex = imageUrl.indexOf('/avatars/')\n if (avatarsIndex !== -1) {\n const cdnBase = imageUrl.substring(0, avatarsIndex)\n const relativePath = imageUrl.substring(avatarsIndex + 1)\n return { cdnBase, relativePath }\n }\n\n return null\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type { BaseRequestOptions } from '../types'\n\n/**\n * A single search result (hit) returned by Lynkow Instant Search.\n *\n * Contains article metadata, URL path, and optional highlighted matches.\n * Only published content appears in search results.\n */\nexport interface SearchHit {\n /** Content UUID. Matches `id` on `Content` / `ContentSummary`. */\n id: string\n /**\n * Content title in the searched locale. Never empty (the indexer skips\n * untitled drafts). May contain `<em>` markers when highlighting is\n * enabled; see `_formatted.title` for the highlighted variant.\n */\n title: string\n /** URL-safe slug. Unique within the site + locale combination. */\n slug: string\n /**\n * Short text summary shown in the search dropdown. Taken from the\n * content's `excerpt` field, or generated from the first paragraph\n * when `excerpt` is empty. Always non-null in search results.\n */\n excerpt: string\n /**\n * SEO meta title. Falls back to {@link title} when the content has no\n * override. Max ~255 characters, generally kept under 60 for search\n * result listings.\n */\n metaTitle: string\n /**\n * SEO meta description. Falls back to {@link excerpt} when the content\n * has no override. Max ~500 characters, typically under 160 for\n * search snippets.\n */\n metaDescription: string\n /** Content locale code (e.g. `'fr'`, `'en'`) */\n locale: string\n /** Full URL path including locale and category prefix (e.g. `'/fr/guides/forms'`) */\n path: string\n /**\n * Content type slug. Currently always `'post'`; the value is exposed\n * to support future content types (product, event, etc.) without a\n * breaking schema change.\n */\n type: string\n /**\n * Categories assigned to this content as minimal `{ name, slug }`\n * projections. Empty array when the content has no category. Use the\n * `slug` to build category URLs and pass to\n * {@link CategoriesService.getBySlug} for full detail.\n */\n categories: Array<{ name: string; slug: string }>\n /**\n * Tags assigned to this content as minimal `{ name, slug }`\n * projections. Empty array when the content has no tag. Use the\n * `slug` for tag-filtered listings.\n */\n tags: Array<{ name: string; slug: string }>\n /**\n * Full name of the content author (or the site's default author\n * when the content has no explicit author). Always non-empty because\n * the indexer requires a tokenizable value.\n */\n authorName: string\n /** Featured image URL, or `null` if none */\n featuredImage: string | null\n /** Publication date as Unix timestamp (seconds) */\n publishedAt: number\n /** Last update date as Unix timestamp (seconds) */\n updatedAt: number\n /**\n * Highlighted matches with `<em>` tags around matching terms.\n * Only present when the search engine returns formatted results.\n * Keys match the field names (e.g. `title`, `excerpt`, `body`).\n *\n * @example\n * ```typescript\n * hit._formatted?.title // \"Getting Started with <em>Pagination</em>\"\n * ```\n */\n _formatted?: Record<string, string>\n}\n\n/**\n * Response from `lynkow.search.search()`.\n *\n * Contains an array of matching articles and pagination metadata.\n */\nexport interface SearchResponse {\n /**\n * Matching articles for the current page of results, already ordered\n * by relevance. Empty array when the query matches nothing.\n */\n data: SearchHit[]\n /**\n * Pagination and query metadata for rendering controls like \"N results\n * in 42 ms\" or a paginator. Uses snake-free naming (`page`,\n * `totalPages`, `perPage`) that differs from the standard\n * {@link PaginationMeta} because the search engine response predates\n * the common pagination type.\n */\n meta: {\n /**\n * Total number of matching results across every page. Useful for\n * \"Showing 1-10 of 142\" UI. Capped at the search engine's\n * configured upper bound (50_000 by default).\n */\n total: number\n /** Current page number (1-based) */\n page: number\n /**\n * Total number of pages for the current query + `perPage`. `1` when\n * results fit on one page, `0` when `total === 0`.\n */\n totalPages: number\n /**\n * Number of results per page as actually applied by the search\n * engine (the request's `limit` clamped to 1-100).\n */\n perPage: number\n /**\n * The search query echoed back from the server, useful for\n * debouncing UIs that drop late responses when the input has\n * changed. Whitespace is preserved as-sent.\n */\n query: string\n /** Search engine processing time in milliseconds */\n processingTimeMs: number\n }\n}\n\n/**\n * Options for `lynkow.search.search()`.\n *\n * All filters are optional. When omitted, searches across all published\n * content in all locales.\n */\nexport interface SearchOptions extends BaseRequestOptions {\n /** Filter by locale code (e.g. `'fr'`, `'en'`). Omit to search all locales. */\n locale?: string\n /** Filter by category slug (e.g. `'guides'`). Omit to search all categories. */\n category?: string\n /** Filter by tag slug (e.g. `'featured'`). Omit to search all tags. */\n tag?: string\n /** Page number (1-based). Defaults to `1`. */\n page?: number\n /** Results per page (1--100). Defaults to `20`. */\n limit?: number\n}\n\n/**\n * Configuration for client-side direct search.\n *\n * Returned by `lynkow.search.getConfig()`. Use these values to initialize\n * a search client in the browser for instant autocomplete without\n * round-tripping through your server.\n */\nexport interface SearchConfig {\n /**\n * Public search engine host URL (e.g. `'https://search.lynkow.com'`).\n * Use as the `host` when instantiating a Meilisearch-compatible\n * browser client. Does not include a trailing slash.\n */\n host: string\n /** Short-lived tenant token (JWT, 1-hour expiry) scoped to your site's index */\n apiKey: string\n /**\n * Meilisearch index name for this site, of the form\n * `site-<siteId>_<locale>` or `site-<siteId>` for single-locale sites.\n * Pass verbatim as the `index` parameter in browser search queries.\n */\n indexName: string\n}\n\n/**\n * Lynkow Instant Search service.\n *\n * Accessible via `lynkow.search`. Provides full-text search with typo\n * tolerance across all published content. Results are not cached (search\n * queries are dynamic by nature).\n *\n * Search must be enabled in the admin dashboard (**Settings > SEO > Search**)\n * before it can be used. When disabled, all methods return a 503 error.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Search with filters\n * const { data, meta } = await lynkow.search.search('pagination', {\n * locale: 'en',\n * category: 'guides',\n * limit: 10,\n * })\n *\n * // Iterate results\n * for (const hit of data) {\n * console.log(hit.title, hit.path, hit._formatted?.title)\n * }\n * ```\n */\nexport class SearchService extends BaseService {\n /**\n * Search published content with typo tolerance.\n *\n * Queries the search index for articles matching the given query string.\n * Results are ordered by relevance and include highlighted matches in\n * the `_formatted` field.\n *\n * @param query - The search query string. Typos are handled automatically\n * (e.g. `'pagniation'` finds articles about pagination).\n * @param options - Optional filters and pagination:\n * - `locale` — filter by locale code (e.g. `'fr'`)\n * - `category` — filter by category slug (e.g. `'guides'`)\n * - `tag` — filter by tag slug (e.g. `'featured'`)\n * - `page` — page number, 1-based (default: `1`)\n * - `limit` — results per page, 1--100 (default: `20`)\n * @returns A `SearchResponse` containing `data` (array of `SearchHit`)\n * and `meta` (pagination info with total, page, totalPages, perPage,\n * query, processingTimeMs)\n * @throws {LynkowError} With code `'NETWORK_ERROR'` if the API is unreachable\n * @throws {LynkowError} With status `503` if search is not enabled for this site\n *\n * @example\n * ```typescript\n * // Basic search\n * const results = await lynkow.search.search('forms')\n *\n * // With filters\n * const results = await lynkow.search.search('formulaire', {\n * locale: 'fr',\n * category: 'guides',\n * page: 1,\n * limit: 5,\n * })\n *\n * // Access highlighted results\n * results.data.forEach(hit => {\n * console.log(hit._formatted?.title || hit.title)\n * })\n * ```\n */\n async search(query: string, options?: SearchOptions): Promise<SearchResponse> {\n return this.get<SearchResponse>('/search', {\n q: query,\n locale: options?.locale,\n category: options?.category,\n tag: options?.tag,\n page: options?.page,\n limit: options?.limit,\n }, options)\n }\n\n /**\n * Get search configuration for client-side direct search.\n *\n * Returns the search host URL, a short-lived tenant token (JWT, 1-hour\n * expiry), and the index name for your site. Use these values to query\n * the search engine directly from the browser without round-tripping\n * through your server.\n *\n * The response is cached for 10 minutes. Tenant tokens expire after\n * 1 hour — for long-lived pages, call this method periodically to\n * refresh the token.\n *\n * @param options - Base request options (custom fetch options)\n * @returns A `SearchConfig` with `host`, `apiKey` (tenant token), and `indexName`\n * @throws {LynkowError} With status `503` if search is not enabled for this site\n *\n * @example\n * ```typescript\n * const config = await lynkow.search.getConfig()\n * // {\n * // host: 'https://search.lynkow.com',\n * // apiKey: 'eyJhbGciOi...',\n * // indexName: 'site_abc123_def4_...'\n * // }\n * ```\n */\n async getConfig(options?: BaseRequestOptions): Promise<SearchConfig> {\n return this.getWithCache<SearchConfig>(\n 'search-config',\n '/search/config',\n undefined,\n options,\n CACHE_TTL.MEDIUM\n )\n }\n}\n","/**\n * Cache manager for Lynkow SDK\n * Uses memory cache on server, localStorage on browser\n */\n\nimport { isBrowser } from './environment'\n\n/**\n * Cache entry with TTL\n */\ninterface CacheEntry<T> {\n value: T\n expiresAt: number\n}\n\n/**\n * Cache configuration\n */\nexport interface CacheConfig {\n /** Default TTL in milliseconds (default: 5 minutes) */\n defaultTtl?: number\n /** Storage key prefix */\n prefix?: string\n}\n\nconst DEFAULT_TTL = 5 * 60 * 1000 // 5 minutes\nconst STORAGE_PREFIX = 'lynkow_cache_'\n\n/**\n * In-memory cache for server environment\n */\nconst memoryCache = new Map<string, CacheEntry<unknown>>()\n\n/**\n * Create a cache manager\n */\nexport function createCache(config: CacheConfig = {}) {\n const defaultTtl = config.defaultTtl ?? DEFAULT_TTL\n const prefix = config.prefix ?? STORAGE_PREFIX\n\n /**\n * Get full cache key with prefix\n */\n function getKey(key: string): string {\n return `${prefix}${key}`\n }\n\n /**\n * Check if an entry is expired\n */\n function isExpired(entry: CacheEntry<unknown>): boolean {\n return Date.now() > entry.expiresAt\n }\n\n /**\n * Get value from cache\n */\n function get<T>(key: string): T | null {\n const fullKey = getKey(key)\n\n if (isBrowser) {\n try {\n const stored = localStorage.getItem(fullKey)\n if (!stored) return null\n\n const entry = JSON.parse(stored) as CacheEntry<T>\n if (isExpired(entry)) {\n localStorage.removeItem(fullKey)\n return null\n }\n return entry.value\n } catch {\n return null\n }\n }\n\n // Server: use memory cache\n const entry = memoryCache.get(fullKey) as CacheEntry<T> | undefined\n if (!entry) return null\n\n if (isExpired(entry)) {\n memoryCache.delete(fullKey)\n return null\n }\n return entry.value\n }\n\n /**\n * Set value in cache\n */\n function set<T>(key: string, value: T, ttl: number = defaultTtl): void {\n const fullKey = getKey(key)\n const entry: CacheEntry<T> = {\n value,\n expiresAt: Date.now() + ttl,\n }\n\n if (isBrowser) {\n try {\n localStorage.setItem(fullKey, JSON.stringify(entry))\n } catch {\n // localStorage full or unavailable, silently fail\n }\n return\n }\n\n // Server: use memory cache\n memoryCache.set(fullKey, entry)\n }\n\n /**\n * Remove value from cache\n */\n function remove(key: string): void {\n const fullKey = getKey(key)\n\n if (isBrowser) {\n try {\n localStorage.removeItem(fullKey)\n } catch {\n // Silently fail\n }\n return\n }\n\n memoryCache.delete(fullKey)\n }\n\n /**\n * Invalidate cache entries matching a pattern\n * If no pattern is provided, clears all cache entries\n */\n function invalidate(pattern?: string): void {\n if (isBrowser) {\n try {\n const keysToRemove: string[] = []\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i)\n if (key && key.startsWith(prefix)) {\n if (!pattern || key.includes(pattern)) {\n keysToRemove.push(key)\n }\n }\n }\n keysToRemove.forEach((key) => localStorage.removeItem(key))\n } catch {\n // Silently fail\n }\n return\n }\n\n // Server: clear memory cache\n if (!pattern) {\n // Clear all entries with our prefix\n for (const key of memoryCache.keys()) {\n if (key.startsWith(prefix)) {\n memoryCache.delete(key)\n }\n }\n } else {\n // Clear entries matching pattern\n for (const key of memoryCache.keys()) {\n if (key.startsWith(prefix) && key.includes(pattern)) {\n memoryCache.delete(key)\n }\n }\n }\n }\n\n /**\n * Get or set value in cache\n * If value exists and is not expired, returns cached value\n * Otherwise, calls factory function and caches the result\n */\n async function getOrSet<T>(\n key: string,\n factory: () => Promise<T>,\n ttl: number = defaultTtl\n ): Promise<T> {\n const cached = get<T>(key)\n if (cached !== null) {\n return cached\n }\n\n const value = await factory()\n set(key, value, ttl)\n return value\n }\n\n return {\n get,\n set,\n remove,\n invalidate,\n getOrSet,\n }\n}\n\nexport type Cache = ReturnType<typeof createCache>\n","/**\n * Conditional logging utility for Lynkow SDK\n * Only logs when debug mode is enabled\n */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error'\n\n/**\n * Logger configuration\n */\nexport interface LoggerConfig {\n debug: boolean\n prefix?: string\n}\n\n/**\n * Create a logger instance with the given configuration\n *\n * @param config - Logger configuration\n * @returns Logger instance\n */\nexport function createLogger(config: LoggerConfig) {\n const prefix = config.prefix || '[Lynkow]'\n\n return {\n /**\n * Log a debug message (only when debug mode is enabled)\n */\n debug(...args: unknown[]): void {\n if (config.debug) {\n console.debug(prefix, ...args)\n }\n },\n\n /**\n * Log an info message\n */\n info(...args: unknown[]): void {\n console.info(prefix, ...args)\n },\n\n /**\n * Log a warning message\n */\n warn(...args: unknown[]): void {\n console.warn(prefix, ...args)\n },\n\n /**\n * Log an error message\n */\n error(...args: unknown[]): void {\n console.error(prefix, ...args)\n },\n\n /**\n * Log a message at the specified level\n */\n log(level: LogLevel, ...args: unknown[]): void {\n switch (level) {\n case 'debug':\n this.debug(...args)\n break\n case 'info':\n this.info(...args)\n break\n case 'warn':\n this.warn(...args)\n break\n case 'error':\n this.error(...args)\n break\n }\n },\n }\n}\n\nexport type Logger = ReturnType<typeof createLogger>\n","/**\n * Simple event emitter for Lynkow SDK.\n * Provides a lightweight pub/sub system for reacting to SDK lifecycle events.\n */\n\n/**\n * Event types emitted by the SDK.\n * Each key is an event name; the value type is the payload passed to listeners.\n * Use `void` payload events with `emit(event, undefined as any)`.\n */\nexport interface LynkowEvents {\n /**\n * Emitted once when the SDK client is fully initialized and ready to make requests.\n * No payload. Subscribe before calling any service methods to ensure readiness.\n */\n ready: void\n\n /**\n * Emitted when the active locale changes (e.g. via user action or detection).\n * Payload is the new locale code (e.g. `'fr'`, `'en'`).\n * Use this to re-fetch locale-dependent data (contents, categories, pages).\n */\n 'locale-changed': string\n\n /**\n * Emitted when the user updates their cookie consent preferences.\n * Payload is a map of category ID to consent boolean\n * (e.g. `{ necessary: true, analytics: false, marketing: false }`).\n * Use this to inject or remove third-party scripts based on consent.\n */\n 'consent-changed': Record<string, boolean>\n\n /**\n * Emitted when an SDK operation encounters an unrecoverable error.\n * Payload is the `Error` (typically a {@link LynkowError}) that occurred.\n * Use this for global error logging or displaying error notifications.\n */\n error: Error\n}\n\nexport type EventName = keyof LynkowEvents\n\n/**\n * Callback function type for event listeners.\n * @typeParam T - The payload type for the event being listened to\n */\nexport type EventListener<T> = (data: T) => void\n\n/**\n * Create a new event emitter instance backing the `lynkow.on/off/once`\n * API of the client. Each SDK client creates its own emitter, so events\n * do not leak between client instances (e.g. multi-tenant server rendering).\n *\n * @returns An {@link EventEmitter} object exposing `on`, `off`, `emit`,\n * `once`, and `removeAllListeners`. `on()` and `once()` return an\n * unsubscribe closure for convenience.\n *\n * @example\n * ```typescript\n * import { createEventEmitter } from 'lynkow'\n *\n * const events = createEventEmitter()\n * const unsubscribe = events.on('locale-changed', (locale) => {\n * refetchContent(locale)\n * })\n * events.emit('locale-changed', 'fr')\n * unsubscribe()\n * ```\n */\nexport function createEventEmitter() {\n const listeners = new Map<string, Set<EventListener<unknown>>>()\n\n /**\n * Subscribe to an event.\n * The listener is called synchronously each time the event is emitted.\n * If a listener throws, the error is caught and logged -- it does not\n * prevent other listeners from being called.\n *\n * @param event - Event name to subscribe to\n * @param listener - Callback invoked with the event payload\n * @returns An unsubscribe function. Call it to remove this specific listener.\n *\n * @example\n * ```typescript\n * const unsubscribe = emitter.on('locale-changed', (locale) => {\n * console.log('Locale changed to:', locale)\n * })\n * // Later: unsubscribe()\n * ```\n */\n function on<K extends EventName>(\n event: K,\n listener: EventListener<LynkowEvents[K]>\n ): () => void {\n if (!listeners.has(event)) {\n listeners.set(event, new Set())\n }\n listeners.get(event)!.add(listener as EventListener<unknown>)\n\n // Return unsubscribe function\n return () => off(event, listener)\n }\n\n /**\n * Unsubscribe a specific listener from an event.\n * No-op if the listener was not previously registered.\n *\n * @param event - Event name to unsubscribe from\n * @param listener - The exact function reference that was passed to `on()`\n */\n function off<K extends EventName>(\n event: K,\n listener: EventListener<LynkowEvents[K]>\n ): void {\n const eventListeners = listeners.get(event)\n if (eventListeners) {\n eventListeners.delete(listener as EventListener<unknown>)\n }\n }\n\n /**\n * Emit an event, calling all registered listeners synchronously.\n * Listener errors are caught and logged to the console without\n * interrupting other listeners.\n *\n * @param event - Event name to emit\n * @param data - Payload to pass to all listeners\n */\n function emit<K extends EventName>(event: K, data: LynkowEvents[K]): void {\n const eventListeners = listeners.get(event)\n if (!eventListeners) return\n\n for (const listener of eventListeners) {\n try {\n listener(data)\n } catch (error) {\n // Don't let one listener's error affect others\n console.error(`[Lynkow] Error in event listener for \"${event}\":`, error)\n }\n }\n }\n\n /**\n * Subscribe to an event for a single emission only.\n * The listener is automatically removed after the first time it is called.\n *\n * @param event - Event name to subscribe to\n * @param listener - Callback invoked once with the event payload\n * @returns An unsubscribe function (can be called to cancel before the event fires)\n */\n function once<K extends EventName>(\n event: K,\n listener: EventListener<LynkowEvents[K]>\n ): () => void {\n const wrappedListener = ((data: LynkowEvents[K]) => {\n off(event, wrappedListener as EventListener<LynkowEvents[K]>)\n listener(data)\n }) as EventListener<LynkowEvents[K]>\n\n return on(event, wrappedListener)\n }\n\n /**\n * Remove all listeners for a specific event, or all listeners for all events.\n *\n * @param event - When provided, only listeners for this event are removed.\n * When omitted, all listeners for all events are cleared.\n */\n function removeAllListeners(event?: EventName): void {\n if (event) {\n listeners.delete(event)\n } else {\n listeners.clear()\n }\n }\n\n return {\n on,\n off,\n emit,\n once,\n removeAllListeners,\n }\n}\n\n/**\n * Type of the event emitter object returned by {@link createEventEmitter}.\n */\nexport type EventEmitter = ReturnType<typeof createEventEmitter>\n","/**\n * Locale detection and management utilities\n */\n\nimport { isBrowser } from '../core/environment'\n\nconst STORAGE_KEY = 'lynkow_locale'\n\n/**\n * Find a locale in the enabled list using case-insensitive matching.\n * Returns the canonical form from enabledLocales, or null if not found.\n *\n * @example\n * findLocale('zh-hans', ['fr', 'zh-Hans']) // → 'zh-Hans'\n * findLocale('EN', ['en', 'fr']) // → 'en'\n */\nfunction findLocale(candidate: string, enabledLocales: string[]): string | null {\n const lower = candidate.toLowerCase()\n return enabledLocales.find((l) => l.toLowerCase() === lower) ?? null\n}\n\n/**\n * Detect the locale based on priority order:\n * 1. localStorage (previous user choice)\n * 2. URL path prefix (/en/, /fr/)\n * 3. HTML lang attribute\n * 4. Default locale\n *\n * Safe to call during SSR: returns `defaultLocale` unchanged when\n * `window` is not available.\n *\n * @param enabledLocales - Canonical locale codes the site accepts (BCP 47,\n * e.g. `['en', 'fr', 'zh-Hans']`). Must be non-empty.\n * @param defaultLocale - Fallback used when nothing else matches. Should be\n * included in `enabledLocales`; not validated at runtime.\n * @returns One of `enabledLocales`, or `defaultLocale`.\n *\n * @example\n * ```typescript\n * import { detectLocale } from 'lynkow'\n * const locale = detectLocale(['en', 'fr', 'es'], 'en')\n * // '/fr/about' → 'fr'\n * // '/about' with <html lang=\"fr\"> → 'fr'\n * // otherwise → 'en'\n * ```\n */\nexport function detectLocale(enabledLocales: string[], defaultLocale: string): string {\n if (!isBrowser) {\n return defaultLocale\n }\n\n // 1. localStorage (previous user choice)\n const stored = getStoredLocale()\n if (stored && enabledLocales.includes(stored)) {\n return stored\n }\n\n // 2. URL path prefix\n const pathLocale = getLocaleFromPath(enabledLocales)\n if (pathLocale) {\n return pathLocale\n }\n\n // 3. HTML lang attribute\n const htmlLang = document.documentElement.lang\n if (htmlLang) {\n // Try exact match first (case-insensitive) for BCP 47 codes like zh-Hans\n const exactMatch = findLocale(htmlLang, enabledLocales)\n if (exactMatch) {\n return exactMatch\n }\n\n // Fallback: try base language (fr-FR → fr, zh-Hant-TW → zh)\n const base = htmlLang.split('-')[0]?.toLowerCase()\n if (base) {\n const baseMatch = findLocale(base, enabledLocales)\n if (baseMatch) {\n return baseMatch\n }\n }\n }\n\n // 4. Default\n return defaultLocale\n}\n\n/**\n * Read the user's previously chosen locale from `localStorage`.\n *\n * Returns `null` under SSR (where `window` is undefined) and when\n * localStorage is unavailable (private browsing in some browsers, storage\n * quota exceeded). No validation is performed against the site's enabled\n * locales; callers should check the result against their allowed list.\n *\n * @returns The stored locale code (BCP 47, as written when saved) or `null`\n * when nothing was stored, SSR context, or localStorage is blocked.\n *\n * @example\n * ```typescript\n * import { getStoredLocale } from 'lynkow'\n * const saved = getStoredLocale() // 'fr' | null\n * ```\n */\nexport function getStoredLocale(): string | null {\n if (!isBrowser) return null\n\n try {\n return localStorage.getItem(STORAGE_KEY)\n } catch {\n return null\n }\n}\n\n/**\n * Persist the user's locale choice to `localStorage` so subsequent\n * visits reuse it (see {@link detectLocale}).\n *\n * Silent no-op under SSR and when localStorage write throws (private\n * browsing, quota). The value is stored verbatim; pass the canonical\n * locale code as found in the site's enabled locales.\n *\n * @param locale - BCP 47 locale code to persist (e.g. `'fr'`, `'zh-Hans'`).\n * @returns void\n *\n * @example\n * ```typescript\n * import { setStoredLocale } from 'lynkow'\n * setStoredLocale('fr') // Remembers 'fr' across page loads\n * ```\n */\nexport function setStoredLocale(locale: string): void {\n if (!isBrowser) return\n\n try {\n localStorage.setItem(STORAGE_KEY, locale)\n } catch {\n // localStorage unavailable, silently fail\n }\n}\n\n/**\n * Clear the stored locale so the next call to {@link detectLocale} falls\n * back to URL path, `<html lang>`, then the site default.\n *\n * Silent no-op under SSR and when localStorage remove throws.\n *\n * @returns void\n *\n * @example\n * ```typescript\n * import { removeStoredLocale } from 'lynkow'\n * removeStoredLocale() // Next visit re-detects from URL / html lang\n * ```\n */\nexport function removeStoredLocale(): void {\n if (!isBrowser) return\n\n try {\n localStorage.removeItem(STORAGE_KEY)\n } catch {\n // Silently fail\n }\n}\n\n/**\n * Extract the leading locale segment from `window.location.pathname` and\n * return it in the canonical form found in `enabledLocales`. Matching is\n * case-insensitive, so `/EN/about` still resolves to `'en'`.\n *\n * Returns `null` under SSR, when the path has no segments, or when the\n * first segment is not a known locale.\n *\n * @param enabledLocales - Canonical BCP 47 codes the site accepts.\n * @returns The canonical locale from `enabledLocales`, or `null`.\n *\n * @example\n * ```typescript\n * // window.location.pathname === '/fr/services'\n * getLocaleFromPath(['en', 'fr']) // 'fr'\n * // pathname === '/about'\n * getLocaleFromPath(['en', 'fr']) // null\n * ```\n */\nexport function getLocaleFromPath(enabledLocales: string[]): string | null {\n if (!isBrowser) return null\n\n const path = window.location.pathname\n const segments = path.split('/').filter(Boolean)\n\n if (segments.length > 0) {\n const firstSegment = segments[0]\n if (firstSegment) {\n // Case-insensitive match, return canonical form from enabledLocales\n const match = findLocale(firstSegment, enabledLocales)\n if (match) {\n return match\n }\n }\n }\n\n return null\n}\n\n/**\n * Strict membership check: is `locale` present in `enabledLocales` in its\n * canonical form? Case-sensitive by design so that `zh-Hans` and `zh-hans`\n * are treated differently; use {@link detectLocale} when you want\n * case-insensitive lookup.\n *\n * @param locale - Candidate locale code.\n * @param enabledLocales - Canonical BCP 47 codes the site accepts.\n * @returns `true` if the exact code is in the list, `false` otherwise.\n *\n * @example\n * ```typescript\n * isValidLocale('fr', ['en', 'fr']) // true\n * isValidLocale('FR', ['en', 'fr']) // false (case sensitive)\n * ```\n */\nexport function isValidLocale(locale: string, enabledLocales: string[]): boolean {\n return enabledLocales.includes(locale)\n}\n","/**\n * Lynkow SDK Client\n * Main entry point for the SDK\n */\n\nimport type { LynkowConfig, LynkowClient, SiteConfig } from './types'\nimport type { InternalConfig } from './services/base'\nimport { ContentsService } from './services/contents'\nimport { CategoriesService } from './services/categories'\nimport { TagsService } from './services/tags'\nimport { PagesService } from './services/pages'\nimport { BlocksService } from './services/blocks'\nimport { FormsService } from './services/forms'\nimport { ReviewsService } from './services/reviews'\nimport { SiteService } from './services/site'\nimport { LegalService } from './services/legal'\nimport { CookiesService } from './services/cookies'\nimport { SeoService } from './services/seo'\nimport { PathsService } from './services/paths'\nimport { AnalyticsService } from './services/analytics'\nimport { ConsentService } from './services/consent'\nimport { BrandingService } from './services/branding'\nimport { EnhancementsService } from './services/enhancements'\nimport { MediaHelperService } from './services/media-helper'\nimport { SearchService } from './services/search'\n\n// Core modules\nimport { createCache, type Cache } from './core/cache'\nimport { createLogger, type Logger } from './core/logger'\nimport { createEventEmitter, type EventEmitter, type EventName, type LynkowEvents } from './utils/events'\nimport { isBrowser } from './core/environment'\nimport { detectLocale, setStoredLocale, isValidLocale } from './utils/locale'\n\n/**\n * Default Lynkow API base URL\n */\nconst DEFAULT_BASE_URL = 'https://api.lynkow.com'\n\n/**\n * Extended configuration for SDK v3\n */\nexport interface ClientConfig extends LynkowConfig {\n /**\n * Enable debug logging\n * @default false\n */\n debug?: boolean\n\n /**\n * Enable the SDK's in-memory response cache.\n * When disabled (default), every SDK call goes through `fetch()`,\n * letting your framework (Next.js, Nuxt, Astro) control caching\n * and revalidation end-to-end.\n * Set to `true` for browser SPAs that benefit from localStorage\n * caching between navigations.\n *\n * @default false\n *\n * @example\n * ```typescript\n * // Browser SPA: enable SDK cache for localStorage caching\n * const lynkow = createClient({\n * siteId: 'your-site-uuid',\n * cache: true,\n * })\n * ```\n */\n cache?: boolean\n}\n\n/**\n * Extended client interface for SDK v3\n */\nexport interface Client extends LynkowClient {\n /**\n * Current locale\n */\n readonly locale: string\n\n /**\n * Available locales from site configuration\n */\n readonly availableLocales: string[]\n\n /**\n * Client configuration (readonly)\n */\n readonly config: Readonly<{\n siteId: string\n baseUrl: string\n debug: boolean\n cache: boolean\n }>\n\n /**\n * Set the current locale\n * @param locale - New locale to use\n */\n setLocale(locale: string): void\n\n /**\n * Clear all cached data\n */\n clearCache(): void\n\n /**\n * Destroy the client and clean up resources\n */\n destroy(): void\n\n /**\n * Subscribe to an event\n * @param event - Event name\n * @param listener - Event listener\n * @returns Unsubscribe function\n */\n on<K extends EventName>(event: K, listener: (data: LynkowEvents[K]) => void): () => void\n\n /**\n * Alias for blocks (v3 naming)\n * @deprecated Use `globals` instead in v3\n */\n blocks: BlocksService\n\n /**\n * Global blocks service (v3 naming)\n */\n globals: BlocksService\n\n /**\n * Analytics service (browser-only)\n */\n analytics: AnalyticsService\n\n /**\n * Consent service (cookie consent management)\n */\n consent: ConsentService\n\n /**\n * Branding service (badge for free plan)\n */\n branding: BrandingService\n\n /**\n * Content enhancements service (code copy, etc.)\n */\n enhancements: EnhancementsService\n\n /**\n * Image transformation helpers.\n * Use these to build optimized image URLs for responsive images.\n */\n readonly media: MediaHelperService\n\n /**\n * Lynkow Instant Search service\n */\n search: SearchService\n}\n\n/**\n * Internal state for the client\n */\ninterface ClientState {\n locale: string\n availableLocales: string[]\n siteConfig: SiteConfig | null\n initialized: boolean\n}\n\n/**\n * Creates a Lynkow client instance (SDK v3)\n *\n * @param config - Client configuration\n * @returns Client instance with all services\n *\n * @example\n * ```typescript\n * const lynkow = createClient({\n * siteId: 'your-site-uuid',\n * locale: 'fr',\n * debug: true\n * })\n *\n * const posts = await lynkow.contents.list()\n * ```\n */\nexport function createClient(config: ClientConfig): Client {\n // Validation\n if (!config.siteId) {\n throw new Error('Lynkow SDK: siteId is required')\n }\n\n // Create core utilities\n const cache = config.cache === true ? createCache() : undefined\n const logger = createLogger({ debug: config.debug ?? false })\n const events = createEventEmitter()\n\n // Normalize configuration\n const normalizedBaseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, '')\n\n const internalConfig: InternalConfig = {\n siteId: config.siteId,\n baseUrl: normalizedBaseUrl,\n locale: config.locale,\n fetchOptions: config.fetchOptions || {},\n ...(cache ? { cache } : {}),\n }\n\n // Client state\n const state: ClientState = {\n locale: config.locale || 'fr',\n availableLocales: ['fr'],\n siteConfig: null,\n initialized: false,\n }\n\n // Create services\n const services = {\n contents: new ContentsService(internalConfig),\n categories: new CategoriesService(internalConfig),\n tags: new TagsService(internalConfig),\n pages: new PagesService(internalConfig),\n blocks: new BlocksService(internalConfig),\n forms: new FormsService(internalConfig),\n reviews: new ReviewsService(internalConfig),\n site: new SiteService(internalConfig),\n legal: new LegalService(internalConfig),\n cookies: new CookiesService(internalConfig),\n seo: new SeoService(internalConfig),\n paths: new PathsService(internalConfig),\n analytics: new AnalyticsService(internalConfig),\n consent: new ConsentService(internalConfig, events),\n branding: new BrandingService(internalConfig),\n enhancements: new EnhancementsService(),\n media: new MediaHelperService(),\n search: new SearchService(internalConfig),\n }\n\n /**\n * Update internal config locale for all services\n */\n function updateServicesLocale(locale: string): void {\n internalConfig.locale = locale\n }\n\n /**\n * Initialize client (fetch site config, detect locale)\n */\n async function initialize(): Promise<void> {\n if (state.initialized) return\n\n try {\n // Fetch site configuration\n const siteConfig = await services.site.getConfig()\n state.siteConfig = siteConfig\n const defaultLocale = siteConfig.defaultLocale || 'fr'\n state.availableLocales = siteConfig.enabledLocales || [defaultLocale]\n\n // Detect locale (browser only)\n if (isBrowser && !config.locale) {\n const detected = detectLocale(state.availableLocales, defaultLocale)\n state.locale = detected\n updateServicesLocale(detected)\n }\n\n state.initialized = true\n logger.debug('Client initialized', { locale: state.locale, availableLocales: state.availableLocales })\n\n // Initialize browser-only features\n if (isBrowser) {\n // Load and initialize the analytics tracker\n // The tracker handles consent checking internally via localStorage\n services.analytics.init()\n\n // Show consent banner if user hasn't made a choice yet\n if (!services.consent.hasConsented()) {\n services.consent.show()\n }\n\n // Inject branding badge if required (free plan)\n if (siteConfig.showBranding) {\n await services.branding.inject()\n }\n\n // Initialize content enhancements (code copy, etc.)\n services.enhancements.init()\n }\n\n events.emit('ready', undefined as void)\n } catch (error) {\n logger.error('Failed to initialize client', error)\n events.emit('error', error as Error)\n }\n }\n\n // Auto-initialize in browser\n // ALL DOM mutations are deferred to the next macrotask via setTimeout(0)\n // to avoid interfering with React concurrent rendering and client-side navigations\n if (isBrowser) {\n setTimeout(() => {\n services.enhancements.init()\n initialize()\n }, 0)\n }\n\n // Build the client object\n const client: Client = {\n // Services\n ...services,\n\n // v3 alias: globals = blocks\n globals: services.blocks,\n\n // Readonly config\n config: Object.freeze({\n siteId: config.siteId,\n baseUrl: normalizedBaseUrl,\n debug: config.debug ?? false,\n cache: config.cache === true,\n }),\n\n // Locale getter\n get locale(): string {\n return state.locale\n },\n\n // Available locales getter\n get availableLocales(): string[] {\n return [...state.availableLocales]\n },\n\n // Set locale\n setLocale(locale: string): void {\n if (!isValidLocale(locale, state.availableLocales)) {\n logger.warn(`Locale \"${locale}\" is not available. Available: ${state.availableLocales.join(', ')}`)\n return\n }\n\n if (locale === state.locale) return\n\n state.locale = locale\n updateServicesLocale(locale)\n\n // Save to localStorage (browser)\n if (isBrowser) {\n setStoredLocale(locale)\n }\n\n // Invalidate cache (content changes with locale)\n cache?.invalidate()\n\n // Emit event\n events.emit('locale-changed', locale)\n logger.debug('Locale changed to', locale)\n },\n\n // Clear cache\n clearCache(): void {\n cache?.invalidate()\n logger.debug('Cache cleared')\n },\n\n // Destroy client\n destroy(): void {\n services.analytics.destroy()\n services.consent.destroy()\n services.branding.destroy()\n services.enhancements.destroy()\n cache?.invalidate()\n events.removeAllListeners()\n logger.debug('Client destroyed')\n },\n\n // Event subscription\n on<K extends EventName>(event: K, listener: (data: LynkowEvents[K]) => void): () => void {\n return events.on(event, listener)\n },\n }\n\n return client\n}\n\n/**\n * Creates a Lynkow client instance (legacy naming)\n *\n * @deprecated Use `createClient` instead\n * @param config - Client configuration\n * @returns Client instance with all services\n *\n * @example\n * ```typescript\n * // Deprecated -- use createClient() instead:\n * const lynkow = createClient({ siteId: 'your-site-uuid', locale: 'fr' })\n * const posts = await lynkow.contents.list()\n * ```\n */\nexport function createLynkowClient(config: LynkowConfig): LynkowClient {\n // For backward compatibility, return the basic client without v3 features\n if (!config.siteId) {\n throw new Error('Lynkow SDK: siteId is required')\n }\n\n const internalConfig: InternalConfig = {\n siteId: config.siteId,\n baseUrl: (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, ''),\n locale: config.locale,\n fetchOptions: config.fetchOptions || {},\n }\n\n return {\n contents: new ContentsService(internalConfig),\n categories: new CategoriesService(internalConfig),\n tags: new TagsService(internalConfig),\n pages: new PagesService(internalConfig),\n blocks: new BlocksService(internalConfig),\n forms: new FormsService(internalConfig),\n reviews: new ReviewsService(internalConfig),\n site: new SiteService(internalConfig),\n legal: new LegalService(internalConfig),\n cookies: new CookiesService(internalConfig),\n seo: new SeoService(internalConfig),\n paths: new PathsService(internalConfig),\n search: new SearchService(internalConfig),\n }\n}\n","// Config\nexport type { LynkowConfig, LynkowClient } from './config'\n\n// Entities\nexport type {\n Content,\n ContentSummary,\n ContentBody,\n TipTapNode,\n TipTapMark,\n Author,\n FaqItem,\n JsonLdFaqPage,\n JsonLdArticle,\n StructuredDataAlternate,\n StructuredData,\n} from './content'\n\nexport type {\n Category,\n CategoryWithCount,\n CategoryDetail,\n CategoryTreeNode,\n} from './category'\n\nexport type { Tag } from './tag'\n\nexport type { ImageVariants } from './image'\n\nexport type { Page, PageSummary, PageSeo, Alternate } from './page'\n\nexport type { GlobalBlock } from './block'\n\nexport type { JsonLdNode, JsonLdNodeSource, JsonLdGraphConfig } from './json-ld'\n\nexport type {\n SchemaFieldType,\n SchemaField,\n SchemaFieldOption,\n SchemaFieldValidation,\n ContentSchema,\n} from './schema'\n\nexport type {\n Form,\n FormField,\n FormFieldType,\n FormFieldOption,\n FormFieldValidation,\n FormSettings,\n FormSubmitData,\n} from './form'\n\nexport type {\n Review,\n ReviewResponse,\n ReviewSettings,\n ReviewSubmitData,\n} from './review'\n\nexport type { SiteConfig, I18nConfig, LocaleInfo } from './site'\n\nexport type {\n LegalDocument,\n LegalListResponse,\n LegalDocumentResponse,\n} from './legal'\n\nexport type {\n CookieConfig,\n CookieCategory,\n CookieTexts,\n CookiePreferences,\n ThirdPartyScript,\n} from './cookie'\n\nexport type { Path, Redirect } from './path'\n\n// Responses\nexport type {\n PaginationMeta,\n PaginatedResponse,\n ContentsListResponse,\n CategoriesListResponse,\n CategoryTreeResponse,\n CategoryDetailResponse,\n TagsListResponse,\n PagesListResponse,\n SiteConfigResponse,\n GlobalBlockResponse,\n ReviewsListResponse,\n FormSubmitResponse,\n ReviewSubmitResponse,\n ConsentLogResponse,\n PathsListResponse,\n ContentResolveResponse,\n CategoryResolveResponse,\n ResolveResponse,\n} from './response'\n\n// Filters\nexport type {\n BaseRequestOptions,\n PaginationOptions,\n SortOptions,\n ContentsFilters,\n CategoryOptions,\n ReviewsFilters,\n SubmitOptions,\n} from './filters'\n\n// Errors\nexport type { ErrorCode, ApiErrorDetail } from './error'\n\n// Type guards\nimport type { ResolveResponse, ContentResolveResponse, CategoryResolveResponse } from './response'\n\n/**\n * Checks if a resolution response is a content\n *\n * @example\n * ```typescript\n * // Resolve a URL path and render the appropriate template\n * const resolved = await lynkow.paths.resolve('/blog/my-article')\n * if (isContentResolve(resolved)) {\n * renderArticle(resolved.data) // TypeScript narrows to ContentResolveResponse\n * }\n * ```\n */\nexport function isContentResolve(\n response: ResolveResponse\n): response is ContentResolveResponse {\n return response.type === 'content'\n}\n\n/**\n * Checks if a resolution response is a category\n *\n * @example\n * ```typescript\n * // Resolve a URL path and render the appropriate template\n * const resolved = await lynkow.paths.resolve('/blog/tutorials')\n * if (isCategoryResolve(resolved)) {\n * renderCategoryPage(resolved.data) // TypeScript narrows to CategoryResolveResponse\n * }\n * ```\n */\nexport function isCategoryResolve(\n response: ResolveResponse\n): response is CategoryResolveResponse {\n return response.type === 'category'\n}\n","/**\n * Render a resolved JSON-LD `@graph` as a single `<script type=\"application/ld+json\">`\n * tag ready to inject into a page `<head>`.\n *\n * The input is the array returned by the Lynkow public API at\n * `content.structuredData.graph` (for articles) or `page.structuredData.graph`\n * (for site blocks of type page). Each entry is already a fully-formed\n * schema.org object with `@context`, `@id`, and `@type`. This helper strips\n * the per-node `@context` and wraps the array in a top-level `@context` +\n * `@graph` to keep the emitted script as compact as possible.\n *\n * No HTML escaping is performed on the JSON body itself, but `</script>`\n * sequences that would otherwise break the enclosing tag are guarded against\n * via a Unicode-safe replacement.\n *\n * @param nodes - Array of JSON-LD node objects. `null`, `undefined`, or an\n * empty array returns an empty string so you can safely\n * spread the result into server-rendered HTML without\n * conditional branches.\n * @returns Serialized `<script>` tag string. Empty string when there are\n * no nodes to render.\n * @throws Never throws. The function is designed for server components\n * and is safe to call with `undefined` or malformed payloads\n * (values that cannot be `JSON.stringify`-ed, e.g. circular\n * references, will throw from the underlying `JSON.stringify`\n * call - caller's responsibility, not a library behaviour).\n *\n * @example\n * ```tsx\n * // Next.js App Router (server component)\n * import { createClient, renderJsonLdGraph } from 'lynkow'\n *\n * const client = createClient({ siteId: process.env.LYNKOW_SITE_ID! })\n *\n * export default async function ArticlePage({ params }: { params: { slug: string } }) {\n * const article = await client.contents.getBySlug(params.slug)\n * return (\n * <>\n * <div\n * dangerouslySetInnerHTML={{\n * __html: renderJsonLdGraph(article.structuredData?.graph),\n * }}\n * />\n * <h1>{article.title}</h1>\n * <article dangerouslySetInnerHTML={{ __html: article.body }} />\n * </>\n * )\n * }\n * ```\n */\nexport function renderJsonLdGraph(nodes: object[] | null | undefined): string {\n if (!nodes || nodes.length === 0) return ''\n\n const graph = nodes.map((node) => {\n // Strip per-node @context so the top-level @context applies once.\n const { ['@context']: _ignoredContext, ...rest } = node as Record<string, unknown>\n return rest\n })\n\n const payload = JSON.stringify({\n '@context': 'https://schema.org',\n '@graph': graph,\n })\n\n // Guard against a `</script>` sequence embedded inside a string value that\n // would otherwise terminate the enclosing <script> tag.\n const escaped = payload.replace(/<\\/(script)/gi, '<\\\\/$1')\n\n return `<script type=\"application/ld+json\">${escaped}</script>`\n}\n"]}
1
+ {"version":3,"sources":["../src/core/errors.ts","../src/utils/fetch.ts","../src/utils/query.ts","../src/services/base.ts","../src/services/contents.ts","../src/services/categories.ts","../src/services/tags.ts","../src/services/pages.ts","../src/services/blocks.ts","../src/utils/spam.ts","../src/services/forms.ts","../src/services/reviews.ts","../src/services/site.ts","../src/services/legal.ts","../src/services/cookies.ts","../src/services/seo.ts","../src/services/paths.ts","../src/core/environment.ts","../src/services/analytics.ts","../src/utils/theme.ts","../src/services/consent.ts","../src/services/branding.ts","../src/services/enhancements.ts","../src/services/media-helper.ts","../src/services/search.ts","../src/core/cache.ts","../src/core/logger.ts","../src/utils/events.ts","../src/utils/locale.ts","../src/client.ts","../src/types/index.ts","../src/utils/json-ld.ts"],"names":["LynkowError","_LynkowError","message","code","status","details","cause","response","body","error","isLynkowError","getErrorCode","fetchWithError","url","options","errorData","errors","buildQueryString","params","searchParams","key","value","CACHE_TTL","BaseService","config","path","query","baseUrl","queryString","locale","queryWithLocale","fetchOptions","cacheKey","ttl","pattern","localOptions","CACHE_PREFIX","ContentsService","filters","slug","CategoriesService","TagsService","PagesService","BlocksService","generateSpamFields","sessionStartTime","FormsService","data","spamFields","ReviewsService","slugOrId","result","SiteService","LegalService","CookiesService","preferences","SeoService","part","contentPath","normalizedPath","PathsService","isBrowser","isServer","browserOnly","fn","fallback","browserOnlyAsync","TRACKER_SCRIPT_ID","AnalyticsService","resolve","reject","checkLoaded","script","storedMode","event","parseBackgroundLuminance","bgColor","match","r","g","b","detectSiteTheme","html","el","attr","colorScheme","normalized","luminance","onSiteThemeChange","callback","currentTheme","cleanups","checkTheme","newTheme","observer","observeOptions","mq","handler","STORAGE_KEY","CONSENT_EXPIRY_MS","SCRIPT_ID_PREFIX","DEFAULT_CATEGORIES","ConsentService","events","action","vid","values","k","allTrue","v","allFalse","stored","categories","storedConsent","wrapper","theme","newCategories","category","accepted","scripts","elements","elId","newScript","scriptId","i","colors","textColor","innerDiv","resolvedTheme","isDark","hex","style","position","borderRadius","fontSize","orientation","floatingStyles","positionStyle","primaryColor","texts","label","currentCategories","categoriesHTML","cat","acceptBtn","rejectBtn","prefsBtn","_config","form","closeBtn","modal","e","formData","BADGE_CONTAINER_ID","STYLES_ID","BrandingService","styleElement","container","CLONE_ATTR","EXECUTABLE_SCRIPT_TYPES","COPY_ICON","CHECK_ICON","ENHANCEMENT_STYLES","EnhancementsService","iframe","button","codeBlock","codeElement","err","toActivate","s","original","replacement","attrs","MediaHelperService","imageUrl","widths","fit","quality","gravity","parsed","w","cdnCgiIndex","cdnBase","afterOptions","firstSlash","relativePath","sitesIndex","avatarsIndex","SearchService","DEFAULT_TTL","STORAGE_PREFIX","memoryCache","createCache","defaultTtl","prefix","getKey","isExpired","entry","get","fullKey","set","remove","invalidate","keysToRemove","getOrSet","factory","cached","createLogger","args","level","createEventEmitter","listeners","on","listener","off","eventListeners","emit","once","wrappedListener","removeAllListeners","findLocale","candidate","enabledLocales","lower","l","detectLocale","defaultLocale","getStoredLocale","pathLocale","getLocaleFromPath","htmlLang","exactMatch","base","baseMatch","setStoredLocale","segments","firstSegment","isValidLocale","DEFAULT_BASE_URL","createClient","cache","logger","normalizedBaseUrl","internalConfig","state","services","updateServicesLocale","initialize","siteConfig","detected","createLynkowClient","isContentResolve","isCategoryResolve","renderJsonLdGraph","nodes","graph","node","_ignoredContext","rest"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error handling utilities for Lynkow SDK\n */\n\n/**\n * Error codes for Lynkow SDK errors.\n *\n * Each code maps to a specific category of failure. Use these in `catch` blocks\n * to handle different error types:\n *\n * | Code | HTTP Status | When it occurs |\n * |----------------------|-------------|-------------------------------------------------------------|\n * | `BAD_REQUEST` | 400 | Malformed request (missing required fields, bad format) |\n * | `VALIDATION_ERROR` | 400 | Request body fails server-side validation (see `details`) |\n * | `UNAUTHORIZED` | 401 | Missing or invalid authentication (bad site ID) |\n * | `FORBIDDEN` | 403 | Valid auth but insufficient permissions |\n * | `NOT_FOUND` | 404 | Resource does not exist or is not published |\n * | `RATE_LIMITED` | 429 | Too many requests; retry after the indicated delay |\n * | `TOO_MANY_REQUESTS` | 429 | Alias for `RATE_LIMITED` |\n * | `TIMEOUT` | - | Request was aborted due to timeout (client-side) |\n * | `NETWORK_ERROR` | - | DNS failure, connection refused, or network offline |\n * | `INTERNAL_ERROR` | 500 | Server-side error (bug or infrastructure issue) |\n * | `SERVICE_UNAVAILABLE`| 503 | API is temporarily down for maintenance |\n * | `UNKNOWN` | other | Unrecognized HTTP status or unexpected error |\n */\nexport type ErrorCode =\n | 'BAD_REQUEST'\n | 'VALIDATION_ERROR'\n | 'UNAUTHORIZED'\n | 'FORBIDDEN'\n | 'NOT_FOUND'\n | 'RATE_LIMITED'\n | 'TOO_MANY_REQUESTS'\n | 'TIMEOUT'\n | 'NETWORK_ERROR'\n | 'INTERNAL_ERROR'\n | 'SERVICE_UNAVAILABLE'\n | 'UNKNOWN'\n\n/**\n * A single error detail from the API's response body.\n * The API returns an array of these in the `errors` field of error responses.\n * For validation errors, each detail corresponds to one invalid field.\n */\nexport interface ApiErrorDetail {\n /**\n * Human-readable error message describing what went wrong.\n * Suitable for display to end users or logging.\n *\n * @example 'The title field is required'\n * @example 'Slug already exists'\n */\n message: string\n\n /**\n * Name of the form/request field that caused the validation error.\n * Only present for `VALIDATION_ERROR` responses.\n * `undefined` for non-field-specific errors (e.g. auth, rate limiting).\n * Use this to display inline validation errors next to form fields.\n *\n * @example 'title', 'email', 'rating'\n */\n field?: string\n\n /**\n * Machine-readable error rule or code from the validation layer.\n * Can be used for programmatic error handling or i18n error message lookups.\n * `undefined` when the API does not provide a structured code.\n *\n * @example 'required', 'unique', 'minLength'\n */\n code?: string\n}\n\n/**\n * Custom error class for Lynkow SDK.\n * Wraps HTTP errors, network failures, and timeouts into a structured format\n * with typed error codes, HTTP status, and optional field-level details.\n *\n * Use the `isLynkowError()` type guard to safely narrow caught errors.\n *\n * @example\n * ```typescript\n * try {\n * await lynkow.contents.getBySlug('my-article')\n * } catch (error) {\n * if (isLynkowError(error)) {\n * switch (error.code) {\n * case 'NOT_FOUND':\n * // Show 404 page\n * break\n * case 'VALIDATION_ERROR':\n * // Display field errors from error.details\n * break\n * case 'RATE_LIMITED':\n * // Back off and retry\n * break\n * }\n * }\n * }\n * ```\n */\nexport class LynkowError extends Error {\n /**\n * Error name, always `'LynkowError'`.\n * Useful for quick identification in logs and error monitoring tools.\n */\n override readonly name = 'LynkowError'\n\n /**\n * Categorized error code for programmatic handling.\n * See {@link ErrorCode} for the full list and when each code occurs.\n */\n readonly code: ErrorCode\n\n /**\n * HTTP status code from the server response.\n * `undefined` for client-side errors (`TIMEOUT`, `NETWORK_ERROR`)\n * where no HTTP response was received.\n *\n * @example 404, 429, 500\n */\n readonly status?: number\n\n /**\n * Array of detailed error information from the API response body.\n * Present for `VALIDATION_ERROR` responses where each entry describes\n * a specific field's validation failure. `undefined` for non-validation errors\n * or when the server response body could not be parsed.\n */\n readonly details?: ApiErrorDetail[]\n\n /**\n * The original `Error` that caused this `LynkowError`.\n * Present when wrapping a network error (`TypeError`) or abort error (`AbortError`).\n * `undefined` when the error originated from an HTTP response.\n */\n override readonly cause?: Error\n\n constructor(\n message: string,\n code: ErrorCode,\n status?: number,\n details?: ApiErrorDetail[],\n cause?: Error\n ) {\n super(message)\n this.code = code\n this.status = status\n this.details = details\n this.cause = cause\n\n // Maintain proper stack trace in V8 environments\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, LynkowError)\n }\n }\n\n /**\n * Create a `LynkowError` from an HTTP `Response` object.\n * Attempts to parse the response body as JSON to extract error details.\n * Falls back to the HTTP status text when the body is not valid JSON.\n *\n * @param response - The HTTP Response object with a non-2xx status\n * @returns A new `LynkowError` with the appropriate code, status, and details\n */\n static async fromResponse(response: Response): Promise<LynkowError> {\n const status = response.status\n let message = `HTTP ${status}`\n let details: ApiErrorDetail[] | undefined\n\n try {\n const body = await response.json()\n if (body.errors && Array.isArray(body.errors)) {\n details = body.errors\n message = body.errors[0]?.message || message\n } else if (body.error) {\n message = body.error\n } else if (body.message) {\n message = body.message\n }\n } catch {\n // Response body is not JSON, use status text\n message = response.statusText || message\n }\n\n const code = LynkowError.statusToCode(status)\n return new LynkowError(message, code, status, details)\n }\n\n /**\n * Create a `LynkowError` from a client-side network or abort error.\n *\n * - `AbortError` (from `AbortController`) becomes `TIMEOUT`\n * - `TypeError` (from `fetch()` DNS/connection failures) becomes `NETWORK_ERROR`\n * - Any other error becomes `UNKNOWN`\n *\n * @param error - The original error thrown by `fetch()`\n * @returns A new `LynkowError` wrapping the original error\n */\n static fromNetworkError(error: Error): LynkowError {\n if (error.name === 'AbortError') {\n return new LynkowError('Request timed out', 'TIMEOUT', undefined, undefined, error)\n }\n\n if (error.name === 'TypeError') {\n return new LynkowError(\n 'Network error - please check your connection',\n 'NETWORK_ERROR',\n undefined,\n undefined,\n error\n )\n }\n\n return new LynkowError(error.message || 'Unknown error', 'UNKNOWN', undefined, undefined, error)\n }\n\n /**\n * Map HTTP status code to error code\n */\n private static statusToCode(status: number): ErrorCode {\n switch (status) {\n case 400:\n return 'VALIDATION_ERROR'\n case 401:\n return 'UNAUTHORIZED'\n case 403:\n return 'FORBIDDEN'\n case 404:\n return 'NOT_FOUND'\n case 429:\n return 'RATE_LIMITED'\n default:\n return 'UNKNOWN'\n }\n }\n\n /**\n * Serialize the error to a plain object.\n * Useful for logging, error reporting services, and JSON serialization.\n * Does not include `cause` or stack trace.\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n status: this.status,\n details: this.details,\n }\n }\n}\n\n/**\n * Type guard to check if an unknown value is a `LynkowError`.\n * Use this in `catch` blocks to safely access `LynkowError` properties.\n *\n * @param error - The caught value to check\n * @returns `true` if the value is an instance of `LynkowError`\n *\n * @example\n * ```typescript\n * try {\n * await lynkow.contents.getBySlug('not-found')\n * } catch (error) {\n * if (isLynkowError(error) && error.code === 'NOT_FOUND') {\n * // Handle 404 -- error.status is 404\n * }\n * }\n * ```\n */\nexport function isLynkowError(error: unknown): error is LynkowError {\n return error instanceof LynkowError\n}\n","import { LynkowError, type ErrorCode, type ApiErrorDetail } from '../core/errors'\n\n/**\n * Converts an HTTP status code to an error code\n */\nfunction getErrorCode(status: number): ErrorCode {\n switch (status) {\n case 400:\n return 'BAD_REQUEST'\n case 401:\n return 'UNAUTHORIZED'\n case 403:\n return 'FORBIDDEN'\n case 404:\n return 'NOT_FOUND'\n case 422:\n return 'VALIDATION_ERROR'\n case 429:\n return 'TOO_MANY_REQUESTS'\n case 503:\n return 'SERVICE_UNAVAILABLE'\n default:\n return 'INTERNAL_ERROR'\n }\n}\n\n/**\n * Performs a fetch request with error handling\n */\nexport async function fetchWithError<T>(\n url: string,\n options: RequestInit\n): Promise<T> {\n let response: Response\n\n try {\n response = await fetch(url, options)\n } catch (error) {\n // Network error\n throw new LynkowError(\n 'Network error: Unable to reach the server',\n 'NETWORK_ERROR',\n 0,\n [{ message: error instanceof Error ? error.message : 'Unknown error' }]\n )\n }\n\n // OK response\n if (response.ok) {\n return response.json()\n }\n\n // HTTP error\n let errorData: Record<string, unknown> = {}\n try {\n errorData = await response.json()\n } catch {\n // No JSON body\n }\n\n const code = getErrorCode(response.status)\n const message =\n (errorData['error'] as string) ||\n (errorData['message'] as string) ||\n `HTTP error: ${response.status}`\n const errors: ApiErrorDetail[] =\n (errorData['errors'] as ApiErrorDetail[]) || [{ message }]\n\n throw new LynkowError(message, code, response.status, errors)\n}\n","/**\n * Serialize a flat object into a URL-encoded query string, dropping any\n * entry whose value is `undefined`, `null`, or the empty string so that\n * optional filters do not appear as `key=` in the final URL.\n *\n * Non-string values are coerced with `String(value)`; arrays and nested\n * objects are therefore serialized via their default `toString()` and\n * should be pre-flattened by the caller if a specific shape is needed.\n *\n * @param params - Flat record of query parameters. Order is preserved.\n * @returns URL-encoded query string without a leading `?`. Returns an\n * empty string when every value was filtered out.\n *\n * @example\n * ```typescript\n * buildQueryString({ page: 1, search: 'ab', tag: undefined, limit: '' })\n * // → 'page=1&search=ab'\n *\n * const url = `/api/items?${buildQueryString({ category: 'tech' })}`\n * ```\n */\nexport function buildQueryString(params: Record<string, unknown>): string {\n const searchParams = new URLSearchParams()\n\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null && value !== '') {\n searchParams.append(key, String(value))\n }\n }\n\n return searchParams.toString()\n}\n","import type { BaseRequestOptions } from '../types'\nimport type { Cache } from '../core/cache'\nimport { fetchWithError } from '../utils/fetch'\nimport { buildQueryString } from '../utils/query'\n\n/**\n * Normalized internal configuration\n */\nexport interface InternalConfig {\n siteId: string\n baseUrl: string\n locale?: string\n fetchOptions: RequestInit\n /** Optional cache manager for caching responses */\n cache?: Cache\n}\n\n/** Cache TTL constants (in milliseconds) */\nexport const CACHE_TTL = {\n /** Short TTL for frequently changing data (5 minutes) */\n SHORT: 5 * 60 * 1000,\n /** Medium TTL for moderately changing data (10 minutes) */\n MEDIUM: 10 * 60 * 1000,\n /** Long TTL for rarely changing data (30 minutes) */\n LONG: 30 * 60 * 1000,\n} as const\n\n/**\n * Base service that all specific services inherit from\n */\nexport abstract class BaseService {\n protected config: InternalConfig\n protected cache?: Cache\n\n constructor(config: InternalConfig) {\n this.config = config\n this.cache = config.cache\n }\n\n /**\n * Builds the full URL for an endpoint\n */\n protected buildEndpointUrl(\n path: string,\n query?: Record<string, unknown>\n ): string {\n const baseUrl = `${this.config.baseUrl}/public/${this.config.siteId}${path}`\n\n if (query && Object.keys(query).length > 0) {\n const queryString = buildQueryString(query)\n return `${baseUrl}?${queryString}`\n }\n\n return baseUrl\n }\n\n /**\n * Performs a GET request\n */\n protected async get<T>(\n path: string,\n query?: Record<string, unknown>,\n options?: BaseRequestOptions\n ): Promise<T> {\n // Add locale if configured\n const locale = options?.locale || this.config.locale\n const queryWithLocale = locale ? { ...query, locale } : query\n\n const url = this.buildEndpointUrl(path, queryWithLocale)\n const fetchOptions = this.mergeFetchOptions(options?.fetchOptions)\n\n return fetchWithError<T>(url, {\n method: 'GET',\n ...fetchOptions,\n })\n }\n\n /**\n * Performs a cached GET request\n * If cache is available and data exists, returns cached data\n * Otherwise fetches from API and caches the result\n *\n * @param cacheKey - Unique cache key\n * @param path - API endpoint path\n * @param query - Query parameters\n * @param options - Request options\n * @param ttl - Cache TTL in milliseconds (default: SHORT)\n */\n protected async getWithCache<T>(\n cacheKey: string,\n path: string,\n query?: Record<string, unknown>,\n options?: BaseRequestOptions,\n ttl: number = CACHE_TTL.SHORT\n ): Promise<T> {\n // If cache is available, use getOrSet\n if (this.cache) {\n return this.cache.getOrSet<T>(\n cacheKey,\n () => this.get<T>(path, query, options),\n ttl\n )\n }\n\n // No cache, just fetch\n return this.get<T>(path, query, options)\n }\n\n /**\n * Invalidates cache entries matching a pattern\n * @param pattern - Pattern to match cache keys\n */\n protected invalidateCache(pattern?: string): void {\n this.cache?.invalidate(pattern)\n }\n\n /**\n * Performs a POST request\n */\n protected async post<T>(\n path: string,\n body: Record<string, unknown>,\n options?: BaseRequestOptions\n ): Promise<T> {\n const url = this.buildEndpointUrl(path)\n const fetchOptions = this.mergeFetchOptions(options?.fetchOptions)\n\n return fetchWithError<T>(url, {\n method: 'POST',\n ...fetchOptions,\n headers: {\n 'Content-Type': 'application/json',\n ...fetchOptions.headers,\n },\n body: JSON.stringify(body),\n })\n }\n\n /**\n * Performs a GET request that returns plain text (XML, txt)\n */\n protected async getText(\n path: string,\n options?: BaseRequestOptions\n ): Promise<string> {\n const url = this.buildEndpointUrl(path)\n const fetchOptions = this.mergeFetchOptions(options?.fetchOptions)\n\n const response = await fetch(url, {\n method: 'GET',\n ...fetchOptions,\n })\n\n if (!response.ok) {\n throw new Error(`HTTP error: ${response.status}`)\n }\n\n return response.text()\n }\n\n /**\n * Merges local fetch options with global ones\n */\n private mergeFetchOptions(localOptions?: RequestInit): RequestInit {\n return {\n ...this.config.fetchOptions,\n ...localOptions,\n headers: {\n ...this.config.fetchOptions.headers,\n ...localOptions?.headers,\n },\n }\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n Content,\n ContentsFilters,\n ContentsListResponse,\n BaseRequestOptions,\n} from '../types'\n\nconst CACHE_PREFIX = 'contents_'\n\n/**\n * Service for retrieving published blog articles (contents).\n *\n * Accessible via `lynkow.contents`. All methods return only published content\n * visible to the public API. Responses are cached in-memory for 5 minutes\n * (SHORT TTL) when a cache adapter is configured on the client.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // List latest articles\n * const { data, meta } = await lynkow.contents.list({ limit: 10 })\n *\n * // Fetch a single article by slug\n * const article = await lynkow.contents.getBySlug('my-article')\n * ```\n */\nexport class ContentsService extends BaseService {\n /**\n * Retrieves a paginated list of published blog articles, sorted by\n * publication date (newest first by default). Results are cached for\n * 5 minutes per unique filter combination.\n *\n * @param filters - Optional filters to narrow down results:\n * - `page` / `limit` — pagination (defaults to page 1, 15 items)\n * - `category` — filter by category slug (e.g. `'tech'`, `'news'`)\n * - `tag` — filter by tag slug (e.g. `'featured'`)\n * - `search` — full-text search across title and body\n * - `sort` / `order` — sort field and direction (`'asc'` or `'desc'`)\n * - `locale` — override the client's default locale\n * @param options - Base request options (locale override, custom fetch options)\n * @returns An object containing `data` (array of `ContentSummary` with id, title, slug,\n * path, excerpt, featuredImage, publishedAt) and `meta` (pagination info with\n * total, currentPage, lastPage, perPage, hasMorePages)\n * @throws {LynkowError} With code `'NETWORK_ERROR'` if the API is unreachable\n *\n * @example\n * ```typescript\n * // Fetch the first page of articles in the \"tech\" category\n * const { data, meta } = await lynkow.contents.list({\n * page: 1,\n * limit: 10,\n * category: 'tech'\n * })\n *\n * // Search for articles\n * const results = await lynkow.contents.list({ search: 'typescript' })\n * ```\n */\n async list(\n filters?: ContentsFilters,\n options?: BaseRequestOptions\n ): Promise<ContentsListResponse> {\n const query: Record<string, unknown> = {}\n\n if (filters?.page) query['page'] = filters.page\n if (filters?.limit ?? filters?.perPage) query['limit'] = filters?.limit ?? filters?.perPage\n if (filters?.category) query['category'] = filters.category\n if (filters?.tag) query['tag'] = filters.tag\n if (filters?.search) query['search'] = filters.search\n if (filters?.sort) query['sort'] = filters.sort\n if (filters?.order) query['order'] = filters.order\n if (filters?.locale) query['locale'] = filters.locale\n\n const cacheKey = `${CACHE_PREFIX}list_${JSON.stringify(filters || {})}`\n return this.getWithCache<ContentsListResponse>(\n cacheKey,\n '/contents',\n query,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Retrieves a single published article by its URL-friendly slug.\n * Returns the full content including body (HTML), SEO metadata,\n * author, categories, tags, and structured data (JSON-LD).\n * Cached for 5 minutes per slug+locale combination.\n *\n * @param slug - The unique URL slug of the content (e.g. `'my-article'`, `'getting-started-with-typescript'`)\n * @param options - Request options; use `locale` to fetch a specific localized version\n * @returns The full `Content` object with body HTML, SEO fields (metaTitle, metaDescription,\n * canonicalUrl, ogImage), author info, associated categories/tags, and structuredData\n * @throws {LynkowError} With code `'NOT_FOUND'` if no published content matches the given slug\n *\n * @example\n * ```typescript\n * const content = await lynkow.contents.getBySlug('my-article')\n * console.log(content.title) // Article title\n * console.log(content.body) // HTML body string\n * console.log(content.author?.fullName) // Author name\n * console.log(content.categories) // Associated categories\n * ```\n */\n async getBySlug(\n slug: string,\n options?: BaseRequestOptions\n ): Promise<Content> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}slug_${slug}_${locale || 'default'}`\n return this.getWithCache<Content>(\n cacheKey,\n `/contents/slug/${encodeURIComponent(slug)}`,\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Invalidate every cached response produced by this service (list\n * queries, slug lookups, single-content fetches). Call after an admin\n * mutation or on receipt of a `content.*` webhook so the next public\n * request bypasses the 5-minute SWR cache and hits the origin.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // In a Next.js route handler receiving a Lynkow webhook:\n * if (event.type === 'content.updated') {\n * lynkow.contents.clearCache()\n * }\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n CategoriesListResponse,\n CategoryTreeResponse,\n CategoryDetailResponse,\n CategoryOptions,\n BaseRequestOptions,\n} from '../types'\n\nconst CACHE_PREFIX = 'categories_'\n\n/**\n * Service for retrieving content categories and their hierarchical structure.\n *\n * Accessible via `lynkow.categories`. Categories organize blog articles and can be\n * nested (parent/child). Each category includes a content count and optional image.\n * Responses are cached for 5 minutes (SHORT TTL) when a cache adapter is configured.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Flat list with content counts\n * const { data } = await lynkow.categories.list()\n *\n * // Hierarchical tree\n * const { data } = await lynkow.categories.tree()\n *\n * // Category detail with paginated articles\n * const { category, contents } = await lynkow.categories.getBySlug('tech')\n * ```\n */\nexport class CategoriesService extends BaseService {\n /**\n * Retrieves a flat list of all categories with their content counts.\n * Useful for building navigation menus, sidebars, or category filters.\n * Cached for 5 minutes per locale.\n *\n * @param options - Request options; use `locale` to fetch localized category names\n * @returns An object containing `data` (array of `CategoryWithCount` with name, slug, path,\n * contentCount, image, description), `blogUrlMode` (`'flat'` or `'nested'`), and `locale`\n *\n * @example\n * ```typescript\n * const { data, blogUrlMode } = await lynkow.categories.list()\n * data.forEach(cat => {\n * console.log(`${cat.name} (${cat.contentCount} articles)`)\n * })\n * ```\n */\n async list(options?: BaseRequestOptions): Promise<CategoriesListResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}list_${locale || 'default'}`\n return this.getWithCache<CategoriesListResponse>(\n cacheKey,\n '/categories',\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Retrieves the full category hierarchy as a nested tree structure.\n * Root categories appear at the top level, each with a `children` array\n * containing their subcategories (recursively). Useful for building\n * hierarchical navigation or breadcrumbs. Cached for 5 minutes per locale.\n *\n * @param options - Request options; use `locale` to fetch localized category names\n * @returns An object containing `data` (array of `CategoryTreeNode`, each with a\n * `children` array of nested subcategories), `blogUrlMode`, and `locale`\n *\n * @example\n * ```typescript\n * const { data } = await lynkow.categories.tree()\n * // Iterate root categories and their children\n * data.forEach(root => {\n * console.log(root.name)\n * root.children.forEach(child => {\n * console.log(` - ${child.name}`)\n * })\n * })\n * ```\n */\n async tree(options?: BaseRequestOptions): Promise<CategoryTreeResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}tree_${locale || 'default'}`\n return this.getWithCache<CategoryTreeResponse>(\n cacheKey,\n '/categories/tree',\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Retrieves a single category by its slug, along with a paginated list of\n * the published articles that belong to it. Cached for 5 minutes per\n * slug+locale+pagination combination.\n *\n * @param slug - The unique URL slug of the category (e.g. `'tech'`, `'news'`, `'tutorials'`)\n * @param options - Combined category and request options:\n * - `page` / `limit` — pagination for the category's articles (defaults to page 1)\n * - `locale` — override the client's default locale\n * @returns An object containing `category` (full `CategoryDetail` with name, slug, path,\n * description, image, contentCount, parentId), `contents` (paginated `ContentSummary`\n * array with `data` and `meta`), and `locale`\n * @throws {LynkowError} With code `'NOT_FOUND'` if no category matches the given slug\n *\n * @example\n * ```typescript\n * const { category, contents } = await lynkow.categories.getBySlug('tech', {\n * page: 1,\n * limit: 10\n * })\n * console.log(category.name) // \"Tech\"\n * console.log(contents.data.length) // Number of articles on this page\n * console.log(contents.meta.hasMorePages) // Whether more pages exist\n * ```\n */\n async getBySlug(\n slug: string,\n options?: CategoryOptions & BaseRequestOptions\n ): Promise<CategoryDetailResponse> {\n const query: Record<string, unknown> = {}\n\n if (options?.page) query['page'] = options.page\n if (options?.limit ?? options?.perPage) query['limit'] = options?.limit ?? options?.perPage\n\n const cacheKey = `${CACHE_PREFIX}slug_${slug}_${JSON.stringify(options || {})}`\n return this.getWithCache<CategoryDetailResponse>(\n cacheKey,\n `/categories/${encodeURIComponent(slug)}`,\n query,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Invalidate every cached category response (flat list, hierarchy\n * tree, detail views with paginated contents). Call after an admin\n * mutation or on receipt of a `category.*` webhook so the next public\n * request bypasses the 30-minute SWR cache.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // On category restructuring:\n * lynkow.categories.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type { TagsListResponse, BaseRequestOptions } from '../types'\n\nconst CACHE_PREFIX = 'tags_'\n\n/**\n * Service for retrieving content tags.\n *\n * Accessible via `lynkow.tags`. Tags are lightweight labels attached to\n * blog articles for cross-cutting classification (unlike categories which\n * are hierarchical). Responses are cached for 5 minutes (SHORT TTL).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n * const { data } = await lynkow.tags.list()\n * ```\n */\nexport class TagsService extends BaseService {\n /**\n * Retrieves the complete list of tags available on the site.\n * Returns all tags (no pagination). Cached for 5 minutes per locale.\n *\n * @param options - Request options; use `locale` to fetch localized tag names\n * @returns An object containing `data` (array of `Tag` with id, name, slug, and locale)\n *\n * @example\n * ```typescript\n * const { data } = await lynkow.tags.list()\n * data.forEach(tag => console.log(tag.name)) // \"Featured\", \"Tutorial\", etc.\n * ```\n */\n async list(options?: BaseRequestOptions): Promise<TagsListResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}list_${locale || 'default'}`\n return this.getWithCache<TagsListResponse>(\n cacheKey,\n '/tags',\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Invalidate every cached tag response (lists, tag-filtered contents).\n * Call after an admin mutation or on receipt of a `tag.*` webhook so\n * the next public request bypasses the SWR cache.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.tags.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type { Page, PagesListResponse, BaseRequestOptions } from '../types'\n\nconst CACHE_PREFIX = 'pages_'\n\n/**\n * Options for listing pages\n */\nexport interface PagesListOptions extends BaseRequestOptions {\n /**\n * Restrict results to pages carrying this tag slug (e.g. `'legal'` to\n * list Privacy Policy + Terms of Service). Tags are declared on each\n * page in the admin under SEO / Organization and used mainly for\n * grouping unrelated pages that share a purpose.\n *\n * Omit to return every published page for the site.\n */\n tag?: string\n}\n\n/**\n * Service for retrieving published pages (Site Blocks of type \"page\").\n *\n * Accessible via `lynkow.pages`. Pages are CMS-managed, schema-driven content\n * blocks (e.g. \"About\", \"Contact\", legal pages). Unlike blog articles, pages\n * use DataSource-resolved data instead of a rich text body. Responses are\n * cached for 5 minutes (SHORT TTL) when a cache adapter is configured.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // List all pages\n * const { data } = await lynkow.pages.list()\n *\n * // Fetch a page by slug or path\n * const page = await lynkow.pages.getBySlug('about')\n * const page = await lynkow.pages.getByPath('/services/consulting')\n * ```\n */\nexport class PagesService extends BaseService {\n /**\n * Retrieves a list of all published pages on the site. Optionally\n * filter by tag to get a subset (e.g. legal documents). Returns page\n * summaries without resolved data. Cached for 5 minutes per locale+tag.\n *\n * @param options - Request options:\n * - `tag` — filter pages by tag slug (e.g. `'legal'` for privacy policy, terms, etc.)\n * - `locale` — override the client's default locale\n * @returns An object containing `data` (array of `PageSummary` with id, slug, name, path,\n * locale, and updatedAt)\n *\n * @example\n * ```typescript\n * // List all pages\n * const { data } = await lynkow.pages.list()\n *\n * // List only legal documents\n * const { data } = await lynkow.pages.list({ tag: 'legal' })\n * ```\n */\n async list(options?: PagesListOptions): Promise<PagesListResponse> {\n const locale = options?.locale || this.config.locale\n const query: Record<string, unknown> = {}\n if (options?.tag) query['tag'] = options.tag\n\n const cacheKey = `${CACHE_PREFIX}list_${locale || 'default'}_${options?.tag || 'all'}`\n return this.getWithCache<PagesListResponse>(\n cacheKey,\n '/pages',\n query,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Retrieves a single page by its slug, including fully resolved DataSource data,\n * SEO settings, and alternate locale versions. Cached for 5 minutes per slug+locale.\n *\n * @param slug - The unique URL slug of the page (e.g. `'about'`, `'contact'`, `'privacy-policy'`)\n * @param options - Request options; use `locale` to fetch a specific localized version\n * @returns The full `Page` object with `data` (resolved DataSource values), `seo`\n * (meta title, description, OG/Twitter tags, canonical URL), and `alternates`\n * (other locale versions with their paths)\n * @throws {LynkowError} With code `'NOT_FOUND'` if no published page matches the slug\n *\n * @example\n * ```typescript\n * const page = await lynkow.pages.getBySlug('about')\n * console.log(page.data) // Resolved DataSource content\n * console.log(page.seo) // SEO metadata\n * console.log(page.alternates) // Other locale versions\n * ```\n */\n async getBySlug(slug: string, options?: BaseRequestOptions): Promise<Page> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}slug_${slug}_${locale || 'default'}`\n\n const response = await this.getWithCache<{ data: Page }>(\n cacheKey,\n `/pages/${encodeURIComponent(slug)}`,\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Retrieves a page by its full URL path, which may include nested segments\n * (e.g. `/services/consulting`). Useful when you have the path from a URL\n * but not the slug. Returns the same full page data as `getBySlug()`.\n * Cached for 5 minutes per path+locale.\n *\n * @param path - The full URL path of the page (e.g. `'/services/consulting'`, `'/about'`).\n * Must start with a forward slash.\n * @param options - Request options; use `locale` to fetch a specific localized version\n * @returns The full `Page` object with resolved data, SEO settings, and alternates\n * @throws {LynkowError} With code `'NOT_FOUND'` if no published page matches the path\n *\n * @example\n * ```typescript\n * // Resolve a page from the current URL\n * const page = await lynkow.pages.getByPath('/services/consulting')\n * console.log(page.name) // \"Consulting\"\n * ```\n */\n async getByPath(path: string, options?: BaseRequestOptions): Promise<Page> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}path_${path}_${locale || 'default'}`\n\n const response = await this.getWithCache<{ data: Page }>(\n cacheKey,\n '/page-by-path',\n { path },\n options,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Retrieves Schema.org JSON-LD structured data for a page, ready to be\n * embedded in a `<script type=\"application/ld+json\">` tag for search\n * engine optimization. Cached for 5 minutes per slug+locale.\n *\n * @param slug - The page slug to generate JSON-LD for (e.g. `'about'`, `'contact'`)\n * @param options - Request options; use `locale` to get locale-specific structured data\n * @returns A JSON-LD object (Schema.org format) as `Record<string, unknown>`.\n * The exact shape depends on the page type (e.g. WebPage, Organization, etc.)\n * @throws {LynkowError} With code `'NOT_FOUND'` if no published page matches the slug\n *\n * @example\n * ```typescript\n * const jsonLd = await lynkow.pages.getJsonLd('about')\n * // Embed in your HTML <head>\n * // <script type=\"application/ld+json\">{JSON.stringify(jsonLd)}</script>\n * ```\n */\n async getJsonLd(\n slug: string,\n options?: BaseRequestOptions\n ): Promise<Record<string, unknown>> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}jsonld_${slug}_${locale || 'default'}`\n\n const response = await this.getWithCache<{ data: Record<string, unknown> }>(\n cacheKey,\n `/pages/${encodeURIComponent(slug)}/json-ld`,\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Invalidate every cached page response (lists, slug lookups, path\n * lookups, and the JSON-LD endpoint). Call after an admin mutation or\n * on receipt of a `page.*` webhook so the next public request bypasses\n * the SWR cache and re-materializes the resolved data (including\n * `structuredData.graph`).\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.pages.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n SiteConfigResponse,\n GlobalBlockResponse,\n BaseRequestOptions,\n} from '../types'\n\nconst CACHE_PREFIX = 'globals_'\n\n/**\n * Service for retrieving global site blocks (header, footer, navigation, etc.).\n *\n * Accessible via `lynkow.globals` (aliased from `lynkow.blocks`). Global blocks\n * are reusable, schema-driven content components shared across all pages of the\n * site. They are resolved server-side with DataSources. Responses are cached for\n * 10 minutes (MEDIUM TTL) since global blocks change infrequently.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Fetch all global blocks at once (recommended for layouts)\n * const { data } = await lynkow.globals.siteConfig()\n * const header = data.globals['header']\n *\n * // Fetch a single global block\n * const { data } = await lynkow.globals.getBySlug('footer')\n * ```\n */\nexport class BlocksService extends BaseService {\n /**\n * Retrieves the complete site configuration including basic site info\n * (name, domain, logo, favicon) and all global blocks resolved in a\n * single request. This is the recommended way to fetch layout data for\n * your site shell (header, footer, navigation). Cached for 10 minutes per locale.\n *\n * @param options - Request options; use `locale` to fetch localized block data\n * @returns An object containing `data` with:\n * - `site` — basic site info (name, domain, logo, favicon)\n * - `locale` — the resolved locale code\n * - `globals` — a record mapping block slugs to their resolved `GlobalBlock` data\n * (e.g. `{ header: {...}, footer: {...}, nav: {...} }`)\n *\n * @example\n * ```typescript\n * const { data } = await lynkow.globals.siteConfig()\n * console.log(data.site.name) // \"My Site\"\n * const header = data.globals['header'] // Resolved header block data\n * const footer = data.globals['footer'] // Resolved footer block data\n * ```\n */\n async siteConfig(options?: BaseRequestOptions): Promise<SiteConfigResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}siteconfig_${locale || 'default'}`\n return this.getWithCache<SiteConfigResponse>(\n cacheKey,\n '/site-config',\n undefined,\n options,\n CACHE_TTL.MEDIUM\n )\n }\n\n /**\n * Retrieves a single global block by its slug, with fully resolved DataSource\n * data. Use this when you only need one specific block rather than all globals.\n * Cached for 10 minutes per slug+locale.\n *\n * @param slug - The unique slug of the global block (e.g. `'header'`, `'footer'`, `'nav'`, `'sidebar'`)\n * @param options - Request options; use `locale` to fetch a localized version\n * @returns An object containing `data` — a `GlobalBlock` with `slug`, `name`, and\n * `data` (the resolved DataSource values as `Record<string, unknown>`)\n * @throws {LynkowError} With code `'NOT_FOUND'` if no global block matches the slug\n *\n * @example\n * ```typescript\n * const { data } = await lynkow.globals.getBySlug('header')\n * console.log(data.name) // \"Header\"\n * console.log(data.data) // Resolved content (links, logo, menu items, etc.)\n * ```\n */\n async getBySlug(\n slug: string,\n options?: BaseRequestOptions\n ): Promise<GlobalBlockResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}${slug}_${locale || 'default'}`\n return this.getWithCache<GlobalBlockResponse>(\n cacheKey,\n `/global/${encodeURIComponent(slug)}`,\n undefined,\n options,\n CACHE_TTL.MEDIUM\n )\n }\n\n /**\n * Retrieves a single global block by slug.\n *\n * @deprecated Use `getBySlug()` instead. This method will be removed in the next major version.\n * @param slug - The block slug\n * @param options - Request options\n * @returns The resolved global block\n * @throws {LynkowError} With code `'NOT_FOUND'` when the slug does not\n * match any global block for the site, `'NETWORK_ERROR'` on transport\n * failure. Same surface as {@link getBySlug}.\n *\n * @example\n * ```typescript\n * // Deprecated -- use getBySlug() instead:\n * const { data } = await lynkow.globals.getBySlug('footer')\n * ```\n */\n async global(\n slug: string,\n options?: BaseRequestOptions\n ): Promise<GlobalBlockResponse> {\n return this.getBySlug(slug, options)\n }\n\n /**\n * Invalidate every cached global block response (site config payload\n * and per-slug lookups). Call after an admin mutation or on receipt of\n * a `globalBlock.updated` webhook so the next public request hits the\n * origin and re-resolves any referenced variables.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.globals.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","/**\n * Hidden anti-spam fields auto-injected into form and review submissions.\n * The Lynkow API's `spamProtection` middleware verifies both values server\n * side; submissions that fail either check are rejected with a generic\n * 200 OK so bots cannot probe the heuristic.\n */\nexport interface SpamFields {\n /**\n * Honeypot field. MUST be left empty by real users; rendered as a hidden\n * input so genuine browsers ignore it but naive scrapers fill it in.\n */\n _hp: string\n\n /**\n * Client-side session start time as Unix milliseconds since epoch\n * (the return value of `Date.now()`). The server rejects submissions\n * whose `(now - _ts)` is below the minimum fill-in threshold (bots) or\n * above the maximum session lifetime (stale).\n */\n _ts: number\n}\n\n/**\n * Generate the anti-spam fields to include on a form or review submission.\n *\n * The SDK's built-in `lynkow.forms.submit()` / `lynkow.reviews.create()`\n * call this automatically; you only need to use it directly if you build\n * a custom submission pipeline and bypass the service.\n *\n * @param sessionStartTime - Unix ms since epoch captured when the form\n * started being rendered or interacted with (typically `Date.now()` at\n * client creation time).\n * @returns `{ _hp, _ts }` ready to merge into the submission payload.\n *\n * @example\n * ```typescript\n * import { generateSpamFields } from 'lynkow'\n *\n * const sessionStart = Date.now()\n * // later, at submit time:\n * const payload = { name: 'Jane', email: 'jane@example.com', ...generateSpamFields(sessionStart) }\n * ```\n */\nexport function generateSpamFields(sessionStartTime: number): SpamFields {\n return {\n _hp: '', // Honeypot - always empty\n _ts: sessionStartTime, // Session timestamp\n }\n}\n","import { BaseService, CACHE_TTL, type InternalConfig } from './base'\nimport type {\n Form,\n FormSubmitData,\n FormSubmitResponse,\n SubmitOptions,\n BaseRequestOptions,\n} from '../types'\nimport { generateSpamFields } from '../utils/spam'\n\nconst CACHE_PREFIX = 'forms_'\n\n/**\n * Service for retrieving form schemas and submitting form data.\n *\n * Accessible via `lynkow.forms`. Forms are dynamic, CMS-managed forms with\n * configurable fields, validation rules, and spam protection. The service\n * automatically handles honeypot anti-spam fields on submissions. Form schemas\n * are cached for 10 minutes (MEDIUM TTL).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Fetch form schema to render the form\n * const form = await lynkow.forms.getBySlug('contact')\n *\n * // Submit form data\n * await lynkow.forms.submit('contact', {\n * name: 'Jane',\n * email: 'jane@example.com',\n * message: 'Hello!'\n * })\n * ```\n */\nexport class FormsService extends BaseService {\n /**\n * Client creation timestamp used for anti-spam time validation.\n * Submissions made too quickly after page load are flagged as spam.\n */\n private sessionStartTime: number\n\n constructor(config: InternalConfig) {\n super(config)\n this.sessionStartTime = Date.now()\n }\n\n /**\n * Retrieves a form definition by its slug, including the field schema,\n * validation rules, settings (submit label, success message), and spam\n * protection configuration (honeypot/reCAPTCHA). Cached for 10 minutes.\n *\n * @param slug - The unique slug of the form (e.g. `'contact'`, `'newsletter'`, `'feedback'`)\n * @returns The `Form` object with `schema` (array of `FormField` with type, label,\n * required, validation, options), `settings` (submitLabel, successMessage, redirectUrl),\n * and spam protection flags (honeypotEnabled, recaptchaEnabled, recaptchaSiteKey)\n * @throws {LynkowError} With code `'NOT_FOUND'` if no active form matches the slug\n *\n * @example\n * ```typescript\n * const form = await lynkow.forms.getBySlug('contact')\n * // Iterate fields to render the form dynamically\n * form.schema.forEach(field => {\n * console.log(field.name, field.type, field.required)\n * })\n * // Check spam protection config\n * if (form.recaptchaEnabled) {\n * // Render reCAPTCHA widget using form.recaptchaSiteKey\n * }\n * ```\n */\n async getBySlug(slug: string): Promise<Form> {\n const cacheKey = `${CACHE_PREFIX}${slug}`\n const response = await this.getWithCache<{ data: Form }>(\n cacheKey,\n `/forms/${encodeURIComponent(slug)}`,\n undefined,\n undefined,\n CACHE_TTL.MEDIUM\n )\n return response.data\n }\n\n /**\n * Submits form data to the API. Anti-spam honeypot fields (`_hp`, `_ts`) are\n * injected automatically by the SDK -- you do not need to add them yourself.\n * If the form has reCAPTCHA enabled, pass the token via `options.recaptchaToken`.\n *\n * @param slug - The slug of the form to submit to (e.g. `'contact'`)\n * @param data - Key-value pairs matching the form's field names.\n * Values can be `string`, `number`, `boolean`, or `File`.\n * @param options - Optional submission options:\n * - `recaptchaToken` — the reCAPTCHA v3 token (required if reCAPTCHA is enabled on the form)\n * - `fetchOptions` — custom fetch options (e.g. AbortSignal)\n * @returns A `FormSubmitResponse` with `message` (confirmation text), `status`\n * (`'success'` for immediate acceptance, `'pending'` if double opt-in is enabled),\n * and optionally `submissionId`\n * @throws {LynkowError} With code `'VALIDATION_ERROR'` if field validation fails\n * @throws {LynkowError} With code `'NOT_FOUND'` if the form slug is invalid\n * @throws {LynkowError} With code `'FORBIDDEN'` if spam protection rejects the submission\n *\n * @example\n * ```typescript\n * const result = await lynkow.forms.submit('contact', {\n * name: 'John Doe',\n * email: 'john@example.com',\n * message: 'Hello!'\n * })\n *\n * if (result.status === 'pending') {\n * // Show \"check your email\" confirmation\n * } else {\n * // Show success message\n * console.log(result.message)\n * }\n * ```\n */\n async submit(\n slug: string,\n data: FormSubmitData,\n options?: SubmitOptions & BaseRequestOptions\n ): Promise<FormSubmitResponse> {\n // Add anti-spam fields\n const spamFields = generateSpamFields(this.sessionStartTime)\n\n // Build the body - form data wrapped in 'data' field per API spec\n const body: Record<string, unknown> = {\n data,\n honeypot: spamFields._hp,\n ...spamFields, // Also include _hp and _ts at root for middleware\n }\n\n // Add reCAPTCHA token if provided\n if (options?.recaptchaToken) {\n body['recaptchaToken'] = options.recaptchaToken\n }\n\n return this.post<FormSubmitResponse>(\n `/forms/${encodeURIComponent(slug)}/submit`,\n body,\n options\n )\n }\n\n /**\n * Invalidate every cached form schema response. Call after an admin\n * mutation or on receipt of a `form.*` webhook so the next public\n * request re-fetches the latest field definitions (required fields,\n * validators, spam settings).\n *\n * Does not affect form submissions, which are never cached.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.forms.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL, type InternalConfig } from './base'\nimport type {\n Review,\n ReviewSettings,\n ReviewSubmitData,\n ReviewSubmitResponse,\n ReviewsListResponse,\n ReviewsFilters,\n SubmitOptions,\n BaseRequestOptions,\n} from '../types'\nimport { generateSpamFields } from '../utils/spam'\n\nconst CACHE_PREFIX = 'reviews_'\n\n/**\n * Service for retrieving and submitting customer reviews.\n *\n * Accessible via `lynkow.reviews`. Only reviews with status `'approved'`\n * are returned by the public API. New submissions go through moderation\n * if `requireApproval` is enabled in the review settings. Anti-spam\n * honeypot fields are injected automatically on submissions. Responses\n * are cached for 5 minutes (SHORT TTL).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // List approved reviews\n * const { data, meta } = await lynkow.reviews.list({ limit: 10 })\n *\n * // Submit a review\n * await lynkow.reviews.submit({\n * authorName: 'Alice',\n * rating: 5,\n * content: 'Great service!'\n * })\n * ```\n */\nexport class ReviewsService extends BaseService {\n /**\n * Client creation timestamp used for anti-spam time validation.\n * Submissions made too quickly after page load are flagged as spam.\n */\n private sessionStartTime: number\n\n constructor(config: InternalConfig) {\n super(config)\n this.sessionStartTime = Date.now()\n }\n\n /**\n * Retrieves a paginated list of approved customer reviews. Only reviews\n * that have passed moderation are returned. Cached for 5 minutes per\n * unique filter combination.\n *\n * @param filters - Optional filters to narrow down results:\n * - `page` / `limit` — pagination (defaults to page 1)\n * - `minRating` / `maxRating` — filter by rating range (1-5 scale)\n * - `sort` / `order` — sort field and direction (`'asc'` or `'desc'`)\n * @param options - Base request options (locale override, custom fetch options)\n * @returns An object containing `data` (array of `Review` with authorName, rating,\n * title, content, createdAt, and optional `response` from the site owner)\n * and `meta` (pagination info with total, currentPage, lastPage, hasMorePages)\n *\n * @example\n * ```typescript\n * // Fetch top-rated reviews (4+ stars)\n * const { data, meta } = await lynkow.reviews.list({\n * minRating: 4,\n * limit: 10\n * })\n *\n * data.forEach(review => {\n * console.log(`${review.authorName}: ${review.rating}/5`)\n * if (review.response) {\n * console.log(`Owner reply: ${review.response.content}`)\n * }\n * })\n * ```\n */\n async list(\n filters?: ReviewsFilters,\n options?: BaseRequestOptions\n ): Promise<ReviewsListResponse> {\n const query: Record<string, unknown> = {}\n\n if (filters?.page) query['page'] = filters.page\n if (filters?.limit ?? filters?.perPage) query['limit'] = filters?.limit ?? filters?.perPage\n if (filters?.minRating) query['minRating'] = filters.minRating\n if (filters?.maxRating) query['maxRating'] = filters.maxRating\n if (filters?.sort) query['sort'] = filters.sort\n if (filters?.order) query['order'] = filters.order\n\n const cacheKey = `${CACHE_PREFIX}list_${JSON.stringify(filters || {})}`\n return this.getWithCache<ReviewsListResponse>(\n cacheKey,\n '/reviews',\n query,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Retrieves a single approved review by its slug or numeric ID.\n * Returns the full review including any owner response. Cached for 5 minutes.\n *\n * @param slugOrId - The URL slug (e.g. `'excellent-service'`) or numeric ID\n * (as string, e.g. `'42'`) of the review\n * @returns The full `Review` object with authorName, rating (1-5), title,\n * content, locale, createdAt, and optional `response` (owner's reply)\n * @throws {LynkowError} With code `'NOT_FOUND'` if no approved review matches the slug/ID\n *\n * @example\n * ```typescript\n * const review = await lynkow.reviews.getBySlug('excellent-service')\n * console.log(review.rating) // 5\n * console.log(review.content) // \"Best experience ever!\"\n * ```\n */\n async getBySlug(slugOrId: string): Promise<Review> {\n const cacheKey = `${CACHE_PREFIX}slug_${slugOrId}`\n const response = await this.getWithCache<{ data: Review }>(\n cacheKey,\n `/reviews/${encodeURIComponent(slugOrId)}`,\n undefined,\n undefined,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Retrieves the public review settings for this site, including whether\n * reviews are enabled, moderation rules, rating scale, and field configuration.\n * Use this to dynamically render the review submission form. Cached for 10 minutes.\n *\n * @returns A `ReviewSettings` object with:\n * - `enabled` — whether reviews are active on this site\n * - `requireApproval` — whether new reviews need moderation before publication\n * - `allowAnonymous` — whether anonymous submissions are allowed\n * - `minRating` / `maxRating` — rating scale bounds (typically 1-5)\n * - `fields.title` — whether the title field is enabled/required\n * - `fields.email` — whether the email field is enabled/required\n *\n * @example\n * ```typescript\n * const settings = await lynkow.reviews.settings()\n * if (!settings.enabled) {\n * // Reviews are disabled, hide the form\n * return\n * }\n * if (settings.fields.email.required) {\n * // Render email field as required\n * }\n * ```\n *\n * @throws {LynkowError} With code `'NETWORK_ERROR'` on transport\n * failure, `'NOT_FOUND'` if the site does not exist.\n */\n async settings(): Promise<ReviewSettings> {\n const cacheKey = `${CACHE_PREFIX}settings`\n return this.getWithCache<ReviewSettings>(\n cacheKey,\n '/reviews/settings',\n undefined,\n undefined,\n CACHE_TTL.MEDIUM\n )\n }\n\n /**\n * Submits a new customer review. Anti-spam honeypot fields (`_hp`, `_ts`) are\n * injected automatically by the SDK. If reCAPTCHA is enabled on the site, pass\n * the token via `options.recaptchaToken`. After a successful submission, the\n * reviews cache is automatically invalidated.\n *\n * @param data - The review data to submit:\n * - `authorName` — reviewer's display name (required)\n * - `authorEmail` — reviewer's email (required if settings demand it)\n * - `rating` — numeric rating (must be within the site's min/max range, typically 1-5)\n * - `title` — optional review title (required if settings demand it)\n * - `content` — the review text (required)\n * @param options - Optional submission options:\n * - `recaptchaToken` — the reCAPTCHA v3 token if reCAPTCHA is enabled\n * - `fetchOptions` — custom fetch options (e.g. AbortSignal)\n * @returns A `ReviewSubmitResponse` with `message` (confirmation text), `status`\n * (`'success'` if auto-approved, `'pending'` if moderation is required),\n * and optionally `reviewId`\n * @throws {LynkowError} With code `'VALIDATION_ERROR'` if required fields are missing or invalid\n * @throws {LynkowError} With code `'FORBIDDEN'` if spam protection rejects the submission\n *\n * @example\n * ```typescript\n * const result = await lynkow.reviews.submit({\n * authorName: 'Alice',\n * rating: 5,\n * content: 'Excellent service!'\n * })\n *\n * if (result.status === 'pending') {\n * // Show \"your review is awaiting moderation\"\n * }\n * ```\n */\n async submit(\n data: ReviewSubmitData,\n options?: SubmitOptions & BaseRequestOptions\n ): Promise<ReviewSubmitResponse> {\n // Add anti-spam fields\n const spamFields = generateSpamFields(this.sessionStartTime)\n\n // Build the body\n const body: Record<string, unknown> = {\n ...data,\n ...spamFields,\n }\n\n // Add reCAPTCHA token if provided\n if (options?.recaptchaToken) {\n body['_recaptcha_token'] = options.recaptchaToken\n }\n\n const result = await this.post<ReviewSubmitResponse>('/reviews', body, options)\n\n // Invalidate cache after submission\n this.invalidateCache(CACHE_PREFIX)\n\n return result\n }\n\n /**\n * Invalidate every cached review response (lists, individual reviews,\n * settings). Automatically invoked after a successful {@link submit};\n * call it manually after an admin moderation action or on receipt of a\n * `review.*` webhook to keep public listings in sync.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // After an admin approved a pending review:\n * lynkow.reviews.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type { SiteConfig } from '../types'\n\nconst CACHE_PREFIX = 'site_'\n\n/**\n * Service for retrieving the public site configuration.\n *\n * Accessible via `lynkow.site`. Provides site-level metadata such as name,\n * domain, locales, timezone, branding settings, and analytics consent mode.\n * Cached for 10 minutes (MEDIUM TTL) since site configuration rarely changes.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n * const config = await lynkow.site.getConfig()\n * console.log(config.name) // \"My Site\"\n * console.log(config.enabledLocales) // ['en', 'fr']\n * ```\n */\nexport class SiteService extends BaseService {\n /**\n * Retrieves the complete public site configuration, including site identity\n * (name, domain, logo, favicon), localization settings (default locale,\n * enabled locales, i18n config with detection rules), branding options,\n * and analytics consent mode. Cached for 10 minutes.\n *\n * @returns A `SiteConfig` object with:\n * - `id` — site UUID\n * - `name`, `domain`, `description` — basic site identity\n * - `logoUrl`, `faviconUrl` — branding assets\n * - `defaultLocale`, `enabledLocales` — locale configuration\n * - `i18n` — rich i18n config (detection, switcher, locale names/flags)\n * - `showBranding` — whether the \"Powered by Lynkow\" badge should be shown\n * - `analytics.consentMode` — `'opt-in'`, `'opt-out'`, or `'disabled'`\n *\n * @example\n * ```typescript\n * const config = await lynkow.site.getConfig()\n * console.log(config.enabledLocales) // ['en', 'fr', 'de']\n * console.log(config.i18n.detection.enabled) // true\n * console.log(config.analytics.consentMode) // 'opt-in'\n * ```\n */\n async getConfig(): Promise<SiteConfig> {\n const cacheKey = `${CACHE_PREFIX}config`\n const response = await this.getWithCache<{ data: SiteConfig }>(\n cacheKey,\n '/site',\n undefined,\n undefined,\n CACHE_TTL.MEDIUM\n )\n return response.data\n }\n\n /**\n * Invalidate the cached site configuration. Call after a settings\n * change on the admin (branding, enabled locales, default author,\n * search config) or on receipt of a `site.updated` webhook so the next\n * `getConfig()` fetches fresh values.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // After enabling a new locale in the admin:\n * lynkow.site.clearCache()\n * const fresh = await lynkow.site.getConfig()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n LegalDocument,\n LegalListResponse,\n LegalDocumentResponse,\n BaseRequestOptions,\n} from '../types'\n\nconst CACHE_PREFIX = 'legal_'\n\n/**\n * Service for accessing legal documents (privacy policy, terms of service, etc.).\n *\n * Accessible via `lynkow.legal`. Legal documents are regular pages tagged with\n * `'legal'`. This service provides convenience methods to access them, but is\n * deprecated in favor of the `pages` service.\n *\n * Responses are cached for 5 minutes (SHORT TTL).\n *\n * @deprecated Use `lynkow.pages.list({ tag: 'legal' })` and `lynkow.pages.getBySlug(slug)` instead.\n * This service will be removed in the next major version.\n *\n * @example\n * ```typescript\n * // Deprecated approach:\n * const docs = await lynkow.legal.list()\n * const privacy = await lynkow.legal.getBySlug('privacy-policy')\n *\n * // Recommended approach:\n * const docs = await lynkow.pages.list({ tag: 'legal' })\n * const privacy = await lynkow.pages.getBySlug('privacy-policy')\n * ```\n */\nexport class LegalService extends BaseService {\n /**\n * Retrieves all published pages tagged with `'legal'`. Returns page summaries\n * including slug, name, path, and locale. Cached for 5 minutes per locale.\n *\n * @deprecated Use `lynkow.pages.list({ tag: 'legal' })` instead.\n *\n * @param options - Request options; use `locale` to fetch localized versions\n * @returns An array of `LegalDocument` objects (page summaries with name, slug, path)\n *\n * @example\n * ```typescript\n * // Deprecated -- migrate to pages service:\n * // Before: const docs = await lynkow.legal.list()\n * // After:\n * const docs = await lynkow.pages.list({ tag: 'legal' })\n * docs.data.forEach(doc => console.log(doc.slug, doc.name))\n * ```\n */\n async list(options?: BaseRequestOptions): Promise<LegalDocument[]> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}list_${locale || 'default'}`\n const response = await this.getWithCache<LegalListResponse>(\n cacheKey,\n '/pages',\n { tag: 'legal' },\n options,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Retrieves a single legal page by its slug, including resolved data and SEO settings.\n * Cached for 5 minutes per slug+locale.\n *\n * @deprecated Use `lynkow.pages.getBySlug(slug)` instead.\n *\n * @param slug - The page slug (e.g. `'privacy-policy'`, `'terms-of-service'`, `'cookie-policy'`)\n * @param options - Request options; use `locale` to fetch a specific localized version\n * @returns The `LegalDocument` object with full page data\n * @throws {LynkowError} With code `'NOT_FOUND'` if no published page matches the slug\n *\n * @example\n * ```typescript\n * // Deprecated -- migrate to pages service:\n * // Before: const privacy = await lynkow.legal.getBySlug('privacy-policy')\n * // After:\n * const privacy = await lynkow.pages.getBySlug('privacy-policy')\n * console.log(privacy.data.name, privacy.data.body)\n * ```\n */\n async getBySlug(\n slug: string,\n options?: BaseRequestOptions\n ): Promise<LegalDocument> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}slug_${slug}_${locale || 'default'}`\n const response = await this.getWithCache<LegalDocumentResponse>(\n cacheKey,\n `/pages/${encodeURIComponent(slug)}`,\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n return response.data\n }\n\n /**\n * Invalidate every cached legal document response. Kept for\n * backwards compatibility with sites that still reference the legacy\n * `/legal/*` surface.\n *\n * @deprecated This service is deprecated. Use `lynkow.pages` instead,\n * which covers legal pages along with every other page type and is\n * actively maintained.\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.legal.clearCache() // Deprecated; prefer lynkow.pages.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n CookieConfig,\n CookiePreferences,\n ConsentLogResponse,\n BaseRequestOptions,\n} from '../types'\n\nconst CACHE_PREFIX = 'cookies_'\n\n/**\n * Low-level service for cookie consent API interactions.\n *\n * Accessible via `lynkow.cookies`. Provides raw API methods for fetching\n * consent configuration and logging user preferences. For a higher-level\n * experience with built-in banner UI and preferences modal, use the\n * `consent` service (`lynkow.consent`) instead.\n *\n * Responses are cached for 10 minutes (MEDIUM TTL).\n *\n * @remarks\n * This service provides the API-only layer. The `consent` module builds\n * on top of it with browser-side UI (banner, preferences modal, script injection).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n * const config = await lynkow.cookies.getConfig()\n * if (config.enabled) {\n * // Build your own consent UI\n * }\n * ```\n */\nexport class CookiesService extends BaseService {\n /**\n * Retrieves the cookie consent banner configuration from the API, including\n * categories (necessary, analytics, marketing, preferences), banner texts,\n * theming options, and third-party scripts to inject per category. Cached\n * for 10 minutes.\n *\n * @returns A `CookieConfig` object with:\n * - `enabled` — whether the consent banner is active\n * - `consentMode` — `'opt-in'` (explicit) or `'opt-out'` (implied)\n * - `position` — banner placement (`'bottom-left'` or `'bottom-right'`)\n * - `theme` — `'light'`, `'dark'`, or `'auto'`\n * - `categories` — array of `CookieCategory` (id, name, description, required)\n * - `texts` — banner copy (description, acceptAll, rejectAll, customize, save)\n * - `thirdPartyScripts` — scripts to inject when their category is accepted\n *\n * @example\n * ```typescript\n * const config = await lynkow.cookies.getConfig()\n * if (config.enabled) {\n * console.log(config.categories) // [{id: 'necessary', ...}, {id: 'analytics', ...}]\n * console.log(config.texts.acceptAll) // \"Accept all\"\n * }\n * ```\n */\n async getConfig(): Promise<CookieConfig> {\n const cacheKey = `${CACHE_PREFIX}config`\n const response = await this.getWithCache<{ data: CookieConfig }>(\n cacheKey,\n '/cookie-consent/config',\n undefined,\n undefined,\n CACHE_TTL.MEDIUM\n )\n return response.data\n }\n\n /**\n * Records the user's cookie consent preferences to the server for audit trail /\n * GDPR compliance. This is a write operation (POST) and is never cached.\n *\n * @param preferences - A record mapping category IDs to boolean acceptance values\n * (e.g. `{ necessary: true, analytics: true, marketing: false }`)\n * @param options - Base request options (custom fetch options)\n * @returns A `ConsentLogResponse` with `message` (confirmation text) and\n * `consentId` (unique audit trail ID)\n *\n * @example\n * ```typescript\n * const result = await lynkow.cookies.logConsent({\n * necessary: true,\n * analytics: true,\n * marketing: false\n * })\n * console.log(result.consentId) // Unique consent record ID\n * ```\n */\n async logConsent(\n preferences: CookiePreferences,\n options?: BaseRequestOptions\n ): Promise<ConsentLogResponse> {\n return this.post<ConsentLogResponse>(\n '/cookie-consent/log',\n { preferences },\n options\n )\n }\n\n /**\n * Invalidate the cached cookie consent configuration (categories,\n * scripts, banner appearance). Call after a settings change on the\n * admin or on receipt of a `cookies.updated` webhook so the next\n * `getConfig()` returns the updated categories and the consent banner\n * reflects the new policy.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.cookies.clearCache()\n * ```\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","import { BaseService } from './base'\nimport type { BaseRequestOptions } from '../types'\n\n/**\n * Service for retrieving SEO-related files (sitemap, robots.txt, LLM-optimized\n * content, and individual Markdown exports).\n *\n * Accessible via `lynkow.seo`. All methods return plain text (XML, TXT, or Markdown)\n * and are NOT cached by the SDK since they are typically served as route handlers\n * with their own HTTP caching headers.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Serve sitemap.xml\n * const xml = await lynkow.seo.sitemap()\n *\n * // Serve robots.txt\n * const txt = await lynkow.seo.robots()\n *\n * // Serve llms.txt for AI crawlers\n * const md = await lynkow.seo.llmsTxt()\n * ```\n */\nexport class SeoService extends BaseService {\n /**\n * Retrieves the complete XML sitemap for the site. If the site uses Sitemap\n * Index mode, this returns the index file pointing to individual parts.\n * Typically served as a route handler with `Content-Type: application/xml`.\n *\n * @param options - Request options (custom fetch options)\n * @returns The sitemap XML content as a raw string\n * @throws {LynkowError} With code `'NETWORK_ERROR'` on transport\n * failure, `'NOT_FOUND'` if the site has sitemap generation disabled.\n *\n * @example\n * ```typescript\n * // In a Next.js route handler (app/sitemap.xml/route.ts)\n * export async function GET() {\n * const xml = await lynkow.seo.sitemap()\n * return new Response(xml, {\n * headers: { 'Content-Type': 'application/xml' }\n * })\n * }\n * ```\n */\n async sitemap(options?: BaseRequestOptions): Promise<string> {\n return this.getText('/sitemap.xml', options)\n }\n\n /**\n * Retrieves a specific sitemap part when Sitemap Index mode is enabled.\n * Each part contains a subset of URLs, useful for large sites that exceed\n * the 50,000 URL limit per sitemap file.\n *\n * @param part - The 1-indexed part number (e.g. `1`, `2`, `3`)\n * @param options - Request options (custom fetch options)\n * @returns The sitemap XML content for the specified part as a raw string\n * @throws {LynkowError} With code `'NOT_FOUND'` if the part number does not exist\n *\n * @example\n * ```typescript\n * // In a Next.js dynamic route handler (app/sitemap-[part].xml/route.ts)\n * export async function GET(req: Request, { params }: { params: { part: string } }) {\n * const xml = await lynkow.seo.sitemapPart(parseInt(params.part))\n * return new Response(xml, {\n * headers: { 'Content-Type': 'application/xml' }\n * })\n * }\n * ```\n */\n async sitemapPart(part: number, options?: BaseRequestOptions): Promise<string> {\n return this.getText(`/sitemap-${part}.xml`, options)\n }\n\n /**\n * Retrieves the generated robots.txt file for the site, including\n * crawl directives and sitemap references. Typically served as a\n * route handler with `Content-Type: text/plain`.\n *\n * @param options - Request options (custom fetch options)\n * @returns The robots.txt content as a raw string\n * @throws {LynkowError} With code `'NETWORK_ERROR'` on transport\n * failure, `'NOT_FOUND'` if the site has robots.txt disabled.\n *\n * @example\n * ```typescript\n * // In a Next.js route handler (app/robots.txt/route.ts)\n * export async function GET() {\n * const txt = await lynkow.seo.robots()\n * return new Response(txt, {\n * headers: { 'Content-Type': 'text/plain' }\n * })\n * }\n * ```\n */\n async robots(options?: BaseRequestOptions): Promise<string> {\n return this.getText('/robots.txt', options)\n }\n\n /**\n * Retrieves the llms.txt file, an LLM-optimized site index in Markdown format.\n * This file provides a structured overview of the site's content, designed for\n * AI crawlers and language models to understand the site's structure.\n * Supports locale-specific versions for multilingual sites.\n *\n * @param options - Request options:\n * - `locale` — fetch the index for a specific locale (e.g. `'en'`, `'fr'`).\n * When set, the API returns `/{locale}/llms.txt`. When omitted, returns\n * the default locale version.\n * @returns The llms.txt Markdown content as a raw string\n * @throws {LynkowError} With code `'NETWORK_ERROR'` on transport\n * failure, `'NOT_FOUND'` if `llmsTxtEnabled` is `false` on the site's\n * SEO settings or the locale has no llms.txt.\n *\n * @example\n * ```typescript\n * // In a Next.js route handler (app/llms.txt/route.ts)\n * export async function GET() {\n * const md = await lynkow.seo.llmsTxt()\n * return new Response(md, {\n * headers: { 'Content-Type': 'text/plain; charset=utf-8' }\n * })\n * }\n *\n * // Fetch the French version\n * const md = await lynkow.seo.llmsTxt({ locale: 'fr' })\n * ```\n */\n async llmsTxt(options?: BaseRequestOptions): Promise<string> {\n const locale = options?.locale\n const path = locale ? `/${locale}/llms.txt` : '/llms.txt'\n return this.getText(path, options)\n }\n\n /**\n * Retrieves the llms-full.txt file, which concatenates all published articles\n * and pages into a single Markdown document. Useful for AI/LLM ingestion of\n * the site's full content. Can be large for sites with many articles.\n * Supports locale-specific versions for multilingual sites.\n *\n * @param options - Request options:\n * - `locale` — fetch the full content for a specific locale (e.g. `'en'`).\n * When set, only content in that locale is included.\n * @returns The full Markdown content of all published articles and pages as a raw string\n * @throws {LynkowError} With code `'NETWORK_ERROR'` on transport\n * failure, `'NOT_FOUND'` if `llmsTxtEnabled` is `false` on the site's\n * SEO settings.\n *\n * @example\n * ```typescript\n * // In a Next.js route handler (app/llms-full.txt/route.ts)\n * export async function GET() {\n * const md = await lynkow.seo.llmsFullTxt()\n * return new Response(md, {\n * headers: { 'Content-Type': 'text/plain; charset=utf-8' }\n * })\n * }\n *\n * // Fetch the French version\n * const md = await lynkow.seo.llmsFullTxt({ locale: 'fr' })\n * ```\n */\n async llmsFullTxt(options?: BaseRequestOptions): Promise<string> {\n const locale = options?.locale\n const path = locale ? `/${locale}/llms-full.txt` : '/llms-full.txt'\n return this.getText(path, options)\n }\n\n /**\n * Retrieves a single content article or page as Markdown by its public URL path.\n * The SDK appends `.md` to the path automatically. This is useful for exposing\n * individual content items to LLMs or for Markdown-based rendering pipelines.\n *\n * @param contentPath - The content's public URL path (must start with `/` or will be auto-prefixed):\n * - For blog articles: use `Content.path` directly (already includes locale prefix)\n * - For pages in single-language sites: use `Page.path` directly\n * - For pages in multi-language sites: prepend the locale manually,\n * e.g. `/${page.locale}${page.path}`\n * @param options - Request options (custom fetch options)\n * @returns The content rendered as a Markdown string\n * @throws {LynkowError} With code `'NOT_FOUND'` if the path does not match any published content\n *\n * @example\n * ```typescript\n * // Get a blog article as Markdown (path includes locale automatically)\n * const article = await lynkow.contents.getBySlug('my-article')\n * const md = await lynkow.seo.getMarkdown(article.path)\n *\n * // Get a page as Markdown (mono-language)\n * const page = await lynkow.pages.getBySlug('about')\n * const md = await lynkow.seo.getMarkdown(page.path!)\n *\n * // Get a page as Markdown (multi-language — prepend locale)\n * const page = await lynkow.pages.getBySlug('about')\n * const md = await lynkow.seo.getMarkdown(`/${page.locale}${page.path}`)\n *\n * // In a Next.js catch-all route handler\n * export async function GET(req: Request) {\n * const url = new URL(req.url)\n * const path = url.pathname.replace(/\\.md$/, '')\n * const md = await lynkow.seo.getMarkdown(path)\n * return new Response(md, {\n * headers: { 'Content-Type': 'text/markdown; charset=utf-8' }\n * })\n * }\n * ```\n */\n async getMarkdown(contentPath: string, options?: BaseRequestOptions): Promise<string> {\n const normalizedPath = contentPath.startsWith('/') ? contentPath : `/${contentPath}`\n return this.getText(`${normalizedPath}.md`, options)\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type {\n PathsListResponse,\n ResolveResponse,\n Redirect,\n BaseRequestOptions,\n} from '../types'\nimport { LynkowError } from '../core/errors'\n\nconst CACHE_PREFIX = 'paths_'\n\n/**\n * Service for URL path resolution and static site generation (SSG).\n *\n * Accessible via `lynkow.paths`. Provides methods to list all available paths\n * for static generation, resolve a URL path to its content or category, and\n * check for configured redirects. Cached for 5 minutes (SHORT TTL).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Generate static params\n * const { paths } = await lynkow.paths.list()\n *\n * // Resolve a URL to content\n * const result = await lynkow.paths.resolve('/blog/tech/my-article')\n *\n * // Check for redirects\n * const redirect = await lynkow.paths.matchRedirect('/old-page')\n * ```\n */\nexport class PathsService extends BaseService {\n /**\n * Retrieves all available URL paths for the site, intended for static site\n * generation (SSG). Returns every published content and category path with\n * segments, type, locale, and last modification date. Cached for 5 minutes\n * per locale.\n *\n * @param options - Request options; use `locale` to fetch paths for a specific locale only\n * @returns A `PathsListResponse` containing:\n * - `paths` — array of `Path` objects, each with `path` (full URL path),\n * `segments` (split path parts for dynamic routes), `type` (`'content'` or `'category'`),\n * `locale`, and `lastModified` (ISO 8601 date)\n * - `blogUrlMode` — `'flat'` (slug only) or `'nested'` (category/slug)\n * - `locale` — resolved locale code, or null for all locales\n *\n * @example\n * ```typescript\n * // In Next.js generateStaticParams()\n * export async function generateStaticParams() {\n * const { paths } = await lynkow.paths.list()\n * return paths.map(p => ({\n * slug: p.segments\n * }))\n * }\n * ```\n */\n async list(options?: BaseRequestOptions): Promise<PathsListResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}list_${locale || 'all'}`\n return this.getWithCache<PathsListResponse>(\n cacheKey,\n '/paths',\n undefined,\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Resolves a URL path to its corresponding content article or category.\n * Returns a discriminated union: check the `type` field to determine whether\n * the path matched a content (`'content'`) or a category (`'category'`).\n * Cached for 5 minutes per path+locale.\n *\n * @param path - The URL path to resolve (e.g. `'/blog/tech/my-article'`, `'/blog/tech'`)\n * @param options - Request options; use `locale` to resolve in a specific locale context\n * @returns A `ResolveResponse` discriminated union:\n * - If `type === 'content'`: includes the full `Content` object (body, SEO, author, etc.)\n * - If `type === 'category'`: includes `CategoryDetail` and paginated `contents`\n * - Both include `locale` and `blogUrlMode`\n * @throws {LynkowError} With code `'NOT_FOUND'` if the path does not match any content or category\n *\n * @example\n * ```typescript\n * const result = await lynkow.paths.resolve('/blog/tech/my-article')\n *\n * if (result.type === 'content') {\n * // Render the full article\n * console.log(result.content.title)\n * } else if (result.type === 'category') {\n * // Render the category listing page\n * console.log(result.category.name)\n * console.log(result.contents.data.length) // Articles in this category\n * }\n * ```\n */\n async resolve(\n path: string,\n options?: BaseRequestOptions\n ): Promise<ResolveResponse> {\n const locale = options?.locale || this.config.locale\n const cacheKey = `${CACHE_PREFIX}resolve_${path}_${locale || 'default'}`\n return this.getWithCache<ResolveResponse>(\n cacheKey,\n '/resolve',\n { path },\n options,\n CACHE_TTL.SHORT\n )\n }\n\n /**\n * Checks if a URL path has a configured server-side redirect. Returns the\n * redirect rule if one matches, or `null` if no redirect is configured.\n * This method is NOT cached to ensure redirect changes take effect immediately.\n * A 404 response from the API is treated as \"no redirect found\" (returns `null`).\n *\n * @param path - The URL path to check for redirects (e.g. `'/old-page'`, `'/legacy/url'`)\n * @param options - Request options (custom fetch options)\n * @returns A `Redirect` object with `source`, `target`, `statusCode` (301/302/307/308),\n * and `preserveQueryString` flag, or `null` if no redirect matches\n * @throws {LynkowError} Re-throws any error other than 404 (e.g. network errors)\n *\n * @example\n * ```typescript\n * // In a Next.js middleware\n * const redirect = await lynkow.paths.matchRedirect(pathname)\n * if (redirect) {\n * return NextResponse.redirect(redirect.target, redirect.statusCode)\n * }\n * // No redirect, continue to normal page rendering\n * ```\n */\n async matchRedirect(\n path: string,\n options?: BaseRequestOptions\n ): Promise<Redirect | null> {\n try {\n const response = await this.get<{ data: Redirect }>(\n '/redirects/match',\n { path },\n options\n )\n return response.data\n } catch (error: unknown) {\n // 404 = no redirect found\n if (error instanceof LynkowError && error.status === 404) {\n return null\n }\n throw error\n }\n }\n\n /**\n * Invalidates all cached path responses (list and resolve lookups).\n * Call this after knowing the site's URL structure has changed\n * (e.g. new content published, slugs updated) to force fresh data.\n */\n clearCache(): void {\n this.invalidateCache(CACHE_PREFIX)\n }\n}\n","/**\n * Environment detection utilities\n * Determines if code is running in browser or server environment\n */\n\n/**\n * Check if running in a browser environment\n */\nexport const isBrowser: boolean =\n typeof window !== 'undefined' &&\n typeof window.document !== 'undefined' &&\n typeof window.document.createElement !== 'undefined'\n\n/**\n * Check if running in a server environment (Node.js, Deno, etc.)\n */\nexport const isServer: boolean = !isBrowser\n\n/**\n * Execute a function only in browser environment\n * Returns the fallback value in server environment\n *\n * @param fn - Function to execute in browser\n * @param fallback - Value to return in server environment\n * @returns Result of fn() in browser, fallback in server\n *\n * @example\n * ```typescript\n * const visitorId = browserOnly(\n * () => localStorage.getItem('visitor_id'),\n * null\n * )\n * ```\n */\nexport function browserOnly<T>(fn: () => T, fallback: T): T {\n if (isBrowser) {\n return fn()\n }\n return fallback\n}\n\n/**\n * Execute an async function only in browser environment\n * Returns the fallback value in server environment\n *\n * @param fn - Async function to execute in browser\n * @param fallback - Value to return in server environment\n * @returns Promise resolving to result of fn() in browser, fallback in server\n *\n * @example\n * ```typescript\n * const config = await browserOnlyAsync(\n * () => fetchConfig(),\n * defaultConfig\n * )\n * ```\n */\nexport async function browserOnlyAsync<T>(fn: () => Promise<T>, fallback: T): Promise<T> {\n if (isBrowser) {\n return fn()\n }\n return fallback\n}\n\n/**\n * No-op function for browser-only features in server environment\n * Useful for methods that should silently do nothing on server\n */\nexport function noop(): void {\n // Intentionally empty\n}\n\n/**\n * Create a no-op version of a function for server environment\n *\n * @param fn - Function to wrap\n * @returns Original function in browser, no-op in server\n */\nexport function browserOnlyFn<T extends (...args: any[]) => any>(fn: T): T {\n if (isBrowser) {\n return fn\n }\n return noop as T\n}\n","/**\n * Analytics module for Lynkow SDK\n *\n * Browser-only module that loads and wraps the Lynkow tracker.js\n * All methods are no-op on server.\n */\n\nimport { isBrowser } from '../core/environment'\nimport type { InternalConfig } from './base'\n\n/**\n * Payload accepted by {@link AnalyticsService.trackPageview}. Every field\n * is optional; unset values fall back to the browser context at call time.\n */\nexport interface PageviewData {\n /**\n * URL path to record. Should start with `/`. Query string and hash are\n * preserved as-is; strip them when they do not identify a distinct\n * pageview (e.g. tracker-specific UTM parameters). Defaults to\n * `window.location.pathname` when omitted.\n */\n path?: string\n /**\n * Human-readable page title as it appears in the browser tab, used for\n * the analytics dashboard listing. Defaults to `document.title` when\n * omitted. Max ~255 characters before server-side truncation.\n */\n title?: string\n /**\n * Referring URL for this pageview, typically `document.referrer`.\n * Pass `''` to record \"direct\" traffic when you want to override the\n * browser's value. Omit to let the tracker fill it from\n * `document.referrer`.\n */\n referrer?: string\n}\n\n/**\n * Payload accepted by {@link AnalyticsService.trackEvent}. `type` is the\n * only required field; every other key becomes an ad-hoc property on the\n * event recorded by the tracker.\n */\nexport interface EventData {\n /**\n * Short slug identifying the event (e.g. `'cta_click'`,\n * `'signup_complete'`). Prefer snake_case so events group cleanly in\n * the analytics dashboard. Required and must be non-empty.\n */\n type: string\n /**\n * Arbitrary extra properties to attach to the event. Values must be\n * JSON-serializable. Keys prefixed with `_` are reserved for the\n * tracker and may be overwritten server-side.\n */\n [key: string]: unknown\n}\n\n/**\n * LynkowAnalytics global interface (from tracker.js)\n */\ninterface LynkowAnalyticsGlobal {\n init: (siteId: string, options?: { endpoint?: string }) => void\n track: (event: Record<string, unknown>) => void\n}\n\ndeclare global {\n interface Window {\n LynkowAnalytics?: LynkowAnalyticsGlobal\n }\n}\n\nconst TRACKER_SCRIPT_ID = 'lynkow-tracker'\n\n/**\n * Service for client-side analytics tracking via the Lynkow tracker.js script.\n *\n * Accessible via `lynkow.analytics`. This is a **browser-only** service -- all methods\n * are no-ops when called on the server (SSR/Node.js). The service lazily loads the\n * `tracker.js` script from the API, which auto-initializes using the site ID. The\n * tracker automatically captures pageviews; use `trackPageview()` for manual SPA\n * navigation tracking and `trackEvent()` for custom events.\n *\n * The service respects the site's consent mode: if consent mode is `'opt-in'`,\n * tracking only starts after consent is granted. The consent state is read from\n * `localStorage` (`_lkw_consent_mode` key).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Initialize analytics (loads tracker.js)\n * await lynkow.analytics.init()\n *\n * // Track a custom event\n * await lynkow.analytics.trackEvent({ type: 'purchase', amount: 49.99 })\n *\n * // Manually track SPA navigation\n * await lynkow.analytics.trackPageview({ path: '/new-page' })\n * ```\n */\nexport class AnalyticsService {\n private config: InternalConfig\n private enabled = true\n private initialized = false\n private loading = false\n private loadPromise: Promise<void> | null = null\n\n constructor(config: InternalConfig) {\n this.config = config\n }\n\n /**\n * Get the tracker script URL\n */\n private getTrackerUrl(): string {\n return `${this.config.baseUrl}/analytics/tracker.js`\n }\n\n /**\n * Load the tracker.js script\n */\n private loadTracker(): Promise<void> {\n if (!isBrowser) return Promise.resolve()\n\n // Already loaded\n if (window.LynkowAnalytics) {\n return Promise.resolve()\n }\n\n // Already loading\n if (this.loadPromise) {\n return this.loadPromise\n }\n\n this.loading = true\n this.loadPromise = new Promise((resolve, reject) => {\n // Check if script tag already exists\n if (document.getElementById(TRACKER_SCRIPT_ID)) {\n // Wait for it to load\n const checkLoaded = setInterval(() => {\n if (window.LynkowAnalytics) {\n clearInterval(checkLoaded)\n this.loading = false\n resolve()\n }\n }, 50)\n\n // Timeout after 10 seconds\n setTimeout(() => {\n clearInterval(checkLoaded)\n this.loading = false\n reject(new Error('Tracker script load timeout'))\n }, 10000)\n\n return\n }\n\n // Create and inject script\n const script = document.createElement('script')\n script.id = TRACKER_SCRIPT_ID\n script.src = this.getTrackerUrl()\n script.async = true\n script.setAttribute('data-site-id', this.config.siteId)\n if (this.config.baseUrl) {\n script.setAttribute('data-api-url', this.config.baseUrl)\n }\n\n // Pass consent mode to tracker if available in localStorage\n try {\n const storedMode = localStorage.getItem('_lkw_consent_mode')\n if (storedMode) {\n script.setAttribute('data-consent-mode', storedMode)\n }\n } catch {}\n\n script.onload = () => {\n this.loading = false\n // Wait a tick for the tracker to auto-initialize\n setTimeout(() => {\n if (window.LynkowAnalytics) {\n resolve()\n } else {\n reject(new Error('Tracker script loaded but LynkowAnalytics not found'))\n }\n }, 0)\n }\n\n script.onerror = () => {\n this.loading = false\n reject(new Error('Failed to load tracker script'))\n }\n\n document.head.appendChild(script)\n })\n\n return this.loadPromise\n }\n\n /**\n * Initializes analytics tracking by loading the tracker.js script into the\n * page and waiting for it to auto-initialize. This is idempotent -- calling\n * it multiple times has no effect after the first successful initialization.\n * No-op on server.\n *\n * The tracker script is loaded from `{baseUrl}/analytics/tracker.js` with\n * `data-site-id` and `data-api-url` attributes. If a consent mode is stored\n * in `localStorage`, it is passed via `data-consent-mode`.\n *\n * @returns Resolves when the tracker is loaded and ready, or immediately if\n * already initialized or running on the server\n * @throws Logs an error to console if the tracker script fails to load\n * (does not reject the promise)\n *\n * @example\n * ```typescript\n * // Manually initialize analytics in an SPA after client-side routing is ready\n * const lynkow = createClient({ siteId: '...' })\n * await lynkow.analytics.init()\n * ```\n */\n async init(): Promise<void> {\n if (!isBrowser || this.initialized) return\n\n try {\n await this.loadTracker()\n\n // The tracker auto-initializes via data-site-id attribute\n // But we can also manually init if needed\n if (window.LynkowAnalytics && !this.initialized) {\n // Tracker already auto-initialized via data-site-id\n this.initialized = true\n }\n } catch (error) {\n console.error('[Lynkow] Failed to initialize analytics:', error)\n }\n }\n\n /**\n * Tracks a custom event via the Lynkow analytics tracker. Automatically\n * initializes the tracker if not already loaded. No-op on server or when\n * tracking is disabled.\n *\n * @param event - Event data object. Must include a `type` string identifier\n * (e.g. `'purchase'`, `'signup'`, `'click'`). Additional properties are\n * passed through as custom event data (any key-value pairs).\n * @returns Resolves when the event has been queued for delivery\n *\n * @example\n * ```typescript\n * await lynkow.analytics.trackEvent({\n * type: 'purchase',\n * productId: 'abc123',\n * amount: 99.99\n * })\n * ```\n */\n async trackEvent(event: EventData): Promise<void> {\n if (!isBrowser || !this.enabled) return\n\n await this.init()\n\n if (window.LynkowAnalytics) {\n window.LynkowAnalytics.track(event)\n }\n }\n\n /**\n * Manually tracks a pageview event. The tracker automatically captures pageviews\n * on initial page load, so this method is only needed for SPA client-side\n * navigation (e.g. Next.js App Router route changes). Automatically initializes\n * the tracker if not already loaded. No-op on server or when tracking is disabled.\n *\n * @param data - Optional pageview data:\n * - `path` — page path (defaults to `window.location.pathname`)\n * - `title` — page title (defaults to `document.title`)\n * - `referrer` — referrer URL (defaults to `document.referrer`)\n * @returns Resolves when the pageview has been queued for delivery\n *\n * @example\n * ```typescript\n * // After SPA client-side navigation\n * await lynkow.analytics.trackPageview({ path: '/new-page' })\n *\n * // Or let it auto-detect from the current URL\n * await lynkow.analytics.trackPageview()\n * ```\n */\n async trackPageview(data?: PageviewData): Promise<void> {\n if (!isBrowser || !this.enabled) return\n\n await this.init()\n\n if (window.LynkowAnalytics) {\n window.LynkowAnalytics.track({\n type: 'pageview',\n path: data?.path || window.location.pathname,\n title: data?.title || document.title,\n referrer: data?.referrer || document.referrer,\n })\n }\n }\n\n /**\n * Enables analytics tracking. Tracking is enabled by default when the\n * service is created. Call this to re-enable after a previous `disable()` call.\n *\n * @example\n * ```typescript\n * // Re-enable tracking after the user grants analytics consent\n * lynkow.on('consent-changed', (categories) => {\n * if (categories.analytics) {\n * lynkow.analytics.enable()\n * } else {\n * lynkow.analytics.disable()\n * }\n * })\n * ```\n *\n * @returns void\n * @throws Never throws.\n */\n enable(): void {\n this.enabled = true\n }\n\n /**\n * Disables analytics tracking. While disabled, all `trackEvent()` and\n * `trackPageview()` calls become no-ops. The tracker script remains loaded;\n * call `destroy()` to fully remove it.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // Disable tracking when the user revokes analytics consent\n * lynkow.analytics.disable()\n * ```\n */\n disable(): void {\n this.enabled = false\n }\n\n /**\n * Returns whether analytics tracking is currently enabled (i.e. whether\n * `trackEvent` and `trackPageview` will actually emit). Does not\n * indicate whether the underlying tracker script has loaded; use\n * {@link isInitialized} for that.\n *\n * @returns `true` if tracking is enabled (default), `false` after\n * {@link disable} has been called.\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * if (lynkow.analytics.isEnabled()) {\n * // Safe to track\n * }\n * ```\n */\n isEnabled(): boolean {\n return this.enabled\n }\n\n /**\n * Returns whether the tracker.js script has been loaded and the\n * `window.LynkowAnalytics` global is available. Always `false` on the\n * server.\n *\n * @returns `true` if the tracker is fully loaded and ready to accept\n * events, `false` otherwise.\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * if (lynkow.analytics.isInitialized()) {\n * lynkow.analytics.trackEvent({ type: 'cta_click' })\n * }\n * ```\n */\n isInitialized(): boolean {\n return this.initialized && !!window.LynkowAnalytics\n }\n\n /**\n * Return the underlying `window.LynkowAnalytics` global for advanced\n * use cases that need direct access to the tracker's lower-level API\n * (e.g. calling `init` again with different options, or invoking\n * undocumented tracker internals).\n *\n * @returns The `LynkowAnalyticsGlobal` with `init()` and `track()`\n * methods, or `undefined` if running on the server or the tracker\n * script has not loaded yet.\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * const tracker = lynkow.analytics.getTracker()\n * tracker?.track({ custom: 'payload' })\n * ```\n */\n getTracker(): LynkowAnalyticsGlobal | undefined {\n if (!isBrowser) return undefined\n return window.LynkowAnalytics\n }\n\n /**\n * Removes the tracker.js script element from the DOM and resets the service\n * state. After calling `destroy()`, you can re-initialize by calling `init()`\n * again. No-op on server.\n *\n * @example\n * ```typescript\n * // Clean up analytics when unmounting (e.g. React useEffect cleanup)\n * useEffect(() => {\n * lynkow.analytics.init()\n * return () => lynkow.analytics.destroy()\n * }, [])\n * ```\n */\n destroy(): void {\n if (!isBrowser) return\n\n const script = document.getElementById(TRACKER_SCRIPT_ID)\n script?.remove()\n\n this.initialized = false\n this.loadPromise = null\n }\n}\n","/**\n * Site theme detection utility\n *\n * Detects the website's actual theme (dark/light) by inspecting DOM indicators,\n * rather than relying on the OS-level prefers-color-scheme media query.\n */\n\nimport { isBrowser } from '../core/environment'\n\n/**\n * Parse the luminance from a CSS background-color value.\n * Returns a number between 0 (black) and 1 (white), or null if unparseable/transparent.\n */\nfunction parseBackgroundLuminance(bgColor: string): number | null {\n // Match rgb(r, g, b) or rgba(r, g, b, a)\n const match = bgColor.match(\n /rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)(?:\\s*,\\s*([\\d.]+))?\\s*\\)/\n )\n if (!match) return null\n\n const alpha = match[4] !== undefined ? parseFloat(match[4]) : 1\n if (alpha === 0) return null // Fully transparent — can't determine theme\n\n const r = parseInt(match[1]!, 10)\n const g = parseInt(match[2]!, 10)\n const b = parseInt(match[3]!, 10)\n\n // Perceived luminance (ITU-R BT.601)\n return (0.299 * r + 0.587 * g + 0.114 * b) / 255\n}\n\n/**\n * Detect the website's theme by inspecting DOM indicators.\n *\n * Detection cascade:\n * 1. data-theme / data-mode / data-color-scheme attributes on <html> or <body>\n * 2. \"dark\" class on <html> or <body> (Tailwind convention)\n * 3. CSS color-scheme property on <html>\n * 4. Background color luminance of <body>\n * 5. Fallback: OS-level prefers-color-scheme media query\n *\n * @returns 'dark' or 'light'\n *\n * @example\n * ```typescript\n * // Apply a conditional class based on the site's current theme\n * const theme = detectSiteTheme()\n * document.body.classList.add(theme === 'dark' ? 'inverted-text' : 'default-text')\n * ```\n */\nexport function detectSiteTheme(): 'dark' | 'light' {\n if (!isBrowser) return 'light'\n\n const html = document.documentElement\n const body = document.body\n\n // 1. Check data attributes on <html> and <body>\n for (const el of [html, body]) {\n for (const attr of ['data-theme', 'data-mode', 'data-color-scheme']) {\n const value = el.getAttribute(attr)?.toLowerCase()\n if (value) {\n if (value.includes('dark')) return 'dark'\n if (value.includes('light')) return 'light'\n }\n }\n }\n\n // 2. Check for \"dark\" class (Tailwind / common convention)\n if (html.classList.contains('dark') || body.classList.contains('dark')) {\n return 'dark'\n }\n\n // 3. Check CSS color-scheme property\n try {\n const colorScheme = getComputedStyle(html).colorScheme\n if (colorScheme) {\n const normalized = colorScheme.toLowerCase().trim()\n if (normalized.startsWith('dark')) return 'dark'\n if (normalized.startsWith('light')) return 'light'\n }\n } catch {\n // getComputedStyle may fail in some environments\n }\n\n // 4. Check background color luminance\n try {\n const bgColor = getComputedStyle(body).backgroundColor\n const luminance = parseBackgroundLuminance(bgColor)\n if (luminance !== null) {\n return luminance < 0.5 ? 'dark' : 'light'\n }\n } catch {\n // getComputedStyle may fail in some environments\n }\n\n // 5. Fallback: OS-level preference\n try {\n if (window.matchMedia('(prefers-color-scheme: dark)').matches) {\n return 'dark'\n }\n } catch {\n // matchMedia may not be available\n }\n\n return 'light'\n}\n\n/**\n * Observe site theme changes in real-time.\n *\n * Watches for:\n * - Attribute changes on <html> and <body> (data-theme, data-mode, class, style)\n * - OS-level prefers-color-scheme media query changes\n *\n * @param callback Called with the new theme when a change is detected\n * @returns Cleanup function to stop observing\n *\n * @example\n * ```typescript\n * // Update a widget's appearance when the site theme changes\n * const stopObserving = onSiteThemeChange((theme) => {\n * widget.setTheme(theme)\n * })\n *\n * // Stop observing when no longer needed\n * stopObserving()\n * ```\n */\nexport function onSiteThemeChange(\n callback: (theme: 'dark' | 'light') => void\n): () => void {\n if (!isBrowser) return () => {}\n\n let currentTheme = detectSiteTheme()\n const cleanups: (() => void)[] = []\n\n const checkTheme = () => {\n const newTheme = detectSiteTheme()\n if (newTheme !== currentTheme) {\n currentTheme = newTheme\n callback(newTheme)\n }\n }\n\n // MutationObserver on <html> and <body> for attribute/class changes\n const observer = new MutationObserver(checkTheme)\n const observeOptions: MutationObserverInit = {\n attributes: true,\n attributeFilter: ['data-theme', 'data-mode', 'data-color-scheme', 'class', 'style'],\n }\n observer.observe(document.documentElement, observeOptions)\n observer.observe(document.body, observeOptions)\n cleanups.push(() => observer.disconnect())\n\n // matchMedia listener for OS-level changes\n try {\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n const handler = () => checkTheme()\n mq.addEventListener('change', handler)\n cleanups.push(() => mq.removeEventListener('change', handler))\n } catch {\n // matchMedia not available\n }\n\n return () => cleanups.forEach((fn) => fn())\n}\n","/**\n * Consent module for Lynkow SDK\n *\n * Hybrid module: API everywhere + UI browser-only\n */\n\nimport { isBrowser } from '../core/environment'\nimport { detectSiteTheme, onSiteThemeChange } from '../utils/theme'\nimport type { InternalConfig } from './base'\nimport type { CookieConfig, CookiePreferences, ThirdPartyScript } from '../types'\nimport type { EventEmitter } from '../utils/events'\n\n// Use same key as tracker.js for consistency\nconst STORAGE_KEY = '_lkw_consent'\n\n// Consent expires after 365 days\nconst CONSENT_EXPIRY_MS = 365 * 24 * 60 * 60 * 1000\n\n// Prefix for injected script element IDs\nconst SCRIPT_ID_PREFIX = 'lkw-script-'\n\n/**\n * User consent state for each cookie category Lynkow manages. Used as\n * input to {@link ConsentService.setCategories} and emitted as the payload\n * of the `'consent-changed'` client event.\n *\n * A category set to `false` means the corresponding scripts (from the\n * site's cookie configuration) MUST NOT be loaded; a category set to\n * `true` authorises them.\n */\nexport interface ConsentCategories {\n /**\n * Strictly necessary cookies (session, CSRF). Always `true`: users\n * cannot opt out because the site would be non-functional without them.\n * Included in the type so consent payloads remain uniform.\n */\n necessary: boolean\n /**\n * Consent to first-party and third-party analytics (e.g. the Lynkow\n * tracker, Google Analytics). When `false`, the analytics service\n * automatically becomes a no-op.\n */\n analytics: boolean\n /**\n * Consent to marketing and advertising cookies (ad networks,\n * retargeting). Required before loading scripts classified as\n * `marketing` in the site's cookie config.\n */\n marketing: boolean\n /**\n * Consent to personalization and preference cookies (theme, language,\n * non-essential UI state). Required before loading scripts classified\n * as `preferences` in the site's cookie config.\n */\n preferences: boolean\n}\n\n/**\n * Default consent categories (no consent given)\n */\nconst DEFAULT_CATEGORIES: ConsentCategories = {\n necessary: true,\n analytics: false,\n marketing: false,\n preferences: false,\n}\n\n/**\n * High-level consent management service with built-in banner UI and preferences modal.\n *\n * Accessible via `lynkow.consent`. This is a hybrid service:\n * - **API methods** (`getConfig`, `logConsent`) work everywhere (server + browser)\n * - **UI methods** (`show`, `hide`, `acceptAll`, `rejectAll`, `showPreferences`, etc.)\n * only work in the browser and are no-ops on the server\n *\n * Consent choices are persisted in `localStorage` under the `_lkw_consent` key\n * (compatible with tracker.js) and expire after 365 days. When consent is granted\n * for a category, any third-party scripts configured for that category are\n * automatically injected into the page.\n *\n * Emits a `'consent-changed'` event (via the SDK event emitter) and a\n * `'lynkow:consent:update'` CustomEvent on `document` whenever consent changes.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Show the consent banner (auto-skips if already consented)\n * lynkow.consent.show()\n *\n * // Check current consent\n * const categories = lynkow.consent.getCategories()\n * console.log(categories.analytics) // true or false\n *\n * // Programmatically accept all\n * lynkow.consent.acceptAll()\n * ```\n */\nexport class ConsentService {\n private config: InternalConfig\n private events: EventEmitter\n private bannerElement: HTMLElement | null = null\n private preferencesElement: HTMLElement | null = null\n private configCache: CookieConfig | null = null\n private injectedScriptIds = new Set<string>()\n private themeCleanup: (() => void) | null = null\n\n constructor(config: InternalConfig, events: EventEmitter) {\n this.config = config\n this.events = events\n }\n\n // === API Methods (work everywhere) ===\n\n /**\n * Fetches the cookie consent configuration from the API. The result is\n * cached in memory for the lifetime of the service instance (not TTL-based).\n * Works on both server and browser.\n *\n * @returns A `CookieConfig` object with banner settings, categories, texts,\n * theming options, and third-party script definitions.\n * @throws {Error} If the API request fails (non-2xx response). The SDK\n * does not wrap this in a {@link LynkowError} for this endpoint,\n * since consent config is fetched via a raw `fetch` call rather than\n * the shared request pipeline.\n *\n * @example\n * ```typescript\n * const config = await lynkow.consent.getConfig()\n * if (config.enabled) {\n * console.log('Categories:', config.categories.map((c) => c.id))\n * }\n * ```\n */\n async getConfig(): Promise<CookieConfig> {\n if (this.configCache) return this.configCache\n\n const url = `${this.config.baseUrl}/public/${this.config.siteId}/cookie-consent/config`\n const response = await fetch(url, {\n method: 'GET',\n headers: { 'Content-Type': 'application/json' },\n ...this.config.fetchOptions,\n })\n\n if (!response.ok) {\n throw new Error(`Failed to fetch consent config: ${response.status}`)\n }\n\n const data = await response.json()\n this.configCache = data.data as CookieConfig\n return this.configCache\n }\n\n /**\n * Logs the user's consent preferences to the server for GDPR audit trail.\n * Automatically generates or retrieves a persistent visitor ID from `localStorage`.\n * Errors are silently swallowed to avoid disrupting the user experience.\n * Works on both server and browser.\n *\n * @param preferences - Consent preferences as a record of category IDs to booleans\n * (e.g. `{ necessary: true, analytics: true, marketing: false }`)\n * @param action - Optional explicit action type. If omitted, the action is inferred\n * from the preferences (all true = `'accept_all'`, all false = `'reject_all'`,\n * mixed = `'customize'`). `'withdraw'` must be passed explicitly.\n * @returns A promise that resolves once the POST has completed or\n * been suppressed on error. Never rejects.\n * @throws Never throws. Network or server errors are caught and silently\n * discarded so the consent UI flow is never interrupted.\n *\n * @example\n * ```typescript\n * await lynkow.consent.logConsent(\n * { necessary: true, analytics: true, marketing: false },\n * 'customize'\n * )\n * ```\n */\n async logConsent(\n preferences: CookiePreferences,\n action?: 'accept_all' | 'reject_all' | 'customize' | 'withdraw'\n ): Promise<void> {\n const url = `${this.config.baseUrl}/public/${this.config.siteId}/cookie-consent/log`\n\n await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n visitorId: this.getOrCreateVisitorId(),\n action: action || this.inferAction(preferences),\n consentGiven: preferences,\n pageUrl: isBrowser ? window.location.href : undefined,\n }),\n ...this.config.fetchOptions,\n }).catch(() => {\n // Silently ignore errors\n })\n }\n\n // === Private Helpers ===\n\n private getOrCreateVisitorId(): string {\n if (!isBrowser) return 'server'\n\n const key = '_lkw_vid'\n try {\n let vid = localStorage.getItem(key)\n if (!vid) {\n vid = crypto.randomUUID()\n localStorage.setItem(key, vid)\n }\n return vid\n } catch {\n return crypto.randomUUID()\n }\n }\n\n private inferAction(\n preferences: CookiePreferences\n ): 'accept_all' | 'reject_all' | 'customize' {\n const values = Object.entries(preferences).filter(([k]) => k !== 'necessary')\n const allTrue = values.every(([, v]) => v === true)\n const allFalse = values.every(([, v]) => v === false)\n if (allTrue) return 'accept_all'\n if (allFalse) return 'reject_all'\n return 'customize'\n }\n\n // === Storage Methods ===\n // Format: { choices: { necessary, analytics, marketing, preferences } }\n // This format is compatible with tracker.js\n\n private getStoredConsent(): ConsentCategories | null {\n if (!isBrowser) return null\n\n try {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) {\n const data = JSON.parse(stored)\n // Handle both old format and new { choices: {...} } format\n if (data.choices) {\n // Check expiry if timestamp exists (old entries without timestamp are kept)\n if (data.timestamp && Date.now() - data.timestamp > CONSENT_EXPIRY_MS) {\n localStorage.removeItem(STORAGE_KEY)\n return null\n }\n return data.choices as ConsentCategories\n }\n return data as ConsentCategories\n }\n } catch {\n // localStorage unavailable\n }\n\n return null\n }\n\n private saveConsent(categories: ConsentCategories): void {\n if (!isBrowser) return\n\n try {\n // Save in { choices: {...}, timestamp } format for tracker.js compatibility + expiry\n localStorage.setItem(STORAGE_KEY, JSON.stringify({ choices: categories, timestamp: Date.now() }))\n } catch {\n // localStorage unavailable\n }\n\n this.events.emit('consent-changed', categories as unknown as Record<string, boolean>)\n\n // Dispatch custom event for tracker.js\n document.dispatchEvent(new CustomEvent('lynkow:consent:update', {\n detail: categories\n }))\n }\n\n // === UI Methods (browser-only) ===\n\n /**\n * Shows the consent banner in the browser. If the user has already consented\n * (choices stored in `localStorage`), the banner is skipped and accepted\n * third-party scripts are injected directly instead. Respects the site's\n * theme (light/dark/auto) and position settings. No-op on server.\n *\n * When theme is `'auto'`, a MutationObserver watches for site theme changes\n * and updates the banner colors in real-time.\n *\n * @returns void\n * @throws Never throws. Config-fetch rejections are caught internally.\n *\n * @example\n * ```typescript\n * // Show the consent banner on page load (skips if already consented)\n * const lynkow = createClient({ siteId: '...' })\n * lynkow.consent.show()\n * ```\n */\n show(): void {\n if (!isBrowser) return\n\n this.getConfig().then((config) => {\n if (!config.enabled) return\n\n // Store consent mode in localStorage for tracker.js to read\n try {\n if ((config as any).consentMode) {\n localStorage.setItem('_lkw_consent_mode', (config as any).consentMode)\n }\n } catch {}\n\n // If consent already stored, just inject scripts\n const storedConsent = this.getStoredConsent()\n if (storedConsent) {\n this.activateScripts(storedConsent)\n return\n }\n\n if (this.bannerElement) return\n\n this.injectBannerStylesOnce()\n const wrapper = document.createElement('div')\n wrapper.innerHTML = this.createBannerHTML(config)\n this.bannerElement = wrapper.firstElementChild as HTMLElement\n document.body.appendChild(this.bannerElement)\n this.attachBannerEvents()\n\n // Observe theme changes when theme is 'auto'\n if (config.theme === 'auto' && !this.themeCleanup) {\n this.themeCleanup = onSiteThemeChange((theme) => {\n this.updateConsentTheme(theme)\n })\n }\n })\n }\n\n /**\n * Hide and remove the consent banner from the DOM. Does not affect\n * stored consent preferences (the user will not be re-prompted on the\n * next page load if they already chose). No-op on server or if the\n * banner is not currently shown.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * // Close the banner after the user clicked \"Accept all\" via custom UI:\n * lynkow.consent.hide()\n * ```\n */\n hide(): void {\n if (!isBrowser) return\n\n this.bannerElement?.remove()\n this.bannerElement = null\n this.cleanupThemeObserverIfIdle()\n }\n\n /**\n * Open the preferences modal so the user can toggle individual consent\n * categories (analytics, marketing, preferences). The `necessary`\n * category is always checked and disabled. Pre-populates checkboxes\n * with the user's current consent state. No-op on server or if the\n * modal is already mounted.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```tsx\n * <button onClick={() => lynkow.consent.showPreferences()}>\n * Cookie settings\n * </button>\n * ```\n */\n showPreferences(): void {\n if (!isBrowser) return\n if (this.preferencesElement) return\n\n this.getConfig().then((config) => {\n const categories = this.getCategories()\n\n const wrapper = document.createElement('div')\n wrapper.innerHTML = this.createPreferencesHTML(config, categories)\n this.preferencesElement = wrapper.firstElementChild as HTMLElement\n document.body.appendChild(this.preferencesElement)\n this.attachPreferencesEvents(config)\n\n // Observe theme changes when theme is 'auto'\n if (config.theme === 'auto' && !this.themeCleanup) {\n this.themeCleanup = onSiteThemeChange((theme) => {\n this.updateConsentTheme(theme)\n })\n }\n })\n }\n\n /**\n * Returns the user's current consent categories. If no consent has been\n * stored yet, returns default values (only `necessary: true`, all others `false`).\n * On the server, always returns defaults.\n *\n * @returns A `ConsentCategories` object with boolean values for `necessary`,\n * `analytics`, `marketing`, and `preferences`\n *\n * @example\n * ```typescript\n * // Read current preferences to conditionally load a third-party widget\n * const prefs = lynkow.consent.getCategories()\n * if (prefs.marketing) {\n * loadChatWidget()\n * }\n * ```\n */\n getCategories(): ConsentCategories {\n if (!isBrowser) return { ...DEFAULT_CATEGORIES }\n return this.getStoredConsent() || { ...DEFAULT_CATEGORIES }\n }\n\n /**\n * Checks whether the user has already made a consent choice (accepted,\n * rejected, or customized). Returns `false` if no consent is stored or\n * if the stored consent has expired (after 365 days).\n *\n * @returns `true` if consent choices exist in `localStorage`, `false` otherwise.\n * Always returns `false` on the server.\n *\n * @example\n * ```typescript\n * // Only show the consent banner if the user hasn't already decided\n * if (!lynkow.consent.hasConsented()) {\n * lynkow.consent.show()\n * }\n * ```\n */\n hasConsented(): boolean {\n if (!isBrowser) return false\n return this.getStoredConsent() !== null\n }\n\n /**\n * Accepts all cookie categories (necessary, analytics, marketing, preferences),\n * saves the choice to `localStorage`, logs consent to the server, injects all\n * accepted third-party scripts, and hides the banner. No-op on server.\n *\n * @example\n * ```typescript\n * // Wire up a one-click \"Accept All\" button in your own UI\n * document.getElementById('accept-cookies').addEventListener('click', () => {\n * lynkow.consent.acceptAll()\n * })\n * ```\n */\n acceptAll(): void {\n if (!isBrowser) return\n\n const categories: ConsentCategories = {\n necessary: true,\n analytics: true,\n marketing: true,\n preferences: true,\n }\n this.saveConsent(categories)\n this.logConsent(categories as unknown as CookiePreferences, 'accept_all')\n this.activateScripts(categories)\n this.hide()\n }\n\n /**\n * Rejects all optional cookie categories (analytics, marketing, preferences).\n * Only `necessary` remains true (cannot be disabled). Saves the choice to\n * `localStorage`, logs consent to the server, and hides the banner.\n * No third-party scripts are injected. No-op on server.\n *\n * @example\n * ```typescript\n * // Reject all non-essential cookies from a custom banner\n * document.getElementById('reject-cookies').addEventListener('click', () => {\n * lynkow.consent.rejectAll()\n * })\n * ```\n */\n rejectAll(): void {\n if (!isBrowser) return\n\n const categories: ConsentCategories = {\n necessary: true,\n analytics: false,\n marketing: false,\n preferences: false,\n }\n this.saveConsent(categories)\n this.logConsent(categories as unknown as CookiePreferences, 'reject_all')\n this.hide()\n }\n\n /**\n * Sets specific consent categories, merging with the current state.\n * The `necessary` category is always forced to `true` regardless of input.\n * Saves the choice, logs to server, and injects scripts for accepted categories.\n * No-op on server.\n *\n * @param categories - Partial consent categories to update. Unspecified categories\n * retain their current value. Example: `{ analytics: true, marketing: false }`\n *\n * @example\n * ```typescript\n * // Save custom preferences from a preferences form\n * lynkow.consent.setCategories({\n * analytics: true,\n * marketing: false,\n * preferences: true,\n * })\n * ```\n */\n setCategories(categories: Partial<ConsentCategories>): void {\n if (!isBrowser) return\n\n const current = this.getCategories()\n const newCategories: ConsentCategories = {\n ...current,\n ...categories,\n necessary: true, // Always required\n }\n this.saveConsent(newCategories)\n this.logConsent(newCategories as unknown as CookiePreferences, 'customize')\n this.activateScripts(newCategories)\n }\n\n /**\n * Resets all consent choices by clearing `localStorage`, removing any\n * previously injected third-party scripts, and re-showing the consent\n * banner. Useful for providing a \"manage cookies\" link that lets users\n * change their preferences. No-op on server.\n *\n * @returns void\n * @throws Never throws. localStorage errors are silently ignored.\n *\n * @example\n * ```typescript\n * // Add a \"Manage cookies\" link in the footer to let users re-choose\n * document.getElementById('manage-cookies').addEventListener('click', () => {\n * lynkow.consent.reset()\n * })\n * ```\n */\n reset(): void {\n if (!isBrowser) return\n\n this.removeInjectedScripts()\n\n try {\n localStorage.removeItem(STORAGE_KEY)\n } catch {\n // Silently ignore\n }\n\n this.events.emit('consent-changed', { ...DEFAULT_CATEGORIES } as unknown as Record<string, boolean>)\n this.show()\n }\n\n // === Script Injection ===\n\n private activateScripts(categories: ConsentCategories): void {\n if (!this.configCache?.thirdPartyScripts?.length) return\n\n for (const [category, accepted] of Object.entries(categories)) {\n if (accepted) {\n this.injectScriptsForCategory(category, this.configCache.thirdPartyScripts)\n }\n }\n }\n\n private injectScriptsForCategory(category: string, scripts: ThirdPartyScript[]): void {\n for (const script of scripts) {\n if (script.category !== category) continue\n if (this.injectedScriptIds.has(script.id)) continue\n\n this.injectedScriptIds.add(script.id)\n\n const wrapper = document.createElement('div')\n wrapper.innerHTML = script.script\n const elements = Array.from(wrapper.children)\n\n for (let i = 0; i < elements.length; i++) {\n const el = elements[i]!\n const elId = `${SCRIPT_ID_PREFIX}${script.id}-${i}`\n\n if (el.tagName === 'SCRIPT') {\n const newScript = document.createElement('script')\n // Copy all attributes from source element\n for (const attr of Array.from(el.attributes)) {\n newScript.setAttribute(attr.name, attr.value)\n }\n if ((el as HTMLScriptElement).textContent) {\n newScript.textContent = (el as HTMLScriptElement).textContent\n }\n newScript.id = elId\n document.head.appendChild(newScript)\n } else {\n (el as HTMLElement).id = elId\n document.body.appendChild(el)\n }\n }\n }\n }\n\n private removeInjectedScripts(): void {\n for (const scriptId of this.injectedScriptIds) {\n let i = 0\n let el: Element | null\n while ((el = document.getElementById(`${SCRIPT_ID_PREFIX}${scriptId}-${i}`))) {\n el.remove()\n i++\n }\n }\n this.injectedScriptIds.clear()\n }\n\n // === Private UI Helpers ===\n\n /**\n * Stop the theme observer if no consent UI is visible.\n */\n private cleanupThemeObserverIfIdle(): void {\n if (!this.bannerElement && !this.preferencesElement) {\n this.themeCleanup?.()\n this.themeCleanup = null\n }\n }\n\n /**\n * Update visible consent UI elements when the site theme changes.\n */\n private updateConsentTheme(theme: 'dark' | 'light'): void {\n const colors = this.configCache\n ? this.resolveColors(this.configCache, theme)\n : { bgColor: theme === 'dark' ? '#18181b' : '#ffffff', textColor: theme === 'dark' ? '#f4f4f5' : '#1a1a1a' }\n const { bgColor, textColor } = colors\n\n // Update banner\n if (this.bannerElement) {\n this.bannerElement.style.background = bgColor\n this.bannerElement.style.color = textColor\n }\n\n // Update preferences modal inner container\n if (this.preferencesElement) {\n const innerDiv = this.preferencesElement.querySelector(':scope > div') as HTMLElement\n if (innerDiv) {\n innerDiv.style.background = bgColor\n innerDiv.style.color = textColor\n }\n }\n }\n\n private resolveTheme(theme: string): 'light' | 'dark' {\n if (theme === 'auto') return detectSiteTheme()\n return theme === 'dark' ? 'dark' : 'light'\n }\n\n private resolveColors(config: CookieConfig, resolvedTheme: 'light' | 'dark') {\n if (config.themeStyles?.[resolvedTheme]) {\n return config.themeStyles[resolvedTheme]\n }\n const isDark = resolvedTheme === 'dark'\n return {\n primaryColor: config.primaryColor || '#0066cc',\n bgColor: isDark ? '#18181b' : '#ffffff',\n textColor: isDark ? '#f4f4f5' : '#1a1a1a',\n }\n }\n\n private contrastColor(hex: string): string {\n const r = parseInt(hex.slice(1, 3), 16)\n const g = parseInt(hex.slice(3, 5), 16)\n const b = parseInt(hex.slice(5, 7), 16)\n return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5 ? '#000000' : '#ffffff'\n }\n\n private injectBannerStylesOnce(): void {\n if (document.getElementById('lynkow-consent-styles')) return\n const style = document.createElement('style')\n style.id = 'lynkow-consent-styles'\n style.textContent = `\n #lynkow-consent-banner {\n box-sizing: border-box;\n max-height: calc(100vh - 40px);\n max-height: calc(100dvh - 40px);\n overflow-y: auto;\n }\n @media (max-width: 600px) {\n #lynkow-consent-banner {\n left: 10px !important;\n right: 10px !important;\n max-width: none !important;\n max-height: calc(100vh - 20px);\n max-height: calc(100dvh - 20px);\n }\n #lynkow-consent-banner .lynkow-consent-row {\n flex-direction: column;\n align-items: stretch;\n }\n #lynkow-consent-banner .lynkow-consent-actions {\n flex-direction: column;\n align-items: stretch;\n }\n #lynkow-consent-banner .lynkow-consent-actions > button {\n width: 100%;\n text-align: center;\n }\n }\n #lynkow-consent-banner.orientation-column .lynkow-consent-row {\n flex-direction: column;\n align-items: stretch;\n }\n #lynkow-consent-banner.orientation-column .lynkow-consent-actions {\n flex-direction: column;\n align-items: stretch;\n }\n #lynkow-consent-banner.orientation-column .lynkow-consent-actions > button {\n width: 100%;\n text-align: center;\n }\n `\n document.head.appendChild(style)\n }\n\n private createBannerHTML(config: CookieConfig): string {\n const position = config.position || 'bottom-right'\n const theme = config.theme || 'light'\n const borderRadius = config.borderRadius ?? 8\n const fontSize = config.fontSize ?? 13\n const orientation = config.orientation || 'auto'\n\n // Floating position styles\n const floatingStyles: Record<string, string> = {\n 'bottom-left': `bottom: 20px; left: 20px; max-width: 580px; border-radius: ${borderRadius}px;`,\n 'bottom-right': `bottom: 20px; right: 20px; max-width: 580px; border-radius: ${borderRadius}px;`,\n }\n\n const positionStyle = floatingStyles[position] || floatingStyles['bottom-right']\n\n const resolvedTheme = this.resolveTheme(theme)\n const colors = this.resolveColors(config, resolvedTheme)\n const { primaryColor, bgColor, textColor } = colors\n\n const texts = config.texts || {\n description:\n 'Ce site utilise des cookies pour ameliorer votre experience.',\n acceptAll: 'Accepter tout',\n rejectAll: 'Refuser',\n customize: 'Personnaliser',\n save: 'Enregistrer',\n }\n\n const bannerClass = orientation === 'column' ? ' class=\"orientation-column\"' : ''\n\n return `\n <div id=\"lynkow-consent-banner\"${bannerClass} style=\"\n position: fixed;\n ${positionStyle}\n z-index: 99999;\n padding: 16px 20px;\n background: ${bgColor};\n color: ${textColor};\n box-shadow: 0 4px 20px rgba(0,0,0,0.15);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: ${fontSize}px;\n \">\n <div class=\"lynkow-consent-row\" style=\"display: flex; align-items: center; gap: 16px; flex-wrap: wrap;\">\n <p style=\"margin: 0; line-height: 1.5; flex: 1; min-width: 200px;\">\n ${texts.description}${(() => {\n const url = config.cookiePolicyUrl || config.privacyPolicyUrl\n if (!url) return ''\n const label = texts.privacyPolicy || 'En savoir plus'\n return ` <a href=\"${url}\" target=\"_blank\" rel=\"noopener\" style=\"text-decoration: underline; color: inherit;\">${label}</a>`\n })()}\n </p>\n <div class=\"lynkow-consent-actions\" style=\"display: flex; gap: 8px; align-items: center; flex-wrap: wrap;\">\n <button id=\"lynkow-consent-accept\" style=\"\n padding: 8px 16px;\n background: ${primaryColor};\n color: ${this.contrastColor(primaryColor)};\n border: none;\n border-radius: ${borderRadius}px;\n cursor: pointer;\n font-size: ${fontSize}px;\n white-space: nowrap;\n \">${texts.acceptAll}</button>\n <button id=\"lynkow-consent-reject\" style=\"\n padding: 8px 16px;\n background: transparent;\n color: inherit;\n border: 1px solid currentColor;\n border-radius: ${borderRadius}px;\n cursor: pointer;\n font-size: ${fontSize}px;\n white-space: nowrap;\n \">${texts.rejectAll}</button>\n ${config.showCustomizeButton !== false ? `<button id=\"lynkow-consent-preferences\" style=\"\n padding: 8px 4px;\n background: transparent;\n color: inherit;\n border: none;\n cursor: pointer;\n font-size: ${fontSize}px;\n text-decoration: underline;\n white-space: nowrap;\n \">${texts.customize}</button>` : ''}\n </div>\n </div>\n </div>\n `\n }\n\n private createPreferencesHTML(\n config: CookieConfig,\n currentCategories: ConsentCategories\n ): string {\n const theme = config.theme || 'light'\n const borderRadius = config.borderRadius ?? 8\n const fontSize = config.fontSize ?? 13\n const resolvedTheme = this.resolveTheme(theme)\n const colors = this.resolveColors(config, resolvedTheme)\n const { primaryColor, bgColor, textColor } = colors\n\n const texts = config.texts || {\n save: 'Enregistrer',\n acceptAll: 'Accepter tout',\n rejectAll: 'Refuser',\n customize: 'Personnaliser',\n description: '',\n }\n\n const categories = config.categories || []\n const categoriesHTML = categories\n .map(\n (cat) => `\n <label style=\"display: flex; align-items: flex-start; gap: 10px; margin: 15px 0; cursor: ${cat.required ? 'not-allowed' : 'pointer'};\">\n <input\n type=\"checkbox\"\n name=\"${cat.id}\"\n ${currentCategories[cat.id as keyof ConsentCategories] ? 'checked' : ''}\n ${cat.required ? 'disabled checked' : ''}\n style=\"width: 18px; height: 18px; margin-top: 2px; accent-color: ${primaryColor};\"\n />\n <div style=\"flex: 1;\">\n <strong style=\"opacity: ${cat.required ? '0.6' : '1'};\">\n ${cat.name}${cat.required ? ' (requis)' : ''}\n </strong>\n <p style=\"margin: 5px 0 0 0; font-size: 13px; opacity: 0.8;\">\n ${cat.description}\n </p>\n </div>\n </label>\n `\n )\n .join('')\n\n return `\n <div id=\"lynkow-consent-preferences-modal\" style=\"\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 100000;\n background: rgba(0,0,0,0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n \">\n <div style=\"\n background: ${bgColor};\n color: ${textColor};\n padding: 30px;\n border-radius: ${borderRadius}px;\n max-width: 500px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n \">\n <form id=\"lynkow-consent-form\">\n ${categoriesHTML}\n\n <div style=\"display: flex; gap: 10px; margin-top: 25px; padding-top: 20px; border-top: 1px solid rgba(128,128,128,0.3);\">\n <button type=\"submit\" style=\"\n padding: 10px 20px;\n background: ${primaryColor};\n color: ${this.contrastColor(primaryColor)};\n border: none;\n border-radius: ${borderRadius}px;\n cursor: pointer;\n font-size: ${fontSize}px;\n \">${texts.save || 'Enregistrer'}</button>\n <button type=\"button\" id=\"lynkow-consent-close\" style=\"\n padding: 10px 20px;\n background: transparent;\n color: inherit;\n border: 1px solid currentColor;\n border-radius: ${borderRadius}px;\n cursor: pointer;\n font-size: ${fontSize}px;\n \">Annuler</button>\n </div>\n </form>\n </div>\n </div>\n `\n }\n\n private attachBannerEvents(): void {\n const acceptBtn = document.getElementById('lynkow-consent-accept')\n const rejectBtn = document.getElementById('lynkow-consent-reject')\n const prefsBtn = document.getElementById('lynkow-consent-preferences')\n\n acceptBtn?.addEventListener('click', () => {\n this.acceptAll()\n })\n\n rejectBtn?.addEventListener('click', () => {\n this.rejectAll()\n })\n\n prefsBtn?.addEventListener('click', () => {\n this.showPreferences()\n })\n }\n\n private attachPreferencesEvents(_config: CookieConfig): void {\n const form = document.getElementById(\n 'lynkow-consent-form'\n ) as HTMLFormElement\n const closeBtn = document.getElementById('lynkow-consent-close')\n const modal = document.getElementById('lynkow-consent-preferences-modal')\n\n form?.addEventListener('submit', (e) => {\n e.preventDefault()\n const formData = new FormData(form)\n\n const newCategories: ConsentCategories = {\n necessary: true,\n analytics: formData.has('analytics'),\n marketing: formData.has('marketing'),\n preferences: formData.has('preferences'),\n }\n\n this.setCategories(newCategories)\n this.hide()\n\n this.preferencesElement?.remove()\n this.preferencesElement = null\n this.cleanupThemeObserverIfIdle()\n })\n\n closeBtn?.addEventListener('click', () => {\n this.preferencesElement?.remove()\n this.preferencesElement = null\n this.cleanupThemeObserverIfIdle()\n })\n\n modal?.addEventListener('click', (e) => {\n if (e.target === modal) {\n this.preferencesElement?.remove()\n this.preferencesElement = null\n this.cleanupThemeObserverIfIdle()\n }\n })\n }\n\n /**\n * Clean up every DOM resource this service owns: removes the banner\n * and preferences modal, removes injected third-party scripts, and\n * stops the theme observer. Call when unmounting the SDK (e.g. inside\n * a React `useEffect` cleanup) so the page can be navigated without\n * leaking listeners. No-op on server.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```tsx\n * useEffect(() => {\n * lynkow.consent.show()\n * return () => lynkow.consent.destroy()\n * }, [])\n * ```\n */\n destroy(): void {\n this.themeCleanup?.()\n this.themeCleanup = null\n this.hide()\n this.preferencesElement?.remove()\n this.preferencesElement = null\n this.removeInjectedScripts()\n }\n}\n","/**\n * Branding module for Lynkow SDK\n *\n * Browser-only module. Displays \"Made with Lynkow\" badge for free plan users.\n * Badge HTML/CSS is fetched from the API so it can be updated server-side.\n */\n\nimport { BaseService, CACHE_TTL } from './base'\nimport { isBrowser } from '../core/environment'\nimport { detectSiteTheme, onSiteThemeChange } from '../utils/theme'\n\nconst BADGE_CONTAINER_ID = 'lynkow-badge-container'\nconst STYLES_ID = 'lynkow-badge-styles'\n\ninterface BadgeResponse {\n data: {\n html: string\n css: string\n }\n}\n\n/**\n * Service for displaying the \"Powered by Lynkow\" branding badge.\n *\n * Accessible via `lynkow.branding`. This is a **browser-only** service -- all\n * methods are no-ops on the server. The badge HTML and CSS are fetched from the\n * API (cached for 30 minutes, LONG TTL) so they can be updated server-side\n * without SDK changes. The badge automatically adapts to the site's light/dark\n * theme via a MutationObserver.\n *\n * The badge is shown for sites on the free plan (`siteConfig.showBranding === true`).\n * It fails silently if the fetch fails (the badge is not critical).\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Inject the badge\n * await lynkow.branding.inject()\n *\n * // Check if visible\n * console.log(lynkow.branding.isVisible()) // true\n *\n * // Remove it\n * lynkow.branding.remove()\n * ```\n */\nexport class BrandingService extends BaseService {\n private containerElement: HTMLElement | null = null\n private themeCleanup: (() => void) | null = null\n\n /**\n * Fetches the badge HTML/CSS from the API and injects it into the page DOM.\n * The badge is appended to `document.body` and styled according to the site's\n * current theme (light/dark). A theme observer is set up to update the badge\n * if the site theme changes dynamically. Idempotent -- calling multiple times\n * has no effect if the badge is already injected. Fails silently on fetch\n * errors. No-op on server.\n *\n * @returns Resolves when the badge is injected, or immediately if already present\n * or running on the server\n *\n * @example\n * ```typescript\n * // Inject the powered-by badge after the page has loaded\n * const lynkow = createClient({ siteId: '...' })\n * await lynkow.branding.inject()\n * ```\n */\n async inject(): Promise<void> {\n if (!isBrowser) return\n if (document.getElementById(BADGE_CONTAINER_ID)) return\n\n try {\n const { data } = await this.getWithCache<BadgeResponse>(\n 'branding:badge',\n '/branding/badge',\n undefined,\n undefined,\n CACHE_TTL.LONG\n )\n\n // Inject styles\n if (!document.getElementById(STYLES_ID)) {\n const styleElement = document.createElement('style')\n styleElement.id = STYLES_ID\n styleElement.textContent = data.css\n document.head.appendChild(styleElement)\n }\n\n // Inject badge HTML\n const container = document.createElement('div')\n container.id = BADGE_CONTAINER_ID\n container.innerHTML = data.html\n\n // Apply theme class based on site's actual theme\n if (detectSiteTheme() === 'light') {\n container.classList.add('lynkow-badge-light')\n }\n\n document.body.appendChild(container)\n this.containerElement = container\n\n // Observe theme changes to update badge in real-time\n this.themeCleanup = onSiteThemeChange((theme) => {\n if (this.containerElement) {\n this.containerElement.classList.toggle('lynkow-badge-light', theme === 'light')\n }\n })\n } catch {\n // Fail silently — badge is not critical\n }\n }\n\n /**\n * Remove the branding badge and its associated `<style>` block from\n * the DOM and stop the theme observer. No-op on server or if the\n * badge is not currently injected.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * lynkow.branding.remove()\n * ```\n */\n remove(): void {\n if (!isBrowser) return\n\n this.themeCleanup?.()\n this.themeCleanup = null\n\n this.containerElement?.remove()\n this.containerElement = null\n\n document.getElementById(STYLES_ID)?.remove()\n }\n\n /**\n * Check whether the branding badge is currently mounted in the DOM.\n * Useful for conditional logic (e.g. show a custom \"Powered by\" only\n * when the badge is not already rendered).\n *\n * @returns `true` if the badge container element exists in the\n * document, `false` otherwise. Always returns `false` on the server.\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * if (!lynkow.branding.isVisible()) {\n * // Render our own attribution\n * }\n * ```\n */\n isVisible(): boolean {\n if (!isBrowser) return false\n return document.getElementById(BADGE_CONTAINER_ID) !== null\n }\n\n /**\n * Alias for {@link remove}. Cleans up the badge, styles, and theme\n * observer. Matches the `destroy()` naming used on other lifecycle\n * services so cleanup code can stay uniform.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```tsx\n * useEffect(() => {\n * return () => lynkow.branding.destroy()\n * }, [])\n * ```\n */\n destroy(): void {\n this.remove()\n }\n}\n","/**\n * Content Enhancements module for Lynkow SDK\n *\n * Browser-only module. Adds interactive features to content rendered from the API:\n * - Copy button for code blocks\n * - (Future: lightbox for images, table of contents, etc.)\n */\n\nimport { isBrowser } from '../core/environment'\n\nconst STYLES_ID = 'lynkow-enhancements-styles'\nconst CLONE_ATTR = 'data-lynkow-clone'\n\n/**\n * MIME types the browser recognizes as executable JavaScript, per HTML spec\n * (https://html.spec.whatwg.org/#javascript-mime-type), plus `text/plain` which\n * is the consent-gated pattern: consumers render `<script type=\"text/plain\">`\n * to prevent execution and rely on `activateScripts()` to strip the type so\n * the clone runs.\n *\n * Anything outside this set (notably `application/ld+json`, `application/json`,\n * `importmap`, `speculationrules`) is data for the browser, not code, and must\n * not be cloned — doing so creates phantom duplicates in the DOM.\n */\nconst EXECUTABLE_SCRIPT_TYPES = new Set([\n '',\n 'module',\n 'text/plain',\n 'text/javascript',\n 'application/javascript',\n 'application/ecmascript',\n 'text/ecmascript',\n 'application/x-javascript',\n 'application/x-ecmascript',\n])\n\n// Copy icon SVG\nconst COPY_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg>`\n\n// Check icon SVG (for copied state)\nconst CHECK_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>`\n\nconst ENHANCEMENT_STYLES = `\n /*\n * Lynkow Content Enhancements\n * These styles ensure that content from the Lynkow API is displayed correctly,\n * even when the client uses CSS frameworks like Tailwind or CSS resets.\n */\n\n /* Text alignment - Ensure inline styles are respected */\n [style*=\"text-align: center\"] {\n text-align: center !important;\n }\n\n [style*=\"text-align: right\"] {\n text-align: right !important;\n }\n\n [style*=\"text-align: justify\"] {\n text-align: justify !important;\n }\n\n /* Preserve common inline formatting */\n h1, h2, h3, h4, h5, h6, p, blockquote {\n /* Allow inline text-align to override */\n text-align: inherit;\n }\n\n /* Code block enhancements */\n .code-block {\n position: relative;\n margin: 1rem 0;\n }\n\n .code-block-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0.5rem 1rem;\n background: #1e1e1e;\n border-radius: 0.5rem 0.5rem 0 0;\n border-bottom: 1px solid #333;\n }\n\n .code-block-language {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 0.75rem;\n color: #888;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n .code-block-copy {\n display: flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n color: #888;\n cursor: pointer;\n padding: 0.25rem;\n border-radius: 0.25rem;\n transition: color 0.2s, background-color 0.2s;\n }\n\n .code-block-copy:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.1);\n }\n\n .code-block-copy.copied {\n color: #22c55e;\n }\n\n .code-block pre {\n margin: 0;\n border-radius: 0 0 0.5rem 0.5rem;\n background: #1e1e1e;\n overflow-x: auto;\n }\n\n .code-block code {\n display: block;\n padding: 1rem;\n font-family: 'Fira Code', 'JetBrains Mono', 'SF Mono', Consolas, monospace;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #e4e4e4;\n }\n\n /* Light mode support */\n @media (prefers-color-scheme: light) {\n .code-block-header {\n background: #f5f5f5;\n border-bottom-color: #e0e0e0;\n }\n\n .code-block-language {\n color: #666;\n }\n\n .code-block-copy {\n color: #666;\n }\n\n .code-block-copy:hover {\n color: #1a1a1a;\n background: rgba(0, 0, 0, 0.05);\n }\n\n .code-block pre {\n background: #f5f5f5;\n }\n\n .code-block code {\n color: #1a1a1a;\n }\n }\n`\n\n/**\n * Service for adding interactive features to content rendered from the Lynkow API.\n *\n * Accessible via `lynkow.enhancements`. This is a **browser-only** service -- all\n * methods are no-ops on the server. Currently provides:\n * - **Copy button** for code blocks (elements with `[data-copy-code]` attribute)\n * - **Script activation** for inline scripts injected via `dangerouslySetInnerHTML`\n * - **Widget iframe auto-resize** for embedded Lynkow widgets\n * - **CSS normalization** to ensure content renders correctly with CSS frameworks (Tailwind, etc.)\n *\n * A `MutationObserver` automatically detects new content added to the DOM and\n * applies enhancements, making it compatible with SPA frameworks like React/Next.js.\n * Script clones are appended to `<head>` (not inline) to avoid React DOM reconciliation issues.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: 'your-site-id' })\n *\n * // Enhancements are automatically initialized by the client\n * // Or manually reinitialize after loading dynamic content:\n * lynkow.enhancements.init()\n * ```\n */\nexport class EnhancementsService {\n private initialized = false\n private observer: MutationObserver | null = null\n private pendingFrame: number | null = null\n private handleWidgetResize = (event: MessageEvent) => {\n if (!event.data || event.data.type !== 'lynkow-widget-resize') return\n const iframes = document.querySelectorAll<HTMLIFrameElement>(\n 'iframe[src*=\"/widgets/calendar/\"]'\n )\n iframes.forEach((iframe) => {\n if (iframe.contentWindow === event.source) {\n iframe.style.height = event.data.height + 'px'\n }\n })\n }\n\n /**\n * Inject styles into document head\n */\n private injectStyles(): void {\n if (!isBrowser) return\n if (document.getElementById(STYLES_ID)) return\n\n const styleElement = document.createElement('style')\n styleElement.id = STYLES_ID\n styleElement.textContent = ENHANCEMENT_STYLES\n document.head.appendChild(styleElement)\n }\n\n /**\n * Handle copy button click\n */\n private async handleCopyClick(button: HTMLElement): Promise<void> {\n const codeBlock = button.closest('.code-block')\n if (!codeBlock) return\n\n const codeElement = codeBlock.querySelector('code')\n if (!codeElement) return\n\n const code = codeElement.textContent || ''\n\n try {\n await navigator.clipboard.writeText(code)\n\n // Show success state\n button.classList.add('copied')\n button.innerHTML = CHECK_ICON\n\n // Reset after 2 seconds\n setTimeout(() => {\n button.classList.remove('copied')\n button.innerHTML = COPY_ICON\n }, 2000)\n } catch (err) {\n console.error('Lynkow SDK: Failed to copy code', err)\n }\n }\n\n /**\n * Bind copy functionality to all code blocks\n */\n private bindCodeBlockCopy(): void {\n if (!isBrowser) return\n\n const copyButtons = document.querySelectorAll<HTMLElement>('[data-copy-code]')\n\n copyButtons.forEach((button) => {\n // Avoid binding twice\n if (button.dataset['lynkowBound']) return\n button.dataset['lynkowBound'] = 'true'\n\n button.addEventListener('click', (e) => {\n e.preventDefault()\n this.handleCopyClick(button)\n })\n })\n }\n\n /**\n * Re-execute inline scripts that were inserted via innerHTML/dangerouslySetInnerHTML.\n * Browsers refuse to run <script> tags injected this way, so we clone them\n * into fresh <script> elements which the browser will execute.\n *\n * The clone is appended to <head> instead of replacing the original in-place.\n * This avoids mutating React's managed DOM tree, which would cause crashes\n * during client-side navigations in Next.js / React 19 (replaceChild would\n * desync the real DOM from React's virtual DOM, leading to\n * \"Node.insertBefore/removeChild: not a child of this node\" errors).\n */\n private activateScripts(container?: Node): void {\n if (!isBrowser) return\n\n const root = container instanceof HTMLElement ? container : document.body\n const scripts = root.querySelectorAll<HTMLScriptElement>('script:not([data-lynkow-activated])')\n\n // Filter to scripts we need to process: inline (no src), still in the DOM,\n // and of an executable JavaScript MIME type. Non-executable types like\n // `application/ld+json` must be skipped — cloning them into <head> would\n // duplicate structured data that Google (and other parsers) then flag as\n // \"duplicate field\" errors in Search Console.\n const toActivate = Array.from(scripts).filter(\n (s) => !s.src && s.isConnected && EXECUTABLE_SCRIPT_TYPES.has(s.type.toLowerCase())\n )\n if (toActivate.length === 0) return\n\n // Remove old clones from <head> before creating new ones.\n // This prevents accumulation across navigations: when React re-renders\n // a page, fresh script elements are created (without data-lynkow-activated),\n // and old clones from the previous page must be cleaned up.\n document.head.querySelectorAll(`script[${CLONE_ATTR}]`).forEach((el) => el.remove())\n\n toActivate.forEach((original) => {\n original.setAttribute('data-lynkow-activated', 'true')\n\n const replacement = document.createElement('script')\n // Copy attributes, but skip type=\"text/plain\" (used to prevent execution before activation)\n // and data-lynkow-activated (marker for the original only)\n const attrs = original.attributes\n for (let i = 0; i < attrs.length; i++) {\n const attr = attrs[i]\n if (!attr) continue\n if (attr.name === 'type' && attr.value === 'text/plain') continue\n if (attr.name === 'data-lynkow-activated') continue\n replacement.setAttribute(attr.name, attr.value)\n }\n replacement.textContent = original.textContent\n replacement.setAttribute(CLONE_ATTR, '')\n // Append to <head> — outside React's managed DOM tree\n document.head.appendChild(replacement)\n })\n }\n\n /**\n * Initializes content enhancements: injects CSS styles, binds copy-to-clipboard\n * handlers on code blocks, activates inline scripts, starts the widget iframe\n * resize listener, and sets up a `MutationObserver` to automatically enhance\n * newly added DOM content. Idempotent -- calling multiple times has no effect\n * after the first initialization. No-op on server.\n *\n * Call this manually if you need to re-initialize after the client is created\n * (e.g. after dynamically loading content outside the initial render).\n *\n * @example\n * ```typescript\n * // Reinitialize enhancements after dynamically loading new content\n * const html = await fetchArticleContent()\n * document.getElementById('article').innerHTML = html\n * lynkow.enhancements.init()\n * ```\n */\n init(): void {\n if (!isBrowser || this.initialized) return\n\n // Inject styles\n this.injectStyles()\n\n // Bind existing elements and activate scripts\n this.bindCodeBlockCopy()\n this.activateScripts()\n\n // Listen for widget iframe resize messages\n window.addEventListener('message', this.handleWidgetResize)\n\n // Set up MutationObserver for dynamic content.\n // Mutations are debounced via requestAnimationFrame to avoid running\n // mid-reconciliation during React concurrent renders / soft navigations.\n if (!this.observer) {\n this.observer = new MutationObserver(() => {\n if (this.pendingFrame !== null) return\n this.pendingFrame = requestAnimationFrame(() => {\n this.pendingFrame = null\n this.activateScripts()\n this.bindCodeBlockCopy()\n })\n })\n\n this.observer.observe(document.body, {\n childList: true,\n subtree: true,\n })\n }\n\n this.initialized = true\n }\n\n /**\n * Check whether the enhancements service has been initialized for the\n * current page. Returns `false` on the server and after a call to\n * {@link destroy} (until `init()` is invoked again).\n *\n * @returns `true` if `init()` has been called successfully, `false`\n * otherwise.\n * @throws Never throws.\n *\n * @example\n * ```typescript\n * if (!lynkow.enhancements.isInitialized()) {\n * lynkow.enhancements.init()\n * }\n * ```\n */\n isInitialized(): boolean {\n return this.initialized\n }\n\n /**\n * Clean up every resource the enhancements service owns: disconnect the\n * MutationObserver, remove the widget resize listener, cancel any\n * pending animation frame, remove injected styles and cloned scripts\n * from `<head>`, and reset the initialized state. Safe to call\n * repeatedly; subsequent calls are no-ops. After `destroy()` you can\n * re-attach by calling `init()` again. No-op on server.\n *\n * @returns void\n * @throws Never throws.\n *\n * @example\n * ```tsx\n * useEffect(() => {\n * lynkow.enhancements.init()\n * return () => lynkow.enhancements.destroy()\n * }, [])\n * ```\n */\n destroy(): void {\n if (!isBrowser) return\n\n // Remove widget resize listener\n window.removeEventListener('message', this.handleWidgetResize)\n\n // Cancel pending debounced frame\n if (this.pendingFrame !== null) {\n cancelAnimationFrame(this.pendingFrame)\n this.pendingFrame = null\n }\n\n // Disconnect observer\n if (this.observer) {\n this.observer.disconnect()\n this.observer = null\n }\n\n // Remove styles and cloned scripts\n document.getElementById(STYLES_ID)?.remove()\n document.head.querySelectorAll(`script[${CLONE_ATTR}]`).forEach((el) => el.remove())\n\n this.initialized = false\n }\n}\n","/**\n * Options for building srcset URLs\n */\nexport interface SrcsetOptions {\n /**\n * Pixel widths to generate in the srcset, in ascending order. Each\n * width becomes a `Nw` entry in the returned string. Defaults to\n * `[400, 800, 1200, 1920]` to cover phone, tablet, desktop, large\n * desktop. Supply a custom list when you know your layout's\n * breakpoints to avoid bandwidth waste.\n */\n widths?: number[]\n /**\n * Resize fit mode. `'scale-down'` (default) preserves aspect ratio and\n * never upscales; `'cover'` fills the box and crops; `'contain'` fits\n * inside the box with letterboxing; `'crop'` hard crops to the exact\n * dimensions. Must pair with `gravity` for `'cover'` / `'crop'` when\n * the subject is not centered.\n */\n fit?: 'cover' | 'contain' | 'scale-down' | 'crop'\n /**\n * JPEG / WebP quality on a 1-100 scale. Higher values produce larger\n * files. Default `80` strikes a good balance for photography; drop to\n * 60-70 for hero images on slow connections.\n */\n quality?: number\n /**\n * Focal point for `fit: 'cover' | 'crop'` as an `XxY` pair of\n * fractions (e.g. `'0.5x0.3'` keeps the horizontal center but biases\n * towards the upper third). Omit to center the crop.\n */\n gravity?: string\n}\n\n/**\n * Options for building a single transformed URL. Each field maps to a\n * query parameter understood by the Lynkow image transformation service;\n * omit any value to let the CDN choose a sensible default.\n */\nexport interface TransformOptions {\n /**\n * Target width in pixels. When set alone, height is derived from the\n * image's aspect ratio (unless `fit` requires both).\n */\n w?: number\n /**\n * Target height in pixels. When set alone, width is derived from the\n * image's aspect ratio (unless `fit` requires both).\n */\n h?: number\n /**\n * Resize fit mode. See {@link SrcsetOptions.fit} for semantics; the\n * default here is `'scale-down'` to avoid accidental upscaling.\n */\n fit?: 'cover' | 'contain' | 'scale-down' | 'crop'\n /** Image quality 1-100 (default: 80) */\n quality?: number\n /**\n * Output image format. `'auto'` (default) lets the CDN negotiate\n * based on `Accept` (WebP on modern browsers, AVIF on the newest).\n * Force a specific format only when the consumer is known.\n */\n format?: 'auto' | 'webp' | 'avif' | 'jpeg'\n /**\n * Focal point for `fit: 'cover' | 'crop'` as an `XxY` pair of\n * fractions (see {@link SrcsetOptions.gravity}). Omit to center.\n */\n gravity?: string\n /**\n * Device Pixel Ratio multiplier on a `1`-`4` scale. Multiplies the\n * rendered width/height so a 400px box renders sharp at 2x on\n * Retina. Prefer using a `srcset` over a hard-coded DPR.\n */\n dpr?: number\n}\n\n/**\n * Service for building optimized image URLs backed by the Lynkow image\n * transformation service.\n *\n * Accessible via `lynkow.media`. This is a pure utility service (no API calls, no\n * caching) that constructs CDN transformation URLs from Lynkow media URLs.\n * Works on both server and browser. Handles both original URLs (`/sites/...`) and\n * already-transformed URLs, re-extracting the original path when needed.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Build responsive srcset for an <img> tag\n * const srcset = lynkow.media.srcset(content.featuredImage)\n * // => \"https://cdn.../cdn-cgi/image/w=400,.../path 400w, ...1920w\"\n *\n * // Build a single transformed URL\n * const url = lynkow.media.transform(content.featuredImage, { w: 800, format: 'webp' })\n * ```\n */\nexport class MediaHelperService {\n /**\n * Generates an HTML `srcset` attribute value from a Lynkow image URL, suitable\n * for use in an `<img srcset=\"...\">` or `<source srcset=\"...\">` tag. Produces\n * one Lynkow CDN transformation URL per width breakpoint.\n *\n * @param imageUrl - Original image URL from the Lynkow API (e.g. `content.featuredImage`).\n * Accepts `null` or `undefined` safely (returns empty string).\n * @param options - Configuration for the srcset:\n * - `widths` — array of pixel widths to generate (default: `[400, 800, 1200, 1920]`)\n * - `fit` — resize mode: `'cover'`, `'contain'`, `'scale-down'`, or `'crop'` (default: `'scale-down'`)\n * - `quality` — image quality 1-100 (default: `80`)\n * - `gravity` — focal point for crop mode (e.g. `'0.5x0.3'`)\n * @returns A srcset string like `\"url 400w, url 800w, url 1200w, url 1920w\"`,\n * or an empty string if the URL is null/undefined or not a valid Lynkow CDN URL\n *\n * @example\n * ```typescript\n * // Build a responsive <img> tag with multiple widths\n * const srcsetValue = lynkow.media.srcset(article.featuredImage, {\n * widths: [480, 768, 1024, 1440],\n * fit: 'cover',\n * quality: 85,\n * })\n * const imgTag = `<img srcset=\"${srcsetValue}\" sizes=\"(max-width: 768px) 100vw, 50vw\" alt=\"${article.title}\" />`\n * ```\n */\n srcset(imageUrl: string | null | undefined, options: SrcsetOptions = {}): string {\n if (!imageUrl) return ''\n\n const {\n widths = [400, 800, 1200, 1920],\n fit = 'scale-down',\n quality = 80,\n gravity,\n } = options\n\n const parsed = this.parseImageUrl(imageUrl)\n if (!parsed) return ''\n\n return widths\n .map((w) => {\n const params = [\n `w=${w}`,\n `fit=${fit}`,\n 'format=auto',\n `quality=${quality}`,\n gravity && `gravity=${gravity}`,\n ]\n .filter(Boolean)\n .join(',')\n\n return `${parsed.cdnBase}/cdn-cgi/image/${params}/${parsed.relativePath} ${w}w`\n })\n .join(', ')\n }\n\n /**\n * Generates a single Lynkow CDN transformation URL from a Lynkow image URL.\n * Useful for thumbnails, hero images, or any context where you need a specific\n * size/format.\n *\n * @param imageUrl - Original image URL from the Lynkow API (e.g. `content.featuredImage`).\n * Accepts `null` or `undefined` safely (returns empty string).\n * @param options - Transformation options:\n * - `w` / `h` — target width/height in pixels\n * - `fit` — resize mode: `'cover'`, `'contain'`, `'scale-down'`, or `'crop'` (default: `'scale-down'`)\n * - `quality` — image quality 1-100 (default: `80`)\n * - `format` — output format: `'auto'`, `'webp'`, `'avif'`, or `'jpeg'` (default: `'auto'`)\n * - `gravity` — focal point for crop mode (e.g. `'0.5x0.3'`)\n * - `dpr` — device pixel ratio 1-4 for retina displays\n * @returns The transformed Lynkow CDN URL, the original URL if transformation is\n * not possible (non-Lynkow URL), or an empty string if the URL is null/undefined\n *\n * @example\n * ```typescript\n * // Build an optimized thumbnail URL for a product card\n * const thumbUrl = lynkow.media.transform(product.image, {\n * w: 400,\n * h: 300,\n * fit: 'cover',\n * format: 'webp',\n * quality: 75,\n * })\n * document.querySelector('.product-thumb').src = thumbUrl\n * ```\n */\n transform(imageUrl: string | null | undefined, options: TransformOptions = {}): string {\n if (!imageUrl) return ''\n\n const parsed = this.parseImageUrl(imageUrl)\n if (!parsed) return imageUrl || ''\n\n const params = [\n options.w && `w=${options.w}`,\n options.h && `h=${options.h}`,\n `fit=${options.fit || 'scale-down'}`,\n `format=${options.format || 'auto'}`,\n `quality=${options.quality || 80}`,\n options.gravity && `gravity=${options.gravity}`,\n options.dpr && `dpr=${options.dpr}`,\n ]\n .filter(Boolean)\n .join(',')\n\n return `${parsed.cdnBase}/cdn-cgi/image/${params}/${parsed.relativePath}`\n }\n\n /**\n * Extracts the CDN base and relative path from a Lynkow image URL.\n *\n * Handles both original URLs and already-transformed URLs.\n *\n * @returns Parsed URL parts, or null if the URL is not a valid Lynkow CDN URL\n */\n private parseImageUrl(\n imageUrl: string\n ): { cdnBase: string; relativePath: string } | null {\n // Case 1: Already a transformed URL\n const cdnCgiIndex = imageUrl.indexOf('/cdn-cgi/image/')\n if (cdnCgiIndex !== -1) {\n const cdnBase = imageUrl.substring(0, cdnCgiIndex)\n const afterOptions = imageUrl.substring(cdnCgiIndex + '/cdn-cgi/image/'.length)\n const firstSlash = afterOptions.indexOf('/')\n if (firstSlash === -1) return null\n const relativePath = afterOptions.substring(firstSlash + 1)\n return { cdnBase, relativePath }\n }\n\n // Case 2: Original URL with /sites/ path\n const sitesIndex = imageUrl.indexOf('/sites/')\n if (sitesIndex !== -1) {\n const cdnBase = imageUrl.substring(0, sitesIndex)\n const relativePath = imageUrl.substring(sitesIndex + 1)\n return { cdnBase, relativePath }\n }\n\n // Case 3: Original URL with /avatars/ path\n const avatarsIndex = imageUrl.indexOf('/avatars/')\n if (avatarsIndex !== -1) {\n const cdnBase = imageUrl.substring(0, avatarsIndex)\n const relativePath = imageUrl.substring(avatarsIndex + 1)\n return { cdnBase, relativePath }\n }\n\n return null\n }\n}\n","import { BaseService, CACHE_TTL } from './base'\nimport type { BaseRequestOptions } from '../types'\n\n/**\n * A single search result (hit) returned by Lynkow Instant Search.\n *\n * Contains article metadata, URL path, and optional highlighted matches.\n * Only published content appears in search results.\n */\nexport interface SearchHit {\n /** Content UUID. Matches `id` on `Content` / `ContentSummary`. */\n id: string\n /**\n * Content title in the searched locale. Never empty (the indexer skips\n * untitled drafts). May contain `<em>` markers when highlighting is\n * enabled; see `_formatted.title` for the highlighted variant.\n */\n title: string\n /** URL-safe slug. Unique within the site + locale combination. */\n slug: string\n /**\n * Short text summary shown in the search dropdown. Taken from the\n * content's `excerpt` field, or generated from the first paragraph\n * when `excerpt` is empty. Always non-null in search results.\n */\n excerpt: string\n /**\n * SEO meta title. Falls back to {@link title} when the content has no\n * override. Max ~255 characters, generally kept under 60 for search\n * result listings.\n */\n metaTitle: string\n /**\n * SEO meta description. Falls back to {@link excerpt} when the content\n * has no override. Max ~500 characters, typically under 160 for\n * search snippets.\n */\n metaDescription: string\n /** Content locale code (e.g. `'fr'`, `'en'`) */\n locale: string\n /** Full URL path including locale and category prefix (e.g. `'/fr/guides/forms'`) */\n path: string\n /**\n * Content type slug. Currently always `'post'`; the value is exposed\n * to support future content types (product, event, etc.) without a\n * breaking schema change.\n */\n type: string\n /**\n * Categories assigned to this content as minimal `{ name, slug }`\n * projections. Empty array when the content has no category. Use the\n * `slug` to build category URLs and pass to\n * {@link CategoriesService.getBySlug} for full detail.\n */\n categories: Array<{ name: string; slug: string }>\n /**\n * Tags assigned to this content as minimal `{ name, slug }`\n * projections. Empty array when the content has no tag. Use the\n * `slug` for tag-filtered listings.\n */\n tags: Array<{ name: string; slug: string }>\n /**\n * Full name of the content author (or the site's default author\n * when the content has no explicit author). Always non-empty because\n * the indexer requires a tokenizable value.\n */\n authorName: string\n /** Featured image URL, or `null` if none */\n featuredImage: string | null\n /** Publication date as Unix timestamp (seconds) */\n publishedAt: number\n /** Last update date as Unix timestamp (seconds) */\n updatedAt: number\n /**\n * Highlighted matches with `<em>` tags around matching terms.\n * Only present when the search engine returns formatted results.\n * Keys match the field names (e.g. `title`, `excerpt`, `body`).\n *\n * @example\n * ```typescript\n * hit._formatted?.title // \"Getting Started with <em>Pagination</em>\"\n * ```\n */\n _formatted?: Record<string, string>\n}\n\n/**\n * Response from `lynkow.search.search()`.\n *\n * Contains an array of matching articles and pagination metadata.\n */\nexport interface SearchResponse {\n /**\n * Matching articles for the current page of results, already ordered\n * by relevance. Empty array when the query matches nothing.\n */\n data: SearchHit[]\n /**\n * Pagination and query metadata for rendering controls like \"N results\n * in 42 ms\" or a paginator. Uses snake-free naming (`page`,\n * `totalPages`, `perPage`) that differs from the standard\n * {@link PaginationMeta} because the search engine response predates\n * the common pagination type.\n */\n meta: {\n /**\n * Total number of matching results across every page. Useful for\n * \"Showing 1-10 of 142\" UI. Capped at the search engine's\n * configured upper bound (50_000 by default).\n */\n total: number\n /** Current page number (1-based) */\n page: number\n /**\n * Total number of pages for the current query + `perPage`. `1` when\n * results fit on one page, `0` when `total === 0`.\n */\n totalPages: number\n /**\n * Number of results per page as actually applied by the search\n * engine (the request's `limit` clamped to 1-100).\n */\n perPage: number\n /**\n * The search query echoed back from the server, useful for\n * debouncing UIs that drop late responses when the input has\n * changed. Whitespace is preserved as-sent.\n */\n query: string\n /** Search engine processing time in milliseconds */\n processingTimeMs: number\n }\n}\n\n/**\n * Options for `lynkow.search.search()`.\n *\n * All filters are optional. When omitted, searches across all published\n * content in all locales.\n */\nexport interface SearchOptions extends BaseRequestOptions {\n /** Filter by locale code (e.g. `'fr'`, `'en'`). Omit to search all locales. */\n locale?: string\n /** Filter by category slug (e.g. `'guides'`). Omit to search all categories. */\n category?: string\n /** Filter by tag slug (e.g. `'featured'`). Omit to search all tags. */\n tag?: string\n /** Page number (1-based). Defaults to `1`. */\n page?: number\n /** Results per page (1--100). Defaults to `20`. */\n limit?: number\n}\n\n/**\n * Configuration for client-side direct search.\n *\n * Returned by `lynkow.search.getConfig()`. Use these values to initialize\n * a search client in the browser for instant autocomplete without\n * round-tripping through your server.\n */\nexport interface SearchConfig {\n /**\n * Public Lynkow search host URL (e.g. `'https://search.lynkow.com'`).\n * Pass verbatim as the `host` when instantiating a search client in\n * the browser (for example the `meilisearch-js` npm package, which\n * speaks the same protocol). Does not include a trailing slash.\n */\n host: string\n /** Short-lived tenant token (JWT, 1-hour expiry) scoped to your site's index */\n apiKey: string\n /**\n * Lynkow search index name for this site, of the form\n * `site-<siteId>_<locale>` or `site-<siteId>` for single-locale sites.\n * Pass verbatim as the `index` parameter in browser search queries.\n */\n indexName: string\n}\n\n/**\n * Lynkow Instant Search service.\n *\n * Accessible via `lynkow.search`. Provides full-text search with typo\n * tolerance across all published content. Results are not cached (search\n * queries are dynamic by nature).\n *\n * Search must be enabled in the admin dashboard (**Settings > SEO > Search**)\n * before it can be used. When disabled, all methods return a 503 error.\n *\n * @example\n * ```typescript\n * const lynkow = createClient({ siteId: '...' })\n *\n * // Search with filters\n * const { data, meta } = await lynkow.search.search('pagination', {\n * locale: 'en',\n * category: 'guides',\n * limit: 10,\n * })\n *\n * // Iterate results\n * for (const hit of data) {\n * console.log(hit.title, hit.path, hit._formatted?.title)\n * }\n * ```\n */\nexport class SearchService extends BaseService {\n /**\n * Search published content with typo tolerance.\n *\n * Queries the search index for articles matching the given query string.\n * Results are ordered by relevance and include highlighted matches in\n * the `_formatted` field.\n *\n * @param query - The search query string. Typos are handled automatically\n * (e.g. `'pagniation'` finds articles about pagination).\n * @param options - Optional filters and pagination:\n * - `locale` — filter by locale code (e.g. `'fr'`)\n * - `category` — filter by category slug (e.g. `'guides'`)\n * - `tag` — filter by tag slug (e.g. `'featured'`)\n * - `page` — page number, 1-based (default: `1`)\n * - `limit` — results per page, 1--100 (default: `20`)\n * @returns A `SearchResponse` containing `data` (array of `SearchHit`)\n * and `meta` (pagination info with total, page, totalPages, perPage,\n * query, processingTimeMs)\n * @throws {LynkowError} With code `'NETWORK_ERROR'` if the API is unreachable\n * @throws {LynkowError} With status `503` if search is not enabled for this site\n *\n * @example\n * ```typescript\n * // Basic search\n * const results = await lynkow.search.search('forms')\n *\n * // With filters\n * const results = await lynkow.search.search('formulaire', {\n * locale: 'fr',\n * category: 'guides',\n * page: 1,\n * limit: 5,\n * })\n *\n * // Access highlighted results\n * results.data.forEach(hit => {\n * console.log(hit._formatted?.title || hit.title)\n * })\n * ```\n */\n async search(query: string, options?: SearchOptions): Promise<SearchResponse> {\n return this.get<SearchResponse>('/search', {\n q: query,\n locale: options?.locale,\n category: options?.category,\n tag: options?.tag,\n page: options?.page,\n limit: options?.limit,\n }, options)\n }\n\n /**\n * Get search configuration for client-side direct search.\n *\n * Returns the search host URL, a short-lived tenant token (JWT, 1-hour\n * expiry), and the index name for your site. Use these values to query\n * the search engine directly from the browser without round-tripping\n * through your server.\n *\n * The response is cached for 10 minutes. Tenant tokens expire after\n * 1 hour — for long-lived pages, call this method periodically to\n * refresh the token.\n *\n * @param options - Base request options (custom fetch options)\n * @returns A `SearchConfig` with `host`, `apiKey` (tenant token), and `indexName`\n * @throws {LynkowError} With status `503` if search is not enabled for this site\n *\n * @example\n * ```typescript\n * const config = await lynkow.search.getConfig()\n * // {\n * // host: 'https://search.lynkow.com',\n * // apiKey: 'eyJhbGciOi...',\n * // indexName: 'site_abc123_def4_...'\n * // }\n * ```\n */\n async getConfig(options?: BaseRequestOptions): Promise<SearchConfig> {\n return this.getWithCache<SearchConfig>(\n 'search-config',\n '/search/config',\n undefined,\n options,\n CACHE_TTL.MEDIUM\n )\n }\n}\n","/**\n * Cache manager for Lynkow SDK\n * Uses memory cache on server, localStorage on browser\n */\n\nimport { isBrowser } from './environment'\n\n/**\n * Cache entry with TTL\n */\ninterface CacheEntry<T> {\n value: T\n expiresAt: number\n}\n\n/**\n * Cache configuration\n */\nexport interface CacheConfig {\n /** Default TTL in milliseconds (default: 5 minutes) */\n defaultTtl?: number\n /** Storage key prefix */\n prefix?: string\n}\n\nconst DEFAULT_TTL = 5 * 60 * 1000 // 5 minutes\nconst STORAGE_PREFIX = 'lynkow_cache_'\n\n/**\n * In-memory cache for server environment\n */\nconst memoryCache = new Map<string, CacheEntry<unknown>>()\n\n/**\n * Create a cache manager\n */\nexport function createCache(config: CacheConfig = {}) {\n const defaultTtl = config.defaultTtl ?? DEFAULT_TTL\n const prefix = config.prefix ?? STORAGE_PREFIX\n\n /**\n * Get full cache key with prefix\n */\n function getKey(key: string): string {\n return `${prefix}${key}`\n }\n\n /**\n * Check if an entry is expired\n */\n function isExpired(entry: CacheEntry<unknown>): boolean {\n return Date.now() > entry.expiresAt\n }\n\n /**\n * Get value from cache\n */\n function get<T>(key: string): T | null {\n const fullKey = getKey(key)\n\n if (isBrowser) {\n try {\n const stored = localStorage.getItem(fullKey)\n if (!stored) return null\n\n const entry = JSON.parse(stored) as CacheEntry<T>\n if (isExpired(entry)) {\n localStorage.removeItem(fullKey)\n return null\n }\n return entry.value\n } catch {\n return null\n }\n }\n\n // Server: use memory cache\n const entry = memoryCache.get(fullKey) as CacheEntry<T> | undefined\n if (!entry) return null\n\n if (isExpired(entry)) {\n memoryCache.delete(fullKey)\n return null\n }\n return entry.value\n }\n\n /**\n * Set value in cache\n */\n function set<T>(key: string, value: T, ttl: number = defaultTtl): void {\n const fullKey = getKey(key)\n const entry: CacheEntry<T> = {\n value,\n expiresAt: Date.now() + ttl,\n }\n\n if (isBrowser) {\n try {\n localStorage.setItem(fullKey, JSON.stringify(entry))\n } catch {\n // localStorage full or unavailable, silently fail\n }\n return\n }\n\n // Server: use memory cache\n memoryCache.set(fullKey, entry)\n }\n\n /**\n * Remove value from cache\n */\n function remove(key: string): void {\n const fullKey = getKey(key)\n\n if (isBrowser) {\n try {\n localStorage.removeItem(fullKey)\n } catch {\n // Silently fail\n }\n return\n }\n\n memoryCache.delete(fullKey)\n }\n\n /**\n * Invalidate cache entries matching a pattern\n * If no pattern is provided, clears all cache entries\n */\n function invalidate(pattern?: string): void {\n if (isBrowser) {\n try {\n const keysToRemove: string[] = []\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i)\n if (key && key.startsWith(prefix)) {\n if (!pattern || key.includes(pattern)) {\n keysToRemove.push(key)\n }\n }\n }\n keysToRemove.forEach((key) => localStorage.removeItem(key))\n } catch {\n // Silently fail\n }\n return\n }\n\n // Server: clear memory cache\n if (!pattern) {\n // Clear all entries with our prefix\n for (const key of memoryCache.keys()) {\n if (key.startsWith(prefix)) {\n memoryCache.delete(key)\n }\n }\n } else {\n // Clear entries matching pattern\n for (const key of memoryCache.keys()) {\n if (key.startsWith(prefix) && key.includes(pattern)) {\n memoryCache.delete(key)\n }\n }\n }\n }\n\n /**\n * Get or set value in cache\n * If value exists and is not expired, returns cached value\n * Otherwise, calls factory function and caches the result\n */\n async function getOrSet<T>(\n key: string,\n factory: () => Promise<T>,\n ttl: number = defaultTtl\n ): Promise<T> {\n const cached = get<T>(key)\n if (cached !== null) {\n return cached\n }\n\n const value = await factory()\n set(key, value, ttl)\n return value\n }\n\n return {\n get,\n set,\n remove,\n invalidate,\n getOrSet,\n }\n}\n\nexport type Cache = ReturnType<typeof createCache>\n","/**\n * Conditional logging utility for Lynkow SDK\n * Only logs when debug mode is enabled\n */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error'\n\n/**\n * Logger configuration\n */\nexport interface LoggerConfig {\n debug: boolean\n prefix?: string\n}\n\n/**\n * Create a logger instance with the given configuration\n *\n * @param config - Logger configuration\n * @returns Logger instance\n */\nexport function createLogger(config: LoggerConfig) {\n const prefix = config.prefix || '[Lynkow]'\n\n return {\n /**\n * Log a debug message (only when debug mode is enabled)\n */\n debug(...args: unknown[]): void {\n if (config.debug) {\n console.debug(prefix, ...args)\n }\n },\n\n /**\n * Log an info message\n */\n info(...args: unknown[]): void {\n console.info(prefix, ...args)\n },\n\n /**\n * Log a warning message\n */\n warn(...args: unknown[]): void {\n console.warn(prefix, ...args)\n },\n\n /**\n * Log an error message\n */\n error(...args: unknown[]): void {\n console.error(prefix, ...args)\n },\n\n /**\n * Log a message at the specified level\n */\n log(level: LogLevel, ...args: unknown[]): void {\n switch (level) {\n case 'debug':\n this.debug(...args)\n break\n case 'info':\n this.info(...args)\n break\n case 'warn':\n this.warn(...args)\n break\n case 'error':\n this.error(...args)\n break\n }\n },\n }\n}\n\nexport type Logger = ReturnType<typeof createLogger>\n","/**\n * Simple event emitter for Lynkow SDK.\n * Provides a lightweight pub/sub system for reacting to SDK lifecycle events.\n */\n\n/**\n * Event types emitted by the SDK.\n * Each key is an event name; the value type is the payload passed to listeners.\n * Use `void` payload events with `emit(event, undefined as any)`.\n */\nexport interface LynkowEvents {\n /**\n * Emitted once when the SDK client is fully initialized and ready to make requests.\n * No payload. Subscribe before calling any service methods to ensure readiness.\n */\n ready: void\n\n /**\n * Emitted when the active locale changes (e.g. via user action or detection).\n * Payload is the new locale code (e.g. `'fr'`, `'en'`).\n * Use this to re-fetch locale-dependent data (contents, categories, pages).\n */\n 'locale-changed': string\n\n /**\n * Emitted when the user updates their cookie consent preferences.\n * Payload is a map of category ID to consent boolean\n * (e.g. `{ necessary: true, analytics: false, marketing: false }`).\n * Use this to inject or remove third-party scripts based on consent.\n */\n 'consent-changed': Record<string, boolean>\n\n /**\n * Emitted when an SDK operation encounters an unrecoverable error.\n * Payload is the `Error` (typically a {@link LynkowError}) that occurred.\n * Use this for global error logging or displaying error notifications.\n */\n error: Error\n}\n\nexport type EventName = keyof LynkowEvents\n\n/**\n * Callback function type for event listeners.\n * @typeParam T - The payload type for the event being listened to\n */\nexport type EventListener<T> = (data: T) => void\n\n/**\n * Create a new event emitter instance backing the `lynkow.on/off/once`\n * API of the client. Each SDK client creates its own emitter, so events\n * do not leak between client instances (e.g. multi-tenant server rendering).\n *\n * @returns An {@link EventEmitter} object exposing `on`, `off`, `emit`,\n * `once`, and `removeAllListeners`. `on()` and `once()` return an\n * unsubscribe closure for convenience.\n *\n * @example\n * ```typescript\n * import { createEventEmitter } from 'lynkow'\n *\n * const events = createEventEmitter()\n * const unsubscribe = events.on('locale-changed', (locale) => {\n * refetchContent(locale)\n * })\n * events.emit('locale-changed', 'fr')\n * unsubscribe()\n * ```\n */\nexport function createEventEmitter() {\n const listeners = new Map<string, Set<EventListener<unknown>>>()\n\n /**\n * Subscribe to an event.\n * The listener is called synchronously each time the event is emitted.\n * If a listener throws, the error is caught and logged -- it does not\n * prevent other listeners from being called.\n *\n * @param event - Event name to subscribe to\n * @param listener - Callback invoked with the event payload\n * @returns An unsubscribe function. Call it to remove this specific listener.\n *\n * @example\n * ```typescript\n * const unsubscribe = emitter.on('locale-changed', (locale) => {\n * console.log('Locale changed to:', locale)\n * })\n * // Later: unsubscribe()\n * ```\n */\n function on<K extends EventName>(\n event: K,\n listener: EventListener<LynkowEvents[K]>\n ): () => void {\n if (!listeners.has(event)) {\n listeners.set(event, new Set())\n }\n listeners.get(event)!.add(listener as EventListener<unknown>)\n\n // Return unsubscribe function\n return () => off(event, listener)\n }\n\n /**\n * Unsubscribe a specific listener from an event.\n * No-op if the listener was not previously registered.\n *\n * @param event - Event name to unsubscribe from\n * @param listener - The exact function reference that was passed to `on()`\n */\n function off<K extends EventName>(\n event: K,\n listener: EventListener<LynkowEvents[K]>\n ): void {\n const eventListeners = listeners.get(event)\n if (eventListeners) {\n eventListeners.delete(listener as EventListener<unknown>)\n }\n }\n\n /**\n * Emit an event, calling all registered listeners synchronously.\n * Listener errors are caught and logged to the console without\n * interrupting other listeners.\n *\n * @param event - Event name to emit\n * @param data - Payload to pass to all listeners\n */\n function emit<K extends EventName>(event: K, data: LynkowEvents[K]): void {\n const eventListeners = listeners.get(event)\n if (!eventListeners) return\n\n for (const listener of eventListeners) {\n try {\n listener(data)\n } catch (error) {\n // Don't let one listener's error affect others\n console.error(`[Lynkow] Error in event listener for \"${event}\":`, error)\n }\n }\n }\n\n /**\n * Subscribe to an event for a single emission only.\n * The listener is automatically removed after the first time it is called.\n *\n * @param event - Event name to subscribe to\n * @param listener - Callback invoked once with the event payload\n * @returns An unsubscribe function (can be called to cancel before the event fires)\n */\n function once<K extends EventName>(\n event: K,\n listener: EventListener<LynkowEvents[K]>\n ): () => void {\n const wrappedListener = ((data: LynkowEvents[K]) => {\n off(event, wrappedListener as EventListener<LynkowEvents[K]>)\n listener(data)\n }) as EventListener<LynkowEvents[K]>\n\n return on(event, wrappedListener)\n }\n\n /**\n * Remove all listeners for a specific event, or all listeners for all events.\n *\n * @param event - When provided, only listeners for this event are removed.\n * When omitted, all listeners for all events are cleared.\n */\n function removeAllListeners(event?: EventName): void {\n if (event) {\n listeners.delete(event)\n } else {\n listeners.clear()\n }\n }\n\n return {\n on,\n off,\n emit,\n once,\n removeAllListeners,\n }\n}\n\n/**\n * Type of the event emitter object returned by {@link createEventEmitter}.\n */\nexport type EventEmitter = ReturnType<typeof createEventEmitter>\n","/**\n * Locale detection and management utilities\n */\n\nimport { isBrowser } from '../core/environment'\n\nconst STORAGE_KEY = 'lynkow_locale'\n\n/**\n * Find a locale in the enabled list using case-insensitive matching.\n * Returns the canonical form from enabledLocales, or null if not found.\n *\n * @example\n * findLocale('zh-hans', ['fr', 'zh-Hans']) // → 'zh-Hans'\n * findLocale('EN', ['en', 'fr']) // → 'en'\n */\nfunction findLocale(candidate: string, enabledLocales: string[]): string | null {\n const lower = candidate.toLowerCase()\n return enabledLocales.find((l) => l.toLowerCase() === lower) ?? null\n}\n\n/**\n * Detect the locale based on priority order:\n * 1. localStorage (previous user choice)\n * 2. URL path prefix (/en/, /fr/)\n * 3. HTML lang attribute\n * 4. Default locale\n *\n * Safe to call during SSR: returns `defaultLocale` unchanged when\n * `window` is not available.\n *\n * @param enabledLocales - Canonical locale codes the site accepts (BCP 47,\n * e.g. `['en', 'fr', 'zh-Hans']`). Must be non-empty.\n * @param defaultLocale - Fallback used when nothing else matches. Should be\n * included in `enabledLocales`; not validated at runtime.\n * @returns One of `enabledLocales`, or `defaultLocale`.\n *\n * @example\n * ```typescript\n * import { detectLocale } from 'lynkow'\n * const locale = detectLocale(['en', 'fr', 'es'], 'en')\n * // '/fr/about' → 'fr'\n * // '/about' with <html lang=\"fr\"> → 'fr'\n * // otherwise → 'en'\n * ```\n */\nexport function detectLocale(enabledLocales: string[], defaultLocale: string): string {\n if (!isBrowser) {\n return defaultLocale\n }\n\n // 1. localStorage (previous user choice)\n const stored = getStoredLocale()\n if (stored && enabledLocales.includes(stored)) {\n return stored\n }\n\n // 2. URL path prefix\n const pathLocale = getLocaleFromPath(enabledLocales)\n if (pathLocale) {\n return pathLocale\n }\n\n // 3. HTML lang attribute\n const htmlLang = document.documentElement.lang\n if (htmlLang) {\n // Try exact match first (case-insensitive) for BCP 47 codes like zh-Hans\n const exactMatch = findLocale(htmlLang, enabledLocales)\n if (exactMatch) {\n return exactMatch\n }\n\n // Fallback: try base language (fr-FR → fr, zh-Hant-TW → zh)\n const base = htmlLang.split('-')[0]?.toLowerCase()\n if (base) {\n const baseMatch = findLocale(base, enabledLocales)\n if (baseMatch) {\n return baseMatch\n }\n }\n }\n\n // 4. Default\n return defaultLocale\n}\n\n/**\n * Read the user's previously chosen locale from `localStorage`.\n *\n * Returns `null` under SSR (where `window` is undefined) and when\n * localStorage is unavailable (private browsing in some browsers, storage\n * quota exceeded). No validation is performed against the site's enabled\n * locales; callers should check the result against their allowed list.\n *\n * @returns The stored locale code (BCP 47, as written when saved) or `null`\n * when nothing was stored, SSR context, or localStorage is blocked.\n *\n * @example\n * ```typescript\n * import { getStoredLocale } from 'lynkow'\n * const saved = getStoredLocale() // 'fr' | null\n * ```\n */\nexport function getStoredLocale(): string | null {\n if (!isBrowser) return null\n\n try {\n return localStorage.getItem(STORAGE_KEY)\n } catch {\n return null\n }\n}\n\n/**\n * Persist the user's locale choice to `localStorage` so subsequent\n * visits reuse it (see {@link detectLocale}).\n *\n * Silent no-op under SSR and when localStorage write throws (private\n * browsing, quota). The value is stored verbatim; pass the canonical\n * locale code as found in the site's enabled locales.\n *\n * @param locale - BCP 47 locale code to persist (e.g. `'fr'`, `'zh-Hans'`).\n * @returns void\n *\n * @example\n * ```typescript\n * import { setStoredLocale } from 'lynkow'\n * setStoredLocale('fr') // Remembers 'fr' across page loads\n * ```\n */\nexport function setStoredLocale(locale: string): void {\n if (!isBrowser) return\n\n try {\n localStorage.setItem(STORAGE_KEY, locale)\n } catch {\n // localStorage unavailable, silently fail\n }\n}\n\n/**\n * Clear the stored locale so the next call to {@link detectLocale} falls\n * back to URL path, `<html lang>`, then the site default.\n *\n * Silent no-op under SSR and when localStorage remove throws.\n *\n * @returns void\n *\n * @example\n * ```typescript\n * import { removeStoredLocale } from 'lynkow'\n * removeStoredLocale() // Next visit re-detects from URL / html lang\n * ```\n */\nexport function removeStoredLocale(): void {\n if (!isBrowser) return\n\n try {\n localStorage.removeItem(STORAGE_KEY)\n } catch {\n // Silently fail\n }\n}\n\n/**\n * Extract the leading locale segment from `window.location.pathname` and\n * return it in the canonical form found in `enabledLocales`. Matching is\n * case-insensitive, so `/EN/about` still resolves to `'en'`.\n *\n * Returns `null` under SSR, when the path has no segments, or when the\n * first segment is not a known locale.\n *\n * @param enabledLocales - Canonical BCP 47 codes the site accepts.\n * @returns The canonical locale from `enabledLocales`, or `null`.\n *\n * @example\n * ```typescript\n * // window.location.pathname === '/fr/services'\n * getLocaleFromPath(['en', 'fr']) // 'fr'\n * // pathname === '/about'\n * getLocaleFromPath(['en', 'fr']) // null\n * ```\n */\nexport function getLocaleFromPath(enabledLocales: string[]): string | null {\n if (!isBrowser) return null\n\n const path = window.location.pathname\n const segments = path.split('/').filter(Boolean)\n\n if (segments.length > 0) {\n const firstSegment = segments[0]\n if (firstSegment) {\n // Case-insensitive match, return canonical form from enabledLocales\n const match = findLocale(firstSegment, enabledLocales)\n if (match) {\n return match\n }\n }\n }\n\n return null\n}\n\n/**\n * Strict membership check: is `locale` present in `enabledLocales` in its\n * canonical form? Case-sensitive by design so that `zh-Hans` and `zh-hans`\n * are treated differently; use {@link detectLocale} when you want\n * case-insensitive lookup.\n *\n * @param locale - Candidate locale code.\n * @param enabledLocales - Canonical BCP 47 codes the site accepts.\n * @returns `true` if the exact code is in the list, `false` otherwise.\n *\n * @example\n * ```typescript\n * isValidLocale('fr', ['en', 'fr']) // true\n * isValidLocale('FR', ['en', 'fr']) // false (case sensitive)\n * ```\n */\nexport function isValidLocale(locale: string, enabledLocales: string[]): boolean {\n return enabledLocales.includes(locale)\n}\n","/**\n * Lynkow SDK Client\n * Main entry point for the SDK\n */\n\nimport type { LynkowConfig, LynkowClient, SiteConfig } from './types'\nimport type { InternalConfig } from './services/base'\nimport { ContentsService } from './services/contents'\nimport { CategoriesService } from './services/categories'\nimport { TagsService } from './services/tags'\nimport { PagesService } from './services/pages'\nimport { BlocksService } from './services/blocks'\nimport { FormsService } from './services/forms'\nimport { ReviewsService } from './services/reviews'\nimport { SiteService } from './services/site'\nimport { LegalService } from './services/legal'\nimport { CookiesService } from './services/cookies'\nimport { SeoService } from './services/seo'\nimport { PathsService } from './services/paths'\nimport { AnalyticsService } from './services/analytics'\nimport { ConsentService } from './services/consent'\nimport { BrandingService } from './services/branding'\nimport { EnhancementsService } from './services/enhancements'\nimport { MediaHelperService } from './services/media-helper'\nimport { SearchService } from './services/search'\n\n// Core modules\nimport { createCache, type Cache } from './core/cache'\nimport { createLogger, type Logger } from './core/logger'\nimport { createEventEmitter, type EventEmitter, type EventName, type LynkowEvents } from './utils/events'\nimport { isBrowser } from './core/environment'\nimport { detectLocale, setStoredLocale, isValidLocale } from './utils/locale'\n\n/**\n * Default Lynkow API base URL\n */\nconst DEFAULT_BASE_URL = 'https://api.lynkow.com'\n\n/**\n * Extended configuration for SDK v3\n */\nexport interface ClientConfig extends LynkowConfig {\n /**\n * Enable debug logging\n * @default false\n */\n debug?: boolean\n\n /**\n * Enable the SDK's in-memory response cache.\n * When disabled (default), every SDK call goes through `fetch()`,\n * letting your framework (Next.js, Nuxt, Astro) control caching\n * and revalidation end-to-end.\n * Set to `true` for browser SPAs that benefit from localStorage\n * caching between navigations.\n *\n * @default false\n *\n * @example\n * ```typescript\n * // Browser SPA: enable SDK cache for localStorage caching\n * const lynkow = createClient({\n * siteId: 'your-site-uuid',\n * cache: true,\n * })\n * ```\n */\n cache?: boolean\n}\n\n/**\n * Extended client interface for SDK v3\n */\nexport interface Client extends LynkowClient {\n /**\n * Current locale\n */\n readonly locale: string\n\n /**\n * Available locales from site configuration\n */\n readonly availableLocales: string[]\n\n /**\n * Client configuration (readonly)\n */\n readonly config: Readonly<{\n siteId: string\n baseUrl: string\n debug: boolean\n cache: boolean\n }>\n\n /**\n * Set the current locale\n * @param locale - New locale to use\n */\n setLocale(locale: string): void\n\n /**\n * Clear all cached data\n */\n clearCache(): void\n\n /**\n * Destroy the client and clean up resources\n */\n destroy(): void\n\n /**\n * Subscribe to an event\n * @param event - Event name\n * @param listener - Event listener\n * @returns Unsubscribe function\n */\n on<K extends EventName>(event: K, listener: (data: LynkowEvents[K]) => void): () => void\n\n /**\n * Alias for blocks (v3 naming)\n * @deprecated Use `globals` instead in v3\n */\n blocks: BlocksService\n\n /**\n * Global blocks service (v3 naming)\n */\n globals: BlocksService\n\n /**\n * Analytics service (browser-only)\n */\n analytics: AnalyticsService\n\n /**\n * Consent service (cookie consent management)\n */\n consent: ConsentService\n\n /**\n * Branding service (badge for free plan)\n */\n branding: BrandingService\n\n /**\n * Content enhancements service (code copy, etc.)\n */\n enhancements: EnhancementsService\n\n /**\n * Image transformation helpers.\n * Use these to build optimized image URLs for responsive images.\n */\n readonly media: MediaHelperService\n\n /**\n * Lynkow Instant Search service\n */\n search: SearchService\n}\n\n/**\n * Internal state for the client\n */\ninterface ClientState {\n locale: string\n availableLocales: string[]\n siteConfig: SiteConfig | null\n initialized: boolean\n}\n\n/**\n * Creates a Lynkow client instance (SDK v3)\n *\n * @param config - Client configuration\n * @returns Client instance with all services\n *\n * @example\n * ```typescript\n * const lynkow = createClient({\n * siteId: 'your-site-uuid',\n * locale: 'fr',\n * debug: true\n * })\n *\n * const posts = await lynkow.contents.list()\n * ```\n */\nexport function createClient(config: ClientConfig): Client {\n // Validation\n if (!config.siteId) {\n throw new Error('Lynkow SDK: siteId is required')\n }\n\n // Create core utilities\n const cache = config.cache === true ? createCache() : undefined\n const logger = createLogger({ debug: config.debug ?? false })\n const events = createEventEmitter()\n\n // Normalize configuration\n const normalizedBaseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, '')\n\n const internalConfig: InternalConfig = {\n siteId: config.siteId,\n baseUrl: normalizedBaseUrl,\n locale: config.locale,\n fetchOptions: config.fetchOptions || {},\n ...(cache ? { cache } : {}),\n }\n\n // Client state\n const state: ClientState = {\n locale: config.locale || 'fr',\n availableLocales: ['fr'],\n siteConfig: null,\n initialized: false,\n }\n\n // Create services\n const services = {\n contents: new ContentsService(internalConfig),\n categories: new CategoriesService(internalConfig),\n tags: new TagsService(internalConfig),\n pages: new PagesService(internalConfig),\n blocks: new BlocksService(internalConfig),\n forms: new FormsService(internalConfig),\n reviews: new ReviewsService(internalConfig),\n site: new SiteService(internalConfig),\n legal: new LegalService(internalConfig),\n cookies: new CookiesService(internalConfig),\n seo: new SeoService(internalConfig),\n paths: new PathsService(internalConfig),\n analytics: new AnalyticsService(internalConfig),\n consent: new ConsentService(internalConfig, events),\n branding: new BrandingService(internalConfig),\n enhancements: new EnhancementsService(),\n media: new MediaHelperService(),\n search: new SearchService(internalConfig),\n }\n\n /**\n * Update internal config locale for all services\n */\n function updateServicesLocale(locale: string): void {\n internalConfig.locale = locale\n }\n\n /**\n * Initialize client (fetch site config, detect locale)\n */\n async function initialize(): Promise<void> {\n if (state.initialized) return\n\n try {\n // Fetch site configuration\n const siteConfig = await services.site.getConfig()\n state.siteConfig = siteConfig\n const defaultLocale = siteConfig.defaultLocale || 'fr'\n state.availableLocales = siteConfig.enabledLocales || [defaultLocale]\n\n // Detect locale (browser only)\n if (isBrowser && !config.locale) {\n const detected = detectLocale(state.availableLocales, defaultLocale)\n state.locale = detected\n updateServicesLocale(detected)\n }\n\n state.initialized = true\n logger.debug('Client initialized', { locale: state.locale, availableLocales: state.availableLocales })\n\n // Initialize browser-only features\n if (isBrowser) {\n // Load and initialize the analytics tracker\n // The tracker handles consent checking internally via localStorage\n services.analytics.init()\n\n // Show consent banner if user hasn't made a choice yet\n if (!services.consent.hasConsented()) {\n services.consent.show()\n }\n\n // Inject branding badge if required (free plan)\n if (siteConfig.showBranding) {\n await services.branding.inject()\n }\n\n // Initialize content enhancements (code copy, etc.)\n services.enhancements.init()\n }\n\n events.emit('ready', undefined as void)\n } catch (error) {\n logger.error('Failed to initialize client', error)\n events.emit('error', error as Error)\n }\n }\n\n // Auto-initialize in browser\n // ALL DOM mutations are deferred to the next macrotask via setTimeout(0)\n // to avoid interfering with React concurrent rendering and client-side navigations\n if (isBrowser) {\n setTimeout(() => {\n services.enhancements.init()\n initialize()\n }, 0)\n }\n\n // Build the client object\n const client: Client = {\n // Services\n ...services,\n\n // v3 alias: globals = blocks\n globals: services.blocks,\n\n // Readonly config\n config: Object.freeze({\n siteId: config.siteId,\n baseUrl: normalizedBaseUrl,\n debug: config.debug ?? false,\n cache: config.cache === true,\n }),\n\n // Locale getter\n get locale(): string {\n return state.locale\n },\n\n // Available locales getter\n get availableLocales(): string[] {\n return [...state.availableLocales]\n },\n\n // Set locale\n setLocale(locale: string): void {\n if (!isValidLocale(locale, state.availableLocales)) {\n logger.warn(`Locale \"${locale}\" is not available. Available: ${state.availableLocales.join(', ')}`)\n return\n }\n\n if (locale === state.locale) return\n\n state.locale = locale\n updateServicesLocale(locale)\n\n // Save to localStorage (browser)\n if (isBrowser) {\n setStoredLocale(locale)\n }\n\n // Invalidate cache (content changes with locale)\n cache?.invalidate()\n\n // Emit event\n events.emit('locale-changed', locale)\n logger.debug('Locale changed to', locale)\n },\n\n // Clear cache\n clearCache(): void {\n cache?.invalidate()\n logger.debug('Cache cleared')\n },\n\n // Destroy client\n destroy(): void {\n services.analytics.destroy()\n services.consent.destroy()\n services.branding.destroy()\n services.enhancements.destroy()\n cache?.invalidate()\n events.removeAllListeners()\n logger.debug('Client destroyed')\n },\n\n // Event subscription\n on<K extends EventName>(event: K, listener: (data: LynkowEvents[K]) => void): () => void {\n return events.on(event, listener)\n },\n }\n\n return client\n}\n\n/**\n * Creates a Lynkow client instance (legacy naming)\n *\n * @deprecated Use `createClient` instead\n * @param config - Client configuration\n * @returns Client instance with all services\n *\n * @example\n * ```typescript\n * // Deprecated -- use createClient() instead:\n * const lynkow = createClient({ siteId: 'your-site-uuid', locale: 'fr' })\n * const posts = await lynkow.contents.list()\n * ```\n */\nexport function createLynkowClient(config: LynkowConfig): LynkowClient {\n // For backward compatibility, return the basic client without v3 features\n if (!config.siteId) {\n throw new Error('Lynkow SDK: siteId is required')\n }\n\n const internalConfig: InternalConfig = {\n siteId: config.siteId,\n baseUrl: (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, ''),\n locale: config.locale,\n fetchOptions: config.fetchOptions || {},\n }\n\n return {\n contents: new ContentsService(internalConfig),\n categories: new CategoriesService(internalConfig),\n tags: new TagsService(internalConfig),\n pages: new PagesService(internalConfig),\n blocks: new BlocksService(internalConfig),\n forms: new FormsService(internalConfig),\n reviews: new ReviewsService(internalConfig),\n site: new SiteService(internalConfig),\n legal: new LegalService(internalConfig),\n cookies: new CookiesService(internalConfig),\n seo: new SeoService(internalConfig),\n paths: new PathsService(internalConfig),\n search: new SearchService(internalConfig),\n }\n}\n","// Config\nexport type { LynkowConfig, LynkowClient } from './config'\n\n// Entities\nexport type {\n Content,\n ContentSummary,\n ContentBody,\n TipTapNode,\n TipTapMark,\n Author,\n FaqItem,\n JsonLdFaqPage,\n JsonLdArticle,\n StructuredDataAlternate,\n StructuredData,\n} from './content'\n\nexport type {\n Category,\n CategoryWithCount,\n CategoryDetail,\n CategoryTreeNode,\n} from './category'\n\nexport type { Tag } from './tag'\n\nexport type { ImageVariants } from './image'\n\nexport type { Page, PageSummary, PageSeo, Alternate } from './page'\n\nexport type { GlobalBlock } from './block'\n\nexport type { JsonLdNode, JsonLdNodeSource, JsonLdGraphConfig } from './json-ld'\n\nexport type {\n SchemaFieldType,\n SchemaField,\n SchemaFieldOption,\n SchemaFieldValidation,\n ContentSchema,\n} from './schema'\n\nexport type {\n Form,\n FormField,\n FormFieldType,\n FormFieldOption,\n FormFieldValidation,\n FormSettings,\n FormSubmitData,\n} from './form'\n\nexport type {\n Review,\n ReviewResponse,\n ReviewSettings,\n ReviewSubmitData,\n} from './review'\n\nexport type { SiteConfig, I18nConfig, LocaleInfo } from './site'\n\nexport type {\n LegalDocument,\n LegalListResponse,\n LegalDocumentResponse,\n} from './legal'\n\nexport type {\n CookieConfig,\n CookieCategory,\n CookieTexts,\n CookiePreferences,\n ThirdPartyScript,\n} from './cookie'\n\nexport type { Path, Redirect } from './path'\n\n// Responses\nexport type {\n PaginationMeta,\n PaginatedResponse,\n ContentsListResponse,\n CategoriesListResponse,\n CategoryTreeResponse,\n CategoryDetailResponse,\n TagsListResponse,\n PagesListResponse,\n SiteConfigResponse,\n GlobalBlockResponse,\n ReviewsListResponse,\n FormSubmitResponse,\n ReviewSubmitResponse,\n ConsentLogResponse,\n PathsListResponse,\n ContentResolveResponse,\n CategoryResolveResponse,\n ResolveResponse,\n} from './response'\n\n// Filters\nexport type {\n BaseRequestOptions,\n PaginationOptions,\n SortOptions,\n ContentsFilters,\n CategoryOptions,\n ReviewsFilters,\n SubmitOptions,\n} from './filters'\n\n// Errors\nexport type { ErrorCode, ApiErrorDetail } from './error'\n\n// Type guards\nimport type { ResolveResponse, ContentResolveResponse, CategoryResolveResponse } from './response'\n\n/**\n * Checks if a resolution response is a content\n *\n * @example\n * ```typescript\n * // Resolve a URL path and render the appropriate template\n * const resolved = await lynkow.paths.resolve('/blog/my-article')\n * if (isContentResolve(resolved)) {\n * renderArticle(resolved.data) // TypeScript narrows to ContentResolveResponse\n * }\n * ```\n */\nexport function isContentResolve(\n response: ResolveResponse\n): response is ContentResolveResponse {\n return response.type === 'content'\n}\n\n/**\n * Checks if a resolution response is a category\n *\n * @example\n * ```typescript\n * // Resolve a URL path and render the appropriate template\n * const resolved = await lynkow.paths.resolve('/blog/tutorials')\n * if (isCategoryResolve(resolved)) {\n * renderCategoryPage(resolved.data) // TypeScript narrows to CategoryResolveResponse\n * }\n * ```\n */\nexport function isCategoryResolve(\n response: ResolveResponse\n): response is CategoryResolveResponse {\n return response.type === 'category'\n}\n","/**\n * Render a resolved JSON-LD `@graph` as a single `<script type=\"application/ld+json\">`\n * tag ready to inject into a page `<head>`.\n *\n * The input is the array returned by the Lynkow public API at\n * `content.structuredData.graph` (for articles) or `page.structuredData.graph`\n * (for site blocks of type page). Each entry is already a fully-formed\n * schema.org object with `@context`, `@id`, and `@type`. This helper strips\n * the per-node `@context` and wraps the array in a top-level `@context` +\n * `@graph` to keep the emitted script as compact as possible.\n *\n * No HTML escaping is performed on the JSON body itself, but `</script>`\n * sequences that would otherwise break the enclosing tag are guarded against\n * via a Unicode-safe replacement.\n *\n * @param nodes - Array of JSON-LD node objects. `null`, `undefined`, or an\n * empty array returns an empty string so you can safely\n * spread the result into server-rendered HTML without\n * conditional branches.\n * @returns Serialized `<script>` tag string. Empty string when there are\n * no nodes to render.\n * @throws Never throws. The function is designed for server components\n * and is safe to call with `undefined` or malformed payloads\n * (values that cannot be `JSON.stringify`-ed, e.g. circular\n * references, will throw from the underlying `JSON.stringify`\n * call - caller's responsibility, not a library behaviour).\n *\n * @example\n * ```tsx\n * // Next.js App Router (server component)\n * import { createClient, renderJsonLdGraph } from 'lynkow'\n *\n * const client = createClient({ siteId: process.env.LYNKOW_SITE_ID! })\n *\n * export default async function ArticlePage({ params }: { params: { slug: string } }) {\n * const article = await client.contents.getBySlug(params.slug)\n * return (\n * <>\n * <div\n * dangerouslySetInnerHTML={{\n * __html: renderJsonLdGraph(article.structuredData?.graph),\n * }}\n * />\n * <h1>{article.title}</h1>\n * <article dangerouslySetInnerHTML={{ __html: article.body }} />\n * </>\n * )\n * }\n * ```\n */\nexport function renderJsonLdGraph(nodes: object[] | null | undefined): string {\n if (!nodes || nodes.length === 0) return ''\n\n const graph = nodes.map((node) => {\n // Strip per-node @context so the top-level @context applies once.\n const { ['@context']: _ignoredContext, ...rest } = node as Record<string, unknown>\n return rest\n })\n\n const payload = JSON.stringify({\n '@context': 'https://schema.org',\n '@graph': graph,\n })\n\n // Guard against a `</script>` sequence embedded inside a string value that\n // would otherwise terminate the enclosing <script> tag.\n const escaped = payload.replace(/<\\/(script)/gi, '<\\\\/$1')\n\n return `<script type=\"application/ld+json\">${escaped}</script>`\n}\n"]}