Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | 13x 13x 13x 4x 2x 23x 23x 23x 25x 20x 20x 23x 25x 25x 25x 25x 5x 25x 25x 2x 2x 2x 2x 23x 5x 5x 18x 18x 14x 18x 2x 14x 14x 5x 5x 5x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x | import { TtlCache } from "./cache.js";
import type { ScanResult, ScanPending, ScanEnqueued, RankingsResponse } from "./types.js";
export class IsAgentReadyApiError extends Error {
constructor(
message: string,
readonly status: number,
readonly details?: unknown,
) {
super(message);
}
}
const sleep = (ms: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, ms));
interface ClientOptions {
maxRetries?: number;
cacheTtlMs?: number;
}
export class IsAgentReadyClient {
private readonly baseUrl: string;
private readonly cache: TtlCache;
private readonly maxRetries: number;
constructor(baseUrl = "https://isagentready.com", options: ClientOptions = {}) {
this.baseUrl = baseUrl.replace(/\/$/, "");
this.cache = new TtlCache(options.cacheTtlMs ?? 120_000);
this.maxRetries = options.maxRetries ?? 3;
}
private async request<T>(
method: "GET" | "POST",
path: string,
body?: Record<string, unknown>,
cacheKey?: string,
): Promise<T> {
if (cacheKey) {
const cached = this.cache.get<T>(cacheKey);
if (cached !== undefined) return cached;
}
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
const url = `${this.baseUrl}${path}`;
const headers = new Headers({ "Content-Type": "application/json" });
const init: RequestInit = { method, headers };
if (body) {
init.body = JSON.stringify(body);
}
const response = await fetch(url, init);
if (response.status === 429 && attempt < this.maxRetries) {
const retryAfter = response.headers.get("retry-after");
const delayMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : Math.pow(2, attempt) * 1000;
await sleep(delayMs);
continue;
}
if (!response.ok) {
const errorBody = await response.json().catch(() => null);
throw new IsAgentReadyApiError(
(errorBody as { error?: string } | null)?.error ?? `HTTP ${response.status}`,
response.status,
errorBody,
);
}
const data = (await response.json()) as T;
if (cacheKey) {
this.cache.set(cacheKey, data);
}
return data;
}
throw new Error("Request failed after retries");
}
async getScanResults(domain: string): Promise<ScanResult | ScanPending> {
const encoded = encodeURIComponent(domain);
return this.request<ScanResult | ScanPending>(
"GET",
`/api/v1/scan/${encoded}`,
undefined,
`scan:${domain}`,
);
}
async createScan(url: string): Promise<ScanEnqueued | ScanResult> {
this.cache.invalidate("scan:");
this.cache.invalidate("rankings:");
return this.request<ScanEnqueued | ScanResult>("POST", "/api/v1/scan", { url });
}
async getRankings(params: {
page?: number;
per_page?: number;
grade_range?: "high" | "mid" | "low";
search?: string;
sort?: "score_desc" | "score_asc" | "domain" | "newest";
} = {}): Promise<RankingsResponse> {
const searchParams = new URLSearchParams();
if (params.page !== undefined) searchParams.set("page", String(params.page));
if (params.per_page !== undefined) searchParams.set("per_page", String(params.per_page));
if (params.grade_range) searchParams.set("grade_range", params.grade_range);
if (params.search) searchParams.set("search", params.search);
if (params.sort) searchParams.set("sort", params.sort);
const query = searchParams.toString();
const path = `/api/v1/rankings${query ? `?${query}` : ""}`;
const cacheKey = `rankings:${query}`;
return this.request<RankingsResponse>("GET", path, undefined, cacheKey);
}
}
|