@bsv/sdk
Version:
BSV Blockchain Software Development Kit
100 lines • 4.49 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StorageDownloader = void 0;
const index_js_1 = require("../overlay-tools/index.js");
const index_js_2 = require("./index.js");
const PushDrop_js_1 = __importDefault(require("../script/templates/PushDrop.js"));
const Transaction_js_1 = __importDefault(require("../transaction/Transaction.js"));
const index_js_3 = require("../primitives/index.js");
class StorageDownloader {
constructor(config) {
this.networkPreset = 'mainnet';
this.networkPreset = config?.networkPreset ?? 'mainnet';
this.lookupResolver = new index_js_1.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_js_1.default.fromBEEF(response.outputs[i].beef);
const { fields } = PushDrop_js_1.default.decode(tx.outputs[response.outputs[i].outputIndex].lockingScript);
const expiryTime = new index_js_3.Utils.Reader(fields[3]).readVarIntNum();
if (expiryTime < currentTime) {
continue;
}
decodedResults.push(index_js_3.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 (!index_js_2.StorageUtils.isValidURL(uhrpUrl)) {
throw new Error('Invalid parameter UHRP url');
}
const hash = index_js_2.StorageUtils.getHashFromURL(uhrpUrl);
const expected = index_js_3.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 index_js_3.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 = index_js_3.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}`);
}
}
exports.StorageDownloader = StorageDownloader;
//# sourceMappingURL=StorageDownloader.js.map