UNPKG

@ensnode/ensrainbow-sdk

Version:

ENSRainbow SDK for interacting with the ENSRainbow API.

288 lines (285 loc) • 9.68 kB
// src/index.ts import { buildEnsRainbowClientLabelSet as buildEnsRainbowClientLabelSet2 } from "@ensnode/ensnode-sdk"; // src/client.ts import { parseLabelHashOrEncodedLabelHash } from "enssdk"; import { buildEnsRainbowClientLabelSet, LruCache } from "@ensnode/ensnode-sdk"; // src/consts.ts var DEFAULT_ENSRAINBOW_URL = "https://api.ensrainbow.io"; var StatusCode = { Success: "success", Error: "error" }; var ErrorCode = { BadRequest: 400, NotFound: 404, ServerError: 500, ServiceUnavailable: 503 }; // src/client.ts var EnsRainbowHttpError = class extends Error { name = "EnsRainbowHttpError"; /** * The HTTP status code returned by the ENSRainbow service. */ status; /** * The HTTP status text returned by the ENSRainbow service, if any. */ statusText; constructor(message, status, statusText = "") { super(message); this.status = status; this.statusText = statusText; } }; var EnsRainbowApiClient = class _EnsRainbowApiClient { options; cache; clientLabelSetSearchParams; static DEFAULT_CACHE_CAPACITY = 1e3; /** * Create default client options. * * @returns default options */ static defaultOptions() { return { endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL), cacheCapacity: _EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY, clientLabelSet: buildEnsRainbowClientLabelSet() }; } constructor(options = {}) { const { clientLabelSet: optionsClientLabelSet, ...rest } = options; const defaultOptions = _EnsRainbowApiClient.defaultOptions(); const copiedLabelSet = buildEnsRainbowClientLabelSet( optionsClientLabelSet?.labelSetId, optionsClientLabelSet?.labelSetVersion ); this.options = { ...defaultOptions, ...rest, clientLabelSet: copiedLabelSet }; this.cache = new LruCache( this.options.cacheCapacity ); this.clientLabelSetSearchParams = new URLSearchParams(); if (this.options.clientLabelSet?.labelSetId !== void 0) { this.clientLabelSetSearchParams.append( "label_set_id", this.options.clientLabelSet.labelSetId ); } if (this.options.clientLabelSet?.labelSetVersion !== void 0) { this.clientLabelSetSearchParams.append( "label_set_version", this.options.clientLabelSet.labelSetVersion.toString() ); } } /** * Attempt to [heal](https://ensnode.io/ensrainbow/concepts/glossary#heal) a labelHash to its original label. * * Note on returned labels: ENSRainbow returns labels exactly as they are * represented in source rainbow table data. This means: * * - Labels may or may not be ENS-normalized * - Labels can contain any valid string, including dots, null bytes, or be empty * - Clients should handle all possible string values appropriately * * @param labelHash - A labelHash to heal, either as a strict `LabelHash`, an `EncodedLabelHash` * (bracket-enclosed), or any string that can be normalized (missing `0x` prefix, uppercase hex * chars, or 63-char hex are all accepted and normalized automatically). * @returns a `HealResponse` indicating the result of the request and the healed label if successful. * Returns a `HealBadRequestError` if the input cannot be normalized to a valid labelHash. * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, * CORS violations, or Invalid URLs * @example * ```typescript * const response = await client.heal( * "0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc" * ); * * console.log(response); * * // Output: * // { * // status: "success", * // label: "vitalik" * // } * * const notFoundResponse = await client.heal( * "0xf64dc17ae2e2b9b16dbcb8cb05f35a2e6080a5ff1dc53ac0bc48f0e79111f264" * ); * * console.log(notFoundResponse); * * // Output: * // { * // status: "error", * // error: "Label not found", * // errorCode: 404 * // } * ``` */ async heal(labelHash) { let normalizedLabelHash; try { normalizedLabelHash = parseLabelHashOrEncodedLabelHash(labelHash); } catch (error) { return { status: StatusCode.Error, error: error instanceof Error ? error.message : String(error), errorCode: ErrorCode.BadRequest }; } const cachedResult = this.cache.get(normalizedLabelHash); if (cachedResult) return cachedResult; const url = new URL(`/v1/heal/${normalizedLabelHash}`, this.options.endpointUrl); this.clientLabelSetSearchParams.forEach((value, key) => { url.searchParams.append(key, value); }); const response = await fetch(url); const healResponse = await response.json(); if (isCacheableHealResponse(healResponse)) { this.cache.set(normalizedLabelHash, healResponse); } return healResponse; } /** * Get Count of Healable Labels * * @returns a `CountResponse` indicating the result and the timestamp of the request and the * number of healable labels if successful * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, * CORS violations, or Invalid URLs * @example * * const response = await client.count(); * * console.log(response); * * // { * // "status": "success", * // "count": 133856894, * // "timestamp": "2024-01-30T11:18:56Z" * // } * */ async count() { const response = await fetch(new URL("/v1/labels/count", this.options.endpointUrl)); return response.json(); } /** * * Simple verification that the service is running, either in your local setup or for the * provided hosted instance. * @returns a status of ENS Rainbow service * @example * * const response = await client.health(); * * console.log(response); * * // { * // "status": "ok", * // } */ async health() { const response = await fetch(new URL("/health", this.options.endpointUrl)); if (!response.ok) { throw new EnsRainbowHttpError( `ENSRainbow health check failed (HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""})`, response.status, response.statusText ); } return response.json(); } /** * Check whether the ENSRainbow service is ready (database is downloaded, validated, and open). * * Unlike {@link EnsRainbowApiClient.health}, which is a pure liveness probe that succeeds as soon * as the HTTP server is accepting requests, `ready()` only resolves once the service has finished * bootstrapping its database. Clients that require a usable database (e.g. ENSIndexer) should * poll this method instead of `health()` during startup. * * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status. The thrown * error carries the HTTP `status` so callers can distinguish the retryable bootstrap case * (`503 Service Unavailable`) from likely-non-retryable misconfiguration / server failures * (e.g. `404`, `500`) and abort retries early in the latter cases. * @throws Network/fetch errors (DNS, ECONNREFUSED, etc.) propagate as their original error * type and should generally remain retryable, since they are common during cold start before * the ENSRainbow HTTP server has bound its port. */ async ready() { const response = await fetch(new URL("/ready", this.options.endpointUrl)); if (!response.ok) { const statusSuffix = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`; if (response.status === 503) { throw new EnsRainbowHttpError( `ENSRainbow readiness check: service not ready yet (${statusSuffix})`, response.status, response.statusText ); } throw new EnsRainbowHttpError( `ENSRainbow readiness check failed (${statusSuffix}). This usually indicates a non-readiness issue (e.g. wrong base URL, misrouting, or a server error).`, response.status, response.statusText ); } return response.json(); } /** * Get the public configuration of the ENSRainbow service. * * @throws {EnsRainbowHttpError} if the service responds with a non-2xx status. */ async config() { const response = await fetch(new URL("/v1/config", this.options.endpointUrl)); if (!response.ok) { throw new EnsRainbowHttpError( `Failed to fetch ENSRainbow config: HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`, response.status, response.statusText ); } return response.json(); } /** * Get a copy of the current client options. * * @returns a copy of the current client options. */ getOptions() { const deepCopy = { cacheCapacity: this.options.cacheCapacity, endpointUrl: new URL(this.options.endpointUrl.href), clientLabelSet: this.options.clientLabelSet ? { ...this.options.clientLabelSet } : void 0 }; return Object.freeze(deepCopy); } }; var isHealError = (response) => { return response.status === StatusCode.Error; }; var isCacheableHealResponse = (response) => { if (response.status === StatusCode.Success) return true; return response.errorCode !== ErrorCode.ServerError && response.errorCode !== ErrorCode.ServiceUnavailable; }; export { DEFAULT_ENSRAINBOW_URL, EnsRainbowApiClient, EnsRainbowHttpError, ErrorCode, StatusCode, buildEnsRainbowClientLabelSet2 as buildEnsRainbowClientLabelSet, isCacheableHealResponse, isHealError }; //# sourceMappingURL=index.js.map