UNPKG

@opendatalabs/vana-sdk

Version:

A TypeScript library for interacting with Vana Network smart contracts.

251 lines 8.15 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var vana_storage_exports = {}; __export(vana_storage_exports, { VanaStorage: () => VanaStorage }); module.exports = __toCommonJS(vana_storage_exports); var import__ = require("../index"); var import_web3_signed_builder = require("../../auth/web3-signed-builder"); const DEFAULT_ENDPOINT = "https://storage.vana.org"; const BLOB_PATH_PREFIX = "/v1/blobs"; const DEFAULT_TOKEN_TTL_SECONDS = 300; class VanaStorage { endpoint; signer; ownerAddress; fetchImpl; constructor(config) { if (!config?.signer?.address || !config?.signer?.signMessage) { throw new import__.StorageError( "VanaStorage requires a signer with address and signMessage", "MISSING_SIGNER", "vana-storage" ); } this.endpoint = (config.endpoint ?? DEFAULT_ENDPOINT).replace(/\/+$/, ""); this.signer = config.signer; this.ownerAddress = (config.ownerAddress ?? config.signer.address).toLowerCase(); this.fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis); } /** * Upload an encrypted blob to vana-storage. * * @param file - The blob to upload. * @param filename - Required relative key in the form `"{scope}/{collectedAt}"`. * The owner address is prepended automatically. */ async upload(file, filename) { if (!filename) { throw new import__.StorageError( "VanaStorage.upload requires a filename of the form '{scope}/{collectedAt}'", "MISSING_FILENAME", "vana-storage" ); } const subpath = encodeRelativePath(filename); const path = `${BLOB_PATH_PREFIX}/${this.ownerAddress}/${subpath}`; const body = new Uint8Array(await file.arrayBuffer()); const contentType = file.type !== "" ? file.type : "application/octet-stream"; const header = await this.signRequest("PUT", path, body); let response; try { response = await this.fetchImpl(`${this.endpoint}${path}`, { method: "PUT", headers: { authorization: header, "content-type": contentType }, body }); } catch (cause) { throw new import__.StorageError( `vana-storage upload network error: ${describe(cause)}`, "UPLOAD_ERROR", "vana-storage", { cause: cause instanceof Error ? cause : void 0 } ); } if (!response.ok) { throw new import__.StorageError( `vana-storage upload failed: ${response.status} ${response.statusText} - ${await safeText(response)}`, "UPLOAD_FAILED", "vana-storage" ); } const result = await response.json(); return { url: result.url, size: result.size, contentType, metadata: { key: result.key, etag: result.etag } }; } /** * Download a blob by URL. The URL must point at a path under this * provider's endpoint. */ async download(url) { const path = this.pathFromUrl(url); const header = await this.signRequest("GET", path); let response; try { response = await this.fetchImpl(`${this.endpoint}${path}`, { method: "GET", headers: { authorization: header } }); } catch (cause) { throw new import__.StorageError( `vana-storage download network error: ${describe(cause)}`, "DOWNLOAD_ERROR", "vana-storage", { cause: cause instanceof Error ? cause : void 0 } ); } if (!response.ok) { throw new import__.StorageError( `vana-storage download failed: ${response.status} ${response.statusText}`, "DOWNLOAD_FAILED", "vana-storage" ); } return await response.blob(); } /** * Listing is not supported by vana-storage — file discovery is handled by * the Gateway DataRegistry, not the storage layer. */ async list(_options) { throw new import__.StorageError( "list is not supported by vana-storage; query the Gateway DataRegistry instead", "NOT_IMPLEMENTED", "vana-storage" ); } async delete(url) { const path = this.pathFromUrl(url); const header = await this.signRequest("DELETE", path); let response; try { response = await this.fetchImpl(`${this.endpoint}${path}`, { method: "DELETE", headers: { authorization: header } }); } catch (cause) { throw new import__.StorageError( `vana-storage delete network error: ${describe(cause)}`, "DELETE_ERROR", "vana-storage", { cause: cause instanceof Error ? cause : void 0 } ); } if (response.status === 404) return false; if (!response.ok) { throw new import__.StorageError( `vana-storage delete failed: ${response.status} ${response.statusText}`, "DELETE_FAILED", "vana-storage" ); } return true; } getConfig() { return { name: "vana-storage", type: "vana-storage", requiresAuth: true, features: { upload: true, download: true, list: false, delete: true } }; } async signRequest(method, path, body) { const now = Math.floor(Date.now() / 1e3); return (0, import_web3_signed_builder.buildWeb3SignedHeader)({ signMessage: this.signer.signMessage, aud: this.endpoint, method, uri: path, iat: now, exp: now + DEFAULT_TOKEN_TTL_SECONDS, ...body !== void 0 && body.length > 0 && { body } }); } pathFromUrl(url) { let parsed; try { parsed = new URL(url); } catch { throw new import__.StorageError( `Invalid URL: ${url}`, "INVALID_URL", "vana-storage" ); } const expectedHost = new URL(this.endpoint).host; if (parsed.host !== expectedHost) { throw new import__.StorageError( `URL host '${parsed.host}' does not match storage endpoint '${expectedHost}'`, "INVALID_URL", "vana-storage" ); } const segments = parsed.pathname.split("/").filter((s) => s.length > 0); const isTraversal = (s) => s === "." || s === ".."; const valid = segments.length === 5 && segments[0] === "v1" && segments[1] === "blobs" && segments[2]?.toLowerCase() === this.ownerAddress && segments[3] !== void 0 && !isTraversal(segments[3]) && segments[4] !== void 0 && !isTraversal(segments[4]); if (!valid) { throw new import__.StorageError( `URL path '${parsed.pathname}' must be /v1/blobs/${this.ownerAddress}/{scope}/{collectedAt}`, "INVALID_URL", "vana-storage" ); } return parsed.pathname; } } function encodeRelativePath(filename) { const parts = filename.split("/"); if (parts.length !== 2 || parts.some((p) => p.length === 0 || p === "." || p === "..")) { throw new import__.StorageError( `filename must be exactly '{scope}/{collectedAt}' with non-empty segments, got '${filename}'`, "INVALID_FILENAME", "vana-storage" ); } return parts.map((p) => encodeURIComponent(p)).join("/"); } function describe(value) { if (value instanceof Error) return value.message; return String(value); } async function safeText(response) { try { return await response.text(); } catch { return ""; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { VanaStorage }); //# sourceMappingURL=vana-storage.cjs.map