import { env } from "$env/dynamic/private"; export const DEFAULT_INDEXNOW_KEY = "d76fae270607bfc761b35c14f3182c9e"; export const DEFAULT_INDEXNOW_HOST = "tangled.org"; export const INDEXNOW_ENDPOINT = "https://api.indexnow.org/indexnow"; export interface IndexNowPayload { host: string; key: string; keyLocation: string; urlList: string[]; } export interface IndexNowOptions { host?: string; key?: string; keyLocation?: string; fetch?: typeof globalThis.fetch; } export interface IndexNowResult { success: boolean; status?: number; error?: string; } /** * Submits a URL or list of URLs to IndexNow (Bing, Yandex, Naver, Seznam, etc.). * Guarantees silent error handling: network or API errors are logged to console * without throwing, ensuring user or client flows are never disrupted. */ export async function submitToIndexNow( urls: string | string[], options: IndexNowOptions = {} ): Promise { const rawUrls = Array.isArray(urls) ? urls : [urls]; const validUrls = rawUrls.filter((u) => typeof u === "string" && u.trim().length > 0); if (validUrls.length === 0) { return { success: false, error: "No URLs provided" }; } const host = options.host || env.INDEXNOW_HOST || DEFAULT_INDEXNOW_HOST; const key = options.key || env.INDEXNOW_KEY || DEFAULT_INDEXNOW_KEY; const keyLocation = options.keyLocation || `https://${host}/${key}.txt`; const customFetch = options.fetch ?? globalThis.fetch; // IndexNow allows maximum 10,000 URLs per request const MAX_BATCH_SIZE = 10000; const urlBatches: string[][] = []; for (let i = 0; i < validUrls.length; i += MAX_BATCH_SIZE) { urlBatches.push(validUrls.slice(i, i + MAX_BATCH_SIZE)); } let allSuccess = true; let lastStatus: number | undefined; for (const batch of urlBatches) { const payload: IndexNowPayload = { host, key, keyLocation, urlList: batch }; try { const res = await customFetch(INDEXNOW_ENDPOINT, { method: "POST", headers: { "content-type": "application/json; charset=utf-8" }, body: JSON.stringify(payload) }); lastStatus = res.status; // 200 = OK, 202 = Accepted (key validation pending) if (!res.ok && res.status !== 202) { const body = await res.text().catch(() => ""); console.warn( `[IndexNow] Non-OK response (${res.status}): ${body || res.statusText}` ); allSuccess = false; } } catch (err) { console.warn("[IndexNow] Failed to submit URLs:", err); allSuccess = false; } } return { success: allSuccess, status: lastStatus, error: allSuccess ? undefined : `IndexNow submission encountered errors (HTTP ${lastStatus ?? "unknown"})` }; } /** * Convenience helper to ping IndexNow when a new profile is created. */ export async function notifyProfileCreated( handle: string, options?: IndexNowOptions ): Promise { const host = options?.host || DEFAULT_INDEXNOW_HOST; return submitToIndexNow(`https://${host}/${handle}`, options); } /** * Convenience helper to ping IndexNow when a new repository is created. */ export async function notifyRepoCreated( handle: string, repoSlug: string, options?: IndexNowOptions ): Promise { const host = options?.host || DEFAULT_INDEXNOW_HOST; return submitToIndexNow(`https://${host}/${handle}/${repoSlug}`, options); }