@opendatalabs/vana-sdk
Version:
A TypeScript library for interacting with Vana Network smart contracts.
251 lines • 8.15 kB
JavaScript
;
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