@bsv/sdk
Version:
BSV Blockchain Software Development Kit
203 lines • 8.95 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StorageUploader = void 0;
const AuthFetch_js_1 = require("../auth/clients/AuthFetch.js");
const StorageUtils = __importStar(require("./StorageUtils.js"));
/**
* The StorageUploader class provides client-side methods for:
* - Uploading files with a specified retention period
* - Finding file metadata by UHRP URL
* - Listing all user uploads
* - Renewing an existing advertisement's expiry time
*/
class StorageUploader {
/**
* Creates a new StorageUploader instance.
* @param {UploaderConfig} config - An object containing the storage server's URL and a wallet interface
*/
constructor(config) {
this.baseURL = config.storageURL;
this.authFetch = new AuthFetch_js_1.AuthFetch(config.wallet);
}
/**
* Requests information from the server to upload a file (including presigned URL and headers).
* @private
* @param {number} fileSize - The size of the file, in bytes
* @param {number} retentionPeriod - The desired hosting time, in minutes
* @returns {Promise<{ uploadURL: string; requiredHeaders: Record<string, string>; amount?: number }>}
* @throws {Error} If the server returns a non-OK response or an error status
*/
async getUploadInfo(fileSize, retentionPeriod) {
const url = `${this.baseURL}/upload`;
const body = { fileSize, retentionPeriod };
const response = await this.authFetch.fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!response.ok) {
throw new Error(`Upload info request failed: HTTP ${response.status}`);
}
const data = await response.json();
if (data.status === 'error') {
throw new Error('Upload route returned an error.');
}
return {
uploadURL: data.uploadURL,
requiredHeaders: data.requiredHeaders,
amount: data.amount
};
}
/**
* Performs the actual file upload (HTTP PUT) to the presigned URL returned by the server.
* @private
* @param {string} uploadURL - The presigned URL where the file is to be uploaded
* @param {UploadableFile} file - The file to upload, including its raw data and MIME type
* @param {Record<string, string>} requiredHeaders - Additional headers required by the server (e.g. content-length)
* @returns {Promise<UploadFileResult>} An object indicating whether publishing was successful and the resulting UHRP URL
* @throws {Error} If the server returns a non-OK response
*/
async uploadFile(uploadURL, file, requiredHeaders) {
const body = file.data instanceof Uint8Array ? file.data : Uint8Array.from(file.data);
const response = await fetch(uploadURL, {
method: 'PUT',
body,
headers: {
'Content-Type': file.type,
...requiredHeaders
}
});
if (!response.ok) {
throw new Error(`File upload failed: HTTP ${response.status}`);
}
const uhrpURL = StorageUtils.getURLForFile(body);
return {
published: true,
uhrpURL
};
}
/**
* Publishes a file to the storage server with the specified retention period.
*
* This will:
* 1. Request an upload URL from the server.
* 2. Perform an HTTP PUT to upload the file’s raw bytes.
* 3. Return a UHRP URL referencing the file once published.
*
* @param {Object} params
* @param {UploadableFile} params.file - The file data + type
* @param {number} params.retentionPeriod - Number of minutes to host the file
* @returns {Promise<UploadFileResult>} An object with the file's UHRP URL
* @throws {Error} If the server or upload step returns a non-OK response
*/
async publishFile(params) {
const { file, retentionPeriod } = params;
const data = file.data instanceof Uint8Array ? file.data : Uint8Array.from(file.data);
const fileSize = data.byteLength;
const { uploadURL, requiredHeaders } = await this.getUploadInfo(fileSize, retentionPeriod);
return await this.uploadFile(uploadURL, { data, type: file.type }, requiredHeaders);
}
/**
* Retrieves metadata for a file matching the given UHRP URL from the `/find` route.
* @param {string} uhrpUrl - The UHRP URL, e.g. "uhrp://abcd..."
* @returns {Promise<FindFileData>} An object with file name, size, MIME type, and expiry time
* @throws {Error} If the server or the route returns an error
*/
async findFile(uhrpUrl) {
const url = new URL(`${this.baseURL}/find`);
url.searchParams.set('uhrpUrl', uhrpUrl);
const response = await this.authFetch.fetch(url.toString(), {
method: 'GET'
});
if (!response.ok) {
throw new Error(`findFile request failed: HTTP ${response.status}`);
}
const data = await response.json();
if (data.status === 'error') {
const errCode = data.code ?? 'unknown-code';
const errDesc = data.description ?? 'no-description';
throw new Error(`findFile returned an error: ${errCode} - ${errDesc}`);
}
return data.data;
}
/**
* Lists all advertisements belonging to the user from the `/list` route.
* @returns {Promise<any>} The array of uploads returned by the server
* @throws {Error} If the server or the route returns an error
*/
async listUploads() {
const url = `${this.baseURL}/list`;
const response = await this.authFetch.fetch(url, {
method: 'GET'
});
if (!response.ok) {
throw new Error(`listUploads request failed: HTTP ${response.status}`);
}
const data = await response.json();
if (data.status === 'error') {
const errCode = data.code ?? 'unknown-code';
const errDesc = data.description ?? 'no-description';
throw new Error(`listUploads returned an error: ${errCode} - ${errDesc}`);
}
return data.uploads;
}
/**
* Renews the hosting time for an existing file advertisement identified by uhrpUrl.
* Calls the `/renew` route to add `additionalMinutes` to the GCS customTime
* and re-mint the advertisement token on-chain.
*
* @param {string} uhrpUrl - The UHRP URL of the file (e.g., "uhrp://abcd1234...")
* @param {number} additionalMinutes - The number of minutes to extend
* @returns {Promise<RenewFileResult>} An object with the new and previous expiry times, plus any cost
* @throws {Error} If the request fails or the server returns an error
*/
async renewFile(uhrpUrl, additionalMinutes) {
const url = `${this.baseURL}/renew`;
const body = { uhrpUrl, additionalMinutes };
const response = await this.authFetch.fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!response.ok) {
throw new Error(`renewFile request failed: HTTP ${response.status}`);
}
const data = await response.json();
if (data.status === 'error') {
const errCode = data.code ?? 'unknown-code';
const errDesc = data.description ?? 'no-description';
throw new Error(`renewFile returned an error: ${errCode} - ${errDesc}`);
}
return {
status: data.status,
prevExpiryTime: data.prevExpiryTime,
newExpiryTime: data.newExpiryTime,
amount: data.amount
};
}
}
exports.StorageUploader = StorageUploader;
//# sourceMappingURL=StorageUploader.js.map