@ar.io/sdk
Version:
[](https://codecov.io/gh/ar-io/ar-io-sdk)
170 lines (169 loc) • 6.56 kB
JavaScript
import { Logger } from '../logger.js';
export class HB {
url;
processId;
isHyperBeamCompatible;
checkHyperBeamPromise;
logger;
hbTimeoutMs;
constructor(config) {
this.url = config.url;
this.processId = config.processId;
this.logger = config.logger ?? Logger.default;
this.hbTimeoutMs = config.hbTimeoutMs ?? 5000;
this.isHyperBeamCompatible = undefined;
this.checkHyperBeamPromise = this.checkHyperBeamCompatibility();
}
/**
* fetches the meta data for the process
*
* @returns The meta data for the process
*
* @example
* const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
* const meta = await hyperbeam.meta();
* console.log(meta);
*/
async meta() {
const url = new URL(`${this.url}/${this.processId}~process@1.0/meta`);
return this.fetchHyperbeamPath({
path: url.toString(),
});
}
/**
* calls the process device /now function, which evaluates the current process state pulling new messages
* to get the latest state
*
* @param path - The path to the hb state
* @param json - Whether to return the result as JSON, defaults to true
* @returns The result of the compute operation
*
* @example
* const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
* const result = await hyperbeam.now({ path: 'balances/QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ' });
* console.log(result);
*/
async now({ path, json = false, }) {
return this.fetchHyperbeamPath({
path: `${this.url}/${this.processId}~process@1.0/now/${path}`,
json,
});
}
/**
* calls the process device /compute function, which uses the currently evaluated state in the node
*
* @param path - The path to the compute resource
* @param json - Whether to return the result as JSON, defaults to true
* @returns The result of the compute operation
*
* @example
* const hyperbeam = new Hyperbeam({ url: 'https://hyperbeam.ario.permaweb.services', processId: 'qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE' });
* const result = await hyperbeam.compute({ path: 'balances/QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ' });
* console.log(result);
*/
async compute({ path, json = true, }) {
return this.fetchHyperbeamPath({
path: `${this.url}/${this.processId}~process@1.0/compute/${path}`,
json,
});
}
/**
* Checks if the process is HyperBeam compatible and caches the result.
*
* @returns {Promise<boolean>} True if the process is HyperBeam compatible, false otherwise.
*/
async checkHyperBeamCompatibility({ minSlot, } = {}) {
// refetch if min slot is provided
if (minSlot !== undefined) {
this.isHyperBeamCompatible = undefined;
this.checkHyperBeamPromise = undefined;
}
if (this.checkHyperBeamPromise !== undefined) {
return this.checkHyperBeamPromise;
}
if (this.isHyperBeamCompatible !== undefined) {
return Promise.resolve(this.isHyperBeamCompatible);
}
const result = fetch(
// use /now to force a refresh of the cache state, then compute when calling it for keys
`${this.url.toString()}/${this.processId}~process@1.0/now`, {
method: 'HEAD',
signal: AbortSignal.timeout(this.hbTimeoutMs),
})
.then(async (res) => {
if (res.ok) {
if (minSlot !== undefined) {
const slotRes = await this.compute({
path: 'at-slot',
json: false,
});
const slot = Number(slotRes);
if (slot < minSlot) {
return false;
}
}
this.isHyperBeamCompatible = true;
return true;
}
this.isHyperBeamCompatible = false;
return false;
})
.catch((error) => {
this.logger.error('Failed to check HyperBeam compatibility', {
cause: error,
});
this.isHyperBeamCompatible = false;
return false;
});
this.checkHyperBeamPromise = result;
return result;
}
async fetchHyperbeamPath({ path, json = true, }) {
try {
const url = new URL(path);
if (json) {
this.logger.debug('Fetching path as JSON', { path });
/**
* This is the (current) way to access data as json
* the old way is /~json@1.0/serialize path
*/
url.searchParams.set('require-codec', 'application/json');
url.searchParams.set('accept-bundle', 'true');
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to fetch path as JSON: ${res.statusText}`);
}
const jsonResult = await res
.json()
.then((json) => json)
.catch((error) => {
this.logger.error('Failed to parse JSON', {
cause: error,
});
throw new Error(`Received response but failed to parse JSON: ${error.message}`);
});
if (typeof jsonResult !== 'object' ||
jsonResult === null ||
!('body' in jsonResult)) {
throw new Error('Response body missing in JSON response');
}
return jsonResult.body;
}
else {
this.logger.debug('Fetching path as text', { path });
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to fetch path: ${res.statusText}`);
}
const body = await res.text();
return body;
}
}
catch (error) {
this.logger.error('Failed to fetch path as JSON', {
cause: error,
});
throw error;
}
}
}