/**
* The client is a frozen plain value: create one with `createClient` and
* pass it as the first argument to the functions in the resource modules
* (`domains`, `vps`, `jobs`, ...).
*
* Authentication is a single Bearer credential: either an API key created
* in the account panel (`ird_...`) or an account JWT from the device login
* flow — pass whichever as `apiKey`. A client without `apiKey` can call the
* anonymous endpoints (domain check/example, device login).
*/
import { API_BASE_URL } from './config.js';
import { attachGate, type Gate, type GateRules } from './gate.js';
export type SleepFn = (ms: number) => Promise<void>;
export type ClientOptions = Readonly<{
/** API key (`ird_...`) or account JWT, sent as a Bearer token. */
apiKey?: string;
/** Base URL override; defaults to https://api.ird.ro. */
apiUrl?: string;
/** Extra headers merged over the computed ones on every request. */
headers?: Readonly<Record<string, string>>;
/** Per-request timeout, applied with AbortSignal.timeout. */
timeoutMs?: number;
/** Transport override (tests, proxies, instrumentation); defaults to global fetch. */
fetch?: typeof fetch;
/** Delay used by the polling helpers; defaults to setTimeout. */
sleep?: SleepFn;
}>;
export type Client = Readonly<{
apiKey?: string;
apiUrl: string;
headers: Readonly<Record<string, string>>;
timeoutMs?: number;
fetch: typeof fetch;
sleep: SleepFn;
/** Present on clients created with `createGatedClient`. */
gate?: Gate;
}>;
export const defaultSleep: SleepFn = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
/** Create an ird API client. Synchronous; makes no requests. */
export const createClient = (options: ClientOptions = {}): Client =>
Object.freeze({
apiKey: options.apiKey,
apiUrl: (options.apiUrl?.trim() || API_BASE_URL).replace(/\/+$/, ''),
headers: Object.freeze({ ...options.headers }),
timeoutMs: options.timeoutMs,
fetch: options.fetch ?? globalThis.fetch,
sleep: options.sleep ?? defaultSleep,
});
/**
* Create a client with a domain gate: `deniedDomains` are names/patterns
* (`"example.ro"`, `"*.example.ro"`) this client must never mutate, and
* `allowedDomains`, when set, are the ONLY names it may mutate (an empty
* list denies every domain mutation; deny wins over allow).
*
* Requires `apiKey`: the account's registrations are fetched up front to
* map registration ids to domain names, so every domain-touching mutation
* is checked client-side before any request is made. Violations throw a
* `domainDenied` error. See `gate.ts`.
*/
export const createGatedClient = async (options: ClientOptions & GateRules): Promise<Client> =>
attachGate(createClient(options), options);