declare global { interface Window { ezyAiBoot?: { restUrl: string; nonce: string; siteUrl: string; /** WordPress Site Icon URL, empty when the admin has not set one. */ siteIcon?: string; pluginVersion: string; connected: boolean; connectionStatus: string; connectUrl: string; widgetsUrl: string; }; } } function boot() { const b = window.ezyAiBoot; if (!b) throw new Error('ezyAiBoot is not injected — check dashboard.php wp_add_inline_script'); return b; } export function buildRestUrl(restUrl: string, path: string): string { const base = restUrl.replace(/\/$/, ''); const queryIndex = path.indexOf('?'); if (queryIndex === -1 || !base.includes('?')) { return base + path; } return base + path.slice(0, queryIndex) + '&' + path.slice(queryIndex + 1); } async function request(path: string, init: RequestInit = {}): Promise { const { restUrl, nonce } = boot(); const url = buildRestUrl(restUrl, path); const res = await fetch(url, { credentials: 'same-origin', ...init, headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce, ...(init.headers || {}), }, }); if (!res.ok) { const text = await res.text(); throw new Error(`${res.status} ${res.statusText}: ${text}`); } return (await res.json()) as T; } export type Period = '24h' | '7d' | '30d' | '90d'; export interface AttributionRow { agent_type: string; count: number; percentage: number; } export interface TimelinePoint { timestamp: string; score: number; gpt: number; claude: number; google: number; perplexity: number; bing: number; meta: number; xai: number; deepseek: number; mistral: number; cohere: number; apple: number; amazon: number; bytedance: number; other: number; human: number; } export interface CrawlerLogRow { timestamp: string; url: string; url_path: string; user_agent: string; agent_type: string | null; agent_name: string | null; is_crawler: boolean; is_browser: boolean; visit_class?: 'crawler' | 'user_like' | 'normal_user' | 'unknown' | null; classification?: 'ai_agent' | 'human_browser' | 'human_referral' | null; confidence: number; signature_verified: boolean; signature_confidence?: number; method: string | null; } export interface ProviderBreakdown { crawler: number; userLike: number; unknown: number; } export interface ClaudeExtrapolated { crawlerFootprint: number; humanReachEstimate: number; methodology: string; multiplier: number; humanReachRatio: number; } export interface HumanLogRow { timestamp: string; source: string; url: string; ip: string; device?: string; browser?: string; os?: string; } export interface TopPageRow { url: string; hits: number; ai_hits: number; human_hits: number; } export interface OverviewResponse { success: boolean; period: Period; mode: 'standalone' | 'connected'; /** * Visitor analytics is opt-in. When false the dashboard is empty because we */ tracking_enabled?: boolean; summary: { averageScore: number; peakScore: number; lowScore: number; trendPercentage: number; }; cards: { visibility: { score: number; trend: number; title: string; unit: string }; aiVisits: { total: number; totalMeasured?: number; totalWithEstimate?: number; wordpress: number; jsConnector: number; cloudflare: number; attribution: AttributionRow[]; claudeBreakdown?: ProviderBreakdown; gptBreakdown?: ProviderBreakdown; claudeMeasured?: ProviderBreakdown; claudeExtrapolated?: ClaudeExtrapolated; trend: number; previous: number; title: string; }; normalUsers: { total: number; previous: number; trend: number; topCountries: unknown[]; deferred: boolean; }; botActivity: { lastCrawledAt: string | null; botName: string }; citations: { locked: boolean; total?: number; new?: number; topPages?: unknown[] }; benchmarking: { locked: boolean }; }; visibilityTimeline: TimelinePoint[]; features: { radar: CrawlerLogRow[]; pagesByEngine: TopPageRow[]; trackedPrompts: unknown[]; humanLogs?: HumanLogRow[]; humanReferralSources?: Array<{ source: string; count: number }>; }; } export interface CloudCardResponse { success: boolean; locked: boolean; reason?: string; card: string; data?: T; } export interface ScoreResponse { success: boolean; score: number; breakdown: { readiness: number; traffic: number; coverage: number }; } export interface RecommendationItem { key: string; label: string; description: string; done: boolean; url: string; external: boolean; } export interface RecommendationsResponse { success: boolean; items: RecommendationItem[]; completed: number; total: number; } export const api = { bootData: boot, overview: (period: Period) => request(`/analytics/overview?period=${period}`), crawlerLogs: (limit = 50) => request<{ success: boolean; items: CrawlerLogRow[] }>(`/analytics/crawler-logs?limit=${limit}`), topPages: (period: Period, limit = 10, aiOnly = false) => request<{ success: boolean; items: TopPageRow[] }>( `/analytics/top-pages?period=${period}&limit=${limit}&ai_only=${aiOnly ? 1 : 0}`, ), score: () => request('/score'), getSettings: () => request<{ success: boolean; settings: { enable_analytics: boolean; retention_days: number }; }>('/settings'), updateSettings: (settings: { enable_analytics?: boolean; retention_days?: number }) => request('/settings', { method: 'POST', body: JSON.stringify(settings) }), cloudCard: (card: 'competitors' | 'next-actions' | 'wins' | 'prompts', period: Period) => request>(`/cloud/${card}?period=${period}`), recommendations: () => request('/recommendations'), };