@ensnode/ensrainbow-sdk
Version:
ENSRainbow SDK for interacting with the ENSRainbow API.
288 lines (285 loc) • 9.68 kB
JavaScript
// 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