@bsv/sdk
Version:
BSV Blockchain Software Development Kit
79 lines • 3.45 kB
JavaScript
import { LookupResolver } from '../overlay-tools/index.js';
import { StorageUtils } from './index.js';
import PushDrop from '../script/templates/PushDrop.js';
import Transaction from '../transaction/Transaction.js';
import { Hash, Utils } from '../primitives/index.js';
export class StorageDownloader {
networkPreset = 'mainnet';
constructor(config) {
this.networkPreset = config?.networkPreset;
}
/**
* Resolves the UHRP URL to a list of HTTP URLs where content can be downloaded.
* @param uhrpUrl The UHRP URL to resolve.
* @returns A promise that resolves to an array of HTTP URLs.
*/
async resolve(uhrpUrl) {
// Use UHRP lookup service
const lookupResolver = new LookupResolver({ networkPreset: this.networkPreset });
const response = await lookupResolver.query({ service: 'ls_uhrp', query: { uhrpUrl } });
if (response.type !== 'output-list') {
throw new Error('Lookup answer must be an output list');
}
const decodedResults = [];
const currentTime = Math.floor(Date.now() / 1000);
for (let i = 0; i < response.outputs.length; i++) {
const tx = Transaction.fromBEEF(response.outputs[i].beef);
const { fields } = PushDrop.decode(tx.outputs[response.outputs[i].outputIndex].lockingScript);
const expiryTime = new Utils.Reader(fields[3]).readVarIntNum();
if (expiryTime < currentTime) {
continue;
}
decodedResults.push(Utils.toUTF8(fields[2]));
}
return decodedResults;
}
/**
* Downloads the content from the UHRP URL after validating the hash for integrity.
* @param uhrpUrl The UHRP URL to download.
* @returns A promise that resolves to the downloaded content.
*/
async download(uhrpUrl) {
if (!StorageUtils.isValidURL(uhrpUrl)) {
throw new Error('Invalid parameter UHRP url');
}
const hash = StorageUtils.getHashFromURL(uhrpUrl);
const downloadURLs = await this.resolve(uhrpUrl);
if (!Array.isArray(downloadURLs) || downloadURLs.length === 0) {
throw new Error('No one currently hosts this file!');
}
for (let i = 0; i < downloadURLs.length; i++) {
try {
// The url is fetched
const result = await fetch(downloadURLs[i], { method: 'GET' });
// If the request fails, continue to the next url
if (!result.ok || result.status >= 400) {
continue;
}
const body = await result.arrayBuffer();
// The body is loaded into a number array
const content = [...new Uint8Array(body)];
const contentHash = Hash.sha256(content);
for (let i = 0; i < contentHash.length; ++i) {
if (contentHash[i] !== hash[i]) {
throw new Error('Value of content does not match hash of the url given');
}
}
return {
data: content,
mimeType: result.headers.get('Content-Type')
};
}
catch (error) {
continue;
}
}
throw new Error(`Unable to download content from ${uhrpUrl}`);
}
}
//# sourceMappingURL=StorageDownloader.js.map