UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

94 lines 4.05 kB
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'; lookupResolver; constructor(config) { this.networkPreset = config?.networkPreset ?? 'mainnet'; this.lookupResolver = new LookupResolver({ networkPreset: this.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 response = await this.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 expected = Utils.toHex(hash); 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 || result.body == null) { continue; } const reader = result.body.getReader(); const hashStream = new Hash.SHA256(); const chunks = []; let totalLength = 0; while (true) { const { done, value } = await reader.read(); if (done) break; hashStream.update(Array.from(value)); chunks.push(value); totalLength += value.length; } const digest = Utils.toHex(hashStream.digest()); if (digest !== expected) { throw new Error('Data integrity error: value of content does not match hash of the url given'); } const data = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { data.set(chunk, offset); offset += chunk.length; } return { data, mimeType: result.headers.get('Content-Type') }; } catch (error) { continue; } } throw new Error(`Unable to download content from ${uhrpUrl}`); } } //# sourceMappingURL=StorageDownloader.js.map